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 | |
65 | using JSON::ArrayOf; |
66 | using WebCore::RuleSourceDataList; |
67 | using WebCore::CSSRuleSourceData; |
68 | |
69 | class ParsedStyleSheet { |
70 | WTF_MAKE_FAST_ALLOCATED; |
71 | public: |
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 | |
82 | private: |
83 | |
84 | String m_text; |
85 | bool m_hasText; |
86 | std::unique_ptr<RuleSourceDataList> m_sourceData; |
87 | }; |
88 | |
89 | ParsedStyleSheet::ParsedStyleSheet() |
90 | : m_hasText(false) |
91 | { |
92 | } |
93 | |
94 | void ParsedStyleSheet::setText(const String& text) |
95 | { |
96 | m_hasText = true; |
97 | m_text = text; |
98 | setSourceData(nullptr); |
99 | } |
100 | |
101 | static 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 | |
113 | void 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 | |
128 | WebCore::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 | |
137 | namespace WebCore { |
138 | |
139 | using namespace Inspector; |
140 | |
141 | static CSSParserContext parserContextForDocument(Document* document) |
142 | { |
143 | return document ? CSSParserContext(*document) : strictCSSParserContext(); |
144 | } |
145 | |
146 | class StyleSheetHandler : public CSSParserObserver { |
147 | public: |
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 | |
156 | private: |
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 | |
177 | void 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 | |
189 | template <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 | |
203 | void 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 | |
213 | void StyleSheetHandler::observeSelector(unsigned startOffset, unsigned endOffset) |
214 | { |
215 | ASSERT(m_currentRuleDataStack.size()); |
216 | m_currentRuleDataStack.last()->selectorRanges.append(SourceRange(startOffset, endOffset)); |
217 | } |
218 | |
219 | void 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 | |
231 | void 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 | |
243 | Ref<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 | |
252 | template <typename CharacterType> |
253 | static 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 | |
302 | void 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 | |
315 | void 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 | |
342 | void 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 = 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>& = 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 | |
382 | enum MediaListSource { |
383 | MediaListSourceLinkedSheet, |
384 | MediaListSourceInlineSheet, |
385 | MediaListSourceMediaRule, |
386 | MediaListSourceImportRule |
387 | }; |
388 | |
389 | static 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 | |
408 | static 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 | |
439 | static 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 | |
451 | static 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 | |
468 | static 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 | |
523 | Ref<InspectorStyle> InspectorStyle::create(const InspectorCSSId& styleId, Ref<CSSStyleDeclaration>&& style, InspectorStyleSheet* parentStyleSheet) |
524 | { |
525 | return adoptRef(*new InspectorStyle(styleId, WTFMove(style), parentStyleSheet)); |
526 | } |
527 | |
528 | InspectorStyle::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 | |
535 | InspectorStyle::~InspectorStyle() = default; |
536 | |
537 | RefPtr<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 | |
553 | Ref<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 | |
571 | ExceptionOr<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 | |
586 | static 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 | |
594 | void 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 | |
619 | Ref<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 | |
738 | RefPtr<CSSRuleSourceData> InspectorStyle::() const |
739 | { |
740 | if (!m_parentStyleSheet || !m_parentStyleSheet->ensureParsedDataReady()) |
741 | return nullptr; |
742 | return m_parentStyleSheet->ruleSourceDataFor(m_style.ptr()); |
743 | } |
744 | |
745 | ExceptionOr<void> InspectorStyle::setText(const String& text) |
746 | { |
747 | return m_parentStyleSheet->setStyleText(m_style.ptr(), text); |
748 | } |
749 | |
750 | String 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 | |
772 | String 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 | |
787 | Vector<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 | |
802 | Ref<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 | |
807 | String InspectorStyleSheet::styleSheetURL(CSSStyleSheet* pageStyleSheet) |
808 | { |
809 | if (pageStyleSheet && !pageStyleSheet->contents().baseURL().isEmpty()) |
810 | return pageStyleSheet->contents().baseURL().string(); |
811 | return emptyString(); |
812 | } |
813 | |
814 | InspectorStyleSheet::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 | |
825 | InspectorStyleSheet::~InspectorStyleSheet() |
826 | { |
827 | delete m_parsedStyleSheet; |
828 | } |
829 | |
830 | String InspectorStyleSheet::finalURL() const |
831 | { |
832 | String url = styleSheetURL(m_pageStyleSheet.get()); |
833 | return url.isEmpty() ? m_documentURL : url; |
834 | } |
835 | |
836 | void 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 | |
854 | ExceptionOr<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 | |
865 | ExceptionOr<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 | |
873 | static 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 | |
881 | ExceptionOr<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 | |
915 | ExceptionOr<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 | |
965 | ExceptionOr<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 | |
994 | CSSStyleRule* 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 | |
1004 | RefPtr<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 | |
1024 | RefPtr<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 | |
1045 | static 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 | |
1065 | static 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 | |
1096 | static 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> ("/\\*[^]*?\\*/" , 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 | |
1120 | Ref<Inspector::Protocol::CSS::CSSSelector> InspectorStyleSheet::buildObjectForSelector(const CSSSelector* selector, Element* element) |
1121 | { |
1122 | return buildObjectForSelectorHelper(selector->selectorText(), *selector, element); |
1123 | } |
1124 | |
1125 | Ref<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 | |
1152 | RefPtr<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 | |
1185 | RefPtr<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 | |
1213 | ExceptionOr<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 | |
1232 | ExceptionOr<String> InspectorStyleSheet::text() const |
1233 | { |
1234 | if (!ensureText()) |
1235 | return Exception { NotFoundError }; |
1236 | return String { m_parsedStyleSheet->text() }; |
1237 | } |
1238 | |
1239 | CSSStyleDeclaration* 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 | |
1248 | void InspectorStyleSheet::fireStyleSheetChanged() |
1249 | { |
1250 | if (m_listener) |
1251 | m_listener->styleSheetChanged(this); |
1252 | } |
1253 | |
1254 | RefPtr<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 | |
1263 | InspectorCSSId 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 | |
1271 | Document* InspectorStyleSheet::ownerDocument() const |
1272 | { |
1273 | return m_pageStyleSheet->ownerDocument(); |
1274 | } |
1275 | |
1276 | RefPtr<CSSRuleSourceData> InspectorStyleSheet::ruleSourceDataFor(CSSStyleDeclaration* style) const |
1277 | { |
1278 | return m_parsedStyleSheet->ruleSourceDataAt(ruleIndexByStyle(style)); |
1279 | } |
1280 | |
1281 | Vector<size_t> InspectorStyleSheet::lineEndings() const |
1282 | { |
1283 | if (!m_parsedStyleSheet->hasText()) |
1284 | return { }; |
1285 | return ContentSearchUtilities::lineEndings(m_parsedStyleSheet->text()); |
1286 | } |
1287 | |
1288 | unsigned 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 | |
1301 | bool InspectorStyleSheet::styleSheetMutated() const |
1302 | { |
1303 | return m_pageStyleSheet && m_pageStyleSheet->hadRulesMutation(); |
1304 | } |
1305 | |
1306 | bool InspectorStyleSheet::ensureParsedDataReady() |
1307 | { |
1308 | bool allowParsedData = m_origin == Inspector::Protocol::CSS::StyleSheetOrigin::Inspector || !styleSheetMutated(); |
1309 | return allowParsedData && ensureText() && ensureSourceData(); |
1310 | } |
1311 | |
1312 | bool 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 | |
1328 | bool 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 | |
1346 | void 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 | |
1353 | ExceptionOr<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 | |
1377 | bool 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 | |
1398 | InspectorCSSId InspectorStyleSheet::ruleId(CSSStyleRule* rule) const |
1399 | { |
1400 | return ruleOrStyleId(&rule->style()); |
1401 | } |
1402 | |
1403 | bool InspectorStyleSheet::originalStyleSheetText(String* result) const |
1404 | { |
1405 | bool success = inlineStyleSheetText(result); |
1406 | if (!success) |
1407 | success = resourceStyleSheetText(result); |
1408 | return success; |
1409 | } |
1410 | |
1411 | bool 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 | |
1425 | bool 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 | |
1441 | Ref<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 | |
1457 | void 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 | |
1475 | Ref<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 | |
1480 | InspectorStyleSheetForInlineStyle::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 | |
1490 | void 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 | |
1498 | ExceptionOr<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 | |
1507 | ExceptionOr<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 | |
1523 | Vector<size_t> InspectorStyleSheetForInlineStyle::lineEndings() const |
1524 | { |
1525 | return ContentSearchUtilities::lineEndings(elementStyleText()); |
1526 | } |
1527 | |
1528 | Document* InspectorStyleSheetForInlineStyle::ownerDocument() const |
1529 | { |
1530 | return &m_element->document(); |
1531 | } |
1532 | |
1533 | bool 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 | |
1550 | RefPtr<InspectorStyle> InspectorStyleSheetForInlineStyle::inspectorStyleForId(const InspectorCSSId& id) |
1551 | { |
1552 | ASSERT_UNUSED(id, !id.ordinal()); |
1553 | return m_inspectorStyle.copyRef(); |
1554 | } |
1555 | |
1556 | CSSStyleDeclaration& InspectorStyleSheetForInlineStyle::inlineStyle() const |
1557 | { |
1558 | return m_element->cssomStyle(); |
1559 | } |
1560 | |
1561 | const String& InspectorStyleSheetForInlineStyle::elementStyleText() const |
1562 | { |
1563 | return m_element->getAttribute("style" ).string(); |
1564 | } |
1565 | |
1566 | Ref<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 | |