12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
- /* 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, Ci } = require("chrome");
- const { GeneratedLocation } = require("devtools/server/actors/common");
- const { DebuggerServer } = require("devtools/server/main");
- const DevToolsUtils = require("devtools/shared/DevToolsUtils");
- const { assert, dumpn } = DevToolsUtils;
- loader.lazyRequireGetter(this, "ThreadSafeChromeUtils");
- const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
- "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
- "Float64Array"];
- // Number of items to preview in objects, arrays, maps, sets, lists,
- // collections, etc.
- const OBJECT_PREVIEW_MAX_ITEMS = 10;
- /**
- * Creates an actor for the specified object.
- *
- * @param obj Debugger.Object
- * The debuggee object.
- * @param hooks Object
- * A collection of abstract methods that are implemented by the caller.
- * ObjectActor requires the following functions to be implemented by
- * the caller:
- * - createValueGrip
- * Creates a value grip for the given object
- * - sources
- * TabSources getter that manages the sources of a thread
- * - createEnvironmentActor
- * Creates and return an environment actor
- * - getGripDepth
- * An actor's grip depth getter
- * - incrementGripDepth
- * Increment the actor's grip depth
- * - decrementGripDepth
- * Decrement the actor's grip depth
- * - globalDebugObject
- * The Debuggee Global Object as given by the ThreadActor
- */
- function ObjectActor(obj, {
- createValueGrip,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- }) {
- assert(!obj.optimizedOut,
- "Should not create object actors for optimized out values!");
- this.obj = obj;
- this.hooks = {
- createValueGrip,
- sources,
- createEnvironmentActor,
- getGripDepth,
- incrementGripDepth,
- decrementGripDepth,
- getGlobalDebugObject
- };
- this.iterators = new Set();
- }
- ObjectActor.prototype = {
- actorPrefix: "obj",
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function () {
- this.hooks.incrementGripDepth();
- let g = {
- "type": "object",
- "actor": this.actorID
- };
- // If it's a proxy, lie and tell that it belongs to an invented
- // "Proxy" class, and avoid calling the [[IsExtensible]] trap
- if(this.obj.isProxy) {
- g.class = "Proxy";
- g.proxyTarget = this.hooks.createValueGrip(this.obj.proxyTarget);
- g.proxyHandler = this.hooks.createValueGrip(this.obj.proxyHandler);
- } else {
- g.class = this.obj.class;
- g.extensible = this.obj.isExtensible();
- g.frozen = this.obj.isFrozen();
- g.sealed = this.obj.isSealed();
- }
- if (g.class != "DeadObject") {
- if (g.class == "Promise") {
- g.promiseState = this._createPromiseState();
- }
- // FF40+: Allow to know how many properties an object has
- // to lazily display them when there is a bunch.
- // Throws on some MouseEvent object in tests.
- try {
- // Bug 1163520: Assert on internal functions
- if (!["Function", "Proxy"].includes(g.class)) {
- g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
- }
- } catch (e) {}
- let raw = this.obj.unsafeDereference();
- // If Cu is not defined, we are running on a worker thread, where xrays
- // don't exist.
- if (Cu) {
- raw = Cu.unwaiveXrays(raw);
- }
- if (!DevToolsUtils.isSafeJSObject(raw)) {
- raw = null;
- }
- let previewers = DebuggerServer.ObjectActorPreviewers[g.class] ||
- DebuggerServer.ObjectActorPreviewers.Object;
- for (let fn of previewers) {
- try {
- if (fn(this, g, raw)) {
- break;
- }
- } catch (e) {
- let msg = "ObjectActor.prototype.grip previewer function";
- DevToolsUtils.reportException(msg, e);
- }
- }
- }
- this.hooks.decrementGripDepth();
- return g;
- },
- /**
- * Returns an object exposing the internal Promise state.
- */
- _createPromiseState: function () {
- const { state, value, reason } = getPromiseState(this.obj);
- let promiseState = { state };
- if (state == "fulfilled") {
- promiseState.value = this.hooks.createValueGrip(value);
- } else if (state == "rejected") {
- promiseState.reason = this.hooks.createValueGrip(reason);
- }
- promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
- // Only add the timeToSettle property if the Promise isn't pending.
- if (state !== "pending") {
- promiseState.timeToSettle = this.obj.promiseTimeToResolution;
- }
- return promiseState;
- },
- /**
- * Releases this actor from the pool.
- */
- release: function () {
- if (this.registeredPool.objectActors) {
- this.registeredPool.objectActors.delete(this.obj);
- }
- this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
- this.iterators.clear();
- this.registeredPool.removeActor(this);
- },
- /**
- * Handle a protocol request to provide the definition site of this function
- * object.
- */
- onDefinitionSite: function () {
- if (this.obj.class != "Function") {
- return {
- from: this.actorID,
- error: "objectNotFunction",
- message: this.actorID + " is not a function."
- };
- }
- if (!this.obj.script) {
- return {
- from: this.actorID,
- error: "noScript",
- message: this.actorID + " has no Debugger.Script"
- };
- }
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
- this.obj.script.startLine,
- 0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn
- };
- });
- },
- /**
- * Handle a protocol request to provide the names of the properties defined on
- * the object and not its prototype.
- */
- onOwnPropertyNames: function () {
- return { from: this.actorID,
- ownPropertyNames: this.obj.getOwnPropertyNames() };
- },
- /**
- * Creates an actor to iterate over an object property names and values.
- * See PropertyIteratorActor constructor for more info about options param.
- *
- * @param request object
- * The protocol request object.
- */
- onEnumProperties: function (request) {
- let actor = new PropertyIteratorActor(this, request.options);
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
- /**
- * Creates an actor to iterate over entries of a Map/Set-like object.
- */
- onEnumEntries: function () {
- let actor = new PropertyIteratorActor(this, { enumEntries: true });
- this.registeredPool.addActor(actor);
- this.iterators.add(actor);
- return { iterator: actor.grip() };
- },
- /**
- * Handle a protocol request to provide the prototype and own properties of
- * the object.
- */
- onPrototypeAndProperties: function () {
- let ownProperties = Object.create(null);
- let names;
- try {
- names = this.obj.getOwnPropertyNames();
- } catch (ex) {
- // The above can throw if this.obj points to a dead object.
- // TODO: we should use Cu.isDeadWrapper() - see bug 885800.
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(null),
- ownProperties: ownProperties,
- safeGetterValues: Object.create(null) };
- }
- for (let name of names) {
- ownProperties[name] = this._propertyDescriptor(name);
- }
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(this.obj.proto),
- ownProperties: ownProperties,
- safeGetterValues: this._findSafeGetterValues(names) };
- },
- /**
- * Find the safe getter values for the current Debugger.Object, |this.obj|.
- *
- * @private
- * @param array ownProperties
- * The array that holds the list of known ownProperties names for
- * |this.obj|.
- * @param number [limit=0]
- * Optional limit of getter values to find.
- * @return object
- * An object that maps property names to safe getter descriptors as
- * defined by the remote debugging protocol.
- */
- _findSafeGetterValues: function (ownProperties, limit = 0) {
- let safeGetterValues = Object.create(null);
- let obj = this.obj;
- let level = 0, i = 0;
- // Most objects don't have any safe getters but inherit some from their
- // prototype. Avoid calling getOwnPropertyNames on objects that may have
- // many properties like Array, strings or js objects. That to avoid
- // freezing firefox when doing so.
- if (TYPED_ARRAY_CLASSES.includes(this.obj.class) ||
- ["Array", "Object", "String"].includes(this.obj.class)) {
- obj = obj.proto;
- level++;
- }
- while (obj) {
- let getters = this._findSafeGetters(obj);
- for (let name of getters) {
- // Avoid overwriting properties from prototypes closer to this.obj. Also
- // avoid providing safeGetterValues from prototypes if property |name|
- // is already defined as an own property.
- if (name in safeGetterValues ||
- (obj != this.obj && ownProperties.indexOf(name) !== -1)) {
- continue;
- }
- // Ignore __proto__ on Object.prototye.
- if (!obj.proto && name == "__proto__") {
- continue;
- }
- let desc = null, getter = null;
- try {
- desc = obj.getOwnPropertyDescriptor(name);
- getter = desc.get;
- } catch (ex) {
- // The above can throw if the cache becomes stale.
- }
- if (!getter) {
- obj._safeGetters = null;
- continue;
- }
- let result = getter.call(this.obj);
- if (result && !("throw" in result)) {
- let getterValue = undefined;
- if ("return" in result) {
- getterValue = result.return;
- } else if ("yield" in result) {
- getterValue = result.yield;
- }
- // WebIDL attributes specified with the LenientThis extended attribute
- // return undefined and should be ignored.
- if (getterValue !== undefined) {
- safeGetterValues[name] = {
- getterValue: this.hooks.createValueGrip(getterValue),
- getterPrototypeLevel: level,
- enumerable: desc.enumerable,
- writable: level == 0 ? desc.writable : true,
- };
- if (limit && ++i == limit) {
- break;
- }
- }
- }
- }
- if (limit && i == limit) {
- break;
- }
- obj = obj.proto;
- level++;
- }
- return safeGetterValues;
- },
- /**
- * Find the safe getters for a given Debugger.Object. Safe getters are native
- * getters which are safe to execute.
- *
- * @private
- * @param Debugger.Object object
- * The Debugger.Object where you want to find safe getters.
- * @return Set
- * A Set of names of safe getters. This result is cached for each
- * Debugger.Object.
- */
- _findSafeGetters: function (object) {
- if (object._safeGetters) {
- return object._safeGetters;
- }
- let getters = new Set();
- let names = [];
- try {
- names = object.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- for (let name of names) {
- let desc = null;
- try {
- desc = object.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (!desc || desc.value !== undefined || !("get" in desc)) {
- continue;
- }
- if (DevToolsUtils.hasSafeGetter(desc)) {
- getters.add(name);
- }
- }
- object._safeGetters = getters;
- return getters;
- },
- /**
- * Handle a protocol request to provide the prototype of the object.
- */
- onPrototype: function () {
- return { from: this.actorID,
- prototype: this.hooks.createValueGrip(this.obj.proto) };
- },
- /**
- * Handle a protocol request to provide the property descriptor of the
- * object's specified property.
- *
- * @param request object
- * The protocol request object.
- */
- onProperty: function (request) {
- if (!request.name) {
- return { error: "missingParameter",
- message: "no property name was specified" };
- }
- return { from: this.actorID,
- descriptor: this._propertyDescriptor(request.name) };
- },
- /**
- * Handle a protocol request to provide the display string for the object.
- */
- onDisplayString: function () {
- const string = stringify(this.obj);
- return { from: this.actorID,
- displayString: this.hooks.createValueGrip(string) };
- },
- /**
- * A helper method that creates a property descriptor for the provided object,
- * properly formatted for sending in a protocol response.
- *
- * @private
- * @param string name
- * The property that the descriptor is generated for.
- * @param boolean [onlyEnumerable]
- * Optional: true if you want a descriptor only for an enumerable
- * property, false otherwise.
- * @return object|undefined
- * The property descriptor, or undefined if this is not an enumerable
- * property and onlyEnumerable=true.
- */
- _propertyDescriptor: function (name, onlyEnumerable) {
- let desc;
- try {
- desc = this.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072). Inform the user with a bogus, but hopefully
- // explanatory, descriptor.
- return {
- configurable: false,
- writable: false,
- enumerable: false,
- value: e.name
- };
- }
- if (!desc || onlyEnumerable && !desc.enumerable) {
- return undefined;
- }
- let retval = {
- configurable: desc.configurable,
- enumerable: desc.enumerable
- };
- if ("value" in desc) {
- retval.writable = desc.writable;
- retval.value = this.hooks.createValueGrip(desc.value);
- } else {
- if ("get" in desc) {
- retval.get = this.hooks.createValueGrip(desc.get);
- }
- if ("set" in desc) {
- retval.set = this.hooks.createValueGrip(desc.set);
- }
- }
- return retval;
- },
- /**
- * Handle a protocol request to provide the source code of a function.
- *
- * @param request object
- * The protocol request object.
- */
- onDecompile: function (request) {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "decompile request is only valid for object grips " +
- "with a 'Function' class." };
- }
- return { from: this.actorID,
- decompiledCode: this.obj.decompile(!!request.pretty) };
- },
- /**
- * Handle a protocol request to provide the parameters of a function.
- */
- onParameterNames: function () {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "'parameterNames' request is only valid for object " +
- "grips with a 'Function' class." };
- }
- return { parameterNames: this.obj.parameterNames };
- },
- /**
- * Handle a protocol request to release a thread-lifetime grip.
- */
- onRelease: function () {
- this.release();
- return {};
- },
- /**
- * Handle a protocol request to provide the lexical scope of a function.
- */
- onScope: function () {
- if (this.obj.class !== "Function") {
- return { error: "objectNotFunction",
- message: "scope request is only valid for object grips with a" +
- " 'Function' class." };
- }
- let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
- this.registeredPool);
- if (!envActor) {
- return { error: "notDebuggee",
- message: "cannot access the environment of this function." };
- }
- return { from: this.actorID, scope: envActor.form() };
- },
- /**
- * Handle a protocol request to get the list of dependent promises of a
- * promise.
- *
- * @return object
- * Returns an object containing an array of object grips of the
- * dependent promises
- */
- onDependentPromises: function () {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'dependentPromises' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
- let promises = this.obj.promiseDependentPromises.map(p => this.hooks.createValueGrip(p));
- return { promises };
- },
- /**
- * Handle a protocol request to get the allocation stack of a promise.
- */
- onAllocationStack: function () {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'allocationStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
- let stack = this.obj.promiseAllocationSite;
- let allocationStacks = [];
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
- if (source) {
- allocationStacks.push(source);
- }
- }
- stack = stack.parent;
- }
- return Promise.all(allocationStacks).then(stacks => {
- return { allocationStack: stacks };
- });
- },
- /**
- * Handle a protocol request to get the fulfillment stack of a promise.
- */
- onFulfillmentStack: function () {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'fulfillmentStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
- let stack = this.obj.promiseResolutionSite;
- let fulfillmentStacks = [];
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
- if (source) {
- fulfillmentStacks.push(source);
- }
- }
- stack = stack.parent;
- }
- return Promise.all(fulfillmentStacks).then(stacks => {
- return { fulfillmentStack: stacks };
- });
- },
- /**
- * Handle a protocol request to get the rejection stack of a promise.
- */
- onRejectionStack: function () {
- if (this.obj.class != "Promise") {
- return { error: "objectNotPromise",
- message: "'rejectionStack' request is only valid for " +
- "object grips with a 'Promise' class." };
- }
- let stack = this.obj.promiseResolutionSite;
- let rejectionStacks = [];
- while (stack) {
- if (stack.source) {
- let source = this._getSourceOriginalLocation(stack);
- if (source) {
- rejectionStacks.push(source);
- }
- }
- stack = stack.parent;
- }
- return Promise.all(rejectionStacks).then(stacks => {
- return { rejectionStack: stacks };
- });
- },
- /**
- * Helper function for fetching the source location of a SavedFrame stack.
- *
- * @param SavedFrame stack
- * The promise allocation stack frame
- * @return object
- * Returns an object containing the source location of the SavedFrame
- * stack.
- */
- _getSourceOriginalLocation: function (stack) {
- let source;
- // Catch any errors if the source actor cannot be found
- try {
- source = this.hooks.sources().getSourceActorByURL(stack.source);
- } catch (e) {}
- if (!source) {
- return null;
- }
- return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
- source,
- stack.line,
- stack.column
- )).then((originalLocation) => {
- return {
- source: originalLocation.originalSourceActor.form(),
- line: originalLocation.originalLine,
- column: originalLocation.originalColumn,
- functionDisplayName: stack.functionDisplayName
- };
- });
- }
- };
- ObjectActor.prototype.requestTypes = {
- "definitionSite": ObjectActor.prototype.onDefinitionSite,
- "parameterNames": ObjectActor.prototype.onParameterNames,
- "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
- "enumProperties": ObjectActor.prototype.onEnumProperties,
- "prototype": ObjectActor.prototype.onPrototype,
- "property": ObjectActor.prototype.onProperty,
- "displayString": ObjectActor.prototype.onDisplayString,
- "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
- "decompile": ObjectActor.prototype.onDecompile,
- "release": ObjectActor.prototype.onRelease,
- "scope": ObjectActor.prototype.onScope,
- "dependentPromises": ObjectActor.prototype.onDependentPromises,
- "allocationStack": ObjectActor.prototype.onAllocationStack,
- "fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
- "rejectionStack": ObjectActor.prototype.onRejectionStack,
- "enumEntries": ObjectActor.prototype.onEnumEntries,
- };
- /**
- * Creates an actor to iterate over an object's property names and values.
- *
- * @param objectActor ObjectActor
- * The object actor.
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - enumEntries Boolean
- * If true, enumerates the entries of a Map or Set object
- * instead of enumerating properties.
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * - query String
- * If non-empty, will filter the properties by names and values
- * containing this query string. The match is not case-sensitive.
- * Regarding value filtering it just compare to the stringification
- * of the property value.
- */
- function PropertyIteratorActor(objectActor, options) {
- if (options.enumEntries) {
- let cls = objectActor.obj.class;
- if (cls == "Map") {
- this.iterator = enumMapEntries(objectActor);
- } else if (cls == "WeakMap") {
- this.iterator = enumWeakMapEntries(objectActor);
- } else if (cls == "Set") {
- this.iterator = enumSetEntries(objectActor);
- } else if (cls == "WeakSet") {
- this.iterator = enumWeakSetEntries(objectActor);
- } else {
- throw new Error("Unsupported class to enumerate entries from: " + cls);
- }
- } else if (options.ignoreNonIndexedProperties && !options.query) {
- this.iterator = enumArrayProperties(objectActor, options);
- } else {
- this.iterator = enumObjectProperties(objectActor, options);
- }
- }
- PropertyIteratorActor.prototype = {
- actorPrefix: "propertyIterator",
- grip() {
- return {
- type: this.actorPrefix,
- actor: this.actorID,
- count: this.iterator.size
- };
- },
- names({ indexes }) {
- let list = [];
- for (let idx of indexes) {
- list.push(this.iterator.propertyName(idx));
- }
- return {
- names: indexes
- };
- },
- slice({ start, count }) {
- let ownProperties = Object.create(null);
- for (let i = start, m = start + count; i < m; i++) {
- let name = this.iterator.propertyName(i);
- ownProperties[name] = this.iterator.propertyDescription(i);
- }
- return {
- ownProperties
- };
- },
- all() {
- return this.slice({ start: 0, count: this.length });
- }
- };
- PropertyIteratorActor.prototype.requestTypes = {
- "names": PropertyIteratorActor.prototype.names,
- "slice": PropertyIteratorActor.prototype.slice,
- "all": PropertyIteratorActor.prototype.all,
- };
- function enumArrayProperties(objectActor, options) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- if (typeof length !== "number") {
- // Pseudo arrays are flagged as ArrayLike if they have
- // subsequent indexed properties without having any length attribute.
- length = 0;
- let names = objectActor.obj.getOwnPropertyNames();
- for (let key of names) {
- if (isNaN(key) || key != length++) {
- break;
- }
- }
- }
- return {
- size: length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- return objectActor._propertyDescriptor(index);
- }
- };
- }
- function enumObjectProperties(objectActor, options) {
- let names = [];
- try {
- names = objectActor.obj.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- if (options.ignoreNonIndexedProperties || options.ignoreIndexedProperties) {
- let length = DevToolsUtils.getProperty(objectActor.obj, "length");
- if (typeof length !== "number") {
- // Pseudo arrays are flagged as ArrayLike if they have
- // subsequent indexed properties without having any length attribute.
- length = 0;
- for (let key of names) {
- if (isNaN(key) || key != length++) {
- break;
- }
- }
- }
- // It appears that getOwnPropertyNames always returns indexed properties
- // first, so we can safely slice `names` for/against indexed properties.
- // We do such clever operation to optimize very large array inspection,
- // like webaudio buffers.
- if (options.ignoreIndexedProperties) {
- // Keep items after `length` index
- names = names.slice(length);
- } else if (options.ignoreNonIndexedProperties) {
- // Remove `length` first items
- names.splice(length);
- }
- }
- let safeGetterValues = objectActor._findSafeGetterValues(names, 0);
- let safeGetterNames = Object.keys(safeGetterValues);
- // Merge the safe getter values into the existing properties list.
- for (let name of safeGetterNames) {
- if (!names.includes(name)) {
- names.push(name);
- }
- }
- if (options.query) {
- let { query } = options;
- query = query.toLowerCase();
- names = names.filter(name => {
- // Filter on attribute names
- if (name.toLowerCase().includes(query)) {
- return true;
- }
- // and then on attribute values
- let desc;
- try {
- desc = objectActor.obj.getOwnPropertyDescriptor(name);
- } catch (e) {
- // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
- // allowed (bug 560072).
- }
- if (desc && desc.value &&
- String(desc.value).includes(query)) {
- return true;
- }
- return false;
- });
- }
- if (options.sort) {
- names.sort();
- }
- return {
- size: names.length,
- propertyName(index) {
- return names[index];
- },
- propertyDescription(index) {
- let name = names[index];
- let desc = objectActor._propertyDescriptor(name);
- if (!desc) {
- desc = safeGetterValues[name];
- } else if (name in safeGetterValues) {
- // Merge the safe getter values into the existing properties list.
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- desc.getterValue = getterValue;
- desc.getterPrototypeLevel = getterPrototypeLevel;
- }
- return desc;
- }
- };
- }
- /**
- * Helper function to create a grip from a Map/Set entry
- */
- function gripFromEntry({ obj, hooks }, entry) {
- return hooks.createValueGrip(
- makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
- }
- function enumMapEntries(objectActor) {
- // Iterating over a Map via .entries goes through various intermediate
- // objects - an Iterator object, then a 2-element Array object, then the
- // actual values we care about. We don't have Xrays to Iterator objects,
- // so we get Opaque wrappers for them. And even though we have Xrays to
- // Arrays, the semantics often deny access to the entires based on the
- // nature of the values. So we need waive Xrays for the iterator object
- // and the tupes, and then re-apply them on the underlying values until
- // we fix bug 1023984.
- //
- // Even then though, we might want to continue waiving Xrays here for the
- // same reason we do so for Arrays above - this filtering behavior is likely
- // to be more confusing than beneficial in the case of Object previews.
- let raw = objectActor.obj.unsafeDereference();
- let keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = Map.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = Map.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
- }
- function enumWeakMapEntries(objectActor) {
- // We currently lack XrayWrappers for WeakMap, so when we iterate over
- // the values, the temporary iterator objects get created in the target
- // compartment. However, we _do_ have Xrays to Object now, so we end up
- // Xraying those temporary objects, and filtering access to |it.value|
- // based on whether or not it's Xrayable and/or callable, which breaks
- // the for/of iteration.
- //
- // This code is designed to handle untrusted objects, so we can safely
- // waive Xrays on the iterable, and relying on the Debugger machinery to
- // make sure we handle the resulting objects carefully.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(raw));
- return {
- [Symbol.iterator]: function* () {
- for (let key of keys) {
- let value = WeakMap.prototype.get.call(raw, key);
- yield [ key, value ].map(val => gripFromEntry(objectActor, val));
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let key = keys[index];
- let val = WeakMap.prototype.get.call(raw, key);
- return {
- enumerable: true,
- value: {
- type: "mapEntry",
- preview: {
- key: gripFromEntry(objectActor, key),
- value: gripFromEntry(objectActor, val)
- }
- }
- };
- }
- };
- }
- function enumSetEntries(objectActor) {
- // We currently lack XrayWrappers for Set, so when we iterate over
- // the values, the temporary iterator objects get created in the target
- // compartment. However, we _do_ have Xrays to Object now, so we end up
- // Xraying those temporary objects, and filtering access to |it.value|
- // based on whether or not it's Xrayable and/or callable, which breaks
- // the for/of iteration.
- //
- // This code is designed to handle untrusted objects, so we can safely
- // waive Xrays on the iterable, and relying on the Debugger machinery to
- // make sure we handle the resulting objects carefully.
- let raw = objectActor.obj.unsafeDereference();
- let values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
- return {
- [Symbol.iterator]: function* () {
- for (let item of values) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: values.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = values[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
- }
- function enumWeakSetEntries(objectActor) {
- // We currently lack XrayWrappers for WeakSet, so when we iterate over
- // the values, the temporary iterator objects get created in the target
- // compartment. However, we _do_ have Xrays to Object now, so we end up
- // Xraying those temporary objects, and filtering access to |it.value|
- // based on whether or not it's Xrayable and/or callable, which breaks
- // the for/of iteration.
- //
- // This code is designed to handle untrusted objects, so we can safely
- // waive Xrays on the iterable, and relying on the Debugger machinery to
- // make sure we handle the resulting objects carefully.
- let raw = objectActor.obj.unsafeDereference();
- let keys = Cu.waiveXrays(
- ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(raw));
- return {
- [Symbol.iterator]: function* () {
- for (let item of keys) {
- yield gripFromEntry(objectActor, item);
- }
- },
- size: keys.length,
- propertyName(index) {
- return index;
- },
- propertyDescription(index) {
- let val = keys[index];
- return {
- enumerable: true,
- value: gripFromEntry(objectActor, val)
- };
- }
- };
- }
- /**
- * Functions for adding information to ObjectActor grips for the purpose of
- * having customized output. This object holds arrays mapped by
- * Debugger.Object.prototype.class.
- *
- * In each array you can add functions that take three
- * arguments:
- * - the ObjectActor instance and its hooks to make a preview for,
- * - the grip object being prepared for the client,
- * - the raw JS object after calling Debugger.Object.unsafeDereference(). This
- * argument is only provided if the object is safe for reading properties and
- * executing methods. See DevToolsUtils.isSafeJSObject().
- *
- * Functions must return false if they cannot provide preview
- * information for the debugger object, or true otherwise.
- */
- DebuggerServer.ObjectActorPreviewers = {
- String: [function (objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj);
- }],
- Boolean: [function (objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
- }],
- Number: [function (objectActor, grip, rawObj) {
- return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
- }],
- Function: [function ({obj, hooks}, grip) {
- if (obj.name) {
- grip.name = obj.name;
- }
- if (obj.displayName) {
- grip.displayName = obj.displayName.substr(0, 500);
- }
- if (obj.parameterNames) {
- grip.parameterNames = obj.parameterNames;
- }
- // Check if the developer has added a de-facto standard displayName
- // property for us to use.
- let userDisplayName;
- try {
- userDisplayName = obj.getOwnPropertyDescriptor("displayName");
- } catch (e) {
- // Calling getOwnPropertyDescriptor with displayName might throw
- // with "permission denied" errors for some functions.
- dumpn(e);
- }
- if (userDisplayName && typeof userDisplayName.value == "string" &&
- userDisplayName.value) {
- grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
- }
- let dbgGlobal = hooks.getGlobalDebugObject();
- if (dbgGlobal) {
- let script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
- if (script) {
- grip.location = {
- url: script.url,
- line: script.startLine
- };
- }
- }
- return true;
- }],
- RegExp: [function ({obj, hooks}, grip) {
- let str = RegExp.prototype.toString.call(obj.unsafeDereference());
- grip.displayString = hooks.createValueGrip(str);
- return true;
- }],
- Date: [function ({obj, hooks}, grip) {
- let time = Date.prototype.getTime.call(obj.unsafeDereference());
- grip.preview = {
- timestamp: hooks.createValueGrip(time),
- };
- return true;
- }],
- Array: [function ({obj, hooks}, grip) {
- let length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- return false;
- }
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
- if (hooks.getGripDepth() > 1) {
- return true;
- }
- let raw = obj.unsafeDereference();
- let items = grip.preview.items = [];
- for (let i = 0; i < length; ++i) {
- // Array Xrays filter out various possibly-unsafe properties (like
- // functions, and claim that the value is undefined instead. This
- // is generally the right thing for privileged code accessing untrusted
- // objects, but quite confusing for Object previews. So we manually
- // override this protection by waiving Xrays on the array, and re-applying
- // Xrays on any indexed value props that we pull off of it.
- let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
- if (desc && !desc.get && !desc.set) {
- let value = Cu.unwaiveXrays(desc.value);
- value = makeDebuggeeValueIfNeeded(obj, value);
- items.push(hooks.createValueGrip(value));
- } else {
- items.push(null);
- }
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- return true;
- }],
- Set: [function (objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
- grip.preview = {
- kind: "ArrayLike",
- length: size,
- };
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
- let items = grip.preview.items = [];
- for (let item of enumSetEntries(objectActor)) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- return true;
- }],
- WeakSet: [function (objectActor, grip) {
- let enumEntries = enumWeakSetEntries(objectActor);
- grip.preview = {
- kind: "ArrayLike",
- length: enumEntries.size
- };
- // Avoid recursive object grips.
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
- let items = grip.preview.items = [];
- for (let item of enumEntries) {
- items.push(item);
- if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- return true;
- }],
- Map: [function (objectActor, grip) {
- let size = DevToolsUtils.getProperty(objectActor.obj, "size");
- if (typeof size != "number") {
- return false;
- }
- grip.preview = {
- kind: "MapLike",
- size: size,
- };
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
- let entries = grip.preview.entries = [];
- for (let entry of enumMapEntries(objectActor)) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- return true;
- }],
- WeakMap: [function (objectActor, grip) {
- let enumEntries = enumWeakMapEntries(objectActor);
- grip.preview = {
- kind: "MapLike",
- size: enumEntries.size
- };
- if (objectActor.hooks.getGripDepth() > 1) {
- return true;
- }
- let entries = grip.preview.entries = [];
- for (let entry of enumEntries) {
- entries.push(entry);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- return true;
- }],
- DOMStringMap: [function ({obj, hooks}, grip, rawObj) {
- if (!rawObj) {
- return false;
- }
- let keys = obj.getOwnPropertyNames();
- grip.preview = {
- kind: "MapLike",
- size: keys.length,
- };
- if (hooks.getGripDepth() > 1) {
- return true;
- }
- let entries = grip.preview.entries = [];
- for (let key of keys) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
- entries.push([key, hooks.createValueGrip(value)]);
- if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- return true;
- }],
- Proxy: [function ({obj, hooks}, grip, rawObj) {
- grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- ownPropertiesLength: 2
- };
- if (hooks.getGripDepth() > 1) {
- return true;
- }
- grip.preview.ownProperties['<target>'] = {value: grip.proxyTarget};
- grip.preview.ownProperties['<handler>'] = {value: grip.proxyHandler};
- return true;
- }],
- };
- /**
- * Generic previewer for classes wrapping primitives, like String,
- * Number and Boolean.
- *
- * @param string className
- * Class name to expect.
- * @param object classObj
- * The class to expect, eg. String. The valueOf() method of the class is
- * invoked on the given object.
- * @param ObjectActor objectActor
- * The object actor
- * @param Object grip
- * The result grip to fill in
- * @return Booolean true if the object was handled, false otherwise
- */
- function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
- let {obj, hooks} = objectActor;
- if (!obj.proto || obj.proto.class != className) {
- return false;
- }
- let v = null;
- try {
- v = classObj.prototype.valueOf.call(rawObj);
- } catch (ex) {
- // valueOf() can throw if the raw JS object is "misbehaved".
- return false;
- }
- if (v === null) {
- return false;
- }
- let canHandle = GenericObject(objectActor, grip, rawObj, className === "String");
- if (!canHandle) {
- return false;
- }
- grip.preview.wrappedValue =
- hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
- return true;
- }
- function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
- let {obj, hooks} = objectActor;
- if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
- return false;
- }
- let i = 0, names = [];
- let preview = grip.preview = {
- kind: "Object",
- ownProperties: Object.create(null),
- };
- try {
- names = obj.getOwnPropertyNames();
- } catch (ex) {
- // Calling getOwnPropertyNames() on some wrapped native prototypes is not
- // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
- }
- preview.ownPropertiesLength = names.length;
- let length;
- if (specialStringBehavior) {
- length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- specialStringBehavior = false;
- }
- }
- for (let name of names) {
- if (specialStringBehavior && /^[0-9]+$/.test(name)) {
- let num = parseInt(name, 10);
- if (num.toString() === name && num >= 0 && num < length) {
- continue;
- }
- }
- let desc = objectActor._propertyDescriptor(name, true);
- if (!desc) {
- continue;
- }
- preview.ownProperties[name] = desc;
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- if (i < OBJECT_PREVIEW_MAX_ITEMS) {
- preview.safeGetterValues = objectActor._findSafeGetterValues(
- Object.keys(preview.ownProperties),
- OBJECT_PREVIEW_MAX_ITEMS - i);
- }
- return true;
- }
- // Preview functions that do not rely on the object class.
- DebuggerServer.ObjectActorPreviewers.Object = [
- function TypedArray({obj, hooks}, grip) {
- if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) {
- return false;
- }
- let length = DevToolsUtils.getProperty(obj, "length");
- if (typeof length != "number") {
- return false;
- }
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
- if (hooks.getGripDepth() > 1) {
- return true;
- }
- let raw = obj.unsafeDereference();
- let global = Cu.getGlobalForObject(DebuggerServer);
- let classProto = global[obj.class].prototype;
- // The Xray machinery for TypedArrays denies indexed access on the grounds
- // that it's slow, and advises callers to do a structured clone instead.
- let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
- OBJECT_PREVIEW_MAX_ITEMS), global);
- let items = grip.preview.items = [];
- for (let i = 0; i < safeView.length; i++) {
- items.push(safeView[i]);
- }
- return true;
- },
- function Error({obj, hooks}, grip) {
- switch (obj.class) {
- case "Error":
- case "EvalError":
- case "RangeError":
- case "ReferenceError":
- case "SyntaxError":
- case "TypeError":
- case "URIError":
- let name = DevToolsUtils.getProperty(obj, "name");
- let msg = DevToolsUtils.getProperty(obj, "message");
- let stack = DevToolsUtils.getProperty(obj, "stack");
- let fileName = DevToolsUtils.getProperty(obj, "fileName");
- let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
- let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
- grip.preview = {
- kind: "Error",
- name: hooks.createValueGrip(name),
- message: hooks.createValueGrip(msg),
- stack: hooks.createValueGrip(stack),
- fileName: hooks.createValueGrip(fileName),
- lineNumber: hooks.createValueGrip(lineNumber),
- columnNumber: hooks.createValueGrip(columnNumber),
- };
- return true;
- default:
- return false;
- }
- },
- function CSSMediaRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSMediaRule)) {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.conditionText),
- };
- return true;
- },
- function CSSStyleRule({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSStyleRule)) {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithText",
- text: hooks.createValueGrip(rawObj.selectorText),
- };
- return true;
- },
- function ObjectWithURL({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSImportRule ||
- rawObj instanceof Ci.nsIDOMCSSStyleSheet ||
- rawObj instanceof Ci.nsIDOMLocation ||
- rawObj instanceof Ci.nsIDOMWindow)) {
- return false;
- }
- let url;
- if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
- url = rawObj.location.href;
- } else if (rawObj.href) {
- url = rawObj.href;
- } else {
- return false;
- }
- grip.preview = {
- kind: "ObjectWithURL",
- url: hooks.createValueGrip(url),
- };
- return true;
- },
- function ArrayLike({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- obj.class != "DOMStringList" &&
- obj.class != "DOMTokenList" &&
- !(rawObj instanceof Ci.nsIDOMMozNamedAttrMap ||
- rawObj instanceof Ci.nsIDOMCSSRuleList ||
- rawObj instanceof Ci.nsIDOMCSSValueList ||
- rawObj instanceof Ci.nsIDOMFileList ||
- rawObj instanceof Ci.nsIDOMFontFaceList ||
- rawObj instanceof Ci.nsIDOMMediaList ||
- rawObj instanceof Ci.nsIDOMNodeList ||
- rawObj instanceof Ci.nsIDOMStyleSheetList)) {
- return false;
- }
- if (typeof rawObj.length != "number") {
- return false;
- }
- grip.preview = {
- kind: "ArrayLike",
- length: rawObj.length,
- };
- if (hooks.getGripDepth() > 1) {
- return true;
- }
- let items = grip.preview.items = [];
- for (let i = 0; i < rawObj.length &&
- items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
- let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
- items.push(hooks.createValueGrip(value));
- }
- return true;
- },
- function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) {
- return false;
- }
- grip.preview = {
- kind: "MapLike",
- size: rawObj.length,
- };
- let entries = grip.preview.entries = [];
- for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
- i < rawObj.length; i++) {
- let prop = rawObj[i];
- let value = rawObj.getPropertyValue(prop);
- entries.push([prop, hooks.createValueGrip(value)]);
- }
- return true;
- },
- function DOMNode({obj, hooks}, grip, rawObj) {
- if (isWorker || obj.class == "Object" || !rawObj ||
- !(rawObj instanceof Ci.nsIDOMNode)) {
- return false;
- }
- let preview = grip.preview = {
- kind: "DOMNode",
- nodeType: rawObj.nodeType,
- nodeName: rawObj.nodeName,
- };
- if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
- preview.location = hooks.createValueGrip(rawObj.location.href);
- } else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
- preview.childNodesLength = rawObj.childNodes.length;
- if (hooks.getGripDepth() < 2) {
- preview.childNodes = [];
- for (let node of rawObj.childNodes) {
- let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
- preview.childNodes.push(actor);
- if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- } else if (rawObj instanceof Ci.nsIDOMElement) {
- // Add preview for DOM element attributes.
- if (rawObj instanceof Ci.nsIDOMHTMLElement) {
- preview.nodeName = preview.nodeName.toLowerCase();
- }
- let i = 0;
- preview.attributes = {};
- preview.attributesLength = rawObj.attributes.length;
- for (let attr of rawObj.attributes) {
- preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
- }
- } else if (rawObj instanceof Ci.nsIDOMAttr) {
- preview.value = hooks.createValueGrip(rawObj.value);
- } else if (rawObj instanceof Ci.nsIDOMText ||
- rawObj instanceof Ci.nsIDOMComment) {
- preview.textContent = hooks.createValueGrip(rawObj.textContent);
- }
- return true;
- },
- function DOMEvent({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
- return false;
- }
- let preview = grip.preview = {
- kind: "DOMEvent",
- type: rawObj.type,
- properties: Object.create(null),
- };
- if (hooks.getGripDepth() < 2) {
- let target = obj.makeDebuggeeValue(rawObj.target);
- preview.target = hooks.createValueGrip(target);
- }
- let props = [];
- if (rawObj instanceof Ci.nsIDOMMouseEvent) {
- props.push("buttons", "clientX", "clientY", "layerX", "layerY");
- } else if (rawObj instanceof Ci.nsIDOMKeyEvent) {
- let modifiers = [];
- if (rawObj.altKey) {
- modifiers.push("Alt");
- }
- if (rawObj.ctrlKey) {
- modifiers.push("Control");
- }
- if (rawObj.metaKey) {
- modifiers.push("Meta");
- }
- if (rawObj.shiftKey) {
- modifiers.push("Shift");
- }
- preview.eventKind = "key";
- preview.modifiers = modifiers;
- props.push("key", "charCode", "keyCode");
- } else if (rawObj instanceof Ci.nsIDOMTransitionEvent) {
- props.push("propertyName", "pseudoElement");
- } else if (rawObj instanceof Ci.nsIDOMAnimationEvent) {
- props.push("animationName", "pseudoElement");
- } else if (rawObj instanceof Ci.nsIDOMClipboardEvent) {
- props.push("clipboardData");
- }
- // Add event-specific properties.
- for (let prop of props) {
- let value = rawObj[prop];
- if (value && (typeof value == "object" || typeof value == "function")) {
- // Skip properties pointing to objects.
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- }
- // Add any properties we find on the event object.
- if (!props.length) {
- let i = 0;
- for (let prop in rawObj) {
- let value = rawObj[prop];
- if (prop == "target" || prop == "type" || value === null ||
- typeof value == "function") {
- continue;
- }
- if (value && typeof value == "object") {
- if (hooks.getGripDepth() > 1) {
- continue;
- }
- value = obj.makeDebuggeeValue(value);
- }
- preview.properties[prop] = hooks.createValueGrip(value);
- if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
- break;
- }
- }
- }
- return true;
- },
- function DOMException({obj, hooks}, grip, rawObj) {
- if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
- return false;
- }
- grip.preview = {
- kind: "DOMException",
- name: hooks.createValueGrip(rawObj.name),
- message: hooks.createValueGrip(rawObj.message),
- code: hooks.createValueGrip(rawObj.code),
- result: hooks.createValueGrip(rawObj.result),
- filename: hooks.createValueGrip(rawObj.filename),
- lineNumber: hooks.createValueGrip(rawObj.lineNumber),
- columnNumber: hooks.createValueGrip(rawObj.columnNumber),
- };
- return true;
- },
- function PseudoArray({obj, hooks}, grip, rawObj) {
- let length;
- let keys = obj.getOwnPropertyNames();
- if (keys.length == 0) {
- return false;
- }
- // If no item is going to be displayed in preview, better display as sparse object.
- // The first key should contain the smallest integer index (if any).
- if(keys[0] >= OBJECT_PREVIEW_MAX_ITEMS) {
- return false;
- }
- // Pseudo-arrays should only have array indices and, optionally, a "length" property.
- // Since integer indices are sorted first, check if the last property is "length".
- if(keys[keys.length-1] === "length") {
- keys.pop();
- length = DevToolsUtils.getProperty(obj, "length");
- } else {
- // Otherwise, let length be the (presumably) greatest array index plus 1.
- length = +keys[keys.length-1] + 1;
- }
- // Check if length is a valid array length, i.e. is a Uint32 number.
- if(typeof length !== "number" || length >>> 0 !== length) {
- return false;
- }
- // Ensure all keys are increasing array indices smaller than length. The order is not
- // guaranteed for exotic objects but, in most cases, big array indices and properties
- // which are not integer indices should be at the end. Then, iterating backwards
- // allows us to return earlier when the object is not completely a pseudo-array.
- let prev = length;
- for(let i = keys.length - 1; i >= 0; --i) {
- let key = keys[i];
- let numKey = key >>> 0; // ToUint32(key)
- if (numKey + '' !== key || numKey >= prev) {
- return false;
- }
- prev = numKey;
- }
- grip.preview = {
- kind: "ArrayLike",
- length: length,
- };
- // Avoid recursive object grips.
- if (hooks.getGripDepth() > 1) {
- return true;
- }
- let items = grip.preview.items = [];
- let numItems = Math.min(OBJECT_PREVIEW_MAX_ITEMS, length);
- for (let i = 0; i < numItems; ++i) {
- let desc = obj.getOwnPropertyDescriptor(i);
- if (desc && 'value' in desc) {
- items.push(hooks.createValueGrip(desc.value));
- } else {
- items.push(null);
- }
- }
- return true;
- },
- function Object(objectActor, grip, rawObj) {
- return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false);
- },
- ];
- /**
- * Get thisDebugger.Object referent's `promiseState`.
- *
- * @returns Object
- * An object of one of the following forms:
- * - { state: "pending" }
- * - { state: "fulfilled", value }
- * - { state: "rejected", reason }
- */
- function getPromiseState(obj) {
- if (obj.class != "Promise") {
- throw new Error(
- "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
- "refer to Promise objects.");
- }
- let state = { state: obj.promiseState };
- if (state.state === "fulfilled") {
- state.value = obj.promiseValue;
- } else if (state.state === "rejected") {
- state.reason = obj.promiseReason;
- }
- return state;
- }
- /**
- * Determine if a given value is non-primitive.
- *
- * @param Any value
- * The value to test.
- * @return Boolean
- * Whether the value is non-primitive.
- */
- function isObject(value) {
- const type = typeof value;
- return type == "object" ? value !== null : type == "function";
- }
- /**
- * Create a function that can safely stringify Debugger.Objects of a given
- * builtin type.
- *
- * @param Function ctor
- * The builtin class constructor.
- * @return Function
- * The stringifier for the class.
- */
- function createBuiltinStringifier(ctor) {
- return obj => ctor.prototype.toString.call(obj.unsafeDereference());
- }
- /**
- * Stringify a Debugger.Object-wrapped Error instance.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification of the object.
- */
- function errorStringify(obj) {
- let name = DevToolsUtils.getProperty(obj, "name");
- if (name === "" || name === undefined) {
- name = obj.class;
- } else if (isObject(name)) {
- name = stringify(name);
- }
- let message = DevToolsUtils.getProperty(obj, "message");
- if (isObject(message)) {
- message = stringify(message);
- }
- if (message === "" || message === undefined) {
- return name;
- }
- return name + ": " + message;
- }
- /**
- * Stringify a Debugger.Object based on its class.
- *
- * @param Debugger.Object obj
- * The object to stringify.
- * @return String
- * The stringification for the object.
- */
- function stringify(obj) {
- if (obj.class == "DeadObject") {
- const error = new Error("Dead object encountered.");
- DevToolsUtils.reportException("stringify", error);
- return "<dead object>";
- }
- const stringifier = stringifiers[obj.class] || stringifiers.Object;
- try {
- return stringifier(obj);
- } catch (e) {
- DevToolsUtils.reportException("stringify", e);
- return "<failed to stringify object>";
- }
- }
- // Used to prevent infinite recursion when an array is found inside itself.
- var seen = null;
- var stringifiers = {
- Error: errorStringify,
- EvalError: errorStringify,
- RangeError: errorStringify,
- ReferenceError: errorStringify,
- SyntaxError: errorStringify,
- TypeError: errorStringify,
- URIError: errorStringify,
- Boolean: createBuiltinStringifier(Boolean),
- Function: createBuiltinStringifier(Function),
- Number: createBuiltinStringifier(Number),
- RegExp: createBuiltinStringifier(RegExp),
- String: createBuiltinStringifier(String),
- Object: obj => "[object " + obj.class + "]",
- Array: obj => {
- // If we're at the top level then we need to create the Set for tracking
- // previously stringified arrays.
- const topLevel = !seen;
- if (topLevel) {
- seen = new Set();
- } else if (seen.has(obj)) {
- return "";
- }
- seen.add(obj);
- const len = DevToolsUtils.getProperty(obj, "length");
- let string = "";
- // The following check is only required because the debuggee could possibly
- // be a Proxy and return any value. For normal objects, array.length is
- // always a non-negative integer.
- if (typeof len == "number" && len > 0) {
- for (let i = 0; i < len; i++) {
- const desc = obj.getOwnPropertyDescriptor(i);
- if (desc) {
- const { value } = desc;
- if (value != null) {
- string += isObject(value) ? stringify(value) : value;
- }
- }
- if (i < len - 1) {
- string += ",";
- }
- }
- }
- if (topLevel) {
- seen = null;
- }
- return string;
- },
- DOMException: obj => {
- const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
- const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
- const code = DevToolsUtils.getProperty(obj, "code");
- const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
- return '[Exception... "' + message + '" ' +
- 'code: "' + code + '" ' +
- 'nsresult: "0x' + result + " (" + name + ')"]';
- },
- Promise: obj => {
- const { state, value, reason } = getPromiseState(obj);
- let statePreview = state;
- if (state != "pending") {
- const settledValue = state === "fulfilled" ? value : reason;
- statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
- ? stringify(settledValue)
- : settledValue);
- }
- return "Promise (" + statePreview + ")";
- },
- };
- /**
- * Make a debuggee value for the given object, if needed. Primitive values
- * are left the same.
- *
- * Use case: you have a raw JS object (after unsafe dereference) and you want to
- * send it to the client. In that case you need to use an ObjectActor which
- * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
- * method works only for JS objects and functions.
- *
- * @param Debugger.Object obj
- * @param any value
- * @return object
- */
- function makeDebuggeeValueIfNeeded(obj, value) {
- if (value && (typeof value == "object" || typeof value == "function")) {
- return obj.makeDebuggeeValue(value);
- }
- return value;
- }
- /**
- * Creates an actor for the specied "very long" string. "Very long" is specified
- * at the server's discretion.
- *
- * @param string String
- * The string.
- */
- function LongStringActor(string) {
- this.string = string;
- this.stringLength = string.length;
- }
- LongStringActor.prototype = {
- actorPrefix: "longString",
- disconnect: function () {
- // Because longStringActors is not a weak map, we won't automatically leave
- // it so we need to manually leave on disconnect so that we don't leak
- // memory.
- this._releaseActor();
- },
- /**
- * Returns a grip for this actor for returning in a protocol message.
- */
- grip: function () {
- return {
- "type": "longString",
- "initial": this.string.substring(
- 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
- "length": this.stringLength,
- "actor": this.actorID
- };
- },
- /**
- * Handle a request to extract part of this actor's string.
- *
- * @param request object
- * The protocol request object.
- */
- onSubstring: function (request) {
- return {
- "from": this.actorID,
- "substring": this.string.substring(request.start, request.end)
- };
- },
- /**
- * Handle a request to release this LongStringActor instance.
- */
- onRelease: function () {
- // TODO: also check if registeredPool === threadActor.threadLifetimePool
- // when the web console moves aray from manually releasing pause-scoped
- // actors.
- this._releaseActor();
- this.registeredPool.removeActor(this);
- return {};
- },
- _releaseActor: function () {
- if (this.registeredPool && this.registeredPool.longStringActors) {
- delete this.registeredPool.longStringActors[this.string];
- }
- }
- };
- LongStringActor.prototype.requestTypes = {
- "substring": LongStringActor.prototype.onSubstring,
- "release": LongStringActor.prototype.onRelease
- };
- /**
- * Create a grip for the given debuggee value. If the value is an
- * object, will create an actor with the given lifetime.
- */
- function createValueGrip(value, pool, makeObjectGrip) {
- switch (typeof value) {
- case "boolean":
- return value;
- case "string":
- if (stringIsLong(value)) {
- return longStringGrip(value, pool);
- }
- return value;
- case "number":
- if (value === Infinity) {
- return { type: "Infinity" };
- } else if (value === -Infinity) {
- return { type: "-Infinity" };
- } else if (Number.isNaN(value)) {
- return { type: "NaN" };
- } else if (!value && 1 / value === -Infinity) {
- return { type: "-0" };
- }
- return value;
- case "undefined":
- return { type: "undefined" };
- case "object":
- if (value === null) {
- return { type: "null" };
- }
- else if (value.optimizedOut ||
- value.uninitialized ||
- value.missingArguments) {
- // The slot is optimized out, an uninitialized binding, or
- // arguments on a dead scope
- return {
- type: "null",
- optimizedOut: value.optimizedOut,
- uninitialized: value.uninitialized,
- missingArguments: value.missingArguments
- };
- }
- return makeObjectGrip(value, pool);
- case "symbol":
- let form = {
- type: "symbol"
- };
- let name = getSymbolName(value);
- if (name !== undefined) {
- form.name = createValueGrip(name, pool, makeObjectGrip);
- }
- return form;
- default:
- assert(false, "Failed to provide a grip for: " + value);
- return null;
- }
- }
- const symbolProtoToString = Symbol.prototype.toString;
- function getSymbolName(symbol) {
- const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
- return name || undefined;
- }
- /**
- * Returns true if the string is long enough to use a LongStringActor instead
- * of passing the value directly over the protocol.
- *
- * @param str String
- * The string we are checking the length of.
- */
- function stringIsLong(str) {
- return str.length >= DebuggerServer.LONG_STRING_LENGTH;
- }
- /**
- * Create a grip for the given string.
- *
- * @param str String
- * The string we are creating a grip for.
- * @param pool ActorPool
- * The actor pool where the new actor will be added.
- */
- function longStringGrip(str, pool) {
- if (!pool.longStringActors) {
- pool.longStringActors = {};
- }
- if (pool.longStringActors.hasOwnProperty(str)) {
- return pool.longStringActors[str].grip();
- }
- let actor = new LongStringActor(str);
- pool.addActor(actor);
- pool.longStringActors[str] = actor;
- return actor.grip();
- }
- exports.ObjectActor = ObjectActor;
- exports.PropertyIteratorActor = PropertyIteratorActor;
- exports.LongStringActor = LongStringActor;
- exports.createValueGrip = createValueGrip;
- exports.stringIsLong = stringIsLong;
- exports.longStringGrip = longStringGrip;
|