| // 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 |
| */ |
| export default class SourceMapManager 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._resolvedSourceMapId = new Map(); |
| |
| /** @type {!Map<string, !SDK.SourceMap>} */ |
| this._sourceMapById = new Map(); |
| /** @type {!Platform.Multimap<string, !T>} */ |
| this._sourceMapIdToLoadingClients = new Platform.Multimap(); |
| /** @type {!Platform.Multimap<string, !T>} */ |
| this._sourceMapIdToClients = new Platform.Multimap(); |
| |
| SDK.targetManager.addEventListener(SDK.TargetManager.Events.InspectedURLChanged, this._inspectedURLChanged, this); |
| } |
| |
| /** |
| * @param {boolean} isEnabled |
| */ |
| setEnabled(isEnabled) { |
| if (isEnabled === this._isEnabled) { |
| return; |
| } |
| this._isEnabled = isEnabled; |
| // We need this copy, because `this._resolvedSourceMapId` is getting modified |
| // in the loop body and trying to iterate over it at the same time leads to |
| // an infinite loop. |
| const clients = [...this._resolvedSourceMapId.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; |
| } |
| |
| // We need this copy, because `this._resolvedSourceMapId` is getting modified |
| // in the loop body and trying to iterate over it at the same time leads to |
| // an infinite loop. |
| const prevSourceMapIds = new Map(this._resolvedSourceMapId); |
| for (const [client, prevSourceMapId] of prevSourceMapIds) { |
| const relativeSourceURL = this._relativeSourceURL.get(client); |
| const relativeSourceMapURL = this._relativeSourceMapURL.get(client); |
| const {sourceMapId} = this._resolveRelativeURLs(relativeSourceURL, relativeSourceMapURL); |
| if (prevSourceMapId !== sourceMapId) { |
| this.detachSourceMap(client); |
| this.attachSourceMap(client, relativeSourceURL, relativeSourceMapURL); |
| } |
| } |
| } |
| |
| /** |
| * @param {!T} client |
| * @return {?SDK.SourceMap} |
| */ |
| sourceMapForClient(client) { |
| const sourceMapId = this._resolvedSourceMapId.get(client); |
| if (!sourceMapId) { |
| return null; |
| } |
| return this._sourceMapById.get(sourceMapId) || null; |
| } |
| |
| /** |
| * @param {!SDK.SourceMap} sourceMap |
| * @return {!Array<!T>} |
| */ |
| clientsForSourceMap(sourceMap) { |
| const sourceMapId = this._getSourceMapId(sourceMap.compiledURL(), sourceMap.url()); |
| if (this._sourceMapIdToClients.has(sourceMapId)) { |
| return this._sourceMapIdToClients.get(sourceMapId).valuesArray(); |
| } |
| return this._sourceMapIdToLoadingClients.get(sourceMapId).valuesArray(); |
| } |
| |
| /** |
| * @param {string} sourceURL |
| * @param {string} sourceMapURL |
| */ |
| _getSourceMapId(sourceURL, sourceMapURL) { |
| return `${sourceURL}:${sourceMapURL}`; |
| } |
| |
| /** |
| * @param {string} sourceURL |
| * @param {string} sourceMapURL |
| * @return {?{sourceURL: string, sourceMapURL: string, sourceMapId: 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); |
| if (!resolvedSourceURL) { |
| return null; |
| } |
| const resolvedSourceMapURL = Common.ParsedURL.completeURL(resolvedSourceURL, sourceMapURL); |
| if (!resolvedSourceMapURL) { |
| return null; |
| } |
| return { |
| sourceURL: resolvedSourceURL, |
| sourceMapURL: resolvedSourceMapURL, |
| sourceMapId: this._getSourceMapId(resolvedSourceURL, resolvedSourceMapURL) |
| }; |
| } |
| |
| /** |
| * @param {!T} client |
| * @param {string} relativeSourceURL |
| * @param {?string} relativeSourceMapURL |
| */ |
| attachSourceMap(client, relativeSourceURL, relativeSourceMapURL) { |
| if (!relativeSourceMapURL) { |
| return; |
| } |
| console.assert(!this._resolvedSourceMapId.has(client), 'SourceMap is already attached to client'); |
| const resolvedURLs = this._resolveRelativeURLs(relativeSourceURL, relativeSourceMapURL); |
| if (!resolvedURLs) { |
| return; |
| } |
| this._relativeSourceURL.set(client, relativeSourceURL); |
| this._relativeSourceMapURL.set(client, relativeSourceMapURL); |
| |
| const {sourceURL, sourceMapURL, sourceMapId} = resolvedURLs; |
| this._resolvedSourceMapId.set(client, sourceMapId); |
| |
| if (!this._isEnabled) { |
| return; |
| } |
| |
| this.dispatchEventToListeners(Events.SourceMapWillAttach, client); |
| |
| if (this._sourceMapById.has(sourceMapId)) { |
| attach.call(this, sourceMapId, client); |
| return; |
| } |
| if (!this._sourceMapIdToLoadingClients.has(sourceMapId)) { |
| const sourceMapPromise = sourceMapURL === SDK.WasmSourceMap.FAKE_URL ? |
| SDK.WasmSourceMap.load(client, sourceURL) : |
| SDK.TextSourceMap.load(sourceMapURL, sourceURL); |
| |
| sourceMapPromise |
| .catch(error => { |
| Common.console.warn(ls`DevTools failed to load SourceMap: ${error.message}`); |
| }) |
| .then(onSourceMap.bind(this, sourceMapId)); |
| } |
| this._sourceMapIdToLoadingClients.set(sourceMapId, client); |
| |
| /** |
| * @param {string} sourceMapId |
| * @param {?SDK.SourceMap} sourceMap |
| * @this {SourceMapManager} |
| */ |
| function onSourceMap(sourceMapId, sourceMap) { |
| this._sourceMapLoadedForTest(); |
| const clients = this._sourceMapIdToLoadingClients.get(sourceMapId); |
| this._sourceMapIdToLoadingClients.deleteAll(sourceMapId); |
| if (!clients.size) { |
| return; |
| } |
| if (!sourceMap) { |
| for (const client of clients) { |
| this.dispatchEventToListeners(Events.SourceMapFailedToAttach, client); |
| } |
| return; |
| } |
| this._sourceMapById.set(sourceMapId, sourceMap); |
| for (const client of clients) { |
| attach.call(this, sourceMapId, client); |
| } |
| } |
| |
| /** |
| * @param {string} sourceMapId |
| * @param {!T} client |
| * @this {SourceMapManager} |
| */ |
| function attach(sourceMapId, client) { |
| this._sourceMapIdToClients.set(sourceMapId, client); |
| const sourceMap = this._sourceMapById.get(sourceMapId); |
| this.dispatchEventToListeners(Events.SourceMapAttached, {client: client, sourceMap: sourceMap}); |
| } |
| } |
| |
| /** |
| * @param {!T} client |
| */ |
| detachSourceMap(client) { |
| const sourceMapId = this._resolvedSourceMapId.get(client); |
| this._relativeSourceURL.delete(client); |
| this._relativeSourceMapURL.delete(client); |
| this._resolvedSourceMapId.delete(client); |
| |
| if (!sourceMapId) { |
| return; |
| } |
| if (!this._sourceMapIdToClients.hasValue(sourceMapId, client)) { |
| if (this._sourceMapIdToLoadingClients.delete(sourceMapId, client)) { |
| this.dispatchEventToListeners(Events.SourceMapFailedToAttach, client); |
| } |
| return; |
| } |
| this._sourceMapIdToClients.delete(sourceMapId, client); |
| const sourceMap = this._sourceMapById.get(sourceMapId); |
| this.dispatchEventToListeners(Events.SourceMapDetached, {client: client, sourceMap: sourceMap}); |
| if (!this._sourceMapIdToClients.has(sourceMapId)) { |
| sourceMap.dispose(); |
| this._sourceMapById.delete(sourceMapId); |
| } |
| } |
| |
| _sourceMapLoadedForTest() { |
| } |
| |
| dispose() { |
| for (const sourceMap of this._sourceMapById.values()) { |
| sourceMap.dispose(); |
| } |
| SDK.targetManager.removeEventListener( |
| SDK.TargetManager.Events.InspectedURLChanged, this._inspectedURLChanged, this); |
| } |
| } |
| |
| export const Events = { |
| SourceMapWillAttach: Symbol('SourceMapWillAttach'), |
| SourceMapFailedToAttach: Symbol('SourceMapFailedToAttach'), |
| SourceMapAttached: Symbol('SourceMapAttached'), |
| SourceMapDetached: Symbol('SourceMapDetached'), |
| SourceMapChanged: Symbol('SourceMapChanged') |
| }; |
| |
| /* Legacy exported object */ |
| self.SDK = self.SDK || {}; |
| |
| /* Legacy exported object */ |
| SDK = SDK || {}; |
| |
| /** @constructor */ |
| SDK.SourceMapManager = SourceMapManager; |
| |
| SDK.SourceMapManager.Events = Events; |