Commit 2ba8a239 authored by Alexey Kozyatinskiy's avatar Alexey Kozyatinskiy Committed by Commit Bot

[DevTools] simplify script with source map blackboxing

Current logic produces one Debugger.setBlackboxedRanges call on each
scriptParsed event. It makes our frontend too much chatty.
This CL simplify blackbox manager. With new logic blackbox manager
sends Debugger.setBlackboxedRanges iff script has sourceMap and at
least one sourceURL from sourceMap is blackboxed.
When blackbox pattern changed frontend will send setBlackboxedRanges
iff ranges are affected by change.

R=lushnikov@chromium.org

Change-Id: I8b9511f9feba50127de4add98c8d191877103145
Reviewed-on: https://chromium-review.googlesource.com/1162308
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: default avatarAndrey Lushnikov <lushnikov@chromium.org>
Cr-Commit-Position: refs/heads/master@{#580742}
parent d090b403
...@@ -13,17 +13,13 @@ Bindings.BlackboxManager = class { ...@@ -13,17 +13,13 @@ Bindings.BlackboxManager = class {
this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
SDK.targetManager.addModelListener( SDK.targetManager.addModelListener(
SDK.DebuggerModel, SDK.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this); SDK.DebuggerModel, SDK.DebuggerModel.Events.GlobalObjectCleared, this._clearCacheIfNeeded.bind(this), this);
SDK.targetManager.addModelListener(
SDK.DebuggerModel, SDK.DebuggerModel.Events.GlobalObjectCleared, this._globalObjectCleared, this);
Common.moduleSetting('skipStackFramesPattern').addChangeListener(this._patternChanged.bind(this)); Common.moduleSetting('skipStackFramesPattern').addChangeListener(this._patternChanged.bind(this));
Common.moduleSetting('skipContentScripts').addChangeListener(this._patternChanged.bind(this)); Common.moduleSetting('skipContentScripts').addChangeListener(this._patternChanged.bind(this));
/** @type {!Set<function()>} */ /** @type {!Set<function()>} */
this._listeners = new Set(); this._listeners = new Set();
/** @type {!Map<!SDK.DebuggerModel, !Map<string, !Array<!Protocol.Debugger.ScriptPosition>>>} */
this._debuggerModelData = new Map();
/** @type {!Map<string, boolean>} */ /** @type {!Map<string, boolean>} */
this._isBlackboxedURLCache = new Map(); this._isBlackboxedURLCache = new Map();
...@@ -57,7 +53,11 @@ Bindings.BlackboxManager = class { ...@@ -57,7 +53,11 @@ Bindings.BlackboxManager = class {
* @param {!SDK.DebuggerModel} debuggerModel * @param {!SDK.DebuggerModel} debuggerModel
*/ */
modelRemoved(debuggerModel) { modelRemoved(debuggerModel) {
this._debuggerModelData.delete(debuggerModel); this._clearCacheIfNeeded();
}
_clearCacheIfNeeded() {
if (this._isBlackboxedURLCache.size > 1024)
this._isBlackboxedURLCache.clear(); this._isBlackboxedURLCache.clear();
} }
...@@ -83,10 +83,10 @@ Bindings.BlackboxManager = class { ...@@ -83,10 +83,10 @@ Bindings.BlackboxManager = class {
const script = location.script(); const script = location.script();
if (!script) if (!script)
return false; return false;
const positions = this._scriptPositions(script); const ranges = script[Bindings.BlackboxManager._blackboxedRanges];
if (!positions) if (!ranges)
return this._isBlackboxedScript(script); return this.isBlackboxedURL(script.sourceURL, script.isContentScript());
const index = positions.lowerBound(location, comparator); const index = ranges.lowerBound(location, comparator);
return !!(index % 2); return !!(index % 2);
/** /**
...@@ -125,7 +125,7 @@ Bindings.BlackboxManager = class { ...@@ -125,7 +125,7 @@ Bindings.BlackboxManager = class {
if (isContentScript && Common.moduleSetting('skipContentScripts').get()) if (isContentScript && Common.moduleSetting('skipContentScripts').get())
return true; return true;
const regex = Common.moduleSetting('skipStackFramesPattern').asRegExp(); const regex = Common.moduleSetting('skipStackFramesPattern').asRegExp();
const isBlackboxed = regex && regex.test(url); const isBlackboxed = (regex && regex.test(url)) || false;
this._isBlackboxedURLCache.set(url, isBlackboxed); this._isBlackboxedURLCache.set(url, isBlackboxed);
return isBlackboxed; return isBlackboxed;
} }
...@@ -135,47 +135,45 @@ Bindings.BlackboxManager = class { ...@@ -135,47 +135,45 @@ Bindings.BlackboxManager = class {
* @param {?SDK.SourceMap} sourceMap * @param {?SDK.SourceMap} sourceMap
* @return {!Promise<undefined>} * @return {!Promise<undefined>}
*/ */
sourceMapLoaded(script, sourceMap) { async sourceMapLoaded(script, sourceMap) {
if (!sourceMap) const hasBlackboxedMappings = sourceMap ? sourceMap.sourceURLs().some(url => this.isBlackboxedURL(url)) : false;
return Promise.resolve(); if (!hasBlackboxedMappings) {
const previousScriptState = this._scriptPositions(script); if (script[Bindings.BlackboxManager._blackboxedRanges] && await script.setBlackboxedRanges([]))
if (!previousScriptState) delete script[Bindings.BlackboxManager._blackboxedRanges];
return Promise.resolve(); return;
}
const hasBlackboxedMappings = sourceMap.sourceURLs().some(url => this.isBlackboxedURL(url));
const mappings = hasBlackboxedMappings ? sourceMap.mappings().slice() : [];
if (!mappings.length) {
if (previousScriptState.length > 0)
return this._setScriptState(script, []);
return Promise.resolve();
}
mappings.sort(mappingComparator);
const mappings = sourceMap.mappings();
const newRanges = [];
let currentBlackboxed = false; let currentBlackboxed = false;
let isBlackboxed = false;
const positions = [];
// If content in script file begin is not mapped and one or more ranges are blackboxed then blackbox it.
if (mappings[0].lineNumber !== 0 || mappings[0].columnNumber !== 0) { if (mappings[0].lineNumber !== 0 || mappings[0].columnNumber !== 0) {
positions.push({lineNumber: 0, columnNumber: 0}); newRanges.push({lineNumber: 0, columnNumber: 0});
currentBlackboxed = true; currentBlackboxed = true;
} }
for (const mapping of mappings) { for (const mapping of mappings) {
if (mapping.sourceURL && currentBlackboxed !== this.isBlackboxedURL(mapping.sourceURL)) { if (mapping.sourceURL && currentBlackboxed !== this.isBlackboxedURL(mapping.sourceURL)) {
positions.push({lineNumber: mapping.lineNumber, columnNumber: mapping.columnNumber}); newRanges.push({lineNumber: mapping.lineNumber, columnNumber: mapping.columnNumber});
currentBlackboxed = !currentBlackboxed; currentBlackboxed = !currentBlackboxed;
} }
isBlackboxed = currentBlackboxed || isBlackboxed;
} }
return this._setScriptState(script, !isBlackboxed ? [] : positions);
const oldRanges = script[Bindings.BlackboxManager._blackboxedRanges] || [];
if (!isEqual(oldRanges, newRanges) && await script.setBlackboxedRanges(newRanges))
script[Bindings.BlackboxManager._blackboxedRanges] = newRanges;
/** /**
* @param {!SDK.SourceMapEntry} a * @param {!Array<!{lineNumber: number, columnNumber: number}>} rangesA
* @param {!SDK.SourceMapEntry} b * @param {!Array<!{lineNumber: number, columnNumber: number}>} rangesB
* @return {number} * @return {boolean}
*/ */
function mappingComparator(a, b) { function isEqual(rangesA, rangesB) {
if (a.lineNumber !== b.lineNumber) if (rangesA.length !== rangesB.length)
return a.lineNumber - b.lineNumber; return false;
return a.columnNumber - b.columnNumber; 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;
} }
} }
...@@ -269,136 +267,29 @@ Bindings.BlackboxManager = class { ...@@ -269,136 +267,29 @@ Bindings.BlackboxManager = class {
Common.moduleSetting('skipStackFramesPattern').setAsArray(regexPatterns); Common.moduleSetting('skipStackFramesPattern').setAsArray(regexPatterns);
} }
_patternChanged() { async _patternChanged() {
this._isBlackboxedURLCache.clear(); this._isBlackboxedURLCache.clear();
/** @type {!Array<!Promise>} */ /** @type {!Array<!Promise>} */
const promises = []; const promises = [];
for (const debuggerModel of SDK.targetManager.models(SDK.DebuggerModel)) { for (const debuggerModel of SDK.targetManager.models(SDK.DebuggerModel)) {
promises.push(this._setBlackboxPatterns(debuggerModel)); promises.push(this._setBlackboxPatterns(debuggerModel));
for (const script of debuggerModel.scripts()) for (const script of debuggerModel.scripts()) {
promises.push(this._addScript(script).then(loadSourceMap.bind(this, script))); promises.push(this.sourceMapLoaded(script, this._debuggerWorkspaceBinding.sourceMapForScript(script))
.then(() => this._debuggerWorkspaceBinding.updateLocations(script)));
}
} }
Promise.all(promises).then(() => { await Promise.all(promises);
const listeners = Array.from(this._listeners); const listeners = Array.from(this._listeners);
for (const listener of listeners) for (const listener of listeners)
listener(); listener();
this._patternChangeFinishedForTests(); this._patternChangeFinishedForTests();
});
/**
* @param {!SDK.Script} script
* @return {!Promise<undefined>}
* @this {Bindings.BlackboxManager}
*/
function loadSourceMap(script) {
return this.sourceMapLoaded(script, this._debuggerWorkspaceBinding.sourceMapForScript(script));
}
} }
_patternChangeFinishedForTests() { _patternChangeFinishedForTests() {
// This method is sniffed in tests. // This method is sniffed in tests.
} }
/**
* @param {!Common.Event} event
*/
_globalObjectCleared(event) {
const debuggerModel = /** @type {!SDK.DebuggerModel} */ (event.data);
this._debuggerModelData.delete(debuggerModel);
this._isBlackboxedURLCache.clear();
}
/**
* @param {!Common.Event} event
*/
_parsedScriptSource(event) {
const script = /** @type {!SDK.Script} */ (event.data);
this._addScript(script);
}
/**
* @param {!SDK.Script} script
* @return {!Promise<undefined>}
*/
_addScript(script) {
if (!script.sourceURL && !script.sourceMapURL)
return Promise.resolve();
const blackboxed = this._isBlackboxedScript(script);
return this._setScriptState(script, blackboxed ? [{lineNumber: 0, columnNumber: 0}] : []);
}
/**
* @param {!SDK.Script} script
* @return {boolean}
*/
_isBlackboxedScript(script) {
return this.isBlackboxedURL(script.sourceURL, script.isContentScript());
}
/**
* @param {!SDK.Script} script
* @return {?Array<!Protocol.Debugger.ScriptPosition>}
*/
_scriptPositions(script) {
if (this._debuggerModelData.has(script.debuggerModel))
return this._debuggerModelData.get(script.debuggerModel).get(script.scriptId) || null;
return null;
}
/**
* @param {!SDK.Script} script
* @param {!Array<!Protocol.Debugger.ScriptPosition>} positions
*/
_setScriptPositions(script, positions) {
const debuggerModel = script.debuggerModel;
if (!this._debuggerModelData.has(debuggerModel))
this._debuggerModelData.set(debuggerModel, new Map());
this._debuggerModelData.get(debuggerModel).set(script.scriptId, positions);
}
/**
* @param {!SDK.Script} script
* @param {!Array<!Protocol.Debugger.ScriptPosition>} positions
* @return {!Promise<undefined>}
*/
_setScriptState(script, positions) {
const previousScriptState = this._scriptPositions(script);
if (previousScriptState) {
let hasChanged = false;
hasChanged = previousScriptState.length !== positions.length;
for (let i = 0; !hasChanged && i < positions.length; ++i) {
hasChanged = positions[i].lineNumber !== previousScriptState[i].lineNumber ||
positions[i].columnNumber !== previousScriptState[i].columnNumber;
}
if (!hasChanged)
return Promise.resolve();
} else {
if (positions.length === 0)
return Promise.resolve().then(updateState.bind(this, false));
}
return script.setBlackboxedRanges(positions).then(updateState.bind(this));
/**
* @param {boolean} success
* @this {Bindings.BlackboxManager}
*/
function updateState(success) {
if (success) {
this._setScriptPositions(script, positions);
this._debuggerWorkspaceBinding.updateLocations(script);
const isBlackboxed = positions.length !== 0;
if (!isBlackboxed && script.sourceMapURL)
this._debuggerWorkspaceBinding.maybeLoadSourceMap(script);
} else {
const hasPositions = !!this._scriptPositions(script);
if (!hasPositions)
this._setScriptPositions(script, []);
}
}
}
/** /**
* @param {string} url * @param {string} url
* @return {string} * @return {string}
...@@ -430,5 +321,7 @@ Bindings.BlackboxManager = class { ...@@ -430,5 +321,7 @@ Bindings.BlackboxManager = class {
} }
}; };
Bindings.BlackboxManager._blackboxedRanges = Symbol('blackboxedRanged');
/** @type {!Bindings.BlackboxManager} */ /** @type {!Bindings.BlackboxManager} */
Bindings.blackboxManager; Bindings.blackboxManager;
...@@ -244,16 +244,6 @@ Bindings.CompilerScriptMapping = class { ...@@ -244,16 +244,6 @@ Bindings.CompilerScriptMapping = class {
return this._sourceMapManager.sourceMapForClient(script); return this._sourceMapManager.sourceMapForClient(script);
} }
/**
* @param {!SDK.Script} script
*/
maybeLoadSourceMap(script) {
const sourceMap = this._sourceMapManager.sourceMapForClient(script);
if (!sourceMap)
return;
this._populateSourceMapSources(script, sourceMap);
}
/** /**
* @param {?SDK.SourceMap} sourceMap * @param {?SDK.SourceMap} sourceMap
*/ */
......
...@@ -254,10 +254,7 @@ SDK.Script = class { ...@@ -254,10 +254,7 @@ SDK.Script = class {
async setBlackboxedRanges(positions) { async setBlackboxedRanges(positions) {
const response = await this.debuggerModel.target().debuggerAgent().invoke_setBlackboxedRanges( const response = await this.debuggerModel.target().debuggerAgent().invoke_setBlackboxedRanges(
{scriptId: this.scriptId, positions}); {scriptId: this.scriptId, positions});
const error = response[Protocol.Error]; return !response[Protocol.Error];
if (error)
console.error(error);
return !error;
} }
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment