DOMBreakpointsSidebarPane.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. /*
  2. * Copyright (C) 2011 Google Inc. 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 Google Inc. 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.NativeBreakpointsSidebarPane}
  33. */
  34. WebInspector.DOMBreakpointsSidebarPane = function()
  35. {
  36. WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("DOM Breakpoints"));
  37. this._breakpointElements = {};
  38. this._breakpointTypes = {
  39. SubtreeModified: "subtree-modified",
  40. AttributeModified: "attribute-modified",
  41. NodeRemoved: "node-removed"
  42. };
  43. this._breakpointTypeLabels = {};
  44. this._breakpointTypeLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString("Subtree Modified");
  45. this._breakpointTypeLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString("Attribute Modified");
  46. this._breakpointTypeLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString("Node Removed");
  47. this._contextMenuLabels = {};
  48. this._contextMenuLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Subtree modifications" : "Subtree Modifications");
  49. this._contextMenuLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Attributes modifications" : "Attributes Modifications");
  50. this._contextMenuLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Node removal" : "Node Removal");
  51. WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this);
  52. WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this);
  53. }
  54. WebInspector.DOMBreakpointsSidebarPane.prototype = {
  55. _inspectedURLChanged: function(event)
  56. {
  57. this._breakpointElements = {};
  58. this._reset();
  59. var url = event.data;
  60. this._inspectedURL = url.removeURLFragment();
  61. },
  62. populateNodeContextMenu: function(node, contextMenu)
  63. {
  64. var nodeBreakpoints = {};
  65. for (var id in this._breakpointElements) {
  66. var element = this._breakpointElements[id];
  67. if (element._node === node)
  68. nodeBreakpoints[element._type] = true;
  69. }
  70. function toggleBreakpoint(type)
  71. {
  72. if (!nodeBreakpoints[type])
  73. this._setBreakpoint(node, type, true);
  74. else
  75. this._removeBreakpoint(node, type);
  76. this._saveBreakpoints();
  77. }
  78. var breakPointSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Break on..."));
  79. for (var key in this._breakpointTypes) {
  80. var type = this._breakpointTypes[key];
  81. var label = this._contextMenuLabels[type];
  82. breakPointSubMenu.appendCheckboxItem(label, toggleBreakpoint.bind(this, type), nodeBreakpoints[type]);
  83. }
  84. },
  85. createBreakpointHitStatusMessage: function(auxData, callback)
  86. {
  87. if (auxData.type === this._breakpointTypes.SubtreeModified) {
  88. var targetNodeObject = WebInspector.RemoteObject.fromPayload(auxData["targetNode"]);
  89. function didPushNodeToFrontend(targetNodeId)
  90. {
  91. if (targetNodeId)
  92. targetNodeObject.release();
  93. this._doCreateBreakpointHitStatusMessage(auxData, targetNodeId, callback);
  94. }
  95. targetNodeObject.pushNodeToFrontend(didPushNodeToFrontend.bind(this));
  96. } else
  97. this._doCreateBreakpointHitStatusMessage(auxData, null, callback);
  98. },
  99. _doCreateBreakpointHitStatusMessage: function (auxData, targetNodeId, callback)
  100. {
  101. var message;
  102. var typeLabel = this._breakpointTypeLabels[auxData.type];
  103. var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeById(auxData.nodeId);
  104. var substitutions = [typeLabel, linkifiedNode];
  105. var targetNode = "";
  106. if (targetNodeId)
  107. targetNode = WebInspector.DOMPresentationUtils.linkifyNodeById(targetNodeId);
  108. if (auxData.type === this._breakpointTypes.SubtreeModified) {
  109. if (auxData.insertion) {
  110. if (targetNodeId !== auxData.nodeId) {
  111. message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s.";
  112. substitutions.push(targetNode);
  113. } else
  114. message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node.";
  115. } else {
  116. message = "Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed.";
  117. substitutions.push(targetNode);
  118. }
  119. } else
  120. message = "Paused on a \"%s\" breakpoint set on %s.";
  121. var element = document.createElement("span");
  122. var formatters = {
  123. s: function(substitution)
  124. {
  125. return substitution;
  126. }
  127. };
  128. function append(a, b)
  129. {
  130. if (typeof b === "string")
  131. b = document.createTextNode(b);
  132. element.appendChild(b);
  133. }
  134. WebInspector.formatLocalized(message, substitutions, formatters, "", append);
  135. callback(element);
  136. },
  137. _nodeRemoved: function(event)
  138. {
  139. var node = event.data.node;
  140. this._removeBreakpointsForNode(event.data.node);
  141. var children = node.children();
  142. if (!children)
  143. return;
  144. for (var i = 0; i < children.length; ++i)
  145. this._removeBreakpointsForNode(children[i]);
  146. this._saveBreakpoints();
  147. },
  148. _removeBreakpointsForNode: function(node)
  149. {
  150. for (var id in this._breakpointElements) {
  151. var element = this._breakpointElements[id];
  152. if (element._node === node)
  153. this._removeBreakpoint(element._node, element._type);
  154. }
  155. },
  156. _setBreakpoint: function(node, type, enabled)
  157. {
  158. var breakpointId = this._createBreakpointId(node.id, type);
  159. if (breakpointId in this._breakpointElements)
  160. return;
  161. var element = document.createElement("li");
  162. element._node = node;
  163. element._type = type;
  164. element.addEventListener("contextmenu", this._contextMenu.bind(this, node, type), true);
  165. var checkboxElement = document.createElement("input");
  166. checkboxElement.className = "checkbox-elem";
  167. checkboxElement.type = "checkbox";
  168. checkboxElement.checked = enabled;
  169. checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, node, type), false);
  170. element._checkboxElement = checkboxElement;
  171. element.appendChild(checkboxElement);
  172. var labelElement = document.createElement("span");
  173. element.appendChild(labelElement);
  174. var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeById(node.id);
  175. linkifiedNode.addStyleClass("monospace");
  176. labelElement.appendChild(linkifiedNode);
  177. var description = document.createElement("div");
  178. description.className = "source-text";
  179. description.textContent = this._breakpointTypeLabels[type];
  180. labelElement.appendChild(description);
  181. var currentElement = this.listElement.firstChild;
  182. while (currentElement) {
  183. if (currentElement._type && currentElement._type < element._type)
  184. break;
  185. currentElement = currentElement.nextSibling;
  186. }
  187. this._addListElement(element, currentElement);
  188. this._breakpointElements[breakpointId] = element;
  189. if (enabled)
  190. DOMDebuggerAgent.setDOMBreakpoint(node.id, type);
  191. },
  192. _removeAllBreakpoints: function()
  193. {
  194. for (var id in this._breakpointElements) {
  195. var element = this._breakpointElements[id];
  196. this._removeBreakpoint(element._node, element._type);
  197. }
  198. this._saveBreakpoints();
  199. },
  200. _removeBreakpoint: function(node, type)
  201. {
  202. var breakpointId = this._createBreakpointId(node.id, type);
  203. var element = this._breakpointElements[breakpointId];
  204. if (!element)
  205. return;
  206. this._removeListElement(element);
  207. delete this._breakpointElements[breakpointId];
  208. if (element._checkboxElement.checked)
  209. DOMDebuggerAgent.removeDOMBreakpoint(node.id, type);
  210. },
  211. _contextMenu: function(node, type, event)
  212. {
  213. var contextMenu = new WebInspector.ContextMenu(event);
  214. function removeBreakpoint()
  215. {
  216. this._removeBreakpoint(node, type);
  217. this._saveBreakpoints();
  218. }
  219. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), removeBreakpoint.bind(this));
  220. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove all DOM breakpoints" : "Remove All DOM Breakpoints"), this._removeAllBreakpoints.bind(this));
  221. contextMenu.show();
  222. },
  223. _checkboxClicked: function(node, type, event)
  224. {
  225. if (event.target.checked)
  226. DOMDebuggerAgent.setDOMBreakpoint(node.id, type);
  227. else
  228. DOMDebuggerAgent.removeDOMBreakpoint(node.id, type);
  229. this._saveBreakpoints();
  230. },
  231. highlightBreakpoint: function(auxData)
  232. {
  233. var breakpointId = this._createBreakpointId(auxData.nodeId, auxData.type);
  234. var element = this._breakpointElements[breakpointId];
  235. if (!element)
  236. return;
  237. this.expand();
  238. element.addStyleClass("breakpoint-hit");
  239. this._highlightedElement = element;
  240. },
  241. clearBreakpointHighlight: function()
  242. {
  243. if (this._highlightedElement) {
  244. this._highlightedElement.removeStyleClass("breakpoint-hit");
  245. delete this._highlightedElement;
  246. }
  247. },
  248. _createBreakpointId: function(nodeId, type)
  249. {
  250. return nodeId + ":" + type;
  251. },
  252. _saveBreakpoints: function()
  253. {
  254. var breakpoints = [];
  255. var storedBreakpoints = WebInspector.settings.domBreakpoints.get();
  256. for (var i = 0; i < storedBreakpoints.length; ++i) {
  257. var breakpoint = storedBreakpoints[i];
  258. if (breakpoint.url !== this._inspectedURL)
  259. breakpoints.push(breakpoint);
  260. }
  261. for (var id in this._breakpointElements) {
  262. var element = this._breakpointElements[id];
  263. breakpoints.push({ url: this._inspectedURL, path: element._node.path(), type: element._type, enabled: element._checkboxElement.checked });
  264. }
  265. WebInspector.settings.domBreakpoints.set(breakpoints);
  266. },
  267. restoreBreakpoints: function()
  268. {
  269. var pathToBreakpoints = {};
  270. /**
  271. * @param {string} path
  272. * @param {?DOMAgent.NodeId} nodeId
  273. */
  274. function didPushNodeByPathToFrontend(path, nodeId)
  275. {
  276. var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : null;
  277. if (!node)
  278. return;
  279. var breakpoints = pathToBreakpoints[path];
  280. for (var i = 0; i < breakpoints.length; ++i)
  281. this._setBreakpoint(node, breakpoints[i].type, breakpoints[i].enabled);
  282. }
  283. var breakpoints = WebInspector.settings.domBreakpoints.get();
  284. for (var i = 0; i < breakpoints.length; ++i) {
  285. var breakpoint = breakpoints[i];
  286. if (breakpoint.url !== this._inspectedURL)
  287. continue;
  288. var path = breakpoint.path;
  289. if (!pathToBreakpoints[path]) {
  290. pathToBreakpoints[path] = [];
  291. WebInspector.domAgent.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path));
  292. }
  293. pathToBreakpoints[path].push(breakpoint);
  294. }
  295. },
  296. /**
  297. * @param {WebInspector.Panel} panel
  298. */
  299. createProxy: function(panel)
  300. {
  301. var proxy = new WebInspector.DOMBreakpointsSidebarPane.Proxy(this, panel);
  302. if (!this._proxies)
  303. this._proxies = [];
  304. this._proxies.push(proxy);
  305. return proxy;
  306. },
  307. onContentReady: function()
  308. {
  309. for (var i = 0; i != this._proxies.length; i++)
  310. this._proxies[i].onContentReady();
  311. },
  312. __proto__: WebInspector.NativeBreakpointsSidebarPane.prototype
  313. }
  314. /**
  315. * @constructor
  316. * @extends {WebInspector.SidebarPane}
  317. * @param {WebInspector.DOMBreakpointsSidebarPane} pane
  318. * @param {WebInspector.Panel} panel
  319. */
  320. WebInspector.DOMBreakpointsSidebarPane.Proxy = function(pane, panel)
  321. {
  322. WebInspector.View._assert(!pane.titleElement.firstChild, "Cannot create proxy for a sidebar pane with a toolbar");
  323. WebInspector.SidebarPane.call(this, pane.title());
  324. this.registerRequiredCSS("breakpointsList.css");
  325. this._wrappedPane = pane;
  326. this._panel = panel;
  327. this.bodyElement.remove();
  328. this.bodyElement = this._wrappedPane.bodyElement;
  329. }
  330. WebInspector.DOMBreakpointsSidebarPane.Proxy.prototype = {
  331. expand: function()
  332. {
  333. this._wrappedPane.expand();
  334. },
  335. onContentReady: function()
  336. {
  337. if (this._panel.isShowing())
  338. this._reattachBody();
  339. WebInspector.SidebarPane.prototype.onContentReady.call(this);
  340. },
  341. wasShown: function()
  342. {
  343. WebInspector.SidebarPane.prototype.wasShown.call(this);
  344. this._reattachBody();
  345. },
  346. _reattachBody: function()
  347. {
  348. if (this.bodyElement.parentNode !== this.element)
  349. this.element.appendChild(this.bodyElement);
  350. },
  351. __proto__: WebInspector.SidebarPane.prototype
  352. }
  353. /**
  354. * @type {?WebInspector.DOMBreakpointsSidebarPane}
  355. */
  356. WebInspector.domBreakpointsSidebarPane = null;