mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-06 15:58:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
833
devtools/server/actors/utils/TabSources.js
Normal file
833
devtools/server/actors/utils/TabSources.js
Normal file
|
|
@ -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;
|
||||
78
devtools/server/actors/utils/actor-registry-utils.js
Normal file
78
devtools/server/actors/utils/actor-registry-utils.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
var { Cu, CC, Ci, Cc } = require("chrome");
|
||||
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const promise = require("promise");
|
||||
|
||||
/**
|
||||
* Support for actor registration. Main used by ActorRegistryActor
|
||||
* for dynamic registration of new actors.
|
||||
*
|
||||
* @param sourceText {String} Source of the actor implementation
|
||||
* @param fileName {String} URL of the actor module (for proper stack traces)
|
||||
* @param options {Object} Configuration object
|
||||
*/
|
||||
exports.registerActor = function (sourceText, fileName, options) {
|
||||
// Register in the current process
|
||||
exports.registerActorInCurrentProcess(sourceText, fileName, options);
|
||||
// Register in any child processes
|
||||
return DebuggerServer.setupInChild({
|
||||
module: "devtools/server/actors/utils/actor-registry-utils",
|
||||
setupChild: "registerActorInCurrentProcess",
|
||||
args: [sourceText, fileName, options],
|
||||
waitForEval: true
|
||||
});
|
||||
};
|
||||
|
||||
exports.registerActorInCurrentProcess = function (sourceText, fileName, options) {
|
||||
const principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
|
||||
const sandbox = Cu.Sandbox(principal);
|
||||
sandbox.exports = {};
|
||||
sandbox.require = require;
|
||||
|
||||
Cu.evalInSandbox(sourceText, sandbox, "1.8", fileName, 1);
|
||||
|
||||
let { prefix, constructor, type } = options;
|
||||
|
||||
if (type.global && !DebuggerServer.globalActorFactories.hasOwnProperty(prefix)) {
|
||||
DebuggerServer.addGlobalActor({
|
||||
constructorName: constructor,
|
||||
constructorFun: sandbox[constructor]
|
||||
}, prefix);
|
||||
}
|
||||
|
||||
if (type.tab && !DebuggerServer.tabActorFactories.hasOwnProperty(prefix)) {
|
||||
DebuggerServer.addTabActor({
|
||||
constructorName: constructor,
|
||||
constructorFun: sandbox[constructor]
|
||||
}, prefix);
|
||||
}
|
||||
};
|
||||
|
||||
exports.unregisterActor = function (options) {
|
||||
// Unregister in the current process
|
||||
exports.unregisterActorInCurrentProcess(options);
|
||||
// Unregister in any child processes
|
||||
DebuggerServer.setupInChild({
|
||||
module: "devtools/server/actors/utils/actor-registry-utils",
|
||||
setupChild: "unregisterActorInCurrentProcess",
|
||||
args: [options]
|
||||
});
|
||||
};
|
||||
|
||||
exports.unregisterActorInCurrentProcess = function (options) {
|
||||
if (options.tab) {
|
||||
DebuggerServer.removeTabActor(options);
|
||||
}
|
||||
|
||||
if (options.global) {
|
||||
DebuggerServer.removeGlobalActor(options);
|
||||
}
|
||||
};
|
||||
113
devtools/server/actors/utils/audionodes.json
Normal file
113
devtools/server/actors/utils/audionodes.json
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"OscillatorNode": {
|
||||
"source": true,
|
||||
"properties": {
|
||||
"type": {},
|
||||
"frequency": {
|
||||
"param": true
|
||||
},
|
||||
"detune": {
|
||||
"param": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"GainNode": {
|
||||
"properties": { "gain": { "param": true }}
|
||||
},
|
||||
"DelayNode": {
|
||||
"properties": { "delayTime": { "param": true }}
|
||||
},
|
||||
"AudioBufferSourceNode": {
|
||||
"source": true,
|
||||
"properties": {
|
||||
"buffer": { "Buffer": true },
|
||||
"playbackRate": {
|
||||
"param": true
|
||||
},
|
||||
"loop": {},
|
||||
"loopStart": {},
|
||||
"loopEnd": {}
|
||||
}
|
||||
},
|
||||
"ScriptProcessorNode": {
|
||||
"properties": { "bufferSize": { "readonly": true }}
|
||||
},
|
||||
"PannerNode": {
|
||||
"properties": {
|
||||
"panningModel": {},
|
||||
"distanceModel": {},
|
||||
"refDistance": {},
|
||||
"maxDistance": {},
|
||||
"rolloffFactor": {},
|
||||
"coneInnerAngle": {},
|
||||
"coneOuterAngle": {},
|
||||
"coneOuterGain": {}
|
||||
}
|
||||
},
|
||||
"ConvolverNode": {
|
||||
"properties": {
|
||||
"buffer": { "Buffer": true },
|
||||
"normalize": {}
|
||||
}
|
||||
},
|
||||
"DynamicsCompressorNode": {
|
||||
"properties": {
|
||||
"threshold": { "param": true },
|
||||
"knee": { "param": true },
|
||||
"ratio": { "param": true },
|
||||
"reduction": {},
|
||||
"attack": { "param": true },
|
||||
"release": { "param": true }
|
||||
}
|
||||
},
|
||||
"BiquadFilterNode": {
|
||||
"properties": {
|
||||
"type": {},
|
||||
"frequency": { "param": true },
|
||||
"Q": { "param": true },
|
||||
"detune": { "param": true },
|
||||
"gain": { "param": true }
|
||||
}
|
||||
},
|
||||
"WaveShaperNode": {
|
||||
"properties": {
|
||||
"curve": { "Float32Array": true },
|
||||
"oversample": {}
|
||||
}
|
||||
},
|
||||
"AnalyserNode": {
|
||||
"properties": {
|
||||
"fftSize": {},
|
||||
"minDecibels": {},
|
||||
"maxDecibels": {},
|
||||
"smoothingTimeConstant": {},
|
||||
"frequencyBinCount": { "readonly": true }
|
||||
}
|
||||
},
|
||||
"AudioDestinationNode": {
|
||||
"unbypassable": true
|
||||
},
|
||||
"ChannelSplitterNode": {
|
||||
"unbypassable": true
|
||||
},
|
||||
"ChannelMergerNode": {
|
||||
"unbypassable": true
|
||||
},
|
||||
"MediaElementAudioSourceNode": {
|
||||
"source": true
|
||||
},
|
||||
"MediaStreamAudioSourceNode": {
|
||||
"source": true
|
||||
},
|
||||
"MediaStreamAudioDestinationNode": {
|
||||
"unbypassable": true,
|
||||
"properties": {
|
||||
"stream": { "MediaStream": true }
|
||||
}
|
||||
},
|
||||
"StereoPannerNode": {
|
||||
"properties": {
|
||||
"pan": { "param": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
373
devtools/server/actors/utils/automation-timeline.js
Normal file
373
devtools/server/actors/utils/automation-timeline.js
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
/**
|
||||
* web-audio-automation-timeline - 1.0.3
|
||||
* https://github.com/jsantell/web-audio-automation-timeline
|
||||
* MIT License, copyright (c) 2014 Jordan Santell
|
||||
*/
|
||||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Timeline=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
module.exports = require("./lib/timeline").Timeline;
|
||||
|
||||
},{"./lib/timeline":4}],2:[function(require,module,exports){
|
||||
var F = require("./formulas");
|
||||
|
||||
function TimelineEvent (eventName, value, time, timeConstant, duration) {
|
||||
this.type = eventName;
|
||||
this.value = value;
|
||||
this.time = time;
|
||||
this.constant = timeConstant || 0;
|
||||
this.duration = duration || 0;
|
||||
}
|
||||
exports.TimelineEvent = TimelineEvent;
|
||||
|
||||
|
||||
TimelineEvent.prototype.exponentialApproach = function (lastValue, time) {
|
||||
return F.exponentialApproach(this.time, lastValue, this.value, this.constant, time);
|
||||
}
|
||||
|
||||
TimelineEvent.prototype.extractValueFromCurve = function (time) {
|
||||
return F.extractValueFromCurve(this.time, this.value, this.value.length, this.duration, time);
|
||||
}
|
||||
|
||||
TimelineEvent.prototype.linearInterpolate = function (next, time) {
|
||||
return F.linearInterpolate(this.time, this.value, next.time, next.value, time);
|
||||
}
|
||||
|
||||
TimelineEvent.prototype.exponentialInterpolate = function (next, time) {
|
||||
return F.exponentialInterpolate(this.time, this.value, next.time, next.value, time);
|
||||
}
|
||||
|
||||
},{"./formulas":3}],3:[function(require,module,exports){
|
||||
var EPSILON = 0.0000000001;
|
||||
|
||||
exports.linearInterpolate = function (t0, v0, t1, v1, t) {
|
||||
return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
|
||||
};
|
||||
|
||||
exports.exponentialInterpolate = function (t0, v0, t1, v1, t) {
|
||||
return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
|
||||
};
|
||||
|
||||
exports.extractValueFromCurve = function (start, curve, curveLength, duration, t) {
|
||||
var ratio;
|
||||
|
||||
// If time is after duration, return the last curve value,
|
||||
// or if ratio is >= 1
|
||||
if (t >= start + duration || (ratio = Math.max((t - start) / duration, 0)) >= 1) {
|
||||
return curve[curveLength - 1];
|
||||
}
|
||||
|
||||
return curve[~~(curveLength * ratio)];
|
||||
};
|
||||
|
||||
exports.exponentialApproach = function (t0, v0, v1, timeConstant, t) {
|
||||
return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant);
|
||||
};
|
||||
|
||||
// Since we are going to accumulate error by adding 0.01 multiple times
|
||||
// in a loop, we want to fuzz the equality check in `getValueAtTime`
|
||||
exports.fuzzyEqual = function (lhs, rhs) {
|
||||
return Math.abs(lhs - rhs) < EPSILON;
|
||||
};
|
||||
|
||||
exports.EPSILON = EPSILON;
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
var TimelineEvent = require("./event").TimelineEvent;
|
||||
var F = require("./formulas");
|
||||
|
||||
exports.Timeline = Timeline;
|
||||
|
||||
function Timeline (defaultValue) {
|
||||
this.events = [];
|
||||
|
||||
this._value = defaultValue || 0;
|
||||
}
|
||||
|
||||
Timeline.prototype.getEventCount = function () {
|
||||
return this.events.length;
|
||||
};
|
||||
|
||||
Timeline.prototype.value = function () {
|
||||
return this._value;
|
||||
};
|
||||
|
||||
Timeline.prototype.setValue = function (value) {
|
||||
if (this.events.length === 0) {
|
||||
this._value = value;
|
||||
}
|
||||
};
|
||||
|
||||
Timeline.prototype.getValue = function () {
|
||||
if (this.events.length) {
|
||||
throw new Error("Can only call `getValue` when there are 0 events.");
|
||||
}
|
||||
|
||||
return this._value;
|
||||
};
|
||||
|
||||
Timeline.prototype.getValueAtTime = function (time) {
|
||||
return this._getValueAtTimeHelper(time);
|
||||
};
|
||||
|
||||
Timeline.prototype._getValueAtTimeHelper = function (time) {
|
||||
var bailOut = false;
|
||||
var previous = null;
|
||||
var next = null;
|
||||
var lastComputedValue = null; // Used for `setTargetAtTime` nodes
|
||||
var events = this.events;
|
||||
var e;
|
||||
|
||||
for (var i = 0; !bailOut && i < events.length; i++) {
|
||||
if (F.fuzzyEqual(time, events[i].time)) {
|
||||
// Find the last event with the same time as `time`
|
||||
do {
|
||||
++i;
|
||||
} while (i < events.length && F.fuzzyEqual(time, events[i].time));
|
||||
|
||||
e = events[i - 1];
|
||||
|
||||
// `setTargetAtTime` can be handled no matter what their next event is (if they have one)
|
||||
if (e.type === "setTargetAtTime") {
|
||||
lastComputedValue = this._lastComputedValue(e);
|
||||
return e.exponentialApproach(lastComputedValue, time);
|
||||
}
|
||||
|
||||
// `setValueCurveAtTime` events can be handled no matter what their next event node is
|
||||
// (if they have one)
|
||||
if (e.type === "setValueCurveAtTime") {
|
||||
return e.extractValueFromCurve(time);
|
||||
}
|
||||
|
||||
// For other event types
|
||||
return e.value;
|
||||
}
|
||||
previous = next;
|
||||
next = events[i];
|
||||
|
||||
if (time < events[i].time) {
|
||||
bailOut = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case where the time is past all of the events
|
||||
if (!bailOut) {
|
||||
previous = next;
|
||||
next = null;
|
||||
}
|
||||
|
||||
// Just return the default value if we did not find anything
|
||||
if (!previous && !next) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
// If the requested time is before all of the existing events
|
||||
if (!previous) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
// `setTargetAtTime` can be handled no matter what their next event is (if they have one)
|
||||
if (previous.type === "setTargetAtTime") {
|
||||
lastComputedValue = this._lastComputedValue(previous);
|
||||
return previous.exponentialApproach(lastComputedValue, time);
|
||||
}
|
||||
|
||||
// `setValueCurveAtTime` events can be handled no matter what their next event node is
|
||||
// (if they have one)
|
||||
if (previous.type === "setValueCurveAtTime") {
|
||||
return previous.extractValueFromCurve(time);
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
if (~["setValueAtTime", "linearRampToValueAtTime", "exponentialRampToValueAtTime"].indexOf(previous.type)) {
|
||||
return previous.value;
|
||||
}
|
||||
if (previous.type === "setValueCurveAtTime") {
|
||||
return previous.extractValueFromCurve(time);
|
||||
}
|
||||
if (previous.type === "setTargetAtTime") {
|
||||
throw new Error("unreached");
|
||||
}
|
||||
throw new Error("unreached");
|
||||
}
|
||||
|
||||
// Finally handle the case where we have both a previous and a next event
|
||||
// First handle the case where our range ends up in a ramp event
|
||||
if (next.type === "linearRampToValueAtTime") {
|
||||
return previous.linearInterpolate(next, time);
|
||||
} else if (next.type === "exponentialRampToValueAtTime") {
|
||||
return previous.exponentialInterpolate(next, time);
|
||||
}
|
||||
|
||||
// Now handle all other cases
|
||||
if (~["setValueAtTime", "linearRampToValueAtTime", "exponentialRampToValueAtTime"].indexOf(previous.type)) {
|
||||
// If the next event type is neither linear or exponential ramp,
|
||||
// the value is constant.
|
||||
return previous.value;
|
||||
}
|
||||
if (previous.type === "setValueCurveAtTime") {
|
||||
return previous.extractValueFromCurve(time);
|
||||
}
|
||||
if (previous.type === "setTargetAtTime") {
|
||||
throw new Error("unreached");
|
||||
}
|
||||
throw new Error("unreached");
|
||||
};
|
||||
|
||||
Timeline.prototype._insertEvent = function (ev) {
|
||||
var events = this.events;
|
||||
|
||||
if (ev.type === "setValueCurveAtTime") {
|
||||
if (!ev.value || !ev.value.length) {
|
||||
throw new Error("NS_ERROR_DOM_SYNTAX_ERR");
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.type === "setTargetAtTime") {
|
||||
if (F.fuzzyEqual(ev.constant, 0)) {
|
||||
throw new Error("NS_ERROR_DOM_SYNTAX_ERR");
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that non-curve events don't fall within the duration of a
|
||||
// curve event.
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].type === "setValueCurveAtTime" &&
|
||||
events[i].time <= ev.time &&
|
||||
(events[i].time + events[i].duration) >= ev.time) {
|
||||
throw new Error("NS_ERROR_DOM_SYNTAX_ERR");
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that curve events don't fall in a range which includes other
|
||||
// events.
|
||||
if (ev.type === "setValueCurveAtTime") {
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].time > ev.time &&
|
||||
events[i].time < (ev.time + ev.duration)) {
|
||||
throw new Error("NS_ERROR_DOM_SYNTAX_ERR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that invalid values are not used for exponential curves
|
||||
if (ev.type === "exponentialRampToValueAtTime") {
|
||||
if (ev.value <= 0) throw new Error("NS_ERROR_DOM_SYNTAX_ERR");
|
||||
var prev = this._getPreviousEvent(ev.time);
|
||||
if (prev) {
|
||||
if (prev.value <= 0) {
|
||||
throw new Error("NS_ERROR_DOM_SYNTAX_ERR");
|
||||
}
|
||||
} else {
|
||||
if (this._value <= 0) {
|
||||
throw new Error("NS_ERROR_DOM_SYNTAX_ERR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (ev.time === events[i].time) {
|
||||
if (ev.type === events[i].type) {
|
||||
// If times and types are equal, replace the event;
|
||||
events[i] = ev;
|
||||
} else {
|
||||
// Otherwise, place the element after the last event of another type
|
||||
do { i++; }
|
||||
while (i < events.length && ev.type !== events[i].type && ev.time === events[i].time);
|
||||
events.splice(i, 0, ev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Otherwise, place the event right after the latest existing event
|
||||
if (ev.time < events[i].time) {
|
||||
events.splice(i, 0, ev);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't find a place for the event, just append it to the list
|
||||
this.events.push(ev);
|
||||
};
|
||||
|
||||
Timeline.prototype._getPreviousEvent = function (time) {
|
||||
var previous = null, next = null;
|
||||
var bailOut = false;
|
||||
var events = this.events;
|
||||
|
||||
for (var i = 0; !bailOut && i < events.length; i++) {
|
||||
if (time === events[i]) {
|
||||
do { ++i; }
|
||||
while (i < events.length && time === events[i].time);
|
||||
return events[i - 1];
|
||||
}
|
||||
previous = next;
|
||||
next = events[i];
|
||||
if (time < events[i].time) {
|
||||
bailOut = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case where the time is past all the events
|
||||
if (!bailOut) {
|
||||
previous = next;
|
||||
}
|
||||
|
||||
return previous;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the previous value of the timeline, used for
|
||||
* `setTargetAtTime` nodes. Takes an event, and returns
|
||||
* the previous computed value for any sample taken during that
|
||||
* exponential approach node.
|
||||
*/
|
||||
Timeline.prototype._lastComputedValue = function (event) {
|
||||
// If equal times, return the value for the previous event, before
|
||||
// the `setTargetAtTime` node.
|
||||
var lastEvent = this._getPreviousEvent(event.time - F.EPSILON);
|
||||
|
||||
// If no event before the setTargetAtTime event, then return the
|
||||
// intrinsic value.
|
||||
if (!lastEvent) {
|
||||
return this._value;
|
||||
}
|
||||
// Otherwise, return the value for the previous event, which should
|
||||
// always be the last computed value (? I think?)
|
||||
else {
|
||||
return lastEvent.value;
|
||||
}
|
||||
};
|
||||
|
||||
Timeline.prototype.setValueAtTime = function (value, startTime) {
|
||||
this._insertEvent(new TimelineEvent("setValueAtTime", value, startTime));
|
||||
};
|
||||
|
||||
Timeline.prototype.linearRampToValueAtTime = function (value, endTime) {
|
||||
this._insertEvent(new TimelineEvent("linearRampToValueAtTime", value, endTime));
|
||||
};
|
||||
|
||||
Timeline.prototype.exponentialRampToValueAtTime = function (value, endTime) {
|
||||
this._insertEvent(new TimelineEvent("exponentialRampToValueAtTime", value, endTime));
|
||||
};
|
||||
|
||||
Timeline.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
|
||||
this._insertEvent(new TimelineEvent("setTargetAtTime", value, startTime, timeConstant));
|
||||
};
|
||||
|
||||
Timeline.prototype.setValueCurveAtTime = function (value, startTime, duration) {
|
||||
this._insertEvent(new TimelineEvent("setValueCurveAtTime", value, startTime, null, duration));
|
||||
};
|
||||
|
||||
Timeline.prototype.cancelScheduledValues = function (time) {
|
||||
for (var i = 0; i < this.events.length; i++) {
|
||||
if (this.events[i].time >= time) {
|
||||
this.events = this.events.slice(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Timeline.prototype.cancelAllEvents = function () {
|
||||
this.events.length = 0;
|
||||
};
|
||||
|
||||
},{"./event":2,"./formulas":3}]},{},[1])(1)
|
||||
});
|
||||
61
devtools/server/actors/utils/css-grid-utils.js
Normal file
61
devtools/server/actors/utils/css-grid-utils.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/* 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 { Cu } = require("chrome");
|
||||
|
||||
/**
|
||||
* Returns the grid fragment array with all the grid fragment data stringifiable.
|
||||
*
|
||||
* @param {Object} fragments
|
||||
* Grid fragment object.
|
||||
* @return {Array} representation with the grid fragment data stringifiable.
|
||||
*/
|
||||
function getStringifiableFragments(fragments = []) {
|
||||
if (fragments[0] && Cu.isDeadWrapper(fragments[0])) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return fragments.map(getStringifiableFragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the CSS Grid data as returned by
|
||||
* node.getGridFragments. This is useful to compare grid state at each update and redraw
|
||||
* the highlighter if needed. It also seralizes the grid fragment data so it can be used
|
||||
* by protocol.js.
|
||||
*
|
||||
* @param {Object} fragments
|
||||
* Grid fragment object.
|
||||
* @return {String} representation of the CSS grid fragment data.
|
||||
*/
|
||||
function stringifyGridFragments(fragments) {
|
||||
return JSON.stringify(getStringifiableFragments(fragments));
|
||||
}
|
||||
|
||||
function getStringifiableFragment(fragment) {
|
||||
return {
|
||||
cols: getStringifiableDimension(fragment.cols),
|
||||
rows: getStringifiableDimension(fragment.rows)
|
||||
};
|
||||
}
|
||||
|
||||
function getStringifiableDimension(dimension) {
|
||||
return {
|
||||
lines: [...dimension.lines].map(getStringifiableLine),
|
||||
tracks: [...dimension.tracks].map(getStringifiableTrack),
|
||||
};
|
||||
}
|
||||
|
||||
function getStringifiableLine({ breadth, number, start, names }) {
|
||||
return { breadth, number, start, names };
|
||||
}
|
||||
|
||||
function getStringifiableTrack({ breadth, start, state, type }) {
|
||||
return { breadth, start, state, type };
|
||||
}
|
||||
|
||||
exports.getStringifiableFragments = getStringifiableFragments;
|
||||
exports.stringifyGridFragments = stringifyGridFragments;
|
||||
101
devtools/server/actors/utils/make-debugger.js
Normal file
101
devtools/server/actors/utils/make-debugger.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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 EventEmitter = require("devtools/shared/event-emitter");
|
||||
const Debugger = require("Debugger");
|
||||
|
||||
const { reportException } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
/**
|
||||
* Multiple actors that use a |Debugger| instance come in a few versions, each
|
||||
* with a different set of debuggees. One version for content tabs (globals
|
||||
* within a tab), one version for chrome debugging (all globals), and sometimes
|
||||
* a third version for addon debugging (chrome globals the addon is loaded in
|
||||
* and content globals the addon injects scripts into). The |makeDebugger|
|
||||
* function helps us avoid repeating the logic for finding and maintaining the
|
||||
* correct set of globals for a given |Debugger| instance across each version of
|
||||
* all of our actors.
|
||||
*
|
||||
* The |makeDebugger| function expects a single object parameter with the
|
||||
* following properties:
|
||||
*
|
||||
* @param Function findDebuggees
|
||||
* Called with one argument: a |Debugger| instance. This function should
|
||||
* return an iterable of globals to be added to the |Debugger|
|
||||
* instance. The globals may be wrapped in a |Debugger.Object|, or
|
||||
* unwrapped.
|
||||
*
|
||||
* @param Function shouldAddNewGlobalAsDebuggee
|
||||
* Called with one argument: a |Debugger.Object| wrapping a global
|
||||
* object. This function must return |true| if the global object should
|
||||
* be added as debuggee, and |false| otherwise.
|
||||
*
|
||||
* @returns Debugger
|
||||
* Returns a |Debugger| instance that can manage its set of debuggee
|
||||
* globals itself and is decorated with the |EventEmitter| class.
|
||||
*
|
||||
* Events emitted by the returned |Debugger| instance:
|
||||
*
|
||||
* - "newGlobal": Emitted when a new global has been added as a
|
||||
* debuggee. Passes the |Debugger.Object| wrapping the new
|
||||
* debuggee global to listeners.
|
||||
*
|
||||
* Existing |Debugger| properties set on the returned |Debugger|
|
||||
* instance:
|
||||
*
|
||||
* - onNewGlobalObject: The |Debugger| will automatically add new
|
||||
* globals as debuggees if calling |shouldAddNewGlobalAsDebuggee|
|
||||
* with the global returns true.
|
||||
*
|
||||
* - uncaughtExceptionHook: The |Debugger| already has an error
|
||||
* reporter attached to |uncaughtExceptionHook|, so if any
|
||||
* |Debugger| hooks fail, the error will be reported.
|
||||
*
|
||||
* New properties set on the returned |Debugger| instance:
|
||||
*
|
||||
* - addDebuggees: A function which takes no arguments. It adds all
|
||||
* current globals that should be debuggees (as determined by
|
||||
* |findDebuggees|) to the |Debugger| instance.
|
||||
*/
|
||||
module.exports = function makeDebugger({ findDebuggees, shouldAddNewGlobalAsDebuggee }) {
|
||||
const dbg = new Debugger();
|
||||
EventEmitter.decorate(dbg);
|
||||
|
||||
dbg.allowUnobservedAsmJS = true;
|
||||
dbg.uncaughtExceptionHook = reportDebuggerHookException;
|
||||
|
||||
dbg.onNewGlobalObject = function (global) {
|
||||
if (shouldAddNewGlobalAsDebuggee(global)) {
|
||||
safeAddDebuggee(this, global);
|
||||
}
|
||||
};
|
||||
|
||||
dbg.addDebuggees = function () {
|
||||
for (let global of findDebuggees(this)) {
|
||||
safeAddDebuggee(this, global);
|
||||
}
|
||||
};
|
||||
|
||||
return dbg;
|
||||
};
|
||||
|
||||
const reportDebuggerHookException = e => reportException("Debugger Hook", e);
|
||||
|
||||
/**
|
||||
* Add |global| as a debuggee to |dbg|, handling error cases.
|
||||
*/
|
||||
function safeAddDebuggee(dbg, global) {
|
||||
try {
|
||||
let wrappedGlobal = dbg.addDebuggee(global);
|
||||
if (wrappedGlobal) {
|
||||
dbg.emit("newGlobal", wrappedGlobal);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignoring attempt to add the debugger's compartment as a debuggee.
|
||||
}
|
||||
}
|
||||
44
devtools/server/actors/utils/map-uri-to-addon-id.js
Normal file
44
devtools/server/actors/utils/map-uri-to-addon-id.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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 DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyServiceGetter(this, "AddonPathService",
|
||||
"@mozilla.org/addon-path-service;1",
|
||||
"amIAddonPathService");
|
||||
|
||||
const B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
|
||||
const GRAPHENE_ID = "{d1bfe7d9-c01e-4237-998b-7b5f960a4314}";
|
||||
|
||||
/**
|
||||
* This is a wrapper around amIAddonPathService.mapURIToAddonID which always returns
|
||||
* false on B2G and graphene to avoid loading the add-on manager there and
|
||||
* reports any exceptions rather than throwing so that the caller doesn't have
|
||||
* to worry about them.
|
||||
*/
|
||||
if (!Services.appinfo
|
||||
|| Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT
|
||||
|| Services.appinfo.ID === undefined /* XPCShell */
|
||||
|| Services.appinfo.ID == B2G_ID
|
||||
|| Services.appinfo.ID == GRAPHENE_ID
|
||||
|| !AddonPathService) {
|
||||
module.exports = function mapURIToAddonId(uri) {
|
||||
return false;
|
||||
};
|
||||
} else {
|
||||
module.exports = function mapURIToAddonId(uri) {
|
||||
try {
|
||||
return AddonPathService.mapURIToAddonId(uri);
|
||||
}
|
||||
catch (e) {
|
||||
DevToolsUtils.reportException("mapURIToAddonId", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
19
devtools/server/actors/utils/moz.build
Normal file
19
devtools/server/actors/utils/moz.build
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'actor-registry-utils.js',
|
||||
'audionodes.json',
|
||||
'automation-timeline.js',
|
||||
'css-grid-utils.js',
|
||||
'make-debugger.js',
|
||||
'map-uri-to-addon-id.js',
|
||||
'stack.js',
|
||||
'TabSources.js',
|
||||
'walker-search.js',
|
||||
'webconsole-utils.js',
|
||||
'webconsole-worker-utils.js',
|
||||
)
|
||||
185
devtools/server/actors/utils/stack.js
Normal file
185
devtools/server/actors/utils/stack.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/* 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";
|
||||
|
||||
var {Class} = require("sdk/core/heritage");
|
||||
|
||||
/**
|
||||
* A helper class that stores stack frame objects. Each frame is
|
||||
* assigned an index, and if a frame is added more than once, the same
|
||||
* index is used. Users of the class can get an array of all frames
|
||||
* that have been added.
|
||||
*/
|
||||
var StackFrameCache = Class({
|
||||
/**
|
||||
* Initialize this object.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._framesToIndices = null;
|
||||
this._framesToForms = null;
|
||||
this._lastEventSize = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare to accept frames.
|
||||
*/
|
||||
initFrames: function () {
|
||||
if (this._framesToIndices) {
|
||||
// The maps are already initialized.
|
||||
return;
|
||||
}
|
||||
|
||||
this._framesToIndices = new Map();
|
||||
this._framesToForms = new Map();
|
||||
this._lastEventSize = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Forget all stored frames and reset to the initialized state.
|
||||
*/
|
||||
clearFrames: function () {
|
||||
this._framesToIndices.clear();
|
||||
this._framesToIndices = null;
|
||||
this._framesToForms.clear();
|
||||
this._framesToForms = null;
|
||||
this._lastEventSize = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a frame to this stack frame cache, and return the index of
|
||||
* the frame.
|
||||
*/
|
||||
addFrame: function (frame) {
|
||||
this._assignFrameIndices(frame);
|
||||
this._createFrameForms(frame);
|
||||
return this._framesToIndices.get(frame);
|
||||
},
|
||||
|
||||
/**
|
||||
* A helper method for the memory actor. This populates the packet
|
||||
* object with "frames" property. Each of these
|
||||
* properties will be an array indexed by frame ID. "frames" will
|
||||
* contain frame objects (see makeEvent).
|
||||
*
|
||||
* @param packet
|
||||
* The packet to update.
|
||||
*
|
||||
* @returns packet
|
||||
*/
|
||||
updateFramePacket: function (packet) {
|
||||
// Now that we are guaranteed to have a form for every frame, we know the
|
||||
// size the "frames" property's array must be. We use that information to
|
||||
// create dense arrays even though we populate them out of order.
|
||||
const size = this._framesToForms.size;
|
||||
packet.frames = Array(size).fill(null);
|
||||
|
||||
// Populate the "frames" properties.
|
||||
for (let [stack, index] of this._framesToIndices) {
|
||||
packet.frames[index] = this._framesToForms.get(stack);
|
||||
}
|
||||
|
||||
return packet;
|
||||
},
|
||||
|
||||
/**
|
||||
* If any new stack frames have been added to this cache since the
|
||||
* last call to makeEvent (clearing the cache also resets the "last
|
||||
* call"), then return a new array describing the new frames. If no
|
||||
* new frames are available, return null.
|
||||
*
|
||||
* The frame cache assumes that the user of the cache keeps track of
|
||||
* all previously-returned arrays and, in theory, concatenates them
|
||||
* all to form a single array holding all frames added to the cache
|
||||
* since the last reset. This concatenated array can be indexed by
|
||||
* the frame ID. The array returned by this function, though, is
|
||||
* dense and starts at 0.
|
||||
*
|
||||
* Each element in the array is an object of the form:
|
||||
* {
|
||||
* line: <line number for this frame>,
|
||||
* column: <column number for this frame>,
|
||||
* source: <filename string for this frame>,
|
||||
* functionDisplayName: <this frame's inferred function name function or null>,
|
||||
* parent: <frame ID -- an index into the concatenated array mentioned above>
|
||||
* asyncCause: the async cause, or null
|
||||
* asyncParent: <frame ID -- an index into the concatenated array mentioned above>
|
||||
* }
|
||||
*
|
||||
* The intent of this approach is to make it simpler to efficiently
|
||||
* send frame information over the debugging protocol, by only
|
||||
* sending new frames.
|
||||
*
|
||||
* @returns array or null
|
||||
*/
|
||||
makeEvent: function () {
|
||||
const size = this._framesToForms.size;
|
||||
if (!size || size <= this._lastEventSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let packet = Array(size - this._lastEventSize).fill(null);
|
||||
for (let [stack, index] of this._framesToIndices) {
|
||||
if (index >= this._lastEventSize) {
|
||||
packet[index - this._lastEventSize] = this._framesToForms.get(stack);
|
||||
}
|
||||
}
|
||||
|
||||
this._lastEventSize = size;
|
||||
|
||||
return packet;
|
||||
},
|
||||
|
||||
/**
|
||||
* Assigns an index to the given frame and its parents, if an index is not
|
||||
* already assigned.
|
||||
*
|
||||
* @param SavedFrame frame
|
||||
* A frame to assign an index to.
|
||||
*/
|
||||
_assignFrameIndices: function (frame) {
|
||||
if (this._framesToIndices.has(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
this._assignFrameIndices(frame.parent);
|
||||
this._assignFrameIndices(frame.asyncParent);
|
||||
}
|
||||
|
||||
const index = this._framesToIndices.size;
|
||||
this._framesToIndices.set(frame, index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the form for the given frame, if one doesn't already exist.
|
||||
*
|
||||
* @param SavedFrame frame
|
||||
* A frame to create a form for.
|
||||
*/
|
||||
_createFrameForms: function (frame) {
|
||||
if (this._framesToForms.has(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let form = null;
|
||||
if (frame) {
|
||||
form = {
|
||||
line: frame.line,
|
||||
column: frame.column,
|
||||
source: frame.source,
|
||||
functionDisplayName: frame.functionDisplayName,
|
||||
parent: this._framesToIndices.get(frame.parent),
|
||||
asyncParent: this._framesToIndices.get(frame.asyncParent),
|
||||
asyncCause: frame.asyncCause
|
||||
};
|
||||
this._createFrameForms(frame.parent);
|
||||
this._createFrameForms(frame.asyncParent);
|
||||
}
|
||||
|
||||
this._framesToForms.set(frame, form);
|
||||
},
|
||||
});
|
||||
|
||||
exports.StackFrameCache = StackFrameCache;
|
||||
278
devtools/server/actors/utils/walker-search.js
Normal file
278
devtools/server/actors/utils/walker-search.js
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* The walker-search module provides a simple API to index and search strings
|
||||
* and elements inside a given document.
|
||||
* It indexes tag names, attribute names and values, and text contents.
|
||||
* It provides a simple search function that returns a list of nodes that
|
||||
* matched.
|
||||
*/
|
||||
|
||||
const {Ci, Cu} = require("chrome");
|
||||
|
||||
/**
|
||||
* The WalkerIndex class indexes the document (and all subdocs) from
|
||||
* a given walker.
|
||||
*
|
||||
* It is only indexed the first time the data is accessed and will be
|
||||
* re-indexed if a mutation happens between requests.
|
||||
*
|
||||
* @param {Walker} walker The walker to be indexed
|
||||
*/
|
||||
function WalkerIndex(walker) {
|
||||
this.walker = walker;
|
||||
this.clearIndex = this.clearIndex.bind(this);
|
||||
|
||||
// Kill the index when mutations occur, the next data get will re-index.
|
||||
this.walker.on("any-mutation", this.clearIndex);
|
||||
}
|
||||
|
||||
WalkerIndex.prototype = {
|
||||
/**
|
||||
* Destroy this instance, releasing all data and references
|
||||
*/
|
||||
destroy: function () {
|
||||
this.walker.off("any-mutation", this.clearIndex);
|
||||
},
|
||||
|
||||
clearIndex: function () {
|
||||
if (!this.currentlyIndexing) {
|
||||
this._data = null;
|
||||
}
|
||||
},
|
||||
|
||||
get doc() {
|
||||
return this.walker.rootDoc;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the indexed data
|
||||
* This getter also indexes if it hasn't been done yet or if the state is
|
||||
* dirty
|
||||
*
|
||||
* @returns Map<String, Array<{type:String, node:DOMNode}>>
|
||||
* A Map keyed on the searchable value, containing an array with
|
||||
* objects containing the 'type' (one of ALL_RESULTS_TYPES), and
|
||||
* the DOM Node.
|
||||
*/
|
||||
get data() {
|
||||
if (!this._data) {
|
||||
this._data = new Map();
|
||||
this.index();
|
||||
}
|
||||
|
||||
return this._data;
|
||||
},
|
||||
|
||||
_addToIndex: function (type, node, value) {
|
||||
// Add an entry for this value if there isn't one
|
||||
let entry = this._data.get(value);
|
||||
if (!entry) {
|
||||
this._data.set(value, []);
|
||||
}
|
||||
|
||||
// Add the type/node to the list
|
||||
this._data.get(value).push({
|
||||
type: type,
|
||||
node: node
|
||||
});
|
||||
},
|
||||
|
||||
index: function () {
|
||||
// Handle case where iterating nextNode() with the deepTreeWalker triggers
|
||||
// a mutation (Bug 1222558)
|
||||
this.currentlyIndexing = true;
|
||||
|
||||
let documentWalker = this.walker.getDocumentWalker(this.doc);
|
||||
while (documentWalker.nextNode()) {
|
||||
let node = documentWalker.currentNode;
|
||||
|
||||
if (node.nodeType === 1) {
|
||||
// For each element node, we get the tagname and all attributes names
|
||||
// and values
|
||||
let localName = node.localName;
|
||||
if (localName === "_moz_generated_content_before") {
|
||||
this._addToIndex("tag", node, "::before");
|
||||
this._addToIndex("text", node, node.textContent.trim());
|
||||
} else if (localName === "_moz_generated_content_after") {
|
||||
this._addToIndex("tag", node, "::after");
|
||||
this._addToIndex("text", node, node.textContent.trim());
|
||||
} else {
|
||||
this._addToIndex("tag", node, node.localName);
|
||||
}
|
||||
|
||||
for (let {name, value} of node.attributes) {
|
||||
this._addToIndex("attributeName", node, name);
|
||||
this._addToIndex("attributeValue", node, value);
|
||||
}
|
||||
} else if (node.textContent && node.textContent.trim().length) {
|
||||
// For comments and text nodes, we get the text
|
||||
this._addToIndex("text", node, node.textContent.trim());
|
||||
}
|
||||
}
|
||||
|
||||
this.currentlyIndexing = false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.WalkerIndex = WalkerIndex;
|
||||
|
||||
/**
|
||||
* The WalkerSearch class provides a way to search an indexed document as well
|
||||
* as find elements that match a given css selector.
|
||||
*
|
||||
* Usage example:
|
||||
* let s = new WalkerSearch(doc);
|
||||
* let res = s.search("lang", index);
|
||||
* for (let {matched, results} of res) {
|
||||
* for (let {node, type} of results) {
|
||||
* console.log("The query matched a node's " + type);
|
||||
* console.log("Node that matched", node);
|
||||
* }
|
||||
* }
|
||||
* s.destroy();
|
||||
*
|
||||
* @param {Walker} the walker to be searched
|
||||
*/
|
||||
function WalkerSearch(walker) {
|
||||
this.walker = walker;
|
||||
this.index = new WalkerIndex(this.walker);
|
||||
}
|
||||
|
||||
WalkerSearch.prototype = {
|
||||
destroy: function () {
|
||||
this.index.destroy();
|
||||
this.walker = null;
|
||||
},
|
||||
|
||||
_addResult: function (node, type, results) {
|
||||
if (!results.has(node)) {
|
||||
results.set(node, []);
|
||||
}
|
||||
|
||||
let matches = results.get(node);
|
||||
|
||||
// Do not add if the exact same result is already in the list
|
||||
let isKnown = false;
|
||||
for (let match of matches) {
|
||||
if (match.type === type) {
|
||||
isKnown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isKnown) {
|
||||
matches.push({type});
|
||||
}
|
||||
},
|
||||
|
||||
_searchIndex: function (query, options, results) {
|
||||
for (let [matched, res] of this.index.data) {
|
||||
if (!options.searchMethod(query, matched)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add any relevant results (skipping non-requested options).
|
||||
res.filter(entry => {
|
||||
return options.types.indexOf(entry.type) !== -1;
|
||||
}).forEach(({node, type}) => {
|
||||
this._addResult(node, type, results);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_searchSelectors: function (query, options, results) {
|
||||
// If the query is just one "word", no need to search because _searchIndex
|
||||
// will lead the same results since it has access to tagnames anyway
|
||||
let isSelector = query && query.match(/[ >~.#\[\]]/);
|
||||
if (options.types.indexOf("selector") === -1 || !isSelector) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nodes = this.walker._multiFrameQuerySelectorAll(query);
|
||||
for (let node of nodes) {
|
||||
this._addResult(node, "selector", results);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Search the document
|
||||
* @param {String} query What to search for
|
||||
* @param {Object} options The following options are accepted:
|
||||
* - searchMethod {String} one of WalkerSearch.SEARCH_METHOD_*
|
||||
* defaults to WalkerSearch.SEARCH_METHOD_CONTAINS (does not apply to
|
||||
* selector search type)
|
||||
* - types {Array} a list of things to search for (tag, text, attributes, etc)
|
||||
* defaults to WalkerSearch.ALL_RESULTS_TYPES
|
||||
* @return {Array} An array is returned with each item being an object like:
|
||||
* {
|
||||
* node: <the dom node that matched>,
|
||||
* type: <the type of match: one of WalkerSearch.ALL_RESULTS_TYPES>
|
||||
* }
|
||||
*/
|
||||
search: function (query, options = {}) {
|
||||
options.searchMethod = options.searchMethod || WalkerSearch.SEARCH_METHOD_CONTAINS;
|
||||
options.types = options.types || WalkerSearch.ALL_RESULTS_TYPES;
|
||||
|
||||
// Empty strings will return no results, as will non-string input
|
||||
if (typeof query !== "string") {
|
||||
query = "";
|
||||
}
|
||||
|
||||
// Store results in a map indexed by nodes to avoid duplicate results
|
||||
let results = new Map();
|
||||
|
||||
// Search through the indexed data
|
||||
this._searchIndex(query, options, results);
|
||||
|
||||
// Search with querySelectorAll
|
||||
this._searchSelectors(query, options, results);
|
||||
|
||||
// Concatenate all results into an Array to return
|
||||
let resultList = [];
|
||||
for (let [node, matches] of results) {
|
||||
for (let {type} of matches) {
|
||||
resultList.push({
|
||||
node: node,
|
||||
type: type,
|
||||
});
|
||||
|
||||
// For now, just do one result per node since the frontend
|
||||
// doesn't have a way to highlight each result individually
|
||||
// yet.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let documents = this.walker.tabActor.windows.map(win=>win.document);
|
||||
|
||||
// Sort the resulting nodes by order of appearance in the DOM
|
||||
resultList.sort((a, b) => {
|
||||
// Disconnected nodes won't get good results from compareDocumentPosition
|
||||
// so check the order of their document instead.
|
||||
if (a.node.ownerDocument != b.node.ownerDocument) {
|
||||
let indA = documents.indexOf(a.node.ownerDocument);
|
||||
let indB = documents.indexOf(b.node.ownerDocument);
|
||||
return indA - indB;
|
||||
}
|
||||
// If the same document, then sort on DOCUMENT_POSITION_FOLLOWING (4)
|
||||
// which means B is after A.
|
||||
return a.node.compareDocumentPosition(b.node) & 4 ? -1 : 1;
|
||||
});
|
||||
|
||||
return resultList;
|
||||
}
|
||||
};
|
||||
|
||||
WalkerSearch.SEARCH_METHOD_CONTAINS = (query, candidate) => {
|
||||
return query && candidate.toLowerCase().indexOf(query.toLowerCase()) !== -1;
|
||||
};
|
||||
|
||||
WalkerSearch.ALL_RESULTS_TYPES = ["tag", "text", "attributeName",
|
||||
"attributeValue", "selector"];
|
||||
|
||||
exports.WalkerSearch = WalkerSearch;
|
||||
1063
devtools/server/actors/utils/webconsole-utils.js
Normal file
1063
devtools/server/actors/utils/webconsole-utils.js
Normal file
File diff suppressed because it is too large
Load diff
20
devtools/server/actors/utils/webconsole-worker-utils.js
Normal file
20
devtools/server/actors/utils/webconsole-worker-utils.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// XXXworkers This file is loaded on the server side for worker debugging.
|
||||
// Since the server is running in the worker thread, it doesn't
|
||||
// have access to Services / Components. This functionality
|
||||
// is stubbed out to prevent errors, and will need to implemented
|
||||
// for Bug 1209353.
|
||||
|
||||
exports.Utils = { L10n: function () {} };
|
||||
exports.ConsoleServiceListener = function () {};
|
||||
exports.ConsoleAPIListener = function () {};
|
||||
exports.addWebConsoleCommands = function () {};
|
||||
exports.ConsoleReflowListener = function () {};
|
||||
exports.CONSOLE_WORKER_IDS = [];
|
||||
Loading…
Add table
Add a link
Reference in a new issue