VariablesViewController.jsm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const { utils: Cu } = Components;
  7. var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
  8. var {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
  9. var {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
  10. var Services = require("Services");
  11. var promise = require("promise");
  12. var defer = require("devtools/shared/defer");
  13. var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
  14. Object.defineProperty(this, "WebConsoleUtils", {
  15. get: function () {
  16. return require("devtools/client/webconsole/utils").Utils;
  17. },
  18. configurable: true,
  19. enumerable: true
  20. });
  21. XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
  22. Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
  23. );
  24. XPCOMUtils.defineLazyModuleGetter(this, "console",
  25. "resource://gre/modules/Console.jsm");
  26. const MAX_LONG_STRING_LENGTH = 200000;
  27. const MAX_PROPERTY_ITEMS = 2000;
  28. const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
  29. this.EXPORTED_SYMBOLS = ["VariablesViewController", "StackFrameUtils"];
  30. /**
  31. * Localization convenience methods.
  32. */
  33. var L10N = new LocalizationHelper(DBG_STRINGS_URI);
  34. /**
  35. * Controller for a VariablesView that handles interfacing with the debugger
  36. * protocol. Is able to populate scopes and variables via the protocol as well
  37. * as manage actor lifespans.
  38. *
  39. * @param VariablesView aView
  40. * The view to attach to.
  41. * @param object aOptions [optional]
  42. * Options for configuring the controller. Supported options:
  43. * - getObjectClient: @see this._setClientGetters
  44. * - getLongStringClient: @see this._setClientGetters
  45. * - getEnvironmentClient: @see this._setClientGetters
  46. * - releaseActor: @see this._setClientGetters
  47. * - overrideValueEvalMacro: @see _setEvaluationMacros
  48. * - getterOrSetterEvalMacro: @see _setEvaluationMacros
  49. * - simpleValueEvalMacro: @see _setEvaluationMacros
  50. */
  51. function VariablesViewController(aView, aOptions = {}) {
  52. this.addExpander = this.addExpander.bind(this);
  53. this._setClientGetters(aOptions);
  54. this._setEvaluationMacros(aOptions);
  55. this._actors = new Set();
  56. this.view = aView;
  57. this.view.controller = this;
  58. }
  59. this.VariablesViewController = VariablesViewController;
  60. VariablesViewController.prototype = {
  61. /**
  62. * The default getter/setter evaluation macro.
  63. */
  64. _getterOrSetterEvalMacro: VariablesView.getterOrSetterEvalMacro,
  65. /**
  66. * The default override value evaluation macro.
  67. */
  68. _overrideValueEvalMacro: VariablesView.overrideValueEvalMacro,
  69. /**
  70. * The default simple value evaluation macro.
  71. */
  72. _simpleValueEvalMacro: VariablesView.simpleValueEvalMacro,
  73. /**
  74. * Set the functions used to retrieve debugger client grips.
  75. *
  76. * @param object aOptions
  77. * Options for getting the client grips. Supported options:
  78. * - getObjectClient: callback for creating an object grip client
  79. * - getLongStringClient: callback for creating a long string grip client
  80. * - getEnvironmentClient: callback for creating an environment client
  81. * - releaseActor: callback for releasing an actor when it's no longer needed
  82. */
  83. _setClientGetters: function (aOptions) {
  84. if (aOptions.getObjectClient) {
  85. this._getObjectClient = aOptions.getObjectClient;
  86. }
  87. if (aOptions.getLongStringClient) {
  88. this._getLongStringClient = aOptions.getLongStringClient;
  89. }
  90. if (aOptions.getEnvironmentClient) {
  91. this._getEnvironmentClient = aOptions.getEnvironmentClient;
  92. }
  93. if (aOptions.releaseActor) {
  94. this._releaseActor = aOptions.releaseActor;
  95. }
  96. },
  97. /**
  98. * Sets the functions used when evaluating strings in the variables view.
  99. *
  100. * @param object aOptions
  101. * Options for configuring the macros. Supported options:
  102. * - overrideValueEvalMacro: callback for creating an overriding eval macro
  103. * - getterOrSetterEvalMacro: callback for creating a getter/setter eval macro
  104. * - simpleValueEvalMacro: callback for creating a simple value eval macro
  105. */
  106. _setEvaluationMacros: function (aOptions) {
  107. if (aOptions.overrideValueEvalMacro) {
  108. this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
  109. }
  110. if (aOptions.getterOrSetterEvalMacro) {
  111. this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
  112. }
  113. if (aOptions.simpleValueEvalMacro) {
  114. this._simpleValueEvalMacro = aOptions.simpleValueEvalMacro;
  115. }
  116. },
  117. /**
  118. * Populate a long string into a target using a grip.
  119. *
  120. * @param Variable aTarget
  121. * The target Variable/Property to put the retrieved string into.
  122. * @param LongStringActor aGrip
  123. * The long string grip that use to retrieve the full string.
  124. * @return Promise
  125. * The promise that will be resolved when the string is retrieved.
  126. */
  127. _populateFromLongString: function (aTarget, aGrip) {
  128. let deferred = defer();
  129. let from = aGrip.initial.length;
  130. let to = Math.min(aGrip.length, MAX_LONG_STRING_LENGTH);
  131. this._getLongStringClient(aGrip).substring(from, to, aResponse => {
  132. // Stop tracking the actor because it's no longer needed.
  133. this.releaseActor(aGrip);
  134. // Replace the preview with the full string and make it non-expandable.
  135. aTarget.onexpand = null;
  136. aTarget.setGrip(aGrip.initial + aResponse.substring);
  137. aTarget.hideArrow();
  138. deferred.resolve();
  139. });
  140. return deferred.promise;
  141. },
  142. /**
  143. * Adds pseudo items in case there is too many properties to display.
  144. * Each item can expand into property slices.
  145. *
  146. * @param Scope aTarget
  147. * The Scope where the properties will be placed into.
  148. * @param object aGrip
  149. * The property iterator grip.
  150. */
  151. _populatePropertySlices: function (aTarget, aGrip) {
  152. if (aGrip.count < MAX_PROPERTY_ITEMS) {
  153. return this._populateFromPropertyIterator(aTarget, aGrip);
  154. }
  155. // Divide the keys into quarters.
  156. let items = Math.ceil(aGrip.count / 4);
  157. let iterator = aGrip.propertyIterator;
  158. let promises = [];
  159. for (let i = 0; i < 4; i++) {
  160. let start = aGrip.start + i * items;
  161. let count = i != 3 ? items : aGrip.count - i * items;
  162. // Create a new kind of grip, with additional fields to define the slice
  163. let sliceGrip = {
  164. type: "property-iterator",
  165. propertyIterator: iterator,
  166. start: start,
  167. count: count
  168. };
  169. // Query the name of the first and last items for this slice
  170. let deferred = defer();
  171. iterator.names([start, start + count - 1], ({ names }) => {
  172. let label = "[" + names[0] + ELLIPSIS + names[1] + "]";
  173. let item = aTarget.addItem(label, {}, { internalItem: true });
  174. item.showArrow();
  175. this.addExpander(item, sliceGrip);
  176. deferred.resolve();
  177. });
  178. promises.push(deferred.promise);
  179. }
  180. return promise.all(promises);
  181. },
  182. /**
  183. * Adds a property slice for a Variable in the view using the already
  184. * property iterator
  185. *
  186. * @param Scope aTarget
  187. * The Scope where the properties will be placed into.
  188. * @param object aGrip
  189. * The property iterator grip.
  190. */
  191. _populateFromPropertyIterator: function (aTarget, aGrip) {
  192. if (aGrip.count >= MAX_PROPERTY_ITEMS) {
  193. // We already started to split, but there is still too many properties, split again.
  194. return this._populatePropertySlices(aTarget, aGrip);
  195. }
  196. // We started slicing properties, and the slice is now small enough to be displayed
  197. let deferred = defer();
  198. aGrip.propertyIterator.slice(aGrip.start, aGrip.count,
  199. ({ ownProperties }) => {
  200. // Add all the variable properties.
  201. if (Object.keys(ownProperties).length > 0) {
  202. aTarget.addItems(ownProperties, {
  203. sorted: true,
  204. // Expansion handlers must be set after the properties are added.
  205. callback: this.addExpander
  206. });
  207. }
  208. deferred.resolve();
  209. });
  210. return deferred.promise;
  211. },
  212. /**
  213. * Adds the properties for a Variable in the view using a new feature in FF40+
  214. * that allows iteration over properties in slices.
  215. *
  216. * @param Scope aTarget
  217. * The Scope where the properties will be placed into.
  218. * @param object aGrip
  219. * The grip to use to populate the target.
  220. * @param string aQuery [optional]
  221. * The query string used to fetch only a subset of properties
  222. */
  223. _populateFromObjectWithIterator: function (aTarget, aGrip, aQuery) {
  224. // FF40+ starts exposing `ownPropertyLength` on ObjectActor's grip,
  225. // as well as `enumProperties` request.
  226. let deferred = defer();
  227. let objectClient = this._getObjectClient(aGrip);
  228. let isArray = aGrip.preview && aGrip.preview.kind === "ArrayLike";
  229. if (isArray) {
  230. // First enumerate array items, e.g. properties from `0` to `array.length`.
  231. let options = {
  232. ignoreNonIndexedProperties: true,
  233. query: aQuery
  234. };
  235. objectClient.enumProperties(options, ({ iterator }) => {
  236. let sliceGrip = {
  237. type: "property-iterator",
  238. propertyIterator: iterator,
  239. start: 0,
  240. count: iterator.count
  241. };
  242. this._populatePropertySlices(aTarget, sliceGrip)
  243. .then(() => {
  244. // Then enumerate the rest of the properties, like length, buffer, etc.
  245. let options = {
  246. ignoreIndexedProperties: true,
  247. sort: true,
  248. query: aQuery
  249. };
  250. objectClient.enumProperties(options, ({ iterator }) => {
  251. let sliceGrip = {
  252. type: "property-iterator",
  253. propertyIterator: iterator,
  254. start: 0,
  255. count: iterator.count
  256. };
  257. deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
  258. });
  259. });
  260. });
  261. } else {
  262. // For objects, we just enumerate all the properties sorted by name.
  263. objectClient.enumProperties({ sort: true, query: aQuery }, ({ iterator }) => {
  264. let sliceGrip = {
  265. type: "property-iterator",
  266. propertyIterator: iterator,
  267. start: 0,
  268. count: iterator.count
  269. };
  270. deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
  271. });
  272. }
  273. return deferred.promise;
  274. },
  275. /**
  276. * Adds the given prototype in the view.
  277. *
  278. * @param Scope aTarget
  279. * The Scope where the properties will be placed into.
  280. * @param object aProtype
  281. * The prototype grip.
  282. */
  283. _populateObjectPrototype: function (aTarget, aPrototype) {
  284. // Add the variable's __proto__.
  285. if (aPrototype && aPrototype.type != "null") {
  286. let proto = aTarget.addItem("__proto__", { value: aPrototype });
  287. this.addExpander(proto, aPrototype);
  288. }
  289. },
  290. /**
  291. * Adds properties to a Scope, Variable, or Property in the view. Triggered
  292. * when a scope is expanded or certain variables are hovered.
  293. *
  294. * @param Scope aTarget
  295. * The Scope where the properties will be placed into.
  296. * @param object aGrip
  297. * The grip to use to populate the target.
  298. */
  299. _populateFromObject: function (aTarget, aGrip) {
  300. if (aGrip.class === "Proxy") {
  301. this.addExpander(
  302. aTarget.addItem("<target>", { value: aGrip.proxyTarget }, { internalItem: true }),
  303. aGrip.proxyTarget);
  304. this.addExpander(
  305. aTarget.addItem("<handler>", { value: aGrip.proxyHandler }, { internalItem: true }),
  306. aGrip.proxyHandler);
  307. // Refuse to play the proxy's stupid game and return immediately
  308. let deferred = defer();
  309. deferred.resolve();
  310. return deferred.promise;
  311. }
  312. if (aGrip.class === "Promise" && aGrip.promiseState) {
  313. const { state, value, reason } = aGrip.promiseState;
  314. aTarget.addItem("<state>", { value: state }, { internalItem: true });
  315. if (state === "fulfilled") {
  316. this.addExpander(
  317. aTarget.addItem("<value>", { value }, { internalItem: true }),
  318. value);
  319. } else if (state === "rejected") {
  320. this.addExpander(
  321. aTarget.addItem("<reason>", { value: reason }, { internalItem: true }),
  322. reason);
  323. }
  324. } else if (["Map", "WeakMap", "Set", "WeakSet"].includes(aGrip.class)) {
  325. let entriesList = aTarget.addItem("<entries>", {}, { internalItem: true });
  326. entriesList.showArrow();
  327. this.addExpander(entriesList, {
  328. type: "entries-list",
  329. obj: aGrip
  330. });
  331. }
  332. // Fetch properties by slices if there is too many in order to prevent UI freeze.
  333. if ("ownPropertyLength" in aGrip && aGrip.ownPropertyLength >= MAX_PROPERTY_ITEMS) {
  334. return this._populateFromObjectWithIterator(aTarget, aGrip)
  335. .then(() => {
  336. let deferred = defer();
  337. let objectClient = this._getObjectClient(aGrip);
  338. objectClient.getPrototype(({ prototype }) => {
  339. this._populateObjectPrototype(aTarget, prototype);
  340. deferred.resolve();
  341. });
  342. return deferred.promise;
  343. });
  344. }
  345. return this._populateProperties(aTarget, aGrip);
  346. },
  347. _populateProperties: function (aTarget, aGrip, aOptions) {
  348. let deferred = defer();
  349. let objectClient = this._getObjectClient(aGrip);
  350. objectClient.getPrototypeAndProperties(aResponse => {
  351. let ownProperties = aResponse.ownProperties || {};
  352. let prototype = aResponse.prototype || null;
  353. // 'safeGetterValues' is new and isn't necessary defined on old actors.
  354. let safeGetterValues = aResponse.safeGetterValues || {};
  355. let sortable = VariablesView.isSortable(aGrip.class);
  356. // Merge the safe getter values into one object such that we can use it
  357. // in VariablesView.
  358. for (let name of Object.keys(safeGetterValues)) {
  359. if (name in ownProperties) {
  360. let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
  361. ownProperties[name].getterValue = getterValue;
  362. ownProperties[name].getterPrototypeLevel = getterPrototypeLevel;
  363. } else {
  364. ownProperties[name] = safeGetterValues[name];
  365. }
  366. }
  367. // Add all the variable properties.
  368. aTarget.addItems(ownProperties, {
  369. // Not all variables need to force sorted properties.
  370. sorted: sortable,
  371. // Expansion handlers must be set after the properties are added.
  372. callback: this.addExpander
  373. });
  374. // Add the variable's __proto__.
  375. this._populateObjectPrototype(aTarget, prototype);
  376. // If the object is a function we need to fetch its scope chain
  377. // to show them as closures for the respective function.
  378. if (aGrip.class == "Function") {
  379. objectClient.getScope(aResponse => {
  380. if (aResponse.error) {
  381. // This function is bound to a built-in object or it's not present
  382. // in the current scope chain. Not necessarily an actual error,
  383. // it just means that there's no closure for the function.
  384. console.warn(aResponse.error + ": " + aResponse.message);
  385. return void deferred.resolve();
  386. }
  387. this._populateWithClosure(aTarget, aResponse.scope).then(deferred.resolve);
  388. });
  389. } else {
  390. deferred.resolve();
  391. }
  392. });
  393. return deferred.promise;
  394. },
  395. /**
  396. * Adds the scope chain elements (closures) of a function variable.
  397. *
  398. * @param Variable aTarget
  399. * The variable where the properties will be placed into.
  400. * @param Scope aScope
  401. * The lexical environment form as specified in the protocol.
  402. */
  403. _populateWithClosure: function (aTarget, aScope) {
  404. let objectScopes = [];
  405. let environment = aScope;
  406. let funcScope = aTarget.addItem("<Closure>");
  407. funcScope.target.setAttribute("scope", "");
  408. funcScope.showArrow();
  409. do {
  410. // Create a scope to contain all the inspected variables.
  411. let label = StackFrameUtils.getScopeLabel(environment);
  412. // Block scopes may have the same label, so make addItem allow duplicates.
  413. let closure = funcScope.addItem(label, undefined, {relaxed: true});
  414. closure.target.setAttribute("scope", "");
  415. closure.showArrow();
  416. // Add nodes for every argument and every other variable in scope.
  417. if (environment.bindings) {
  418. this._populateWithEnvironmentBindings(closure, environment.bindings);
  419. } else {
  420. let deferred = defer();
  421. objectScopes.push(deferred.promise);
  422. this._getEnvironmentClient(environment).getBindings(response => {
  423. this._populateWithEnvironmentBindings(closure, response.bindings);
  424. deferred.resolve();
  425. });
  426. }
  427. } while ((environment = environment.parent));
  428. return promise.all(objectScopes).then(() => {
  429. // Signal that scopes have been fetched.
  430. this.view.emit("fetched", "scopes", funcScope);
  431. });
  432. },
  433. /**
  434. * Adds nodes for every specified binding to the closure node.
  435. *
  436. * @param Variable aTarget
  437. * The variable where the bindings will be placed into.
  438. * @param object aBindings
  439. * The bindings form as specified in the protocol.
  440. */
  441. _populateWithEnvironmentBindings: function (aTarget, aBindings) {
  442. // Add nodes for every argument in the scope.
  443. aTarget.addItems(aBindings.arguments.reduce((accumulator, arg) => {
  444. let name = Object.getOwnPropertyNames(arg)[0];
  445. let descriptor = arg[name];
  446. accumulator[name] = descriptor;
  447. return accumulator;
  448. }, {}), {
  449. // Arguments aren't sorted.
  450. sorted: false,
  451. // Expansion handlers must be set after the properties are added.
  452. callback: this.addExpander
  453. });
  454. // Add nodes for every other variable in the scope.
  455. aTarget.addItems(aBindings.variables, {
  456. // Not all variables need to force sorted properties.
  457. sorted: VARIABLES_SORTING_ENABLED,
  458. // Expansion handlers must be set after the properties are added.
  459. callback: this.addExpander
  460. });
  461. },
  462. _populateFromEntries: function (target, grip) {
  463. let objGrip = grip.obj;
  464. let objectClient = this._getObjectClient(objGrip);
  465. return new promise((resolve, reject) => {
  466. objectClient.enumEntries((response) => {
  467. if (response.error) {
  468. // Older server might not support the enumEntries method
  469. console.warn(response.error + ": " + response.message);
  470. resolve();
  471. } else {
  472. let sliceGrip = {
  473. type: "property-iterator",
  474. propertyIterator: response.iterator,
  475. start: 0,
  476. count: response.iterator.count
  477. };
  478. resolve(this._populatePropertySlices(target, sliceGrip));
  479. }
  480. });
  481. });
  482. },
  483. /**
  484. * Adds an 'onexpand' callback for a variable, lazily handling
  485. * the addition of new properties.
  486. *
  487. * @param Variable aTarget
  488. * The variable where the properties will be placed into.
  489. * @param any aSource
  490. * The source to use to populate the target.
  491. */
  492. addExpander: function (aTarget, aSource) {
  493. // Attach evaluation macros as necessary.
  494. if (aTarget.getter || aTarget.setter) {
  495. aTarget.evaluationMacro = this._overrideValueEvalMacro;
  496. let getter = aTarget.get("get");
  497. if (getter) {
  498. getter.evaluationMacro = this._getterOrSetterEvalMacro;
  499. }
  500. let setter = aTarget.get("set");
  501. if (setter) {
  502. setter.evaluationMacro = this._getterOrSetterEvalMacro;
  503. }
  504. } else {
  505. aTarget.evaluationMacro = this._simpleValueEvalMacro;
  506. }
  507. // If the source is primitive then an expander is not needed.
  508. if (VariablesView.isPrimitive({ value: aSource })) {
  509. return;
  510. }
  511. // If the source is a long string then show the arrow.
  512. if (WebConsoleUtils.isActorGrip(aSource) && aSource.type == "longString") {
  513. aTarget.showArrow();
  514. }
  515. // Make sure that properties are always available on expansion.
  516. aTarget.onexpand = () => this.populate(aTarget, aSource);
  517. // Some variables are likely to contain a very large number of properties.
  518. // It's a good idea to be prepared in case of an expansion.
  519. if (aTarget.shouldPrefetch) {
  520. aTarget.addEventListener("mouseover", aTarget.onexpand, false);
  521. }
  522. // Register all the actors that this controller now depends on.
  523. for (let grip of [aTarget.value, aTarget.getter, aTarget.setter]) {
  524. if (WebConsoleUtils.isActorGrip(grip)) {
  525. this._actors.add(grip.actor);
  526. }
  527. }
  528. },
  529. /**
  530. * Adds properties to a Scope, Variable, or Property in the view. Triggered
  531. * when a scope is expanded or certain variables are hovered.
  532. *
  533. * This does not expand the target, it only populates it.
  534. *
  535. * @param Scope aTarget
  536. * The Scope to be expanded.
  537. * @param object aSource
  538. * The source to use to populate the target.
  539. * @return Promise
  540. * The promise that is resolved once the target has been expanded.
  541. */
  542. populate: function (aTarget, aSource) {
  543. // Fetch the variables only once.
  544. if (aTarget._fetched) {
  545. return aTarget._fetched;
  546. }
  547. // Make sure the source grip is available.
  548. if (!aSource) {
  549. return promise.reject(new Error("No actor grip was given for the variable."));
  550. }
  551. let deferred = defer();
  552. aTarget._fetched = deferred.promise;
  553. if (aSource.type === "property-iterator") {
  554. return this._populateFromPropertyIterator(aTarget, aSource);
  555. }
  556. if (aSource.type === "entries-list") {
  557. return this._populateFromEntries(aTarget, aSource);
  558. }
  559. if (aSource.type === "mapEntry") {
  560. aTarget.addItems({
  561. key: { value: aSource.preview.key },
  562. value: { value: aSource.preview.value }
  563. }, {
  564. callback: this.addExpander
  565. });
  566. return promise.resolve();
  567. }
  568. // If the target is a Variable or Property then we're fetching properties.
  569. if (VariablesView.isVariable(aTarget)) {
  570. this._populateFromObject(aTarget, aSource).then(() => {
  571. // Signal that properties have been fetched.
  572. this.view.emit("fetched", "properties", aTarget);
  573. // Commit the hierarchy because new items were added.
  574. this.view.commitHierarchy();
  575. deferred.resolve();
  576. });
  577. return deferred.promise;
  578. }
  579. switch (aSource.type) {
  580. case "longString":
  581. this._populateFromLongString(aTarget, aSource).then(() => {
  582. // Signal that a long string has been fetched.
  583. this.view.emit("fetched", "longString", aTarget);
  584. deferred.resolve();
  585. });
  586. break;
  587. case "with":
  588. case "object":
  589. this._populateFromObject(aTarget, aSource.object).then(() => {
  590. // Signal that variables have been fetched.
  591. this.view.emit("fetched", "variables", aTarget);
  592. // Commit the hierarchy because new items were added.
  593. this.view.commitHierarchy();
  594. deferred.resolve();
  595. });
  596. break;
  597. case "block":
  598. case "function":
  599. this._populateWithEnvironmentBindings(aTarget, aSource.bindings);
  600. // No need to signal that variables have been fetched, since
  601. // the scope arguments and variables are already attached to the
  602. // environment bindings, so pausing the active thread is unnecessary.
  603. // Commit the hierarchy because new items were added.
  604. this.view.commitHierarchy();
  605. deferred.resolve();
  606. break;
  607. default:
  608. let error = "Unknown Debugger.Environment type: " + aSource.type;
  609. console.error(error);
  610. deferred.reject(error);
  611. }
  612. return deferred.promise;
  613. },
  614. /**
  615. * Indicates to the view if the targeted actor supports properties search
  616. *
  617. * @return boolean True, if the actor supports enumProperty request
  618. */
  619. supportsSearch: function () {
  620. // FF40+ starts exposing ownPropertyLength on object actor's grip
  621. // as well as enumProperty which allows to query a subset of properties.
  622. return this.objectActor && ("ownPropertyLength" in this.objectActor);
  623. },
  624. /**
  625. * Try to use the actor to perform an attribute search.
  626. *
  627. * @param Scope aScope
  628. * The Scope instance to populate with properties
  629. * @param string aToken
  630. * The query string
  631. */
  632. performSearch: function (aScope, aToken) {
  633. this._populateFromObjectWithIterator(aScope, this.objectActor, aToken)
  634. .then(() => {
  635. this.view.emit("fetched", "search", aScope);
  636. });
  637. },
  638. /**
  639. * Release an actor from the controller.
  640. *
  641. * @param object aActor
  642. * The actor to release.
  643. */
  644. releaseActor: function (aActor) {
  645. if (this._releaseActor) {
  646. this._releaseActor(aActor);
  647. }
  648. this._actors.delete(aActor);
  649. },
  650. /**
  651. * Release all the actors referenced by the controller, optionally filtered.
  652. *
  653. * @param function aFilter [optional]
  654. * Callback to filter which actors are released.
  655. */
  656. releaseActors: function (aFilter) {
  657. for (let actor of this._actors) {
  658. if (!aFilter || aFilter(actor)) {
  659. this.releaseActor(actor);
  660. }
  661. }
  662. },
  663. /**
  664. * Helper function for setting up a single Scope with a single Variable
  665. * contained within it.
  666. *
  667. * This function will empty the variables view.
  668. *
  669. * @param object options
  670. * Options for the contents of the view:
  671. * - objectActor: the grip of the new ObjectActor to show.
  672. * - rawObject: the raw object to show.
  673. * - label: the label for the inspected object.
  674. * @param object configuration
  675. * Additional options for the controller:
  676. * - overrideValueEvalMacro: @see _setEvaluationMacros
  677. * - getterOrSetterEvalMacro: @see _setEvaluationMacros
  678. * - simpleValueEvalMacro: @see _setEvaluationMacros
  679. * @return Object
  680. * - variable: the created Variable.
  681. * - expanded: the Promise that resolves when the variable expands.
  682. */
  683. setSingleVariable: function (options, configuration = {}) {
  684. this._setEvaluationMacros(configuration);
  685. this.view.empty();
  686. let scope = this.view.addScope(options.label);
  687. scope.expanded = true; // Expand the scope by default.
  688. scope.locked = true; // Prevent collapsing the scope.
  689. let variable = scope.addItem(undefined, { enumerable: true });
  690. let populated;
  691. if (options.objectActor) {
  692. // Save objectActor for properties filtering
  693. this.objectActor = options.objectActor;
  694. if (VariablesView.isPrimitive({ value: this.objectActor })) {
  695. populated = promise.resolve();
  696. } else {
  697. populated = this.populate(variable, options.objectActor);
  698. variable.expand();
  699. }
  700. } else if (options.rawObject) {
  701. variable.populate(options.rawObject, { expanded: true });
  702. populated = promise.resolve();
  703. }
  704. return { variable: variable, expanded: populated };
  705. },
  706. };
  707. /**
  708. * Attaches a VariablesViewController to a VariablesView if it doesn't already
  709. * have one.
  710. *
  711. * @param VariablesView aView
  712. * The view to attach to.
  713. * @param object aOptions
  714. * The options to use in creating the controller.
  715. * @return VariablesViewController
  716. */
  717. VariablesViewController.attach = function (aView, aOptions) {
  718. if (aView.controller) {
  719. return aView.controller;
  720. }
  721. return new VariablesViewController(aView, aOptions);
  722. };
  723. /**
  724. * Utility functions for handling stackframes.
  725. */
  726. var StackFrameUtils = this.StackFrameUtils = {
  727. /**
  728. * Create a textual representation for the specified stack frame
  729. * to display in the stackframes container.
  730. *
  731. * @param object aFrame
  732. * The stack frame to label.
  733. */
  734. getFrameTitle: function (aFrame) {
  735. if (aFrame.type == "call") {
  736. let c = aFrame.callee;
  737. return (c.name || c.userDisplayName || c.displayName || "(anonymous)");
  738. }
  739. return "(" + aFrame.type + ")";
  740. },
  741. /**
  742. * Constructs a scope label based on its environment.
  743. *
  744. * @param object aEnv
  745. * The scope's environment.
  746. * @return string
  747. * The scope's label.
  748. */
  749. getScopeLabel: function (aEnv) {
  750. let name = "";
  751. // Name the outermost scope Global.
  752. if (!aEnv.parent) {
  753. name = L10N.getStr("globalScopeLabel");
  754. }
  755. // Otherwise construct the scope name.
  756. else {
  757. name = aEnv.type.charAt(0).toUpperCase() + aEnv.type.slice(1);
  758. }
  759. let label = L10N.getFormatStr("scopeLabel", name);
  760. switch (aEnv.type) {
  761. case "with":
  762. case "object":
  763. label += " [" + aEnv.object.class + "]";
  764. break;
  765. case "function":
  766. let f = aEnv.function;
  767. label += " [" +
  768. (f.name || f.userDisplayName || f.displayName || "(anonymous)") +
  769. "]";
  770. break;
  771. }
  772. return label;
  773. }
  774. };