mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 16:55:40 +00:00
Merge mozilla-central to mozilla-inbound. a=merge
This commit is contained in:
commit
eb5c88a05d
@ -38,7 +38,7 @@ const SUITES = {
|
||||
type: TEST_TYPES.JEST,
|
||||
},
|
||||
netmonitor: {
|
||||
path: "../netmonitor",
|
||||
path: "../netmonitor/test/node",
|
||||
type: TEST_TYPES.JEST,
|
||||
},
|
||||
webconsole: {
|
||||
|
@ -3576,11 +3576,11 @@ Toolbox.prototype = {
|
||||
settleAll(outstanding)
|
||||
.catch(console.error)
|
||||
.then(() => {
|
||||
const api = this._netMonitorAPI;
|
||||
this._netMonitorAPI = null;
|
||||
return api ? api.destroy() : null;
|
||||
}, console.error)
|
||||
.then(() => {
|
||||
if (this._netMonitorAPI) {
|
||||
this._netMonitorAPI.destroy();
|
||||
this._netMonitorAPI = null;
|
||||
}
|
||||
|
||||
this._removeWindowListeners();
|
||||
this._removeChromeEventHandlerEvents();
|
||||
|
||||
|
@ -27,11 +27,8 @@ const ChangesApp = createFactory(require("./components/ChangesApp"));
|
||||
const { getChangesStylesheet } = require("./selectors/changes");
|
||||
|
||||
const {
|
||||
TELEMETRY_SCALAR_CONTEXTMENU,
|
||||
TELEMETRY_SCALAR_CONTEXTMENU_COPY,
|
||||
TELEMETRY_SCALAR_CONTEXTMENU_COPY_DECLARATION,
|
||||
TELEMETRY_SCALAR_CONTEXTMENU_COPY_RULE,
|
||||
TELEMETRY_SCALAR_COPY,
|
||||
TELEMETRY_SCALAR_COPY_ALL_CHANGES,
|
||||
TELEMETRY_SCALAR_COPY_RULE,
|
||||
} = require("./constants");
|
||||
@ -50,7 +47,6 @@ class ChangesView {
|
||||
this.onClearChanges = this.onClearChanges.bind(this);
|
||||
this.onChangesFront = this.onChangesFront.bind(this);
|
||||
this.onContextMenu = this.onContextMenu.bind(this);
|
||||
this.onCopy = this.onCopy.bind(this);
|
||||
this.onCopyAllChanges = this.copyAllChanges.bind(this);
|
||||
this.onCopyRule = this.copyRule.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
@ -69,7 +65,6 @@ class ChangesView {
|
||||
init() {
|
||||
const changesApp = ChangesApp({
|
||||
onContextMenu: this.onContextMenu,
|
||||
onCopy: this.onCopy,
|
||||
onCopyAllChanges: this.onCopyAllChanges,
|
||||
onCopyRule: this.onCopyRule,
|
||||
});
|
||||
@ -211,7 +206,6 @@ class ChangesView {
|
||||
*/
|
||||
copySelection() {
|
||||
clipboardHelper.copyString(this.window.getSelection().toString());
|
||||
this.telemetry.scalarAdd(TELEMETRY_SCALAR_CONTEXTMENU_COPY, 1);
|
||||
}
|
||||
|
||||
onAddChange(change) {
|
||||
@ -229,15 +223,6 @@ class ChangesView {
|
||||
*/
|
||||
onContextMenu(e) {
|
||||
this.contextMenu.show(e);
|
||||
this.telemetry.scalarAdd(TELEMETRY_SCALAR_CONTEXTMENU, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for the "copy" event fired when content is copied to the clipboard.
|
||||
* We don't change the default behavior. We only log the increment count of this action.
|
||||
*/
|
||||
onCopy() {
|
||||
this.telemetry.scalarAdd(TELEMETRY_SCALAR_COPY, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,8 +24,6 @@ class ChangesApp extends PureComponent {
|
||||
changesTree: PropTypes.object.isRequired,
|
||||
// Event handler for "contextmenu" event
|
||||
onContextMenu: PropTypes.func.isRequired,
|
||||
// Event handler for "copy" event
|
||||
onCopy: PropTypes.func.isRequired,
|
||||
// Event handler for click on "Copy All Changes" button
|
||||
onCopyAllChanges: PropTypes.func.isRequired,
|
||||
// Event handler for click on "Copy Rule" button
|
||||
@ -221,7 +219,6 @@ class ChangesApp extends PureComponent {
|
||||
className: "theme-sidebar inspector-tabpanel",
|
||||
id: "sidebar-panel-changes",
|
||||
onContextMenu: this.props.onContextMenu,
|
||||
onCopy: this.props.onCopy,
|
||||
},
|
||||
!hasChanges && this.renderEmptyState(),
|
||||
hasChanges && this.renderCopyAllChangesButton(),
|
||||
|
@ -7,13 +7,10 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
TELEMETRY_SCALAR_CONTEXTMENU: "devtools.changesview.contextmenu",
|
||||
TELEMETRY_SCALAR_CONTEXTMENU_COPY: "devtools.changesview.contextmenu_copy",
|
||||
TELEMETRY_SCALAR_CONTEXTMENU_COPY_DECLARATION:
|
||||
"devtools.changesview.contextmenu_copy_declaration",
|
||||
TELEMETRY_SCALAR_CONTEXTMENU_COPY_RULE:
|
||||
"devtools.changesview.contextmenu_copy_rule",
|
||||
TELEMETRY_SCALAR_COPY: "devtools.changesview.copy",
|
||||
TELEMETRY_SCALAR_COPY_ALL_CHANGES: "devtools.changesview.copy_all_changes",
|
||||
TELEMETRY_SCALAR_COPY_RULE: "devtools.changesview.copy_rule",
|
||||
};
|
||||
|
@ -14,6 +14,11 @@ frame.unknownSource=(unknown)
|
||||
# %S represents the URL to match in the debugger.
|
||||
frame.viewsourceindebugger=View source in Debugger → %S
|
||||
|
||||
# LOCALIZATION NOTE (frame.viewsourceinstyleeditor): The label for the tooltip when hovering over
|
||||
# a source link that links to the Style Editor.
|
||||
# %S represents the URL to match in the style editor.
|
||||
frame.viewsourceinstyleeditor=View source in Style Editor → %S
|
||||
|
||||
# LOCALIZATION NOTE (notificationBox.closeTooltip): The content of a tooltip that
|
||||
# appears when hovering over the close button in a notification box.
|
||||
notificationBox.closeTooltip=Close this message
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-react-jsx",
|
||||
"transform-object-rest-spread",
|
||||
"transform-flow-strip-types",
|
||||
],
|
||||
}
|
@ -14,18 +14,6 @@ Files used to run the Network Monitor inside of the DevTools toolbox.
|
||||
* `index.html` panel UI and launch scripts.
|
||||
* `src/connector/` wrap function call for Browser specific API. Current support Firefox and Chrome(experimental).
|
||||
|
||||
### Run in the browser tab (experimental)
|
||||
|
||||
Files used to run the Network Monitor in the browser tab
|
||||
|
||||
* `bin/` files to launch test server.
|
||||
* `configs/` dev configs.
|
||||
* `launchpad.js` the entry point, equivalent to `index.html`.
|
||||
* `webpack.config.js` the webpack config file, including plenty of module alias map to shims and polyfills.
|
||||
* `package.json` declare every required packages and available commands.
|
||||
|
||||
To run in the browser tab, the Network Monitor needs to get some dependencies from npm module. Check `package.json` to see all dependencies. Check `webpack.config.js` to find the module alias, and check [devtools-core](https://github.com/firefox-devtools/devtools-core) packages to dive into actual modules used by the Network Monitor and other Devtools.
|
||||
|
||||
### UI
|
||||
|
||||
The Network Monitor UI is built using [React](http://searchfox.org/mozilla-central/source/devtools/docs/frontend/react.md) components (in `src/components/`).
|
||||
|
@ -1,29 +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 node */
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function getConfig() {
|
||||
if (process.env.TARGET === "firefox-panel") {
|
||||
return require("../configs/firefox-panel.json");
|
||||
}
|
||||
|
||||
const developmentConfig = require("../configs/development.json");
|
||||
|
||||
let localConfig = {};
|
||||
if (fs.existsSync(path.resolve(__dirname, "../configs/local.json"))) {
|
||||
localConfig = require("../configs/local.json");
|
||||
}
|
||||
|
||||
return Object.assign({}, developmentConfig, localConfig);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getConfig,
|
||||
};
|
@ -1,19 +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 node */
|
||||
|
||||
"use strict";
|
||||
|
||||
const toolbox = require("devtools-launchpad/index");
|
||||
const feature = require("devtools-config");
|
||||
const { getConfig } = require("./configure");
|
||||
|
||||
const envConfig = getConfig();
|
||||
|
||||
feature.setConfig(envConfig);
|
||||
|
||||
const webpackConfig = require("../webpack.config");
|
||||
|
||||
toolbox.startDevServer(envConfig, webpackConfig, __dirname);
|
@ -1,37 +0,0 @@
|
||||
{
|
||||
"title": "Netmonitor",
|
||||
"environment": "development",
|
||||
"baseWorkerURL": "http://localhost:8000/public/build/",
|
||||
"defaultURL": "http://softwareishard.com/test/harexporttrigger/",
|
||||
"host": "",
|
||||
"theme": "light",
|
||||
"dir": "ltr",
|
||||
"logging": {
|
||||
"client": false,
|
||||
"firefoxProxy": false,
|
||||
"actions": false
|
||||
},
|
||||
"features": {
|
||||
},
|
||||
"chrome": {
|
||||
"debug": true,
|
||||
"host": "localhost",
|
||||
"port": 9222
|
||||
},
|
||||
"node": {
|
||||
"debug": true,
|
||||
"host": "localhost",
|
||||
"port": 9229
|
||||
},
|
||||
"firefox": {
|
||||
"webSocketConnection": false,
|
||||
"host": "localhost",
|
||||
"webSocketPort": 8116,
|
||||
"tcpPort": 6080,
|
||||
"mcPath": "./firefox"
|
||||
},
|
||||
"development": {
|
||||
"serverPort": 8000,
|
||||
"examplesPort": 7999
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"environment": "firefox-panel",
|
||||
"baseWorkerURL": "resource://devtools/client/netmonitor/",
|
||||
"logging": false,
|
||||
"clientLogging": false,
|
||||
"firefox": {
|
||||
"mcPath": "./firefox"
|
||||
},
|
||||
"features": {
|
||||
}
|
||||
}
|
@ -1,87 +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";
|
||||
|
||||
/**
|
||||
* This script is the entry point of devtools-launchpad. Make netmonitor possible
|
||||
* to run on standalone browser tab without chrome privilege.
|
||||
* See README.md for more information.
|
||||
*/
|
||||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
const { bindActionCreators } = require("redux");
|
||||
const { bootstrap, renderRoot } = require("devtools-launchpad");
|
||||
const { Services: { appinfo, pref }} = require("devtools-modules");
|
||||
|
||||
// Initialize preferences as early as possible
|
||||
pref("devtools.theme", "light");
|
||||
pref("devtools.cache.disabled", false);
|
||||
pref("devtools.netmonitor.enabled", true);
|
||||
pref("devtools.netmonitor.filters", "[\"all\"]");
|
||||
pref("devtools.netmonitor.visibleColumns",
|
||||
"[\"status\",\"method\",\"domain,\"file\",\"domain\",\"cause\"," +
|
||||
"\"type\",\"transferred\",\"contentSize\",\"waterfall\"]"
|
||||
);
|
||||
pref("devtools.netmonitor.panes-network-details-width", 550);
|
||||
pref("devtools.netmonitor.panes-network-details-height", 450);
|
||||
pref("devtools.netmonitor.har.defaultLogDir", "");
|
||||
pref("devtools.netmonitor.har.defaultFileName", "Archive %date");
|
||||
pref("devtools.netmonitor.har.jsonp", false);
|
||||
pref("devtools.netmonitor.har.jsonpCallback", "");
|
||||
pref("devtools.netmonitor.har.includeResponseBodies", true);
|
||||
pref("devtools.netmonitor.har.compress", false);
|
||||
pref("devtools.netmonitor.har.forceExport", false);
|
||||
pref("devtools.netmonitor.har.pageLoadedTimeout", 1500);
|
||||
pref("devtools.netmonitor.har.enableAutoExportToFile", false);
|
||||
pref("devtools.netmonitor.persistlog", false);
|
||||
pref("devtools.styleeditor.enabled", true);
|
||||
|
||||
require("./src/assets/styles/netmonitor.css");
|
||||
|
||||
const EventEmitter = require("devtools-modules/src/utils/event-emitter");
|
||||
EventEmitter.decorate(window);
|
||||
|
||||
const { configureStore } = require("./src/create-store");
|
||||
const App = require("./src/components/App");
|
||||
const { Connector } = require("./src/connector/index");
|
||||
const connector = new Connector();
|
||||
const store = configureStore(connector);
|
||||
const actions = bindActionCreators(require("./src/actions"), store.dispatch);
|
||||
|
||||
// Inject to global window for testing
|
||||
window.store = store;
|
||||
|
||||
/**
|
||||
* Stylesheet links in devtools xhtml files are using chrome or resource URLs.
|
||||
* Rewrite the href attribute to remove the protocol. web-server.js contains redirects
|
||||
* to map CSS urls to the proper file. Supports urls using:
|
||||
* - devtools/client/
|
||||
* - devtools/content/
|
||||
* - skin/
|
||||
* Will also add mandatory classnames and attributes to be compatible with devtools theme
|
||||
* stylesheet.
|
||||
*/
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
for (const link of document.head.querySelectorAll("link")) {
|
||||
link.href = link.href.replace(/(resource|chrome)\:\/\//, "/");
|
||||
}
|
||||
|
||||
if (appinfo.OS === "Darwin") {
|
||||
document.documentElement.setAttribute("platform", "mac");
|
||||
} else if (appinfo.OS === "Linux") {
|
||||
document.documentElement.setAttribute("platform", "linux");
|
||||
} else {
|
||||
document.documentElement.setAttribute("platform", "win");
|
||||
}
|
||||
});
|
||||
|
||||
bootstrap(React, ReactDOM).then((connection) => {
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderRoot(React, ReactDOM, App, store, {actions, connector});
|
||||
connector.connect(connection, actions, store.getState);
|
||||
});
|
@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "netmonitor",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node": ">=7.10.0"
|
||||
},
|
||||
"description": "Network monitor in developer tools",
|
||||
"dependencies": {
|
||||
"babel-plugin-transform-flow-strip-types": "^6.22.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"codemirror": "^5.24.2",
|
||||
"devtools-config": "=0.0.12",
|
||||
"devtools-contextmenu": "=0.0.3",
|
||||
"devtools-launchpad": "^0.0.119",
|
||||
"devtools-modules": "=0.0.35",
|
||||
"devtools-source-editor": "=0.0.3",
|
||||
"file-loader": "^1.1.6",
|
||||
"jszip": "^3.1.3",
|
||||
"react": "=16.2.0",
|
||||
"react-dom": "=16.2.0",
|
||||
"react-prop-types": "=0.4.0",
|
||||
"react-redux": "=5.0.6",
|
||||
"redux": "^3.7.2",
|
||||
"reselect": "^3.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node bin/dev-server",
|
||||
"test": "jest",
|
||||
"test-ci": "jest --json"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
@ -29,10 +29,9 @@ NetMonitorPanel.prototype = {
|
||||
return this;
|
||||
},
|
||||
|
||||
async destroy() {
|
||||
await this.panelWin.Netmonitor.destroy();
|
||||
destroy() {
|
||||
this.panelWin.Netmonitor.destroy();
|
||||
this.emit("destroyed");
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -80,13 +80,13 @@ NetMonitorAPI.prototype = {
|
||||
/**
|
||||
* Clean up (unmount from DOM, remove listeners, disconnect).
|
||||
*/
|
||||
async destroy() {
|
||||
destroy() {
|
||||
this.off(EVENTS.PAYLOAD_READY, this.onPayloadReady);
|
||||
|
||||
await this.connector.disconnect();
|
||||
this.connector.disconnect();
|
||||
|
||||
if (this.harExportConnector) {
|
||||
await this.harExportConnector.disconnect();
|
||||
this.harExportConnector.disconnect();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -68,14 +68,14 @@ NetMonitorApp.prototype = {
|
||||
/**
|
||||
* Clean up (unmount from DOM, remove listeners, disconnect).
|
||||
*/
|
||||
async destroy() {
|
||||
destroy() {
|
||||
unmountComponentAtNode(this.mount);
|
||||
|
||||
// Make sure to destroy the API object. It's usually destroyed
|
||||
// in the Toolbox destroy method, but we need it here for case
|
||||
// where the Network panel is initialized without the toolbox
|
||||
// and running in a tab (see initialize.js for details).
|
||||
await this.api.destroy();
|
||||
this.api.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,12 @@
|
||||
const { ACTIVITY_TYPE } = require("../constants");
|
||||
const { CDPConnector } = require("./chrome/events");
|
||||
|
||||
/**
|
||||
* DO NOT DELETE THIS FILE
|
||||
*
|
||||
* The ChromeConnector is currently not used, but is kept in tree to illustrate
|
||||
* the Connector abstraction.
|
||||
*/
|
||||
class ChromeConnector {
|
||||
constructor() {
|
||||
// Internal properties
|
||||
@ -31,7 +37,7 @@ class ChromeConnector {
|
||||
this.connector.willNavigate(this.willNavigate);
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
disconnect() {
|
||||
this.connector.disconnect();
|
||||
}
|
||||
|
||||
|
@ -88,12 +88,12 @@ class FirefoxConnector {
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
disconnect() {
|
||||
if (this.actions) {
|
||||
this.actions.batchReset();
|
||||
}
|
||||
|
||||
await this.removeListeners();
|
||||
this.removeListeners();
|
||||
|
||||
if (this.emulationFront) {
|
||||
this.emulationFront.destroy();
|
||||
@ -162,7 +162,7 @@ class FirefoxConnector {
|
||||
await this.webConsoleClient.startListeners(["DocumentEvents"]);
|
||||
}
|
||||
|
||||
async removeListeners() {
|
||||
removeListeners() {
|
||||
if (this.tabTarget) {
|
||||
this.tabTarget.off("close", this.disconnect);
|
||||
if (this.webSocketFront) {
|
||||
@ -179,7 +179,6 @@ class FirefoxConnector {
|
||||
this.dataProvider.onFrameReceived
|
||||
);
|
||||
this.webSocketFront.off("frameSent", this.dataProvider.onFrameSent);
|
||||
this.webSocketFront.stopListening();
|
||||
}
|
||||
}
|
||||
if (this.webConsoleClient) {
|
||||
|
14
devtools/client/netmonitor/test/node/jest.config.js
Normal file
14
devtools/client/netmonitor/test/node/jest.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global __dirname */
|
||||
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
moduleNameMapper: {
|
||||
// Map all require("devtools/...") to the real devtools root.
|
||||
"^devtools\\/(.*)": `${__dirname}/../../../../$1`,
|
||||
},
|
||||
};
|
15
devtools/client/netmonitor/test/node/package.json
Normal file
15
devtools/client/netmonitor/test/node/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "netmonitor-tests",
|
||||
"license": "MPL-2.0",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node": ">=8.9.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test-ci": "jest --json"
|
||||
},
|
||||
"dependencies": {
|
||||
"jest": "^24.6.0"
|
||||
}
|
||||
}
|
@ -3,8 +3,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Sort, sortReducer } = require("../../src/reducers/sort");
|
||||
const { SORT_BY } = require("../../src/constants");
|
||||
const {
|
||||
Sort,
|
||||
sortReducer,
|
||||
} = require("devtools/client/netmonitor/src/reducers/sort");
|
||||
const { SORT_BY } = require("devtools/client/netmonitor/src/constants");
|
||||
|
||||
describe("sorting reducer", () => {
|
||||
it("it should sort by sort type", () => {
|
3553
devtools/client/netmonitor/test/node/yarn.lock
Normal file
3553
devtools/client/netmonitor/test/node/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,187 +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 node */
|
||||
/* eslint max-len: [0] */
|
||||
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const { NormalModuleReplacementPlugin } = require("webpack");
|
||||
const { toolboxConfig } = require("./node_modules/devtools-launchpad/index");
|
||||
const { getConfig } = require("./bin/configure");
|
||||
|
||||
const webpackConfig = {
|
||||
entry: {
|
||||
netmonitor: [path.join(__dirname, "launchpad.js")],
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|svg)$/,
|
||||
loader: "file-loader?name=[path][name].[ext]",
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loaders: [
|
||||
/**
|
||||
* The version of webpack used in the launchpad seems to have trouble
|
||||
* with the require("raw!${file}") that we use for the properties
|
||||
* file in l10.js.
|
||||
* This loader goes through the whole code and remove the "raw!" prefix
|
||||
* so the raw-loader declared in devtools-launchpad config can load
|
||||
* those files.
|
||||
*/
|
||||
"rewrite-raw",
|
||||
// Replace all references to this.browserRequire() by require()
|
||||
"rewrite-browser-require",
|
||||
// Replace all references to loader.lazyRequire() by require()
|
||||
"rewrite-lazy-require",
|
||||
// Replace all references to loader.lazyGetter() by require()
|
||||
"rewrite-lazy-getter",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
resolveLoader: {
|
||||
modules: ["node_modules", path.resolve("../shared/webpack")],
|
||||
},
|
||||
|
||||
output: {
|
||||
path: path.join(__dirname, "assets/build"),
|
||||
filename: "[name].js",
|
||||
publicPath: "/assets/build",
|
||||
libraryTarget: "umd",
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
// Make sure webpack is always looking for modules in
|
||||
// `netmonitor/node_modules` directory first.
|
||||
path.resolve(__dirname, "node_modules"),
|
||||
"node_modules",
|
||||
],
|
||||
alias: {
|
||||
Services: "devtools-modules/src/Services",
|
||||
react: path.join(__dirname, "node_modules/react"),
|
||||
|
||||
"devtools/client/framework/devtools": path.join(
|
||||
__dirname,
|
||||
"../../client/shared/webpack/shims/framework-devtools-shim"
|
||||
),
|
||||
"devtools/client/framework/menu": "devtools-modules/src/menu",
|
||||
|
||||
"devtools/client/shared/components/menu/utils": "devtools-contextmenu",
|
||||
|
||||
"devtools/client/shared/vendor/react": "react",
|
||||
"devtools/client/shared/vendor/react-dom": "react-dom",
|
||||
"devtools/client/shared/vendor/react-redux": "react-redux",
|
||||
"devtools/client/shared/vendor/redux": "redux",
|
||||
"devtools/client/shared/vendor/reselect": "reselect",
|
||||
"devtools/client/shared/vendor/jszip": "jszip",
|
||||
|
||||
"devtools/client/shared/sourceeditor/editor":
|
||||
"devtools-source-editor/src/source-editor",
|
||||
|
||||
"devtools/shared/event-emitter":
|
||||
"devtools-modules/src/utils/event-emitter",
|
||||
"devtools/shared/platform/clipboard": path.join(
|
||||
__dirname,
|
||||
"../../client/shared/webpack/shims/platform-clipboard-stub"
|
||||
),
|
||||
"devtools/client/netmonitor/src/utils/firefox/open-request-in-tab": path.join(
|
||||
__dirname,
|
||||
"src/utils/open-request-in-tab"
|
||||
),
|
||||
"devtools/client/shared/unicode-url":
|
||||
"./node_modules/devtools-modules/src/unicode-url",
|
||||
|
||||
// Locales need to be explicitly mapped to the en-US subfolder
|
||||
"devtools/client/locales": path.join(
|
||||
__dirname,
|
||||
"../../client/locales/en-US"
|
||||
),
|
||||
"devtools/shared/locales": path.join(
|
||||
__dirname,
|
||||
"../../shared/locales/en-US"
|
||||
),
|
||||
"devtools/startup/locales": path.join(
|
||||
__dirname,
|
||||
"../../shared/locales/en-US"
|
||||
),
|
||||
"toolkit/locales": path.join(__dirname, "../../../toolkit/locales/en-US"),
|
||||
|
||||
// Unless a path explicitly needs to be rewritten or shimmed, all devtools paths can
|
||||
// be mapped to ../../
|
||||
devtools: path.join(__dirname, "../../"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mappings = [
|
||||
[
|
||||
/chrome:\/\/devtools\/skin/,
|
||||
result => {
|
||||
result.request = result.request.replace(
|
||||
"./chrome://devtools/skin",
|
||||
path.join(__dirname, "../themes")
|
||||
);
|
||||
},
|
||||
],
|
||||
[
|
||||
/chrome:\/\/devtools\/content/,
|
||||
result => {
|
||||
result.request = result.request.replace(
|
||||
"./chrome://devtools/content",
|
||||
path.join(__dirname, "..")
|
||||
);
|
||||
},
|
||||
],
|
||||
[
|
||||
/resource:\/\/devtools/,
|
||||
result => {
|
||||
result.request = result.request.replace(
|
||||
"./resource://devtools/client",
|
||||
path.join(__dirname, "..")
|
||||
);
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
webpackConfig.plugins = mappings.map(
|
||||
([regex, res]) => new NormalModuleReplacementPlugin(regex, res)
|
||||
);
|
||||
|
||||
const basePath = path.join(__dirname, "../../").replace(/\\/g, "\\\\");
|
||||
const baseName = path.basename(__dirname);
|
||||
|
||||
const config = toolboxConfig(webpackConfig, getConfig(), {
|
||||
// Exclude to transpile all scripts in devtools/ but not for this folder
|
||||
babelExcludes: new RegExp(`^${basePath}(.(?!${baseName}))*$`),
|
||||
});
|
||||
|
||||
// Remove loaders from devtools-launchpad's webpack.config.js
|
||||
// For svg-inline loader:
|
||||
// Using file loader to bundle image assets instead of svg-inline-loader
|
||||
config.module.rules = config.module.rules.filter(
|
||||
rule => !["svg-inline-loader"].includes(rule.loader)
|
||||
);
|
||||
|
||||
// For PostCSS loader:
|
||||
// Disable PostCSS loader
|
||||
config.module.rules.forEach(rule => {
|
||||
if (Array.isArray(rule.use)) {
|
||||
rule.use.some((use, idx) => {
|
||||
if (use.loader === "postcss-loader") {
|
||||
rule.use = rule.use.slice(0, idx);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = config;
|
File diff suppressed because it is too large
Load Diff
@ -290,6 +290,10 @@ pref("devtools.webconsole.features.editor", false);
|
||||
// Saved editor mode state in the console.
|
||||
pref("devtools.webconsole.input.editor", false);
|
||||
|
||||
// Editor width for webconsole and browserconsole
|
||||
pref("devtools.webconsole.input.editorWidth", 0);
|
||||
pref("devtools.browserconsole.input.editorWidth", 0);
|
||||
|
||||
// Disable the new performance recording panel by default
|
||||
pref("devtools.performance.new-panel-enabled", false);
|
||||
|
||||
|
@ -19,6 +19,7 @@ const {
|
||||
getSourceMappedFile,
|
||||
} = require("devtools/client/shared/source-utils");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const { MESSAGE_SOURCE } = require("devtools/client/webconsole/constants");
|
||||
|
||||
const l10n = new LocalizationHelper(
|
||||
"devtools/client/locales/components.properties"
|
||||
@ -51,6 +52,8 @@ class Frame extends Component {
|
||||
showFullSourceUrl: PropTypes.bool,
|
||||
// Service to enable the source map feature for console.
|
||||
sourceMapService: PropTypes.object,
|
||||
// The source of the message
|
||||
messageSource: PropTypes.string,
|
||||
};
|
||||
}
|
||||
|
||||
@ -131,6 +134,7 @@ class Frame extends Component {
|
||||
showHost,
|
||||
showEmptyPathAsHost,
|
||||
showFullSourceUrl,
|
||||
messageSource,
|
||||
} = this.props;
|
||||
|
||||
if (this.state && this.state.isSourceMapped && this.state.frame) {
|
||||
@ -267,13 +271,21 @@ class Frame extends Component {
|
||||
|
||||
// Inner el is useful for achieving ellipsis on the left and correct LTR/RTL
|
||||
// ordering. See CSS styles for frame-link-source-[inner] and bug 1290056.
|
||||
let tooltipMessage;
|
||||
if (messageSource && messageSource === MESSAGE_SOURCE.CSS) {
|
||||
tooltipMessage = l10n.getFormatStr(
|
||||
"frame.viewsourceinstyleeditor",
|
||||
tooltip
|
||||
);
|
||||
} else {
|
||||
tooltipMessage = l10n.getFormatStr("frame.viewsourceindebugger", tooltip);
|
||||
}
|
||||
|
||||
const sourceInnerEl = dom.span(
|
||||
{
|
||||
key: "source-inner",
|
||||
className: "frame-link-source-inner",
|
||||
title: isLinkable
|
||||
? l10n.getFormatStr("frame.viewsourceindebugger", tooltip)
|
||||
: tooltip,
|
||||
title: isLinkable ? tooltipMessage : tooltip,
|
||||
},
|
||||
sourceElements
|
||||
);
|
||||
|
@ -35,7 +35,17 @@
|
||||
color: var(--theme-toolbar-color);
|
||||
background-color: var(--theme-body-background);
|
||||
text-shadow: none;
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
border-color: var(--theme-splitter-color);
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.notificationbox.border-top .notification {
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.notificationbox.border-bottom .notification {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.notificationbox .notification[data-type="info"] {
|
||||
|
@ -76,12 +76,18 @@ class NotificationBox extends Component {
|
||||
closeButtonTooltip: PropTypes.string,
|
||||
// Wraps text when passed from console window as wrapping: true
|
||||
wrapping: PropTypes.bool,
|
||||
// Display a top border (default to false)
|
||||
displayBorderTop: PropTypes.bool,
|
||||
// Display a bottom border (default to true)
|
||||
displayBorderBottom: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip"),
|
||||
displayBorderTop: false,
|
||||
displayBorderBottom: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -251,10 +257,20 @@ class NotificationBox extends Component {
|
||||
const notifications = this.props.notifications || this.state.notifications;
|
||||
const notification = getHighestPriorityNotification(notifications);
|
||||
const content = notification ? this.renderNotification(notification) : null;
|
||||
|
||||
const classNames = ["notificationbox"];
|
||||
if (this.props.wrapping) {
|
||||
classNames.push("wrapping");
|
||||
}
|
||||
|
||||
if (this.props.displayBorderBottom) {
|
||||
classNames.push("border-bottom");
|
||||
}
|
||||
|
||||
if (this.props.displayBorderTop) {
|
||||
classNames.push("border-top");
|
||||
}
|
||||
|
||||
return div(
|
||||
{
|
||||
className: classNames.join(" "),
|
||||
|
@ -40,7 +40,7 @@ window.onload = async function () {
|
||||
|
||||
const newSizes = [];
|
||||
|
||||
function renderBox(props) {
|
||||
async function renderBox(props) {
|
||||
const boxProps = Object.assign({
|
||||
start: "hello!",
|
||||
end: "world!",
|
||||
@ -49,10 +49,15 @@ window.onload = async function () {
|
||||
newSizes.push(newSize);
|
||||
}
|
||||
}, props);
|
||||
return ReactDOM.render(HSplitBox(boxProps), window.document.body);
|
||||
const el = ReactDOM.render(HSplitBox(boxProps), window.document.body);
|
||||
// wait until the element is rendered.
|
||||
await SimpleTest.promiseWaitForCondition(
|
||||
() => document.querySelector(".devtools-side-splitter")
|
||||
);
|
||||
return el;
|
||||
}
|
||||
|
||||
const box = renderBox();
|
||||
const box = await renderBox();
|
||||
|
||||
// Test that we properly rendered our two panes.
|
||||
|
||||
@ -65,7 +70,7 @@ window.onload = async function () {
|
||||
|
||||
// Now change the left width and assert that the changes are reflected.
|
||||
|
||||
renderBox({ startWidth: .25 });
|
||||
await renderBox({ startWidth: .25 });
|
||||
panes = document.querySelectorAll(".h-split-box-pane");
|
||||
is(panes.length, 2, "Should still have two panes");
|
||||
is(panes[0].style.flexGrow, "0.25", "First pane's width should be .25");
|
||||
|
@ -66,6 +66,21 @@ window.onload = async function () {
|
||||
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55",
|
||||
});
|
||||
|
||||
// Check when there's an error in CSS (View source in Style Editor)
|
||||
await checkFrameComponent({
|
||||
frame: {
|
||||
source: "http://myfile.com/cafebabe.css",
|
||||
line: 13,
|
||||
},
|
||||
messageSource: "css",
|
||||
}, {
|
||||
file: "cafebabe.css",
|
||||
line: 13,
|
||||
shouldLink: true,
|
||||
tooltip: "View source in Style Editor → http://myfile.com/cafebabe.css:13",
|
||||
});
|
||||
|
||||
|
||||
// Check when there's no parseable URL source;
|
||||
// should not link but should render line/columns
|
||||
await checkFrameComponent({
|
||||
|
@ -30,27 +30,20 @@ window.onload = async function () {
|
||||
const renderedBox = shallowRenderComponent(NotificationBox, {});
|
||||
is(renderedBox.type, "div", "NotificationBox is rendered as <div>");
|
||||
|
||||
// Test rendering
|
||||
info("Test rendering NotificationBox with default props");
|
||||
let boxElement = React.createElement(NotificationBox);
|
||||
let notificationBox = TestUtils.renderIntoDocument(boxElement);
|
||||
let notificationNode = ReactDOM.findDOMNode(notificationBox);
|
||||
|
||||
is(notificationNode.className, "notificationbox",
|
||||
"NotificationBox has expected classname");
|
||||
ok(notificationNode.classList.contains("notificationbox"),
|
||||
"NotificationBox has expected class");
|
||||
ok(notificationNode.classList.contains("border-bottom"),
|
||||
"NotificationBox has expected class");
|
||||
is(notificationNode.textContent, "",
|
||||
"Empty NotificationBox has no text content");
|
||||
|
||||
checkNumberOfNotifications(notificationBox, 0);
|
||||
|
||||
// Append wrapping classname to the dom element when passing wrapping prop
|
||||
const renderedBoxWrapped = shallowRenderComponent(NotificationBox, {});
|
||||
let boxElementWrapped = React.createElement(NotificationBox, {wrapping: true});
|
||||
let notificationBoxWrapped = TestUtils.renderIntoDocument(boxElementWrapped);
|
||||
let wrappedNotificationNode = ReactDOM.findDOMNode(notificationBoxWrapped);
|
||||
|
||||
is(wrappedNotificationNode.className, "notificationbox wrapping",
|
||||
"Wrapped notificationBox has expected classname");
|
||||
|
||||
// Append a notification
|
||||
notificationBox.appendNotification(
|
||||
"Info message",
|
||||
@ -100,6 +93,28 @@ window.onload = async function () {
|
||||
|
||||
notificationBox.getNotificationWithValue("id3").close();
|
||||
checkNumberOfNotifications(notificationBox, 0);
|
||||
|
||||
info(`Check "wrapping" prop works as expected`);
|
||||
// Append wrapping classname to the dom element when passing wrapping prop
|
||||
let boxElementWrapped = React.createElement(NotificationBox, {wrapping: true});
|
||||
let notificationBoxWrapped = TestUtils.renderIntoDocument(boxElementWrapped);
|
||||
let wrappedNotificationNode = ReactDOM.findDOMNode(notificationBoxWrapped);
|
||||
|
||||
ok(wrappedNotificationNode.classList.contains("wrapping"),
|
||||
"Wrapped notificationBox has expected class");
|
||||
|
||||
info(`Check "displayBorderTop/displayBorderBottom" props work as expected`);
|
||||
let element = React.createElement(NotificationBox, {
|
||||
displayBorderTop: true,
|
||||
displayBorderBottom: false,
|
||||
});
|
||||
let renderedElement = TestUtils.renderIntoDocument(element);
|
||||
let elementNode = ReactDOM.findDOMNode(renderedElement);
|
||||
|
||||
ok(elementNode.classList.contains("border-top"),
|
||||
"truthy displayBorderTop render a border-top className");
|
||||
ok(!elementNode.classList.contains("border-bottom"),
|
||||
"falsy displayBorderBottom does not render a border-bottom className");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
|
@ -463,10 +463,16 @@ a {
|
||||
html .jsterm-input-node-html,
|
||||
html #webconsole-notificationbox {
|
||||
flex: 0;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.webconsole-output:empty ~ .notificationbox .notification {
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.jsterm-input-container {
|
||||
max-width: 100%;
|
||||
background-color: var(--console-input-background);
|
||||
border-top: 1px solid var(--theme-splitter-color);
|
||||
position: relative;
|
||||
@ -846,7 +852,7 @@ a.learn-more-link.webconsole-learn-more-link {
|
||||
}
|
||||
|
||||
/* console.table() */
|
||||
.new-consoletable {
|
||||
.message .new-consoletable {
|
||||
width: 100%;
|
||||
--consoletable-border: var(--theme-splitter-color);
|
||||
margin-block-end: var(--attachment-margin-block-end);
|
||||
@ -886,93 +892,6 @@ a.learn-more-link.webconsole-learn-more-link {
|
||||
background-color: var(--table-zebra-background);
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.webconsole-output {
|
||||
direction: ltr;
|
||||
overflow: auto;
|
||||
overflow-anchor: none;
|
||||
-moz-user-select: text;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-wrapper {
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Here's what the layout of the console looks like:
|
||||
*
|
||||
* +------------------------------+--------------+
|
||||
* | | |
|
||||
* | WEBCONSOLE WRAPPER | SIDEBAR |
|
||||
* | | |
|
||||
* +------------------------------+--------------+
|
||||
*/
|
||||
.webconsole-app {
|
||||
--object-inspector-hover-background: transparent;
|
||||
--attachment-margin-block-end: 3px;
|
||||
--primary-toolbar-height: 29px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(200px, 1fr) auto;
|
||||
max-height: 100vh !important;
|
||||
height: 100vh !important;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
color: var(--console-output-color);
|
||||
-moz-user-focus: normal;
|
||||
}
|
||||
|
||||
.webconsole-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.webconsole-wrapper .webconsole-filteringbar-wrapper {
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.webconsole-wrapper .webconsole-output {
|
||||
flex-shrink: 100000;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.webconsole-wrapper > .webconsole-output:not(:empty) {
|
||||
min-height: 19px;
|
||||
}
|
||||
|
||||
.webconsole-app #webconsole-notificationbox {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.webconsole-app .jsterm-input-container {
|
||||
min-height: 28px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.jsterm-cm .jsterm-input-container {
|
||||
padding-block-start: 2px;
|
||||
}
|
||||
|
||||
.webconsole-wrapper > .webconsole-output:empty ~ .jsterm-input-container {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* Object Inspector */
|
||||
.webconsole-app .object-inspector.tree {
|
||||
display: inline-block;
|
||||
@ -1073,142 +992,6 @@ body {
|
||||
fill: currentColor
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
display: flex;
|
||||
grid-row: 1 / -1;
|
||||
grid-column: -1 / -2;
|
||||
background-color: var(--theme-sidebar-background);
|
||||
border-inline-start: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.sidebar .splitter {
|
||||
/* Let the parent component handle the border. This is needed otherwise there is a visual
|
||||
glitch between the input and the sidebar borders */
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.split-box.vert.sidebar {
|
||||
/* Set to prevent the sidebar from extending past the right edge of the page */
|
||||
width: unset;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.webconsole-sidebar-toolbar {
|
||||
grid-row: 1 / 2;
|
||||
min-height: var(--primary-toolbar-height);
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar-contents {
|
||||
grid-row: 2 / 3;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.webconsole-sidebar-toolbar .sidebar-close-button {
|
||||
padding: 4px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-close-button::before {
|
||||
background-image: url(chrome://devtools/skin/images/close.svg);
|
||||
}
|
||||
|
||||
.sidebar-contents .object-inspector {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/** EDITOR MODE */
|
||||
.jsterm-editor .webconsole-wrapper {
|
||||
display: grid;
|
||||
/*
|
||||
* Here's the design we want in editor mode
|
||||
* +----------------------------------------------------+
|
||||
* | Editor Toolbar | Filter bar primary |
|
||||
* +----------------------------------------------------+
|
||||
* | | (Filter bar secondary) |
|
||||
* | +-------------------------+
|
||||
* | Editor | |
|
||||
* | | |
|
||||
* | | |
|
||||
* | | output |
|
||||
* | | |
|
||||
* | | |
|
||||
* +--------------------------+ |
|
||||
* | (Reverse search input) | |
|
||||
* +----------------------------------------------------+
|
||||
*
|
||||
* Elements in parens may not be visible, so we set some rows to be auto to make them
|
||||
* have no height when the element they contain is hidden.
|
||||
*/
|
||||
grid-template-rows: var(--primary-toolbar-height) auto 1fr auto;
|
||||
/* The first column is set to auto so we can resize it */
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-filteringbar-wrapper {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1 / 3;
|
||||
grid-template-rows: subgrid;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-filterbar-primary {
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
/* Only put the filter buttons toolbar on its own row in narrow filterbar layout */
|
||||
.jsterm-editor .narrow .devtools-toolbar.webconsole-filterbar-secondary {
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-editor-toolbar {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1 / 2;
|
||||
border-inline-end: 1px solid var(--theme-splitter-color);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-output {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 3 / -1;
|
||||
}
|
||||
|
||||
.jsterm-editor .jsterm-input-container {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2 / 4;
|
||||
width: 50vw;
|
||||
border-inline-end: 1px solid var(--theme-splitter-color);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.jsterm-editor #webconsole-notificationbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jsterm-cm.jsterm-editor .jsterm-input-container > .CodeMirror {
|
||||
padding-inline-start: 0;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.jsterm-editor .reverse-search {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: -1 / -2;
|
||||
border-top: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
/** Utils **/
|
||||
.clipboard-only {
|
||||
position: absolute;
|
||||
|
@ -23,6 +23,7 @@ const {
|
||||
WARNING_GROUPS_TOGGLE,
|
||||
FILTERBAR_DISPLAY_MODE_SET,
|
||||
EDITOR_TOGGLE,
|
||||
EDITOR_SET_WIDTH,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
|
||||
function persistToggle() {
|
||||
@ -98,6 +99,16 @@ function editorToggle() {
|
||||
};
|
||||
}
|
||||
|
||||
function setEditorWidth(width) {
|
||||
return ({ dispatch, prefsService }) => {
|
||||
dispatch({
|
||||
type: EDITOR_SET_WIDTH,
|
||||
width,
|
||||
});
|
||||
prefsService.setIntPref(PREFS.UI.EDITOR_WIDTH, width);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a SHOW_OBJECT_IN_SIDEBAR action, with a grip property corresponding to the
|
||||
* {actor} parameter in the {messageId} message.
|
||||
@ -148,6 +159,7 @@ module.exports = {
|
||||
persistToggle,
|
||||
reverseSearchInputToggle,
|
||||
selectNetworkMessageTab,
|
||||
setEditorWidth,
|
||||
showMessageObjectInSidebar,
|
||||
showObjectInSidebar,
|
||||
sidebarClose,
|
||||
|
264
devtools/client/webconsole/components/App.css
Normal file
264
devtools/client/webconsole/components/App.css
Normal file
@ -0,0 +1,264 @@
|
||||
/* 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/. */
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-wrapper {
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.webconsole-output {
|
||||
direction: ltr;
|
||||
overflow: auto;
|
||||
overflow-anchor: none;
|
||||
-moz-user-select: text;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.webconsole-app {
|
||||
--object-inspector-hover-background: transparent;
|
||||
--attachment-margin-block-end: 3px;
|
||||
--primary-toolbar-height: 29px;
|
||||
display: grid;
|
||||
/*
|
||||
* Here's the design we want in in-line mode
|
||||
* +----------------------------------------------+
|
||||
* | Filter bar primary ↔ |
|
||||
* +----------------------------↔ |
|
||||
* | [Filter bar secondary] ↔ |
|
||||
* +----------------------------↔ |
|
||||
* | ↔ |
|
||||
* + +----------------------+ ↔ |
|
||||
* | | | ↔ |
|
||||
* | | Output | ↔ [sidebar] |
|
||||
* | | | ↔ |
|
||||
* | +----------------------+ ↔ |
|
||||
* | | [NotificationBox] | ↔ |
|
||||
* | +----------------------+ ↔ |
|
||||
* | | | ↔ |
|
||||
* | | JSTerm | ↔ |
|
||||
* | | | ↔ |
|
||||
* | +----------------------+ ↔ |
|
||||
* | ↔ |
|
||||
* +----------------------------↔ |
|
||||
* | [Reverse search input] ↔ |
|
||||
* +----------------------------------------------+
|
||||
*
|
||||
* - ↔ are width resizers
|
||||
* - Elements inside brackets may not be visible, so we set
|
||||
* rows/columns to "auto" to make them collapse when the element
|
||||
* they contain is hidden.
|
||||
*/
|
||||
grid-template-rows: var(--primary-toolbar-height) auto 1fr auto;
|
||||
grid-template-columns: minmax(200px, 1fr) minmax(0, auto);
|
||||
max-height: 100vh !important;
|
||||
height: 100vh !important;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
color: var(--console-output-color);
|
||||
-moz-user-focus: normal;
|
||||
}
|
||||
|
||||
.webconsole-filteringbar-wrapper {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1 / 3;
|
||||
grid-template-rows: subgrid;
|
||||
}
|
||||
|
||||
.webconsole-filterbar-primary {
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
/* Only put the filter buttons toolbar on its own row in narrow filterbar layout */
|
||||
.narrow .devtools-toolbar.webconsole-filterbar-secondary {
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
.flexible-output-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 3 / 4;
|
||||
/* Don't take more height than the grid allows to */
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flexible-output-input .webconsole-output {
|
||||
flex-shrink: 100000;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.flexible-output-input > .webconsole-output:not(:empty) {
|
||||
min-height: 19px;
|
||||
}
|
||||
|
||||
.webconsole-app .jsterm-input-container {
|
||||
min-height: 28px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.jsterm-cm .jsterm-input-container {
|
||||
padding-block-start: 2px;
|
||||
}
|
||||
|
||||
.webconsole-app .webconsole-output:empty ~ .jsterm-input-container {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.reverse-search {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: -1 / -2;
|
||||
border-top: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: grid;
|
||||
grid-row: 1 / -1;
|
||||
grid-column: -1 / -2;
|
||||
grid-template-rows: subgrid;
|
||||
background-color: var(--theme-sidebar-background);
|
||||
width: 200px;
|
||||
min-width: 150px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
/* We want the splitter to cover the whole column (minus self-xss message) */
|
||||
grid-row: 1 / -1;
|
||||
grid-column: -1 / -2;
|
||||
}
|
||||
|
||||
.webconsole-sidebar-toolbar {
|
||||
grid-row: 1 / 2;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar-contents {
|
||||
grid-row: 2 / -1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.webconsole-sidebar-toolbar .sidebar-close-button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-close-button::before {
|
||||
background-image: url(chrome://devtools/skin/images/close.svg);
|
||||
}
|
||||
|
||||
.sidebar-contents .object-inspector {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
/** EDITOR MODE */
|
||||
.webconsole-app.jsterm-editor {
|
||||
display: grid;
|
||||
/*
|
||||
* Here's the design we want in editor mode
|
||||
* +-----------------------------------------------------------------------+
|
||||
* | [Notification Box (self XSS warning)] |
|
||||
* +--------------------------+--------------------------+-----------------+
|
||||
* | Editor Toolbar ↔ Filter bar primary ↔ |
|
||||
* +--------------------------↔--------------------------↔ |
|
||||
* | ↔ [Filter bar secondary] ↔ |
|
||||
* | ↔--------------------------↔ |
|
||||
* | ↔ ↔ |
|
||||
* | Editor ↔ output ↔ [sidebar] |
|
||||
* | ↔ ↔ |
|
||||
* | ↔ ↔ |
|
||||
* | ↔ ↔ |
|
||||
* | ↔ ↔ |
|
||||
* +--------------------------↔ ↔ |
|
||||
* | [Reverse search input] ↔ ↔ |
|
||||
* +-----------------------------------------------------+-----------------+
|
||||
*
|
||||
* - ↔ are width resizers
|
||||
* - Elements inside brackets may not be visible, so we set
|
||||
* rows/columns to "auto" to make them collapse when the element
|
||||
* they contain is hidden.
|
||||
*/
|
||||
grid-template-rows:
|
||||
auto
|
||||
var(--primary-toolbar-height)
|
||||
auto
|
||||
1fr
|
||||
auto;
|
||||
grid-template-columns: minmax(150px, auto) minmax(200px, 1fr) minmax(0, auto);
|
||||
}
|
||||
|
||||
.jsterm-editor .flexible-output-input {
|
||||
/* This allow us to place the div children (jsterm, output, notification) on the grid */
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-filteringbar-wrapper {
|
||||
grid-row: 2 / 4;
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-filterbar-primary {
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-editor-toolbar {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2 / 3;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.jsterm-editor .webconsole-output {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 4 / -1;
|
||||
}
|
||||
|
||||
.jsterm-editor .jsterm-input-container {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 3 / 5;
|
||||
width: 30vw;
|
||||
min-width: 150px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.jsterm-editor #webconsole-notificationbox {
|
||||
grid-row: 1 / 2;
|
||||
grid-column: -1 / 1;
|
||||
}
|
||||
|
||||
.jsterm-cm.jsterm-editor .jsterm-input-container > .CodeMirror {
|
||||
padding-inline-start: 0;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.jsterm-editor .sidebar {
|
||||
grid-row: 2 / -1;
|
||||
}
|
||||
|
||||
.jsterm-editor .editor-resizer {
|
||||
grid-column: 1 / 2;
|
||||
/* We want the splitter to cover the whole column (minus self-xss message) */
|
||||
grid-row: 2 / -1;
|
||||
}
|
||||
|
||||
.jsterm-editor .sidebar-resizer {
|
||||
grid-column: -1 / -2;
|
||||
/* We want the splitter to cover the whole column (minus self-xss message) */
|
||||
grid-row: 2 / -1;
|
||||
}
|
@ -46,6 +46,9 @@ const ConfirmDialog = createFactory(
|
||||
const NotificationBox = createFactory(
|
||||
require("devtools/client/shared/components/NotificationBox").NotificationBox
|
||||
);
|
||||
const GridElementWidthResizer = createFactory(
|
||||
require("devtools/client/shared/components/splitter/GridElementWidthResizer")
|
||||
);
|
||||
|
||||
const l10n = require("devtools/client/webconsole/webconsole-l10n");
|
||||
const { Utils: WebConsoleUtils } = require("devtools/client/webconsole/utils");
|
||||
@ -82,6 +85,7 @@ class App extends Component {
|
||||
reverseSearchInputVisible: PropTypes.bool,
|
||||
reverseSearchInitialValue: PropTypes.string,
|
||||
editorMode: PropTypes.bool,
|
||||
editorWidth: PropTypes.number,
|
||||
hideShowContentMessagesCheckbox: PropTypes.bool,
|
||||
sidebarVisible: PropTypes.bool.isRequired,
|
||||
filterBarDisplayMode: PropTypes.oneOf([
|
||||
@ -231,40 +235,111 @@ class App extends Component {
|
||||
input.addEventListener("keyup", pasteKeyUpHandler);
|
||||
}
|
||||
|
||||
// Rendering
|
||||
|
||||
render() {
|
||||
renderFilterBar() {
|
||||
const {
|
||||
webConsoleUI,
|
||||
notifications,
|
||||
onFirstMeaningfulPaint,
|
||||
serviceContainer,
|
||||
closeSplitConsole,
|
||||
jstermCodeMirror,
|
||||
autocomplete,
|
||||
reverseSearchInitialValue,
|
||||
editorMode,
|
||||
filterBarDisplayMode,
|
||||
sidebarVisible,
|
||||
hideShowContentMessagesCheckbox,
|
||||
} = this.props;
|
||||
|
||||
return FilterBar({
|
||||
key: "filterbar",
|
||||
hidePersistLogsCheckbox: webConsoleUI.isBrowserConsole,
|
||||
hideShowContentMessagesCheckbox,
|
||||
closeSplitConsole,
|
||||
displayMode: filterBarDisplayMode,
|
||||
});
|
||||
}
|
||||
|
||||
renderConsoleOutput() {
|
||||
const { onFirstMeaningfulPaint, serviceContainer } = this.props;
|
||||
|
||||
return ConsoleOutput({
|
||||
key: "console-output",
|
||||
serviceContainer,
|
||||
onFirstMeaningfulPaint,
|
||||
});
|
||||
}
|
||||
|
||||
renderJsTerm() {
|
||||
const {
|
||||
webConsoleUI,
|
||||
serviceContainer,
|
||||
jstermCodeMirror,
|
||||
autocomplete,
|
||||
editorMode,
|
||||
editorWidth,
|
||||
} = this.props;
|
||||
|
||||
return JSTerm({
|
||||
key: "jsterm",
|
||||
webConsoleUI,
|
||||
serviceContainer,
|
||||
onPaste: this.onPaste,
|
||||
codeMirrorEnabled: jstermCodeMirror,
|
||||
autocomplete,
|
||||
editorMode,
|
||||
editorWidth,
|
||||
});
|
||||
}
|
||||
|
||||
renderReverseSearch() {
|
||||
const { serviceContainer, reverseSearchInitialValue } = this.props;
|
||||
|
||||
return ReverseSearchInput({
|
||||
key: "reverse-search-input",
|
||||
setInputValue: serviceContainer.setInputValue,
|
||||
focusInput: serviceContainer.focusInput,
|
||||
evaluateInput: serviceContainer.evaluateInput,
|
||||
initialValue: reverseSearchInitialValue,
|
||||
});
|
||||
}
|
||||
|
||||
renderSideBar() {
|
||||
const { serviceContainer, sidebarVisible } = this.props;
|
||||
return SideBar({
|
||||
key: "sidebar",
|
||||
serviceContainer,
|
||||
visible: sidebarVisible,
|
||||
});
|
||||
}
|
||||
|
||||
renderNotificationBox() {
|
||||
const { notifications, editorMode } = this.props;
|
||||
|
||||
return NotificationBox({
|
||||
id: "webconsole-notificationbox",
|
||||
key: "notification-box",
|
||||
displayBorderTop: !editorMode,
|
||||
displayBorderBottom: editorMode,
|
||||
wrapping: true,
|
||||
notifications,
|
||||
});
|
||||
}
|
||||
|
||||
renderConfirmDialog() {
|
||||
const { webConsoleUI, serviceContainer, jstermCodeMirror } = this.props;
|
||||
|
||||
return ConfirmDialog({
|
||||
webConsoleUI,
|
||||
serviceContainer,
|
||||
codeMirrorEnabled: jstermCodeMirror,
|
||||
key: "confirm-dialog",
|
||||
});
|
||||
}
|
||||
|
||||
renderRootElement(children) {
|
||||
const { jstermCodeMirror, editorMode } = this.props;
|
||||
|
||||
const classNames = ["webconsole-app"];
|
||||
if (jstermCodeMirror) {
|
||||
classNames.push("jsterm-cm");
|
||||
}
|
||||
if (editorMode) {
|
||||
classNames.push("jsterm-editor");
|
||||
}
|
||||
if (jstermCodeMirror) {
|
||||
classNames.push("jsterm-cm");
|
||||
}
|
||||
|
||||
// Render the entire Console panel. The panel consists
|
||||
// from the following parts:
|
||||
// * FilterBar - Buttons & free text for content filtering
|
||||
// * Content - List of logs & messages
|
||||
// * NotificationBox - Notifications for JSTerm (self-xss warning at the moment)
|
||||
// * JSTerm - Input command line.
|
||||
// * ReverseSearchInput - Reverse search input.
|
||||
// * SideBar - Object inspector
|
||||
return div(
|
||||
{
|
||||
className: classNames.join(" "),
|
||||
@ -274,53 +349,47 @@ class App extends Component {
|
||||
this.node = node;
|
||||
},
|
||||
},
|
||||
div(
|
||||
{ className: "webconsole-wrapper" },
|
||||
FilterBar({
|
||||
hidePersistLogsCheckbox: webConsoleUI.isBrowserConsole,
|
||||
hideShowContentMessagesCheckbox,
|
||||
closeSplitConsole,
|
||||
displayMode: filterBarDisplayMode,
|
||||
}),
|
||||
ConsoleOutput({
|
||||
serviceContainer,
|
||||
onFirstMeaningfulPaint,
|
||||
}),
|
||||
NotificationBox({
|
||||
id: "webconsole-notificationbox",
|
||||
wrapping: true,
|
||||
notifications,
|
||||
}),
|
||||
EditorToolbar({
|
||||
editorMode,
|
||||
webConsoleUI,
|
||||
}),
|
||||
JSTerm({
|
||||
webConsoleUI,
|
||||
serviceContainer,
|
||||
onPaste: this.onPaste,
|
||||
codeMirrorEnabled: jstermCodeMirror,
|
||||
autocomplete,
|
||||
editorMode,
|
||||
}),
|
||||
ReverseSearchInput({
|
||||
setInputValue: serviceContainer.setInputValue,
|
||||
focusInput: serviceContainer.focusInput,
|
||||
evaluateInput: serviceContainer.evaluateInput,
|
||||
initialValue: reverseSearchInitialValue,
|
||||
})
|
||||
),
|
||||
SideBar({
|
||||
serviceContainer,
|
||||
visible: sidebarVisible,
|
||||
}),
|
||||
ConfirmDialog({
|
||||
webConsoleUI,
|
||||
serviceContainer,
|
||||
codeMirrorEnabled: jstermCodeMirror,
|
||||
})
|
||||
...children
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { webConsoleUI, editorMode, dispatch } = this.props;
|
||||
|
||||
const filterBar = this.renderFilterBar();
|
||||
const consoleOutput = this.renderConsoleOutput();
|
||||
const notificationBox = this.renderNotificationBox();
|
||||
const jsterm = this.renderJsTerm();
|
||||
const reverseSearch = this.renderReverseSearch();
|
||||
const sidebar = this.renderSideBar();
|
||||
const confirmDialog = this.renderConfirmDialog();
|
||||
|
||||
return this.renderRootElement([
|
||||
filterBar,
|
||||
editorMode
|
||||
? EditorToolbar({
|
||||
editorMode,
|
||||
webConsoleUI,
|
||||
})
|
||||
: null,
|
||||
dom.div(
|
||||
{ className: "flexible-output-input" },
|
||||
consoleOutput,
|
||||
notificationBox,
|
||||
jsterm
|
||||
),
|
||||
GridElementWidthResizer({
|
||||
enabled: editorMode,
|
||||
position: "end",
|
||||
className: "editor-resizer",
|
||||
getControlledElementNode: () => webConsoleUI.jsterm.node,
|
||||
onResizeEnd: width => dispatch(actions.setEditorWidth(width)),
|
||||
}),
|
||||
reverseSearch,
|
||||
sidebar,
|
||||
confirmDialog,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
@ -328,6 +397,7 @@ const mapStateToProps = state => ({
|
||||
reverseSearchInputVisible: state.ui.reverseSearchInputVisible,
|
||||
reverseSearchInitialValue: state.ui.reverseSearchInitialValue,
|
||||
editorMode: state.ui.editor,
|
||||
editorWidth: state.ui.editorWidth,
|
||||
sidebarVisible: state.ui.sidebarVisible,
|
||||
filterBarDisplayMode: state.ui.filterBarDisplayMode,
|
||||
});
|
||||
|
@ -114,6 +114,7 @@ class JSTerm extends Component {
|
||||
autocompleteData: PropTypes.object.isRequired,
|
||||
// Is the input in editor mode.
|
||||
editorMode: PropTypes.bool,
|
||||
editorWidth: PropTypes.number,
|
||||
autocomplete: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
@ -154,6 +155,10 @@ class JSTerm extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.editorMode) {
|
||||
this.setEditorWidth(this.props.editorWidth);
|
||||
}
|
||||
|
||||
const autocompleteOptions = {
|
||||
onSelect: this.onAutocompleteSelect.bind(this),
|
||||
onClick: this.acceptProposedCompletion.bind(this),
|
||||
@ -539,8 +544,33 @@ class JSTerm extends Component {
|
||||
this.updateAutocompletionPopup(nextProps.autocompleteData);
|
||||
}
|
||||
|
||||
if (this.editor && nextProps.editorMode !== this.props.editorMode) {
|
||||
this.editor.setOption("lineNumbers", nextProps.editorMode);
|
||||
if (nextProps.editorMode !== this.props.editorMode) {
|
||||
if (this.editor) {
|
||||
this.editor.setOption("lineNumbers", nextProps.editorMode);
|
||||
}
|
||||
|
||||
if (nextProps.editorMode && nextProps.editorWidth) {
|
||||
this.setEditorWidth(nextProps.editorWidth);
|
||||
} else {
|
||||
this.setEditorWidth(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Number|null} editorWidth: The width to set the node to. If null, removes any
|
||||
* `width` property on node style.
|
||||
*/
|
||||
setEditorWidth(editorWidth) {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editorWidth) {
|
||||
this.node.style.width = `${editorWidth}px`;
|
||||
} else {
|
||||
this.node.style.removeProperty("width");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1795,6 +1825,9 @@ class JSTerm extends Component {
|
||||
key: "jsterm-container",
|
||||
style: { direction: "ltr" },
|
||||
"aria-live": "off",
|
||||
ref: node => {
|
||||
this.node = node;
|
||||
},
|
||||
},
|
||||
dom.textarea({
|
||||
className: "jsterm-complete-node devtools-monospace",
|
||||
|
@ -69,6 +69,7 @@ class ConsoleTable extends Component {
|
||||
{
|
||||
className: "new-consoletable-header",
|
||||
role: "columnheader",
|
||||
key,
|
||||
},
|
||||
value
|
||||
)
|
||||
@ -88,6 +89,7 @@ class ConsoleTable extends Component {
|
||||
{
|
||||
role: "gridcell",
|
||||
className: index % 2 ? "odd" : "even",
|
||||
key,
|
||||
},
|
||||
GripMessageBody({
|
||||
grip: item[key],
|
||||
@ -196,11 +198,11 @@ function getTableItems(data = {}, type, headers = null) {
|
||||
const { preview } = property;
|
||||
const entries = preview.ownProperties || preview.items;
|
||||
if (entries) {
|
||||
for (const key of Object.keys(entries)) {
|
||||
const entry = entries[key];
|
||||
item[key] = Object.prototype.hasOwnProperty.call(entry, "value")
|
||||
? entry.value
|
||||
: entry;
|
||||
for (const [key, entry] of Object.entries(entries)) {
|
||||
item[key] =
|
||||
entry && Object.prototype.hasOwnProperty.call(entry, "value")
|
||||
? entry.value
|
||||
: entry;
|
||||
}
|
||||
} else {
|
||||
if (preview.key) {
|
||||
|
@ -90,8 +90,8 @@ class Message extends Component {
|
||||
openLink: PropTypes.func.isRequired,
|
||||
sourceMapService: PropTypes.any,
|
||||
canRewind: PropTypes.func.isRequired,
|
||||
jumpToExecutionPoint: PropTypes.func.isRequired,
|
||||
onMessageHover: PropTypes.func.isRequired,
|
||||
jumpToExecutionPoint: PropTypes.func,
|
||||
onMessageHover: PropTypes.func,
|
||||
}),
|
||||
notes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
@ -418,6 +418,7 @@ class Message extends Component {
|
||||
sourceMapService: serviceContainer
|
||||
? serviceContainer.sourceMapService
|
||||
: undefined,
|
||||
messageSource: source,
|
||||
})
|
||||
: null
|
||||
);
|
||||
|
@ -5,10 +5,13 @@
|
||||
|
||||
const {
|
||||
Component,
|
||||
createElement,
|
||||
createFactory,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const GridElementWidthResizer = createFactory(
|
||||
require("devtools/client/shared/components/splitter/GridElementWidthResizer")
|
||||
);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"dom",
|
||||
@ -30,11 +33,6 @@ loader.lazyRequireGetter(
|
||||
"PropTypes",
|
||||
"devtools/client/shared/vendor/react-prop-types"
|
||||
);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"SplitBox",
|
||||
"devtools/client/shared/components/splitter/SplitBox"
|
||||
);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"reps",
|
||||
@ -78,7 +76,7 @@ class SideBar extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { grip, serviceContainer, onResized } = this.props;
|
||||
const { grip, serviceContainer } = this.props;
|
||||
|
||||
const objectInspector = getObjectInspector(grip, serviceContainer, {
|
||||
autoExpandDepth: 1,
|
||||
@ -86,37 +84,40 @@ class SideBar extends Component {
|
||||
autoFocusRoot: true,
|
||||
});
|
||||
|
||||
const endPanel = dom.aside(
|
||||
{
|
||||
className: "sidebar-wrapper",
|
||||
},
|
||||
dom.header(
|
||||
{
|
||||
className: "devtools-toolbar webconsole-sidebar-toolbar",
|
||||
},
|
||||
dom.button({
|
||||
className: "devtools-button sidebar-close-button",
|
||||
title: l10n.getStr("webconsole.closeSidebarButton.tooltip"),
|
||||
onClick: this.onClickSidebarClose,
|
||||
})
|
||||
),
|
||||
return [
|
||||
dom.aside(
|
||||
{
|
||||
className: "sidebar-contents",
|
||||
className: "sidebar",
|
||||
key: "sidebar",
|
||||
ref: node => {
|
||||
this.node = node;
|
||||
},
|
||||
},
|
||||
objectInspector
|
||||
)
|
||||
);
|
||||
|
||||
return createElement(SplitBox, {
|
||||
className: "sidebar",
|
||||
endPanel,
|
||||
endPanelControl: true,
|
||||
initialSize: "200px",
|
||||
minSize: "100px",
|
||||
vert: true,
|
||||
onControlledPanelResized: onResized,
|
||||
});
|
||||
dom.header(
|
||||
{
|
||||
className: "devtools-toolbar webconsole-sidebar-toolbar",
|
||||
},
|
||||
dom.button({
|
||||
className: "devtools-button sidebar-close-button",
|
||||
title: l10n.getStr("webconsole.closeSidebarButton.tooltip"),
|
||||
onClick: this.onClickSidebarClose,
|
||||
})
|
||||
),
|
||||
dom.aside(
|
||||
{
|
||||
className: "sidebar-contents",
|
||||
},
|
||||
objectInspector
|
||||
)
|
||||
),
|
||||
GridElementWidthResizer({
|
||||
key: "resizer",
|
||||
enabled: true,
|
||||
position: "start",
|
||||
className: "sidebar-resizer",
|
||||
getControlledElementNode: () => this.node,
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ DIRS += [
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'App.css',
|
||||
'App.js',
|
||||
'SideBar.js',
|
||||
)
|
||||
|
@ -47,6 +47,7 @@ const actionTypes = {
|
||||
PAUSED_EXCECUTION_POINT: "PAUSED_EXCECUTION_POINT",
|
||||
WARNING_GROUPS_TOGGLE: "WARNING_GROUPS_TOGGLE",
|
||||
WILL_NAVIGATE: "WILL_NAVIGATE",
|
||||
EDITOR_SET_WIDTH: "EDITOR_SET_WIDTH",
|
||||
};
|
||||
|
||||
const prefs = {
|
||||
@ -74,6 +75,8 @@ const prefs = {
|
||||
CONTENT_MESSAGES: "devtools.browserconsole.contentMessages",
|
||||
// Display timestamp in messages.
|
||||
MESSAGE_TIMESTAMP: "devtools.webconsole.timestampMessages",
|
||||
// Store the editor width.
|
||||
EDITOR_WIDTH: "input.editorWidth",
|
||||
},
|
||||
FEATURES: {
|
||||
// We use the same pref to enable the sidebar on webconsole and browser console.
|
||||
|
@ -18,13 +18,14 @@
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/reps/reps.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/Tabs.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/NotificationBox.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/splitter/GridElementResizer.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/httpi.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/webconsole/components/Input/ReverseSearchInput.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/webconsole/components/App.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/StatusCode.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/RequestList.css"/>
|
||||
|
||||
<script src="chrome://devtools/content/shared/theme-switching.js"></script>
|
||||
<script src="resource://devtools/client/webconsole/main.js"></script>
|
||||
</head>
|
||||
<body class="theme-sidebar" role="application">
|
||||
<main id="app-wrapper" class="theme-body" role="document" aria-live="polite">
|
||||
|
@ -1,22 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global BrowserLoader */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { BrowserLoader } = ChromeUtils.import(
|
||||
"resource://devtools/client/shared/browser-loader.js"
|
||||
);
|
||||
|
||||
this.WebConsoleWrapper = function(parentNode, webConsoleUI, toolbox, document) {
|
||||
// Initialize module loader and load all modules of the new inline
|
||||
// preview feature. The entire code-base doesn't need any extra
|
||||
// privileges and runs entirely in content scope.
|
||||
const WebConsoleWrapper = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/webconsole/",
|
||||
window,
|
||||
}).require("./webconsole-wrapper");
|
||||
return new WebConsoleWrapper(parentNode, webConsoleUI, toolbox, document);
|
||||
};
|
@ -18,7 +18,6 @@ DevToolsModules(
|
||||
'browser-console.js',
|
||||
'constants.js',
|
||||
'hudservice.js',
|
||||
'main.js',
|
||||
'panel.js',
|
||||
'store.js',
|
||||
'types.js',
|
||||
|
@ -19,6 +19,7 @@ const {
|
||||
FILTERBAR_DISPLAY_MODE_SET,
|
||||
FILTERBAR_DISPLAY_MODES,
|
||||
EDITOR_TOGGLE,
|
||||
EDITOR_SET_WIDTH,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
|
||||
const { PANELS } = require("devtools/client/netmonitor/src/constants");
|
||||
@ -38,6 +39,7 @@ const UiState = overrides =>
|
||||
reverseSearchInputVisible: false,
|
||||
reverseSearchInitialValue: "",
|
||||
editor: false,
|
||||
editorWidth: null,
|
||||
filterBarDisplayMode: FILTERBAR_DISPLAY_MODES.WIDE,
|
||||
},
|
||||
overrides
|
||||
@ -87,6 +89,11 @@ function ui(state = UiState(), action) {
|
||||
...state,
|
||||
editor: !state.editor,
|
||||
};
|
||||
case EDITOR_SET_WIDTH:
|
||||
return {
|
||||
...state,
|
||||
editorWidth: action.width,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
|
@ -78,6 +78,7 @@ function configureStore(webConsoleUI, options = {}) {
|
||||
? getBoolPref(PREFS.UI.CONTENT_MESSAGES)
|
||||
: true,
|
||||
editor: getBoolPref(PREFS.UI.EDITOR),
|
||||
editorWidth: getIntPref(PREFS.UI.EDITOR_WIDTH),
|
||||
timestampsVisible: getBoolPref(PREFS.UI.MESSAGE_TIMESTAMP),
|
||||
}),
|
||||
};
|
||||
|
@ -29,6 +29,7 @@ pref("devtools.webconsole.input.editor", false);
|
||||
pref("devtools.webconsole.input.autocomplete", true);
|
||||
pref("devtools.browserconsole.contentMessages", true);
|
||||
pref("devtools.webconsole.features.editor", true);
|
||||
pref("devtools.webconsole.input.editorWidth", 800);
|
||||
|
||||
global.loader = {
|
||||
lazyServiceGetter: () => {},
|
||||
|
@ -216,6 +216,7 @@ skip-if = os != 'mac' # The tested ctrl+key shortcuts are OSX only
|
||||
[browser_jsterm_editor_execute.js]
|
||||
[browser_jsterm_editor_gutter.js]
|
||||
[browser_jsterm_editor_toggle_keyboard_shortcut.js]
|
||||
[browser_jsterm_editor_resize.js]
|
||||
[browser_jsterm_editor_toolbar.js]
|
||||
[browser_jsterm_error_docs.js]
|
||||
[browser_jsterm_error_outside_valid_range.js]
|
||||
|
@ -0,0 +1,88 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that the editor can be resized and that its width is persisted.
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI =
|
||||
"data:text/html;charset=utf-8,Web Console test for editor resize";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.features.editor", true);
|
||||
await pushPref("devtools.webconsole.input.editor", true);
|
||||
|
||||
// Run test with legacy JsTerm
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", false);
|
||||
await performTests();
|
||||
// And then run it with the CodeMirror-powered one.
|
||||
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
|
||||
await performTests();
|
||||
});
|
||||
|
||||
async function performTests() {
|
||||
// Reset editorWidth pref so we have steady results when running multiple times.
|
||||
await pushPref("devtools.webconsole.input.editorWidth", null);
|
||||
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
const getEditorEl = () =>
|
||||
hud.ui.outputNode.querySelector(".jsterm-input-container");
|
||||
const resizerEl = hud.ui.outputNode.querySelector(".editor-resizer");
|
||||
|
||||
const editorBoundingRect = getEditorEl().getBoundingClientRect();
|
||||
const delta = 100;
|
||||
const originalWidth = editorBoundingRect.width;
|
||||
const clientX = editorBoundingRect.right + delta;
|
||||
await resize(resizerEl, clientX);
|
||||
|
||||
const newWidth = Math.floor(originalWidth + delta);
|
||||
is(
|
||||
Math.floor(getEditorEl().getBoundingClientRect().width),
|
||||
newWidth,
|
||||
"The editor element was resized as expected"
|
||||
);
|
||||
info("Close and re-open the console to check if editor width was persisted");
|
||||
await closeConsole();
|
||||
hud = await openConsole();
|
||||
|
||||
is(
|
||||
Math.floor(getEditorEl().getBoundingClientRect().width),
|
||||
newWidth,
|
||||
"The editor element width was persisted"
|
||||
);
|
||||
await toggleLayout(hud);
|
||||
|
||||
ok(!getEditorEl().style.width, "The width isn't applied in in-line layout");
|
||||
|
||||
await toggleLayout(hud);
|
||||
is(
|
||||
getEditorEl().style.width,
|
||||
`${newWidth}px`,
|
||||
"The width is applied again when switching back to editor"
|
||||
);
|
||||
}
|
||||
|
||||
async function resize(resizer, clientX) {
|
||||
const doc = resizer.ownerDocument;
|
||||
const win = doc.defaultView;
|
||||
|
||||
info("Mouse down to start dragging");
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
resizer,
|
||||
{ button: 0, type: "mousedown" },
|
||||
win
|
||||
);
|
||||
await waitFor(() => doc.querySelector(".dragging"));
|
||||
|
||||
const event = new MouseEvent("mousemove", { clientX });
|
||||
resizer.ownerDocument.dispatchEvent(event);
|
||||
|
||||
info("Mouse up to stop resizing");
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
doc.body,
|
||||
{ button: 0, type: "mouseup" },
|
||||
win
|
||||
);
|
||||
|
||||
await waitFor(() => !doc.querySelector(".dragging"));
|
||||
}
|
@ -29,24 +29,31 @@ async function performTest() {
|
||||
await pushPref(EDITOR_PREF, false);
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
const INPUT_VALUE = "hello";
|
||||
setInputValue(hud, INPUT_VALUE);
|
||||
|
||||
is(isEditorModeEnabled(hud), false, "The console isn't in editor mode");
|
||||
|
||||
info("Enable the editor mode");
|
||||
await toggleLayout(hud);
|
||||
is(isEditorModeEnabled(hud), true, "Editor mode is enabled");
|
||||
is(getInputValue(hud), INPUT_VALUE, "The input value wasn't cleared");
|
||||
|
||||
info("Close the console and reopen it");
|
||||
await closeConsole();
|
||||
hud = await openConsole();
|
||||
is(isEditorModeEnabled(hud), true, "Editor mode is still enabled");
|
||||
setInputValue(hud, INPUT_VALUE);
|
||||
|
||||
info("Disable the editor mode");
|
||||
await toggleLayout(hud);
|
||||
is(isEditorModeEnabled(hud), false, "Editor was disabled");
|
||||
is(getInputValue(hud), INPUT_VALUE, "The input value wasn't cleared");
|
||||
|
||||
info("Enable the editor mode again");
|
||||
await toggleLayout(hud);
|
||||
is(isEditorModeEnabled(hud), true, "Editor mode was enabled again");
|
||||
is(getInputValue(hud), INPUT_VALUE, "The input value wasn't cleared");
|
||||
|
||||
Services.prefs.clearUserPref(EDITOR_PREF);
|
||||
}
|
||||
|
@ -50,6 +50,15 @@ add_task(async function() {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
info: "Testing when data argument has holey array",
|
||||
// eslint-disable-next-line no-sparse-arrays
|
||||
input: [[1, , 2]],
|
||||
expected: {
|
||||
columns: ["(index)", "0", "1", "2"],
|
||||
rows: [["0", "1", "undefined", "2"]],
|
||||
},
|
||||
},
|
||||
{
|
||||
info: "Testing when data argument is an object",
|
||||
input: new Person("John", "Smith"),
|
||||
@ -147,6 +156,15 @@ add_task(async function() {
|
||||
rows: [["0", "apples"], ["1", "oranges"], ["2", "bananas"]],
|
||||
},
|
||||
},
|
||||
{
|
||||
info: "Testing overflow-y",
|
||||
input: Array.from({ length: 50 }, (_, i) => `item-${i}`),
|
||||
expected: {
|
||||
columns: ["(index)", "Values"],
|
||||
rows: Array.from({ length: 50 }, (_, i) => [i.toString(), `item-${i}`]),
|
||||
overflow: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, testCases, function(tests) {
|
||||
@ -199,6 +217,11 @@ function testItem(testCase, node) {
|
||||
const rowCells = cells.slice(startIndex, startIndex + columnsNumber);
|
||||
is(rowCells.map(x => x.textContent).join(" | "), expectedRow.join(" | "));
|
||||
});
|
||||
|
||||
if (testCase.expected.overflow) {
|
||||
ok(node.scrollHeight > node.clientHeight, "table overflows");
|
||||
ok(getComputedStyle(node).overflowY !== "hidden", "table can be scrolled");
|
||||
}
|
||||
}
|
||||
|
||||
function findConsoleTable(node, index) {
|
||||
|
@ -28,9 +28,11 @@ async function performTests() {
|
||||
const { ui } = hud;
|
||||
const { document } = ui;
|
||||
const appNode = document.querySelector(".webconsole-app");
|
||||
const [filterBarNode, outputNode, , inputNode] = appNode.querySelector(
|
||||
".webconsole-wrapper"
|
||||
).childNodes;
|
||||
const filterBarNode = appNode.querySelector(
|
||||
".webconsole-filteringbar-wrapper"
|
||||
);
|
||||
const outputNode = appNode.querySelector(".webconsole-output");
|
||||
const inputNode = appNode.querySelector(".jsterm-input-container");
|
||||
|
||||
testLayout(appNode);
|
||||
|
||||
|
@ -35,6 +35,8 @@ function getPrefsService(hud) {
|
||||
Services.prefs.getIntPref(getPrefName(pref), deflt),
|
||||
setBoolPref: (pref, value) =>
|
||||
Services.prefs.setBoolPref(getPrefName(pref), value),
|
||||
setIntPref: (pref, value) =>
|
||||
Services.prefs.setIntPref(getPrefName(pref), value),
|
||||
clearUserPref: pref => Services.prefs.clearUserPref(getPrefName(pref)),
|
||||
getPrefName,
|
||||
};
|
||||
|
@ -17,6 +17,11 @@ const {
|
||||
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
|
||||
const { l10n } = require("devtools/client/webconsole/utils/messages");
|
||||
|
||||
var ChromeUtils = require("ChromeUtils");
|
||||
const { BrowserLoader } = ChromeUtils.import(
|
||||
"resource://devtools/client/shared/browser-loader.js"
|
||||
);
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"AppConstants",
|
||||
@ -232,7 +237,14 @@ class WebConsoleUI {
|
||||
|
||||
const toolbox = gDevTools.getToolbox(this.hud.target);
|
||||
|
||||
this.wrapper = new this.window.WebConsoleWrapper(
|
||||
// Initialize module loader and load all the WebConsoleWrapper. The entire code-base
|
||||
// doesn't need any extra privileges and runs entirely in content scope.
|
||||
const WebConsoleWrapper = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/webconsole/",
|
||||
window: this.window,
|
||||
}).require("./webconsole-wrapper");
|
||||
|
||||
this.wrapper = new WebConsoleWrapper(
|
||||
this.outputNode,
|
||||
this,
|
||||
toolbox,
|
||||
|
@ -193,7 +193,7 @@ const previewers = {
|
||||
value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, value);
|
||||
items.push(hooks.createValueGrip(value));
|
||||
} else {
|
||||
items.push(null);
|
||||
items.push(hooks.createValueGrip(undefined));
|
||||
}
|
||||
} else {
|
||||
// Workers do not have access to Cu, and when recording/replaying we
|
||||
|
@ -11,6 +11,14 @@
|
||||
|
||||
#include "prsystem.h"
|
||||
|
||||
// Short macro to get the size of a member of a
|
||||
// given struct at compile time.
|
||||
// t is the type of struct, m the name of the
|
||||
// member:
|
||||
// DOM_SIZEOF_MEMBER(struct mystruct, myint)
|
||||
// will give you the size of the type of myint.
|
||||
#define DOM_SIZEOF_MEMBER(t, m) sizeof(((t*)0)->m)
|
||||
|
||||
#if defined(XP_UNIX)
|
||||
# include "unistd.h"
|
||||
# include "dirent.h"
|
||||
@ -602,6 +610,9 @@ static const dom::ConstantSpec gLibcProperties[] = {
|
||||
|
||||
{"OSFILE_SIZEOF_STATVFS", JS::Int32Value(sizeof(struct statvfs))},
|
||||
|
||||
// We have no guarantee how big "f_frsize" is, so we have to calculate that.
|
||||
{"OSFILE_SIZEOF_STATVFS_F_FRSIZE",
|
||||
JS::Int32Value(DOM_SIZEOF_MEMBER(struct statvfs, f_frsize))},
|
||||
{"OSFILE_OFFSETOF_STATVFS_F_FRSIZE",
|
||||
JS::Int32Value(offsetof(struct statvfs, f_frsize))},
|
||||
{"OSFILE_OFFSETOF_STATVFS_F_BAVAIL",
|
||||
|
@ -122,15 +122,7 @@ void brush_vs(
|
||||
|
||||
vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
|
||||
|
||||
#ifdef WR_FEATURE_ALPHA_PASS
|
||||
int color_mode = prim_user_data.x & 0xffff;
|
||||
int blend_mode = prim_user_data.x >> 16;
|
||||
int raster_space = prim_user_data.y;
|
||||
|
||||
if (color_mode == COLOR_MODE_FROM_PASS) {
|
||||
color_mode = uMode;
|
||||
}
|
||||
|
||||
// Derive the texture coordinates for this image, based on
|
||||
// whether the source image is a local-space or screen-space
|
||||
// image.
|
||||
@ -145,6 +137,14 @@ void brush_vs(
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef WR_FEATURE_ALPHA_PASS
|
||||
int color_mode = prim_user_data.x & 0xffff;
|
||||
int blend_mode = prim_user_data.x >> 16;
|
||||
|
||||
if (color_mode == COLOR_MODE_FROM_PASS) {
|
||||
color_mode = uMode;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Offset and scale vUv here to avoid doing it in the fragment shader.
|
||||
|
@ -1226,7 +1226,7 @@ impl BatchBuilder {
|
||||
textures,
|
||||
[
|
||||
ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
|
||||
RasterizationSpace::Local as i32,
|
||||
RasterizationSpace::Screen as i32,
|
||||
get_shader_opacity(1.0),
|
||||
0,
|
||||
],
|
||||
|
@ -240,7 +240,7 @@ impl FrameBuilder {
|
||||
retained_tiles: RetainedTiles,
|
||||
globals: FrameGlobalResources,
|
||||
) {
|
||||
assert!(self.pending_retained_tiles.caches.is_empty());
|
||||
debug_assert!(self.pending_retained_tiles.tiles.is_empty());
|
||||
self.pending_retained_tiles = retained_tiles;
|
||||
self.globals = globals;
|
||||
}
|
||||
|
@ -111,35 +111,26 @@ struct PictureInfo {
|
||||
_spatial_node_index: SpatialNodeIndex,
|
||||
}
|
||||
|
||||
pub struct PictureCacheState {
|
||||
/// The tiles retained by this picture cache.
|
||||
pub tiles: FastHashMap<TileOffset, Tile>,
|
||||
/// The current fractional offset of the cache transform root.
|
||||
fract_offset: PictureVector2D,
|
||||
}
|
||||
|
||||
/// Stores a list of cached picture tiles that are retained
|
||||
/// between new scenes.
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
pub struct RetainedTiles {
|
||||
/// The tiles retained between display lists.
|
||||
#[cfg_attr(feature = "capture", serde(skip))] //TODO
|
||||
pub caches: FastHashMap<usize, PictureCacheState>,
|
||||
pub tiles: FastHashMap<TileKey, Tile>,
|
||||
}
|
||||
|
||||
impl RetainedTiles {
|
||||
pub fn new() -> Self {
|
||||
RetainedTiles {
|
||||
caches: FastHashMap::default(),
|
||||
tiles: FastHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge items from one retained tiles into another.
|
||||
pub fn merge(&mut self, other: RetainedTiles) {
|
||||
assert!(self.caches.is_empty() || other.caches.is_empty());
|
||||
if self.caches.is_empty() {
|
||||
self.caches = other.caches;
|
||||
}
|
||||
assert!(self.tiles.is_empty() || other.tiles.is_empty());
|
||||
self.tiles.extend(other.tiles);
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +198,14 @@ impl From<PropertyBinding<f32>> for OpacityBinding {
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct TileId(usize);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
|
||||
pub struct TileKey {
|
||||
/// The picture cache slice that this belongs to.
|
||||
slice: usize,
|
||||
/// Offset (in tile coords) of the tile within this slice.
|
||||
offset: TileOffset,
|
||||
}
|
||||
|
||||
/// Information about a cached tile.
|
||||
#[derive(Debug)]
|
||||
pub struct Tile {
|
||||
@ -505,7 +504,7 @@ pub struct TileCacheInstance {
|
||||
/// The positioning node for this tile cache.
|
||||
pub spatial_node_index: SpatialNodeIndex,
|
||||
/// Hash of tiles present in this picture.
|
||||
pub tiles: FastHashMap<TileOffset, Tile>,
|
||||
pub tiles: FastHashMap<TileKey, Tile>,
|
||||
/// A helper struct to map local rects into surface coords.
|
||||
map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>,
|
||||
/// List of opacity bindings, with some extra information
|
||||
@ -526,7 +525,7 @@ pub struct TileCacheInstance {
|
||||
/// Local clip rect for this tile cache.
|
||||
pub local_clip_rect: PictureRect,
|
||||
/// A list of tiles that are valid and visible, which should be drawn to the main scene.
|
||||
pub tiles_to_draw: Vec<TileOffset>,
|
||||
pub tiles_to_draw: Vec<TileKey>,
|
||||
/// The world space viewport that this tile cache draws into.
|
||||
/// Any clips outside this viewport can be ignored (and must be removed so that
|
||||
/// we can draw outside the bounds of the viewport).
|
||||
@ -543,10 +542,6 @@ pub struct TileCacheInstance {
|
||||
/// The allowed subpixel mode for this surface, which depends on the detected
|
||||
/// opacity of the background.
|
||||
pub subpixel_mode: SubpixelMode,
|
||||
/// The current fractional offset of the cache transform root. If this changes,
|
||||
/// all tiles need to be invalidated and redrawn, since snapping differences are
|
||||
/// likely to occur.
|
||||
fract_offset: PictureVector2D,
|
||||
}
|
||||
|
||||
impl TileCacheInstance {
|
||||
@ -577,7 +572,6 @@ impl TileCacheInstance {
|
||||
background_color,
|
||||
opaque_rect: PictureRect::zero(),
|
||||
subpixel_mode: SubpixelMode::Allow,
|
||||
fract_offset: PictureVector2D::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -645,35 +639,6 @@ impl TileCacheInstance {
|
||||
frame_context.clip_scroll_tree,
|
||||
);
|
||||
|
||||
// If there are pending retained state, retrieve it.
|
||||
if let Some(prev_state) = frame_state.retained_tiles.caches.remove(&self.slice) {
|
||||
self.tiles.extend(prev_state.tiles);
|
||||
self.fract_offset = prev_state.fract_offset;
|
||||
}
|
||||
|
||||
// Map an arbitrary point in picture space to world space, to work out
|
||||
// what the fractional translation is that's applied by this scroll root.
|
||||
// TODO(gw): I'm not 100% sure this is right. At least, in future, we should
|
||||
// make a specific API for this, and/or enforce that the picture
|
||||
// cache transform only includes scale and/or translation (we
|
||||
// already ensure it doesn't have perspective).
|
||||
let world_origin = pic_to_world_mapper
|
||||
.map(&PictureRect::new(PicturePoint::zero(), PictureSize::new(1.0, 1.0)))
|
||||
.expect("bug: unable to map origin to world space")
|
||||
.origin;
|
||||
let fract_offset = PictureVector2D::new(
|
||||
world_origin.x.fract(),
|
||||
world_origin.y.fract(),
|
||||
);
|
||||
|
||||
// Determine if the fractional offset of the transform is different this frame
|
||||
// from the currently cached tile set.
|
||||
let fract_changed = (self.fract_offset.x - fract_offset.x).abs() > 0.001 ||
|
||||
(self.fract_offset.y - fract_offset.y).abs() > 0.001;
|
||||
if fract_changed {
|
||||
self.fract_offset = fract_offset;
|
||||
}
|
||||
|
||||
let spatial_node = &frame_context
|
||||
.clip_scroll_tree
|
||||
.spatial_nodes[self.spatial_node_index.0 as usize];
|
||||
@ -786,16 +751,31 @@ impl TileCacheInstance {
|
||||
self.tile_bounds_p0 = TileOffset::new(x0, y0);
|
||||
self.tile_bounds_p1 = TileOffset::new(x1, y1);
|
||||
|
||||
let mut world_culling_rect = WorldRect::zero();
|
||||
// TODO(gw): Tidy this up as we add better support for retaining
|
||||
// slices and sub-grid dirty areas.
|
||||
let mut keys = Vec::new();
|
||||
for key in frame_state.retained_tiles.tiles.keys() {
|
||||
if key.slice == self.slice {
|
||||
keys.push(*key);
|
||||
}
|
||||
}
|
||||
for key in keys {
|
||||
self.tiles.insert(key, frame_state.retained_tiles.tiles.remove(&key).unwrap());
|
||||
}
|
||||
|
||||
let mut old_tiles = mem::replace(
|
||||
&mut self.tiles,
|
||||
FastHashMap::default(),
|
||||
);
|
||||
|
||||
let mut world_culling_rect = WorldRect::zero();
|
||||
|
||||
for y in y0 .. y1 {
|
||||
for x in x0 .. x1 {
|
||||
let key = TileOffset::new(x, y);
|
||||
let key = TileKey {
|
||||
offset: TileOffset::new(x, y),
|
||||
slice: self.slice,
|
||||
};
|
||||
|
||||
let mut tile = old_tiles
|
||||
.remove(&key)
|
||||
@ -804,13 +784,10 @@ impl TileCacheInstance {
|
||||
Tile::new(next_id)
|
||||
});
|
||||
|
||||
// Ensure each tile is offset by the appropriate amount from the
|
||||
// origin, such that the content origin will be a whole number and
|
||||
// the snapping will be consistent.
|
||||
tile.rect = PictureRect::new(
|
||||
PicturePoint::new(
|
||||
x as f32 * self.tile_size.width - fract_offset.x,
|
||||
y as f32 * self.tile_size.height - fract_offset.y,
|
||||
x as f32 * self.tile_size.width,
|
||||
y as f32 * self.tile_size.height,
|
||||
),
|
||||
self.tile_size,
|
||||
);
|
||||
@ -831,9 +808,8 @@ impl TileCacheInstance {
|
||||
|
||||
// Do tile invalidation for any dependencies that we know now.
|
||||
for (_, tile) in &mut self.tiles {
|
||||
// Start frame assuming that the tile has the same content,
|
||||
// unless the fractional offset of the transform root changed.
|
||||
tile.is_same_content = !fract_changed;
|
||||
// Start frame assuming that the tile has the same content.
|
||||
tile.is_same_content = true;
|
||||
|
||||
// Content has changed if any opacity bindings changed.
|
||||
for binding in tile.descriptor.opacity_bindings.items() {
|
||||
@ -1067,7 +1043,10 @@ impl TileCacheInstance {
|
||||
for y in p0.y .. p1.y {
|
||||
for x in p0.x .. p1.x {
|
||||
// TODO(gw): Convert to 2d array temporarily to avoid hash lookups per-tile?
|
||||
let key = TileOffset::new(x, y);
|
||||
let key = TileKey {
|
||||
slice: self.slice,
|
||||
offset: TileOffset::new(x, y),
|
||||
};
|
||||
let tile = self.tiles.get_mut(&key).expect("bug: no tile");
|
||||
|
||||
// Mark if the tile is cacheable at all.
|
||||
@ -1260,9 +1239,29 @@ impl TileCacheInstance {
|
||||
TILE_SIZE_WIDTH,
|
||||
TILE_SIZE_HEIGHT,
|
||||
);
|
||||
|
||||
let content_origin_f = tile.world_rect.origin * frame_context.global_device_pixel_scale;
|
||||
let content_origin_i = content_origin_f.floor();
|
||||
|
||||
// Calculate the UV coords for this tile. These are generally 0-1, but if the
|
||||
// local rect of the cache has fractional coordinates, then the content origin
|
||||
// of the tile is floor'ed, and so we need to adjust the UV rect in order to
|
||||
// ensure a correct 1:1 texel:pixel mapping and correct snapping.
|
||||
let s0 = (content_origin_f.x - content_origin_i.x) / tile.world_rect.size.width;
|
||||
let t0 = (content_origin_f.y - content_origin_i.y) / tile.world_rect.size.height;
|
||||
let s1 = 1.0;
|
||||
let t1 = 1.0;
|
||||
|
||||
let uv_rect_kind = UvRectKind::Quad {
|
||||
top_left: DeviceHomogeneousVector::new(s0, t0, 0.0, 1.0),
|
||||
top_right: DeviceHomogeneousVector::new(s1, t0, 0.0, 1.0),
|
||||
bottom_left: DeviceHomogeneousVector::new(s0, t1, 0.0, 1.0),
|
||||
bottom_right: DeviceHomogeneousVector::new(s1, t1, 0.0, 1.0),
|
||||
};
|
||||
resource_cache.texture_cache.update_picture_cache(
|
||||
tile_size,
|
||||
&mut tile.handle,
|
||||
uv_rect_kind,
|
||||
gpu_cache,
|
||||
);
|
||||
}
|
||||
@ -2049,15 +2048,7 @@ impl PicturePrimitive {
|
||||
retained_tiles: &mut RetainedTiles,
|
||||
) {
|
||||
if let Some(tile_cache) = self.tile_cache.take() {
|
||||
if !tile_cache.tiles.is_empty() {
|
||||
retained_tiles.caches.insert(
|
||||
tile_cache.slice,
|
||||
PictureCacheState {
|
||||
tiles: tile_cache.tiles,
|
||||
fract_offset: tile_cache.fract_offset,
|
||||
},
|
||||
);
|
||||
}
|
||||
retained_tiles.tiles.extend(tile_cache.tiles);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2443,10 +2434,10 @@ impl PicturePrimitive {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The content origin for surfaces is always an integer value (this preserves
|
||||
// the same snapping on a surface as would occur if drawn directly to parent).
|
||||
let content_origin_f = tile.world_rect.origin * device_pixel_scale;
|
||||
let content_origin = content_origin_f.round();
|
||||
debug_assert!((content_origin_f.x - content_origin.x).abs() < 0.01);
|
||||
debug_assert!((content_origin_f.y - content_origin.y).abs() < 0.01);
|
||||
let content_origin = content_origin_f.floor().to_i32();
|
||||
|
||||
let cache_item = frame_state.resource_cache.texture_cache.get(&tile.handle);
|
||||
|
||||
@ -2458,7 +2449,7 @@ impl PicturePrimitive {
|
||||
},
|
||||
tile_size,
|
||||
pic_index,
|
||||
content_origin.to_i32(),
|
||||
content_origin,
|
||||
UvRectKind::Rect,
|
||||
surface_spatial_node_index,
|
||||
device_pixel_scale,
|
||||
|
@ -1316,6 +1316,7 @@ impl TextureCache {
|
||||
&mut self,
|
||||
tile_size: DeviceIntSize,
|
||||
handle: &mut TextureCacheHandle,
|
||||
uv_rect_kind: UvRectKind,
|
||||
gpu_cache: &mut GpuCache,
|
||||
) {
|
||||
debug_assert!(self.now.is_valid());
|
||||
@ -1347,10 +1348,11 @@ impl TextureCache {
|
||||
}
|
||||
|
||||
// Upload the resource rect and texture array layer.
|
||||
self.entries
|
||||
let entry = self.entries
|
||||
.get_opt_mut(handle)
|
||||
.expect("BUG: handle must be valid now")
|
||||
.update_gpu_cache(gpu_cache);
|
||||
.expect("BUG: handle must be valid now");
|
||||
entry.uv_rect_kind = uv_rect_kind;
|
||||
entry.update_gpu_cache(gpu_cache);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,7 +480,7 @@ pref("media.cubeb.logging_level", "");
|
||||
// Cubeb sandbox (remoting) control
|
||||
#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
|
||||
pref("media.cubeb.sandbox", true);
|
||||
pref("media.audioipc.pool_size", 2);
|
||||
pref("media.audioipc.pool_size", 1);
|
||||
// 64 * 4 kB stack per pool thread.
|
||||
pref("media.audioipc.stack_size", 262144);
|
||||
#else
|
||||
|
@ -12,6 +12,7 @@ class Domain {
|
||||
this.name = this.constructor.name;
|
||||
|
||||
this.eventListeners_ = new Set();
|
||||
this._requestCounter = 0;
|
||||
}
|
||||
|
||||
destructor() {}
|
||||
@ -30,6 +31,29 @@ class Domain {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the provided method in the child domain that has the same domain
|
||||
* name. eg. calling this.executeInChild from domains/parent/Input.jsm will
|
||||
* attempt to execute the method in domains/content/Input.jsm.
|
||||
*
|
||||
* This can only be called from parent domains managed by a TabSession.
|
||||
*
|
||||
* @param {String} method
|
||||
* Name of the method to call on the child domain.
|
||||
* @param {Object} params
|
||||
* Optional parameters. Must be serializable.
|
||||
*/
|
||||
executeInChild(method, params) {
|
||||
if (!this.session.executeInChild) {
|
||||
throw new Error(
|
||||
"executeInChild can only be used in Domains managed by a TabSession"
|
||||
);
|
||||
}
|
||||
this._requestCounter++;
|
||||
const id = this.name + "-" + this._requestCounter;
|
||||
return this.session.executeInChild(id, this.name, method, params);
|
||||
}
|
||||
|
||||
addEventListener(listener) {
|
||||
if (typeof listener != "function" && !isEventHandler(listener)) {
|
||||
throw new TypeError();
|
||||
|
@ -10,4 +10,47 @@ const { ContentProcessDomain } = ChromeUtils.import(
|
||||
"chrome://remote/content/domains/ContentProcessDomain.jsm"
|
||||
);
|
||||
|
||||
class Input extends ContentProcessDomain {}
|
||||
class Input extends ContentProcessDomain {
|
||||
constructor(session) {
|
||||
super(session);
|
||||
|
||||
// Map of event name -> event handler promise.
|
||||
this._eventPromises = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not a CDP method.
|
||||
*
|
||||
* Add an event listener in the content page for the provided eventName.
|
||||
* To wait for the event, you should use `waitForContentEvent` with the same event name.
|
||||
*
|
||||
* Example usage from a parent process domain:
|
||||
*
|
||||
* await this.executeInChild("addContentEventListener", "click");
|
||||
* // do something that triggers a click in content
|
||||
* await this.executeInChild("waitForContentEvent", "click");
|
||||
*/
|
||||
addContentEventListener(eventName) {
|
||||
const eventPromise = new Promise(r => {
|
||||
this.chromeEventHandler.addEventListener(eventName, r, {
|
||||
mozSystemGroup: true,
|
||||
once: true,
|
||||
});
|
||||
});
|
||||
this._eventPromises.set(eventName, eventPromise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not a CDP method.
|
||||
*
|
||||
* Wait for an event listener added via `addContentEventListener` to be fired.
|
||||
*/
|
||||
async waitForContentEvent(eventName) {
|
||||
const eventPromise = this._eventPromises.get(eventName);
|
||||
if (!eventPromise) {
|
||||
throw new Error("No event promise available for " + eventName);
|
||||
}
|
||||
await eventPromise;
|
||||
this._eventPromises.delete(eventName);
|
||||
}
|
||||
}
|
||||
|
@ -50,12 +50,7 @@ class Input extends Domain {
|
||||
const browserWindow = browser.ownerGlobal;
|
||||
|
||||
const EventUtils = this._getEventUtils(browserWindow);
|
||||
const onEvent = new Promise(r => {
|
||||
browserWindow.addEventListener(domType, r, {
|
||||
mozSystemGroup: true,
|
||||
once: true,
|
||||
});
|
||||
});
|
||||
await this.executeInChild("addContentEventListener", domType);
|
||||
|
||||
if (type == "char") {
|
||||
// type == "char" is used when doing `await page.keyboard.type( 'I’m a list' );`
|
||||
@ -77,7 +72,8 @@ class Input extends Domain {
|
||||
browserWindow
|
||||
);
|
||||
}
|
||||
await onEvent;
|
||||
|
||||
await this.executeInChild("waitForContentEvent", domType);
|
||||
}
|
||||
|
||||
async dispatchMouseEvent({ type, button, x, y, modifiers, clickCount }) {
|
||||
|
@ -4,6 +4,7 @@ subsuite = remote
|
||||
prefs = remote.enabled=true
|
||||
support-files =
|
||||
chrome-remote-interface.js
|
||||
doc_input_dispatchKeyEvent_race.html
|
||||
doc_network_requestWillBeSent.html
|
||||
doc_page_frameNavigated.html
|
||||
doc_page_frameNavigated_iframe.html
|
||||
@ -12,6 +13,7 @@ support-files =
|
||||
|
||||
[browser_cdp.js]
|
||||
[browser_input_dispatchKeyEvent.js]
|
||||
[browser_input_dispatchKeyEvent_race.js]
|
||||
[browser_input_dispatchMouseEvent.js]
|
||||
[browser_main_target.js]
|
||||
[browser_network_requestWillBeSent.js]
|
||||
|
@ -59,13 +59,11 @@ add_task(async function() {
|
||||
await checkInputContent("hH", 2);
|
||||
|
||||
info("Send char type event for char [’]");
|
||||
await addInputEventListener("input");
|
||||
await Input.dispatchKeyEvent({
|
||||
type: "char",
|
||||
modifiers: 0,
|
||||
key: "’",
|
||||
});
|
||||
await waitForInputEvent();
|
||||
await checkInputContent("hH’", 3);
|
||||
|
||||
info("Send Left");
|
||||
@ -107,29 +105,14 @@ add_task(async function() {
|
||||
await RemoteAgent.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Send a text key and wait for the input event to be fired.
|
||||
*/
|
||||
async function sendTextKey(Input, key) {
|
||||
await addInputEventListener("input");
|
||||
|
||||
await dispatchKeyEvent(Input, key, "keyDown");
|
||||
await dispatchKeyEvent(Input, key, "keyUp");
|
||||
|
||||
await waitForInputEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an arrow key that will update the position of the cursor in the input and wait
|
||||
* for the selectionchange event to be fired.
|
||||
*/
|
||||
async function sendArrowKey(Input, arrowKey, modifiers = 0) {
|
||||
await addInputEventListener("selectionchange");
|
||||
|
||||
await dispatchKeyEvent(Input, arrowKey, "rawKeyDown", modifiers);
|
||||
await dispatchKeyEvent(Input, arrowKey, "keyUp", modifiers);
|
||||
|
||||
await waitForInputEvent();
|
||||
}
|
||||
|
||||
function dispatchKeyEvent(Input, key, type, modifiers = 0) {
|
||||
@ -166,50 +149,8 @@ async function checkInputContent(expectedValue, expectedCaret) {
|
||||
|
||||
async function sendBackspace(Input, expected) {
|
||||
info("Send Backspace");
|
||||
await addInputEventListener("input");
|
||||
|
||||
await dispatchKeyEvent(Input, "Backspace", "rawKeyDown");
|
||||
await dispatchKeyEvent(Input, "Backspace", "keyUp");
|
||||
|
||||
await waitForInputEvent();
|
||||
|
||||
await checkInputContent(expected, expected.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener on the content page input. This will resolve when the listener is
|
||||
* added.
|
||||
*
|
||||
* Since ContentTask.spawn is asynchronous make sure to await on this method before
|
||||
* executing the code that should fire the event. Otherwise the test will be exposed to
|
||||
* race conditions where the listener is added too late and is not triggered.
|
||||
*
|
||||
* Use `waitForInputEvent` afterwards to wait for the event to be fired.
|
||||
*
|
||||
* await addInputEventListener("click");
|
||||
* await someCodeThatWillTriggerClick();
|
||||
* await waitForInputEvent();
|
||||
*/
|
||||
function addInputEventListener(eventName) {
|
||||
return ContentTask.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
eventName,
|
||||
async _eventName => {
|
||||
const input = content.document.querySelector("input");
|
||||
this.__onInputEvent = new Promise(r =>
|
||||
input.addEventListener(_eventName, r, { once: true })
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* See documentation for addInputEventListener.
|
||||
*/
|
||||
function waitForInputEvent() {
|
||||
return ContentTask.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
null,
|
||||
() => this.__onInputEvent
|
||||
);
|
||||
}
|
||||
|
88
remote/test/browser/browser_input_dispatchKeyEvent_race.js
Normal file
88
remote/test/browser/browser_input_dispatchKeyEvent_race.js
Normal file
@ -0,0 +1,88 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Here we test that the `dispatchKeyEvent` API resolves after all the synchronous event
|
||||
// handlers from the content page have been flushed.
|
||||
//
|
||||
// Say the content page has an event handler such as:
|
||||
//
|
||||
// el.addEventListener("keyup", () => {
|
||||
// doSomeVeryLongProcessing(); // <- takes a long time but is synchronous!
|
||||
// window.myVariable = "newValue";
|
||||
// });
|
||||
//
|
||||
// And imagine this is tested via:
|
||||
//
|
||||
// await Input.dispatchKeyEvent(...);
|
||||
// const myVariable = await Runtime.evaluate({ expression: "window.myVariable" });
|
||||
// equals(myVariable, "newValue");
|
||||
//
|
||||
// In order for this to work, we need to be sure that `await Input.dispatchKeyEvent`
|
||||
// resolves only after the content page flushed the event handlers (and
|
||||
// `window.myVariable = "newValue"` was executed).
|
||||
//
|
||||
// This can be racy because Input.dispatchKeyEvent and window.myVariable = "newValue" run
|
||||
// in different processes.
|
||||
|
||||
const PAGE_URI =
|
||||
"http://example.com/browser/remote/test/browser/doc_input_dispatchKeyEvent_race.html";
|
||||
|
||||
add_task(async function() {
|
||||
const { client, tab } = await setupTestForUri(PAGE_URI);
|
||||
is(gBrowser.selectedTab, tab, "Selected tab is the target tab");
|
||||
|
||||
const { Input, Runtime } = client;
|
||||
|
||||
// Need an enabled Runtime domain to run evaluate.
|
||||
info("Enable the Runtime domain");
|
||||
await Runtime.enable();
|
||||
const { context } = await Runtime.executionContextCreated();
|
||||
|
||||
info("Focus the input on the page");
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
const input = content.document.querySelector("input");
|
||||
input.focus();
|
||||
is(input, content.document.activeElement, "Input should be focused");
|
||||
});
|
||||
|
||||
// See doc_input_dispatchKeyEvent_race.html
|
||||
// The page listens to `input` events to update a property on window.
|
||||
// We will check that the value is updated as soon dispatchKeyEvent has resolved.
|
||||
await checkWindowTestValue("initial-value", context.id, Runtime);
|
||||
|
||||
info("Write 'hhhhhh' ('h' times 6)");
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await dispatchKeyEvent(Input, "h", 72, "keyDown");
|
||||
await dispatchKeyEvent(Input, "h", 72, "keyUp");
|
||||
}
|
||||
await checkWindowTestValue("hhhhhh", context.id, Runtime);
|
||||
|
||||
await client.close();
|
||||
ok(true, "The client is closed");
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
|
||||
await RemoteAgent.close();
|
||||
});
|
||||
|
||||
function dispatchKeyEvent(Input, key, keyCode, type, modifiers = 0) {
|
||||
info(`Send ${type} for key ${key}`);
|
||||
return Input.dispatchKeyEvent({
|
||||
type,
|
||||
modifiers,
|
||||
windowsVirtualKeyCode: keyCode,
|
||||
key,
|
||||
});
|
||||
}
|
||||
|
||||
async function checkWindowTestValue(expected, contextId, Runtime) {
|
||||
info("Retrieve the value of `window.testValue` in the test page");
|
||||
const { result } = await Runtime.evaluate({
|
||||
contextId,
|
||||
expression: "window.testValue",
|
||||
});
|
||||
|
||||
is(result.value, expected, "Content window test value is correct");
|
||||
}
|
27
remote/test/browser/doc_input_dispatchKeyEvent_race.html
Normal file
27
remote/test/browser/doc_input_dispatchKeyEvent_race.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test page for dispatchKeyEvent race test</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text">
|
||||
<script type="text/javascript">
|
||||
window.testValue = "initial-value";
|
||||
|
||||
// Small helper to synchronously pause for a given delay, in ms.
|
||||
function sleep(delay) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < delay) {
|
||||
// Wait for the condition to fail.
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector("input").addEventListener("input", () => {
|
||||
// Block the execution synchronously for one second.
|
||||
sleep(1000);
|
||||
// Update the value that will be checked by the test.
|
||||
window.testValue = document.querySelector("input").value;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -257,6 +257,16 @@
|
||||
Type.fsblkcnt_t = Type.uintn_t(Const.OSFILE_SIZEOF_FSBLKCNT_T).withName(
|
||||
"fsblkcnt_t"
|
||||
);
|
||||
// There is no guarantee of the size or order of members in sys-header structs
|
||||
// It mostly is "unsigned long", but can be "unsigned int" as well.
|
||||
// So it has its own "type".
|
||||
// NOTE: This is still only partially correct, as signedness is also not guaranteed,
|
||||
// so assuming an unsigned int might still be wrong here.
|
||||
// But unsigned seems to have worked all those years, even though its signed
|
||||
// on various platforms.
|
||||
Type.statvfs_f_frsize = Type.uintn_t(
|
||||
Const.OSFILE_SIZEOF_STATVFS_F_FRSIZE
|
||||
).withName("statvfs_f_rsize");
|
||||
|
||||
// Structure |statvfs|
|
||||
// Use an hollow structure
|
||||
@ -269,7 +279,7 @@
|
||||
statvfs.add_field_at(
|
||||
Const.OSFILE_OFFSETOF_STATVFS_F_FRSIZE,
|
||||
"f_frsize",
|
||||
Type.unsigned_long.implementation
|
||||
Type.statvfs_f_frsize.implementation
|
||||
);
|
||||
statvfs.add_field_at(
|
||||
Const.OSFILE_OFFSETOF_STATVFS_F_BAVAIL,
|
||||
|
@ -2309,24 +2309,6 @@ devtools.changesview:
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
copy:
|
||||
bug_numbers:
|
||||
- 1522843
|
||||
description: >
|
||||
Number of times any text content from the Changes panel is copied to the clipboard.
|
||||
expires: "69"
|
||||
kind: uint
|
||||
notification_emails:
|
||||
- dev-developer-tools@lists.mozilla.org
|
||||
- mbalfanz@mozilla.com
|
||||
release_channel_collection: opt-out
|
||||
products:
|
||||
- 'firefox'
|
||||
- 'fennec'
|
||||
- 'geckoview'
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
copy_all_changes:
|
||||
bug_numbers:
|
||||
- 1532583
|
||||
@ -2363,42 +2345,6 @@ devtools.changesview:
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
contextmenu:
|
||||
bug_numbers:
|
||||
- 1522843
|
||||
description: >
|
||||
Number of times the Changes panel context menu is shown.
|
||||
expires: "69"
|
||||
kind: uint
|
||||
notification_emails:
|
||||
- dev-developer-tools@lists.mozilla.org
|
||||
- mbalfanz@mozilla.com
|
||||
release_channel_collection: opt-out
|
||||
products:
|
||||
- 'firefox'
|
||||
- 'fennec'
|
||||
- 'geckoview'
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
contextmenu_copy:
|
||||
bug_numbers:
|
||||
- 1522843
|
||||
description: >
|
||||
Number of times the Copy option is picked from the Changes panel context menu.
|
||||
expires: "69"
|
||||
kind: uint
|
||||
notification_emails:
|
||||
- dev-developer-tools@lists.mozilla.org
|
||||
- mbalfanz@mozilla.com
|
||||
release_channel_collection: opt-out
|
||||
products:
|
||||
- 'firefox'
|
||||
- 'fennec'
|
||||
- 'geckoview'
|
||||
record_in_processes:
|
||||
- 'main'
|
||||
|
||||
contextmenu_copy_declaration:
|
||||
bug_numbers:
|
||||
- 1532583
|
||||
|
Loading…
Reference in New Issue
Block a user