debugger-view.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
  6. const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
  7. const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
  8. const STACK_FRAMES_SCROLL_DELAY = 100; // ms
  9. const BREAKPOINT_SMALL_WINDOW_WIDTH = 850; // px
  10. const RESULTS_PANEL_POPUP_POSITION = "before_end";
  11. const RESULTS_PANEL_MAX_RESULTS = 10;
  12. const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms
  13. const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
  14. const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
  15. const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
  16. const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
  17. const SEARCH_GLOBAL_FLAG = "!";
  18. const SEARCH_FUNCTION_FLAG = "@";
  19. const SEARCH_TOKEN_FLAG = "#";
  20. const SEARCH_LINE_FLAG = ":";
  21. const SEARCH_VARIABLE_FLAG = "*";
  22. const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
  23. const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
  24. const RESIZE_REFRESH_RATE = 50; // ms
  25. const EventListenersView = require("./content/views/event-listeners-view");
  26. const SourcesView = require("./content/views/sources-view");
  27. var actions = Object.assign(
  28. {},
  29. require("./content/globalActions"),
  30. require("./content/actions/breakpoints"),
  31. require("./content/actions/sources"),
  32. require("./content/actions/event-listeners")
  33. );
  34. var queries = require("./content/queries");
  35. var constants = require("./content/constants");
  36. /**
  37. * Object defining the debugger view components.
  38. */
  39. var DebuggerView = {
  40. /**
  41. * This is attached so tests can change it without needing to load an
  42. * actual large file in automation
  43. */
  44. LARGE_FILE_SIZE: 1048576, // 1 MB in bytes
  45. /**
  46. * Initializes the debugger view.
  47. *
  48. * @return object
  49. * A promise that is resolved when the view finishes initializing.
  50. */
  51. initialize: function (isWorker) {
  52. if (this._startup) {
  53. return this._startup;
  54. }
  55. const deferred = promise.defer();
  56. this._startup = deferred.promise;
  57. this._initializePanes();
  58. this._initializeEditor(deferred.resolve);
  59. this.Toolbar.initialize();
  60. this.Options.initialize();
  61. this.Filtering.initialize();
  62. this.StackFrames.initialize();
  63. this.StackFramesClassicList.initialize();
  64. this.Workers.initialize();
  65. this.Sources.initialize(isWorker);
  66. this.VariableBubble.initialize();
  67. this.WatchExpressions.initialize();
  68. this.EventListeners.initialize();
  69. this.GlobalSearch.initialize();
  70. this._initializeVariablesView();
  71. this._editorSource = {};
  72. this._editorDocuments = {};
  73. this.editor.on("cursorActivity", this.Sources._onEditorCursorActivity);
  74. this.controller = DebuggerController;
  75. const getState = this.controller.getState;
  76. onReducerEvents(this.controller, {
  77. "source-text-loaded": this.renderSourceText,
  78. "source-selected": this.renderSourceText,
  79. "blackboxed": this.renderBlackBoxed,
  80. "prettyprinted": this.renderPrettyPrinted,
  81. "breakpoint-added": this.addEditorBreakpoint,
  82. "breakpoint-enabled": this.addEditorBreakpoint,
  83. "breakpoint-disabled": this.removeEditorBreakpoint,
  84. "breakpoint-removed": this.removeEditorBreakpoint,
  85. "breakpoint-condition-updated": this.renderEditorBreakpointCondition,
  86. "breakpoint-moved": ({ breakpoint, prevLocation }) => {
  87. const selectedSource = queries.getSelectedSource(getState());
  88. const { location } = breakpoint;
  89. if (selectedSource &&
  90. selectedSource.actor === location.actor) {
  91. this.editor.moveBreakpoint(prevLocation.line - 1,
  92. location.line - 1);
  93. }
  94. }
  95. }, this);
  96. return deferred.promise;
  97. },
  98. /**
  99. * Destroys the debugger view.
  100. *
  101. * @return object
  102. * A promise that is resolved when the view finishes destroying.
  103. */
  104. destroy: function () {
  105. if (this._hasShutdown) {
  106. return;
  107. }
  108. this._hasShutdown = true;
  109. window.removeEventListener("resize", this._onResize, false);
  110. this.editor.off("cursorActivity", this.Sources._onEditorCursorActivity);
  111. this.Toolbar.destroy();
  112. this.Options.destroy();
  113. this.Filtering.destroy();
  114. this.StackFrames.destroy();
  115. this.StackFramesClassicList.destroy();
  116. this.Sources.destroy();
  117. this.VariableBubble.destroy();
  118. this.WatchExpressions.destroy();
  119. this.EventListeners.destroy();
  120. this.GlobalSearch.destroy();
  121. this._destroyPanes();
  122. this.editor.destroy();
  123. this.editor = null;
  124. this.controller.dispatch(actions.removeAllBreakpoints());
  125. },
  126. /**
  127. * Initializes the UI for all the displayed panes.
  128. */
  129. _initializePanes: function () {
  130. dumpn("Initializing the DebuggerView panes");
  131. this._body = document.getElementById("body");
  132. this._editorDeck = document.getElementById("editor-deck");
  133. this._workersAndSourcesPane = document.getElementById("workers-and-sources-pane");
  134. this._instrumentsPane = document.getElementById("instruments-pane");
  135. this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
  136. this.showEditor = this.showEditor.bind(this);
  137. this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
  138. this.showProgressBar = this.showProgressBar.bind(this);
  139. this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
  140. this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
  141. this._collapsePaneString = L10N.getStr("collapsePanes");
  142. this._expandPaneString = L10N.getStr("expandPanes");
  143. this._workersAndSourcesPane.setAttribute("width", Prefs.workersAndSourcesWidth);
  144. this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
  145. this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup });
  146. this.updateLayoutMode();
  147. this._onResize = this._onResize.bind(this);
  148. window.addEventListener("resize", this._onResize, false);
  149. },
  150. /**
  151. * Destroys the UI for all the displayed panes.
  152. */
  153. _destroyPanes: function () {
  154. dumpn("Destroying the DebuggerView panes");
  155. if (gHostType != "side") {
  156. Prefs.workersAndSourcesWidth = this._workersAndSourcesPane.getAttribute("width");
  157. Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
  158. }
  159. this._workersAndSourcesPane = null;
  160. this._instrumentsPane = null;
  161. this._instrumentsPaneToggleButton = null;
  162. },
  163. /**
  164. * Initializes the VariablesView instance and attaches a controller.
  165. */
  166. _initializeVariablesView: function () {
  167. this.Variables = new VariablesView(document.getElementById("variables"), {
  168. searchPlaceholder: L10N.getStr("emptyVariablesFilterText"),
  169. emptyText: L10N.getStr("emptyVariablesText"),
  170. onlyEnumVisible: Prefs.variablesOnlyEnumVisible,
  171. searchEnabled: Prefs.variablesSearchboxVisible,
  172. eval: (variable, value) => {
  173. let string = variable.evaluationMacro(variable, value);
  174. DebuggerController.StackFrames.evaluate(string);
  175. },
  176. lazyEmpty: true
  177. });
  178. // Attach the current toolbox to the VView so it can link DOMNodes to
  179. // the inspector/highlighter
  180. this.Variables.toolbox = DebuggerController._toolbox;
  181. // Attach a controller that handles interfacing with the debugger protocol.
  182. VariablesViewController.attach(this.Variables, {
  183. getEnvironmentClient: aObject => gThreadClient.environment(aObject),
  184. getObjectClient: aObject => {
  185. return gThreadClient.pauseGrip(aObject);
  186. }
  187. });
  188. // Relay events from the VariablesView.
  189. this.Variables.on("fetched", (aEvent, aType) => {
  190. switch (aType) {
  191. case "scopes":
  192. window.emit(EVENTS.FETCHED_SCOPES);
  193. break;
  194. case "variables":
  195. window.emit(EVENTS.FETCHED_VARIABLES);
  196. break;
  197. case "properties":
  198. window.emit(EVENTS.FETCHED_PROPERTIES);
  199. break;
  200. }
  201. });
  202. },
  203. /**
  204. * Initializes the Editor instance.
  205. *
  206. * @param function aCallback
  207. * Called after the editor finishes initializing.
  208. */
  209. _initializeEditor: function (callback) {
  210. dumpn("Initializing the DebuggerView editor");
  211. let extraKeys = {};
  212. bindKey("_doTokenSearch", "tokenSearchKey");
  213. bindKey("_doGlobalSearch", "globalSearchKey", { alt: true });
  214. bindKey("_doFunctionSearch", "functionSearchKey");
  215. extraKeys[Editor.keyFor("jumpToLine")] = false;
  216. extraKeys["Esc"] = false;
  217. function bindKey(func, key, modifiers = {}) {
  218. key = document.getElementById(key).getAttribute("key");
  219. let shortcut = Editor.accel(key, modifiers);
  220. extraKeys[shortcut] = () => DebuggerView.Filtering[func]();
  221. }
  222. let gutters = ["breakpoints"];
  223. this.editor = new Editor({
  224. mode: Editor.modes.text,
  225. readOnly: true,
  226. lineNumbers: true,
  227. showAnnotationRuler: true,
  228. gutters: gutters,
  229. extraKeys: extraKeys,
  230. contextMenu: "sourceEditorContextMenu",
  231. enableCodeFolding: false
  232. });
  233. this.editor.appendTo(document.getElementById("editor")).then(() => {
  234. this.editor.extend(DebuggerEditor);
  235. this._loadingText = L10N.getStr("loadingText");
  236. callback();
  237. });
  238. this.editor.on("gutterClick", (ev, line, button) => {
  239. // A right-click shouldn't do anything but keep track of where
  240. // it was clicked.
  241. if (button == 2) {
  242. this.clickedLine = line;
  243. }
  244. else {
  245. const source = queries.getSelectedSource(this.controller.getState());
  246. if (source) {
  247. const location = { actor: source.actor, line: line + 1 };
  248. if (this.editor.hasBreakpoint(line)) {
  249. this.controller.dispatch(actions.removeBreakpoint(location));
  250. } else {
  251. this.controller.dispatch(actions.addBreakpoint(location));
  252. }
  253. }
  254. }
  255. });
  256. this.editor.on("cursorActivity", () => {
  257. this.clickedLine = null;
  258. });
  259. },
  260. updateEditorBreakpoints: function (source) {
  261. const breakpoints = queries.getBreakpoints(this.controller.getState());
  262. const sources = queries.getSources(this.controller.getState());
  263. for (let bp of breakpoints) {
  264. if (sources[bp.location.actor] && !bp.disabled) {
  265. this.addEditorBreakpoint(bp);
  266. }
  267. else {
  268. this.removeEditorBreakpoint(bp);
  269. }
  270. }
  271. },
  272. addEditorBreakpoint: function (breakpoint) {
  273. const { location, condition } = breakpoint;
  274. const source = queries.getSelectedSource(this.controller.getState());
  275. if (source &&
  276. source.actor === location.actor &&
  277. !breakpoint.disabled) {
  278. this.editor.addBreakpoint(location.line - 1, condition);
  279. }
  280. },
  281. removeEditorBreakpoint: function (breakpoint) {
  282. const { location } = breakpoint;
  283. const source = queries.getSelectedSource(this.controller.getState());
  284. if (source && source.actor === location.actor) {
  285. this.editor.removeBreakpoint(location.line - 1);
  286. this.editor.removeBreakpointCondition(location.line - 1);
  287. }
  288. },
  289. renderEditorBreakpointCondition: function (breakpoint) {
  290. const { location, condition, disabled } = breakpoint;
  291. const source = queries.getSelectedSource(this.controller.getState());
  292. if (source && source.actor === location.actor && !disabled) {
  293. if (condition) {
  294. this.editor.setBreakpointCondition(location.line - 1);
  295. } else {
  296. this.editor.removeBreakpointCondition(location.line - 1);
  297. }
  298. }
  299. },
  300. /**
  301. * Display the source editor.
  302. */
  303. showEditor: function () {
  304. this._editorDeck.selectedIndex = 0;
  305. },
  306. /**
  307. * Display the black box message.
  308. */
  309. showBlackBoxMessage: function () {
  310. this._editorDeck.selectedIndex = 1;
  311. },
  312. /**
  313. * Display the progress bar.
  314. */
  315. showProgressBar: function () {
  316. this._editorDeck.selectedIndex = 2;
  317. },
  318. /**
  319. * Sets the currently displayed text contents in the source editor.
  320. * This resets the mode and undo stack.
  321. *
  322. * @param string documentKey
  323. * Key to get the correct editor document
  324. *
  325. * @param string aTextContent
  326. * The source text content.
  327. *
  328. * @param boolean shouldUpdateText
  329. Forces a text and mode reset
  330. */
  331. _setEditorText: function (documentKey, aTextContent = "", shouldUpdateText = false) {
  332. const isNew = this._setEditorDocument(documentKey);
  333. this.editor.clearDebugLocation();
  334. this.editor.clearHistory();
  335. this.editor.removeBreakpoints();
  336. // Only set editor's text and mode if it is a new document
  337. if (isNew || shouldUpdateText) {
  338. this.editor.setMode(Editor.modes.text);
  339. this.editor.setText(aTextContent);
  340. }
  341. },
  342. /**
  343. * Sets the proper editor mode (JS or HTML) according to the specified
  344. * content type, or by determining the type from the url or text content.
  345. *
  346. * @param string aUrl
  347. * The source url.
  348. * @param string aContentType [optional]
  349. * The source content type.
  350. * @param string aTextContent [optional]
  351. * The source text content.
  352. */
  353. _setEditorMode: function (aUrl, aContentType = "", aTextContent = "") {
  354. // Use JS mode for files with .js and .jsm extensions.
  355. if (SourceUtils.isJavaScript(aUrl, aContentType)) {
  356. return void this.editor.setMode(Editor.modes.js);
  357. }
  358. if (aContentType === "text/wasm") {
  359. return void this.editor.setMode(Editor.modes.text);
  360. }
  361. // Use HTML mode for files in which the first non whitespace character is
  362. // <, regardless of extension.
  363. if (aTextContent.match(/^\s*</)) {
  364. return void this.editor.setMode(Editor.modes.html);
  365. }
  366. // Unknown language, use text.
  367. this.editor.setMode(Editor.modes.text);
  368. },
  369. /**
  370. * Sets the editor's displayed document.
  371. * If there isn't a document for the source, create one
  372. *
  373. * @param string key - key used to access the editor document cache
  374. *
  375. * @return boolean isNew - was the document just created
  376. */
  377. _setEditorDocument: function (key) {
  378. let isNew;
  379. if (!this._editorDocuments[key]) {
  380. isNew = true;
  381. this._editorDocuments[key] = this.editor.createDocument();
  382. } else {
  383. isNew = false;
  384. }
  385. const doc = this._editorDocuments[key];
  386. this.editor.replaceDocument(doc);
  387. return isNew;
  388. },
  389. renderBlackBoxed: function (source) {
  390. this._renderSourceText(
  391. source,
  392. queries.getSourceText(this.controller.getState(), source.actor)
  393. );
  394. },
  395. renderPrettyPrinted: function (source) {
  396. this._renderSourceText(
  397. source,
  398. queries.getSourceText(this.controller.getState(), source.actor)
  399. );
  400. },
  401. renderSourceText: function (source) {
  402. this._renderSourceText(
  403. source,
  404. queries.getSourceText(this.controller.getState(), source.actor),
  405. queries.getSelectedSourceOpts(this.controller.getState())
  406. );
  407. },
  408. _renderSourceText: function (source, textInfo, opts = {}) {
  409. const selectedSource = queries.getSelectedSource(this.controller.getState());
  410. // Exit early if we're attempting to render an unselected source
  411. if (!selectedSource || selectedSource.actor !== source.actor) {
  412. return;
  413. }
  414. if (source.isBlackBoxed) {
  415. this.showBlackBoxMessage();
  416. setTimeout(() => {
  417. window.emit(EVENTS.SOURCE_SHOWN, source);
  418. }, 0);
  419. return;
  420. }
  421. else {
  422. this.showEditor();
  423. }
  424. if (textInfo.loading) {
  425. // TODO: bug 1228866, we need to update `_editorSource` here but
  426. // still make the editor be updated when the full text comes
  427. // through somehow.
  428. this._setEditorText("loading", L10N.getStr("loadingText"));
  429. return;
  430. }
  431. else if (textInfo.error) {
  432. let msg = L10N.getFormatStr("errorLoadingText2", textInfo.error);
  433. this._setEditorText("error", msg);
  434. console.error(new Error(msg));
  435. dumpn(msg);
  436. this.showEditor();
  437. window.emit(EVENTS.SOURCE_ERROR_SHOWN, source);
  438. return;
  439. }
  440. // If the line is not specified, default to the current frame's position,
  441. // if available and the frame's url corresponds to the requested url.
  442. if (!("line" in opts)) {
  443. let cachedFrames = DebuggerController.activeThread.cachedFrames;
  444. let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
  445. let frame = cachedFrames[currentDepth];
  446. if (frame && frame.source.actor == source.actor) {
  447. opts.line = frame.where.line;
  448. }
  449. }
  450. if (this._editorSource.actor === source.actor &&
  451. this._editorSource.prettyPrinted === source.isPrettyPrinted &&
  452. this._editorSource.blackboxed === source.isBlackBoxed) {
  453. this.updateEditorPosition(opts);
  454. return;
  455. }
  456. let { text, contentType } = textInfo;
  457. let shouldUpdateText = this._editorSource.prettyPrinted != source.isPrettyPrinted;
  458. this._setEditorText(source.actor, text, shouldUpdateText);
  459. this._editorSource.actor = source.actor;
  460. this._editorSource.prettyPrinted = source.isPrettyPrinted;
  461. this._editorSource.blackboxed = source.isBlackBoxed;
  462. this._editorSource.prettyPrinted = source.isPrettyPrinted;
  463. this._setEditorMode(source.url, contentType, text);
  464. this.updateEditorBreakpoints(source);
  465. setTimeout(() => {
  466. window.emit(EVENTS.SOURCE_SHOWN, source);
  467. }, 0);
  468. this.updateEditorPosition(opts);
  469. },
  470. updateEditorPosition: function (opts) {
  471. let line = opts.line || 0;
  472. // Line numbers in the source editor should start from 1. If
  473. // invalid or not specified, then don't do anything.
  474. if (line < 1) {
  475. window.emit(EVENTS.EDITOR_LOCATION_SET);
  476. return;
  477. }
  478. if (opts.charOffset) {
  479. line += this.editor.getPosition(opts.charOffset).line;
  480. }
  481. if (opts.lineOffset) {
  482. line += opts.lineOffset;
  483. }
  484. if (opts.moveCursor) {
  485. let location = { line: line - 1, ch: opts.columnOffset || 0 };
  486. this.editor.setCursor(location);
  487. }
  488. if (!opts.noDebug) {
  489. this.editor.setDebugLocation(line - 1);
  490. }
  491. window.emit(EVENTS.EDITOR_LOCATION_SET);
  492. },
  493. /**
  494. * Update the source editor's current caret and debug location based on
  495. * a requested url and line.
  496. *
  497. * @param string aActor
  498. * The target actor id.
  499. * @param number aLine [optional]
  500. * The target line in the source.
  501. * @param object aFlags [optional]
  502. * Additional options for showing the source. Supported options:
  503. * - charOffset: character offset for the caret or debug location
  504. * - lineOffset: line offset for the caret or debug location
  505. * - columnOffset: column offset for the caret or debug location
  506. * - noCaret: don't set the caret location at the specified line
  507. * - noDebug: don't set the debug location at the specified line
  508. * - align: string specifying whether to align the specified line
  509. * at the "top", "center" or "bottom" of the editor
  510. * - force: boolean forcing all text to be reshown in the editor
  511. * @return object
  512. * A promise that is resolved after the source text has been set.
  513. */
  514. setEditorLocation: function (aActor, aLine, aFlags = {}) {
  515. // Avoid trying to set a source for a url that isn't known yet.
  516. if (!this.Sources.containsValue(aActor)) {
  517. throw new Error("Unknown source for the specified URL.");
  518. }
  519. let sourceItem = this.Sources.getItemByValue(aActor);
  520. let source = sourceItem.attachment.source;
  521. // Make sure the requested source client is shown in the editor,
  522. // then update the source editor's caret position and debug
  523. // location.
  524. this.controller.dispatch(actions.selectSource(source, {
  525. line: aLine,
  526. charOffset: aFlags.charOffset,
  527. lineOffset: aFlags.lineOffset,
  528. columnOffset: aFlags.columnOffset,
  529. moveCursor: !aFlags.noCaret,
  530. noDebug: aFlags.noDebug,
  531. forceUpdate: aFlags.force
  532. }));
  533. },
  534. /**
  535. * Gets the visibility state of the instruments pane.
  536. * @return boolean
  537. */
  538. get instrumentsPaneHidden() {
  539. return this._instrumentsPane.classList.contains("pane-collapsed");
  540. },
  541. /**
  542. * Gets the currently selected tab in the instruments pane.
  543. * @return string
  544. */
  545. get instrumentsPaneTab() {
  546. return this._instrumentsPane.selectedTab.id;
  547. },
  548. /**
  549. * Sets the instruments pane hidden or visible.
  550. *
  551. * @param object aFlags
  552. * An object containing some of the following properties:
  553. * - visible: true if the pane should be shown, false to hide
  554. * - animated: true to display an animation on toggle
  555. * - delayed: true to wait a few cycles before toggle
  556. * - callback: a function to invoke when the toggle finishes
  557. * @param number aTabIndex [optional]
  558. * The index of the intended selected tab in the details pane.
  559. */
  560. toggleInstrumentsPane: function (aFlags, aTabIndex) {
  561. let pane = this._instrumentsPane;
  562. let button = this._instrumentsPaneToggleButton;
  563. ViewHelpers.togglePane(aFlags, pane);
  564. if (aFlags.visible) {
  565. button.classList.remove("pane-collapsed");
  566. button.setAttribute("tooltiptext", this._collapsePaneString);
  567. } else {
  568. button.classList.add("pane-collapsed");
  569. button.setAttribute("tooltiptext", this._expandPaneString);
  570. }
  571. if (aTabIndex !== undefined) {
  572. pane.selectedIndex = aTabIndex;
  573. }
  574. },
  575. /**
  576. * Sets the instruments pane visible after a short period of time.
  577. *
  578. * @param function aCallback
  579. * A function to invoke when the toggle finishes.
  580. */
  581. showInstrumentsPane: function (aCallback) {
  582. DebuggerView.toggleInstrumentsPane({
  583. visible: true,
  584. animated: true,
  585. delayed: true,
  586. callback: aCallback
  587. }, 0);
  588. },
  589. /**
  590. * Handles a tab selection event on the instruments pane.
  591. */
  592. _onInstrumentsPaneTabSelect: function () {
  593. if (this._instrumentsPane.selectedTab.id == "events-tab") {
  594. this.controller.dispatch(actions.fetchEventListeners());
  595. }
  596. },
  597. /**
  598. * Handles a host change event issued by the parent toolbox.
  599. *
  600. * @param string aType
  601. * The host type, either "bottom", "side" or "window".
  602. */
  603. handleHostChanged: function (hostType) {
  604. this._hostType = hostType;
  605. this.updateLayoutMode();
  606. },
  607. /**
  608. * Resize handler for this container's window.
  609. */
  610. _onResize: function (evt) {
  611. // Allow requests to settle down first.
  612. setNamedTimeout(
  613. "resize-events", RESIZE_REFRESH_RATE, () => this.updateLayoutMode());
  614. },
  615. /**
  616. * Set the layout to "vertical" or "horizontal" depending on the host type.
  617. */
  618. updateLayoutMode: function () {
  619. if (this._isSmallWindowHost() || this._hostType == "side") {
  620. this._setLayoutMode("vertical");
  621. } else {
  622. this._setLayoutMode("horizontal");
  623. }
  624. },
  625. /**
  626. * Check if the current host is in window mode and is
  627. * too small for horizontal layout
  628. */
  629. _isSmallWindowHost: function () {
  630. if (this._hostType != "window") {
  631. return false;
  632. }
  633. return window.outerWidth <= BREAKPOINT_SMALL_WINDOW_WIDTH;
  634. },
  635. /**
  636. * Enter the provided layoutMode. Do nothing if the layout is the same as the current one.
  637. * @param {String} layoutMode new layout ("vertical" or "horizontal")
  638. */
  639. _setLayoutMode: function (layoutMode) {
  640. if (this._body.getAttribute("layout") == layoutMode) {
  641. return;
  642. }
  643. if (layoutMode == "vertical") {
  644. this._enterVerticalLayout();
  645. } else {
  646. this._enterHorizontalLayout();
  647. }
  648. this._body.setAttribute("layout", layoutMode);
  649. window.emit(EVENTS.LAYOUT_CHANGED, layoutMode);
  650. },
  651. /**
  652. * Switches the debugger widgets to a vertical layout.
  653. */
  654. _enterVerticalLayout: function () {
  655. let vertContainer = document.getElementById("vertical-layout-panes-container");
  656. // Move the soruces and instruments panes in a different container.
  657. let splitter = document.getElementById("sources-and-instruments-splitter");
  658. vertContainer.insertBefore(this._workersAndSourcesPane, splitter);
  659. vertContainer.appendChild(this._instrumentsPane);
  660. // Make sure the vertical layout container's height doesn't repeatedly
  661. // grow or shrink based on the displayed sources, variables etc.
  662. vertContainer.setAttribute("height",
  663. vertContainer.getBoundingClientRect().height);
  664. },
  665. /**
  666. * Switches the debugger widgets to a horizontal layout.
  667. */
  668. _enterHorizontalLayout: function () {
  669. let normContainer = document.getElementById("debugger-widgets");
  670. let editorPane = document.getElementById("editor-and-instruments-pane");
  671. // The sources and instruments pane need to be inserted at their
  672. // previous locations in their normal container.
  673. let splitter = document.getElementById("sources-and-editor-splitter");
  674. normContainer.insertBefore(this._workersAndSourcesPane, splitter);
  675. editorPane.appendChild(this._instrumentsPane);
  676. // Revert to the preferred sources and instruments widths, because
  677. // they flexed in the vertical layout.
  678. this._workersAndSourcesPane.setAttribute("width", Prefs.workersAndSourcesWidth);
  679. this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
  680. },
  681. /**
  682. * Handles any initialization on a tab navigation event issued by the client.
  683. */
  684. handleTabNavigation: function () {
  685. dumpn("Handling tab navigation in the DebuggerView");
  686. this.Filtering.clearSearch();
  687. this.GlobalSearch.clearView();
  688. this.StackFrames.empty();
  689. this.Sources.empty();
  690. this.Variables.empty();
  691. this.EventListeners.empty();
  692. if (this.editor) {
  693. this.editor.setMode(Editor.modes.text);
  694. this.editor.setText("");
  695. this.editor.clearHistory();
  696. this._editorSource = {};
  697. this._editorDocuments = {};
  698. }
  699. },
  700. Toolbar: null,
  701. Options: null,
  702. Filtering: null,
  703. GlobalSearch: null,
  704. StackFrames: null,
  705. Sources: null,
  706. Variables: null,
  707. VariableBubble: null,
  708. WatchExpressions: null,
  709. EventListeners: null,
  710. editor: null,
  711. _loadingText: "",
  712. _body: null,
  713. _editorDeck: null,
  714. _workersAndSourcesPane: null,
  715. _instrumentsPane: null,
  716. _instrumentsPaneToggleButton: null,
  717. _collapsePaneString: "",
  718. _expandPaneString: ""
  719. };
  720. /**
  721. * A custom items container, used for displaying views like the
  722. * FilteredSources, FilteredFunctions etc., inheriting the generic WidgetMethods.
  723. */
  724. function ResultsPanelContainer() {
  725. }
  726. ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, {
  727. /**
  728. * Sets the anchor node for this container panel.
  729. * @param nsIDOMNode aNode
  730. */
  731. set anchor(aNode) {
  732. this._anchor = aNode;
  733. // If the anchor node is not null, create a panel to attach to the anchor
  734. // when showing the popup.
  735. if (aNode) {
  736. if (!this._panel) {
  737. this._panel = document.createElement("panel");
  738. this._panel.id = "results-panel";
  739. this._panel.setAttribute("level", "top");
  740. this._panel.setAttribute("noautofocus", "true");
  741. this._panel.setAttribute("consumeoutsideclicks", "false");
  742. document.documentElement.appendChild(this._panel);
  743. }
  744. if (!this.widget) {
  745. this.widget = new SimpleListWidget(this._panel);
  746. this.autoFocusOnFirstItem = false;
  747. this.autoFocusOnSelection = false;
  748. this.maintainSelectionVisible = false;
  749. }
  750. }
  751. // Cleanup the anchor and remove the previously created panel.
  752. else {
  753. this._panel.remove();
  754. this._panel = null;
  755. this.widget = null;
  756. }
  757. },
  758. /**
  759. * Gets the anchor node for this container panel.
  760. * @return nsIDOMNode
  761. */
  762. get anchor() {
  763. return this._anchor;
  764. },
  765. /**
  766. * Sets the container panel hidden or visible. It's hidden by default.
  767. * @param boolean aFlag
  768. */
  769. set hidden(aFlag) {
  770. if (aFlag) {
  771. this._panel.hidden = true;
  772. this._panel.hidePopup();
  773. } else {
  774. this._panel.hidden = false;
  775. this._panel.openPopup(this._anchor, this.position, this.left, this.top);
  776. }
  777. },
  778. /**
  779. * Gets this container's visibility state.
  780. * @return boolean
  781. */
  782. get hidden() {
  783. return this._panel.state == "closed" ||
  784. this._panel.state == "hiding";
  785. },
  786. /**
  787. * Removes all items from this container and hides it.
  788. */
  789. clearView: function () {
  790. this.hidden = true;
  791. this.empty();
  792. },
  793. /**
  794. * Selects the next found item in this container.
  795. * Does not change the currently focused node.
  796. */
  797. selectNext: function () {
  798. let nextIndex = this.selectedIndex + 1;
  799. if (nextIndex >= this.itemCount) {
  800. nextIndex = 0;
  801. }
  802. this.selectedItem = this.getItemAtIndex(nextIndex);
  803. },
  804. /**
  805. * Selects the previously found item in this container.
  806. * Does not change the currently focused node.
  807. */
  808. selectPrev: function () {
  809. let prevIndex = this.selectedIndex - 1;
  810. if (prevIndex < 0) {
  811. prevIndex = this.itemCount - 1;
  812. }
  813. this.selectedItem = this.getItemAtIndex(prevIndex);
  814. },
  815. /**
  816. * Customization function for creating an item's UI.
  817. *
  818. * @param string aLabel
  819. * The item's label string.
  820. * @param string aBeforeLabel
  821. * An optional string shown before the label.
  822. * @param string aBelowLabel
  823. * An optional string shown underneath the label.
  824. */
  825. _createItemView: function (aLabel, aBelowLabel, aBeforeLabel) {
  826. let container = document.createElement("vbox");
  827. container.className = "results-panel-item";
  828. let firstRowLabels = document.createElement("hbox");
  829. let secondRowLabels = document.createElement("hbox");
  830. if (aBeforeLabel) {
  831. let beforeLabelNode = document.createElement("label");
  832. beforeLabelNode.className = "plain results-panel-item-label-before";
  833. beforeLabelNode.setAttribute("value", aBeforeLabel);
  834. firstRowLabels.appendChild(beforeLabelNode);
  835. }
  836. let labelNode = document.createElement("label");
  837. labelNode.className = "plain results-panel-item-label";
  838. labelNode.setAttribute("value", aLabel);
  839. firstRowLabels.appendChild(labelNode);
  840. if (aBelowLabel) {
  841. let belowLabelNode = document.createElement("label");
  842. belowLabelNode.className = "plain results-panel-item-label-below";
  843. belowLabelNode.setAttribute("value", aBelowLabel);
  844. secondRowLabels.appendChild(belowLabelNode);
  845. }
  846. container.appendChild(firstRowLabels);
  847. container.appendChild(secondRowLabels);
  848. return container;
  849. },
  850. _anchor: null,
  851. _panel: null,
  852. position: RESULTS_PANEL_POPUP_POSITION,
  853. left: 0,
  854. top: 0
  855. });
  856. DebuggerView.EventListeners = new EventListenersView(DebuggerController);
  857. DebuggerView.Sources = new SourcesView(DebuggerController, DebuggerView);