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