Web Inspector: Display Named Flows in the Tabbed Pane of the "CSS Named Flows" Drawer
[WebKit-https.git] / Source / WebCore / inspector / front-end / CSSNamedFlowCollectionsView.js
1 /*
2  * Copyright (C) 2012 Adobe Systems Incorporated. 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  *
8  * 1. Redistributions of source code must retain the above
9  *    copyright notice, this list of conditions and the following
10  *    disclaimer.
11  * 2. Redistributions in binary form must reproduce the above
12  *    copyright notice, this list of conditions and the following
13  *    disclaimer in the documentation and/or other materials
14  *    provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27  * OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 /**
31  * @constructor
32  * @extends {WebInspector.SplitView}
33  */
34 WebInspector.CSSNamedFlowCollectionsView = function()
35 {
36     WebInspector.SplitView.call(this, WebInspector.SplitView.SidebarPosition.Left);
37     this.registerRequiredCSS("cssNamedFlows.css");
38
39     this._namedFlows = {};
40     this._contentNodes = {};
41     this._regionNodes = {};
42
43     this.element.addStyleClass("css-named-flow-collections-view");
44     this.element.addStyleClass("fill");
45
46     this._statusElement = document.createElement("span");
47     this._statusElement.textContent = WebInspector.UIString("CSS Named Flows");
48
49     var sidebarHeader = this._leftElement.createChild("div", "tabbed-pane-header selected sidebar-header")
50     var tab = sidebarHeader.createChild("div", "tabbed-pane-header-tab");
51     tab.createChild("span", "tabbed-pane-header-tab-title").textContent = WebInspector.UIString("CSS Named Flows");
52
53     this._sidebarContentElement = this._leftElement.createChild("div", "sidebar-content outline-disclosure");
54     this._flowListElement = this._sidebarContentElement.createChild("ol");
55     this._flowTree = new TreeOutline(this._flowListElement);
56
57     this._emptyElement = document.createElement("div");
58     this._emptyElement.addStyleClass("info");
59     this._emptyElement.textContent = WebInspector.UIString("No CSS Named Flows");
60
61     this._tabbedPane = new WebInspector.TabbedPane();
62     this._tabbedPane.closeableTabs = true;
63     this._tabbedPane.show(this._rightElement);
64 }
65
66 WebInspector.CSSNamedFlowCollectionsView.prototype = {
67     showInDrawer: function()
68     {
69         WebInspector.showViewInDrawer(this._statusElement, this);
70     },
71
72     reset: function()
73     {
74         if (!this._document)
75             return;
76
77         WebInspector.cssModel.getNamedFlowCollectionAsync(this._document.id, this._resetNamedFlows.bind(this));
78     },
79
80     /**
81      * @param {WebInspector.DOMDocument} document
82      */
83     _setDocument: function(document)
84     {
85         this._document = document;
86         this.reset();
87     },
88
89     /**
90      * @param {WebInspector.Event} event
91      */
92     _documentUpdated: function(event)
93     {
94         var document = /** @type {WebInspector.DOMDocument} */ event.data;
95         this._setDocument(document);
96     },
97
98     /**
99      * @param {boolean} hasContent
100      */
101     _setSidebarHasContent: function(hasContent)
102     {
103         if (hasContent) {
104             if (!this._emptyElement.parentNode)
105                 return;
106
107             this._sidebarContentElement.removeChild(this._emptyElement);
108             this._sidebarContentElement.appendChild(this._flowListElement);
109         } else {
110             if (!this._flowListElement.parentNode)
111                 return;
112
113             this._sidebarContentElement.removeChild(this._flowListElement);
114             this._sidebarContentElement.appendChild(this._emptyElement);
115         }
116     },
117
118     /**
119      * @param {WebInspector.NamedFlow} flow
120      */
121     _appendNamedFlow: function(flow)
122     {
123         var flowHash = this._hashNamedFlow(flow.documentNodeId, flow.name);
124         var flowContainer = { flow: flow, flowHash: flowHash };
125
126         for (var i = 0; i < flow.content.length; ++i)
127             this._contentNodes[flow.content[i]] = flowHash;
128         for (var i = 0; i < flow.regions.length; ++i)
129             this._regionNodes[flow.regions[i].nodeId] = flowHash;
130
131         var flowTreeItem = new WebInspector.FlowTreeElement(flowContainer);
132         flowTreeItem.onselect = this._selectNamedFlowTab.bind(this, flowHash);
133
134         flowContainer.flowTreeItem = flowTreeItem;
135         this._namedFlows[flowHash] = flowContainer;
136
137         if (!this._flowTree.children.length)
138             this._setSidebarHasContent(true);
139         this._flowTree.appendChild(flowTreeItem);
140     },
141
142     /**
143      * @param {string} flowHash
144      */
145     _removeNamedFlow: function(flowHash)
146     {
147         var flowContainer = this._namedFlows[flowHash];
148
149         if (this._tabbedPane._tabsById[flowHash])
150             this._tabbedPane.closeTab(flowHash);
151         this._flowTree.removeChild(flowContainer.flowTreeItem);
152
153         var flow = flowContainer.flow;
154         for (var i = 0; i < flow.content.length; ++i)
155             delete this._contentNodes[flow.content[i]];
156         for (var i = 0; i < flow.regions.length; ++i)
157             delete this._regionNodes[flow.regions[i].nodeId];
158
159         delete this._namedFlows[flowHash];
160
161         if (!this._flowTree.children.length)
162             this._setSidebarHasContent(false);
163     },
164
165     /**
166      * @param {WebInspector.NamedFlow} flow
167      */
168     _updateNamedFlow: function(flow)
169     {
170         var flowHash = this._hashNamedFlow(flow.documentNodeId, flow.name);
171         var flowContainer = this._namedFlows[flowHash];
172
173         if (!flowContainer)
174             return;
175
176         var oldFlow = flowContainer.flow;
177         flowContainer.flow = flow;
178
179         for (var i = 0; i < oldFlow.content.length; ++i)
180             delete this._contentNodes[oldFlow.content[i]];
181         for (var i = 0; i < oldFlow.regions.length; ++i)
182             delete this._regionNodes[oldFlow.regions[i].nodeId];
183
184         for (var i = 0; i < flow.content.length; ++i)
185             this._contentNodes[flow.content[i]] = flowHash;
186         for (var i = 0; i < flow.regions.length; ++i)
187             this._regionNodes[flow.regions[i].nodeId] = flowHash;
188
189         flowContainer.flowTreeItem.setOverset(flow.overset);
190
191         if (flowContainer.flowView)
192             flowContainer.flowView.flow = flow;
193     },
194
195     /**
196      * @param {WebInspector.NamedFlowCollection} namedFlowCollection
197      */
198     _resetNamedFlows: function(namedFlowCollection)
199     {
200         for (var flowHash in this._namedFlows)
201             this._removeNamedFlow(flowHash);
202
203         var namedFlows = namedFlowCollection.namedFlowMap;
204         for (var flowName in namedFlows)
205             this._appendNamedFlow(namedFlows[flowName]);
206
207         if (!this._flowTree.children.length)
208             this._setSidebarHasContent(false);
209         else
210             this._showNamedFlowForNode(WebInspector.panel("elements").treeOutline.selectedDOMNode());
211     },
212
213     /**
214      * @param {WebInspector.Event} event
215      */
216     _namedFlowCreated: function(event)
217     {
218         // FIXME: We only have support for Named Flows in the main document.
219         if (event.data.documentNodeId !== this._document.id)
220             return;
221
222         var flow = /** @type {WebInspector.NamedFlow} */ event.data;
223         this._appendNamedFlow(flow);
224     },
225
226     /**
227      * @param {WebInspector.Event} event
228      */
229     _namedFlowRemoved: function(event)
230     {
231         // FIXME: We only have support for Named Flows in the main document.
232         if (event.data.documentNodeId !== this._document.id)
233             return;
234
235         this._removeNamedFlow(this._hashNamedFlow(event.data.documentNodeId, event.data.flowName));
236     },
237
238     /**
239      * @param {WebInspector.Event} event
240      */
241     _regionLayoutUpdated: function(event)
242     {
243         // FIXME: We only have support for Named Flows in the main document.
244         if (event.data.documentNodeId !== this._document.id)
245             return;
246
247         var flow = /** @type {WebInspector.NamedFlow} */ event.data;
248         this._updateNamedFlow(flow);
249     },
250
251     /**
252      * @param {DOMAgent.NodeId} documentNodeId
253      * @param {string} flowName
254      */
255     _hashNamedFlow: function(documentNodeId, flowName)
256     {
257         return documentNodeId + "|" + flowName;
258     },
259
260     /**
261      * @param {string} flowHash
262      */
263     _showNamedFlow: function(flowHash)
264     {
265         this._selectNamedFlowInSidebar(flowHash);
266         this._selectNamedFlowTab(flowHash);
267     },
268
269     /**
270      * @param {string} flowHash
271      */
272     _selectNamedFlowInSidebar: function(flowHash)
273     {
274         this._namedFlows[flowHash].flowTreeItem.select(true);
275     },
276
277     /**
278      * @param {string} flowHash
279      */
280     _selectNamedFlowTab: function(flowHash)
281     {
282         var flowContainer = this._namedFlows[flowHash];
283
284         if (this._tabbedPane.selectedTabId === flowHash)
285             return;
286
287         if (!this._tabbedPane.selectTab(flowHash)) {
288             if (!flowContainer.flowView)
289                 flowContainer.flowView = new WebInspector.CSSNamedFlowView(flowContainer.flow);
290
291             this._tabbedPane.appendTab(flowHash, flowContainer.flow.name, flowContainer.flowView);
292             this._tabbedPane.selectTab(flowHash);
293         }
294     },
295
296     /**
297      * @param {WebInspector.Event} event
298      */
299     _selectedNodeChanged: function(event)
300     {
301         var node = /** @type {WebInspector.DOMNode} */ event.data;
302         this._showNamedFlowForNode(node);
303     },
304
305     /**
306      * @param {WebInspector.Event} event
307      */
308     _tabSelected: function(event)
309     {
310         this._selectNamedFlowInSidebar(event.data.tabId);
311     },
312
313     /**
314      * @param {WebInspector.Event} event
315      */
316     _tabClosed: function(event)
317     {
318         this._namedFlows[event.data.tabId].flowTreeItem.deselect();
319     },
320
321     /**
322      * @param {?WebInspector.DOMNode} node
323      */
324     _showNamedFlowForNode: function(node)
325     {
326         if (!node)
327             return;
328
329         if (this._regionNodes[node.id]) {
330             this._showNamedFlow(this._regionNodes[node.id]);
331             return;
332         }
333
334         while (node) {
335             if (this._contentNodes[node.id]) {
336                 this._showNamedFlow(this._contentNodes[node.id]);
337                 return;
338             }
339
340             node = node.parentNode;
341         }
342     },
343
344     wasShown: function()
345     {
346         WebInspector.SplitView.prototype.wasShown.call(this);
347
348         WebInspector.domAgent.requestDocument(this._setDocument.bind(this));
349
350         WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdated, this);
351
352         WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.NamedFlowCreated, this._namedFlowCreated, this);
353         WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.NamedFlowRemoved, this._namedFlowRemoved, this);
354         WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.RegionLayoutUpdated, this._regionLayoutUpdated, this);
355
356         WebInspector.panel("elements").treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
357
358         this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
359         this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._tabClosed, this);
360     },
361
362     willHide: function()
363     {
364         WebInspector.domAgent.removeEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdated, this);
365
366         WebInspector.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.NamedFlowCreated, this._namedFlowCreated, this);
367         WebInspector.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.NamedFlowRemoved, this._namedFlowRemoved, this);
368         WebInspector.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.RegionLayoutUpdated, this._regionLayoutUpdated, this);
369
370         WebInspector.panel("elements").treeOutline.removeEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
371
372         this._tabbedPane.removeEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
373         this._tabbedPane.removeEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._tabClosed, this);
374     }
375 }
376
377 WebInspector.CSSNamedFlowCollectionsView.prototype.__proto__ = WebInspector.SplitView.prototype;
378
379 /**
380  * @constructor
381  * @extends {TreeElement}
382  */
383 WebInspector.FlowTreeElement = function(flowContainer)
384 {
385     var container = document.createElement("div");
386     container.createChild("div", "selection");
387     container.createChild("span", "title").createChild("span").textContent = flowContainer.flow.name;
388
389     TreeElement.call(this, container, flowContainer, false);
390
391     this._overset = false;
392     this.setOverset(flowContainer.flow.overset);
393 }
394
395 WebInspector.FlowTreeElement.prototype = {
396     /**
397      * @param {boolean} newOverset
398      */
399     setOverset: function(newOverset)
400     {
401         if (this._overset === newOverset)
402             return;
403
404         if (newOverset) {
405             this.title.addStyleClass("named-flow-overflow");
406             this.tooltip = WebInspector.UIString("Overflows.");
407         } else {
408             this.title.removeStyleClass("named-flow-overflow");
409             this.tooltip = "";
410         }
411
412         this._overset = newOverset;
413     }
414 }
415
416 WebInspector.FlowTreeElement.prototype.__proto__ = TreeElement.prototype;