1/*
2 * Copyright (C) 2010, Google 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'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26#include "InspectorStyleSheet.h"
27
28#include "CSSImportRule.h"
29#include "CSSKeyframesRule.h"
30#include "CSSMediaRule.h"
31#include "CSSParser.h"
32#include "CSSParserObserver.h"
33#include "CSSPropertyNames.h"
34#include "CSSPropertyParser.h"
35#include "CSSPropertySourceData.h"
36#include "CSSRule.h"
37#include "CSSRuleList.h"
38#include "CSSStyleRule.h"
39#include "CSSStyleSheet.h"
40#include "CSSSupportsRule.h"
41#include "ContentSecurityPolicy.h"
42#include "Document.h"
43#include "Element.h"
44#include "HTMLHeadElement.h"
45#include "HTMLNames.h"
46#include "HTMLParserIdioms.h"
47#include "HTMLStyleElement.h"
48#include "InspectorCSSAgent.h"
49#include "InspectorDOMAgent.h"
50#include "InspectorPageAgent.h"
51#include "MediaList.h"
52#include "Node.h"
53#include "SVGElement.h"
54#include "SVGStyleElement.h"
55#include "StyleProperties.h"
56#include "StyleResolver.h"
57#include "StyleRule.h"
58#include "StyleRuleImport.h"
59#include "StyleSheetContents.h"
60#include "StyleSheetList.h"
61#include <JavaScriptCore/ContentSearchUtilities.h>
62#include <JavaScriptCore/RegularExpression.h>
63#include <wtf/text/StringBuilder.h>
64
65using JSON::ArrayOf;
66using WebCore::RuleSourceDataList;
67using WebCore::CSSRuleSourceData;
68
69class ParsedStyleSheet {
70 WTF_MAKE_FAST_ALLOCATED;
71public:
72 ParsedStyleSheet();
73
74 const String& text() const { ASSERT(m_hasText); return m_text; }
75 void setText(const String& text);
76 bool hasText() const { return m_hasText; }
77 RuleSourceDataList* sourceData() const { return m_sourceData.get(); }
78 void setSourceData(std::unique_ptr<RuleSourceDataList>);
79 bool hasSourceData() const { return m_sourceData != nullptr; }
80 WebCore::CSSRuleSourceData* ruleSourceDataAt(unsigned) const;
81
82private:
83
84 String m_text;
85 bool m_hasText;
86 std::unique_ptr<RuleSourceDataList> m_sourceData;
87};
88
89ParsedStyleSheet::ParsedStyleSheet()
90 : m_hasText(false)
91{
92}
93
94void ParsedStyleSheet::setText(const String& text)
95{
96 m_hasText = true;
97 m_text = text;
98 setSourceData(nullptr);
99}
100
101static void flattenSourceData(RuleSourceDataList& dataList, RuleSourceDataList& target)
102{
103 for (auto& data : dataList) {
104 if (data->type == WebCore::StyleRule::Style)
105 target.append(data.copyRef());
106 else if (data->type == WebCore::StyleRule::Media)
107 flattenSourceData(data->childRules, target);
108 else if (data->type == WebCore::StyleRule::Supports)
109 flattenSourceData(data->childRules, target);
110 }
111}
112
113void ParsedStyleSheet::setSourceData(std::unique_ptr<RuleSourceDataList> sourceData)
114{
115 if (!sourceData) {
116 m_sourceData.reset();
117 return;
118 }
119
120 m_sourceData = std::make_unique<RuleSourceDataList>();
121
122 // FIXME: This is a temporary solution to retain the original flat sourceData structure
123 // containing only style rules, even though CSSParser now provides the full rule source data tree.
124 // Normally, we should just assign m_sourceData = sourceData;
125 flattenSourceData(*sourceData, *m_sourceData);
126}
127
128WebCore::CSSRuleSourceData* ParsedStyleSheet::ruleSourceDataAt(unsigned index) const
129{
130 if (!hasSourceData() || index >= m_sourceData->size())
131 return nullptr;
132
133 return m_sourceData->at(index).ptr();
134}
135
136
137namespace WebCore {
138
139using namespace Inspector;
140
141static CSSParserContext parserContextForDocument(Document* document)
142{
143 return document ? CSSParserContext(*document) : strictCSSParserContext();
144}
145
146class StyleSheetHandler : public CSSParserObserver {
147public:
148 StyleSheetHandler(const String& parsedText, Document* document, RuleSourceDataList* result)
149 : m_parsedText(parsedText)
150 , m_document(document)
151 , m_ruleSourceDataResult(result)
152 {
153 ASSERT(m_ruleSourceDataResult);
154 }
155
156private:
157 void startRuleHeader(StyleRule::Type, unsigned) override;
158 void endRuleHeader(unsigned) override;
159 void observeSelector(unsigned startOffset, unsigned endOffset) override;
160 void startRuleBody(unsigned) override;
161 void endRuleBody(unsigned) override;
162 void observeProperty(unsigned startOffset, unsigned endOffset, bool isImportant, bool isParsed) override;
163 void observeComment(unsigned startOffset, unsigned endOffset) override;
164
165 Ref<CSSRuleSourceData> popRuleData();
166 template <typename CharacterType> inline void setRuleHeaderEnd(const CharacterType*, unsigned);
167 void fixUnparsedPropertyRanges(CSSRuleSourceData*);
168
169 const String& m_parsedText;
170 Document* m_document;
171
172 RuleSourceDataList m_currentRuleDataStack;
173 RefPtr<CSSRuleSourceData> m_currentRuleData;
174 RuleSourceDataList* m_ruleSourceDataResult { nullptr };
175};
176
177void StyleSheetHandler::startRuleHeader(StyleRule::Type type, unsigned offset)
178{
179 // Pop off data for a previous invalid rule.
180 if (m_currentRuleData)
181 m_currentRuleDataStack.removeLast();
182
183 auto data = CSSRuleSourceData::create(type);
184 data->ruleHeaderRange.start = offset;
185 m_currentRuleData = data.copyRef();
186 m_currentRuleDataStack.append(WTFMove(data));
187}
188
189template <typename CharacterType> inline void StyleSheetHandler::setRuleHeaderEnd(const CharacterType* dataStart, unsigned listEndOffset)
190{
191 while (listEndOffset > 1) {
192 if (isHTMLSpace<CharacterType>(*(dataStart + listEndOffset - 1)))
193 --listEndOffset;
194 else
195 break;
196 }
197
198 m_currentRuleDataStack.last()->ruleHeaderRange.end = listEndOffset;
199 if (!m_currentRuleDataStack.last()->selectorRanges.isEmpty())
200 m_currentRuleDataStack.last()->selectorRanges.last().end = listEndOffset;
201}
202
203void StyleSheetHandler::endRuleHeader(unsigned offset)
204{
205 ASSERT(!m_currentRuleDataStack.isEmpty());
206
207 if (m_parsedText.is8Bit())
208 setRuleHeaderEnd<LChar>(m_parsedText.characters8(), offset);
209 else
210 setRuleHeaderEnd<UChar>(m_parsedText.characters16(), offset);
211}
212
213void StyleSheetHandler::observeSelector(unsigned startOffset, unsigned endOffset)
214{
215 ASSERT(m_currentRuleDataStack.size());
216 m_currentRuleDataStack.last()->selectorRanges.append(SourceRange(startOffset, endOffset));
217}
218
219void StyleSheetHandler::startRuleBody(unsigned offset)
220{
221 m_currentRuleData = nullptr;
222 ASSERT(!m_currentRuleDataStack.isEmpty());
223
224 // Skip the rule body opening brace.
225 if (m_parsedText[offset] == '{')
226 ++offset;
227
228 m_currentRuleDataStack.last()->ruleBodyRange.start = offset;
229}
230
231void StyleSheetHandler::endRuleBody(unsigned offset)
232{
233 ASSERT(!m_currentRuleDataStack.isEmpty());
234 m_currentRuleDataStack.last()->ruleBodyRange.end = offset;
235 auto rule = popRuleData();
236 fixUnparsedPropertyRanges(rule.ptr());
237 if (m_currentRuleDataStack.isEmpty())
238 m_ruleSourceDataResult->append(WTFMove(rule));
239 else
240 m_currentRuleDataStack.last()->childRules.append(WTFMove(rule));
241}
242
243Ref<CSSRuleSourceData> StyleSheetHandler::popRuleData()
244{
245 ASSERT(!m_currentRuleDataStack.isEmpty());
246 m_currentRuleData = nullptr;
247 auto data = WTFMove(m_currentRuleDataStack.last());
248 m_currentRuleDataStack.removeLast();
249 return data;
250}
251
252template <typename CharacterType>
253static inline void fixUnparsedProperties(const CharacterType* characters, CSSRuleSourceData* ruleData)
254{
255 Vector<CSSPropertySourceData>& propertyData = ruleData->styleSourceData->propertyData;
256 unsigned size = propertyData.size();
257 if (!size)
258 return;
259
260 unsigned styleStart = ruleData->ruleBodyRange.start;
261
262 CSSPropertySourceData* nextData = &(propertyData.at(0));
263 for (unsigned i = 0; i < size; ++i) {
264 CSSPropertySourceData* currentData = nextData;
265 nextData = i < size - 1 ? &(propertyData.at(i + 1)) : nullptr;
266
267 if (currentData->parsedOk)
268 continue;
269 if (currentData->range.end > 0 && characters[styleStart + currentData->range.end - 1] == ';')
270 continue;
271
272 unsigned propertyEnd;
273 if (!nextData)
274 propertyEnd = ruleData->ruleBodyRange.end - 1;
275 else
276 propertyEnd = styleStart + nextData->range.start - 1;
277
278 while (isHTMLSpace<CharacterType>(characters[propertyEnd]))
279 --propertyEnd;
280
281 // propertyEnd points at the last property text character.
282 unsigned newRangeEnd = (propertyEnd - styleStart) + 1;
283 if (currentData->range.end != newRangeEnd) {
284 currentData->range.end = newRangeEnd;
285 unsigned valueStart = styleStart + currentData->range.start + currentData->name.length();
286 while (valueStart < propertyEnd && characters[valueStart] != ':')
287 ++valueStart;
288
289 // Shift past the ':'.
290 if (valueStart < propertyEnd)
291 ++valueStart;
292
293 while (valueStart < propertyEnd && isHTMLSpace<CharacterType>(characters[valueStart]))
294 ++valueStart;
295
296 // Need to exclude the trailing ';' from the property value.
297 currentData->value = String(characters + valueStart, propertyEnd - valueStart + (characters[propertyEnd] == ';' ? 0 : 1));
298 }
299 }
300}
301
302void StyleSheetHandler::fixUnparsedPropertyRanges(CSSRuleSourceData* ruleData)
303{
304 if (!ruleData->styleSourceData)
305 return;
306
307 if (m_parsedText.is8Bit()) {
308 fixUnparsedProperties<LChar>(m_parsedText.characters8(), ruleData);
309 return;
310 }
311
312 fixUnparsedProperties<UChar>(m_parsedText.characters16(), ruleData);
313}
314
315void StyleSheetHandler::observeProperty(unsigned startOffset, unsigned endOffset, bool isImportant, bool isParsed)
316{
317 if (m_currentRuleDataStack.isEmpty() || !m_currentRuleDataStack.last()->styleSourceData)
318 return;
319
320 ASSERT(endOffset <= m_parsedText.length());
321
322 // Include semicolon in the property text.
323 if (endOffset < m_parsedText.length() && m_parsedText[endOffset] == ';')
324 ++endOffset;
325
326 ASSERT(startOffset < endOffset);
327 String propertyString = m_parsedText.substring(startOffset, endOffset - startOffset).stripWhiteSpace();
328 if (propertyString.endsWith(';'))
329 propertyString = propertyString.left(propertyString.length() - 1);
330 size_t colonIndex = propertyString.find(':');
331 ASSERT(colonIndex != notFound);
332
333 String name = propertyString.left(colonIndex).stripWhiteSpace();
334 String value = propertyString.substring(colonIndex + 1, propertyString.length()).stripWhiteSpace();
335
336 // FIXME-NEWPARSER: The property range is relative to the declaration start offset, but no
337 // good reason for it, and it complicates fixUnparsedProperties.
338 SourceRange& topRuleBodyRange = m_currentRuleDataStack.last()->ruleBodyRange;
339 m_currentRuleDataStack.last()->styleSourceData->propertyData.append(CSSPropertySourceData(name, value, isImportant, false, isParsed, SourceRange(startOffset - topRuleBodyRange.start, endOffset - topRuleBodyRange.start)));
340}
341
342void StyleSheetHandler::observeComment(unsigned startOffset, unsigned endOffset)
343{
344 ASSERT(endOffset <= m_parsedText.length());
345
346 if (m_currentRuleDataStack.isEmpty() || !m_currentRuleDataStack.last()->ruleHeaderRange.end || !m_currentRuleDataStack.last()->styleSourceData)
347 return;
348
349 // The lexer is not inside a property AND it is scanning a declaration-aware
350 // rule body.
351 String commentText = m_parsedText.substring(startOffset, endOffset - startOffset);
352
353 ASSERT(commentText.startsWith("/*"));
354 commentText = commentText.substring(2);
355
356 // Require well-formed comments.
357 if (!commentText.endsWith("*/"))
358 return;
359 commentText = commentText.substring(0, commentText.length() - 2).stripWhiteSpace();
360 if (commentText.isEmpty())
361 return;
362
363 // FIXME: Use the actual rule type rather than STYLE_RULE?
364 RuleSourceDataList sourceData;
365
366 StyleSheetHandler handler(commentText, m_document, &sourceData);
367 CSSParser::parseDeclarationForInspector(parserContextForDocument(m_document), commentText, handler);
368 Vector<CSSPropertySourceData>& commentPropertyData = sourceData.first()->styleSourceData->propertyData;
369 if (commentPropertyData.size() != 1)
370 return;
371 CSSPropertySourceData& propertyData = commentPropertyData.at(0);
372 bool parsedOk = propertyData.parsedOk || propertyData.name.startsWith("-moz-") || propertyData.name.startsWith("-o-") || propertyData.name.startsWith("-webkit-") || propertyData.name.startsWith("-ms-");
373 if (!parsedOk || propertyData.range.length() != commentText.length())
374 return;
375
376 // FIXME-NEWPARSER: The property range is relative to the declaration start offset, but no
377 // good reason for it, and it complicates fixUnparsedProperties.
378 SourceRange& topRuleBodyRange = m_currentRuleDataStack.last()->ruleBodyRange;
379 m_currentRuleDataStack.last()->styleSourceData->propertyData.append(CSSPropertySourceData(propertyData.name, propertyData.value, false, true, true, SourceRange(startOffset - topRuleBodyRange.start, endOffset - topRuleBodyRange.start)));
380}
381
382enum MediaListSource {
383 MediaListSourceLinkedSheet,
384 MediaListSourceInlineSheet,
385 MediaListSourceMediaRule,
386 MediaListSourceImportRule
387};
388
389static RefPtr<Inspector::Protocol::CSS::SourceRange> buildSourceRangeObject(const SourceRange& range, const Vector<size_t>& lineEndings, int* endingLine = nullptr)
390{
391 if (lineEndings.isEmpty())
392 return nullptr;
393
394 TextPosition start = ContentSearchUtilities::textPositionFromOffset(range.start, lineEndings);
395 TextPosition end = ContentSearchUtilities::textPositionFromOffset(range.end, lineEndings);
396
397 if (endingLine)
398 *endingLine = end.m_line.zeroBasedInt();
399
400 return Inspector::Protocol::CSS::SourceRange::create()
401 .setStartLine(start.m_line.zeroBasedInt())
402 .setStartColumn(start.m_column.zeroBasedInt())
403 .setEndLine(end.m_line.zeroBasedInt())
404 .setEndColumn(end.m_column.zeroBasedInt())
405 .release();
406}
407
408static Ref<Inspector::Protocol::CSS::CSSMedia> buildMediaObject(const MediaList* media, MediaListSource mediaListSource, const String& sourceURL)
409{
410 // Make certain compilers happy by initializing |source| up-front.
411 Inspector::Protocol::CSS::CSSMedia::Source source = Inspector::Protocol::CSS::CSSMedia::Source::InlineSheet;
412 switch (mediaListSource) {
413 case MediaListSourceMediaRule:
414 source = Inspector::Protocol::CSS::CSSMedia::Source::MediaRule;
415 break;
416 case MediaListSourceImportRule:
417 source = Inspector::Protocol::CSS::CSSMedia::Source::ImportRule;
418 break;
419 case MediaListSourceLinkedSheet:
420 source = Inspector::Protocol::CSS::CSSMedia::Source::LinkedSheet;
421 break;
422 case MediaListSourceInlineSheet:
423 source = Inspector::Protocol::CSS::CSSMedia::Source::InlineSheet;
424 break;
425 }
426
427 auto mediaObject = Inspector::Protocol::CSS::CSSMedia::create()
428 .setText(media->mediaText())
429 .setSource(source)
430 .release();
431
432 if (!sourceURL.isEmpty()) {
433 mediaObject->setSourceURL(sourceURL);
434 mediaObject->setSourceLine(media->queries()->lastLine());
435 }
436 return mediaObject;
437}
438
439static RefPtr<CSSRuleList> asCSSRuleList(CSSStyleSheet* styleSheet)
440{
441 if (!styleSheet)
442 return nullptr;
443
444 auto list = StaticCSSRuleList::create();
445 Vector<RefPtr<CSSRule>>& listRules = list->rules();
446 for (unsigned i = 0, size = styleSheet->length(); i < size; ++i)
447 listRules.append(styleSheet->item(i));
448 return list;
449}
450
451static RefPtr<CSSRuleList> asCSSRuleList(CSSRule* rule)
452{
453 if (!rule)
454 return nullptr;
455
456 if (is<CSSMediaRule>(*rule))
457 return &downcast<CSSMediaRule>(*rule).cssRules();
458
459 if (is<CSSKeyframesRule>(*rule))
460 return &downcast<CSSKeyframesRule>(*rule).cssRules();
461
462 if (is<CSSSupportsRule>(*rule))
463 return &downcast<CSSSupportsRule>(*rule).cssRules();
464
465 return nullptr;
466}
467
468static void fillMediaListChain(CSSRule* rule, JSON::ArrayOf<Inspector::Protocol::CSS::CSSMedia>& mediaArray)
469{
470 MediaList* mediaList;
471 CSSRule* parentRule = rule;
472 String sourceURL;
473 while (parentRule) {
474 CSSStyleSheet* parentStyleSheet = nullptr;
475 bool isMediaRule = true;
476 if (is<CSSMediaRule>(*parentRule)) {
477 CSSMediaRule& mediaRule = downcast<CSSMediaRule>(*parentRule);
478 mediaList = mediaRule.media();
479 parentStyleSheet = mediaRule.parentStyleSheet();
480 } else if (is<CSSImportRule>(*parentRule)) {
481 CSSImportRule& importRule = downcast<CSSImportRule>(*parentRule);
482 mediaList = &importRule.media();
483 parentStyleSheet = importRule.parentStyleSheet();
484 isMediaRule = false;
485 } else
486 mediaList = nullptr;
487
488 if (parentStyleSheet) {
489 sourceURL = parentStyleSheet->contents().baseURL();
490 if (sourceURL.isEmpty())
491 sourceURL = InspectorDOMAgent::documentURLString(parentStyleSheet->ownerDocument());
492 } else
493 sourceURL = emptyString();
494
495 if (mediaList && mediaList->length())
496 mediaArray.addItem(buildMediaObject(mediaList, isMediaRule ? MediaListSourceMediaRule : MediaListSourceImportRule, sourceURL));
497
498 if (parentRule->parentRule())
499 parentRule = parentRule->parentRule();
500 else {
501 CSSStyleSheet* styleSheet = parentRule->parentStyleSheet();
502 while (styleSheet) {
503 mediaList = styleSheet->media();
504 if (mediaList && mediaList->length()) {
505 Document* doc = styleSheet->ownerDocument();
506 if (doc)
507 sourceURL = doc->url();
508 else if (!styleSheet->contents().baseURL().isEmpty())
509 sourceURL = styleSheet->contents().baseURL();
510 else
511 sourceURL = emptyString();
512 mediaArray.addItem(buildMediaObject(mediaList, styleSheet->ownerNode() ? MediaListSourceLinkedSheet : MediaListSourceInlineSheet, sourceURL));
513 }
514 parentRule = styleSheet->ownerRule();
515 if (parentRule)
516 break;
517 styleSheet = styleSheet->parentStyleSheet();
518 }
519 }
520 }
521}
522
523Ref<InspectorStyle> InspectorStyle::create(const InspectorCSSId& styleId, Ref<CSSStyleDeclaration>&& style, InspectorStyleSheet* parentStyleSheet)
524{
525 return adoptRef(*new InspectorStyle(styleId, WTFMove(style), parentStyleSheet));
526}
527
528InspectorStyle::InspectorStyle(const InspectorCSSId& styleId, Ref<CSSStyleDeclaration>&& style, InspectorStyleSheet* parentStyleSheet)
529 : m_styleId(styleId)
530 , m_style(WTFMove(style))
531 , m_parentStyleSheet(parentStyleSheet)
532{
533}
534
535InspectorStyle::~InspectorStyle() = default;
536
537RefPtr<Inspector::Protocol::CSS::CSSStyle> InspectorStyle::buildObjectForStyle() const
538{
539 Ref<Inspector::Protocol::CSS::CSSStyle> result = styleWithProperties();
540 if (!m_styleId.isEmpty())
541 result->setStyleId(m_styleId.asProtocolValue<Inspector::Protocol::CSS::CSSStyleId>());
542
543 result->setWidth(m_style->getPropertyValue("width"));
544 result->setHeight(m_style->getPropertyValue("height"));
545
546 RefPtr<CSSRuleSourceData> sourceData = extractSourceData();
547 if (sourceData)
548 result->setRange(buildSourceRangeObject(sourceData->ruleBodyRange, m_parentStyleSheet->lineEndings()));
549
550 return result;
551}
552
553Ref<JSON::ArrayOf<Inspector::Protocol::CSS::CSSComputedStyleProperty>> InspectorStyle::buildArrayForComputedStyle() const
554{
555 auto result = JSON::ArrayOf<Inspector::Protocol::CSS::CSSComputedStyleProperty>::create();
556 Vector<InspectorStyleProperty> properties;
557 populateAllProperties(&properties);
558
559 for (auto& property : properties) {
560 const CSSPropertySourceData& propertyEntry = property.sourceData;
561 auto entry = Inspector::Protocol::CSS::CSSComputedStyleProperty::create()
562 .setName(propertyEntry.name)
563 .setValue(propertyEntry.value)
564 .release();
565 result->addItem(WTFMove(entry));
566 }
567
568 return result;
569}
570
571ExceptionOr<String> InspectorStyle::text() const
572{
573 // Precondition: m_parentStyleSheet->ensureParsedDataReady() has been called successfully.
574 auto sourceData = extractSourceData();
575 if (!sourceData)
576 return Exception { NotFoundError };
577
578 auto result = m_parentStyleSheet->text();
579 if (result.hasException())
580 return result.releaseException();
581
582 auto& bodyRange = sourceData->ruleBodyRange;
583 return result.releaseReturnValue().substring(bodyRange.start, bodyRange.end - bodyRange.start);
584}
585
586static String lowercasePropertyName(const String& name)
587{
588 // Custom properties are case-sensitive.
589 if (name.startsWith("--"))
590 return name;
591 return name.convertToASCIILowercase();
592}
593
594void InspectorStyle::populateAllProperties(Vector<InspectorStyleProperty>* result) const
595{
596 HashSet<String> sourcePropertyNames;
597
598 auto sourceData = extractSourceData();
599 auto* sourcePropertyData = sourceData ? &sourceData->styleSourceData->propertyData : nullptr;
600 if (sourcePropertyData) {
601 auto styleDeclarationOrException = text();
602 ASSERT(!styleDeclarationOrException.hasException());
603 String styleDeclaration = styleDeclarationOrException.hasException() ? emptyString() : styleDeclarationOrException.releaseReturnValue();
604 for (auto& sourceData : *sourcePropertyData) {
605 InspectorStyleProperty p(sourceData, true, sourceData.disabled);
606 p.setRawTextFromStyleDeclaration(styleDeclaration);
607 result->append(p);
608 sourcePropertyNames.add(lowercasePropertyName(sourceData.name));
609 }
610 }
611
612 for (int i = 0, size = m_style->length(); i < size; ++i) {
613 String name = m_style->item(i);
614 if (sourcePropertyNames.add(lowercasePropertyName(name)))
615 result->append(InspectorStyleProperty(CSSPropertySourceData(name, m_style->getPropertyValue(name), !m_style->getPropertyPriority(name).isEmpty(), false, true, SourceRange()), false, false));
616 }
617}
618
619Ref<Inspector::Protocol::CSS::CSSStyle> InspectorStyle::styleWithProperties() const
620{
621 Vector<InspectorStyleProperty> properties;
622 populateAllProperties(&properties);
623
624 auto propertiesObject = JSON::ArrayOf<Inspector::Protocol::CSS::CSSProperty>::create();
625 auto shorthandEntries = ArrayOf<Inspector::Protocol::CSS::ShorthandEntry>::create();
626 HashMap<String, RefPtr<Inspector::Protocol::CSS::CSSProperty>> propertyNameToPreviousActiveProperty;
627 HashSet<String> foundShorthands;
628 String previousPriority;
629 String previousStatus;
630 Vector<size_t> lineEndings = m_parentStyleSheet ? m_parentStyleSheet->lineEndings() : Vector<size_t> { };
631 auto sourceData = extractSourceData();
632 unsigned ruleBodyRangeStart = sourceData ? sourceData->ruleBodyRange.start : 0;
633
634 for (Vector<InspectorStyleProperty>::iterator it = properties.begin(), itEnd = properties.end(); it != itEnd; ++it) {
635 const CSSPropertySourceData& propertyEntry = it->sourceData;
636 const String& name = propertyEntry.name;
637
638 auto status = it->disabled ? Inspector::Protocol::CSS::CSSPropertyStatus::Disabled : Inspector::Protocol::CSS::CSSPropertyStatus::Active;
639
640 auto property = Inspector::Protocol::CSS::CSSProperty::create()
641 .setName(name.convertToASCIILowercase())
642 .setValue(propertyEntry.value)
643 .release();
644
645 propertiesObject->addItem(property.copyRef());
646
647 CSSPropertyID propertyId = cssPropertyID(name);
648
649 // Default "parsedOk" == true.
650 if (!propertyEntry.parsedOk || isInternalCSSProperty(propertyId))
651 property->setParsedOk(false);
652 if (it->hasRawText())
653 property->setText(it->rawText);
654
655 // Default "priority" == "".
656 if (propertyEntry.important)
657 property->setPriority("important");
658
659 if (it->hasSource) {
660 // The property range is relative to the style body start.
661 // Should be converted into an absolute range (relative to the stylesheet start)
662 // for the proper conversion into line:column.
663 SourceRange absolutePropertyRange = propertyEntry.range;
664 absolutePropertyRange.start += ruleBodyRangeStart;
665 absolutePropertyRange.end += ruleBodyRangeStart;
666 property->setRange(buildSourceRangeObject(absolutePropertyRange, lineEndings));
667 }
668
669 if (!it->disabled) {
670 if (it->hasSource) {
671 ASSERT(sourceData);
672 property->setImplicit(false);
673
674 // Parsed property overrides any property with the same name. Non-parsed property overrides
675 // previous non-parsed property with the same name (if any).
676 bool shouldInactivate = false;
677
678 // Canonicalize property names to treat non-prefixed and vendor-prefixed property names the same (opacity vs. -webkit-opacity).
679 String canonicalPropertyName = propertyId ? getPropertyNameString(propertyId) : name;
680 HashMap<String, RefPtr<Inspector::Protocol::CSS::CSSProperty>>::iterator activeIt = propertyNameToPreviousActiveProperty.find(canonicalPropertyName);
681 if (activeIt != propertyNameToPreviousActiveProperty.end()) {
682 if (propertyEntry.parsedOk) {
683 bool successPriority = activeIt->value->getString(Inspector::Protocol::CSS::CSSProperty::Priority, previousPriority);
684 bool successStatus = activeIt->value->getString(Inspector::Protocol::CSS::CSSProperty::Status, previousStatus);
685 if (successStatus && previousStatus != "inactive") {
686 if (propertyEntry.important || !successPriority) // Priority not set == "not important".
687 shouldInactivate = true;
688 else if (status == Inspector::Protocol::CSS::CSSPropertyStatus::Active) {
689 // Inactivate a non-important property following the same-named important property.
690 status = Inspector::Protocol::CSS::CSSPropertyStatus::Inactive;
691 }
692 }
693 } else {
694 bool previousParsedOk;
695 bool success = activeIt->value->getBoolean(Inspector::Protocol::CSS::CSSProperty::ParsedOk, previousParsedOk);
696 if (success && !previousParsedOk)
697 shouldInactivate = true;
698 }
699 } else
700 propertyNameToPreviousActiveProperty.set(canonicalPropertyName, property.copyRef());
701
702 if (shouldInactivate) {
703 activeIt->value->setStatus(Inspector::Protocol::CSS::CSSPropertyStatus::Inactive);
704 propertyNameToPreviousActiveProperty.set(canonicalPropertyName, property.copyRef());
705 }
706 } else {
707 bool implicit = m_style->isPropertyImplicit(name);
708 // Default "implicit" == false.
709 if (implicit)
710 property->setImplicit(true);
711 status = Inspector::Protocol::CSS::CSSPropertyStatus::Style;
712
713 String shorthand = m_style->getPropertyShorthand(name);
714 if (!shorthand.isEmpty()) {
715 if (!foundShorthands.contains(shorthand)) {
716 foundShorthands.add(shorthand);
717 auto entry = Inspector::Protocol::CSS::ShorthandEntry::create()
718 .setName(shorthand)
719 .setValue(shorthandValue(shorthand))
720 .release();
721 shorthandEntries->addItem(WTFMove(entry));
722 }
723 }
724 }
725 }
726
727 // Default "status" == "style".
728 if (status != Inspector::Protocol::CSS::CSSPropertyStatus::Style)
729 property->setStatus(status);
730 }
731
732 return Inspector::Protocol::CSS::CSSStyle::create()
733 .setCssProperties(WTFMove(propertiesObject))
734 .setShorthandEntries(WTFMove(shorthandEntries))
735 .release();
736}
737
738RefPtr<CSSRuleSourceData> InspectorStyle::extractSourceData() const
739{
740 if (!m_parentStyleSheet || !m_parentStyleSheet->ensureParsedDataReady())
741 return nullptr;
742 return m_parentStyleSheet->ruleSourceDataFor(m_style.ptr());
743}
744
745ExceptionOr<void> InspectorStyle::setText(const String& text)
746{
747 return m_parentStyleSheet->setStyleText(m_style.ptr(), text);
748}
749
750String InspectorStyle::shorthandValue(const String& shorthandProperty) const
751{
752 String value = m_style->getPropertyValue(shorthandProperty);
753 if (!value.isEmpty())
754 return value;
755 StringBuilder builder;
756 for (unsigned i = 0; i < m_style->length(); ++i) {
757 String individualProperty = m_style->item(i);
758 if (m_style->getPropertyShorthand(individualProperty) != shorthandProperty)
759 continue;
760 if (m_style->isPropertyImplicit(individualProperty))
761 continue;
762 String individualValue = m_style->getPropertyValue(individualProperty);
763 if (individualValue == "initial")
764 continue;
765 if (!builder.isEmpty())
766 builder.append(' ');
767 builder.append(individualValue);
768 }
769 return builder.toString();
770}
771
772String InspectorStyle::shorthandPriority(const String& shorthandProperty) const
773{
774 String priority = m_style->getPropertyPriority(shorthandProperty);
775 if (priority.isEmpty()) {
776 for (unsigned i = 0; i < m_style->length(); ++i) {
777 String individualProperty = m_style->item(i);
778 if (m_style->getPropertyShorthand(individualProperty) != shorthandProperty)
779 continue;
780 priority = m_style->getPropertyPriority(individualProperty);
781 break;
782 }
783 }
784 return priority;
785}
786
787Vector<String> InspectorStyle::longhandProperties(const String& shorthandProperty) const
788{
789 Vector<String> properties;
790 HashSet<String> foundProperties;
791 for (unsigned i = 0; i < m_style->length(); ++i) {
792 String individualProperty = m_style->item(i);
793 if (foundProperties.contains(individualProperty) || m_style->getPropertyShorthand(individualProperty) != shorthandProperty)
794 continue;
795
796 foundProperties.add(individualProperty);
797 properties.append(individualProperty);
798 }
799 return properties;
800}
801
802Ref<InspectorStyleSheet> InspectorStyleSheet::create(InspectorPageAgent* pageAgent, const String& id, RefPtr<CSSStyleSheet>&& pageStyleSheet, Inspector::Protocol::CSS::StyleSheetOrigin origin, const String& documentURL, Listener* listener)
803{
804 return adoptRef(*new InspectorStyleSheet(pageAgent, id, WTFMove(pageStyleSheet), origin, documentURL, listener));
805}
806
807String InspectorStyleSheet::styleSheetURL(CSSStyleSheet* pageStyleSheet)
808{
809 if (pageStyleSheet && !pageStyleSheet->contents().baseURL().isEmpty())
810 return pageStyleSheet->contents().baseURL().string();
811 return emptyString();
812}
813
814InspectorStyleSheet::InspectorStyleSheet(InspectorPageAgent* pageAgent, const String& id, RefPtr<CSSStyleSheet>&& pageStyleSheet, Inspector::Protocol::CSS::StyleSheetOrigin origin, const String& documentURL, Listener* listener)
815 : m_pageAgent(pageAgent)
816 , m_id(id)
817 , m_pageStyleSheet(WTFMove(pageStyleSheet))
818 , m_origin(origin)
819 , m_documentURL(documentURL)
820 , m_listener(listener)
821{
822 m_parsedStyleSheet = new ParsedStyleSheet();
823}
824
825InspectorStyleSheet::~InspectorStyleSheet()
826{
827 delete m_parsedStyleSheet;
828}
829
830String InspectorStyleSheet::finalURL() const
831{
832 String url = styleSheetURL(m_pageStyleSheet.get());
833 return url.isEmpty() ? m_documentURL : url;
834}
835
836void InspectorStyleSheet::reparseStyleSheet(const String& text)
837{
838 {
839 // Have a separate scope for clearRules() (bug 95324).
840 CSSStyleSheet::RuleMutationScope mutationScope(m_pageStyleSheet.get());
841 m_pageStyleSheet->contents().clearRules();
842 }
843 {
844 CSSStyleSheet::RuleMutationScope mutationScope(m_pageStyleSheet.get());
845 m_pageStyleSheet->contents().parseString(text);
846 m_pageStyleSheet->clearChildRuleCSSOMWrappers();
847 fireStyleSheetChanged();
848 }
849
850 // We just wiped the entire contents of the stylesheet. Clear the mutation flag.
851 m_pageStyleSheet->clearHadRulesMutation();
852}
853
854ExceptionOr<void> InspectorStyleSheet::setText(const String& text)
855{
856 if (!m_pageStyleSheet)
857 return Exception { NotSupportedError };
858
859 m_parsedStyleSheet->setText(text);
860 m_flatRules.clear();
861
862 return { };
863}
864
865ExceptionOr<String> InspectorStyleSheet::ruleSelector(const InspectorCSSId& id)
866{
867 CSSStyleRule* rule = ruleForId(id);
868 if (!rule)
869 return Exception { NotFoundError };
870 return rule->selectorText();
871}
872
873static bool isValidSelectorListString(const String& selector, Document* document)
874{
875 CSSSelectorList selectorList;
876 CSSParser parser(parserContextForDocument(document));
877 parser.parseSelector(selector, selectorList);
878 return selectorList.isValid();
879}
880
881ExceptionOr<void> InspectorStyleSheet::setRuleSelector(const InspectorCSSId& id, const String& selector)
882{
883 if (!m_pageStyleSheet)
884 return Exception { NotSupportedError };
885
886 // If the selector is invalid, do not proceed any further.
887 if (!isValidSelectorListString(selector, m_pageStyleSheet->ownerDocument()))
888 return Exception { SyntaxError };
889
890 CSSStyleRule* rule = ruleForId(id);
891 if (!rule)
892 return Exception { NotFoundError };
893
894 CSSStyleSheet* styleSheet = rule->parentStyleSheet();
895 if (!styleSheet || !ensureParsedDataReady())
896 return Exception { NotFoundError };
897
898 // If the stylesheet is already mutated at this point, that must mean that our data has been modified
899 // elsewhere. This should never happen as ensureParsedDataReady would return false in that case.
900 ASSERT(!styleSheetMutated());
901
902 rule->setSelectorText(selector);
903 auto sourceData = ruleSourceDataFor(&rule->style());
904 if (!sourceData)
905 return Exception { NotFoundError };
906
907 String sheetText = m_parsedStyleSheet->text();
908 sheetText.replace(sourceData->ruleHeaderRange.start, sourceData->ruleHeaderRange.length(), selector);
909 m_parsedStyleSheet->setText(sheetText);
910 m_pageStyleSheet->clearHadRulesMutation();
911 fireStyleSheetChanged();
912 return { };
913}
914
915ExceptionOr<CSSStyleRule*> InspectorStyleSheet::addRule(const String& selector)
916{
917 if (!m_pageStyleSheet)
918 return Exception { NotSupportedError };
919
920 if (!isValidSelectorListString(selector, m_pageStyleSheet->ownerDocument()))
921 return Exception { SyntaxError };
922
923 auto text = this->text();
924 if (text.hasException())
925 return text.releaseException();
926
927 auto addRuleResult = m_pageStyleSheet->addRule(selector, emptyString(), WTF::nullopt);
928 if (addRuleResult.hasException())
929 return addRuleResult.releaseException();
930
931 StringBuilder styleSheetText;
932 styleSheetText.append(text.releaseReturnValue());
933
934 if (!styleSheetText.isEmpty())
935 styleSheetText.append('\n');
936
937 styleSheetText.append(selector);
938 styleSheetText.appendLiteral(" {}");
939
940 // Using setText() as this operation changes the stylesheet rule set.
941 setText(styleSheetText.toString());
942
943 // Inspector Style Sheets are always treated as though their parsed data is ready.
944 if (m_origin == Inspector::Protocol::CSS::StyleSheetOrigin::Inspector)
945 fireStyleSheetChanged();
946 else
947 reparseStyleSheet(styleSheetText.toString());
948
949 ASSERT(m_pageStyleSheet->length());
950 unsigned lastRuleIndex = m_pageStyleSheet->length() - 1;
951 CSSRule* rule = m_pageStyleSheet->item(lastRuleIndex);
952 ASSERT(rule);
953
954 CSSStyleRule* styleRule = InspectorCSSAgent::asCSSStyleRule(*rule);
955 if (!styleRule) {
956 // What we just added has to be a CSSStyleRule - we cannot handle other types of rules yet.
957 // If it is not a style rule, pretend we never touched the stylesheet.
958 m_pageStyleSheet->deleteRule(lastRuleIndex);
959 return Exception { SyntaxError };
960 }
961
962 return styleRule;
963}
964
965ExceptionOr<void> InspectorStyleSheet::deleteRule(const InspectorCSSId& id)
966{
967 if (!m_pageStyleSheet)
968 return Exception { NotSupportedError };
969
970 RefPtr<CSSStyleRule> rule = ruleForId(id);
971 if (!rule)
972 return Exception { NotFoundError };
973 CSSStyleSheet* styleSheet = rule->parentStyleSheet();
974 if (!styleSheet || !ensureParsedDataReady())
975 return Exception { NotFoundError };
976
977 auto sourceData = ruleSourceDataFor(&rule->style());
978 if (!sourceData)
979 return Exception { NotFoundError };
980
981 auto deleteRuleResult = styleSheet->deleteRule(id.ordinal());
982 if (deleteRuleResult.hasException())
983 return deleteRuleResult.releaseException();
984
985 // |rule| MAY NOT be addressed after this!
986
987 String sheetText = m_parsedStyleSheet->text();
988 sheetText.remove(sourceData->ruleHeaderRange.start, sourceData->ruleBodyRange.end - sourceData->ruleHeaderRange.start + 1);
989 setText(sheetText);
990 fireStyleSheetChanged();
991 return { };
992}
993
994CSSStyleRule* InspectorStyleSheet::ruleForId(const InspectorCSSId& id) const
995{
996 if (!m_pageStyleSheet)
997 return nullptr;
998
999 ASSERT(!id.isEmpty());
1000 ensureFlatRules();
1001 return id.ordinal() >= m_flatRules.size() ? nullptr : m_flatRules.at(id.ordinal()).get();
1002}
1003
1004RefPtr<Inspector::Protocol::CSS::CSSStyleSheetBody> InspectorStyleSheet::buildObjectForStyleSheet()
1005{
1006 CSSStyleSheet* styleSheet = pageStyleSheet();
1007 if (!styleSheet)
1008 return nullptr;
1009
1010 RefPtr<CSSRuleList> cssRuleList = asCSSRuleList(styleSheet);
1011
1012 auto result = Inspector::Protocol::CSS::CSSStyleSheetBody::create()
1013 .setStyleSheetId(id())
1014 .setRules(buildArrayForRuleList(cssRuleList.get()))
1015 .release();
1016
1017 auto styleSheetText = text();
1018 if (!styleSheetText.hasException())
1019 result->setText(styleSheetText.releaseReturnValue());
1020
1021 return result;
1022}
1023
1024RefPtr<Inspector::Protocol::CSS::CSSStyleSheetHeader> InspectorStyleSheet::buildObjectForStyleSheetInfo()
1025{
1026 CSSStyleSheet* styleSheet = pageStyleSheet();
1027 if (!styleSheet)
1028 return nullptr;
1029
1030 Document* document = styleSheet->ownerDocument();
1031 Frame* frame = document ? document->frame() : nullptr;
1032 return Inspector::Protocol::CSS::CSSStyleSheetHeader::create()
1033 .setStyleSheetId(id())
1034 .setOrigin(m_origin)
1035 .setDisabled(styleSheet->disabled())
1036 .setSourceURL(finalURL())
1037 .setTitle(styleSheet->title())
1038 .setFrameId(m_pageAgent->frameId(frame))
1039 .setIsInline(styleSheet->isInline() && styleSheet->startPosition() != TextPosition())
1040 .setStartLine(styleSheet->startPosition().m_line.zeroBasedInt())
1041 .setStartColumn(styleSheet->startPosition().m_column.zeroBasedInt())
1042 .release();
1043}
1044
1045static bool hasDynamicSpecificity(const CSSSelector& simpleSelector)
1046{
1047 // It is possible that these can have a static specificity if each selector in the list has
1048 // equal specificity, but lets always report that they can be dynamic.
1049 for (const CSSSelector* selector = &simpleSelector; selector; selector = selector->tagHistory()) {
1050 if (selector->match() == CSSSelector::PseudoClass) {
1051 CSSSelector::PseudoClassType pseudoClassType = selector->pseudoClassType();
1052 if (pseudoClassType == CSSSelector::PseudoClassMatches)
1053 return true;
1054 if (pseudoClassType == CSSSelector::PseudoClassNthChild || pseudoClassType == CSSSelector::PseudoClassNthLastChild) {
1055 if (selector->selectorList())
1056 return true;
1057 return false;
1058 }
1059 }
1060 }
1061
1062 return false;
1063}
1064
1065static Ref<Inspector::Protocol::CSS::CSSSelector> buildObjectForSelectorHelper(const String& selectorText, const CSSSelector& selector, Element* element)
1066{
1067 auto inspectorSelector = Inspector::Protocol::CSS::CSSSelector::create()
1068 .setText(selectorText)
1069 .release();
1070
1071 if (element) {
1072 bool dynamic = hasDynamicSpecificity(selector);
1073 if (dynamic)
1074 inspectorSelector->setDynamic(true);
1075
1076 SelectorChecker::CheckingContext context(SelectorChecker::Mode::CollectingRules);
1077 SelectorChecker selectorChecker(element->document());
1078
1079 unsigned specificity;
1080 bool okay = selectorChecker.match(selector, *element, context, specificity);
1081 if (!okay)
1082 specificity = selector.staticSpecificity(okay);
1083
1084 if (okay) {
1085 auto tuple = JSON::ArrayOf<int>::create();
1086 tuple->addItem(static_cast<int>((specificity & CSSSelector::idMask) >> 16));
1087 tuple->addItem(static_cast<int>((specificity & CSSSelector::classMask) >> 8));
1088 tuple->addItem(static_cast<int>(specificity & CSSSelector::elementMask));
1089 inspectorSelector->setSpecificity(WTFMove(tuple));
1090 }
1091 }
1092
1093 return inspectorSelector;
1094}
1095
1096static Ref<JSON::ArrayOf<Inspector::Protocol::CSS::CSSSelector>> selectorsFromSource(const CSSRuleSourceData* sourceData, const String& sheetText, const CSSSelectorList& selectorList, Element* element)
1097{
1098 static NeverDestroyed<JSC::Yarr::RegularExpression> comment("/\\*[^]*?\\*/", JSC::Yarr::TextCaseSensitive, JSC::Yarr::MultilineEnabled);
1099
1100 auto result = JSON::ArrayOf<Inspector::Protocol::CSS::CSSSelector>::create();
1101 const CSSSelector* selector = selectorList.first();
1102 for (auto& range : sourceData->selectorRanges) {
1103 // If we don't have a selector, that means the SourceData for this CSSStyleSheet
1104 // no longer matches up with the actual rules in the CSSStyleSheet.
1105 ASSERT(selector);
1106 if (!selector)
1107 break;
1108
1109 String selectorText = sheetText.substring(range.start, range.length());
1110
1111 // We don't want to see any comments in the selector components, only the meaningful parts.
1112 replace(selectorText, comment, String());
1113 result->addItem(buildObjectForSelectorHelper(selectorText.stripWhiteSpace(), *selector, element));
1114
1115 selector = CSSSelectorList::next(selector);
1116 }
1117 return result;
1118}
1119
1120Ref<Inspector::Protocol::CSS::CSSSelector> InspectorStyleSheet::buildObjectForSelector(const CSSSelector* selector, Element* element)
1121{
1122 return buildObjectForSelectorHelper(selector->selectorText(), *selector, element);
1123}
1124
1125Ref<Inspector::Protocol::CSS::SelectorList> InspectorStyleSheet::buildObjectForSelectorList(CSSStyleRule* rule, Element* element, int& endingLine)
1126{
1127 RefPtr<CSSRuleSourceData> sourceData;
1128 if (ensureParsedDataReady())
1129 sourceData = ruleSourceDataFor(&rule->style());
1130 RefPtr<JSON::ArrayOf<Inspector::Protocol::CSS::CSSSelector>> selectors;
1131
1132 // This intentionally does not rely on the source data to avoid catching the trailing comments (before the declaration starting '{').
1133 String selectorText = rule->selectorText();
1134
1135 if (sourceData)
1136 selectors = selectorsFromSource(sourceData.get(), m_parsedStyleSheet->text(), rule->styleRule().selectorList(), element);
1137 else {
1138 selectors = JSON::ArrayOf<Inspector::Protocol::CSS::CSSSelector>::create();
1139 const CSSSelectorList& selectorList = rule->styleRule().selectorList();
1140 for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector))
1141 selectors->addItem(buildObjectForSelector(selector, element));
1142 }
1143 auto result = Inspector::Protocol::CSS::SelectorList::create()
1144 .setSelectors(WTFMove(selectors))
1145 .setText(selectorText)
1146 .release();
1147 if (sourceData)
1148 result->setRange(buildSourceRangeObject(sourceData->ruleHeaderRange, lineEndings(), &endingLine));
1149 return result;
1150}
1151
1152RefPtr<Inspector::Protocol::CSS::CSSRule> InspectorStyleSheet::buildObjectForRule(CSSStyleRule* rule, Element* element)
1153{
1154 CSSStyleSheet* styleSheet = pageStyleSheet();
1155 if (!styleSheet)
1156 return nullptr;
1157
1158 int endingLine = 0;
1159 auto result = Inspector::Protocol::CSS::CSSRule::create()
1160 .setSelectorList(buildObjectForSelectorList(rule, element, endingLine))
1161 .setSourceLine(endingLine)
1162 .setOrigin(m_origin)
1163 .setStyle(buildObjectForStyle(&rule->style()))
1164 .release();
1165
1166 // "sourceURL" is present only for regular rules, otherwise "origin" should be used in the frontend.
1167 if (m_origin == Inspector::Protocol::CSS::StyleSheetOrigin::Regular)
1168 result->setSourceURL(finalURL());
1169
1170 if (canBind()) {
1171 InspectorCSSId id(ruleId(rule));
1172 if (!id.isEmpty())
1173 result->setRuleId(id.asProtocolValue<Inspector::Protocol::CSS::CSSRuleId>());
1174 }
1175
1176 auto mediaArray = ArrayOf<Inspector::Protocol::CSS::CSSMedia>::create();
1177
1178 fillMediaListChain(rule, mediaArray.get());
1179 if (mediaArray->length())
1180 result->setMedia(WTFMove(mediaArray));
1181
1182 return result;
1183}
1184
1185RefPtr<Inspector::Protocol::CSS::CSSStyle> InspectorStyleSheet::buildObjectForStyle(CSSStyleDeclaration* style)
1186{
1187 RefPtr<CSSRuleSourceData> sourceData;
1188 if (ensureParsedDataReady())
1189 sourceData = ruleSourceDataFor(style);
1190
1191 InspectorCSSId id = ruleOrStyleId(style);
1192 if (id.isEmpty()) {
1193 return Inspector::Protocol::CSS::CSSStyle::create()
1194 .setCssProperties(ArrayOf<Inspector::Protocol::CSS::CSSProperty>::create())
1195 .setShorthandEntries(ArrayOf<Inspector::Protocol::CSS::ShorthandEntry>::create())
1196 .release();
1197 }
1198 RefPtr<InspectorStyle> inspectorStyle = inspectorStyleForId(id);
1199 RefPtr<Inspector::Protocol::CSS::CSSStyle> result = inspectorStyle->buildObjectForStyle();
1200
1201 // Style text cannot be retrieved without stylesheet, so set cssText here.
1202 if (sourceData) {
1203 auto sheetText = text();
1204 if (!sheetText.hasException()) {
1205 auto& bodyRange = sourceData->ruleBodyRange;
1206 result->setCssText(sheetText.releaseReturnValue().substring(bodyRange.start, bodyRange.end - bodyRange.start));
1207 }
1208 }
1209
1210 return result;
1211}
1212
1213ExceptionOr<void> InspectorStyleSheet::setStyleText(const InspectorCSSId& id, const String& text, String* oldText)
1214{
1215 auto inspectorStyle = inspectorStyleForId(id);
1216 if (!inspectorStyle)
1217 return Exception { NotFoundError };
1218
1219 if (oldText) {
1220 auto result = inspectorStyle->text();
1221 if (result.hasException())
1222 return result.releaseException();
1223 *oldText = result.releaseReturnValue();
1224 }
1225
1226 auto result = inspectorStyle->setText(text);
1227 if (!result.hasException())
1228 fireStyleSheetChanged();
1229 return result;
1230}
1231
1232ExceptionOr<String> InspectorStyleSheet::text() const
1233{
1234 if (!ensureText())
1235 return Exception { NotFoundError };
1236 return String { m_parsedStyleSheet->text() };
1237}
1238
1239CSSStyleDeclaration* InspectorStyleSheet::styleForId(const InspectorCSSId& id) const
1240{
1241 CSSStyleRule* rule = ruleForId(id);
1242 if (!rule)
1243 return nullptr;
1244
1245 return &rule->style();
1246}
1247
1248void InspectorStyleSheet::fireStyleSheetChanged()
1249{
1250 if (m_listener)
1251 m_listener->styleSheetChanged(this);
1252}
1253
1254RefPtr<InspectorStyle> InspectorStyleSheet::inspectorStyleForId(const InspectorCSSId& id)
1255{
1256 CSSStyleDeclaration* style = styleForId(id);
1257 if (!style)
1258 return nullptr;
1259
1260 return InspectorStyle::create(id, *style, this);
1261}
1262
1263InspectorCSSId InspectorStyleSheet::ruleOrStyleId(CSSStyleDeclaration* style) const
1264{
1265 unsigned index = ruleIndexByStyle(style);
1266 if (index != UINT_MAX)
1267 return InspectorCSSId(id(), index);
1268 return InspectorCSSId();
1269}
1270
1271Document* InspectorStyleSheet::ownerDocument() const
1272{
1273 return m_pageStyleSheet->ownerDocument();
1274}
1275
1276RefPtr<CSSRuleSourceData> InspectorStyleSheet::ruleSourceDataFor(CSSStyleDeclaration* style) const
1277{
1278 return m_parsedStyleSheet->ruleSourceDataAt(ruleIndexByStyle(style));
1279}
1280
1281Vector<size_t> InspectorStyleSheet::lineEndings() const
1282{
1283 if (!m_parsedStyleSheet->hasText())
1284 return { };
1285 return ContentSearchUtilities::lineEndings(m_parsedStyleSheet->text());
1286}
1287
1288unsigned InspectorStyleSheet::ruleIndexByStyle(CSSStyleDeclaration* pageStyle) const
1289{
1290 ensureFlatRules();
1291 unsigned index = 0;
1292 for (auto& rule : m_flatRules) {
1293 if (&rule->style() == pageStyle)
1294 return index;
1295
1296 ++index;
1297 }
1298 return UINT_MAX;
1299}
1300
1301bool InspectorStyleSheet::styleSheetMutated() const
1302{
1303 return m_pageStyleSheet && m_pageStyleSheet->hadRulesMutation();
1304}
1305
1306bool InspectorStyleSheet::ensureParsedDataReady()
1307{
1308 bool allowParsedData = m_origin == Inspector::Protocol::CSS::StyleSheetOrigin::Inspector || !styleSheetMutated();
1309 return allowParsedData && ensureText() && ensureSourceData();
1310}
1311
1312bool InspectorStyleSheet::ensureText() const
1313{
1314 if (!m_parsedStyleSheet)
1315 return false;
1316 if (m_parsedStyleSheet->hasText())
1317 return true;
1318
1319 String text;
1320 bool success = originalStyleSheetText(&text);
1321 if (success)
1322 m_parsedStyleSheet->setText(text);
1323 // No need to clear m_flatRules here - it's empty.
1324
1325 return success;
1326}
1327
1328bool InspectorStyleSheet::ensureSourceData()
1329{
1330 if (m_parsedStyleSheet->hasSourceData())
1331 return true;
1332
1333 if (!m_parsedStyleSheet->hasText())
1334 return false;
1335
1336 auto newStyleSheet = StyleSheetContents::create();
1337 auto ruleSourceDataResult = std::make_unique<RuleSourceDataList>();
1338
1339 CSSParserContext context(parserContextForDocument(m_pageStyleSheet->ownerDocument()));
1340 StyleSheetHandler handler(m_parsedStyleSheet->text(), m_pageStyleSheet->ownerDocument(), ruleSourceDataResult.get());
1341 CSSParser::parseSheetForInspector(context, newStyleSheet.ptr(), m_parsedStyleSheet->text(), handler);
1342 m_parsedStyleSheet->setSourceData(WTFMove(ruleSourceDataResult));
1343 return m_parsedStyleSheet->hasSourceData();
1344}
1345
1346void InspectorStyleSheet::ensureFlatRules() const
1347{
1348 // We are fine with redoing this for empty stylesheets as this will run fast.
1349 if (m_flatRules.isEmpty())
1350 collectFlatRules(asCSSRuleList(pageStyleSheet()), &m_flatRules);
1351}
1352
1353ExceptionOr<void> InspectorStyleSheet::setStyleText(CSSStyleDeclaration* style, const String& text)
1354{
1355 if (!m_pageStyleSheet)
1356 return Exception { NotFoundError };
1357 if (!ensureParsedDataReady())
1358 return Exception { NotFoundError };
1359
1360 String patchedStyleSheetText;
1361 bool success = styleSheetTextWithChangedStyle(style, text, &patchedStyleSheetText);
1362 if (!success)
1363 return Exception { NotFoundError };
1364
1365 InspectorCSSId id = ruleOrStyleId(style);
1366 if (id.isEmpty())
1367 return Exception { NotFoundError };
1368
1369 auto setCssTextResult = style->setCssText(text);
1370 if (setCssTextResult.hasException())
1371 return setCssTextResult.releaseException();
1372
1373 m_parsedStyleSheet->setText(patchedStyleSheetText);
1374 return { };
1375}
1376
1377bool InspectorStyleSheet::styleSheetTextWithChangedStyle(CSSStyleDeclaration* style, const String& newStyleText, String* result)
1378{
1379 if (!style)
1380 return false;
1381
1382 if (!ensureParsedDataReady())
1383 return false;
1384
1385 RefPtr<CSSRuleSourceData> sourceData = ruleSourceDataFor(style);
1386 unsigned bodyStart = sourceData->ruleBodyRange.start;
1387 unsigned bodyEnd = sourceData->ruleBodyRange.end;
1388 ASSERT(bodyStart <= bodyEnd);
1389
1390 String text = m_parsedStyleSheet->text();
1391 ASSERT_WITH_SECURITY_IMPLICATION(bodyEnd <= text.length()); // bodyEnd is exclusive
1392
1393 text.replace(bodyStart, bodyEnd - bodyStart, newStyleText);
1394 *result = text;
1395 return true;
1396}
1397
1398InspectorCSSId InspectorStyleSheet::ruleId(CSSStyleRule* rule) const
1399{
1400 return ruleOrStyleId(&rule->style());
1401}
1402
1403bool InspectorStyleSheet::originalStyleSheetText(String* result) const
1404{
1405 bool success = inlineStyleSheetText(result);
1406 if (!success)
1407 success = resourceStyleSheetText(result);
1408 return success;
1409}
1410
1411bool InspectorStyleSheet::resourceStyleSheetText(String* result) const
1412{
1413 if (m_origin == Inspector::Protocol::CSS::StyleSheetOrigin::User || m_origin == Inspector::Protocol::CSS::StyleSheetOrigin::UserAgent)
1414 return false;
1415
1416 if (!m_pageStyleSheet || !ownerDocument() || !ownerDocument()->frame())
1417 return false;
1418
1419 String error;
1420 bool base64Encoded;
1421 InspectorPageAgent::resourceContent(error, ownerDocument()->frame(), URL({ }, m_pageStyleSheet->href()), result, &base64Encoded);
1422 return error.isEmpty() && !base64Encoded;
1423}
1424
1425bool InspectorStyleSheet::inlineStyleSheetText(String* result) const
1426{
1427 if (!m_pageStyleSheet)
1428 return false;
1429
1430 Node* ownerNode = m_pageStyleSheet->ownerNode();
1431 if (!is<Element>(ownerNode))
1432 return false;
1433 Element& ownerElement = downcast<Element>(*ownerNode);
1434
1435 if (!is<HTMLStyleElement>(ownerElement) && !is<SVGStyleElement>(ownerElement))
1436 return false;
1437 *result = ownerElement.textContent();
1438 return true;
1439}
1440
1441Ref<JSON::ArrayOf<Inspector::Protocol::CSS::CSSRule>> InspectorStyleSheet::buildArrayForRuleList(CSSRuleList* ruleList)
1442{
1443 auto result = JSON::ArrayOf<Inspector::Protocol::CSS::CSSRule>::create();
1444 if (!ruleList)
1445 return result;
1446
1447 RefPtr<CSSRuleList> refRuleList = ruleList;
1448 CSSStyleRuleVector rules;
1449 collectFlatRules(WTFMove(refRuleList), &rules);
1450
1451 for (auto& rule : rules)
1452 result->addItem(buildObjectForRule(rule.get(), nullptr));
1453
1454 return result;
1455}
1456
1457void InspectorStyleSheet::collectFlatRules(RefPtr<CSSRuleList>&& ruleList, CSSStyleRuleVector* result)
1458{
1459 if (!ruleList)
1460 return;
1461
1462 for (unsigned i = 0, size = ruleList->length(); i < size; ++i) {
1463 CSSRule* rule = ruleList->item(i);
1464 CSSStyleRule* styleRule = InspectorCSSAgent::asCSSStyleRule(*rule);
1465 if (styleRule)
1466 result->append(styleRule);
1467 else {
1468 RefPtr<CSSRuleList> childRuleList = asCSSRuleList(rule);
1469 if (childRuleList)
1470 collectFlatRules(WTFMove(childRuleList), result);
1471 }
1472 }
1473}
1474
1475Ref<InspectorStyleSheetForInlineStyle> InspectorStyleSheetForInlineStyle::create(InspectorPageAgent* pageAgent, const String& id, Ref<StyledElement>&& element, Inspector::Protocol::CSS::StyleSheetOrigin origin, Listener* listener)
1476{
1477 return adoptRef(*new InspectorStyleSheetForInlineStyle(pageAgent, id, WTFMove(element), origin, listener));
1478}
1479
1480InspectorStyleSheetForInlineStyle::InspectorStyleSheetForInlineStyle(InspectorPageAgent* pageAgent, const String& id, Ref<StyledElement>&& element, Inspector::Protocol::CSS::StyleSheetOrigin origin, Listener* listener)
1481 : InspectorStyleSheet(pageAgent, id, nullptr, origin, String(), listener)
1482 , m_element(WTFMove(element))
1483 , m_ruleSourceData(nullptr)
1484 , m_isStyleTextValid(false)
1485{
1486 m_inspectorStyle = InspectorStyle::create(InspectorCSSId(id, 0), inlineStyle(), this);
1487 m_styleText = m_element->getAttribute("style").string();
1488}
1489
1490void InspectorStyleSheetForInlineStyle::didModifyElementAttribute()
1491{
1492 m_isStyleTextValid = false;
1493 if (&m_element->cssomStyle() != &m_inspectorStyle->cssStyle())
1494 m_inspectorStyle = InspectorStyle::create(InspectorCSSId(id(), 0), inlineStyle(), this);
1495 m_ruleSourceData = nullptr;
1496}
1497
1498ExceptionOr<String> InspectorStyleSheetForInlineStyle::text() const
1499{
1500 if (!m_isStyleTextValid) {
1501 m_styleText = elementStyleText();
1502 m_isStyleTextValid = true;
1503 }
1504 return String { m_styleText };
1505}
1506
1507ExceptionOr<void> InspectorStyleSheetForInlineStyle::setStyleText(CSSStyleDeclaration* style, const String& text)
1508{
1509 ASSERT_UNUSED(style, style == &inlineStyle());
1510
1511 {
1512 InspectorCSSAgent::InlineStyleOverrideScope overrideScope(m_element->document());
1513 m_element->setAttribute(HTMLNames::styleAttr, text);
1514 }
1515
1516 m_styleText = text;
1517 m_isStyleTextValid = true;
1518 m_ruleSourceData = nullptr;
1519
1520 return { };
1521}
1522
1523Vector<size_t> InspectorStyleSheetForInlineStyle::lineEndings() const
1524{
1525 return ContentSearchUtilities::lineEndings(elementStyleText());
1526}
1527
1528Document* InspectorStyleSheetForInlineStyle::ownerDocument() const
1529{
1530 return &m_element->document();
1531}
1532
1533bool InspectorStyleSheetForInlineStyle::ensureParsedDataReady()
1534{
1535 // The "style" property value can get changed indirectly, e.g. via element.style.borderWidth = "2px".
1536 const String& currentStyleText = elementStyleText();
1537 if (m_styleText != currentStyleText) {
1538 m_ruleSourceData = nullptr;
1539 m_styleText = currentStyleText;
1540 m_isStyleTextValid = true;
1541 }
1542
1543 if (m_ruleSourceData)
1544 return true;
1545
1546 m_ruleSourceData = ruleSourceData();
1547 return true;
1548}
1549
1550RefPtr<InspectorStyle> InspectorStyleSheetForInlineStyle::inspectorStyleForId(const InspectorCSSId& id)
1551{
1552 ASSERT_UNUSED(id, !id.ordinal());
1553 return m_inspectorStyle.copyRef();
1554}
1555
1556CSSStyleDeclaration& InspectorStyleSheetForInlineStyle::inlineStyle() const
1557{
1558 return m_element->cssomStyle();
1559}
1560
1561const String& InspectorStyleSheetForInlineStyle::elementStyleText() const
1562{
1563 return m_element->getAttribute("style").string();
1564}
1565
1566Ref<CSSRuleSourceData> InspectorStyleSheetForInlineStyle::ruleSourceData() const
1567{
1568 if (m_styleText.isEmpty()) {
1569 auto result = CSSRuleSourceData::create(StyleRule::Style);
1570 result->ruleBodyRange.start = 0;
1571 result->ruleBodyRange.end = 0;
1572 return result;
1573 }
1574
1575 CSSParserContext context(parserContextForDocument(&m_element->document()));
1576 RuleSourceDataList ruleSourceDataResult;
1577 StyleSheetHandler handler(m_styleText, &m_element->document(), &ruleSourceDataResult);
1578 CSSParser::parseDeclarationForInspector(context, m_styleText, handler);
1579 return WTFMove(ruleSourceDataResult.first());
1580}
1581
1582} // namespace WebCore
1583