blob: 56afa11c4f45d367267e0e558cf5125cc71f49b1 [file] [log] [blame]
// 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);
}
}
}
};