OLD | NEW |
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 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 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 }; | |
OLD | NEW |