Bug 1442249 - Add Copy context menu to PropertiesView. r=Honza

Differential Revision: https://phabricator.services.mozilla.com/D23770

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Laphets 2019-04-04 16:57:22 +00:00
parent 69592562e9
commit 62b3b67137
7 changed files with 226 additions and 1 deletions

View File

@ -917,7 +917,7 @@ netmonitor.trackingResource.tooltip=This URL matches a known tracker and it woul
netmonitor.context.copy=Copy
# LOCALIZATION NOTE (netmonitor.context.copy.accesskey): This is the access key
# for the copy sub-menu displayed in the context menu for a request
# for the copy menu/sub-menu displayed in the context menu for a request
netmonitor.context.copy.accesskey=C
# LOCALIZATION NOTE (netmonitor.context.copyUrl): This is the label displayed
@ -994,6 +994,14 @@ netmonitor.context.saveImageAs=Save Image As
# for the Copy Image As Data URI menu item displayed in the context menu for a request
netmonitor.context.saveImageAs.accesskey=v
# LOCALIZATION NOTE (netmonitor.context.copyAll): This is the label displayed
# on the context menu that copies all data
netmonitor.context.copyAll=Copy All
# LOCALIZATION NOTE (netmonitor.context.copyAll.accesskey): This is the access key
# for the Copy All menu item displayed in the context menu for a properties view panel
netmonitor.context.copyAll.accesskey=A
# LOCALIZATION NOTE (netmonitor.context.copyAllAsHar): This is the label displayed
# on the context menu that copies all as HAR format
netmonitor.context.copyAllAsHar=Copy All As HAR

View File

@ -15,6 +15,7 @@ const { FILTER_SEARCH_DELAY } = require("../constants");
// Components
const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
const PropertiesViewContextMenu = require("../widgets/PropertiesViewContextMenu");
const TreeView = createFactory(TreeViewClass);
loader.lazyGetter(this, "SearchBox", function() {
@ -155,6 +156,23 @@ class PropertiesView extends Component {
return TreeRow(props);
}
onContextMenuRow(member, evt) {
evt.preventDefault();
const { object } = member;
// Select the right clicked row
this.selectRow(evt.currentTarget);
// if data exists and can be copied, then show the contextmenu
if (typeof (object) === "object") {
if (!this.contextMenu) {
this.contextMenu = new PropertiesViewContextMenu({});
}
this.contextMenu.open(evt, { member, object: this.props.object });
}
}
renderValueWithRep(props) {
const { member } = props;
@ -240,6 +258,7 @@ class PropertiesView extends Component {
renderRow: renderRow || this.renderRowWithExtras,
renderValue: renderValue || this.renderValueWithRep,
openLink,
onContextMenuRow: this.onContextMenuRow,
}),
),
)

View File

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { L10N } = require("../utils/l10n");
loader.lazyRequireGetter(this, "copyString", "devtools/shared/platform/clipboard", true);
loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
class PropertiesViewContextMenu {
constructor(props) {
this.props = props;
}
/**
* Handle the context menu opening.
* @param {*} event open event
* @param {*} member member of the right-clicked row
* @param {*} object the whole data object
*/
open(event = {}, { member, object }) {
const menu = [];
menu.push({
id: "properties-view-context-menu-copy",
label: L10N.getStr("netmonitor.context.copy"),
accesskey: L10N.getStr("netmonitor.context.copy.accesskey"),
click: () => this.copySelected(member),
});
menu.push({
id: "properties-view-context-menu-copyall",
label: L10N.getStr("netmonitor.context.copyAll"),
accesskey: L10N.getStr("netmonitor.context.copyAll.accesskey"),
click: () => this.copyAll(object),
});
showMenu(menu, {
screenX: event.screenX,
screenY: event.screenY,
});
}
copyAll(data) {
try {
copyString(JSON.stringify(data));
} catch (error) {}
}
copySelected({ object, hasChildren }) {
if (hasChildren) {
// If has children, copy the data as JSON
try {
copyString(JSON.stringify({ [object.name]: object.value }));
} catch (error) {}
} else {
// Copy the data as key-value format
copyString(`${object.name}: ${object.value}`);
}
}
}
module.exports = PropertiesViewContextMenu;

View File

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'PropertiesViewContextMenu.js',
'RequestListContextMenu.js',
'RequestListHeaderContextMenu.js',
'WaterfallBackground.js',

View File

@ -103,6 +103,8 @@ subsuite = clipboard
subsuite = clipboard
[browser_net_copy_url.js]
subsuite = clipboard
[browser_net_propertiesview-copy.js]
subsuite = clipboard
[browser_net_copy_params.js]
subsuite = clipboard
skip-if = (verify && !debug && (os == 'mac')) # bug 1328915, disable linux32 debug devtools for timeouts

View File

