Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / sources / WatchExpressionsSidebarPane.js
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 /**
32  * @constructor
33  * @extends {WebInspector.SidebarPane}
34  */
35 WebInspector.WatchExpressionsSidebarPane = function()
36 {
37     WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions"));
38
39     this.section = new WebInspector.WatchExpressionsSection();
40     this.bodyElement.appendChild(this.section.element);
41
42     var refreshButton = document.createElement("button");
43     refreshButton.className = "pane-title-button refresh";
44     refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
45     refreshButton.title = WebInspector.UIString("Refresh");
46     this.titleElement.appendChild(refreshButton);
47
48     var addButton = document.createElement("button");
49     addButton.className = "pane-title-button add";
50     addButton.addEventListener("click", this._addButtonClicked.bind(this), false);
51     this.titleElement.appendChild(addButton);
52     addButton.title = WebInspector.UIString("Add watch expression");
53
54     this._requiresUpdate = true;
55     WebInspector.context.addFlavorChangeListener(WebInspector.ExecutionContext ,this.refreshExpressions, this);
56 }
57
58 WebInspector.WatchExpressionsSidebarPane.prototype = {
59     wasShown: function()
60     {
61         this._refreshExpressionsIfNeeded();
62     },
63
64     refreshExpressions: function()
65     {
66         this._requiresUpdate = true;
67         this._refreshExpressionsIfNeeded();
68     },
69
70     addExpression: function(expression)
71     {
72         this.section.addExpression(expression);
73         this.expand();
74     },
75
76     _refreshExpressionsIfNeeded: function()
77     {
78         if (this._requiresUpdate && this.isShowing()) {
79             this.section.update();
80             delete this._requiresUpdate;
81         } else
82             this._requiresUpdate = true;
83     },
84
85     _addButtonClicked: function(event)
86     {
87         event.consume();
88         this.expand();
89         this.section.addNewExpressionAndEdit();
90     },
91
92     _refreshButtonClicked: function(event)
93     {
94         event.consume();
95         this.refreshExpressions();
96     },
97
98     __proto__: WebInspector.SidebarPane.prototype
99 }
100
101 /**
102  * @constructor
103  * @extends {WebInspector.ObjectPropertiesSection}
104  */
105 WebInspector.WatchExpressionsSection = function()
106 {
107     this._watchObjectGroupId = "watch-group";
108
109     WebInspector.ObjectPropertiesSection.call(this, WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(""));
110
111     this.treeElementConstructor = WebInspector.WatchedPropertyTreeElement;
112     this._expandedExpressions = {};
113     this._expandedProperties = {};
114
115     this.emptyElement = document.createElement("div");
116     this.emptyElement.className = "info";
117     this.emptyElement.textContent = WebInspector.UIString("No Watch Expressions");
118
119     this.watchExpressions = WebInspector.settings.watchExpressions.get();
120
121     this.headerElement.className = "hidden";
122     this.editable = true;
123     this.expanded = true;
124     this.propertiesElement.classList.add("watch-expressions");
125
126     this.element.addEventListener("mousemove", this._mouseMove.bind(this), true);
127     this.element.addEventListener("mouseout", this._mouseOut.bind(this), true);
128     this.element.addEventListener("dblclick", this._sectionDoubleClick.bind(this), false);
129     this.emptyElement.addEventListener("contextmenu", this._emptyElementContextMenu.bind(this), false);
130 }
131
132 WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0";
133
134 WebInspector.WatchExpressionsSection.prototype = {
135     /**
136      * @param {!Event=} e
137      */
138     update: function(e)
139     {
140         if (e)
141             e.consume();
142
143         /***
144          * @param {string} expression
145          * @param {number} watchIndex
146          * @param {?WebInspector.RemoteObject} result
147          * @param {boolean} wasThrown
148          * @this {WebInspector.WatchExpressionsSection}
149          */
150         function appendResult(expression, watchIndex, result, wasThrown)
151         {
152             if (!result)
153                 return;
154
155             var property = new WebInspector.RemoteObjectProperty(expression, result);
156             property.watchIndex = watchIndex;
157             property.wasThrown = wasThrown;
158
159             // To clarify what's going on here:
160             // In the outer function, we calculate the number of properties
161             // that we're going to be updating, and set that in the
162             // propertyCount variable.
163             // In this function, we test to see when we are processing the
164             // last property, and then call the superclass's updateProperties()
165             // method to get all the properties refreshed at once.
166             properties.push(property);
167
168             if (properties.length == propertyCount) {
169                 this.updateProperties(properties, [], WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties);
170
171                 // check to see if we just added a new watch expression,
172                 // which will always be the last property
173                 if (this._newExpressionAdded) {
174                     delete this._newExpressionAdded;
175
176                     var treeElement = this.findAddedTreeElement();
177                     if (treeElement)
178                         treeElement.startEditing();
179                 }
180
181                 // Force displaying delete button for hovered element.
182                 if (this._lastMouseMovePageY)
183                     this._updateHoveredElement(this._lastMouseMovePageY);
184             }
185         }
186
187         // TODO: pass exact injected script id.
188         WebInspector.targetManager.targets().forEach(function(target) {target.runtimeAgent().releaseObjectGroup(this._watchObjectGroupId)}, this);
189         var properties = [];
190
191         // Count the properties, so we known when to call this.updateProperties()
192         // in appendResult()
193         var propertyCount = 0;
194         for (var i = 0; i < this.watchExpressions.length; ++i) {
195             if (!this.watchExpressions[i])
196                 continue;
197             ++propertyCount;
198         }
199
200         // Now process all the expressions, since we have the actual count,
201         // which is checked in the appendResult inner function.
202         var currentExecutionContext = WebInspector.context.flavor(WebInspector.ExecutionContext);
203         if (currentExecutionContext) {
204             for (var i = 0; i < this.watchExpressions.length; ++i) {
205                 var expression = this.watchExpressions[i];
206                 if (!expression)
207                     continue;
208
209                 currentExecutionContext.evaluate(expression, this._watchObjectGroupId, false, true, false, false, appendResult.bind(this, expression, i));
210             }
211         }
212
213         if (!propertyCount) {
214             if (!this.emptyElement.parentNode)
215                 this.element.appendChild(this.emptyElement);
216         } else {
217             if (this.emptyElement.parentNode)
218                 this.element.removeChild(this.emptyElement);
219         }
220
221         // note this is setting the expansion of the tree, not the section;
222         // with no expressions, and expanded tree, we get some extra vertical
223         // white space
224         this.expanded = (propertyCount != 0);
225     },
226
227     addExpression: function(expression)
228     {
229         this.watchExpressions.push(expression);
230         this.saveExpressions();
231         this.update();
232     },
233
234     addNewExpressionAndEdit: function()
235     {
236         this._newExpressionAdded = true;
237         this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression);
238         this.update();
239     },
240
241     _sectionDoubleClick: function(event)
242     {
243         if (event.target !== this.element && event.target !== this.propertiesElement && event.target !== this.emptyElement)
244             return;
245         event.consume();
246         this.addNewExpressionAndEdit();
247     },
248
249     updateExpression: function(element, value)
250     {
251         if (value === null) {
252             var index = element.property.watchIndex;
253             this.watchExpressions.splice(index, 1);
254         }
255         else
256             this.watchExpressions[element.property.watchIndex] = value;
257         this.saveExpressions();
258         this.update();
259     },
260
261     _deleteAllExpressions: function()
262     {
263         this.watchExpressions = [];
264         this.saveExpressions();
265         this.update();
266     },
267
268     /**
269      * @return {?TreeElement}
270      */
271     findAddedTreeElement: function()
272     {
273         var children = this.propertiesTreeOutline.children;
274         for (var i = 0; i < children.length; ++i) {
275             if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
276                 return children[i];
277         }
278         return null;
279     },
280
281     /**
282      * @return {number}
283      */
284     saveExpressions: function()
285     {
286         var toSave = [];
287         for (var i = 0; i < this.watchExpressions.length; i++)
288             if (this.watchExpressions[i])
289                 toSave.push(this.watchExpressions[i]);
290
291         WebInspector.settings.watchExpressions.set(toSave);
292         return toSave.length;
293     },
294
295     _mouseMove: function(e)
296     {
297         if (this.propertiesElement.firstChild)
298             this._updateHoveredElement(e.pageY);
299     },
300
301     _mouseOut: function()
302     {
303         if (this._hoveredElement) {
304             this._hoveredElement.classList.remove("hovered");
305             delete this._hoveredElement;
306         }
307         delete this._lastMouseMovePageY;
308     },
309
310     _updateHoveredElement: function(pageY)
311     {
312         var candidateElement = this.propertiesElement.firstChild;
313         while (true) {
314             var next = candidateElement.nextSibling;
315             while (next && !next.clientHeight)
316                 next = next.nextSibling;
317             if (!next || next.totalOffsetTop() > pageY)
318                 break;
319             candidateElement = next;
320         }
321
322         if (this._hoveredElement !== candidateElement) {
323             if (this._hoveredElement)
324                 this._hoveredElement.classList.remove("hovered");
325             if (candidateElement)
326                 candidateElement.classList.add("hovered");
327             this._hoveredElement = candidateElement;
328         }
329
330         this._lastMouseMovePageY = pageY;
331     },
332
333     _emptyElementContextMenu: function(event)
334     {
335         var contextMenu = new WebInspector.ContextMenu(event);
336         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.addNewExpressionAndEdit.bind(this));
337         contextMenu.show();
338     },
339
340     __proto__: WebInspector.ObjectPropertiesSection.prototype
341 }
342
343 /**
344  * @param {!WebInspector.RemoteObjectProperty} propertyA
345  * @param {!WebInspector.RemoteObjectProperty} propertyB
346  * @return {number}
347  */
348 WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB)
349 {
350     if (propertyA.watchIndex == propertyB.watchIndex)
351         return 0;
352     else if (propertyA.watchIndex < propertyB.watchIndex)
353         return -1;
354     else
355         return 1;
356 }
357
358 /**
359  * @constructor
360  * @extends {WebInspector.ObjectPropertyTreeElement}
361  * @param {!WebInspector.RemoteObjectProperty} property
362  */
363 WebInspector.WatchExpressionTreeElement = function(property)
364 {
365     WebInspector.ObjectPropertyTreeElement.call(this, property);
366 }
367
368 WebInspector.WatchExpressionTreeElement.prototype = {
369     onexpand: function()
370     {
371         WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
372         this.treeOutline.section._expandedExpressions[this._expression()] = true;
373     },
374
375     oncollapse: function()
376     {
377         WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
378         delete this.treeOutline.section._expandedExpressions[this._expression()];
379     },
380
381     onattach: function()
382     {
383         WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
384         if (this.treeOutline.section._expandedExpressions[this._expression()])
385             this.expanded = true;
386     },
387
388     _expression: function()
389     {
390         return this.property.name;
391     },
392
393     update: function()
394     {
395         WebInspector.ObjectPropertyTreeElement.prototype.update.call(this);
396
397         if (this.property.wasThrown) {
398             this.valueElement.textContent = WebInspector.UIString("<not available>");
399             this.listItemElement.classList.add("dimmed");
400         } else
401             this.listItemElement.classList.remove("dimmed");
402
403         var deleteButton = document.createElement("input");
404         deleteButton.type = "button";
405         deleteButton.title = WebInspector.UIString("Delete watch expression.");
406         deleteButton.classList.add("enabled-button");
407         deleteButton.classList.add("delete-button");
408         deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);
409         this.listItemElement.addEventListener("contextmenu", this._contextMenu.bind(this), false);
410         this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild);
411     },
412
413     /**
414      * @param {!WebInspector.ContextMenu} contextMenu
415      * @override
416      */
417     populateContextMenu: function(contextMenu)
418     {
419         if (!this.isEditing()) {
420             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.treeOutline.section.addNewExpressionAndEdit.bind(this.treeOutline.section));
421             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete watch expression" : "Delete Watch Expression"), this._deleteButtonClicked.bind(this));
422         }
423         if (this.treeOutline.section.watchExpressions.length > 1)
424             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete all watch expressions" : "Delete All Watch Expressions"), this._deleteAllButtonClicked.bind(this));
425     },
426
427     _contextMenu: function(event)
428     {
429         var contextMenu = new WebInspector.ContextMenu(event);
430         this.populateContextMenu(contextMenu);
431         contextMenu.show();
432     },
433
434     _deleteAllButtonClicked: function()
435     {
436         this.treeOutline.section._deleteAllExpressions();
437     },
438
439     _deleteButtonClicked: function()
440     {
441         this.treeOutline.section.updateExpression(this, null);
442     },
443
444     /**
445      * @return {boolean}
446      */
447     renderPromptAsBlock: function()
448     {
449         return true;
450     },
451
452     /**
453      * @override
454      * @return {{element: !Element, value: (string|undefined)}}
455      */
456     elementAndValueToEdit: function()
457     {
458         return { element: this.nameElement, value: this.property.name.trim() };
459     },
460
461     /**
462      * @override
463      */
464     editingCancelled: function(element, context)
465     {
466         if (!context.elementToEdit.textContent)
467             this.treeOutline.section.updateExpression(this, null);
468
469         WebInspector.ObjectPropertyTreeElement.prototype.editingCancelled.call(this, element, context);
470     },
471
472     /**
473      * @override
474      * @param {string} expression
475      */
476     applyExpression: function(expression)
477     {
478         expression = expression.trim();
479         this.property.name = expression || null;
480         this.treeOutline.section.updateExpression(this, expression);
481     },
482
483     __proto__: WebInspector.ObjectPropertyTreeElement.prototype
484 }
485
486
487 /**
488  * @constructor
489  * @extends {WebInspector.ObjectPropertyTreeElement}
490  * @param {!WebInspector.RemoteObjectProperty} property
491  */
492 WebInspector.WatchedPropertyTreeElement = function(property)
493 {
494     WebInspector.ObjectPropertyTreeElement.call(this, property);
495 }
496
497 WebInspector.WatchedPropertyTreeElement.prototype = {
498     onattach: function()
499     {
500         WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
501         if (this.hasChildren && this.propertyPath() in this.treeOutline.section._expandedProperties)
502             this.expand();
503     },
504
505     onexpand: function()
506     {
507         WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
508         this.treeOutline.section._expandedProperties[this.propertyPath()] = true;
509     },
510
511     oncollapse: function()
512     {
513         WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
514         delete this.treeOutline.section._expandedProperties[this.propertyPath()];
515     },
516
517     __proto__: WebInspector.ObjectPropertyTreeElement.prototype
518 }