Add support for window.open to WK1BrowserWindowController
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / CSSStyleDeclarationSection.js
1 /*
2  * Copyright (C) 2013 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 WebInspector.CSSStyleDeclarationSection = function(delegate, style)
27 {
28     // FIXME: Convert this to a WebInspector.Object subclass, and call super().
29     // WebInspector.Object.call(this);
30
31     this._delegate = delegate || null;
32
33     console.assert(style);
34     this._style = style || null;
35     this._selectorElements = [];
36     this._ruleDisabled = false;
37
38     this._element = document.createElement("div");
39     this._element.className = "style-declaration-section";
40
41     this._headerElement = document.createElement("div");
42     this._headerElement.className = "header";
43
44     this._iconElement = document.createElement("img");
45     this._iconElement.className = "icon";
46     this._headerElement.appendChild(this._iconElement);
47
48     this._selectorElement = document.createElement("span");
49     this._selectorElement.className = "selector";
50     this._selectorElement.setAttribute("spellcheck", "false");
51     this._selectorElement.addEventListener("mouseover", this._handleMouseOver.bind(this));
52     this._selectorElement.addEventListener("mouseout", this._handleMouseOut.bind(this));
53     this._selectorElement.addEventListener("keydown", this._handleKeyDown.bind(this));
54     this._selectorElement.addEventListener("keyup", this._handleKeyUp.bind(this));
55     this._headerElement.appendChild(this._selectorElement);
56
57     this._originElement = document.createElement("span");
58     this._originElement.className = "origin";
59     this._headerElement.appendChild(this._originElement);
60
61     this._propertiesElement = document.createElement("div");
62     this._propertiesElement.className = "properties";
63
64     this._propertiesTextEditor = new WebInspector.CSSStyleDeclarationTextEditor(this, style);
65     this._propertiesElement.appendChild(this._propertiesTextEditor.element);
66
67     this._element.appendChild(this._headerElement);
68     this._element.appendChild(this._propertiesElement);
69
70     var iconClassName;
71     switch (style.type) {
72     case WebInspector.CSSStyleDeclaration.Type.Rule:
73         console.assert(style.ownerRule);
74
75         if (style.inherited)
76             iconClassName = WebInspector.CSSStyleDeclarationSection.InheritedStyleRuleIconStyleClassName;
77         else if (style.ownerRule.type === WebInspector.CSSRule.Type.Author)
78             iconClassName = WebInspector.CSSStyleDeclarationSection.AuthorStyleRuleIconStyleClassName;
79         else if (style.ownerRule.type === WebInspector.CSSRule.Type.User)
80             iconClassName = WebInspector.CSSStyleDeclarationSection.UserStyleRuleIconStyleClassName;
81         else if (style.ownerRule.type === WebInspector.CSSRule.Type.UserAgent)
82             iconClassName = WebInspector.CSSStyleDeclarationSection.UserAgentStyleRuleIconStyleClassName;
83         else if (style.ownerRule.type === WebInspector.CSSRule.Type.Inspector)
84             iconClassName = WebInspector.CSSStyleDeclarationSection.InspectorStyleRuleIconStyleClassName;
85         break;
86
87     case WebInspector.CSSStyleDeclaration.Type.Inline:
88     case WebInspector.CSSStyleDeclaration.Type.Attribute:
89         if (style.inherited)
90             iconClassName = WebInspector.CSSStyleDeclarationSection.InheritedElementStyleRuleIconStyleClassName;
91         else
92             iconClassName = WebInspector.DOMTreeElementPathComponent.DOMElementIconStyleClassName;
93         break;
94     }
95
96     // Matches all situations except for User Agent styles.
97     if (!(style.ownerRule && style.ownerRule.type === WebInspector.CSSRule.Type.UserAgent)) {
98         this._iconElement.classList.add("toggle-able");
99         this._iconElement.title = WebInspector.UIString("Comment All Properties");
100         this._iconElement.addEventListener("click", this._toggleRuleOnOff.bind(this));
101     }
102
103     console.assert(iconClassName);
104     this._element.classList.add(iconClassName);
105
106     if (!style.editable)
107         this._element.classList.add(WebInspector.CSSStyleDeclarationSection.LockedStyleClassName);
108     else if (style.ownerRule) {
109         this._style.ownerRule.addEventListener(WebInspector.CSSRule.Event.SelectorChanged, this._markSelector.bind(this));
110         this._commitSelectorKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Enter, this._commitSelector.bind(this), this._selectorElement);
111         this._selectorElement.addEventListener("blur", this._commitSelector.bind(this));
112     } else
113         this._element.classList.add(WebInspector.CSSStyleDeclarationSection.SelectorLockedStyleClassName);
114
115     if (!WebInspector.CSSStyleDeclarationSection._generatedLockImages) {
116         WebInspector.CSSStyleDeclarationSection._generatedLockImages = true;
117
118         var specifications = {"style-lock-normal": {fillColor: [0, 0, 0, 0.5]}};
119         generateColoredImagesForCSS("Images/Locked.svg", specifications, 8, 10);
120     }
121
122     this.refresh();
123
124     this._headerElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this));
125 };
126
127 WebInspector.CSSStyleDeclarationSection.LockedStyleClassName = "locked";
128 WebInspector.CSSStyleDeclarationSection.SelectorLockedStyleClassName = "selector-locked";
129 WebInspector.CSSStyleDeclarationSection.SelectorInvalidClassName = "invalid-selector";
130 WebInspector.CSSStyleDeclarationSection.LastInGroupStyleClassName = "last-in-group";
131 WebInspector.CSSStyleDeclarationSection.MatchedSelectorElementStyleClassName = "matched";
132
133 WebInspector.CSSStyleDeclarationSection.AuthorStyleRuleIconStyleClassName = "author-style-rule-icon";
134 WebInspector.CSSStyleDeclarationSection.UserStyleRuleIconStyleClassName = "user-style-rule-icon";
135 WebInspector.CSSStyleDeclarationSection.UserAgentStyleRuleIconStyleClassName = "user-agent-style-rule-icon";
136 WebInspector.CSSStyleDeclarationSection.InspectorStyleRuleIconStyleClassName = "inspector-style-rule-icon";
137 WebInspector.CSSStyleDeclarationSection.InheritedStyleRuleIconStyleClassName = "inherited-style-rule-icon";
138 WebInspector.CSSStyleDeclarationSection.InheritedElementStyleRuleIconStyleClassName = "inherited-element-style-rule-icon";
139
140 WebInspector.CSSStyleDeclarationSection.prototype = {
141     constructor: WebInspector.CSSStyleDeclarationSection,
142
143     // Public
144
145     get element()
146     {
147         return this._element;
148     },
149
150     get style()
151     {
152         return this._style;
153     },
154
155     get lastInGroup()
156     {
157         return this._element.classList.contains(WebInspector.CSSStyleDeclarationSection.LastInGroupStyleClassName);
158     },
159
160     set lastInGroup(last)
161     {
162         if (last)
163             this._element.classList.add(WebInspector.CSSStyleDeclarationSection.LastInGroupStyleClassName);
164         else
165             this._element.classList.remove(WebInspector.CSSStyleDeclarationSection.LastInGroupStyleClassName);
166     },
167
168     get focused()
169     {
170         return this._propertiesTextEditor.focused;
171     },
172
173     focus: function()
174     {
175         this._propertiesTextEditor.focus();
176     },
177
178     refresh: function()
179     {
180         this._selectorElement.removeChildren();
181         this._originElement.removeChildren();
182         this._selectorElements = [];
183
184         this._originElement.append(" \u2014 ");
185
186         function appendSelector(selector, matched)
187         {
188             console.assert(selector instanceof WebInspector.CSSSelector);
189
190             var selectorElement = document.createElement("span");
191             selectorElement.textContent = selector.text;
192
193             if (matched)
194                 selectorElement.className = WebInspector.CSSStyleDeclarationSection.MatchedSelectorElementStyleClassName;
195
196             var specificity = selector.specificity;
197             if (specificity) {
198                 var tooltip = WebInspector.UIString("Specificity: (%d, %d, %d)").format(specificity[0], specificity[1], specificity[2]);
199                 if (selector.dynamic) {
200                     tooltip += "\n";
201                     if (this._style.inherited)
202                         tooltip += WebInspector.UIString("Dynamically calculated for the parent element");
203                     else
204                         tooltip += WebInspector.UIString("Dynamically calculated for the selected element");
205                 }
206                 selectorElement.title = tooltip;
207             } else if (selector.dynamic) {
208                 var tooltip = WebInspector.UIString("Specificity: No value for selected element");
209                 tooltip += "\n";
210                 tooltip += WebInspector.UIString("Dynamically calculated for the selected element and did not match");
211                 selectorElement.title = tooltip;
212             }
213
214             this._selectorElement.appendChild(selectorElement);
215             this._selectorElements.push(selectorElement);
216         }
217
218         function appendSelectorTextKnownToMatch(selectorText)
219         {
220             var selectorElement = document.createElement("span");
221             selectorElement.textContent = selectorText;
222             selectorElement.className = WebInspector.CSSStyleDeclarationSection.MatchedSelectorElementStyleClassName;
223             this._selectorElement.appendChild(selectorElement);
224         }
225
226         switch (this._style.type) {
227         case WebInspector.CSSStyleDeclaration.Type.Rule:
228             console.assert(this._style.ownerRule);
229
230             var selectors = this._style.ownerRule.selectors;
231             var matchedSelectorIndices = this._style.ownerRule.matchedSelectorIndices;
232             var alwaysMatch = !matchedSelectorIndices.length;
233             if (selectors.length) {
234                 for (var i = 0; i < selectors.length; ++i) {
235                     appendSelector.call(this, selectors[i], alwaysMatch || matchedSelectorIndices.includes(i));
236                     if (i < selectors.length - 1)
237                         this._selectorElement.append(", ");
238                 }
239             } else
240                 appendSelectorTextKnownToMatch.call(this, this._style.ownerRule.selectorText);
241
242             if (this._style.ownerRule.sourceCodeLocation) {
243                 var sourceCodeLink = WebInspector.createSourceCodeLocationLink(this._style.ownerRule.sourceCodeLocation, true);
244                 this._originElement.appendChild(sourceCodeLink);
245             } else {
246                 var originString;
247                 switch (this._style.ownerRule.type) {
248                 case WebInspector.CSSRule.Type.Author:
249                     originString = WebInspector.UIString("Author Stylesheet");
250                     break;
251
252                 case WebInspector.CSSRule.Type.User:
253                     originString = WebInspector.UIString("User Stylesheet");
254                     break;
255
256                 case WebInspector.CSSRule.Type.UserAgent:
257                     originString = WebInspector.UIString("User Agent Stylesheet");
258                     break;
259
260                 case WebInspector.CSSRule.Type.Inspector:
261                     originString = WebInspector.UIString("Web Inspector");
262                     break;
263                 }
264
265                 console.assert(originString);
266                 if (originString)
267                     this._originElement.append(originString);
268             }
269
270             break;
271
272         case WebInspector.CSSStyleDeclaration.Type.Inline:
273             appendSelectorTextKnownToMatch.call(this, WebInspector.displayNameForNode(this._style.node));
274             this._originElement.append(WebInspector.UIString("Style Attribute"));
275             break;
276
277         case WebInspector.CSSStyleDeclaration.Type.Attribute:
278             appendSelectorTextKnownToMatch.call(this, WebInspector.displayNameForNode(this._style.node));
279             this._originElement.append(WebInspector.UIString("HTML Attributes"));
280             break;
281         }
282     },
283
284     highlightProperty: function(property)
285     {
286         if (this._propertiesTextEditor.highlightProperty(property)) {
287             this._element.scrollIntoView();
288             return true;
289         }
290
291         return false;
292     },
293
294     findMatchingPropertiesAndSelectors: function(needle)
295     {
296         this._element.classList.remove(WebInspector.CSSStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName, WebInspector.CSSStyleDetailsSidebarPanel.FilterMatchingSectionHasLabelClassName);
297
298         var hasMatchingSelector = false;
299
300         for (var selectorElement of this._selectorElements) {
301             selectorElement.classList.remove(WebInspector.CSSStyleDetailsSidebarPanel.FilterMatchSectionClassName);
302
303             if (needle && selectorElement.textContent.includes(needle)) {
304                 selectorElement.classList.add(WebInspector.CSSStyleDetailsSidebarPanel.FilterMatchSectionClassName);
305                 hasMatchingSelector = true;
306             }
307         }
308
309         if (!needle) {
310             this._propertiesTextEditor.resetFilteredProperties();
311             return false;
312         }
313
314         var hasMatchingProperty = this._propertiesTextEditor.findMatchingProperties(needle);
315
316         if (!hasMatchingProperty && !hasMatchingSelector) {
317             this._element.classList.add(WebInspector.CSSStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName);
318             return false;
319         }
320
321         return true;
322     },
323
324     updateLayout: function()
325     {
326         this._propertiesTextEditor.updateLayout();
327     },
328
329     clearSelection: function()
330     {
331         this._propertiesTextEditor.clearSelection();
332     },
333
334     cssStyleDeclarationTextEditorFocused: function()
335     {
336         if (typeof this._delegate.cssStyleDeclarationSectionEditorFocused === "function")
337             this._delegate.cssStyleDeclarationSectionEditorFocused(this);
338     },
339
340     cssStyleDeclarationTextEditorSwitchRule: function(reverse)
341     {
342         if (!this._delegate)
343             return;
344
345         if (reverse && typeof this._delegate.cssStyleDeclarationSectionEditorPreviousRule === "function")
346             this._delegate.cssStyleDeclarationSectionEditorPreviousRule(this);
347         else if (!reverse && typeof this._delegate.cssStyleDeclarationSectionEditorNextRule === "function")
348             this._delegate.cssStyleDeclarationSectionEditorNextRule(this);
349     },
350
351     focusRuleSelector: function(reverse)
352     {
353         if (this.selectorLocked) {
354             this.focus();
355             return;
356         }
357
358         if (this.locked) {
359             this.cssStyleDeclarationTextEditorSwitchRule(reverse);
360             return;
361         }
362
363         var selection = window.getSelection();
364         selection.removeAllRanges();
365
366         this._element.scrollIntoViewIfNeeded();
367
368         var range = document.createRange();
369         range.selectNodeContents(this._selectorElement);
370         selection.addRange(range);
371     },
372
373     selectLastProperty: function()
374     {
375         this._propertiesTextEditor.selectLastProperty();
376     },
377
378     get selectorLocked()
379     {
380         return !this.locked && !this._style.ownerRule;
381     },
382
383     get locked()
384     {
385         return !this._style.editable;
386     },
387
388     // Private
389
390     get _currentSelectorText()
391     {
392         if (!this._style.ownerRule)
393             return;
394
395         var selectorText = this._selectorElement.textContent;
396         if (!selectorText || !selectorText.length)
397             selectorText = this._style.ownerRule.selectorText;
398
399         return selectorText.trim();
400     },
401
402     _handleContextMenuEvent: function(event)
403     {
404         if (window.getSelection().toString().length)
405             return;
406
407         var contextMenu = new WebInspector.ContextMenu(event);
408
409         contextMenu.appendItem(WebInspector.UIString("Copy Rule"), function() {
410             InspectorFrontendHost.copyText(this._generateCSSRuleString());
411         }.bind(this));
412
413         contextMenu.show();
414     },
415
416     _generateCSSRuleString: function()
417     {
418         var numMediaQueries = 0;
419         var styleText = "";
420
421         if (this._style.ownerRule) {
422             var mediaList = this._style.ownerRule.mediaList;
423             if (mediaList.length) {
424                 numMediaQueries = mediaList.length;
425
426                 for (var i = numMediaQueries - 1; i >= 0; --i)
427                     styleText += "    ".repeat(numMediaQueries - i - 1) + "@media " + mediaList[i].text + " {\n";
428             }
429
430             styleText += "    ".repeat(numMediaQueries) + this._style.ownerRule.selectorText;
431         } else
432             styleText += this._selectorElement.textContent;
433
434         styleText += " {\n";
435
436         for (var property of this._style.visibleProperties) {
437             styleText += "    ".repeat(numMediaQueries + 1) + property.text.trim();
438
439             if (!styleText.endsWith(";"))
440                 styleText += ";";
441
442             styleText += "\n";
443         }
444
445         for (var i = numMediaQueries; i > 0; --i)
446             styleText += "    ".repeat(i) + "}\n";
447
448         styleText += "}";
449
450         return styleText;
451     },
452
453     _toggleRuleOnOff: function()
454     {
455         if (this._hasInvalidSelector)
456             return;
457
458         this._ruleDisabled = this._ruleDisabled ? !this._propertiesTextEditor.uncommentAllProperties() : this._propertiesTextEditor.commentAllProperties();
459         this._iconElement.title = this._ruleDisabled ? WebInspector.UIString("Uncomment All Properties") : WebInspector.UIString("Comment All Properties");
460         this._element.classList.toggle("rule-disabled", this._ruleDisabled);
461     },
462
463     _highlightNodesWithSelector: function()
464     {
465         if (!this._style.ownerRule) {
466             WebInspector.domTreeManager.highlightDOMNode(this._style.node.id);
467             return;
468         }
469
470         WebInspector.domTreeManager.highlightSelector(this._currentSelectorText, this._style.node.ownerDocument.frameIdentifier);
471     },
472
473     _hideDOMNodeHighlight: function()
474     {
475         WebInspector.domTreeManager.hideDOMNodeHighlight();
476     },
477
478     _handleMouseOver: function(event)
479     {
480         this._highlightNodesWithSelector();
481     },
482
483     _handleMouseOut: function(event)
484     {
485         this._hideDOMNodeHighlight();
486     },
487
488     _handleKeyDown: function(event)
489     {
490         if (event.keyCode !== 9) {
491             this._highlightNodesWithSelector();
492             return;
493         }
494
495         if (event.shiftKey && this._delegate && typeof this._delegate.cssStyleDeclarationSectionEditorPreviousRule === "function") {
496             event.preventDefault();
497             this._delegate.cssStyleDeclarationSectionEditorPreviousRule(this, true);
498             return;
499         }
500
501         if (!event.metaKey) {
502             event.preventDefault();
503             this.focus();
504             this._propertiesTextEditor.selectFirstProperty();
505             return;
506         }
507     },
508
509     _handleKeyUp: function(event)
510     {
511         this._highlightNodesWithSelector();
512     },
513
514     _commitSelector: function(mutations)
515     {
516         console.assert(this._style.ownerRule);
517         if (!this._style.ownerRule)
518             return;
519
520         var newSelectorText = this._selectorElement.textContent.trim();
521         if (!newSelectorText) {
522             // Revert to the current selector (by doing a refresh) since the new selector is empty.
523             this.refresh();
524             return;
525         }
526
527         this._style.ownerRule.selectorText = newSelectorText;
528     },
529
530     _markSelector: function(event)
531     {
532         var valid = event && event.data && event.data.valid;
533         this._element.classList.toggle(WebInspector.CSSStyleDeclarationSection.SelectorInvalidClassName, !valid);
534         if (valid) {
535             this._iconElement.title = this._ruleDisabled ? WebInspector.UIString("Uncomment All Properties") : WebInspector.UIString("Comment All Properties");
536             this._selectorElement.title = null;
537             this.refresh();
538             return;
539         }
540
541         this._iconElement.title = WebInspector.UIString("The selector '%s' is invalid.").format(this._selectorElement.textContent.trim());
542         this._selectorElement.title = WebInspector.UIString("Using the previous selector '%s'.").format(this._style.ownerRule.selectorText);
543         for (var i = 0; i < this._selectorElement.children.length; ++i)
544             this._selectorElement.children[i].title = null;
545     },
546
547     get _hasInvalidSelector()
548     {
549         return this._element.classList.contains(WebInspector.CSSStyleDeclarationSection.SelectorInvalidClassName);
550     }
551 };
552
553 WebInspector.CSSStyleDeclarationSection.prototype.__proto__ = WebInspector.StyleDetailsPanel.prototype;