@ -0,0 +1,120 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Test if response JSON in PropertiesView can be copied
*/
add_task(async function() {
const { tab, monitor } = await initNetMonitor(JSON_BASIC_URL + "?name=nogrip");
info("Starting test... ");
const { document, store, windowRequire } = monitor.panelWin;
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
store.dispatch(Actions.batchEnable(false));
await performRequests(monitor, tab, 1);
const onResponsePanelReady = waitForDOM(document, "#response-panel .treeTable");
store.dispatch(Actions.toggleNetworkDetails());
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector("#response-tab"));
await onResponsePanelReady;
const responsePanel = document.querySelector("#response-panel");
const objectRow = responsePanel.querySelectorAll(".objectRow")[1];
const stringRow = responsePanel.querySelectorAll(".stringRow")[0];
/* Test for copy an object */
EventUtils.sendMouseEvent({ type: "contextmenu" },
objectRow);
await waitForClipboardPromise(function setup() {
monitor.panelWin.parent.document
.querySelector("#properties-view-context-menu-copy").click();
}, `{"obj":{"type":"string"}}`);
/* Test for copy all */
EventUtils.sendMouseEvent({ type: "contextmenu" },
objectRow);
await waitForClipboardPromise(function setup() {
monitor.panelWin.parent.document
.querySelector("#properties-view-context-menu-copyall").click();
}, `{"JSON":{"obj":{"type":"string"}},` +
`"Response payload":{"EDITOR_CONFIG":{"text":` +
`"{\\"obj\\": {\\"type\\": \\"string\\" }}","mode":"application/json"}}}`);
/* Test for copy a single row */
EventUtils.sendMouseEvent({ type: "contextmenu" },
stringRow);
await waitForClipboardPromise(function setup() {
monitor.panelWin.parent.document
.querySelector("#properties-view-context-menu-copy").click();
}, "type: string");
await teardown(monitor);
});
/**
* Test if response/request Cookies in PropertiesView can be copied
*/
add_task(async function() {
const { tab, monitor } = await initNetMonitor(SIMPLE_UNSORTED_COOKIES_SJS);
info("Starting test... ");
const { document, store, windowRequire } = monitor.panelWin;
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
store.dispatch(Actions.batchEnable(false));
tab.linkedBrowser.reload();
let wait = waitForNetworkEvents(monitor, 1);
await wait;
wait = waitForDOM(document, ".headers-overview");
EventUtils.sendMouseEvent({ type: "mousedown" },
document.querySelectorAll(".request-list-item")[0]);
await wait;
EventUtils.sendMouseEvent({ type: "mousedown" },
document.querySelectorAll(".request-list-item")[0]);
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector("#cookies-tab"));
const cookiesPanel = document.querySelector("#cookies-panel");
const objectRows = cookiesPanel.querySelectorAll(".objectRow:not(.tree-section)");
const stringRows = cookiesPanel.querySelectorAll(".stringRow");
const expectedResponseCookies = [
{ bob: { httpOnly: true, value: "true" } },
{ foo: { httpOnly: true, value: "bar" } },
{ tom: { httpOnly: true, value: "cool" } }];
for (let i = 0; i < objectRows.length; i++) {
const cur = objectRows[i];
EventUtils.sendMouseEvent({ type: "contextmenu" },
cur);
await waitForClipboardPromise(function setup() {
monitor.panelWin.parent.document
.querySelector("#properties-view-context-menu-copy").click();
}, JSON.stringify(expectedResponseCookies[i]));
}
const expectedRequestCookies = ["bob: true", "foo: bar", "tom: cool"];
for (let i = 0; i < expectedRequestCookies.length; i++) {
const cur = stringRows[objectRows.length + i];
EventUtils.sendMouseEvent({ type: "contextmenu" },
cur);
await waitForClipboardPromise(function setup() {
monitor.panelWin.parent.document
.querySelector("#properties-view-context-menu-copy").click();
}, expectedRequestCookies[i]);
}
await teardown(monitor);
});

View File

@ -127,6 +127,8 @@ define(function(require, exports, module) {
onSort: PropTypes.func,
// Custom row click callback
onClickRow: PropTypes.func,
// Row context menu event handler
onContextMenuRow: PropTypes.func,
// Tree context menu event handler
onContextMenuTree: PropTypes.func,
// A header is displayed if set to true
@ -368,6 +370,13 @@ define(function(require, exports, module) {
this.selectRow(event.currentTarget);
}
onContextMenu(member, event) {
const onContextMenuRow = this.props.onContextMenuRow;
if (onContextMenuRow) {
onContextMenuRow.call(this, member, event);
}
}
getSelectedRow() {
if (!this.state.selected || this.rows.length === 0) {
return null;
@ -539,6 +548,7 @@ define(function(require, exports, module) {
id: member.path,
ref: row => row && this.rows.push(row),
onClick: this.onClickRow.bind(this, member.path),
onContextMenu: this.onContextMenu.bind(this, member),
});
// Render single row.