| /* |
| * Copyright (C) 2010, Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/inspector/inspector_style_sheet.h" |
| |
| #include <algorithm> |
| #include "third_party/blink/renderer/bindings/core/v8/script_regexp.h" |
| #include "third_party/blink/renderer/core/css/css_import_rule.h" |
| #include "third_party/blink/renderer/core/css/css_keyframe_rule.h" |
| #include "third_party/blink/renderer/core/css/css_keyframes_rule.h" |
| #include "third_party/blink/renderer/core/css/css_media_rule.h" |
| #include "third_party/blink/renderer/core/css/css_property_names.h" |
| #include "third_party/blink/renderer/core/css/css_property_value_set.h" |
| #include "third_party/blink/renderer/core/css/css_rule_list.h" |
| #include "third_party/blink/renderer/core/css/css_style_rule.h" |
| #include "third_party/blink/renderer/core/css/css_style_sheet.h" |
| #include "third_party/blink/renderer/core/css/css_supports_rule.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser_observer.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/css/style_rule.h" |
| #include "third_party/blink/renderer/core/css/style_sheet_contents.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/html/html_style_element.h" |
| #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" |
| #include "third_party/blink/renderer/core/inspector/identifiers_factory.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_css_agent.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_network_agent.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_resource_container.h" |
| #include "third_party/blink/renderer/core/svg/svg_style_element.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/text_position.h" |
| |
| using blink::protocol::Array; |
| |
| namespace blink { |
| |
| namespace { |
| |
| static const CSSParserContext* ParserContextForDocument(Document* document) { |
| // Fallback to an insecure context parser if no document is present. |
| return document ? MakeGarbageCollected<CSSParserContext>(*document) |
| : StrictCSSParserContext(SecureContextMode::kInsecureContext); |
| } |
| |
| String FindMagicComment(const String& content, const String& name) { |
| DCHECK(name.Find("=") == kNotFound); |
| |
| wtf_size_t length = content.length(); |
| wtf_size_t name_length = name.length(); |
| const bool kMultiline = true; |
| |
| wtf_size_t pos = length; |
| wtf_size_t equal_sign_pos = 0; |
| wtf_size_t closing_comment_pos = 0; |
| while (true) { |
| pos = content.ReverseFind(name, pos); |
| if (pos == kNotFound) |
| return g_empty_string; |
| |
| // Check for a /\/[\/*][@#][ \t]/ regexp (length of 4) before found name. |
| if (pos < 4) |
| return g_empty_string; |
| pos -= 4; |
| if (content[pos] != '/') |
| continue; |
| if ((content[pos + 1] != '/' || kMultiline) && |
| (content[pos + 1] != '*' || !kMultiline)) |
| continue; |
| if (content[pos + 2] != '#' && content[pos + 2] != '@') |
| continue; |
| if (content[pos + 3] != ' ' && content[pos + 3] != '\t') |
| continue; |
| equal_sign_pos = pos + 4 + name_length; |
| if (equal_sign_pos < length && content[equal_sign_pos] != '=') |
| continue; |
| if (kMultiline) { |
| closing_comment_pos = content.Find("*/", equal_sign_pos + 1); |
| if (closing_comment_pos == kNotFound) |
| return g_empty_string; |
| } |
| |
| break; |
| } |
| |
| DCHECK(equal_sign_pos); |
| DCHECK(!kMultiline || closing_comment_pos); |
| wtf_size_t url_pos = equal_sign_pos + 1; |
| String match = kMultiline |
| ? content.Substring(url_pos, closing_comment_pos - url_pos) |
| : content.Substring(url_pos); |
| |
| wtf_size_t new_line = match.Find("\n"); |
| if (new_line != kNotFound) |
| match = match.Substring(0, new_line); |
| match = match.StripWhiteSpace(); |
| |
| String disallowed_chars("\"' \t"); |
| for (uint32_t i = 0; i < match.length(); ++i) { |
| if (disallowed_chars.find(match[i]) != kNotFound) |
| return g_empty_string; |
| } |
| |
| return match; |
| } |
| |
| void GetClassNamesFromRule(CSSStyleRule* rule, HashSet<String>& unique_names) { |
| const CSSSelectorList& selector_list = rule->GetStyleRule()->SelectorList(); |
| if (!selector_list.IsValid()) |
| return; |
| |
| for (const CSSSelector* sub_selector = selector_list.First(); sub_selector; |
| sub_selector = CSSSelectorList::Next(*sub_selector)) { |
| const CSSSelector* simple_selector = sub_selector; |
| while (simple_selector) { |
| if (simple_selector->Match() == CSSSelector::kClass) |
| unique_names.insert(simple_selector->Value()); |
| simple_selector = simple_selector->TagHistory(); |
| } |
| } |
| } |
| |
| class StyleSheetHandler final : public CSSParserObserver { |
| STACK_ALLOCATED(); |
| |
| public: |
| StyleSheetHandler(const String& parsed_text, |
| Document* document, |
| CSSRuleSourceDataList* result) |
| : parsed_text_(parsed_text), |
| document_(document), |
| result_(result), |
| current_rule_data_(nullptr) { |
| DCHECK(result_); |
| } |
| |
| private: |
| void StartRuleHeader(StyleRule::RuleType, unsigned) override; |
| void EndRuleHeader(unsigned) override; |
| void ObserveSelector(unsigned start_offset, unsigned end_offset) override; |
| void StartRuleBody(unsigned) override; |
| void EndRuleBody(unsigned) override; |
| void ObserveProperty(unsigned start_offset, |
| unsigned end_offset, |
| bool is_important, |
| bool is_parsed) override; |
| void ObserveComment(unsigned start_offset, unsigned end_offset) override; |
| |
| void AddNewRuleToSourceTree(CSSRuleSourceData*); |
| CSSRuleSourceData* PopRuleData(); |
| template <typename CharacterType> |
| inline void SetRuleHeaderEnd(const CharacterType*, unsigned); |
| |
| const String& parsed_text_; |
| Document* document_; |
| CSSRuleSourceDataList* result_; |
| CSSRuleSourceDataList current_rule_data_stack_; |
| CSSRuleSourceData* current_rule_data_; |
| }; |
| |
| void StyleSheetHandler::StartRuleHeader(StyleRule::RuleType type, |
| unsigned offset) { |
| // Pop off data for a previous invalid rule. |
| if (current_rule_data_) |
| current_rule_data_stack_.pop_back(); |
| |
| CSSRuleSourceData* data = MakeGarbageCollected<CSSRuleSourceData>(type); |
| data->rule_header_range.start = offset; |
| current_rule_data_ = data; |
| current_rule_data_stack_.push_back(data); |
| } |
| |
| template <typename CharacterType> |
| inline void StyleSheetHandler::SetRuleHeaderEnd(const CharacterType* data_start, |
| unsigned list_end_offset) { |
| while (list_end_offset > 1) { |
| if (IsHTMLSpace<CharacterType>(*(data_start + list_end_offset - 1))) |
| --list_end_offset; |
| else |
| break; |
| } |
| |
| current_rule_data_stack_.back()->rule_header_range.end = list_end_offset; |
| if (!current_rule_data_stack_.back()->selector_ranges.IsEmpty()) |
| current_rule_data_stack_.back()->selector_ranges.back().end = |
| list_end_offset; |
| } |
| |
| void StyleSheetHandler::EndRuleHeader(unsigned offset) { |
| DCHECK(!current_rule_data_stack_.IsEmpty()); |
| |
| if (parsed_text_.Is8Bit()) |
| SetRuleHeaderEnd<LChar>(parsed_text_.Characters8(), offset); |
| else |
| SetRuleHeaderEnd<UChar>(parsed_text_.Characters16(), offset); |
| } |
| |
| void StyleSheetHandler::ObserveSelector(unsigned start_offset, |
| unsigned end_offset) { |
| DCHECK(current_rule_data_stack_.size()); |
| current_rule_data_stack_.back()->selector_ranges.push_back( |
| SourceRange(start_offset, end_offset)); |
| } |
| |
| void StyleSheetHandler::StartRuleBody(unsigned offset) { |
| current_rule_data_ = nullptr; |
| DCHECK(!current_rule_data_stack_.IsEmpty()); |
| if (parsed_text_[offset] == '{') |
| ++offset; // Skip the rule body opening brace. |
| current_rule_data_stack_.back()->rule_body_range.start = offset; |
| } |
| |
| void StyleSheetHandler::EndRuleBody(unsigned offset) { |
| // Pop off data for a previous invalid rule. |
| if (current_rule_data_) { |
| current_rule_data_ = nullptr; |
| current_rule_data_stack_.pop_back(); |
| } |
| DCHECK(!current_rule_data_stack_.IsEmpty()); |
| current_rule_data_stack_.back()->rule_body_range.end = offset; |
| AddNewRuleToSourceTree(PopRuleData()); |
| } |
| |
| void StyleSheetHandler::AddNewRuleToSourceTree(CSSRuleSourceData* rule) { |
| if (current_rule_data_stack_.IsEmpty()) |
| result_->push_back(rule); |
| else |
| current_rule_data_stack_.back()->child_rules.push_back(rule); |
| } |
| |
| CSSRuleSourceData* StyleSheetHandler::PopRuleData() { |
| DCHECK(!current_rule_data_stack_.IsEmpty()); |
| current_rule_data_ = nullptr; |
| CSSRuleSourceData* data = current_rule_data_stack_.back().Get(); |
| current_rule_data_stack_.pop_back(); |
| return data; |
| } |
| |
| void StyleSheetHandler::ObserveProperty(unsigned start_offset, |
| unsigned end_offset, |
| bool is_important, |
| bool is_parsed) { |
| if (current_rule_data_stack_.IsEmpty() || |
| !current_rule_data_stack_.back()->HasProperties()) |
| return; |
| |
| DCHECK_LE(end_offset, parsed_text_.length()); |
| if (end_offset < parsed_text_.length() && |
| parsed_text_[end_offset] == |
| ';') // Include semicolon into the property text. |
| ++end_offset; |
| |
| DCHECK_LT(start_offset, end_offset); |
| String property_string = |
| parsed_text_.Substring(start_offset, end_offset - start_offset) |
| .StripWhiteSpace(); |
| if (property_string.EndsWith(';')) |
| property_string = property_string.Left(property_string.length() - 1); |
| wtf_size_t colon_index = property_string.find(':'); |
| DCHECK_NE(colon_index, kNotFound); |
| |
| String name = property_string.Left(colon_index).StripWhiteSpace(); |
| String value = |
| property_string.Substring(colon_index + 1, property_string.length()) |
| .StripWhiteSpace(); |
| current_rule_data_stack_.back()->property_data.push_back( |
| CSSPropertySourceData(name, value, is_important, false, is_parsed, |
| SourceRange(start_offset, end_offset))); |
| } |
| |
| void StyleSheetHandler::ObserveComment(unsigned start_offset, |
| unsigned end_offset) { |
| DCHECK_LE(end_offset, parsed_text_.length()); |
| |
| if (current_rule_data_stack_.IsEmpty() || |
| !current_rule_data_stack_.back()->rule_header_range.end || |
| !current_rule_data_stack_.back()->HasProperties()) |
| return; |
| |
| // The lexer is not inside a property AND it is scanning a declaration-aware |
| // rule body. |
| String comment_text = |
| parsed_text_.Substring(start_offset, end_offset - start_offset); |
| |
| DCHECK(comment_text.StartsWith("/*")); |
| comment_text = comment_text.Substring(2); |
| |
| // Require well-formed comments. |
| if (!comment_text.EndsWith("*/")) |
| return; |
| comment_text = |
| comment_text.Substring(0, comment_text.length() - 2).StripWhiteSpace(); |
| if (comment_text.IsEmpty()) |
| return; |
| |
| // FIXME: Use the actual rule type rather than STYLE_RULE? |
| CSSRuleSourceDataList* source_data = |
| MakeGarbageCollected<CSSRuleSourceDataList>(); |
| |
| StyleSheetHandler handler(comment_text, document_, source_data); |
| CSSParser::ParseDeclarationListForInspector( |
| ParserContextForDocument(document_), comment_text, handler); |
| Vector<CSSPropertySourceData>& comment_property_data = |
| source_data->front()->property_data; |
| if (comment_property_data.size() != 1) |
| return; |
| CSSPropertySourceData& property_data = comment_property_data.at(0); |
| bool parsed_ok = property_data.parsed_ok || |
| property_data.name.StartsWith("-moz-") || |
| property_data.name.StartsWith("-o-") || |
| property_data.name.StartsWith("-webkit-") || |
| property_data.name.StartsWith("-ms-"); |
| if (!parsed_ok || property_data.range.length() != comment_text.length()) |
| return; |
| |
| current_rule_data_stack_.back()->property_data.push_back( |
| CSSPropertySourceData(property_data.name, property_data.value, false, |
| true, true, SourceRange(start_offset, end_offset))); |
| } |
| |
| bool VerifyRuleText(Document* document, const String& rule_text) { |
| DEFINE_STATIC_LOCAL(String, bogus_property_name, ("-webkit-boguz-propertee")); |
| auto* style_sheet = MakeGarbageCollected<StyleSheetContents>( |
| ParserContextForDocument(document)); |
| CSSRuleSourceDataList* source_data = |
| MakeGarbageCollected<CSSRuleSourceDataList>(); |
| String text = rule_text + " div { " + bogus_property_name + ": none; }"; |
| StyleSheetHandler handler(text, document, source_data); |
| CSSParser::ParseSheetForInspector(ParserContextForDocument(document), |
| style_sheet, text, handler); |
| unsigned rule_count = source_data->size(); |
| |
| // Exactly two rules should be parsed. |
| if (rule_count != 2) |
| return false; |
| |
| // Added rule must be style rule. |
| if (!source_data->at(0)->HasProperties()) |
| return false; |
| |
| Vector<CSSPropertySourceData>& property_data = |
| source_data->at(1)->property_data; |
| unsigned property_count = property_data.size(); |
| |
| // Exactly one property should be in rule. |
| if (property_count != 1) |
| return false; |
| |
| // Check for the property name. |
| if (property_data.at(0).name != bogus_property_name) |
| return false; |
| |
| return true; |
| } |
| |
| bool VerifyStyleText(Document* document, const String& text) { |
| return VerifyRuleText(document, "div {" + text + "}"); |
| } |
| |
| bool VerifyKeyframeKeyText(Document* document, const String& key_text) { |
| auto* style_sheet = MakeGarbageCollected<StyleSheetContents>( |
| ParserContextForDocument(document)); |
| CSSRuleSourceDataList* source_data = |
| MakeGarbageCollected<CSSRuleSourceDataList>(); |
| String text = "@keyframes boguzAnim { " + key_text + |
| " { -webkit-boguz-propertee : none; } }"; |
| StyleSheetHandler handler(text, document, source_data); |
| CSSParser::ParseSheetForInspector(ParserContextForDocument(document), |
| style_sheet, text, handler); |
| |
| // Exactly one should be parsed. |
| unsigned rule_count = source_data->size(); |
| if (rule_count != 1 || source_data->at(0)->type != StyleRule::kKeyframes) |
| return false; |
| |
| const CSSRuleSourceData& keyframe_data = *source_data->at(0); |
| if (keyframe_data.child_rules.size() != 1 || |
| keyframe_data.child_rules.at(0)->type != StyleRule::kKeyframe) |
| return false; |
| |
| // Exactly one property should be in keyframe rule. |
| const unsigned property_count = |
| keyframe_data.child_rules.at(0)->property_data.size(); |
| if (property_count != 1) |
| return false; |
| |
| return true; |
| } |
| |
| bool VerifySelectorText(Document* document, const String& selector_text) { |
| DEFINE_STATIC_LOCAL(String, bogus_property_name, ("-webkit-boguz-propertee")); |
| auto* style_sheet = MakeGarbageCollected<StyleSheetContents>( |
| ParserContextForDocument(document)); |
| CSSRuleSourceDataList* source_data = |
| MakeGarbageCollected<CSSRuleSourceDataList>(); |
| String text = selector_text + " { " + bogus_property_name + ": none; }"; |
| StyleSheetHandler handler(text, document, source_data); |
| CSSParser::ParseSheetForInspector(ParserContextForDocument(document), |
| style_sheet, text, handler); |
| |
| // Exactly one rule should be parsed. |
| unsigned rule_count = source_data->size(); |
| if (rule_count != 1 || source_data->at(0)->type != StyleRule::kStyle) |
| return false; |
| |
| // Exactly one property should be in style rule. |
| Vector<CSSPropertySourceData>& property_data = |
| source_data->at(0)->property_data; |
| unsigned property_count = property_data.size(); |
| if (property_count != 1) |
| return false; |
| |
| // Check for the property name. |
| if (property_data.at(0).name != bogus_property_name) |
| return false; |
| |
| return true; |
| } |
| |
| bool VerifyMediaText(Document* document, const String& media_text) { |
| DEFINE_STATIC_LOCAL(String, bogus_property_name, ("-webkit-boguz-propertee")); |
| auto* style_sheet = MakeGarbageCollected<StyleSheetContents>( |
| ParserContextForDocument(document)); |
| CSSRuleSourceDataList* source_data = |
| MakeGarbageCollected<CSSRuleSourceDataList>(); |
| String text = "@media " + media_text + " { div { " + bogus_property_name + |
| ": none; } }"; |
| StyleSheetHandler handler(text, document, source_data); |
| CSSParser::ParseSheetForInspector(ParserContextForDocument(document), |
| style_sheet, text, handler); |
| |
| // Exactly one media rule should be parsed. |
| unsigned rule_count = source_data->size(); |
| if (rule_count != 1 || source_data->at(0)->type != StyleRule::kMedia) |
| return false; |
| |
| // Media rule should have exactly one style rule child. |
| CSSRuleSourceDataList& child_source_data = source_data->at(0)->child_rules; |
| rule_count = child_source_data.size(); |
| if (rule_count != 1 || !child_source_data.at(0)->HasProperties()) |
| return false; |
| |
| // Exactly one property should be in style rule. |
| Vector<CSSPropertySourceData>& property_data = |
| child_source_data.at(0)->property_data; |
| unsigned property_count = property_data.size(); |
| if (property_count != 1) |
| return false; |
| |
| // Check for the property name. |
| if (property_data.at(0).name != bogus_property_name) |
| return false; |
| |
| return true; |
| } |
| |
| void FlattenSourceData(const CSSRuleSourceDataList& data_list, |
| CSSRuleSourceDataList* result) { |
| for (CSSRuleSourceData* data : data_list) { |
| // The result->append()'ed types should be exactly the same as in |
| // collectFlatRules(). |
| switch (data->type) { |
| case StyleRule::kStyle: |
| case StyleRule::kImport: |
| case StyleRule::kPage: |
| case StyleRule::kFontFace: |
| case StyleRule::kViewport: |
| case StyleRule::kKeyframe: |
| result->push_back(data); |
| break; |
| case StyleRule::kMedia: |
| case StyleRule::kSupports: |
| case StyleRule::kKeyframes: |
| result->push_back(data); |
| FlattenSourceData(data->child_rules, result); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| CSSRuleList* AsCSSRuleList(CSSRule* rule) { |
| if (!rule) |
| return nullptr; |
| |
| if (auto* media_rule = DynamicTo<CSSMediaRule>(rule)) |
| return media_rule->cssRules(); |
| |
| if (auto* supports_rule = DynamicTo<CSSSupportsRule>(rule)) |
| return supports_rule->cssRules(); |
| |
| if (auto* keyframes_rule = DynamicTo<CSSKeyframesRule>(rule)) |
| return keyframes_rule->cssRules(); |
| |
| return nullptr; |
| } |
| |
| template <typename RuleList> |
| void CollectFlatRules(RuleList rule_list, CSSRuleVector* result) { |
| if (!rule_list) |
| return; |
| |
| for (unsigned i = 0, size = rule_list->length(); i < size; ++i) { |
| CSSRule* rule = rule_list->item(i); |
| |
| // The result->append()'ed types should be exactly the same as in |
| // flattenSourceData(). |
| switch (rule->GetType()) { |
| case CSSRule::kStyleRule: |
| case CSSRule::kImportRule: |
| case CSSRule::kCharsetRule: |
| case CSSRule::kPageRule: |
| case CSSRule::kFontFaceRule: |
| case CSSRule::kViewportRule: |
| case CSSRule::kKeyframeRule: |
| result->push_back(rule); |
| break; |
| case CSSRule::kMediaRule: |
| case CSSRule::kSupportsRule: |
| case CSSRule::kKeyframesRule: |
| result->push_back(rule); |
| CollectFlatRules(AsCSSRuleList(rule), result); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| typedef HashMap<unsigned, |
| unsigned, |
| WTF::IntHash<unsigned>, |
| WTF::UnsignedWithZeroKeyHashTraits<unsigned>> |
| IndexMap; |
| |
| void Diff(const Vector<String>& list_a, |
| const Vector<String>& list_b, |
| IndexMap* a_to_b, |
| IndexMap* b_to_a) { |
| // Cut of common prefix. |
| wtf_size_t start_offset = 0; |
| while (start_offset < list_a.size() && start_offset < list_b.size()) { |
| if (list_a.at(start_offset) != list_b.at(start_offset)) |
| break; |
| a_to_b->Set(start_offset, start_offset); |
| b_to_a->Set(start_offset, start_offset); |
| ++start_offset; |
| } |
| |
| // Cut of common suffix. |
| wtf_size_t end_offset = 0; |
| while (end_offset < list_a.size() - start_offset && |
| end_offset < list_b.size() - start_offset) { |
| wtf_size_t index_a = list_a.size() - end_offset - 1; |
| wtf_size_t index_b = list_b.size() - end_offset - 1; |
| if (list_a.at(index_a) != list_b.at(index_b)) |
| break; |
| a_to_b->Set(index_a, index_b); |
| b_to_a->Set(index_b, index_a); |
| ++end_offset; |
| } |
| |
| wtf_size_t n = list_a.size() - start_offset - end_offset; |
| wtf_size_t m = list_b.size() - start_offset - end_offset; |
| |
| // If we mapped either of arrays, we have no more work to do. |
| if (n == 0 || m == 0) |
| return; |
| |
| int** diff = new int*[n]; |
| int** backtrack = new int*[n]; |
| for (wtf_size_t i = 0; i < n; ++i) { |
| diff[i] = new int[m]; |
| backtrack[i] = new int[m]; |
| } |
| |
| // Compute longest common subsequence of two cssom models. |
| for (wtf_size_t i = 0; i < n; ++i) { |
| for (wtf_size_t j = 0; j < m; ++j) { |
| int max = 0; |
| int track = 0; |
| |
| if (i > 0 && diff[i - 1][j] > max) { |
| max = diff[i - 1][j]; |
| track = 1; |
| } |
| |
| if (j > 0 && diff[i][j - 1] > max) { |
| max = diff[i][j - 1]; |
| track = 2; |
| } |
| |
| if (list_a.at(i + start_offset) == list_b.at(j + start_offset)) { |
| int value = i > 0 && j > 0 ? diff[i - 1][j - 1] + 1 : 1; |
| if (value > max) { |
| max = value; |
| track = 3; |
| } |
| } |
| |
| diff[i][j] = max; |
| backtrack[i][j] = track; |
| } |
| } |
| |
| // Backtrack and add missing mapping. |
| int i = n - 1, j = m - 1; |
| while (i >= 0 && j >= 0 && backtrack[i][j]) { |
| switch (backtrack[i][j]) { |
| case 1: |
| i -= 1; |
| break; |
| case 2: |
| j -= 1; |
| break; |
| case 3: |
| a_to_b->Set(i + start_offset, j + start_offset); |
| b_to_a->Set(j + start_offset, i + start_offset); |
| i -= 1; |
| j -= 1; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| for (wtf_size_t i = 0; i < n; ++i) { |
| delete[] diff[i]; |
| delete[] backtrack[i]; |
| } |
| delete[] diff; |
| delete[] backtrack; |
| } |
| |
| // Warning: it does not always produce valid CSS. |
| // Use the rule's cssText method if you need to expose CSS externally. |
| String CanonicalCSSText(CSSRule* rule) { |
| auto* style_rule = DynamicTo<CSSStyleRule>(rule); |
| if (!style_rule) |
| return rule->cssText(); |
| |
| Vector<String> property_names; |
| CSSStyleDeclaration* style = style_rule->style(); |
| for (unsigned i = 0; i < style->length(); ++i) |
| property_names.push_back(style->item(i)); |
| |
| std::sort(property_names.begin(), property_names.end(), |
| WTF::CodeUnitCompareLessThan); |
| |
| StringBuilder builder; |
| builder.Append(style_rule->selectorText()); |
| builder.Append('{'); |
| for (unsigned i = 0; i < property_names.size(); ++i) { |
| String name = property_names.at(i); |
| builder.Append(' '); |
| builder.Append(name); |
| builder.Append(':'); |
| builder.Append(style->getPropertyValue(name)); |
| if (!style->getPropertyPriority(name).IsEmpty()) { |
| builder.Append(' '); |
| builder.Append(style->getPropertyPriority(name)); |
| } |
| builder.Append(';'); |
| } |
| builder.Append('}'); |
| |
| return builder.ToString(); |
| } |
| |
| } // namespace |
| |
| enum MediaListSource { |
| kMediaListSourceLinkedSheet, |
| kMediaListSourceInlineSheet, |
| kMediaListSourceMediaRule, |
| kMediaListSourceImportRule |
| }; |
| |
| std::unique_ptr<protocol::CSS::SourceRange> |
| InspectorStyleSheetBase::BuildSourceRangeObject(const SourceRange& range) { |
| const LineEndings* line_endings = this->GetLineEndings(); |
| if (!line_endings) |
| return nullptr; |
| TextPosition start = |
| TextPosition::FromOffsetAndLineEndings(range.start, *line_endings); |
| TextPosition end = |
| TextPosition::FromOffsetAndLineEndings(range.end, *line_endings); |
| |
| std::unique_ptr<protocol::CSS::SourceRange> result = |
| protocol::CSS::SourceRange::create() |
| .setStartLine(start.line_.ZeroBasedInt()) |
| .setStartColumn(start.column_.ZeroBasedInt()) |
| .setEndLine(end.line_.ZeroBasedInt()) |
| .setEndColumn(end.column_.ZeroBasedInt()) |
| .build(); |
| return result; |
| } |
| |
| InspectorStyle::InspectorStyle(CSSStyleDeclaration* style, |
| CSSRuleSourceData* source_data, |
| InspectorStyleSheetBase* parent_style_sheet) |
| : style_(style), |
| source_data_(source_data), |
| parent_style_sheet_(parent_style_sheet) { |
| DCHECK(style_); |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyle> InspectorStyle::BuildObjectForStyle() { |
| std::unique_ptr<protocol::CSS::CSSStyle> result = StyleWithProperties(); |
| if (source_data_) { |
| if (parent_style_sheet_ && !parent_style_sheet_->Id().IsEmpty()) |
| result->setStyleSheetId(parent_style_sheet_->Id()); |
| result->setRange(parent_style_sheet_->BuildSourceRangeObject( |
| source_data_->rule_body_range)); |
| String sheet_text; |
| bool success = parent_style_sheet_->GetText(&sheet_text); |
| if (success) { |
| const SourceRange& body_range = source_data_->rule_body_range; |
| result->setCssText(sheet_text.Substring( |
| body_range.start, body_range.end - body_range.start)); |
| } |
| } |
| |
| return result; |
| } |
| |
| bool InspectorStyle::StyleText(String* result) { |
| if (!source_data_) |
| return false; |
| |
| return TextForRange(source_data_->rule_body_range, result); |
| } |
| |
| bool InspectorStyle::TextForRange(const SourceRange& range, String* result) { |
| String style_sheet_text; |
| bool success = parent_style_sheet_->GetText(&style_sheet_text); |
| if (!success) |
| return false; |
| |
| DCHECK(0 <= range.start); |
| DCHECK_LE(range.start, range.end); |
| DCHECK_LE(range.end, style_sheet_text.length()); |
| *result = style_sheet_text.Substring(range.start, range.end - range.start); |
| return true; |
| } |
| |
| void InspectorStyle::PopulateAllProperties( |
| Vector<CSSPropertySourceData>& result) { |
| HashSet<String> source_property_names; |
| |
| if (source_data_ && source_data_->HasProperties()) { |
| Vector<CSSPropertySourceData>& source_property_data = |
| source_data_->property_data; |
| for (const auto& data : source_property_data) { |
| result.push_back(data); |
| source_property_names.insert(data.name.DeprecatedLower()); |
| } |
| } |
| |
| for (int i = 0, size = style_->length(); i < size; ++i) { |
| String name = style_->item(i); |
| if (!source_property_names.insert(name.DeprecatedLower()).is_new_entry) |
| continue; |
| |
| String value = style_->getPropertyValue(name); |
| if (value.IsEmpty()) |
| continue; |
| bool important = !style_->getPropertyPriority(name).IsEmpty(); |
| if (important) |
| value = value + " !important"; |
| result.push_back(CSSPropertySourceData( |
| name, value, !style_->getPropertyPriority(name).IsEmpty(), false, true, |
| SourceRange())); |
| } |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyle> InspectorStyle::StyleWithProperties() { |
| auto properties_object = |
| std::make_unique<protocol::Array<protocol::CSS::CSSProperty>>(); |
| auto shorthand_entries = |
| std::make_unique<protocol::Array<protocol::CSS::ShorthandEntry>>(); |
| HashSet<String> found_shorthands; |
| |
| Vector<CSSPropertySourceData> properties; |
| PopulateAllProperties(properties); |
| |
| for (auto& style_property : properties) { |
| const CSSPropertySourceData& property_entry = style_property; |
| const String& name = property_entry.name; |
| |
| std::unique_ptr<protocol::CSS::CSSProperty> property = |
| protocol::CSS::CSSProperty::create() |
| .setName(name) |
| .setValue(property_entry.value) |
| .build(); |
| |
| // Default "parsedOk" == true. |
| if (!property_entry.parsed_ok) |
| property->setParsedOk(false); |
| String text; |
| if (style_property.range.length() && |
| TextForRange(style_property.range, &text)) |
| property->setText(text); |
| if (property_entry.important) |
| property->setImportant(true); |
| if (style_property.range.length()) { |
| property->setRange(parent_style_sheet_ |
| ? parent_style_sheet_->BuildSourceRangeObject( |
| property_entry.range) |
| : nullptr); |
| if (!property_entry.disabled) { |
| property->setImplicit(false); |
| } |
| property->setDisabled(property_entry.disabled); |
| } else if (!property_entry.disabled) { |
| bool implicit = style_->IsPropertyImplicit(name); |
| // Default "implicit" == false. |
| if (implicit) |
| property->setImplicit(true); |
| |
| String shorthand = style_->GetPropertyShorthand(name); |
| if (!shorthand.IsEmpty()) { |
| if (found_shorthands.insert(shorthand).is_new_entry) { |
| std::unique_ptr<protocol::CSS::ShorthandEntry> entry = |
| protocol::CSS::ShorthandEntry::create() |
| .setName(shorthand) |
| .setValue(ShorthandValue(shorthand)) |
| .build(); |
| if (!style_->getPropertyPriority(name).IsEmpty()) |
| entry->setImportant(true); |
| shorthand_entries->emplace_back(std::move(entry)); |
| } |
| } |
| } |
| properties_object->emplace_back(std::move(property)); |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyle> result = |
| protocol::CSS::CSSStyle::create() |
| .setCssProperties(std::move(properties_object)) |
| .setShorthandEntries(std::move(shorthand_entries)) |
| .build(); |
| return result; |
| } |
| |
| String InspectorStyle::ShorthandValue(const String& shorthand_property) { |
| StringBuilder builder; |
| String value = style_->getPropertyValue(shorthand_property); |
| if (value.IsEmpty()) { |
| for (unsigned i = 0; i < style_->length(); ++i) { |
| String individual_property = style_->item(i); |
| if (style_->GetPropertyShorthand(individual_property) != |
| shorthand_property) |
| continue; |
| if (style_->IsPropertyImplicit(individual_property)) |
| continue; |
| String individual_value = style_->getPropertyValue(individual_property); |
| if (individual_value == "initial") |
| continue; |
| if (!builder.IsEmpty()) |
| builder.Append(' '); |
| builder.Append(individual_value); |
| } |
| } else { |
| builder.Append(value); |
| } |
| |
| if (!style_->getPropertyPriority(shorthand_property).IsEmpty()) |
| builder.Append(" !important"); |
| |
| return builder.ToString(); |
| } |
| |
| void InspectorStyle::Trace(Visitor* visitor) const { |
| visitor->Trace(style_); |
| visitor->Trace(parent_style_sheet_); |
| visitor->Trace(source_data_); |
| } |
| |
| InspectorStyleSheetBase::InspectorStyleSheetBase(Listener* listener) |
| : id_(IdentifiersFactory::CreateIdentifier()), |
| listener_(listener), |
| line_endings_(std::make_unique<LineEndings>()) {} |
| |
| void InspectorStyleSheetBase::OnStyleSheetTextChanged() { |
| line_endings_ = std::make_unique<LineEndings>(); |
| if (GetListener()) |
| GetListener()->StyleSheetChanged(this); |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyle> |
| InspectorStyleSheetBase::BuildObjectForStyle(CSSStyleDeclaration* style) { |
| return GetInspectorStyle(style)->BuildObjectForStyle(); |
| } |
| |
| const LineEndings* InspectorStyleSheetBase::GetLineEndings() { |
| if (line_endings_->size() > 0) |
| return line_endings_.get(); |
| String text; |
| if (GetText(&text)) |
| line_endings_ = WTF::GetLineEndings(text); |
| return line_endings_.get(); |
| } |
| |
| void InspectorStyleSheetBase::ResetLineEndings() { |
| line_endings_ = std::make_unique<LineEndings>(); |
| } |
| |
| bool InspectorStyleSheetBase::LineNumberAndColumnToOffset( |
| unsigned line_number, |
| unsigned column_number, |
| unsigned* offset) { |
| const LineEndings* endings = GetLineEndings(); |
| if (line_number >= endings->size()) |
| return false; |
| unsigned characters_in_line = |
| line_number > 0 |
| ? endings->at(line_number) - endings->at(line_number - 1) - 1 |
| : endings->at(0); |
| if (column_number > characters_in_line) |
| return false; |
| TextPosition position(OrdinalNumber::FromZeroBasedInt(line_number), |
| OrdinalNumber::FromZeroBasedInt(column_number)); |
| *offset = position.ToOffset(*endings).ZeroBasedInt(); |
| return true; |
| } |
| |
| InspectorStyleSheet::InspectorStyleSheet( |
| InspectorNetworkAgent* network_agent, |
| CSSStyleSheet* page_style_sheet, |
| const String& origin, |
| const String& document_url, |
| InspectorStyleSheetBase::Listener* listener, |
| InspectorResourceContainer* resource_container) |
| : InspectorStyleSheetBase(listener), |
| resource_container_(resource_container), |
| network_agent_(network_agent), |
| page_style_sheet_(page_style_sheet), |
| origin_(origin), |
| document_url_(document_url) { |
| UpdateText(); |
| } |
| |
| InspectorStyleSheet::~InspectorStyleSheet() = default; |
| |
| void InspectorStyleSheet::Trace(Visitor* visitor) const { |
| visitor->Trace(resource_container_); |
| visitor->Trace(network_agent_); |
| visitor->Trace(page_style_sheet_); |
| visitor->Trace(cssom_flat_rules_); |
| visitor->Trace(parsed_flat_rules_); |
| visitor->Trace(source_data_); |
| InspectorStyleSheetBase::Trace(visitor); |
| } |
| |
| static String StyleSheetURL(CSSStyleSheet* page_style_sheet) { |
| if (page_style_sheet && !page_style_sheet->Contents()->BaseURL().IsEmpty()) |
| return page_style_sheet->Contents()->BaseURL().GetString(); |
| return g_empty_string; |
| } |
| |
| String InspectorStyleSheet::FinalURL() { |
| String url = StyleSheetURL(page_style_sheet_.Get()); |
| return url.IsEmpty() ? document_url_ : url; |
| } |
| |
| bool InspectorStyleSheet::SetText(const String& text, |
| ExceptionState& exception_state) { |
| page_style_sheet_->SetText(text, CSSImportRules::kAllow); |
| InnerSetText(text, true); |
| OnStyleSheetTextChanged(); |
| return true; |
| } |
| |
| CSSStyleRule* InspectorStyleSheet::SetRuleSelector( |
| const SourceRange& range, |
| const String& text, |
| SourceRange* new_range, |
| String* old_text, |
| ExceptionState& exception_state) { |
| if (!VerifySelectorText(page_style_sheet_->OwnerDocument(), text)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, |
| "Selector or media text is not valid."); |
| return nullptr; |
| } |
| |
| CSSRuleSourceData* source_data = FindRuleByHeaderRange(range); |
| if (!source_data || !source_data->HasProperties()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing source range"); |
| return nullptr; |
| } |
| |
| CSSRule* rule = RuleForSourceData(source_data); |
| if (!rule || !rule->parentStyleSheet() || |
| rule->GetType() != CSSRule::kStyleRule) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing style source range"); |
| return nullptr; |
| } |
| |
| CSSStyleRule* style_rule = InspectorCSSAgent::AsCSSStyleRule(rule); |
| style_rule->setSelectorText( |
| page_style_sheet_->OwnerDocument()->GetExecutionContext(), text); |
| |
| ReplaceText(source_data->rule_header_range, text, new_range, old_text); |
| OnStyleSheetTextChanged(); |
| |
| return style_rule; |
| } |
| |
| CSSKeyframeRule* InspectorStyleSheet::SetKeyframeKey( |
| const SourceRange& range, |
| const String& text, |
| SourceRange* new_range, |
| String* old_text, |
| ExceptionState& exception_state) { |
| if (!VerifyKeyframeKeyText(page_style_sheet_->OwnerDocument(), text)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, |
| "Keyframe key text is not valid."); |
| return nullptr; |
| } |
| |
| CSSRuleSourceData* source_data = FindRuleByHeaderRange(range); |
| if (!source_data || !source_data->HasProperties()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing source range"); |
| return nullptr; |
| } |
| |
| CSSRule* rule = RuleForSourceData(source_data); |
| if (!rule || !rule->parentStyleSheet() || |
| rule->GetType() != CSSRule::kKeyframeRule) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing style source range"); |
| return nullptr; |
| } |
| |
| CSSKeyframeRule* keyframe_rule = To<CSSKeyframeRule>(rule); |
| keyframe_rule->setKeyText(text, exception_state); |
| |
| ReplaceText(source_data->rule_header_range, text, new_range, old_text); |
| OnStyleSheetTextChanged(); |
| |
| return keyframe_rule; |
| } |
| |
| CSSRule* InspectorStyleSheet::SetStyleText(const SourceRange& range, |
| const String& text, |
| SourceRange* new_range, |
| String* old_text, |
| ExceptionState& exception_state) { |
| if (!VerifyStyleText(page_style_sheet_->OwnerDocument(), text)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, |
| "Style text is not valid."); |
| return nullptr; |
| } |
| |
| CSSRuleSourceData* source_data = FindRuleByBodyRange(range); |
| if (!source_data || !source_data->HasProperties()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing style source range"); |
| return nullptr; |
| } |
| |
| CSSRule* rule = RuleForSourceData(source_data); |
| if (!rule || !rule->parentStyleSheet() || |
| (!IsA<CSSStyleRule>(rule) && !IsA<CSSKeyframeRule>(rule))) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing style source range"); |
| return nullptr; |
| } |
| |
| CSSStyleDeclaration* style = nullptr; |
| if (auto* style_rule = DynamicTo<CSSStyleRule>(rule)) |
| style = style_rule->style(); |
| else |
| style = To<CSSKeyframeRule>(rule)->style(); |
| |
| Document* owner_document = page_style_sheet_->OwnerDocument(); |
| ExecutionContext* execution_context = |
| owner_document ? owner_document->GetExecutionContext() : nullptr; |
| |
| style->setCSSText(execution_context, text, exception_state); |
| |
| ReplaceText(source_data->rule_body_range, text, new_range, old_text); |
| OnStyleSheetTextChanged(); |
| |
| return rule; |
| } |
| |
| CSSMediaRule* InspectorStyleSheet::SetMediaRuleText( |
| const SourceRange& range, |
| const String& text, |
| SourceRange* new_range, |
| String* old_text, |
| ExceptionState& exception_state) { |
| if (!VerifyMediaText(page_style_sheet_->OwnerDocument(), text)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, |
| "Selector or media text is not valid."); |
| return nullptr; |
| } |
| |
| CSSRuleSourceData* source_data = FindRuleByHeaderRange(range); |
| if (!source_data || !source_data->HasMedia()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing source range"); |
| return nullptr; |
| } |
| |
| CSSRule* rule = RuleForSourceData(source_data); |
| if (!rule || !rule->parentStyleSheet() || |
| rule->GetType() != CSSRule::kMediaRule) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Source range didn't match existing style source range"); |
| return nullptr; |
| } |
| |
| CSSMediaRule* media_rule = InspectorCSSAgent::AsCSSMediaRule(rule); |
| media_rule->media()->setMediaText( |
| page_style_sheet_->OwnerDocument()->GetExecutionContext(), text); |
| |
| ReplaceText(source_data->rule_header_range, text, new_range, old_text); |
| OnStyleSheetTextChanged(); |
| |
| return media_rule; |
| } |
| |
| CSSRuleSourceData* InspectorStyleSheet::RuleSourceDataAfterSourceRange( |
| const SourceRange& source_range) { |
| DCHECK(source_data_); |
| unsigned index = 0; |
| for (; index < source_data_->size(); ++index) { |
| CSSRuleSourceData* sd = source_data_->at(index).Get(); |
| if (sd->rule_header_range.start >= source_range.end) |
| break; |
| } |
| return index < source_data_->size() ? source_data_->at(index).Get() : nullptr; |
| } |
| |
| CSSStyleRule* InspectorStyleSheet::InsertCSSOMRuleInStyleSheet( |
| CSSRule* insert_before, |
| const String& rule_text, |
| ExceptionState& exception_state) { |
| unsigned index = 0; |
| for (; index < page_style_sheet_->length(); ++index) { |
| CSSRule* rule = page_style_sheet_->item(index); |
| if (rule == insert_before) |
| break; |
| } |
| |
| page_style_sheet_->insertRule(rule_text, index, exception_state); |
| CSSRule* rule = page_style_sheet_->item(index); |
| CSSStyleRule* style_rule = InspectorCSSAgent::AsCSSStyleRule(rule); |
| if (!style_rule) { |
| page_style_sheet_->deleteRule(index, ASSERT_NO_EXCEPTION); |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kSyntaxError, |
| "The rule '" + rule_text + "' could not be added in style sheet."); |
| return nullptr; |
| } |
| return style_rule; |
| } |
| |
| CSSStyleRule* InspectorStyleSheet::InsertCSSOMRuleInMediaRule( |
| CSSMediaRule* media_rule, |
| CSSRule* insert_before, |
| const String& rule_text, |
| ExceptionState& exception_state) { |
| unsigned index = 0; |
| for (; index < media_rule->length(); ++index) { |
| CSSRule* rule = media_rule->Item(index); |
| if (rule == insert_before) |
| break; |
| } |
| |
| media_rule->insertRule( |
| page_style_sheet_->OwnerDocument()->GetExecutionContext(), rule_text, |
| index, exception_state); |
| CSSRule* rule = media_rule->Item(index); |
| CSSStyleRule* style_rule = InspectorCSSAgent::AsCSSStyleRule(rule); |
| if (!style_rule) { |
| media_rule->deleteRule(index, ASSERT_NO_EXCEPTION); |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kSyntaxError, |
| "The rule '" + rule_text + "' could not be added in media rule."); |
| return nullptr; |
| } |
| return style_rule; |
| } |
| |
| CSSStyleRule* InspectorStyleSheet::InsertCSSOMRuleBySourceRange( |
| const SourceRange& source_range, |
| const String& rule_text, |
| ExceptionState& exception_state) { |
| DCHECK(source_data_); |
| |
| CSSRuleSourceData* containing_rule_source_data = nullptr; |
| for (wtf_size_t i = 0; i < source_data_->size(); ++i) { |
| CSSRuleSourceData* rule_source_data = source_data_->at(i).Get(); |
| if (rule_source_data->rule_header_range.start < source_range.start && |
| source_range.start < rule_source_data->rule_body_range.start) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Cannot insert rule inside rule selector."); |
| return nullptr; |
| } |
| if (source_range.start < rule_source_data->rule_body_range.start || |
| rule_source_data->rule_body_range.end < source_range.start) |
| continue; |
| if (!containing_rule_source_data || |
| containing_rule_source_data->rule_body_range.length() > |
| rule_source_data->rule_body_range.length()) |
| containing_rule_source_data = rule_source_data; |
| } |
| |
| CSSRuleSourceData* insert_before = |
| RuleSourceDataAfterSourceRange(source_range); |
| CSSRule* insert_before_rule = RuleForSourceData(insert_before); |
| |
| if (!containing_rule_source_data) |
| return InsertCSSOMRuleInStyleSheet(insert_before_rule, rule_text, |
| exception_state); |
| |
| CSSRule* rule = RuleForSourceData(containing_rule_source_data); |
| if (!rule || rule->GetType() != CSSRule::kMediaRule) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError, |
| "Cannot insert rule in non-media rule."); |
| return nullptr; |
| } |
| |
| return InsertCSSOMRuleInMediaRule(To<CSSMediaRule>(rule), insert_before_rule, |
| rule_text, exception_state); |
| } |
| |
| CSSStyleRule* InspectorStyleSheet::AddRule(const String& rule_text, |
| const SourceRange& location, |
| SourceRange* added_range, |
| ExceptionState& exception_state) { |
| if (location.start != location.end) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError, |
| "Source range must be collapsed."); |
| return nullptr; |
| } |
| |
| if (!VerifyRuleText(page_style_sheet_->OwnerDocument(), rule_text)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, |
| "Rule text is not valid."); |
| return nullptr; |
| } |
| |
| if (!source_data_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError, |
| "Style is read-only."); |
| return nullptr; |
| } |
| |
| CSSStyleRule* style_rule = |
| InsertCSSOMRuleBySourceRange(location, rule_text, exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| |
| ReplaceText(location, rule_text, added_range, nullptr); |
| OnStyleSheetTextChanged(); |
| return style_rule; |
| } |
| |
| bool InspectorStyleSheet::DeleteRule(const SourceRange& range, |
| ExceptionState& exception_state) { |
| if (!source_data_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError, |
| "Style is read-only."); |
| return false; |
| } |
| |
| // Find index of CSSRule that entirely belongs to the range. |
| CSSRuleSourceData* found_data = nullptr; |
| |
| for (wtf_size_t i = 0; i < source_data_->size(); ++i) { |
| CSSRuleSourceData* rule_source_data = source_data_->at(i).Get(); |
| unsigned rule_start = rule_source_data->rule_header_range.start; |
| unsigned rule_end = rule_source_data->rule_body_range.end + 1; |
| bool start_belongs = rule_start >= range.start && rule_start < range.end; |
| bool end_belongs = rule_end > range.start && rule_end <= range.end; |
| |
| if (start_belongs != end_belongs) |
| break; |
| if (!start_belongs) |
| continue; |
| if (!found_data || found_data->rule_body_range.length() > |
| rule_source_data->rule_body_range.length()) |
| found_data = rule_source_data; |
| } |
| CSSRule* rule = RuleForSourceData(found_data); |
| if (!rule) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "No style rule could be found in given range."); |
| return false; |
| } |
| CSSStyleSheet* style_sheet = rule->parentStyleSheet(); |
| if (!style_sheet) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError, |
| "No parent stylesheet could be found."); |
| return false; |
| } |
| CSSRule* parent_rule = rule->parentRule(); |
| if (parent_rule) { |
| if (parent_rule->GetType() != CSSRule::kMediaRule) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "Cannot remove rule from non-media rule."); |
| return false; |
| } |
| CSSMediaRule* parent_media_rule = To<CSSMediaRule>(parent_rule); |
| wtf_size_t index = 0; |
| while (index < parent_media_rule->length() && |
| parent_media_rule->Item(index) != rule) |
| ++index; |
| DCHECK_LT(index, parent_media_rule->length()); |
| parent_media_rule->deleteRule(index, exception_state); |
| } else { |
| wtf_size_t index = 0; |
| while (index < style_sheet->length() && style_sheet->item(index) != rule) |
| ++index; |
| DCHECK_LT(index, style_sheet->length()); |
| style_sheet->deleteRule(index, exception_state); |
| } |
| // |rule| MAY NOT be addressed after this line! |
| |
| if (exception_state.HadException()) |
| return false; |
| |
| ReplaceText(range, "", nullptr, nullptr); |
| OnStyleSheetTextChanged(); |
| return true; |
| } |
| |
| std::unique_ptr<protocol::Array<String>> |
| InspectorStyleSheet::CollectClassNames() { |
| HashSet<String> unique_names; |
| auto result = std::make_unique<protocol::Array<String>>(); |
| |
| for (wtf_size_t i = 0; i < parsed_flat_rules_.size(); ++i) { |
| if (auto* style_rule = |
| DynamicTo<CSSStyleRule>(parsed_flat_rules_.at(i).Get())) |
| GetClassNamesFromRule(style_rule, unique_names); |
| } |
| for (const String& class_name : unique_names) |
| result->emplace_back(class_name); |
| return result; |
| } |
| |
| void InspectorStyleSheet::ReplaceText(const SourceRange& range, |
| const String& text, |
| SourceRange* new_range, |
| String* old_text) { |
| String sheet_text = text_; |
| if (old_text) |
| *old_text = sheet_text.Substring(range.start, range.length()); |
| sheet_text.replace(range.start, range.length(), text); |
| if (new_range) |
| *new_range = SourceRange(range.start, range.start + text.length()); |
| InnerSetText(sheet_text, true); |
| } |
| |
| void InspectorStyleSheet::ParseText(const String& text) { |
| CSSRuleSourceDataList* rule_tree = |
| MakeGarbageCollected<CSSRuleSourceDataList>(); |
| auto* style_sheet = MakeGarbageCollected<StyleSheetContents>( |
| page_style_sheet_->Contents()->ParserContext()); |
| StyleSheetHandler handler(text, page_style_sheet_->OwnerDocument(), |
| rule_tree); |
| CSSParser::ParseSheetForInspector( |
| page_style_sheet_->Contents()->ParserContext(), style_sheet, text, |
| handler); |
| CSSStyleSheet* source_data_sheet = nullptr; |
| if (auto* import_rule = |
| DynamicTo<CSSImportRule>(page_style_sheet_->ownerRule())) { |
| source_data_sheet = |
| MakeGarbageCollected<CSSStyleSheet>(style_sheet, import_rule); |
| } else { |
| if (page_style_sheet_->ownerNode()) { |
| source_data_sheet = MakeGarbageCollected<CSSStyleSheet>( |
| style_sheet, *page_style_sheet_->ownerNode()); |
| } else { |
| source_data_sheet = MakeGarbageCollected<CSSStyleSheet>(style_sheet); |
| } |
| } |
| |
| parsed_flat_rules_.clear(); |
| CollectFlatRules(source_data_sheet, &parsed_flat_rules_); |
| |
| source_data_ = MakeGarbageCollected<CSSRuleSourceDataList>(); |
| FlattenSourceData(*rule_tree, source_data_.Get()); |
| } |
| |
| // The stylesheet text might be out of sync with `page_style_sheet_` rules. |
| // This method checks if a rule is present in the source text using |
| // `SourceDataForRule` and produces a new text with all rules merged into the |
| // original text. For example, if the source text is |
| // |
| // /* comment */ .rule1 {} .rule3 {} |
| // |
| // and the page_style_sheet_ contains |
| // |
| // .rule0 {} .rule1 {} .rule2 {} .rule3 {} .rule4 {} |
| // |
| // The result should be |
| // |
| // .rule0 {} /* comment */ .rule1 {} .rule2 {} .rule3 {} .rule4 {} |
| // |
| // Note that page_style_sheet_ does not maintain comments and original |
| // formatting. |
| String InspectorStyleSheet::MergeCSSOMRulesWithText(const String& text) { |
| String merged_text = text; |
| unsigned original_insert_pos = 0; |
| unsigned inserted_count = 0; |
| for (unsigned i = 0; i < page_style_sheet_->length(); i++) { |
| CSSRuleSourceData* source_data = |
| SourceDataForRule(page_style_sheet_->item(i)); |
| if (source_data) { |
| original_insert_pos = source_data->rule_body_range.end + 1; |
| continue; |
| } |
| String rule_text = page_style_sheet_->item(i)->cssText(); |
| merged_text.replace(original_insert_pos + inserted_count, 0, rule_text); |
| inserted_count += rule_text.length(); |
| } |
| rule_to_source_data_.clear(); |
| source_data_to_rule_.clear(); |
| cssom_flat_rules_.clear(); |
| return merged_text; |
| } |
| |
| void InspectorStyleSheet::InnerSetText(const String& text, |
| bool mark_as_locally_modified) { |
| ParseText(text); |
| |
| text_ = text; |
| |
| if (mark_as_locally_modified) { |
| Element* element = OwnerStyleElement(); |
| if (element) |
| resource_container_->StoreStyleElementContent( |
| DOMNodeIds::IdForNode(element), text); |
| else if (origin_ == protocol::CSS::StyleSheetOriginEnum::Inspector) |
| resource_container_->StoreStyleElementContent( |
| DOMNodeIds::IdForNode(page_style_sheet_->OwnerDocument()), text); |
| else |
| resource_container_->StoreStyleSheetContent(FinalURL(), text); |
| } |
| } |
| |
| namespace { |
| |
| TextPosition TextPositionFromOffsetAndLineEndingsRelativeToStartPosition( |
| unsigned offset, |
| const Vector<unsigned>& line_endings, |
| const TextPosition& start_position) { |
| TextPosition position = |
| TextPosition::FromOffsetAndLineEndings(offset, line_endings); |
| unsigned column = position.column_.ZeroBasedInt(); |
| // A non-zero `start_position.column_` means that the text started in the |
| // middle of a line, so the start column position must be added if `offset` |
| // translates to a `position` in the first line of the text. |
| if (position.line_.ZeroBasedInt() == 0) { |
| column += start_position.column_.ZeroBasedInt(); |
| } |
| unsigned line_index = |
| start_position.line_.ZeroBasedInt() + position.line_.ZeroBasedInt(); |
| return TextPosition(OrdinalNumber::FromZeroBasedInt(line_index), |
| OrdinalNumber::FromZeroBasedInt(column)); |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<protocol::CSS::CSSStyleSheetHeader> |
| InspectorStyleSheet::BuildObjectForStyleSheetInfo() { |
| CSSStyleSheet* style_sheet = PageStyleSheet(); |
| if (!style_sheet) |
| return nullptr; |
| |
| Document* document = style_sheet->OwnerDocument(); |
| LocalFrame* frame = document ? document->GetFrame() : nullptr; |
| const LineEndings* line_endings = this->GetLineEndings(); |
| TextPosition start = style_sheet->StartPositionInSource(); |
| TextPosition end = start; |
| unsigned text_length = 0; |
| if (line_endings->size() > 0) { |
| text_length = line_endings->back(); |
| end = TextPositionFromOffsetAndLineEndingsRelativeToStartPosition( |
| text_length, *line_endings, start); |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyleSheetHeader> result = |
| protocol::CSS::CSSStyleSheetHeader::create() |
| .setStyleSheetId(Id()) |
| .setOrigin(origin_) |
| .setDisabled(style_sheet->disabled()) |
| .setSourceURL(Url()) |
| .setTitle(style_sheet->title()) |
| .setFrameId(frame ? IdentifiersFactory::FrameId(frame) : "") |
| .setIsInline(style_sheet->IsInline() && !StartsAtZero()) |
| .setIsMutable(style_sheet->Contents()->IsMutable()) |
| .setStartLine(start.line_.ZeroBasedInt()) |
| .setStartColumn(start.column_.ZeroBasedInt()) |
| .setLength(text_length) |
| .setEndLine(end.line_.ZeroBasedInt()) |
| .setEndColumn(end.column_.ZeroBasedInt()) |
| .build(); |
| |
| if (HasSourceURL()) |
| result->setHasSourceURL(true); |
| |
| if (style_sheet->ownerNode()) { |
| result->setOwnerNode( |
| IdentifiersFactory::IntIdForNode(style_sheet->ownerNode())); |
| } |
| |
| String source_map_url_value = SourceMapURL(); |
| if (!source_map_url_value.IsEmpty()) |
| result->setSourceMapURL(source_map_url_value); |
| return result; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::CSS::Value>> |
| InspectorStyleSheet::SelectorsFromSource(CSSRuleSourceData* source_data, |
| const String& sheet_text) { |
| ScriptRegexp comment("/\\*[^]*?\\*/", kTextCaseSensitive, kMultilineEnabled); |
| auto result = std::make_unique<protocol::Array<protocol::CSS::Value>>(); |
| const Vector<SourceRange>& ranges = source_data->selector_ranges; |
| for (wtf_size_t i = 0, size = ranges.size(); i < size; ++i) { |
| const SourceRange& range = ranges.at(i); |
| String selector = sheet_text.Substring(range.start, range.length()); |
| |
| // We don't want to see any comments in the selector components, only the |
| // meaningful parts. |
| int match_length; |
| int offset = 0; |
| while ((offset = comment.Match(selector, offset, &match_length)) >= 0) |
| selector.replace(offset, match_length, ""); |
| |
| std::unique_ptr<protocol::CSS::Value> simple_selector = |
| protocol::CSS::Value::create() |
| .setText(selector.StripWhiteSpace()) |
| .build(); |
| simple_selector->setRange(BuildSourceRangeObject(range)); |
| result->emplace_back(std::move(simple_selector)); |
| } |
| return result; |
| } |
| |
| std::unique_ptr<protocol::CSS::SelectorList> |
| InspectorStyleSheet::BuildObjectForSelectorList(CSSStyleRule* rule) { |
| CSSRuleSourceData* source_data = SourceDataForRule(rule); |
| std::unique_ptr<protocol::Array<protocol::CSS::Value>> selectors; |
| |
| // This intentionally does not rely on the source data to avoid catching the |
| // trailing comments (before the declaration starting '{'). |
| String selector_text = rule->selectorText(); |
| |
| if (source_data) { |
| selectors = SelectorsFromSource(source_data, text_); |
| } else { |
| selectors = std::make_unique<protocol::Array<protocol::CSS::Value>>(); |
| const CSSSelectorList& selector_list = rule->GetStyleRule()->SelectorList(); |
| for (const CSSSelector* selector = selector_list.First(); selector; |
| selector = CSSSelectorList::Next(*selector)) { |
| selectors->emplace_back(protocol::CSS::Value::create() |
| .setText(selector->SelectorText()) |
| .build()); |
| } |
| } |
| return protocol::CSS::SelectorList::create() |
| .setSelectors(std::move(selectors)) |
| .setText(selector_text) |
| .build(); |
| } |
| |
| static bool CanBind(const String& origin) { |
| return origin != protocol::CSS::StyleSheetOriginEnum::UserAgent && |
| origin != protocol::CSS::StyleSheetOriginEnum::Injected; |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSRule> |
| InspectorStyleSheet::BuildObjectForRuleWithoutMedia(CSSStyleRule* rule) { |
| std::unique_ptr<protocol::CSS::CSSRule> result = |
| protocol::CSS::CSSRule::create() |
| .setSelectorList(BuildObjectForSelectorList(rule)) |
| .setOrigin(origin_) |
| .setStyle(BuildObjectForStyle(rule->style())) |
| .build(); |
| |
| if (CanBind(origin_)) { |
| if (!Id().IsEmpty()) |
| result->setStyleSheetId(Id()); |
| } |
| |
| return result; |
| } |
| |
| std::unique_ptr<protocol::CSS::RuleUsage> |
| InspectorStyleSheet::BuildObjectForRuleUsage(CSSRule* rule, bool was_used) { |
| CSSRuleSourceData* source_data = SourceDataForRule(rule); |
| |
| if (!source_data) |
| return nullptr; |
| |
| SourceRange whole_rule_range(source_data->rule_header_range.start, |
| source_data->rule_body_range.end + 1); |
| std::unique_ptr<protocol::CSS::RuleUsage> result = |
| protocol::CSS::RuleUsage::create() |
| .setStyleSheetId(Id()) |
| .setStartOffset(whole_rule_range.start) |
| .setEndOffset(whole_rule_range.end) |
| .setUsed(was_used) |
| .build(); |
| |
| return result; |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSKeyframeRule> |
| InspectorStyleSheet::BuildObjectForKeyframeRule( |
| CSSKeyframeRule* keyframe_rule) { |
| std::unique_ptr<protocol::CSS::Value> key_text = |
| protocol::CSS::Value::create().setText(keyframe_rule->keyText()).build(); |
| CSSRuleSourceData* source_data = SourceDataForRule(keyframe_rule); |
| if (source_data) |
| key_text->setRange(BuildSourceRangeObject(source_data->rule_header_range)); |
| std::unique_ptr<protocol::CSS::CSSKeyframeRule> result = |
| protocol::CSS::CSSKeyframeRule::create() |
| // TODO(samli): keyText() normalises 'from' and 'to' keyword values. |
| .setKeyText(std::move(key_text)) |
| .setOrigin(origin_) |
| .setStyle(BuildObjectForStyle(keyframe_rule->style())) |
| .build(); |
| if (CanBind(origin_) && !Id().IsEmpty()) |
| result->setStyleSheetId(Id()); |
| return result; |
| } |
| |
| bool InspectorStyleSheet::GetText(String* result) { |
| if (source_data_) { |
| *result = text_; |
| return true; |
| } |
| return false; |
| } |
| |
| std::unique_ptr<protocol::CSS::SourceRange> |
| InspectorStyleSheet::RuleHeaderSourceRange(CSSRule* rule) { |
| if (!source_data_) |
| return nullptr; |
| CSSRuleSourceData* source_data = SourceDataForRule(rule); |
| if (!source_data) |
| return nullptr; |
| return BuildSourceRangeObject(source_data->rule_header_range); |
| } |
| |
| std::unique_ptr<protocol::CSS::SourceRange> |
| InspectorStyleSheet::MediaQueryExpValueSourceRange( |
| CSSRule* rule, |
| wtf_size_t media_query_index, |
| wtf_size_t media_query_exp_index) { |
| if (!source_data_) |
| return nullptr; |
| CSSRuleSourceData* source_data = SourceDataForRule(rule); |
| if (!source_data || !source_data->HasMedia() || |
| media_query_index >= source_data->media_query_exp_value_ranges.size()) |
| return nullptr; |
| const Vector<SourceRange>& media_query_exp_data = |
| source_data->media_query_exp_value_ranges[media_query_index]; |
| if (media_query_exp_index >= media_query_exp_data.size()) |
| return nullptr; |
| return BuildSourceRangeObject(media_query_exp_data[media_query_exp_index]); |
| } |
| |
| InspectorStyle* InspectorStyleSheet::GetInspectorStyle( |
| CSSStyleDeclaration* style) { |
| return style ? MakeGarbageCollected<InspectorStyle>( |
| style, SourceDataForRule(style->parentRule()), this) |
| : nullptr; |
| } |
| |
| String InspectorStyleSheet::SourceURL() { |
| if (!source_url_.IsNull()) |
| return source_url_; |
| if (origin_ != protocol::CSS::StyleSheetOriginEnum::Regular) { |
| source_url_ = ""; |
| return source_url_; |
| } |
| |
| String style_sheet_text; |
| bool success = GetText(&style_sheet_text); |
| if (success) { |
| String comment_value = FindMagicComment(style_sheet_text, "sourceURL"); |
| if (!comment_value.IsEmpty()) { |
| source_url_ = comment_value; |
| return comment_value; |
| } |
| } |
| source_url_ = ""; |
| return source_url_; |
| } |
| |
| String InspectorStyleSheet::Url() { |
| // "sourceURL" is present only for regular rules, otherwise "origin" should be |
| // used in the frontend. |
| if (origin_ != protocol::CSS::StyleSheetOriginEnum::Regular) |
| return String(); |
| |
| CSSStyleSheet* style_sheet = PageStyleSheet(); |
| if (!style_sheet) |
| return String(); |
| |
| if (HasSourceURL()) |
| return SourceURL(); |
| |
| if (style_sheet->IsInline() && StartsAtZero()) |
| return String(); |
| |
| return FinalURL(); |
| } |
| |
| bool InspectorStyleSheet::HasSourceURL() { |
| return !SourceURL().IsEmpty(); |
| } |
| |
| bool InspectorStyleSheet::StartsAtZero() { |
| CSSStyleSheet* style_sheet = PageStyleSheet(); |
| if (!style_sheet) |
| return true; |
| |
| return style_sheet->StartPositionInSource() == |
| TextPosition::MinimumPosition(); |
| } |
| |
| String InspectorStyleSheet::SourceMapURL() { |
| if (origin_ != protocol::CSS::StyleSheetOriginEnum::Regular) |
| return String(); |
| |
| String style_sheet_text; |
| bool success = GetText(&style_sheet_text); |
| if (success) { |
| String comment_value = |
| FindMagicComment(style_sheet_text, "sourceMappingURL"); |
| if (!comment_value.IsEmpty()) |
| return comment_value; |
| } |
| return page_style_sheet_->Contents()->SourceMapURL(); |
| } |
| |
| const Document* InspectorStyleSheet::GetDocument() { |
| return CSSStyleSheet::SingleOwnerDocument( |
| InspectorStyleSheet::PageStyleSheet()); |
| } |
| |
| CSSRuleSourceData* InspectorStyleSheet::FindRuleByHeaderRange( |
| const SourceRange& source_range) { |
| if (!source_data_) |
| return nullptr; |
| |
| for (wtf_size_t i = 0; i < source_data_->size(); ++i) { |
| CSSRuleSourceData* rule_source_data = source_data_->at(i).Get(); |
| if (rule_source_data->rule_header_range.start == source_range.start && |
| rule_source_data->rule_header_range.end == source_range.end) { |
| return rule_source_data; |
| } |
| } |
| return nullptr; |
| } |
| |
| CSSRuleSourceData* InspectorStyleSheet::FindRuleByBodyRange( |
| const SourceRange& source_range) { |
| if (!source_data_) |
| return nullptr; |
| |
| for (wtf_size_t i = 0; i < source_data_->size(); ++i) { |
| CSSRuleSourceData* rule_source_data = source_data_->at(i).Get(); |
| if (rule_source_data->rule_body_range.start == source_range.start && |
| rule_source_data->rule_body_range.end == source_range.end) { |
| return rule_source_data; |
| } |
| } |
| return nullptr; |
| } |
| |
| CSSRule* InspectorStyleSheet::RuleForSourceData( |
| CSSRuleSourceData* source_data) { |
| if (!source_data_ || !source_data) |
| return nullptr; |
| |
| RemapSourceDataToCSSOMIfNecessary(); |
| |
| wtf_size_t index = source_data_->Find(source_data); |
| if (index == kNotFound) |
| return nullptr; |
| IndexMap::iterator it = source_data_to_rule_.find(index); |
| if (it == source_data_to_rule_.end()) |
| return nullptr; |
| |
| DCHECK_LT(it->value, cssom_flat_rules_.size()); |
| |
| // Check that CSSOM did not mutate this rule. |
| CSSRule* result = cssom_flat_rules_.at(it->value); |
| if (CanonicalCSSText(parsed_flat_rules_.at(index)) != |
| CanonicalCSSText(result)) |
| return nullptr; |
| return result; |
| } |
| |
| CSSRuleSourceData* InspectorStyleSheet::SourceDataForRule(CSSRule* rule) { |
| if (!source_data_ || !rule) |
| return nullptr; |
| |
| RemapSourceDataToCSSOMIfNecessary(); |
| |
| wtf_size_t index = cssom_flat_rules_.Find(rule); |
| if (index == kNotFound) |
| return nullptr; |
| IndexMap::iterator it = rule_to_source_data_.find(index); |
| if (it == rule_to_source_data_.end()) |
| return nullptr; |
| |
| DCHECK_LT(it->value, source_data_->size()); |
| |
| // Check that CSSOM did not mutate this rule. |
| CSSRule* parsed_rule = parsed_flat_rules_.at(it->value); |
| if (CanonicalCSSText(rule) != CanonicalCSSText(parsed_rule)) |
| return nullptr; |
| return source_data_->at(it->value).Get(); |
| } |
| |
| void InspectorStyleSheet::RemapSourceDataToCSSOMIfNecessary() { |
| CSSRuleVector cssom_rules; |
| CollectFlatRules(page_style_sheet_.Get(), &cssom_rules); |
| |
| if (cssom_rules.size() != cssom_flat_rules_.size()) { |
| MapSourceDataToCSSOM(); |
| return; |
| } |
| |
| for (wtf_size_t i = 0; i < cssom_flat_rules_.size(); ++i) { |
| if (cssom_flat_rules_.at(i) != cssom_rules.at(i)) { |
| MapSourceDataToCSSOM(); |
| return; |
| } |
| } |
| } |
| |
| void InspectorStyleSheet::MapSourceDataToCSSOM() { |
| rule_to_source_data_.clear(); |
| source_data_to_rule_.clear(); |
| |
| cssom_flat_rules_.clear(); |
| CSSRuleVector& cssom_rules = cssom_flat_rules_; |
| CollectFlatRules(page_style_sheet_.Get(), &cssom_rules); |
| |
| if (!source_data_) |
| return; |
| |
| CSSRuleVector& parsed_rules = parsed_flat_rules_; |
| |
| Vector<String> cssom_rules_text = Vector<String>(); |
| Vector<String> parsed_rules_text = Vector<String>(); |
| for (wtf_size_t i = 0; i < cssom_rules.size(); ++i) |
| cssom_rules_text.push_back(CanonicalCSSText(cssom_rules.at(i))); |
| for (wtf_size_t j = 0; j < parsed_rules.size(); ++j) |
| parsed_rules_text.push_back(CanonicalCSSText(parsed_rules.at(j))); |
| |
| Diff(cssom_rules_text, parsed_rules_text, &rule_to_source_data_, |
| &source_data_to_rule_); |
| } |
| |
| const CSSRuleVector& InspectorStyleSheet::FlatRules() { |
| RemapSourceDataToCSSOMIfNecessary(); |
| return cssom_flat_rules_; |
| } |
| |
| bool InspectorStyleSheet::ResourceStyleSheetText(String* result) { |
| if (origin_ == protocol::CSS::StyleSheetOriginEnum::Injected || |
| origin_ == protocol::CSS::StyleSheetOriginEnum::UserAgent) |
| return false; |
| |
| if (!page_style_sheet_->OwnerDocument()) |
| return false; |
| |
| KURL url(page_style_sheet_->href()); |
| if (page_style_sheet_->href() && |
| resource_container_->LoadStyleSheetContent(url, result)) |
| return true; |
| |
| bool base64_encoded; |
| bool success = network_agent_->FetchResourceContent( |
| page_style_sheet_->OwnerDocument(), url, result, &base64_encoded); |
| return success && !base64_encoded; |
| } |
| |
| Element* InspectorStyleSheet::OwnerStyleElement() { |
| Node* owner_node = page_style_sheet_->ownerNode(); |
| auto* owner_element = DynamicTo<Element>(owner_node); |
| if (!owner_element) |
| return nullptr; |
| |
| if (!IsA<HTMLStyleElement>(owner_element) && |
| !IsA<SVGStyleElement>(owner_element)) |
| return nullptr; |
| return owner_element; |
| } |
| |
| String InspectorStyleSheet::CollectStyleSheetRules() { |
| StringBuilder builder; |
| for (unsigned i = 0; i < page_style_sheet_->length(); i++) { |
| builder.Append(page_style_sheet_->item(i)->cssText()); |
| builder.Append('\n'); |
| } |
| return builder.ToString(); |
| } |
| |
| bool InspectorStyleSheet::CSSOMStyleSheetText(String* result) { |
| if (origin_ != protocol::CSS::StyleSheetOriginEnum::Regular) { |
| return false; |
| } |
| *result = CollectStyleSheetRules(); |
| return true; |
| } |
| |
| void InspectorStyleSheet::Reset() { |
| ResetLineEndings(); |
| if (source_data_) |
| source_data_->clear(); |
| cssom_flat_rules_.clear(); |
| parsed_flat_rules_.clear(); |
| rule_to_source_data_.clear(); |
| source_data_to_rule_.clear(); |
| } |
| |
| void InspectorStyleSheet::SyncTextIfNeeded() { |
| if (!marked_for_sync_) |
| return; |
| Reset(); |
| UpdateText(); |
| marked_for_sync_ = false; |
| } |
| |
| void InspectorStyleSheet::UpdateText() { |
| String text; |
| bool success = InspectorStyleSheetText(&text); |
| if (!success) |
| success = InlineStyleSheetText(&text); |
| if (!success) |
| success = ResourceStyleSheetText(&text); |
| if (!success) |
| success = CSSOMStyleSheetText(&text); |
| if (success) |
| InnerSetText(text, false); |
| } |
| |
| bool InspectorStyleSheet::IsMutable() const { |
| return page_style_sheet_->Contents()->IsMutable(); |
| } |
| |
| bool InspectorStyleSheet::InlineStyleSheetText(String* out) { |
| Element* owner_element = OwnerStyleElement(); |
| bool result = false; |
| if (!owner_element) |
| return result; |
| |
| result = resource_container_->LoadStyleElementContent( |
| DOMNodeIds::IdForNode(owner_element), out); |
| |
| if (!result) { |
| *out = owner_element->textContent(); |
| result = true; |
| } |
| |
| if (result && IsMutable()) { |
| ParseText(*out); |
| *out = MergeCSSOMRulesWithText(*out); |
| } |
| |
| return result; |
| } |
| |
| bool InspectorStyleSheet::InspectorStyleSheetText(String* result) { |
| if (origin_ != protocol::CSS::StyleSheetOriginEnum::Inspector) |
| return false; |
| if (!page_style_sheet_->OwnerDocument()) |
| return false; |
| if (resource_container_->LoadStyleElementContent( |
| DOMNodeIds::IdForNode(page_style_sheet_->OwnerDocument()), result)) |
| return true; |
| *result = ""; |
| return true; |
| } |
| |
| InspectorStyleSheetForInlineStyle::InspectorStyleSheetForInlineStyle( |
| Element* element, |
| Listener* listener) |
| : InspectorStyleSheetBase(listener), element_(element) { |
| DCHECK(element_); |
| } |
| |
| void InspectorStyleSheetForInlineStyle::DidModifyElementAttribute() { |
| inspector_style_.Clear(); |
| OnStyleSheetTextChanged(); |
| } |
| |
| bool InspectorStyleSheetForInlineStyle::SetText( |
| const String& text, |
| ExceptionState& exception_state) { |
| if (!VerifyStyleText(&element_->GetDocument(), text)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, |
| "Style text is not valid."); |
| return false; |
| } |
| |
| { |
| InspectorCSSAgent::InlineStyleOverrideScope override_scope( |
| &element_->GetExecutionContext()->GetSecurityContext()); |
| element_->setAttribute("style", AtomicString(text), exception_state); |
| } |
| if (!exception_state.HadException()) |
| OnStyleSheetTextChanged(); |
| return !exception_state.HadException(); |
| } |
| |
| bool InspectorStyleSheetForInlineStyle::GetText(String* result) { |
| *result = ElementStyleText(); |
| return true; |
| } |
| |
| InspectorStyle* InspectorStyleSheetForInlineStyle::GetInspectorStyle( |
| CSSStyleDeclaration* style) { |
| if (!inspector_style_) { |
| inspector_style_ = MakeGarbageCollected<InspectorStyle>( |
| element_->style(), RuleSourceData(), this); |
| } |
| |
| return inspector_style_; |
| } |
| |
| CSSRuleSourceData* InspectorStyleSheetForInlineStyle::RuleSourceData() { |
| const String& text = ElementStyleText(); |
| CSSRuleSourceData* rule_source_data = nullptr; |
| if (text.IsEmpty()) { |
| rule_source_data = |
| MakeGarbageCollected<CSSRuleSourceData>(StyleRule::kStyle); |
| rule_source_data->rule_body_range.start = 0; |
| rule_source_data->rule_body_range.end = 0; |
| } else { |
| CSSRuleSourceDataList* rule_source_data_result = |
| MakeGarbageCollected<CSSRuleSourceDataList>(); |
| StyleSheetHandler handler(text, &element_->GetDocument(), |
| rule_source_data_result); |
| CSSParser::ParseDeclarationListForInspector( |
| ParserContextForDocument(&element_->GetDocument()), text, handler); |
| rule_source_data = rule_source_data_result->front(); |
| } |
| return rule_source_data; |
| } |
| |
| CSSStyleDeclaration* InspectorStyleSheetForInlineStyle::InlineStyle() { |
| return element_->style(); |
| } |
| |
| const String& InspectorStyleSheetForInlineStyle::ElementStyleText() { |
| return element_->getAttribute("style").GetString(); |
| } |
| |
| void InspectorStyleSheetForInlineStyle::Trace(Visitor* visitor) const { |
| visitor->Trace(element_); |
| visitor->Trace(inspector_style_); |
| InspectorStyleSheetBase::Trace(visitor); |
| } |
| |
| const Document* InspectorStyleSheetForInlineStyle::GetDocument() { |
| return &InspectorStyleSheetForInlineStyle::element_->GetDocument(); |
| } |
| |
| } // namespace blink |