| /* |
| * 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 "core/inspector/InspectorCSSAgent.h" |
| |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/ExceptionStatePlaceholder.h" |
| #include "core/CSSPropertyNames.h" |
| #include "core/StylePropertyShorthand.h" |
| #include "core/animation/css/CSSAnimationData.h" |
| #include "core/css/CSSColorValue.h" |
| #include "core/css/CSSComputedStyleDeclaration.h" |
| #include "core/css/CSSDefaultStyleSheets.h" |
| #include "core/css/CSSGradientValue.h" |
| #include "core/css/CSSImportRule.h" |
| #include "core/css/CSSKeyframeRule.h" |
| #include "core/css/CSSMediaRule.h" |
| #include "core/css/CSSRule.h" |
| #include "core/css/CSSRuleList.h" |
| #include "core/css/CSSStyleRule.h" |
| #include "core/css/CSSStyleSheet.h" |
| #include "core/css/CSSVariableData.h" |
| #include "core/css/MediaList.h" |
| #include "core/css/MediaQuery.h" |
| #include "core/css/MediaValues.h" |
| #include "core/css/StylePropertySet.h" |
| #include "core/css/StyleRule.h" |
| #include "core/css/StyleSheet.h" |
| #include "core/css/StyleSheetContents.h" |
| #include "core/css/StyleSheetList.h" |
| #include "core/css/parser/CSSParser.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "core/dom/Node.h" |
| #include "core/dom/StyleChangeReason.h" |
| #include "core/dom/StyleEngine.h" |
| #include "core/dom/Text.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/html/HTMLHeadElement.h" |
| #include "core/html/VoidCallback.h" |
| #include "core/inspector/IdentifiersFactory.h" |
| #include "core/inspector/InspectedFrames.h" |
| #include "core/inspector/InspectorHistory.h" |
| #include "core/inspector/InspectorNetworkAgent.h" |
| #include "core/inspector/InspectorResourceContainer.h" |
| #include "core/inspector/InspectorResourceContentLoader.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/LayoutObjectInlines.h" |
| #include "core/layout/LayoutText.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/page/Page.h" |
| #include "core/style/StyleGeneratedImage.h" |
| #include "core/style/StyleImage.h" |
| #include "core/svg/SVGElement.h" |
| #include "platform/fonts/Font.h" |
| #include "platform/fonts/FontCache.h" |
| #include "platform/fonts/GlyphBuffer.h" |
| #include "platform/fonts/shaping/SimpleShaper.h" |
| #include "platform/text/TextRun.h" |
| #include "wtf/CurrentTime.h" |
| #include "wtf/text/CString.h" |
| #include "wtf/text/StringConcatenate.h" |
| |
| namespace { |
| |
| int s_frontendOperationCounter = 0; |
| |
| class FrontendOperationScope { |
| public: |
| FrontendOperationScope() { ++s_frontendOperationCounter; } |
| ~FrontendOperationScope() { --s_frontendOperationCounter; } |
| }; |
| |
| using namespace blink; |
| |
| String createShorthandValue(Document* document, |
| const String& shorthand, |
| const String& oldText, |
| const String& longhand, |
| const String& newValue) { |
| StyleSheetContents* styleSheetContents = |
| StyleSheetContents::create(strictCSSParserContext()); |
| String text = " div { " + shorthand + ": " + oldText + "; }"; |
| CSSParser::parseSheet(CSSParserContext(*document, nullptr), |
| styleSheetContents, text); |
| |
| CSSStyleSheet* styleSheet = CSSStyleSheet::create(styleSheetContents); |
| CSSStyleRule* rule = toCSSStyleRule(styleSheet->item(0)); |
| CSSStyleDeclaration* style = rule->style(); |
| TrackExceptionState exceptionState; |
| style->setProperty(longhand, newValue, style->getPropertyPriority(longhand), |
| exceptionState); |
| return style->getPropertyValue(shorthand); |
| } |
| |
| HeapVector<Member<CSSStyleRule>> filterDuplicateRules(CSSRuleList* ruleList) { |
| HeapVector<Member<CSSStyleRule>> uniqRules; |
| HeapHashSet<Member<CSSRule>> uniqRulesSet; |
| for (unsigned i = ruleList ? ruleList->length() : 0; i > 0; --i) { |
| CSSRule* rule = ruleList->item(i - 1); |
| if (!rule || rule->type() != CSSRule::kStyleRule || |
| uniqRulesSet.contains(rule)) |
| continue; |
| uniqRulesSet.add(rule); |
| uniqRules.append(toCSSStyleRule(rule)); |
| } |
| uniqRules.reverse(); |
| return uniqRules; |
| } |
| |
| // Get the elements which overlap the given rectangle. |
| HeapVector<Member<Element>> elementsFromRect(LayoutRect rect, |
| Document& document) { |
| HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | |
| HitTestRequest::ListBased | |
| HitTestRequest::PenetratingList | |
| HitTestRequest::IgnoreClipping); |
| |
| LayoutPoint center = rect.center(); |
| unsigned leftPadding, rightPadding, topPadding, bottomPadding; |
| leftPadding = rightPadding = (rect.width() / 2).toUnsigned(); |
| topPadding = bottomPadding = (rect.height() / 2).toUnsigned(); |
| HitTestResult result(request, center, topPadding, rightPadding, bottomPadding, |
| leftPadding); |
| document.frame()->contentLayoutItem().hitTest(result); |
| return document.elementsFromHitTestResult(result); |
| } |
| |
| // Blends the colors from the given gradient with the existing colors. |
| void blendWithColorsFromGradient(CSSGradientValue* gradient, |
| Vector<Color>& colors, |
| bool& foundNonTransparentColor, |
| bool& foundOpaqueColor, |
| const LayoutObject& layoutObject) { |
| Vector<Color> stopColors; |
| gradient->getStopColors(stopColors, layoutObject); |
| |
| if (colors.isEmpty()) { |
| colors.appendRange(stopColors.begin(), stopColors.end()); |
| } else { |
| if (colors.size() > 1) { |
| // Gradient on gradient is too complicated, bail out |
| colors.clear(); |
| return; |
| } |
| |
| Color existingColor = colors.first(); |
| colors.clear(); |
| for (auto stopColor : stopColors) { |
| foundNonTransparentColor = |
| foundNonTransparentColor || (stopColor.alpha() != 0); |
| colors.append(existingColor.blend(stopColor)); |
| } |
| } |
| foundOpaqueColor = |
| foundOpaqueColor || gradient->knownToBeOpaque(layoutObject); |
| } |
| |
| // Gets the colors from an image style, if one exists and it is a gradient. |
| void addColorsFromImageStyle(const ComputedStyle& style, |
| Vector<Color>& colors, |
| bool& foundOpaqueColor, |
| bool& foundNonTransparentColor, |
| const LayoutObject& layoutObject) { |
| const FillLayer& backgroundLayers = style.backgroundLayers(); |
| if (!backgroundLayers.hasImage()) |
| return; |
| |
| StyleImage* styleImage = backgroundLayers.image(); |
| // hasImage() does not always indicate that this is non-null |
| if (!styleImage) |
| return; |
| |
| if (!styleImage->isGeneratedImage()) { |
| // Make no assertions about the colors in non-generated images |
| colors.clear(); |
| foundOpaqueColor = false; |
| return; |
| } |
| |
| StyleGeneratedImage* genImage = toStyleGeneratedImage(styleImage); |
| CSSValue* imageCSS = genImage->cssValue(); |
| if (imageCSS->isGradientValue()) { |
| CSSGradientValue* gradient = toCSSGradientValue(imageCSS); |
| blendWithColorsFromGradient(gradient, colors, foundNonTransparentColor, |
| foundOpaqueColor, layoutObject); |
| } |
| return; |
| } |
| |
| // Get the background colors behind the given rect in the given document, by |
| // walking up all the elements returned by a hit test (but not going beyond |
| // |topElement|) covering the area of the rect, and blending their background |
| // colors. |
| bool getColorsFromRect(LayoutRect rect, |
| Document& document, |
| Element* topElement, |
| Vector<Color>& colors) { |
| HeapVector<Member<Element>> elementsUnderRect = |
| elementsFromRect(rect, document); |
| |
| bool foundOpaqueColor = false; |
| bool foundTopElement = false; |
| |
| for (auto e = elementsUnderRect.rbegin(); |
| !foundTopElement && e != elementsUnderRect.rend(); ++e) { |
| const Element* element = *e; |
| if (element == topElement) |
| foundTopElement = true; |
| |
| const LayoutObject* layoutObject = element->layoutObject(); |
| if (!layoutObject) |
| continue; |
| |
| if (isHTMLCanvasElement(element) || isHTMLEmbedElement(element) || |
| isHTMLImageElement(element) || isHTMLObjectElement(element) || |
| isHTMLPictureElement(element) || element->isSVGElement() || |
| isHTMLVideoElement(element)) { |
| colors.clear(); |
| foundOpaqueColor = false; |
| continue; |
| } |
| |
| const ComputedStyle* style = layoutObject->style(); |
| if (!style) |
| continue; |
| |
| Color backgroundColor = |
| style->visitedDependentColor(CSSPropertyBackgroundColor); |
| bool foundNonTransparentColor = false; |
| if (backgroundColor.alpha() != 0) { |
| foundNonTransparentColor = true; |
| if (colors.isEmpty()) { |
| if (!backgroundColor.hasAlpha()) |
| foundOpaqueColor = true; |
| colors.append(backgroundColor); |
| } else { |
| if (!backgroundColor.hasAlpha()) { |
| colors.clear(); |
| colors.append(backgroundColor); |
| foundOpaqueColor = true; |
| } else { |
| for (size_t i = 0; i < colors.size(); i++) |
| colors[i] = colors[i].blend(backgroundColor); |
| foundOpaqueColor = foundOpaqueColor || backgroundColor.hasAlpha(); |
| } |
| } |
| } |
| |
| addColorsFromImageStyle(*style, colors, foundOpaqueColor, |
| foundNonTransparentColor, *layoutObject); |
| |
| bool contains = foundTopElement || element->boundingBox().contains(rect); |
| if (!contains && foundNonTransparentColor) { |
| // Only return colors if some opaque element covers up this one. |
| colors.clear(); |
| foundOpaqueColor = false; |
| } |
| } |
| return foundOpaqueColor; |
| } |
| |
| } // namespace |
| |
| namespace CSSAgentState { |
| static const char cssAgentEnabled[] = "cssAgentEnabled"; |
| } |
| |
| typedef blink::protocol::CSS::Backend::EnableCallback EnableCallback; |
| |
| namespace blink { |
| |
| enum ForcePseudoClassFlags { |
| PseudoNone = 0, |
| PseudoHover = 1 << 0, |
| PseudoFocus = 1 << 1, |
| PseudoActive = 1 << 2, |
| PseudoVisited = 1 << 3 |
| }; |
| |
| static unsigned computePseudoClassMask( |
| std::unique_ptr<protocol::Array<String>> pseudoClassArray) { |
| DEFINE_STATIC_LOCAL(String, active, ("active")); |
| DEFINE_STATIC_LOCAL(String, hover, ("hover")); |
| DEFINE_STATIC_LOCAL(String, focus, ("focus")); |
| DEFINE_STATIC_LOCAL(String, visited, ("visited")); |
| if (!pseudoClassArray || !pseudoClassArray->length()) |
| return PseudoNone; |
| |
| unsigned result = PseudoNone; |
| for (size_t i = 0; i < pseudoClassArray->length(); ++i) { |
| String pseudoClass = pseudoClassArray->get(i); |
| if (pseudoClass == active) |
| result |= PseudoActive; |
| else if (pseudoClass == hover) |
| result |= PseudoHover; |
| else if (pseudoClass == focus) |
| result |= PseudoFocus; |
| else if (pseudoClass == visited) |
| result |= PseudoVisited; |
| } |
| |
| return result; |
| } |
| |
| class InspectorCSSAgent::StyleSheetAction : public InspectorHistory::Action { |
| WTF_MAKE_NONCOPYABLE(StyleSheetAction); |
| |
| public: |
| StyleSheetAction(const String& name) : InspectorHistory::Action(name) {} |
| |
| virtual std::unique_ptr<protocol::CSS::CSSStyle> takeSerializedStyle() { |
| return nullptr; |
| } |
| }; |
| |
| class InspectorCSSAgent::SetStyleSheetTextAction final |
| : public InspectorCSSAgent::StyleSheetAction { |
| WTF_MAKE_NONCOPYABLE(SetStyleSheetTextAction); |
| |
| public: |
| SetStyleSheetTextAction(InspectorStyleSheetBase* styleSheet, |
| const String& text) |
| : InspectorCSSAgent::StyleSheetAction("SetStyleSheetText"), |
| m_styleSheet(styleSheet), |
| m_text(text) {} |
| |
| bool perform(ExceptionState& exceptionState) override { |
| if (!m_styleSheet->getText(&m_oldText)) |
| return false; |
| return redo(exceptionState); |
| } |
| |
| bool undo(ExceptionState& exceptionState) override { |
| return m_styleSheet->setText(m_oldText, exceptionState); |
| } |
| |
| bool redo(ExceptionState& exceptionState) override { |
| return m_styleSheet->setText(m_text, exceptionState); |
| } |
| |
| String mergeId() override { |
| return String::format("SetStyleSheetText %s", |
| m_styleSheet->id().utf8().data()); |
| } |
| |
| void merge(Action* action) override { |
| ASSERT(action->mergeId() == mergeId()); |
| |
| SetStyleSheetTextAction* other = |
| static_cast<SetStyleSheetTextAction*>(action); |
| m_text = other->m_text; |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { |
| visitor->trace(m_styleSheet); |
| InspectorCSSAgent::StyleSheetAction::trace(visitor); |
| } |
| |
| private: |
| Member<InspectorStyleSheetBase> m_styleSheet; |
| String m_text; |
| String m_oldText; |
| }; |
| |
| class InspectorCSSAgent::ModifyRuleAction final |
| : public InspectorCSSAgent::StyleSheetAction { |
| WTF_MAKE_NONCOPYABLE(ModifyRuleAction); |
| |
| public: |
| enum Type { SetRuleSelector, SetStyleText, SetMediaRuleText, SetKeyframeKey }; |
| |
| ModifyRuleAction(Type type, |
| InspectorStyleSheet* styleSheet, |
| const SourceRange& range, |
| const String& text) |
| : InspectorCSSAgent::StyleSheetAction("ModifyRuleAction"), |
| m_styleSheet(styleSheet), |
| m_type(type), |
| m_newText(text), |
| m_oldRange(range), |
| m_cssRule(nullptr) {} |
| |
| bool perform(ExceptionState& exceptionState) override { |
| return redo(exceptionState); |
| } |
| |
| bool undo(ExceptionState& exceptionState) override { |
| switch (m_type) { |
| case SetRuleSelector: |
| return m_styleSheet->setRuleSelector(m_newRange, m_oldText, nullptr, |
| nullptr, exceptionState); |
| case SetStyleText: |
| return m_styleSheet->setStyleText(m_newRange, m_oldText, nullptr, |
| nullptr, exceptionState); |
| case SetMediaRuleText: |
| return m_styleSheet->setMediaRuleText(m_newRange, m_oldText, nullptr, |
| nullptr, exceptionState); |
| case SetKeyframeKey: |
| return m_styleSheet->setKeyframeKey(m_newRange, m_oldText, nullptr, |
| nullptr, exceptionState); |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| return false; |
| } |
| |
| bool redo(ExceptionState& exceptionState) override { |
| switch (m_type) { |
| case SetRuleSelector: |
| m_cssRule = m_styleSheet->setRuleSelector( |
| m_oldRange, m_newText, &m_newRange, &m_oldText, exceptionState); |
| break; |
| case SetStyleText: |
| m_cssRule = m_styleSheet->setStyleText( |
| m_oldRange, m_newText, &m_newRange, &m_oldText, exceptionState); |
| break; |
| case SetMediaRuleText: |
| m_cssRule = m_styleSheet->setMediaRuleText( |
| m_oldRange, m_newText, &m_newRange, &m_oldText, exceptionState); |
| break; |
| case SetKeyframeKey: |
| m_cssRule = m_styleSheet->setKeyframeKey( |
| m_oldRange, m_newText, &m_newRange, &m_oldText, exceptionState); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| return m_cssRule; |
| } |
| |
| CSSRule* takeRule() { |
| CSSRule* result = m_cssRule; |
| m_cssRule = nullptr; |
| return result; |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyle> takeSerializedStyle() override { |
| if (m_type != SetStyleText) |
| return nullptr; |
| CSSRule* rule = takeRule(); |
| if (rule->type() == CSSRule::kStyleRule) |
| return m_styleSheet->buildObjectForStyle(toCSSStyleRule(rule)->style()); |
| if (rule->type() == CSSRule::kKeyframeRule) |
| return m_styleSheet->buildObjectForStyle( |
| toCSSKeyframeRule(rule)->style()); |
| return nullptr; |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { |
| visitor->trace(m_styleSheet); |
| visitor->trace(m_cssRule); |
| InspectorCSSAgent::StyleSheetAction::trace(visitor); |
| } |
| |
| String mergeId() override { |
| return String::format("ModifyRuleAction:%d %s:%d", m_type, |
| m_styleSheet->id().utf8().data(), m_oldRange.start); |
| } |
| |
| bool isNoop() override { return m_oldText == m_newText; } |
| |
| void merge(Action* action) override { |
| ASSERT(action->mergeId() == mergeId()); |
| |
| ModifyRuleAction* other = static_cast<ModifyRuleAction*>(action); |
| m_newText = other->m_newText; |
| m_newRange = other->m_newRange; |
| } |
| |
| private: |
| Member<InspectorStyleSheet> m_styleSheet; |
| Type m_type; |
| String m_oldText; |
| String m_newText; |
| SourceRange m_oldRange; |
| SourceRange m_newRange; |
| Member<CSSRule> m_cssRule; |
| }; |
| |
| class InspectorCSSAgent::SetElementStyleAction final |
| : public InspectorCSSAgent::StyleSheetAction { |
| WTF_MAKE_NONCOPYABLE(SetElementStyleAction); |
| |
| public: |
| SetElementStyleAction(InspectorStyleSheetForInlineStyle* styleSheet, |
| const String& text) |
| : InspectorCSSAgent::StyleSheetAction("SetElementStyleAction"), |
| m_styleSheet(styleSheet), |
| m_text(text) {} |
| |
| bool perform(ExceptionState& exceptionState) override { |
| return redo(exceptionState); |
| } |
| |
| bool undo(ExceptionState& exceptionState) override { |
| return m_styleSheet->setText(m_oldText, exceptionState); |
| } |
| |
| bool redo(ExceptionState& exceptionState) override { |
| if (!m_styleSheet->getText(&m_oldText)) |
| return false; |
| return m_styleSheet->setText(m_text, exceptionState); |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { |
| visitor->trace(m_styleSheet); |
| InspectorCSSAgent::StyleSheetAction::trace(visitor); |
| } |
| |
| String mergeId() override { |
| return String::format("SetElementStyleAction:%s", |
| m_styleSheet->id().utf8().data()); |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyle> takeSerializedStyle() override { |
| return m_styleSheet->buildObjectForStyle(m_styleSheet->inlineStyle()); |
| } |
| |
| void merge(Action* action) override { |
| ASSERT(action->mergeId() == mergeId()); |
| |
| SetElementStyleAction* other = static_cast<SetElementStyleAction*>(action); |
| m_text = other->m_text; |
| } |
| |
| private: |
| Member<InspectorStyleSheetForInlineStyle> m_styleSheet; |
| String m_text; |
| String m_oldText; |
| }; |
| |
| class InspectorCSSAgent::AddRuleAction final |
| : public InspectorCSSAgent::StyleSheetAction { |
| WTF_MAKE_NONCOPYABLE(AddRuleAction); |
| |
| public: |
| AddRuleAction(InspectorStyleSheet* styleSheet, |
| const String& ruleText, |
| const SourceRange& location) |
| : InspectorCSSAgent::StyleSheetAction("AddRule"), |
| m_styleSheet(styleSheet), |
| m_ruleText(ruleText), |
| m_location(location) {} |
| |
| bool perform(ExceptionState& exceptionState) override { |
| return redo(exceptionState); |
| } |
| |
| bool undo(ExceptionState& exceptionState) override { |
| return m_styleSheet->deleteRule(m_addedRange, exceptionState); |
| } |
| |
| bool redo(ExceptionState& exceptionState) override { |
| m_cssRule = m_styleSheet->addRule(m_ruleText, m_location, &m_addedRange, |
| exceptionState); |
| if (exceptionState.hadException()) |
| return false; |
| return true; |
| } |
| |
| CSSStyleRule* takeRule() { |
| CSSStyleRule* result = m_cssRule; |
| m_cssRule = nullptr; |
| return result; |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { |
| visitor->trace(m_styleSheet); |
| visitor->trace(m_cssRule); |
| InspectorCSSAgent::StyleSheetAction::trace(visitor); |
| } |
| |
| private: |
| Member<InspectorStyleSheet> m_styleSheet; |
| Member<CSSStyleRule> m_cssRule; |
| String m_ruleText; |
| String m_oldText; |
| SourceRange m_location; |
| SourceRange m_addedRange; |
| }; |
| |
| // static |
| CSSStyleRule* InspectorCSSAgent::asCSSStyleRule(CSSRule* rule) { |
| if (!rule || rule->type() != CSSRule::kStyleRule) |
| return nullptr; |
| return toCSSStyleRule(rule); |
| } |
| |
| // static |
| CSSMediaRule* InspectorCSSAgent::asCSSMediaRule(CSSRule* rule) { |
| if (!rule || rule->type() != CSSRule::kMediaRule) |
| return nullptr; |
| return toCSSMediaRule(rule); |
| } |
| |
| InspectorCSSAgent::InspectorCSSAgent( |
| InspectorDOMAgent* domAgent, |
| InspectedFrames* inspectedFrames, |
| InspectorNetworkAgent* networkAgent, |
| InspectorResourceContentLoader* resourceContentLoader, |
| InspectorResourceContainer* resourceContainer) |
| : m_domAgent(domAgent), |
| m_inspectedFrames(inspectedFrames), |
| m_networkAgent(networkAgent), |
| m_resourceContentLoader(resourceContentLoader), |
| m_resourceContainer(resourceContainer), |
| m_resourceContentLoaderClientId(resourceContentLoader->createClientId()) { |
| } |
| |
| InspectorCSSAgent::~InspectorCSSAgent() {} |
| |
| void InspectorCSSAgent::restore() { |
| if (m_state->booleanProperty(CSSAgentState::cssAgentEnabled, false)) |
| wasEnabled(); |
| } |
| |
| void InspectorCSSAgent::flushPendingProtocolNotifications() { |
| if (!m_invalidatedDocuments.size()) |
| return; |
| HeapHashSet<Member<Document>> invalidatedDocuments; |
| m_invalidatedDocuments.swap(invalidatedDocuments); |
| for (Document* document : invalidatedDocuments) |
| updateActiveStyleSheets(document, ExistingFrontendRefresh); |
| } |
| |
| void InspectorCSSAgent::reset() { |
| m_idToInspectorStyleSheet.clear(); |
| m_idToInspectorStyleSheetForInlineStyle.clear(); |
| m_cssStyleSheetToInspectorStyleSheet.clear(); |
| m_documentToCSSStyleSheets.clear(); |
| m_invalidatedDocuments.clear(); |
| m_nodeToInspectorStyleSheet.clear(); |
| resetNonPersistentData(); |
| } |
| |
| void InspectorCSSAgent::resetNonPersistentData() { |
| resetPseudoStates(); |
| } |
| |
| void InspectorCSSAgent::enable(std::unique_ptr<EnableCallback> prpCallback) { |
| if (!m_domAgent->enabled()) { |
| prpCallback->sendFailure("DOM agent needs to be enabled first."); |
| return; |
| } |
| m_state->setBoolean(CSSAgentState::cssAgentEnabled, true); |
| m_resourceContentLoader->ensureResourcesContentLoaded( |
| m_resourceContentLoaderClientId, |
| WTF::bind(&InspectorCSSAgent::resourceContentLoaded, wrapPersistent(this), |
| passed(std::move(prpCallback)))); |
| } |
| |
| void InspectorCSSAgent::resourceContentLoaded( |
| std::unique_ptr<EnableCallback> callback) { |
| wasEnabled(); |
| callback->sendSuccess(); |
| } |
| |
| void InspectorCSSAgent::wasEnabled() { |
| if (!m_state->booleanProperty(CSSAgentState::cssAgentEnabled, false)) { |
| // We were disabled while fetching resources. |
| return; |
| } |
| |
| m_instrumentingAgents->addInspectorCSSAgent(this); |
| m_domAgent->setDOMListener(this); |
| HeapVector<Member<Document>> documents = m_domAgent->documents(); |
| for (Document* document : documents) |
| updateActiveStyleSheets(document, InitialFrontendLoad); |
| } |
| |
| void InspectorCSSAgent::disable(ErrorString*) { |
| reset(); |
| m_domAgent->setDOMListener(nullptr); |
| m_instrumentingAgents->removeInspectorCSSAgent(this); |
| m_state->setBoolean(CSSAgentState::cssAgentEnabled, false); |
| m_resourceContentLoader->cancel(m_resourceContentLoaderClientId); |
| } |
| |
| void InspectorCSSAgent::didCommitLoadForLocalFrame(LocalFrame* frame) { |
| if (frame == m_inspectedFrames->root()) |
| reset(); |
| } |
| |
| void InspectorCSSAgent::mediaQueryResultChanged() { |
| flushPendingProtocolNotifications(); |
| frontend()->mediaQueryResultChanged(); |
| } |
| |
| void InspectorCSSAgent::fontsUpdated() { |
| flushPendingProtocolNotifications(); |
| frontend()->fontsUpdated(); |
| } |
| |
| void InspectorCSSAgent::activeStyleSheetsUpdated(Document* document) { |
| m_invalidatedDocuments.add(document); |
| } |
| |
| void InspectorCSSAgent::updateActiveStyleSheets( |
| Document* document, |
| StyleSheetsUpdateType styleSheetsUpdateType) { |
| HeapVector<Member<CSSStyleSheet>> newSheetsVector; |
| InspectorCSSAgent::collectAllDocumentStyleSheets(document, newSheetsVector); |
| setActiveStyleSheets(document, newSheetsVector, styleSheetsUpdateType); |
| } |
| |
| void InspectorCSSAgent::setActiveStyleSheets( |
| Document* document, |
| const HeapVector<Member<CSSStyleSheet>>& allSheetsVector, |
| StyleSheetsUpdateType styleSheetsUpdateType) { |
| bool isInitialFrontendLoad = styleSheetsUpdateType == InitialFrontendLoad; |
| |
| HeapHashSet<Member<CSSStyleSheet>>* documentCSSStyleSheets = |
| m_documentToCSSStyleSheets.get(document); |
| if (!documentCSSStyleSheets) { |
| documentCSSStyleSheets = new HeapHashSet<Member<CSSStyleSheet>>(); |
| m_documentToCSSStyleSheets.set(document, documentCSSStyleSheets); |
| } |
| |
| HeapHashSet<Member<CSSStyleSheet>> removedSheets(*documentCSSStyleSheets); |
| HeapVector<Member<CSSStyleSheet>> addedSheets; |
| for (CSSStyleSheet* cssStyleSheet : allSheetsVector) { |
| if (removedSheets.contains(cssStyleSheet)) { |
| removedSheets.remove(cssStyleSheet); |
| if (isInitialFrontendLoad) |
| addedSheets.append(cssStyleSheet); |
| } else { |
| addedSheets.append(cssStyleSheet); |
| } |
| } |
| |
| for (CSSStyleSheet* cssStyleSheet : removedSheets) { |
| InspectorStyleSheet* inspectorStyleSheet = |
| m_cssStyleSheetToInspectorStyleSheet.get(cssStyleSheet); |
| ASSERT(inspectorStyleSheet); |
| |
| documentCSSStyleSheets->remove(cssStyleSheet); |
| if (m_idToInspectorStyleSheet.contains(inspectorStyleSheet->id())) { |
| String id = unbindStyleSheet(inspectorStyleSheet); |
| if (frontend() && !isInitialFrontendLoad) |
| frontend()->styleSheetRemoved(id); |
| } |
| } |
| |
| for (CSSStyleSheet* cssStyleSheet : addedSheets) { |
| bool isNew = isInitialFrontendLoad || |
| !m_cssStyleSheetToInspectorStyleSheet.contains(cssStyleSheet); |
| if (isNew) { |
| InspectorStyleSheet* newStyleSheet = bindStyleSheet(cssStyleSheet); |
| documentCSSStyleSheets->add(cssStyleSheet); |
| if (frontend()) |
| frontend()->styleSheetAdded( |
| newStyleSheet->buildObjectForStyleSheetInfo()); |
| } |
| } |
| |
| if (documentCSSStyleSheets->isEmpty()) |
| m_documentToCSSStyleSheets.remove(document); |
| } |
| |
| void InspectorCSSAgent::documentDetached(Document* document) { |
| m_invalidatedDocuments.remove(document); |
| setActiveStyleSheets(document, HeapVector<Member<CSSStyleSheet>>(), |
| ExistingFrontendRefresh); |
| } |
| |
| bool InspectorCSSAgent::forcePseudoState(Element* element, |
| CSSSelector::PseudoType pseudoType) { |
| if (m_nodeIdToForcedPseudoState.isEmpty()) |
| return false; |
| |
| int nodeId = m_domAgent->boundNodeId(element); |
| if (!nodeId) |
| return false; |
| |
| NodeIdToForcedPseudoState::iterator it = |
| m_nodeIdToForcedPseudoState.find(nodeId); |
| if (it == m_nodeIdToForcedPseudoState.end()) |
| return false; |
| |
| unsigned forcedPseudoState = it->value; |
| switch (pseudoType) { |
| case CSSSelector::PseudoActive: |
| return forcedPseudoState & PseudoActive; |
| case CSSSelector::PseudoFocus: |
| return forcedPseudoState & PseudoFocus; |
| case CSSSelector::PseudoHover: |
| return forcedPseudoState & PseudoHover; |
| case CSSSelector::PseudoVisited: |
| return forcedPseudoState & PseudoVisited; |
| default: |
| return false; |
| } |
| } |
| |
| void InspectorCSSAgent::getMediaQueries( |
| ErrorString* errorString, |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSMedia>>* medias) { |
| *medias = protocol::Array<protocol::CSS::CSSMedia>::create(); |
| for (auto& style : m_idToInspectorStyleSheet) { |
| InspectorStyleSheet* styleSheet = style.value; |
| collectMediaQueriesFromStyleSheet(styleSheet->pageStyleSheet(), |
| medias->get()); |
| const CSSRuleVector& flatRules = styleSheet->flatRules(); |
| for (unsigned i = 0; i < flatRules.size(); ++i) { |
| CSSRule* rule = flatRules.at(i).get(); |
| if (rule->type() == CSSRule::kMediaRule || |
| rule->type() == CSSRule::kImportRule) |
| collectMediaQueriesFromRule(rule, medias->get()); |
| } |
| } |
| } |
| |
| void InspectorCSSAgent::getMatchedStylesForNode( |
| ErrorString* errorString, |
| int nodeId, |
| Maybe<protocol::CSS::CSSStyle>* inlineStyle, |
| Maybe<protocol::CSS::CSSStyle>* attributesStyle, |
| Maybe<protocol::Array<protocol::CSS::RuleMatch>>* matchedCSSRules, |
| Maybe<protocol::Array<protocol::CSS::PseudoElementMatches>>* |
| pseudoIdMatches, |
| Maybe<protocol::Array<protocol::CSS::InheritedStyleEntry>>* |
| inheritedEntries, |
| Maybe<protocol::Array<protocol::CSS::CSSKeyframesRule>>* |
| cssKeyframesRules) { |
| Element* element = elementForId(errorString, nodeId); |
| if (!element) { |
| *errorString = "Node not found"; |
| return; |
| } |
| |
| Element* originalElement = element; |
| PseudoId elementPseudoId = element->getPseudoId(); |
| if (elementPseudoId) { |
| element = element->parentOrShadowHostElement(); |
| if (!element) { |
| *errorString = "Pseudo element has no parent"; |
| return; |
| } |
| } |
| |
| Document* ownerDocument = element->ownerDocument(); |
| // A non-active document has no styles. |
| if (!ownerDocument->isActive()) { |
| *errorString = "Document is not active"; |
| return; |
| } |
| |
| // FIXME: It's really gross for the inspector to reach in and access |
| // StyleResolver directly here. We need to provide the Inspector better APIs |
| // to get this information without grabbing at internal style classes! |
| |
| // Matched rules. |
| StyleResolver& styleResolver = ownerDocument->ensureStyleResolver(); |
| |
| element->updateDistribution(); |
| CSSRuleList* matchedRules = styleResolver.pseudoCSSRulesForElement( |
| element, elementPseudoId, StyleResolver::AllCSSRules); |
| *matchedCSSRules = |
| buildArrayForMatchedRuleList(matchedRules, originalElement, PseudoIdNone); |
| |
| // Pseudo elements. |
| if (elementPseudoId) |
| return; |
| |
| InspectorStyleSheetForInlineStyle* inlineStyleSheet = |
| asInspectorStyleSheet(element); |
| if (inlineStyleSheet) { |
| *inlineStyle = inlineStyleSheet->buildObjectForStyle(element->style()); |
| *attributesStyle = buildObjectForAttributesStyle(element); |
| } |
| |
| *pseudoIdMatches = |
| protocol::Array<protocol::CSS::PseudoElementMatches>::create(); |
| for (PseudoId pseudoId = FirstPublicPseudoId; |
| pseudoId < AfterLastInternalPseudoId; |
| pseudoId = static_cast<PseudoId>(pseudoId + 1)) { |
| CSSRuleList* matchedRules = styleResolver.pseudoCSSRulesForElement( |
| element, pseudoId, StyleResolver::AllCSSRules); |
| protocol::DOM::PseudoType pseudoType; |
| if (matchedRules && matchedRules->length() && |
| m_domAgent->getPseudoElementType(pseudoId, &pseudoType)) { |
| pseudoIdMatches->fromJust()->addItem( |
| protocol::CSS::PseudoElementMatches::create() |
| .setPseudoType(pseudoType) |
| .setMatches( |
| buildArrayForMatchedRuleList(matchedRules, element, pseudoId)) |
| .build()); |
| } |
| } |
| |
| // Inherited styles. |
| *inheritedEntries = |
| protocol::Array<protocol::CSS::InheritedStyleEntry>::create(); |
| Element* parentElement = element->parentOrShadowHostElement(); |
| while (parentElement) { |
| StyleResolver& parentStyleResolver = |
| parentElement->ownerDocument()->ensureStyleResolver(); |
| CSSRuleList* parentMatchedRules = parentStyleResolver.cssRulesForElement( |
| parentElement, StyleResolver::AllCSSRules); |
| std::unique_ptr<protocol::CSS::InheritedStyleEntry> entry = |
| protocol::CSS::InheritedStyleEntry::create() |
| .setMatchedCSSRules(buildArrayForMatchedRuleList( |
| parentMatchedRules, parentElement, PseudoIdNone)) |
| .build(); |
| if (parentElement->style() && parentElement->style()->length()) { |
| InspectorStyleSheetForInlineStyle* styleSheet = |
| asInspectorStyleSheet(parentElement); |
| if (styleSheet) |
| entry->setInlineStyle( |
| styleSheet->buildObjectForStyle(styleSheet->inlineStyle())); |
| } |
| |
| inheritedEntries->fromJust()->addItem(std::move(entry)); |
| parentElement = parentElement->parentOrShadowHostElement(); |
| } |
| |
| *cssKeyframesRules = animationsForNode(element); |
| } |
| |
| template <class CSSRuleCollection> |
| static CSSKeyframesRule* findKeyframesRule(CSSRuleCollection* cssRules, |
| StyleRuleKeyframes* keyframesRule) { |
| CSSKeyframesRule* result = 0; |
| for (unsigned j = 0; cssRules && j < cssRules->length() && !result; ++j) { |
| CSSRule* cssRule = cssRules->item(j); |
| if (cssRule->type() == CSSRule::kKeyframesRule) { |
| CSSKeyframesRule* cssStyleRule = toCSSKeyframesRule(cssRule); |
| if (cssStyleRule->keyframes() == keyframesRule) |
| result = cssStyleRule; |
| } else if (cssRule->type() == CSSRule::kImportRule) { |
| CSSImportRule* cssImportRule = toCSSImportRule(cssRule); |
| result = findKeyframesRule(cssImportRule->styleSheet(), keyframesRule); |
| } else { |
| result = findKeyframesRule(cssRule->cssRules(), keyframesRule); |
| } |
| } |
| return result; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSKeyframesRule>> |
| InspectorCSSAgent::animationsForNode(Element* element) { |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSKeyframesRule>> |
| cssKeyframesRules = |
| protocol::Array<protocol::CSS::CSSKeyframesRule>::create(); |
| Document* ownerDocument = element->ownerDocument(); |
| |
| StyleResolver& styleResolver = ownerDocument->ensureStyleResolver(); |
| RefPtr<ComputedStyle> style = styleResolver.styleForElement(element); |
| if (!style) |
| return cssKeyframesRules; |
| const CSSAnimationData* animationData = style->animations(); |
| for (size_t i = 0; animationData && i < animationData->nameList().size(); |
| ++i) { |
| AtomicString animationName(animationData->nameList()[i]); |
| if (animationName == CSSAnimationData::initialName()) |
| continue; |
| StyleRuleKeyframes* keyframesRule = |
| styleResolver.findKeyframesRule(element, animationName); |
| if (!keyframesRule) |
| continue; |
| |
| // Find CSSOM wrapper. |
| CSSKeyframesRule* cssKeyframesRule = nullptr; |
| for (CSSStyleSheet* styleSheet : |
| *m_documentToCSSStyleSheets.get(ownerDocument)) { |
| cssKeyframesRule = findKeyframesRule(styleSheet, keyframesRule); |
| if (cssKeyframesRule) |
| break; |
| } |
| if (!cssKeyframesRule) |
| continue; |
| |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSKeyframeRule>> keyframes = |
| protocol::Array<protocol::CSS::CSSKeyframeRule>::create(); |
| for (unsigned j = 0; j < cssKeyframesRule->length(); ++j) { |
| InspectorStyleSheet* inspectorStyleSheet = |
| bindStyleSheet(cssKeyframesRule->parentStyleSheet()); |
| keyframes->addItem(inspectorStyleSheet->buildObjectForKeyframeRule( |
| cssKeyframesRule->item(j))); |
| } |
| |
| InspectorStyleSheet* inspectorStyleSheet = |
| bindStyleSheet(cssKeyframesRule->parentStyleSheet()); |
| CSSRuleSourceData* sourceData = |
| inspectorStyleSheet->sourceDataForRule(cssKeyframesRule); |
| std::unique_ptr<protocol::CSS::Value> name = |
| protocol::CSS::Value::create() |
| .setText(cssKeyframesRule->name()) |
| .build(); |
| if (sourceData) |
| name->setRange(inspectorStyleSheet->buildSourceRangeObject( |
| sourceData->ruleHeaderRange)); |
| cssKeyframesRules->addItem(protocol::CSS::CSSKeyframesRule::create() |
| .setAnimationName(std::move(name)) |
| .setKeyframes(std::move(keyframes)) |
| .build()); |
| } |
| return cssKeyframesRules; |
| } |
| |
| void InspectorCSSAgent::getInlineStylesForNode( |
| ErrorString* errorString, |
| int nodeId, |
| Maybe<protocol::CSS::CSSStyle>* inlineStyle, |
| Maybe<protocol::CSS::CSSStyle>* attributesStyle) { |
| Element* element = elementForId(errorString, nodeId); |
| if (!element) |
| return; |
| |
| InspectorStyleSheetForInlineStyle* styleSheet = |
| asInspectorStyleSheet(element); |
| if (!styleSheet) |
| return; |
| |
| *inlineStyle = styleSheet->buildObjectForStyle(element->style()); |
| *attributesStyle = buildObjectForAttributesStyle(element); |
| } |
| |
| void InspectorCSSAgent::getComputedStyleForNode( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSComputedStyleProperty>>* |
| style) { |
| Node* node = m_domAgent->assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| |
| CSSComputedStyleDeclaration* computedStyleInfo = |
| CSSComputedStyleDeclaration::create(node, true); |
| *style = protocol::Array<protocol::CSS::CSSComputedStyleProperty>::create(); |
| for (int id = firstCSSProperty; id <= lastCSSProperty; ++id) { |
| CSSPropertyID propertyId = static_cast<CSSPropertyID>(id); |
| if (!CSSPropertyMetadata::isEnabledProperty(propertyId) || |
| isShorthandProperty(propertyId) || |
| CSSPropertyMetadata::isDescriptorOnly(propertyId)) |
| continue; |
| (*style)->addItem( |
| protocol::CSS::CSSComputedStyleProperty::create() |
| .setName(getPropertyNameString(propertyId)) |
| .setValue(computedStyleInfo->getPropertyValue(propertyId)) |
| .build()); |
| } |
| |
| std::unique_ptr<HashMap<AtomicString, RefPtr<CSSVariableData>>> variables = |
| computedStyleInfo->getVariables(); |
| |
| if (variables && !variables->isEmpty()) { |
| for (const auto& it : *variables) { |
| if (!it.value) |
| continue; |
| (*style)->addItem(protocol::CSS::CSSComputedStyleProperty::create() |
| .setName(it.key) |
| .setValue(it.value->tokenRange().serialize()) |
| .build()); |
| } |
| } |
| } |
| |
| void InspectorCSSAgent::collectPlatformFontsForLayoutObject( |
| LayoutObject* layoutObject, |
| HashCountedSet<std::pair<int, String>>* fontStats) { |
| if (!layoutObject->isText()) |
| return; |
| |
| FontCachePurgePreventer preventer; |
| LayoutText* layoutText = toLayoutText(layoutObject); |
| for (InlineTextBox* box = layoutText->firstTextBox(); box; |
| box = box->nextTextBox()) { |
| const ComputedStyle& style = layoutText->styleRef(box->isFirstLineStyle()); |
| const Font& font = style.font(); |
| TextRun run = box->constructTextRunForInspector(style); |
| TextRunPaintInfo paintInfo(run); |
| GlyphBuffer glyphBuffer; |
| font.buildGlyphBuffer(paintInfo, glyphBuffer); |
| for (unsigned i = 0; i < glyphBuffer.size(); ++i) { |
| const SimpleFontData* simpleFontData = glyphBuffer.fontDataAt(i); |
| String familyName = simpleFontData->platformData().fontFamilyName(); |
| if (familyName.isNull()) |
| familyName = ""; |
| fontStats->add( |
| std::make_pair(simpleFontData->isCustomFont() ? 1 : 0, familyName)); |
| } |
| } |
| } |
| |
| void InspectorCSSAgent::getPlatformFontsForNode( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::Array<protocol::CSS::PlatformFontUsage>>* |
| platformFonts) { |
| Node* node = m_domAgent->assertNode(errorString, nodeId); |
| if (!node) |
| return; |
| |
| HashCountedSet<std::pair<int, String>> fontStats; |
| LayoutObject* root = node->layoutObject(); |
| if (root) { |
| collectPlatformFontsForLayoutObject(root, &fontStats); |
| // Iterate upto two layers deep. |
| for (LayoutObject* child = root->slowFirstChild(); child; |
| child = child->nextSibling()) { |
| collectPlatformFontsForLayoutObject(child, &fontStats); |
| for (LayoutObject* child2 = child->slowFirstChild(); child2; |
| child2 = child2->nextSibling()) |
| collectPlatformFontsForLayoutObject(child2, &fontStats); |
| } |
| } |
| *platformFonts = protocol::Array<protocol::CSS::PlatformFontUsage>::create(); |
| for (auto& font : fontStats) { |
| std::pair<int, String>& fontDescription = font.key; |
| bool isCustomFont = fontDescription.first == 1; |
| String fontName = fontDescription.second; |
| (*platformFonts) |
| ->addItem(protocol::CSS::PlatformFontUsage::create() |
| .setFamilyName(fontName) |
| .setIsCustomFont(isCustomFont) |
| .setGlyphCount(font.value) |
| .build()); |
| } |
| } |
| |
| void InspectorCSSAgent::getStyleSheetText(ErrorString* errorString, |
| const String& styleSheetId, |
| String* result) { |
| InspectorStyleSheetBase* inspectorStyleSheet = |
| assertStyleSheetForId(errorString, styleSheetId); |
| if (!inspectorStyleSheet) |
| return; |
| |
| inspectorStyleSheet->getText(result); |
| } |
| |
| void InspectorCSSAgent::collectClassNames( |
| ErrorString* errorString, |
| const String& styleSheetId, |
| std::unique_ptr<protocol::Array<String>>* classNames) { |
| IdToInspectorStyleSheet::iterator it = |
| m_idToInspectorStyleSheet.find(styleSheetId); |
| if (it == m_idToInspectorStyleSheet.end()) { |
| *errorString = "No style sheet with given id found"; |
| return; |
| } |
| *classNames = it->value.get()->collectClassNames(); |
| } |
| |
| void InspectorCSSAgent::setStyleSheetText( |
| ErrorString* errorString, |
| const String& styleSheetId, |
| const String& text, |
| protocol::Maybe<String>* sourceMapURL) { |
| FrontendOperationScope scope; |
| InspectorStyleSheetBase* inspectorStyleSheet = |
| assertStyleSheetForId(errorString, styleSheetId); |
| if (!inspectorStyleSheet) { |
| *errorString = "Style sheet with id " + styleSheetId + " not found"; |
| return; |
| } |
| |
| TrackExceptionState exceptionState; |
| m_domAgent->history()->perform( |
| new SetStyleSheetTextAction(inspectorStyleSheet, text), exceptionState); |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| if (!inspectorStyleSheet->sourceMapURL().isEmpty()) |
| *sourceMapURL = inspectorStyleSheet->sourceMapURL(); |
| } |
| |
| static bool verifyRangeComponent(ErrorString* errorString, |
| bool valid, |
| const String& component) { |
| if (!valid) |
| *errorString = "range." + component + " must be a non-negative integer"; |
| return valid; |
| } |
| |
| static bool jsonRangeToSourceRange(ErrorString* errorString, |
| InspectorStyleSheetBase* inspectorStyleSheet, |
| protocol::CSS::SourceRange* range, |
| SourceRange* sourceRange) { |
| if (!verifyRangeComponent(errorString, range->getStartLine() >= 0, |
| "startLine") || |
| !verifyRangeComponent(errorString, range->getStartColumn() >= 0, |
| "startColumn") || |
| !verifyRangeComponent(errorString, range->getEndLine() >= 0, "endLine") || |
| !verifyRangeComponent(errorString, range->getEndColumn() >= 0, |
| "endColumn")) |
| return false; |
| |
| unsigned startOffset = 0; |
| unsigned endOffset = 0; |
| bool success = |
| inspectorStyleSheet->lineNumberAndColumnToOffset( |
| range->getStartLine(), range->getStartColumn(), &startOffset) && |
| inspectorStyleSheet->lineNumberAndColumnToOffset( |
| range->getEndLine(), range->getEndColumn(), &endOffset); |
| if (!success) { |
| *errorString = "Specified range is out of bounds"; |
| return false; |
| } |
| |
| if (startOffset > endOffset) { |
| *errorString = "Range start must not succeed its end"; |
| return false; |
| } |
| sourceRange->start = startOffset; |
| sourceRange->end = endOffset; |
| return true; |
| } |
| |
| void InspectorCSSAgent::setRuleSelector( |
| ErrorString* errorString, |
| const String& styleSheetId, |
| std::unique_ptr<protocol::CSS::SourceRange> range, |
| const String& selector, |
| std::unique_ptr<protocol::CSS::SelectorList>* result) { |
| FrontendOperationScope scope; |
| InspectorStyleSheet* inspectorStyleSheet = |
| assertInspectorStyleSheetForId(errorString, styleSheetId); |
| if (!inspectorStyleSheet) { |
| *errorString = "Stylesheet not found"; |
| return; |
| } |
| SourceRange selectorRange; |
| if (!jsonRangeToSourceRange(errorString, inspectorStyleSheet, range.get(), |
| &selectorRange)) |
| return; |
| |
| TrackExceptionState exceptionState; |
| ModifyRuleAction* action = |
| new ModifyRuleAction(ModifyRuleAction::SetRuleSelector, |
| inspectorStyleSheet, selectorRange, selector); |
| bool success = m_domAgent->history()->perform(action, exceptionState); |
| if (success) { |
| CSSStyleRule* rule = InspectorCSSAgent::asCSSStyleRule(action->takeRule()); |
| InspectorStyleSheet* inspectorStyleSheet = inspectorStyleSheetForRule(rule); |
| if (!inspectorStyleSheet) { |
| *errorString = "Failed to get inspector style sheet for rule."; |
| return; |
| } |
| *result = inspectorStyleSheet->buildObjectForSelectorList(rule); |
| } |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| } |
| |
| void InspectorCSSAgent::setKeyframeKey( |
| ErrorString* errorString, |
| const String& styleSheetId, |
| std::unique_ptr<protocol::CSS::SourceRange> range, |
| const String& keyText, |
| std::unique_ptr<protocol::CSS::Value>* result) { |
| FrontendOperationScope scope; |
| InspectorStyleSheet* inspectorStyleSheet = |
| assertInspectorStyleSheetForId(errorString, styleSheetId); |
| if (!inspectorStyleSheet) { |
| *errorString = "Stylesheet not found"; |
| return; |
| } |
| SourceRange keyRange; |
| if (!jsonRangeToSourceRange(errorString, inspectorStyleSheet, range.get(), |
| &keyRange)) |
| return; |
| |
| TrackExceptionState exceptionState; |
| ModifyRuleAction* action = new ModifyRuleAction( |
| ModifyRuleAction::SetKeyframeKey, inspectorStyleSheet, keyRange, keyText); |
| bool success = m_domAgent->history()->perform(action, exceptionState); |
| if (success) { |
| CSSKeyframeRule* rule = toCSSKeyframeRule(action->takeRule()); |
| InspectorStyleSheet* inspectorStyleSheet = |
| bindStyleSheet(rule->parentStyleSheet()); |
| if (!inspectorStyleSheet) { |
| *errorString = "Failed to get inspector style sheet for rule."; |
| return; |
| } |
| |
| CSSRuleSourceData* sourceData = |
| inspectorStyleSheet->sourceDataForRule(rule); |
| *result = protocol::CSS::Value::create() |
| .setText(rule->keyText()) |
| .setRange(inspectorStyleSheet->buildSourceRangeObject( |
| sourceData->ruleHeaderRange)) |
| .build(); |
| } |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| } |
| |
| bool InspectorCSSAgent::multipleStyleTextsActions( |
| ErrorString* errorString, |
| std::unique_ptr<protocol::Array<protocol::CSS::StyleDeclarationEdit>> edits, |
| HeapVector<Member<StyleSheetAction>>* actions) { |
| int n = edits->length(); |
| if (n == 0) { |
| *errorString = "Edits should not be empty"; |
| return false; |
| } |
| |
| for (int i = 0; i < n; ++i) { |
| protocol::CSS::StyleDeclarationEdit* edit = edits->get(i); |
| InspectorStyleSheetBase* inspectorStyleSheet = |
| assertStyleSheetForId(errorString, edit->getStyleSheetId()); |
| if (!inspectorStyleSheet) { |
| *errorString = |
| String::format("StyleSheet not found for edit #%d of %d", i + 1, n); |
| return false; |
| } |
| |
| SourceRange range; |
| if (!jsonRangeToSourceRange(errorString, inspectorStyleSheet, |
| edit->getRange(), &range)) |
| return false; |
| |
| if (inspectorStyleSheet->isInlineStyle()) { |
| InspectorStyleSheetForInlineStyle* inlineStyleSheet = |
| static_cast<InspectorStyleSheetForInlineStyle*>(inspectorStyleSheet); |
| SetElementStyleAction* action = |
| new SetElementStyleAction(inlineStyleSheet, edit->getText()); |
| actions->append(action); |
| } else { |
| ModifyRuleAction* action = new ModifyRuleAction( |
| ModifyRuleAction::SetStyleText, |
| static_cast<InspectorStyleSheet*>(inspectorStyleSheet), range, |
| edit->getText()); |
| actions->append(action); |
| } |
| } |
| return true; |
| } |
| |
| void InspectorCSSAgent::setStyleTexts( |
| ErrorString* errorString, |
| std::unique_ptr<protocol::Array<protocol::CSS::StyleDeclarationEdit>> edits, |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSStyle>>* result) { |
| FrontendOperationScope scope; |
| HeapVector<Member<StyleSheetAction>> actions; |
| if (!multipleStyleTextsActions(errorString, std::move(edits), &actions)) |
| return; |
| |
| TrackExceptionState exceptionState; |
| |
| int n = actions.size(); |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSStyle>> serializedStyles = |
| protocol::Array<protocol::CSS::CSSStyle>::create(); |
| for (int i = 0; i < n; ++i) { |
| Member<StyleSheetAction> action = actions.at(i); |
| bool success = action->perform(exceptionState); |
| if (!success) { |
| for (int j = i - 1; j >= 0; --j) { |
| Member<StyleSheetAction> revert = actions.at(j); |
| TrackExceptionState undoExceptionState; |
| revert->undo(undoExceptionState); |
| ASSERT(!undoExceptionState.hadException()); |
| } |
| *errorString = String::format( |
| "Failed applying edit #%d: %s", i, |
| InspectorDOMAgent::toErrorString(exceptionState).utf8().data()); |
| return; |
| } |
| serializedStyles->addItem(action->takeSerializedStyle()); |
| } |
| |
| for (int i = 0; i < n; ++i) { |
| Member<StyleSheetAction> action = actions.at(i); |
| m_domAgent->history()->appendPerformedAction(action); |
| } |
| *result = std::move(serializedStyles); |
| } |
| |
| CSSStyleDeclaration* InspectorCSSAgent::setStyleText( |
| ErrorString* errorString, |
| InspectorStyleSheetBase* inspectorStyleSheet, |
| const SourceRange& range, |
| const String& text) { |
| TrackExceptionState exceptionState; |
| if (inspectorStyleSheet->isInlineStyle()) { |
| InspectorStyleSheetForInlineStyle* inlineStyleSheet = |
| static_cast<InspectorStyleSheetForInlineStyle*>(inspectorStyleSheet); |
| SetElementStyleAction* action = |
| new SetElementStyleAction(inlineStyleSheet, text); |
| bool success = m_domAgent->history()->perform(action, exceptionState); |
| if (success) |
| return inlineStyleSheet->inlineStyle(); |
| } else { |
| ModifyRuleAction* action = new ModifyRuleAction( |
| ModifyRuleAction::SetStyleText, |
| static_cast<InspectorStyleSheet*>(inspectorStyleSheet), range, text); |
| bool success = m_domAgent->history()->perform(action, exceptionState); |
| if (success) { |
| CSSRule* rule = action->takeRule(); |
| if (rule->type() == CSSRule::kStyleRule) |
| return toCSSStyleRule(rule)->style(); |
| if (rule->type() == CSSRule::kKeyframeRule) |
| return toCSSKeyframeRule(rule)->style(); |
| } |
| } |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| return nullptr; |
| } |
| |
| void InspectorCSSAgent::setMediaText( |
| ErrorString* errorString, |
| const String& styleSheetId, |
| std::unique_ptr<protocol::CSS::SourceRange> range, |
| const String& text, |
| std::unique_ptr<protocol::CSS::CSSMedia>* result) { |
| FrontendOperationScope scope; |
| InspectorStyleSheet* inspectorStyleSheet = |
| assertInspectorStyleSheetForId(errorString, styleSheetId); |
| if (!inspectorStyleSheet) { |
| *errorString = "Stylesheet not found"; |
| return; |
| } |
| SourceRange textRange; |
| if (!jsonRangeToSourceRange(errorString, inspectorStyleSheet, range.get(), |
| &textRange)) |
| return; |
| |
| TrackExceptionState exceptionState; |
| ModifyRuleAction* action = new ModifyRuleAction( |
| ModifyRuleAction::SetMediaRuleText, inspectorStyleSheet, textRange, text); |
| bool success = m_domAgent->history()->perform(action, exceptionState); |
| if (success) { |
| CSSMediaRule* rule = InspectorCSSAgent::asCSSMediaRule(action->takeRule()); |
| String sourceURL = rule->parentStyleSheet()->contents()->baseURL(); |
| if (sourceURL.isEmpty()) |
| sourceURL = InspectorDOMAgent::documentURLString( |
| rule->parentStyleSheet()->ownerDocument()); |
| *result = buildMediaObject(rule->media(), MediaListSourceMediaRule, |
| sourceURL, rule->parentStyleSheet()); |
| } |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| } |
| |
| void InspectorCSSAgent::createStyleSheet( |
| ErrorString* errorString, |
| const String& frameId, |
| protocol::CSS::StyleSheetId* outStyleSheetId) { |
| LocalFrame* frame = IdentifiersFactory::frameById(m_inspectedFrames, frameId); |
| if (!frame) { |
| *errorString = "Frame not found"; |
| return; |
| } |
| |
| Document* document = frame->document(); |
| if (!document) { |
| *errorString = "Frame does not have a document"; |
| return; |
| } |
| |
| InspectorStyleSheet* inspectorStyleSheet = viaInspectorStyleSheet(document); |
| if (!inspectorStyleSheet) { |
| *errorString = "No target stylesheet found"; |
| return; |
| } |
| |
| updateActiveStyleSheets(document, ExistingFrontendRefresh); |
| |
| *outStyleSheetId = inspectorStyleSheet->id(); |
| } |
| |
| void InspectorCSSAgent::addRule( |
| ErrorString* errorString, |
| const String& styleSheetId, |
| const String& ruleText, |
| std::unique_ptr<protocol::CSS::SourceRange> location, |
| std::unique_ptr<protocol::CSS::CSSRule>* result) { |
| FrontendOperationScope scope; |
| InspectorStyleSheet* inspectorStyleSheet = |
| assertInspectorStyleSheetForId(errorString, styleSheetId); |
| if (!inspectorStyleSheet) |
| return; |
| SourceRange ruleLocation; |
| if (!jsonRangeToSourceRange(errorString, inspectorStyleSheet, location.get(), |
| &ruleLocation)) |
| return; |
| |
| TrackExceptionState exceptionState; |
| AddRuleAction* action = |
| new AddRuleAction(inspectorStyleSheet, ruleText, ruleLocation); |
| bool success = m_domAgent->history()->perform(action, exceptionState); |
| if (!success) { |
| *errorString = InspectorDOMAgent::toErrorString(exceptionState); |
| return; |
| } |
| |
| CSSStyleRule* rule = action->takeRule(); |
| *result = buildObjectForRule(rule); |
| } |
| |
| void InspectorCSSAgent::forcePseudoState( |
| ErrorString* errorString, |
| int nodeId, |
| std::unique_ptr<protocol::Array<String>> forcedPseudoClasses) { |
| Element* element = m_domAgent->assertElement(errorString, nodeId); |
| if (!element) |
| return; |
| |
| unsigned forcedPseudoState = |
| computePseudoClassMask(std::move(forcedPseudoClasses)); |
| NodeIdToForcedPseudoState::iterator it = |
| m_nodeIdToForcedPseudoState.find(nodeId); |
| unsigned currentForcedPseudoState = |
| it == m_nodeIdToForcedPseudoState.end() ? 0 : it->value; |
| bool needStyleRecalc = forcedPseudoState != currentForcedPseudoState; |
| if (!needStyleRecalc) |
| return; |
| |
| if (forcedPseudoState) |
| m_nodeIdToForcedPseudoState.set(nodeId, forcedPseudoState); |
| else |
| m_nodeIdToForcedPseudoState.remove(nodeId); |
| element->ownerDocument()->setNeedsStyleRecalc( |
| SubtreeStyleChange, |
| StyleChangeReasonForTracing::create(StyleChangeReason::Inspector)); |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSMedia> InspectorCSSAgent::buildMediaObject( |
| const MediaList* media, |
| MediaListSource mediaListSource, |
| const String& sourceURL, |
| CSSStyleSheet* parentStyleSheet) { |
| // Make certain compilers happy by initializing |source| up-front. |
| String source = protocol::CSS::CSSMedia::SourceEnum::InlineSheet; |
| switch (mediaListSource) { |
| case MediaListSourceMediaRule: |
| source = protocol::CSS::CSSMedia::SourceEnum::MediaRule; |
| break; |
| case MediaListSourceImportRule: |
| source = protocol::CSS::CSSMedia::SourceEnum::ImportRule; |
| break; |
| case MediaListSourceLinkedSheet: |
| source = protocol::CSS::CSSMedia::SourceEnum::LinkedSheet; |
| break; |
| case MediaListSourceInlineSheet: |
| source = protocol::CSS::CSSMedia::SourceEnum::InlineSheet; |
| break; |
| } |
| |
| const MediaQuerySet* queries = media->queries(); |
| const HeapVector<Member<MediaQuery>>& queryVector = queries->queryVector(); |
| LocalFrame* frame = nullptr; |
| if (parentStyleSheet) { |
| if (Document* document = parentStyleSheet->ownerDocument()) |
| frame = document->frame(); |
| } |
| MediaQueryEvaluator* mediaEvaluator = new MediaQueryEvaluator(frame); |
| |
| InspectorStyleSheet* inspectorStyleSheet = |
| parentStyleSheet |
| ? m_cssStyleSheetToInspectorStyleSheet.get(parentStyleSheet) |
| : nullptr; |
| std::unique_ptr<protocol::Array<protocol::CSS::MediaQuery>> mediaListArray = |
| protocol::Array<protocol::CSS::MediaQuery>::create(); |
| MediaValues* mediaValues = MediaValues::createDynamicIfFrameExists(frame); |
| bool hasMediaQueryItems = false; |
| for (size_t i = 0; i < queryVector.size(); ++i) { |
| MediaQuery* query = queryVector.at(i).get(); |
| const ExpressionHeapVector& expressions = query->expressions(); |
| std::unique_ptr<protocol::Array<protocol::CSS::MediaQueryExpression>> |
| expressionArray = |
| protocol::Array<protocol::CSS::MediaQueryExpression>::create(); |
| bool hasExpressionItems = false; |
| for (size_t j = 0; j < expressions.size(); ++j) { |
| MediaQueryExp* mediaQueryExp = expressions.at(j).get(); |
| MediaQueryExpValue expValue = mediaQueryExp->expValue(); |
| if (!expValue.isValue) |
| continue; |
| const char* valueName = |
| CSSPrimitiveValue::unitTypeToString(expValue.unit); |
| std::unique_ptr<protocol::CSS::MediaQueryExpression> |
| mediaQueryExpression = protocol::CSS::MediaQueryExpression::create() |
| .setValue(expValue.value) |
| .setUnit(String(valueName)) |
| .setFeature(mediaQueryExp->mediaFeature()) |
| .build(); |
| |
| if (inspectorStyleSheet && media->parentRule()) |
| mediaQueryExpression->setValueRange( |
| inspectorStyleSheet->mediaQueryExpValueSourceRange( |
| media->parentRule(), i, j)); |
| |
| int computedLength; |
| if (mediaValues->computeLength(expValue.value, expValue.unit, |
| computedLength)) |
| mediaQueryExpression->setComputedLength(computedLength); |
| |
| expressionArray->addItem(std::move(mediaQueryExpression)); |
| hasExpressionItems = true; |
| } |
| if (!hasExpressionItems) |
| continue; |
| std::unique_ptr<protocol::CSS::MediaQuery> mediaQuery = |
| protocol::CSS::MediaQuery::create() |
| .setActive(mediaEvaluator->eval(query, nullptr)) |
| .setExpressions(std::move(expressionArray)) |
| .build(); |
| mediaListArray->addItem(std::move(mediaQuery)); |
| hasMediaQueryItems = true; |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSMedia> mediaObject = |
| protocol::CSS::CSSMedia::create() |
| .setText(media->mediaText()) |
| .setSource(source) |
| .build(); |
| if (hasMediaQueryItems) |
| mediaObject->setMediaList(std::move(mediaListArray)); |
| |
| if (inspectorStyleSheet && mediaListSource != MediaListSourceLinkedSheet) |
| mediaObject->setStyleSheetId(inspectorStyleSheet->id()); |
| |
| if (!sourceURL.isEmpty()) { |
| mediaObject->setSourceURL(sourceURL); |
| |
| CSSRule* parentRule = media->parentRule(); |
| if (!parentRule) |
| return mediaObject; |
| InspectorStyleSheet* inspectorStyleSheet = |
| bindStyleSheet(parentRule->parentStyleSheet()); |
| mediaObject->setRange( |
| inspectorStyleSheet->ruleHeaderSourceRange(parentRule)); |
| } |
| return mediaObject; |
| } |
| |
| void InspectorCSSAgent::collectMediaQueriesFromStyleSheet( |
| CSSStyleSheet* styleSheet, |
| protocol::Array<protocol::CSS::CSSMedia>* mediaArray) { |
| MediaList* mediaList = styleSheet->media(); |
| String sourceURL; |
| if (mediaList && mediaList->length()) { |
| Document* doc = styleSheet->ownerDocument(); |
| if (doc) |
| sourceURL = doc->url(); |
| else if (!styleSheet->contents()->baseURL().isEmpty()) |
| sourceURL = styleSheet->contents()->baseURL(); |
| else |
| sourceURL = ""; |
| mediaArray->addItem(buildMediaObject( |
| mediaList, styleSheet->ownerNode() ? MediaListSourceLinkedSheet |
| : MediaListSourceInlineSheet, |
| sourceURL, styleSheet)); |
| } |
| } |
| |
| void InspectorCSSAgent::collectMediaQueriesFromRule( |
| CSSRule* rule, |
| protocol::Array<protocol::CSS::CSSMedia>* mediaArray) { |
| MediaList* mediaList; |
| String sourceURL; |
| CSSStyleSheet* parentStyleSheet = nullptr; |
| bool isMediaRule = true; |
| if (rule->type() == CSSRule::kMediaRule) { |
| CSSMediaRule* mediaRule = toCSSMediaRule(rule); |
| mediaList = mediaRule->media(); |
| parentStyleSheet = mediaRule->parentStyleSheet(); |
| } else if (rule->type() == CSSRule::kImportRule) { |
| CSSImportRule* importRule = toCSSImportRule(rule); |
| mediaList = importRule->media(); |
| parentStyleSheet = importRule->parentStyleSheet(); |
| isMediaRule = false; |
| } else { |
| mediaList = nullptr; |
| } |
| |
| if (parentStyleSheet) { |
| sourceURL = parentStyleSheet->contents()->baseURL(); |
| if (sourceURL.isEmpty()) |
| sourceURL = InspectorDOMAgent::documentURLString( |
| parentStyleSheet->ownerDocument()); |
| } else { |
| sourceURL = ""; |
| } |
| |
| if (mediaList && mediaList->length()) |
| mediaArray->addItem(buildMediaObject( |
| mediaList, |
| isMediaRule ? MediaListSourceMediaRule : MediaListSourceImportRule, |
| sourceURL, parentStyleSheet)); |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSMedia>> |
| InspectorCSSAgent::buildMediaListChain(CSSRule* rule) { |
| if (!rule) |
| return nullptr; |
| std::unique_ptr<protocol::Array<protocol::CSS::CSSMedia>> mediaArray = |
| protocol::Array<protocol::CSS::CSSMedia>::create(); |
| CSSRule* parentRule = rule; |
| while (parentRule) { |
| collectMediaQueriesFromRule(parentRule, mediaArray.get()); |
| if (parentRule->parentRule()) { |
| parentRule = parentRule->parentRule(); |
| } else { |
| CSSStyleSheet* styleSheet = parentRule->parentStyleSheet(); |
| while (styleSheet) { |
| collectMediaQueriesFromStyleSheet(styleSheet, mediaArray.get()); |
| parentRule = styleSheet->ownerRule(); |
| if (parentRule) |
| break; |
| styleSheet = styleSheet->parentStyleSheet(); |
| } |
| } |
| } |
| return mediaArray; |
| } |
| |
| InspectorStyleSheetForInlineStyle* InspectorCSSAgent::asInspectorStyleSheet( |
| Element* element) { |
| NodeToInspectorStyleSheet::iterator it = |
| m_nodeToInspectorStyleSheet.find(element); |
| if (it != m_nodeToInspectorStyleSheet.end()) |
| return it->value.get(); |
| |
| CSSStyleDeclaration* style = element->style(); |
| if (!style) |
| return nullptr; |
| |
| InspectorStyleSheetForInlineStyle* inspectorStyleSheet = |
| InspectorStyleSheetForInlineStyle::create(element, this); |
| m_idToInspectorStyleSheetForInlineStyle.set(inspectorStyleSheet->id(), |
| inspectorStyleSheet); |
| m_nodeToInspectorStyleSheet.set(element, inspectorStyleSheet); |
| return inspectorStyleSheet; |
| } |
| |
| Element* InspectorCSSAgent::elementForId(ErrorString* errorString, int nodeId) { |
| Node* node = m_domAgent->nodeForId(nodeId); |
| if (!node) { |
| *errorString = "No node with given id found"; |
| return nullptr; |
| } |
| if (!node->isElementNode()) { |
| *errorString = "Not an element node"; |
| return nullptr; |
| } |
| return toElement(node); |
| } |
| |
| // static |
| void InspectorCSSAgent::collectAllDocumentStyleSheets( |
| Document* document, |
| HeapVector<Member<CSSStyleSheet>>& result) { |
| const HeapVector<Member<CSSStyleSheet>> activeStyleSheets = |
| document->styleEngine().activeStyleSheetsForInspector(); |
| for (const auto& style : activeStyleSheets) { |
| CSSStyleSheet* styleSheet = style.get(); |
| InspectorCSSAgent::collectStyleSheets(styleSheet, result); |
| } |
| } |
| |
| // static |
| void InspectorCSSAgent::collectStyleSheets( |
| CSSStyleSheet* styleSheet, |
| HeapVector<Member<CSSStyleSheet>>& result) { |
| result.append(styleSheet); |
| for (unsigned i = 0, size = styleSheet->length(); i < size; ++i) { |
| CSSRule* rule = styleSheet->item(i); |
| if (rule->type() == CSSRule::kImportRule) { |
| CSSStyleSheet* importedStyleSheet = toCSSImportRule(rule)->styleSheet(); |
| if (importedStyleSheet) |
| InspectorCSSAgent::collectStyleSheets(importedStyleSheet, result); |
| } |
| } |
| } |
| |
| InspectorStyleSheet* InspectorCSSAgent::bindStyleSheet( |
| CSSStyleSheet* styleSheet) { |
| InspectorStyleSheet* inspectorStyleSheet = |
| m_cssStyleSheetToInspectorStyleSheet.get(styleSheet); |
| if (!inspectorStyleSheet) { |
| Document* document = styleSheet->ownerDocument(); |
| inspectorStyleSheet = InspectorStyleSheet::create( |
| m_networkAgent, styleSheet, detectOrigin(styleSheet, document), |
| InspectorDOMAgent::documentURLString(document), this, |
| m_resourceContainer); |
| m_idToInspectorStyleSheet.set(inspectorStyleSheet->id(), |
| inspectorStyleSheet); |
| m_cssStyleSheetToInspectorStyleSheet.set(styleSheet, inspectorStyleSheet); |
| } |
| return inspectorStyleSheet; |
| } |
| |
| String InspectorCSSAgent::styleSheetId(CSSStyleSheet* styleSheet) { |
| return bindStyleSheet(styleSheet)->id(); |
| } |
| |
| String InspectorCSSAgent::unbindStyleSheet( |
| InspectorStyleSheet* inspectorStyleSheet) { |
| String id = inspectorStyleSheet->id(); |
| m_idToInspectorStyleSheet.remove(id); |
| if (inspectorStyleSheet->pageStyleSheet()) |
| m_cssStyleSheetToInspectorStyleSheet.remove( |
| inspectorStyleSheet->pageStyleSheet()); |
| return id; |
| } |
| |
| InspectorStyleSheet* InspectorCSSAgent::inspectorStyleSheetForRule( |
| CSSStyleRule* rule) { |
| if (!rule) |
| return nullptr; |
| |
| // CSSRules returned by StyleResolver::pseudoCSSRulesForElement lack parent |
| // pointers if they are coming from user agent stylesheets. To work around |
| // this issue, we use CSSOM wrapper created by inspector. |
| if (!rule->parentStyleSheet()) { |
| if (!m_inspectorUserAgentStyleSheet) |
| m_inspectorUserAgentStyleSheet = CSSStyleSheet::create( |
| CSSDefaultStyleSheets::instance().defaultStyleSheet()); |
| rule->setParentStyleSheet(m_inspectorUserAgentStyleSheet.get()); |
| } |
| return bindStyleSheet(rule->parentStyleSheet()); |
| } |
| |
| InspectorStyleSheet* InspectorCSSAgent::viaInspectorStyleSheet( |
| Document* document) { |
| if (!document) |
| return nullptr; |
| |
| if (!document->isHTMLDocument() && !document->isSVGDocument()) |
| return nullptr; |
| |
| CSSStyleSheet& inspectorSheet = |
| document->styleEngine().ensureInspectorStyleSheet(); |
| |
| flushPendingProtocolNotifications(); |
| |
| return m_cssStyleSheetToInspectorStyleSheet.get(&inspectorSheet); |
| } |
| |
| InspectorStyleSheet* InspectorCSSAgent::assertInspectorStyleSheetForId( |
| ErrorString* errorString, |
| const String& styleSheetId) { |
| IdToInspectorStyleSheet::iterator it = |
| m_idToInspectorStyleSheet.find(styleSheetId); |
| if (it == m_idToInspectorStyleSheet.end()) { |
| *errorString = "No style sheet with given id found"; |
| return nullptr; |
| } |
| return it->value.get(); |
| } |
| |
| InspectorStyleSheetBase* InspectorCSSAgent::assertStyleSheetForId( |
| ErrorString* errorString, |
| const String& styleSheetId) { |
| ErrorString placeholder; |
| InspectorStyleSheetBase* result = |
| assertInspectorStyleSheetForId(&placeholder, styleSheetId); |
| if (result) |
| return result; |
| IdToInspectorStyleSheetForInlineStyle::iterator it = |
| m_idToInspectorStyleSheetForInlineStyle.find(styleSheetId); |
| if (it == m_idToInspectorStyleSheetForInlineStyle.end()) { |
| *errorString = "No style sheet with given id found"; |
| return nullptr; |
| } |
| return it->value.get(); |
| } |
| |
| protocol::CSS::StyleSheetOrigin InspectorCSSAgent::detectOrigin( |
| CSSStyleSheet* pageStyleSheet, |
| Document* ownerDocument) { |
| DCHECK(pageStyleSheet); |
| |
| if (!pageStyleSheet->ownerNode() && pageStyleSheet->href().isEmpty()) |
| return protocol::CSS::StyleSheetOriginEnum::UserAgent; |
| |
| if (pageStyleSheet->ownerNode() && |
| pageStyleSheet->ownerNode()->isDocumentNode()) { |
| if (pageStyleSheet == ownerDocument->styleEngine().inspectorStyleSheet()) |
| return protocol::CSS::StyleSheetOriginEnum::Inspector; |
| return protocol::CSS::StyleSheetOriginEnum::Injected; |
| } |
| return protocol::CSS::StyleSheetOriginEnum::Regular; |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSRule> InspectorCSSAgent::buildObjectForRule( |
| CSSStyleRule* rule) { |
| InspectorStyleSheet* inspectorStyleSheet = inspectorStyleSheetForRule(rule); |
| if (!inspectorStyleSheet) |
| return nullptr; |
| |
| std::unique_ptr<protocol::CSS::CSSRule> result = |
| inspectorStyleSheet->buildObjectForRuleWithoutMedia(rule); |
| result->setMedia(buildMediaListChain(rule)); |
| return result; |
| } |
| |
| static inline bool matchesPseudoElement(const CSSSelector* selector, |
| PseudoId elementPseudoId) { |
| // According to http://www.w3.org/TR/css3-selectors/#pseudo-elements, "Only |
| // one pseudo-element may appear per selector." |
| // As such, check the last selector in the tag history. |
| for (; !selector->isLastInTagHistory(); ++selector) { |
| } |
| PseudoId selectorPseudoId = CSSSelector::pseudoId(selector->getPseudoType()); |
| |
| // FIXME: This only covers the case of matching pseudo-element selectors |
| // against PseudoElements. We should come up with a solution for matching |
| // pseudo-element selectors against ordinary Elements, too. |
| return selectorPseudoId == elementPseudoId; |
| } |
| |
| std::unique_ptr<protocol::Array<protocol::CSS::RuleMatch>> |
| InspectorCSSAgent::buildArrayForMatchedRuleList(CSSRuleList* ruleList, |
| Element* element, |
| PseudoId matchesForPseudoId) { |
| std::unique_ptr<protocol::Array<protocol::CSS::RuleMatch>> result = |
| protocol::Array<protocol::CSS::RuleMatch>::create(); |
| if (!ruleList) |
| return result; |
| |
| HeapVector<Member<CSSStyleRule>> uniqRules = filterDuplicateRules(ruleList); |
| for (unsigned i = 0; i < uniqRules.size(); ++i) { |
| CSSStyleRule* rule = uniqRules.at(i).get(); |
| std::unique_ptr<protocol::CSS::CSSRule> ruleObject = |
| buildObjectForRule(rule); |
| if (!ruleObject) |
| continue; |
| std::unique_ptr<protocol::Array<int>> matchingSelectors = |
| protocol::Array<int>::create(); |
| const CSSSelectorList& selectorList = rule->styleRule()->selectorList(); |
| long index = 0; |
| PseudoId elementPseudoId = |
| matchesForPseudoId ? matchesForPseudoId : element->getPseudoId(); |
| for (const CSSSelector* selector = selectorList.first(); selector; |
| selector = CSSSelectorList::next(*selector)) { |
| const CSSSelector* firstTagHistorySelector = selector; |
| bool matched = false; |
| if (elementPseudoId) |
| matched = matchesPseudoElement( |
| selector, elementPseudoId); // Modifies |selector|. |
| else |
| matched = element->matches(firstTagHistorySelector->selectorText(), |
| IGNORE_EXCEPTION); |
| if (matched) |
| matchingSelectors->addItem(index); |
| ++index; |
| } |
| result->addItem(protocol::CSS::RuleMatch::create() |
| .setRule(std::move(ruleObject)) |
| .setMatchingSelectors(std::move(matchingSelectors)) |
| .build()); |
| } |
| |
| return result; |
| } |
| |
| std::unique_ptr<protocol::CSS::CSSStyle> |
| InspectorCSSAgent::buildObjectForAttributesStyle(Element* element) { |
| if (!element->isStyledElement()) |
| return nullptr; |
| |
| // FIXME: Ugliness below. |
| StylePropertySet* attributeStyle = |
| const_cast<StylePropertySet*>(element->presentationAttributeStyle()); |
| if (!attributeStyle) |
| return nullptr; |
| |
| MutableStylePropertySet* mutableAttributeStyle = |
| toMutableStylePropertySet(attributeStyle); |
| |
| InspectorStyle* inspectorStyle = InspectorStyle::create( |
| mutableAttributeStyle->ensureCSSStyleDeclaration(), nullptr, nullptr); |
| return inspectorStyle->buildObjectForStyle(); |
| } |
| |
| void InspectorCSSAgent::didRemoveDocument(Document* document) {} |
| |
| void InspectorCSSAgent::didRemoveDOMNode(Node* node) { |
| if (!node) |
| return; |
| |
| int nodeId = m_domAgent->boundNodeId(node); |
| if (nodeId) |
| m_nodeIdToForcedPseudoState.remove(nodeId); |
| |
| NodeToInspectorStyleSheet::iterator it = |
| m_nodeToInspectorStyleSheet.find(node); |
| if (it == m_nodeToInspectorStyleSheet.end()) |
| return; |
| |
| m_idToInspectorStyleSheetForInlineStyle.remove(it->value->id()); |
| m_nodeToInspectorStyleSheet.remove(node); |
| } |
| |
| void InspectorCSSAgent::didModifyDOMAttr(Element* element) { |
| if (!element) |
| return; |
| |
| NodeToInspectorStyleSheet::iterator it = |
| m_nodeToInspectorStyleSheet.find(element); |
| if (it == m_nodeToInspectorStyleSheet.end()) |
| return; |
| |
| it->value->didModifyElementAttribute(); |
| } |
| |
| void InspectorCSSAgent::styleSheetChanged(InspectorStyleSheetBase* styleSheet) { |
| if (s_frontendOperationCounter) |
| return; |
| flushPendingProtocolNotifications(); |
| frontend()->styleSheetChanged(styleSheet->id()); |
| } |
| |
| void InspectorCSSAgent::resetPseudoStates() { |
| HeapHashSet<Member<Document>> documentsToChange; |
| for (auto& state : m_nodeIdToForcedPseudoState) { |
| Element* element = toElement(m_domAgent->nodeForId(state.key)); |
| if (element && element->ownerDocument()) |
| documentsToChange.add(element->ownerDocument()); |
| } |
| |
| m_nodeIdToForcedPseudoState.clear(); |
| for (auto& document : documentsToChange) |
| document->setNeedsStyleRecalc( |
| SubtreeStyleChange, |
| StyleChangeReasonForTracing::create(StyleChangeReason::Inspector)); |
| } |
| |
| HeapVector<Member<CSSStyleDeclaration>> InspectorCSSAgent::matchingStyles( |
| Element* element) { |
| PseudoId pseudoId = element->getPseudoId(); |
| if (pseudoId) |
| element = element->parentElement(); |
| StyleResolver& styleResolver = |
| element->ownerDocument()->ensureStyleResolver(); |
| element->updateDistribution(); |
| |
| HeapVector<Member<CSSStyleRule>> rules = |
| filterDuplicateRules(styleResolver.pseudoCSSRulesForElement( |
| element, pseudoId, StyleResolver::AllCSSRules)); |
| HeapVector<Member<CSSStyleDeclaration>> styles; |
| if (!pseudoId && element->style()) |
| styles.append(element->style()); |
| for (unsigned i = rules.size(); i > 0; --i) { |
| CSSStyleSheet* parentStyleSheet = rules.at(i - 1)->parentStyleSheet(); |
| if (!parentStyleSheet || !parentStyleSheet->ownerNode()) |
| continue; // User agent. |
| styles.append(rules.at(i - 1)->style()); |
| } |
| return styles; |
| } |
| |
| CSSStyleDeclaration* InspectorCSSAgent::findEffectiveDeclaration( |
| CSSPropertyID propertyId, |
| const HeapVector<Member<CSSStyleDeclaration>>& styles) { |
| if (!styles.size()) |
| return nullptr; |
| |
| String longhand = getPropertyNameString(propertyId); |
| CSSStyleDeclaration* foundStyle = nullptr; |
| |
| for (unsigned i = 0; i < styles.size(); ++i) { |
| CSSStyleDeclaration* style = styles.at(i).get(); |
| if (style->getPropertyValue(longhand).isEmpty()) |
| continue; |
| if (style->getPropertyPriority(longhand) == "important") |
| return style; |
| if (!foundStyle) |
| foundStyle = style; |
| } |
| |
| return foundStyle ? foundStyle : styles.at(0).get(); |
| } |
| |
| void InspectorCSSAgent::setLayoutEditorValue(ErrorString* errorString, |
| Element* element, |
| CSSStyleDeclaration* style, |
| CSSPropertyID propertyId, |
| const String& value, |
| bool forceImportant) { |
| InspectorStyleSheetBase* inspectorStyleSheet = nullptr; |
| RefPtr<CSSRuleSourceData> sourceData; |
| // An absence of the parent rule means that given style is an inline style. |
| if (style->parentRule()) { |
| InspectorStyleSheet* styleSheet = bindStyleSheet(style->parentStyleSheet()); |
| inspectorStyleSheet = styleSheet; |
| sourceData = styleSheet->sourceDataForRule(style->parentRule()); |
| } else { |
| InspectorStyleSheetForInlineStyle* inlineStyleSheet = |
| asInspectorStyleSheet(element); |
| inspectorStyleSheet = inlineStyleSheet; |
| sourceData = inlineStyleSheet->ruleSourceData(); |
| } |
| |
| if (!sourceData) { |
| *errorString = "Can't find a source to edit"; |
| return; |
| } |
| |
| Vector<StylePropertyShorthand, 4> shorthands; |
| getMatchingShorthandsForLonghand(propertyId, &shorthands); |
| |
| String shorthand = shorthands.size() > 0 |
| ? getPropertyNameString(shorthands[0].id()) |
| : String(); |
| String longhand = getPropertyNameString(propertyId); |
| |
| int foundIndex = -1; |
| Vector<CSSPropertySourceData> properties = |
| sourceData->styleSourceData->propertyData; |
| for (unsigned i = 0; i < properties.size(); ++i) { |
| CSSPropertySourceData property = properties[properties.size() - i - 1]; |
| String name = property.name; |
| if (property.disabled) |
| continue; |
| |
| if (name != shorthand && name != longhand) |
| continue; |
| |
| if (property.important || foundIndex == -1) |
| foundIndex = properties.size() - i - 1; |
| |
| if (property.important) |
| break; |
| } |
| |
| SourceRange bodyRange = sourceData->ruleBodyRange; |
| String styleSheetText; |
| inspectorStyleSheet->getText(&styleSheetText); |
| String styleText = |
| styleSheetText.substring(bodyRange.start, bodyRange.length()); |
| SourceRange changeRange; |
| if (foundIndex == -1) { |
| String newPropertyText = "\n" + longhand + ": " + value + |
| (forceImportant ? " !important" : "") + ";"; |
| if (!styleText.isEmpty() && !styleText.stripWhiteSpace().endsWith(';')) |
| newPropertyText = ";" + newPropertyText; |
| styleText.append(newPropertyText); |
| changeRange.start = bodyRange.end; |
| changeRange.end = bodyRange.end + newPropertyText.length(); |
| } else { |
| CSSPropertySourceData declaration = properties[foundIndex]; |
| String newValueText; |
| if (declaration.name == shorthand) |
| newValueText = createShorthandValue(element->ownerDocument(), shorthand, |
| declaration.value, longhand, value); |
| else |
| newValueText = value; |
| |
| String newPropertyText = |
| declaration.name + ": " + newValueText + |
| (declaration.important || forceImportant ? " !important" : "") + ";"; |
| styleText.replace(declaration.range.start - bodyRange.start, |
| declaration.range.length(), newPropertyText); |
| changeRange.start = declaration.range.start; |
| changeRange.end = changeRange.start + newPropertyText.length(); |
| } |
| CSSStyleDeclaration* resultStyle = |
| setStyleText(errorString, inspectorStyleSheet, bodyRange, styleText); |
| if (resultStyle) |
| frontend()->layoutEditorChange( |
| inspectorStyleSheet->id(), |
| inspectorStyleSheet->buildSourceRangeObject(changeRange)); |
| } |
| |
| void InspectorCSSAgent::layoutEditorItemSelected(Element* element, |
| CSSStyleDeclaration* style) { |
| InspectorStyleSheetBase* inspectorStyleSheet = nullptr; |
| RefPtr<CSSRuleSourceData> sourceData; |
| if (style->parentRule()) { |
| InspectorStyleSheet* styleSheet = bindStyleSheet(style->parentStyleSheet()); |
| inspectorStyleSheet = styleSheet; |
| sourceData = styleSheet->sourceDataForRule(style->parentRule()); |
| } else { |
| InspectorStyleSheetForInlineStyle* inlineStyleSheet = |
| asInspectorStyleSheet(element); |
| inspectorStyleSheet = inlineStyleSheet; |
| sourceData = inlineStyleSheet->ruleSourceData(); |
| } |
| |
| if (sourceData) |
| frontend()->layoutEditorChange(inspectorStyleSheet->id(), |
| inspectorStyleSheet->buildSourceRangeObject( |
| sourceData->ruleHeaderRange)); |
| } |
| |
| void InspectorCSSAgent::setEffectivePropertyValueForNode( |
| ErrorString* errorString, |
| int nodeId, |
| const String& propertyName, |
| const String& value) { |
| // TODO: move testing from CSSAgent to layout editor. |
| Element* element = elementForId(errorString, nodeId); |
| if (!element || element->getPseudoId()) |
| return; |
| |
| CSSPropertyID property = cssPropertyID(propertyName); |
| if (!property) { |
| *errorString = "Invalid property name"; |
| return; |
| } |
| |
| Document* ownerDocument = element->ownerDocument(); |
| if (!ownerDocument->isActive()) { |
| *errorString = "Can't edit a node from a non-active document"; |
| return; |
| } |
| |
| CSSPropertyID propertyId = cssPropertyID(propertyName); |
| CSSStyleDeclaration* style = |
| findEffectiveDeclaration(propertyId, matchingStyles(element)); |
| if (!style) { |
| *errorString = "Can't find a style to edit"; |
| return; |
| } |
| |
| setLayoutEditorValue(errorString, element, style, propertyId, value); |
| } |
| |
| void InspectorCSSAgent::getBackgroundColors( |
| ErrorString* errorString, |
| int nodeId, |
| Maybe<protocol::Array<String>>* result) { |
| Element* element = elementForId(errorString, nodeId); |
| if (!element) { |
| *errorString = "Node not found"; |
| return; |
| } |
| |
| LayoutRect textBounds; |
| LayoutObject* elementLayout = element->layoutObject(); |
| if (!elementLayout) |
| return; |
| |
| for (const LayoutObject* child = elementLayout->slowFirstChild(); child; |
| child = child->nextSibling()) { |
| if (!child->isText()) |
| continue; |
| textBounds.unite(LayoutRect(child->absoluteBoundingBoxRect())); |
| } |
| if (textBounds.size().isEmpty()) |
| return; |
| |
| Vector<Color> colors; |
| FrameView* view = element->document().view(); |
| if (!view) { |
| *errorString = "No view."; |
| return; |
| } |
| Document& document = element->document(); |
| bool isMainFrame = document.isInMainFrame(); |
| bool foundOpaqueColor = false; |
| if (isMainFrame && !view->isTransparent()) { |
| // Start with the "default" page color (typically white). |
| Color baseBackgroundColor = view->baseBackgroundColor(); |
| colors.append(view->baseBackgroundColor()); |
| foundOpaqueColor = !baseBackgroundColor.hasAlpha(); |
| } |
| |
| foundOpaqueColor = |
| getColorsFromRect(textBounds, element->document(), element, colors); |
| |
| if (!foundOpaqueColor && !isMainFrame) { |
| for (HTMLFrameOwnerElement* ownerElement = document.localOwner(); |
| !foundOpaqueColor && ownerElement; |
| ownerElement = ownerElement->document().localOwner()) { |
| foundOpaqueColor = getColorsFromRect(textBounds, ownerElement->document(), |
| nullptr, colors); |
| } |
| } |
| |
| *result = protocol::Array<String>::create(); |
| for (auto color : colors) |
| result->fromJust()->addItem(color.serializedAsCSSComponentValue()); |
| } |
| |
| DEFINE_TRACE(InspectorCSSAgent) { |
| visitor->trace(m_domAgent); |
| visitor->trace(m_inspectedFrames); |
| visitor->trace(m_networkAgent); |
| visitor->trace(m_resourceContentLoader); |
| visitor->trace(m_resourceContainer); |
| visitor->trace(m_idToInspectorStyleSheet); |
| visitor->trace(m_idToInspectorStyleSheetForInlineStyle); |
| visitor->trace(m_cssStyleSheetToInspectorStyleSheet); |
| visitor->trace(m_documentToCSSStyleSheets); |
| visitor->trace(m_invalidatedDocuments); |
| visitor->trace(m_nodeToInspectorStyleSheet); |
| visitor->trace(m_inspectorUserAgentStyleSheet); |
| InspectorBaseAgent::trace(visitor); |
| } |
| |
| } // namespace blink |