Files
tubestation/devtools/client/webconsole/utils/context-menu.js

313 lines
9.4 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item");
const { MESSAGE_SOURCE } = require("devtools/client/webconsole/constants");
const clipboardHelper = require("devtools/shared/platform/clipboard");
const { l10n } = require("devtools/client/webconsole/utils/messages");
loader.lazyRequireGetter(this, "saveAs", "devtools/shared/DevToolsUtils", true);
loader.lazyRequireGetter(
this,
"openContentLink",
"devtools/client/shared/link",
true
);
loader.lazyRequireGetter(
this,
"getElementText",
"devtools/client/webconsole/utils/clipboard",
true
);
/**
* Create a Menu instance for the webconsole.
*
* @param {WebConsoleUI} webConsoleUI
* The webConsoleUI instance.
* @param {Element} parentNode
* The container of the new console frontend output wrapper.
* @param {Object} options
* - {String} actor (optional) actor id to use for context menu actions
* - {String} clipboardText (optional) text to "Copy" if no selection is available
* - {String} variableText (optional) which is the textual frontend
* representation of the variable
* - {Object} message (optional) message object containing metadata such as:
* - {String} source
* - {String} request
* - {Function} openSidebar (optional) function that will open the object
* inspector sidebar
* - {String} rootActorId (optional) actor id for the root object being clicked on
* - {Object} executionPoint (optional) when replaying, the execution point where
* this message was logged
*/
function createContextMenu(
webConsoleUI,
parentNode,
{
actor,
clipboardText,
variableText,
message,
serviceContainer,
openSidebar,
rootActorId,
executionPoint,
toolbox,
url,
}
) {
const win = parentNode.ownerDocument.defaultView;
const selection = win.getSelection();
const { source, request } = message || {};
const menu = new Menu({
id: "webconsole-menu",
});
// Copy URL for a network request.
menu.append(
new MenuItem({
id: "console-menu-copy-url",
label: l10n.getStr("webconsole.menu.copyURL.label"),
accesskey: l10n.getStr("webconsole.menu.copyURL.accesskey"),
visible: source === MESSAGE_SOURCE.NETWORK,
click: () => {
if (!request) {
return;
}
clipboardHelper.copyString(request.url);
},
})
);
// Open Network message in the Network panel.
if (serviceContainer.openNetworkPanel && request) {
menu.append(
new MenuItem({
id: "console-menu-open-in-network-panel",
label: l10n.getStr("webconsole.menu.openInNetworkPanel.label"),
accesskey: l10n.getStr("webconsole.menu.openInNetworkPanel.accesskey"),
visible: source === MESSAGE_SOURCE.NETWORK,
click: () => serviceContainer.openNetworkPanel(message.messageId),
})
);
}
// Resend Network message.
if (serviceContainer.resendNetworkRequest && request) {
menu.append(
new MenuItem({
id: "console-menu-resend-network-request",
label: l10n.getStr("webconsole.menu.resendNetworkRequest.label"),
accesskey: l10n.getStr(
"webconsole.menu.resendNetworkRequest.accesskey"
),
visible: source === MESSAGE_SOURCE.NETWORK,
click: () => serviceContainer.resendNetworkRequest(message.messageId),
})
);
}
// Open URL in a new tab for a network request.
menu.append(
new MenuItem({
id: "console-menu-open-url",
label: l10n.getStr("webconsole.menu.openURL.label"),
accesskey: l10n.getStr("webconsole.menu.openURL.accesskey"),
visible: source === MESSAGE_SOURCE.NETWORK,
click: () => {
if (!request) {
return;
}
openContentLink(request.url);
},
})
);
// Store as global variable.
menu.append(
new MenuItem({
id: "console-menu-store",
label: l10n.getStr("webconsole.menu.storeAsGlobalVar.label"),
accesskey: l10n.getStr("webconsole.menu.storeAsGlobalVar.accesskey"),
disabled: !actor,
click: () => {
const evalString = `{ let i = 0;
while (this.hasOwnProperty("temp" + i) && i < 1000) {
i++;
}
this["temp" + i] = _self;
"temp" + i;
}`;
const options = {
selectedObjectActor: actor,
};
webConsoleUI.evaluateJSAsync(evalString, options).then(res => {
webConsoleUI.jsterm.focus();
webConsoleUI.hud.setInputValue(res.result);
});
},
})
);
// Copy message or grip.
menu.append(
new MenuItem({
id: "console-menu-copy",
label: l10n.getStr("webconsole.menu.copyMessage.label"),
accesskey: l10n.getStr("webconsole.menu.copyMessage.accesskey"),
// Disabled if there is no selection and no message element available to copy.
disabled: selection.isCollapsed && !clipboardText,
click: () => {
if (selection.isCollapsed) {
// If the selection is empty/collapsed, copy the text content of the
// message for which the context menu was opened.
clipboardHelper.copyString(clipboardText);
} else {
clipboardHelper.copyString(selection.toString());
}
},
})
);
// Copy message object.
menu.append(
new MenuItem({
id: "console-menu-copy-object",
label: l10n.getStr("webconsole.menu.copyObject.label"),
accesskey: l10n.getStr("webconsole.menu.copyObject.accesskey"),
// Disabled if there is no actor and no variable text associated.
disabled: !actor && !variableText,
click: () => {
if (actor) {
// The Debugger.Object of the OA will be bound to |_self| during evaluation.
// See server/actors/webconsole/eval-with-debugger.js `evalWithDebugger`.
webConsoleUI
.evaluateJSAsync("copy(_self)", {
selectedObjectActor: actor,
})
.then(res => {
clipboardHelper.copyString(res.helperResult.value);
});
} else {
clipboardHelper.copyString(variableText);
}
},
})
);
// Select all.
menu.append(
new MenuItem({
id: "console-menu-select",
label: l10n.getStr("webconsole.menu.selectAll.label"),
accesskey: l10n.getStr("webconsole.menu.selectAll.accesskey"),
disabled: false,
click: () => {
const webconsoleOutput = parentNode.querySelector(".webconsole-output");
selection.selectAllChildren(webconsoleOutput);
},
})
);
const exportSubmenu = new Menu({
id: "export-submenu",
});
// Export to clipboard
exportSubmenu.append(
new MenuItem({
id: "console-menu-export-clipboard",
label: l10n.getStr("webconsole.menu.exportSubmenu.exportCliboard.label"),
disabled: false,
click: () => {
const webconsoleOutput = parentNode.querySelector(".webconsole-output");
clipboardHelper.copyString(getElementText(webconsoleOutput));
},
})
);
// Export to file
exportSubmenu.append(
new MenuItem({
id: "console-menu-export-file",
label: l10n.getStr("webconsole.menu.exportSubmenu.exportFile.label"),
disabled: false,
// Note: not async, but returns a promise for the actual save.
click: () => {
const date = new Date();
const suggestedName =
`console-export-${date.getFullYear()}-` +
`${date.getMonth() + 1}-${date.getDate()}_${date.getHours()}-` +
`${date.getMinutes()}-${date.getSeconds()}.txt`;
const webconsoleOutput = parentNode.querySelector(".webconsole-output");
const data = new TextEncoder().encode(getElementText(webconsoleOutput));
return saveAs(window, data, suggestedName);
},
})
);
menu.append(
new MenuItem({
id: "console-menu-export",
label: l10n.getStr("webconsole.menu.exportSubmenu.label"),
disabled: false,
submenu: exportSubmenu,
})
);
// Open object in sidebar.
if (openSidebar) {
menu.append(
new MenuItem({
id: "console-menu-open-sidebar",
label: l10n.getStr("webconsole.menu.openInSidebar.label"),
accesskey: l10n.getStr("webconsole.menu.openInSidebar.accesskey"),
disabled: !rootActorId,
click: () => openSidebar(message.messageId),
})
);
}
// Add time warp option if available.
if (executionPoint) {
menu.append(
new MenuItem({
id: "console-menu-time-warp",
label: l10n.getStr("webconsole.menu.timeWarp.label"),
disabled: false,
click: () => {
const threadFront = toolbox.threadFront;
threadFront.timeWarp(executionPoint);
},
})
);
}
if (url) {
menu.append(
new MenuItem({
id: "console-menu-copy-url",
label: l10n.getStr("webconsole.menu.copyURL.label"),
accesskey: l10n.getStr("webconsole.menu.copyURL.accesskey"),
click: () => clipboardHelper.copyString(url),
})
);
}
return menu;
}
exports.createContextMenu = createContextMenu;