WatchExpressionsSidebarPane.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /*
  2. * Copyright (C) IBM Corp. 2009 All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of IBM Corp. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @constructor
  32. * @extends {WebInspector.SidebarPane}
  33. */
  34. WebInspector.WatchExpressionsSidebarPane = function()
  35. {
  36. WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions"));
  37. this.section = new WebInspector.WatchExpressionsSection();
  38. this.bodyElement.appendChild(this.section.element);
  39. var refreshButton = document.createElement("button");
  40. refreshButton.className = "pane-title-button refresh";
  41. refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
  42. refreshButton.title = WebInspector.UIString("Refresh");
  43. this.titleElement.appendChild(refreshButton);
  44. var addButton = document.createElement("button");
  45. addButton.className = "pane-title-button add";
  46. addButton.addEventListener("click", this._addButtonClicked.bind(this), false);
  47. this.titleElement.appendChild(addButton);
  48. addButton.title = WebInspector.UIString("Add watch expression");
  49. this._requiresUpdate = true;
  50. }
  51. WebInspector.WatchExpressionsSidebarPane.prototype = {
  52. wasShown: function()
  53. {
  54. this._refreshExpressionsIfNeeded();
  55. },
  56. reset: function()
  57. {
  58. this.refreshExpressions();
  59. },
  60. refreshExpressions: function()
  61. {
  62. this._requiresUpdate = true;
  63. this._refreshExpressionsIfNeeded();
  64. },
  65. addExpression: function(expression)
  66. {
  67. this.section.addExpression(expression);
  68. this.expand();
  69. },
  70. _refreshExpressionsIfNeeded: function()
  71. {
  72. if (this._requiresUpdate && this.isShowing()) {
  73. this.section.update();
  74. delete this._requiresUpdate;
  75. } else
  76. this._requiresUpdate = true;
  77. },
  78. _addButtonClicked: function(event)
  79. {
  80. event.consume();
  81. this.expand();
  82. this.section.addNewExpressionAndEdit();
  83. },
  84. _refreshButtonClicked: function(event)
  85. {
  86. event.consume();
  87. this.refreshExpressions();
  88. },
  89. __proto__: WebInspector.SidebarPane.prototype
  90. }
  91. /**
  92. * @constructor
  93. * @extends {WebInspector.ObjectPropertiesSection}
  94. */
  95. WebInspector.WatchExpressionsSection = function()
  96. {
  97. this._watchObjectGroupId = "watch-group";
  98. WebInspector.ObjectPropertiesSection.call(this, WebInspector.RemoteObject.fromPrimitiveValue(""));
  99. this.treeElementConstructor = WebInspector.WatchedPropertyTreeElement;
  100. this._expandedExpressions = {};
  101. this._expandedProperties = {};
  102. this.emptyElement = document.createElement("div");
  103. this.emptyElement.className = "info";
  104. this.emptyElement.textContent = WebInspector.UIString("No Watch Expressions");
  105. this.watchExpressions = WebInspector.settings.watchExpressions.get();
  106. this.headerElement.className = "hidden";
  107. this.editable = true;
  108. this.expanded = true;
  109. this.propertiesElement.addStyleClass("watch-expressions");
  110. this.element.addEventListener("mousemove", this._mouseMove.bind(this), true);
  111. this.element.addEventListener("mouseout", this._mouseOut.bind(this), true);
  112. this.element.addEventListener("dblclick", this._sectionDoubleClick.bind(this), false);
  113. this.emptyElement.addEventListener("contextmenu", this._emptyElementContextMenu.bind(this), false);
  114. }
  115. WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0";
  116. WebInspector.WatchExpressionsSection.prototype = {
  117. update: function(e)
  118. {
  119. if (e)
  120. e.consume();
  121. function appendResult(expression, watchIndex, result, wasThrown)
  122. {
  123. if (!result)
  124. return;
  125. var property = new WebInspector.RemoteObjectProperty(expression, result);
  126. property.watchIndex = watchIndex;
  127. property.wasThrown = wasThrown;
  128. // To clarify what's going on here:
  129. // In the outer function, we calculate the number of properties
  130. // that we're going to be updating, and set that in the
  131. // propertyCount variable.
  132. // In this function, we test to see when we are processing the
  133. // last property, and then call the superclass's updateProperties()
  134. // method to get all the properties refreshed at once.
  135. properties.push(property);
  136. if (properties.length == propertyCount) {
  137. this.updateProperties(properties, [], WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties);
  138. // check to see if we just added a new watch expression,
  139. // which will always be the last property
  140. if (this._newExpressionAdded) {
  141. delete this._newExpressionAdded;
  142. var treeElement = this.findAddedTreeElement();
  143. if (treeElement)
  144. treeElement.startEditing();
  145. }
  146. // Force displaying delete button for hovered element.
  147. if (this._lastMouseMovePageY)
  148. this._updateHoveredElement(this._lastMouseMovePageY);
  149. }
  150. }
  151. // TODO: pass exact injected script id.
  152. RuntimeAgent.releaseObjectGroup(this._watchObjectGroupId)
  153. var properties = [];
  154. // Count the properties, so we known when to call this.updateProperties()
  155. // in appendResult()
  156. var propertyCount = 0;
  157. for (var i = 0; i < this.watchExpressions.length; ++i) {
  158. if (!this.watchExpressions[i])
  159. continue;
  160. ++propertyCount;
  161. }
  162. // Now process all the expressions, since we have the actual count,
  163. // which is checked in the appendResult inner function.
  164. for (var i = 0; i < this.watchExpressions.length; ++i) {
  165. var expression = this.watchExpressions[i];
  166. if (!expression)
  167. continue;
  168. WebInspector.runtimeModel.evaluate(expression, this._watchObjectGroupId, false, true, false, false, appendResult.bind(this, expression, i));
  169. }
  170. if (!propertyCount) {
  171. if (!this.emptyElement.parentNode)
  172. this.element.appendChild(this.emptyElement);
  173. } else {
  174. if (this.emptyElement.parentNode)
  175. this.element.removeChild(this.emptyElement);
  176. }
  177. // note this is setting the expansion of the tree, not the section;
  178. // with no expressions, and expanded tree, we get some extra vertical
  179. // white space
  180. this.expanded = (propertyCount != 0);
  181. },
  182. addExpression: function(expression)
  183. {
  184. this.watchExpressions.push(expression);
  185. this.saveExpressions();
  186. this.update();
  187. },
  188. addNewExpressionAndEdit: function()
  189. {
  190. this._newExpressionAdded = true;
  191. this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression);
  192. this.update();
  193. },
  194. _sectionDoubleClick: function(event)
  195. {
  196. if (event.target !== this.element && event.target !== this.propertiesElement && event.target !== this.emptyElement)
  197. return;
  198. event.consume();
  199. this.addNewExpressionAndEdit();
  200. },
  201. updateExpression: function(element, value)
  202. {
  203. if (value === null) {
  204. var index = element.property.watchIndex;
  205. this.watchExpressions.splice(index, 1);
  206. }
  207. else
  208. this.watchExpressions[element.property.watchIndex] = value;
  209. this.saveExpressions();
  210. this.update();
  211. },
  212. _deleteAllExpressions: function()
  213. {
  214. this.watchExpressions = [];
  215. this.saveExpressions();
  216. this.update();
  217. },
  218. findAddedTreeElement: function()
  219. {
  220. var children = this.propertiesTreeOutline.children;
  221. for (var i = 0; i < children.length; ++i) {
  222. if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
  223. return children[i];
  224. }
  225. },
  226. saveExpressions: function()
  227. {
  228. var toSave = [];
  229. for (var i = 0; i < this.watchExpressions.length; i++)
  230. if (this.watchExpressions[i])
  231. toSave.push(this.watchExpressions[i]);
  232. WebInspector.settings.watchExpressions.set(toSave);
  233. return toSave.length;
  234. },
  235. _mouseMove: function(e)
  236. {
  237. if (this.propertiesElement.firstChild)
  238. this._updateHoveredElement(e.pageY);
  239. },
  240. _mouseOut: function()
  241. {
  242. if (this._hoveredElement) {
  243. this._hoveredElement.removeStyleClass("hovered");
  244. delete this._hoveredElement;
  245. }
  246. delete this._lastMouseMovePageY;
  247. },
  248. _updateHoveredElement: function(pageY)
  249. {
  250. var candidateElement = this.propertiesElement.firstChild;
  251. while (true) {
  252. var next = candidateElement.nextSibling;
  253. while (next && !next.clientHeight)
  254. next = next.nextSibling;
  255. if (!next || next.totalOffsetTop() > pageY)
  256. break;
  257. candidateElement = next;
  258. }
  259. if (this._hoveredElement !== candidateElement) {
  260. if (this._hoveredElement)
  261. this._hoveredElement.removeStyleClass("hovered");
  262. if (candidateElement)
  263. candidateElement.addStyleClass("hovered");
  264. this._hoveredElement = candidateElement;
  265. }
  266. this._lastMouseMovePageY = pageY;
  267. },
  268. _emptyElementContextMenu: function(event)
  269. {
  270. var contextMenu = new WebInspector.ContextMenu(event);
  271. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.addNewExpressionAndEdit.bind(this));
  272. contextMenu.show();
  273. },
  274. __proto__: WebInspector.ObjectPropertiesSection.prototype
  275. }
  276. WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB)
  277. {
  278. if (propertyA.watchIndex == propertyB.watchIndex)
  279. return 0;
  280. else if (propertyA.watchIndex < propertyB.watchIndex)
  281. return -1;
  282. else
  283. return 1;
  284. }
  285. /**
  286. * @constructor
  287. * @extends {WebInspector.ObjectPropertyTreeElement}
  288. * @param {WebInspector.RemoteObjectProperty} property
  289. */
  290. WebInspector.WatchExpressionTreeElement = function(property)
  291. {
  292. WebInspector.ObjectPropertyTreeElement.call(this, property);
  293. }
  294. WebInspector.WatchExpressionTreeElement.prototype = {
  295. onexpand: function()
  296. {
  297. WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
  298. this.treeOutline.section._expandedExpressions[this._expression()] = true;
  299. },
  300. oncollapse: function()
  301. {
  302. WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
  303. delete this.treeOutline.section._expandedExpressions[this._expression()];
  304. },
  305. onattach: function()
  306. {
  307. WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
  308. if (this.treeOutline.section._expandedExpressions[this._expression()])
  309. this.expanded = true;
  310. },
  311. _expression: function()
  312. {
  313. return this.property.name;
  314. },
  315. update: function()
  316. {
  317. WebInspector.ObjectPropertyTreeElement.prototype.update.call(this);
  318. if (this.property.wasThrown) {
  319. this.valueElement.textContent = WebInspector.UIString("<not available>");
  320. this.listItemElement.addStyleClass("dimmed");
  321. } else
  322. this.listItemElement.removeStyleClass("dimmed");
  323. var deleteButton = document.createElement("input");
  324. deleteButton.type = "button";
  325. deleteButton.title = WebInspector.UIString("Delete watch expression.");
  326. deleteButton.addStyleClass("enabled-button");
  327. deleteButton.addStyleClass("delete-button");
  328. deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);
  329. this.listItemElement.addEventListener("contextmenu", this._contextMenu.bind(this), false);
  330. this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild);
  331. },
  332. /**
  333. * @param {WebInspector.ContextMenu} contextMenu
  334. * @override
  335. */
  336. populateContextMenu: function(contextMenu)
  337. {
  338. if (!this.isEditing()) {
  339. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.treeOutline.section.addNewExpressionAndEdit.bind(this.treeOutline.section));
  340. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete watch expression" : "Delete Watch Expression"), this._deleteButtonClicked.bind(this));
  341. }
  342. if (this.treeOutline.section.watchExpressions.length > 1)
  343. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete all watch expressions" : "Delete All Watch Expressions"), this._deleteAllButtonClicked.bind(this));
  344. },
  345. _contextMenu: function(event)
  346. {
  347. var contextMenu = new WebInspector.ContextMenu(event);
  348. this.populateContextMenu(contextMenu);
  349. contextMenu.show();
  350. },
  351. _deleteAllButtonClicked: function()
  352. {
  353. this.treeOutline.section._deleteAllExpressions();
  354. },
  355. _deleteButtonClicked: function()
  356. {
  357. this.treeOutline.section.updateExpression(this, null);
  358. },
  359. renderPromptAsBlock: function()
  360. {
  361. return true;
  362. },
  363. /**
  364. * @param {Event=} event
  365. */
  366. elementAndValueToEdit: function(event)
  367. {
  368. return [this.nameElement, this.property.name.trim()];
  369. },
  370. editingCancelled: function(element, context)
  371. {
  372. if (!context.elementToEdit.textContent)
  373. this.treeOutline.section.updateExpression(this, null);
  374. WebInspector.ObjectPropertyTreeElement.prototype.editingCancelled.call(this, element, context);
  375. },
  376. applyExpression: function(expression, updateInterface)
  377. {
  378. expression = expression.trim();
  379. if (!expression)
  380. expression = null;
  381. this.property.name = expression;
  382. this.treeOutline.section.updateExpression(this, expression);
  383. },
  384. __proto__: WebInspector.ObjectPropertyTreeElement.prototype
  385. }
  386. /**
  387. * @constructor
  388. * @extends {WebInspector.ObjectPropertyTreeElement}
  389. * @param {WebInspector.RemoteObjectProperty} property
  390. */
  391. WebInspector.WatchedPropertyTreeElement = function(property)
  392. {
  393. WebInspector.ObjectPropertyTreeElement.call(this, property);
  394. }
  395. WebInspector.WatchedPropertyTreeElement.prototype = {
  396. onattach: function()
  397. {
  398. WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
  399. if (this.hasChildren && this.propertyPath() in this.treeOutline.section._expandedProperties)
  400. this.expand();
  401. },
  402. onexpand: function()
  403. {
  404. WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
  405. this.treeOutline.section._expandedProperties[this.propertyPath()] = true;
  406. },
  407. oncollapse: function()
  408. {
  409. WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
  410. delete this.treeOutline.section._expandedProperties[this.propertyPath()];
  411. },
  412. __proto__: WebInspector.ObjectPropertyTreeElement.prototype
  413. }