Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2007 Apple Inc. All rights reserved. 2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro 3 * Copyright (C) 2009 Joseph Pecoraro
4 * 4 *
5 * Redistribution and use in source and binary forms, with or without 5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions 6 * modification, are permitted provided that the following conditions
7 * are met: 7 * are met:
8 * 8 *
9 * 1. Redistributions of source code must retain the above copyright 9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer. 10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright 11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the 12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution. 13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived 15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission. 16 * from this software without specific prior written permission.
17 * 17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */ 28 */
29
30 /** 29 /**
31 * @constructor 30 * @unrestricted
32 * @extends {WebInspector.ElementsSidebarPane}
33 */ 31 */
34 WebInspector.StylesSidebarPane = function() 32 WebInspector.StylesSidebarPane = class extends WebInspector.ElementsSidebarPane {
35 { 33 constructor() {
36 WebInspector.ElementsSidebarPane.call(this); 34 super();
37 this.setMinimumSize(96, 26); 35 this.setMinimumSize(96, 26);
38 36
39 WebInspector.moduleSetting("colorFormat").addChangeListener(this.update.bind (this)); 37 WebInspector.moduleSetting('colorFormat').addChangeListener(this.update.bind (this));
40 WebInspector.moduleSetting("textEditorIndent").addChangeListener(this.update .bind(this)); 38 WebInspector.moduleSetting('textEditorIndent').addChangeListener(this.update .bind(this));
41 39
42 this._sectionsContainer = this.element.createChild("div"); 40 this._sectionsContainer = this.element.createChild('div');
43 this._swatchPopoverHelper = new WebInspector.SwatchPopoverHelper(); 41 this._swatchPopoverHelper = new WebInspector.SwatchPopoverHelper();
44 this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.Defa ultCSSFormatter()); 42 this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.Defa ultCSSFormatter());
45 43
46 this.element.classList.add("styles-pane"); 44 this.element.classList.add('styles-pane');
47 45
48 /** @type {!Array<!WebInspector.SectionBlock>} */ 46 /** @type {!Array<!WebInspector.SectionBlock>} */
49 this._sectionBlocks = []; 47 this._sectionBlocks = [];
50 WebInspector.StylesSidebarPane._instance = this; 48 WebInspector.StylesSidebarPane._instance = this;
51 49
52 WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspec tor.CSSModel.Events.LayoutEditorChange, this._onLayoutEditorChange, this); 50 WebInspector.targetManager.addModelListener(
51 WebInspector.CSSModel, WebInspector.CSSModel.Events.LayoutEditorChange, this._onLayoutEditorChange, this);
53 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this.forc eUpdate, this); 52 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this.forc eUpdate, this);
54 }; 53 }
55 54
56 /** 55 /**
57 * @param {!WebInspector.CSSProperty} property 56 * @param {!WebInspector.CSSProperty} property
58 * @return {!Element} 57 * @return {!Element}
59 */ 58 */
60 WebInspector.StylesSidebarPane.createExclamationMark = function(property) 59 static createExclamationMark(property) {
61 { 60 var exclamationElement = createElement('label', 'dt-icon-label');
62 var exclamationElement = createElement("label", "dt-icon-label"); 61 exclamationElement.className = 'exclamation-mark';
63 exclamationElement.className = "exclamation-mark";
64 if (!WebInspector.StylesSidebarPane.ignoreErrorsForProperty(property)) 62 if (!WebInspector.StylesSidebarPane.ignoreErrorsForProperty(property))
65 exclamationElement.type = "warning-icon"; 63 exclamationElement.type = 'warning-icon';
66 exclamationElement.title = WebInspector.cssMetadata().isCSSPropertyName(prop erty.name) ? WebInspector.UIString("Invalid property value") : WebInspector.UISt ring("Unknown property name"); 64 exclamationElement.title = WebInspector.cssMetadata().isCSSPropertyName(prop erty.name) ?
65 WebInspector.UIString('Invalid property value') :
66 WebInspector.UIString('Unknown property name');
67 return exclamationElement; 67 return exclamationElement;
68 }; 68 }
69 69
70 /** 70 /**
71 * @param {!WebInspector.CSSProperty} property 71 * @param {!WebInspector.CSSProperty} property
72 * @return {boolean} 72 * @return {boolean}
73 */ 73 */
74 WebInspector.StylesSidebarPane.ignoreErrorsForProperty = function(property) { 74 static ignoreErrorsForProperty(property) {
75 /** 75 /**
76 * @param {string} string 76 * @param {string} string
77 */ 77 */
78 function hasUnknownVendorPrefix(string) 78 function hasUnknownVendorPrefix(string) {
79 { 79 return !string.startsWith('-webkit-') && /^[-_][\w\d]+-\w/.test(string);
80 return !string.startsWith("-webkit-") && /^[-_][\w\d]+-\w/.test(string);
81 } 80 }
82 81
83 var name = property.name.toLowerCase(); 82 var name = property.name.toLowerCase();
84 83
85 // IE hack. 84 // IE hack.
86 if (name.charAt(0) === "_") 85 if (name.charAt(0) === '_')
87 return true; 86 return true;
88 87
89 // IE has a different format for this. 88 // IE has a different format for this.
90 if (name === "filter") 89 if (name === 'filter')
91 return true; 90 return true;
92 91
93 // Common IE-specific property prefix. 92 // Common IE-specific property prefix.
94 if (name.startsWith("scrollbar-")) 93 if (name.startsWith('scrollbar-'))
95 return true; 94 return true;
96 if (hasUnknownVendorPrefix(name)) 95 if (hasUnknownVendorPrefix(name))
97 return true; 96 return true;
98 97
99 var value = property.value.toLowerCase(); 98 var value = property.value.toLowerCase();
100 99
101 // IE hack. 100 // IE hack.
102 if (value.endsWith("\\9")) 101 if (value.endsWith('\\9'))
103 return true; 102 return true;
104 if (hasUnknownVendorPrefix(value)) 103 if (hasUnknownVendorPrefix(value))
105 return true; 104 return true;
106 105
107 return false; 106 return false;
108 }; 107 }
109 108
110 WebInspector.StylesSidebarPane.prototype = { 109 /**
111 /** 110 * @param {string} placeholder
112 * @param {!WebInspector.Event} event 111 * @param {!Element} container
113 */ 112 * @param {function(?RegExp)} filterCallback
114 _onLayoutEditorChange: function(event) 113 * @return {!Element}
115 { 114 */
116 var cssModel = /** @type {!WebInspector.CSSModel} */(event.target); 115 static createPropertyFilterElement(placeholder, container, filterCallback) {
117 var styleSheetId = event.data["id"]; 116 var input = createElement('input');
118 var sourceRange = /** @type {!CSSAgent.SourceRange} */(event.data["range "]); 117 input.placeholder = placeholder;
119 var range = WebInspector.TextRange.fromObject(sourceRange); 118
120 this._decorator = new WebInspector.PropertyChangeHighlighter(this, cssMo del, styleSheetId, range); 119 function searchHandler() {
121 this.update(); 120 var regex = input.value ? new RegExp(input.value.escapeForRegExp(), 'i') : null;
122 }, 121 filterCallback(regex);
123 122 container.classList.toggle('styles-filter-engaged', !!input.value);
124 /** 123 }
125 * @param {!WebInspector.CSSProperty} cssProperty 124 input.addEventListener('input', searchHandler, false);
126 */
127 revealProperty: function(cssProperty)
128 {
129 this._decorator = new WebInspector.PropertyRevealHighlighter(this, cssPr operty);
130 this._decorator.perform();
131 this.update();
132 },
133
134 forceUpdate: function()
135 {
136 this._swatchPopoverHelper.hide();
137 this._resetCache();
138 this.update();
139 },
140 125
141 /** 126 /**
142 * @param {!Event} event 127 * @param {!Event} event
143 */ 128 */
144 _onAddButtonLongClick: function(event) 129 function keydownHandler(event) {
145 { 130 if (event.key !== 'Escape' || !input.value)
146 var cssModel = this.cssModel(); 131 return;
147 if (!cssModel) 132 event.consume(true);
148 return; 133 input.value = '';
149 var headers = cssModel.styleSheetHeaders().filter(styleSheetResourceHead er); 134 searchHandler();
150 135 }
151 /** @type {!Array.<{text: string, handler: function()}>} */ 136 input.addEventListener('keydown', keydownHandler, false);
152 var contextMenuDescriptors = [];
153 for (var i = 0; i < headers.length; ++i) {
154 var header = headers[i];
155 var handler = this._createNewRuleInStyleSheet.bind(this, header);
156 contextMenuDescriptors.push({
157 text: WebInspector.displayNameForURL(header.resourceURL()),
158 handler: handler
159 });
160 }
161
162 contextMenuDescriptors.sort(compareDescriptors);
163
164 var contextMenu = new WebInspector.ContextMenu(event);
165 for (var i = 0; i < contextMenuDescriptors.length; ++i) {
166 var descriptor = contextMenuDescriptors[i];
167 contextMenu.appendItem(descriptor.text, descriptor.handler);
168 }
169 if (!contextMenu.isEmpty())
170 contextMenu.appendSeparator();
171 contextMenu.appendItem("inspector-stylesheet", this._createNewRuleInViaI nspectorStyleSheet.bind(this));
172 contextMenu.show();
173
174 /**
175 * @param {!{text: string, handler: function()}} descriptor1
176 * @param {!{text: string, handler: function()}} descriptor2
177 * @return {number}
178 */
179 function compareDescriptors(descriptor1, descriptor2)
180 {
181 return String.naturalOrderComparator(descriptor1.text, descriptor2.t ext);
182 }
183
184 /**
185 * @param {!WebInspector.CSSStyleSheetHeader} header
186 * @return {boolean}
187 */
188 function styleSheetResourceHeader(header)
189 {
190 return !header.isViaInspector() && !header.isInline && !!header.reso urceURL();
191 }
192 },
193
194 /**
195 * @param {?RegExp} regex
196 */
197 onFilterChanged: function(regex)
198 {
199 this._filterRegex = regex;
200 this._updateFilter();
201 },
202
203 /**
204 * @param {!WebInspector.StylePropertiesSection=} editedSection
205 */
206 _refreshUpdate: function(editedSection)
207 {
208 var node = this.node();
209 if (!node)
210 return;
211
212 var fullRefresh = Runtime.experiments.isEnabled("liveSASS");
213 for (var section of this.allSections()) {
214 if (section.isBlank)
215 continue;
216 section.update(fullRefresh || section === editedSection);
217 }
218
219 if (this._filterRegex)
220 this._updateFilter();
221 this._nodeStylesUpdatedForTest(node, false);
222 },
223
224 /**
225 * @override
226 * @return {!Promise.<?>}
227 */
228 doUpdate: function()
229 {
230 return this._fetchMatchedCascade()
231 .then(this._innerRebuildUpdate.bind(this));
232 },
233
234 _resetCache: function()
235 {
236 if (this.cssModel())
237 this.cssModel().discardCachedMatchedCascade();
238 },
239
240 /**
241 * @return {!Promise.<?WebInspector.CSSMatchedStyles>}
242 */
243 _fetchMatchedCascade: function()
244 {
245 var node = this.node();
246 if (!node || !this.cssModel())
247 return Promise.resolve(/** @type {?WebInspector.CSSMatchedStyles} */ (null));
248
249 return this.cssModel().cachedMatchedCascadeForNode(node).then(validateSt yles.bind(this));
250
251 /**
252 * @param {?WebInspector.CSSMatchedStyles} matchedStyles
253 * @return {?WebInspector.CSSMatchedStyles}
254 * @this {WebInspector.StylesSidebarPane}
255 */
256 function validateStyles(matchedStyles)
257 {
258 return matchedStyles && matchedStyles.node() === this.node() ? match edStyles : null;
259 }
260 },
261
262 /**
263 * @param {boolean} editing
264 */
265 setEditingStyle: function(editing)
266 {
267 if (this._isEditingStyle === editing)
268 return;
269 this.element.classList.toggle("is-editing-style", editing);
270 this._isEditingStyle = editing;
271 },
272
273 /**
274 * @override
275 * @param {!WebInspector.Event=} event
276 */
277 onCSSModelChanged: function(event)
278 {
279 var edit = event && event.data ? /** @type {?WebInspector.CSSModel.Edit} */(event.data.edit) : null;
280 if (edit) {
281 for (var section of this.allSections())
282 section._styleSheetEdited(edit);
283 return;
284 }
285
286 if (this._userOperation || this._isEditingStyle)
287 return;
288
289 this._resetCache();
290 this.update();
291 },
292
293 /**
294 * @param {?WebInspector.CSSMatchedStyles} matchedStyles
295 */
296 _innerRebuildUpdate: function(matchedStyles)
297 {
298 this._linkifier.reset();
299 this._sectionsContainer.removeChildren();
300 this._sectionBlocks = [];
301
302 var node = this.node();
303 if (!matchedStyles || !node)
304 return;
305
306 this._sectionBlocks = this._rebuildSectionsForMatchedStyleRules(matchedS tyles);
307 var pseudoTypes = [];
308 var keys = new Set(matchedStyles.pseudoStyles().keys());
309 if (keys.delete(DOMAgent.PseudoType.Before))
310 pseudoTypes.push(DOMAgent.PseudoType.Before);
311 pseudoTypes = pseudoTypes.concat(keys.valuesArray().sort());
312 for (var pseudoType of pseudoTypes) {
313 var block = WebInspector.SectionBlock.createPseudoTypeBlock(pseudoTy pe);
314 var styles = /** @type {!Array<!WebInspector.CSSStyleDeclaration>} * /(matchedStyles.pseudoStyles().get(pseudoType));
315 for (var style of styles) {
316 var section = new WebInspector.StylePropertiesSection(this, matc hedStyles, style);
317 block.sections.push(section);
318 }
319 this._sectionBlocks.push(block);
320 }
321
322 for (var keyframesRule of matchedStyles.keyframes()) {
323 var block = WebInspector.SectionBlock.createKeyframesBlock(keyframes Rule.name().text);
324 for (var keyframe of keyframesRule.keyframes())
325 block.sections.push(new WebInspector.KeyframePropertiesSection(t his, matchedStyles, keyframe.style));
326 this._sectionBlocks.push(block);
327 }
328
329 for (var block of this._sectionBlocks) {
330 var titleElement = block.titleElement();
331 if (titleElement)
332 this._sectionsContainer.appendChild(titleElement);
333 for (var section of block.sections)
334 this._sectionsContainer.appendChild(section.element);
335 }
336
337 if (this._filterRegex)
338 this._updateFilter();
339
340 this._nodeStylesUpdatedForTest(node, true);
341 if (this._decorator) {
342 this._decorator.perform();
343 delete this._decorator;
344 }
345 },
346
347 /**
348 * @param {!WebInspector.DOMNode} node
349 * @param {boolean} rebuild
350 */
351 _nodeStylesUpdatedForTest: function(node, rebuild)
352 {
353 // For sniffing in tests.
354 },
355
356 /**
357 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
358 * @return {!Array.<!WebInspector.SectionBlock>}
359 */
360 _rebuildSectionsForMatchedStyleRules: function(matchedStyles)
361 {
362 var blocks = [new WebInspector.SectionBlock(null)];
363 var lastParentNode = null;
364 for (var style of matchedStyles.nodeStyles()) {
365 var parentNode = matchedStyles.isInherited(style) ? matchedStyles.no deForStyle(style) : null;
366 if (parentNode && parentNode !== lastParentNode) {
367 lastParentNode = parentNode;
368 var block = WebInspector.SectionBlock.createInheritedNodeBlock(l astParentNode);
369 blocks.push(block);
370 }
371
372 var section = new WebInspector.StylePropertiesSection(this, matchedS tyles, style);
373 blocks.peekLast().sections.push(section);
374 }
375 return blocks;
376 },
377
378 _createNewRuleInViaInspectorStyleSheet: function()
379 {
380 var cssModel = this.cssModel();
381 var node = this.node();
382 if (!cssModel || !node)
383 return;
384 this._userOperation = true;
385 cssModel.requestViaInspectorStylesheet(node, onViaInspectorStyleSheet.bi nd(this));
386
387 /**
388 * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
389 * @this {WebInspector.StylesSidebarPane}
390 */
391 function onViaInspectorStyleSheet(styleSheetHeader)
392 {
393 delete this._userOperation;
394 this._createNewRuleInStyleSheet(styleSheetHeader);
395 }
396 },
397
398 /**
399 * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
400 */
401 _createNewRuleInStyleSheet: function(styleSheetHeader)
402 {
403 if (!styleSheetHeader)
404 return;
405 styleSheetHeader.requestContent().then(onStyleSheetContent.bind(this, st yleSheetHeader.id));
406
407 /**
408 * @param {string} styleSheetId
409 * @param {?string} text
410 * @this {WebInspector.StylesSidebarPane}
411 */
412 function onStyleSheetContent(styleSheetId, text)
413 {
414 text = text || "";
415 var lines = text.split("\n");
416 var range = WebInspector.TextRange.createFromLocation(lines.length - 1, lines[lines.length - 1].length);
417 this._addBlankSection(this._sectionBlocks[0].sections[0], styleSheet Id, range);
418 }
419 },
420
421 /**
422 * @param {!WebInspector.StylePropertiesSection} insertAfterSection
423 * @param {string} styleSheetId
424 * @param {!WebInspector.TextRange} ruleLocation
425 */
426 _addBlankSection: function(insertAfterSection, styleSheetId, ruleLocation)
427 {
428 var node = this.node();
429 var blankSection = new WebInspector.BlankStylePropertiesSection(this, in sertAfterSection._matchedStyles, node ? WebInspector.DOMPresentationUtils.simple Selector(node) : "", styleSheetId, ruleLocation, insertAfterSection._style);
430
431 this._sectionsContainer.insertBefore(blankSection.element, insertAfterSe ction.element.nextSibling);
432
433 for (var block of this._sectionBlocks) {
434 var index = block.sections.indexOf(insertAfterSection);
435 if (index === -1)
436 continue;
437 block.sections.splice(index + 1, 0, blankSection);
438 blankSection.startEditingSelector();
439 }
440 },
441
442 /**
443 * @param {!WebInspector.StylePropertiesSection} section
444 */
445 removeSection: function(section)
446 {
447 for (var block of this._sectionBlocks) {
448 var index = block.sections.indexOf(section);
449 if (index === -1)
450 continue;
451 block.sections.splice(index, 1);
452 section.element.remove();
453 }
454 },
455
456 /**
457 * @return {?RegExp}
458 */
459 filterRegex: function()
460 {
461 return this._filterRegex;
462 },
463
464 _updateFilter: function()
465 {
466 for (var block of this._sectionBlocks)
467 block.updateFilter();
468 },
469
470 /**
471 * @override
472 */
473 willHide: function()
474 {
475 this._swatchPopoverHelper.hide();
476 WebInspector.ElementsSidebarPane.prototype.willHide.call(this);
477 },
478
479 /**
480 * @return {!Array<!WebInspector.StylePropertiesSection>}
481 */
482 allSections: function()
483 {
484 var sections = [];
485 for (var block of this._sectionBlocks)
486 sections = sections.concat(block.sections);
487 return sections;
488 },
489
490 __proto__: WebInspector.ElementsSidebarPane.prototype
491 };
492
493 /**
494 * @param {string} placeholder
495 * @param {!Element} container
496 * @param {function(?RegExp)} filterCallback
497 * @return {!Element}
498 */
499 WebInspector.StylesSidebarPane.createPropertyFilterElement = function(placeholde r, container, filterCallback)
500 {
501 var input = createElement("input");
502 input.placeholder = placeholder;
503
504 function searchHandler()
505 {
506 var regex = input.value ? new RegExp(input.value.escapeForRegExp(), "i") : null;
507 filterCallback(regex);
508 container.classList.toggle("styles-filter-engaged", !!input.value);
509 }
510 input.addEventListener("input", searchHandler, false);
511
512 /**
513 * @param {!Event} event
514 */
515 function keydownHandler(event)
516 {
517 if (event.key !== "Escape" || !input.value)
518 return;
519 event.consume(true);
520 input.value = "";
521 searchHandler();
522 }
523 input.addEventListener("keydown", keydownHandler, false);
524 137
525 input.setFilterValue = setFilterValue; 138 input.setFilterValue = setFilterValue;
526 139
527 /** 140 /**
528 * @param {string} value 141 * @param {string} value
529 */ 142 */
530 function setFilterValue(value) 143 function setFilterValue(value) {
531 { 144 input.value = value;
532 input.value = value; 145 input.focus();
533 input.focus(); 146 searchHandler();
534 searchHandler();
535 } 147 }
536 148
537 return input; 149 return input;
150 }
151
152 /**
153 * @param {!WebInspector.Event} event
154 */
155 _onLayoutEditorChange(event) {
156 var cssModel = /** @type {!WebInspector.CSSModel} */ (event.target);
157 var styleSheetId = event.data['id'];
158 var sourceRange = /** @type {!CSSAgent.SourceRange} */ (event.data['range']) ;
159 var range = WebInspector.TextRange.fromObject(sourceRange);
160 this._decorator = new WebInspector.PropertyChangeHighlighter(this, cssModel, styleSheetId, range);
161 this.update();
162 }
163
164 /**
165 * @param {!WebInspector.CSSProperty} cssProperty
166 */
167 revealProperty(cssProperty) {
168 this._decorator = new WebInspector.PropertyRevealHighlighter(this, cssProper ty);
169 this._decorator.perform();
170 this.update();
171 }
172
173 forceUpdate() {
174 this._swatchPopoverHelper.hide();
175 this._resetCache();
176 this.update();
177 }
178
179 /**
180 * @param {!Event} event
181 */
182 _onAddButtonLongClick(event) {
183 var cssModel = this.cssModel();
184 if (!cssModel)
185 return;
186 var headers = cssModel.styleSheetHeaders().filter(styleSheetResourceHeader);
187
188 /** @type {!Array.<{text: string, handler: function()}>} */
189 var contextMenuDescriptors = [];
190 for (var i = 0; i < headers.length; ++i) {
191 var header = headers[i];
192 var handler = this._createNewRuleInStyleSheet.bind(this, header);
193 contextMenuDescriptors.push({text: WebInspector.displayNameForURL(header.r esourceURL()), handler: handler});
194 }
195
196 contextMenuDescriptors.sort(compareDescriptors);
197
198 var contextMenu = new WebInspector.ContextMenu(event);
199 for (var i = 0; i < contextMenuDescriptors.length; ++i) {
200 var descriptor = contextMenuDescriptors[i];
201 contextMenu.appendItem(descriptor.text, descriptor.handler);
202 }
203 if (!contextMenu.isEmpty())
204 contextMenu.appendSeparator();
205 contextMenu.appendItem('inspector-stylesheet', this._createNewRuleInViaInspe ctorStyleSheet.bind(this));
206 contextMenu.show();
207
208 /**
209 * @param {!{text: string, handler: function()}} descriptor1
210 * @param {!{text: string, handler: function()}} descriptor2
211 * @return {number}
212 */
213 function compareDescriptors(descriptor1, descriptor2) {
214 return String.naturalOrderComparator(descriptor1.text, descriptor2.text);
215 }
216
217 /**
218 * @param {!WebInspector.CSSStyleSheetHeader} header
219 * @return {boolean}
220 */
221 function styleSheetResourceHeader(header) {
222 return !header.isViaInspector() && !header.isInline && !!header.resourceUR L();
223 }
224 }
225
226 /**
227 * @param {?RegExp} regex
228 */
229 onFilterChanged(regex) {
230 this._filterRegex = regex;
231 this._updateFilter();
232 }
233
234 /**
235 * @param {!WebInspector.StylePropertiesSection=} editedSection
236 */
237 _refreshUpdate(editedSection) {
238 var node = this.node();
239 if (!node)
240 return;
241
242 var fullRefresh = Runtime.experiments.isEnabled('liveSASS');
243 for (var section of this.allSections()) {
244 if (section.isBlank)
245 continue;
246 section.update(fullRefresh || section === editedSection);
247 }
248
249 if (this._filterRegex)
250 this._updateFilter();
251 this._nodeStylesUpdatedForTest(node, false);
252 }
253
254 /**
255 * @override
256 * @return {!Promise.<?>}
257 */
258 doUpdate() {
259 return this._fetchMatchedCascade().then(this._innerRebuildUpdate.bind(this)) ;
260 }
261
262 _resetCache() {
263 if (this.cssModel())
264 this.cssModel().discardCachedMatchedCascade();
265 }
266
267 /**
268 * @return {!Promise.<?WebInspector.CSSMatchedStyles>}
269 */
270 _fetchMatchedCascade() {
271 var node = this.node();
272 if (!node || !this.cssModel())
273 return Promise.resolve(/** @type {?WebInspector.CSSMatchedStyles} */ (null ));
274
275 return this.cssModel().cachedMatchedCascadeForNode(node).then(validateStyles .bind(this));
276
277 /**
278 * @param {?WebInspector.CSSMatchedStyles} matchedStyles
279 * @return {?WebInspector.CSSMatchedStyles}
280 * @this {WebInspector.StylesSidebarPane}
281 */
282 function validateStyles(matchedStyles) {
283 return matchedStyles && matchedStyles.node() === this.node() ? matchedStyl es : null;
284 }
285 }
286
287 /**
288 * @param {boolean} editing
289 */
290 setEditingStyle(editing) {
291 if (this._isEditingStyle === editing)
292 return;
293 this.element.classList.toggle('is-editing-style', editing);
294 this._isEditingStyle = editing;
295 }
296
297 /**
298 * @override
299 * @param {!WebInspector.Event=} event
300 */
301 onCSSModelChanged(event) {
302 var edit = event && event.data ? /** @type {?WebInspector.CSSModel.Edit} */ (event.data.edit) : null;
303 if (edit) {
304 for (var section of this.allSections())
305 section._styleSheetEdited(edit);
306 return;
307 }
308
309 if (this._userOperation || this._isEditingStyle)
310 return;
311
312 this._resetCache();
313 this.update();
314 }
315
316 /**
317 * @param {?WebInspector.CSSMatchedStyles} matchedStyles
318 */
319 _innerRebuildUpdate(matchedStyles) {
320 this._linkifier.reset();
321 this._sectionsContainer.removeChildren();
322 this._sectionBlocks = [];
323
324 var node = this.node();
325 if (!matchedStyles || !node)
326 return;
327
328 this._sectionBlocks = this._rebuildSectionsForMatchedStyleRules(matchedStyle s);
329 var pseudoTypes = [];
330 var keys = new Set(matchedStyles.pseudoStyles().keys());
331 if (keys.delete(DOMAgent.PseudoType.Before))
332 pseudoTypes.push(DOMAgent.PseudoType.Before);
333 pseudoTypes = pseudoTypes.concat(keys.valuesArray().sort());
334 for (var pseudoType of pseudoTypes) {
335 var block = WebInspector.SectionBlock.createPseudoTypeBlock(pseudoType);
336 var styles =
337 /** @type {!Array<!WebInspector.CSSStyleDeclaration>} */ (matchedStyle s.pseudoStyles().get(pseudoType));
338 for (var style of styles) {
339 var section = new WebInspector.StylePropertiesSection(this, matchedStyle s, style);
340 block.sections.push(section);
341 }
342 this._sectionBlocks.push(block);
343 }
344
345 for (var keyframesRule of matchedStyles.keyframes()) {
346 var block = WebInspector.SectionBlock.createKeyframesBlock(keyframesRule.n ame().text);
347 for (var keyframe of keyframesRule.keyframes())
348 block.sections.push(new WebInspector.KeyframePropertiesSection(this, mat chedStyles, keyframe.style));
349 this._sectionBlocks.push(block);
350 }
351
352 for (var block of this._sectionBlocks) {
353 var titleElement = block.titleElement();
354 if (titleElement)
355 this._sectionsContainer.appendChild(titleElement);
356 for (var section of block.sections)
357 this._sectionsContainer.appendChild(section.element);
358 }
359
360 if (this._filterRegex)
361 this._updateFilter();
362
363 this._nodeStylesUpdatedForTest(node, true);
364 if (this._decorator) {
365 this._decorator.perform();
366 delete this._decorator;
367 }
368 }
369
370 /**
371 * @param {!WebInspector.DOMNode} node
372 * @param {boolean} rebuild
373 */
374 _nodeStylesUpdatedForTest(node, rebuild) {
375 // For sniffing in tests.
376 }
377
378 /**
379 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
380 * @return {!Array.<!WebInspector.SectionBlock>}
381 */
382 _rebuildSectionsForMatchedStyleRules(matchedStyles) {
383 var blocks = [new WebInspector.SectionBlock(null)];
384 var lastParentNode = null;
385 for (var style of matchedStyles.nodeStyles()) {
386 var parentNode = matchedStyles.isInherited(style) ? matchedStyles.nodeForS tyle(style) : null;
387 if (parentNode && parentNode !== lastParentNode) {
388 lastParentNode = parentNode;
389 var block = WebInspector.SectionBlock.createInheritedNodeBlock(lastParen tNode);
390 blocks.push(block);
391 }
392
393 var section = new WebInspector.StylePropertiesSection(this, matchedStyles, style);
394 blocks.peekLast().sections.push(section);
395 }
396 return blocks;
397 }
398
399 _createNewRuleInViaInspectorStyleSheet() {
400 var cssModel = this.cssModel();
401 var node = this.node();
402 if (!cssModel || !node)
403 return;
404 this._userOperation = true;
405 cssModel.requestViaInspectorStylesheet(node, onViaInspectorStyleSheet.bind(t his));
406
407 /**
408 * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
409 * @this {WebInspector.StylesSidebarPane}
410 */
411 function onViaInspectorStyleSheet(styleSheetHeader) {
412 delete this._userOperation;
413 this._createNewRuleInStyleSheet(styleSheetHeader);
414 }
415 }
416
417 /**
418 * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
419 */
420 _createNewRuleInStyleSheet(styleSheetHeader) {
421 if (!styleSheetHeader)
422 return;
423 styleSheetHeader.requestContent().then(onStyleSheetContent.bind(this, styleS heetHeader.id));
424
425 /**
426 * @param {string} styleSheetId
427 * @param {?string} text
428 * @this {WebInspector.StylesSidebarPane}
429 */
430 function onStyleSheetContent(styleSheetId, text) {
431 text = text || '';
432 var lines = text.split('\n');
433 var range = WebInspector.TextRange.createFromLocation(lines.length - 1, li nes[lines.length - 1].length);
434 this._addBlankSection(this._sectionBlocks[0].sections[0], styleSheetId, ra nge);
435 }
436 }
437
438 /**
439 * @param {!WebInspector.StylePropertiesSection} insertAfterSection
440 * @param {string} styleSheetId
441 * @param {!WebInspector.TextRange} ruleLocation
442 */
443 _addBlankSection(insertAfterSection, styleSheetId, ruleLocation) {
444 var node = this.node();
445 var blankSection = new WebInspector.BlankStylePropertiesSection(
446 this, insertAfterSection._matchedStyles, node ? WebInspector.DOMPresenta tionUtils.simpleSelector(node) : '',
447 styleSheetId, ruleLocation, insertAfterSection._style);
448
449 this._sectionsContainer.insertBefore(blankSection.element, insertAfterSectio n.element.nextSibling);
450
451 for (var block of this._sectionBlocks) {
452 var index = block.sections.indexOf(insertAfterSection);
453 if (index === -1)
454 continue;
455 block.sections.splice(index + 1, 0, blankSection);
456 blankSection.startEditingSelector();
457 }
458 }
459
460 /**
461 * @param {!WebInspector.StylePropertiesSection} section
462 */
463 removeSection(section) {
464 for (var block of this._sectionBlocks) {
465 var index = block.sections.indexOf(section);
466 if (index === -1)
467 continue;
468 block.sections.splice(index, 1);
469 section.element.remove();
470 }
471 }
472
473 /**
474 * @return {?RegExp}
475 */
476 filterRegex() {
477 return this._filterRegex;
478 }
479
480 _updateFilter() {
481 for (var block of this._sectionBlocks)
482 block.updateFilter();
483 }
484
485 /**
486 * @override
487 */
488 willHide() {
489 this._swatchPopoverHelper.hide();
490 super.willHide();
491 }
492
493 /**
494 * @return {!Array<!WebInspector.StylePropertiesSection>}
495 */
496 allSections() {
497 var sections = [];
498 for (var block of this._sectionBlocks)
499 sections = sections.concat(block.sections);
500 return sections;
501 }
538 }; 502 };
539 503
504
540 /** 505 /**
541 * @constructor 506 * @unrestricted
542 * @param {?Element} titleElement
543 */ 507 */
544 WebInspector.SectionBlock = function(titleElement) 508 WebInspector.SectionBlock = class {
545 { 509 /**
510 * @param {?Element} titleElement
511 */
512 constructor(titleElement) {
546 this._titleElement = titleElement; 513 this._titleElement = titleElement;
547 this.sections = []; 514 this.sections = [];
548 }; 515 }
549 516
550 /** 517 /**
551 * @param {!DOMAgent.PseudoType} pseudoType 518 * @param {!DOMAgent.PseudoType} pseudoType
552 * @return {!WebInspector.SectionBlock} 519 * @return {!WebInspector.SectionBlock}
553 */ 520 */
554 WebInspector.SectionBlock.createPseudoTypeBlock = function(pseudoType) 521 static createPseudoTypeBlock(pseudoType) {
555 { 522 var separatorElement = createElement('div');
556 var separatorElement = createElement("div"); 523 separatorElement.className = 'sidebar-separator';
557 separatorElement.className = "sidebar-separator"; 524 separatorElement.textContent = WebInspector.UIString('Pseudo ::%s element', pseudoType);
558 separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoType);
559 return new WebInspector.SectionBlock(separatorElement); 525 return new WebInspector.SectionBlock(separatorElement);
560 }; 526 }
561 527
562 /** 528 /**
563 * @param {string} keyframesName 529 * @param {string} keyframesName
564 * @return {!WebInspector.SectionBlock} 530 * @return {!WebInspector.SectionBlock}
565 */ 531 */
566 WebInspector.SectionBlock.createKeyframesBlock = function(keyframesName) 532 static createKeyframesBlock(keyframesName) {
567 { 533 var separatorElement = createElement('div');
568 var separatorElement = createElement("div"); 534 separatorElement.className = 'sidebar-separator';
569 separatorElement.className = "sidebar-separator"; 535 separatorElement.textContent = WebInspector.UIString('@keyframes ' + keyfram esName);
570 separatorElement.textContent = WebInspector.UIString("@keyframes " + keyfram esName);
571 return new WebInspector.SectionBlock(separatorElement); 536 return new WebInspector.SectionBlock(separatorElement);
572 }; 537 }
573 538
574 /** 539 /**
575 * @param {!WebInspector.DOMNode} node 540 * @param {!WebInspector.DOMNode} node
576 * @return {!WebInspector.SectionBlock} 541 * @return {!WebInspector.SectionBlock}
577 */ 542 */
578 WebInspector.SectionBlock.createInheritedNodeBlock = function(node) 543 static createInheritedNodeBlock(node) {
579 { 544 var separatorElement = createElement('div');
580 var separatorElement = createElement("div"); 545 separatorElement.className = 'sidebar-separator';
581 separatorElement.className = "sidebar-separator";
582 var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(node); 546 var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
583 separatorElement.createTextChild(WebInspector.UIString("Inherited from") + " "); 547 separatorElement.createTextChild(WebInspector.UIString('Inherited from') + ' ');
584 separatorElement.appendChild(link); 548 separatorElement.appendChild(link);
585 return new WebInspector.SectionBlock(separatorElement); 549 return new WebInspector.SectionBlock(separatorElement);
550 }
551
552 updateFilter() {
553 var hasAnyVisibleSection = false;
554 for (var section of this.sections)
555 hasAnyVisibleSection |= section._updateFilter();
556 if (this._titleElement)
557 this._titleElement.classList.toggle('hidden', !hasAnyVisibleSection);
558 }
559
560 /**
561 * @return {?Element}
562 */
563 titleElement() {
564 return this._titleElement;
565 }
586 }; 566 };
587 567
588 WebInspector.SectionBlock.prototype = {
589 updateFilter: function()
590 {
591 var hasAnyVisibleSection = false;
592 for (var section of this.sections)
593 hasAnyVisibleSection |= section._updateFilter();
594 if (this._titleElement)
595 this._titleElement.classList.toggle("hidden", !hasAnyVisibleSection) ;
596 },
597
598 /**
599 * @return {?Element}
600 */
601 titleElement: function()
602 {
603 return this._titleElement;
604 }
605 };
606 568
607 /** 569 /**
608 * @constructor 570 * @unrestricted
609 * @param {!WebInspector.StylesSidebarPane} parentPane
610 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
611 * @param {!WebInspector.CSSStyleDeclaration} style
612 */ 571 */
613 WebInspector.StylePropertiesSection = function(parentPane, matchedStyles, style) 572 WebInspector.StylePropertiesSection = class {
614 { 573 /**
574 * @param {!WebInspector.StylesSidebarPane} parentPane
575 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
576 * @param {!WebInspector.CSSStyleDeclaration} style
577 */
578 constructor(parentPane, matchedStyles, style) {
615 this._parentPane = parentPane; 579 this._parentPane = parentPane;
616 this._style = style; 580 this._style = style;
617 this._matchedStyles = matchedStyles; 581 this._matchedStyles = matchedStyles;
618 this.editable = !!(style.styleSheetId && style.range); 582 this.editable = !!(style.styleSheetId && style.range);
619 583
620 var rule = style.parentRule; 584 var rule = style.parentRule;
621 this.element = createElementWithClass("div", "styles-section matched-styles monospace"); 585 this.element = createElementWithClass('div', 'styles-section matched-styles monospace');
622 this.element._section = this; 586 this.element._section = this;
623 587
624 this._titleElement = this.element.createChild("div", "styles-section-title " + (rule ? "styles-selector" : "")); 588 this._titleElement = this.element.createChild('div', 'styles-section-title ' + (rule ? 'styles-selector' : ''));
625 589
626 this.propertiesTreeOutline = new TreeOutlineInShadow(); 590 this.propertiesTreeOutline = new TreeOutlineInShadow();
627 this.propertiesTreeOutline.registerRequiredCSS("elements/stylesSectionTree.c ss"); 591 this.propertiesTreeOutline.registerRequiredCSS('elements/stylesSectionTree.c ss');
628 this.propertiesTreeOutline.element.classList.add("style-properties", "matche d-styles", "monospace"); 592 this.propertiesTreeOutline.element.classList.add('style-properties', 'matche d-styles', 'monospace');
629 this.propertiesTreeOutline.section = this; 593 this.propertiesTreeOutline.section = this;
630 this.element.appendChild(this.propertiesTreeOutline.element); 594 this.element.appendChild(this.propertiesTreeOutline.element);
631 595
632 var selectorContainer = createElement("div"); 596 var selectorContainer = createElement('div');
633 this._selectorElement = createElementWithClass("span", "selector"); 597 this._selectorElement = createElementWithClass('span', 'selector');
634 this._selectorElement.textContent = this._headerText(); 598 this._selectorElement.textContent = this._headerText();
635 selectorContainer.appendChild(this._selectorElement); 599 selectorContainer.appendChild(this._selectorElement);
636 this._selectorElement.addEventListener("mouseenter", this._onMouseEnterSelec tor.bind(this), false); 600 this._selectorElement.addEventListener('mouseenter', this._onMouseEnterSelec tor.bind(this), false);
637 this._selectorElement.addEventListener("mouseleave", this._onMouseOutSelecto r.bind(this), false); 601 this._selectorElement.addEventListener('mouseleave', this._onMouseOutSelecto r.bind(this), false);
638 602
639 var openBrace = createElement("span"); 603 var openBrace = createElement('span');
640 openBrace.textContent = " {"; 604 openBrace.textContent = ' {';
641 selectorContainer.appendChild(openBrace); 605 selectorContainer.appendChild(openBrace);
642 selectorContainer.addEventListener("mousedown", this._handleEmptySpaceMouseD own.bind(this), false); 606 selectorContainer.addEventListener('mousedown', this._handleEmptySpaceMouseD own.bind(this), false);
643 selectorContainer.addEventListener("click", this._handleSelectorContainerCli ck.bind(this), false); 607 selectorContainer.addEventListener('click', this._handleSelectorContainerCli ck.bind(this), false);
644 608
645 var closeBrace = this.element.createChild("div", "sidebar-pane-closing-brace "); 609 var closeBrace = this.element.createChild('div', 'sidebar-pane-closing-brace ');
646 closeBrace.textContent = "}"; 610 closeBrace.textContent = '}';
647 611
648 this._createHoverMenuToolbar(closeBrace); 612 this._createHoverMenuToolbar(closeBrace);
649 613
650 this._selectorElement.addEventListener("click", this._handleSelectorClick.bi nd(this), false); 614 this._selectorElement.addEventListener('click', this._handleSelectorClick.bi nd(this), false);
651 this.element.addEventListener("mousedown", this._handleEmptySpaceMouseDown.b ind(this), false); 615 this.element.addEventListener('mousedown', this._handleEmptySpaceMouseDown.b ind(this), false);
652 this.element.addEventListener("click", this._handleEmptySpaceClick.bind(this ), false); 616 this.element.addEventListener('click', this._handleEmptySpaceClick.bind(this ), false);
653 this.element.addEventListener("mousemove", this._onMouseMove.bind(this), fal se); 617 this.element.addEventListener('mousemove', this._onMouseMove.bind(this), fal se);
654 this.element.addEventListener("mouseleave", this._setSectionHovered.bind(thi s, false), false); 618 this.element.addEventListener('mouseleave', this._setSectionHovered.bind(thi s, false), false);
655 619
656 if (rule) { 620 if (rule) {
657 // Prevent editing the user agent and user rules. 621 // Prevent editing the user agent and user rules.
658 if (rule.isUserAgent() || rule.isInjected()) { 622 if (rule.isUserAgent() || rule.isInjected()) {
659 this.editable = false; 623 this.editable = false;
660 } else { 624 } else {
661 // Check this is a real CSSRule, not a bogus object coming from WebI nspector.BlankStylePropertiesSection. 625 // Check this is a real CSSRule, not a bogus object coming from WebInspe ctor.BlankStylePropertiesSection.
662 if (rule.styleSheetId) 626 if (rule.styleSheetId)
663 this.navigable = !!rule.resourceURL(); 627 this.navigable = !!rule.resourceURL();
664 } 628 }
665 } 629 }
666 630
667 this._mediaListElement = this._titleElement.createChild("div", "media-list m edia-matches"); 631 this._mediaListElement = this._titleElement.createChild('div', 'media-list m edia-matches');
668 this._selectorRefElement = this._titleElement.createChild("div", "styles-sec tion-subtitle"); 632 this._selectorRefElement = this._titleElement.createChild('div', 'styles-sec tion-subtitle');
669 this._updateMediaList(); 633 this._updateMediaList();
670 this._updateRuleOrigin(); 634 this._updateRuleOrigin();
671 this._titleElement.appendChild(selectorContainer); 635 this._titleElement.appendChild(selectorContainer);
672 this._selectorContainer = selectorContainer; 636 this._selectorContainer = selectorContainer;
673 637
674 if (this.navigable) 638 if (this.navigable)
675 this.element.classList.add("navigable"); 639 this.element.classList.add('navigable');
676 640
677 if (!this.editable) { 641 if (!this.editable) {
678 this.element.classList.add("read-only"); 642 this.element.classList.add('read-only');
679 this.propertiesTreeOutline.element.classList.add("read-only"); 643 this.propertiesTreeOutline.element.classList.add('read-only');
680 } 644 }
681 645
682 this._hoverableSelectorsMode = false; 646 this._hoverableSelectorsMode = false;
683 this._markSelectorMatches(); 647 this._markSelectorMatches();
684 this.onpopulate(); 648 this.onpopulate();
685 }; 649 }
686 650
687 WebInspector.StylePropertiesSection.prototype = { 651 /**
688 /** 652 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
689 * @param {boolean} isHovered 653 * @param {!WebInspector.Linkifier} linkifier
690 */ 654 * @param {?WebInspector.CSSRule} rule
691 _setSectionHovered: function(isHovered) 655 * @return {!Node}
692 { 656 */
693 this.element.classList.toggle("styles-panel-hovered", isHovered); 657 static createRuleOriginNode(matchedStyles, linkifier, rule) {
694 this.propertiesTreeOutline.element.classList.toggle("styles-panel-hovere d", isHovered);
695 if (this._hoverableSelectorsMode !== isHovered) {
696 this._hoverableSelectorsMode = isHovered;
697 this._markSelectorMatches();
698 }
699 },
700
701 /**
702 * @param {!Event} event
703 */
704 _onMouseMove: function(event)
705 {
706 var hasCtrlOrMeta = WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEvent} */(event));
707 this._setSectionHovered(hasCtrlOrMeta);
708 },
709
710 /**
711 * @param {!Element} container
712 */
713 _createHoverMenuToolbar: function(container)
714 {
715 if (!this.editable)
716 return;
717 var items = [];
718
719 var textShadowButton = new WebInspector.ToolbarButton(WebInspector.UIStr ing("Add text-shadow"), "text-shadow-toolbar-item");
720 textShadowButton.addEventListener("click", this._onInsertShadowPropertyC lick.bind(this, "text-shadow"));
721 items.push(textShadowButton);
722
723 var boxShadowButton = new WebInspector.ToolbarButton(WebInspector.UIStri ng("Add box-shadow"), "box-shadow-toolbar-item");
724 boxShadowButton.addEventListener("click", this._onInsertShadowPropertyCl ick.bind(this, "box-shadow"));
725 items.push(boxShadowButton);
726
727 var colorButton = new WebInspector.ToolbarButton(WebInspector.UIString(" Add color"), "foreground-color-toolbar-item");
728 colorButton.addEventListener("click", this._onInsertColorPropertyClick.b ind(this));
729 items.push(colorButton);
730
731 var backgroundButton = new WebInspector.ToolbarButton(WebInspector.UIStr ing("Add background-color"), "background-color-toolbar-item");
732 backgroundButton.addEventListener("click", this._onInsertBackgroundColor PropertyClick.bind(this));
733 items.push(backgroundButton);
734
735 var newRuleButton = null;
736 if (this._style.parentRule) {
737 newRuleButton = new WebInspector.ToolbarButton(WebInspector.UIString ("Insert Style Rule Below"), "add-toolbar-item");
738 newRuleButton.addEventListener("click", this._onNewRuleClick.bind(th is));
739 items.push(newRuleButton);
740 }
741
742 var sectionToolbar = new WebInspector.Toolbar("sidebar-pane-section-tool bar", container);
743 for (var i = 0; i < items.length; ++i)
744 sectionToolbar.appendToolbarItem(items[i]);
745
746 var menuButton = new WebInspector.ToolbarButton(WebInspector.UIString("M ore tools\u2026"), "menu-toolbar-item");
747 sectionToolbar.appendToolbarItem(menuButton);
748 setItemsVisibility.call(this, items, false);
749 sectionToolbar.element.addEventListener("mouseenter", setItemsVisibility .bind(this, items, true));
750 sectionToolbar.element.addEventListener("mouseleave", setItemsVisibility .bind(this, items, false));
751
752 /**
753 * @param {!Array<!WebInspector.ToolbarButton>} items
754 * @param {boolean} value
755 * @this {WebInspector.StylePropertiesSection}
756 */
757 function setItemsVisibility(items, value)
758 {
759 for (var i = 0; i < items.length; ++i)
760 items[i].setVisible(value);
761 menuButton.setVisible(!value);
762 if (this._isSASSStyle())
763 newRuleButton.setVisible(false);
764 }
765 },
766
767 /**
768 * @return {boolean}
769 */
770 _isSASSStyle: function()
771 {
772 var header = this._style.styleSheetId ? this._style.cssModel().styleShee tHeaderForId(this._style.styleSheetId) : null;
773 if (!header)
774 return false;
775 var sourceMap = header.cssModel().sourceMapForHeader(header);
776 return sourceMap ? sourceMap.editable() : false;
777 },
778
779 /**
780 * @return {!WebInspector.CSSStyleDeclaration}
781 */
782 style: function()
783 {
784 return this._style;
785 },
786
787 /**
788 * @return {string}
789 */
790 _headerText: function()
791 {
792 var node = this._matchedStyles.nodeForStyle(this._style);
793 if (this._style.type === WebInspector.CSSStyleDeclaration.Type.Inline)
794 return this._matchedStyles.isInherited(this._style) ? WebInspector.U IString("Style Attribute") : "element.style";
795 if (this._style.type === WebInspector.CSSStyleDeclaration.Type.Attribute s)
796 return node.nodeNameInCorrectCase() + "[" + WebInspector.UIString("A ttributes Style") + "]";
797 return this._style.parentRule.selectorText();
798 },
799
800 _onMouseOutSelector: function()
801 {
802 if (this._hoverTimer)
803 clearTimeout(this._hoverTimer);
804 WebInspector.DOMModel.hideDOMNodeHighlight();
805 },
806
807 _onMouseEnterSelector: function()
808 {
809 if (this._hoverTimer)
810 clearTimeout(this._hoverTimer);
811 this._hoverTimer = setTimeout(this._highlight.bind(this), 300);
812 },
813
814 _highlight: function()
815 {
816 WebInspector.DOMModel.hideDOMNodeHighlight();
817 var node = this._parentPane.node();
818 var domModel = node.domModel();
819 var selectors = this._style.parentRule ? this._style.parentRule.selector Text() : undefined;
820 domModel.highlightDOMNodeWithConfig(node.id, { mode: "all", showInfo: un defined, selectors: selectors });
821 },
822
823 /**
824 * @return {?WebInspector.StylePropertiesSection}
825 */
826 firstSibling: function()
827 {
828 var parent = this.element.parentElement;
829 if (!parent)
830 return null;
831
832 var childElement = parent.firstChild;
833 while (childElement) {
834 if (childElement._section)
835 return childElement._section;
836 childElement = childElement.nextSibling;
837 }
838
839 return null;
840 },
841
842 /**
843 * @return {?WebInspector.StylePropertiesSection}
844 */
845 lastSibling: function()
846 {
847 var parent = this.element.parentElement;
848 if (!parent)
849 return null;
850
851 var childElement = parent.lastChild;
852 while (childElement) {
853 if (childElement._section)
854 return childElement._section;
855 childElement = childElement.previousSibling;
856 }
857
858 return null;
859 },
860
861 /**
862 * @return {?WebInspector.StylePropertiesSection}
863 */
864 nextSibling: function()
865 {
866 var curElement = this.element;
867 do {
868 curElement = curElement.nextSibling;
869 } while (curElement && !curElement._section);
870
871 return curElement ? curElement._section : null;
872 },
873
874 /**
875 * @return {?WebInspector.StylePropertiesSection}
876 */
877 previousSibling: function()
878 {
879 var curElement = this.element;
880 do {
881 curElement = curElement.previousSibling;
882 } while (curElement && !curElement._section);
883
884 return curElement ? curElement._section : null;
885 },
886
887 /**
888 * @param {!WebInspector.Event} event
889 */
890 _onNewRuleClick: function(event)
891 {
892 event.consume();
893 var rule = this._style.parentRule;
894 var range = WebInspector.TextRange.createFromLocation(rule.style.range.e ndLine, rule.style.range.endColumn + 1);
895 this._parentPane._addBlankSection(this, /** @type {string} */(rule.style SheetId), range);
896 },
897
898 /**
899 * @param {string} propertyName
900 * @param {!WebInspector.Event} event
901 */
902 _onInsertShadowPropertyClick: function(propertyName, event)
903 {
904 event.consume(true);
905 var treeElement = this.addNewBlankProperty();
906 treeElement.property.name = propertyName;
907 treeElement.property.value = "0 0 black";
908 treeElement.updateTitle();
909 var shadowSwatchPopoverHelper = WebInspector.ShadowSwatchPopoverHelper.f orTreeElement(treeElement);
910 if (shadowSwatchPopoverHelper)
911 shadowSwatchPopoverHelper.showPopover();
912 },
913
914 /**
915 * @param {!WebInspector.Event} event
916 */
917 _onInsertColorPropertyClick: function(event)
918 {
919 event.consume(true);
920 var treeElement = this.addNewBlankProperty();
921 treeElement.property.name = "color";
922 treeElement.property.value = "black";
923 treeElement.updateTitle();
924 var colorSwatch = WebInspector.ColorSwatchPopoverIcon.forTreeElement(tre eElement);
925 if (colorSwatch)
926 colorSwatch.showPopover();
927 },
928
929 /**
930 * @param {!WebInspector.Event} event
931 */
932 _onInsertBackgroundColorPropertyClick: function(event)
933 {
934 event.consume(true);
935 var treeElement = this.addNewBlankProperty();
936 treeElement.property.name = "background-color";
937 treeElement.property.value = "white";
938 treeElement.updateTitle();
939 var colorSwatch = WebInspector.ColorSwatchPopoverIcon.forTreeElement(tre eElement);
940 if (colorSwatch)
941 colorSwatch.showPopover();
942 },
943
944 /**
945 * @param {!WebInspector.CSSModel.Edit} edit
946 */
947 _styleSheetEdited: function(edit)
948 {
949 var rule = this._style.parentRule;
950 if (rule)
951 rule.rebase(edit);
952 else
953 this._style.rebase(edit);
954
955 this._updateMediaList();
956 this._updateRuleOrigin();
957 },
958
959 /**
960 * @param {!Array.<!WebInspector.CSSMedia>} mediaRules
961 */
962 _createMediaList: function(mediaRules)
963 {
964 for (var i = mediaRules.length - 1; i >= 0; --i) {
965 var media = mediaRules[i];
966 // Don't display trivial non-print media types.
967 if (!media.text.includes("(") && media.text !== "print")
968 continue;
969 var mediaDataElement = this._mediaListElement.createChild("div", "me dia");
970 var mediaContainerElement = mediaDataElement.createChild("span");
971 var mediaTextElement = mediaContainerElement.createChild("span", "me dia-text");
972 switch (media.source) {
973 case WebInspector.CSSMedia.Source.LINKED_SHEET:
974 case WebInspector.CSSMedia.Source.INLINE_SHEET:
975 mediaTextElement.textContent = "media=\"" + media.text + "\"";
976 break;
977 case WebInspector.CSSMedia.Source.MEDIA_RULE:
978 var decoration = mediaContainerElement.createChild("span");
979 mediaContainerElement.insertBefore(decoration, mediaTextElement) ;
980 decoration.textContent = "@media ";
981 mediaTextElement.textContent = media.text;
982 if (media.styleSheetId) {
983 mediaDataElement.classList.add("editable-media");
984 mediaTextElement.addEventListener("click", this._handleMedia RuleClick.bind(this, media, mediaTextElement), false);
985 }
986 break;
987 case WebInspector.CSSMedia.Source.IMPORT_RULE:
988 mediaTextElement.textContent = "@import " + media.text;
989 break;
990 }
991 }
992 },
993
994 _updateMediaList: function()
995 {
996 this._mediaListElement.removeChildren();
997 if (this._style.parentRule && this._style.parentRule instanceof WebInspe ctor.CSSStyleRule)
998 this._createMediaList(this._style.parentRule.media);
999 },
1000
1001 /**
1002 * @param {string} propertyName
1003 * @return {boolean}
1004 */
1005 isPropertyInherited: function(propertyName)
1006 {
1007 if (this._matchedStyles.isInherited(this._style)) {
1008 // While rendering inherited stylesheet, reverse meaning of this pro perty.
1009 // Render truly inherited properties with black, i.e. return them as non-inherited.
1010 return !WebInspector.cssMetadata().isPropertyInherited(propertyName) ;
1011 }
1012 return false;
1013 },
1014
1015 /**
1016 * @return {?WebInspector.StylePropertiesSection}
1017 */
1018 nextEditableSibling: function()
1019 {
1020 var curSection = this;
1021 do {
1022 curSection = curSection.nextSibling();
1023 } while (curSection && !curSection.editable);
1024
1025 if (!curSection) {
1026 curSection = this.firstSibling();
1027 while (curSection && !curSection.editable)
1028 curSection = curSection.nextSibling();
1029 }
1030
1031 return (curSection && curSection.editable) ? curSection : null;
1032 },
1033
1034 /**
1035 * @return {?WebInspector.StylePropertiesSection}
1036 */
1037 previousEditableSibling: function()
1038 {
1039 var curSection = this;
1040 do {
1041 curSection = curSection.previousSibling();
1042 } while (curSection && !curSection.editable);
1043
1044 if (!curSection) {
1045 curSection = this.lastSibling();
1046 while (curSection && !curSection.editable)
1047 curSection = curSection.previousSibling();
1048 }
1049
1050 return (curSection && curSection.editable) ? curSection : null;
1051 },
1052
1053 /**
1054 * @param {boolean} full
1055 */
1056 update: function(full)
1057 {
1058 this._selectorElement.textContent = this._headerText();
1059 this._markSelectorMatches();
1060 if (full) {
1061 this.propertiesTreeOutline.removeChildren();
1062 this.onpopulate();
1063 } else {
1064 var child = this.propertiesTreeOutline.firstChild();
1065 while (child) {
1066 child.setOverloaded(this._isPropertyOverloaded(child.property));
1067 child = child.traverseNextTreeElement(false, null, true);
1068 }
1069 }
1070 this.afterUpdate();
1071 },
1072
1073 afterUpdate: function()
1074 {
1075 if (this._afterUpdate) {
1076 this._afterUpdate(this);
1077 delete this._afterUpdate;
1078 this._afterUpdateFinishedForTest();
1079 }
1080 },
1081
1082 _afterUpdateFinishedForTest: function()
1083 {
1084 },
1085
1086 onpopulate: function()
1087 {
1088 var style = this._style;
1089 for (var property of style.leadingProperties()) {
1090 var isShorthand = !!style.longhandProperties(property.name).length;
1091 var inherited = this.isPropertyInherited(property.name);
1092 var overloaded = this._isPropertyOverloaded(property);
1093 var item = new WebInspector.StylePropertyTreeElement(this._parentPan e, this._matchedStyles, property, isShorthand, inherited, overloaded);
1094 this.propertiesTreeOutline.appendChild(item);
1095 }
1096 },
1097
1098 /**
1099 * @param {!WebInspector.CSSProperty} property
1100 * @return {boolean}
1101 */
1102 _isPropertyOverloaded: function(property)
1103 {
1104 return this._matchedStyles.propertyState(property) === WebInspector.CSSM atchedStyles.PropertyState.Overloaded;
1105 },
1106
1107 /**
1108 * @return {boolean}
1109 */
1110 _updateFilter: function()
1111 {
1112 var hasMatchingChild = false;
1113 for (var child of this.propertiesTreeOutline.rootElement().children())
1114 hasMatchingChild |= child._updateFilter();
1115
1116 var regex = this._parentPane.filterRegex();
1117 var hideRule = !hasMatchingChild && !!regex && !regex.test(this.element. deepTextContent());
1118 this.element.classList.toggle("hidden", hideRule);
1119 if (!hideRule && this._style.parentRule)
1120 this._markSelectorHighlights();
1121 return !hideRule;
1122 },
1123
1124 _markSelectorMatches: function()
1125 {
1126 var rule = this._style.parentRule;
1127 if (!rule)
1128 return;
1129
1130 this._mediaListElement.classList.toggle("media-matches", this._matchedSt yles.mediaMatches(this._style));
1131
1132 var selectorTexts = rule.selectors.map(selector => selector.text);
1133 var matchingSelectorIndexes = this._matchedStyles.matchingSelectors(/** @type {!WebInspector.CSSStyleRule} */(rule));
1134 var matchingSelectors = new Array(selectorTexts.length).fill(false);
1135 for (var matchingIndex of matchingSelectorIndexes)
1136 matchingSelectors[matchingIndex] = true;
1137
1138 var fragment = this._hoverableSelectorsMode ? this._renderHoverableSelec tors(selectorTexts, matchingSelectors) : this._renderSimplifiedSelectors(selecto rTexts, matchingSelectors);
1139 this._selectorElement.removeChildren();
1140 this._selectorElement.appendChild(fragment);
1141 this._markSelectorHighlights();
1142 },
1143
1144 /**
1145 * @param {!Array<string>} selectors
1146 * @param {!Array<boolean>} matchingSelectors
1147 * @return {!DocumentFragment}
1148 */
1149 _renderHoverableSelectors: function(selectors, matchingSelectors)
1150 {
1151 var fragment = createDocumentFragment();
1152 for (var i = 0; i < selectors.length ; ++i) {
1153 if (i)
1154 fragment.createTextChild(", ");
1155 fragment.appendChild(this._createSelectorElement(selectors[i], match ingSelectors[i], i));
1156 }
1157 return fragment;
1158 },
1159
1160 /**
1161 * @param {string} text
1162 * @param {boolean} isMatching
1163 * @param {number=} navigationIndex
1164 * @return {!Element}
1165 */
1166 _createSelectorElement: function(text, isMatching, navigationIndex)
1167 {
1168 var element = createElementWithClass("span", "simple-selector");
1169 element.classList.toggle("selector-matches", isMatching);
1170 if (typeof navigationIndex === "number")
1171 element._selectorIndex = navigationIndex;
1172 element.textContent = text;
1173 return element;
1174 },
1175
1176 /**
1177 * @param {!Array<string>} selectors
1178 * @param {!Array<boolean>} matchingSelectors
1179 * @return {!DocumentFragment}
1180 */
1181 _renderSimplifiedSelectors: function(selectors, matchingSelectors)
1182 {
1183 var fragment = createDocumentFragment();
1184 var currentMatching = false;
1185 var text = "";
1186 for (var i = 0; i < selectors.length; ++i) {
1187 if (currentMatching !== matchingSelectors[i] && text) {
1188 fragment.appendChild(this._createSelectorElement(text, currentMa tching));
1189 text = "";
1190 }
1191 currentMatching = matchingSelectors[i];
1192 text += selectors[i] + (i === selectors.length - 1 ? "" : ", ");
1193 }
1194 if (text)
1195 fragment.appendChild(this._createSelectorElement(text, currentMatchi ng));
1196 return fragment;
1197 },
1198
1199 _markSelectorHighlights: function()
1200 {
1201 var selectors = this._selectorElement.getElementsByClassName("simple-sel ector");
1202 var regex = this._parentPane.filterRegex();
1203 for (var i = 0; i < selectors.length; ++i) {
1204 var selectorMatchesFilter = !!regex && regex.test(selectors[i].textC ontent);
1205 selectors[i].classList.toggle("filter-match", selectorMatchesFilter) ;
1206 }
1207 },
1208
1209 /**
1210 * @return {boolean}
1211 */
1212 _checkWillCancelEditing: function()
1213 {
1214 var willCauseCancelEditing = this._willCauseCancelEditing;
1215 delete this._willCauseCancelEditing;
1216 return willCauseCancelEditing;
1217 },
1218
1219 /**
1220 * @param {!Event} event
1221 */
1222 _handleSelectorContainerClick: function(event)
1223 {
1224 if (this._checkWillCancelEditing() || !this.editable)
1225 return;
1226 if (event.target === this._selectorContainer) {
1227 this.addNewBlankProperty(0).startEditing();
1228 event.consume(true);
1229 }
1230 },
1231
1232 /**
1233 * @param {number=} index
1234 * @return {!WebInspector.StylePropertyTreeElement}
1235 */
1236 addNewBlankProperty: function(index)
1237 {
1238 var property = this._style.newBlankProperty(index);
1239 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, t his._matchedStyles, property, false, false, false);
1240 index = property.index;
1241 this.propertiesTreeOutline.insertChild(item, index);
1242 item.listItemElement.textContent = "";
1243 item._newProperty = true;
1244 item.updateTitle();
1245 return item;
1246 },
1247
1248 _handleEmptySpaceMouseDown: function()
1249 {
1250 this._willCauseCancelEditing = this._parentPane._isEditingStyle;
1251 },
1252
1253 /**
1254 * @param {!Event} event
1255 */
1256 _handleEmptySpaceClick: function(event)
1257 {
1258 if (!this.editable)
1259 return;
1260
1261 var targetElement = event.deepElementFromPoint();
1262 if (targetElement && !targetElement.isComponentSelectionCollapsed())
1263 return;
1264
1265 if (!event.target.isComponentSelectionCollapsed())
1266 return;
1267
1268 if (this._checkWillCancelEditing())
1269 return;
1270
1271 if (event.target.enclosingNodeOrSelfWithNodeName("a"))
1272 return;
1273
1274 if (event.target.classList.contains("header") || this.element.classList. contains("read-only") || event.target.enclosingNodeOrSelfWithClass("media")) {
1275 event.consume();
1276 return;
1277 }
1278 this.addNewBlankProperty().startEditing();
1279 event.consume(true);
1280 },
1281
1282 /**
1283 * @param {!WebInspector.CSSMedia} media
1284 * @param {!Element} element
1285 * @param {!Event} event
1286 */
1287 _handleMediaRuleClick: function(media, element, event)
1288 {
1289 if (WebInspector.isBeingEdited(element))
1290 return;
1291
1292 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEv ent} */(event)) && this.navigable) {
1293 var location = media.rawLocation();
1294 if (!location) {
1295 event.consume(true);
1296 return;
1297 }
1298 var uiLocation = WebInspector.cssWorkspaceBinding.rawLocationToUILoc ation(location);
1299 if (uiLocation)
1300 WebInspector.Revealer.reveal(uiLocation);
1301 event.consume(true);
1302 return;
1303 }
1304
1305 if (!this.editable || this._isSASSStyle())
1306 return;
1307
1308 var config = new WebInspector.InplaceEditor.Config(this._editingMediaCom mitted.bind(this, media), this._editingMediaCancelled.bind(this, element), undef ined, this._editingMediaBlurHandler.bind(this));
1309 WebInspector.InplaceEditor.startEditing(element, config);
1310
1311 element.getComponentSelection().setBaseAndExtent(element, 0, element, 1) ;
1312 this._parentPane.setEditingStyle(true);
1313 var parentMediaElement = element.enclosingNodeOrSelfWithClass("media");
1314 parentMediaElement.classList.add("editing-media");
1315
1316 event.consume(true);
1317 },
1318
1319 /**
1320 * @param {!Element} element
1321 */
1322 _editingMediaFinished: function(element)
1323 {
1324 this._parentPane.setEditingStyle(false);
1325 var parentMediaElement = element.enclosingNodeOrSelfWithClass("media");
1326 parentMediaElement.classList.remove("editing-media");
1327 },
1328
1329 /**
1330 * @param {!Element} element
1331 */
1332 _editingMediaCancelled: function(element)
1333 {
1334 this._editingMediaFinished(element);
1335 // Mark the selectors in group if necessary.
1336 // This is overridden by BlankStylePropertiesSection.
1337 this._markSelectorMatches();
1338 element.getComponentSelection().collapse(element, 0);
1339 },
1340
1341 /**
1342 * @param {!Element} editor
1343 * @param {!Event} blurEvent
1344 * @return {boolean}
1345 */
1346 _editingMediaBlurHandler: function(editor, blurEvent)
1347 {
1348 return true;
1349 },
1350
1351 /**
1352 * @param {!WebInspector.CSSMedia} media
1353 * @param {!Element} element
1354 * @param {string} newContent
1355 * @param {string} oldContent
1356 * @param {(!WebInspector.StylePropertyTreeElement.Context|undefined)} conte xt
1357 * @param {string} moveDirection
1358 */
1359 _editingMediaCommitted: function(media, element, newContent, oldContent, con text, moveDirection)
1360 {
1361 this._parentPane.setEditingStyle(false);
1362 this._editingMediaFinished(element);
1363
1364 if (newContent)
1365 newContent = newContent.trim();
1366
1367 /**
1368 * @param {boolean} success
1369 * @this {WebInspector.StylePropertiesSection}
1370 */
1371 function userCallback(success)
1372 {
1373 if (success) {
1374 this._matchedStyles.resetActiveProperties();
1375 this._parentPane._refreshUpdate(this);
1376 }
1377 delete this._parentPane._userOperation;
1378 this._editingMediaTextCommittedForTest();
1379 }
1380
1381 // This gets deleted in finishOperation(), which is called both on succe ss and failure.
1382 this._parentPane._userOperation = true;
1383 this._parentPane.cssModel().setMediaText(media.styleSheetId, media.range , newContent)
1384 .then(userCallback.bind(this));
1385 },
1386
1387 _editingMediaTextCommittedForTest: function() { },
1388
1389 /**
1390 * @param {!Event} event
1391 */
1392 _handleSelectorClick: function(event)
1393 {
1394 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEv ent} */(event)) && this.navigable && event.target.classList.contains("simple-sel ector")) {
1395 this._navigateToSelectorSource(event.target._selectorIndex, true);
1396 event.consume(true);
1397 return;
1398 }
1399 this._startEditingOnMouseEvent();
1400 event.consume(true);
1401 },
1402
1403 /**
1404 * @param {number} index
1405 * @param {boolean} focus
1406 */
1407 _navigateToSelectorSource: function(index, focus)
1408 {
1409 var cssModel = this._parentPane.cssModel();
1410 var rule = this._style.parentRule;
1411 var header = cssModel.styleSheetHeaderForId(/** @type {string} */(rule.s tyleSheetId));
1412 if (!header)
1413 return;
1414 var rawLocation = new WebInspector.CSSLocation(header, rule.lineNumberIn Source(index), rule.columnNumberInSource(index));
1415 var uiLocation = WebInspector.cssWorkspaceBinding.rawLocationToUILocatio n(rawLocation);
1416 if (uiLocation)
1417 WebInspector.Revealer.reveal(uiLocation, !focus);
1418 },
1419
1420 _startEditingOnMouseEvent: function()
1421 {
1422 if (!this.editable || this._isSASSStyle())
1423 return;
1424
1425 var rule = this._style.parentRule;
1426 if (!rule && !this.propertiesTreeOutline.rootElement().childCount()) {
1427 this.addNewBlankProperty().startEditing();
1428 return;
1429 }
1430
1431 if (!rule)
1432 return;
1433
1434 this.startEditingSelector();
1435 },
1436
1437 startEditingSelector: function()
1438 {
1439 var element = this._selectorElement;
1440 if (WebInspector.isBeingEdited(element))
1441 return;
1442
1443 element.scrollIntoViewIfNeeded(false);
1444 element.textContent = element.textContent; // Reset selector marks in gr oup.
1445
1446 var config = new WebInspector.InplaceEditor.Config(this.editingSelectorC ommitted.bind(this), this.editingSelectorCancelled.bind(this));
1447 WebInspector.InplaceEditor.startEditing(this._selectorElement, config);
1448
1449 element.getComponentSelection().setBaseAndExtent(element, 0, element, 1) ;
1450 this._parentPane.setEditingStyle(true);
1451 if (element.classList.contains("simple-selector"))
1452 this._navigateToSelectorSource(0, false);
1453 },
1454
1455 /**
1456 * @param {string} moveDirection
1457 */
1458 _moveEditorFromSelector: function(moveDirection)
1459 {
1460 this._markSelectorMatches();
1461
1462 if (!moveDirection)
1463 return;
1464
1465 if (moveDirection === "forward") {
1466 var firstChild = this.propertiesTreeOutline.firstChild();
1467 while (firstChild && firstChild.inherited())
1468 firstChild = firstChild.nextSibling;
1469 if (!firstChild)
1470 this.addNewBlankProperty().startEditing();
1471 else
1472 firstChild.startEditing(firstChild.nameElement);
1473 } else {
1474 var previousSection = this.previousEditableSibling();
1475 if (!previousSection)
1476 return;
1477
1478 previousSection.addNewBlankProperty().startEditing();
1479 }
1480 },
1481
1482 /**
1483 * @param {!Element} element
1484 * @param {string} newContent
1485 * @param {string} oldContent
1486 * @param {(!WebInspector.StylePropertyTreeElement.Context|undefined)} conte xt
1487 * @param {string} moveDirection
1488 */
1489 editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1490 {
1491 this._editingSelectorEnded();
1492 if (newContent)
1493 newContent = newContent.trim();
1494 if (newContent === oldContent) {
1495 // Revert to a trimmed version of the selector if need be.
1496 this._selectorElement.textContent = newContent;
1497 this._moveEditorFromSelector(moveDirection);
1498 return;
1499 }
1500 var rule = this._style.parentRule;
1501 if (!rule)
1502 return;
1503
1504 /**
1505 * @this {WebInspector.StylePropertiesSection}
1506 */
1507 function headerTextCommitted()
1508 {
1509 delete this._parentPane._userOperation;
1510 this._moveEditorFromSelector(moveDirection);
1511 this._editingSelectorCommittedForTest();
1512 }
1513
1514 // This gets deleted in finishOperationAndMoveEditor(), which is called both on success and failure.
1515 this._parentPane._userOperation = true;
1516 this._setHeaderText(rule, newContent).then(headerTextCommitted.bind(this ));
1517 },
1518
1519 /**
1520 * @param {!WebInspector.CSSRule} rule
1521 * @param {string} newContent
1522 * @return {!Promise}
1523 */
1524 _setHeaderText: function(rule, newContent)
1525 {
1526 /**
1527 * @param {!WebInspector.CSSStyleRule} rule
1528 * @param {boolean} success
1529 * @return {!Promise}
1530 * @this {WebInspector.StylePropertiesSection}
1531 */
1532 function onSelectorsUpdated(rule, success)
1533 {
1534 if (!success)
1535 return Promise.resolve();
1536 return this._matchedStyles.recomputeMatchingSelectors(rule)
1537 .then(updateSourceRanges.bind(this, rule));
1538 }
1539
1540 /**
1541 * @param {!WebInspector.CSSStyleRule} rule
1542 * @this {WebInspector.StylePropertiesSection}
1543 */
1544 function updateSourceRanges(rule)
1545 {
1546 var doesAffectSelectedNode = this._matchedStyles.matchingSelectors(r ule).length > 0;
1547 this.propertiesTreeOutline.element.classList.toggle("no-affect", !do esAffectSelectedNode);
1548 this._matchedStyles.resetActiveProperties();
1549 this._parentPane._refreshUpdate(this);
1550 }
1551
1552 console.assert(rule instanceof WebInspector.CSSStyleRule);
1553 var oldSelectorRange = rule.selectorRange();
1554 if (!oldSelectorRange)
1555 return Promise.resolve();
1556 var selectedNode = this._parentPane.node();
1557 return rule.setSelectorText(newContent)
1558 .then(onSelectorsUpdated.bind(this, /** @type {!WebInspector.CSSStyl eRule} */(rule), oldSelectorRange));
1559 },
1560
1561 _editingSelectorCommittedForTest: function() { },
1562
1563 _updateRuleOrigin: function()
1564 {
1565 this._selectorRefElement.removeChildren();
1566 this._selectorRefElement.appendChild(WebInspector.StylePropertiesSection .createRuleOriginNode(this._matchedStyles, this._parentPane._linkifier, this._st yle.parentRule));
1567 },
1568
1569 _editingSelectorEnded: function()
1570 {
1571 this._parentPane.setEditingStyle(false);
1572 },
1573
1574 editingSelectorCancelled: function()
1575 {
1576 this._editingSelectorEnded();
1577
1578 // Mark the selectors in group if necessary.
1579 // This is overridden by BlankStylePropertiesSection.
1580 this._markSelectorMatches();
1581 }
1582 };
1583
1584 /**
1585 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
1586 * @param {!WebInspector.Linkifier} linkifier
1587 * @param {?WebInspector.CSSRule} rule
1588 * @return {!Node}
1589 */
1590 WebInspector.StylePropertiesSection.createRuleOriginNode = function(matchedStyle s, linkifier, rule)
1591 {
1592 if (!rule) 658 if (!rule)
1593 return createTextNode(""); 659 return createTextNode('');
1594 660
1595 var ruleLocation; 661 var ruleLocation;
1596 if (rule instanceof WebInspector.CSSStyleRule) { 662 if (rule instanceof WebInspector.CSSStyleRule) {
1597 var matchingSelectors = matchedStyles.matchingSelectors(rule); 663 var matchingSelectors = matchedStyles.matchingSelectors(rule);
1598 var firstMatchingIndex = matchingSelectors.length ? matchingSelectors[0] : 0; 664 var firstMatchingIndex = matchingSelectors.length ? matchingSelectors[0] : 0;
1599 ruleLocation = rule.selectors[firstMatchingIndex].range; 665 ruleLocation = rule.selectors[firstMatchingIndex].range;
1600 } else if (rule instanceof WebInspector.CSSKeyframeRule) { 666 } else if (rule instanceof WebInspector.CSSKeyframeRule) {
1601 ruleLocation = rule.key().range; 667 ruleLocation = rule.key().range;
1602 } 668 }
1603 669
1604 var header = rule.styleSheetId ? matchedStyles.cssModel().styleSheetHeaderFo rId(rule.styleSheetId) : null; 670 var header = rule.styleSheetId ? matchedStyles.cssModel().styleSheetHeaderFo rId(rule.styleSheetId) : null;
1605 if (ruleLocation && rule.styleSheetId && header && header.resourceURL()) 671 if (ruleLocation && rule.styleSheetId && header && header.resourceURL())
1606 return WebInspector.StylePropertiesSection._linkifyRuleLocation(matchedS tyles.cssModel(), linkifier, rule.styleSheetId, ruleLocation); 672 return WebInspector.StylePropertiesSection._linkifyRuleLocation(
673 matchedStyles.cssModel(), linkifier, rule.styleSheetId, ruleLocation);
1607 674
1608 if (rule.isUserAgent()) 675 if (rule.isUserAgent())
1609 return createTextNode(WebInspector.UIString("user agent stylesheet")); 676 return createTextNode(WebInspector.UIString('user agent stylesheet'));
1610 if (rule.isInjected()) 677 if (rule.isInjected())
1611 return createTextNode(WebInspector.UIString("injected stylesheet")); 678 return createTextNode(WebInspector.UIString('injected stylesheet'));
1612 if (rule.isViaInspector()) 679 if (rule.isViaInspector())
1613 return createTextNode(WebInspector.UIString("via inspector")); 680 return createTextNode(WebInspector.UIString('via inspector'));
1614 681
1615 if (header && header.ownerNode) { 682 if (header && header.ownerNode) {
1616 var link = WebInspector.DOMPresentationUtils.linkifyDeferredNodeReferenc e(header.ownerNode); 683 var link = WebInspector.DOMPresentationUtils.linkifyDeferredNodeReference( header.ownerNode);
1617 link.textContent = "<style>…</style>"; 684 link.textContent = '<style>…</style>';
1618 return link; 685 return link;
1619 } 686 }
1620 687
1621 return createTextNode(""); 688 return createTextNode('');
1622 }; 689 }
1623 690
1624 /** 691 /**
1625 * @param {!WebInspector.CSSModel} cssModel 692 * @param {!WebInspector.CSSModel} cssModel
1626 * @param {!WebInspector.Linkifier} linkifier 693 * @param {!WebInspector.Linkifier} linkifier
1627 * @param {string} styleSheetId 694 * @param {string} styleSheetId
1628 * @param {!WebInspector.TextRange} ruleLocation 695 * @param {!WebInspector.TextRange} ruleLocation
1629 * @return {!Node} 696 * @return {!Node}
1630 */ 697 */
1631 WebInspector.StylePropertiesSection._linkifyRuleLocation = function(cssModel, li nkifier, styleSheetId, ruleLocation) 698 static _linkifyRuleLocation(cssModel, linkifier, styleSheetId, ruleLocation) {
1632 {
1633 var styleSheetHeader = cssModel.styleSheetHeaderForId(styleSheetId); 699 var styleSheetHeader = cssModel.styleSheetHeaderForId(styleSheetId);
1634 var lineNumber = styleSheetHeader.lineNumberInSource(ruleLocation.startLine) ; 700 var lineNumber = styleSheetHeader.lineNumberInSource(ruleLocation.startLine) ;
1635 var columnNumber = styleSheetHeader.columnNumberInSource(ruleLocation.startL ine, ruleLocation.startColumn); 701 var columnNumber = styleSheetHeader.columnNumberInSource(ruleLocation.startL ine, ruleLocation.startColumn);
1636 var matchingSelectorLocation = new WebInspector.CSSLocation(styleSheetHeader , lineNumber, columnNumber); 702 var matchingSelectorLocation = new WebInspector.CSSLocation(styleSheetHeader , lineNumber, columnNumber);
1637 return linkifier.linkifyCSSLocation(matchingSelectorLocation); 703 return linkifier.linkifyCSSLocation(matchingSelectorLocation);
704 }
705
706 /**
707 * @param {boolean} isHovered
708 */
709 _setSectionHovered(isHovered) {
710 this.element.classList.toggle('styles-panel-hovered', isHovered);
711 this.propertiesTreeOutline.element.classList.toggle('styles-panel-hovered', isHovered);
712 if (this._hoverableSelectorsMode !== isHovered) {
713 this._hoverableSelectorsMode = isHovered;
714 this._markSelectorMatches();
715 }
716 }
717
718 /**
719 * @param {!Event} event
720 */
721 _onMouseMove(event) {
722 var hasCtrlOrMeta = WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @ty pe {!MouseEvent} */ (event));
723 this._setSectionHovered(hasCtrlOrMeta);
724 }
725
726 /**
727 * @param {!Element} container
728 */
729 _createHoverMenuToolbar(container) {
730 if (!this.editable)
731 return;
732 var items = [];
733
734 var textShadowButton =
735 new WebInspector.ToolbarButton(WebInspector.UIString('Add text-shadow'), 'text-shadow-toolbar-item');
736 textShadowButton.addEventListener('click', this._onInsertShadowPropertyClick .bind(this, 'text-shadow'));
737 items.push(textShadowButton);
738
739 var boxShadowButton =
740 new WebInspector.ToolbarButton(WebInspector.UIString('Add box-shadow'), 'box-shadow-toolbar-item');
741 boxShadowButton.addEventListener('click', this._onInsertShadowPropertyClick. bind(this, 'box-shadow'));
742 items.push(boxShadowButton);
743
744 var colorButton =
745 new WebInspector.ToolbarButton(WebInspector.UIString('Add color'), 'fore ground-color-toolbar-item');
746 colorButton.addEventListener('click', this._onInsertColorPropertyClick.bind( this));
747 items.push(colorButton);
748
749 var backgroundButton =
750 new WebInspector.ToolbarButton(WebInspector.UIString('Add background-col or'), 'background-color-toolbar-item');
751 backgroundButton.addEventListener('click', this._onInsertBackgroundColorProp ertyClick.bind(this));
752 items.push(backgroundButton);
753
754 var newRuleButton = null;
755 if (this._style.parentRule) {
756 newRuleButton =
757 new WebInspector.ToolbarButton(WebInspector.UIString('Insert Style Rul e Below'), 'add-toolbar-item');
758 newRuleButton.addEventListener('click', this._onNewRuleClick.bind(this));
759 items.push(newRuleButton);
760 }
761
762 var sectionToolbar = new WebInspector.Toolbar('sidebar-pane-section-toolbar' , container);
763 for (var i = 0; i < items.length; ++i)
764 sectionToolbar.appendToolbarItem(items[i]);
765
766 var menuButton = new WebInspector.ToolbarButton(WebInspector.UIString('More tools\u2026'), 'menu-toolbar-item');
767 sectionToolbar.appendToolbarItem(menuButton);
768 setItemsVisibility.call(this, items, false);
769 sectionToolbar.element.addEventListener('mouseenter', setItemsVisibility.bin d(this, items, true));
770 sectionToolbar.element.addEventListener('mouseleave', setItemsVisibility.bin d(this, items, false));
771
772 /**
773 * @param {!Array<!WebInspector.ToolbarButton>} items
774 * @param {boolean} value
775 * @this {WebInspector.StylePropertiesSection}
776 */
777 function setItemsVisibility(items, value) {
778 for (var i = 0; i < items.length; ++i)
779 items[i].setVisible(value);
780 menuButton.setVisible(!value);
781 if (this._isSASSStyle())
782 newRuleButton.setVisible(false);
783 }
784 }
785
786 /**
787 * @return {boolean}
788 */
789 _isSASSStyle() {
790 var header =
791 this._style.styleSheetId ? this._style.cssModel().styleSheetHeaderForId( this._style.styleSheetId) : null;
792 if (!header)
793 return false;
794 var sourceMap = header.cssModel().sourceMapForHeader(header);
795 return sourceMap ? sourceMap.editable() : false;
796 }
797
798 /**
799 * @return {!WebInspector.CSSStyleDeclaration}
800 */
801 style() {
802 return this._style;
803 }
804
805 /**
806 * @return {string}
807 */
808 _headerText() {
809 var node = this._matchedStyles.nodeForStyle(this._style);
810 if (this._style.type === WebInspector.CSSStyleDeclaration.Type.Inline)
811 return this._matchedStyles.isInherited(this._style) ? WebInspector.UIStrin g('Style Attribute') : 'element.style';
812 if (this._style.type === WebInspector.CSSStyleDeclaration.Type.Attributes)
813 return node.nodeNameInCorrectCase() + '[' + WebInspector.UIString('Attribu tes Style') + ']';
814 return this._style.parentRule.selectorText();
815 }
816
817 _onMouseOutSelector() {
818 if (this._hoverTimer)
819 clearTimeout(this._hoverTimer);
820 WebInspector.DOMModel.hideDOMNodeHighlight();
821 }
822
823 _onMouseEnterSelector() {
824 if (this._hoverTimer)
825 clearTimeout(this._hoverTimer);
826 this._hoverTimer = setTimeout(this._highlight.bind(this), 300);
827 }
828
829 _highlight() {
830 WebInspector.DOMModel.hideDOMNodeHighlight();
831 var node = this._parentPane.node();
832 var domModel = node.domModel();
833 var selectors = this._style.parentRule ? this._style.parentRule.selectorText () : undefined;
834 domModel.highlightDOMNodeWithConfig(node.id, {mode: 'all', showInfo: undefin ed, selectors: selectors});
835 }
836
837 /**
838 * @return {?WebInspector.StylePropertiesSection}
839 */
840 firstSibling() {
841 var parent = this.element.parentElement;
842 if (!parent)
843 return null;
844
845 var childElement = parent.firstChild;
846 while (childElement) {
847 if (childElement._section)
848 return childElement._section;
849 childElement = childElement.nextSibling;
850 }
851
852 return null;
853 }
854
855 /**
856 * @return {?WebInspector.StylePropertiesSection}
857 */
858 lastSibling() {
859 var parent = this.element.parentElement;
860 if (!parent)
861 return null;
862
863 var childElement = parent.lastChild;
864 while (childElement) {
865 if (childElement._section)
866 return childElement._section;
867 childElement = childElement.previousSibling;
868 }
869
870 return null;
871 }
872
873 /**
874 * @return {?WebInspector.StylePropertiesSection}
875 */
876 nextSibling() {
877 var curElement = this.element;
878 do {
879 curElement = curElement.nextSibling;
880 } while (curElement && !curElement._section);
881
882 return curElement ? curElement._section : null;
883 }
884
885 /**
886 * @return {?WebInspector.StylePropertiesSection}
887 */
888 previousSibling() {
889 var curElement = this.element;
890 do {
891 curElement = curElement.previousSibling;
892 } while (curElement && !curElement._section);
893
894 return curElement ? curElement._section : null;
895 }
896
897 /**
898 * @param {!WebInspector.Event} event
899 */
900 _onNewRuleClick(event) {
901 event.consume();
902 var rule = this._style.parentRule;
903 var range = WebInspector.TextRange.createFromLocation(rule.style.range.endLi ne, rule.style.range.endColumn + 1);
904 this._parentPane._addBlankSection(this, /** @type {string} */ (rule.styleShe etId), range);
905 }
906
907 /**
908 * @param {string} propertyName
909 * @param {!WebInspector.Event} event
910 */
911 _onInsertShadowPropertyClick(propertyName, event) {
912 event.consume(true);
913 var treeElement = this.addNewBlankProperty();
914 treeElement.property.name = propertyName;
915 treeElement.property.value = '0 0 black';
916 treeElement.updateTitle();
917 var shadowSwatchPopoverHelper = WebInspector.ShadowSwatchPopoverHelper.forTr eeElement(treeElement);
918 if (shadowSwatchPopoverHelper)
919 shadowSwatchPopoverHelper.showPopover();
920 }
921
922 /**
923 * @param {!WebInspector.Event} event
924 */
925 _onInsertColorPropertyClick(event) {
926 event.consume(true);
927 var treeElement = this.addNewBlankProperty();
928 treeElement.property.name = 'color';
929 treeElement.property.value = 'black';
930 treeElement.updateTitle();
931 var colorSwatch = WebInspector.ColorSwatchPopoverIcon.forTreeElement(treeEle ment);
932 if (colorSwatch)
933 colorSwatch.showPopover();
934 }
935
936 /**
937 * @param {!WebInspector.Event} event
938 */
939 _onInsertBackgroundColorPropertyClick(event) {
940 event.consume(true);
941 var treeElement = this.addNewBlankProperty();
942 treeElement.property.name = 'background-color';
943 treeElement.property.value = 'white';
944 treeElement.updateTitle();
945 var colorSwatch = WebInspector.ColorSwatchPopoverIcon.forTreeElement(treeEle ment);
946 if (colorSwatch)
947 colorSwatch.showPopover();
948 }
949
950 /**
951 * @param {!WebInspector.CSSModel.Edit} edit
952 */
953 _styleSheetEdited(edit) {
954 var rule = this._style.parentRule;
955 if (rule)
956 rule.rebase(edit);
957 else
958 this._style.rebase(edit);
959
960 this._updateMediaList();
961 this._updateRuleOrigin();
962 }
963
964 /**
965 * @param {!Array.<!WebInspector.CSSMedia>} mediaRules
966 */
967 _createMediaList(mediaRules) {
968 for (var i = mediaRules.length - 1; i >= 0; --i) {
969 var media = mediaRules[i];
970 // Don't display trivial non-print media types.
971 if (!media.text.includes('(') && media.text !== 'print')
972 continue;
973 var mediaDataElement = this._mediaListElement.createChild('div', 'media');
974 var mediaContainerElement = mediaDataElement.createChild('span');
975 var mediaTextElement = mediaContainerElement.createChild('span', 'media-te xt');
976 switch (media.source) {
977 case WebInspector.CSSMedia.Source.LINKED_SHEET:
978 case WebInspector.CSSMedia.Source.INLINE_SHEET:
979 mediaTextElement.textContent = 'media="' + media.text + '"';
980 break;
981 case WebInspector.CSSMedia.Source.MEDIA_RULE:
982 var decoration = mediaContainerElement.createChild('span');
983 mediaContainerElement.insertBefore(decoration, mediaTextElement);
984 decoration.textContent = '@media ';
985 mediaTextElement.textContent = media.text;
986 if (media.styleSheetId) {
987 mediaDataElement.classList.add('editable-media');
988 mediaTextElement.addEventListener(
989 'click', this._handleMediaRuleClick.bind(this, media, mediaTextE lement), false);
990 }
991 break;
992 case WebInspector.CSSMedia.Source.IMPORT_RULE:
993 mediaTextElement.textContent = '@import ' + media.text;
994 break;
995 }
996 }
997 }
998
999 _updateMediaList() {
1000 this._mediaListElement.removeChildren();
1001 if (this._style.parentRule && this._style.parentRule instanceof WebInspector .CSSStyleRule)
1002 this._createMediaList(this._style.parentRule.media);
1003 }
1004
1005 /**
1006 * @param {string} propertyName
1007 * @return {boolean}
1008 */
1009 isPropertyInherited(propertyName) {
1010 if (this._matchedStyles.isInherited(this._style)) {
1011 // While rendering inherited stylesheet, reverse meaning of this property.
1012 // Render truly inherited properties with black, i.e. return them as non-i nherited.
1013 return !WebInspector.cssMetadata().isPropertyInherited(propertyName);
1014 }
1015 return false;
1016 }
1017
1018 /**
1019 * @return {?WebInspector.StylePropertiesSection}
1020 */
1021 nextEditableSibling() {
1022 var curSection = this;
1023 do {
1024 curSection = curSection.nextSibling();
1025 } while (curSection && !curSection.editable);
1026
1027 if (!curSection) {
1028 curSection = this.firstSibling();
1029 while (curSection && !curSection.editable)
1030 curSection = curSection.nextSibling();
1031 }
1032
1033 return (curSection && curSection.editable) ? curSection : null;
1034 }
1035
1036 /**
1037 * @return {?WebInspector.StylePropertiesSection}
1038 */
1039 previousEditableSibling() {
1040 var curSection = this;
1041 do {
1042 curSection = curSection.previousSibling();
1043 } while (curSection && !curSection.editable);
1044
1045 if (!curSection) {
1046 curSection = this.lastSibling();
1047 while (curSection && !curSection.editable)
1048 curSection = curSection.previousSibling();
1049 }
1050
1051 return (curSection && curSection.editable) ? curSection : null;
1052 }
1053
1054 /**
1055 * @param {boolean} full
1056 */
1057 update(full) {
1058 this._selectorElement.textContent = this._headerText();
1059 this._markSelectorMatches();
1060 if (full) {
1061 this.propertiesTreeOutline.removeChildren();
1062 this.onpopulate();
1063 } else {
1064 var child = this.propertiesTreeOutline.firstChild();
1065 while (child) {
1066 child.setOverloaded(this._isPropertyOverloaded(child.property));
1067 child = child.traverseNextTreeElement(false, null, true);
1068 }
1069 }
1070 this.afterUpdate();
1071 }
1072
1073 afterUpdate() {
1074 if (this._afterUpdate) {
1075 this._afterUpdate(this);
1076 delete this._afterUpdate;
1077 this._afterUpdateFinishedForTest();
1078 }
1079 }
1080
1081 _afterUpdateFinishedForTest() {
1082 }
1083
1084 onpopulate() {
1085 var style = this._style;
1086 for (var property of style.leadingProperties()) {
1087 var isShorthand = !!style.longhandProperties(property.name).length;
1088 var inherited = this.isPropertyInherited(property.name);
1089 var overloaded = this._isPropertyOverloaded(property);
1090 var item = new WebInspector.StylePropertyTreeElement(
1091 this._parentPane, this._matchedStyles, property, isShorthand, inherite d, overloaded);
1092 this.propertiesTreeOutline.appendChild(item);
1093 }
1094 }
1095
1096 /**
1097 * @param {!WebInspector.CSSProperty} property
1098 * @return {boolean}
1099 */
1100 _isPropertyOverloaded(property) {
1101 return this._matchedStyles.propertyState(property) === WebInspector.CSSMatch edStyles.PropertyState.Overloaded;
1102 }
1103
1104 /**
1105 * @return {boolean}
1106 */
1107 _updateFilter() {
1108 var hasMatchingChild = false;
1109 for (var child of this.propertiesTreeOutline.rootElement().children())
1110 hasMatchingChild |= child._updateFilter();
1111
1112 var regex = this._parentPane.filterRegex();
1113 var hideRule = !hasMatchingChild && !!regex && !regex.test(this.element.deep TextContent());
1114 this.element.classList.toggle('hidden', hideRule);
1115 if (!hideRule && this._style.parentRule)
1116 this._markSelectorHighlights();
1117 return !hideRule;
1118 }
1119
1120 _markSelectorMatches() {
1121 var rule = this._style.parentRule;
1122 if (!rule)
1123 return;
1124
1125 this._mediaListElement.classList.toggle('media-matches', this._matchedStyles .mediaMatches(this._style));
1126
1127 var selectorTexts = rule.selectors.map(selector => selector.text);
1128 var matchingSelectorIndexes =
1129 this._matchedStyles.matchingSelectors(/** @type {!WebInspector.CSSStyleR ule} */ (rule));
1130 var matchingSelectors = new Array(selectorTexts.length).fill(false);
1131 for (var matchingIndex of matchingSelectorIndexes)
1132 matchingSelectors[matchingIndex] = true;
1133
1134 var fragment = this._hoverableSelectorsMode ? this._renderHoverableSelectors (selectorTexts, matchingSelectors) :
1135 this._renderSimplifiedSelector s(selectorTexts, matchingSelectors);
1136 this._selectorElement.removeChildren();
1137 this._selectorElement.appendChild(fragment);
1138 this._markSelectorHighlights();
1139 }
1140
1141 /**
1142 * @param {!Array<string>} selectors
1143 * @param {!Array<boolean>} matchingSelectors
1144 * @return {!DocumentFragment}
1145 */
1146 _renderHoverableSelectors(selectors, matchingSelectors) {
1147 var fragment = createDocumentFragment();
1148 for (var i = 0; i < selectors.length; ++i) {
1149 if (i)
1150 fragment.createTextChild(', ');
1151 fragment.appendChild(this._createSelectorElement(selectors[i], matchingSel ectors[i], i));
1152 }
1153 return fragment;
1154 }
1155
1156 /**
1157 * @param {string} text
1158 * @param {boolean} isMatching
1159 * @param {number=} navigationIndex
1160 * @return {!Element}
1161 */
1162 _createSelectorElement(text, isMatching, navigationIndex) {
1163 var element = createElementWithClass('span', 'simple-selector');
1164 element.classList.toggle('selector-matches', isMatching);
1165 if (typeof navigationIndex === 'number')
1166 element._selectorIndex = navigationIndex;
1167 element.textContent = text;
1168 return element;
1169 }
1170
1171 /**
1172 * @param {!Array<string>} selectors
1173 * @param {!Array<boolean>} matchingSelectors
1174 * @return {!DocumentFragment}
1175 */
1176 _renderSimplifiedSelectors(selectors, matchingSelectors) {
1177 var fragment = createDocumentFragment();
1178 var currentMatching = false;
1179 var text = '';
1180 for (var i = 0; i < selectors.length; ++i) {
1181 if (currentMatching !== matchingSelectors[i] && text) {
1182 fragment.appendChild(this._createSelectorElement(text, currentMatching)) ;
1183 text = '';
1184 }
1185 currentMatching = matchingSelectors[i];
1186 text += selectors[i] + (i === selectors.length - 1 ? '' : ', ');
1187 }
1188 if (text)
1189 fragment.appendChild(this._createSelectorElement(text, currentMatching));
1190 return fragment;
1191 }
1192
1193 _markSelectorHighlights() {
1194 var selectors = this._selectorElement.getElementsByClassName('simple-selecto r');
1195 var regex = this._parentPane.filterRegex();
1196 for (var i = 0; i < selectors.length; ++i) {
1197 var selectorMatchesFilter = !!regex && regex.test(selectors[i].textContent );
1198 selectors[i].classList.toggle('filter-match', selectorMatchesFilter);
1199 }
1200 }
1201
1202 /**
1203 * @return {boolean}
1204 */
1205 _checkWillCancelEditing() {
1206 var willCauseCancelEditing = this._willCauseCancelEditing;
1207 delete this._willCauseCancelEditing;
1208 return willCauseCancelEditing;
1209 }
1210
1211 /**
1212 * @param {!Event} event
1213 */
1214 _handleSelectorContainerClick(event) {
1215 if (this._checkWillCancelEditing() || !this.editable)
1216 return;
1217 if (event.target === this._selectorContainer) {
1218 this.addNewBlankProperty(0).startEditing();
1219 event.consume(true);
1220 }
1221 }
1222
1223 /**
1224 * @param {number=} index
1225 * @return {!WebInspector.StylePropertyTreeElement}
1226 */
1227 addNewBlankProperty(index) {
1228 var property = this._style.newBlankProperty(index);
1229 var item =
1230 new WebInspector.StylePropertyTreeElement(this._parentPane, this._matche dStyles, property, false, false, false);
1231 index = property.index;
1232 this.propertiesTreeOutline.insertChild(item, index);
1233 item.listItemElement.textContent = '';
1234 item._newProperty = true;
1235 item.updateTitle();
1236 return item;
1237 }
1238
1239 _handleEmptySpaceMouseDown() {
1240 this._willCauseCancelEditing = this._parentPane._isEditingStyle;
1241 }
1242
1243 /**
1244 * @param {!Event} event
1245 */
1246 _handleEmptySpaceClick(event) {
1247 if (!this.editable)
1248 return;
1249
1250 var targetElement = event.deepElementFromPoint();
1251 if (targetElement && !targetElement.isComponentSelectionCollapsed())
1252 return;
1253
1254 if (!event.target.isComponentSelectionCollapsed())
1255 return;
1256
1257 if (this._checkWillCancelEditing())
1258 return;
1259
1260 if (event.target.enclosingNodeOrSelfWithNodeName('a'))
1261 return;
1262
1263 if (event.target.classList.contains('header') || this.element.classList.cont ains('read-only') ||
1264 event.target.enclosingNodeOrSelfWithClass('media')) {
1265 event.consume();
1266 return;
1267 }
1268 this.addNewBlankProperty().startEditing();
1269 event.consume(true);
1270 }
1271
1272 /**
1273 * @param {!WebInspector.CSSMedia} media
1274 * @param {!Element} element
1275 * @param {!Event} event
1276 */
1277 _handleMediaRuleClick(media, element, event) {
1278 if (WebInspector.isBeingEdited(element))
1279 return;
1280
1281 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEvent} */ (event)) && this.navigable) {
1282 var location = media.rawLocation();
1283 if (!location) {
1284 event.consume(true);
1285 return;
1286 }
1287 var uiLocation = WebInspector.cssWorkspaceBinding.rawLocationToUILocation( location);
1288 if (uiLocation)
1289 WebInspector.Revealer.reveal(uiLocation);
1290 event.consume(true);
1291 return;
1292 }
1293
1294 if (!this.editable || this._isSASSStyle())
1295 return;
1296
1297 var config = new WebInspector.InplaceEditor.Config(
1298 this._editingMediaCommitted.bind(this, media), this._editingMediaCancell ed.bind(this, element), undefined,
1299 this._editingMediaBlurHandler.bind(this));
1300 WebInspector.InplaceEditor.startEditing(element, config);
1301
1302 element.getComponentSelection().setBaseAndExtent(element, 0, element, 1);
1303 this._parentPane.setEditingStyle(true);
1304 var parentMediaElement = element.enclosingNodeOrSelfWithClass('media');
1305 parentMediaElement.classList.add('editing-media');
1306
1307 event.consume(true);
1308 }
1309
1310 /**
1311 * @param {!Element} element
1312 */
1313 _editingMediaFinished(element) {
1314 this._parentPane.setEditingStyle(false);
1315 var parentMediaElement = element.enclosingNodeOrSelfWithClass('media');
1316 parentMediaElement.classList.remove('editing-media');
1317 }
1318
1319 /**
1320 * @param {!Element} element
1321 */
1322 _editingMediaCancelled(element) {
1323 this._editingMediaFinished(element);
1324 // Mark the selectors in group if necessary.
1325 // This is overridden by BlankStylePropertiesSection.
1326 this._markSelectorMatches();
1327 element.getComponentSelection().collapse(element, 0);
1328 }
1329
1330 /**
1331 * @param {!Element} editor
1332 * @param {!Event} blurEvent
1333 * @return {boolean}
1334 */
1335 _editingMediaBlurHandler(editor, blurEvent) {
1336 return true;
1337 }
1338
1339 /**
1340 * @param {!WebInspector.CSSMedia} media
1341 * @param {!Element} element
1342 * @param {string} newContent
1343 * @param {string} oldContent
1344 * @param {(!WebInspector.StylePropertyTreeElement.Context|undefined)} context
1345 * @param {string} moveDirection
1346 */
1347 _editingMediaCommitted(media, element, newContent, oldContent, context, moveDi rection) {
1348 this._parentPane.setEditingStyle(false);
1349 this._editingMediaFinished(element);
1350
1351 if (newContent)
1352 newContent = newContent.trim();
1353
1354 /**
1355 * @param {boolean} success
1356 * @this {WebInspector.StylePropertiesSection}
1357 */
1358 function userCallback(success) {
1359 if (success) {
1360 this._matchedStyles.resetActiveProperties();
1361 this._parentPane._refreshUpdate(this);
1362 }
1363 delete this._parentPane._userOperation;
1364 this._editingMediaTextCommittedForTest();
1365 }
1366
1367 // This gets deleted in finishOperation(), which is called both on success a nd failure.
1368 this._parentPane._userOperation = true;
1369 this._parentPane.cssModel().setMediaText(media.styleSheetId, media.range, ne wContent).then(userCallback.bind(this));
1370 }
1371
1372 _editingMediaTextCommittedForTest() {
1373 }
1374
1375 /**
1376 * @param {!Event} event
1377 */
1378 _handleSelectorClick(event) {
1379 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEvent} */ (event)) && this.navigable &&
1380 event.target.classList.contains('simple-selector')) {
1381 this._navigateToSelectorSource(event.target._selectorIndex, true);
1382 event.consume(true);
1383 return;
1384 }
1385 this._startEditingOnMouseEvent();
1386 event.consume(true);
1387 }
1388
1389 /**
1390 * @param {number} index
1391 * @param {boolean} focus
1392 */
1393 _navigateToSelectorSource(index, focus) {
1394 var cssModel = this._parentPane.cssModel();
1395 var rule = this._style.parentRule;
1396 var header = cssModel.styleSheetHeaderForId(/** @type {string} */ (rule.styl eSheetId));
1397 if (!header)
1398 return;
1399 var rawLocation =
1400 new WebInspector.CSSLocation(header, rule.lineNumberInSource(index), rul e.columnNumberInSource(index));
1401 var uiLocation = WebInspector.cssWorkspaceBinding.rawLocationToUILocation(ra wLocation);
1402 if (uiLocation)
1403 WebInspector.Revealer.reveal(uiLocation, !focus);
1404 }
1405
1406 _startEditingOnMouseEvent() {
1407 if (!this.editable || this._isSASSStyle())
1408 return;
1409
1410 var rule = this._style.parentRule;
1411 if (!rule && !this.propertiesTreeOutline.rootElement().childCount()) {
1412 this.addNewBlankProperty().startEditing();
1413 return;
1414 }
1415
1416 if (!rule)
1417 return;
1418
1419 this.startEditingSelector();
1420 }
1421
1422 startEditingSelector() {
1423 var element = this._selectorElement;
1424 if (WebInspector.isBeingEdited(element))
1425 return;
1426
1427 element.scrollIntoViewIfNeeded(false);
1428 element.textContent = element.textContent; // Reset selector marks in group .
1429
1430 var config = new WebInspector.InplaceEditor.Config(
1431 this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled. bind(this));
1432 WebInspector.InplaceEditor.startEditing(this._selectorElement, config);
1433
1434 element.getComponentSelection().setBaseAndExtent(element, 0, element, 1);
1435 this._parentPane.setEditingStyle(true);
1436 if (element.classList.contains('simple-selector'))
1437 this._navigateToSelectorSource(0, false);
1438 }
1439
1440 /**
1441 * @param {string} moveDirection
1442 */
1443 _moveEditorFromSelector(moveDirection) {
1444 this._markSelectorMatches();
1445
1446 if (!moveDirection)
1447 return;
1448
1449 if (moveDirection === 'forward') {
1450 var firstChild = this.propertiesTreeOutline.firstChild();
1451 while (firstChild && firstChild.inherited())
1452 firstChild = firstChild.nextSibling;
1453 if (!firstChild)
1454 this.addNewBlankProperty().startEditing();
1455 else
1456 firstChild.startEditing(firstChild.nameElement);
1457 } else {
1458 var previousSection = this.previousEditableSibling();
1459 if (!previousSection)
1460 return;
1461
1462 previousSection.addNewBlankProperty().startEditing();
1463 }
1464 }
1465
1466 /**
1467 * @param {!Element} element
1468 * @param {string} newContent
1469 * @param {string} oldContent
1470 * @param {(!WebInspector.StylePropertyTreeElement.Context|undefined)} context
1471 * @param {string} moveDirection
1472 */
1473 editingSelectorCommitted(element, newContent, oldContent, context, moveDirecti on) {
1474 this._editingSelectorEnded();
1475 if (newContent)
1476 newContent = newContent.trim();
1477 if (newContent === oldContent) {
1478 // Revert to a trimmed version of the selector if need be.
1479 this._selectorElement.textContent = newContent;
1480 this._moveEditorFromSelector(moveDirection);
1481 return;
1482 }
1483 var rule = this._style.parentRule;
1484 if (!rule)
1485 return;
1486
1487 /**
1488 * @this {WebInspector.StylePropertiesSection}
1489 */
1490 function headerTextCommitted() {
1491 delete this._parentPane._userOperation;
1492 this._moveEditorFromSelector(moveDirection);
1493 this._editingSelectorCommittedForTest();
1494 }
1495
1496 // This gets deleted in finishOperationAndMoveEditor(), which is called both on success and failure.
1497 this._parentPane._userOperation = true;
1498 this._setHeaderText(rule, newContent).then(headerTextCommitted.bind(this));
1499 }
1500
1501 /**
1502 * @param {!WebInspector.CSSRule} rule
1503 * @param {string} newContent
1504 * @return {!Promise}
1505 */
1506 _setHeaderText(rule, newContent) {
1507 /**
1508 * @param {!WebInspector.CSSStyleRule} rule
1509 * @param {boolean} success
1510 * @return {!Promise}
1511 * @this {WebInspector.StylePropertiesSection}
1512 */
1513 function onSelectorsUpdated(rule, success) {
1514 if (!success)
1515 return Promise.resolve();
1516 return this._matchedStyles.recomputeMatchingSelectors(rule).then(updateSou rceRanges.bind(this, rule));
1517 }
1518
1519 /**
1520 * @param {!WebInspector.CSSStyleRule} rule
1521 * @this {WebInspector.StylePropertiesSection}
1522 */
1523 function updateSourceRanges(rule) {
1524 var doesAffectSelectedNode = this._matchedStyles.matchingSelectors(rule).l ength > 0;
1525 this.propertiesTreeOutline.element.classList.toggle('no-affect', !doesAffe ctSelectedNode);
1526 this._matchedStyles.resetActiveProperties();
1527 this._parentPane._refreshUpdate(this);
1528 }
1529
1530 console.assert(rule instanceof WebInspector.CSSStyleRule);
1531 var oldSelectorRange = rule.selectorRange();
1532 if (!oldSelectorRange)
1533 return Promise.resolve();
1534 var selectedNode = this._parentPane.node();
1535 return rule.setSelectorText(newContent)
1536 .then(onSelectorsUpdated.bind(this, /** @type {!WebInspector.CSSStyleRul e} */ (rule), oldSelectorRange));
1537 }
1538
1539 _editingSelectorCommittedForTest() {
1540 }
1541
1542 _updateRuleOrigin() {
1543 this._selectorRefElement.removeChildren();
1544 this._selectorRefElement.appendChild(WebInspector.StylePropertiesSection.cre ateRuleOriginNode(
1545 this._matchedStyles, this._parentPane._linkifier, this._style.parentRule ));
1546 }
1547
1548 _editingSelectorEnded() {
1549 this._parentPane.setEditingStyle(false);
1550 }
1551
1552 editingSelectorCancelled() {
1553 this._editingSelectorEnded();
1554
1555 // Mark the selectors in group if necessary.
1556 // This is overridden by BlankStylePropertiesSection.
1557 this._markSelectorMatches();
1558 }
1638 }; 1559 };
1639 1560
1561
1640 /** 1562 /**
1641 * @constructor 1563 * @unrestricted
1642 * @extends {WebInspector.StylePropertiesSection}
1643 * @param {!WebInspector.StylesSidebarPane} stylesPane
1644 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
1645 * @param {string} defaultSelectorText
1646 * @param {string} styleSheetId
1647 * @param {!WebInspector.TextRange} ruleLocation
1648 * @param {!WebInspector.CSSStyleDeclaration} insertAfterStyle
1649 */ 1564 */
1650 WebInspector.BlankStylePropertiesSection = function(stylesPane, matchedStyles, d efaultSelectorText, styleSheetId, ruleLocation, insertAfterStyle) 1565 WebInspector.BlankStylePropertiesSection = class extends WebInspector.StylePrope rtiesSection {
1651 { 1566 /**
1652 var cssModel = /** @type {!WebInspector.CSSModel} */(stylesPane.cssModel()); 1567 * @param {!WebInspector.StylesSidebarPane} stylesPane
1568 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
1569 * @param {string} defaultSelectorText
1570 * @param {string} styleSheetId
1571 * @param {!WebInspector.TextRange} ruleLocation
1572 * @param {!WebInspector.CSSStyleDeclaration} insertAfterStyle
1573 */
1574 constructor(stylesPane, matchedStyles, defaultSelectorText, styleSheetId, rule Location, insertAfterStyle) {
1575 var cssModel = /** @type {!WebInspector.CSSModel} */ (stylesPane.cssModel()) ;
1653 var rule = WebInspector.CSSStyleRule.createDummyRule(cssModel, defaultSelect orText); 1576 var rule = WebInspector.CSSStyleRule.createDummyRule(cssModel, defaultSelect orText);
1654 WebInspector.StylePropertiesSection.call(this, stylesPane, matchedStyles, ru le.style); 1577 super(stylesPane, matchedStyles, rule.style);
1655 this._ruleLocation = ruleLocation; 1578 this._ruleLocation = ruleLocation;
1656 this._styleSheetId = styleSheetId; 1579 this._styleSheetId = styleSheetId;
1657 this._selectorRefElement.removeChildren(); 1580 this._selectorRefElement.removeChildren();
1658 this._selectorRefElement.appendChild(WebInspector.StylePropertiesSection._li nkifyRuleLocation(cssModel, this._parentPane._linkifier, styleSheetId, this._act ualRuleLocation())); 1581 this._selectorRefElement.appendChild(WebInspector.StylePropertiesSection._li nkifyRuleLocation(
1582 cssModel, this._parentPane._linkifier, styleSheetId, this._actualRuleLoc ation()));
1659 if (insertAfterStyle && insertAfterStyle.parentRule) 1583 if (insertAfterStyle && insertAfterStyle.parentRule)
1660 this._createMediaList(insertAfterStyle.parentRule.media); 1584 this._createMediaList(insertAfterStyle.parentRule.media);
1661 this.element.classList.add("blank-section"); 1585 this.element.classList.add('blank-section');
1586 }
1587
1588 /**
1589 * @return {!WebInspector.TextRange}
1590 */
1591 _actualRuleLocation() {
1592 var prefix = this._rulePrefix();
1593 var lines = prefix.split('\n');
1594 var editRange = new WebInspector.TextRange(0, 0, lines.length - 1, lines.pee kLast().length);
1595 return this._ruleLocation.rebaseAfterTextEdit(WebInspector.TextRange.createF romLocation(0, 0), editRange);
1596 }
1597
1598 /**
1599 * @return {string}
1600 */
1601 _rulePrefix() {
1602 return this._ruleLocation.startLine === 0 && this._ruleLocation.startColumn === 0 ? '' : '\n\n';
1603 }
1604
1605 /**
1606 * @return {boolean}
1607 */
1608 get isBlank() {
1609 return !this._normal;
1610 }
1611
1612 /**
1613 * @override
1614 * @param {!Element} element
1615 * @param {string} newContent
1616 * @param {string} oldContent
1617 * @param {!WebInspector.StylePropertyTreeElement.Context|undefined} context
1618 * @param {string} moveDirection
1619 */
1620 editingSelectorCommitted(element, newContent, oldContent, context, moveDirecti on) {
1621 if (!this.isBlank) {
1622 super.editingSelectorCommitted(element, newContent, oldContent, context, m oveDirection);
1623 return;
1624 }
1625
1626 /**
1627 * @param {?WebInspector.CSSStyleRule} newRule
1628 * @return {!Promise}
1629 * @this {WebInspector.StylePropertiesSection}
1630 */
1631 function onRuleAdded(newRule) {
1632 if (!newRule) {
1633 this.editingSelectorCancelled();
1634 this._editingSelectorCommittedForTest();
1635 return Promise.resolve();
1636 }
1637 return this._matchedStyles.addNewRule(newRule, this._matchedStyles.node())
1638 .then(onAddedToCascade.bind(this, newRule));
1639 }
1640
1641 /**
1642 * @param {!WebInspector.CSSStyleRule} newRule
1643 * @this {WebInspector.StylePropertiesSection}
1644 */
1645 function onAddedToCascade(newRule) {
1646 var doesSelectorAffectSelectedNode = this._matchedStyles.matchingSelectors (newRule).length > 0;
1647 this._makeNormal(newRule);
1648
1649 if (!doesSelectorAffectSelectedNode)
1650 this.propertiesTreeOutline.element.classList.add('no-affect');
1651
1652 this._updateRuleOrigin();
1653 if (this.element.parentElement) // Might have been detached already.
1654 this._moveEditorFromSelector(moveDirection);
1655
1656 delete this._parentPane._userOperation;
1657 this._editingSelectorEnded();
1658 this._markSelectorMatches();
1659
1660 this._editingSelectorCommittedForTest();
1661 }
1662
1663 if (newContent)
1664 newContent = newContent.trim();
1665 this._parentPane._userOperation = true;
1666
1667 var cssModel = this._parentPane.cssModel();
1668 var ruleText = this._rulePrefix() + newContent + ' {}';
1669 cssModel.addRule(this._styleSheetId, ruleText, this._ruleLocation).then(onRu leAdded.bind(this));
1670 }
1671
1672 /**
1673 * @override
1674 */
1675 editingSelectorCancelled() {
1676 delete this._parentPane._userOperation;
1677 if (!this.isBlank) {
1678 super.editingSelectorCancelled();
1679 return;
1680 }
1681
1682 this._editingSelectorEnded();
1683 this._parentPane.removeSection(this);
1684 }
1685
1686 /**
1687 * @param {!WebInspector.CSSRule} newRule
1688 */
1689 _makeNormal(newRule) {
1690 this.element.classList.remove('blank-section');
1691 this._style = newRule.style;
1692 // FIXME: replace this instance by a normal WebInspector.StylePropertiesSect ion.
1693 this._normal = true;
1694 }
1662 }; 1695 };
1663 1696
1664 WebInspector.BlankStylePropertiesSection.prototype = { 1697 /**
1698 * @unrestricted
1699 */
1700 WebInspector.KeyframePropertiesSection = class extends WebInspector.StylePropert iesSection {
1701 /**
1702 * @param {!WebInspector.StylesSidebarPane} stylesPane
1703 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
1704 * @param {!WebInspector.CSSStyleDeclaration} style
1705 */
1706 constructor(stylesPane, matchedStyles, style) {
1707 super(stylesPane, matchedStyles, style);
1708 this._selectorElement.className = 'keyframe-key';
1709 }
1710
1711 /**
1712 * @override
1713 * @return {string}
1714 */
1715 _headerText() {
1716 return this._style.parentRule.key().text;
1717 }
1718
1719 /**
1720 * @override
1721 * @param {!WebInspector.CSSRule} rule
1722 * @param {string} newContent
1723 * @return {!Promise}
1724 */
1725 _setHeaderText(rule, newContent) {
1665 /** 1726 /**
1666 * @return {!WebInspector.TextRange} 1727 * @param {boolean} success
1728 * @this {WebInspector.KeyframePropertiesSection}
1667 */ 1729 */
1668 _actualRuleLocation: function() 1730 function updateSourceRanges(success) {
1669 { 1731 if (!success)
1670 var prefix = this._rulePrefix(); 1732 return;
1671 var lines = prefix.split("\n"); 1733 this._parentPane._refreshUpdate(this);
1672 var editRange = new WebInspector.TextRange(0, 0, lines.length - 1, lines .peekLast().length); 1734 }
1673 return this._ruleLocation.rebaseAfterTextEdit(WebInspector.TextRange.cre ateFromLocation(0, 0), editRange); 1735
1674 }, 1736 console.assert(rule instanceof WebInspector.CSSKeyframeRule);
1675 1737 var oldRange = rule.key().range;
1676 /** 1738 if (!oldRange)
1677 * @return {string} 1739 return Promise.resolve();
1678 */ 1740 var selectedNode = this._parentPane.node();
1679 _rulePrefix: function() 1741 return rule.setKeyText(newContent).then(updateSourceRanges.bind(this));
1680 { 1742 }
1681 return this._ruleLocation.startLine === 0 && this._ruleLocation.startCol umn === 0 ? "" : "\n\n"; 1743
1682 }, 1744 /**
1683 1745 * @override
1684 /** 1746 * @param {string} propertyName
1685 * @return {boolean} 1747 * @return {boolean}
1686 */ 1748 */
1687 get isBlank() 1749 isPropertyInherited(propertyName) {
1688 { 1750 return false;
1689 return !this._normal; 1751 }
1690 }, 1752
1691 1753 /**
1692 /** 1754 * @override
1693 * @override 1755 * @param {!WebInspector.CSSProperty} property
1694 * @param {!Element} element 1756 * @return {boolean}
1695 * @param {string} newContent 1757 */
1696 * @param {string} oldContent 1758 _isPropertyOverloaded(property) {
1697 * @param {!WebInspector.StylePropertyTreeElement.Context|undefined} context 1759 return false;
1698 * @param {string} moveDirection 1760 }
1699 */ 1761
1700 editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection) 1762 /**
1701 { 1763 * @override
1702 if (!this.isBlank) { 1764 */
1703 WebInspector.StylePropertiesSection.prototype.editingSelectorCommitt ed.call(this, element, newContent, oldContent, context, moveDirection); 1765 _markSelectorHighlights() {
1704 return; 1766 }
1705 } 1767
1706 1768 /**
1707 /** 1769 * @override
1708 * @param {?WebInspector.CSSStyleRule} newRule 1770 */
1709 * @return {!Promise} 1771 _markSelectorMatches() {
1710 * @this {WebInspector.StylePropertiesSection} 1772 this._selectorElement.textContent = this._style.parentRule.key().text;
1711 */ 1773 }
1712 function onRuleAdded(newRule) 1774
1713 { 1775 /**
1714 if (!newRule) { 1776 * @override
1715 this.editingSelectorCancelled(); 1777 */
1716 this._editingSelectorCommittedForTest(); 1778 _highlight() {
1717 return Promise.resolve(); 1779 }
1718 }
1719 return this._matchedStyles.addNewRule(newRule, this._matchedStyles.n ode())
1720 .then(onAddedToCascade.bind(this, newRule));
1721 }
1722
1723 /**
1724 * @param {!WebInspector.CSSStyleRule} newRule
1725 * @this {WebInspector.StylePropertiesSection}
1726 */
1727 function onAddedToCascade(newRule)
1728 {
1729 var doesSelectorAffectSelectedNode = this._matchedStyles.matchingSel ectors(newRule).length > 0;
1730 this._makeNormal(newRule);
1731
1732 if (!doesSelectorAffectSelectedNode)
1733 this.propertiesTreeOutline.element.classList.add("no-affect");
1734
1735 this._updateRuleOrigin();
1736 if (this.element.parentElement) // Might have been detached already.
1737 this._moveEditorFromSelector(moveDirection);
1738
1739 delete this._parentPane._userOperation;
1740 this._editingSelectorEnded();
1741 this._markSelectorMatches();
1742
1743 this._editingSelectorCommittedForTest();
1744 }
1745
1746 if (newContent)
1747 newContent = newContent.trim();
1748 this._parentPane._userOperation = true;
1749
1750 var cssModel = this._parentPane.cssModel();
1751 var ruleText = this._rulePrefix() + newContent + " {}";
1752 cssModel.addRule(this._styleSheetId, ruleText, this._ruleLocation)
1753 .then(onRuleAdded.bind(this));
1754 },
1755
1756 editingSelectorCancelled: function()
1757 {
1758 delete this._parentPane._userOperation;
1759 if (!this.isBlank) {
1760 WebInspector.StylePropertiesSection.prototype.editingSelectorCancell ed.call(this);
1761 return;
1762 }
1763
1764 this._editingSelectorEnded();
1765 this._parentPane.removeSection(this);
1766 },
1767
1768 /**
1769 * @param {!WebInspector.CSSRule} newRule
1770 */
1771 _makeNormal: function(newRule)
1772 {
1773 this.element.classList.remove("blank-section");
1774 this._style = newRule.style;
1775 // FIXME: replace this instance by a normal WebInspector.StyleProperties Section.
1776 this._normal = true;
1777 },
1778
1779 __proto__: WebInspector.StylePropertiesSection.prototype
1780 }; 1780 };
1781 1781
1782 /** 1782 /**
1783 * @constructor 1783 * @unrestricted
1784 * @extends {WebInspector.StylePropertiesSection}
1785 * @param {!WebInspector.StylesSidebarPane} stylesPane
1786 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
1787 * @param {!WebInspector.CSSStyleDeclaration} style
1788 */ 1784 */
1789 WebInspector.KeyframePropertiesSection = function(stylesPane, matchedStyles, sty le) 1785 WebInspector.StylePropertyTreeElement = class extends TreeElement {
1790 { 1786 /**
1791 WebInspector.StylePropertiesSection.call(this, stylesPane, matchedStyles, st yle); 1787 * @param {!WebInspector.StylesSidebarPane} stylesPane
1792 this._selectorElement.className = "keyframe-key"; 1788 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
1793 }; 1789 * @param {!WebInspector.CSSProperty} property
1794 1790 * @param {boolean} isShorthand
1795 WebInspector.KeyframePropertiesSection.prototype = { 1791 * @param {boolean} inherited
1796 /** 1792 * @param {boolean} overloaded
1797 * @override 1793 */
1798 * @return {string} 1794 constructor(stylesPane, matchedStyles, property, isShorthand, inherited, overl oaded) {
1799 */
1800 _headerText: function()
1801 {
1802 return this._style.parentRule.key().text;
1803 },
1804
1805 /**
1806 * @override
1807 * @param {!WebInspector.CSSRule} rule
1808 * @param {string} newContent
1809 * @return {!Promise}
1810 */
1811 _setHeaderText: function(rule, newContent)
1812 {
1813 /**
1814 * @param {boolean} success
1815 * @this {WebInspector.KeyframePropertiesSection}
1816 */
1817 function updateSourceRanges(success)
1818 {
1819 if (!success)
1820 return;
1821 this._parentPane._refreshUpdate(this);
1822 }
1823
1824 console.assert(rule instanceof WebInspector.CSSKeyframeRule);
1825 var oldRange = rule.key().range;
1826 if (!oldRange)
1827 return Promise.resolve();
1828 var selectedNode = this._parentPane.node();
1829 return rule.setKeyText(newContent).then(updateSourceRanges.bind(this));
1830 },
1831
1832 /**
1833 * @override
1834 * @param {string} propertyName
1835 * @return {boolean}
1836 */
1837 isPropertyInherited: function(propertyName)
1838 {
1839 return false;
1840 },
1841
1842 /**
1843 * @override
1844 * @param {!WebInspector.CSSProperty} property
1845 * @return {boolean}
1846 */
1847 _isPropertyOverloaded: function(property)
1848 {
1849 return false;
1850 },
1851
1852 /**
1853 * @override
1854 */
1855 _markSelectorHighlights: function()
1856 {
1857 },
1858
1859 /**
1860 * @override
1861 */
1862 _markSelectorMatches: function()
1863 {
1864 this._selectorElement.textContent = this._style.parentRule.key().text;
1865 },
1866
1867 /**
1868 * @override
1869 */
1870 _highlight: function()
1871 {
1872 },
1873
1874 __proto__: WebInspector.StylePropertiesSection.prototype
1875 };
1876
1877 /**
1878 * @constructor
1879 * @extends {TreeElement}
1880 * @param {!WebInspector.StylesSidebarPane} stylesPane
1881 * @param {!WebInspector.CSSMatchedStyles} matchedStyles
1882 * @param {!WebInspector.CSSProperty} property
1883 * @param {boolean} isShorthand
1884 * @param {boolean} inherited
1885 * @param {boolean} overloaded
1886 */
1887 WebInspector.StylePropertyTreeElement = function(stylesPane, matchedStyles, prop erty, isShorthand, inherited, overloaded)
1888 {
1889 // Pass an empty title, the title gets made later in onattach. 1795 // Pass an empty title, the title gets made later in onattach.
1890 TreeElement.call(this, "", isShorthand); 1796 super('', isShorthand);
1891 this._style = property.ownerStyle; 1797 this._style = property.ownerStyle;
1892 this._matchedStyles = matchedStyles; 1798 this._matchedStyles = matchedStyles;
1893 this.property = property; 1799 this.property = property;
1894 this._inherited = inherited; 1800 this._inherited = inherited;
1895 this._overloaded = overloaded; 1801 this._overloaded = overloaded;
1896 this.selectable = false; 1802 this.selectable = false;
1897 this._parentPane = stylesPane; 1803 this._parentPane = stylesPane;
1898 this.isShorthand = isShorthand; 1804 this.isShorthand = isShorthand;
1899 this._applyStyleThrottler = new WebInspector.Throttler(0); 1805 this._applyStyleThrottler = new WebInspector.Throttler(0);
1900 }; 1806 }
1901 1807
1902 /** @typedef {{expanded: boolean, hasChildren: boolean, isEditingName: boolean, previousContent: string}} */ 1808 /**
1903 WebInspector.StylePropertyTreeElement.Context; 1809 * @return {boolean}
1904 1810 */
1905 WebInspector.StylePropertyTreeElement.prototype = { 1811 _editable() {
1812 return this._style.styleSheetId && this._style.range;
1813 }
1814
1815 /**
1816 * @return {boolean}
1817 */
1818 inherited() {
1819 return this._inherited;
1820 }
1821
1822 /**
1823 * @return {boolean}
1824 */
1825 overloaded() {
1826 return this._overloaded;
1827 }
1828
1829 /**
1830 * @param {boolean} x
1831 */
1832 setOverloaded(x) {
1833 if (x === this._overloaded)
1834 return;
1835 this._overloaded = x;
1836 this._updateState();
1837 }
1838
1839 get name() {
1840 return this.property.name;
1841 }
1842
1843 get value() {
1844 return this.property.value;
1845 }
1846
1847 /**
1848 * @return {boolean}
1849 */
1850 _updateFilter() {
1851 var regex = this._parentPane.filterRegex();
1852 var matches = !!regex && (regex.test(this.property.name) || regex.test(this. property.value));
1853 this.listItemElement.classList.toggle('filter-match', matches);
1854
1855 this.onpopulate();
1856 var hasMatchingChildren = false;
1857 for (var i = 0; i < this.childCount(); ++i)
1858 hasMatchingChildren |= this.childAt(i)._updateFilter();
1859
1860 if (!regex) {
1861 if (this._expandedDueToFilter)
1862 this.collapse();
1863 this._expandedDueToFilter = false;
1864 } else if (hasMatchingChildren && !this.expanded) {
1865 this.expand();
1866 this._expandedDueToFilter = true;
1867 } else if (!hasMatchingChildren && this.expanded && this._expandedDueToFilte r) {
1868 this.collapse();
1869 this._expandedDueToFilter = false;
1870 }
1871 return matches;
1872 }
1873
1874 /**
1875 * @param {string} text
1876 * @return {!Node}
1877 */
1878 _processColor(text) {
1879 // We can be called with valid non-color values of |text| (like 'none' from border style)
1880 var color = WebInspector.Color.parse(text);
1881 if (!color)
1882 return createTextNode(text);
1883
1884 if (!this._editable()) {
1885 var swatch = WebInspector.ColorSwatch.create();
1886 swatch.setColor(color);
1887 return swatch;
1888 }
1889
1890 var swatchPopoverHelper = this._parentPane._swatchPopoverHelper;
1891 var swatch = WebInspector.ColorSwatch.create();
1892 swatch.setColor(color);
1893 swatch.setFormat(WebInspector.Color.detectColorFormat(swatch.color()));
1894 var swatchIcon = new WebInspector.ColorSwatchPopoverIcon(this, swatchPopover Helper, swatch);
1895
1906 /** 1896 /**
1907 * @return {boolean} 1897 * @param {?Array<string>} backgroundColors
1908 */ 1898 */
1909 _editable: function() 1899 function computedCallback(backgroundColors) {
1910 { 1900 // TODO(aboxhall): distinguish between !backgroundColors (no text) and
1911 return this._style.styleSheetId && this._style.range; 1901 // !backgroundColors.length (no computed bg color)
1912 }, 1902 if (!backgroundColors || !backgroundColors.length)
1903 return;
1904 // TODO(samli): figure out what to do in the case of multiple background c olors (i.e. gradients)
1905 var bgColorText = backgroundColors[0];
1906 var bgColor = WebInspector.Color.parse(bgColorText);
1907 if (!bgColor)
1908 return;
1909
1910 // If we have a semi-transparent background color over an unknown
1911 // background, draw the line for the "worst case" scenario: where
1912 // the unknown background is the same color as the text.
1913 if (bgColor.hasAlpha) {
1914 var blendedRGBA = [];
1915 WebInspector.Color.blendColors(bgColor.rgba(), color.rgba(), blendedRGBA );
1916 bgColor = new WebInspector.Color(blendedRGBA, WebInspector.Color.Format. RGBA);
1917 }
1918
1919 swatchIcon.setContrastColor(bgColor);
1920 }
1921
1922 if (Runtime.experiments.isEnabled('colorContrastRatio') && this.property.nam e === 'color' &&
1923 this._parentPane.cssModel() && this.node()) {
1924 var cssModel = this._parentPane.cssModel();
1925 cssModel.backgroundColorsPromise(this.node().id).then(computedCallback);
1926 }
1927
1928 return swatch;
1929 }
1930
1931 /**
1932 * @return {string}
1933 */
1934 renderedPropertyText() {
1935 return this.nameElement.textContent + ': ' + this.valueElement.textContent;
1936 }
1937
1938 /**
1939 * @param {string} text
1940 * @return {!Node}
1941 */
1942 _processBezier(text) {
1943 if (!this._editable() || !WebInspector.Geometry.CubicBezier.parse(text))
1944 return createTextNode(text);
1945 var swatchPopoverHelper = this._parentPane._swatchPopoverHelper;
1946 var swatch = WebInspector.BezierSwatch.create();
1947 swatch.setBezierText(text);
1948 new WebInspector.BezierPopoverIcon(this, swatchPopoverHelper, swatch);
1949 return swatch;
1950 }
1951
1952 /**
1953 * @param {string} propertyValue
1954 * @param {string} propertyName
1955 * @return {!Node}
1956 */
1957 _processShadow(propertyValue, propertyName) {
1958 if (!this._editable())
1959 return createTextNode(propertyValue);
1960 var shadows;
1961 if (propertyName === 'text-shadow')
1962 shadows = WebInspector.CSSShadowModel.parseTextShadow(propertyValue);
1963 else
1964 shadows = WebInspector.CSSShadowModel.parseBoxShadow(propertyValue);
1965 if (!shadows.length)
1966 return createTextNode(propertyValue);
1967 var container = createDocumentFragment();
1968 var swatchPopoverHelper = this._parentPane._swatchPopoverHelper;
1969 for (var i = 0; i < shadows.length; i++) {
1970 if (i !== 0)
1971 container.appendChild(createTextNode(', ')); // Add back commas and spa ces between each shadow.
1972 // TODO(flandy): editing the property value should use the original value with all spaces.
1973 var cssShadowSwatch = WebInspector.CSSShadowSwatch.create();
1974 cssShadowSwatch.setCSSShadow(shadows[i]);
1975 new WebInspector.ShadowSwatchPopoverHelper(this, swatchPopoverHelper, cssS hadowSwatch);
1976 var colorSwatch = cssShadowSwatch.colorSwatch();
1977 if (colorSwatch)
1978 new WebInspector.ColorSwatchPopoverIcon(this, swatchPopoverHelper, color Swatch);
1979 container.appendChild(cssShadowSwatch);
1980 }
1981 return container;
1982 }
1983
1984 _updateState() {
1985 if (!this.listItemElement)
1986 return;
1987
1988 if (this._style.isPropertyImplicit(this.name))
1989 this.listItemElement.classList.add('implicit');
1990 else
1991 this.listItemElement.classList.remove('implicit');
1992
1993 var hasIgnorableError =
1994 !this.property.parsedOk && WebInspector.StylesSidebarPane.ignoreErrorsFo rProperty(this.property);
1995 if (hasIgnorableError)
1996 this.listItemElement.classList.add('has-ignorable-error');
1997 else
1998 this.listItemElement.classList.remove('has-ignorable-error');
1999
2000 if (this.inherited())
2001 this.listItemElement.classList.add('inherited');
2002 else
2003 this.listItemElement.classList.remove('inherited');
2004
2005 if (this.overloaded())
2006 this.listItemElement.classList.add('overloaded');
2007 else
2008 this.listItemElement.classList.remove('overloaded');
2009
2010 if (this.property.disabled)
2011 this.listItemElement.classList.add('disabled');
2012 else
2013 this.listItemElement.classList.remove('disabled');
2014 }
2015
2016 /**
2017 * @return {?WebInspector.DOMNode}
2018 */
2019 node() {
2020 return this._parentPane.node();
2021 }
2022
2023 /**
2024 * @return {!WebInspector.StylesSidebarPane}
2025 */
2026 parentPane() {
2027 return this._parentPane;
2028 }
2029
2030 /**
2031 * @return {?WebInspector.StylePropertiesSection}
2032 */
2033 section() {
2034 return this.treeOutline && this.treeOutline.section;
2035 }
2036
2037 _updatePane() {
2038 var section = this.section();
2039 if (section && section._parentPane)
2040 section._parentPane._refreshUpdate(section);
2041 }
2042
2043 /**
2044 * @param {!Event} event
2045 */
2046 _toggleEnabled(event) {
2047 var disabled = !event.target.checked;
2048 var oldStyleRange = this._style.range;
2049 if (!oldStyleRange)
2050 return;
1913 2051
1914 /** 2052 /**
1915 * @return {boolean} 2053 * @param {boolean} success
2054 * @this {WebInspector.StylePropertyTreeElement}
1916 */ 2055 */
1917 inherited: function() 2056 function callback(success) {
1918 { 2057 delete this._parentPane._userOperation;
1919 return this._inherited; 2058
1920 }, 2059 if (!success)
2060 return;
2061 this._matchedStyles.resetActiveProperties();
2062 this._updatePane();
2063 this.styleTextAppliedForTest();
2064 }
2065
2066 event.consume();
2067 this._parentPane._userOperation = true;
2068 this.property.setDisabled(disabled).then(callback.bind(this));
2069 }
2070
2071 /**
2072 * @override
2073 */
2074 onpopulate() {
2075 // Only populate once and if this property is a shorthand.
2076 if (this.childCount() || !this.isShorthand)
2077 return;
2078
2079 var longhandProperties = this._style.longhandProperties(this.name);
2080 for (var i = 0; i < longhandProperties.length; ++i) {
2081 var name = longhandProperties[i].name;
2082 var inherited = false;
2083 var overloaded = false;
2084
2085 var section = this.section();
2086 if (section) {
2087 inherited = section.isPropertyInherited(name);
2088 overloaded = this._matchedStyles.propertyState(longhandProperties[i]) == =
2089 WebInspector.CSSMatchedStyles.PropertyState.Overloaded;
2090 }
2091
2092 var item = new WebInspector.StylePropertyTreeElement(
2093 this._parentPane, this._matchedStyles, longhandProperties[i], false, i nherited, overloaded);
2094 this.appendChild(item);
2095 }
2096 }
2097
2098 /**
2099 * @override
2100 */
2101 onattach() {
2102 this.updateTitle();
2103
2104 this.listItemElement.addEventListener('mousedown', this._mouseDown.bind(this ));
2105 this.listItemElement.addEventListener('mouseup', this._resetMouseDownElement .bind(this));
2106 this.listItemElement.addEventListener('click', this._mouseClick.bind(this));
2107 }
2108
2109 /**
2110 * @param {!Event} event
2111 */
2112 _mouseDown(event) {
2113 if (this._parentPane) {
2114 this._parentPane._mouseDownTreeElement = this;
2115 this._parentPane._mouseDownTreeElementIsName =
2116 this.nameElement && this.nameElement.isSelfOrAncestor(event.target);
2117 this._parentPane._mouseDownTreeElementIsValue =
2118 this.valueElement && this.valueElement.isSelfOrAncestor(event.target);
2119 }
2120 }
2121
2122 _resetMouseDownElement() {
2123 if (this._parentPane) {
2124 delete this._parentPane._mouseDownTreeElement;
2125 delete this._parentPane._mouseDownTreeElementIsName;
2126 delete this._parentPane._mouseDownTreeElementIsValue;
2127 }
2128 }
2129
2130 updateTitle() {
2131 this._updateState();
2132 this._expandElement = createElement('span');
2133 this._expandElement.className = 'expand-element';
2134
2135 var propertyRenderer =
2136 new WebInspector.StylesSidebarPropertyRenderer(this._style.parentRule, t his.node(), this.name, this.value);
2137 if (this.property.parsedOk) {
2138 propertyRenderer.setColorHandler(this._processColor.bind(this));
2139 propertyRenderer.setBezierHandler(this._processBezier.bind(this));
2140 propertyRenderer.setShadowHandler(this._processShadow.bind(this));
2141 }
2142
2143 this.listItemElement.removeChildren();
2144 this.nameElement = propertyRenderer.renderName();
2145 this.valueElement = propertyRenderer.renderValue();
2146 if (!this.treeOutline)
2147 return;
2148
2149 var indent = WebInspector.moduleSetting('textEditorIndent').get();
2150 this.listItemElement.createChild('span', 'styles-clipboard-only')
2151 .createTextChild(indent + (this.property.disabled ? '/* ' : ''));
2152 this.listItemElement.appendChild(this.nameElement);
2153 this.listItemElement.createTextChild(': ');
2154 this.listItemElement.appendChild(this._expandElement);
2155 this.listItemElement.appendChild(this.valueElement);
2156 this.listItemElement.createTextChild(';');
2157 if (this.property.disabled)
2158 this.listItemElement.createChild('span', 'styles-clipboard-only').createTe xtChild(' */');
2159
2160 if (!this.property.parsedOk) {
2161 // Avoid having longhands under an invalid shorthand.
2162 this.listItemElement.classList.add('not-parsed-ok');
2163
2164 // Add a separate exclamation mark IMG element with a tooltip.
2165 this.listItemElement.insertBefore(
2166 WebInspector.StylesSidebarPane.createExclamationMark(this.property), t his.listItemElement.firstChild);
2167 }
2168 if (!this.property.activeInStyle())
2169 this.listItemElement.classList.add('inactive');
2170 this._updateFilter();
2171
2172 if (this.property.parsedOk && this.section() && this.parent.root) {
2173 var enabledCheckboxElement = createElement('input');
2174 enabledCheckboxElement.className = 'enabled-button';
2175 enabledCheckboxElement.type = 'checkbox';
2176 enabledCheckboxElement.checked = !this.property.disabled;
2177 enabledCheckboxElement.addEventListener('click', this._toggleEnabled.bind( this), false);
2178 this.listItemElement.insertBefore(enabledCheckboxElement, this.listItemEle ment.firstChild);
2179 }
2180 }
2181
2182 /**
2183 * @param {!Event} event
2184 */
2185 _mouseClick(event) {
2186 if (!event.target.isComponentSelectionCollapsed())
2187 return;
2188
2189 event.consume(true);
2190
2191 if (event.target === this.listItemElement) {
2192 var section = this.section();
2193 if (!section || !section.editable)
2194 return;
2195
2196 if (section._checkWillCancelEditing())
2197 return;
2198 section.addNewBlankProperty(this.property.index + 1).startEditing();
2199 return;
2200 }
2201
2202 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEvent} */ (event)) &&
2203 this.section().navigable) {
2204 this._navigateToSource(/** @type {!Element} */ (event.target));
2205 return;
2206 }
2207
2208 this.startEditing(/** @type {!Element} */ (event.target));
2209 }
2210
2211 /**
2212 * @param {!Element} element
2213 * @param {boolean=} omitFocus
2214 */
2215 _navigateToSource(element, omitFocus) {
2216 if (!this.section().navigable)
2217 return;
2218 var propertyNameClicked = element === this.nameElement;
2219 var uiLocation = WebInspector.cssWorkspaceBinding.propertyUILocation(this.pr operty, propertyNameClicked);
2220 if (uiLocation)
2221 WebInspector.Revealer.reveal(uiLocation, omitFocus);
2222 }
2223
2224 /**
2225 * @param {?Element=} selectElement
2226 */
2227 startEditing(selectElement) {
2228 // FIXME: we don't allow editing of longhand properties under a shorthand ri ght now.
2229 if (this.parent.isShorthand)
2230 return;
2231
2232 if (selectElement === this._expandElement)
2233 return;
2234
2235 var section = this.section();
2236 if (section && !section.editable)
2237 return;
2238
2239 if (selectElement)
2240 selectElement = selectElement.enclosingNodeOrSelfWithClass('webkit-css-pro perty') ||
2241 selectElement.enclosingNodeOrSelfWithClass('value');
2242 if (!selectElement)
2243 selectElement = this.nameElement;
2244
2245 if (WebInspector.isBeingEdited(selectElement))
2246 return;
2247
2248 var isEditingName = selectElement === this.nameElement;
2249 if (!isEditingName)
2250 this.valueElement.textContent = restoreURLs(this.valueElement.textContent, this.value);
1921 2251
1922 /** 2252 /**
1923 * @return {boolean} 2253 * @param {string} fieldValue
1924 */ 2254 * @param {string} modelValue
1925 overloaded: function()
1926 {
1927 return this._overloaded;
1928 },
1929
1930 /**
1931 * @param {boolean} x
1932 */
1933 setOverloaded: function(x)
1934 {
1935 if (x === this._overloaded)
1936 return;
1937 this._overloaded = x;
1938 this._updateState();
1939 },
1940
1941 get name()
1942 {
1943 return this.property.name;
1944 },
1945
1946 get value()
1947 {
1948 return this.property.value;
1949 },
1950
1951 /**
1952 * @return {boolean}
1953 */
1954 _updateFilter: function()
1955 {
1956 var regex = this._parentPane.filterRegex();
1957 var matches = !!regex && (regex.test(this.property.name) || regex.test(t his.property.value));
1958 this.listItemElement.classList.toggle("filter-match", matches);
1959
1960 this.onpopulate();
1961 var hasMatchingChildren = false;
1962 for (var i = 0; i < this.childCount(); ++i)
1963 hasMatchingChildren |= this.childAt(i)._updateFilter();
1964
1965 if (!regex) {
1966 if (this._expandedDueToFilter)
1967 this.collapse();
1968 this._expandedDueToFilter = false;
1969 } else if (hasMatchingChildren && !this.expanded) {
1970 this.expand();
1971 this._expandedDueToFilter = true;
1972 } else if (!hasMatchingChildren && this.expanded && this._expandedDueToF ilter) {
1973 this.collapse();
1974 this._expandedDueToFilter = false;
1975 }
1976 return matches;
1977 },
1978
1979 /**
1980 * @param {string} text
1981 * @return {!Node}
1982 */
1983 _processColor: function(text)
1984 {
1985 // We can be called with valid non-color values of |text| (like 'none' f rom border style)
1986 var color = WebInspector.Color.parse(text);
1987 if (!color)
1988 return createTextNode(text);
1989
1990 if (!this._editable()) {
1991 var swatch = WebInspector.ColorSwatch.create();
1992 swatch.setColor(color);
1993 return swatch;
1994 }
1995
1996 var swatchPopoverHelper = this._parentPane._swatchPopoverHelper;
1997 var swatch = WebInspector.ColorSwatch.create();
1998 swatch.setColor(color);
1999 swatch.setFormat(WebInspector.Color.detectColorFormat(swatch.color()));
2000 var swatchIcon = new WebInspector.ColorSwatchPopoverIcon(this, swatchPop overHelper, swatch);
2001
2002 /**
2003 * @param {?Array<string>} backgroundColors
2004 */
2005 function computedCallback(backgroundColors)
2006 {
2007 // TODO(aboxhall): distinguish between !backgroundColors (no text) a nd
2008 // !backgroundColors.length (no computed bg color)
2009 if (!backgroundColors || !backgroundColors.length)
2010 return;
2011 // TODO(samli): figure out what to do in the case of multiple backgr ound colors (i.e. gradients)
2012 var bgColorText = backgroundColors[0];
2013 var bgColor = WebInspector.Color.parse(bgColorText);
2014 if (!bgColor)
2015 return;
2016
2017 // If we have a semi-transparent background color over an unknown
2018 // background, draw the line for the "worst case" scenario: where
2019 // the unknown background is the same color as the text.
2020 if (bgColor.hasAlpha) {
2021 var blendedRGBA = [];
2022 WebInspector.Color.blendColors(bgColor.rgba(), color.rgba(), ble ndedRGBA);
2023 bgColor = new WebInspector.Color(blendedRGBA, WebInspector.Color .Format.RGBA);
2024 }
2025
2026 swatchIcon.setContrastColor(bgColor);
2027 }
2028
2029 if (Runtime.experiments.isEnabled("colorContrastRatio") && this.property .name === "color" && this._parentPane.cssModel() && this.node()) {
2030 var cssModel = this._parentPane.cssModel();
2031 cssModel.backgroundColorsPromise(this.node().id).then(computedCallba ck);
2032 }
2033
2034 return swatch;
2035 },
2036
2037 /**
2038 * @return {string} 2255 * @return {string}
2039 */ 2256 */
2040 renderedPropertyText: function() 2257 function restoreURLs(fieldValue, modelValue) {
2041 { 2258 const urlRegex = /\b(url\([^)]*\))/g;
2042 return this.nameElement.textContent + ": " + this.valueElement.textConte nt; 2259 var splitFieldValue = fieldValue.split(urlRegex);
2043 }, 2260 if (splitFieldValue.length === 1)
2044 2261 return fieldValue;
2045 /** 2262 var modelUrlRegex = new RegExp(urlRegex);
2046 * @param {string} text 2263 for (var i = 1; i < splitFieldValue.length; i += 2) {
2047 * @return {!Node} 2264 var match = modelUrlRegex.exec(modelValue);
2048 */ 2265 if (match)
2049 _processBezier: function(text) 2266 splitFieldValue[i] = match[0];
2050 { 2267 }
2051 if (!this._editable() || !WebInspector.Geometry.CubicBezier.parse(text)) 2268 return splitFieldValue.join('');
2052 return createTextNode(text); 2269 }
2053 var swatchPopoverHelper = this._parentPane._swatchPopoverHelper; 2270
2054 var swatch = WebInspector.BezierSwatch.create(); 2271 /** @type {!WebInspector.StylePropertyTreeElement.Context} */
2055 swatch.setBezierText(text); 2272 var context = {
2056 new WebInspector.BezierPopoverIcon(this, swatchPopoverHelper, swatch); 2273 expanded: this.expanded,
2057 return swatch; 2274 hasChildren: this.isExpandable(),
2058 }, 2275 isEditingName: isEditingName,
2059 2276 previousContent: selectElement.textContent
2060 /** 2277 };
2061 * @param {string} propertyValue 2278
2062 * @param {string} propertyName 2279 // Lie about our children to prevent expanding on double click and to collap se shorthands.
2063 * @return {!Node} 2280 this.setExpandable(false);
2064 */ 2281
2065 _processShadow: function(propertyValue, propertyName) 2282 if (selectElement.parentElement)
2066 { 2283 selectElement.parentElement.classList.add('child-editing');
2067 if (!this._editable()) 2284 selectElement.textContent = selectElement.textContent; // remove color swat ch and the like
2068 return createTextNode(propertyValue);
2069 var shadows;
2070 if (propertyName === "text-shadow")
2071 shadows = WebInspector.CSSShadowModel.parseTextShadow(propertyValue) ;
2072 else
2073 shadows = WebInspector.CSSShadowModel.parseBoxShadow(propertyValue);
2074 if (!shadows.length)
2075 return createTextNode(propertyValue);
2076 var container = createDocumentFragment();
2077 var swatchPopoverHelper = this._parentPane._swatchPopoverHelper;
2078 for (var i = 0; i < shadows.length; i++) {
2079 if (i !== 0)
2080 container.appendChild(createTextNode(", ")); // Add back commas and spaces between each shadow.
2081 // TODO(flandy): editing the property value should use the original value with all spaces.
2082 var cssShadowSwatch = WebInspector.CSSShadowSwatch.create();
2083 cssShadowSwatch.setCSSShadow(shadows[i]);
2084 new WebInspector.ShadowSwatchPopoverHelper(this, swatchPopoverHelper , cssShadowSwatch);
2085 var colorSwatch = cssShadowSwatch.colorSwatch();
2086 if (colorSwatch)
2087 new WebInspector.ColorSwatchPopoverIcon(this, swatchPopoverHelpe r, colorSwatch);
2088 container.appendChild(cssShadowSwatch);
2089 }
2090 return container;
2091 },
2092
2093 _updateState: function()
2094 {
2095 if (!this.listItemElement)
2096 return;
2097
2098 if (this._style.isPropertyImplicit(this.name))
2099 this.listItemElement.classList.add("implicit");
2100 else
2101 this.listItemElement.classList.remove("implicit");
2102
2103 var hasIgnorableError = !this.property.parsedOk && WebInspector.StylesSi debarPane.ignoreErrorsForProperty(this.property);
2104 if (hasIgnorableError)
2105 this.listItemElement.classList.add("has-ignorable-error");
2106 else
2107 this.listItemElement.classList.remove("has-ignorable-error");
2108
2109 if (this.inherited())
2110 this.listItemElement.classList.add("inherited");
2111 else
2112 this.listItemElement.classList.remove("inherited");
2113
2114 if (this.overloaded())
2115 this.listItemElement.classList.add("overloaded");
2116 else
2117 this.listItemElement.classList.remove("overloaded");
2118
2119 if (this.property.disabled)
2120 this.listItemElement.classList.add("disabled");
2121 else
2122 this.listItemElement.classList.remove("disabled");
2123 },
2124
2125 /**
2126 * @return {?WebInspector.DOMNode}
2127 */
2128 node: function()
2129 {
2130 return this._parentPane.node();
2131 },
2132
2133 /**
2134 * @return {!WebInspector.StylesSidebarPane}
2135 */
2136 parentPane: function()
2137 {
2138 return this._parentPane;
2139 },
2140
2141 /**
2142 * @return {?WebInspector.StylePropertiesSection}
2143 */
2144 section: function()
2145 {
2146 return this.treeOutline && this.treeOutline.section;
2147 },
2148
2149 _updatePane: function()
2150 {
2151 var section = this.section();
2152 if (section && section._parentPane)
2153 section._parentPane._refreshUpdate(section);
2154 },
2155
2156 /**
2157 * @param {!Event} event
2158 */
2159 _toggleEnabled: function(event)
2160 {
2161 var disabled = !event.target.checked;
2162 var oldStyleRange = this._style.range;
2163 if (!oldStyleRange)
2164 return;
2165
2166 /**
2167 * @param {boolean} success
2168 * @this {WebInspector.StylePropertyTreeElement}
2169 */
2170 function callback(success)
2171 {
2172 delete this._parentPane._userOperation;
2173
2174 if (!success)
2175 return;
2176 this._matchedStyles.resetActiveProperties();
2177 this._updatePane();
2178 this.styleTextAppliedForTest();
2179 }
2180
2181 event.consume();
2182 this._parentPane._userOperation = true;
2183 this.property.setDisabled(disabled)
2184 .then(callback.bind(this));
2185 },
2186
2187 /**
2188 * @override
2189 */
2190 onpopulate: function()
2191 {
2192 // Only populate once and if this property is a shorthand.
2193 if (this.childCount() || !this.isShorthand)
2194 return;
2195
2196 var longhandProperties = this._style.longhandProperties(this.name);
2197 for (var i = 0; i < longhandProperties.length; ++i) {
2198 var name = longhandProperties[i].name;
2199 var inherited = false;
2200 var overloaded = false;
2201
2202 var section = this.section();
2203 if (section) {
2204 inherited = section.isPropertyInherited(name);
2205 overloaded = this._matchedStyles.propertyState(longhandPropertie s[i]) === WebInspector.CSSMatchedStyles.PropertyState.Overloaded;
2206 }
2207
2208 var item = new WebInspector.StylePropertyTreeElement(this._parentPan e, this._matchedStyles, longhandProperties[i], false, inherited, overloaded);
2209 this.appendChild(item);
2210 }
2211 },
2212
2213 /**
2214 * @override
2215 */
2216 onattach: function()
2217 {
2218 this.updateTitle();
2219
2220 this.listItemElement.addEventListener("mousedown", this._mouseDown.bind( this));
2221 this.listItemElement.addEventListener("mouseup", this._resetMouseDownEle ment.bind(this));
2222 this.listItemElement.addEventListener("click", this._mouseClick.bind(thi s));
2223 },
2224
2225 /**
2226 * @param {!Event} event
2227 */
2228 _mouseDown: function(event)
2229 {
2230 if (this._parentPane) {
2231 this._parentPane._mouseDownTreeElement = this;
2232 this._parentPane._mouseDownTreeElementIsName = this.nameElement && t his.nameElement.isSelfOrAncestor(event.target);
2233 this._parentPane._mouseDownTreeElementIsValue = this.valueElement && this.valueElement.isSelfOrAncestor(event.target);
2234 }
2235 },
2236
2237 _resetMouseDownElement: function()
2238 {
2239 if (this._parentPane) {
2240 delete this._parentPane._mouseDownTreeElement;
2241 delete this._parentPane._mouseDownTreeElementIsName;
2242 delete this._parentPane._mouseDownTreeElementIsValue;
2243 }
2244 },
2245
2246 updateTitle: function()
2247 {
2248 this._updateState();
2249 this._expandElement = createElement("span");
2250 this._expandElement.className = "expand-element";
2251
2252 var propertyRenderer = new WebInspector.StylesSidebarPropertyRenderer(th is._style.parentRule, this.node(), this.name, this.value);
2253 if (this.property.parsedOk) {
2254 propertyRenderer.setColorHandler(this._processColor.bind(this));
2255 propertyRenderer.setBezierHandler(this._processBezier.bind(this));
2256 propertyRenderer.setShadowHandler(this._processShadow.bind(this));
2257 }
2258
2259 this.listItemElement.removeChildren();
2260 this.nameElement = propertyRenderer.renderName();
2261 this.valueElement = propertyRenderer.renderValue();
2262 if (!this.treeOutline)
2263 return;
2264
2265 var indent = WebInspector.moduleSetting("textEditorIndent").get();
2266 this.listItemElement.createChild("span", "styles-clipboard-only").create TextChild(indent + (this.property.disabled ? "/* " : ""));
2267 this.listItemElement.appendChild(this.nameElement);
2268 this.listItemElement.createTextChild(": ");
2269 this.listItemElement.appendChild(this._expandElement);
2270 this.listItemElement.appendChild(this.valueElement);
2271 this.listItemElement.createTextChild(";");
2272 if (this.property.disabled)
2273 this.listItemElement.createChild("span", "styles-clipboard-only").cr eateTextChild(" */");
2274
2275 if (!this.property.parsedOk) {
2276 // Avoid having longhands under an invalid shorthand.
2277 this.listItemElement.classList.add("not-parsed-ok");
2278
2279 // Add a separate exclamation mark IMG element with a tooltip.
2280 this.listItemElement.insertBefore(WebInspector.StylesSidebarPane.cre ateExclamationMark(this.property), this.listItemElement.firstChild);
2281 }
2282 if (!this.property.activeInStyle())
2283 this.listItemElement.classList.add("inactive");
2284 this._updateFilter();
2285
2286 if (this.property.parsedOk && this.section() && this.parent.root) {
2287 var enabledCheckboxElement = createElement("input");
2288 enabledCheckboxElement.className = "enabled-button";
2289 enabledCheckboxElement.type = "checkbox";
2290 enabledCheckboxElement.checked = !this.property.disabled;
2291 enabledCheckboxElement.addEventListener("click", this._toggleEnabled .bind(this), false);
2292 this.listItemElement.insertBefore(enabledCheckboxElement, this.listI temElement.firstChild);
2293 }
2294 },
2295
2296 /**
2297 * @param {!Event} event
2298 */
2299 _mouseClick: function(event)
2300 {
2301 if (!event.target.isComponentSelectionCollapsed())
2302 return;
2303
2304 event.consume(true);
2305
2306 if (event.target === this.listItemElement) {
2307 var section = this.section();
2308 if (!section || !section.editable)
2309 return;
2310
2311 if (section._checkWillCancelEditing())
2312 return;
2313 section.addNewBlankProperty(this.property.index + 1).startEditing();
2314 return;
2315 }
2316
2317 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEv ent} */(event)) && this.section().navigable) {
2318 this._navigateToSource(/** @type {!Element} */(event.target));
2319 return;
2320 }
2321
2322 this.startEditing(/** @type {!Element} */(event.target));
2323 },
2324
2325 /**
2326 * @param {!Element} element
2327 * @param {boolean=} omitFocus
2328 */
2329 _navigateToSource: function(element, omitFocus)
2330 {
2331 if (!this.section().navigable)
2332 return;
2333 var propertyNameClicked = element === this.nameElement;
2334 var uiLocation = WebInspector.cssWorkspaceBinding.propertyUILocation(thi s.property, propertyNameClicked);
2335 if (uiLocation)
2336 WebInspector.Revealer.reveal(uiLocation, omitFocus);
2337 },
2338
2339 /**
2340 * @param {?Element=} selectElement
2341 */
2342 startEditing: function(selectElement)
2343 {
2344 // FIXME: we don't allow editing of longhand properties under a shorthan d right now.
2345 if (this.parent.isShorthand)
2346 return;
2347
2348 if (selectElement === this._expandElement)
2349 return;
2350
2351 var section = this.section();
2352 if (section && !section.editable)
2353 return;
2354
2355 if (selectElement)
2356 selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-c ss-property") || selectElement.enclosingNodeOrSelfWithClass("value");
2357 if (!selectElement)
2358 selectElement = this.nameElement;
2359
2360 if (WebInspector.isBeingEdited(selectElement))
2361 return;
2362
2363 var isEditingName = selectElement === this.nameElement;
2364 if (!isEditingName)
2365 this.valueElement.textContent = restoreURLs(this.valueElement.textCo ntent, this.value);
2366
2367 /**
2368 * @param {string} fieldValue
2369 * @param {string} modelValue
2370 * @return {string}
2371 */
2372 function restoreURLs(fieldValue, modelValue)
2373 {
2374 const urlRegex = /\b(url\([^)]*\))/g;
2375 var splitFieldValue = fieldValue.split(urlRegex);
2376 if (splitFieldValue.length === 1)
2377 return fieldValue;
2378 var modelUrlRegex = new RegExp(urlRegex);
2379 for (var i = 1; i < splitFieldValue.length; i += 2) {
2380 var match = modelUrlRegex.exec(modelValue);
2381 if (match)
2382 splitFieldValue[i] = match[0];
2383 }
2384 return splitFieldValue.join("");
2385 }
2386
2387 /** @type {!WebInspector.StylePropertyTreeElement.Context} */
2388 var context = {
2389 expanded: this.expanded,
2390 hasChildren: this.isExpandable(),
2391 isEditingName: isEditingName,
2392 previousContent: selectElement.textContent
2393 };
2394
2395 // Lie about our children to prevent expanding on double click and to co llapse shorthands.
2396 this.setExpandable(false);
2397
2398 if (selectElement.parentElement)
2399 selectElement.parentElement.classList.add("child-editing");
2400 selectElement.textContent = selectElement.textContent; // remove color s watch and the like
2401
2402 /**
2403 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2404 * @param {!Event} event
2405 * @this {WebInspector.StylePropertyTreeElement}
2406 */
2407 function pasteHandler(context, event)
2408 {
2409 var data = event.clipboardData.getData("Text");
2410 if (!data)
2411 return;
2412 var colonIdx = data.indexOf(":");
2413 if (colonIdx < 0)
2414 return;
2415 var name = data.substring(0, colonIdx).trim();
2416 var value = data.substring(colonIdx + 1).trim();
2417
2418 event.preventDefault();
2419
2420 if (!("originalName" in context)) {
2421 context.originalName = this.nameElement.textContent;
2422 context.originalValue = this.valueElement.textContent;
2423 }
2424 this.property.name = name;
2425 this.property.value = value;
2426 this.nameElement.textContent = name;
2427 this.valueElement.textContent = value;
2428 this.nameElement.normalize();
2429 this.valueElement.normalize();
2430
2431 this._editingCommitted(event.target.textContent, context, "forward") ;
2432 }
2433
2434 /**
2435 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2436 * @param {!Event} event
2437 * @this {WebInspector.StylePropertyTreeElement}
2438 */
2439 function blurListener(context, event)
2440 {
2441 var treeElement = this._parentPane._mouseDownTreeElement;
2442 var moveDirection = "";
2443 if (treeElement === this) {
2444 if (isEditingName && this._parentPane._mouseDownTreeElementIsVal ue)
2445 moveDirection = "forward";
2446 if (!isEditingName && this._parentPane._mouseDownTreeElementIsNa me)
2447 moveDirection = "backward";
2448 }
2449 var text = event.target.textContent;
2450 if (!context.isEditingName)
2451 text = this.value || text;
2452 this._editingCommitted(text, context, moveDirection);
2453 }
2454
2455 this._originalPropertyText = this.property.propertyText;
2456
2457 this._parentPane.setEditingStyle(true);
2458 if (selectElement.parentElement)
2459 selectElement.parentElement.scrollIntoViewIfNeeded(false);
2460
2461 var applyItemCallback = !isEditingName ? this._applyFreeFlowStyleTextEdi t.bind(this) : undefined;
2462 var cssCompletions = isEditingName ? WebInspector.cssMetadata().allPrope rties() : WebInspector.cssMetadata().propertyValues(this.nameElement.textContent );
2463 this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(cssC ompletions, this, isEditingName);
2464 this._prompt.setAutocompletionTimeout(0);
2465 if (applyItemCallback) {
2466 this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemApp lied, applyItemCallback, this);
2467 this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemAcc epted, applyItemCallback, this);
2468 }
2469 var proxyElement = this._prompt.attachAndStartEditing(selectElement, blu rListener.bind(this, context));
2470 this._navigateToSource(selectElement, true);
2471
2472 proxyElement.addEventListener("keydown", this._editingNameValueKeyDown.b ind(this, context), false);
2473 proxyElement.addEventListener("keypress", this._editingNameValueKeyPress .bind(this, context), false);
2474 proxyElement.addEventListener("input", this._editingNameValueInput.bind( this, context), false);
2475 if (isEditingName)
2476 proxyElement.addEventListener("paste", pasteHandler.bind(this, conte xt), false);
2477
2478 selectElement.getComponentSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
2479 },
2480 2285
2481 /** 2286 /**
2482 * @param {!WebInspector.StylePropertyTreeElement.Context} context 2287 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2483 * @param {!Event} event 2288 * @param {!Event} event
2289 * @this {WebInspector.StylePropertyTreeElement}
2484 */ 2290 */
2485 _editingNameValueKeyDown: function(context, event) 2291 function pasteHandler(context, event) {
2486 { 2292 var data = event.clipboardData.getData('Text');
2487 if (event.handled) 2293 if (!data)
2488 return; 2294 return;
2489 2295 var colonIdx = data.indexOf(':');
2490 var result; 2296 if (colonIdx < 0)
2491 2297 return;
2492 if (isEnterKey(event)) { 2298 var name = data.substring(0, colonIdx).trim();
2493 event.preventDefault(); 2299 var value = data.substring(colonIdx + 1).trim();
2494 result = "forward"; 2300
2495 } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.key === "Escape") 2301 event.preventDefault();
2496 result = "cancel"; 2302
2497 else if (!context.isEditingName && this._newProperty && event.keyCode == = WebInspector.KeyboardShortcut.Keys.Backspace.code) { 2303 if (!('originalName' in context)) {
2498 // For a new property, when Backspace is pressed at the beginning of new property value, move back to the property name. 2304 context.originalName = this.nameElement.textContent;
2499 var selection = event.target.getComponentSelection(); 2305 context.originalValue = this.valueElement.textContent;
2500 if (selection.isCollapsed && !selection.focusOffset) { 2306 }
2501 event.preventDefault(); 2307 this.property.name = name;
2502 result = "backward"; 2308 this.property.value = value;
2503 } 2309 this.nameElement.textContent = name;
2504 } else if (event.key === "Tab") { 2310 this.valueElement.textContent = value;
2505 result = event.shiftKey ? "backward" : "forward"; 2311 this.nameElement.normalize();
2506 event.preventDefault(); 2312 this.valueElement.normalize();
2507 } 2313
2508 2314 this._editingCommitted(event.target.textContent, context, 'forward');
2509 if (result) { 2315 }
2510 switch (result) {
2511 case "cancel":
2512 this.editingCancelled(null, context);
2513 break;
2514 case "forward":
2515 case "backward":
2516 this._editingCommitted(event.target.textContent, context, result );
2517 break;
2518 }
2519
2520 event.consume();
2521 return;
2522 }
2523 },
2524 2316
2525 /** 2317 /**
2526 * @param {!WebInspector.StylePropertyTreeElement.Context} context 2318 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2527 * @param {!Event} event 2319 * @param {!Event} event
2320 * @this {WebInspector.StylePropertyTreeElement}
2528 */ 2321 */
2529 _editingNameValueKeyPress: function(context, event) 2322 function blurListener(context, event) {
2530 { 2323 var treeElement = this._parentPane._mouseDownTreeElement;
2531 /** 2324 var moveDirection = '';
2532 * @param {string} text 2325 if (treeElement === this) {
2533 * @param {number} cursorPosition 2326 if (isEditingName && this._parentPane._mouseDownTreeElementIsValue)
2534 * @return {boolean} 2327 moveDirection = 'forward';
2535 */ 2328 if (!isEditingName && this._parentPane._mouseDownTreeElementIsName)
2536 function shouldCommitValueSemicolon(text, cursorPosition) 2329 moveDirection = 'backward';
2537 { 2330 }
2538 // FIXME: should this account for semicolons inside comments? 2331 var text = event.target.textContent;
2539 var openQuote = ""; 2332 if (!context.isEditingName)
2540 for (var i = 0; i < cursorPosition; ++i) { 2333 text = this.value || text;
2541 var ch = text[i]; 2334 this._editingCommitted(text, context, moveDirection);
2542 if (ch === "\\" && openQuote !== "") 2335 }
2543 ++i; // skip next character inside string 2336
2544 else if (!openQuote && (ch === "\"" || ch === "'")) 2337 this._originalPropertyText = this.property.propertyText;
2545 openQuote = ch; 2338
2546 else if (openQuote === ch) 2339 this._parentPane.setEditingStyle(true);
2547 openQuote = ""; 2340 if (selectElement.parentElement)
2548 } 2341 selectElement.parentElement.scrollIntoViewIfNeeded(false);
2549 return !openQuote; 2342
2550 } 2343 var applyItemCallback = !isEditingName ? this._applyFreeFlowStyleTextEdit.bi nd(this) : undefined;
2551 2344 var cssCompletions = isEditingName ? WebInspector.cssMetadata().allPropertie s() :
2552 var keyChar = String.fromCharCode(event.charCode); 2345 WebInspector.cssMetadata().propertyValu es(this.nameElement.textContent);
2553 var isFieldInputTerminated = (context.isEditingName ? keyChar === ":" : keyChar === ";" && shouldCommitValueSemicolon(event.target.textContent, event.ta rget.selectionLeftOffset())); 2346 this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(cssCompl etions, this, isEditingName);
2554 if (isFieldInputTerminated) { 2347 this._prompt.setAutocompletionTimeout(0);
2555 // Enter or colon (for name)/semicolon outside of string (for value) . 2348 if (applyItemCallback) {
2556 event.consume(true); 2349 this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemApplied, applyItemCallback, this);
2557 this._editingCommitted(event.target.textContent, context, "forward") ; 2350 this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemAccepted, applyItemCallback, this);
2558 return; 2351 }
2559 } 2352 var proxyElement = this._prompt.attachAndStartEditing(selectElement, blurLis tener.bind(this, context));
2560 }, 2353 this._navigateToSource(selectElement, true);
2561 2354
2355 proxyElement.addEventListener('keydown', this._editingNameValueKeyDown.bind( this, context), false);
2356 proxyElement.addEventListener('keypress', this._editingNameValueKeyPress.bin d(this, context), false);
2357 proxyElement.addEventListener('input', this._editingNameValueInput.bind(this , context), false);
2358 if (isEditingName)
2359 proxyElement.addEventListener('paste', pasteHandler.bind(this, context), f alse);
2360
2361 selectElement.getComponentSelection().setBaseAndExtent(selectElement, 0, sel ectElement, 1);
2362 }
2363
2364 /**
2365 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2366 * @param {!Event} event
2367 */
2368 _editingNameValueKeyDown(context, event) {
2369 if (event.handled)
2370 return;
2371
2372 var result;
2373
2374 if (isEnterKey(event)) {
2375 event.preventDefault();
2376 result = 'forward';
2377 } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.key === 'Escape')
2378 result = 'cancel';
2379 else if (
2380 !context.isEditingName && this._newProperty &&
2381 event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code) {
2382 // For a new property, when Backspace is pressed at the beginning of new p roperty value, move back to the property name.
2383 var selection = event.target.getComponentSelection();
2384 if (selection.isCollapsed && !selection.focusOffset) {
2385 event.preventDefault();
2386 result = 'backward';
2387 }
2388 } else if (event.key === 'Tab') {
2389 result = event.shiftKey ? 'backward' : 'forward';
2390 event.preventDefault();
2391 }
2392
2393 if (result) {
2394 switch (result) {
2395 case 'cancel':
2396 this.editingCancelled(null, context);
2397 break;
2398 case 'forward':
2399 case 'backward':
2400 this._editingCommitted(event.target.textContent, context, result);
2401 break;
2402 }
2403
2404 event.consume();
2405 return;
2406 }
2407 }
2408
2409 /**
2410 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2411 * @param {!Event} event
2412 */
2413 _editingNameValueKeyPress(context, event) {
2562 /** 2414 /**
2563 * @param {!WebInspector.StylePropertyTreeElement.Context} context 2415 * @param {string} text
2564 * @param {!Event} event 2416 * @param {number} cursorPosition
2565 */
2566 _editingNameValueInput: function(context, event)
2567 {
2568 // Do not live-edit "content" property of pseudo elements. crbug.com/433 889
2569 if (!context.isEditingName && (!this._parentPane.node().pseudoType() || this.name !== "content"))
2570 this._applyFreeFlowStyleTextEdit();
2571 },
2572
2573 _applyFreeFlowStyleTextEdit: function()
2574 {
2575 var valueText = this.valueElement.textContent;
2576 if (valueText.indexOf(";") === -1)
2577 this.applyStyleText(this.nameElement.textContent + ": " + valueText, false);
2578 },
2579
2580 kickFreeFlowStyleEditForTest: function()
2581 {
2582 this._applyFreeFlowStyleTextEdit();
2583 },
2584
2585 /**
2586 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2587 */
2588 editingEnded: function(context)
2589 {
2590 this._resetMouseDownElement();
2591
2592 this.setExpandable(context.hasChildren);
2593 if (context.expanded)
2594 this.expand();
2595 var editedElement = context.isEditingName ? this.nameElement : this.valu eElement;
2596 // The proxyElement has been deleted, no need to remove listener.
2597 if (editedElement.parentElement)
2598 editedElement.parentElement.classList.remove("child-editing");
2599
2600 this._parentPane.setEditingStyle(false);
2601 },
2602
2603 /**
2604 * @param {?Element} element
2605 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2606 */
2607 editingCancelled: function(element, context)
2608 {
2609 this._removePrompt();
2610 this._revertStyleUponEditingCanceled();
2611 // This should happen last, as it clears the info necessary to restore t he property value after [Page]Up/Down changes.
2612 this.editingEnded(context);
2613 },
2614
2615 _revertStyleUponEditingCanceled: function()
2616 {
2617 if (this._propertyHasBeenEditedIncrementally) {
2618 this.applyStyleText(this._originalPropertyText, false);
2619 delete this._originalPropertyText;
2620 } else if (this._newProperty) {
2621 this.treeOutline.removeChild(this);
2622 } else {
2623 this.updateTitle();
2624 }
2625 },
2626
2627 /**
2628 * @param {string} moveDirection
2629 * @return {?WebInspector.StylePropertyTreeElement}
2630 */
2631 _findSibling: function(moveDirection)
2632 {
2633 var target = this;
2634 do {
2635 target = (moveDirection === "forward" ? target.nextSibling : target. previousSibling);
2636 } while (target && target.inherited());
2637
2638 return target;
2639 },
2640
2641 /**
2642 * @param {string} userInput
2643 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2644 * @param {string} moveDirection
2645 */
2646 _editingCommitted: function(userInput, context, moveDirection)
2647 {
2648 this._removePrompt();
2649 this.editingEnded(context);
2650 var isEditingName = context.isEditingName;
2651
2652 // Determine where to move to before making changes
2653 var createNewProperty, moveToPropertyName, moveToSelector;
2654 var isDataPasted = "originalName" in context;
2655 var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== context.originalName || this.valueElement.textContent !== context.originalValue) ;
2656 var isPropertySplitPaste = isDataPasted && isEditingName && this.valueEl ement.textContent !== context.originalValue;
2657 var moveTo = this;
2658 var moveToOther = (isEditingName ^ (moveDirection === "forward"));
2659 var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
2660 if (moveDirection === "forward" && (!isEditingName || isPropertySplitPas te) || moveDirection === "backward" && isEditingName) {
2661 moveTo = moveTo._findSibling(moveDirection);
2662 if (moveTo)
2663 moveToPropertyName = moveTo.name;
2664 else if (moveDirection === "forward" && (!this._newProperty || userI nput))
2665 createNewProperty = true;
2666 else if (moveDirection === "backward")
2667 moveToSelector = true;
2668 }
2669
2670 // Make the Changes and trigger the moveToNextCallback after updating.
2671 var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.rootElem ent().indexOfChild(moveTo) : -1;
2672 var blankInput = userInput.isWhitespace();
2673 var shouldCommitNewProperty = this._newProperty && (isPropertySplitPaste || moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blank Input));
2674 var section = /** @type {!WebInspector.StylePropertiesSection} */(this.s ection());
2675 if (((userInput !== context.previousContent || isDirtyViaPaste) && !this ._newProperty) || shouldCommitNewProperty) {
2676 section._afterUpdate = moveToNextCallback.bind(this, this._newProper ty, !blankInput, section);
2677 var propertyText;
2678 if (blankInput || (this._newProperty && this.valueElement.textConten t.isWhitespace()))
2679 propertyText = "";
2680 else {
2681 if (isEditingName)
2682 propertyText = userInput + ": " + this.property.value;
2683 else
2684 propertyText = this.property.name + ": " + userInput;
2685 }
2686 this.applyStyleText(propertyText, true);
2687 } else {
2688 if (isEditingName)
2689 this.property.name = userInput;
2690 else
2691 this.property.value = userInput;
2692 if (!isDataPasted && !this._newProperty)
2693 this.updateTitle();
2694 moveToNextCallback.call(this, this._newProperty, false, section);
2695 }
2696
2697 /**
2698 * The Callback to start editing the next/previous property/selector.
2699 * @param {boolean} alreadyNew
2700 * @param {boolean} valueChanged
2701 * @param {!WebInspector.StylePropertiesSection} section
2702 * @this {WebInspector.StylePropertyTreeElement}
2703 */
2704 function moveToNextCallback(alreadyNew, valueChanged, section)
2705 {
2706 if (!moveDirection)
2707 return;
2708
2709 // User just tabbed through without changes.
2710 if (moveTo && moveTo.parent) {
2711 moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo .valueElement);
2712 return;
2713 }
2714
2715 // User has made a change then tabbed, wiping all the original treeE lements.
2716 // Recalculate the new treeElement for the same property we were goi ng to edit next.
2717 if (moveTo && !moveTo.parent) {
2718 var rootElement = section.propertiesTreeOutline.rootElement();
2719 if (moveDirection === "forward" && blankInput && !isEditingName)
2720 --moveToIndex;
2721 if (moveToIndex >= rootElement.childCount() && !this._newPropert y)
2722 createNewProperty = true;
2723 else {
2724 var treeElement = moveToIndex >= 0 ? rootElement.childAt(mov eToIndex) : null;
2725 if (treeElement) {
2726 var elementToEdit = !isEditingName || isPropertySplitPas te ? treeElement.nameElement : treeElement.valueElement;
2727 if (alreadyNew && blankInput)
2728 elementToEdit = moveDirection === "forward" ? treeEl ement.nameElement : treeElement.valueElement;
2729 treeElement.startEditing(elementToEdit);
2730 return;
2731 } else if (!alreadyNew)
2732 moveToSelector = true;
2733 }
2734 }
2735
2736 // Create a new attribute in this section (or move to next editable selector if possible).
2737 if (createNewProperty) {
2738 if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirecti on === "backward")))
2739 return;
2740
2741 section.addNewBlankProperty().startEditing();
2742 return;
2743 }
2744
2745 if (abandonNewProperty) {
2746 moveTo = this._findSibling(moveDirection);
2747 var sectionToEdit = (moveTo || moveDirection === "backward") ? s ection : section.nextEditableSibling();
2748 if (sectionToEdit) {
2749 if (sectionToEdit.style().parentRule)
2750 sectionToEdit.startEditingSelector();
2751 else
2752 sectionToEdit._moveEditorFromSelector(moveDirection);
2753 }
2754 return;
2755 }
2756
2757 if (moveToSelector) {
2758 if (section.style().parentRule)
2759 section.startEditingSelector();
2760 else
2761 section._moveEditorFromSelector(moveDirection);
2762 }
2763 }
2764 },
2765
2766 _removePrompt: function()
2767 {
2768 // BUG 53242. This cannot go into editingEnded(), as it should always ha ppen first for any editing outcome.
2769 if (this._prompt) {
2770 this._prompt.detach();
2771 delete this._prompt;
2772 }
2773 },
2774
2775 styleTextAppliedForTest: function() { },
2776
2777 /**
2778 * @param {string} styleText
2779 * @param {boolean} majorChange
2780 */
2781 applyStyleText: function(styleText, majorChange)
2782 {
2783 this._applyStyleThrottler.schedule(this._innerApplyStyleText.bind(this, styleText, majorChange));
2784 },
2785
2786 /**
2787 * @param {string} styleText
2788 * @param {boolean} majorChange
2789 * @return {!Promise.<undefined>}
2790 */
2791 _innerApplyStyleText: function(styleText, majorChange)
2792 {
2793 if (!this.treeOutline)
2794 return Promise.resolve();
2795
2796 var oldStyleRange = this._style.range;
2797 if (!oldStyleRange)
2798 return Promise.resolve();
2799
2800 styleText = styleText.replace(/\s/g, " ").trim(); // Replace &nbsp; with whitespace.
2801 if (!styleText.length && majorChange && this._newProperty && !this._prop ertyHasBeenEditedIncrementally) {
2802 // The user deleted everything and never applied a new property valu e via Up/Down scrolling/live editing, so remove the tree element and update.
2803 var section = this.section();
2804 this.parent.removeChild(this);
2805 section.afterUpdate();
2806 return Promise.resolve();
2807 }
2808
2809 var currentNode = this._parentPane.node();
2810 this._parentPane._userOperation = true;
2811
2812 /**
2813 * @param {boolean} success
2814 * @this {WebInspector.StylePropertyTreeElement}
2815 */
2816 function callback(success)
2817 {
2818 delete this._parentPane._userOperation;
2819
2820 if (!success) {
2821 if (majorChange) {
2822 // It did not apply, cancel editing.
2823 this._revertStyleUponEditingCanceled();
2824 }
2825 this.styleTextAppliedForTest();
2826 return;
2827 }
2828
2829 this._matchedStyles.resetActiveProperties();
2830 this._propertyHasBeenEditedIncrementally = true;
2831 this.property = this._style.propertyAt(this.property.index);
2832
2833 // We are happy to update UI if user is not editing.
2834 if (!this._parentPane._isEditingStyle && currentNode === this.node() )
2835 this._updatePane();
2836
2837 this.styleTextAppliedForTest();
2838 }
2839
2840 // Append a ";" if the new text does not end in ";".
2841 // FIXME: this does not handle trailing comments.
2842 if (styleText.length && !/;\s*$/.test(styleText))
2843 styleText += ";";
2844 var overwriteProperty = !this._newProperty || this._propertyHasBeenEdite dIncrementally;
2845 return this.property.setText(styleText, majorChange, overwriteProperty)
2846 .then(callback.bind(this));
2847 },
2848
2849 /**
2850 * @override
2851 * @return {boolean} 2417 * @return {boolean}
2852 */ 2418 */
2853 ondblclick: function() 2419 function shouldCommitValueSemicolon(text, cursorPosition) {
2854 { 2420 // FIXME: should this account for semicolons inside comments?
2855 return true; // handled 2421 var openQuote = '';
2856 }, 2422 for (var i = 0; i < cursorPosition; ++i) {
2423 var ch = text[i];
2424 if (ch === '\\' && openQuote !== '')
2425 ++i; // skip next character inside string
2426 else if (!openQuote && (ch === '"' || ch === '\''))
2427 openQuote = ch;
2428 else if (openQuote === ch)
2429 openQuote = '';
2430 }
2431 return !openQuote;
2432 }
2433
2434 var keyChar = String.fromCharCode(event.charCode);
2435 var isFieldInputTerminated =
2436 (context.isEditingName ? keyChar === ':' :
2437 keyChar === ';' &&
2438 shouldCommitValueSemicolon(event.target.textContent, event.targ et.selectionLeftOffset()));
2439 if (isFieldInputTerminated) {
2440 // Enter or colon (for name)/semicolon outside of string (for value).
2441 event.consume(true);
2442 this._editingCommitted(event.target.textContent, context, 'forward');
2443 return;
2444 }
2445 }
2446
2447 /**
2448 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2449 * @param {!Event} event
2450 */
2451 _editingNameValueInput(context, event) {
2452 // Do not live-edit "content" property of pseudo elements. crbug.com/433889
2453 if (!context.isEditingName && (!this._parentPane.node().pseudoType() || this .name !== 'content'))
2454 this._applyFreeFlowStyleTextEdit();
2455 }
2456
2457 _applyFreeFlowStyleTextEdit() {
2458 var valueText = this.valueElement.textContent;
2459 if (valueText.indexOf(';') === -1)
2460 this.applyStyleText(this.nameElement.textContent + ': ' + valueText, false );
2461 }
2462
2463 kickFreeFlowStyleEditForTest() {
2464 this._applyFreeFlowStyleTextEdit();
2465 }
2466
2467 /**
2468 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2469 */
2470 editingEnded(context) {
2471 this._resetMouseDownElement();
2472
2473 this.setExpandable(context.hasChildren);
2474 if (context.expanded)
2475 this.expand();
2476 var editedElement = context.isEditingName ? this.nameElement : this.valueEle ment;
2477 // The proxyElement has been deleted, no need to remove listener.
2478 if (editedElement.parentElement)
2479 editedElement.parentElement.classList.remove('child-editing');
2480
2481 this._parentPane.setEditingStyle(false);
2482 }
2483
2484 /**
2485 * @param {?Element} element
2486 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2487 */
2488 editingCancelled(element, context) {
2489 this._removePrompt();
2490 this._revertStyleUponEditingCanceled();
2491 // This should happen last, as it clears the info necessary to restore the p roperty value after [Page]Up/Down changes.
2492 this.editingEnded(context);
2493 }
2494
2495 _revertStyleUponEditingCanceled() {
2496 if (this._propertyHasBeenEditedIncrementally) {
2497 this.applyStyleText(this._originalPropertyText, false);
2498 delete this._originalPropertyText;
2499 } else if (this._newProperty) {
2500 this.treeOutline.removeChild(this);
2501 } else {
2502 this.updateTitle();
2503 }
2504 }
2505
2506 /**
2507 * @param {string} moveDirection
2508 * @return {?WebInspector.StylePropertyTreeElement}
2509 */
2510 _findSibling(moveDirection) {
2511 var target = this;
2512 do {
2513 target = (moveDirection === 'forward' ? target.nextSibling : target.previo usSibling);
2514 } while (target && target.inherited());
2515
2516 return target;
2517 }
2518
2519 /**
2520 * @param {string} userInput
2521 * @param {!WebInspector.StylePropertyTreeElement.Context} context
2522 * @param {string} moveDirection
2523 */
2524 _editingCommitted(userInput, context, moveDirection) {
2525 this._removePrompt();
2526 this.editingEnded(context);
2527 var isEditingName = context.isEditingName;
2528
2529 // Determine where to move to before making changes
2530 var createNewProperty, moveToPropertyName, moveToSelector;
2531 var isDataPasted = 'originalName' in context;
2532 var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== cont ext.originalName ||
2533 this.valueElement.textContent !== con text.originalValue);
2534 var isPropertySplitPaste = isDataPasted && isEditingName && this.valueElemen t.textContent !== context.originalValue;
2535 var moveTo = this;
2536 var moveToOther = (isEditingName ^ (moveDirection === 'forward'));
2537 var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
2538 if (moveDirection === 'forward' && (!isEditingName || isPropertySplitPaste) ||
2539 moveDirection === 'backward' && isEditingName) {
2540 moveTo = moveTo._findSibling(moveDirection);
2541 if (moveTo)
2542 moveToPropertyName = moveTo.name;
2543 else if (moveDirection === 'forward' && (!this._newProperty || userInput))
2544 createNewProperty = true;
2545 else if (moveDirection === 'backward')
2546 moveToSelector = true;
2547 }
2548
2549 // Make the Changes and trigger the moveToNextCallback after updating.
2550 var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.rootElement( ).indexOfChild(moveTo) : -1;
2551 var blankInput = userInput.isWhitespace();
2552 var shouldCommitNewProperty = this._newProperty &&
2553 (isPropertySplitPaste || moveToOther || (!moveDirection && !isEditingNam e) || (isEditingName && blankInput));
2554 var section = /** @type {!WebInspector.StylePropertiesSection} */ (this.sect ion());
2555 if (((userInput !== context.previousContent || isDirtyViaPaste) && !this._ne wProperty) || shouldCommitNewProperty) {
2556 section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !b lankInput, section);
2557 var propertyText;
2558 if (blankInput || (this._newProperty && this.valueElement.textContent.isWh itespace()))
2559 propertyText = '';
2560 else {
2561 if (isEditingName)
2562 propertyText = userInput + ': ' + this.property.value;
2563 else
2564 propertyText = this.property.name + ': ' + userInput;
2565 }
2566 this.applyStyleText(propertyText, true);
2567 } else {
2568 if (isEditingName)
2569 this.property.name = userInput;
2570 else
2571 this.property.value = userInput;
2572 if (!isDataPasted && !this._newProperty)
2573 this.updateTitle();
2574 moveToNextCallback.call(this, this._newProperty, false, section);
2575 }
2857 2576
2858 /** 2577 /**
2859 * @override 2578 * The Callback to start editing the next/previous property/selector.
2860 * @param {!Event} event 2579 * @param {boolean} alreadyNew
2861 * @return {boolean} 2580 * @param {boolean} valueChanged
2581 * @param {!WebInspector.StylePropertiesSection} section
2582 * @this {WebInspector.StylePropertyTreeElement}
2862 */ 2583 */
2863 isEventWithinDisclosureTriangle: function(event) 2584 function moveToNextCallback(alreadyNew, valueChanged, section) {
2864 { 2585 if (!moveDirection)
2865 return event.target === this._expandElement; 2586 return;
2866 }, 2587
2867 2588 // User just tabbed through without changes.
2868 __proto__: TreeElement.prototype 2589 if (moveTo && moveTo.parent) {
2590 moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueEl ement);
2591 return;
2592 }
2593
2594 // User has made a change then tabbed, wiping all the original treeElement s.
2595 // Recalculate the new treeElement for the same property we were going to edit next.
2596 if (moveTo && !moveTo.parent) {
2597 var rootElement = section.propertiesTreeOutline.rootElement();
2598 if (moveDirection === 'forward' && blankInput && !isEditingName)
2599 --moveToIndex;
2600 if (moveToIndex >= rootElement.childCount() && !this._newProperty)
2601 createNewProperty = true;
2602 else {
2603 var treeElement = moveToIndex >= 0 ? rootElement.childAt(moveToIndex) : null;
2604 if (treeElement) {
2605 var elementToEdit =
2606 !isEditingName || isPropertySplitPaste ? treeElement.nameElement : treeElement.valueElement;
2607 if (alreadyNew && blankInput)
2608 elementToEdit = moveDirection === 'forward' ? treeElement.nameElem ent : treeElement.valueElement;
2609 treeElement.startEditing(elementToEdit);
2610 return;
2611 } else if (!alreadyNew)
2612 moveToSelector = true;
2613 }
2614 }
2615
2616 // Create a new attribute in this section (or move to next editable select or if possible).
2617 if (createNewProperty) {
2618 if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === ' backward')))
2619 return;
2620
2621 section.addNewBlankProperty().startEditing();
2622 return;
2623 }
2624
2625 if (abandonNewProperty) {
2626 moveTo = this._findSibling(moveDirection);
2627 var sectionToEdit = (moveTo || moveDirection === 'backward') ? section : section.nextEditableSibling();
2628 if (sectionToEdit) {
2629 if (sectionToEdit.style().parentRule)
2630 sectionToEdit.startEditingSelector();
2631 else
2632 sectionToEdit._moveEditorFromSelector(moveDirection);
2633 }
2634 return;
2635 }
2636
2637 if (moveToSelector) {
2638 if (section.style().parentRule)
2639 section.startEditingSelector();
2640 else
2641 section._moveEditorFromSelector(moveDirection);
2642 }
2643 }
2644 }
2645
2646 _removePrompt() {
2647 // BUG 53242. This cannot go into editingEnded(), as it should always happen first for any editing outcome.
2648 if (this._prompt) {
2649 this._prompt.detach();
2650 delete this._prompt;
2651 }
2652 }
2653
2654 styleTextAppliedForTest() {
2655 }
2656
2657 /**
2658 * @param {string} styleText
2659 * @param {boolean} majorChange
2660 */
2661 applyStyleText(styleText, majorChange) {
2662 this._applyStyleThrottler.schedule(this._innerApplyStyleText.bind(this, styl eText, majorChange));
2663 }
2664
2665 /**
2666 * @param {string} styleText
2667 * @param {boolean} majorChange
2668 * @return {!Promise.<undefined>}
2669 */
2670 _innerApplyStyleText(styleText, majorChange) {
2671 if (!this.treeOutline)
2672 return Promise.resolve();
2673
2674 var oldStyleRange = this._style.range;
2675 if (!oldStyleRange)
2676 return Promise.resolve();
2677
2678 styleText = styleText.replace(/\s/g, ' ').trim(); // Replace &nbsp; with wh itespace.
2679 if (!styleText.length && majorChange && this._newProperty && !this._property HasBeenEditedIncrementally) {
2680 // The user deleted everything and never applied a new property value via Up/Down scrolling/live editing, so remove the tree element and update.
2681 var section = this.section();
2682 this.parent.removeChild(this);
2683 section.afterUpdate();
2684 return Promise.resolve();
2685 }
2686
2687 var currentNode = this._parentPane.node();
2688 this._parentPane._userOperation = true;
2689
2690 /**
2691 * @param {boolean} success
2692 * @this {WebInspector.StylePropertyTreeElement}
2693 */
2694 function callback(success) {
2695 delete this._parentPane._userOperation;
2696
2697 if (!success) {
2698 if (majorChange) {
2699 // It did not apply, cancel editing.
2700 this._revertStyleUponEditingCanceled();
2701 }
2702 this.styleTextAppliedForTest();
2703 return;
2704 }
2705
2706 this._matchedStyles.resetActiveProperties();
2707 this._propertyHasBeenEditedIncrementally = true;
2708 this.property = this._style.propertyAt(this.property.index);
2709
2710 // We are happy to update UI if user is not editing.
2711 if (!this._parentPane._isEditingStyle && currentNode === this.node())
2712 this._updatePane();
2713
2714 this.styleTextAppliedForTest();
2715 }
2716
2717 // Append a ";" if the new text does not end in ";".
2718 // FIXME: this does not handle trailing comments.
2719 if (styleText.length && !/;\s*$/.test(styleText))
2720 styleText += ';';
2721 var overwriteProperty = !this._newProperty || this._propertyHasBeenEditedInc rementally;
2722 return this.property.setText(styleText, majorChange, overwriteProperty).then (callback.bind(this));
2723 }
2724
2725 /**
2726 * @override
2727 * @return {boolean}
2728 */
2729 ondblclick() {
2730 return true; // handled
2731 }
2732
2733 /**
2734 * @override
2735 * @param {!Event} event
2736 * @return {boolean}
2737 */
2738 isEventWithinDisclosureTriangle(event) {
2739 return event.target === this._expandElement;
2740 }
2869 }; 2741 };
2870 2742
2743 /** @typedef {{expanded: boolean, hasChildren: boolean, isEditingName: boolean, previousContent: string}} */
2744 WebInspector.StylePropertyTreeElement.Context;
2745
2871 /** 2746 /**
2872 * @constructor 2747 * @unrestricted
2873 * @extends {WebInspector.TextPrompt}
2874 * @param {!Array<string>} cssCompletions
2875 * @param {!WebInspector.StylePropertyTreeElement} treeElement
2876 * @param {boolean} isEditingName
2877 */ 2748 */
2878 WebInspector.StylesSidebarPane.CSSPropertyPrompt = function(cssCompletions, tree Element, isEditingName) 2749 WebInspector.StylesSidebarPane.CSSPropertyPrompt = class extends WebInspector.Te xtPrompt {
2879 { 2750 /**
2751 * @param {!Array<string>} cssCompletions
2752 * @param {!WebInspector.StylePropertyTreeElement} treeElement
2753 * @param {boolean} isEditingName
2754 */
2755 constructor(cssCompletions, treeElement, isEditingName) {
2880 // Use the same callback both for applyItemCallback and acceptItemCallback. 2756 // Use the same callback both for applyItemCallback and acceptItemCallback.
2881 WebInspector.TextPrompt.call(this); 2757 super();
2882 this.initialize(this._buildPropertyCompletions.bind(this), WebInspector.Styl eValueDelimiters); 2758 this.initialize(this._buildPropertyCompletions.bind(this), WebInspector.Styl eValueDelimiters);
2883 this.setSuggestBoxEnabled(true); 2759 this.setSuggestBoxEnabled(true);
2884 this._cssCompletions = cssCompletions; 2760 this._cssCompletions = cssCompletions;
2885 this._treeElement = treeElement; 2761 this._treeElement = treeElement;
2886 this._isEditingName = isEditingName; 2762 this._isEditingName = isEditingName;
2887 2763
2888 if (!isEditingName) { 2764 if (!isEditingName) {
2889 this.disableDefaultSuggestionForEmptyInput(); 2765 this.disableDefaultSuggestionForEmptyInput();
2890 2766
2891 // If a CSS value is being edited that has a numeric or hex substring, h int that precision modifier shortcuts are available. 2767 // If a CSS value is being edited that has a numeric or hex substring, hin t that precision modifier shortcuts are available.
2892 if (treeElement && treeElement.valueElement) { 2768 if (treeElement && treeElement.valueElement) {
2893 var cssValueText = treeElement.valueElement.textContent; 2769 var cssValueText = treeElement.valueElement.textContent;
2894 if (cssValueText.match(/#[\da-f]{3,6}$/i)) 2770 if (cssValueText.match(/#[\da-f]{3,6}$/i))
2895 this.setTitle(WebInspector.UIString("Increment/decrement with mo usewheel or up/down keys. %s: R ±1, Shift: G ±1, Alt: B ±1", WebInspector.isMac( ) ? "Cmd" : "Ctrl")); 2771 this.setTitle(WebInspector.UIString(
2896 else if (cssValueText.match(/\d+/)) 2772 'Increment/decrement with mousewheel or up/down keys. %s: R ±1, Sh ift: G ±1, Alt: B ±1',
2897 this.setTitle(WebInspector.UIString("Increment/decrement with mo usewheel or up/down keys. %s: ±100, Shift: ±10, Alt: ±0.1", WebInspector.isMac() ? "Cmd" : "Ctrl")); 2773 WebInspector.isMac() ? 'Cmd' : 'Ctrl'));
2774 else if (cssValueText.match(/\d+/))
2775 this.setTitle(WebInspector.UIString(
2776 'Increment/decrement with mousewheel or up/down keys. %s: ±100, Sh ift: ±10, Alt: ±0.1',
2777 WebInspector.isMac() ? 'Cmd' : 'Ctrl'));
2778 }
2779 }
2780 }
2781
2782 /**
2783 * @override
2784 * @param {!Event} event
2785 */
2786 onKeyDown(event) {
2787 switch (event.key) {
2788 case 'ArrowUp':
2789 case 'ArrowDown':
2790 case 'PageUp':
2791 case 'PageDown':
2792 if (this._handleNameOrValueUpDown(event)) {
2793 event.preventDefault();
2794 return;
2898 } 2795 }
2899 2796 break;
2900 } 2797 case 'Enter':
2798 // Accept any available autocompletions and advance to the next field.
2799 if (this.textWithCurrentSuggestion() !== this.text()) {
2800 this.tabKeyPressed();
2801 return;
2802 }
2803 break;
2804 }
2805
2806 super.onKeyDown(event);
2807 }
2808
2809 /**
2810 * @override
2811 * @param {!Event} event
2812 */
2813 onMouseWheel(event) {
2814 if (this._handleNameOrValueUpDown(event)) {
2815 event.consume(true);
2816 return;
2817 }
2818 super.onMouseWheel(event);
2819 }
2820
2821 /**
2822 * @override
2823 * @return {boolean}
2824 */
2825 tabKeyPressed() {
2826 this.acceptAutoComplete();
2827
2828 // Always tab to the next field.
2829 return false;
2830 }
2831
2832 /**
2833 * @param {!Event} event
2834 * @return {boolean}
2835 */
2836 _handleNameOrValueUpDown(event) {
2837 /**
2838 * @param {string} originalValue
2839 * @param {string} replacementString
2840 * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
2841 */
2842 function finishHandler(originalValue, replacementString) {
2843 // Synthesize property text disregarding any comments, custom whitespace e tc.
2844 this._treeElement.applyStyleText(
2845 this._treeElement.nameElement.textContent + ': ' + this._treeElement.v alueElement.textContent, false);
2846 }
2847
2848 /**
2849 * @param {string} prefix
2850 * @param {number} number
2851 * @param {string} suffix
2852 * @return {string}
2853 * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
2854 */
2855 function customNumberHandler(prefix, number, suffix) {
2856 if (number !== 0 && !suffix.length &&
2857 WebInspector.cssMetadata().isLengthProperty(this._treeElement.property .name))
2858 suffix = 'px';
2859 return prefix + number + suffix;
2860 }
2861
2862 // Handle numeric value increment/decrement only at this point.
2863 if (!this._isEditingName &&
2864 WebInspector.handleElementValueModifications(
2865 event, this._treeElement.valueElement, finishHandler.bind(this), thi s._isValueSuggestion.bind(this),
2866 customNumberHandler.bind(this)))
2867 return true;
2868
2869 return false;
2870 }
2871
2872 /**
2873 * @param {string} word
2874 * @return {boolean}
2875 */
2876 _isValueSuggestion(word) {
2877 if (!word)
2878 return false;
2879 word = word.toLowerCase();
2880 return this._cssCompletions.indexOf(word) !== -1;
2881 }
2882
2883 /**
2884 * @param {!Element} proxyElement
2885 * @param {!Range} wordRange
2886 * @param {boolean} force
2887 * @param {function(!Array.<string>, number=)} completionsReadyCallback
2888 */
2889 _buildPropertyCompletions(proxyElement, wordRange, force, completionsReadyCall back) {
2890 var prefix = wordRange.toString().toLowerCase();
2891 if (!prefix && !force && (this._isEditingName || proxyElement.textContent.le ngth)) {
2892 completionsReadyCallback([]);
2893 return;
2894 }
2895
2896 var results = this._cssCompletions.filter(completion => completion.startsWit h(prefix));
2897 if (!this._isEditingName && !results.length && prefix.length > 1 && '!import ant'.startsWith(prefix))
2898 results.push('!important');
2899 var userEnteredText = wordRange.toString().replace('-', '');
2900 if (userEnteredText && (userEnteredText === userEnteredText.toUpperCase())) {
2901 for (var i = 0; i < results.length; ++i)
2902 results[i] = results[i].toUpperCase();
2903 }
2904 var selectedIndex = this._isEditingName ? WebInspector.cssMetadata().mostUse dProperty(results) : 0;
2905 completionsReadyCallback(results, selectedIndex);
2906 }
2901 }; 2907 };
2902 2908
2903 WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype = {
2904 /**
2905 * @override
2906 * @param {!Event} event
2907 */
2908 onKeyDown: function(event)
2909 {
2910 switch (event.key) {
2911 case "ArrowUp":
2912 case "ArrowDown":
2913 case "PageUp":
2914 case "PageDown":
2915 if (this._handleNameOrValueUpDown(event)) {
2916 event.preventDefault();
2917 return;
2918 }
2919 break;
2920 case "Enter":
2921 // Accept any available autocompletions and advance to the next fiel d.
2922 if (this.textWithCurrentSuggestion() !== this.text()) {
2923 this.tabKeyPressed();
2924 return;
2925 }
2926 break;
2927 }
2928
2929 WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
2930 },
2931
2932 /**
2933 * @override
2934 * @param {!Event} event
2935 */
2936 onMouseWheel: function(event)
2937 {
2938 if (this._handleNameOrValueUpDown(event)) {
2939 event.consume(true);
2940 return;
2941 }
2942 WebInspector.TextPrompt.prototype.onMouseWheel.call(this, event);
2943 },
2944
2945 /**
2946 * @override
2947 * @return {boolean}
2948 */
2949 tabKeyPressed: function()
2950 {
2951 this.acceptAutoComplete();
2952
2953 // Always tab to the next field.
2954 return false;
2955 },
2956
2957 /**
2958 * @param {!Event} event
2959 * @return {boolean}
2960 */
2961 _handleNameOrValueUpDown: function(event)
2962 {
2963 /**
2964 * @param {string} originalValue
2965 * @param {string} replacementString
2966 * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
2967 */
2968 function finishHandler(originalValue, replacementString)
2969 {
2970 // Synthesize property text disregarding any comments, custom whites pace etc.
2971 this._treeElement.applyStyleText(this._treeElement.nameElement.textC ontent + ": " + this._treeElement.valueElement.textContent, false);
2972 }
2973
2974 /**
2975 * @param {string} prefix
2976 * @param {number} number
2977 * @param {string} suffix
2978 * @return {string}
2979 * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
2980 */
2981 function customNumberHandler(prefix, number, suffix)
2982 {
2983 if (number !== 0 && !suffix.length && WebInspector.cssMetadata().isL engthProperty(this._treeElement.property.name))
2984 suffix = "px";
2985 return prefix + number + suffix;
2986 }
2987
2988 // Handle numeric value increment/decrement only at this point.
2989 if (!this._isEditingName && WebInspector.handleElementValueModifications (event, this._treeElement.valueElement, finishHandler.bind(this), this._isValueS uggestion.bind(this), customNumberHandler.bind(this)))
2990 return true;
2991
2992 return false;
2993 },
2994
2995 /**
2996 * @param {string} word
2997 * @return {boolean}
2998 */
2999 _isValueSuggestion: function(word)
3000 {
3001 if (!word)
3002 return false;
3003 word = word.toLowerCase();
3004 return this._cssCompletions.indexOf(word) !== -1;
3005 },
3006
3007 /**
3008 * @param {!Element} proxyElement
3009 * @param {!Range} wordRange
3010 * @param {boolean} force
3011 * @param {function(!Array.<string>, number=)} completionsReadyCallback
3012 */
3013 _buildPropertyCompletions: function(proxyElement, wordRange, force, completi onsReadyCallback)
3014 {
3015 var prefix = wordRange.toString().toLowerCase();
3016 if (!prefix && !force && (this._isEditingName || proxyElement.textConten t.length)) {
3017 completionsReadyCallback([]);
3018 return;
3019 }
3020
3021 var results = this._cssCompletions.filter(completion => completion.start sWith(prefix));
3022 if (!this._isEditingName && !results.length && prefix.length > 1 && "!im portant".startsWith(prefix))
3023 results.push("!important");
3024 var userEnteredText = wordRange.toString().replace("-", "");
3025 if (userEnteredText && (userEnteredText === userEnteredText.toUpperCase( ))) {
3026 for (var i = 0; i < results.length; ++i)
3027 results[i] = results[i].toUpperCase();
3028 }
3029 var selectedIndex = this._isEditingName ? WebInspector.cssMetadata().mos tUsedProperty(results) : 0;
3030 completionsReadyCallback(results, selectedIndex);
3031 },
3032
3033 __proto__: WebInspector.TextPrompt.prototype
3034 };
3035
3036 /** 2909 /**
3037 * @constructor 2910 * @unrestricted
3038 * @param {?WebInspector.CSSRule} rule
3039 * @param {?WebInspector.DOMNode} node
3040 * @param {string} name
3041 * @param {string} value
3042 */ 2911 */
3043 WebInspector.StylesSidebarPropertyRenderer = function(rule, node, name, value) 2912 WebInspector.StylesSidebarPropertyRenderer = class {
3044 { 2913 /**
2914 * @param {?WebInspector.CSSRule} rule
2915 * @param {?WebInspector.DOMNode} node
2916 * @param {string} name
2917 * @param {string} value
2918 */
2919 constructor(rule, node, name, value) {
3045 this._rule = rule; 2920 this._rule = rule;
3046 this._node = node; 2921 this._node = node;
3047 this._propertyName = name; 2922 this._propertyName = name;
3048 this._propertyValue = value; 2923 this._propertyValue = value;
2924 }
2925
2926 /**
2927 * @param {function(string):!Node} handler
2928 */
2929 setColorHandler(handler) {
2930 this._colorHandler = handler;
2931 }
2932
2933 /**
2934 * @param {function(string):!Node} handler
2935 */
2936 setBezierHandler(handler) {
2937 this._bezierHandler = handler;
2938 }
2939
2940 /**
2941 * @param {function(string, string):!Node} handler
2942 */
2943 setShadowHandler(handler) {
2944 this._shadowHandler = handler;
2945 }
2946
2947 /**
2948 * @return {!Element}
2949 */
2950 renderName() {
2951 var nameElement = createElement('span');
2952 nameElement.className = 'webkit-css-property';
2953 nameElement.textContent = this._propertyName;
2954 nameElement.normalize();
2955 return nameElement;
2956 }
2957
2958 /**
2959 * @return {!Element}
2960 */
2961 renderValue() {
2962 var valueElement = createElement('span');
2963 valueElement.className = 'value';
2964 if (!this._propertyValue)
2965 return valueElement;
2966
2967 if (this._shadowHandler && (this._propertyName === 'box-shadow' || this._pro pertyName === 'text-shadow' ||
2968 this._propertyName === '-webkit-box-shadow') &&
2969 !WebInspector.CSSMetadata.VariableRegex.test(this._propertyValue)) {
2970 valueElement.appendChild(this._shadowHandler(this._propertyValue, this._pr opertyName));
2971 valueElement.normalize();
2972 return valueElement;
2973 }
2974
2975 var regexes = [WebInspector.CSSMetadata.VariableRegex, WebInspector.CSSMetad ata.URLRegex];
2976 var processors = [createTextNode, this._processURL.bind(this)];
2977 if (this._bezierHandler && WebInspector.cssMetadata().isBezierAwareProperty( this._propertyName)) {
2978 regexes.push(WebInspector.Geometry.CubicBezier.Regex);
2979 processors.push(this._bezierHandler);
2980 }
2981 if (this._colorHandler && WebInspector.cssMetadata().isColorAwareProperty(th is._propertyName)) {
2982 regexes.push(WebInspector.Color.Regex);
2983 processors.push(this._colorHandler);
2984 }
2985 var results = WebInspector.TextUtils.splitStringByRegexes(this._propertyValu e, regexes);
2986 for (var i = 0; i < results.length; i++) {
2987 var result = results[i];
2988 var processor = result.regexIndex === -1 ? createTextNode : processors[res ult.regexIndex];
2989 valueElement.appendChild(processor(result.value));
2990 }
2991 valueElement.normalize();
2992 return valueElement;
2993 }
2994
2995 /**
2996 * @param {string} text
2997 * @return {!Node}
2998 */
2999 _processURL(text) {
3000 // Strip "url(" and ")" along with whitespace.
3001 var url = text.substring(4, text.length - 1).trim();
3002 var isQuoted = /^'.*'$/.test(url) || /^".*"$/.test(url);
3003 if (isQuoted)
3004 url = url.substring(1, url.length - 1);
3005 var container = createDocumentFragment();
3006 container.createTextChild('url(');
3007 var hrefUrl = null;
3008 if (this._rule && this._rule.resourceURL())
3009 hrefUrl = WebInspector.ParsedURL.completeURL(this._rule.resourceURL(), url );
3010 else if (this._node)
3011 hrefUrl = this._node.resolveURL(url);
3012 var hasResource = hrefUrl && !!WebInspector.resourceForURL(hrefUrl);
3013 // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI.
3014 container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl || url, url, und efined, !hasResource));
3015 container.createTextChild(')');
3016 return container;
3017 }
3049 }; 3018 };
3050 3019
3051 WebInspector.StylesSidebarPropertyRenderer.prototype = {
3052 /**
3053 * @param {function(string):!Node} handler
3054 */
3055 setColorHandler: function(handler)
3056 {
3057 this._colorHandler = handler;
3058 },
3059
3060 /**
3061 * @param {function(string):!Node} handler
3062 */
3063 setBezierHandler: function(handler)
3064 {
3065 this._bezierHandler = handler;
3066 },
3067
3068 /**
3069 * @param {function(string, string):!Node} handler
3070 */
3071 setShadowHandler: function(handler)
3072 {
3073 this._shadowHandler = handler;
3074 },
3075
3076 /**
3077 * @return {!Element}
3078 */
3079 renderName: function()
3080 {
3081 var nameElement = createElement("span");
3082 nameElement.className = "webkit-css-property";
3083 nameElement.textContent = this._propertyName;
3084 nameElement.normalize();
3085 return nameElement;
3086 },
3087
3088 /**
3089 * @return {!Element}
3090 */
3091 renderValue: function()
3092 {
3093 var valueElement = createElement("span");
3094 valueElement.className = "value";
3095 if (!this._propertyValue)
3096 return valueElement;
3097
3098 if (this._shadowHandler && (this._propertyName === "box-shadow" || this. _propertyName === "text-shadow" || this._propertyName === "-webkit-box-shadow")
3099 && !WebInspector.CSSMetadata.VariableRegex.test(this._propertyVa lue)) {
3100 valueElement.appendChild(this._shadowHandler(this._propertyValue, th is._propertyName));
3101 valueElement.normalize();
3102 return valueElement;
3103 }
3104
3105 var regexes = [WebInspector.CSSMetadata.VariableRegex, WebInspector.CSSM etadata.URLRegex];
3106 var processors = [createTextNode, this._processURL.bind(this)];
3107 if (this._bezierHandler && WebInspector.cssMetadata().isBezierAwarePrope rty(this._propertyName)) {
3108 regexes.push(WebInspector.Geometry.CubicBezier.Regex);
3109 processors.push(this._bezierHandler);
3110 }
3111 if (this._colorHandler && WebInspector.cssMetadata().isColorAwarePropert y(this._propertyName)) {
3112 regexes.push(WebInspector.Color.Regex);
3113 processors.push(this._colorHandler);
3114 }
3115 var results = WebInspector.TextUtils.splitStringByRegexes(this._property Value, regexes);
3116 for (var i = 0; i < results.length; i++) {
3117 var result = results[i];
3118 var processor = result.regexIndex === -1 ? createTextNode : processo rs[result.regexIndex];
3119 valueElement.appendChild(processor(result.value));
3120 }
3121 valueElement.normalize();
3122 return valueElement;
3123 },
3124
3125 /**
3126 * @param {string} text
3127 * @return {!Node}
3128 */
3129 _processURL: function(text)
3130 {
3131 // Strip "url(" and ")" along with whitespace.
3132 var url = text.substring(4, text.length - 1).trim();
3133 var isQuoted = /^'.*'$/.test(url) || /^".*"$/.test(url);
3134 if (isQuoted)
3135 url = url.substring(1, url.length - 1);
3136 var container = createDocumentFragment();
3137 container.createTextChild("url(");
3138 var hrefUrl = null;
3139 if (this._rule && this._rule.resourceURL())
3140 hrefUrl = WebInspector.ParsedURL.completeURL(this._rule.resourceURL( ), url);
3141 else if (this._node)
3142 hrefUrl = this._node.resolveURL(url);
3143 var hasResource = hrefUrl && !!WebInspector.resourceForURL(hrefUrl);
3144 // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI.
3145 container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl || url, url, undefined, !hasResource));
3146 container.createTextChild(")");
3147 return container;
3148 }
3149 };
3150
3151 /** 3020 /**
3152 * @constructor
3153 * @implements {WebInspector.ToolbarItem.Provider} 3021 * @implements {WebInspector.ToolbarItem.Provider}
3022 * @unrestricted
3154 */ 3023 */
3155 WebInspector.StylesSidebarPane.ButtonProvider = function() 3024 WebInspector.StylesSidebarPane.ButtonProvider = class {
3156 { 3025 constructor() {
3157 this._button = new WebInspector.ToolbarButton(WebInspector.UIString("New Sty le Rule"), "add-toolbar-item"); 3026 this._button = new WebInspector.ToolbarButton(WebInspector.UIString('New Sty le Rule'), 'add-toolbar-item');
3158 this._button.addEventListener("click", this._clicked, this); 3027 this._button.addEventListener('click', this._clicked, this);
3159 this._button.element.createChild("div", "long-click-glyph toolbar-button-the me"); 3028 this._button.element.createChild('div', 'long-click-glyph toolbar-button-the me');
3160 new WebInspector.LongClickController(this._button.element, this._longClicked .bind(this)); 3029 new WebInspector.LongClickController(this._button.element, this._longClicked .bind(this));
3161 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, onNodeCha nged.bind(this)); 3030 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, onNodeCha nged.bind(this));
3162 onNodeChanged.call(this); 3031 onNodeChanged.call(this);
3163 3032
3164 /** 3033 /**
3165 * @this {WebInspector.StylesSidebarPane.ButtonProvider} 3034 * @this {WebInspector.StylesSidebarPane.ButtonProvider}
3166 */ 3035 */
3167 function onNodeChanged() 3036 function onNodeChanged() {
3168 { 3037 var node = WebInspector.context.flavor(WebInspector.DOMNode);
3169 var node = WebInspector.context.flavor(WebInspector.DOMNode); 3038 node = node ? node.enclosingElementOrSelf() : null;
3170 node = node ? node.enclosingElementOrSelf() : null; 3039 this._button.setEnabled(!!node);
3171 this._button.setEnabled(!!node);
3172 } 3040 }
3041 }
3042
3043 _clicked() {
3044 WebInspector.StylesSidebarPane._instance._createNewRuleInViaInspectorStyleSh eet();
3045 }
3046
3047 /**
3048 * @param {!Event} e
3049 */
3050 _longClicked(e) {
3051 WebInspector.StylesSidebarPane._instance._onAddButtonLongClick(e);
3052 }
3053
3054 /**
3055 * @override
3056 * @return {!WebInspector.ToolbarItem}
3057 */
3058 item() {
3059 return this._button;
3060 }
3173 }; 3061 };
3174
3175 WebInspector.StylesSidebarPane.ButtonProvider.prototype = {
3176 _clicked: function()
3177 {
3178 WebInspector.StylesSidebarPane._instance._createNewRuleInViaInspectorSty leSheet();
3179 },
3180
3181 /**
3182 * @param {!Event} e
3183 */
3184 _longClicked: function(e)
3185 {
3186 WebInspector.StylesSidebarPane._instance._onAddButtonLongClick(e);
3187 },
3188
3189 /**
3190 * @override
3191 * @return {!WebInspector.ToolbarItem}
3192 */
3193 item: function()
3194 {
3195 return this._button;
3196 }
3197 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698