UNPKG

chrome-devtools-frontend

Version:
403 lines (361 loc) • 12.8 kB
// Copyright 2014 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. import * as Common from '../common/common.js'; import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as Workspace from '../workspace/workspace.js'; import {DebuggerWorkspaceBinding} from './DebuggerWorkspaceBinding.js'; // eslint-disable-line no-unused-vars /** * @type {!IgnoreListManager} */ let ignoreListManagerInstance; /** * @implements {SDK.SDKModel.SDKModelObserver<!SDK.DebuggerModel.DebuggerModel>} */ export class IgnoreListManager { /** * @private * @param {!DebuggerWorkspaceBinding} debuggerWorkspaceBinding */ constructor(debuggerWorkspaceBinding) { this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; SDK.SDKModel.TargetManager.instance().addModelListener( SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.GlobalObjectCleared, this._clearCacheIfNeeded.bind(this), this); Common.Settings.Settings.instance() .moduleSetting('skipStackFramesPattern') .addChangeListener(this._patternChanged.bind(this)); Common.Settings.Settings.instance() .moduleSetting('skipContentScripts') .addChangeListener(this._patternChanged.bind(this)); /** @type {!Set<function():void>} */ this._listeners = new Set(); /** @type {!Map<string, boolean>} */ this._isIgnoreListedURLCache = new Map(); SDK.SDKModel.TargetManager.instance().observeModels(SDK.DebuggerModel.DebuggerModel, this); } /** * @param {{forceNew: ?boolean, debuggerWorkspaceBinding: ?DebuggerWorkspaceBinding}} opts */ static instance(opts = {forceNew: null, debuggerWorkspaceBinding: null}) { const {forceNew, debuggerWorkspaceBinding} = opts; if (!ignoreListManagerInstance || forceNew) { if (!debuggerWorkspaceBinding) { throw new Error( `Unable to create settings: targetManager, workspace, and debuggerWorkspaceBinding must be provided: ${ new Error().stack}`); } ignoreListManagerInstance = new IgnoreListManager(debuggerWorkspaceBinding); } return ignoreListManagerInstance; } /** * @param {function():void} listener */ addChangeListener(listener) { this._listeners.add(listener); } /** * @param {function():void} listener */ removeChangeListener(listener) { this._listeners.delete(listener); } /** * @override * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel */ modelAdded(debuggerModel) { this._setIgnoreListPatterns(debuggerModel); const sourceMapManager = debuggerModel.sourceMapManager(); sourceMapManager.addEventListener(SDK.SourceMapManager.Events.SourceMapAttached, this._sourceMapAttached, this); sourceMapManager.addEventListener(SDK.SourceMapManager.Events.SourceMapDetached, this._sourceMapDetached, this); } /** * @override * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel */ modelRemoved(debuggerModel) { this._clearCacheIfNeeded(); const sourceMapManager = debuggerModel.sourceMapManager(); sourceMapManager.removeEventListener(SDK.SourceMapManager.Events.SourceMapAttached, this._sourceMapAttached, this); sourceMapManager.removeEventListener(SDK.SourceMapManager.Events.SourceMapDetached, this._sourceMapDetached, this); } _clearCacheIfNeeded() { if (this._isIgnoreListedURLCache.size > 1024) { this._isIgnoreListedURLCache.clear(); } } _getSkipStackFramesPatternSetting() { return /** @type {!Common.Settings.RegExpSetting} */ ( Common.Settings.Settings.instance().moduleSetting('skipStackFramesPattern')); } /** * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel * @return {!Promise<boolean>} */ _setIgnoreListPatterns(debuggerModel) { const regexPatterns = this._getSkipStackFramesPatternSetting().getAsArray(); const patterns = /** @type {!Array<string>} */ ([]); for (const item of regexPatterns) { if (!item.disabled && item.pattern) { patterns.push(item.pattern); } } return debuggerModel.setBlackboxPatterns(patterns); } /** * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode * @return {boolean} */ isIgnoreListedUISourceCode(uiSourceCode) { const projectType = uiSourceCode.project().type(); const isContentScript = projectType === Workspace.Workspace.projectTypes.ContentScripts; if (isContentScript && Common.Settings.Settings.instance().moduleSetting('skipContentScripts').get()) { return true; } const url = this._uiSourceCodeURL(uiSourceCode); return url ? this.isIgnoreListedURL(url) : false; } /** * @param {string} url * @param {boolean=} isContentScript * @return {boolean} */ isIgnoreListedURL(url, isContentScript) { if (this._isIgnoreListedURLCache.has(url)) { return Boolean(this._isIgnoreListedURLCache.get(url)); } if (isContentScript && Common.Settings.Settings.instance().moduleSetting('skipContentScripts').get()) { return true; } const regex = this._getSkipStackFramesPatternSetting().asRegExp(); const isIgnoreListed = (regex && regex.test(url)) || false; this._isIgnoreListedURLCache.set(url, isIgnoreListed); return isIgnoreListed; } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _sourceMapAttached(event) { const script = /** @type {!SDK.Script.Script} */ (event.data.client); const sourceMap = /** @type {!SDK.SourceMap.SourceMap} */ (event.data.sourceMap); this._updateScriptRanges(script, sourceMap); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _sourceMapDetached(event) { const script = /** @type {!SDK.Script.Script} */ (event.data.client); this._updateScriptRanges(script, null); } /** * @param {!SDK.Script.Script} script * @param {?SDK.SourceMap.SourceMap} sourceMap * @return {!Promise<void>} */ async _updateScriptRanges(script, sourceMap) { let hasIgnoreListedMappings = false; if (!IgnoreListManager.instance().isIgnoreListedURL(script.sourceURL, script.isContentScript())) { hasIgnoreListedMappings = sourceMap ? sourceMap.sourceURLs().some(url => this.isIgnoreListedURL(url)) : false; } if (!hasIgnoreListedMappings) { if (scriptToRange.get(script) && await script.setBlackboxedRanges([])) { scriptToRange.delete(script); } await this._debuggerWorkspaceBinding.updateLocations(script); return; } if (!sourceMap) { return; } const mappings = sourceMap.mappings(); /** @type {!Array<!SourceRange>} */ const newRanges = []; if (mappings.length > 0) { let currentIgnoreListed = false; if (mappings[0].lineNumber !== 0 || mappings[0].columnNumber !== 0) { newRanges.push({lineNumber: 0, columnNumber: 0}); currentIgnoreListed = true; } for (const mapping of mappings) { if (mapping.sourceURL && currentIgnoreListed !== this.isIgnoreListedURL(mapping.sourceURL)) { newRanges.push({lineNumber: mapping.lineNumber, columnNumber: mapping.columnNumber}); currentIgnoreListed = !currentIgnoreListed; } } } const oldRanges = scriptToRange.get(script) || []; if (!isEqual(oldRanges, newRanges) && await script.setBlackboxedRanges(newRanges)) { scriptToRange.set(script, newRanges); } this._debuggerWorkspaceBinding.updateLocations(script); /** * @param {!Array<!SourceRange>} rangesA * @param {!Array<!SourceRange>} rangesB * @return {boolean} */ function isEqual(rangesA, rangesB) { if (rangesA.length !== rangesB.length) { return false; } for (let i = 0; i < rangesA.length; ++i) { if (rangesA[i].lineNumber !== rangesB[i].lineNumber || rangesA[i].columnNumber !== rangesB[i].columnNumber) { return false; } } return true; } } /** * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode * @return {?string} */ _uiSourceCodeURL(uiSourceCode) { return uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Debugger ? null : uiSourceCode.url(); } /** * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode * @return {boolean} */ canIgnoreListUISourceCode(uiSourceCode) { const url = this._uiSourceCodeURL(uiSourceCode); return url ? Boolean(this._urlToRegExpString(url)) : false; } /** * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode */ ignoreListUISourceCode(uiSourceCode) { const url = this._uiSourceCodeURL(uiSourceCode); if (url) { this._ignoreListURL(url); } } /** * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode */ unIgnoreListUISourceCode(uiSourceCode) { const url = this._uiSourceCodeURL(uiSourceCode); if (url) { this._unIgnoreListURL(url); } } ignoreListContentScripts() { Common.Settings.Settings.instance().moduleSetting('skipContentScripts').set(true); } unIgnoreListContentScripts() { Common.Settings.Settings.instance().moduleSetting('skipContentScripts').set(false); } /** * @param {string} url */ _ignoreListURL(url) { const regexPatterns = this._getSkipStackFramesPatternSetting().getAsArray(); const regexValue = this._urlToRegExpString(url); if (!regexValue) { return; } let found = false; for (let i = 0; i < regexPatterns.length; ++i) { const item = regexPatterns[i]; if (item.pattern === regexValue) { item.disabled = false; found = true; break; } } if (!found) { regexPatterns.push({pattern: regexValue, disabled: undefined}); } this._getSkipStackFramesPatternSetting().setAsArray(regexPatterns); } /** * @param {string} url */ _unIgnoreListURL(url) { let regexPatterns = this._getSkipStackFramesPatternSetting().getAsArray(); const regexValue = IgnoreListManager.instance()._urlToRegExpString(url); if (!regexValue) { return; } regexPatterns = regexPatterns.filter(function(item) { return item.pattern !== regexValue; }); for (let i = 0; i < regexPatterns.length; ++i) { const item = regexPatterns[i]; if (item.disabled) { continue; } try { const regex = new RegExp(item.pattern); if (regex.test(url)) { item.disabled = true; } } catch (e) { } } this._getSkipStackFramesPatternSetting().setAsArray(regexPatterns); } async _patternChanged() { this._isIgnoreListedURLCache.clear(); /** @type {!Array<!Promise<*>>} */ const promises = []; for (const debuggerModel of SDK.SDKModel.TargetManager.instance().models(SDK.DebuggerModel.DebuggerModel)) { promises.push(this._setIgnoreListPatterns(debuggerModel)); const sourceMapManager = debuggerModel.sourceMapManager(); for (const script of debuggerModel.scripts()) { promises.push(this._updateScriptRanges(script, sourceMapManager.sourceMapForClient(script))); } } await Promise.all(promises); const listeners = Array.from(this._listeners); for (const listener of listeners) { listener(); } this._patternChangeFinishedForTests(); } _patternChangeFinishedForTests() { // This method is sniffed in tests. } /** * @param {string} url * @return {string} */ _urlToRegExpString(url) { const parsedURL = new Common.ParsedURL.ParsedURL(url); if (parsedURL.isAboutBlank() || parsedURL.isDataURL()) { return ''; } if (!parsedURL.isValid) { return '^' + Platform.StringUtilities.escapeForRegExp(url) + '$'; } let name = parsedURL.lastPathComponent; if (name) { name = '/' + name; } else if (parsedURL.folderPathComponents) { name = parsedURL.folderPathComponents + '/'; } if (!name) { name = parsedURL.host; } if (!name) { return ''; } const scheme = parsedURL.scheme; let prefix = ''; if (scheme && scheme !== 'http' && scheme !== 'https') { prefix = '^' + scheme + '://'; if (scheme === 'chrome-extension') { prefix += parsedURL.host + '\\b'; } prefix += '.*'; } return prefix + Platform.StringUtilities.escapeForRegExp(name) + (url.endsWith(name) ? '$' : '\\b'); } } /** @typedef {{lineNumber: number, columnNumber: number}} */ // @ts-ignore export let SourceRange; /** @type {!WeakMap<!SDK.Script.Script, !Array<!SourceRange>>} */ const scriptToRange = new WeakMap();