webconsole-ui.js (24320B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 8 const KeyShortcuts = require("resource://devtools/client/shared/key-shortcuts.js"); 9 const { 10 l10n, 11 } = require("resource://devtools/client/webconsole/utils/messages.js"); 12 13 const { BrowserLoader } = ChromeUtils.importESModule( 14 "resource://devtools/shared/loader/browser-loader.sys.mjs" 15 ); 16 const { 17 getAdHocFrontOrPrimitiveGrip, 18 } = require("resource://devtools/client/fronts/object.js"); 19 20 const { 21 PREFS, 22 FILTERS, 23 } = require("resource://devtools/client/webconsole/constants.js"); 24 25 const FirefoxDataProvider = require("resource://devtools/client/netmonitor/src/connector/firefox-data-provider.js"); 26 27 const lazy = {}; 28 ChromeUtils.defineESModuleGetters(lazy, { 29 AppConstants: "resource://gre/modules/AppConstants.sys.mjs", 30 }); 31 32 loader.lazyRequireGetter( 33 this, 34 "START_IGNORE_ACTION", 35 "resource://devtools/client/shared/redux/middleware/ignore.js", 36 true 37 ); 38 const ZoomKeys = require("resource://devtools/client/shared/zoom-keys.js"); 39 loader.lazyRequireGetter( 40 this, 41 "TRACER_LOG_METHODS", 42 "resource://devtools/shared/specs/tracer.js", 43 true 44 ); 45 46 const PREF_SIDEBAR_ENABLED = "devtools.webconsole.sidebarToggle"; 47 const PREF_BROWSERTOOLBOX_SCOPE = "devtools.browsertoolbox.scope"; 48 49 /** 50 * A WebConsoleUI instance is an interactive console initialized *per target* 51 * that displays console log data as well as provides an interactive terminal to 52 * manipulate the target's document content. 53 * 54 * The WebConsoleUI is responsible for the actual Web Console UI 55 * implementation. 56 */ 57 class WebConsoleUI { 58 /** 59 * @param {WebConsole} hud: The WebConsole owner object. 60 */ 61 constructor(hud) { 62 this.hud = hud; 63 this.hudId = this.hud.hudId; 64 this.isBrowserConsole = this.hud.isBrowserConsole; 65 66 this.isBrowserToolboxConsole = 67 this.hud.commands.descriptorFront.isBrowserProcessDescriptor && 68 !this.isBrowserConsole; 69 70 this.window = this.hud.iframeWindow; 71 72 this._onPanelSelected = this._onPanelSelected.bind(this); 73 this._onChangeSplitConsoleState = 74 this._onChangeSplitConsoleState.bind(this); 75 this._onTargetAvailable = this._onTargetAvailable.bind(this); 76 this._onTargetDestroyed = this._onTargetDestroyed.bind(this); 77 this._onResourceAvailable = this._onResourceAvailable.bind(this); 78 this._onNetworkResourceUpdated = this._onNetworkResourceUpdated.bind(this); 79 this._onScopePrefChanged = this._onScopePrefChanged.bind(this); 80 this._onShowConsoleEvaluation = this._onShowConsoleEvaluation.bind(this); 81 82 if (this.isBrowserConsole) { 83 Services.prefs.addObserver( 84 PREF_BROWSERTOOLBOX_SCOPE, 85 this._onScopePrefChanged 86 ); 87 } 88 89 EventEmitter.decorate(this); 90 } 91 92 /** 93 * Initialize the WebConsoleUI instance. 94 * 95 * @return {object} 96 * A promise object that resolves once the frame is ready to use. 97 */ 98 init() { 99 if (this._initializer) { 100 return this._initializer; 101 } 102 103 this._initializer = (async () => { 104 this._initUI(); 105 106 if (this.isBrowserConsole) { 107 // Bug 1605763: 108 // TargetCommand.startListening will start fetching additional targets 109 // and may overload the Browser Console with loads of targets and resources. 110 // We can call it from here, as `_attachTargets` is called after the UI is initialized. 111 // Bug 1642599: 112 // TargetCommand.startListening has to be called before: 113 // - `_attachTargets`, in order to set TargetCommand.watcherFront which is used by ResourceWatcher.watchResources. 114 // - `ConsoleCommands`, in order to set TargetCommand.targetFront which is wrapped by hud.currentTarget 115 await this.hud.commands.targetCommand.startListening(); 116 if (this._destroyed) { 117 return; 118 } 119 } 120 121 await this.wrapper.init(); 122 if (this._destroyed) { 123 return; 124 } 125 126 // Bug 1605763: It's important to call _attachTargets once the UI is initialized, as 127 // it may overload the Browser Console with many updates. 128 // It is also important to do it only after the wrapper is initialized, 129 // otherwise its `store` will be null while we already call a few dispatch methods 130 // from onResourceAvailable 131 await this._attachTargets(); 132 if (this._destroyed) { 133 return; 134 } 135 136 // `_attachTargets` will process resources and throttle some actions 137 // Wait for these actions to be dispatched before reporting that the 138 // console is initialized. Otherwise `showToolbox` will resolve before 139 // all already existing console messages are displayed. 140 await this.wrapper.waitAsyncDispatches(); 141 this._initNotifications(); 142 })(); 143 144 return this._initializer; 145 } 146 147 destroy() { 148 if (this._destroyed) { 149 return; 150 } 151 152 this._destroyed = true; 153 154 this.React = this.ReactDOM = this.FrameView = null; 155 156 if (this.wrapper) { 157 this.wrapper.getStore()?.dispatch(START_IGNORE_ACTION); 158 this.wrapper.destroy(); 159 } 160 161 if (this.jsterm) { 162 this.jsterm.destroy(); 163 this.jsterm = null; 164 } 165 166 const { toolbox } = this.hud; 167 if (toolbox) { 168 toolbox.off("webconsole-selected", this._onPanelSelected); 169 toolbox.off("split-console", this._onChangeSplitConsoleState); 170 toolbox.off("select", this._onChangeSplitConsoleState); 171 toolbox.off( 172 "show-original-variable-mapping-warnings", 173 this._onShowConsoleEvaluation 174 ); 175 } 176 177 if (this.isBrowserConsole) { 178 Services.prefs.removeObserver( 179 PREF_BROWSERTOOLBOX_SCOPE, 180 this._onScopePrefChanged 181 ); 182 } 183 184 // Stop listening for targets 185 this.hud.commands.targetCommand.unwatchTargets({ 186 types: this.hud.commands.targetCommand.ALL_TYPES, 187 onAvailable: this._onTargetAvailable, 188 onDestroyed: this._onTargetDestroyed, 189 }); 190 191 const resourceCommand = this.hud.resourceCommand; 192 if (this._watchedResources) { 193 resourceCommand.unwatchResources(this._watchedResources, { 194 onAvailable: this._onResourceAvailable, 195 }); 196 } 197 198 this.stopWatchingNetworkResources(); 199 200 if (this.networkDataProvider) { 201 this.networkDataProvider.destroy(); 202 this.networkDataProvider = null; 203 } 204 205 // Nullify `hud` last as it nullify also target which is used on destroy 206 this.window = this.hud = this.wrapper = null; 207 } 208 209 /** 210 * Clear the Web Console output. 211 * 212 * This method emits the "messages-cleared" notification. 213 * 214 * @param {boolean} clearStorage 215 * True if you want to clear the console messages storage associated to 216 * this Web Console. 217 * @param {object} event 218 * If the event exists, calls preventDefault on it. 219 */ 220 async clearOutput(clearStorage, event) { 221 if (event) { 222 event.preventDefault(); 223 } 224 if (this.wrapper) { 225 this.wrapper.dispatchMessagesClear(); 226 } 227 228 if (clearStorage) { 229 await this.clearMessagesCache(); 230 } 231 this.emitForTests("messages-cleared"); 232 } 233 234 async clearMessagesCache() { 235 if (this._destroyed) { 236 return; 237 } 238 239 // This can be called during console destruction and getAllFronts would reject in such case. 240 try { 241 const consoleFronts = await this.hud.commands.targetCommand.getAllFronts( 242 this.hud.commands.targetCommand.ALL_TYPES, 243 "console" 244 ); 245 const promises = []; 246 for (const consoleFront of consoleFronts) { 247 promises.push(consoleFront.clearMessagesCacheAsync()); 248 } 249 await Promise.all(promises); 250 this.emitForTests("messages-cache-cleared"); 251 } catch (e) { 252 console.warn("Exception in clearMessagesCache", e); 253 } 254 } 255 256 /** 257 * Remove all of the private messages from the Web Console output. 258 * 259 * This method emits the "private-messages-cleared" notification. 260 */ 261 clearPrivateMessages() { 262 if (this._destroyed) { 263 return; 264 } 265 266 this.wrapper.dispatchPrivateMessagesClear(); 267 this.emitForTests("private-messages-cleared"); 268 } 269 270 inspectObjectActor(objectActor) { 271 const { targetFront } = this.hud.commands.targetCommand; 272 this.wrapper.dispatchMessageAdd( 273 { 274 helperResult: { 275 type: "inspectObject", 276 object: 277 objectActor && objectActor.getGrip 278 ? objectActor 279 : getAdHocFrontOrPrimitiveGrip(objectActor, targetFront), 280 }, 281 }, 282 true 283 ); 284 return this.wrapper; 285 } 286 287 disableAllNetworkMessages() { 288 if (this._destroyed) { 289 return; 290 } 291 this.wrapper.dispatchNetworkMessagesDisable(); 292 } 293 294 getPanelWindow() { 295 return this.window; 296 } 297 298 logWarningAboutReplacedAPI() { 299 return this.hud.currentTarget.logWarningInPage( 300 l10n.getStr("ConsoleAPIDisabled"), 301 "ConsoleAPIDisabled" 302 ); 303 } 304 305 /** 306 * Connect to the server using the remote debugging protocol. 307 * 308 * @private 309 * @return {object} 310 * A promise object that is resolved/reject based on the proxies connections. 311 */ 312 async _attachTargets() { 313 const { commands, resourceCommand } = this.hud; 314 this.networkDataProvider = new FirefoxDataProvider({ 315 commands, 316 actions: { 317 updateRequest: (id, data) => 318 this.wrapper.batchedRequestUpdates({ id, data }), 319 }, 320 owner: this, 321 }); 322 323 // Listen for all target types, including: 324 // - frames, in order to get the parent process target 325 // which is considered as a frame rather than a process. 326 // - workers, for similar reason. When we open a toolbox 327 // for just a worker, the top level target is a worker target. 328 // - processes, as we want to spawn additional proxies for them. 329 await commands.targetCommand.watchTargets({ 330 types: this.hud.commands.targetCommand.ALL_TYPES, 331 onAvailable: this._onTargetAvailable, 332 onDestroyed: this._onTargetDestroyed, 333 }); 334 335 this._watchedResources = [ 336 resourceCommand.TYPES.CONSOLE_MESSAGE, 337 resourceCommand.TYPES.ERROR_MESSAGE, 338 resourceCommand.TYPES.PLATFORM_MESSAGE, 339 resourceCommand.TYPES.DOCUMENT_EVENT, 340 resourceCommand.TYPES.LAST_PRIVATE_CONTEXT_EXIT, 341 resourceCommand.TYPES.JSTRACER_TRACE, 342 resourceCommand.TYPES.JSTRACER_STATE, 343 ]; 344 345 // CSS Warnings are only enabled when the user explicitely requested to show them 346 // as it can slow down page load. 347 const shouldShowCssWarnings = this.wrapper.getFilterState(FILTERS.CSS); 348 if (shouldShowCssWarnings) { 349 this._watchedResources.push(resourceCommand.TYPES.CSS_MESSAGE); 350 } 351 352 await resourceCommand.watchResources(this._watchedResources, { 353 onAvailable: this._onResourceAvailable, 354 }); 355 356 if (this.isBrowserConsole || this.isBrowserToolboxConsole) { 357 const shouldEnableNetworkMonitoring = Services.prefs.getBoolPref( 358 PREFS.UI.ENABLE_NETWORK_MONITORING 359 ); 360 if (shouldEnableNetworkMonitoring) { 361 await this.startWatchingNetworkResources(); 362 } else { 363 await this.stopWatchingNetworkResources(); 364 } 365 } else { 366 // We should always watch for network resources in the webconsole 367 await this.startWatchingNetworkResources(); 368 } 369 } 370 371 async startWatchingNetworkResources() { 372 const { commands, resourceCommand } = this.hud; 373 await resourceCommand.watchResources( 374 [ 375 resourceCommand.TYPES.NETWORK_EVENT, 376 resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE, 377 ], 378 { 379 onAvailable: this._onResourceAvailable, 380 onUpdated: this._onNetworkResourceUpdated, 381 } 382 ); 383 384 // When opening a worker toolbox from about:debugging, 385 // we do not instantiate any Watcher actor yet and would throw here. 386 // But even once we do, we wouldn't support network inspection anyway. 387 if (commands.targetCommand.hasTargetWatcherSupport()) { 388 const networkFront = await commands.watcherFront.getNetworkParentActor(); 389 // There is no way to view response bodies from the Browser Console, so do 390 // not waste the memory. 391 const saveBodies = 392 !this.isBrowserConsole && 393 Services.prefs.getBoolPref( 394 "devtools.netmonitor.saveRequestAndResponseBodies" 395 ); 396 await networkFront.setSaveRequestAndResponseBodies(saveBodies); 397 } 398 } 399 400 async stopWatchingNetworkResources() { 401 if (this._destroyed) { 402 return; 403 } 404 405 await this.hud.resourceCommand.unwatchResources( 406 [ 407 this.hud.resourceCommand.TYPES.NETWORK_EVENT, 408 this.hud.resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE, 409 ], 410 { 411 onAvailable: this._onResourceAvailable, 412 onUpdated: this._onNetworkResourceUpdated, 413 } 414 ); 415 } 416 417 handleDocumentEvent(resource) { 418 // Only consider top level document, and ignore remote iframes top document 419 if (!resource.targetFront.isTopLevel) { 420 return; 421 } 422 423 if (resource.name == "will-navigate") { 424 this.handleWillNavigate({ 425 timeStamp: resource.time, 426 url: resource.newURI, 427 }); 428 } else if (resource.name == "dom-complete") { 429 this.handleNavigated({ 430 hasNativeConsoleAPI: resource.hasNativeConsoleAPI, 431 }); 432 } 433 // For now, ignore all other DOCUMENT_EVENT's. 434 } 435 436 /** 437 * Handler for when the page is done loading. 438 * 439 * @param {boolean} hasNativeConsoleAPI 440 * True if the `console` object is the native one and hasn't been overloaded by a custom 441 * object by the page itself. 442 */ 443 async handleNavigated({ hasNativeConsoleAPI }) { 444 // Updates instant evaluation on page navigation 445 this.wrapper.dispatchUpdateInstantEvaluationResultForCurrentExpression(); 446 447 // Wait for completion of any async dispatch before notifying that the console 448 // is fully updated after a page reload 449 await this.wrapper.waitAsyncDispatches(); 450 451 if (!hasNativeConsoleAPI) { 452 this.logWarningAboutReplacedAPI(); 453 } 454 455 this.emit("reloaded"); 456 } 457 458 handleWillNavigate({ timeStamp, url }) { 459 this.wrapper.dispatchTabWillNavigate({ timeStamp, url }); 460 } 461 462 /** 463 * Called when the CSS Warning filter is enabled, in order to start observing for them in the backend. 464 */ 465 async watchCssMessages() { 466 const { resourceCommand } = this.hud; 467 if (this._watchedResources.includes(resourceCommand.TYPES.CSS_MESSAGE)) { 468 return; 469 } 470 await resourceCommand.watchResources([resourceCommand.TYPES.CSS_MESSAGE], { 471 onAvailable: this._onResourceAvailable, 472 }); 473 this._watchedResources.push(resourceCommand.TYPES.CSS_MESSAGE); 474 } 475 476 // eslint-disable-next-line complexity 477 _onResourceAvailable(resources) { 478 if (this._destroyed) { 479 return; 480 } 481 482 const { logMethod } = this.hud.commands.tracerCommand.getTracingOptions(); 483 484 const messages = []; 485 for (const resource of resources) { 486 const { TYPES } = this.hud.resourceCommand; 487 if (resource.resourceType === TYPES.DOCUMENT_EVENT) { 488 this.handleDocumentEvent(resource); 489 continue; 490 } 491 if (resource.resourceType == TYPES.LAST_PRIVATE_CONTEXT_EXIT) { 492 // Private messages only need to be removed from the output in Browser Console/Browser Toolbox 493 // (but in theory this resource should only be send from parent process watchers) 494 if (this.isBrowserConsole || this.isBrowserToolboxConsole) { 495 this.clearPrivateMessages(); 496 } 497 continue; 498 } 499 // Ignore messages forwarded from content processes if we're in fission browser toolbox. 500 if ( 501 !this.wrapper || 502 ((resource.resourceType === TYPES.ERROR_MESSAGE || 503 resource.resourceType === TYPES.CSS_MESSAGE) && 504 resource.pageError?.isForwardedFromContentProcess && 505 (this.isBrowserToolboxConsole || this.isBrowserConsole)) 506 ) { 507 continue; 508 } 509 510 // Don't show messages emitted from a private window before the Browser Console was 511 // opened to avoid leaking data from past usage of the browser (e.g. content message 512 // from now closed private tabs) 513 if ( 514 (this.isBrowserToolboxConsole || this.isBrowserConsole) && 515 resource.isAlreadyExistingResource && 516 (resource.pageError?.private || resource.private) 517 ) { 518 continue; 519 } 520 521 if ( 522 resource.resourceType === TYPES.JSTRACER_TRACE && 523 logMethod != TRACER_LOG_METHODS.CONSOLE 524 ) { 525 continue; 526 } 527 if (resource.resourceType === TYPES.NETWORK_EVENT_STACKTRACE) { 528 this.networkDataProvider?.onStackTraceAvailable(resource); 529 continue; 530 } 531 532 if (resource.resourceType === TYPES.NETWORK_EVENT) { 533 this.networkDataProvider?.onNetworkResourceAvailable(resource); 534 } 535 messages.push(resource); 536 } 537 this.wrapper.dispatchMessagesAdd(messages); 538 } 539 540 _onNetworkResourceUpdated(updates) { 541 if (this._destroyed) { 542 return; 543 } 544 545 const messageUpdates = []; 546 for (const { resource, update } of updates) { 547 if ( 548 resource.resourceType == this.hud.resourceCommand.TYPES.NETWORK_EVENT 549 ) { 550 this.networkDataProvider?.onNetworkResourceUpdated(resource, update); 551 messageUpdates.push(resource); 552 } 553 } 554 this.wrapper.dispatchMessagesUpdate(messageUpdates); 555 } 556 557 /** 558 * Called any time a new target is available. 559 * i.e. it was already existing or has just been created. 560 * 561 * @private 562 */ 563 async _onTargetAvailable() { 564 // onTargetAvailable is a mandatory argument for watchTargets, 565 // we still define it solely for being able to use onTargetDestroyed. 566 } 567 568 _onTargetDestroyed({ targetFront, isModeSwitching }) { 569 // Don't try to do anything if the WebConsole is being destroyed 570 if (this._destroyed) { 571 return; 572 } 573 574 // We only want to remove messages from a target destroyed when we're switching mode 575 // in the Browser Console/Browser Toolbox Console. 576 // For regular cases, we want to keep the message history (the output will still be 577 // cleared when the top level target navigates, if "Persist Logs" isn't true, via handleWillNavigate) 578 if (isModeSwitching) { 579 this.wrapper.dispatchTargetMessagesRemove(targetFront); 580 } 581 } 582 583 _initUI() { 584 this.document = this.window.document; 585 this.rootElement = this.document.documentElement; 586 587 this.outputNode = this.document.getElementById("app-wrapper"); 588 589 const { toolbox } = this.hud; 590 591 // Initialize module loader and load all the WebConsoleWrapper. The entire code-base 592 // doesn't need any extra privileges and runs entirely in content scope. 593 const browserLoader = BrowserLoader({ 594 baseURI: "resource://devtools/client/webconsole/", 595 window: this.window, 596 }); 597 // Expose `require` for the CustomFormatter ESM in order to allow it to load 598 // ObjectInspector, which are still CommonJS modules, via the same BrowserLoader instance. 599 this.window.browserLoaderRequire = browserLoader.require; 600 const WebConsoleWrapper = browserLoader.require( 601 "resource://devtools/client/webconsole/webconsole-wrapper.js" 602 ); 603 604 this.wrapper = new WebConsoleWrapper( 605 this.outputNode, 606 this, 607 toolbox, 608 this.document 609 ); 610 611 this._initShortcuts(); 612 this._initOutputSyntaxHighlighting(); 613 614 if (toolbox) { 615 toolbox.on("webconsole-selected", this._onPanelSelected); 616 toolbox.on("split-console", this._onChangeSplitConsoleState); 617 toolbox.on("select", this._onChangeSplitConsoleState); 618 } 619 } 620 621 _initOutputSyntaxHighlighting() { 622 // Given a DOM node, we syntax highlight identically to how the input field 623 // looks. See https://codemirror.net/demo/runmode.html; 624 const syntaxHighlightNode = node => { 625 const editor = this.jsterm && this.jsterm.editor; 626 if (node && editor) { 627 node.classList.add("cm-s-mozilla"); 628 editor.CodeMirror.runMode( 629 node.textContent, 630 "application/javascript", 631 node 632 ); 633 } 634 }; 635 636 // Use a Custom Element to handle syntax highlighting to avoid 637 // dealing with refs or innerHTML from React. 638 const win = this.window; 639 win.customElements.define( 640 "syntax-highlighted", 641 class extends win.HTMLElement { 642 connectedCallback() { 643 if (!this.connected) { 644 this.connected = true; 645 syntaxHighlightNode(this); 646 647 // Highlight Again when the innerText changes 648 // We remove the listener before running codemirror mode and add 649 // it again to capture text changes 650 this.observer = new win.MutationObserver((mutations, observer) => { 651 observer.disconnect(); 652 syntaxHighlightNode(this); 653 observer.observe(this, { childList: true }); 654 }); 655 656 this.observer.observe(this, { childList: true }); 657 } 658 } 659 } 660 ); 661 } 662 663 _initNotifications() { 664 if (this.hud.toolbox) { 665 this.wrapper.toggleOriginalVariableMappingEvaluationNotification( 666 !!this.hud.toolbox 667 .getPanel("jsdebugger") 668 ?.shouldShowOriginalVariableMappingWarnings() 669 ); 670 this.hud.toolbox.on( 671 "show-original-variable-mapping-warnings", 672 this._onShowConsoleEvaluation 673 ); 674 } 675 } 676 677 _initShortcuts() { 678 const shortcuts = new KeyShortcuts({ 679 window: this.window, 680 }); 681 682 for (const clearShortcut of this.getClearKeyShortcuts()) { 683 shortcuts.on(clearShortcut, event => this.clearOutput(true, event)); 684 } 685 686 if (this.isBrowserConsole) { 687 // Make sure keyboard shortcuts work immediately after opening 688 // the Browser Console (Bug 1461366). 689 this.window.focus(); 690 shortcuts.on( 691 l10n.getStr("webconsole.close.key"), 692 this.window.close.bind(this.window) 693 ); 694 695 ZoomKeys.register(this.window, shortcuts); 696 697 /* This is the same as DevelopmentHelpers.quickRestart, but it runs in all 698 * builds (even official). This allows a user to do a restart + session restore 699 * with Ctrl+Shift+J (open Browser Console) and then Ctrl+Alt+R (restart). 700 */ 701 shortcuts.on("CmdOrCtrl+Alt+R", () => { 702 this.hud.commands.targetCommand.reloadTopLevelTarget(); 703 }); 704 } else if (Services.prefs.getBoolPref(PREF_SIDEBAR_ENABLED)) { 705 shortcuts.on("Esc", () => { 706 this.wrapper.dispatchSidebarClose(); 707 if (this.jsterm) { 708 this.jsterm.focus(); 709 } 710 }); 711 } 712 } 713 714 /** 715 * Returns system-specific key shortcuts for clearing the console. 716 * 717 * @return {string[]} 718 * An array of key shortcut strings. 719 */ 720 getClearKeyShortcuts() { 721 if (lazy.AppConstants.platform === "macosx") { 722 return [ 723 l10n.getStr("webconsole.clear.alternativeKeyOSX"), 724 l10n.getStr("webconsole.clear.keyOSX"), 725 ]; 726 } 727 728 return [l10n.getStr("webconsole.clear.key")]; 729 } 730 731 /** 732 * Sets the focus to JavaScript input field when the web console tab is 733 * selected or when there is a split console present. 734 * 735 * @private 736 */ 737 _onPanelSelected() { 738 // We can only focus when we have the jsterm reference. This is fine because if the 739 // jsterm is not mounted yet, it will be focused in JSTerm's componentDidMount. 740 if (this.jsterm) { 741 this.jsterm.focus(); 742 } 743 } 744 745 _onChangeSplitConsoleState() { 746 this.wrapper.dispatchSplitConsoleCloseButtonToggle(); 747 } 748 749 _onScopePrefChanged() { 750 if (this.isBrowserConsole) { 751 this.hud.updateWindowTitle(); 752 } 753 } 754 755 _onShowConsoleEvaluation(isOriginalVariableMappingEnabled) { 756 this.wrapper.toggleOriginalVariableMappingEvaluationNotification( 757 isOriginalVariableMappingEnabled 758 ); 759 } 760 761 getInputCursor() { 762 return this.jsterm && this.jsterm.getSelectionStart(); 763 } 764 765 getJsTermTooltipAnchor() { 766 return this.outputNode.querySelector(".CodeMirror-cursor"); 767 } 768 769 attachRef(id, node) { 770 this[id] = node; 771 } 772 773 getSelectedNodeActorID() { 774 const inspectorSelection = this.hud.getInspectorSelection(); 775 return inspectorSelection?.nodeFront?.actorID; 776 } 777 } 778 779 exports.WebConsoleUI = WebConsoleUI;