VariablesView.jsm 120 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182
  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 Ci = Components.interfaces;
  7. const Cu = Components.utils;
  8. const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
  9. const LAZY_EMPTY_DELAY = 150; // ms
  10. const SCROLL_PAGE_SIZE_DEFAULT = 0;
  11. const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100;
  12. const PAGE_SIZE_MAX_JUMPS = 30;
  13. const SEARCH_ACTION_MAX_DELAY = 300; // ms
  14. const ITEM_FLASH_DURATION = 300; // ms
  15. const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
  16. const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
  17. const EventEmitter = require("devtools/shared/event-emitter");
  18. const DevToolsUtils = require("devtools/shared/DevToolsUtils");
  19. const Services = require("Services");
  20. const { getSourceNames } = require("devtools/client/shared/source-utils");
  21. const promise = require("promise");
  22. const defer = require("devtools/shared/defer");
  23. const { Heritage, ViewHelpers, setNamedTimeout } =
  24. require("devtools/client/shared/widgets/view-helpers");
  25. const { Task } = require("devtools/shared/task");
  26. const nodeConstants = require("devtools/shared/dom-node-constants");
  27. const {KeyCodes} = require("devtools/client/shared/keycodes");
  28. const {PluralForm} = require("devtools/shared/plural-form");
  29. const {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
  30. const L10N = new LocalizationHelper(DBG_STRINGS_URI);
  31. XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
  32. "@mozilla.org/widget/clipboardhelper;1",
  33. "nsIClipboardHelper");
  34. Object.defineProperty(this, "WebConsoleUtils", {
  35. get: function () {
  36. return require("devtools/client/webconsole/utils").Utils;
  37. },
  38. configurable: true,
  39. enumerable: true
  40. });
  41. Object.defineProperty(this, "NetworkHelper", {
  42. get: function () {
  43. return require("devtools/shared/webconsole/network-helper");
  44. },
  45. configurable: true,
  46. enumerable: true
  47. });
  48. this.EXPORTED_SYMBOLS = ["VariablesView", "escapeHTML"];
  49. /**
  50. * A tree view for inspecting scopes, objects and properties.
  51. * Iterable via "for (let [id, scope] of instance) { }".
  52. * Requires the devtools common.css and debugger.css skin stylesheets.
  53. *
  54. * To allow replacing variable or property values in this view, provide an
  55. * "eval" function property. To allow replacing variable or property names,
  56. * provide a "switch" function. To handle deleting variables or properties,
  57. * provide a "delete" function.
  58. *
  59. * @param nsIDOMNode aParentNode
  60. * The parent node to hold this view.
  61. * @param object aFlags [optional]
  62. * An object contaning initialization options for this view.
  63. * e.g. { lazyEmpty: true, searchEnabled: true ... }
  64. */
  65. this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
  66. this._store = []; // Can't use a Map because Scope names needn't be unique.
  67. this._itemsByElement = new WeakMap();
  68. this._prevHierarchy = new Map();
  69. this._currHierarchy = new Map();
  70. this._parent = aParentNode;
  71. this._parent.classList.add("variables-view-container");
  72. this._parent.classList.add("theme-body");
  73. this._appendEmptyNotice();
  74. this._onSearchboxInput = this._onSearchboxInput.bind(this);
  75. this._onSearchboxKeyPress = this._onSearchboxKeyPress.bind(this);
  76. this._onViewKeyPress = this._onViewKeyPress.bind(this);
  77. this._onViewKeyDown = this._onViewKeyDown.bind(this);
  78. // Create an internal scrollbox container.
  79. this._list = this.document.createElement("scrollbox");
  80. this._list.setAttribute("orient", "vertical");
  81. this._list.addEventListener("keypress", this._onViewKeyPress, false);
  82. this._list.addEventListener("keydown", this._onViewKeyDown, false);
  83. this._parent.appendChild(this._list);
  84. for (let name in aFlags) {
  85. this[name] = aFlags[name];
  86. }
  87. EventEmitter.decorate(this);
  88. };
  89. VariablesView.prototype = {
  90. /**
  91. * Helper setter for populating this container with a raw object.
  92. *
  93. * @param object aObject
  94. * The raw object to display. You can only provide this object
  95. * if you want the variables view to work in sync mode.
  96. */
  97. set rawObject(aObject) {
  98. this.empty();
  99. this.addScope()
  100. .addItem(undefined, { enumerable: true })
  101. .populate(aObject, { sorted: true });
  102. },
  103. /**
  104. * Adds a scope to contain any inspected variables.
  105. *
  106. * This new scope will be considered the parent of any other scope
  107. * added afterwards.
  108. *
  109. * @param string aName
  110. * The scope's name (e.g. "Local", "Global" etc.).
  111. * @param string aCustomClass
  112. * An additional class name for the containing element.
  113. * @return Scope
  114. * The newly created Scope instance.
  115. */
  116. addScope: function (aName = "", aCustomClass = "") {
  117. this._removeEmptyNotice();
  118. this._toggleSearchVisibility(true);
  119. let scope = new Scope(this, aName, { customClass: aCustomClass });
  120. this._store.push(scope);
  121. this._itemsByElement.set(scope._target, scope);
  122. this._currHierarchy.set(aName, scope);
  123. scope.header = !!aName;
  124. return scope;
  125. },
  126. /**
  127. * Removes all items from this container.
  128. *
  129. * @param number aTimeout [optional]
  130. * The number of milliseconds to delay the operation if
  131. * lazy emptying of this container is enabled.
  132. */
  133. empty: function (aTimeout = this.lazyEmptyDelay) {
  134. // If there are no items in this container, emptying is useless.
  135. if (!this._store.length) {
  136. return;
  137. }
  138. this._store.length = 0;
  139. this._itemsByElement = new WeakMap();
  140. this._prevHierarchy = this._currHierarchy;
  141. this._currHierarchy = new Map(); // Don't clear, this is just simple swapping.
  142. // Check if this empty operation may be executed lazily.
  143. if (this.lazyEmpty && aTimeout > 0) {
  144. this._emptySoon(aTimeout);
  145. return;
  146. }
  147. while (this._list.hasChildNodes()) {
  148. this._list.firstChild.remove();
  149. }
  150. this._appendEmptyNotice();
  151. this._toggleSearchVisibility(false);
  152. },
  153. /**
  154. * Emptying this container and rebuilding it immediately afterwards would
  155. * result in a brief redraw flicker, because the previously expanded nodes
  156. * may get asynchronously re-expanded, after fetching the prototype and
  157. * properties from a server.
  158. *
  159. * To avoid such behaviour, a normal container list is rebuild, but not
  160. * immediately attached to the parent container. The old container list
  161. * is kept around for a short period of time, hopefully accounting for the
  162. * data fetching delay. In the meantime, any operations can be executed
  163. * normally.
  164. *
  165. * @see VariablesView.empty
  166. * @see VariablesView.commitHierarchy
  167. */
  168. _emptySoon: function (aTimeout) {
  169. let prevList = this._list;
  170. let currList = this._list = this.document.createElement("scrollbox");
  171. this.window.setTimeout(() => {
  172. prevList.removeEventListener("keypress", this._onViewKeyPress, false);
  173. prevList.removeEventListener("keydown", this._onViewKeyDown, false);
  174. currList.addEventListener("keypress", this._onViewKeyPress, false);
  175. currList.addEventListener("keydown", this._onViewKeyDown, false);
  176. currList.setAttribute("orient", "vertical");
  177. this._parent.removeChild(prevList);
  178. this._parent.appendChild(currList);
  179. if (!this._store.length) {
  180. this._appendEmptyNotice();
  181. this._toggleSearchVisibility(false);
  182. }
  183. }, aTimeout);
  184. },
  185. /**
  186. * Optional DevTools toolbox containing this VariablesView. Used to
  187. * communicate with the inspector and highlighter.
  188. */
  189. toolbox: null,
  190. /**
  191. * The controller for this VariablesView, if it has one.
  192. */
  193. controller: null,
  194. /**
  195. * The amount of time (in milliseconds) it takes to empty this view lazily.
  196. */
  197. lazyEmptyDelay: LAZY_EMPTY_DELAY,
  198. /**
  199. * Specifies if this view may be emptied lazily.
  200. * @see VariablesView.prototype.empty
  201. */
  202. lazyEmpty: false,
  203. /**
  204. * Specifies if nodes in this view may be searched lazily.
  205. */
  206. lazySearch: true,
  207. /**
  208. * The number of elements in this container to jump when Page Up or Page Down
  209. * keys are pressed. If falsy, then the page size will be based on the
  210. * container height.
  211. */
  212. scrollPageSize: SCROLL_PAGE_SIZE_DEFAULT,
  213. /**
  214. * Function called each time a variable or property's value is changed via
  215. * user interaction. If null, then value changes are disabled.
  216. *
  217. * This property is applied recursively onto each scope in this view and
  218. * affects only the child nodes when they're created.
  219. */
  220. eval: null,
  221. /**
  222. * Function called each time a variable or property's name is changed via
  223. * user interaction. If null, then name changes are disabled.
  224. *
  225. * This property is applied recursively onto each scope in this view and
  226. * affects only the child nodes when they're created.
  227. */
  228. switch: null,
  229. /**
  230. * Function called each time a variable or property is deleted via
  231. * user interaction. If null, then deletions are disabled.
  232. *
  233. * This property is applied recursively onto each scope in this view and
  234. * affects only the child nodes when they're created.
  235. */
  236. delete: null,
  237. /**
  238. * Function called each time a property is added via user interaction. If
  239. * null, then property additions are disabled.
  240. *
  241. * This property is applied recursively onto each scope in this view and
  242. * affects only the child nodes when they're created.
  243. */
  244. new: null,
  245. /**
  246. * Specifies if after an eval or switch operation, the variable or property
  247. * which has been edited should be disabled.
  248. */
  249. preventDisableOnChange: false,
  250. /**
  251. * Specifies if, whenever a variable or property descriptor is available,
  252. * configurable, enumerable, writable, frozen, sealed and extensible
  253. * attributes should not affect presentation.
  254. *
  255. * This flag is applied recursively onto each scope in this view and
  256. * affects only the child nodes when they're created.
  257. */
  258. preventDescriptorModifiers: false,
  259. /**
  260. * The tooltip text shown on a variable or property's value if an |eval|
  261. * function is provided, in order to change the variable or property's value.
  262. *
  263. * This flag is applied recursively onto each scope in this view and
  264. * affects only the child nodes when they're created.
  265. */
  266. editableValueTooltip: L10N.getStr("variablesEditableValueTooltip"),
  267. /**
  268. * The tooltip text shown on a variable or property's name if a |switch|
  269. * function is provided, in order to change the variable or property's name.
  270. *
  271. * This flag is applied recursively onto each scope in this view and
  272. * affects only the child nodes when they're created.
  273. */
  274. editableNameTooltip: L10N.getStr("variablesEditableNameTooltip"),
  275. /**
  276. * The tooltip text shown on a variable or property's edit button if an
  277. * |eval| function is provided and a getter/setter descriptor is present,
  278. * in order to change the variable or property to a plain value.
  279. *
  280. * This flag is applied recursively onto each scope in this view and
  281. * affects only the child nodes when they're created.
  282. */
  283. editButtonTooltip: L10N.getStr("variablesEditButtonTooltip"),
  284. /**
  285. * The tooltip text shown on a variable or property's value if that value is
  286. * a DOMNode that can be highlighted and selected in the inspector.
  287. *
  288. * This flag is applied recursively onto each scope in this view and
  289. * affects only the child nodes when they're created.
  290. */
  291. domNodeValueTooltip: L10N.getStr("variablesDomNodeValueTooltip"),
  292. /**
  293. * The tooltip text shown on a variable or property's delete button if a
  294. * |delete| function is provided, in order to delete the variable or property.
  295. *
  296. * This flag is applied recursively onto each scope in this view and
  297. * affects only the child nodes when they're created.
  298. */
  299. deleteButtonTooltip: L10N.getStr("variablesCloseButtonTooltip"),
  300. /**
  301. * Specifies the context menu attribute set on variables and properties.
  302. *
  303. * This flag is applied recursively onto each scope in this view and
  304. * affects only the child nodes when they're created.
  305. */
  306. contextMenuId: "",
  307. /**
  308. * The separator label between the variables or properties name and value.
  309. *
  310. * This flag is applied recursively onto each scope in this view and
  311. * affects only the child nodes when they're created.
  312. */
  313. separatorStr: L10N.getStr("variablesSeparatorLabel"),
  314. /**
  315. * Specifies if enumerable properties and variables should be displayed.
  316. * These variables and properties are visible by default.
  317. * @param boolean aFlag
  318. */
  319. set enumVisible(aFlag) {
  320. this._enumVisible = aFlag;
  321. for (let scope of this._store) {
  322. scope._enumVisible = aFlag;
  323. }
  324. },
  325. /**
  326. * Specifies if non-enumerable properties and variables should be displayed.
  327. * These variables and properties are visible by default.
  328. * @param boolean aFlag
  329. */
  330. set nonEnumVisible(aFlag) {
  331. this._nonEnumVisible = aFlag;
  332. for (let scope of this._store) {
  333. scope._nonEnumVisible = aFlag;
  334. }
  335. },
  336. /**
  337. * Specifies if only enumerable properties and variables should be displayed.
  338. * Both types of these variables and properties are visible by default.
  339. * @param boolean aFlag
  340. */
  341. set onlyEnumVisible(aFlag) {
  342. if (aFlag) {
  343. this.enumVisible = true;
  344. this.nonEnumVisible = false;
  345. } else {
  346. this.enumVisible = true;
  347. this.nonEnumVisible = true;
  348. }
  349. },
  350. /**
  351. * Sets if the variable and property searching is enabled.
  352. * @param boolean aFlag
  353. */
  354. set searchEnabled(aFlag) {
  355. aFlag ? this._enableSearch() : this._disableSearch();
  356. },
  357. /**
  358. * Gets if the variable and property searching is enabled.
  359. * @return boolean
  360. */
  361. get searchEnabled() {
  362. return !!this._searchboxContainer;
  363. },
  364. /**
  365. * Sets the text displayed for the searchbox in this container.
  366. * @param string aValue
  367. */
  368. set searchPlaceholder(aValue) {
  369. if (this._searchboxNode) {
  370. this._searchboxNode.setAttribute("placeholder", aValue);
  371. }
  372. this._searchboxPlaceholder = aValue;
  373. },
  374. /**
  375. * Gets the text displayed for the searchbox in this container.
  376. * @return string
  377. */
  378. get searchPlaceholder() {
  379. return this._searchboxPlaceholder;
  380. },
  381. /**
  382. * Enables variable and property searching in this view.
  383. * Use the "searchEnabled" setter to enable searching.
  384. */
  385. _enableSearch: function () {
  386. // If searching was already enabled, no need to re-enable it again.
  387. if (this._searchboxContainer) {
  388. return;
  389. }
  390. let document = this.document;
  391. let ownerNode = this._parent.parentNode;
  392. let container = this._searchboxContainer = document.createElement("hbox");
  393. container.className = "devtools-toolbar";
  394. // Hide the variables searchbox container if there are no variables or
  395. // properties to display.
  396. container.hidden = !this._store.length;
  397. let searchbox = this._searchboxNode = document.createElement("textbox");
  398. searchbox.className = "variables-view-searchinput devtools-filterinput";
  399. searchbox.setAttribute("placeholder", this._searchboxPlaceholder);
  400. searchbox.setAttribute("type", "search");
  401. searchbox.setAttribute("flex", "1");
  402. searchbox.addEventListener("command", this._onSearchboxInput, false);
  403. searchbox.addEventListener("keypress", this._onSearchboxKeyPress, false);
  404. container.appendChild(searchbox);
  405. ownerNode.insertBefore(container, this._parent);
  406. },
  407. /**
  408. * Disables variable and property searching in this view.
  409. * Use the "searchEnabled" setter to disable searching.
  410. */
  411. _disableSearch: function () {
  412. // If searching was already disabled, no need to re-disable it again.
  413. if (!this._searchboxContainer) {
  414. return;
  415. }
  416. this._searchboxContainer.remove();
  417. this._searchboxNode.removeEventListener("command", this._onSearchboxInput, false);
  418. this._searchboxNode.removeEventListener("keypress", this._onSearchboxKeyPress, false);
  419. this._searchboxContainer = null;
  420. this._searchboxNode = null;
  421. },
  422. /**
  423. * Sets the variables searchbox container hidden or visible.
  424. * It's hidden by default.
  425. *
  426. * @param boolean aVisibleFlag
  427. * Specifies the intended visibility.
  428. */
  429. _toggleSearchVisibility: function (aVisibleFlag) {
  430. // If searching was already disabled, there's no need to hide it.
  431. if (!this._searchboxContainer) {
  432. return;
  433. }
  434. this._searchboxContainer.hidden = !aVisibleFlag;
  435. },
  436. /**
  437. * Listener handling the searchbox input event.
  438. */
  439. _onSearchboxInput: function () {
  440. this.scheduleSearch(this._searchboxNode.value);
  441. },
  442. /**
  443. * Listener handling the searchbox key press event.
  444. */
  445. _onSearchboxKeyPress: function (e) {
  446. switch (e.keyCode) {
  447. case KeyCodes.DOM_VK_RETURN:
  448. this._onSearchboxInput();
  449. return;
  450. case KeyCodes.DOM_VK_ESCAPE:
  451. this._searchboxNode.value = "";
  452. this._onSearchboxInput();
  453. return;
  454. }
  455. },
  456. /**
  457. * Schedules searching for variables or properties matching the query.
  458. *
  459. * @param string aToken
  460. * The variable or property to search for.
  461. * @param number aWait
  462. * The amount of milliseconds to wait until draining.
  463. */
  464. scheduleSearch: function (aToken, aWait) {
  465. // Check if this search operation may not be executed lazily.
  466. if (!this.lazySearch) {
  467. this._doSearch(aToken);
  468. return;
  469. }
  470. // The amount of time to wait for the requests to settle.
  471. let maxDelay = SEARCH_ACTION_MAX_DELAY;
  472. let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
  473. // Allow requests to settle down first.
  474. setNamedTimeout("vview-search", delay, () => this._doSearch(aToken));
  475. },
  476. /**
  477. * Performs a case insensitive search for variables or properties matching
  478. * the query, and hides non-matched items.
  479. *
  480. * If aToken is falsy, then all the scopes are unhidden and expanded,
  481. * while the available variables and properties inside those scopes are
  482. * just unhidden.
  483. *
  484. * @param string aToken
  485. * The variable or property to search for.
  486. */
  487. _doSearch: function (aToken) {
  488. if (this.controller && this.controller.supportsSearch()) {
  489. // Retrieve the main Scope in which we add attributes
  490. let scope = this._store[0]._store.get(undefined);
  491. if (!aToken) {
  492. // Prune the view from old previous content
  493. // so that we delete the intermediate search results
  494. // we created in previous searches
  495. for (let property of scope._store.values()) {
  496. property.remove();
  497. }
  498. }
  499. // Retrieve new attributes eventually hidden in splits
  500. this.controller.performSearch(scope, aToken);
  501. // Filter already displayed attributes
  502. if (aToken) {
  503. scope._performSearch(aToken.toLowerCase());
  504. }
  505. return;
  506. }
  507. for (let scope of this._store) {
  508. switch (aToken) {
  509. case "":
  510. case null:
  511. case undefined:
  512. scope.expand();
  513. scope._performSearch("");
  514. break;
  515. default:
  516. scope._performSearch(aToken.toLowerCase());
  517. break;
  518. }
  519. }
  520. },
  521. /**
  522. * Find the first item in the tree of visible items in this container that
  523. * matches the predicate. Searches in visual order (the order seen by the
  524. * user). Descends into each scope to check the scope and its children.
  525. *
  526. * @param function aPredicate
  527. * A function that returns true when a match is found.
  528. * @return Scope | Variable | Property
  529. * The first visible scope, variable or property, or null if nothing
  530. * is found.
  531. */
  532. _findInVisibleItems: function (aPredicate) {
  533. for (let scope of this._store) {
  534. let result = scope._findInVisibleItems(aPredicate);
  535. if (result) {
  536. return result;
  537. }
  538. }
  539. return null;
  540. },
  541. /**
  542. * Find the last item in the tree of visible items in this container that
  543. * matches the predicate. Searches in reverse visual order (opposite of the
  544. * order seen by the user). Descends into each scope to check the scope and
  545. * its children.
  546. *
  547. * @param function aPredicate
  548. * A function that returns true when a match is found.
  549. * @return Scope | Variable | Property
  550. * The last visible scope, variable or property, or null if nothing
  551. * is found.
  552. */
  553. _findInVisibleItemsReverse: function (aPredicate) {
  554. for (let i = this._store.length - 1; i >= 0; i--) {
  555. let scope = this._store[i];
  556. let result = scope._findInVisibleItemsReverse(aPredicate);
  557. if (result) {
  558. return result;
  559. }
  560. }
  561. return null;
  562. },
  563. /**
  564. * Gets the scope at the specified index.
  565. *
  566. * @param number aIndex
  567. * The scope's index.
  568. * @return Scope
  569. * The scope if found, undefined if not.
  570. */
  571. getScopeAtIndex: function (aIndex) {
  572. return this._store[aIndex];
  573. },
  574. /**
  575. * Recursively searches this container for the scope, variable or property
  576. * displayed by the specified node.
  577. *
  578. * @param nsIDOMNode aNode
  579. * The node to search for.
  580. * @return Scope | Variable | Property
  581. * The matched scope, variable or property, or null if nothing is found.
  582. */
  583. getItemForNode: function (aNode) {
  584. return this._itemsByElement.get(aNode);
  585. },
  586. /**
  587. * Gets the scope owning a Variable or Property.
  588. *
  589. * @param Variable | Property
  590. * The variable or property to retrieven the owner scope for.
  591. * @return Scope
  592. * The owner scope.
  593. */
  594. getOwnerScopeForVariableOrProperty: function (aItem) {
  595. if (!aItem) {
  596. return null;
  597. }
  598. // If this is a Scope, return it.
  599. if (!(aItem instanceof Variable)) {
  600. return aItem;
  601. }
  602. // If this is a Variable or Property, find its owner scope.
  603. if (aItem instanceof Variable && aItem.ownerView) {
  604. return this.getOwnerScopeForVariableOrProperty(aItem.ownerView);
  605. }
  606. return null;
  607. },
  608. /**
  609. * Gets the parent scopes for a specified Variable or Property.
  610. * The returned list will not include the owner scope.
  611. *
  612. * @param Variable | Property
  613. * The variable or property for which to find the parent scopes.
  614. * @return array
  615. * A list of parent Scopes.
  616. */
  617. getParentScopesForVariableOrProperty: function (aItem) {
  618. let scope = this.getOwnerScopeForVariableOrProperty(aItem);
  619. return this._store.slice(0, Math.max(this._store.indexOf(scope), 0));
  620. },
  621. /**
  622. * Gets the currently focused scope, variable or property in this view.
  623. *
  624. * @return Scope | Variable | Property
  625. * The focused scope, variable or property, or null if nothing is found.
  626. */
  627. getFocusedItem: function () {
  628. let focused = this.document.commandDispatcher.focusedElement;
  629. return this.getItemForNode(focused);
  630. },
  631. /**
  632. * Focuses the first visible scope, variable, or property in this container.
  633. */
  634. focusFirstVisibleItem: function () {
  635. let focusableItem = this._findInVisibleItems(item => item.focusable);
  636. if (focusableItem) {
  637. this._focusItem(focusableItem);
  638. }
  639. this._parent.scrollTop = 0;
  640. this._parent.scrollLeft = 0;
  641. },
  642. /**
  643. * Focuses the last visible scope, variable, or property in this container.
  644. */
  645. focusLastVisibleItem: function () {
  646. let focusableItem = this._findInVisibleItemsReverse(item => item.focusable);
  647. if (focusableItem) {
  648. this._focusItem(focusableItem);
  649. }
  650. this._parent.scrollTop = this._parent.scrollHeight;
  651. this._parent.scrollLeft = 0;
  652. },
  653. /**
  654. * Focuses the next scope, variable or property in this view.
  655. */
  656. focusNextItem: function () {
  657. this.focusItemAtDelta(+1);
  658. },
  659. /**
  660. * Focuses the previous scope, variable or property in this view.
  661. */
  662. focusPrevItem: function () {
  663. this.focusItemAtDelta(-1);
  664. },
  665. /**
  666. * Focuses another scope, variable or property in this view, based on
  667. * the index distance from the currently focused item.
  668. *
  669. * @param number aDelta
  670. * A scalar specifying by how many items should the selection change.
  671. */
  672. focusItemAtDelta: function (aDelta) {
  673. let direction = aDelta > 0 ? "advanceFocus" : "rewindFocus";
  674. let distance = Math.abs(Math[aDelta > 0 ? "ceil" : "floor"](aDelta));
  675. while (distance--) {
  676. if (!this._focusChange(direction)) {
  677. break; // Out of bounds.
  678. }
  679. }
  680. },
  681. /**
  682. * Focuses the next or previous scope, variable or property in this view.
  683. *
  684. * @param string aDirection
  685. * Either "advanceFocus" or "rewindFocus".
  686. * @return boolean
  687. * False if the focus went out of bounds and the first or last element
  688. * in this view was focused instead.
  689. */
  690. _focusChange: function (aDirection) {
  691. let commandDispatcher = this.document.commandDispatcher;
  692. let prevFocusedElement = commandDispatcher.focusedElement;
  693. let currFocusedItem = null;
  694. do {
  695. commandDispatcher.suppressFocusScroll = true;
  696. commandDispatcher[aDirection]();
  697. // Make sure the newly focused item is a part of this view.
  698. // If the focus goes out of bounds, revert the previously focused item.
  699. if (!(currFocusedItem = this.getFocusedItem())) {
  700. prevFocusedElement.focus();
  701. return false;
  702. }
  703. } while (!currFocusedItem.focusable);
  704. // Focus remained within bounds.
  705. return true;
  706. },
  707. /**
  708. * Focuses a scope, variable or property and makes sure it's visible.
  709. *
  710. * @param aItem Scope | Variable | Property
  711. * The item to focus.
  712. * @param boolean aCollapseFlag
  713. * True if the focused item should also be collapsed.
  714. * @return boolean
  715. * True if the item was successfully focused.
  716. */
  717. _focusItem: function (aItem, aCollapseFlag) {
  718. if (!aItem.focusable) {
  719. return false;
  720. }
  721. if (aCollapseFlag) {
  722. aItem.collapse();
  723. }
  724. aItem._target.focus();
  725. this.boxObject.ensureElementIsVisible(aItem._arrow);
  726. return true;
  727. },
  728. /**
  729. * Listener handling a key press event on the view.
  730. */
  731. _onViewKeyPress: function (e) {
  732. let item = this.getFocusedItem();
  733. // Prevent scrolling when pressing navigation keys.
  734. ViewHelpers.preventScrolling(e);
  735. switch (e.keyCode) {
  736. case KeyCodes.DOM_VK_UP:
  737. // Always rewind focus.
  738. this.focusPrevItem(true);
  739. return;
  740. case KeyCodes.DOM_VK_DOWN:
  741. // Always advance focus.
  742. this.focusNextItem(true);
  743. return;
  744. case KeyCodes.DOM_VK_LEFT:
  745. // Collapse scopes, variables and properties before rewinding focus.
  746. if (item._isExpanded && item._isArrowVisible) {
  747. item.collapse();
  748. } else {
  749. this._focusItem(item.ownerView);
  750. }
  751. return;
  752. case KeyCodes.DOM_VK_RIGHT:
  753. // Nothing to do here if this item never expands.
  754. if (!item._isArrowVisible) {
  755. return;
  756. }
  757. // Expand scopes, variables and properties before advancing focus.
  758. if (!item._isExpanded) {
  759. item.expand();
  760. } else {
  761. this.focusNextItem(true);
  762. }
  763. return;
  764. case KeyCodes.DOM_VK_PAGE_UP:
  765. // Rewind a certain number of elements based on the container height.
  766. this.focusItemAtDelta(-(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight /
  767. PAGE_SIZE_SCROLL_HEIGHT_RATIO),
  768. PAGE_SIZE_MAX_JUMPS)));
  769. return;
  770. case KeyCodes.DOM_VK_PAGE_DOWN:
  771. // Advance a certain number of elements based on the container height.
  772. this.focusItemAtDelta(+(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight /
  773. PAGE_SIZE_SCROLL_HEIGHT_RATIO),
  774. PAGE_SIZE_MAX_JUMPS)));
  775. return;
  776. case KeyCodes.DOM_VK_HOME:
  777. this.focusFirstVisibleItem();
  778. return;
  779. case KeyCodes.DOM_VK_END:
  780. this.focusLastVisibleItem();
  781. return;
  782. case KeyCodes.DOM_VK_RETURN:
  783. // Start editing the value or name of the Variable or Property.
  784. if (item instanceof Variable) {
  785. if (e.metaKey || e.altKey || e.shiftKey) {
  786. item._activateNameInput();
  787. } else {
  788. item._activateValueInput();
  789. }
  790. }
  791. return;
  792. case KeyCodes.DOM_VK_DELETE:
  793. case KeyCodes.DOM_VK_BACK_SPACE:
  794. // Delete the Variable or Property if allowed.
  795. if (item instanceof Variable) {
  796. item._onDelete(e);
  797. }
  798. return;
  799. case KeyCodes.DOM_VK_INSERT:
  800. item._onAddProperty(e);
  801. return;
  802. }
  803. },
  804. /**
  805. * Listener handling a key down event on the view.
  806. */
  807. _onViewKeyDown: function (e) {
  808. if (e.keyCode == KeyCodes.DOM_VK_C) {
  809. // Copy current selection to clipboard.
  810. if (e.ctrlKey || e.metaKey) {
  811. let item = this.getFocusedItem();
  812. clipboardHelper.copyString(
  813. item._nameString + item.separatorStr + item._valueString
  814. );
  815. }
  816. }
  817. },
  818. /**
  819. * Sets the text displayed in this container when there are no available items.
  820. * @param string aValue
  821. */
  822. set emptyText(aValue) {
  823. if (this._emptyTextNode) {
  824. this._emptyTextNode.setAttribute("value", aValue);
  825. }
  826. this._emptyTextValue = aValue;
  827. this._appendEmptyNotice();
  828. },
  829. /**
  830. * Creates and appends a label signaling that this container is empty.
  831. */
  832. _appendEmptyNotice: function () {
  833. if (this._emptyTextNode || !this._emptyTextValue) {
  834. return;
  835. }
  836. let label = this.document.createElement("label");
  837. label.className = "variables-view-empty-notice";
  838. label.setAttribute("value", this._emptyTextValue);
  839. this._parent.appendChild(label);
  840. this._emptyTextNode = label;
  841. },
  842. /**
  843. * Removes the label signaling that this container is empty.
  844. */
  845. _removeEmptyNotice: function () {
  846. if (!this._emptyTextNode) {
  847. return;
  848. }
  849. this._parent.removeChild(this._emptyTextNode);
  850. this._emptyTextNode = null;
  851. },
  852. /**
  853. * Gets if all values should be aligned together.
  854. * @return boolean
  855. */
  856. get alignedValues() {
  857. return this._alignedValues;
  858. },
  859. /**
  860. * Sets if all values should be aligned together.
  861. * @param boolean aFlag
  862. */
  863. set alignedValues(aFlag) {
  864. this._alignedValues = aFlag;
  865. if (aFlag) {
  866. this._parent.setAttribute("aligned-values", "");
  867. } else {
  868. this._parent.removeAttribute("aligned-values");
  869. }
  870. },
  871. /**
  872. * Gets if action buttons (like delete) should be placed at the beginning or
  873. * end of a line.
  874. * @return boolean
  875. */
  876. get actionsFirst() {
  877. return this._actionsFirst;
  878. },
  879. /**
  880. * Sets if action buttons (like delete) should be placed at the beginning or
  881. * end of a line.
  882. * @param boolean aFlag
  883. */
  884. set actionsFirst(aFlag) {
  885. this._actionsFirst = aFlag;
  886. if (aFlag) {
  887. this._parent.setAttribute("actions-first", "");
  888. } else {
  889. this._parent.removeAttribute("actions-first");
  890. }
  891. },
  892. /**
  893. * Gets the parent node holding this view.
  894. * @return nsIDOMNode
  895. */
  896. get boxObject() {
  897. return this._list.boxObject;
  898. },
  899. /**
  900. * Gets the parent node holding this view.
  901. * @return nsIDOMNode
  902. */
  903. get parentNode() {
  904. return this._parent;
  905. },
  906. /**
  907. * Gets the owner document holding this view.
  908. * @return nsIHTMLDocument
  909. */
  910. get document() {
  911. return this._document || (this._document = this._parent.ownerDocument);
  912. },
  913. /**
  914. * Gets the default window holding this view.
  915. * @return nsIDOMWindow
  916. */
  917. get window() {
  918. return this._window || (this._window = this.document.defaultView);
  919. },
  920. _document: null,
  921. _window: null,
  922. _store: null,
  923. _itemsByElement: null,
  924. _prevHierarchy: null,
  925. _currHierarchy: null,
  926. _enumVisible: true,
  927. _nonEnumVisible: true,
  928. _alignedValues: false,
  929. _actionsFirst: false,
  930. _parent: null,
  931. _list: null,
  932. _searchboxNode: null,
  933. _searchboxContainer: null,
  934. _searchboxPlaceholder: "",
  935. _emptyTextNode: null,
  936. _emptyTextValue: ""
  937. };
  938. VariablesView.NON_SORTABLE_CLASSES = [
  939. "Array",
  940. "Int8Array",
  941. "Uint8Array",
  942. "Uint8ClampedArray",
  943. "Int16Array",
  944. "Uint16Array",
  945. "Int32Array",
  946. "Uint32Array",
  947. "Float32Array",
  948. "Float64Array",
  949. "NodeList"
  950. ];
  951. /**
  952. * Determine whether an object's properties should be sorted based on its class.
  953. *
  954. * @param string aClassName
  955. * The class of the object.
  956. */
  957. VariablesView.isSortable = function (aClassName) {
  958. return VariablesView.NON_SORTABLE_CLASSES.indexOf(aClassName) == -1;
  959. };
  960. /**
  961. * Generates the string evaluated when performing simple value changes.
  962. *
  963. * @param Variable | Property aItem
  964. * The current variable or property.
  965. * @param string aCurrentString
  966. * The trimmed user inputted string.
  967. * @param string aPrefix [optional]
  968. * Prefix for the symbolic name.
  969. * @return string
  970. * The string to be evaluated.
  971. */
  972. VariablesView.simpleValueEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
  973. return aPrefix + aItem.symbolicName + "=" + aCurrentString;
  974. };
  975. /**
  976. * Generates the string evaluated when overriding getters and setters with
  977. * plain values.
  978. *
  979. * @param Property aItem
  980. * The current getter or setter property.
  981. * @param string aCurrentString
  982. * The trimmed user inputted string.
  983. * @param string aPrefix [optional]
  984. * Prefix for the symbolic name.
  985. * @return string
  986. * The string to be evaluated.
  987. */
  988. VariablesView.overrideValueEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
  989. let property = escapeString(aItem._nameString);
  990. let parent = aPrefix + aItem.ownerView.symbolicName || "this";
  991. return "Object.defineProperty(" + parent + "," + property + "," +
  992. "{ value: " + aCurrentString +
  993. ", enumerable: " + parent + ".propertyIsEnumerable(" + property + ")" +
  994. ", configurable: true" +
  995. ", writable: true" +
  996. "})";
  997. };
  998. /**
  999. * Generates the string evaluated when performing getters and setters changes.
  1000. *
  1001. * @param Property aItem
  1002. * The current getter or setter property.
  1003. * @param string aCurrentString
  1004. * The trimmed user inputted string.
  1005. * @param string aPrefix [optional]
  1006. * Prefix for the symbolic name.
  1007. * @return string
  1008. * The string to be evaluated.
  1009. */
  1010. VariablesView.getterOrSetterEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
  1011. let type = aItem._nameString;
  1012. let propertyObject = aItem.ownerView;
  1013. let parentObject = propertyObject.ownerView;
  1014. let property = escapeString(propertyObject._nameString);
  1015. let parent = aPrefix + parentObject.symbolicName || "this";
  1016. switch (aCurrentString) {
  1017. case "":
  1018. case "null":
  1019. case "undefined":
  1020. let mirrorType = type == "get" ? "set" : "get";
  1021. let mirrorLookup = type == "get" ? "__lookupSetter__" : "__lookupGetter__";
  1022. // If the parent object will end up without any getter or setter,
  1023. // morph it into a plain value.
  1024. if ((type == "set" && propertyObject.getter.type == "undefined") ||
  1025. (type == "get" && propertyObject.setter.type == "undefined")) {
  1026. // Make sure the right getter/setter to value override macro is applied
  1027. // to the target object.
  1028. return propertyObject.evaluationMacro(propertyObject, "undefined", aPrefix);
  1029. }
  1030. // Construct and return the getter/setter removal evaluation string.
  1031. // e.g: Object.defineProperty(foo, "bar", {
  1032. // get: foo.__lookupGetter__("bar"),
  1033. // set: undefined,
  1034. // enumerable: true,
  1035. // configurable: true
  1036. // })
  1037. return "Object.defineProperty(" + parent + "," + property + "," +
  1038. "{" + mirrorType + ":" + parent + "." + mirrorLookup + "(" + property + ")" +
  1039. "," + type + ":" + undefined +
  1040. ", enumerable: " + parent + ".propertyIsEnumerable(" + property + ")" +
  1041. ", configurable: true" +
  1042. "})";
  1043. default:
  1044. // Wrap statements inside a function declaration if not already wrapped.
  1045. if (!aCurrentString.startsWith("function")) {
  1046. let header = "function(" + (type == "set" ? "value" : "") + ")";
  1047. let body = "";
  1048. // If there's a return statement explicitly written, always use the
  1049. // standard function definition syntax
  1050. if (aCurrentString.includes("return ")) {
  1051. body = "{" + aCurrentString + "}";
  1052. }
  1053. // If block syntax is used, use the whole string as the function body.
  1054. else if (aCurrentString.startsWith("{")) {
  1055. body = aCurrentString;
  1056. }
  1057. // Prefer an expression closure.
  1058. else {
  1059. body = "(" + aCurrentString + ")";
  1060. }
  1061. aCurrentString = header + body;
  1062. }
  1063. // Determine if a new getter or setter should be defined.
  1064. let defineType = type == "get" ? "__defineGetter__" : "__defineSetter__";
  1065. // Make sure all quotes are escaped in the expression's syntax,
  1066. let defineFunc = "eval(\"(" + aCurrentString.replace(/"/g, "\\$&") + ")\")";
  1067. // Construct and return the getter/setter evaluation string.
  1068. // e.g: foo.__defineGetter__("bar", eval("(function() { return 42; })"))
  1069. return parent + "." + defineType + "(" + property + "," + defineFunc + ")";
  1070. }
  1071. };
  1072. /**
  1073. * Function invoked when a getter or setter is deleted.
  1074. *
  1075. * @param Property aItem
  1076. * The current getter or setter property.
  1077. */
  1078. VariablesView.getterOrSetterDeleteCallback = function (aItem) {
  1079. aItem._disable();
  1080. // Make sure the right getter/setter to value override macro is applied
  1081. // to the target object.
  1082. aItem.ownerView.eval(aItem, "");
  1083. return true; // Don't hide the element.
  1084. };
  1085. /**
  1086. * A Scope is an object holding Variable instances.
  1087. * Iterable via "for (let [name, variable] of instance) { }".
  1088. *
  1089. * @param VariablesView aView
  1090. * The view to contain this scope.
  1091. * @param string aName
  1092. * The scope's name.
  1093. * @param object aFlags [optional]
  1094. * Additional options or flags for this scope.
  1095. */
  1096. function Scope(aView, aName, aFlags = {}) {
  1097. this.ownerView = aView;
  1098. this._onClick = this._onClick.bind(this);
  1099. this._openEnum = this._openEnum.bind(this);
  1100. this._openNonEnum = this._openNonEnum.bind(this);
  1101. // Inherit properties and flags from the parent view. You can override
  1102. // each of these directly onto any scope, variable or property instance.
  1103. this.scrollPageSize = aView.scrollPageSize;
  1104. this.eval = aView.eval;
  1105. this.switch = aView.switch;
  1106. this.delete = aView.delete;
  1107. this.new = aView.new;
  1108. this.preventDisableOnChange = aView.preventDisableOnChange;
  1109. this.preventDescriptorModifiers = aView.preventDescriptorModifiers;
  1110. this.editableNameTooltip = aView.editableNameTooltip;
  1111. this.editableValueTooltip = aView.editableValueTooltip;
  1112. this.editButtonTooltip = aView.editButtonTooltip;
  1113. this.deleteButtonTooltip = aView.deleteButtonTooltip;
  1114. this.domNodeValueTooltip = aView.domNodeValueTooltip;
  1115. this.contextMenuId = aView.contextMenuId;
  1116. this.separatorStr = aView.separatorStr;
  1117. this._init(aName, aFlags);
  1118. }
  1119. Scope.prototype = {
  1120. /**
  1121. * Whether this Scope should be prefetched when it is remoted.
  1122. */
  1123. shouldPrefetch: true,
  1124. /**
  1125. * Whether this Scope should paginate its contents.
  1126. */
  1127. allowPaginate: false,
  1128. /**
  1129. * The class name applied to this scope's target element.
  1130. */
  1131. targetClassName: "variables-view-scope",
  1132. /**
  1133. * Create a new Variable that is a child of this Scope.
  1134. *
  1135. * @param string aName
  1136. * The name of the new Property.
  1137. * @param object aDescriptor
  1138. * The variable's descriptor.
  1139. * @param object aOptions
  1140. * Options of the form accepted by addItem.
  1141. * @return Variable
  1142. * The newly created child Variable.
  1143. */
  1144. _createChild: function (aName, aDescriptor, aOptions) {
  1145. return new Variable(this, aName, aDescriptor, aOptions);
  1146. },
  1147. /**
  1148. * Adds a child to contain any inspected properties.
  1149. *
  1150. * @param string aName
  1151. * The child's name.
  1152. * @param object aDescriptor
  1153. * Specifies the value and/or type & class of the child,
  1154. * or 'get' & 'set' accessor properties. If the type is implicit,
  1155. * it will be inferred from the value. If this parameter is omitted,
  1156. * a property without a value will be added (useful for branch nodes).
  1157. * e.g. - { value: 42 }
  1158. * - { value: true }
  1159. * - { value: "nasu" }
  1160. * - { value: { type: "undefined" } }
  1161. * - { value: { type: "null" } }
  1162. * - { value: { type: "object", class: "Object" } }
  1163. * - { get: { type: "object", class: "Function" },
  1164. * set: { type: "undefined" } }
  1165. * @param object aOptions
  1166. * Specifies some options affecting the new variable.
  1167. * Recognized properties are
  1168. * * boolean relaxed true if name duplicates should be allowed.
  1169. * You probably shouldn't do it. Use this
  1170. * with caution.
  1171. * * boolean internalItem true if the item is internally generated.
  1172. * This is used for special variables
  1173. * like <return> or <exception> and distinguishes
  1174. * them from ordinary properties that happen
  1175. * to have the same name
  1176. * @return Variable
  1177. * The newly created Variable instance, null if it already exists.
  1178. */
  1179. addItem: function (aName, aDescriptor = {}, aOptions = {}) {
  1180. let {relaxed} = aOptions;
  1181. if (this._store.has(aName) && !relaxed) {
  1182. return this._store.get(aName);
  1183. }
  1184. let child = this._createChild(aName, aDescriptor, aOptions);
  1185. this._store.set(aName, child);
  1186. this._variablesView._itemsByElement.set(child._target, child);
  1187. this._variablesView._currHierarchy.set(child.absoluteName, child);
  1188. child.header = aName !== undefined;
  1189. return child;
  1190. },
  1191. /**
  1192. * Adds items for this variable.
  1193. *
  1194. * @param object aItems
  1195. * An object containing some { name: descriptor } data properties,
  1196. * specifying the value and/or type & class of the variable,
  1197. * or 'get' & 'set' accessor properties. If the type is implicit,
  1198. * it will be inferred from the value.
  1199. * e.g. - { someProp0: { value: 42 },
  1200. * someProp1: { value: true },
  1201. * someProp2: { value: "nasu" },
  1202. * someProp3: { value: { type: "undefined" } },
  1203. * someProp4: { value: { type: "null" } },
  1204. * someProp5: { value: { type: "object", class: "Object" } },
  1205. * someProp6: { get: { type: "object", class: "Function" },
  1206. * set: { type: "undefined" } } }
  1207. * @param object aOptions [optional]
  1208. * Additional options for adding the properties. Supported options:
  1209. * - sorted: true to sort all the properties before adding them
  1210. * - callback: function invoked after each item is added
  1211. */
  1212. addItems: function (aItems, aOptions = {}) {
  1213. let names = Object.keys(aItems);
  1214. // Sort all of the properties before adding them, if preferred.
  1215. if (aOptions.sorted) {
  1216. names.sort(this._naturalSort);
  1217. }
  1218. // Add the properties to the current scope.
  1219. for (let name of names) {
  1220. let descriptor = aItems[name];
  1221. let item = this.addItem(name, descriptor);
  1222. if (aOptions.callback) {
  1223. aOptions.callback(item, descriptor && descriptor.value);
  1224. }
  1225. }
  1226. },
  1227. /**
  1228. * Remove this Scope from its parent and remove all children recursively.
  1229. */
  1230. remove: function () {
  1231. let view = this._variablesView;
  1232. view._store.splice(view._store.indexOf(this), 1);
  1233. view._itemsByElement.delete(this._target);
  1234. view._currHierarchy.delete(this._nameString);
  1235. this._target.remove();
  1236. for (let variable of this._store.values()) {
  1237. variable.remove();
  1238. }
  1239. },
  1240. /**
  1241. * Gets the variable in this container having the specified name.
  1242. *
  1243. * @param string aName
  1244. * The name of the variable to get.
  1245. * @return Variable
  1246. * The matched variable, or null if nothing is found.
  1247. */
  1248. get: function (aName) {
  1249. return this._store.get(aName);
  1250. },
  1251. /**
  1252. * Recursively searches for the variable or property in this container
  1253. * displayed by the specified node.
  1254. *
  1255. * @param nsIDOMNode aNode
  1256. * The node to search for.
  1257. * @return Variable | Property
  1258. * The matched variable or property, or null if nothing is found.
  1259. */
  1260. find: function (aNode) {
  1261. for (let [, variable] of this._store) {
  1262. let match;
  1263. if (variable._target == aNode) {
  1264. match = variable;
  1265. } else {
  1266. match = variable.find(aNode);
  1267. }
  1268. if (match) {
  1269. return match;
  1270. }
  1271. }
  1272. return null;
  1273. },
  1274. /**
  1275. * Determines if this scope is a direct child of a parent variables view,
  1276. * scope, variable or property.
  1277. *
  1278. * @param VariablesView | Scope | Variable | Property
  1279. * The parent to check.
  1280. * @return boolean
  1281. * True if the specified item is a direct child, false otherwise.
  1282. */
  1283. isChildOf: function (aParent) {
  1284. return this.ownerView == aParent;
  1285. },
  1286. /**
  1287. * Determines if this scope is a descendant of a parent variables view,
  1288. * scope, variable or property.
  1289. *
  1290. * @param VariablesView | Scope | Variable | Property
  1291. * The parent to check.
  1292. * @return boolean
  1293. * True if the specified item is a descendant, false otherwise.
  1294. */
  1295. isDescendantOf: function (aParent) {
  1296. if (this.isChildOf(aParent)) {
  1297. return true;
  1298. }
  1299. // Recurse to parent if it is a Scope, Variable, or Property.
  1300. if (this.ownerView instanceof Scope) {
  1301. return this.ownerView.isDescendantOf(aParent);
  1302. }
  1303. return false;
  1304. },
  1305. /**
  1306. * Shows the scope.
  1307. */
  1308. show: function () {
  1309. this._target.hidden = false;
  1310. this._isContentVisible = true;
  1311. if (this.onshow) {
  1312. this.onshow(this);
  1313. }
  1314. },
  1315. /**
  1316. * Hides the scope.
  1317. */
  1318. hide: function () {
  1319. this._target.hidden = true;
  1320. this._isContentVisible = false;
  1321. if (this.onhide) {
  1322. this.onhide(this);
  1323. }
  1324. },
  1325. /**
  1326. * Expands the scope, showing all the added details.
  1327. */
  1328. expand: function () {
  1329. if (this._isExpanded || this._isLocked) {
  1330. return;
  1331. }
  1332. if (this._variablesView._enumVisible) {
  1333. this._openEnum();
  1334. }
  1335. if (this._variablesView._nonEnumVisible) {
  1336. Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0);
  1337. }
  1338. this._isExpanded = true;
  1339. if (this.onexpand) {
  1340. // We return onexpand as it sometimes returns a promise
  1341. // (up to the user of VariableView to do it)
  1342. // that can indicate when the view is done expanding
  1343. // and attributes are available. (Mostly used for tests)
  1344. return this.onexpand(this);
  1345. }
  1346. },
  1347. /**
  1348. * Collapses the scope, hiding all the added details.
  1349. */
  1350. collapse: function () {
  1351. if (!this._isExpanded || this._isLocked) {
  1352. return;
  1353. }
  1354. this._arrow.removeAttribute("open");
  1355. this._enum.removeAttribute("open");
  1356. this._nonenum.removeAttribute("open");
  1357. this._isExpanded = false;
  1358. if (this.oncollapse) {
  1359. this.oncollapse(this);
  1360. }
  1361. },
  1362. /**
  1363. * Toggles between the scope's collapsed and expanded state.
  1364. */
  1365. toggle: function (e) {
  1366. if (e && e.button != 0) {
  1367. // Only allow left-click to trigger this event.
  1368. return;
  1369. }
  1370. this.expanded ^= 1;
  1371. // Make sure the scope and its contents are visibile.
  1372. for (let [, variable] of this._store) {
  1373. variable.header = true;
  1374. variable._matched = true;
  1375. }
  1376. if (this.ontoggle) {
  1377. this.ontoggle(this);
  1378. }
  1379. },
  1380. /**
  1381. * Shows the scope's title header.
  1382. */
  1383. showHeader: function () {
  1384. if (this._isHeaderVisible || !this._nameString) {
  1385. return;
  1386. }
  1387. this._target.removeAttribute("untitled");
  1388. this._isHeaderVisible = true;
  1389. },
  1390. /**
  1391. * Hides the scope's title header.
  1392. * This action will automatically expand the scope.
  1393. */
  1394. hideHeader: function () {
  1395. if (!this._isHeaderVisible) {
  1396. return;
  1397. }
  1398. this.expand();
  1399. this._target.setAttribute("untitled", "");
  1400. this._isHeaderVisible = false;
  1401. },
  1402. /**
  1403. * Sort in ascending order
  1404. * This only needs to compare non-numbers since it is dealing with an array
  1405. * which numeric-based indices are placed in order.
  1406. *
  1407. * @param string a
  1408. * @param string b
  1409. * @return number
  1410. * -1 if a is less than b, 0 if no change in order, +1 if a is greater than 0
  1411. */
  1412. _naturalSort: function (a, b) {
  1413. if (isNaN(parseFloat(a)) && isNaN(parseFloat(b))) {
  1414. return a < b ? -1 : 1;
  1415. }
  1416. },
  1417. /**
  1418. * Shows the scope's expand/collapse arrow.
  1419. */
  1420. showArrow: function () {
  1421. if (this._isArrowVisible) {
  1422. return;
  1423. }
  1424. this._arrow.removeAttribute("invisible");
  1425. this._isArrowVisible = true;
  1426. },
  1427. /**
  1428. * Hides the scope's expand/collapse arrow.
  1429. */
  1430. hideArrow: function () {
  1431. if (!this._isArrowVisible) {
  1432. return;
  1433. }
  1434. this._arrow.setAttribute("invisible", "");
  1435. this._isArrowVisible = false;
  1436. },
  1437. /**
  1438. * Gets the visibility state.
  1439. * @return boolean
  1440. */
  1441. get visible() {
  1442. return this._isContentVisible;
  1443. },
  1444. /**
  1445. * Gets the expanded state.
  1446. * @return boolean
  1447. */
  1448. get expanded() {
  1449. return this._isExpanded;
  1450. },
  1451. /**
  1452. * Gets the header visibility state.
  1453. * @return boolean
  1454. */
  1455. get header() {
  1456. return this._isHeaderVisible;
  1457. },
  1458. /**
  1459. * Gets the twisty visibility state.
  1460. * @return boolean
  1461. */
  1462. get twisty() {
  1463. return this._isArrowVisible;
  1464. },
  1465. /**
  1466. * Gets the expand lock state.
  1467. * @return boolean
  1468. */
  1469. get locked() {
  1470. return this._isLocked;
  1471. },
  1472. /**
  1473. * Sets the visibility state.
  1474. * @param boolean aFlag
  1475. */
  1476. set visible(aFlag) {
  1477. aFlag ? this.show() : this.hide();
  1478. },
  1479. /**
  1480. * Sets the expanded state.
  1481. * @param boolean aFlag
  1482. */
  1483. set expanded(aFlag) {
  1484. aFlag ? this.expand() : this.collapse();
  1485. },
  1486. /**
  1487. * Sets the header visibility state.
  1488. * @param boolean aFlag
  1489. */
  1490. set header(aFlag) {
  1491. aFlag ? this.showHeader() : this.hideHeader();
  1492. },
  1493. /**
  1494. * Sets the twisty visibility state.
  1495. * @param boolean aFlag
  1496. */
  1497. set twisty(aFlag) {
  1498. aFlag ? this.showArrow() : this.hideArrow();
  1499. },
  1500. /**
  1501. * Sets the expand lock state.
  1502. * @param boolean aFlag
  1503. */
  1504. set locked(aFlag) {
  1505. this._isLocked = aFlag;
  1506. },
  1507. /**
  1508. * Specifies if this target node may be focused.
  1509. * @return boolean
  1510. */
  1511. get focusable() {
  1512. // Check if this target node is actually visibile.
  1513. if (!this._nameString ||
  1514. !this._isContentVisible ||
  1515. !this._isHeaderVisible ||
  1516. !this._isMatch) {
  1517. return false;
  1518. }
  1519. // Check if all parent objects are expanded.
  1520. let item = this;
  1521. // Recurse while parent is a Scope, Variable, or Property
  1522. while ((item = item.ownerView) && item instanceof Scope) {
  1523. if (!item._isExpanded) {
  1524. return false;
  1525. }
  1526. }
  1527. return true;
  1528. },
  1529. /**
  1530. * Focus this scope.
  1531. */
  1532. focus: function () {
  1533. this._variablesView._focusItem(this);
  1534. },
  1535. /**
  1536. * Adds an event listener for a certain event on this scope's title.
  1537. * @param string aName
  1538. * @param function aCallback
  1539. * @param boolean aCapture
  1540. */
  1541. addEventListener: function (aName, aCallback, aCapture) {
  1542. this._title.addEventListener(aName, aCallback, aCapture);
  1543. },
  1544. /**
  1545. * Removes an event listener for a certain event on this scope's title.
  1546. * @param string aName
  1547. * @param function aCallback
  1548. * @param boolean aCapture
  1549. */
  1550. removeEventListener: function (aName, aCallback, aCapture) {
  1551. this._title.removeEventListener(aName, aCallback, aCapture);
  1552. },
  1553. /**
  1554. * Gets the id associated with this item.
  1555. * @return string
  1556. */
  1557. get id() {
  1558. return this._idString;
  1559. },
  1560. /**
  1561. * Gets the name associated with this item.
  1562. * @return string
  1563. */
  1564. get name() {
  1565. return this._nameString;
  1566. },
  1567. /**
  1568. * Gets the displayed value for this item.
  1569. * @return string
  1570. */
  1571. get displayValue() {
  1572. return this._valueString;
  1573. },
  1574. /**
  1575. * Gets the class names used for the displayed value.
  1576. * @return string
  1577. */
  1578. get displayValueClassName() {
  1579. return this._valueClassName;
  1580. },
  1581. /**
  1582. * Gets the element associated with this item.
  1583. * @return nsIDOMNode
  1584. */
  1585. get target() {
  1586. return this._target;
  1587. },
  1588. /**
  1589. * Initializes this scope's id, view and binds event listeners.
  1590. *
  1591. * @param string aName
  1592. * The scope's name.
  1593. * @param object aFlags [optional]
  1594. * Additional options or flags for this scope.
  1595. */
  1596. _init: function (aName, aFlags) {
  1597. this._idString = generateId(this._nameString = aName);
  1598. this._displayScope(aName, `${this.targetClassName} ${aFlags.customClass}`,
  1599. "devtools-toolbar");
  1600. this._addEventListeners();
  1601. this.parentNode.appendChild(this._target);
  1602. },
  1603. /**
  1604. * Creates the necessary nodes for this scope.
  1605. *
  1606. * @param string aName
  1607. * The scope's name.
  1608. * @param string aTargetClassName
  1609. * A custom class name for this scope's target element.
  1610. * @param string aTitleClassName [optional]
  1611. * A custom class name for this scope's title element.
  1612. */
  1613. _displayScope: function (aName = "", aTargetClassName, aTitleClassName = "") {
  1614. let document = this.document;
  1615. let element = this._target = document.createElement("vbox");
  1616. element.id = this._idString;
  1617. element.className = aTargetClassName;
  1618. let arrow = this._arrow = document.createElement("hbox");
  1619. arrow.className = "arrow theme-twisty";
  1620. let name = this._name = document.createElement("label");
  1621. name.className = "plain name";
  1622. name.setAttribute("value", aName.trim());
  1623. name.setAttribute("crop", "end");
  1624. let title = this._title = document.createElement("hbox");
  1625. title.className = "title " + aTitleClassName;
  1626. title.setAttribute("align", "center");
  1627. let enumerable = this._enum = document.createElement("vbox");
  1628. let nonenum = this._nonenum = document.createElement("vbox");
  1629. enumerable.className = "variables-view-element-details enum";
  1630. nonenum.className = "variables-view-element-details nonenum";
  1631. title.appendChild(arrow);
  1632. title.appendChild(name);
  1633. element.appendChild(title);
  1634. element.appendChild(enumerable);
  1635. element.appendChild(nonenum);
  1636. },
  1637. /**
  1638. * Adds the necessary event listeners for this scope.
  1639. */
  1640. _addEventListeners: function () {
  1641. this._title.addEventListener("mousedown", this._onClick, false);
  1642. },
  1643. /**
  1644. * The click listener for this scope's title.
  1645. */
  1646. _onClick: function (e) {
  1647. if (this.editing ||
  1648. e.button != 0 ||
  1649. e.target == this._editNode ||
  1650. e.target == this._deleteNode ||
  1651. e.target == this._addPropertyNode) {
  1652. return;
  1653. }
  1654. this.toggle();
  1655. this.focus();
  1656. },
  1657. /**
  1658. * Opens the enumerable items container.
  1659. */
  1660. _openEnum: function () {
  1661. this._arrow.setAttribute("open", "");
  1662. this._enum.setAttribute("open", "");
  1663. },
  1664. /**
  1665. * Opens the non-enumerable items container.
  1666. */
  1667. _openNonEnum: function () {
  1668. this._nonenum.setAttribute("open", "");
  1669. },
  1670. /**
  1671. * Specifies if enumerable properties and variables should be displayed.
  1672. * @param boolean aFlag
  1673. */
  1674. set _enumVisible(aFlag) {
  1675. for (let [, variable] of this._store) {
  1676. variable._enumVisible = aFlag;
  1677. if (!this._isExpanded) {
  1678. continue;
  1679. }
  1680. if (aFlag) {
  1681. this._enum.setAttribute("open", "");
  1682. } else {
  1683. this._enum.removeAttribute("open");
  1684. }
  1685. }
  1686. },
  1687. /**
  1688. * Specifies if non-enumerable properties and variables should be displayed.
  1689. * @param boolean aFlag
  1690. */
  1691. set _nonEnumVisible(aFlag) {
  1692. for (let [, variable] of this._store) {
  1693. variable._nonEnumVisible = aFlag;
  1694. if (!this._isExpanded) {
  1695. continue;
  1696. }
  1697. if (aFlag) {
  1698. this._nonenum.setAttribute("open", "");
  1699. } else {
  1700. this._nonenum.removeAttribute("open");
  1701. }
  1702. }
  1703. },
  1704. /**
  1705. * Performs a case insensitive search for variables or properties matching
  1706. * the query, and hides non-matched items.
  1707. *
  1708. * @param string aLowerCaseQuery
  1709. * The lowercased name of the variable or property to search for.
  1710. */
  1711. _performSearch: function (aLowerCaseQuery) {
  1712. for (let [, variable] of this._store) {
  1713. let currentObject = variable;
  1714. let lowerCaseName = variable._nameString.toLowerCase();
  1715. let lowerCaseValue = variable._valueString.toLowerCase();
  1716. // Non-matched variables or properties require a corresponding attribute.
  1717. if (!lowerCaseName.includes(aLowerCaseQuery) &&
  1718. !lowerCaseValue.includes(aLowerCaseQuery)) {
  1719. variable._matched = false;
  1720. }
  1721. // Variable or property is matched.
  1722. else {
  1723. variable._matched = true;
  1724. // If the variable was ever expanded, there's a possibility it may
  1725. // contain some matched properties, so make sure they're visible
  1726. // ("expand downwards").
  1727. if (variable._store.size) {
  1728. variable.expand();
  1729. }
  1730. // If the variable is contained in another Scope, Variable, or Property,
  1731. // the parent may not be a match, thus hidden. It should be visible
  1732. // ("expand upwards").
  1733. while ((variable = variable.ownerView) && variable instanceof Scope) {
  1734. variable._matched = true;
  1735. variable.expand();
  1736. }
  1737. }
  1738. // Proceed with the search recursively inside this variable or property.
  1739. if (currentObject._store.size || currentObject.getter || currentObject.setter) {
  1740. currentObject._performSearch(aLowerCaseQuery);
  1741. }
  1742. }
  1743. },
  1744. /**
  1745. * Sets if this object instance is a matched or non-matched item.
  1746. * @param boolean aStatus
  1747. */
  1748. set _matched(aStatus) {
  1749. if (this._isMatch == aStatus) {
  1750. return;
  1751. }
  1752. if (aStatus) {
  1753. this._isMatch = true;
  1754. this.target.removeAttribute("unmatched");
  1755. } else {
  1756. this._isMatch = false;
  1757. this.target.setAttribute("unmatched", "");
  1758. }
  1759. },
  1760. /**
  1761. * Find the first item in the tree of visible items in this item that matches
  1762. * the predicate. Searches in visual order (the order seen by the user).
  1763. * Tests itself, then descends into first the enumerable children and then
  1764. * the non-enumerable children (since they are presented in separate groups).
  1765. *
  1766. * @param function aPredicate
  1767. * A function that returns true when a match is found.
  1768. * @return Scope | Variable | Property
  1769. * The first visible scope, variable or property, or null if nothing
  1770. * is found.
  1771. */
  1772. _findInVisibleItems: function (aPredicate) {
  1773. if (aPredicate(this)) {
  1774. return this;
  1775. }
  1776. if (this._isExpanded) {
  1777. if (this._variablesView._enumVisible) {
  1778. for (let item of this._enumItems) {
  1779. let result = item._findInVisibleItems(aPredicate);
  1780. if (result) {
  1781. return result;
  1782. }
  1783. }
  1784. }
  1785. if (this._variablesView._nonEnumVisible) {
  1786. for (let item of this._nonEnumItems) {
  1787. let result = item._findInVisibleItems(aPredicate);
  1788. if (result) {
  1789. return result;
  1790. }
  1791. }
  1792. }
  1793. }
  1794. return null;
  1795. },
  1796. /**
  1797. * Find the last item in the tree of visible items in this item that matches
  1798. * the predicate. Searches in reverse visual order (opposite of the order
  1799. * seen by the user). Descends into first the non-enumerable children, then
  1800. * the enumerable children (since they are presented in separate groups), and
  1801. * finally tests itself.
  1802. *
  1803. * @param function aPredicate
  1804. * A function that returns true when a match is found.
  1805. * @return Scope | Variable | Property
  1806. * The last visible scope, variable or property, or null if nothing
  1807. * is found.
  1808. */
  1809. _findInVisibleItemsReverse: function (aPredicate) {
  1810. if (this._isExpanded) {
  1811. if (this._variablesView._nonEnumVisible) {
  1812. for (let i = this._nonEnumItems.length - 1; i >= 0; i--) {
  1813. let item = this._nonEnumItems[i];
  1814. let result = item._findInVisibleItemsReverse(aPredicate);
  1815. if (result) {
  1816. return result;
  1817. }
  1818. }
  1819. }
  1820. if (this._variablesView._enumVisible) {
  1821. for (let i = this._enumItems.length - 1; i >= 0; i--) {
  1822. let item = this._enumItems[i];
  1823. let result = item._findInVisibleItemsReverse(aPredicate);
  1824. if (result) {
  1825. return result;
  1826. }
  1827. }
  1828. }
  1829. }
  1830. if (aPredicate(this)) {
  1831. return this;
  1832. }
  1833. return null;
  1834. },
  1835. /**
  1836. * Gets top level variables view instance.
  1837. * @return VariablesView
  1838. */
  1839. get _variablesView() {
  1840. return this._topView || (this._topView = (() => {
  1841. let parentView = this.ownerView;
  1842. let topView;
  1843. while ((topView = parentView.ownerView)) {
  1844. parentView = topView;
  1845. }
  1846. return parentView;
  1847. })());
  1848. },
  1849. /**
  1850. * Gets the parent node holding this scope.
  1851. * @return nsIDOMNode
  1852. */
  1853. get parentNode() {
  1854. return this.ownerView._list;
  1855. },
  1856. /**
  1857. * Gets the owner document holding this scope.
  1858. * @return nsIHTMLDocument
  1859. */
  1860. get document() {
  1861. return this._document || (this._document = this.ownerView.document);
  1862. },
  1863. /**
  1864. * Gets the default window holding this scope.
  1865. * @return nsIDOMWindow
  1866. */
  1867. get window() {
  1868. return this._window || (this._window = this.ownerView.window);
  1869. },
  1870. _topView: null,
  1871. _document: null,
  1872. _window: null,
  1873. ownerView: null,
  1874. eval: null,
  1875. switch: null,
  1876. delete: null,
  1877. new: null,
  1878. preventDisableOnChange: false,
  1879. preventDescriptorModifiers: false,
  1880. editing: false,
  1881. editableNameTooltip: "",
  1882. editableValueTooltip: "",
  1883. editButtonTooltip: "",
  1884. deleteButtonTooltip: "",
  1885. domNodeValueTooltip: "",
  1886. contextMenuId: "",
  1887. separatorStr: "",
  1888. _store: null,
  1889. _enumItems: null,
  1890. _nonEnumItems: null,
  1891. _fetched: false,
  1892. _committed: false,
  1893. _isLocked: false,
  1894. _isExpanded: false,
  1895. _isContentVisible: true,
  1896. _isHeaderVisible: true,
  1897. _isArrowVisible: true,
  1898. _isMatch: true,
  1899. _idString: "",
  1900. _nameString: "",
  1901. _target: null,
  1902. _arrow: null,
  1903. _name: null,
  1904. _title: null,
  1905. _enum: null,
  1906. _nonenum: null,
  1907. };
  1908. // Creating maps and arrays thousands of times for variables or properties
  1909. // with a large number of children fills up a lot of memory. Make sure
  1910. // these are instantiated only if needed.
  1911. DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", () => new Map());
  1912. DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array);
  1913. DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array);
  1914. /**
  1915. * A Variable is a Scope holding Property instances.
  1916. * Iterable via "for (let [name, property] of instance) { }".
  1917. *
  1918. * @param Scope aScope
  1919. * The scope to contain this variable.
  1920. * @param string aName
  1921. * The variable's name.
  1922. * @param object aDescriptor
  1923. * The variable's descriptor.
  1924. * @param object aOptions
  1925. * Options of the form accepted by Scope.addItem
  1926. */
  1927. function Variable(aScope, aName, aDescriptor, aOptions) {
  1928. this._setTooltips = this._setTooltips.bind(this);
  1929. this._activateNameInput = this._activateNameInput.bind(this);
  1930. this._activateValueInput = this._activateValueInput.bind(this);
  1931. this.openNodeInInspector = this.openNodeInInspector.bind(this);
  1932. this.highlightDomNode = this.highlightDomNode.bind(this);
  1933. this.unhighlightDomNode = this.unhighlightDomNode.bind(this);
  1934. this._internalItem = aOptions.internalItem;
  1935. // Treat safe getter descriptors as descriptors with a value.
  1936. if ("getterValue" in aDescriptor) {
  1937. aDescriptor.value = aDescriptor.getterValue;
  1938. delete aDescriptor.get;
  1939. delete aDescriptor.set;
  1940. }
  1941. Scope.call(this, aScope, aName, this._initialDescriptor = aDescriptor);
  1942. this.setGrip(aDescriptor.value);
  1943. }
  1944. Variable.prototype = Heritage.extend(Scope.prototype, {
  1945. /**
  1946. * Whether this Variable should be prefetched when it is remoted.
  1947. */
  1948. get shouldPrefetch() {
  1949. return this.name == "window" || this.name == "this";
  1950. },
  1951. /**
  1952. * Whether this Variable should paginate its contents.
  1953. */
  1954. get allowPaginate() {
  1955. return this.name != "window" && this.name != "this";
  1956. },
  1957. /**
  1958. * The class name applied to this variable's target element.
  1959. */
  1960. targetClassName: "variables-view-variable variable-or-property",
  1961. /**
  1962. * Create a new Property that is a child of Variable.
  1963. *
  1964. * @param string aName
  1965. * The name of the new Property.
  1966. * @param object aDescriptor
  1967. * The property's descriptor.
  1968. * @param object aOptions
  1969. * Options of the form accepted by Scope.addItem
  1970. * @return Property
  1971. * The newly created child Property.
  1972. */
  1973. _createChild: function (aName, aDescriptor, aOptions) {
  1974. return new Property(this, aName, aDescriptor, aOptions);
  1975. },
  1976. /**
  1977. * Remove this Variable from its parent and remove all children recursively.
  1978. */
  1979. remove: function () {
  1980. if (this._linkedToInspector) {
  1981. this.unhighlightDomNode();
  1982. this._valueLabel.removeEventListener("mouseover", this.highlightDomNode, false);
  1983. this._valueLabel.removeEventListener("mouseout", this.unhighlightDomNode, false);
  1984. this._openInspectorNode.removeEventListener("mousedown", this.openNodeInInspector, false);
  1985. }
  1986. this.ownerView._store.delete(this._nameString);
  1987. this._variablesView._itemsByElement.delete(this._target);
  1988. this._variablesView._currHierarchy.delete(this.absoluteName);
  1989. this._target.remove();
  1990. for (let property of this._store.values()) {
  1991. property.remove();
  1992. }
  1993. },
  1994. /**
  1995. * Populates this variable to contain all the properties of an object.
  1996. *
  1997. * @param object aObject
  1998. * The raw object you want to display.
  1999. * @param object aOptions [optional]
  2000. * Additional options for adding the properties. Supported options:
  2001. * - sorted: true to sort all the properties before adding them
  2002. * - expanded: true to expand all the properties after adding them
  2003. */
  2004. populate: function (aObject, aOptions = {}) {
  2005. // Retrieve the properties only once.
  2006. if (this._fetched) {
  2007. return;
  2008. }
  2009. this._fetched = true;
  2010. let propertyNames = Object.getOwnPropertyNames(aObject);
  2011. let prototype = Object.getPrototypeOf(aObject);
  2012. // Sort all of the properties before adding them, if preferred.
  2013. if (aOptions.sorted) {
  2014. propertyNames.sort(this._naturalSort);
  2015. }
  2016. // Add all the variable properties.
  2017. for (let name of propertyNames) {
  2018. let descriptor = Object.getOwnPropertyDescriptor(aObject, name);
  2019. if (descriptor.get || descriptor.set) {
  2020. let prop = this._addRawNonValueProperty(name, descriptor);
  2021. if (aOptions.expanded) {
  2022. prop.expanded = true;
  2023. }
  2024. } else {
  2025. let prop = this._addRawValueProperty(name, descriptor, aObject[name]);
  2026. if (aOptions.expanded) {
  2027. prop.expanded = true;
  2028. }
  2029. }
  2030. }
  2031. // Add the variable's __proto__.
  2032. if (prototype) {
  2033. this._addRawValueProperty("__proto__", {}, prototype);
  2034. }
  2035. },
  2036. /**
  2037. * Populates a specific variable or property instance to contain all the
  2038. * properties of an object
  2039. *
  2040. * @param Variable | Property aVar
  2041. * The target variable to populate.
  2042. * @param object aObject [optional]
  2043. * The raw object you want to display. If unspecified, the object is
  2044. * assumed to be defined in a _sourceValue property on the target.
  2045. */
  2046. _populateTarget: function (aVar, aObject = aVar._sourceValue) {
  2047. aVar.populate(aObject);
  2048. },
  2049. /**
  2050. * Adds a property for this variable based on a raw value descriptor.
  2051. *
  2052. * @param string aName
  2053. * The property's name.
  2054. * @param object aDescriptor
  2055. * Specifies the exact property descriptor as returned by a call to
  2056. * Object.getOwnPropertyDescriptor.
  2057. * @param object aValue
  2058. * The raw property value you want to display.
  2059. * @return Property
  2060. * The newly added property instance.
  2061. */
  2062. _addRawValueProperty: function (aName, aDescriptor, aValue) {
  2063. let descriptor = Object.create(aDescriptor);
  2064. descriptor.value = VariablesView.getGrip(aValue);
  2065. let propertyItem = this.addItem(aName, descriptor);
  2066. propertyItem._sourceValue = aValue;
  2067. // Add an 'onexpand' callback for the property, lazily handling
  2068. // the addition of new child properties.
  2069. if (!VariablesView.isPrimitive(descriptor)) {
  2070. propertyItem.onexpand = this._populateTarget;
  2071. }
  2072. return propertyItem;
  2073. },
  2074. /**
  2075. * Adds a property for this variable based on a getter/setter descriptor.
  2076. *
  2077. * @param string aName
  2078. * The property's name.
  2079. * @param object aDescriptor
  2080. * Specifies the exact property descriptor as returned by a call to
  2081. * Object.getOwnPropertyDescriptor.
  2082. * @return Property
  2083. * The newly added property instance.
  2084. */
  2085. _addRawNonValueProperty: function (aName, aDescriptor) {
  2086. let descriptor = Object.create(aDescriptor);
  2087. descriptor.get = VariablesView.getGrip(aDescriptor.get);
  2088. descriptor.set = VariablesView.getGrip(aDescriptor.set);
  2089. return this.addItem(aName, descriptor);
  2090. },
  2091. /**
  2092. * Gets this variable's path to the topmost scope in the form of a string
  2093. * meant for use via eval() or a similar approach.
  2094. * For example, a symbolic name may look like "arguments['0']['foo']['bar']".
  2095. * @return string
  2096. */
  2097. get symbolicName() {
  2098. return this._nameString || "";
  2099. },
  2100. /**
  2101. * Gets full path to this variable, including name of the scope.
  2102. * @return string
  2103. */
  2104. get absoluteName() {
  2105. if (this._absoluteName) {
  2106. return this._absoluteName;
  2107. }
  2108. this._absoluteName = this.ownerView._nameString + "[" + escapeString(this._nameString) + "]";
  2109. return this._absoluteName;
  2110. },
  2111. /**
  2112. * Gets this variable's symbolic path to the topmost scope.
  2113. * @return array
  2114. * @see Variable._buildSymbolicPath
  2115. */
  2116. get symbolicPath() {
  2117. if (this._symbolicPath) {
  2118. return this._symbolicPath;
  2119. }
  2120. this._symbolicPath = this._buildSymbolicPath();
  2121. return this._symbolicPath;
  2122. },
  2123. /**
  2124. * Build this variable's path to the topmost scope in form of an array of
  2125. * strings, one for each segment of the path.
  2126. * For example, a symbolic path may look like ["0", "foo", "bar"].
  2127. * @return array
  2128. */
  2129. _buildSymbolicPath: function (path = []) {
  2130. if (this.name) {
  2131. path.unshift(this.name);
  2132. if (this.ownerView instanceof Variable) {
  2133. return this.ownerView._buildSymbolicPath(path);
  2134. }
  2135. }
  2136. return path;
  2137. },
  2138. /**
  2139. * Returns this variable's value from the descriptor if available.
  2140. * @return any
  2141. */
  2142. get value() {
  2143. return this._initialDescriptor.value;
  2144. },
  2145. /**
  2146. * Returns this variable's getter from the descriptor if available.
  2147. * @return object
  2148. */
  2149. get getter() {
  2150. return this._initialDescriptor.get;
  2151. },
  2152. /**
  2153. * Returns this variable's getter from the descriptor if available.
  2154. * @return object
  2155. */
  2156. get setter() {
  2157. return this._initialDescriptor.set;
  2158. },
  2159. /**
  2160. * Sets the specific grip for this variable (applies the text content and
  2161. * class name to the value label).
  2162. *
  2163. * The grip should contain the value or the type & class, as defined in the
  2164. * remote debugger protocol. For convenience, undefined and null are
  2165. * both considered types.
  2166. *
  2167. * @param any aGrip
  2168. * Specifies the value and/or type & class of the variable.
  2169. * e.g. - 42
  2170. * - true
  2171. * - "nasu"
  2172. * - { type: "undefined" }
  2173. * - { type: "null" }
  2174. * - { type: "object", class: "Object" }
  2175. */
  2176. setGrip: function (aGrip) {
  2177. // Don't allow displaying grip information if there's no name available
  2178. // or the grip is malformed.
  2179. if (this._nameString === undefined || aGrip === undefined || aGrip === null) {
  2180. return;
  2181. }
  2182. // Getters and setters should display grip information in sub-properties.
  2183. if (this.getter || this.setter) {
  2184. return;
  2185. }
  2186. let prevGrip = this._valueGrip;
  2187. if (prevGrip) {
  2188. this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
  2189. }
  2190. this._valueGrip = aGrip;
  2191. if (aGrip && (aGrip.optimizedOut || aGrip.uninitialized || aGrip.missingArguments)) {
  2192. if (aGrip.optimizedOut) {
  2193. this._valueString = L10N.getStr("variablesViewOptimizedOut");
  2194. }
  2195. else if (aGrip.uninitialized) {
  2196. this._valueString = L10N.getStr("variablesViewUninitialized");
  2197. }
  2198. else if (aGrip.missingArguments) {
  2199. this._valueString = L10N.getStr("variablesViewMissingArgs");
  2200. }
  2201. this.eval = null;
  2202. }
  2203. else {
  2204. this._valueString = VariablesView.getString(aGrip, {
  2205. concise: true,
  2206. noEllipsis: true,
  2207. });
  2208. this.eval = this.ownerView.eval;
  2209. }
  2210. this._valueClassName = VariablesView.getClass(aGrip);
  2211. this._valueLabel.classList.add(this._valueClassName);
  2212. this._valueLabel.setAttribute("value", this._valueString);
  2213. this._separatorLabel.hidden = false;
  2214. // DOMNodes get special treatment since they can be linked to the inspector
  2215. if (this._valueGrip.preview && this._valueGrip.preview.kind === "DOMNode") {
  2216. this._linkToInspector();
  2217. }
  2218. },
  2219. /**
  2220. * Marks this variable as overridden.
  2221. *
  2222. * @param boolean aFlag
  2223. * Whether this variable is overridden or not.
  2224. */
  2225. setOverridden: function (aFlag) {
  2226. if (aFlag) {
  2227. this._target.setAttribute("overridden", "");
  2228. } else {
  2229. this._target.removeAttribute("overridden");
  2230. }
  2231. },
  2232. /**
  2233. * Briefly flashes this variable.
  2234. *
  2235. * @param number aDuration [optional]
  2236. * An optional flash animation duration.
  2237. */
  2238. flash: function (aDuration = ITEM_FLASH_DURATION) {
  2239. let fadeInDelay = this._variablesView.lazyEmptyDelay + 1;
  2240. let fadeOutDelay = fadeInDelay + aDuration;
  2241. setNamedTimeout("vview-flash-in" + this.absoluteName,
  2242. fadeInDelay, () => this._target.setAttribute("changed", ""));
  2243. setNamedTimeout("vview-flash-out" + this.absoluteName,
  2244. fadeOutDelay, () => this._target.removeAttribute("changed"));
  2245. },
  2246. /**
  2247. * Initializes this variable's id, view and binds event listeners.
  2248. *
  2249. * @param string aName
  2250. * The variable's name.
  2251. * @param object aDescriptor
  2252. * The variable's descriptor.
  2253. */
  2254. _init: function (aName, aDescriptor) {
  2255. this._idString = generateId(this._nameString = aName);
  2256. this._displayScope(aName, this.targetClassName);
  2257. this._displayVariable();
  2258. this._customizeVariable();
  2259. this._prepareTooltips();
  2260. this._setAttributes();
  2261. this._addEventListeners();
  2262. if (this._initialDescriptor.enumerable ||
  2263. this._nameString == "this" ||
  2264. this._internalItem) {
  2265. this.ownerView._enum.appendChild(this._target);
  2266. this.ownerView._enumItems.push(this);
  2267. } else {
  2268. this.ownerView._nonenum.appendChild(this._target);
  2269. this.ownerView._nonEnumItems.push(this);
  2270. }
  2271. },
  2272. /**
  2273. * Creates the necessary nodes for this variable.
  2274. */
  2275. _displayVariable: function () {
  2276. let document = this.document;
  2277. let descriptor = this._initialDescriptor;
  2278. let separatorLabel = this._separatorLabel = document.createElement("label");
  2279. separatorLabel.className = "plain separator";
  2280. separatorLabel.setAttribute("value", this.separatorStr + " ");
  2281. let valueLabel = this._valueLabel = document.createElement("label");
  2282. valueLabel.className = "plain value";
  2283. valueLabel.setAttribute("flex", "1");
  2284. valueLabel.setAttribute("crop", "center");
  2285. this._title.appendChild(separatorLabel);
  2286. this._title.appendChild(valueLabel);
  2287. if (VariablesView.isPrimitive(descriptor)) {
  2288. this.hideArrow();
  2289. }
  2290. // If no value will be displayed, we don't need the separator.
  2291. if (!descriptor.get && !descriptor.set && !("value" in descriptor)) {
  2292. separatorLabel.hidden = true;
  2293. }
  2294. // If this is a getter/setter property, create two child pseudo-properties
  2295. // called "get" and "set" that display the corresponding functions.
  2296. if (descriptor.get || descriptor.set) {
  2297. separatorLabel.hidden = true;
  2298. valueLabel.hidden = true;
  2299. // Changing getter/setter names is never allowed.
  2300. this.switch = null;
  2301. // Getter/setter properties require special handling when it comes to
  2302. // evaluation and deletion.
  2303. if (this.ownerView.eval) {
  2304. this.delete = VariablesView.getterOrSetterDeleteCallback;
  2305. this.evaluationMacro = VariablesView.overrideValueEvalMacro;
  2306. }
  2307. // Deleting getters and setters individually is not allowed if no
  2308. // evaluation method is provided.
  2309. else {
  2310. this.delete = null;
  2311. this.evaluationMacro = null;
  2312. }
  2313. let getter = this.addItem("get", { value: descriptor.get });
  2314. let setter = this.addItem("set", { value: descriptor.set });
  2315. getter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
  2316. setter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
  2317. getter.hideArrow();
  2318. setter.hideArrow();
  2319. this.expand();
  2320. }
  2321. },
  2322. /**
  2323. * Adds specific nodes for this variable based on custom flags.
  2324. */
  2325. _customizeVariable: function () {
  2326. let ownerView = this.ownerView;
  2327. let descriptor = this._initialDescriptor;
  2328. if (ownerView.eval && this.getter || this.setter) {
  2329. let editNode = this._editNode = this.document.createElement("toolbarbutton");
  2330. editNode.className = "plain variables-view-edit";
  2331. editNode.addEventListener("mousedown", this._onEdit.bind(this), false);
  2332. this._title.insertBefore(editNode, this._spacer);
  2333. }
  2334. if (ownerView.delete) {
  2335. let deleteNode = this._deleteNode = this.document.createElement("toolbarbutton");
  2336. deleteNode.className = "plain variables-view-delete";
  2337. deleteNode.addEventListener("click", this._onDelete.bind(this), false);
  2338. this._title.appendChild(deleteNode);
  2339. }
  2340. if (ownerView.new) {
  2341. let addPropertyNode = this._addPropertyNode = this.document.createElement("toolbarbutton");
  2342. addPropertyNode.className = "plain variables-view-add-property";
  2343. addPropertyNode.addEventListener("mousedown", this._onAddProperty.bind(this), false);
  2344. this._title.appendChild(addPropertyNode);
  2345. // Can't add properties to primitive values, hide the node in those cases.
  2346. if (VariablesView.isPrimitive(descriptor)) {
  2347. addPropertyNode.setAttribute("invisible", "");
  2348. }
  2349. }
  2350. if (ownerView.contextMenuId) {
  2351. this._title.setAttribute("context", ownerView.contextMenuId);
  2352. }
  2353. if (ownerView.preventDescriptorModifiers) {
  2354. return;
  2355. }
  2356. if (!descriptor.writable && !ownerView.getter && !ownerView.setter) {
  2357. let nonWritableIcon = this.document.createElement("hbox");
  2358. nonWritableIcon.className = "plain variable-or-property-non-writable-icon";
  2359. nonWritableIcon.setAttribute("optional-visibility", "");
  2360. this._title.appendChild(nonWritableIcon);
  2361. }
  2362. if (descriptor.value && typeof descriptor.value == "object") {
  2363. if (descriptor.value.frozen) {
  2364. let frozenLabel = this.document.createElement("label");
  2365. frozenLabel.className = "plain variable-or-property-frozen-label";
  2366. frozenLabel.setAttribute("optional-visibility", "");
  2367. frozenLabel.setAttribute("value", "F");
  2368. this._title.appendChild(frozenLabel);
  2369. }
  2370. if (descriptor.value.sealed) {
  2371. let sealedLabel = this.document.createElement("label");
  2372. sealedLabel.className = "plain variable-or-property-sealed-label";
  2373. sealedLabel.setAttribute("optional-visibility", "");
  2374. sealedLabel.setAttribute("value", "S");
  2375. this._title.appendChild(sealedLabel);
  2376. }
  2377. if (!descriptor.value.extensible) {
  2378. let nonExtensibleLabel = this.document.createElement("label");
  2379. nonExtensibleLabel.className = "plain variable-or-property-non-extensible-label";
  2380. nonExtensibleLabel.setAttribute("optional-visibility", "");
  2381. nonExtensibleLabel.setAttribute("value", "N");
  2382. this._title.appendChild(nonExtensibleLabel);
  2383. }
  2384. }
  2385. },
  2386. /**
  2387. * Prepares all tooltips for this variable.
  2388. */
  2389. _prepareTooltips: function () {
  2390. this._target.addEventListener("mouseover", this._setTooltips, false);
  2391. },
  2392. /**
  2393. * Sets all tooltips for this variable.
  2394. */
  2395. _setTooltips: function () {
  2396. this._target.removeEventListener("mouseover", this._setTooltips, false);
  2397. let ownerView = this.ownerView;
  2398. if (ownerView.preventDescriptorModifiers) {
  2399. return;
  2400. }
  2401. let tooltip = this.document.createElement("tooltip");
  2402. tooltip.id = "tooltip-" + this._idString;
  2403. tooltip.setAttribute("orient", "horizontal");
  2404. let labels = [
  2405. "configurable", "enumerable", "writable",
  2406. "frozen", "sealed", "extensible", "overridden", "WebIDL"];
  2407. for (let type of labels) {
  2408. let labelElement = this.document.createElement("label");
  2409. labelElement.className = type;
  2410. labelElement.setAttribute("value", L10N.getStr(type + "Tooltip"));
  2411. tooltip.appendChild(labelElement);
  2412. }
  2413. this._target.appendChild(tooltip);
  2414. this._target.setAttribute("tooltip", tooltip.id);
  2415. if (this._editNode && ownerView.eval) {
  2416. this._editNode.setAttribute("tooltiptext", ownerView.editButtonTooltip);
  2417. }
  2418. if (this._openInspectorNode && this._linkedToInspector) {
  2419. this._openInspectorNode.setAttribute("tooltiptext", this.ownerView.domNodeValueTooltip);
  2420. }
  2421. if (this._valueLabel && ownerView.eval) {
  2422. this._valueLabel.setAttribute("tooltiptext", ownerView.editableValueTooltip);
  2423. }
  2424. if (this._name && ownerView.switch) {
  2425. this._name.setAttribute("tooltiptext", ownerView.editableNameTooltip);
  2426. }
  2427. if (this._deleteNode && ownerView.delete) {
  2428. this._deleteNode.setAttribute("tooltiptext", ownerView.deleteButtonTooltip);
  2429. }
  2430. },
  2431. /**
  2432. * Get the parent variablesview toolbox, if any.
  2433. */
  2434. get toolbox() {
  2435. return this._variablesView.toolbox;
  2436. },
  2437. /**
  2438. * Checks if this variable is a DOMNode and is part of a variablesview that
  2439. * has been linked to the toolbox, so that highlighting and jumping to the
  2440. * inspector can be done.
  2441. */
  2442. _isLinkableToInspector: function () {
  2443. let isDomNode = this._valueGrip && this._valueGrip.preview.kind === "DOMNode";
  2444. let hasBeenLinked = this._linkedToInspector;
  2445. let hasToolbox = !!this.toolbox;
  2446. return isDomNode && !hasBeenLinked && hasToolbox;
  2447. },
  2448. /**
  2449. * If the variable is a DOMNode, and if a toolbox is set, then link it to the
  2450. * inspector (highlight on hover, and jump to markup-view on click)
  2451. */
  2452. _linkToInspector: function () {
  2453. if (!this._isLinkableToInspector()) {
  2454. return;
  2455. }
  2456. // Listen to value mouseover/click events to highlight and jump
  2457. this._valueLabel.addEventListener("mouseover", this.highlightDomNode, false);
  2458. this._valueLabel.addEventListener("mouseout", this.unhighlightDomNode, false);
  2459. // Add a button to open the node in the inspector
  2460. this._openInspectorNode = this.document.createElement("toolbarbutton");
  2461. this._openInspectorNode.className = "plain variables-view-open-inspector";
  2462. this._openInspectorNode.addEventListener("mousedown", this.openNodeInInspector, false);
  2463. this._title.appendChild(this._openInspectorNode);
  2464. this._linkedToInspector = true;
  2465. },
  2466. /**
  2467. * In case this variable is a DOMNode and part of a variablesview that has been
  2468. * linked to the toolbox's inspector, then select the corresponding node in
  2469. * the inspector, and switch the inspector tool in the toolbox
  2470. * @return a promise that resolves when the node is selected and the inspector
  2471. * has been switched to and is ready
  2472. */
  2473. openNodeInInspector: function (event) {
  2474. if (!this.toolbox) {
  2475. return promise.reject(new Error("Toolbox not available"));
  2476. }
  2477. event && event.stopPropagation();
  2478. return Task.spawn(function* () {
  2479. yield this.toolbox.initInspector();
  2480. let nodeFront = this._nodeFront;
  2481. if (!nodeFront) {
  2482. nodeFront = yield this.toolbox.walker.getNodeActorFromObjectActor(this._valueGrip.actor);
  2483. }
  2484. if (nodeFront) {
  2485. yield this.toolbox.selectTool("inspector");
  2486. let inspectorReady = defer();
  2487. this.toolbox.getPanel("inspector").once("inspector-updated", inspectorReady.resolve);
  2488. yield this.toolbox.selection.setNodeFront(nodeFront, "variables-view");
  2489. yield inspectorReady.promise;
  2490. }
  2491. }.bind(this));
  2492. },
  2493. /**
  2494. * In case this variable is a DOMNode and part of a variablesview that has been
  2495. * linked to the toolbox's inspector, then highlight the corresponding node
  2496. */
  2497. highlightDomNode: function () {
  2498. if (this.toolbox) {
  2499. if (this._nodeFront) {
  2500. // If the nodeFront has been retrieved before, no need to ask the server
  2501. // again for it
  2502. this.toolbox.highlighterUtils.highlightNodeFront(this._nodeFront);
  2503. return;
  2504. }
  2505. this.toolbox.highlighterUtils.highlightDomValueGrip(this._valueGrip).then(front => {
  2506. this._nodeFront = front;
  2507. });
  2508. }
  2509. },
  2510. /**
  2511. * Unhighlight a previously highlit node
  2512. * @see highlightDomNode
  2513. */
  2514. unhighlightDomNode: function () {
  2515. if (this.toolbox) {
  2516. this.toolbox.highlighterUtils.unhighlight();
  2517. }
  2518. },
  2519. /**
  2520. * Sets a variable's configurable, enumerable and writable attributes,
  2521. * and specifies if it's a 'this', '<exception>', '<return>' or '__proto__'
  2522. * reference.
  2523. */
  2524. _setAttributes: function () {
  2525. let ownerView = this.ownerView;
  2526. if (ownerView.preventDescriptorModifiers) {
  2527. return;
  2528. }
  2529. let descriptor = this._initialDescriptor;
  2530. let target = this._target;
  2531. let name = this._nameString;
  2532. if (ownerView.eval) {
  2533. target.setAttribute("editable", "");
  2534. }
  2535. if (!descriptor.configurable) {
  2536. target.setAttribute("non-configurable", "");
  2537. }
  2538. if (!descriptor.enumerable) {
  2539. target.setAttribute("non-enumerable", "");
  2540. }
  2541. if (!descriptor.writable && !ownerView.getter && !ownerView.setter) {
  2542. target.setAttribute("non-writable", "");
  2543. }
  2544. if (descriptor.value && typeof descriptor.value == "object") {
  2545. if (descriptor.value.frozen) {
  2546. target.setAttribute("frozen", "");
  2547. }
  2548. if (descriptor.value.sealed) {
  2549. target.setAttribute("sealed", "");
  2550. }
  2551. if (!descriptor.value.extensible) {
  2552. target.setAttribute("non-extensible", "");
  2553. }
  2554. }
  2555. if (descriptor && "getterValue" in descriptor) {
  2556. target.setAttribute("safe-getter", "");
  2557. }
  2558. if (name == "this") {
  2559. target.setAttribute("self", "");
  2560. }
  2561. else if (this._internalItem && name == "<exception>") {
  2562. target.setAttribute("exception", "");
  2563. target.setAttribute("pseudo-item", "");
  2564. }
  2565. else if (this._internalItem && name == "<return>") {
  2566. target.setAttribute("return", "");
  2567. target.setAttribute("pseudo-item", "");
  2568. }
  2569. else if (name == "__proto__") {
  2570. target.setAttribute("proto", "");
  2571. target.setAttribute("pseudo-item", "");
  2572. }
  2573. if (Object.keys(descriptor).length == 0) {
  2574. target.setAttribute("pseudo-item", "");
  2575. }
  2576. },
  2577. /**
  2578. * Adds the necessary event listeners for this variable.
  2579. */
  2580. _addEventListeners: function () {
  2581. this._name.addEventListener("dblclick", this._activateNameInput, false);
  2582. this._valueLabel.addEventListener("mousedown", this._activateValueInput, false);
  2583. this._title.addEventListener("mousedown", this._onClick, false);
  2584. },
  2585. /**
  2586. * Makes this variable's name editable.
  2587. */
  2588. _activateNameInput: function (e) {
  2589. if (!this._variablesView.alignedValues) {
  2590. this._separatorLabel.hidden = true;
  2591. this._valueLabel.hidden = true;
  2592. }
  2593. EditableName.create(this, {
  2594. onSave: aKey => {
  2595. if (!this._variablesView.preventDisableOnChange) {
  2596. this._disable();
  2597. }
  2598. this.ownerView.switch(this, aKey);
  2599. },
  2600. onCleanup: () => {
  2601. if (!this._variablesView.alignedValues) {
  2602. this._separatorLabel.hidden = false;
  2603. this._valueLabel.hidden = false;
  2604. }
  2605. }
  2606. }, e);
  2607. },
  2608. /**
  2609. * Makes this variable's value editable.
  2610. */
  2611. _activateValueInput: function (e) {
  2612. EditableValue.create(this, {
  2613. onSave: aString => {
  2614. if (this._linkedToInspector) {
  2615. this.unhighlightDomNode();
  2616. }
  2617. if (!this._variablesView.preventDisableOnChange) {
  2618. this._disable();
  2619. }
  2620. this.ownerView.eval(this, aString);
  2621. }
  2622. }, e);
  2623. },
  2624. /**
  2625. * Disables this variable prior to a new name switch or value evaluation.
  2626. */
  2627. _disable: function () {
  2628. // Prevent the variable from being collapsed or expanded.
  2629. this.hideArrow();
  2630. // Hide any nodes that may offer information about the variable.
  2631. for (let node of this._title.childNodes) {
  2632. node.hidden = node != this._arrow && node != this._name;
  2633. }
  2634. this._enum.hidden = true;
  2635. this._nonenum.hidden = true;
  2636. },
  2637. /**
  2638. * The current macro used to generate the string evaluated when performing
  2639. * a variable or property value change.
  2640. */
  2641. evaluationMacro: VariablesView.simpleValueEvalMacro,
  2642. /**
  2643. * The click listener for the edit button.
  2644. */
  2645. _onEdit: function (e) {
  2646. if (e.button != 0) {
  2647. return;
  2648. }
  2649. e.preventDefault();
  2650. e.stopPropagation();
  2651. this._activateValueInput();
  2652. },
  2653. /**
  2654. * The click listener for the delete button.
  2655. */
  2656. _onDelete: function (e) {
  2657. if ("button" in e && e.button != 0) {
  2658. return;
  2659. }
  2660. e.preventDefault();
  2661. e.stopPropagation();
  2662. if (this.ownerView.delete) {
  2663. if (!this.ownerView.delete(this)) {
  2664. this.hide();
  2665. }
  2666. }
  2667. },
  2668. /**
  2669. * The click listener for the add property button.
  2670. */
  2671. _onAddProperty: function (e) {
  2672. if ("button" in e && e.button != 0) {
  2673. return;
  2674. }
  2675. e.preventDefault();
  2676. e.stopPropagation();
  2677. this.expanded = true;
  2678. let item = this.addItem(" ", {
  2679. value: undefined,
  2680. configurable: true,
  2681. enumerable: true,
  2682. writable: true
  2683. }, {relaxed: true});
  2684. // Force showing the separator.
  2685. item._separatorLabel.hidden = false;
  2686. EditableNameAndValue.create(item, {
  2687. onSave: ([aKey, aValue]) => {
  2688. if (!this._variablesView.preventDisableOnChange) {
  2689. this._disable();
  2690. }
  2691. this.ownerView.new(this, aKey, aValue);
  2692. }
  2693. }, e);
  2694. },
  2695. _symbolicName: null,
  2696. _symbolicPath: null,
  2697. _absoluteName: null,
  2698. _initialDescriptor: null,
  2699. _separatorLabel: null,
  2700. _valueLabel: null,
  2701. _spacer: null,
  2702. _editNode: null,
  2703. _deleteNode: null,
  2704. _addPropertyNode: null,
  2705. _tooltip: null,
  2706. _valueGrip: null,
  2707. _valueString: "",
  2708. _valueClassName: "",
  2709. _prevExpandable: false,
  2710. _prevExpanded: false
  2711. });
  2712. /**
  2713. * A Property is a Variable holding additional child Property instances.
  2714. * Iterable via "for (let [name, property] of instance) { }".
  2715. *
  2716. * @param Variable aVar
  2717. * The variable to contain this property.
  2718. * @param string aName
  2719. * The property's name.
  2720. * @param object aDescriptor
  2721. * The property's descriptor.
  2722. * @param object aOptions
  2723. * Options of the form accepted by Scope.addItem
  2724. */
  2725. function Property(aVar, aName, aDescriptor, aOptions) {
  2726. Variable.call(this, aVar, aName, aDescriptor, aOptions);
  2727. }
  2728. Property.prototype = Heritage.extend(Variable.prototype, {
  2729. /**
  2730. * The class name applied to this property's target element.
  2731. */
  2732. targetClassName: "variables-view-property variable-or-property",
  2733. /**
  2734. * @see Variable.symbolicName
  2735. * @return string
  2736. */
  2737. get symbolicName() {
  2738. if (this._symbolicName) {
  2739. return this._symbolicName;
  2740. }
  2741. this._symbolicName = this.ownerView.symbolicName + "[" + escapeString(this._nameString) + "]";
  2742. return this._symbolicName;
  2743. },
  2744. /**
  2745. * @see Variable.absoluteName
  2746. * @return string
  2747. */
  2748. get absoluteName() {
  2749. if (this._absoluteName) {
  2750. return this._absoluteName;
  2751. }
  2752. this._absoluteName = this.ownerView.absoluteName + "[" + escapeString(this._nameString) + "]";
  2753. return this._absoluteName;
  2754. }
  2755. });
  2756. /**
  2757. * A generator-iterator over the VariablesView, Scopes, Variables and Properties.
  2758. */
  2759. VariablesView.prototype[Symbol.iterator] =
  2760. Scope.prototype[Symbol.iterator] =
  2761. Variable.prototype[Symbol.iterator] =
  2762. Property.prototype[Symbol.iterator] = function* () {
  2763. yield* this._store;
  2764. };
  2765. /**
  2766. * Forget everything recorded about added scopes, variables or properties.
  2767. * @see VariablesView.commitHierarchy
  2768. */
  2769. VariablesView.prototype.clearHierarchy = function () {
  2770. this._prevHierarchy.clear();
  2771. this._currHierarchy.clear();
  2772. };
  2773. /**
  2774. * Perform operations on all the VariablesView Scopes, Variables and Properties
  2775. * after you've added all the items you wanted.
  2776. *
  2777. * Calling this method is optional, and does the following:
  2778. * - styles the items overridden by other items in parent scopes
  2779. * - reopens the items which were previously expanded
  2780. * - flashes the items whose values changed
  2781. */
  2782. VariablesView.prototype.commitHierarchy = function () {
  2783. for (let [, currItem] of this._currHierarchy) {
  2784. // Avoid performing expensive operations.
  2785. if (this.commitHierarchyIgnoredItems[currItem._nameString]) {
  2786. continue;
  2787. }
  2788. let overridden = this.isOverridden(currItem);
  2789. if (overridden) {
  2790. currItem.setOverridden(true);
  2791. }
  2792. let expanded = !currItem._committed && this.wasExpanded(currItem);
  2793. if (expanded) {
  2794. currItem.expand();
  2795. }
  2796. let changed = !currItem._committed && this.hasChanged(currItem);
  2797. if (changed) {
  2798. currItem.flash();
  2799. }
  2800. currItem._committed = true;
  2801. }
  2802. if (this.oncommit) {
  2803. this.oncommit(this);
  2804. }
  2805. };
  2806. // Some variables are likely to contain a very large number of properties.
  2807. // It would be a bad idea to re-expand them or perform expensive operations.
  2808. VariablesView.prototype.commitHierarchyIgnoredItems = Heritage.extend(null, {
  2809. "window": true,
  2810. "this": true
  2811. });
  2812. /**
  2813. * Checks if the an item was previously expanded, if it existed in a
  2814. * previous hierarchy.
  2815. *
  2816. * @param Scope | Variable | Property aItem
  2817. * The item to verify.
  2818. * @return boolean
  2819. * Whether the item was expanded.
  2820. */
  2821. VariablesView.prototype.wasExpanded = function (aItem) {
  2822. if (!(aItem instanceof Scope)) {
  2823. return false;
  2824. }
  2825. let prevItem = this._prevHierarchy.get(aItem.absoluteName || aItem._nameString);
  2826. return prevItem ? prevItem._isExpanded : false;
  2827. };
  2828. /**
  2829. * Checks if the an item's displayed value (a representation of the grip)
  2830. * has changed, if it existed in a previous hierarchy.
  2831. *
  2832. * @param Variable | Property aItem
  2833. * The item to verify.
  2834. * @return boolean
  2835. * Whether the item has changed.
  2836. */
  2837. VariablesView.prototype.hasChanged = function (aItem) {
  2838. // Only analyze Variables and Properties for displayed value changes.
  2839. // Scopes are just collections of Variables and Properties and
  2840. // don't have a "value", so they can't change.
  2841. if (!(aItem instanceof Variable)) {
  2842. return false;
  2843. }
  2844. let prevItem = this._prevHierarchy.get(aItem.absoluteName);
  2845. return prevItem ? prevItem._valueString != aItem._valueString : false;
  2846. };
  2847. /**
  2848. * Checks if the an item was previously expanded, if it existed in a
  2849. * previous hierarchy.
  2850. *
  2851. * @param Scope | Variable | Property aItem
  2852. * The item to verify.
  2853. * @return boolean
  2854. * Whether the item was expanded.
  2855. */
  2856. VariablesView.prototype.isOverridden = function (aItem) {
  2857. // Only analyze Variables for being overridden in different Scopes.
  2858. if (!(aItem instanceof Variable) || aItem instanceof Property) {
  2859. return false;
  2860. }
  2861. let currVariableName = aItem._nameString;
  2862. let parentScopes = this.getParentScopesForVariableOrProperty(aItem);
  2863. for (let otherScope of parentScopes) {
  2864. for (let [otherVariableName] of otherScope) {
  2865. if (otherVariableName == currVariableName) {
  2866. return true;
  2867. }
  2868. }
  2869. }
  2870. return false;
  2871. };
  2872. /**
  2873. * Returns true if the descriptor represents an undefined, null or
  2874. * primitive value.
  2875. *
  2876. * @param object aDescriptor
  2877. * The variable's descriptor.
  2878. */
  2879. VariablesView.isPrimitive = function (aDescriptor) {
  2880. // For accessor property descriptors, the getter and setter need to be
  2881. // contained in 'get' and 'set' properties.
  2882. let getter = aDescriptor.get;
  2883. let setter = aDescriptor.set;
  2884. if (getter || setter) {
  2885. return false;
  2886. }
  2887. // As described in the remote debugger protocol, the value grip
  2888. // must be contained in a 'value' property.
  2889. let grip = aDescriptor.value;
  2890. if (typeof grip != "object") {
  2891. return true;
  2892. }
  2893. // For convenience, undefined, null, Infinity, -Infinity, NaN, -0, and long
  2894. // strings are considered types.
  2895. let type = grip.type;
  2896. if (type == "undefined" ||
  2897. type == "null" ||
  2898. type == "Infinity" ||
  2899. type == "-Infinity" ||
  2900. type == "NaN" ||
  2901. type == "-0" ||
  2902. type == "symbol" ||
  2903. type == "longString") {
  2904. return true;
  2905. }
  2906. return false;
  2907. };
  2908. /**
  2909. * Returns true if the descriptor represents an undefined value.
  2910. *
  2911. * @param object aDescriptor
  2912. * The variable's descriptor.
  2913. */
  2914. VariablesView.isUndefined = function (aDescriptor) {
  2915. // For accessor property descriptors, the getter and setter need to be
  2916. // contained in 'get' and 'set' properties.
  2917. let getter = aDescriptor.get;
  2918. let setter = aDescriptor.set;
  2919. if (typeof getter == "object" && getter.type == "undefined" &&
  2920. typeof setter == "object" && setter.type == "undefined") {
  2921. return true;
  2922. }
  2923. // As described in the remote debugger protocol, the value grip
  2924. // must be contained in a 'value' property.
  2925. let grip = aDescriptor.value;
  2926. if (typeof grip == "object" && grip.type == "undefined") {
  2927. return true;
  2928. }
  2929. return false;
  2930. };
  2931. /**
  2932. * Returns true if the descriptor represents a falsy value.
  2933. *
  2934. * @param object aDescriptor
  2935. * The variable's descriptor.
  2936. */
  2937. VariablesView.isFalsy = function (aDescriptor) {
  2938. // As described in the remote debugger protocol, the value grip
  2939. // must be contained in a 'value' property.
  2940. let grip = aDescriptor.value;
  2941. if (typeof grip != "object") {
  2942. return !grip;
  2943. }
  2944. // For convenience, undefined, null, NaN, and -0 are all considered types.
  2945. let type = grip.type;
  2946. if (type == "undefined" ||
  2947. type == "null" ||
  2948. type == "NaN" ||
  2949. type == "-0") {
  2950. return true;
  2951. }
  2952. return false;
  2953. };
  2954. /**
  2955. * Returns true if the value is an instance of Variable or Property.
  2956. *
  2957. * @param any aValue
  2958. * The value to test.
  2959. */
  2960. VariablesView.isVariable = function (aValue) {
  2961. return aValue instanceof Variable;
  2962. };
  2963. /**
  2964. * Returns a standard grip for a value.
  2965. *
  2966. * @param any aValue
  2967. * The raw value to get a grip for.
  2968. * @return any
  2969. * The value's grip.
  2970. */
  2971. VariablesView.getGrip = function (aValue) {
  2972. switch (typeof aValue) {
  2973. case "boolean":
  2974. case "string":
  2975. return aValue;
  2976. case "number":
  2977. if (aValue === Infinity) {
  2978. return { type: "Infinity" };
  2979. } else if (aValue === -Infinity) {
  2980. return { type: "-Infinity" };
  2981. } else if (Number.isNaN(aValue)) {
  2982. return { type: "NaN" };
  2983. } else if (1 / aValue === -Infinity) {
  2984. return { type: "-0" };
  2985. }
  2986. return aValue;
  2987. case "undefined":
  2988. // document.all is also "undefined"
  2989. if (aValue === undefined) {
  2990. return { type: "undefined" };
  2991. }
  2992. case "object":
  2993. if (aValue === null) {
  2994. return { type: "null" };
  2995. }
  2996. case "function":
  2997. return { type: "object",
  2998. class: WebConsoleUtils.getObjectClassName(aValue) };
  2999. default:
  3000. console.error("Failed to provide a grip for value of " + typeof value +
  3001. ": " + aValue);
  3002. return null;
  3003. }
  3004. };
  3005. /**
  3006. * Returns a custom formatted property string for a grip.
  3007. *
  3008. * @param any aGrip
  3009. * @see Variable.setGrip
  3010. * @param object aOptions
  3011. * Options:
  3012. * - concise: boolean that tells you want a concisely formatted string.
  3013. * - noStringQuotes: boolean that tells to not quote strings.
  3014. * - noEllipsis: boolean that tells to not add an ellipsis after the
  3015. * initial text of a longString.
  3016. * @return string
  3017. * The formatted property string.
  3018. */
  3019. VariablesView.getString = function (aGrip, aOptions = {}) {
  3020. if (aGrip && typeof aGrip == "object") {
  3021. switch (aGrip.type) {
  3022. case "undefined":
  3023. case "null":
  3024. case "NaN":
  3025. case "Infinity":
  3026. case "-Infinity":
  3027. case "-0":
  3028. return aGrip.type;
  3029. default:
  3030. let stringifier = VariablesView.stringifiers.byType[aGrip.type];
  3031. if (stringifier) {
  3032. let result = stringifier(aGrip, aOptions);
  3033. if (result != null) {
  3034. return result;
  3035. }
  3036. }
  3037. if (aGrip.displayString) {
  3038. return VariablesView.getString(aGrip.displayString, aOptions);
  3039. }
  3040. if (aGrip.type == "object" && aOptions.concise) {
  3041. return aGrip.class;
  3042. }
  3043. return "[" + aGrip.type + " " + aGrip.class + "]";
  3044. }
  3045. }
  3046. switch (typeof aGrip) {
  3047. case "string":
  3048. return VariablesView.stringifiers.byType.string(aGrip, aOptions);
  3049. case "boolean":
  3050. return aGrip ? "true" : "false";
  3051. case "number":
  3052. if (!aGrip && 1 / aGrip === -Infinity) {
  3053. return "-0";
  3054. }
  3055. default:
  3056. return aGrip + "";
  3057. }
  3058. };
  3059. /**
  3060. * The VariablesView stringifiers are used by VariablesView.getString(). These
  3061. * are organized by object type, object class and by object actor preview kind.
  3062. * Some objects share identical ways for previews, for example Arrays, Sets and
  3063. * NodeLists.
  3064. *
  3065. * Any stringifier function must return a string. If null is returned, * then
  3066. * the default stringifier will be used. When invoked, the stringifier is
  3067. * given the same two arguments as those given to VariablesView.getString().
  3068. */
  3069. VariablesView.stringifiers = {};
  3070. VariablesView.stringifiers.byType = {
  3071. string: function (aGrip, {noStringQuotes}) {
  3072. if (noStringQuotes) {
  3073. return aGrip;
  3074. }
  3075. return '"' + aGrip + '"';
  3076. },
  3077. longString: function ({initial}, {noStringQuotes, noEllipsis}) {
  3078. let ellipsis = noEllipsis ? "" : ELLIPSIS;
  3079. if (noStringQuotes) {
  3080. return initial + ellipsis;
  3081. }
  3082. let result = '"' + initial + '"';
  3083. if (!ellipsis) {
  3084. return result;
  3085. }
  3086. return result.substr(0, result.length - 1) + ellipsis + '"';
  3087. },
  3088. object: function (aGrip, aOptions) {
  3089. let {preview} = aGrip;
  3090. let stringifier;
  3091. if (aGrip.class) {
  3092. stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
  3093. }
  3094. if (!stringifier && preview && preview.kind) {
  3095. stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
  3096. }
  3097. if (stringifier) {
  3098. return stringifier(aGrip, aOptions);
  3099. }
  3100. return null;
  3101. },
  3102. symbol: function (aGrip, aOptions) {
  3103. const name = aGrip.name || "";
  3104. return "Symbol(" + name + ")";
  3105. },
  3106. mapEntry: function (aGrip, {concise}) {
  3107. let { preview: { key, value }} = aGrip;
  3108. let keyString = VariablesView.getString(key, {
  3109. concise: true,
  3110. noStringQuotes: true,
  3111. });
  3112. let valueString = VariablesView.getString(value, { concise: true });
  3113. return keyString + " \u2192 " + valueString;
  3114. },
  3115. }; // VariablesView.stringifiers.byType
  3116. VariablesView.stringifiers.byObjectClass = {
  3117. Function: function (aGrip, {concise}) {
  3118. // TODO: Bug 948484 - support arrow functions and ES6 generators
  3119. let name = aGrip.userDisplayName || aGrip.displayName || aGrip.name || "";
  3120. name = VariablesView.getString(name, { noStringQuotes: true });
  3121. // TODO: Bug 948489 - Support functions with destructured parameters and
  3122. // rest parameters
  3123. let params = aGrip.parameterNames || "";
  3124. if (!concise) {
  3125. return "function " + name + "(" + params + ")";
  3126. }
  3127. return (name || "function ") + "(" + params + ")";
  3128. },
  3129. RegExp: function ({displayString}) {
  3130. return VariablesView.getString(displayString, { noStringQuotes: true });
  3131. },
  3132. Date: function ({preview}) {
  3133. if (!preview || !("timestamp" in preview)) {
  3134. return null;
  3135. }
  3136. if (typeof preview.timestamp != "number") {
  3137. return new Date(preview.timestamp).toString(); // invalid date
  3138. }
  3139. return "Date " + new Date(preview.timestamp).toISOString();
  3140. },
  3141. Number: function (aGrip) {
  3142. let {preview} = aGrip;
  3143. if (preview === undefined) {
  3144. return null;
  3145. }
  3146. return aGrip.class + " { " + VariablesView.getString(preview.wrappedValue) +
  3147. " }";
  3148. },
  3149. }; // VariablesView.stringifiers.byObjectClass
  3150. VariablesView.stringifiers.byObjectClass.Boolean =
  3151. VariablesView.stringifiers.byObjectClass.Number;
  3152. VariablesView.stringifiers.byObjectKind = {
  3153. ArrayLike: function (aGrip, {concise}) {
  3154. let {preview} = aGrip;
  3155. if (concise) {
  3156. return aGrip.class + "[" + preview.length + "]";
  3157. }
  3158. if (!preview.items) {
  3159. return null;
  3160. }
  3161. let shown = 0, result = [], lastHole = null;
  3162. for (let item of preview.items) {
  3163. if (item === null) {
  3164. if (lastHole !== null) {
  3165. result[lastHole] += ",";
  3166. } else {
  3167. result.push("");
  3168. }
  3169. lastHole = result.length - 1;
  3170. } else {
  3171. lastHole = null;
  3172. result.push(VariablesView.getString(item, { concise: true }));
  3173. }
  3174. shown++;
  3175. }
  3176. if (shown < preview.length) {
  3177. let n = preview.length - shown;
  3178. result.push(VariablesView.stringifiers._getNMoreString(n));
  3179. } else if (lastHole !== null) {
  3180. // make sure we have the right number of commas...
  3181. result[lastHole] += ",";
  3182. }
  3183. let prefix = aGrip.class == "Array" ? "" : aGrip.class + " ";
  3184. return prefix + "[" + result.join(", ") + "]";
  3185. },
  3186. MapLike: function (aGrip, {concise}) {
  3187. let {preview} = aGrip;
  3188. if (concise || !preview.entries) {
  3189. let size = typeof preview.size == "number" ?
  3190. "[" + preview.size + "]" : "";
  3191. return aGrip.class + size;
  3192. }
  3193. let entries = [];
  3194. for (let [key, value] of preview.entries) {
  3195. let keyString = VariablesView.getString(key, {
  3196. concise: true,
  3197. noStringQuotes: true,
  3198. });
  3199. let valueString = VariablesView.getString(value, { concise: true });
  3200. entries.push(keyString + ": " + valueString);
  3201. }
  3202. if (typeof preview.size == "number" && preview.size > entries.length) {
  3203. let n = preview.size - entries.length;
  3204. entries.push(VariablesView.stringifiers._getNMoreString(n));
  3205. }
  3206. return aGrip.class + " {" + entries.join(", ") + "}";
  3207. },
  3208. ObjectWithText: function (aGrip, {concise}) {
  3209. if (concise) {
  3210. return aGrip.class;
  3211. }
  3212. return aGrip.class + " " + VariablesView.getString(aGrip.preview.text);
  3213. },
  3214. ObjectWithURL: function (aGrip, {concise}) {
  3215. let result = aGrip.class;
  3216. let url = aGrip.preview.url;
  3217. if (!VariablesView.isFalsy({ value: url })) {
  3218. result += ` \u2192 ${getSourceNames(url)[concise ? "short" : "long"]}`;
  3219. }
  3220. return result;
  3221. },
  3222. // Stringifier for any kind of object.
  3223. Object: function (aGrip, {concise}) {
  3224. if (concise) {
  3225. return aGrip.class;
  3226. }
  3227. let {preview} = aGrip;
  3228. let props = [];
  3229. if (aGrip.class == "Promise" && aGrip.promiseState) {
  3230. let { state, value, reason } = aGrip.promiseState;
  3231. props.push("<state>: " + VariablesView.getString(state));
  3232. if (state == "fulfilled") {
  3233. props.push("<value>: " + VariablesView.getString(value, { concise: true }));
  3234. } else if (state == "rejected") {
  3235. props.push("<reason>: " + VariablesView.getString(reason, { concise: true }));
  3236. }
  3237. }
  3238. for (let key of Object.keys(preview.ownProperties || {})) {
  3239. let value = preview.ownProperties[key];
  3240. let valueString = "";
  3241. if (value.get) {
  3242. valueString = "Getter";
  3243. } else if (value.set) {
  3244. valueString = "Setter";
  3245. } else {
  3246. valueString = VariablesView.getString(value.value, { concise: true });
  3247. }
  3248. props.push(key + ": " + valueString);
  3249. }
  3250. for (let key of Object.keys(preview.safeGetterValues || {})) {
  3251. let value = preview.safeGetterValues[key];
  3252. let valueString = VariablesView.getString(value.getterValue,
  3253. { concise: true });
  3254. props.push(key + ": " + valueString);
  3255. }
  3256. if (!props.length) {
  3257. return null;
  3258. }
  3259. if (preview.ownPropertiesLength) {
  3260. let previewLength = Object.keys(preview.ownProperties).length;
  3261. let diff = preview.ownPropertiesLength - previewLength;
  3262. if (diff > 0) {
  3263. props.push(VariablesView.stringifiers._getNMoreString(diff));
  3264. }
  3265. }
  3266. let prefix = aGrip.class != "Object" ? aGrip.class + " " : "";
  3267. return prefix + "{" + props.join(", ") + "}";
  3268. }, // Object
  3269. Error: function (aGrip, {concise}) {
  3270. let {preview} = aGrip;
  3271. let name = VariablesView.getString(preview.name, { noStringQuotes: true });
  3272. if (concise) {
  3273. return name || aGrip.class;
  3274. }
  3275. let msg = name + ": " +
  3276. VariablesView.getString(preview.message, { noStringQuotes: true });
  3277. if (!VariablesView.isFalsy({ value: preview.stack })) {
  3278. msg += "\n" + L10N.getStr("variablesViewErrorStacktrace") +
  3279. "\n" + preview.stack;
  3280. }
  3281. return msg;
  3282. },
  3283. DOMException: function (aGrip, {concise}) {
  3284. let {preview} = aGrip;
  3285. if (concise) {
  3286. return preview.name || aGrip.class;
  3287. }
  3288. let msg = aGrip.class + " [" + preview.name + ": " +
  3289. VariablesView.getString(preview.message) + "\n" +
  3290. "code: " + preview.code + "\n" +
  3291. "nsresult: 0x" + (+preview.result).toString(16);
  3292. if (preview.filename) {
  3293. msg += "\nlocation: " + preview.filename;
  3294. if (preview.lineNumber) {
  3295. msg += ":" + preview.lineNumber;
  3296. }
  3297. }
  3298. return msg + "]";
  3299. },
  3300. DOMEvent: function (aGrip, {concise}) {
  3301. let {preview} = aGrip;
  3302. if (!preview.type) {
  3303. return null;
  3304. }
  3305. if (concise) {
  3306. return aGrip.class + " " + preview.type;
  3307. }
  3308. let result = preview.type;
  3309. if (preview.eventKind == "key" && preview.modifiers &&
  3310. preview.modifiers.length) {
  3311. result += " " + preview.modifiers.join("-");
  3312. }
  3313. let props = [];
  3314. if (preview.target) {
  3315. let target = VariablesView.getString(preview.target, { concise: true });
  3316. props.push("target: " + target);
  3317. }
  3318. for (let prop in preview.properties) {
  3319. let value = preview.properties[prop];
  3320. props.push(prop + ": " + VariablesView.getString(value, { concise: true }));
  3321. }
  3322. return result + " {" + props.join(", ") + "}";
  3323. }, // DOMEvent
  3324. DOMNode: function (aGrip, {concise}) {
  3325. let {preview} = aGrip;
  3326. switch (preview.nodeType) {
  3327. case nodeConstants.DOCUMENT_NODE: {
  3328. let result = aGrip.class;
  3329. if (preview.location) {
  3330. result += ` \u2192 ${getSourceNames(preview.location)[concise ? "short" : "long"]}`;
  3331. }
  3332. return result;
  3333. }
  3334. case nodeConstants.ATTRIBUTE_NODE: {
  3335. let value = VariablesView.getString(preview.value, { noStringQuotes: true });
  3336. return preview.nodeName + '="' + escapeHTML(value) + '"';
  3337. }
  3338. case nodeConstants.TEXT_NODE:
  3339. return preview.nodeName + " " +
  3340. VariablesView.getString(preview.textContent);
  3341. case nodeConstants.COMMENT_NODE: {
  3342. let comment = VariablesView.getString(preview.textContent,
  3343. { noStringQuotes: true });
  3344. return "<!--" + comment + "-->";
  3345. }
  3346. case nodeConstants.DOCUMENT_FRAGMENT_NODE: {
  3347. if (concise || !preview.childNodes) {
  3348. return aGrip.class + "[" + preview.childNodesLength + "]";
  3349. }
  3350. let nodes = [];
  3351. for (let node of preview.childNodes) {
  3352. nodes.push(VariablesView.getString(node));
  3353. }
  3354. if (nodes.length < preview.childNodesLength) {
  3355. let n = preview.childNodesLength - nodes.length;
  3356. nodes.push(VariablesView.stringifiers._getNMoreString(n));
  3357. }
  3358. return aGrip.class + " [" + nodes.join(", ") + "]";
  3359. }
  3360. case nodeConstants.ELEMENT_NODE: {
  3361. let attrs = preview.attributes;
  3362. if (!concise) {
  3363. let n = 0, result = "<" + preview.nodeName;
  3364. for (let name in attrs) {
  3365. let value = VariablesView.getString(attrs[name],
  3366. { noStringQuotes: true });
  3367. result += " " + name + '="' + escapeHTML(value) + '"';
  3368. n++;
  3369. }
  3370. if (preview.attributesLength > n) {
  3371. result += " " + ELLIPSIS;
  3372. }
  3373. return result + ">";
  3374. }
  3375. let result = "<" + preview.nodeName;
  3376. if (attrs.id) {
  3377. result += "#" + attrs.id;
  3378. }
  3379. if (attrs.class) {
  3380. result += "." + attrs.class.trim().replace(/\s+/, ".");
  3381. }
  3382. return result + ">";
  3383. }
  3384. default:
  3385. return null;
  3386. }
  3387. }, // DOMNode
  3388. }; // VariablesView.stringifiers.byObjectKind
  3389. /**
  3390. * Get the "N more…" formatted string, given an N. This is used for displaying
  3391. * how many elements are not displayed in an object preview (eg. an array).
  3392. *
  3393. * @private
  3394. * @param number aNumber
  3395. * @return string
  3396. */
  3397. VariablesView.stringifiers._getNMoreString = function (aNumber) {
  3398. let str = L10N.getStr("variablesViewMoreObjects");
  3399. return PluralForm.get(aNumber, str).replace("#1", aNumber);
  3400. };
  3401. /**
  3402. * Returns a custom class style for a grip.
  3403. *
  3404. * @param any aGrip
  3405. * @see Variable.setGrip
  3406. * @return string
  3407. * The custom class style.
  3408. */
  3409. VariablesView.getClass = function (aGrip) {
  3410. if (aGrip && typeof aGrip == "object") {
  3411. if (aGrip.preview) {
  3412. switch (aGrip.preview.kind) {
  3413. case "DOMNode":
  3414. return "token-domnode";
  3415. }
  3416. }
  3417. switch (aGrip.type) {
  3418. case "undefined":
  3419. return "token-undefined";
  3420. case "null":
  3421. return "token-null";
  3422. case "Infinity":
  3423. case "-Infinity":
  3424. case "NaN":
  3425. case "-0":
  3426. return "token-number";
  3427. case "longString":
  3428. return "token-string";
  3429. }
  3430. }
  3431. switch (typeof aGrip) {
  3432. case "string":
  3433. return "token-string";
  3434. case "boolean":
  3435. return "token-boolean";
  3436. case "number":
  3437. return "token-number";
  3438. default:
  3439. return "token-other";
  3440. }
  3441. };
  3442. /**
  3443. * A monotonically-increasing counter, that guarantees the uniqueness of scope,
  3444. * variables and properties ids.
  3445. *
  3446. * @param string aName
  3447. * An optional string to prefix the id with.
  3448. * @return number
  3449. * A unique id.
  3450. */
  3451. var generateId = (function () {
  3452. let count = 0;
  3453. return function (aName = "") {
  3454. return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count);
  3455. };
  3456. })();
  3457. /**
  3458. * Serialize a string to JSON. The result can be inserted in a string evaluated by `eval`.
  3459. *
  3460. * @param string aString
  3461. * The string to be escaped. If undefined, the function returns the empty string.
  3462. * @return string
  3463. */
  3464. function escapeString(aString) {
  3465. return JSON.stringify(aString) || "";
  3466. }
  3467. /**
  3468. * Escape some HTML special characters. We do not need full HTML serialization
  3469. * here, we just want to make strings safe to display in HTML attributes, for
  3470. * the stringifiers.
  3471. *
  3472. * @param string aString
  3473. * @return string
  3474. */
  3475. function escapeHTML(aString) {
  3476. return aString.replace(/&/g, "&amp;")
  3477. .replace(/"/g, "&quot;")
  3478. .replace(/</g, "&lt;")
  3479. .replace(/>/g, "&gt;");
  3480. }
  3481. /**
  3482. * An Editable encapsulates the UI of an edit box that overlays a label,
  3483. * allowing the user to edit the value.
  3484. *
  3485. * @param Variable aVariable
  3486. * The Variable or Property to make editable.
  3487. * @param object aOptions
  3488. * - onSave
  3489. * The callback to call with the value when editing is complete.
  3490. * - onCleanup
  3491. * The callback to call when the editable is removed for any reason.
  3492. */
  3493. function Editable(aVariable, aOptions) {
  3494. this._variable = aVariable;
  3495. this._onSave = aOptions.onSave;
  3496. this._onCleanup = aOptions.onCleanup;
  3497. }
  3498. Editable.create = function (aVariable, aOptions, aEvent) {
  3499. let editable = new this(aVariable, aOptions);
  3500. editable.activate(aEvent);
  3501. return editable;
  3502. };
  3503. Editable.prototype = {
  3504. /**
  3505. * The class name for targeting this Editable type's label element. Overridden
  3506. * by inheriting classes.
  3507. */
  3508. className: null,
  3509. /**
  3510. * Boolean indicating whether this Editable should activate. Overridden by
  3511. * inheriting classes.
  3512. */
  3513. shouldActivate: null,
  3514. /**
  3515. * The label element for this Editable. Overridden by inheriting classes.
  3516. */
  3517. label: null,
  3518. /**
  3519. * Activate this editable by replacing the input box it overlays and
  3520. * initialize the handlers.
  3521. *
  3522. * @param Event e [optional]
  3523. * Optionally, the Event object that was used to activate the Editable.
  3524. */
  3525. activate: function (e) {
  3526. if (!this.shouldActivate) {
  3527. this._onCleanup && this._onCleanup();
  3528. return;
  3529. }
  3530. let { label } = this;
  3531. let initialString = label.getAttribute("value");
  3532. if (e) {
  3533. e.preventDefault();
  3534. e.stopPropagation();
  3535. }
  3536. // Create a texbox input element which will be shown in the current
  3537. // element's specified label location.
  3538. let input = this._input = this._variable.document.createElement("textbox");
  3539. input.className = "plain " + this.className;
  3540. input.setAttribute("value", initialString);
  3541. input.setAttribute("flex", "1");
  3542. // Replace the specified label with a textbox input element.
  3543. label.parentNode.replaceChild(input, label);
  3544. this._variable._variablesView.boxObject.ensureElementIsVisible(input);
  3545. input.select();
  3546. // When the value is a string (displayed as "value"), then we probably want
  3547. // to change it to another string in the textbox, so to avoid typing the ""
  3548. // again, tackle with the selection bounds just a bit.
  3549. if (initialString.match(/^".+"$/)) {
  3550. input.selectionEnd--;
  3551. input.selectionStart++;
  3552. }
  3553. this._onKeypress = this._onKeypress.bind(this);
  3554. this._onBlur = this._onBlur.bind(this);
  3555. input.addEventListener("keypress", this._onKeypress);
  3556. input.addEventListener("blur", this._onBlur);
  3557. this._prevExpandable = this._variable.twisty;
  3558. this._prevExpanded = this._variable.expanded;
  3559. this._variable.collapse();
  3560. this._variable.hideArrow();
  3561. this._variable.locked = true;
  3562. this._variable.editing = true;
  3563. },
  3564. /**
  3565. * Remove the input box and restore the Variable or Property to its previous
  3566. * state.
  3567. */
  3568. deactivate: function () {
  3569. this._input.removeEventListener("keypress", this._onKeypress);
  3570. this._input.removeEventListener("blur", this.deactivate);
  3571. this._input.parentNode.replaceChild(this.label, this._input);
  3572. this._input = null;
  3573. let { boxObject } = this._variable._variablesView;
  3574. boxObject.scrollBy(-this._variable._target, 0);
  3575. this._variable.locked = false;
  3576. this._variable.twisty = this._prevExpandable;
  3577. this._variable.expanded = this._prevExpanded;
  3578. this._variable.editing = false;
  3579. this._onCleanup && this._onCleanup();
  3580. },
  3581. /**
  3582. * Save the current value and deactivate the Editable.
  3583. */
  3584. _save: function () {
  3585. let initial = this.label.getAttribute("value");
  3586. let current = this._input.value.trim();
  3587. this.deactivate();
  3588. if (initial != current) {
  3589. this._onSave(current);
  3590. }
  3591. },
  3592. /**
  3593. * Called when tab is pressed, allowing subclasses to link different
  3594. * behavior to tabbing if desired.
  3595. */
  3596. _next: function () {
  3597. this._save();
  3598. },
  3599. /**
  3600. * Called when escape is pressed, indicating a cancelling of editing without
  3601. * saving.
  3602. */
  3603. _reset: function () {
  3604. this.deactivate();
  3605. this._variable.focus();
  3606. },
  3607. /**
  3608. * Event handler for when the input loses focus.
  3609. */
  3610. _onBlur: function () {
  3611. this.deactivate();
  3612. },
  3613. /**
  3614. * Event handler for when the input receives a key press.
  3615. */
  3616. _onKeypress: function (e) {
  3617. e.stopPropagation();
  3618. switch (e.keyCode) {
  3619. case KeyCodes.DOM_VK_TAB:
  3620. this._next();
  3621. break;
  3622. case KeyCodes.DOM_VK_RETURN:
  3623. this._save();
  3624. break;
  3625. case KeyCodes.DOM_VK_ESCAPE:
  3626. this._reset();
  3627. break;
  3628. }
  3629. },
  3630. };
  3631. /**
  3632. * An Editable specific to editing the name of a Variable or Property.
  3633. */
  3634. function EditableName(aVariable, aOptions) {
  3635. Editable.call(this, aVariable, aOptions);
  3636. }
  3637. EditableName.create = Editable.create;
  3638. EditableName.prototype = Heritage.extend(Editable.prototype, {
  3639. className: "element-name-input",
  3640. get label() {
  3641. return this._variable._name;
  3642. },
  3643. get shouldActivate() {
  3644. return !!this._variable.ownerView.switch;
  3645. },
  3646. });
  3647. /**
  3648. * An Editable specific to editing the value of a Variable or Property.
  3649. */
  3650. function EditableValue(aVariable, aOptions) {
  3651. Editable.call(this, aVariable, aOptions);
  3652. }
  3653. EditableValue.create = Editable.create;
  3654. EditableValue.prototype = Heritage.extend(Editable.prototype, {
  3655. className: "element-value-input",
  3656. get label() {
  3657. return this._variable._valueLabel;
  3658. },
  3659. get shouldActivate() {
  3660. return !!this._variable.ownerView.eval;
  3661. },
  3662. });
  3663. /**
  3664. * An Editable specific to editing the key and value of a new property.
  3665. */
  3666. function EditableNameAndValue(aVariable, aOptions) {
  3667. EditableName.call(this, aVariable, aOptions);
  3668. }
  3669. EditableNameAndValue.create = Editable.create;
  3670. EditableNameAndValue.prototype = Heritage.extend(EditableName.prototype, {
  3671. _reset: function (e) {
  3672. // Hide the Variable or Property if the user presses escape.
  3673. this._variable.remove();
  3674. this.deactivate();
  3675. },
  3676. _next: function (e) {
  3677. // Override _next so as to set both key and value at the same time.
  3678. let key = this._input.value;
  3679. this.label.setAttribute("value", key);
  3680. let valueEditable = EditableValue.create(this._variable, {
  3681. onSave: aValue => {
  3682. this._onSave([key, aValue]);
  3683. }
  3684. });
  3685. valueEditable._reset = () => {
  3686. this._variable.remove();
  3687. valueEditable.deactivate();
  3688. };
  3689. },
  3690. _save: function (e) {
  3691. // Both _save and _next activate the value edit box.
  3692. this._next(e);
  3693. }
  3694. });