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
26WebInspector.JavaScriptBreakpointsSidebarPane = function(model, showSourceLineDelegate)
27{
28    WebInspector.SidebarPane.call(this, WebInspector.UIString("Breakpoints"));
29
30    this._model = model;
31    this._showSourceLineDelegate = showSourceLineDelegate;
32
33    this.listElement = document.createElement("ol");
34    this.listElement.className = "breakpoint-list";
35
36    this.emptyElement = document.createElement("div");
37    this.emptyElement.className = "info";
38    this.emptyElement.textContent = WebInspector.UIString("No Breakpoints");
39
40    this.bodyElement.appendChild(this.emptyElement);
41
42    this._items = {};
43}
44
45WebInspector.JavaScriptBreakpointsSidebarPane.prototype = {
46    addBreakpoint: function(breakpoint)
47    {
48        var element = document.createElement("li");
49        element.addStyleClass("cursor-pointer");
50        element.addEventListener("contextmenu", this._contextMenu.bind(this, breakpoint), true);
51        element.addEventListener("click", this._breakpointClicked.bind(this, breakpoint), false);
52
53        var checkbox = document.createElement("input");
54        checkbox.className = "checkbox-elem";
55        checkbox.type = "checkbox";
56        checkbox.checked = breakpoint.enabled;
57        checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, breakpoint), false);
58        element.appendChild(checkbox);
59
60        var displayName = breakpoint.url ? WebInspector.displayNameForURL(breakpoint.url) : WebInspector.UIString("(program)");
61        var labelElement = document.createTextNode(displayName + ":" + (breakpoint.lineNumber + 1));
62        element.appendChild(labelElement);
63
64        var snippetElement = document.createElement("div");
65        snippetElement.className = "source-text monospace";
66        element.appendChild(snippetElement);
67        if (breakpoint.loadSnippet) {
68            function didLoadSnippet(snippet)
69            {
70                snippetElement.textContent = snippet;
71            }
72            breakpoint.loadSnippet(didLoadSnippet);
73        }
74
75        element._data = breakpoint;
76        var currentElement = this.listElement.firstChild;
77        while (currentElement) {
78            if (currentElement._data && this._compareBreakpoints(currentElement._data, element._data) > 0)
79                break;
80            currentElement = currentElement.nextSibling;
81        }
82        this._addListElement(element, currentElement);
83
84        var breakpointItem = {};
85        breakpointItem.element = element;
86        breakpointItem.checkbox = checkbox;
87        this._items[this._createBreakpointItemId(breakpoint.sourceFileId, breakpoint.lineNumber)] = breakpointItem;
88
89        if (!this.expanded)
90            this.expanded = true;
91    },
92
93    removeBreakpoint: function(sourceFileId, lineNumber)
94    {
95        var breakpointItemId = this._createBreakpointItemId(sourceFileId, lineNumber);
96        var breakpointItem = this._items[breakpointItemId];
97        if (!breakpointItem)
98            return;
99        delete this._items[breakpointItemId];
100        this._removeListElement(breakpointItem.element);
101    },
102
103    highlightBreakpoint: function(sourceFileId, lineNumber)
104    {
105        var breakpointItem = this._items[this._createBreakpointItemId(sourceFileId, lineNumber)];
106        if (!breakpointItem)
107            return;
108        breakpointItem.element.addStyleClass("breakpoint-hit");
109        this._highlightedBreakpointItem = breakpointItem;
110    },
111
112    clearBreakpointHighlight: function()
113    {
114        if (this._highlightedBreakpointItem) {
115            this._highlightedBreakpointItem.element.removeStyleClass("breakpoint-hit");
116            delete this._highlightedBreakpointItem;
117        }
118    },
119
120    _createBreakpointItemId: function(sourceFileId, lineNumber)
121    {
122        return sourceFileId + ":" + lineNumber;
123    },
124
125    _breakpointClicked: function(breakpoint, event)
126    {
127        this._showSourceLineDelegate(breakpoint.sourceFileId, breakpoint.lineNumber);
128    },
129
130    _breakpointCheckboxClicked: function(breakpoint, event)
131    {
132        // Breakpoint element has it's own click handler.
133        event.stopPropagation();
134
135        this._model.setBreakpointEnabled(breakpoint.sourceFileId, breakpoint.lineNumber, event.target.checked);
136    },
137
138    _contextMenu: function(breakpoint, event)
139    {
140        var contextMenu = new WebInspector.ContextMenu();
141
142        var removeHandler = this._model.removeBreakpoint.bind(this._model, breakpoint.sourceFileId, breakpoint.lineNumber);
143        contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeHandler);
144
145        contextMenu.show(event);
146    },
147
148    _addListElement: function(element, beforeElement)
149    {
150        if (beforeElement)
151            this.listElement.insertBefore(element, beforeElement);
152        else {
153            if (!this.listElement.firstChild) {
154                this.bodyElement.removeChild(this.emptyElement);
155                this.bodyElement.appendChild(this.listElement);
156            }
157            this.listElement.appendChild(element);
158        }
159    },
160
161    _removeListElement: function(element)
162    {
163        this.listElement.removeChild(element);
164        if (!this.listElement.firstChild) {
165            this.bodyElement.removeChild(this.listElement);
166            this.bodyElement.appendChild(this.emptyElement);
167        }
168    },
169
170    _compare: function(x, y)
171    {
172        if (x !== y)
173            return x < y ? -1 : 1;
174        return 0;
175    },
176
177    _compareBreakpoints: function(b1, b2)
178    {
179        return this._compare(b1.url, b2.url) || this._compare(b1.lineNumber, b2.lineNumber);
180    },
181
182    reset: function()
183    {
184        this.listElement.removeChildren();
185        if (this.listElement.parentElement) {
186            this.bodyElement.removeChild(this.listElement);
187            this.bodyElement.appendChild(this.emptyElement);
188        }
189        this._items = {};
190    }
191}
192
193WebInspector.JavaScriptBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
194
195WebInspector.NativeBreakpointsSidebarPane = function(title)
196{
197    WebInspector.SidebarPane.call(this, title);
198
199    this.listElement = document.createElement("ol");
200    this.listElement.className = "breakpoint-list";
201
202    this.emptyElement = document.createElement("div");
203    this.emptyElement.className = "info";
204    this.emptyElement.textContent = WebInspector.UIString("No Breakpoints");
205
206    this.bodyElement.appendChild(this.emptyElement);
207}
208
209WebInspector.NativeBreakpointsSidebarPane.prototype = {
210    _addListElement: function(element, beforeElement)
211    {
212        if (beforeElement)
213            this.listElement.insertBefore(element, beforeElement);
214        else {
215            if (!this.listElement.firstChild) {
216                this.bodyElement.removeChild(this.emptyElement);
217                this.bodyElement.appendChild(this.listElement);
218            }
219            this.listElement.appendChild(element);
220        }
221    },
222
223    _removeListElement: function(element)
224    {
225        this.listElement.removeChild(element);
226        if (!this.listElement.firstChild) {
227            this.bodyElement.removeChild(this.listElement);
228            this.bodyElement.appendChild(this.emptyElement);
229        }
230    },
231
232    _reset: function()
233    {
234        this.listElement.removeChildren();
235        if (this.listElement.parentElement) {
236            this.bodyElement.removeChild(this.listElement);
237            this.bodyElement.appendChild(this.emptyElement);
238        }
239    }
240}
241
242WebInspector.NativeBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
243
244WebInspector.XHRBreakpointsSidebarPane = function()
245{
246    WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("XHR Breakpoints"));
247
248    this._breakpointElements = {};
249
250    var addButton = document.createElement("button");
251    addButton.className = "add";
252    addButton.addEventListener("click", this._addButtonClicked.bind(this), false);
253    this.titleElement.appendChild(addButton);
254
255    this._restoreBreakpoints();
256}
257
258WebInspector.XHRBreakpointsSidebarPane.prototype = {
259    _addButtonClicked: function(event)
260    {
261        event.stopPropagation();
262
263        this.expanded = true;
264
265        var inputElement = document.createElement("span");
266        inputElement.className = "breakpoint-condition editing";
267        this._addListElement(inputElement, this.listElement.firstChild);
268
269        function finishEditing(accept, e, text)
270        {
271            this._removeListElement(inputElement);
272            if (accept) {
273                this._setBreakpoint(text, true);
274                this._saveBreakpoints();
275            }
276        }
277
278        WebInspector.startEditing(inputElement, {
279            commitHandler: finishEditing.bind(this, true),
280            cancelHandler: finishEditing.bind(this, false)
281        });
282    },
283
284    _setBreakpoint: function(url, enabled)
285    {
286        if (url in this._breakpointElements)
287            return;
288
289        var element = document.createElement("li");
290        element._url = url;
291        element.addEventListener("contextmenu", this._contextMenu.bind(this, url), true);
292
293        var checkboxElement = document.createElement("input");
294        checkboxElement.className = "checkbox-elem";
295        checkboxElement.type = "checkbox";
296        checkboxElement.checked = enabled;
297        checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, url), false);
298        element._checkboxElement = checkboxElement;
299        element.appendChild(checkboxElement);
300
301        var labelElement = document.createElement("span");
302        if (!url)
303            labelElement.textContent = WebInspector.UIString("Any XHR");
304        else
305            labelElement.textContent = WebInspector.UIString("URL contains \"%s\"", url);
306        labelElement.addStyleClass("cursor-auto");
307        labelElement.addEventListener("dblclick", this._labelClicked.bind(this, url), false);
308        element.appendChild(labelElement);
309
310        var currentElement = this.listElement.firstChild;
311        while (currentElement) {
312            if (currentElement._url && currentElement._url < element._url)
313                break;
314            currentElement = currentElement.nextSibling;
315        }
316        this._addListElement(element, currentElement);
317        this._breakpointElements[url] = element;
318        if (enabled)
319            BrowserDebuggerAgent.setXHRBreakpoint(url);
320    },
321
322    _removeBreakpoint: function(url)
323    {
324        var element = this._breakpointElements[url];
325        if (!element)
326            return;
327
328        this._removeListElement(element);
329        delete this._breakpointElements[url];
330        if (element._checkboxElement.checked)
331            BrowserDebuggerAgent.removeXHRBreakpoint(url);
332    },
333
334    _contextMenu: function(url, event)
335    {
336        var contextMenu = new WebInspector.ContextMenu();
337        function removeBreakpoint()
338        {
339            this._removeBreakpoint(url);
340            this._saveBreakpoints();
341        }
342        contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeBreakpoint.bind(this));
343        contextMenu.show(event);
344    },
345
346    _checkboxClicked: function(url, event)
347    {
348        if (event.target.checked)
349            BrowserDebuggerAgent.setXHRBreakpoint(url);
350        else
351            BrowserDebuggerAgent.removeXHRBreakpoint(url);
352        this._saveBreakpoints();
353    },
354
355    _labelClicked: function(url)
356    {
357        var element = this._breakpointElements[url];
358        var inputElement = document.createElement("span");
359        inputElement.className = "breakpoint-condition editing";
360        inputElement.textContent = url;
361        this.listElement.insertBefore(inputElement, element);
362        element.addStyleClass("hidden");
363
364        function finishEditing(accept, e, text)
365        {
366            this._removeListElement(inputElement);
367            if (accept) {
368                this._removeBreakpoint(url);
369                this._setBreakpoint(text, element._checkboxElement.checked);
370                this._saveBreakpoints();
371            } else
372                element.removeStyleClass("hidden");
373        }
374
375        WebInspector.startEditing(inputElement, {
376            commitHandler: finishEditing.bind(this, true),
377            cancelHandler: finishEditing.bind(this, false)
378        });
379    },
380
381    highlightBreakpoint: function(url)
382    {
383        var element = this._breakpointElements[url];
384        if (!element)
385            return;
386        this.expanded = true;
387        element.addStyleClass("breakpoint-hit");
388        this._highlightedElement = element;
389    },
390
391    clearBreakpointHighlight: function()
392    {
393        if (this._highlightedElement) {
394            this._highlightedElement.removeStyleClass("breakpoint-hit");
395            delete this._highlightedElement;
396        }
397    },
398
399    _saveBreakpoints: function()
400    {
401        var breakpoints = [];
402        for (var url in this._breakpointElements)
403            breakpoints.push({ url: url, enabled: this._breakpointElements[url]._checkboxElement.checked });
404        WebInspector.settings.xhrBreakpoints = breakpoints;
405    },
406
407    _restoreBreakpoints: function()
408    {
409        var breakpoints = WebInspector.settings.xhrBreakpoints;
410        for (var i = 0; i < breakpoints.length; ++i) {
411            var breakpoint = breakpoints[i];
412            if (breakpoint && typeof breakpoint.url === "string")
413                this._setBreakpoint(breakpoint.url, breakpoint.enabled);
414        }
415    }
416}
417
418WebInspector.XHRBreakpointsSidebarPane.prototype.__proto__ = WebInspector.NativeBreakpointsSidebarPane.prototype;
419
420WebInspector.EventListenerBreakpointsSidebarPane = function()
421{
422    WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listener Breakpoints"));
423
424    this.categoriesElement = document.createElement("ol");
425    this.categoriesElement.tabIndex = 0;
426    this.categoriesElement.addStyleClass("properties-tree");
427    this.categoriesElement.addStyleClass("event-listener-breakpoints");
428    this.categoriesTreeOutline = new TreeOutline(this.categoriesElement);
429    this.bodyElement.appendChild(this.categoriesElement);
430
431    this._breakpointItems = {};
432    this._createCategory(WebInspector.UIString("Keyboard"), "listener", ["keydown", "keyup", "keypress", "textInput"]);
433    this._createCategory(WebInspector.UIString("Mouse"), "listener", ["click", "dblclick", "mousedown", "mouseup", "mouseover", "mousemove", "mouseout", "mousewheel"]);
434    // FIXME: uncomment following once inspector stops being drop targer in major ports.
435    // Otherwise, inspector page reacts on drop event and tries to load the event data.
436    // this._createCategory(WebInspector.UIString("Drag"), "listener", ["drag", "drop", "dragstart", "dragend", "dragenter", "dragleave", "dragover"]);
437    this._createCategory(WebInspector.UIString("Control"), "listener", ["resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"]);
438    this._createCategory(WebInspector.UIString("Clipboard"), "listener", ["copy", "cut", "paste", "beforecopy", "beforecut", "beforepaste"]);
439    this._createCategory(WebInspector.UIString("Load"), "listener", ["load", "unload", "abort", "error"]);
440    this._createCategory(WebInspector.UIString("DOM Mutation"), "listener", ["DOMActivate", "DOMFocusIn", "DOMFocusOut", "DOMAttrModified", "DOMCharacterDataModified", "DOMNodeInserted", "DOMNodeInsertedIntoDocument", "DOMNodeRemoved", "DOMNodeRemovedFromDocument", "DOMSubtreeModified", "DOMContentLoaded"]);
441    this._createCategory(WebInspector.UIString("Device"), "listener", ["deviceorientation", "devicemotion"]);
442    this._createCategory(WebInspector.UIString("Timer"), "instrumentation", ["setTimer", "clearTimer", "timerFired"]);
443
444    this._restoreBreakpoints();
445}
446
447WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI = function(eventName)
448{
449    if (!WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI) {
450        WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI = {
451            "instrumentation:setTimer": WebInspector.UIString("Set Timer"),
452            "instrumentation:clearTimer": WebInspector.UIString("Clear Timer"),
453            "instrumentation:timerFired": WebInspector.UIString("Timer Fired")
454        };
455    }
456    return WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI[eventName] || eventName.substring(eventName.indexOf(":") + 1);
457}
458
459WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
460    _createCategory: function(name, type, eventNames)
461    {
462        var categoryItem = {};
463        categoryItem.element = new TreeElement(name);
464        this.categoriesTreeOutline.appendChild(categoryItem.element);
465        categoryItem.element.listItemElement.addStyleClass("event-category");
466        categoryItem.element.selectable = true;
467
468        categoryItem.checkbox = this._createCheckbox(categoryItem.element);
469        categoryItem.checkbox.addEventListener("click", this._categoryCheckboxClicked.bind(this, categoryItem), true);
470
471        categoryItem.children = {};
472        for (var i = 0; i < eventNames.length; ++i) {
473            var eventName = type + ":" + eventNames[i];
474
475            var breakpointItem = {};
476            var title = WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName);
477            breakpointItem.element = new TreeElement(title);
478            categoryItem.element.appendChild(breakpointItem.element);
479            var hitMarker = document.createElement("div");
480            hitMarker.className = "breakpoint-hit-marker";
481            breakpointItem.element.listItemElement.appendChild(hitMarker);
482            breakpointItem.element.listItemElement.addStyleClass("source-code");
483            breakpointItem.element.selectable = true;
484
485            breakpointItem.checkbox = this._createCheckbox(breakpointItem.element);
486            breakpointItem.checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, eventName), true);
487            breakpointItem.parent = categoryItem;
488
489            this._breakpointItems[eventName] = breakpointItem;
490            categoryItem.children[eventName] = breakpointItem;
491        }
492    },
493
494    _createCheckbox: function(treeElement)
495    {
496        var checkbox = document.createElement("input");
497        checkbox.className = "checkbox-elem";
498        checkbox.type = "checkbox";
499        treeElement.listItemElement.insertBefore(checkbox, treeElement.listItemElement.firstChild);
500        return checkbox;
501    },
502
503    _categoryCheckboxClicked: function(categoryItem)
504    {
505        var checked = categoryItem.checkbox.checked;
506        for (var eventName in categoryItem.children) {
507            var breakpointItem = categoryItem.children[eventName];
508            if (breakpointItem.checkbox.checked === checked)
509                continue;
510            if (checked)
511                this._setBreakpoint(eventName);
512            else
513                this._removeBreakpoint(eventName);
514        }
515        this._saveBreakpoints();
516    },
517
518    _breakpointCheckboxClicked: function(eventName, event)
519    {
520        if (event.target.checked)
521            this._setBreakpoint(eventName);
522        else
523            this._removeBreakpoint(eventName);
524        this._saveBreakpoints();
525    },
526
527    _setBreakpoint: function(eventName)
528    {
529        var breakpointItem = this._breakpointItems[eventName];
530        if (!breakpointItem)
531            return;
532        breakpointItem.checkbox.checked = true;
533        BrowserDebuggerAgent.setEventListenerBreakpoint(eventName);
534        this._updateCategoryCheckbox(breakpointItem.parent);
535    },
536
537    _removeBreakpoint: function(eventName)
538    {
539        var breakpointItem = this._breakpointItems[eventName];
540        if (!breakpointItem)
541            return;
542        breakpointItem.checkbox.checked = false;
543        BrowserDebuggerAgent.removeEventListenerBreakpoint(eventName);
544        this._updateCategoryCheckbox(breakpointItem.parent);
545    },
546
547    _updateCategoryCheckbox: function(categoryItem)
548    {
549        var hasEnabled = false, hasDisabled = false;
550        for (var eventName in categoryItem.children) {
551            var breakpointItem = categoryItem.children[eventName];
552            if (breakpointItem.checkbox.checked)
553                hasEnabled = true;
554            else
555                hasDisabled = true;
556        }
557        categoryItem.checkbox.checked = hasEnabled;
558        categoryItem.checkbox.indeterminate = hasEnabled && hasDisabled;
559    },
560
561    highlightBreakpoint: function(eventName)
562    {
563        var breakpointItem = this._breakpointItems[eventName];
564        if (!breakpointItem)
565            return;
566        this.expanded = true;
567        breakpointItem.parent.element.expand();
568        breakpointItem.element.listItemElement.addStyleClass("breakpoint-hit");
569        this._highlightedElement = breakpointItem.element.listItemElement;
570    },
571
572    clearBreakpointHighlight: function()
573    {
574        if (this._highlightedElement) {
575            this._highlightedElement.removeStyleClass("breakpoint-hit");
576            delete this._highlightedElement;
577        }
578    },
579
580    _saveBreakpoints: function()
581    {
582        var breakpoints = [];
583        for (var eventName in this._breakpointItems) {
584            if (this._breakpointItems[eventName].checkbox.checked)
585                breakpoints.push({ eventName: eventName });
586        }
587        WebInspector.settings.eventListenerBreakpoints = breakpoints;
588    },
589
590    _restoreBreakpoints: function()
591    {
592        var breakpoints = WebInspector.settings.eventListenerBreakpoints;
593        for (var i = 0; i < breakpoints.length; ++i) {
594            var breakpoint = breakpoints[i];
595            if (breakpoint && typeof breakpoint.eventName === "string")
596                this._setBreakpoint(breakpoint.eventName);
597        }
598    }
599}
600
601WebInspector.EventListenerBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
602