Web Inspector: Remove redundant tooltips
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / BreakpointTreeElement.js
1 /*
2  * Copyright (C) 2013-2015 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.BreakpointTreeElement = class BreakpointTreeElement extends WI.GeneralTreeElement
27 {
28     constructor(breakpoint, className, title)
29     {
30         console.assert(breakpoint instanceof WI.Breakpoint);
31
32         if (!className)
33             className = WI.BreakpointTreeElement.GenericLineIconStyleClassName;
34
35         const subtitle = null;
36         super(["breakpoint", className], title, subtitle, breakpoint);
37
38         this._breakpoint = breakpoint;
39         this._probeSet = null;
40
41         this._listenerSet = new WI.EventListenerSet(this, "BreakpointTreeElement listeners");
42         if (!title)
43             this._listenerSet.register(breakpoint, WI.Breakpoint.Event.LocationDidChange, this._breakpointLocationDidChange);
44         this._listenerSet.register(breakpoint, WI.Breakpoint.Event.DisabledStateDidChange, this._updateStatus);
45         this._listenerSet.register(breakpoint, WI.Breakpoint.Event.AutoContinueDidChange, this._updateStatus);
46         this._listenerSet.register(breakpoint, WI.Breakpoint.Event.ResolvedStateDidChange, this._updateStatus);
47         this._listenerSet.register(WI.debuggerManager, WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this._updateStatus);
48
49         this._listenerSet.register(WI.probeManager, WI.ProbeManager.Event.ProbeSetAdded, this._probeSetAdded);
50         this._listenerSet.register(WI.probeManager, WI.ProbeManager.Event.ProbeSetRemoved, this._probeSetRemoved);
51
52         this._statusImageElement = document.createElement("img");
53         this._statusImageElement.className = WI.BreakpointTreeElement.StatusImageElementStyleClassName;
54         this._listenerSet.register(this._statusImageElement, "mousedown", this._statusImageElementMouseDown);
55         this._listenerSet.register(this._statusImageElement, "click", this._statusImageElementClicked);
56
57         if (!title)
58             this._updateTitles();
59         this._updateStatus();
60
61         this.status = this._statusImageElement;
62
63         this._iconAnimationLayerElement = document.createElement("span");
64         this.iconElement.appendChild(this._iconAnimationLayerElement);
65         this.tooltipHandledSeparately = true;
66     }
67
68     // Public
69
70     get breakpoint()
71     {
72         return this._breakpoint;
73     }
74
75     get filterableData()
76     {
77         return {text: [this.breakpoint.contentIdentifier]};
78     }
79
80     ondelete()
81     {
82         if (!WI.debuggerManager.isBreakpointRemovable(this._breakpoint))
83             return false;
84
85         // We set this flag so that TreeOutlines that will remove this
86         // BreakpointTreeElement will know whether it was deleted from
87         // within the TreeOutline or from outside it (e.g. TextEditor).
88         this.__deletedViaDeleteKeyboardShortcut = true;
89
90         WI.debuggerManager.removeBreakpoint(this._breakpoint);
91         return true;
92     }
93
94     onenter()
95     {
96         this._breakpoint.cycleToNextMode();
97         return true;
98     }
99
100     onspace()
101     {
102         this._breakpoint.cycleToNextMode();
103         return true;
104     }
105
106     onattach()
107     {
108         super.onattach();
109
110         this._listenerSet.install();
111
112         for (var probeSet of WI.probeManager.probeSets)
113             if (probeSet.breakpoint === this._breakpoint)
114                 this._addProbeSet(probeSet);
115     }
116
117     ondetach()
118     {
119         super.ondetach();
120
121         this._listenerSet.uninstall();
122
123         if (this._probeSet)
124             this._removeProbeSet(this._probeSet);
125     }
126
127     populateContextMenu(contextMenu, event)
128     {
129         WI.breakpointPopoverController.appendContextMenuItems(contextMenu, this._breakpoint, this._statusImageElement);
130
131         super.populateContextMenu(contextMenu, event);
132     }
133
134     removeStatusImage()
135     {
136         this._statusImageElement.remove();
137         this._statusImageElement = null;
138     }
139
140     // Private
141
142     _updateTitles()
143     {
144         var sourceCodeLocation = this._breakpoint.sourceCodeLocation;
145
146         var displayLineNumber = sourceCodeLocation.displayLineNumber;
147         var displayColumnNumber = sourceCodeLocation.displayColumnNumber;
148         if (displayColumnNumber > 0)
149             this.mainTitle = WI.UIString("Line %d:%d").format(displayLineNumber + 1, displayColumnNumber + 1); // The user visible line and column numbers are 1-based.
150         else
151             this.mainTitle = WI.UIString("Line %d").format(displayLineNumber + 1); // The user visible line number is 1-based.
152
153         if (sourceCodeLocation.hasMappedLocation()) {
154             this.subtitle = sourceCodeLocation.formattedLocationString();
155
156             if (sourceCodeLocation.hasFormattedLocation())
157                 this.subtitleElement.classList.add(WI.BreakpointTreeElement.FormattedLocationStyleClassName);
158             else
159                 this.subtitleElement.classList.remove(WI.BreakpointTreeElement.FormattedLocationStyleClassName);
160
161             this.tooltip = this.mainTitle + " \u2014 " + WI.UIString("originally %s").format(sourceCodeLocation.originalLocationString());
162         }
163     }
164
165     _updateStatus()
166     {
167         if (!this._statusImageElement)
168             return;
169
170         if (this._breakpoint.disabled)
171             this._statusImageElement.classList.add(WI.BreakpointTreeElement.StatusImageDisabledStyleClassName);
172         else
173             this._statusImageElement.classList.remove(WI.BreakpointTreeElement.StatusImageDisabledStyleClassName);
174
175         if (this._breakpoint.autoContinue)
176             this._statusImageElement.classList.add(WI.BreakpointTreeElement.StatusImageAutoContinueStyleClassName);
177         else
178             this._statusImageElement.classList.remove(WI.BreakpointTreeElement.StatusImageAutoContinueStyleClassName);
179
180         if (this._breakpoint.resolved && WI.debuggerManager.breakpointsEnabled)
181             this._statusImageElement.classList.add(WI.BreakpointTreeElement.StatusImageResolvedStyleClassName);
182         else
183             this._statusImageElement.classList.remove(WI.BreakpointTreeElement.StatusImageResolvedStyleClassName);
184     }
185
186     _addProbeSet(probeSet)
187     {
188         console.assert(probeSet instanceof WI.ProbeSet);
189         console.assert(probeSet.breakpoint === this._breakpoint);
190         console.assert(probeSet !== this._probeSet);
191
192         this._probeSet = probeSet;
193         probeSet.addEventListener(WI.ProbeSet.Event.SamplesCleared, this._samplesCleared, this);
194         probeSet.dataTable.addEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
195     }
196
197     _removeProbeSet(probeSet)
198     {
199         console.assert(probeSet instanceof WI.ProbeSet);
200         console.assert(probeSet === this._probeSet);
201
202         probeSet.removeEventListener(WI.ProbeSet.Event.SamplesCleared, this._samplesCleared, this);
203         probeSet.dataTable.removeEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
204         this._probeSet = null;
205     }
206
207     _probeSetAdded(event)
208     {
209         var probeSet = event.data.probeSet;
210         if (probeSet.breakpoint === this._breakpoint)
211             this._addProbeSet(probeSet);
212     }
213
214     _probeSetRemoved(event)
215     {
216         var probeSet = event.data.probeSet;
217         if (probeSet.breakpoint === this._breakpoint)
218             this._removeProbeSet(probeSet);
219     }
220
221     _samplesCleared(event)
222     {
223         console.assert(this._probeSet);
224
225         var oldTable = event.data.oldTable;
226         oldTable.removeEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
227         this._probeSet.dataTable.addEventListener(WI.ProbeSetDataTable.Event.FrameInserted, this._dataUpdated, this);
228     }
229
230     _dataUpdated()
231     {
232         if (this.element.classList.contains(WI.BreakpointTreeElement.ProbeDataUpdatedStyleClassName)) {
233             clearTimeout(this._removeIconAnimationTimeoutIdentifier);
234             this.element.classList.remove(WI.BreakpointTreeElement.ProbeDataUpdatedStyleClassName);
235             // We want to restart the animation, which can only be done by removing the class,
236             // performing layout, and re-adding the class. Try adding class back on next run loop.
237             window.requestAnimationFrame(this._dataUpdated.bind(this));
238             return;
239         }
240
241         this.element.classList.add(WI.BreakpointTreeElement.ProbeDataUpdatedStyleClassName);
242         this._removeIconAnimationTimeoutIdentifier = setTimeout(() => {
243             this.element.classList.remove(WI.BreakpointTreeElement.ProbeDataUpdatedStyleClassName);
244         }, WI.BreakpointTreeElement.ProbeDataUpdatedAnimationDuration);
245     }
246
247     _breakpointLocationDidChange(event)
248     {
249         console.assert(event.target === this._breakpoint);
250
251         // The Breakpoint has a new display SourceCode. The sidebar will remove us, and ondetach() will clear listeners.
252         if (event.data.oldDisplaySourceCode === this._breakpoint.displaySourceCode)
253             return;
254
255         this._updateTitles();
256     }
257
258     _statusImageElementMouseDown(event)
259     {
260         // To prevent the tree element from selecting.
261         event.stopPropagation();
262     }
263
264     _statusImageElementClicked(event)
265     {
266         this._breakpoint.cycleToNextMode();
267     }
268 };
269
270 WI.BreakpointTreeElement.GenericLineIconStyleClassName = "breakpoint-generic-line-icon";
271 WI.BreakpointTreeElement.StatusImageElementStyleClassName = "status-image";
272 WI.BreakpointTreeElement.StatusImageResolvedStyleClassName = "resolved";
273 WI.BreakpointTreeElement.StatusImageAutoContinueStyleClassName = "auto-continue";
274 WI.BreakpointTreeElement.StatusImageDisabledStyleClassName = "disabled";
275 WI.BreakpointTreeElement.FormattedLocationStyleClassName = "formatted-location";
276 WI.BreakpointTreeElement.ProbeDataUpdatedStyleClassName = "data-updated";
277
278 WI.BreakpointTreeElement.ProbeDataUpdatedAnimationDuration = 400; // milliseconds