Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/devtools/client/framework/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 14 kB image not shown  

Quelle  source-map-url-service.js   Sprache: JAVA

 
/* 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";

const SOURCE_MAP_PREF = "devtools.source-map.client-service.enabled";

/**
 * 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;

    this._syncPrevValue = this._syncPrevValue.bind(this);
    this._clearAllState = this._clearAllState.bind(this);

    Services.prefs.addObserver(SOURCE_MAP_PREF, this._syncPrevValue);

    // 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)
    );
  }

  destroy() {
    Services.prefs.removeObserver(SOURCE_MAP_PREF, this._syncPrevValue);

    this._clearAllState();

    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.
    }

    this._sourcesLoading = null;
    this._pendingIDSubscriptions = null;
    this._pendingURLSubscriptions = null;
    this._urlToIDMap = null;
    this._mapsById = null;
  }

  /**
   * 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);
    }

    return () => {
      entry.unsubscribed = true;
      entry.owner.delete(entry);
    };
  }

  /**
   * 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);
      }
    }

    return () => {
      entry.unsubscribed = true;
      entry.owner.delete(entry);
    };
  }

  /**
   * 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) {
      return this.subscribeByID(id, line, column, callback);
    }

    return this.subscribeByURL(url, 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);
        }
      }
    }
  }

  _syncPrevValue() {
    this._prefValue = Services.prefs.getBoolPref(SOURCE_MAP_PREF);

    for (const map of this._mapsById.values()) {
      for (const query of map.queries.values()) {
        this._ensureSubscribersSynchronized(query);
      }
    }
  }

  _clearAllState() {
    this._sourceMapLoader.clearSourceMaps();
    this._pendingIDSubscriptions.clear();
    this._pendingURLSubscriptions.clear();
    this._urlToIDMap.clear();
    this._mapsById.clear();
  }

  _onNewJavascript(source) {
    const { url, actor: id, sourceMapBaseURL, sourceMapURL } = source;

    this._onNewSource(id, url, sourceMapURL, sourceMapBaseURL);
  }

  _onNewStyleSheet(sheet) {
    const {
      href,
      nodeHref,
      sourceMapBaseURL,
      sourceMapURL,
      resourceId: id,
    } = sheet;
    const url = href || nodeHref;

    this._onNewSource(id, url, sourceMapURL, sourceMapBaseURL);
  }

  _onNewSource(id, url, sourceMapURL, sourceMapBaseURL) {
    this._urlToIDMap.set(url, id);
    this._convertPendingURLSubscriptionsToID(url, id);

    let map = this._mapsById.get(id);
    if (!map) {
      map = {
        id,
        url,
        sourceMapURL,
        sourceMapBaseURL,
        loaded: null,
        queries: new Map(),
      };
      this._mapsById.set(id, map);
    } else if (
      map.id !== id &&
      map.url !== url &&
      map.sourceMapURL !== sourceMapURL &&
      map.sourceMapBaseURL !== sourceMapBaseURL
    ) {
      console.warn(
        `Attempted to load populate sourcemap for source ${id} multiple times`
      );
    }

    this._flushPendingIDSubscriptionsToMapQueries(map);
  }

  _buildQuery(map, line, column) {
    const key = `${line}:${column}`;
    let query = map.queries.get(key);
    if (!query) {
      query = {
        map,
        line,
        column,
        subscribers: new Set(),
        action: null,
        result: null,
        mostRecentEmitted: null,
      };
      map.queries.set(key, query);
    }
    return query;
  }

  _dispatchQuery(query) {
    if (!this._prefValue) {
      throw new 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;
      }

      try {
        this._runningCallback = true;

        const { callback } = subscriber;
        callback(position ? { ...position } : null);
      } catch (err) {
        console.error("Error in source map url service subscriber", err);
      } finally {
        this._runningCallback = false;
      }
    }
  }

  _flushPendingIDSubscriptionsToMapQueries(map) {
    const subscriptions = this._pendingIDSubscriptions.get(map.id);
    if (!subscriptions || subscriptions.size === 0) {
      return;
    }
    this._pendingIDSubscriptions.delete(map.id);

    for (const entry of subscriptions) {
      const query = this._buildQuery(map, entry.line, entry.column);

      const { subscribers } = query;

      entry.owner = subscribers;
      subscribers.add(entry);

      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) {
      return null;
    }

    if (!this._sourcesLoading) {
      const { resourceCommand } = this._commands;
      const { STYLESHEET, SOURCE, DOCUMENT_EVENT } = resourceCommand.TYPES;

      const onResources = resourceCommand.watchResources(
        [STYLESHEET, SOURCE, DOCUMENT_EVENT],
        {
          onAvailable: this._onResourceAvailable,
        }
      );
      this._sourcesLoading = onResources;
    }

    return this._sourcesLoading;
  }

  waitForSourcesLoading() {
    if (this._sourcesLoading) {
      return this._sourcesLoading;
    }
    return Promise.resolve();
  }

  _onResourceAvailable(resources) {
    const { resourceCommand } = this._commands;
    const { STYLESHEET, SOURCE, DOCUMENT_EVENT } = resourceCommand.TYPES;
    for (const resource of resources) {
      // Only consider top level document, and ignore remote iframes top document
      if (
        resource.resourceType == DOCUMENT_EVENT &&
        resource.name == "will-navigate" &&
        resource.targetFront.isTopLevel
      ) {
        this._clearAllState();
      } else if (resource.resourceType == STYLESHEET) {
        this._onNewStyleSheet(resource);
      } else if (resource.resourceType == SOURCE) {
        this._onNewJavascript(resource);
      }
    }
  }

  _convertPendingURLSubscriptionsToID(url, id) {
    const urlSubscriptions = this._pendingURLSubscriptions.get(url);
    if (!urlSubscriptions) {
      return;
    }
    this._pendingURLSubscriptions.delete(url);

    let pending = this._pendingIDSubscriptions.get(id);
    if (!pending) {
      pending = new Set();
      this._pendingIDSubscriptions.set(id, pending);
    }
    for (const entry of urlSubscriptions) {
      entry.owner = pending;
      pending.add(entry);
    }
  }
}

exports.SourceMapURLService = SourceMapURLService;

96%


¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.