blob: a792444b2e07ecdc05090efc5685d88d5546643f [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2015 Apple 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"
#include "CSSComputedStyleDeclaration.h"
#include "CSSImportRule.h"
#include "CSSPropertyNames.h"
#include "CSSPropertySourceData.h"
#include "CSSRule.h"
#include "CSSRuleList.h"
#include "CSSStyleRule.h"
#include "CSSStyleSheet.h"
#include "ContentSecurityPolicy.h"
#include "DOMWindow.h"
#include "ExceptionCodePlaceholder.h"
#include "FontCache.h"
#include "HTMLHeadElement.h"
#include "HTMLStyleElement.h"
#include "InspectorDOMAgent.h"
#include "InspectorHistory.h"
#include "InspectorPageAgent.h"
#include "InstrumentingAgents.h"
#include "NamedFlowCollection.h"
#include "Node.h"
#include "NodeList.h"
#include "PseudoElement.h"
#include "RenderNamedFlowFragment.h"
#include "SVGStyleElement.h"
#include "SelectorChecker.h"
#include "StyleProperties.h"
#include "StylePropertyShorthand.h"
#include "StyleResolver.h"
#include "StyleRule.h"
#include "StyleSheetList.h"
#include "WebKitNamedFlow.h"
#include <inspector/InspectorProtocolObjects.h>
#include <wtf/HashSet.h>
#include <wtf/Ref.h>
#include <wtf/Vector.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringConcatenate.h>
using namespace Inspector;
namespace WebCore {
enum ForcePseudoClassFlags {
PseudoClassNone = 0,
PseudoClassHover = 1 << 0,
PseudoClassFocus = 1 << 1,
PseudoClassActive = 1 << 2,
PseudoClassVisited = 1 << 3
};
static unsigned computePseudoClassMask(const InspectorArray& pseudoClassArray)
{
DEPRECATED_DEFINE_STATIC_LOCAL(String, active, (ASCIILiteral("active")));
DEPRECATED_DEFINE_STATIC_LOCAL(String, hover, (ASCIILiteral("hover")));
DEPRECATED_DEFINE_STATIC_LOCAL(String, focus, (ASCIILiteral("focus")));
DEPRECATED_DEFINE_STATIC_LOCAL(String, visited, (ASCIILiteral("visited")));
if (!pseudoClassArray.length())
return PseudoClassNone;
unsigned result = PseudoClassNone;
for (size_t i = 0; i < pseudoClassArray.length(); ++i) {
RefPtr<InspectorValue> pseudoClassValue = pseudoClassArray.get(i);
String pseudoClass;
bool success = pseudoClassValue->asString(pseudoClass);
if (!success)
continue;
if (pseudoClass == active)
result |= PseudoClassActive;
else if (pseudoClass == hover)
result |= PseudoClassHover;
else if (pseudoClass == focus)
result |= PseudoClassFocus;
else if (pseudoClass == visited)
result |= PseudoClassVisited;
}
return result;
}
class ChangeRegionOversetTask {
public:
ChangeRegionOversetTask(InspectorCSSAgent*);
void scheduleFor(WebKitNamedFlow*, int documentNodeId);
void unschedule(WebKitNamedFlow*);
void reset();
void timerFired();
private:
InspectorCSSAgent* m_cssAgent;
Timer m_timer;
HashMap<WebKitNamedFlow*, int> m_namedFlows;
};
ChangeRegionOversetTask::ChangeRegionOversetTask(InspectorCSSAgent* cssAgent)
: m_cssAgent(cssAgent)
, m_timer(*this, &ChangeRegionOversetTask::timerFired)
{
}
void ChangeRegionOversetTask::scheduleFor(WebKitNamedFlow* namedFlow, int documentNodeId)
{
m_namedFlows.add(namedFlow, documentNodeId);
if (!m_timer.isActive())
m_timer.startOneShot(0);
}
void ChangeRegionOversetTask::unschedule(WebKitNamedFlow* namedFlow)
{
m_namedFlows.remove(namedFlow);
}
void ChangeRegionOversetTask::reset()
{
m_timer.stop();
m_namedFlows.clear();
}
void ChangeRegionOversetTask::timerFired()
{
// The timer is stopped on m_cssAgent destruction, so this method will never be called after m_cssAgent has been destroyed.
for (HashMap<WebKitNamedFlow*, int>::iterator it = m_namedFlows.begin(), end = m_namedFlows.end(); it != end; ++it)
m_cssAgent->regionOversetChanged(it->key, it->value);
m_namedFlows.clear();
}
class InspectorCSSAgent::StyleSheetAction : public InspectorHistory::Action {
WTF_MAKE_NONCOPYABLE(StyleSheetAction);
public:
StyleSheetAction(const String& name, InspectorStyleSheet* styleSheet)
: InspectorHistory::Action(name)
, m_styleSheet(styleSheet)
{
}
protected:
RefPtr<InspectorStyleSheet> m_styleSheet;
};
class InspectorCSSAgent::SetStyleSheetTextAction final : public InspectorCSSAgent::StyleSheetAction {
WTF_MAKE_NONCOPYABLE(SetStyleSheetTextAction);
public:
SetStyleSheetTextAction(InspectorStyleSheet* styleSheet, const String& text)
: InspectorCSSAgent::StyleSheetAction(ASCIILiteral("SetStyleSheetText"), styleSheet)
, m_text(text)
{
}
virtual bool perform(ExceptionCode& ec) override
{
if (!m_styleSheet->getText(&m_oldText))
return false;
return redo(ec);
}
virtual bool undo(ExceptionCode& ec) override
{
if (m_styleSheet->setText(m_oldText, ec)) {
m_styleSheet->reparseStyleSheet(m_oldText);
return true;
}
return false;
}
virtual bool redo(ExceptionCode& ec) override
{
if (m_styleSheet->setText(m_text, ec)) {
m_styleSheet->reparseStyleSheet(m_text);
return true;
}
return false;
}
virtual String mergeId() override
{
return String::format("SetStyleSheetText %s", m_styleSheet->id().utf8().data());
}
virtual void merge(std::unique_ptr<Action> action) override
{
ASSERT(action->mergeId() == mergeId());
SetStyleSheetTextAction* other = static_cast<SetStyleSheetTextAction*>(action.get());
m_text = other->m_text;
}
private:
String m_text;
String m_oldText;
};
class InspectorCSSAgent::SetStyleTextAction final : public InspectorCSSAgent::StyleSheetAction {
WTF_MAKE_NONCOPYABLE(SetStyleTextAction);
public:
SetStyleTextAction(InspectorStyleSheet* styleSheet, const InspectorCSSId& cssId, const String& text)
: InspectorCSSAgent::StyleSheetAction(ASCIILiteral("SetStyleText"), styleSheet)
, m_cssId(cssId)
, m_text(text)
{
}
virtual bool perform(ExceptionCode& ec) override
{
return redo(ec);
}
virtual bool undo(ExceptionCode& ec) override
{
return m_styleSheet->setStyleText(m_cssId, m_oldText, nullptr, ec);
}
virtual bool redo(ExceptionCode& ec) override
{
return m_styleSheet->setStyleText(m_cssId, m_text, &m_oldText, ec);
}
virtual String mergeId() override
{
ASSERT(m_styleSheet->id() == m_cssId.styleSheetId());
return String::format("SetStyleText %s:%u", m_styleSheet->id().utf8().data(), m_cssId.ordinal());
}
virtual void merge(std::unique_ptr<Action> action) override
{
ASSERT(action->mergeId() == mergeId());
SetStyleTextAction* other = static_cast<SetStyleTextAction*>(action.get());
m_text = other->m_text;
}
private:
InspectorCSSId m_cssId;
String m_text;
String m_oldText;
};
class InspectorCSSAgent::SetRuleSelectorAction final : public InspectorCSSAgent::StyleSheetAction {
WTF_MAKE_NONCOPYABLE(SetRuleSelectorAction);
public:
SetRuleSelectorAction(InspectorStyleSheet* styleSheet, const InspectorCSSId& cssId, const String& selector)
: InspectorCSSAgent::StyleSheetAction(ASCIILiteral("SetRuleSelector"), styleSheet)
, m_cssId(cssId)
, m_selector(selector)
{
}
virtual bool perform(ExceptionCode& ec) override
{
m_oldSelector = m_styleSheet->ruleSelector(m_cssId, ec);
if (ec)
return false;
return redo(ec);
}
virtual bool undo(ExceptionCode& ec) override
{
return m_styleSheet->setRuleSelector(m_cssId, m_oldSelector, ec);
}
virtual bool redo(ExceptionCode& ec) override
{
return m_styleSheet->setRuleSelector(m_cssId, m_selector, ec);
}
private:
InspectorCSSId m_cssId;
String m_selector;
String m_oldSelector;
};
class InspectorCSSAgent::AddRuleAction final : public InspectorCSSAgent::StyleSheetAction {
WTF_MAKE_NONCOPYABLE(AddRuleAction);
public:
AddRuleAction(InspectorStyleSheet* styleSheet, const String& selector)
: InspectorCSSAgent::StyleSheetAction(ASCIILiteral("AddRule"), styleSheet)
, m_selector(selector)
{
}
virtual bool perform(ExceptionCode& ec) override
{
return redo(ec);
}
virtual bool undo(ExceptionCode& ec) override
{
return m_styleSheet->deleteRule(m_newId, ec);
}
virtual bool redo(ExceptionCode& ec) override
{
CSSStyleRule* cssStyleRule = m_styleSheet->addRule(m_selector, ec);
if (ec)
return false;
m_newId = m_styleSheet->ruleId(cssStyleRule);
return true;
}
InspectorCSSId newRuleId() { return m_newId; }
private:
InspectorCSSId m_newId;
String m_selector;
String m_oldSelector;
};
// static
CSSStyleRule* InspectorCSSAgent::asCSSStyleRule(CSSRule& rule)
{
if (!is<CSSStyleRule>(rule))
return nullptr;
return downcast<CSSStyleRule>(&rule);
}
InspectorCSSAgent::InspectorCSSAgent(InstrumentingAgents& instrumentingAgents, InspectorDOMAgent* domAgent)
: InspectorAgentBase(ASCIILiteral("CSS"), instrumentingAgents)
, m_domAgent(domAgent)
{
m_domAgent->setDOMListener(this);
}
InspectorCSSAgent::~InspectorCSSAgent()
{
ASSERT(!m_domAgent);
reset();
}
void InspectorCSSAgent::didCreateFrontendAndBackend(Inspector::FrontendChannel* frontendChannel, Inspector::BackendDispatcher* backendDispatcher)
{
m_frontendDispatcher = std::make_unique<CSSFrontendDispatcher>(frontendChannel);
m_backendDispatcher = CSSBackendDispatcher::create(backendDispatcher, this);
}
void InspectorCSSAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
{
m_frontendDispatcher = nullptr;
m_backendDispatcher = nullptr;
resetNonPersistentData();
}
void InspectorCSSAgent::discardAgent()
{
m_domAgent->setDOMListener(nullptr);
m_domAgent = nullptr;
}
void InspectorCSSAgent::reset()
{
// FIXME: Should we be resetting on main frame navigations?
m_idToInspectorStyleSheet.clear();
m_cssStyleSheetToInspectorStyleSheet.clear();
m_nodeToInspectorStyleSheet.clear();
m_documentToInspectorStyleSheet.clear();
m_documentToKnownCSSStyleSheets.clear();
resetNonPersistentData();
}
void InspectorCSSAgent::resetNonPersistentData()
{
m_namedFlowCollectionsRequested.clear();
if (m_changeRegionOversetTask)
m_changeRegionOversetTask->reset();
resetPseudoStates();
}
void InspectorCSSAgent::enable(ErrorString&)
{
m_instrumentingAgents.setInspectorCSSAgent(this);
for (auto* document : m_domAgent->documents())
activeStyleSheetsUpdated(*document);
}
void InspectorCSSAgent::disable(ErrorString&)
{
m_instrumentingAgents.setInspectorCSSAgent(nullptr);
}
void InspectorCSSAgent::documentDetached(Document& document)
{
Vector<CSSStyleSheet*> emptyList;
setActiveStyleSheetsForDocument(document, emptyList);
m_documentToKnownCSSStyleSheets.remove(&document);
}
void InspectorCSSAgent::mediaQueryResultChanged()
{
m_frontendDispatcher->mediaQueryResultChanged();
}
void InspectorCSSAgent::activeStyleSheetsUpdated(Document& document)
{
Vector<CSSStyleSheet*> cssStyleSheets;
collectAllDocumentStyleSheets(document, cssStyleSheets);
setActiveStyleSheetsForDocument(document, cssStyleSheets);
}
void InspectorCSSAgent::setActiveStyleSheetsForDocument(Document& document, Vector<CSSStyleSheet*>& activeStyleSheets)
{
HashSet<CSSStyleSheet*>& previouslyKnownActiveStyleSheets = m_documentToKnownCSSStyleSheets.add(&document, HashSet<CSSStyleSheet*>()).iterator->value;
HashSet<CSSStyleSheet*> removedStyleSheets(previouslyKnownActiveStyleSheets);
Vector<CSSStyleSheet*> addedStyleSheets;
for (auto& activeStyleSheet : activeStyleSheets) {
if (removedStyleSheets.contains(activeStyleSheet))
removedStyleSheets.remove(activeStyleSheet);
else
addedStyleSheets.append(activeStyleSheet);
}
for (auto* cssStyleSheet : removedStyleSheets) {
previouslyKnownActiveStyleSheets.remove(cssStyleSheet);
RefPtr<InspectorStyleSheet> inspectorStyleSheet = m_cssStyleSheetToInspectorStyleSheet.get(cssStyleSheet);
if (m_idToInspectorStyleSheet.contains(inspectorStyleSheet->id())) {
String id = unbindStyleSheet(inspectorStyleSheet.get());
m_frontendDispatcher->styleSheetRemoved(id);
}
}
for (auto* cssStyleSheet : addedStyleSheets) {
previouslyKnownActiveStyleSheets.add(cssStyleSheet);
if (!m_cssStyleSheetToInspectorStyleSheet.contains(cssStyleSheet)) {
InspectorStyleSheet* inspectorStyleSheet = bindStyleSheet(cssStyleSheet);
m_frontendDispatcher->styleSheetAdded(inspectorStyleSheet->buildObjectForStyleSheetInfo());
}
}
}
void InspectorCSSAgent::didCreateNamedFlow(Document& document, WebKitNamedFlow& namedFlow)
{
int documentNodeId = documentNodeWithRequestedFlowsId(&document);
if (!documentNodeId)
return;
ErrorString unused;
m_frontendDispatcher->namedFlowCreated(buildObjectForNamedFlow(unused, &namedFlow, documentNodeId));
}
void InspectorCSSAgent::willRemoveNamedFlow(Document& document, WebKitNamedFlow& namedFlow)
{
int documentNodeId = documentNodeWithRequestedFlowsId(&document);
if (!documentNodeId)
return;
if (m_changeRegionOversetTask)
m_changeRegionOversetTask->unschedule(&namedFlow);
m_frontendDispatcher->namedFlowRemoved(documentNodeId, namedFlow.name().string());
}
void InspectorCSSAgent::didChangeRegionOverset(Document& document, WebKitNamedFlow& namedFlow)
{
int documentNodeId = documentNodeWithRequestedFlowsId(&document);
if (!documentNodeId)
return;
if (!m_changeRegionOversetTask)
m_changeRegionOversetTask = std::make_unique<ChangeRegionOversetTask>(this);
m_changeRegionOversetTask->scheduleFor(&namedFlow, documentNodeId);
}
void InspectorCSSAgent::regionOversetChanged(WebKitNamedFlow* namedFlow, int documentNodeId)
{
if (namedFlow->flowState() == WebKitNamedFlow::FlowStateNull)
return;
ErrorString unused;
Ref<WebKitNamedFlow> protect(*namedFlow);
m_frontendDispatcher->regionOversetChanged(buildObjectForNamedFlow(unused, namedFlow, documentNodeId));
}
void InspectorCSSAgent::didRegisterNamedFlowContentElement(Document& document, WebKitNamedFlow& namedFlow, Node& contentElement, Node* nextContentElement)
{
int documentNodeId = documentNodeWithRequestedFlowsId(&document);
if (!documentNodeId)
return;
ErrorString unused;
int contentElementNodeId = m_domAgent->pushNodeToFrontend(unused, documentNodeId, &contentElement);
int nextContentElementNodeId = nextContentElement ? m_domAgent->pushNodeToFrontend(unused, documentNodeId, nextContentElement) : 0;
m_frontendDispatcher->registeredNamedFlowContentElement(documentNodeId, namedFlow.name().string(), contentElementNodeId, nextContentElementNodeId);
}
void InspectorCSSAgent::didUnregisterNamedFlowContentElement(Document& document, WebKitNamedFlow& namedFlow, Node& contentElement)
{
int documentNodeId = documentNodeWithRequestedFlowsId(&document);
if (!documentNodeId)
return;
ErrorString unused;
int contentElementNodeId = m_domAgent->pushNodeToFrontend(unused, documentNodeId, &contentElement);
if (!contentElementNodeId) {
// We've already notified that the DOM node was removed from the DOM, so there's no need to send another event.
return;
}
m_frontendDispatcher->unregisteredNamedFlowContentElement(documentNodeId, namedFlow.name().string(), contentElementNodeId);
}
bool InspectorCSSAgent::forcePseudoState(Element& element, CSSSelector::PseudoClassType pseudoClassType)
{
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 (pseudoClassType) {
case CSSSelector::PseudoClassActive:
return forcedPseudoState & PseudoClassActive;
case CSSSelector::PseudoClassFocus:
return forcedPseudoState & PseudoClassFocus;
case CSSSelector::PseudoClassHover:
return forcedPseudoState & PseudoClassHover;
case CSSSelector::PseudoClassVisited:
return forcedPseudoState & PseudoClassVisited;
default:
return false;
}
}
void InspectorCSSAgent::getMatchedStylesForNode(ErrorString& errorString, int nodeId, const bool* includePseudo, const bool* includeInherited, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>>& matchedCSSRules, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::PseudoIdMatches>>& pseudoIdMatches, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::InheritedStyleEntry>>& inheritedEntries)
{
Element* element = elementForId(errorString, nodeId);
if (!element)
return;
Element* originalElement = element;
PseudoId elementPseudoId = element->pseudoId();
if (elementPseudoId) {
element = downcast<PseudoElement>(*element).hostElement();
if (!element) {
errorString = ASCIILiteral("Pseudo element has no parent");
return;
}
}
// Matched rules.
StyleResolver& styleResolver = element->document().ensureStyleResolver();
auto matchedRules = styleResolver.pseudoStyleRulesForElement(element, elementPseudoId, StyleResolver::AllCSSRules);
matchedCSSRules = buildArrayForMatchedRuleList(matchedRules, styleResolver, element, elementPseudoId);
if (!originalElement->isPseudoElement()) {
// Pseudo elements.
if (!includePseudo || *includePseudo) {
auto pseudoElements = Inspector::Protocol::Array<Inspector::Protocol::CSS::PseudoIdMatches>::create();
for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < AFTER_LAST_INTERNAL_PSEUDOID; pseudoId = static_cast<PseudoId>(pseudoId + 1)) {
auto matchedRules = styleResolver.pseudoStyleRulesForElement(element, pseudoId, StyleResolver::AllCSSRules);
if (!matchedRules.isEmpty()) {
auto matches = Inspector::Protocol::CSS::PseudoIdMatches::create()
.setPseudoId(static_cast<int>(pseudoId))
.setMatches(buildArrayForMatchedRuleList(matchedRules, styleResolver, element, pseudoId))
.release();
pseudoElements->addItem(WTF::move(matches));
}
}
pseudoIdMatches = WTF::move(pseudoElements);
}
// Inherited styles.
if (!includeInherited || *includeInherited) {
auto entries = Inspector::Protocol::Array<Inspector::Protocol::CSS::InheritedStyleEntry>::create();
Element* parentElement = element->parentElement();
while (parentElement) {
StyleResolver& parentStyleResolver = parentElement->document().ensureStyleResolver();
auto parentMatchedRules = parentStyleResolver.styleRulesForElement(parentElement, StyleResolver::AllCSSRules);
auto entry = Inspector::Protocol::CSS::InheritedStyleEntry::create()
.setMatchedCSSRules(buildArrayForMatchedRuleList(parentMatchedRules, styleResolver, parentElement, NOPSEUDO))
.release();
if (parentElement->style() && parentElement->style()->length()) {
if (InspectorStyleSheetForInlineStyle* styleSheet = asInspectorStyleSheet(parentElement))
entry->setInlineStyle(styleSheet->buildObjectForStyle(styleSheet->styleForId(InspectorCSSId(styleSheet->id(), 0))));
}
entries->addItem(WTF::move(entry));
parentElement = parentElement->parentElement();
}
inheritedEntries = WTF::move(entries);
}
}
}
void InspectorCSSAgent::getInlineStylesForNode(ErrorString& errorString, int nodeId, RefPtr<Inspector::Protocol::CSS::CSSStyle>& inlineStyle, RefPtr<Inspector::Protocol::CSS::CSSStyle>& attributesStyle)
{
Element* element = elementForId(errorString, nodeId);
if (!element)
return;
InspectorStyleSheetForInlineStyle* styleSheet = asInspectorStyleSheet(element);
if (!styleSheet)
return;
inlineStyle = styleSheet->buildObjectForStyle(element->style());
RefPtr<Inspector::Protocol::CSS::CSSStyle> attributes = buildObjectForAttributesStyle(element);
attributesStyle = attributes ? attributes.release() : nullptr;
}
void InspectorCSSAgent::getComputedStyleForNode(ErrorString& errorString, int nodeId, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSComputedStyleProperty>>& style)
{
Element* element = elementForId(errorString, nodeId);
if (!element)
return;
RefPtr<CSSComputedStyleDeclaration> computedStyleInfo = CSSComputedStyleDeclaration::create(element, true);
Ref<InspectorStyle> inspectorStyle = InspectorStyle::create(InspectorCSSId(), computedStyleInfo, nullptr);
style = inspectorStyle->buildArrayForComputedStyle();
}
void InspectorCSSAgent::getAllStyleSheets(ErrorString&, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSStyleSheetHeader>>& styleInfos)
{
styleInfos = Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSStyleSheetHeader>::create();
Vector<InspectorStyleSheet*> inspectorStyleSheets;
collectAllStyleSheets(inspectorStyleSheets);
for (auto* inspectorStyleSheet : inspectorStyleSheets)
styleInfos->addItem(inspectorStyleSheet->buildObjectForStyleSheetInfo());
}
void InspectorCSSAgent::collectAllStyleSheets(Vector<InspectorStyleSheet*>& result)
{
Vector<CSSStyleSheet*> cssStyleSheets;
for (auto* document : m_domAgent->documents())
collectAllDocumentStyleSheets(*document, cssStyleSheets);
for (auto* cssStyleSheet : cssStyleSheets)
result.append(bindStyleSheet(cssStyleSheet));
}
void InspectorCSSAgent::collectAllDocumentStyleSheets(Document& document, Vector<CSSStyleSheet*>& result)
{
Vector<RefPtr<CSSStyleSheet>> cssStyleSheets = document.styleSheetCollection().activeStyleSheetsForInspector();
for (auto& cssStyleSheet : cssStyleSheets)
collectStyleSheets(cssStyleSheet.get(), result);
}
void InspectorCSSAgent::collectStyleSheets(CSSStyleSheet* styleSheet, Vector<CSSStyleSheet*>& result)
{
result.append(styleSheet);
for (unsigned i = 0, size = styleSheet->length(); i < size; ++i) {
CSSRule* rule = styleSheet->item(i);
if (is<CSSImportRule>(*rule)) {
if (CSSStyleSheet* importedStyleSheet = downcast<CSSImportRule>(*rule).styleSheet())
collectStyleSheets(importedStyleSheet, result);
}
}
}
void InspectorCSSAgent::getStyleSheet(ErrorString& errorString, const String& styleSheetId, RefPtr<Inspector::Protocol::CSS::CSSStyleSheetBody>& 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->getText(result);
}
void InspectorCSSAgent::setStyleSheetText(ErrorString& errorString, const String& styleSheetId, const String& text)
{
InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, styleSheetId);
if (!inspectorStyleSheet)
return;
ExceptionCode ec = 0;
m_domAgent->history()->perform(std::make_unique<SetStyleSheetTextAction>(inspectorStyleSheet, text), ec);
errorString = InspectorDOMAgent::toErrorString(ec);
}
void InspectorCSSAgent::setStyleText(ErrorString& errorString, const InspectorObject& fullStyleId, const String& text, RefPtr<Inspector::Protocol::CSS::CSSStyle>& result)
{
InspectorCSSId compoundId(fullStyleId);
ASSERT(!compoundId.isEmpty());
InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, compoundId.styleSheetId());
if (!inspectorStyleSheet)
return;
ExceptionCode ec = 0;
bool success = m_domAgent->history()->perform(std::make_unique<SetStyleTextAction>(inspectorStyleSheet, compoundId, text), ec);
if (success)
result = inspectorStyleSheet->buildObjectForStyle(inspectorStyleSheet->styleForId(compoundId));
errorString = InspectorDOMAgent::toErrorString(ec);
}
void InspectorCSSAgent::setRuleSelector(ErrorString& errorString, const InspectorObject& fullRuleId, const String& selector, RefPtr<Inspector::Protocol::CSS::CSSRule>& result)
{
InspectorCSSId compoundId(fullRuleId);
ASSERT(!compoundId.isEmpty());
InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, compoundId.styleSheetId());
if (!inspectorStyleSheet)
return;
ExceptionCode ec = 0;
bool success = m_domAgent->history()->perform(std::make_unique<SetRuleSelectorAction>(inspectorStyleSheet, compoundId, selector), ec);
if (success)
result = inspectorStyleSheet->buildObjectForRule(inspectorStyleSheet->ruleForId(compoundId), nullptr);
errorString = InspectorDOMAgent::toErrorString(ec);
}
void InspectorCSSAgent::createStyleSheet(ErrorString& errorString, const String& frameId, String* styleSheetId)
{
Frame* frame = m_domAgent->pageAgent()->frameForId(frameId);
if (!frame) {
errorString = ASCIILiteral("No frame for given id found");
return;
}
Document* document = frame->document();
if (!document) {
errorString = ASCIILiteral("No document for frame");
return;
}
InspectorStyleSheet* inspectorStyleSheet = createInspectorStyleSheetForDocument(*document);
if (!inspectorStyleSheet) {
errorString = ASCIILiteral("Could not create stylesheet for the frame.");
return;
}
*styleSheetId = inspectorStyleSheet->id();
}
InspectorStyleSheet* InspectorCSSAgent::createInspectorStyleSheetForDocument(Document& document)
{
if (!document.isHTMLDocument() && !document.isSVGDocument())
return nullptr;
ExceptionCode ec = 0;
RefPtr<Element> styleElement = document.createElement("style", ec);
if (ec)
return nullptr;
styleElement->setAttribute("type", "text/css", ec);
if (ec)
return nullptr;
ContainerNode* targetNode;
// HEAD is absent in ImageDocuments, for example.
if (auto* head = document.head())
targetNode = head;
else if (auto* body = document.bodyOrFrameset())
targetNode = body;
else
return nullptr;
// Inserting this <style> into the document will trigger activeStyleSheetsUpdated
// and we will create an InspectorStyleSheet for this <style>'s CSSStyleSheet.
// Set this flag, so when we create it, we put it into the via inspector map.
m_creatingViaInspectorStyleSheet = true;
InlineStyleOverrideScope overrideScope(document);
targetNode->appendChild(styleElement, ec);
m_creatingViaInspectorStyleSheet = false;
if (ec)
return nullptr;
auto iterator = m_documentToInspectorStyleSheet.find(&document);
ASSERT(iterator != m_documentToInspectorStyleSheet.end());
if (iterator == m_documentToInspectorStyleSheet.end())
return nullptr;
auto& inspectorStyleSheetsForDocument = iterator->value;
ASSERT(!inspectorStyleSheetsForDocument.isEmpty());
if (inspectorStyleSheetsForDocument.isEmpty())
return nullptr;
return inspectorStyleSheetsForDocument.last().get();
}
void InspectorCSSAgent::addRule(ErrorString& errorString, const String& styleSheetId, const String& selector, RefPtr<Inspector::Protocol::CSS::CSSRule>& result)
{
InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, styleSheetId);
if (!inspectorStyleSheet) {
errorString = ASCIILiteral("No target stylesheet found");
return;
}
ExceptionCode ec = 0;
auto action = std::make_unique<AddRuleAction>(inspectorStyleSheet, selector);
AddRuleAction* rawAction = action.get();
bool success = m_domAgent->history()->perform(WTF::move(action), ec);
if (!success) {
errorString = InspectorDOMAgent::toErrorString(ec);
return;
}
InspectorCSSId ruleId = rawAction->newRuleId();
CSSStyleRule* rule = inspectorStyleSheet->ruleForId(ruleId);
result = inspectorStyleSheet->buildObjectForRule(rule, nullptr);
}
void InspectorCSSAgent::getSupportedCSSProperties(ErrorString&, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSPropertyInfo>>& cssProperties)
{
auto properties = Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSPropertyInfo>::create();
for (int i = firstCSSProperty; i <= lastCSSProperty; ++i) {
CSSPropertyID id = convertToCSSPropertyID(i);
auto property = Inspector::Protocol::CSS::CSSPropertyInfo::create()
.setName(getPropertyNameString(id))
.release();
const StylePropertyShorthand& shorthand = shorthandForProperty(id);
if (!shorthand.length()) {
properties->addItem(WTF::move(property));
continue;
}
auto longhands = Inspector::Protocol::Array<String>::create();
for (unsigned j = 0; j < shorthand.length(); ++j) {
CSSPropertyID longhandID = shorthand.properties()[j];
longhands->addItem(getPropertyNameString(longhandID));
}
property->setLonghands(WTF::move(longhands));
properties->addItem(WTF::move(property));
}
cssProperties = WTF::move(properties);
}
void InspectorCSSAgent::getSupportedSystemFontFamilyNames(ErrorString&, RefPtr<Inspector::Protocol::Array<String>>& fontFamilyNames)
{
auto families = Inspector::Protocol::Array<String>::create();
Vector<String> systemFontFamilies = FontCache::singleton().systemFontFamilies();
for (const auto& familyName : systemFontFamilies)
families->addItem(familyName);
fontFamilyNames = WTF::move(families);
}
void InspectorCSSAgent::forcePseudoState(ErrorString& errorString, int nodeId, const InspectorArray& forcedPseudoClasses)
{
Element* element = m_domAgent->assertElement(errorString, nodeId);
if (!element)
return;
unsigned forcedPseudoState = computePseudoClassMask(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->document().styleResolverChanged(RecalcStyleImmediately);
}
void InspectorCSSAgent::getNamedFlowCollection(ErrorString& errorString, int documentNodeId, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::NamedFlow>>& result)
{
Document* document = m_domAgent->assertDocument(errorString, documentNodeId);
if (!document)
return;
m_namedFlowCollectionsRequested.add(documentNodeId);
Vector<RefPtr<WebKitNamedFlow>> namedFlowsVector = document->namedFlows().namedFlows();
auto namedFlows = Inspector::Protocol::Array<Inspector::Protocol::CSS::NamedFlow>::create();
for (Vector<RefPtr<WebKitNamedFlow>>::iterator it = namedFlowsVector.begin(); it != namedFlowsVector.end(); ++it)
namedFlows->addItem(buildObjectForNamedFlow(errorString, it->get(), documentNodeId));
result = WTF::move(namedFlows);
}
InspectorStyleSheetForInlineStyle* InspectorCSSAgent::asInspectorStyleSheet(Element* element)
{
NodeToInspectorStyleSheet::iterator it = m_nodeToInspectorStyleSheet.find(element);
if (it == m_nodeToInspectorStyleSheet.end()) {
CSSStyleDeclaration* style = element->isStyledElement() ? element->style() : nullptr;
if (!style)
return nullptr;
String newStyleSheetId = String::number(m_lastStyleSheetId++);
RefPtr<InspectorStyleSheetForInlineStyle> inspectorStyleSheet = InspectorStyleSheetForInlineStyle::create(m_domAgent->pageAgent(), newStyleSheetId, element, Inspector::Protocol::CSS::StyleSheetOrigin::Regular, this);
m_idToInspectorStyleSheet.set(newStyleSheetId, inspectorStyleSheet);
m_nodeToInspectorStyleSheet.set(element, inspectorStyleSheet);
return inspectorStyleSheet.get();
}
return it->value.get();
}
Element* InspectorCSSAgent::elementForId(ErrorString& errorString, int nodeId)
{
Node* node = m_domAgent->nodeForId(nodeId);
if (!node) {
errorString = ASCIILiteral("No node with given id found");
return nullptr;
}
if (!is<Element>(*node)) {
errorString = ASCIILiteral("Not an element node");
return nullptr;
}
return downcast<Element>(node);
}
int InspectorCSSAgent::documentNodeWithRequestedFlowsId(Document* document)
{
int documentNodeId = m_domAgent->boundNodeId(document);
if (!documentNodeId || !m_namedFlowCollectionsRequested.contains(documentNodeId))
return 0;
return documentNodeId;
}
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::bindStyleSheet(CSSStyleSheet* styleSheet)
{
RefPtr<InspectorStyleSheet> inspectorStyleSheet = m_cssStyleSheetToInspectorStyleSheet.get(styleSheet);
if (!inspectorStyleSheet) {
String id = String::number(m_lastStyleSheetId++);
Document* document = styleSheet->ownerDocument();
inspectorStyleSheet = InspectorStyleSheet::create(m_domAgent->pageAgent(), id, styleSheet, detectOrigin(styleSheet, document), InspectorDOMAgent::documentURLString(document), this);
m_idToInspectorStyleSheet.set(id, inspectorStyleSheet);
m_cssStyleSheetToInspectorStyleSheet.set(styleSheet, inspectorStyleSheet);
if (m_creatingViaInspectorStyleSheet) {
auto& inspectorStyleSheetsForDocument = m_documentToInspectorStyleSheet.add(document, Vector<RefPtr<InspectorStyleSheet>>()).iterator->value;
inspectorStyleSheetsForDocument.append(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 = ASCIILiteral("No stylesheet with given id found");
return nullptr;
}
return it->value.get();
}
Inspector::Protocol::CSS::StyleSheetOrigin InspectorCSSAgent::detectOrigin(CSSStyleSheet* pageStyleSheet, Document* ownerDocument)
{
if (m_creatingViaInspectorStyleSheet)
return Inspector::Protocol::CSS::StyleSheetOrigin::Inspector;
if (pageStyleSheet && !pageStyleSheet->ownerNode() && pageStyleSheet->href().isEmpty())
return Inspector::Protocol::CSS::StyleSheetOrigin::UserAgent;
if (pageStyleSheet && pageStyleSheet->ownerNode() && pageStyleSheet->ownerNode()->nodeName() == "#document")
return Inspector::Protocol::CSS::StyleSheetOrigin::User;
auto iterator = m_documentToInspectorStyleSheet.find(ownerDocument);
if (iterator != m_documentToInspectorStyleSheet.end()) {
for (auto& inspectorStyleSheet : iterator->value) {
if (pageStyleSheet == inspectorStyleSheet->pageStyleSheet())
return Inspector::Protocol::CSS::StyleSheetOrigin::Inspector;
}
}
return Inspector::Protocol::CSS::StyleSheetOrigin::Regular;
}
RefPtr<Inspector::Protocol::CSS::CSSRule> InspectorCSSAgent::buildObjectForRule(StyleRule* styleRule, StyleResolver& styleResolver, Element* element)
{
if (!styleRule)
return nullptr;
// StyleRules returned by StyleResolver::styleRulesForElement lack parent pointers since that infomation is not cheaply available.
// Since the inspector wants to walk the parent chain, we construct the full wrappers here.
CSSStyleRule* cssomWrapper = styleResolver.inspectorCSSOMWrappers().getWrapperForRuleInSheets(styleRule, styleResolver.document().styleSheetCollection());
if (!cssomWrapper)
return nullptr;
InspectorStyleSheet* inspectorStyleSheet = bindStyleSheet(cssomWrapper->parentStyleSheet());
return inspectorStyleSheet ? inspectorStyleSheet->buildObjectForRule(cssomWrapper, element) : nullptr;
}
RefPtr<Inspector::Protocol::CSS::CSSRule> InspectorCSSAgent::buildObjectForRule(CSSStyleRule* rule)
{
if (!rule)
return nullptr;
ASSERT(rule->parentStyleSheet());
InspectorStyleSheet* inspectorStyleSheet = bindStyleSheet(rule->parentStyleSheet());
return inspectorStyleSheet ? inspectorStyleSheet->buildObjectForRule(rule, nullptr) : nullptr;
}
RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>> InspectorCSSAgent::buildArrayForMatchedRuleList(const Vector<RefPtr<StyleRule>>& matchedRules, StyleResolver& styleResolver, Element* element, PseudoId psuedoId)
{
auto result = Inspector::Protocol::Array<Inspector::Protocol::CSS::RuleMatch>::create();
SelectorChecker::CheckingContext context(SelectorChecker::Mode::CollectingRules);
context.pseudoId = psuedoId ? psuedoId : element->pseudoId();
SelectorChecker selectorChecker(element->document());
for (auto& matchedRule : matchedRules) {
RefPtr<Inspector::Protocol::CSS::CSSRule> ruleObject = buildObjectForRule(matchedRule.get(), styleResolver, element);
if (!ruleObject)
continue;
auto matchingSelectors = Inspector::Protocol::Array<int>::create();
const CSSSelectorList& selectorList = matchedRule->selectorList();
long index = 0;
for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector)) {
unsigned ignoredSpecificity;
bool matched = selectorChecker.match(selector, element, context, ignoredSpecificity);
if (matched)
matchingSelectors->addItem(index);
++index;
}
auto match = Inspector::Protocol::CSS::RuleMatch::create()
.setRule(WTF::move(ruleObject))
.setMatchingSelectors(WTF::move(matchingSelectors))
.release();
result->addItem(WTF::move(match));
}
return WTF::move(result);
}
RefPtr<Inspector::Protocol::CSS::CSSStyle> InspectorCSSAgent::buildObjectForAttributesStyle(Element* element)
{
ASSERT(element);
if (!is<StyledElement>(*element))
return nullptr;
// FIXME: Ugliness below.
StyleProperties* attributeStyle = const_cast<StyleProperties*>(downcast<StyledElement>(element)->presentationAttributeStyle());
if (!attributeStyle)
return nullptr;
ASSERT_WITH_SECURITY_IMPLICATION(attributeStyle->isMutable());
MutableStyleProperties* mutableAttributeStyle = static_cast<MutableStyleProperties*>(attributeStyle);
Ref<InspectorStyle> inspectorStyle = InspectorStyle::create(InspectorCSSId(), mutableAttributeStyle->ensureCSSStyleDeclaration(), nullptr);
return inspectorStyle->buildObjectForStyle();
}
RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::Region>> InspectorCSSAgent::buildArrayForRegions(ErrorString& errorString, RefPtr<NodeList>&& regionList, int documentNodeId)
{
auto regions = Inspector::Protocol::Array<Inspector::Protocol::CSS::Region>::create();
for (unsigned i = 0; i < regionList->length(); ++i) {
Inspector::Protocol::CSS::Region::RegionOverset regionOverset;
switch (downcast<Element>(regionList->item(i))->regionOversetState()) {
case RegionFit:
regionOverset = Inspector::Protocol::CSS::Region::RegionOverset::Fit;
break;
case RegionEmpty:
regionOverset = Inspector::Protocol::CSS::Region::RegionOverset::Empty;
break;
case RegionOverset:
regionOverset = Inspector::Protocol::CSS::Region::RegionOverset::Overset;
break;
case RegionUndefined:
continue;
default:
ASSERT_NOT_REACHED();
continue;
}
auto region = Inspector::Protocol::CSS::Region::create()
.setRegionOverset(regionOverset)
// documentNodeId was previously asserted
.setNodeId(m_domAgent->pushNodeToFrontend(errorString, documentNodeId, regionList->item(i)))
.release();
regions->addItem(WTF::move(region));
}
return WTF::move(regions);
}
RefPtr<Inspector::Protocol::CSS::NamedFlow> InspectorCSSAgent::buildObjectForNamedFlow(ErrorString& errorString, WebKitNamedFlow* webkitNamedFlow, int documentNodeId)
{
RefPtr<NodeList> contentList = webkitNamedFlow->getContent();
auto content = Inspector::Protocol::Array<int>::create();
for (unsigned i = 0; i < contentList->length(); ++i) {
// documentNodeId was previously asserted
content->addItem(m_domAgent->pushNodeToFrontend(errorString, documentNodeId, contentList->item(i)));
}
return Inspector::Protocol::CSS::NamedFlow::create()
.setDocumentNodeId(documentNodeId)
.setName(webkitNamedFlow->name().string())
.setOverset(webkitNamedFlow->overset())
.setContent(WTF::move(content))
.setRegions(buildArrayForRegions(errorString, webkitNamedFlow->getRegions(), documentNodeId))
.release();
}
void InspectorCSSAgent::didRemoveDocument(Document* document)
{
if (document)
m_documentToInspectorStyleSheet.remove(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_idToInspectorStyleSheet.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(InspectorStyleSheet* styleSheet)
{
if (m_frontendDispatcher)
m_frontendDispatcher->styleSheetChanged(styleSheet->id());
}
void InspectorCSSAgent::resetPseudoStates()
{
HashSet<Document*> documentsToChange;
for (NodeIdToForcedPseudoState::iterator it = m_nodeIdToForcedPseudoState.begin(), end = m_nodeIdToForcedPseudoState.end(); it != end; ++it) {
if (Element* element = downcast<Element>(m_domAgent->nodeForId(it->key)))
documentsToChange.add(&element->document());
}
m_nodeIdToForcedPseudoState.clear();
for (HashSet<Document*>::iterator it = documentsToChange.begin(), end = documentsToChange.end(); it != end; ++it)
(*it)->styleResolverChanged(RecalcStyleImmediately);
}
} // namespace WebCore