| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /** |
| * @implements {SDK.SDKModelObserver<!SDK.RuntimeModel>} |
| * @implements {UI.SoftDropDown.Delegate<!SDK.ExecutionContext>} |
| */ |
| Console.ConsoleContextSelector = class { |
| constructor() { |
| /** @type {!UI.ListModel<!SDK.ExecutionContext>} */ |
| this._items = new UI.ListModel(); |
| /** @type {!UI.SoftDropDown<!SDK.ExecutionContext>} */ |
| this._dropDown = new UI.SoftDropDown(this._items, this); |
| this._dropDown.setRowHeight(36); |
| this._toolbarItem = new UI.ToolbarItem(this._dropDown.element); |
| this._toolbarItem.setEnabled(false); |
| this._toolbarItem.setTitle(ls`JavaScript contexts`); |
| this._items.addEventListener( |
| UI.ListModel.Events.ItemsReplaced, () => this._toolbarItem.setEnabled(!!this._items.length)); |
| |
| /** @type {!Map<!SDK.ExecutionContext, !ProductRegistry.BadgePool>} */ |
| this._badgePoolForExecutionContext = new Map(); |
| |
| this._toolbarItem.element.classList.add('toolbar-has-dropdown'); |
| |
| SDK.targetManager.addModelListener( |
| SDK.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextCreated, this._onExecutionContextCreated, this); |
| SDK.targetManager.addModelListener( |
| SDK.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextChanged, this._onExecutionContextChanged, this); |
| SDK.targetManager.addModelListener( |
| SDK.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextDestroyed, this._onExecutionContextDestroyed, this); |
| SDK.targetManager.addModelListener( |
| SDK.ResourceTreeModel, SDK.ResourceTreeModel.Events.FrameNavigated, this._frameNavigated, this); |
| |
| UI.context.addFlavorChangeListener(SDK.ExecutionContext, this._executionContextChangedExternally, this); |
| UI.context.addFlavorChangeListener(SDK.DebuggerModel.CallFrame, this._callFrameSelectedInUI, this); |
| SDK.targetManager.observeModels(SDK.RuntimeModel, this); |
| SDK.targetManager.addModelListener( |
| SDK.DebuggerModel, SDK.DebuggerModel.Events.CallFrameSelected, this._callFrameSelectedInModel, this); |
| } |
| |
| /** |
| * @return {!UI.ToolbarItem} |
| */ |
| toolbarItem() { |
| return this._toolbarItem; |
| } |
| |
| /** |
| * @override |
| * @param {?SDK.ExecutionContext} from |
| * @param {?SDK.ExecutionContext} to |
| * @param {?Element} fromElement |
| * @param {?Element} toElement |
| */ |
| highlightedItemChanged(from, to, fromElement, toElement) { |
| SDK.OverlayModel.hideDOMNodeHighlight(); |
| if (to && to.frameId) { |
| const overlayModel = to.target().model(SDK.OverlayModel); |
| if (overlayModel) |
| overlayModel.highlightFrame(to.frameId); |
| } |
| if (fromElement) |
| fromElement.classList.remove('highlighted'); |
| if (toElement) |
| toElement.classList.add('highlighted'); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.ExecutionContext} executionContext |
| * @return {string} |
| */ |
| titleFor(executionContext) { |
| const target = executionContext.target(); |
| let label = executionContext.label() ? target.decorateLabel(executionContext.label()) : ''; |
| if (executionContext.frameId) { |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel); |
| const frame = resourceTreeModel && resourceTreeModel.frameForId(executionContext.frameId); |
| if (frame) |
| label = label || frame.displayName(); |
| } |
| label = label || executionContext.origin; |
| |
| return label; |
| } |
| |
| /** |
| * @param {!SDK.ExecutionContext} executionContext |
| * @return {number} |
| */ |
| _depthFor(executionContext) { |
| let target = executionContext.target(); |
| let depth = 0; |
| if (!executionContext.isDefault) |
| depth++; |
| if (executionContext.frameId) { |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel); |
| let frame = resourceTreeModel && resourceTreeModel.frameForId(executionContext.frameId); |
| while (frame) { |
| frame = frame.parentFrame || frame.crossTargetParentFrame(); |
| if (frame) { |
| depth++; |
| target = frame.resourceTreeModel().target(); |
| } |
| } |
| } |
| let targetDepth = 0; |
| while (target.parentTarget()) { |
| if (target.parentTarget().hasJSCapability()) { |
| targetDepth++; |
| } else { |
| // Special casing service workers to be top-level. |
| targetDepth = 0; |
| break; |
| } |
| target = target.parentTarget(); |
| } |
| depth += targetDepth; |
| return depth; |
| } |
| |
| /** |
| * @param {!SDK.ExecutionContext} executionContext |
| * @return {?Element} |
| */ |
| _badgeFor(executionContext) { |
| if (!executionContext.frameId || !executionContext.isDefault) |
| return null; |
| const resourceTreeModel = executionContext.target().model(SDK.ResourceTreeModel); |
| const frame = resourceTreeModel && resourceTreeModel.frameForId(executionContext.frameId); |
| if (!frame) |
| return null; |
| const badgePool = new ProductRegistry.BadgePool(); |
| this._badgePoolForExecutionContext.set(executionContext, badgePool); |
| return badgePool.badgeForFrame(frame); |
| } |
| |
| /** |
| * @param {!SDK.ExecutionContext} executionContext |
| */ |
| _disposeExecutionContextBadge(executionContext) { |
| const badgePool = this._badgePoolForExecutionContext.get(executionContext); |
| if (!badgePool) |
| return; |
| badgePool.reset(); |
| this._badgePoolForExecutionContext.delete(executionContext); |
| } |
| |
| /** |
| * @param {!SDK.ExecutionContext} executionContext |
| */ |
| _executionContextCreated(executionContext) { |
| // FIXME(413886): We never want to show execution context for the main thread of shadow page in service/shared worker frontend. |
| // This check could be removed once we do not send this context to frontend. |
| if (!executionContext.target().hasJSCapability()) |
| return; |
| |
| this._items.insertWithComparator(executionContext, executionContext.runtimeModel.executionContextComparator()); |
| |
| if (executionContext === UI.context.flavor(SDK.ExecutionContext)) |
| this._dropDown.selectItem(executionContext); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onExecutionContextCreated(event) { |
| const executionContext = /** @type {!SDK.ExecutionContext} */ (event.data); |
| this._executionContextCreated(executionContext); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onExecutionContextChanged(event) { |
| const executionContext = /** @type {!SDK.ExecutionContext} */ (event.data); |
| if (this._items.indexOf(executionContext) === -1) |
| return; |
| this._executionContextDestroyed(executionContext); |
| this._executionContextCreated(executionContext); |
| } |
| |
| /** |
| * @param {!SDK.ExecutionContext} executionContext |
| */ |
| _executionContextDestroyed(executionContext) { |
| const index = this._items.indexOf(executionContext); |
| if (index === -1) |
| return; |
| this._disposeExecutionContextBadge(executionContext); |
| this._items.remove(index); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onExecutionContextDestroyed(event) { |
| const executionContext = /** @type {!SDK.ExecutionContext} */ (event.data); |
| this._executionContextDestroyed(executionContext); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _executionContextChangedExternally(event) { |
| const executionContext = /** @type {?SDK.ExecutionContext} */ (event.data); |
| this._dropDown.selectItem(executionContext); |
| } |
| |
| /** |
| * @param {?SDK.ExecutionContext} executionContext |
| * @return {boolean} |
| */ |
| _isTopContext(executionContext) { |
| if (!executionContext || !executionContext.isDefault) |
| return false; |
| const resourceTreeModel = executionContext.target().model(SDK.ResourceTreeModel); |
| const frame = |
| executionContext.frameId && resourceTreeModel && resourceTreeModel.frameForId(executionContext.frameId); |
| if (!frame) |
| return false; |
| return frame.isTopFrame(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| _hasTopContext() { |
| return this._items.some(executionContext => this._isTopContext(executionContext)); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.RuntimeModel} runtimeModel |
| */ |
| modelAdded(runtimeModel) { |
| runtimeModel.executionContexts().forEach(this._executionContextCreated, this); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.RuntimeModel} runtimeModel |
| */ |
| modelRemoved(runtimeModel) { |
| for (let i = this._items.length - 1; i >= 0; i--) { |
| if (this._items.at(i).runtimeModel === runtimeModel) |
| this._executionContextDestroyed(this._items.at(i)); |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.ExecutionContext} item |
| * @return {!Element} |
| */ |
| createElementForItem(item) { |
| const element = createElementWithClass('div'); |
| const shadowRoot = UI.createShadowRootWithCoreStyles(element, 'console/consoleContextSelector.css'); |
| const title = shadowRoot.createChild('div', 'title'); |
| title.createTextChild(this.titleFor(item).trimEnd(100)); |
| const subTitle = shadowRoot.createChild('div', 'subtitle'); |
| const badgeElement = this._badgeFor(item); |
| if (badgeElement) { |
| badgeElement.classList.add('badge'); |
| subTitle.appendChild(badgeElement); |
| } |
| subTitle.createTextChild(this._subtitleFor(item)); |
| element.style.paddingLeft = (8 + this._depthFor(item) * 15) + 'px'; |
| return element; |
| } |
| |
| /** |
| * @param {!SDK.ExecutionContext} executionContext |
| * @return {string} |
| */ |
| _subtitleFor(executionContext) { |
| const target = executionContext.target(); |
| let frame; |
| if (executionContext.frameId) { |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel); |
| frame = resourceTreeModel && resourceTreeModel.frameForId(executionContext.frameId); |
| } |
| if (executionContext.origin.startsWith('chrome-extension://')) |
| return Common.UIString('Extension'); |
| if (!frame || !frame.parentFrame || frame.parentFrame.securityOrigin !== executionContext.origin) { |
| const url = executionContext.origin.asParsedURL(); |
| if (url) |
| return url.domain(); |
| } |
| |
| if (frame) { |
| const callFrame = frame.findCreationCallFrame(callFrame => !!callFrame.url); |
| if (callFrame) |
| return new Common.ParsedURL(callFrame.url).domain(); |
| return Common.UIString('IFrame'); |
| } |
| return ''; |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.ExecutionContext} item |
| * @return {boolean} |
| */ |
| isItemSelectable(item) { |
| const callFrame = item.debuggerModel.selectedCallFrame(); |
| const callFrameContext = callFrame && callFrame.script.executionContext(); |
| return !callFrameContext || item === callFrameContext; |
| } |
| |
| /** |
| * @override |
| * @param {?SDK.ExecutionContext} item |
| */ |
| itemSelected(item) { |
| this._toolbarItem.element.classList.toggle('warning', !this._isTopContext(item) && this._hasTopContext()); |
| UI.context.setFlavor(SDK.ExecutionContext, item); |
| } |
| |
| _callFrameSelectedInUI() { |
| const callFrame = UI.context.flavor(SDK.DebuggerModel.CallFrame); |
| const callFrameContext = callFrame && callFrame.script.executionContext(); |
| if (callFrameContext) |
| UI.context.setFlavor(SDK.ExecutionContext, callFrameContext); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _callFrameSelectedInModel(event) { |
| const debuggerModel = /** @type {!SDK.DebuggerModel} */ (event.data); |
| for (const executionContext of this._items) { |
| if (executionContext.debuggerModel === debuggerModel) { |
| this._disposeExecutionContextBadge(executionContext); |
| this._dropDown.refreshItem(executionContext); |
| } |
| } |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _frameNavigated(event) { |
| const frame = /** @type {!SDK.ResourceTreeFrame} */ (event.data); |
| const runtimeModel = frame.resourceTreeModel().target().model(SDK.RuntimeModel); |
| if (!runtimeModel) |
| return; |
| for (const executionContext of runtimeModel.executionContexts()) { |
| if (frame.id === executionContext.frameId) { |
| this._disposeExecutionContextBadge(executionContext); |
| this._dropDown.refreshItem(executionContext); |
| } |
| } |
| } |
| }; |