/* * 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 "config.h" #include "InspectorCSSAgent.h" #if ENABLE(INSPECTOR) #include "CSSComputedStyleDeclaration.h" #include "CSSMutableStyleDeclaration.h" #include "CSSPropertyNames.h" #include "CSSPropertySourceData.h" #include "CSSRule.h" #include "CSSRuleList.h" #include "CSSStyleRule.h" #include "CSSStyleSelector.h" #include "CSSStyleSheet.h" #include "DOMWindow.h" #include "HTMLHeadElement.h" #include "InspectorDOMAgent.h" #include "InspectorValues.h" #include "InstrumentingAgents.h" #include "Node.h" #include "NodeList.h" #include "StyleSheetList.h" #include #include #include // Currently implemented model: // // cssProperty = { // name : , // value : , // priority : , // "" for non-parsedOk properties // implicit : , // parsedOk : , // whether property is understood by WebCore // status : , // "disabled" | "active" | "inactive" | "style" // shorthandName : , // startOffset : , // Optional - property text start offset in enclosing style declaration. Absent for computed styles and such. // endOffset : , // Optional - property text end offset in enclosing style declaration. Absent for computed styles and such. // } // // name + value + priority : present when the property is enabled // text : present when the property is disabled // // For disabled properties, startOffset === endOffset === insertion point for the property. // // status: // "disabled" == property disabled by user // "active" == property participates in the computed style calculation // "inactive" == property does no participate in the computed style calculation (i.e. overridden by a subsequent property with the same name) // "style" == property is active and originates from the WebCore CSSStyleDeclaration rather than CSS source code (e.g. implicit longhand properties) // // cssStyle = { // styleId : , // Optional // cssProperties : [ // #cssProperty, // ... // #cssProperty // ], // shorthandEntries : [ // #shorthandEntry, // ... // #shorthandEntry // ], // cssText : , // Optional - declaration text // properties : { // width, // height, // startOffset, // Optional - for source-based styles only // endOffset, // Optional - for source-based styles only // } // } // // shorthandEntry = { // name: , // value: // } // // cssRule = { // ruleId : , // Optional // selectorText : , // sourceURL : , // sourceLine : , // origin : , // "" || "user-agent" || "user" || "inspector" // style : #cssStyle, // selectorRange: { start: , end: } // Optional - for source-based rules only // } // // cssStyleSheetInfo = { // styleSheetId : // sourceURL : // title : // disabled : // } // // cssStyleSheet = { // styleSheetId : // rules : [ // #cssRule, // ... // #cssRule // ] // text : // Optional - whenever the text is available for a text-based stylesheet // } namespace WebCore { // static CSSStyleSheet* InspectorCSSAgent::parentStyleSheet(StyleBase* styleBase) { if (!styleBase) return 0; StyleSheet* styleSheet = styleBase->stylesheet(); if (styleSheet && styleSheet->isCSSStyleSheet()) return static_cast(styleSheet); return 0; } // static CSSStyleRule* InspectorCSSAgent::asCSSStyleRule(StyleBase* styleBase) { if (!styleBase->isStyleRule()) return 0; CSSRule* rule = static_cast(styleBase); if (rule->type() != CSSRule::STYLE_RULE) return 0; return static_cast(rule); } InspectorCSSAgent::InspectorCSSAgent(InstrumentingAgents* instrumentingAgents, InspectorDOMAgent* domAgent) : m_instrumentingAgents(instrumentingAgents) , m_domAgent(domAgent) , m_lastStyleSheetId(1) , m_lastRuleId(1) , m_lastStyleId(1) { m_domAgent->setDOMListener(this); m_instrumentingAgents->setInspectorCSSAgent(this); } InspectorCSSAgent::~InspectorCSSAgent() { m_instrumentingAgents->setInspectorCSSAgent(0); // DOM agent should be destroyed after CSS agent. m_domAgent->setDOMListener(0); m_domAgent = 0; reset(); } void InspectorCSSAgent::reset() { m_idToInspectorStyleSheet.clear(); m_cssStyleSheetToInspectorStyleSheet.clear(); m_nodeToInspectorStyleSheet.clear(); m_documentToInspectorStyleSheet.clear(); } void InspectorCSSAgent::getStylesForNode(ErrorString* errorString, int nodeId, RefPtr* result) { Element* element = elementForId(errorString, nodeId); if (!element) return; RefPtr resultObject = InspectorObject::create(); InspectorStyleSheetForInlineStyle* styleSheet = asInspectorStyleSheet(element); if (styleSheet) resultObject->setObject("inlineStyle", styleSheet->buildObjectForStyle(element->style())); RefPtr computedStyleInfo = computedStyle(element, true); // Support the viewing of :visited information in computed style. RefPtr computedInspectorStyle = InspectorStyle::create(InspectorCSSId(), computedStyleInfo, 0); resultObject->setObject("computedStyle", computedInspectorStyle->buildObjectForStyle()); CSSStyleSelector* selector = element->ownerDocument()->styleSelector(); RefPtr matchedRules = selector->styleRulesForElement(element, false, true); resultObject->setArray("matchedCSSRules", buildArrayForRuleList(matchedRules.get())); resultObject->setArray("styleAttributes", buildArrayForAttributeStyles(element)); RefPtr pseudoElements = InspectorArray::create(); for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < AFTER_LAST_INTERNAL_PSEUDOID; pseudoId = static_cast(pseudoId + 1)) { RefPtr matchedRules = selector->pseudoStyleRulesForElement(element, pseudoId, false, true); if (matchedRules && matchedRules->length()) { RefPtr pseudoStyles = InspectorObject::create(); pseudoStyles->setNumber("pseudoId", static_cast(pseudoId)); pseudoStyles->setArray("rules", buildArrayForRuleList(matchedRules.get())); pseudoElements->pushObject(pseudoStyles.release()); } } resultObject->setArray("pseudoElements", pseudoElements.release()); RefPtr inheritedStyles = InspectorArray::create(); Element* parentElement = element->parentElement(); while (parentElement) { RefPtr parentStyle = InspectorObject::create(); if (parentElement->style() && parentElement->style()->length()) { InspectorStyleSheetForInlineStyle* styleSheet = asInspectorStyleSheet(parentElement); if (styleSheet) parentStyle->setObject("inlineStyle", styleSheet->buildObjectForStyle(styleSheet->styleForId(InspectorCSSId(styleSheet->id(), 0)))); } CSSStyleSelector* parentSelector = parentElement->ownerDocument()->styleSelector(); RefPtr parentMatchedRules = parentSelector->styleRulesForElement(parentElement, false, true); parentStyle->setArray("matchedCSSRules", buildArrayForRuleList(parentMatchedRules.get())); inheritedStyles->pushObject(parentStyle.release()); parentElement = parentElement->parentElement(); } resultObject->setArray("inherited", inheritedStyles.release()); *result = resultObject.release(); } void InspectorCSSAgent::getInlineStyleForNode(ErrorString* errorString, int nodeId, RefPtr* style) { Element* element = elementForId(errorString, nodeId); if (!element) return; InspectorStyleSheetForInlineStyle* styleSheet = asInspectorStyleSheet(element); if (!styleSheet) return; *style = styleSheet->buildObjectForStyle(element->style()); } void InspectorCSSAgent::getComputedStyleForNode(ErrorString* errorString, int nodeId, RefPtr* style) { Element* element = elementForId(errorString, nodeId); if (!element) return; RefPtr computedStyleInfo = computedStyle(element, true); RefPtr inspectorStyle = InspectorStyle::create(InspectorCSSId(), computedStyleInfo, 0); *style = inspectorStyle->buildObjectForStyle(); } void InspectorCSSAgent::getAllStyleSheets(ErrorString*, RefPtr* styleInfos) { Vector documents = m_domAgent->documents(); for (Vector::iterator it = documents.begin(); it != documents.end(); ++it) { StyleSheetList* list = (*it)->styleSheets(); for (unsigned i = 0; i < list->length(); ++i) { StyleSheet* styleSheet = list->item(i); if (styleSheet->isCSSStyleSheet()) { InspectorStyleSheet* inspectorStyleSheet = bindStyleSheet(static_cast(styleSheet)); (*styleInfos)->pushObject(inspectorStyleSheet->buildObjectForStyleSheetInfo()); } } } } void InspectorCSSAgent::getStyleSheet(ErrorString* errorString, const String& styleSheetId, RefPtr* styleSheetObject) { InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, styleSheetId); if (!inspectorStyleSheet) return; *styleSheetObject = inspectorStyleSheet->buildObjectForStyleSheet(); } void InspectorCSSAgent::getStyleSheetText(ErrorString* errorString, const String& styleSheetId, String* result) { InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, styleSheetId); if (!inspectorStyleSheet) return; inspectorStyleSheet->text(result); } void InspectorCSSAgent::setStyleSheetText(ErrorString* errorString, const String& styleSheetId, const String& text) { InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, styleSheetId); if (!inspectorStyleSheet) return; if (inspectorStyleSheet->setText(text)) inspectorStyleSheet->reparseStyleSheet(text); else *errorString = "Internal error setting style sheet text"; } void InspectorCSSAgent::setPropertyText(ErrorString* errorString, const RefPtr& fullStyleId, int propertyIndex, const String& text, bool overwrite, RefPtr* result) { InspectorCSSId compoundId(fullStyleId); ASSERT(!compoundId.isEmpty()); InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, compoundId.styleSheetId()); if (!inspectorStyleSheet) return; bool success = inspectorStyleSheet->setPropertyText(errorString, compoundId, propertyIndex, text, overwrite); if (success) *result = inspectorStyleSheet->buildObjectForStyle(inspectorStyleSheet->styleForId(compoundId)); } void InspectorCSSAgent::toggleProperty(ErrorString* errorString, const RefPtr& fullStyleId, int propertyIndex, bool disable, RefPtr* result) { InspectorCSSId compoundId(fullStyleId); ASSERT(!compoundId.isEmpty()); InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, compoundId.styleSheetId()); if (!inspectorStyleSheet) return; bool success = inspectorStyleSheet->toggleProperty(errorString, compoundId, propertyIndex, disable); if (success) *result = inspectorStyleSheet->buildObjectForStyle(inspectorStyleSheet->styleForId(compoundId)); } void InspectorCSSAgent::setRuleSelector(ErrorString* errorString, const RefPtr& fullRuleId, const String& selector, RefPtr* result) { InspectorCSSId compoundId(fullRuleId); ASSERT(!compoundId.isEmpty()); InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, compoundId.styleSheetId()); if (!inspectorStyleSheet) return; bool success = inspectorStyleSheet->setRuleSelector(compoundId, selector); if (!success) return; *result = inspectorStyleSheet->buildObjectForRule(inspectorStyleSheet->ruleForId(compoundId)); } void InspectorCSSAgent::addRule(ErrorString*, const int contextNodeId, const String& selector, RefPtr* result) { Node* node = m_domAgent->nodeForId(contextNodeId); if (!node) return; InspectorStyleSheet* inspectorStyleSheet = viaInspectorStyleSheet(node->document(), true); if (!inspectorStyleSheet) return; CSSStyleRule* newRule = inspectorStyleSheet->addRule(selector); if (!newRule) return; *result = inspectorStyleSheet->buildObjectForRule(newRule); } void InspectorCSSAgent::getSupportedCSSProperties(ErrorString*, RefPtr* cssProperties) { RefPtr properties = InspectorArray::create(); for (int i = 0; i < numCSSProperties; ++i) properties->pushString(propertyNameStrings[i]); *cssProperties = properties.release(); } // static Element* InspectorCSSAgent::inlineStyleElement(CSSStyleDeclaration* style) { if (!style || !style->isMutableStyleDeclaration()) return 0; Node* node = static_cast(style)->node(); if (!node || !node->isStyledElement() || static_cast(node)->getInlineStyleDecl() != style) return 0; return static_cast(node); } InspectorStyleSheetForInlineStyle* InspectorCSSAgent::asInspectorStyleSheet(Element* element) { NodeToInspectorStyleSheet::iterator it = m_nodeToInspectorStyleSheet.find(element); if (it == m_nodeToInspectorStyleSheet.end()) { CSSStyleDeclaration* style = element->isStyledElement() ? element->style() : 0; if (!style) return 0; String newStyleSheetId = String::number(m_lastStyleSheetId++); RefPtr inspectorStyleSheet = InspectorStyleSheetForInlineStyle::create(newStyleSheetId, element, ""); m_idToInspectorStyleSheet.set(newStyleSheetId, inspectorStyleSheet); m_nodeToInspectorStyleSheet.set(element, inspectorStyleSheet); return inspectorStyleSheet.get(); } return it->second.get(); } Element* InspectorCSSAgent::elementForId(ErrorString* errorString, int nodeId) { Node* node = m_domAgent->nodeForId(nodeId); if (!node) { *errorString = "No node with given id found"; return 0; } if (node->nodeType() != Node::ELEMENT_NODE) { *errorString = "Not an element node"; return 0; } return static_cast(node); } InspectorStyleSheet* InspectorCSSAgent::bindStyleSheet(CSSStyleSheet* styleSheet) { RefPtr inspectorStyleSheet = m_cssStyleSheetToInspectorStyleSheet.get(styleSheet); if (!inspectorStyleSheet) { String id = String::number(m_lastStyleSheetId++); inspectorStyleSheet = InspectorStyleSheet::create(id, styleSheet, detectOrigin(styleSheet, styleSheet->document()), m_domAgent->documentURLString(styleSheet->document())); m_idToInspectorStyleSheet.set(id, inspectorStyleSheet); m_cssStyleSheetToInspectorStyleSheet.set(styleSheet, inspectorStyleSheet); } return inspectorStyleSheet.get(); } InspectorStyleSheet* InspectorCSSAgent::viaInspectorStyleSheet(Document* document, bool createIfAbsent) { if (!document) { ASSERT(!createIfAbsent); return 0; } RefPtr inspectorStyleSheet = m_documentToInspectorStyleSheet.get(document); if (inspectorStyleSheet || !createIfAbsent) return inspectorStyleSheet.get(); ExceptionCode ec = 0; RefPtr styleElement = document->createElement("style", ec); if (!ec) styleElement->setAttribute("type", "text/css", ec); if (!ec) { ContainerNode* targetNode; // HEAD is absent in ImageDocuments, for example. if (document->head()) targetNode = document->head(); else if (document->body()) targetNode = document->body(); else return 0; targetNode->appendChild(styleElement, ec); } if (ec) return 0; StyleSheetList* styleSheets = document->styleSheets(); StyleSheet* styleSheet = styleSheets->item(styleSheets->length() - 1); if (!styleSheet->isCSSStyleSheet()) return 0; CSSStyleSheet* cssStyleSheet = static_cast(styleSheet); String id = String::number(m_lastStyleSheetId++); inspectorStyleSheet = InspectorStyleSheet::create(id, cssStyleSheet, "inspector", m_domAgent->documentURLString(document)); m_idToInspectorStyleSheet.set(id, inspectorStyleSheet); m_cssStyleSheetToInspectorStyleSheet.set(cssStyleSheet, inspectorStyleSheet); m_documentToInspectorStyleSheet.set(document, inspectorStyleSheet); return inspectorStyleSheet.get(); } InspectorStyleSheet* InspectorCSSAgent::assertStyleSheetForId(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 0; } return it->second.get(); } String InspectorCSSAgent::detectOrigin(CSSStyleSheet* pageStyleSheet, Document* ownerDocument) { DEFINE_STATIC_LOCAL(String, userAgent, ("user-agent")); DEFINE_STATIC_LOCAL(String, user, ("user")); DEFINE_STATIC_LOCAL(String, inspector, ("inspector")); String origin(""); if (pageStyleSheet && !pageStyleSheet->ownerNode() && pageStyleSheet->href().isEmpty()) origin = userAgent; else if (pageStyleSheet && pageStyleSheet->ownerNode() && pageStyleSheet->ownerNode()->nodeName() == "#document") origin = user; else { InspectorStyleSheet* viaInspectorStyleSheetForOwner = viaInspectorStyleSheet(ownerDocument, false); if (viaInspectorStyleSheetForOwner && pageStyleSheet == viaInspectorStyleSheetForOwner->pageStyleSheet()) origin = inspector; } return origin; } PassRefPtr InspectorCSSAgent::buildArrayForRuleList(CSSRuleList* ruleList) { RefPtr result = InspectorArray::create(); if (!ruleList) return result.release(); for (unsigned i = 0, size = ruleList->length(); i < size; ++i) { CSSStyleRule* rule = asCSSStyleRule(ruleList->item(i)); if (!rule) continue; InspectorStyleSheet* styleSheet = bindStyleSheet(parentStyleSheet(rule)); if (styleSheet) result->pushObject(styleSheet->buildObjectForRule(rule)); } return result.release(); } PassRefPtr InspectorCSSAgent::buildArrayForAttributeStyles(Element* element) { RefPtr attrStyles = InspectorArray::create(); NamedNodeMap* attributes = element->attributes(); for (unsigned i = 0; attributes && i < attributes->length(); ++i) { Attribute* attribute = attributes->attributeItem(i); if (attribute->style()) { RefPtr attrStyleObject = InspectorObject::create(); String attributeName = attribute->localName(); RefPtr inspectorStyle = InspectorStyle::create(InspectorCSSId(), attribute->style(), 0); attrStyleObject->setString("name", attributeName.utf8().data()); attrStyleObject->setObject("style", inspectorStyle->buildObjectForStyle()); attrStyles->pushObject(attrStyleObject.release()); } } return attrStyles.release(); } void InspectorCSSAgent::didRemoveDocument(Document* document) { if (document) m_documentToInspectorStyleSheet.remove(document); } void InspectorCSSAgent::didRemoveDOMNode(Node* node) { if (!node) return; NodeToInspectorStyleSheet::iterator it = m_nodeToInspectorStyleSheet.find(node); if (it == m_nodeToInspectorStyleSheet.end()) return; m_idToInspectorStyleSheet.remove(it->second->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->second->didModifyElementAttribute(); } } // namespace WebCore #endif // ENABLE(INSPECTOR)