Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / sources / WatchExpressionsSidebarPane.js
blob581d01f5f2e924e51870f33b4977cdd79432582f
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  */
31 /**
32  * @constructor
33  * @extends {WebInspector.SidebarPane}
34  */
35 WebInspector.WatchExpressionsSidebarPane = function()
37     WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch"));
38     this.registerRequiredCSS("components/objectValue.css");
40     this._requiresUpdate = true;
41     /** @type {!Array.<!WebInspector.WatchExpression>} */
42     this._watchExpressions = [];
43     this._watchExpressionsSetting = WebInspector.settings.createLocalSetting("watchExpressions", []);
45     var addButton = new WebInspector.ToolbarButton(WebInspector.UIString("Add expression"), "add-toolbar-item");
46     addButton.addEventListener("click", this._addButtonClicked.bind(this));
47     this.toolbar().appendToolbarItem(addButton);
48     var refreshButton = new WebInspector.ToolbarButton(WebInspector.UIString("Refresh"), "refresh-toolbar-item");
49     refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this));
50     this.toolbar().appendToolbarItem(refreshButton);
52     this._bodyElement = this.element.createChild("div", "vbox watch-expressions");
53     this._bodyElement.addEventListener("contextmenu", this._contextMenu.bind(this), false);
55     WebInspector.context.addFlavorChangeListener(WebInspector.ExecutionContext, this.refreshExpressions, this);
58 WebInspector.WatchExpressionsSidebarPane.prototype = {
59     wasShown: function()
60     {
61         this._refreshExpressionsIfNeeded();
62     },
64     refreshExpressions: function()
65     {
66         this._requiresUpdate = true;
67         this._refreshExpressionsIfNeeded();
68     },
70     /**
71      * @param {string} expressionString
72      */
73     addExpression: function(expressionString)
74     {
75         this.expand();
76         if (this._requiresUpdate) {
77             this._rebuildWatchExpressions();
78             delete this._requiresUpdate;
79         }
80         this._createWatchExpression(expressionString);
81         this._saveExpressions();
82     },
84     expandIfNecessary: function()
85     {
86         if (this._watchExpressionsSetting.get().length)
87             this.expand();
88     },
90     _saveExpressions: function()
91     {
92         var toSave = [];
93         for (var i = 0; i < this._watchExpressions.length; i++)
94             if (this._watchExpressions[i].expression())
95                 toSave.push(this._watchExpressions[i].expression());
97         this._watchExpressionsSetting.set(toSave);
98     },
100     _refreshExpressionsIfNeeded: function()
101     {
102         if (this._requiresUpdate && this.isShowing()) {
103             this._rebuildWatchExpressions();
104             delete this._requiresUpdate;
105         } else
106             this._requiresUpdate = true;
107     },
109     /**
110      * @param {!WebInspector.Event=} event
111      */
112     _addButtonClicked: function(event)
113     {
114         if (event)
115             event.consume(true);
116         this.expand();
117         this._createWatchExpression(null).startEditing();
118     },
120     /**
121      * @param {!WebInspector.Event} event
122      */
123     _refreshButtonClicked: function(event)
124     {
125         event.consume();
126         this.refreshExpressions();
127     },
129     _rebuildWatchExpressions: function()
130     {
131         this._bodyElement.removeChildren();
132         this._watchExpressions = [];
133         this._emptyElement = this._bodyElement.createChild("div", "info");
134         this._emptyElement.textContent = WebInspector.UIString("No Watch Expressions");
135         var watchExpressionStrings = this._watchExpressionsSetting.get();
136         for (var i = 0; i < watchExpressionStrings.length; ++i) {
137             var expression = watchExpressionStrings[i];
138             if (!expression)
139                 continue;
141             this._createWatchExpression(expression);
142         }
143     },
145     /**
146      * @param {?string} expression
147      * @return {!WebInspector.WatchExpression}
148      */
149     _createWatchExpression: function(expression)
150     {
151         this._emptyElement.classList.add("hidden");
152         var watchExpression = new WebInspector.WatchExpression(expression);
153         watchExpression.addEventListener(WebInspector.WatchExpression.Events.ExpressionUpdated, this._watchExpressionUpdated.bind(this));
154         this._bodyElement.appendChild(watchExpression.element());
155         this._watchExpressions.push(watchExpression);
156         return watchExpression;
157     },
159     /**
160      * @param {!WebInspector.Event} event
161      */
162     _watchExpressionUpdated: function(event)
163     {
164         var watchExpression = /** @type {!WebInspector.WatchExpression} */ (event.target);
165         if (!watchExpression.expression()) {
166             this._watchExpressions.remove(watchExpression);
167             this._bodyElement.removeChild(watchExpression.element());
168             this._emptyElement.classList.toggle("hidden", !!this._watchExpressions.length);
169         }
171         this._saveExpressions();
172     },
174     /**
175      * @param {!Event} event
176      */
177     _contextMenu: function(event)
178     {
179         var contextMenu = new WebInspector.ContextMenu(event);
180         this._populateContextMenu(contextMenu, event);
181         contextMenu.show();
182     },
184     /**
185      * @param {!WebInspector.ContextMenu} contextMenu
186      * @param {!Event} event
187      */
188     _populateContextMenu: function(contextMenu, event)
189     {
190         var isEditing = false;
191         for (var watchExpression of this._watchExpressions)
192            isEditing |=  watchExpression.isEditing();
194         if (!isEditing)
195             contextMenu.appendItem(WebInspector.UIString.capitalize("Add ^watch ^expression"), this._addButtonClicked.bind(this));
197         if (this._watchExpressions.length > 1)
198             contextMenu.appendItem(WebInspector.UIString.capitalize("Delete ^all ^watch ^expressions"), this._deleteAllButtonClicked.bind(this));
200         for (var watchExpression of this._watchExpressions)
201             if (watchExpression.element().containsEventPoint(event))
202                 watchExpression._populateContextMenu(contextMenu, event);
203     },
205     _deleteAllButtonClicked: function()
206     {
207         this._watchExpressions = [];
208         this._saveExpressions();
209         this._rebuildWatchExpressions();
210     },
212     __proto__: WebInspector.SidebarPane.prototype
216  * @constructor
217  * @extends {WebInspector.Object}
218  * @param {?string} expression
219  */
220 WebInspector.WatchExpression = function(expression)
222     this._expression = expression;
223     this._element = createElementWithClass("div", "watch-expression monospace");
224     this._editing = false;
226     this._createWatchExpression(null, false);
227     this.update();
230 WebInspector.WatchExpression._watchObjectGroupId = "watch-group";
232 WebInspector.WatchExpression.Events = {
233     ExpressionUpdated: "ExpressionUpdated"
236 WebInspector.WatchExpression.prototype = {
238     /**
239      * @return {!Element}
240      */
241     element: function()
242     {
243         return this._element;
244     },
246     /**
247      * @return {?string}
248      */
249     expression: function()
250     {
251         return this._expression;
252     },
254     update: function()
255     {
256         var currentExecutionContext = WebInspector.context.flavor(WebInspector.ExecutionContext);
257         if (currentExecutionContext && this._expression)
258             currentExecutionContext.evaluate(this._expression, WebInspector.WatchExpression._watchObjectGroupId, false, true, false, false, this._createWatchExpression.bind(this));
259     },
261     startEditing: function()
262     {
263         this._editing = true;
264         this._element.removeChild(this._objectPresentationElement);
265         var newDiv = this._element.createChild("div");
266         newDiv.textContent = this._nameElement.textContent;
267         this._textPrompt = new WebInspector.ObjectPropertyPrompt();
268         this._textPrompt.renderAsBlock();
269         var proxyElement = this._textPrompt.attachAndStartEditing(newDiv, this._finishEditing.bind(this));
270         proxyElement.classList.add("watch-expression-text-prompt-proxy");
271         proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this), false);
272         this._element.getComponentSelection().setBaseAndExtent(newDiv, 0, newDiv, 1);
273     },
275     /**
276      * @return {boolean}
277      */
278     isEditing: function()
279     {
280         return !!this._editing;
281     },
283     /**
284      * @param {!Event} event
285      * @param {boolean=} canceled
286      */
287     _finishEditing: function(event, canceled)
288     {
289         if (event)
290             event.consume(true);
292         this._editing = false;
293         this._textPrompt.detach();
294         var newExpression = canceled ? this._expression : this._textPrompt.text();
295         delete this._textPrompt;
296         this._element.removeChildren();
297         this._element.appendChild(this._objectPresentationElement);
298         this._updateExpression(newExpression);
299     },
301     /**
302      * @param {!Event} event
303      */
304     _dblClickOnWatchExpression: function(event)
305     {
306         event.consume();
307         if (!this.isEditing())
308             this.startEditing();
309     },
311     /**
312      * @param {?string} newExpression
313      */
314     _updateExpression: function(newExpression)
315     {
316         this._expression = newExpression;
317         this.update();
318         this.dispatchEventToListeners(WebInspector.WatchExpression.Events.ExpressionUpdated);
319     },
321     /**
322      * @param {!Event} event
323      */
324     _deleteWatchExpression: function(event)
325     {
326         event.consume(true);
327         this._updateExpression(null);
328     },
330     /**
331      * @param {?WebInspector.RemoteObject} result
332      * @param {boolean} wasThrown
333      */
334     _createWatchExpression: function(result, wasThrown)
335     {
336         this._result = result;
338         var headerElement= createElementWithClass("div", "watch-expression-header");
339         var deleteButton = headerElement.createChild("button", "watch-expression-delete-button");
340         deleteButton.title = WebInspector.UIString("Delete watch expression");
341         deleteButton.addEventListener("click", this._deleteWatchExpression.bind(this), false);
343         var titleElement = headerElement.createChild("div", "watch-expression-title");
344         this._nameElement = WebInspector.ObjectPropertiesSection.createNameElement(this._expression);
345         if (wasThrown || !result) {
346             this._valueElement = createElementWithClass("span", "error-message value");
347             titleElement.classList.add("dimmed");
348             this._valueElement.textContent = WebInspector.UIString("<not available>");
349         } else {
350             this._valueElement = WebInspector.ObjectPropertiesSection.createValueElementWithCustomSupport(result, wasThrown, titleElement);
351         }
352         var separatorElement = createElementWithClass("span", "watch-expressions-separator");
353         separatorElement.textContent = ": ";
354         titleElement.appendChildren(this._nameElement, separatorElement, this._valueElement);
356         this._element.removeChildren();
357         this._objectPropertiesSection = null;
358         if (!wasThrown && result && result.hasChildren && !result.customPreview()) {
359             this._objectPropertiesSection = new WebInspector.ObjectPropertiesSection(result, headerElement);
360             this._objectPresentationElement = this._objectPropertiesSection.element;
361             var objectTreeElement = this._objectPropertiesSection.objectTreeElement();
362             objectTreeElement.toggleOnClick = false;
363             objectTreeElement.listItemElement.addEventListener("click", this._onSectionClick.bind(this), false);
364             objectTreeElement.listItemElement.addEventListener("dblclick", this._dblClickOnWatchExpression.bind(this));
365         } else {
366             this._objectPresentationElement = headerElement;
367             this._objectPresentationElement.addEventListener("dblclick", this._dblClickOnWatchExpression.bind(this));
368         }
370         this._element.appendChild(this._objectPresentationElement);
371     },
373     /**
374      * @param {!Event} event
375      */
376     _onSectionClick: function(event)
377     {
378         event.consume(true);
379         if (event.detail == 1) {
380             this._preventClickTimeout = setTimeout(handleClick.bind(this), 333);
381         } else {
382             clearTimeout(this._preventClickTimeout);
383             delete this._preventClickTimeout;
384         }
386         /**
387          * @this {WebInspector.WatchExpression}
388          */
389         function handleClick()
390         {
391             if (!this._objectPropertiesSection)
392                 return;
394             var objectTreeElement = this._objectPropertiesSection.objectTreeElement();
395             if (objectTreeElement.expanded)
396                 objectTreeElement.collapse();
397             else
398                 objectTreeElement.expand();
399         }
400     },
402     /**
403      * @param {!Event} event
404      */
405     _promptKeyDown: function(event)
406     {
407         if (isEnterKey(event) || isEscKey(event))
408             this._finishEditing(event, isEscKey(event));
409     },
411     /**
412      * @param {!WebInspector.ContextMenu} contextMenu
413      * @param {!Event} event
414      */
415     _populateContextMenu: function(contextMenu, event)
416     {
417         if (!this.isEditing())
418             contextMenu.appendItem(WebInspector.UIString.capitalize("Delete ^watch ^expression"), this._updateExpression.bind(this, null));
420         if (!this.isEditing() && this._result && (this._result.type === "number" || this._result.type === "string"))
421             contextMenu.appendItem(WebInspector.UIString.capitalize("Copy ^value"), this._copyValueButtonClicked.bind(this));
423         if (this._valueElement.containsEventPoint(event))
424             contextMenu.appendApplicableItems(this._result);
425     },
427     _copyValueButtonClicked: function()
428     {
429         InspectorFrontendHost.copyText(this._valueElement.textContent);
430     },
432     __proto__: WebInspector.Object.prototype