summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/utils/TabSources.js
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/server/actors/utils/TabSources.js
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/server/actors/utils/TabSources.js')
-rw-r--r--devtools/server/actors/utils/TabSources.js833
1 files changed, 833 insertions, 0 deletions
diff --git a/devtools/server/actors/utils/TabSources.js b/devtools/server/actors/utils/TabSources.js
new file mode 100644
index 000000000..56e862939
--- /dev/null
+++ b/devtools/server/actors/utils/TabSources.js
@@ -0,0 +1,833 @@
+/* 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 { Ci, Cu } = require("chrome");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { assert, fetch } = DevToolsUtils;
+const EventEmitter = require("devtools/shared/event-emitter");
+const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
+const { resolve } = require("promise");
+const { joinURI } = require("devtools/shared/path");
+
+loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/source", true);
+loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/source", true);
+loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
+loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
+
+/**
+ * Manages the sources for a thread. Handles source maps, locations in the
+ * sources, etc for ThreadActors.
+ */
+function TabSources(threadActor, allowSourceFn = () => true) {
+ EventEmitter.decorate(this);
+
+ this._thread = threadActor;
+ this._useSourceMaps = true;
+ this._autoBlackBox = true;
+ this._anonSourceMapId = 1;
+ this.allowSource = source => {
+ return !isHiddenSource(source) && allowSourceFn(source);
+ };
+
+ this.blackBoxedSources = new Set();
+ this.prettyPrintedSources = new Map();
+ this.neverAutoBlackBoxSources = new Set();
+
+ // generated Debugger.Source -> promise of SourceMapConsumer
+ this._sourceMaps = new Map();
+ // sourceMapURL -> promise of SourceMapConsumer
+ this._sourceMapCache = Object.create(null);
+ // Debugger.Source -> SourceActor
+ this._sourceActors = new Map();
+ // url -> SourceActor
+ this._sourceMappedSourceActors = Object.create(null);
+}
+
+/**
+ * Matches strings of the form "foo.min.js" or "foo-min.js", etc. If the regular
+ * expression matches, we can be fairly sure that the source is minified, and
+ * treat it as such.
+ */
+const MINIFIED_SOURCE_REGEXP = /\bmin\.js$/;
+
+TabSources.prototype = {
+ /**
+ * Update preferences and clear out existing sources
+ */
+ setOptions: function (options) {
+ let shouldReset = false;
+
+ if ("useSourceMaps" in options) {
+ shouldReset = true;
+ this._useSourceMaps = options.useSourceMaps;
+ }
+
+ if ("autoBlackBox" in options) {
+ shouldReset = true;
+ this._autoBlackBox = options.autoBlackBox;
+ }
+
+ if (shouldReset) {
+ this.reset();
+ }
+ },
+
+ /**
+ * Clear existing sources so they are recreated on the next access.
+ *
+ * @param Object opts
+ * Specify { sourceMaps: true } if you also want to clear
+ * the source map cache (usually done on reload).
+ */
+ reset: function (opts = {}) {
+ this._sourceActors = new Map();
+ this._sourceMaps = new Map();
+ this._sourceMappedSourceActors = Object.create(null);
+
+ if (opts.sourceMaps) {
+ this._sourceMapCache = Object.create(null);
+ }
+ },
+
+ /**
+ * Return the source actor representing the `source` (or
+ * `originalUrl`), creating one if none exists already. May return
+ * null if the source is disallowed.
+ *
+ * @param Debugger.Source source
+ * The source to make an actor for
+ * @param String originalUrl
+ * The original source URL of a sourcemapped source
+ * @param optional Debguger.Source generatedSource
+ * The generated source that introduced this source via source map,
+ * if any.
+ * @param optional String contentType
+ * The content type of the source, if immediately available.
+ * @returns a SourceActor representing the source or null.
+ */
+ source: function ({ source, originalUrl, generatedSource,
+ isInlineSource, contentType }) {
+ assert(source || (originalUrl && generatedSource),
+ "TabSources.prototype.source needs an originalUrl or a source");
+
+ if (source) {
+ // If a source is passed, we are creating an actor for a real
+ // source, which may or may not be sourcemapped.
+
+ if (!this.allowSource(source)) {
+ return null;
+ }
+
+ // It's a hack, but inline HTML scripts each have real sources,
+ // but we want to represent all of them as one source as the
+ // HTML page. The actor representing this fake HTML source is
+ // stored in this array, which always has a URL, so check it
+ // first.
+ if (source.url in this._sourceMappedSourceActors) {
+ return this._sourceMappedSourceActors[source.url];
+ }
+
+ if (isInlineSource) {
+ // If it's an inline source, the fake HTML source hasn't been
+ // created yet (would have returned above), so flip this source
+ // into a sourcemapped state by giving it an `originalUrl` which
+ // is the HTML url.
+ originalUrl = source.url;
+ source = null;
+ }
+ else if (this._sourceActors.has(source)) {
+ return this._sourceActors.get(source);
+ }
+ }
+ else if (originalUrl) {
+ // Not all "original" scripts are distinctly separate from the
+ // generated script. Pretty-printed sources have a sourcemap for
+ // themselves, so we need to make sure there a real source
+ // doesn't already exist with this URL.
+ for (let [source, actor] of this._sourceActors) {
+ if (source.url === originalUrl) {
+ return actor;
+ }
+ }
+
+ if (originalUrl in this._sourceMappedSourceActors) {
+ return this._sourceMappedSourceActors[originalUrl];
+ }
+ }
+
+ let actor = new SourceActor({
+ thread: this._thread,
+ source: source,
+ originalUrl: originalUrl,
+ generatedSource: generatedSource,
+ isInlineSource: isInlineSource,
+ contentType: contentType
+ });
+
+ let sourceActorStore = this._thread.sourceActorStore;
+ var id = sourceActorStore.getReusableActorId(source, originalUrl);
+ if (id) {
+ actor.actorID = id;
+ }
+
+ this._thread.threadLifetimePool.addActor(actor);
+ sourceActorStore.setReusableActorId(source, originalUrl, actor.actorID);
+
+ if (this._autoBlackBox &&
+ !this.neverAutoBlackBoxSources.has(actor.url) &&
+ this._isMinifiedURL(actor.url)) {
+
+ this.blackBox(actor.url);
+ this.neverAutoBlackBoxSources.add(actor.url);
+ }
+
+ if (source) {
+ this._sourceActors.set(source, actor);
+ }
+ else {
+ this._sourceMappedSourceActors[originalUrl] = actor;
+ }
+
+ this._emitNewSource(actor);
+ return actor;
+ },
+
+ _emitNewSource: function (actor) {
+ if (!actor.source) {
+ // Always notify if we don't have a source because that means
+ // it's something that has been sourcemapped, or it represents
+ // the HTML file that contains inline sources.
+ this.emit("newSource", actor);
+ }
+ else {
+ // If sourcemapping is enabled and a source has sourcemaps, we
+ // create `SourceActor` instances for both the original and
+ // generated sources. The source actors for the generated
+ // sources are only for internal use, however; breakpoints are
+ // managed by these internal actors. We only want to notify the
+ // user of the original sources though, so if the actor has a
+ // `Debugger.Source` instance and a valid source map (meaning
+ // it's a generated source), don't send the notification.
+ this.fetchSourceMap(actor.source).then(map => {
+ if (!map) {
+ this.emit("newSource", actor);
+ }
+ });
+ }
+ },
+
+ getSourceActor: function (source) {
+ if (source.url in this._sourceMappedSourceActors) {
+ return this._sourceMappedSourceActors[source.url];
+ }
+
+ if (this._sourceActors.has(source)) {
+ return this._sourceActors.get(source);
+ }
+
+ throw new Error("getSource: could not find source actor for " +
+ (source.url || "source"));
+ },
+
+ getSourceActorByURL: function (url) {
+ if (url) {
+ for (let [source, actor] of this._sourceActors) {
+ if (source.url === url) {
+ return actor;
+ }
+ }
+
+ if (url in this._sourceMappedSourceActors) {
+ return this._sourceMappedSourceActors[url];
+ }
+ }
+
+ throw new Error("getSourceActorByURL: could not find source for " + url);
+ return null;
+ },
+
+ /**
+ * Returns true if the URL likely points to a minified resource, false
+ * otherwise.
+ *
+ * @param String aURL
+ * The URL to test.
+ * @returns Boolean
+ */
+ _isMinifiedURL: function (aURL) {
+ if (!aURL) {
+ return false;
+ }
+
+ try {
+ let url = new URL(aURL);
+ let pathname = url.pathname;
+ return MINIFIED_SOURCE_REGEXP.test(pathname.slice(pathname.lastIndexOf("/") + 1));
+ } catch (e) {
+ // Not a valid URL so don't try to parse out the filename, just test the
+ // whole thing with the minified source regexp.
+ return MINIFIED_SOURCE_REGEXP.test(aURL);
+ }
+ },
+
+ /**
+ * Create a source actor representing this source. This ignores
+ * source mapping and always returns an actor representing this real
+ * source. Use `createSourceActors` if you want to respect source maps.
+ *
+ * @param Debugger.Source aSource
+ * The source instance to create an actor for.
+ * @returns SourceActor
+ */
+ createNonSourceMappedActor: function (aSource) {
+ // Don't use getSourceURL because we don't want to consider the
+ // displayURL property if it's an eval source. We only want to
+ // consider real URLs, otherwise if there is a URL but it's
+ // invalid the code below will not set the content type, and we
+ // will later try to fetch the contents of the URL to figure out
+ // the content type, but it's a made up URL for eval sources.
+ let url = isEvalSource(aSource) ? null : aSource.url;
+ let spec = { source: aSource };
+
+ // XXX bug 915433: We can't rely on Debugger.Source.prototype.text
+ // if the source is an HTML-embedded <script> tag. Since we don't
+ // have an API implemented to detect whether this is the case, we
+ // need to be conservative and only treat valid js files as real
+ // sources. Otherwise, use the `originalUrl` property to treat it
+ // as an HTML source that manages multiple inline sources.
+
+ // Assume the source is inline if the element that introduced it is not a
+ // script element, or does not have a src attribute.
+ let element = aSource.element ? aSource.element.unsafeDereference() : null;
+ if (element && (element.tagName !== "SCRIPT" || !element.hasAttribute("src"))) {
+ spec.isInlineSource = true;
+ } else if (aSource.introductionType === "wasm") {
+ // Wasm sources are not JavaScript. Give them their own content-type.
+ spec.contentType = "text/wasm";
+ } else {
+ if (url) {
+ // There are a few special URLs that we know are JavaScript:
+ // inline `javascript:` and code coming from the console
+ if (url.indexOf("Scratchpad/") === 0 ||
+ url.indexOf("javascript:") === 0 ||
+ url === "debugger eval code") {
+ spec.contentType = "text/javascript";
+ } else {
+ try {
+ let pathname = new URL(url).pathname;
+ let filename = pathname.slice(pathname.lastIndexOf("/") + 1);
+ let index = filename.lastIndexOf(".");
+ let extension = index >= 0 ? filename.slice(index + 1) : "";
+ if (extension === "xml") {
+ // XUL inline scripts may not correctly have the
+ // `source.element` property, so do a blunt check here if
+ // it's an xml page.
+ spec.isInlineSource = true;
+ }
+ else if (extension === "js") {
+ spec.contentType = "text/javascript";
+ }
+ } catch (e) {
+ // This only needs to be here because URL is not yet exposed to
+ // workers. (BUG 1258892)
+ const filename = url;
+ const index = filename.lastIndexOf(".");
+ const extension = index >= 0 ? filename.slice(index + 1) : "";
+ if (extension === "js") {
+ spec.contentType = "text/javascript";
+ }
+ }
+ }
+ }
+ else {
+ // Assume the content is javascript if there's no URL
+ spec.contentType = "text/javascript";
+ }
+ }
+
+ return this.source(spec);
+ },
+
+ /**
+ * This is an internal function that returns a promise of an array
+ * of source actors representing all the source mapped sources of
+ * `aSource`, or `null` if the source is not sourcemapped or
+ * sourcemapping is disabled. Users should call `createSourceActors`
+ * instead of this.
+ *
+ * @param Debugger.Source aSource
+ * The source instance to create actors for.
+ * @return Promise of an array of source actors
+ */
+ _createSourceMappedActors: function (aSource) {
+ if (!this._useSourceMaps || !aSource.sourceMapURL) {
+ return resolve(null);
+ }
+
+ return this.fetchSourceMap(aSource)
+ .then(map => {
+ if (map) {
+ return map.sources.map(s => {
+ return this.source({ originalUrl: s, generatedSource: aSource });
+ }).filter(isNotNull);
+ }
+ return null;
+ });
+ },
+
+ /**
+ * Creates the source actors representing the appropriate sources
+ * of `aSource`. If sourcemapped, returns actors for all of the original
+ * sources, otherwise returns a 1-element array with the actor for
+ * `aSource`.
+ *
+ * @param Debugger.Source aSource
+ * The source instance to create actors for.
+ * @param Promise of an array of source actors
+ */
+ createSourceActors: function (aSource) {
+ return this._createSourceMappedActors(aSource).then(actors => {
+ let actor = this.createNonSourceMappedActor(aSource);
+ return (actors || [actor]).filter(isNotNull);
+ });
+ },
+
+ /**
+ * Return a promise of a SourceMapConsumer for the source map for
+ * `aSource`; if we already have such a promise extant, return that.
+ * This will fetch the source map if we don't have a cached object
+ * and source maps are enabled (see `_fetchSourceMap`).
+ *
+ * @param Debugger.Source aSource
+ * The source instance to get sourcemaps for.
+ * @return Promise of a SourceMapConsumer
+ */
+ fetchSourceMap: function (aSource) {
+ if (!this._useSourceMaps) {
+ return resolve(null);
+ }
+ else if (this._sourceMaps.has(aSource)) {
+ return this._sourceMaps.get(aSource);
+ }
+ else if (!aSource || !aSource.sourceMapURL) {
+ return resolve(null);
+ }
+
+ let sourceMapURL = aSource.sourceMapURL;
+ if (aSource.url) {
+ sourceMapURL = joinURI(aSource.url, sourceMapURL);
+ }
+ let result = this._fetchSourceMap(sourceMapURL, aSource.url);
+
+ // The promises in `_sourceMaps` must be the exact same instances
+ // as returned by `_fetchSourceMap` for `clearSourceMapCache` to
+ // work.
+ this._sourceMaps.set(aSource, result);
+ return result;
+ },
+
+ /**
+ * Return a promise of a SourceMapConsumer for the source map for
+ * `aSource`. The resolved result may be null if the source does not
+ * have a source map or source maps are disabled.
+ */
+ getSourceMap: function (aSource) {
+ return resolve(this._sourceMaps.get(aSource));
+ },
+
+ /**
+ * Set a SourceMapConsumer for the source map for
+ * |aSource|.
+ */
+ setSourceMap: function (aSource, aMap) {
+ this._sourceMaps.set(aSource, resolve(aMap));
+ },
+
+ /**
+ * Return a promise of a SourceMapConsumer for the source map located at
+ * |aAbsSourceMapURL|, which must be absolute. If there is already such a
+ * promise extant, return it. This will not fetch if source maps are
+ * disabled.
+ *
+ * @param string aAbsSourceMapURL
+ * The source map URL, in absolute form, not relative.
+ * @param string aScriptURL
+ * When the source map URL is a data URI, there is no sourceRoot on the
+ * source map, and the source map's sources are relative, we resolve
+ * them from aScriptURL.
+ */
+ _fetchSourceMap: function (aAbsSourceMapURL, aSourceURL) {
+ assert(this._useSourceMaps,
+ "Cannot fetch sourcemaps if they are disabled");
+
+ if (this._sourceMapCache[aAbsSourceMapURL]) {
+ return this._sourceMapCache[aAbsSourceMapURL];
+ }
+
+ let fetching = fetch(aAbsSourceMapURL, { loadFromCache: false })
+ .then(({ content }) => {
+ let map = new SourceMapConsumer(content);
+ this._setSourceMapRoot(map, aAbsSourceMapURL, aSourceURL);
+ return map;
+ })
+ .then(null, error => {
+ if (!DevToolsUtils.reportingDisabled) {
+ DevToolsUtils.reportException("TabSources.prototype._fetchSourceMap", error);
+ }
+ return null;
+ });
+ this._sourceMapCache[aAbsSourceMapURL] = fetching;
+ return fetching;
+ },
+
+ /**
+ * Sets the source map's sourceRoot to be relative to the source map url.
+ */
+ _setSourceMapRoot: function (aSourceMap, aAbsSourceMapURL, aScriptURL) {
+ // No need to do this fiddling if we won't be fetching any sources over the
+ // wire.
+ if (aSourceMap.hasContentsOfAllSources()) {
+ return;
+ }
+
+ const base = this._dirname(
+ aAbsSourceMapURL.indexOf("data:") === 0
+ ? aScriptURL
+ : aAbsSourceMapURL);
+ aSourceMap.sourceRoot = aSourceMap.sourceRoot
+ ? joinURI(base, aSourceMap.sourceRoot)
+ : base;
+ },
+
+ _dirname: function (aPath) {
+ let url = new URL(aPath);
+ let href = url.href;
+ return href.slice(0, href.lastIndexOf("/"));
+ },
+
+ /**
+ * Clears the source map cache. Source maps are cached by URL so
+ * they can be reused across separate Debugger instances (once in
+ * this cache, they will never be reparsed again). They are
+ * also cached by Debugger.Source objects for usefulness. By default
+ * this just removes the Debugger.Source cache, but you can remove
+ * the lower-level URL cache with the `hard` option.
+ *
+ * @param aSourceMapURL string
+ * The source map URL to uncache
+ * @param opts object
+ * An object with the following properties:
+ * - hard: Also remove the lower-level URL cache, which will
+ * make us completely forget about the source map.
+ */
+ clearSourceMapCache: function (aSourceMapURL, opts = { hard: false }) {
+ let oldSm = this._sourceMapCache[aSourceMapURL];
+
+ if (opts.hard) {
+ delete this._sourceMapCache[aSourceMapURL];
+ }
+
+ if (oldSm) {
+ // Clear out the current cache so all sources will get the new one
+ for (let [source, sm] of this._sourceMaps.entries()) {
+ if (sm === oldSm) {
+ this._sourceMaps.delete(source);
+ }
+ }
+ }
+ },
+
+ /*
+ * Forcefully change the source map of a source, changing the
+ * sourceMapURL and installing the source map in the cache. This is
+ * necessary to expose changes across Debugger instances
+ * (pretty-printing is the use case). Generate a random url if one
+ * isn't specified, allowing you to set "anonymous" source maps.
+ *
+ * @param aSource Debugger.Source
+ * The source to change the sourceMapURL property
+ * @param aUrl string
+ * The source map URL (optional)
+ * @param aMap SourceMapConsumer
+ * The source map instance
+ */
+ setSourceMapHard: function (aSource, aUrl, aMap) {
+ let url = aUrl;
+ if (!url) {
+ // This is a littly hacky, but we want to forcefully set a
+ // sourcemap regardless of sourcemap settings. We want to
+ // literally change the sourceMapURL so that all debuggers will
+ // get this and pretty-printing will Just Work (Debugger.Source
+ // instances are per-debugger, so we can't key off that). To
+ // avoid tons of work serializing the sourcemap into a data url,
+ // just make a fake URL and stick the sourcemap there.
+ url = "internal://sourcemap" + (this._anonSourceMapId++) + "/";
+ }
+ aSource.sourceMapURL = url;
+
+ // Forcefully set the sourcemap cache. This will be used even if
+ // sourcemaps are disabled.
+ this._sourceMapCache[url] = resolve(aMap);
+ this.emit("updatedSource", this.getSourceActor(aSource));
+ },
+
+ /**
+ * Return the non-source-mapped location of the given Debugger.Frame. If the
+ * frame does not have a script, the location's properties are all null.
+ *
+ * @param Debugger.Frame aFrame
+ * The frame whose location we are getting.
+ * @returns Object
+ * Returns an object of the form { source, line, column }
+ */
+ getFrameLocation: function (aFrame) {
+ if (!aFrame || !aFrame.script) {
+ return new GeneratedLocation();
+ }
+ let {lineNumber, columnNumber} =
+ aFrame.script.getOffsetLocation(aFrame.offset);
+ return new GeneratedLocation(
+ this.createNonSourceMappedActor(aFrame.script.source),
+ lineNumber,
+ columnNumber
+ );
+ },
+
+ /**
+ * Returns a promise of the location in the original source if the source is
+ * source mapped, otherwise a promise of the same location. This can
+ * be called with a source from *any* Debugger instance and we make
+ * sure to that it works properly, reusing source maps if already
+ * fetched. Use this from any actor that needs sourcemapping.
+ */
+ getOriginalLocation: function (generatedLocation) {
+ let {
+ generatedSourceActor,
+ generatedLine,
+ generatedColumn
+ } = generatedLocation;
+ let source = generatedSourceActor.source;
+ let url = source ? source.url : generatedSourceActor._originalUrl;
+
+ // In certain scenarios the source map may have not been fetched
+ // yet (or at least tied to this Debugger.Source instance), so use
+ // `fetchSourceMap` instead of `getSourceMap`. This allows this
+ // function to be called from anywere (across debuggers) and it
+ // should just automatically work.
+ return this.fetchSourceMap(source).then(map => {
+ if (map) {
+ let {
+ source: originalUrl,
+ line: originalLine,
+ column: originalColumn,
+ name: originalName
+ } = map.originalPositionFor({
+ line: generatedLine,
+ column: generatedColumn == null ? Infinity : generatedColumn
+ });
+
+ // Since the `Debugger.Source` instance may come from a
+ // different `Debugger` instance (any actor can call this
+ // method), we can't rely on any of the source discovery
+ // setup (`_discoverSources`, etc) to have been run yet. So
+ // we have to assume that the actor may not already exist,
+ // and we might need to create it, so use `source` and give
+ // it the required parameters for a sourcemapped source.
+ return new OriginalLocation(
+ originalUrl ? this.source({
+ originalUrl: originalUrl,
+ generatedSource: source
+ }) : null,
+ originalLine,
+ originalColumn,
+ originalName
+ );
+ }
+
+ // No source map
+ return OriginalLocation.fromGeneratedLocation(generatedLocation);
+ });
+ },
+
+ getAllGeneratedLocations: function (originalLocation) {
+ let {
+ originalSourceActor,
+ originalLine,
+ originalColumn
+ } = originalLocation;
+
+ let source = (originalSourceActor.source ||
+ originalSourceActor.generatedSource);
+
+ return this.fetchSourceMap(source).then((map) => {
+ if (map) {
+ map.computeColumnSpans();
+
+ return map.allGeneratedPositionsFor({
+ source: originalSourceActor.url,
+ line: originalLine,
+ column: originalColumn
+ }).map(({ line, column, lastColumn }) => {
+ return new GeneratedLocation(
+ this.createNonSourceMappedActor(source),
+ line,
+ column,
+ lastColumn
+ );
+ });
+ }
+
+ return [GeneratedLocation.fromOriginalLocation(originalLocation)];
+ });
+ },
+
+
+ /**
+ * Returns a promise of the location in the generated source corresponding to
+ * the original source and line given.
+ *
+ * When we pass a script S representing generated code to `sourceMap`,
+ * above, that returns a promise P. The process of resolving P populates
+ * the tables this function uses; thus, it won't know that S's original
+ * source URLs map to S until P is resolved.
+ */
+ getGeneratedLocation: function (originalLocation) {
+ let { originalSourceActor } = originalLocation;
+
+ // Both original sources and normal sources could have sourcemaps,
+ // because normal sources can be pretty-printed which generates a
+ // sourcemap for itself. Check both of the source properties to make it work
+ // for both kinds of sources.
+ let source = originalSourceActor.source || originalSourceActor.generatedSource;
+
+ // See comment about `fetchSourceMap` in `getOriginalLocation`.
+ return this.fetchSourceMap(source).then((map) => {
+ if (map) {
+ let {
+ originalLine,
+ originalColumn
+ } = originalLocation;
+
+ let {
+ line: generatedLine,
+ column: generatedColumn
+ } = map.generatedPositionFor({
+ source: originalSourceActor.url,
+ line: originalLine,
+ column: originalColumn == null ? 0 : originalColumn,
+ bias: SourceMapConsumer.LEAST_UPPER_BOUND
+ });
+
+ return new GeneratedLocation(
+ this.createNonSourceMappedActor(source),
+ generatedLine,
+ generatedColumn
+ );
+ }
+
+ return GeneratedLocation.fromOriginalLocation(originalLocation);
+ });
+ },
+
+ /**
+ * Returns true if URL for the given source is black boxed.
+ *
+ * @param aURL String
+ * The URL of the source which we are checking whether it is black
+ * boxed or not.
+ */
+ isBlackBoxed: function (aURL) {
+ return this.blackBoxedSources.has(aURL);
+ },
+
+ /**
+ * Add the given source URL to the set of sources that are black boxed.
+ *
+ * @param aURL String
+ * The URL of the source which we are black boxing.
+ */
+ blackBox: function (aURL) {
+ this.blackBoxedSources.add(aURL);
+ },
+
+ /**
+ * Remove the given source URL to the set of sources that are black boxed.
+ *
+ * @param aURL String
+ * The URL of the source which we are no longer black boxing.
+ */
+ unblackBox: function (aURL) {
+ this.blackBoxedSources.delete(aURL);
+ },
+
+ /**
+ * Returns true if the given URL is pretty printed.
+ *
+ * @param aURL String
+ * The URL of the source that might be pretty printed.
+ */
+ isPrettyPrinted: function (aURL) {
+ return this.prettyPrintedSources.has(aURL);
+ },
+
+ /**
+ * Add the given URL to the set of sources that are pretty printed.
+ *
+ * @param aURL String
+ * The URL of the source to be pretty printed.
+ */
+ prettyPrint: function (aURL, aIndent) {
+ this.prettyPrintedSources.set(aURL, aIndent);
+ },
+
+ /**
+ * Return the indent the given URL was pretty printed by.
+ */
+ prettyPrintIndent: function (aURL) {
+ return this.prettyPrintedSources.get(aURL);
+ },
+
+ /**
+ * Remove the given URL from the set of sources that are pretty printed.
+ *
+ * @param aURL String
+ * The URL of the source that is no longer pretty printed.
+ */
+ disablePrettyPrint: function (aURL) {
+ this.prettyPrintedSources.delete(aURL);
+ },
+
+ iter: function () {
+ let actors = Object.keys(this._sourceMappedSourceActors).map(k => {
+ return this._sourceMappedSourceActors[k];
+ });
+ for (let actor of this._sourceActors.values()) {
+ if (!this._sourceMaps.has(actor.source)) {
+ actors.push(actor);
+ }
+ }
+ return actors;
+ }
+};
+
+/*
+ * Checks if a source should never be displayed to the user because
+ * it's either internal or we don't support in the UI yet.
+ */
+function isHiddenSource(aSource) {
+ // Ignore the internal Function.prototype script
+ return aSource.text === "() {\n}";
+}
+
+/**
+ * Returns true if its argument is not null.
+ */
+function isNotNull(aThing) {
+ return aThing !== null;
+}
+
+exports.TabSources = TabSources;
+exports.isHiddenSource = isHiddenSource;