6103c009ab741acb072c53a2d2fc6d5523b688d7
[WebKit-https.git] / Source / WebCore / inspector / front-end / BreakpointsSidebarPane.js
1 /*
2  * Copyright (C) 2008 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.BreakpointsSidebarPane = function(title)
27 {
28     WebInspector.SidebarPane.call(this, title);
29
30     this.listElement = document.createElement("ol");
31     this.listElement.className = "breakpoint-list";
32
33     this.emptyElement = document.createElement("div");
34     this.emptyElement.className = "info";
35     this.emptyElement.textContent = WebInspector.UIString("No Breakpoints");
36
37     this.bodyElement.appendChild(this.emptyElement);
38
39     WebInspector.breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.ProjectChanged, this._projectChanged, this);
40 }
41
42 WebInspector.BreakpointsSidebarPane.prototype = {
43     addBreakpointItem: function(breakpointItem)
44     {
45         var element = breakpointItem.element;
46         element._breakpointItem = breakpointItem;
47
48         breakpointItem.addEventListener("breakpoint-hit", this.expand, this);
49         breakpointItem.addEventListener("removed", this._removeListElement.bind(this, element), this);
50
51         var currentElement = this.listElement.firstChild;
52         while (currentElement) {
53             if (currentElement._breakpointItem && currentElement._breakpointItem.compareTo(element._breakpointItem) > 0)
54                 break;
55             currentElement = currentElement.nextSibling;
56         }
57         this._addListElement(element, currentElement);
58
59         if (breakpointItem.click) {
60             element.addStyleClass("cursor-pointer");
61             element.addEventListener("click", breakpointItem.click.bind(breakpointItem), false);
62         }
63         element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, breakpointItem), true);
64     },
65
66     _contextMenuEventFired: function(breakpointItem, event)
67     {
68         var contextMenu = new WebInspector.ContextMenu();
69         contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), breakpointItem.remove.bind(breakpointItem));
70         contextMenu.show(event);
71     },
72
73     _addListElement: function(element, beforeElement)
74     {
75         if (beforeElement)
76             this.listElement.insertBefore(element, beforeElement);
77         else {
78             if (!this.listElement.firstChild) {
79                 this.bodyElement.removeChild(this.emptyElement);
80                 this.bodyElement.appendChild(this.listElement);
81             }
82             this.listElement.appendChild(element);
83         }
84     },
85
86     _removeListElement: function(element)
87     {
88         this.listElement.removeChild(element);
89         if (!this.listElement.firstChild) {
90             this.bodyElement.removeChild(this.listElement);
91             this.bodyElement.appendChild(this.emptyElement);
92         }
93     },
94
95     _projectChanged: function()
96     {
97         this.listElement.removeChildren();
98         if (this.listElement.parentElement) {
99             this.bodyElement.removeChild(this.listElement);
100             this.bodyElement.appendChild(this.emptyElement);
101         }
102     }
103 }
104
105 WebInspector.BreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
106
107 WebInspector.XHRBreakpointsSidebarPane = function()
108 {
109     WebInspector.BreakpointsSidebarPane.call(this, WebInspector.UIString("XHR Breakpoints"));
110
111     function addButtonClicked(event)
112     {
113         event.stopPropagation();
114         this._startEditingBreakpoint(null);
115     }
116
117     var addButton = document.createElement("button");
118     addButton.className = "add";
119     addButton.addEventListener("click", addButtonClicked.bind(this), false);
120     this.titleElement.appendChild(addButton);
121 }
122
123 WebInspector.XHRBreakpointsSidebarPane.prototype = {
124     addBreakpointItem: function(breakpointItem)
125     {
126         WebInspector.BreakpointsSidebarPane.prototype.addBreakpointItem.call(this, breakpointItem);
127         breakpointItem._labelElement.addEventListener("dblclick", this._startEditingBreakpoint.bind(this, breakpointItem), false);
128     },
129
130     _startEditingBreakpoint: function(breakpointItem)
131     {
132         if (this._editingBreakpoint)
133             return;
134         this._editingBreakpoint = true;
135
136         if (!this.expanded)
137             this.expanded = true;
138
139         var inputElement = document.createElement("span");
140         inputElement.className = "breakpoint-condition editing";
141         if (breakpointItem) {
142             breakpointItem.populateEditElement(inputElement);
143             this.listElement.insertBefore(inputElement, breakpointItem.element);
144             breakpointItem.element.addStyleClass("hidden");
145         } else
146             this._addListElement(inputElement, this.listElement.firstChild);
147
148         var commitHandler = this._hideEditBreakpointDialog.bind(this, inputElement, true, breakpointItem);
149         var cancelHandler = this._hideEditBreakpointDialog.bind(this, inputElement, false, breakpointItem);
150         WebInspector.startEditing(inputElement, {
151             commitHandler: commitHandler,
152             cancelHandler: cancelHandler
153         });
154     },
155
156     _hideEditBreakpointDialog: function(inputElement, accept, breakpointItem)
157     {
158         this._removeListElement(inputElement);
159         this._editingBreakpoint = false;
160         if (accept) {
161             if (breakpointItem)
162                 breakpointItem.remove();
163             WebInspector.breakpointManager.createXHRBreakpoint(inputElement.textContent.toLowerCase());
164         } else if (breakpointItem)
165             breakpointItem.element.removeStyleClass("hidden");
166     }
167 }
168
169 WebInspector.XHRBreakpointsSidebarPane.prototype.__proto__ = WebInspector.BreakpointsSidebarPane.prototype;
170
171 WebInspector.BreakpointItem = function(breakpoint)
172 {
173     this._breakpoint = breakpoint;
174
175     this._element = document.createElement("li");
176
177     var checkboxElement = document.createElement("input");
178     checkboxElement.className = "checkbox-elem";
179     checkboxElement.type = "checkbox";
180     checkboxElement.checked = this._breakpoint.enabled;
181     checkboxElement.addEventListener("click", this._checkboxClicked.bind(this), false);
182     this._element.appendChild(checkboxElement);
183
184     this._createLabelElement();
185
186     this._breakpoint.addEventListener("enable-changed", this._enableChanged, this);
187     this._breakpoint.addEventListener("hit-state-changed", this._hitStateChanged, this);
188     this._breakpoint.addEventListener("label-changed", this._labelChanged, this);
189     this._breakpoint.addEventListener("removed", this.dispatchEventToListeners.bind(this, "removed"));
190     if (breakpoint.click)
191         this.click = breakpoint.click.bind(breakpoint);
192 }
193
194 WebInspector.BreakpointItem.prototype = {
195     get element()
196     {
197         return this._element;
198     },
199
200     compareTo: function(other)
201     {
202         return this._breakpoint.compareTo(other._breakpoint);
203     },
204
205     populateEditElement: function(element)
206     {
207         this._breakpoint.populateEditElement(element);
208     },
209
210     remove: function()
211     {
212         this._breakpoint.remove();
213     },
214
215     _checkboxClicked: function(event)
216     {
217         this._breakpoint.enabled = !this._breakpoint.enabled;
218
219         // Breakpoint element may have it's own click handler.
220         event.stopPropagation();
221     },
222
223     _enableChanged: function(event)
224     {
225         var checkbox = this._element.firstChild;
226         checkbox.checked = this._breakpoint.enabled;
227     },
228
229     _hitStateChanged: function(event)
230     {
231         if (event.target.hit) {
232             this._element.addStyleClass("breakpoint-hit");
233             this.dispatchEventToListeners("breakpoint-hit");
234         } else
235             this._element.removeStyleClass("breakpoint-hit");
236     },
237
238     _labelChanged: function(event)
239     {
240         this._element.removeChild(this._labelElement);
241         this._createLabelElement();
242     },
243
244     _createLabelElement: function()
245     {
246         this._labelElement = document.createElement("span");
247         this._breakpoint.populateLabelElement(this._labelElement);
248         this._element.appendChild(this._labelElement);
249     }
250 }
251
252 WebInspector.BreakpointItem.prototype.__proto__ = WebInspector.Object.prototype;
253
254 WebInspector.EventListenerBreakpointsSidebarPane = function()
255 {
256     WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listener Breakpoints"));
257
258     this.categoriesElement = document.createElement("ol");
259     this.categoriesElement.tabIndex = 0;
260     this.categoriesElement.addStyleClass("properties-tree event-listener-breakpoints");
261     this.categoriesTreeOutline = new TreeOutline(this.categoriesElement);
262     this.bodyElement.appendChild(this.categoriesElement);
263
264     WebInspector.breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.ProjectChanged, this._projectChanged, this);
265     WebInspector.breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.EventListenerBreakpointAdded, this._breakpointAdded, this);
266
267     this._breakpointItems = {};
268     this._createCategory(/*@LS*/"Keyboard", "listener", ["keydown", "keyup", "keypress", "textInput"]);
269     this._createCategory(/*@LS*/"Mouse", "listener", ["click", "dblclick", "mousedown", "mouseup", "mouseover", "mousemove", "mouseout", "mousewheel"]);
270     // FIXME: uncomment following once inspector stops being drop targer in major ports.
271     // Otherwise, inspector page reacts on drop event and tries to load the event data.
272     // this._createCategory(/*@LS*/"Drag", "listener", ["drag", "drop", "dragstart", "dragend", "dragenter", "dragleave", "dragover"]);
273     this._createCategory(/*@LS*/"Control", "listener", ["resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"]);
274     this._createCategory(/*@LS*/"Clipboard", "listener", ["copy", "cut", "paste", "beforecopy", "beforecut", "beforepaste"]);
275     this._createCategory(/*@LS*/"Load", "listener", ["load", "unload", "abort", "error"]);
276     this._createCategory(/*@LS*/"DOM Mutation", "listener", ["DOMActivate", "DOMFocusIn", "DOMFocusOut", "DOMAttrModified", "DOMCharacterDataModified", "DOMNodeInserted", "DOMNodeInsertedIntoDocument", "DOMNodeRemoved", "DOMNodeRemovedFromDocument", "DOMSubtreeModified", "DOMContentLoaded"]);
277     this._createCategory(/*@LS*/"Device", "listener", ["deviceorientation", "devicemotion"]);
278     this._createCategory(/*@LS*/"Timer", "instrumentation", ["setTimer", "clearTimer", "timerFired"]);
279 }
280
281 WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
282     _createCategory: function(name, type, eventNames)
283     {
284         var categoryItem = {};
285         categoryItem.element = new TreeElement(WebInspector.UIString(name));
286         this.categoriesTreeOutline.appendChild(categoryItem.element);
287         categoryItem.element.listItemElement.addStyleClass("event-category");
288         categoryItem.element.selectable = true;
289
290         categoryItem.checkbox = this._createCheckbox(categoryItem.element);
291         categoryItem.checkbox.addEventListener("click", this._categoryCheckboxClicked.bind(this, categoryItem), true);
292
293         categoryItem.children = {};
294         for (var i = 0; i < eventNames.length; ++i) {
295             var eventName = type + ":" + eventNames[i];
296
297             var breakpointItem = {};
298             var title = WebInspector.EventListenerBreakpointView.eventNameForUI(eventName);
299             breakpointItem.element = new TreeElement(title);
300             categoryItem.element.appendChild(breakpointItem.element);
301             var hitMarker = document.createElement("div");
302             hitMarker.className = "breakpoint-hit-marker";
303             breakpointItem.element.listItemElement.appendChild(hitMarker);
304             breakpointItem.element.listItemElement.addStyleClass("source-code");
305             breakpointItem.element.selectable = true;
306
307             breakpointItem.checkbox = this._createCheckbox(breakpointItem.element);
308             breakpointItem.checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, breakpointItem), true);
309             breakpointItem.parent = categoryItem;
310             breakpointItem.eventName = eventName;
311
312             this._breakpointItems[eventName] = breakpointItem;
313             categoryItem.children[eventName] = breakpointItem;
314         }
315     },
316
317     _createCheckbox: function(treeElement)
318     {
319         var checkbox = document.createElement("input");
320         checkbox.className = "checkbox-elem";
321         checkbox.type = "checkbox";
322         treeElement.listItemElement.insertBefore(checkbox, treeElement.listItemElement.firstChild);
323         return checkbox;
324     },
325
326     _categoryCheckboxClicked: function(categoryItem)
327     {
328         var checked = categoryItem.checkbox.checked;
329         for (var eventName in categoryItem.children) {
330             var breakpointItem = categoryItem.children[eventName];
331             if (breakpointItem.checkbox.checked !== checked) {
332                 breakpointItem.checkbox.checked = checked;
333                 this._breakpointCheckboxClicked(breakpointItem);
334             }
335         }
336     },
337
338     _breakpointCheckboxClicked: function(breakpointItem)
339     {
340         if (breakpointItem.checkbox.checked)
341             WebInspector.breakpointManager.createEventListenerBreakpoint(breakpointItem.eventName);
342         else
343             breakpointItem.breakpoint.remove();
344     },
345
346     _breakpointAdded: function(event)
347     {
348         var breakpoint = event.data;
349
350         var breakpointItem = this._breakpointItems[breakpoint.eventName];
351         breakpointItem.breakpoint = breakpoint;
352         breakpoint.addEventListener("hit-state-changed", this._breakpointHitStateChanged.bind(this, breakpointItem));
353         breakpoint.addEventListener("removed", this._breakpointRemoved.bind(this, breakpointItem));
354         breakpointItem.checkbox.checked = true;
355         this._updateCategoryCheckbox(breakpointItem);
356     },
357
358     _breakpointHitStateChanged: function(breakpointItem, event)
359     {
360         if (event.target.hit) {
361             this.expanded = true;
362             var categoryItem = breakpointItem.parent;
363             categoryItem.element.expand();
364             breakpointItem.element.listItemElement.addStyleClass("breakpoint-hit");
365         } else
366             breakpointItem.element.listItemElement.removeStyleClass("breakpoint-hit");
367     },
368
369     _breakpointRemoved: function(breakpointItem)
370     {
371         breakpointItem.breakpoint = null;
372         breakpointItem.checkbox.checked = false;
373         this._updateCategoryCheckbox(breakpointItem);
374     },
375
376     _updateCategoryCheckbox: function(breakpointItem)
377     {
378         var categoryItem = breakpointItem.parent;
379         var hasEnabled = false, hasDisabled = false;
380         for (var eventName in categoryItem.children) {
381             var breakpointItem = categoryItem.children[eventName];
382             if (breakpointItem.checkbox.checked)
383                 hasEnabled = true;
384             else
385                 hasDisabled = true;
386         }
387         categoryItem.checkbox.checked = hasEnabled;
388         categoryItem.checkbox.indeterminate = hasEnabled && hasDisabled;
389     },
390
391     _projectChanged: function()
392     {
393         for (var eventName in this._breakpointItems) {
394             var breakpointItem = this._breakpointItems[eventName];
395             breakpointItem.breakpoint = null;
396             breakpointItem.checkbox.checked = false;
397             this._updateCategoryCheckbox(breakpointItem);
398         }
399     }
400 }
401
402 WebInspector.EventListenerBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;