mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-02 18:11:04 +08:00
For all comments that contained both the unrestricted annotation and any other potential annotation, remove only the unrestricted annotation. R=szuend@chromium.org No-Presubmit: True Bug: 1011811 Change-Id: I27dd67f657cdb1a9233d768eea81cabe2591f5f7 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2569759 Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org>
402 lines
12 KiB
JavaScript
402 lines
12 KiB
JavaScript
// 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 SDK from '../sdk/sdk.js';
|
|
import * as Workspace from '../workspace/workspace.js';
|
|
|
|
import {DebuggerWorkspaceBinding} from './DebuggerWorkspaceBinding.js'; // eslint-disable-line no-unused-vars
|
|
|
|
/**
|
|
* @type {!BlackboxManager}
|
|
*/
|
|
let blackboxManagerInstance;
|
|
|
|
/**
|
|
* @implements {SDK.SDKModel.SDKModelObserver<!SDK.DebuggerModel.DebuggerModel>}
|
|
*/
|
|
export class BlackboxManager {
|
|
/**
|
|
* @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._isBlackboxedURLCache = 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 (!blackboxManagerInstance || forceNew) {
|
|
if (!debuggerWorkspaceBinding) {
|
|
throw new Error(
|
|
`Unable to create settings: targetManager, workspace, and debuggerWorkspaceBinding must be provided: ${
|
|
new Error().stack}`);
|
|
}
|
|
|
|
blackboxManagerInstance = new BlackboxManager(debuggerWorkspaceBinding);
|
|
}
|
|
|
|
return blackboxManagerInstance;
|
|
}
|
|
|
|
/**
|
|
* @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._setBlackboxPatterns(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._isBlackboxedURLCache.size > 1024) {
|
|
this._isBlackboxedURLCache.clear();
|
|
}
|
|
}
|
|
|
|
_getSkipStackFramesPatternSetting() {
|
|
return /** @type {!Common.Settings.RegExpSetting} */ (
|
|
Common.Settings.Settings.instance().moduleSetting('skipStackFramesPattern'));
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
_setBlackboxPatterns(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}
|
|
*/
|
|
isBlackboxedUISourceCode(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.isBlackboxedURL(url) : false;
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @param {boolean=} isContentScript
|
|
* @return {boolean}
|
|
*/
|
|
isBlackboxedURL(url, isContentScript) {
|
|
if (this._isBlackboxedURLCache.has(url)) {
|
|
return !!this._isBlackboxedURLCache.get(url);
|
|
}
|
|
if (isContentScript && Common.Settings.Settings.instance().moduleSetting('skipContentScripts').get()) {
|
|
return true;
|
|
}
|
|
const regex = this._getSkipStackFramesPatternSetting().asRegExp();
|
|
const isBlackboxed = (regex && regex.test(url)) || false;
|
|
this._isBlackboxedURLCache.set(url, isBlackboxed);
|
|
return isBlackboxed;
|
|
}
|
|
|
|
/**
|
|
* @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 hasBlackboxedMappings = false;
|
|
if (!BlackboxManager.instance().isBlackboxedURL(script.sourceURL, script.isContentScript())) {
|
|
hasBlackboxedMappings = sourceMap ? sourceMap.sourceURLs().some(url => this.isBlackboxedURL(url)) : false;
|
|
}
|
|
if (!hasBlackboxedMappings) {
|
|
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 currentBlackboxed = false;
|
|
if (mappings[0].lineNumber !== 0 || mappings[0].columnNumber !== 0) {
|
|
newRanges.push({lineNumber: 0, columnNumber: 0});
|
|
currentBlackboxed = true;
|
|
}
|
|
for (const mapping of mappings) {
|
|
if (mapping.sourceURL && currentBlackboxed !== this.isBlackboxedURL(mapping.sourceURL)) {
|
|
newRanges.push({lineNumber: mapping.lineNumber, columnNumber: mapping.columnNumber});
|
|
currentBlackboxed = !currentBlackboxed;
|
|
}
|
|
}
|
|
}
|
|
|
|
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}
|
|
*/
|
|
canBlackboxUISourceCode(uiSourceCode) {
|
|
const url = this._uiSourceCodeURL(uiSourceCode);
|
|
return url ? !!this._urlToRegExpString(url) : false;
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
*/
|
|
blackboxUISourceCode(uiSourceCode) {
|
|
const url = this._uiSourceCodeURL(uiSourceCode);
|
|
if (url) {
|
|
this._blackboxURL(url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
|
|
*/
|
|
unblackboxUISourceCode(uiSourceCode) {
|
|
const url = this._uiSourceCodeURL(uiSourceCode);
|
|
if (url) {
|
|
this._unblackboxURL(url);
|
|
}
|
|
}
|
|
|
|
blackboxContentScripts() {
|
|
Common.Settings.Settings.instance().moduleSetting('skipContentScripts').set(true);
|
|
}
|
|
|
|
unblackboxContentScripts() {
|
|
Common.Settings.Settings.instance().moduleSetting('skipContentScripts').set(false);
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
*/
|
|
_blackboxURL(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
|
|
*/
|
|
_unblackboxURL(url) {
|
|
let regexPatterns = this._getSkipStackFramesPatternSetting().getAsArray();
|
|
const regexValue = BlackboxManager.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._isBlackboxedURLCache.clear();
|
|
|
|
/** @type {!Array<!Promise<*>>} */
|
|
const promises = [];
|
|
for (const debuggerModel of SDK.SDKModel.TargetManager.instance().models(SDK.DebuggerModel.DebuggerModel)) {
|
|
promises.push(this._setBlackboxPatterns(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 '^' + url.escapeForRegExp() + '$';
|
|
}
|
|
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 + name.escapeForRegExp() + (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();
|