/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict";
/** * A simple service to track source actors and keep a mapping between * original URLs and objects holding the source or style actor's ID * (which is used as a cookie by the devtools-source-map service) and * the source map URL. * * @param {object} commands * The commands object with all interfaces defined from devtools/shared/commands/ * @param {SourceMapLoader} sourceMapLoader * The source-map-loader implemented in devtools/client/shared/source-map-loader/
*/ class SourceMapURLService {
constructor(commands, sourceMapLoader) { this._commands = commands; this._sourceMapLoader = sourceMapLoader;
this._prefValue = Services.prefs.getBoolPref(SOURCE_MAP_PREF); this._pendingIDSubscriptions = new Map(); this._pendingURLSubscriptions = new Map(); this._urlToIDMap = new Map(); this._mapsById = new Map(); this._sourcesLoading = null; this._onResourceAvailable = this._onResourceAvailable.bind(this); this._runningCallback = false;
// If a tool has changed or introduced a source map // (e.g, by pretty-printing a source), tell the // source map URL service about the change, so that // subscribers to that service can be updated as // well. this._sourceMapLoader.on( "source-map-created", this.newSourceMapCreated.bind(this)
);
}
const { resourceCommand } = this._commands; try {
resourceCommand.unwatchResources(
[
resourceCommand.TYPES.STYLESHEET,
resourceCommand.TYPES.SOURCE,
resourceCommand.TYPES.DOCUMENT_EVENT,
],
{ onAvailable: this._onResourceAvailable }
);
} catch (e) { // If unwatchResources is called before finishing process of watchResources, // it throws an error during stopping listener.
}
/** * Subscribe to notifications about the original location of a given * generated location, as it may not be known at this time, may become * available at some unknown time in the future, or may change from one * location to another. * * @param {string} id The actor ID of the source. * @param {number} line The line number in the source. * @param {number} column The column number in the source. * @param {Function} callback A callback that may eventually be passed an * an object with url/line/column properties specifying a location in * the original file, or null if no particular original location could * be found. The callback will run synchronously if the location is * already know to the URL service. * * @return {Function} A function to call to remove this subscription. The * "callback" argument is guaranteed to never run once unsubscribed.
*/
subscribeByID(id, line, column, callback) { this._ensureAllSourcesPopulated();
let pending = this._pendingIDSubscriptions.get(id); if (!pending) {
pending = new Set(); this._pendingIDSubscriptions.set(id, pending);
} const entry = {
line,
column,
callback,
unsubscribed: false,
owner: pending,
};
pending.add(entry);
const map = this._mapsById.get(id); if (map) { this._flushPendingIDSubscriptionsToMapQueries(map);
}
/** * Subscribe to notifications about the original location of a given * generated location, as it may not be known at this time, may become * available at some unknown time in the future, or may change from one * location to another. * * @param {string} id The actor ID of the source. * @param {number} line The line number in the source. * @param {number} column The column number in the source. * @param {Function} callback A callback that may eventually be passed an * an object with url/line/column properties specifying a location in * the original file, or null if no particular original location could * be found. The callback will run synchronously if the location is * already know to the URL service. * * @return {Function} A function to call to remove this subscription. The * "callback" argument is guaranteed to never run once unsubscribed.
*/
subscribeByURL(url, line, column, callback) { this._ensureAllSourcesPopulated();
let pending = this._pendingURLSubscriptions.get(url); if (!pending) {
pending = new Set(); this._pendingURLSubscriptions.set(url, pending);
} const entry = {
line,
column,
callback,
unsubscribed: false,
owner: pending,
};
pending.add(entry);
const id = this._urlToIDMap.get(url); if (id) { this._convertPendingURLSubscriptionsToID(url, id); const map = this._mapsById.get(id); if (map) { this._flushPendingIDSubscriptionsToMapQueries(map);
}
}
/** * Subscribe generically based on either an ID or a URL. * * In an ideal world we'd always know which of these to use, but there are * still cases where end up with a mixture of both, so this is provided as * a helper. If you can specifically use one of these, please do that * instead however.
*/
subscribeByLocation({ id, url, line, column }, callback) { if (id) { returnthis.subscribeByID(id, line, column, callback);
}
/** * Tell the URL service than some external entity has registered a sourcemap * in the worker for one of the source files. * * @param {Array<string>} ids The actor ids of the sources that had the map registered.
*/
async newSourceMapCreated(ids) {
await this._ensureAllSourcesPopulated();
for (const id of ids) { const map = this._mapsById.get(id); if (!map) { // State could have been cleared. continue;
}
map.loaded = Promise.resolve(); for (const query of map.queries.values()) {
query.action = null;
query.result = null; if (this._prefValue) { this._dispatchQuery(query);
}
}
}
}
_dispatchQuery(query) { if (!this._prefValue) { thrownew Error("This function should only be called if the pref is on.");
}
if (!query.action) { const { map } = query;
// Call getOriginalURLs to make sure the source map has been // fetched. We don't actually need the result of this though. if (!map.loaded) {
map.loaded = this._sourceMapLoader.getOriginalURLs({
id: map.id,
url: map.url,
sourceMapBaseURL: map.sourceMapBaseURL,
sourceMapURL: map.sourceMapURL,
});
}
const action = (async () => {
let result = null; try {
await map.loaded;
} catch (e) { // SourceMapLoader.getOriginalURLs may throw, but it will handle // the exception and notify the user via a console message. // So ignore the exception here, which is meant to be used by the Debugger.
}
try { const position = await this._sourceMapLoader.getOriginalLocation({
sourceId: map.id,
line: query.line,
column: query.column,
}); if (position && position.sourceId !== map.id) {
result = {
url: position.sourceUrl,
line: position.line,
column: position.column,
};
}
} finally { // If this action was dispatched and then the file was pretty-printed // we want to ignore the result since the query has restarted. if (action === query.action) { // It is important that we consistently set the query result and // trigger the subscribers here in order to maintain the invariant // that if 'result' is truthy, then the subscribers will have run. const position = result;
query.result = { position }; this._ensureSubscribersSynchronized(query);
}
}
})();
query.action = action;
}
this._ensureSubscribersSynchronized(query);
}
_ensureSubscribersSynchronized(query) { // Synchronize the subscribers with the pref-disabled state if they need it. if (!this._prefValue) { if (query.mostRecentEmitted) {
query.mostRecentEmitted = null; this._dispatchSubscribers(null, query.subscribers);
} return;
}
// Synchronize the subscribers with the newest computed result if they // need it. const { result } = query; if (result && query.mostRecentEmitted !== result.position) {
query.mostRecentEmitted = result.position; this._dispatchSubscribers(result.position, query.subscribers);
}
}
_dispatchSubscribers(position, subscribers) { // We copy the subscribers before iterating because something could be // removed while we're calling the callbacks, which is also why we check // the 'unsubscribed' flag. for (const subscriber of Array.from(subscribers)) { if (subscriber.unsubscribed) { continue;
}
if (this._runningCallback) {
console.error( "The source map url service does not support reentrant subscribers."
); continue;
}
if (query.mostRecentEmitted) { // Maintain the invariant that if a query has emitted a value, then // _all_ subscribers will have received that value. this._dispatchSubscribers(query.mostRecentEmitted, [entry]);
}
if (this._prefValue) { this._dispatchQuery(query);
}
}
}
_ensureAllSourcesPopulated() { if (!this._prefValue || this._commands.descriptorFront.isWorkerDescriptor) { returnnull;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.