blob: 0298991f1c8fdf7e59b0cf6c4fa32e31f81a35d2 [file] [log] [blame]
// Copyright 2017 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.
/**
* @template T
*/
SDK.SourceMapManager = class extends Common.Object {
/**
* @param {!SDK.Target} target
*/
constructor(target) {
super();
this._target = target;
this._isEnabled = true;
/** @type {!Map<!T, string>} */
this._relativeSourceURL = new Map();
/** @type {!Map<!T, string>} */
this._relativeSourceMapURL = new Map();
/** @type {!Map<!T, string>} */
this._resolvedSourceMapURL = new Map();
/** @type {!Map<string, !SDK.SourceMap>} */
this._sourceMapByURL = new Map();
/** @type {!Multimap<string, !T>} */
this._sourceMapURLToLoadingClients = new Multimap();
/** @type {!Multimap<string, !T>} */
this._sourceMapURLToClients = new Multimap();
SDK.targetManager.addEventListener(SDK.TargetManager.Events.InspectedURLChanged, this._inspectedURLChanged, this);
}
/**
* @param {boolean} isEnabled
*/
setEnabled(isEnabled) {
if (isEnabled === this._isEnabled)
return;
this._isEnabled = isEnabled;
const clients = Array.from(this._resolvedSourceMapURL.keys());
for (const client of clients) {
const relativeSourceURL = this._relativeSourceURL.get(client);
const relativeSourceMapURL = this._relativeSourceMapURL.get(client);
this.detachSourceMap(client);
this.attachSourceMap(client, relativeSourceURL, relativeSourceMapURL);
}
}
/**
* @param {!Common.Event} event
*/
_inspectedURLChanged(event) {
if (event.data !== this._target)
return;
const clients = Array.from(this._resolvedSourceMapURL.keys());
for (const client of clients) {
const relativeSourceURL = this._relativeSourceURL.get(client);
const relativeSourceMapURL = this._relativeSourceMapURL.get(client);
const resolvedSourceMapURL = this._resolvedSourceMapURL.get(client);
const sourceMapURL = this._resolveRelativeURLs(relativeSourceURL, relativeSourceMapURL).sourceMapURL;
if (sourceMapURL !== resolvedSourceMapURL) {
this.detachSourceMap(client);
this.attachSourceMap(client, relativeSourceURL, relativeSourceMapURL);
}
}
}
/**
* @param {!T} client
* @return {?SDK.SourceMap}
*/
sourceMapForClient(client) {
const sourceMapURL = this._resolvedSourceMapURL.get(client);
return sourceMapURL ? this._sourceMapByURL.get(sourceMapURL) || null : null;
}
/**
* @param {!SDK.SourceMap} sourceMap
* @return {!Array<!T>}
*/
clientsForSourceMap(sourceMap) {
if (this._sourceMapURLToClients.has(sourceMap.url()))
return this._sourceMapURLToClients.get(sourceMap.url()).valuesArray();
return this._sourceMapURLToLoadingClients.get(sourceMap.url()).valuesArray();
}
/**
* @param {!SDK.SourceMap.EditResult} editResult
*/
applySourceMapEdit(editResult) {
console.assert(
this._sourceMapByURL.has(editResult.map.url()), 'Cannot apply edit result for non-existing source map');
this._sourceMapByURL.set(editResult.map.url(), editResult.map);
this.dispatchEventToListeners(
SDK.SourceMapManager.Events.SourceMapChanged, {sourceMap: editResult.map, newSources: editResult.newSources});
}
/**
* @param {string} sourceURL
* @param {string} sourceMapURL
* @return {!{sourceURL: ?string, sourceMapURL: ?string}}
*/
_resolveRelativeURLs(sourceURL, sourceMapURL) {
// |sourceURL| can be a random string, but is generally an absolute path.
// Complete it to inspected page url for relative links.
const resolvedSourceURL = Common.ParsedURL.completeURL(this._target.inspectedURL(), sourceURL);
const resolvedSourceMapURL =
resolvedSourceURL ? Common.ParsedURL.completeURL(resolvedSourceURL, sourceMapURL) : null;
return {sourceURL: resolvedSourceURL, sourceMapURL: resolvedSourceMapURL};
}
/**
* @param {!T} client
* @param {string} sourceURL
* @param {?string} sourceMapURL
*/
attachSourceMap(client, sourceURL, sourceMapURL) {
if (!sourceMapURL)
return;
console.assert(!this._resolvedSourceMapURL.has(client), 'SourceMap is already attached to client');
const resolvedURLs = this._resolveRelativeURLs(sourceURL, sourceMapURL);
if (!resolvedURLs.sourceURL || !resolvedURLs.sourceMapURL)
return;
this._relativeSourceURL.set(client, sourceURL);
this._relativeSourceMapURL.set(client, sourceMapURL);
this._resolvedSourceMapURL.set(client, resolvedURLs.sourceMapURL);
sourceURL = resolvedURLs.sourceURL;
sourceMapURL = resolvedURLs.sourceMapURL;
if (!this._isEnabled)
return;
this.dispatchEventToListeners(SDK.SourceMapManager.Events.SourceMapWillAttach, client);
if (this._sourceMapByURL.has(sourceMapURL)) {
attach.call(this, sourceMapURL, client);
return;
}
if (!this._sourceMapURLToLoadingClients.has(sourceMapURL)) {
SDK.TextSourceMap.load(sourceMapURL, sourceURL)
.then(onTextSourceMapLoaded.bind(this, sourceMapURL))
.then(onSourceMap.bind(this, sourceMapURL));
}
this._sourceMapURLToLoadingClients.set(sourceMapURL, client);
/**
* @param {string} sourceMapURL
* @param {?SDK.TextSourceMap} sourceMap
* @return {!Promise<?SDK.SourceMap>}
* @this {SDK.SourceMapManager}
*/
function onTextSourceMapLoaded(sourceMapURL, sourceMap) {
if (!sourceMap)
return Promise.resolve(/** @type {?SDK.SourceMap} */ (null));
const factoryExtension = this._factoryForSourceMap(sourceMap);
if (!factoryExtension)
return Promise.resolve(/** @type {?SDK.SourceMap} */ (sourceMap));
return factoryExtension.instance()
.then(factory => factory.editableSourceMap(this._target, sourceMap))
.then(map => map || sourceMap)
.catchException(/** @type {?SDK.SourceMap} */ (null));
}
/**
* @param {string} sourceMapURL
* @param {?SDK.SourceMap} sourceMap
* @this {SDK.SourceMapManager}
*/
function onSourceMap(sourceMapURL, sourceMap) {
this._sourceMapLoadedForTest();
const clients = this._sourceMapURLToLoadingClients.get(sourceMapURL);
this._sourceMapURLToLoadingClients.deleteAll(sourceMapURL);
if (!clients.size)
return;
if (!sourceMap) {
for (const client of clients)
this.dispatchEventToListeners(SDK.SourceMapManager.Events.SourceMapFailedToAttach, client);
return;
}
this._sourceMapByURL.set(sourceMapURL, sourceMap);
for (const client of clients)
attach.call(this, sourceMapURL, client);
}
/**
* @param {string} sourceMapURL
* @param {!T} client
* @this {SDK.SourceMapManager}
*/
function attach(sourceMapURL, client) {
this._sourceMapURLToClients.set(sourceMapURL, client);
const sourceMap = this._sourceMapByURL.get(sourceMapURL);
this.dispatchEventToListeners(
SDK.SourceMapManager.Events.SourceMapAttached, {client: client, sourceMap: sourceMap});
}
}
/**
* @param {!SDK.SourceMap} sourceMap
* @return {?Runtime.Extension}
*/
_factoryForSourceMap(sourceMap) {
const sourceExtensions = new Set();
for (const url of sourceMap.sourceURLs())
sourceExtensions.add(Common.ParsedURL.extractExtension(url));
for (const runtimeExtension of self.runtime.extensions(SDK.SourceMapFactory)) {
const supportedExtensions = new Set(runtimeExtension.descriptor()['extensions']);
if (supportedExtensions.containsAll(sourceExtensions))
return runtimeExtension;
}
return null;
}
/**
* @param {!T} client
*/
detachSourceMap(client) {
const sourceMapURL = this._resolvedSourceMapURL.get(client);
this._relativeSourceURL.delete(client);
this._relativeSourceMapURL.delete(client);
this._resolvedSourceMapURL.delete(client);
if (!sourceMapURL)
return;
if (!this._sourceMapURLToClients.hasValue(sourceMapURL, client)) {
if (this._sourceMapURLToLoadingClients.delete(sourceMapURL, client))
this.dispatchEventToListeners(SDK.SourceMapManager.Events.SourceMapFailedToAttach, client);
return;
}
this._sourceMapURLToClients.delete(sourceMapURL, client);
const sourceMap = this._sourceMapByURL.get(sourceMapURL);
if (!this._sourceMapURLToClients.has(sourceMapURL))
this._sourceMapByURL.delete(sourceMapURL);
this.dispatchEventToListeners(
SDK.SourceMapManager.Events.SourceMapDetached, {client: client, sourceMap: sourceMap});
}
_sourceMapLoadedForTest() {
}
dispose() {
SDK.targetManager.removeEventListener(
SDK.TargetManager.Events.InspectedURLChanged, this._inspectedURLChanged, this);
}
};
SDK.SourceMapManager.Events = {
SourceMapWillAttach: Symbol('SourceMapWillAttach'),
SourceMapFailedToAttach: Symbol('SourceMapFailedToAttach'),
SourceMapAttached: Symbol('SourceMapAttached'),
SourceMapDetached: Symbol('SourceMapDetached'),
SourceMapChanged: Symbol('SourceMapChanged')
};