blob: 65ff11af8968c631200a5231f48b0b850e2cdf5e [file] [log] [blame]
Erik Luo6294d3b2018-07-12 21:54:161// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Tim van der Lippe9b2f8712020-02-12 17:46:225import * as Common from '../common/common.js';
6import * as ObjectUI from '../object_ui/object_ui.js';
7import * as SDK from '../sdk/sdk.js';
8import * as TextUtils from '../text_utils/text_utils.js';
9import * as UI from '../ui/ui.js';
10
Paul Lewisbf7aa3c2019-11-20 17:03:3811const _PinSymbol = Symbol('pinSymbol');
12
Tim van der Lippe9b2f8712020-02-12 17:46:2213export class ConsolePinPane extends UI.ThrottledWidget.ThrottledWidget {
Michael Liao5be62342019-10-22 23:24:2914 /**
Tim van der Lippe9b2f8712020-02-12 17:46:2215 * @param {!UI.Toolbar.ToolbarButton} liveExpressionButton
Michael Liao5be62342019-10-22 23:24:2916 */
17 constructor(liveExpressionButton) {
Erik Luod8eee2b2018-07-24 01:36:1818 super(true, 250);
Michael Liao5be62342019-10-22 23:24:2919 this._liveExpressionButton = liveExpressionButton;
Erik Luo6294d3b2018-07-12 21:54:1620 this.registerRequiredCSS('console/consolePinPane.css');
Erik Luod8eee2b2018-07-24 01:36:1821 this.registerRequiredCSS('object_ui/objectValue.css');
Erik Luo6294d3b2018-07-12 21:54:1622 this.contentElement.classList.add('console-pins', 'monospace');
23 this.contentElement.addEventListener('contextmenu', this._contextMenuEventFired.bind(this), false);
24
Tim van der Lippeeaacb722020-01-10 12:16:0025 /** @type {!Set<!ConsolePin>} */
Erik Luo6294d3b2018-07-12 21:54:1626 this._pins = new Set();
Paul Lewis2d7d65c2020-03-16 17:26:3027 this._pinsSetting = Common.Settings.Settings.instance().createLocalSetting('consolePins', []);
Tim van der Lippe1d6e57a2019-09-30 11:55:3428 for (const expression of this._pinsSetting.get()) {
Erik Luo66bcf102018-08-25 02:15:3729 this.addPin(expression);
Tim van der Lippe1d6e57a2019-09-30 11:55:3430 }
Erik Luo66bcf102018-08-25 02:15:3731 }
32
Erik Luoc75946e2018-10-08 21:44:2533 /**
34 * @override
35 */
36 willHide() {
Tim van der Lippe1d6e57a2019-09-30 11:55:3437 for (const pin of this._pins) {
Erik Luoc75946e2018-10-08 21:44:2538 pin.setHovered(false);
Tim van der Lippe1d6e57a2019-09-30 11:55:3439 }
Erik Luoc75946e2018-10-08 21:44:2540 }
41
Erik Luo66bcf102018-08-25 02:15:3742 _savePins() {
43 const toSave = Array.from(this._pins).map(pin => pin.expression());
44 this._pinsSetting.set(toSave);
Erik Luo6294d3b2018-07-12 21:54:1645 }
46
47 /**
48 * @param {!Event} event
49 */
50 _contextMenuEventFired(event) {
Tim van der Lippe9b2f8712020-02-12 17:46:2251 const contextMenu = new UI.ContextMenu.ContextMenu(event);
Erik Luo6294d3b2018-07-12 21:54:1652 const target = event.deepElementFromPoint();
53 if (target) {
54 const targetPinElement = target.enclosingNodeOrSelfWithClass('console-pin');
55 if (targetPinElement) {
Paul Lewisbf7aa3c2019-11-20 17:03:3856 const targetPin = targetPinElement[_PinSymbol];
Erik Luo212d2812018-08-30 01:26:3957 contextMenu.editSection().appendItem(ls`Edit expression`, targetPin.focus.bind(targetPin));
58 contextMenu.editSection().appendItem(ls`Remove expression`, this._removePin.bind(this, targetPin));
Erik Luod8eee2b2018-07-24 01:36:1859 targetPin.appendToContextMenu(contextMenu);
Erik Luo6294d3b2018-07-12 21:54:1660 }
61 }
Erik Luo212d2812018-08-30 01:26:3962 contextMenu.editSection().appendItem(ls`Remove all expressions`, this._removeAllPins.bind(this));
Erik Luo6294d3b2018-07-12 21:54:1663 contextMenu.show();
64 }
65
66 _removeAllPins() {
Tim van der Lippe1d6e57a2019-09-30 11:55:3467 for (const pin of this._pins) {
Erik Luo6294d3b2018-07-12 21:54:1668 this._removePin(pin);
Tim van der Lippe1d6e57a2019-09-30 11:55:3469 }
Erik Luo6294d3b2018-07-12 21:54:1670 }
71
72 /**
Tim van der Lippeeaacb722020-01-10 12:16:0073 * @param {!ConsolePin} pin
Erik Luo6294d3b2018-07-12 21:54:1674 */
75 _removePin(pin) {
76 pin.element().remove();
77 this._pins.delete(pin);
Erik Luo66bcf102018-08-25 02:15:3778 this._savePins();
Michael Liao5be62342019-10-22 23:24:2979 this._liveExpressionButton.element.focus();
Erik Luo6294d3b2018-07-12 21:54:1680 }
81
82 /**
83 * @param {string} expression
Erik Luo66bcf102018-08-25 02:15:3784 * @param {boolean=} userGesture
Erik Luo6294d3b2018-07-12 21:54:1685 */
Erik Luo66bcf102018-08-25 02:15:3786 addPin(expression, userGesture) {
Paul Lewisbf7aa3c2019-11-20 17:03:3887 const pin = new ConsolePin(expression, this);
Erik Luo6294d3b2018-07-12 21:54:1688 this.contentElement.appendChild(pin.element());
89 this._pins.add(pin);
Erik Luo66bcf102018-08-25 02:15:3790 this._savePins();
Tim van der Lippe1d6e57a2019-09-30 11:55:3491 if (userGesture) {
Erik Luo66bcf102018-08-25 02:15:3792 pin.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:3493 }
Erik Luod8eee2b2018-07-24 01:36:1894 this.update();
95 }
96
97 /**
98 * @override
99 */
100 doUpdate() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34101 if (!this._pins.size || !this.isShowing()) {
Erik Luod8eee2b2018-07-24 01:36:18102 return Promise.resolve();
Tim van der Lippe1d6e57a2019-09-30 11:55:34103 }
104 if (this.isShowing()) {
Erik Luod8eee2b2018-07-24 01:36:18105 this.update();
Tim van der Lippe1d6e57a2019-09-30 11:55:34106 }
Erik Luod8eee2b2018-07-24 01:36:18107 const updatePromises = Array.from(this._pins, pin => pin.updatePreview());
108 return Promise.all(updatePromises).then(this._updatedForTest.bind(this));
109 }
110
111 _updatedForTest() {
Erik Luo6294d3b2018-07-12 21:54:16112 }
Paul Lewisbf7aa3c2019-11-20 17:03:38113}
Erik Luo6294d3b2018-07-12 21:54:16114
Paul Lewisbf7aa3c2019-11-20 17:03:38115/**
116 * @unrestricted
117 */
Tim van der Lippe9b2f8712020-02-12 17:46:22118export class ConsolePin extends Common.ObjectWrapper.ObjectWrapper {
Erik Luo6294d3b2018-07-12 21:54:16119 /**
120 * @param {string} expression
Tim van der Lippeeaacb722020-01-10 12:16:00121 * @param {!ConsolePinPane} pinPane
Erik Luo6294d3b2018-07-12 21:54:16122 */
Erik Luo66bcf102018-08-25 02:15:37123 constructor(expression, pinPane) {
124 super();
Tim van der Lippe9b2f8712020-02-12 17:46:22125 const deletePinIcon = UI.Icon.Icon.create('smallicon-cross', 'console-delete-pin');
Michael Liao5be62342019-10-22 23:24:29126 self.onInvokeElement(deletePinIcon, event => {
127 pinPane._removePin(this);
128 event.consume(true);
129 });
John Emau53d64092019-05-31 22:41:56130 deletePinIcon.tabIndex = 0;
131 UI.ARIAUtils.setAccessibleName(deletePinIcon, ls`Remove expression`);
132 UI.ARIAUtils.markAsButton(deletePinIcon);
Erik Luo6294d3b2018-07-12 21:54:16133
Tim van der Lippe9b2f8712020-02-12 17:46:22134 const fragment = UI.Fragment.Fragment.build`
Erik Luo6294d3b2018-07-12 21:54:16135 <div class='console-pin'>
136 ${deletePinIcon}
137 <div class='console-pin-name' $='name'></div>
Erik Luod8eee2b2018-07-24 01:36:18138 <div class='console-pin-preview' $='preview'>${ls`not available`}</div>
Erik Luo6294d3b2018-07-12 21:54:16139 </div>`;
140 this._pinElement = fragment.element();
Erik Luod8eee2b2018-07-24 01:36:18141 this._pinPreview = fragment.$('preview');
Erik Luo6294d3b2018-07-12 21:54:16142 const nameElement = fragment.$('name');
143 nameElement.title = expression;
Paul Lewisbf7aa3c2019-11-20 17:03:38144 this._pinElement[_PinSymbol] = this;
Erik Luo6294d3b2018-07-12 21:54:16145
Erik Luo9f24d202018-09-10 08:51:51146 /** @type {?SDK.RuntimeModel.EvaluationResult} */
147 this._lastResult = null;
Tim van der Lippe9b2f8712020-02-12 17:46:22148 /** @type {?SDK.RuntimeModel.ExecutionContext} */
Erik Luo9f24d202018-09-10 08:51:51149 this._lastExecutionContext = null;
Tim van der Lippe9b2f8712020-02-12 17:46:22150 /** @type {?UI.TextEditor.TextEditor} */
Erik Luo6294d3b2018-07-12 21:54:16151 this._editor = null;
Erik Luo66bcf102018-08-25 02:15:37152 this._committedExpression = expression;
Erik Luoc75946e2018-10-08 21:44:25153 this._hovered = false;
Tim van der Lippe9b2f8712020-02-12 17:46:22154 /** @type {?SDK.RemoteObject.RemoteObject} */
Erik Luoc75946e2018-10-08 21:44:25155 this._lastNode = null;
156
157 this._pinPreview.addEventListener('mouseenter', this.setHovered.bind(this, true), false);
158 this._pinPreview.addEventListener('mouseleave', this.setHovered.bind(this, false), false);
159 this._pinPreview.addEventListener('click', event => {
160 if (this._lastNode) {
161 Common.Revealer.reveal(this._lastNode);
162 event.consume();
163 }
164 }, false);
Erik Luo6294d3b2018-07-12 21:54:16165
Tim van der Lippe9b2f8712020-02-12 17:46:22166 this._editorPromise = self.runtime.extension(UI.TextEditor.TextEditorFactory).instance().then(factory => {
Erik Luo6294d3b2018-07-12 21:54:16167 this._editor = factory.createEditor({
John Emaud3bef012019-06-05 18:08:40168 devtoolsAccessibleName: ls`Live expression editor`,
Erik Luo6294d3b2018-07-12 21:54:16169 lineNumbers: false,
170 lineWrapping: true,
171 mimeType: 'javascript',
172 autoHeight: true,
173 placeholder: ls`Expression`
174 });
Tim van der Lippe9b2f8712020-02-12 17:46:22175 this._editor.configureAutocomplete(
176 ObjectUI.JavaScriptAutocomplete.JavaScriptAutocompleteConfig.createConfigForEditor(this._editor));
Erik Luo6294d3b2018-07-12 21:54:16177 this._editor.widget().show(nameElement);
178 this._editor.widget().element.classList.add('console-pin-editor');
179 this._editor.widget().element.tabIndex = -1;
180 this._editor.setText(expression);
181 this._editor.widget().element.addEventListener('keydown', event => {
Erik Luo34a22cf2018-09-20 02:29:30182 if (event.key === 'Tab' && !this._editor.text()) {
Erik Luo6294d3b2018-07-12 21:54:16183 event.consume();
Erik Luo34a22cf2018-09-20 02:29:30184 return;
185 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34186 if (event.keyCode === UI.KeyboardShortcut.Keys.Esc.code) {
Erik Luo34a22cf2018-09-20 02:29:30187 this._editor.setText(this._committedExpression);
Tim van der Lippe1d6e57a2019-09-30 11:55:34188 }
Erik Luo6294d3b2018-07-12 21:54:16189 }, true);
Erik Luo66bcf102018-08-25 02:15:37190 this._editor.widget().element.addEventListener('focusout', event => {
191 const text = this._editor.text();
192 const trimmedText = text.trim();
Tim van der Lippe1d6e57a2019-09-30 11:55:34193 if (text.length !== trimmedText.length) {
Erik Luo66bcf102018-08-25 02:15:37194 this._editor.setText(trimmedText);
Tim van der Lippe1d6e57a2019-09-30 11:55:34195 }
Erik Luo66bcf102018-08-25 02:15:37196 this._committedExpression = trimmedText;
197 pinPane._savePins();
Tim van der Lippe9b2f8712020-02-12 17:46:22198 this._editor.setSelection(TextUtils.TextRange.TextRange.createFromLocation(Infinity, Infinity));
Erik Luo66bcf102018-08-25 02:15:37199 });
Erik Luo6294d3b2018-07-12 21:54:16200 });
201 }
202
203 /**
Erik Luoc75946e2018-10-08 21:44:25204 * @param {boolean} hovered
205 */
206 setHovered(hovered) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34207 if (this._hovered === hovered) {
Erik Luoc75946e2018-10-08 21:44:25208 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34209 }
Erik Luoc75946e2018-10-08 21:44:25210 this._hovered = hovered;
Tim van der Lippe1d6e57a2019-09-30 11:55:34211 if (!hovered && this._lastNode) {
Tim van der Lippe9b2f8712020-02-12 17:46:22212 SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
Tim van der Lippe1d6e57a2019-09-30 11:55:34213 }
Erik Luoc75946e2018-10-08 21:44:25214 }
215
216 /**
Erik Luo66bcf102018-08-25 02:15:37217 * @return {string}
218 */
219 expression() {
220 return this._committedExpression;
221 }
222
223 /**
Erik Luo6294d3b2018-07-12 21:54:16224 * @return {!Element}
225 */
226 element() {
227 return this._pinElement;
228 }
229
230 async focus() {
231 await this._editorPromise;
232 this._editor.widget().focus();
Tim van der Lippe9b2f8712020-02-12 17:46:22233 this._editor.setSelection(TextUtils.TextRange.TextRange.createFromLocation(Infinity, Infinity));
Erik Luo6294d3b2018-07-12 21:54:16234 }
Erik Luod8eee2b2018-07-24 01:36:18235
236 /**
Tim van der Lippe9b2f8712020-02-12 17:46:22237 * @param {!UI.ContextMenu.ContextMenu} contextMenu
Erik Luod8eee2b2018-07-24 01:36:18238 */
239 appendToContextMenu(contextMenu) {
Erik Luoc75946e2018-10-08 21:44:25240 if (this._lastResult && this._lastResult.object) {
Erik Luo9f24d202018-09-10 08:51:51241 contextMenu.appendApplicableItems(this._lastResult.object);
Erik Luoc75946e2018-10-08 21:44:25242 // Prevent result from being released manually. It will release along with 'console' group.
243 this._lastResult = null;
244 }
Erik Luod8eee2b2018-07-24 01:36:18245 }
246
247 /**
248 * @return {!Promise}
249 */
250 async updatePreview() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34251 if (!this._editor) {
Erik Luod8eee2b2018-07-24 01:36:18252 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34253 }
Erik Luod8eee2b2018-07-24 01:36:18254 const text = this._editor.textWithCurrentSuggestion().trim();
255 const isEditing = this._pinElement.hasFocus();
Erik Luo66bcf102018-08-25 02:15:37256 const throwOnSideEffect = isEditing && text !== this._committedExpression;
257 const timeout = throwOnSideEffect ? 250 : undefined;
Tim van der Lippe9b2f8712020-02-12 17:46:22258 const executionContext = self.UI.context.flavor(SDK.RuntimeModel.ExecutionContext);
259 const {preview, result} = await ObjectUI.JavaScriptREPL.JavaScriptREPL.evaluateAndBuildPreview(
Erik Luoc75946e2018-10-08 21:44:25260 text, throwOnSideEffect, timeout, !isEditing /* allowErrors */, 'console');
Tim van der Lippe1d6e57a2019-09-30 11:55:34261 if (this._lastResult && this._lastExecutionContext) {
Erik Luo9f24d202018-09-10 08:51:51262 this._lastExecutionContext.runtimeModel.releaseEvaluationResult(this._lastResult);
Tim van der Lippe1d6e57a2019-09-30 11:55:34263 }
Erik Luo9f24d202018-09-10 08:51:51264 this._lastResult = result || null;
Erik Luo824d9c22018-10-10 00:09:25265 this._lastExecutionContext = executionContext || null;
Erik Luoc75946e2018-10-08 21:44:25266
Erik Luod8eee2b2018-07-24 01:36:18267 const previewText = preview.deepTextContent();
268 if (!previewText || previewText !== this._pinPreview.deepTextContent()) {
269 this._pinPreview.removeChildren();
Tim van der Lippe9b2f8712020-02-12 17:46:22270 if (result && SDK.RuntimeModel.RuntimeModel.isSideEffectFailure(result)) {
Erik Luo66bcf102018-08-25 02:15:37271 const sideEffectLabel = this._pinPreview.createChild('span', 'object-value-calculate-value-button');
Mathias Bynens23ee1aa2020-03-02 12:06:38272 sideEffectLabel.textContent = '(…)';
Erik Luo66bcf102018-08-25 02:15:37273 sideEffectLabel.title = ls`Evaluate, allowing side effects`;
274 } else if (previewText) {
275 this._pinPreview.appendChild(preview);
276 } else if (!isEditing) {
277 this._pinPreview.createTextChild(ls`not available`);
278 }
279 this._pinPreview.title = previewText;
Erik Luod8eee2b2018-07-24 01:36:18280 }
Erik Luo66bcf102018-08-25 02:15:37281
Erik Luoc75946e2018-10-08 21:44:25282 let node = null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34283 if (result && result.object && result.object.type === 'object' && result.object.subtype === 'node') {
Erik Luoc75946e2018-10-08 21:44:25284 node = result.object;
Tim van der Lippe1d6e57a2019-09-30 11:55:34285 }
Erik Luoc75946e2018-10-08 21:44:25286 if (this._hovered) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34287 if (node) {
Tim van der Lippe9b2f8712020-02-12 17:46:22288 SDK.OverlayModel.OverlayModel.highlightObjectAsDOMNode(node);
Tim van der Lippe1d6e57a2019-09-30 11:55:34289 } else if (this._lastNode) {
Tim van der Lippe9b2f8712020-02-12 17:46:22290 SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
Tim van der Lippe1d6e57a2019-09-30 11:55:34291 }
Erik Luoc75946e2018-10-08 21:44:25292 }
293 this._lastNode = node || null;
294
Tim van der Lippe9b2f8712020-02-12 17:46:22295 const isError = result && result.exceptionDetails && !SDK.RuntimeModel.RuntimeModel.isSideEffectFailure(result);
Tim van der Lippeffa78622019-09-16 12:07:12296 this._pinElement.classList.toggle('error-level', !!isError);
Erik Luod8eee2b2018-07-24 01:36:18297 }
Paul Lewisbf7aa3c2019-11-20 17:03:38298}