Web Inspector: Styles Redesign: rework Computed panel to use Spreadsheet classes
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / SpreadsheetStyleProperty.js
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
27 {
28     constructor(delegate, property)
29     {
30         super();
31
32         console.assert(property instanceof WI.CSSProperty);
33
34         this._delegate = delegate || null;
35         this._property = property;
36         this._element = document.createElement("div");
37
38         this._contentElement = null;
39         this._nameElement = null;
40         this._valueElement = null;
41
42         this._nameTextField = null;
43         this._valueTextField = null;
44
45         this._property.__propertyView = this;
46
47         this._hasInvalidVariableValue = false;
48
49         this._update();
50         property.addEventListener(WI.CSSProperty.Event.OverriddenStatusChanged, this.updateStatus, this);
51         property.addEventListener(WI.CSSProperty.Event.Changed, this.updateStatus, this);
52     }
53
54     // Public
55
56     get element() { return this._element; }
57     get nameTextField() { return this._nameTextField; }
58     get valueTextField() { return this._valueTextField; }
59     get enabled() { return this._property.enabled; }
60
61     set index(index)
62     {
63         this._element.dataset.propertyIndex = index;
64     }
65
66     detached()
67     {
68         this._property.__propertyView = null;
69
70         if (this._nameTextField)
71             this._nameTextField.detached();
72
73         if (this._valueTextField)
74             this._valueTextField.detached();
75     }
76
77     hidden()
78     {
79         if (this._nameTextField && this._nameTextField.editing)
80             this._nameTextField.element.blur();
81         else if (this._valueTextField && this._valueTextField.editing)
82             this._valueTextField.element.blur();
83     }
84
85     highlight()
86     {
87         this._element.classList.add("highlighted");
88     }
89
90     updateStatus()
91     {
92         let duplicatePropertyExistsBelow = (cssProperty) => {
93             let propertyFound = false;
94
95             for (let property of this._property.ownerStyle.properties) {
96                 if (property === cssProperty)
97                     propertyFound = true;
98                 else if (property.name === cssProperty.name && propertyFound)
99                     return true;
100             }
101
102             return false;
103         };
104
105         let classNames = [WI.SpreadsheetStyleProperty.StyleClassName];
106         let elementTitle = "";
107
108         if (this._property.overridden) {
109             classNames.push("overridden");
110             if (duplicatePropertyExistsBelow(this._property)) {
111                 classNames.push("has-warning");
112                 elementTitle = WI.UIString("Duplicate property");
113             }
114         }
115
116         if (this._property.implicit)
117             classNames.push("implicit");
118
119         if (this._property.ownerStyle.inherited && !this._property.inherited)
120             classNames.push("not-inherited");
121
122         if (!this._property.valid && this._property.hasOtherVendorNameOrKeyword())
123             classNames.push("other-vendor");
124         else if (this._hasInvalidVariableValue || (!this._property.valid && this._property.value !== "")) {
125             let propertyNameIsValid = false;
126             if (WI.CSSCompletions.cssNameCompletions)
127                 propertyNameIsValid = WI.CSSCompletions.cssNameCompletions.isValidPropertyName(this._property.name);
128
129             classNames.push("has-warning");
130
131             if (!propertyNameIsValid) {
132                 classNames.push("invalid-name");
133                 elementTitle = WI.UIString("Unsupported property name");
134             } else {
135                 classNames.push("invalid-value");
136                 elementTitle = WI.UIString("Unsupported property value");
137             }
138         }
139
140         if (!this._property.enabled)
141             classNames.push("disabled");
142
143         this._element.className = classNames.join(" ");
144         this._element.title = elementTitle;
145     }
146
147     applyFilter(filterText)
148     {
149         let matchesName = this._nameElement.textContent.includes(filterText);
150         this._nameElement.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName, !!matchesName);
151
152         let matchesValue = this._valueElement.textContent.includes(filterText);
153         this._valueElement.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName, !!matchesValue);
154
155         let matches = matchesName || matchesValue;
156         this._contentElement.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInPropertyClassName, !matches);
157         return matches;
158     }
159
160     // Private
161
162     _remove(replacement = "")
163     {
164         this.element.remove();
165
166         if (replacement)
167             this._property.replaceWithText(replacement);
168         else
169             this._property.remove();
170
171         this.detached();
172
173         if (this._delegate && typeof this._delegate.spreadsheetStylePropertyRemoved === "function")
174             this._delegate.spreadsheetStylePropertyRemoved(this);
175     }
176
177     _update()
178     {
179         this.element.removeChildren();
180
181         if (this._property.editable) {
182             this._checkboxElement = this.element.appendChild(document.createElement("input"));
183             this._checkboxElement.classList.add("property-toggle");
184             this._checkboxElement.type = "checkbox";
185             this._checkboxElement.checked = this._property.enabled;
186             this._checkboxElement.tabIndex = -1;
187             this._checkboxElement.addEventListener("click", (event) => {
188                 event.stopPropagation();
189                 let disabled = !this._checkboxElement.checked;
190                 this._property.commentOut(disabled);
191                 this._update();
192             });
193         }
194
195         this._contentElement = this.element.appendChild(document.createElement("span"));
196         this._contentElement.className = "content";
197
198         if (!this._property.enabled)
199             this._contentElement.append("/* ");
200
201         this._nameElement = this._contentElement.appendChild(document.createElement("span"));
202         this._nameElement.classList.add("name");
203         this._nameElement.textContent = this._property.name;
204
205         let colonElement = this._contentElement.appendChild(document.createElement("span"));
206         colonElement.textContent = ": ";
207
208         this._valueElement = this._contentElement.appendChild(document.createElement("span"));
209         this._valueElement.classList.add("value");
210         this._renderValue(this._property.rawValue);
211
212         if (this._property.editable && this._property.enabled) {
213             this._nameElement.tabIndex = 0;
214             this._nameElement.addEventListener("beforeinput", this._handleNameBeforeInput.bind(this));
215             this._nameElement.addEventListener("paste", this._handleNamePaste.bind(this));
216
217             this._nameTextField = new WI.SpreadsheetTextField(this, this._nameElement, this._nameCompletionDataProvider.bind(this));
218
219             this._valueElement.tabIndex = 0;
220             this._valueElement.addEventListener("beforeinput", this._handleValueBeforeInput.bind(this));
221
222             this._valueTextField = new WI.SpreadsheetTextField(this, this._valueElement, this._valueCompletionDataProvider.bind(this));
223         }
224
225         if (this._property.editable) {
226             this._setupJumpToSymbol(this._nameElement);
227             this._setupJumpToSymbol(this._valueElement);
228         }
229
230         let semicolonElement = this._contentElement.appendChild(document.createElement("span"));
231         semicolonElement.textContent = ";";
232
233         if (this._property.enabled) {
234             this._warningElement = this.element.appendChild(document.createElement("span"));
235             this._warningElement.className = "warning";
236         } else
237             this._contentElement.append(" */");
238
239         if (!this._property.implicit && this._property.ownerStyle.type === WI.CSSStyleDeclaration.Type.Computed) {
240             let effectiveProperty = this._property.ownerStyle.nodeStyles.effectivePropertyForName(this._property.name);
241             if (effectiveProperty && !effectiveProperty.styleSheetTextRange)
242                 effectiveProperty = effectiveProperty.relatedShorthandProperty;
243
244             let ownerRule = effectiveProperty ? effectiveProperty.ownerStyle.ownerRule : null;
245
246             let arrowElement = this._contentElement.appendChild(WI.createGoToArrowButton());
247             arrowElement.addEventListener("click", (event) => {
248                 if (!effectiveProperty || !ownerRule || !event.altKey) {
249                     if (this._delegate.spreadsheetStylePropertyShowProperty)
250                         this._delegate.spreadsheetStylePropertyShowProperty(this, this._property);
251                     return;
252                 }
253
254                 let sourceCode = ownerRule.sourceCodeLocation.sourceCode;
255                 let {startLine, startColumn} = effectiveProperty.styleSheetTextRange;
256                 WI.showSourceCodeLocation(sourceCode.createSourceCodeLocation(startLine, startColumn), {
257                     ignoreNetworkTab: true,
258                     ignoreSearchTab: true,
259                 });
260             });
261
262             if (effectiveProperty && ownerRule)
263                 arrowElement.title = WI.UIString("Option-click to show source");
264         }
265
266         this.updateStatus();
267     }
268
269     // SpreadsheetTextField delegate
270
271     spreadsheetTextFieldWillStartEditing(textField)
272     {
273         let isEditingName = textField === this._nameTextField;
274         textField.value = isEditingName ? this._property.name : this._property.rawValue;
275     }
276
277     spreadsheetTextFieldDidChange(textField)
278     {
279         if (textField === this._valueTextField)
280             this._handleValueChange();
281         else if (textField === this._nameTextField)
282             this._handleNameChange();
283     }
284
285     spreadsheetTextFieldDidCommit(textField, {direction})
286     {
287         let propertyName = this._nameTextField.value.trim();
288         let propertyValue = this._valueTextField.value.trim();
289         let willRemoveProperty = false;
290         let isEditingName = textField === this._nameTextField;
291
292         if (!propertyName || (!propertyValue && !isEditingName && direction === "forward"))
293             willRemoveProperty = true;
294
295         if (!isEditingName && !willRemoveProperty)
296             this._renderValue(propertyValue);
297
298         if (direction === "forward") {
299             if (isEditingName && !willRemoveProperty) {
300                 // Move focus from the name to the value.
301                 this._valueTextField.startEditing();
302                 return;
303             }
304         } else {
305             if (!isEditingName) {
306                 // Move focus from the value to the name.
307                 this._nameTextField.startEditing();
308                 return;
309             }
310         }
311
312         if (typeof this._delegate.spreadsheetStylePropertyFocusMoved === "function") {
313             // Move focus away from the current property, to the next or previous one, if exists, or to the next or previous rule, if exists.
314             this._delegate.spreadsheetStylePropertyFocusMoved(this, {direction, willRemoveProperty});
315         }
316
317         if (willRemoveProperty)
318             this._remove();
319     }
320
321     spreadsheetTextFieldDidBlur(textField, event)
322     {
323         let focusedOutsideThisProperty = event.relatedTarget !== this._nameElement && event.relatedTarget !== this._valueElement;
324         if (focusedOutsideThisProperty && (!this._nameTextField.value.trim() || !this._valueTextField.value.trim())) {
325             this._remove();
326             return;
327         }
328
329         if (textField === this._valueTextField)
330             this._renderValue(this._valueElement.textContent);
331
332         if (typeof this._delegate.spreadsheetStylePropertyFocusMoved === "function")
333             this._delegate.spreadsheetStylePropertyFocusMoved(this, {direction: null});
334     }
335
336     spreadsheetTextFieldDidBackspace(textField)
337     {
338         if (textField === this._nameTextField)
339             this.spreadsheetTextFieldDidCommit(textField, {direction: "backward"});
340         else if (textField === this._valueTextField)
341             this._nameTextField.startEditing();
342     }
343
344     // Private
345
346     _renderValue(value)
347     {
348         this._hasInvalidVariableValue = false;
349
350         const maxValueLength = 150;
351         let tokens = WI.tokenizeCSSValue(value);
352
353         if (this._property.enabled) {
354             // FIXME: <https://webkit.org/b/178636> Web Inspector: Styles: Make inline widgets work with CSS functions (var(), calc(), etc.)
355             tokens = this._addGradientTokens(tokens);
356             tokens = this._addColorTokens(tokens);
357             tokens = this._addTimingFunctionTokens(tokens, "cubic-bezier");
358             tokens = this._addTimingFunctionTokens(tokens, "spring");
359             tokens = this._addVariableTokens(tokens);
360         }
361
362         tokens = tokens.map((token) => {
363             if (token instanceof Element)
364                 return token;
365
366             let className = "";
367
368             if (token.type) {
369                 if (token.type.includes("string"))
370                     className = "token-string";
371                 else if (token.type.includes("link"))
372                     className = "token-link";
373                 else if (token.type.includes("comment"))
374                     className = "token-comment";
375             }
376
377             if (className) {
378                 let span = document.createElement("span");
379                 span.classList.add(className);
380                 span.textContent = token.value.truncateMiddle(maxValueLength);
381                 return span;
382             }
383
384             return token.value;
385         });
386
387         this._valueElement.removeChildren();
388         this._valueElement.append(...tokens);
389     }
390
391     _createInlineSwatch(type, text, valueObject)
392     {
393         let tokenElement = document.createElement("span");
394         let innerElement = document.createElement("span");
395         innerElement.textContent = text;
396
397         let readOnly = !this._property.editable;
398         let swatch = new WI.InlineSwatch(type, valueObject, readOnly);
399
400         swatch.addEventListener(WI.InlineSwatch.Event.ValueChanged, (event) => {
401             let value = event.data.value && event.data.value.toString();
402             if (!value)
403                 return;
404
405             innerElement.textContent = value;
406             this._handleValueChange();
407         }, this);
408
409         if (typeof this._delegate.stylePropertyInlineSwatchActivated === "function") {
410             swatch.addEventListener(WI.InlineSwatch.Event.Activated, () => {
411                 this._delegate.stylePropertyInlineSwatchActivated();
412             });
413         }
414
415         if (typeof this._delegate.stylePropertyInlineSwatchDeactivated === "function") {
416             swatch.addEventListener(WI.InlineSwatch.Event.Deactivated, () => {
417                 this._delegate.stylePropertyInlineSwatchDeactivated();
418             });
419         }
420
421         tokenElement.append(swatch.element, innerElement);
422
423         // Prevent the value from editing when clicking on the swatch.
424         swatch.element.addEventListener("mousedown", (event) => { event.stop(); });
425
426         return tokenElement;
427     }
428
429     _addGradientTokens(tokens)
430     {
431         let gradientRegex = /^(repeating-)?(linear|radial)-gradient$/i;
432         let newTokens = [];
433         let gradientStartIndex = NaN;
434         let openParenthesis = 0;
435
436         for (let i = 0; i < tokens.length; i++) {
437             let token = tokens[i];
438             if (token.type && token.type.includes("atom") && gradientRegex.test(token.value)) {
439                 gradientStartIndex = i;
440                 openParenthesis = 0;
441             } else if (token.value === "(" && !isNaN(gradientStartIndex))
442                 openParenthesis++;
443             else if (token.value === ")" && !isNaN(gradientStartIndex)) {
444                 openParenthesis--;
445                 if (openParenthesis > 0) {
446                     // Matched a CSS function inside of the gradient.
447                     continue;
448                 }
449
450                 let rawTokens = tokens.slice(gradientStartIndex, i + 1);
451                 let text = rawTokens.map((token) => token.value).join("");
452                 let gradient = WI.Gradient.fromString(text);
453                 if (gradient)
454                     newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Gradient, text, gradient));
455                 else
456                     newTokens.push(...rawTokens);
457
458                 gradientStartIndex = NaN;
459             } else if (isNaN(gradientStartIndex))
460                 newTokens.push(token);
461         }
462
463         return newTokens;
464     }
465
466     _addColorTokens(tokens)
467     {
468         let newTokens = [];
469
470         let pushPossibleColorToken = (text, ...rawTokens) => {
471             let color = WI.Color.fromString(text);
472             if (color)
473                 newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Color, text, color));
474             else
475                 newTokens.push(...rawTokens);
476         };
477
478         let colorFunctionStartIndex = NaN;
479
480         for (let i = 0; i < tokens.length; i++) {
481             let token = tokens[i];
482             if (token.type && token.type.includes("hex-color")) {
483                 // Hex
484                 pushPossibleColorToken(token.value, token);
485             } else if (WI.Color.FunctionNames.has(token.value) && token.type && (token.type.includes("atom") || token.type.includes("keyword"))) {
486                 // Color Function start
487                 colorFunctionStartIndex = i;
488             } else if (isNaN(colorFunctionStartIndex) && token.type && token.type.includes("keyword")) {
489                 // Color keyword
490                 pushPossibleColorToken(token.value, token);
491             } else if (!isNaN(colorFunctionStartIndex)) {
492                 // Color Function end
493                 if (token.value !== ")")
494                     continue;
495
496                 let rawTokens = tokens.slice(colorFunctionStartIndex, i + 1);
497                 let text = rawTokens.map((token) => token.value).join("");
498                 pushPossibleColorToken(text, ...rawTokens);
499                 colorFunctionStartIndex = NaN;
500             } else
501                 newTokens.push(token);
502         }
503
504         return newTokens;
505     }
506
507     _addTimingFunctionTokens(tokens, tokenType)
508     {
509         let newTokens = [];
510         let startIndex = NaN;
511         let openParenthesis = 0;
512
513         for (let i = 0; i < tokens.length; i++) {
514             let token = tokens[i];
515             if (token.value === tokenType && token.type && token.type.includes("atom")) {
516                 startIndex = i;
517                 openParenthesis = 0;
518             } else if (token.value === "(" && !isNaN(startIndex))
519                 openParenthesis++;
520             else if (token.value === ")" && !isNaN(startIndex)) {
521
522                 openParenthesis--;
523                 if (openParenthesis > 0)
524                     continue;
525
526                 let rawTokens = tokens.slice(startIndex, i + 1);
527                 let text = rawTokens.map((token) => token.value).join("");
528
529                 let valueObject;
530                 let inlineSwatchType;
531                 if (tokenType === "cubic-bezier") {
532                     valueObject = WI.CubicBezier.fromString(text);
533                     inlineSwatchType = WI.InlineSwatch.Type.Bezier;
534                 } else if (tokenType === "spring") {
535                     valueObject = WI.Spring.fromString(text);
536                     inlineSwatchType = WI.InlineSwatch.Type.Spring;
537                 }
538
539                 if (valueObject)
540                     newTokens.push(this._createInlineSwatch(inlineSwatchType, text, valueObject));
541                 else
542                     newTokens.push(...rawTokens);
543
544                 startIndex = NaN;
545             } else if (isNaN(startIndex))
546                 newTokens.push(token);
547         }
548
549         return newTokens;
550     }
551
552     _addVariableTokens(tokens)
553     {
554         let newTokens = [];
555         let startIndex = NaN;
556         let openParenthesis = 0;
557
558         for (let i = 0; i < tokens.length; i++) {
559             let token = tokens[i];
560             if (token.value === "var" && token.type && token.type.includes("atom")) {
561                 startIndex = i;
562                 openParenthesis = 0;
563             } else if (token.value === "(" && !isNaN(startIndex))
564                 ++openParenthesis;
565             else if (token.value === ")" && !isNaN(startIndex)) {
566                 --openParenthesis;
567                 if (openParenthesis > 0)
568                     continue;
569
570                 let rawTokens = tokens.slice(startIndex, i + 1);
571                 let tokenValues = rawTokens.map((token) => token.value);
572                 let variableName = tokenValues.find((value, i) => value.startsWith("--") && /\bvariable-2\b/.test(rawTokens[i].type));
573
574                 const dontCreateIfMissing = true;
575                 let variableProperty = this._property.ownerStyle.nodeStyles.computedStyle.propertyForName(variableName, dontCreateIfMissing);
576                 if (variableProperty) {
577                     let valueObject = variableProperty.value.trim();
578                     newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Variable, tokenValues.join(""), valueObject));
579                 } else {
580                     this._hasInvalidVariableValue = true;
581                     newTokens.push(...rawTokens);
582                 }
583
584                 startIndex = NaN;
585             } else if (isNaN(startIndex))
586                 newTokens.push(token);
587         }
588
589         return newTokens;
590     }
591
592     _handleNameChange()
593     {
594         this._property.name = this._nameElement.textContent.trim();
595     }
596
597     _handleValueChange()
598     {
599         this._property.rawValue = this._valueElement.textContent.trim();
600     }
601
602     _handleNameBeforeInput(event)
603     {
604         if (event.data !== ":" || event.inputType !== "insertText")
605             return;
606
607         event.preventDefault();
608         this._nameTextField.discardCompletion();
609         this._valueTextField.startEditing();
610     }
611
612     _handleNamePaste(event)
613     {
614         let text = event.clipboardData.getData("text/plain");
615         if (!text || !text.includes(":"))
616             return;
617
618         event.preventDefault();
619
620         this._remove(text);
621
622         if (this._delegate.spreadsheetStylePropertyAddBlankPropertySoon) {
623             this._delegate.spreadsheetStylePropertyAddBlankPropertySoon(this, {
624                 index: parseInt(this._element.dataset.propertyIndex) + 1,
625             });
626         }
627     }
628
629     _nameCompletionDataProvider(prefix)
630     {
631         return WI.CSSCompletions.cssNameCompletions.startsWith(prefix);
632     }
633
634     _handleValueBeforeInput(event)
635     {
636         if (event.data !== ";" || event.inputType !== "insertText")
637             return;
638
639         let text = this._valueTextField.valueWithoutSuggestion();
640         let selection = window.getSelection();
641         if (!selection.rangeCount || selection.getRangeAt(0).endOffset !== text.length)
642             return;
643
644         // Find the first and last index (if any) of a quote character to ensure that the string
645         // doesn't contain unbalanced quotes. If so, then there's no way that the semicolon could be
646         // part of a string within the value, so we can assume that it's the property "terminator".
647         const quoteRegex = /["']/g;
648         let start = -1;
649         let end = text.length;
650         let match = null;
651         while (match = quoteRegex.exec(text)) {
652             if (start < 0)
653                 start = match.index;
654             end = match.index + 1;
655         }
656
657         if (start !== -1 && !text.substring(start, end).hasMatchingEscapedQuotes())
658             return;
659
660         event.preventDefault();
661         this._valueTextField.stopEditing();
662         this.spreadsheetTextFieldDidCommit(this._valueTextField, {direction: "forward"});
663     }
664
665     _valueCompletionDataProvider(prefix)
666     {
667         let propertyName = this._nameElement.textContent.trim();
668         return WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(prefix);
669     }
670
671     _setupJumpToSymbol(element)
672     {
673         element.addEventListener("mousedown", (event) => {
674             if (event.button !== 0)
675                 return;
676
677             if (!WI.modifierKeys.metaKey)
678                 return;
679
680             if (element.isContentEditable)
681                 return;
682
683             let sourceCodeLocation = null;
684             if (this._property.ownerStyle.ownerRule)
685                 sourceCodeLocation = this._property.ownerStyle.ownerRule.sourceCodeLocation;
686
687             if (!sourceCodeLocation)
688                 return;
689
690             let range = this._property.styleSheetTextRange;
691             const options = {
692                 ignoreNetworkTab: true,
693                 ignoreSearchTab: true,
694             };
695             let sourceCode = sourceCodeLocation.sourceCode;
696             WI.showSourceCodeLocation(sourceCode.createSourceCodeLocation(range.startLine, range.startColumn), options);
697         });
698     }
699 };
700
701 WI.SpreadsheetStyleProperty.StyleClassName = "property";