Web Inspector: REGRESSION(r225569): CSSStyleDetailsSidebarPanel no longer exists
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / SpreadsheetCSSStyleDeclarationSection.js
1 /*
2  * Copyright (C) 2017 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.SpreadsheetCSSStyleDeclarationSection = class SpreadsheetCSSStyleDeclarationSection extends WI.View
27 {
28     constructor(delegate, style)
29     {
30         console.assert(style instanceof WI.CSSStyleDeclaration, style);
31
32         let element = document.createElement("section");
33         element.classList.add("spreadsheet-css-declaration");
34         super(element);
35
36         this._delegate = delegate || null;
37         this._style = style;
38         this._propertiesEditor = null;
39         this._selectorElements = [];
40         this._mediaElements = [];
41         this._filterText = null;
42         this._shouldFocusSelectorElement = false;
43         this._wasFocused = false;
44     }
45
46     // Public
47
48     get style() { return this._style; }
49
50     get propertiesEditor() { return this._propertiesEditor; }
51
52     get editable()
53     {
54         return this._style.editable;
55     }
56
57     initialLayout()
58     {
59         super.initialLayout();
60
61         this._headerElement = document.createElement("div");
62         this._headerElement.classList.add("header");
63
64         this._originElement = document.createElement("span");
65         this._originElement.classList.add("origin");
66         this._headerElement.append(this._originElement);
67
68         this._selectorElement = document.createElement("span");
69         this._selectorElement.classList.add("selector");
70         this._selectorElement.addEventListener("mouseenter", this._highlightNodesWithSelector.bind(this));
71         this._selectorElement.addEventListener("mouseleave", this._hideDOMNodeHighlight.bind(this));
72         this._headerElement.append(this._selectorElement);
73
74         this._openBrace = document.createElement("span");
75         this._openBrace.classList.add("open-brace");
76         this._openBrace.textContent = " {";
77         this._headerElement.append(this._openBrace);
78
79         if (this._style.selectorEditable) {
80             this._selectorTextField = new WI.SpreadsheetSelectorField(this, this._selectorElement);
81             this._selectorElement.tabIndex = 0;
82             this._selectorElement.addEventListener("focus", () => this._headerElement.classList.add("editing-selector"));
83             this._selectorElement.addEventListener("blur", () => this._headerElement.classList.remove("editing-selector"));
84         }
85
86         this._propertiesEditor = new WI.SpreadsheetCSSStyleDeclarationEditor(this, this._style);
87         this._propertiesEditor.element.classList.add("properties");
88         this._propertiesEditor.addEventListener(WI.SpreadsheetCSSStyleDeclarationEditor.Event.FilterApplied, this._handleEditorFilterApplied, this);
89
90         this._closeBrace = document.createElement("span");
91         this._closeBrace.classList.add("close-brace");
92         this._closeBrace.textContent = "}";
93
94         this._element.append(this._createMediaHeader(), this._headerElement);
95         this.addSubview(this._propertiesEditor);
96         this._propertiesEditor.needsLayout();
97         this._element.append(this._closeBrace);
98
99         if (!this._style.editable)
100             this._element.classList.add("locked");
101         else if (!this._style.ownerRule)
102             this._element.classList.add("selector-locked");
103
104         if (this._style.editable) {
105             this.element.addEventListener("click", this._handleClick.bind(this));
106             this.element.addEventListener("mousedown", this._handleMouseDown.bind(this));
107         }
108     }
109
110     layout()
111     {
112         super.layout();
113
114         this._renderOrigin();
115         this._renderSelector();
116
117         if (this._shouldFocusSelectorElement)
118             this.startEditingRuleSelector();
119     }
120
121     startEditingRuleSelector()
122     {
123         if (!this._selectorElement) {
124             this._shouldFocusSelectorElement = true;
125             return;
126         }
127
128         this._selectorElement.focus();
129         this._shouldFocusSelectorElement = false;
130     }
131
132     highlightProperty(property)
133     {
134         // When navigating from the Computed panel to the Styles panel, the latter
135         // could be empty. Layout all properties so they can be highlighted.
136         if (!this.didInitialLayout)
137             this.updateLayout();
138
139         if (this._propertiesEditor.highlightProperty(property)) {
140             this._element.scrollIntoView();
141             return true;
142         }
143
144         return false;
145     }
146
147     cssStyleDeclarationTextEditorStartEditingRuleSelector()
148     {
149         this.startEditingRuleSelector();
150     }
151
152     spreadsheetSelectorFieldDidChange(direction)
153     {
154         let selectorText = this._selectorElement.textContent.trim();
155
156         if (!selectorText || selectorText === this._style.ownerRule.selectorText)
157             this._discardSelectorChange();
158         else {
159             this._style.ownerRule.singleFireEventListener(WI.CSSRule.Event.SelectorChanged, this._renderSelector, this);
160             this._style.ownerRule.selectorText = selectorText;
161         }
162
163         if (!direction) {
164             // Don't do anything when it's a blur event.
165             return;
166         }
167
168         if (direction === "forward")
169             this._propertiesEditor.startEditingFirstProperty();
170         else if (direction === "backward") {
171             if (typeof this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule === "function")
172                 this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule(this);
173         }
174     }
175
176     spreadsheetSelectorFieldDidDiscard()
177     {
178         this._discardSelectorChange();
179     }
180
181     cssStyleDeclarationEditorStartEditingAdjacentRule(toPreviousRule)
182     {
183         if (!this._delegate)
184             return;
185
186         if (toPreviousRule && typeof this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule === "function")
187             this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule(this);
188         else if (!toPreviousRule && typeof this._delegate.cssStyleDeclarationSectionStartEditingNextRule === "function")
189             this._delegate.cssStyleDeclarationSectionStartEditingNextRule(this);
190     }
191
192     applyFilter(filterText)
193     {
194         this._filterText = filterText;
195
196         if (!this.didInitialLayout)
197             return;
198
199         this._element.classList.remove(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName);
200
201         this._propertiesEditor.applyFilter(this._filterText);
202     }
203
204     // Private
205
206     _discardSelectorChange()
207     {
208         // Re-render selector for syntax highlighting.
209         this._renderSelector();
210     }
211
212     _renderSelector()
213     {
214         this._selectorElement.removeChildren();
215         this._selectorElements = [];
216
217         let appendSelector = (selector, matched) => {
218             console.assert(selector instanceof WI.CSSSelector);
219
220             let selectorElement = this._selectorElement.appendChild(document.createElement("span"));
221             selectorElement.textContent = selector.text;
222
223             if (matched)
224                 selectorElement.classList.add(WI.SpreadsheetCSSStyleDeclarationSection.MatchedSelectorElementStyleClassName);
225
226             if (selector.specificity) {
227                 let specificity = selector.specificity.map((number) => number.toLocaleString());
228                 let tooltip = WI.UIString("Specificity: (%d, %d, %d)").format(...specificity);
229                 if (selector.dynamic) {
230                     tooltip += "\n";
231                     if (this._style.inherited)
232                         tooltip += WI.UIString("Dynamically calculated for the parent element");
233                     else
234                         tooltip += WI.UIString("Dynamically calculated for the selected element");
235                 }
236                 selectorElement.title = tooltip;
237             } else if (selector.dynamic) {
238                 let tooltip = WI.UIString("Specificity: No value for selected element");
239                 tooltip += "\n";
240                 tooltip += WI.UIString("Dynamically calculated for the selected element and did not match");
241                 selectorElement.title = tooltip;
242             }
243
244             this._selectorElements.push(selectorElement);
245         };
246
247         let appendSelectorTextKnownToMatch = (selectorText) => {
248             let selectorElement = this._selectorElement.appendChild(document.createElement("span"));
249             selectorElement.textContent = selectorText;
250             selectorElement.classList.add(WI.SpreadsheetCSSStyleDeclarationSection.MatchedSelectorElementStyleClassName);
251         };
252
253         switch (this._style.type) {
254         case WI.CSSStyleDeclaration.Type.Rule:
255             console.assert(this._style.ownerRule);
256
257             var selectors = this._style.ownerRule.selectors;
258             var matchedSelectorIndices = this._style.ownerRule.matchedSelectorIndices;
259             var alwaysMatch = !matchedSelectorIndices.length;
260             if (selectors.length) {
261                 let hasMatchingPseudoElementSelector = false;
262                 for (let i = 0; i < selectors.length; ++i) {
263                     appendSelector(selectors[i], alwaysMatch || matchedSelectorIndices.includes(i));
264                     if (i < selectors.length - 1)
265                         this._selectorElement.append(", ");
266
267                     if (matchedSelectorIndices.includes(i) && selectors[i].isPseudoElementSelector())
268                         hasMatchingPseudoElementSelector = true;
269                 }
270                 this._element.classList.toggle("pseudo-element-selector", hasMatchingPseudoElementSelector);
271             } else
272                 appendSelectorTextKnownToMatch(this._style.ownerRule.selectorText);
273
274             break;
275
276         case WI.CSSStyleDeclaration.Type.Inline:
277             this._selectorElement.textContent = WI.UIString("Style Attribute");
278             this._selectorElement.classList.add("style-attribute");
279             break;
280
281         case WI.CSSStyleDeclaration.Type.Attribute:
282             appendSelectorTextKnownToMatch(this._style.node.displayName);
283             break;
284         }
285
286         if (this._filterText)
287             this.applyFilter(this._filterText);
288     }
289
290     _renderOrigin()
291     {
292         this._originElement.removeChildren();
293
294         switch (this._style.type) {
295         case WI.CSSStyleDeclaration.Type.Rule:
296             console.assert(this._style.ownerRule);
297
298             if (this._style.ownerRule.sourceCodeLocation) {
299                 let options = {
300                     dontFloat: true,
301                     ignoreNetworkTab: true,
302                     ignoreSearchTab: true,
303                 };
304
305                 if (this._style.ownerStyleSheet.isInspectorStyleSheet()) {
306                     options.nameStyle = WI.SourceCodeLocation.NameStyle.None;
307                     options.prefix = WI.UIString("Inspector Style Sheet") + ":";
308                 }
309
310                 let sourceCodeLink = WI.createSourceCodeLocationLink(this._style.ownerRule.sourceCodeLocation, options);
311                 this._originElement.appendChild(sourceCodeLink);
312             } else {
313                 let originString = "";
314
315                 switch (this._style.ownerRule.type) {
316                 case WI.CSSStyleSheet.Type.Author:
317                     originString = WI.UIString("Author Stylesheet");
318                     break;
319
320                 case WI.CSSStyleSheet.Type.User:
321                     originString = WI.UIString("User Stylesheet");
322                     break;
323
324                 case WI.CSSStyleSheet.Type.UserAgent:
325                     originString = WI.UIString("User Agent Stylesheet");
326                     break;
327
328                 case WI.CSSStyleSheet.Type.Inspector:
329                     originString = WI.UIString("Web Inspector");
330                     break;
331                 }
332
333                 console.assert(originString);
334                 if (originString)
335                     this._originElement.append(originString);
336
337                 if (!this._style.editable) {
338                     let styleTitle = "";
339                     if (this._style.ownerRule && this._style.ownerRule.type === WI.CSSStyleSheet.Type.UserAgent)
340                         styleTitle = WI.UIString("User Agent Stylesheet");
341                     else
342                         styleTitle = WI.UIString("Style rule");
343
344                     this._originElement.title = WI.UIString("%s cannot be modified").format(styleTitle);
345                 }
346             }
347             break;
348
349         case WI.CSSStyleDeclaration.Type.Attribute:
350             this._originElement.append(WI.UIString("HTML Attributes"));
351             break;
352         }
353     }
354
355     _createMediaHeader()
356     {
357         let mediaList = this._style.mediaList;
358         if (!mediaList.length || (mediaList.length === 1 && (mediaList[0].text === "all" || mediaList[0].text === "screen")))
359             return "";
360
361         let mediaElement = document.createElement("div");
362         mediaElement.classList.add("header-media");
363
364         let mediaLabel = mediaElement.appendChild(document.createElement("div"));
365         mediaLabel.className = "media-label";
366         mediaLabel.append("@media ");
367
368         this._mediaElements = mediaList.map((media, i) => {
369             if (i)
370                 mediaLabel.append(", ");
371
372             let span = mediaLabel.appendChild(document.createElement("span"));
373             span.textContent = media.text;
374             return span;
375         });
376
377         return mediaElement;
378     }
379
380     _handleMouseDown(event)
381     {
382         this._wasFocused = this._propertiesEditor.isFocused();
383     }
384
385     _handleClick(event)
386     {
387         if (this._wasFocused)
388             return;
389
390         event.stop();
391
392         if (event.target.classList.contains(WI.SpreadsheetStyleProperty.StyleClassName)) {
393             let propertyIndex = parseInt(event.target.dataset.propertyIndex);
394             this._propertiesEditor.addBlankProperty(propertyIndex + 1);
395             return;
396         }
397
398         if (event.target === this._headerElement || event.target === this._openBrace) {
399             this._propertiesEditor.addBlankProperty(0);
400             return;
401         }
402
403         if (event.target === this._element || event.target === this._closeBrace) {
404             const appendAfterLast = -1;
405             this._propertiesEditor.addBlankProperty(appendAfterLast);
406         }
407     }
408
409     _highlightNodesWithSelector()
410     {
411         if (!this._style.ownerRule) {
412             WI.domTreeManager.highlightDOMNode(this._style.node.id);
413             return;
414         }
415
416         let selectorText = this._selectorElement.textContent.trim();
417         WI.domTreeManager.highlightSelector(selectorText, this._style.node.ownerDocument.frameIdentifier);
418     }
419
420     _hideDOMNodeHighlight()
421     {
422         WI.domTreeManager.hideDOMNodeHighlight();
423     }
424
425     _handleEditorFilterApplied(event)
426     {
427         let matchesMedia = false;
428         for (let mediaElement of this._mediaElements) {
429             mediaElement.classList.remove(WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName);
430
431             if (mediaElement.textContent.includes(this._filterText)) {
432                 mediaElement.classList.add(WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName);
433                 matchesMedia = true;
434             }
435         }
436
437         let matchesSelector = false;
438         for (let selectorElement of this._selectorElements) {
439             selectorElement.classList.remove(WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName);
440
441             if (selectorElement.textContent.includes(this._filterText)) {
442                 selectorElement.classList.add(WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName);
443                 matchesSelector = true;
444             }
445         }
446
447         let matches = event.data.matches || matchesMedia || matchesSelector;
448         if (!matches)
449             this._element.classList.add(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName);
450
451         this.dispatchEventToListeners(WI.SpreadsheetCSSStyleDeclarationSection.Event.FilterApplied, {matches});
452     }
453 };
454
455 WI.SpreadsheetCSSStyleDeclarationSection.Event = {
456     FilterApplied: "spreadsheet-css-style-declaration-section-filter-applied",
457 };
458
459 WI.SpreadsheetCSSStyleDeclarationSection.MatchedSelectorElementStyleClassName = "matched";