AuthorStyleSheets.cpp [plain text]
#include "config.h"
#include "AuthorStyleSheets.h"
#include "CSSStyleSheet.h"
#include "Element.h"
#include "ExtensionStyleSheets.h"
#include "HTMLIFrameElement.h"
#include "HTMLLinkElement.h"
#include "HTMLStyleElement.h"
#include "InspectorInstrumentation.h"
#include "Page.h"
#include "PageGroup.h"
#include "ProcessingInstruction.h"
#include "SVGNames.h"
#include "SVGStyleElement.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "StyleInvalidationAnalysis.h"
#include "StyleResolver.h"
#include "StyleSheetContents.h"
#include "StyleSheetList.h"
#include "UserContentController.h"
#include "UserContentURLPattern.h"
#include "UserStyleSheet.h"
namespace WebCore {
using namespace ContentExtensions;
using namespace HTMLNames;
AuthorStyleSheets::AuthorStyleSheets(Document& document)
: m_document(document)
{
}
AuthorStyleSheets::AuthorStyleSheets(ShadowRoot& shadowRoot)
: m_document(shadowRoot.documentScope())
, m_shadowRoot(&shadowRoot)
{
}
void AuthorStyleSheets::removePendingSheet(RemovePendingSheetNotificationType notification)
{
ASSERT(m_pendingStyleSheetCount > 0);
m_pendingStyleSheetCount--;
#ifdef INSTRUMENT_LAYOUT_SCHEDULING
if (!ownerElement())
printf("Stylesheet loaded at time %d. %d stylesheets still remain.\n", elapsedTime(), m_pendingStylesheets);
#endif
if (m_pendingStyleSheetCount)
return;
if (notification == RemovePendingSheetNotifyLater) {
m_document.setNeedsNotifyRemoveAllPendingStylesheet();
return;
}
if (m_shadowRoot) {
m_shadowRoot->updateStyle();
return;
}
m_document.didRemoveAllPendingStylesheet();
}
void AuthorStyleSheets::addStyleSheetCandidateNode(Node& node, bool createdByParser)
{
if (!node.inDocument())
return;
if ((createdByParser && m_document.bodyOrFrameset()) || m_styleSheetCandidateNodes.isEmpty()) {
m_styleSheetCandidateNodes.add(&node);
return;
}
auto begin = m_styleSheetCandidateNodes.begin();
auto end = m_styleSheetCandidateNodes.end();
auto it = end;
Node* followingNode = nullptr;
do {
--it;
Node* n = *it;
unsigned short position = n->compareDocumentPosition(&node);
if (position == Node::DOCUMENT_POSITION_FOLLOWING) {
m_styleSheetCandidateNodes.insertBefore(followingNode, &node);
return;
}
followingNode = n;
} while (it != begin);
m_styleSheetCandidateNodes.insertBefore(followingNode, &node);
}
void AuthorStyleSheets::removeStyleSheetCandidateNode(Node& node)
{
m_styleSheetCandidateNodes.remove(&node);
}
void AuthorStyleSheets::collectActiveStyleSheets(Vector<RefPtr<StyleSheet>>& sheets)
{
if (m_document.settings() && !m_document.settings()->authorAndUserStylesEnabled())
return;
for (auto& node : m_styleSheetCandidateNodes) {
StyleSheet* sheet = nullptr;
if (is<ProcessingInstruction>(*node)) {
ProcessingInstruction& pi = downcast<ProcessingInstruction>(*node);
sheet = pi.sheet();
#if ENABLE(XSLT)
if (pi.isXSL() && !m_document.transformSourceDocument()) {
if (!m_document.parsing())
m_document.applyXSLTransform(&pi);
return;
}
#endif
} else if (is<HTMLLinkElement>(*node) || is<HTMLStyleElement>(*node) || is<SVGStyleElement>(*node)) {
Element& element = downcast<Element>(*node);
AtomicString title = element.attributeWithoutSynchronization(titleAttr);
bool enabledViaScript = false;
if (is<HTMLLinkElement>(element)) {
HTMLLinkElement& linkElement = downcast<HTMLLinkElement>(element);
if (linkElement.isDisabled())
continue;
enabledViaScript = linkElement.isEnabledViaScript();
if (linkElement.styleSheetIsLoading()) {
if (!enabledViaScript && !title.isEmpty() && m_preferredStylesheetSetName.isEmpty()) {
if (!linkElement.attributeWithoutSynchronization(relAttr).contains("alternate")) {
m_preferredStylesheetSetName = title;
m_selectedStylesheetSetName = title;
}
}
continue;
}
if (!linkElement.sheet())
title = nullAtom;
}
if (is<SVGStyleElement>(element))
sheet = downcast<SVGStyleElement>(element).sheet();
else if (is<HTMLLinkElement>(element))
sheet = downcast<HTMLLinkElement>(element).sheet();
else
sheet = downcast<HTMLStyleElement>(element).sheet();
auto& rel = element.attributeWithoutSynchronization(relAttr);
if (!enabledViaScript && !title.isEmpty()) {
if (m_preferredStylesheetSetName.isEmpty()) {
if (is<HTMLStyleElement>(element) || !rel.contains("alternate"))
m_preferredStylesheetSetName = m_selectedStylesheetSetName = title;
}
if (title != m_preferredStylesheetSetName)
sheet = nullptr;
}
if (rel.contains("alternate") && title.isEmpty())
sheet = nullptr;
}
if (sheet)
sheets.append(sheet);
}
}
AuthorStyleSheets::StyleResolverUpdateType AuthorStyleSheets::analyzeStyleSheetChange(UpdateFlag updateFlag, const Vector<RefPtr<CSSStyleSheet>>& newStylesheets, bool& requiresFullStyleRecalc)
{
requiresFullStyleRecalc = true;
bool hasActiveLoadingStylesheet = false;
unsigned newStylesheetCount = newStylesheets.size();
for (auto& sheet : newStylesheets) {
if (sheet->isLoading())
hasActiveLoadingStylesheet = true;
}
if (m_hadActiveLoadingStylesheet && !hasActiveLoadingStylesheet) {
m_hadActiveLoadingStylesheet = false;
return Reconstruct;
}
m_hadActiveLoadingStylesheet = hasActiveLoadingStylesheet;
if (updateFlag != OptimizedUpdate)
return Reconstruct;
if (!m_document.styleResolverIfExists())
return Reconstruct;
StyleResolver& styleResolver = *m_document.styleResolverIfExists();
unsigned oldStylesheetCount = m_activeStyleSheets.size();
if (newStylesheetCount < oldStylesheetCount)
return Reconstruct;
Vector<StyleSheetContents*> addedSheets;
unsigned newIndex = 0;
for (unsigned oldIndex = 0; oldIndex < oldStylesheetCount; ++oldIndex) {
if (newIndex >= newStylesheetCount)
return Reconstruct;
while (m_activeStyleSheets[oldIndex] != newStylesheets[newIndex]) {
addedSheets.append(&newStylesheets[newIndex]->contents());
++newIndex;
if (newIndex == newStylesheetCount)
return Reconstruct;
}
++newIndex;
}
bool hasInsertions = !addedSheets.isEmpty();
while (newIndex < newStylesheetCount) {
addedSheets.append(&newStylesheets[newIndex]->contents());
++newIndex;
}
auto styleResolverUpdateType = hasInsertions ? Reset : Additive;
if (!m_document.bodyOrFrameset() || m_document.hasNodesWithPlaceholderStyle())
return styleResolverUpdateType;
StyleInvalidationAnalysis invalidationAnalysis(addedSheets, styleResolver.mediaQueryEvaluator());
if (invalidationAnalysis.dirtiesAllStyle())
return styleResolverUpdateType;
invalidationAnalysis.invalidateStyle(m_document);
requiresFullStyleRecalc = false;
return styleResolverUpdateType;
}
static void filterEnabledNonemptyCSSStyleSheets(Vector<RefPtr<CSSStyleSheet>>& result, const Vector<RefPtr<StyleSheet>>& sheets)
{
for (auto& sheet : sheets) {
if (!is<CSSStyleSheet>(*sheet))
continue;
CSSStyleSheet& styleSheet = downcast<CSSStyleSheet>(*sheet);
if (styleSheet.disabled())
continue;
if (!styleSheet.length())
continue;
result.append(&styleSheet);
}
}
bool AuthorStyleSheets::updateActiveStyleSheets(UpdateFlag updateFlag)
{
if (m_document.inStyleRecalc() || m_document.inRenderTreeUpdate()) {
m_pendingUpdateType = FullUpdate;
m_document.scheduleForcedStyleRecalc();
return false;
}
if (!m_document.hasLivingRenderTree())
return false;
Vector<RefPtr<StyleSheet>> activeStyleSheets;
collectActiveStyleSheets(activeStyleSheets);
Vector<RefPtr<CSSStyleSheet>> activeCSSStyleSheets;
activeCSSStyleSheets.appendVector(m_document.extensionStyleSheets().injectedAuthorStyleSheets());
activeCSSStyleSheets.appendVector(m_document.extensionStyleSheets().authorStyleSheetsForTesting());
filterEnabledNonemptyCSSStyleSheets(activeCSSStyleSheets, activeStyleSheets);
bool requiresFullStyleRecalc;
auto styleResolverUpdateType = analyzeStyleSheetChange(updateFlag, activeCSSStyleSheets, requiresFullStyleRecalc);
updateStyleResolver(activeCSSStyleSheets, styleResolverUpdateType);
m_weakCopyOfActiveStyleSheetListForFastLookup = nullptr;
m_activeStyleSheets.swap(activeCSSStyleSheets);
m_styleSheetsForStyleSheetList.swap(activeStyleSheets);
InspectorInstrumentation::activeStyleSheetsUpdated(m_document);
for (const auto& sheet : m_activeStyleSheets) {
if (sheet->contents().usesRemUnits())
m_usesRemUnits = true;
if (sheet->contents().usesStyleBasedEditability())
m_usesStyleBasedEditability = true;
}
m_pendingUpdateType = NoUpdate;
return requiresFullStyleRecalc;
}
void AuthorStyleSheets::updateStyleResolver(Vector<RefPtr<CSSStyleSheet>>& activeStyleSheets, StyleResolverUpdateType updateType)
{
if (updateType == Reconstruct) {
if (m_shadowRoot)
m_shadowRoot->resetStyleResolver();
else
m_document.clearStyleResolver();
return;
}
auto& styleResolver = m_document.ensureStyleResolver();
auto& userAgentShadowTreeStyleResolver = m_document.userAgentShadowTreeStyleResolver();
if (updateType == Reset) {
styleResolver.ruleSets().resetAuthorStyle();
styleResolver.appendAuthorStyleSheets(activeStyleSheets);
} else {
ASSERT(updateType == Additive);
unsigned firstNewIndex = m_activeStyleSheets.size();
Vector<RefPtr<CSSStyleSheet>> newStyleSheets;
newStyleSheets.appendRange(activeStyleSheets.begin() + firstNewIndex, activeStyleSheets.end());
styleResolver.appendAuthorStyleSheets(newStyleSheets);
}
userAgentShadowTreeStyleResolver.ruleSets().resetAuthorStyle();
auto& authorRuleSet = *styleResolver.ruleSets().authorStyle();
if (authorRuleSet.hasShadowPseudoElementRules())
userAgentShadowTreeStyleResolver.ruleSets().authorStyle()->copyShadowPseudoElementRulesFrom(authorRuleSet);
}
const Vector<RefPtr<CSSStyleSheet>> AuthorStyleSheets::activeStyleSheetsForInspector() const
{
Vector<RefPtr<CSSStyleSheet>> result;
result.appendVector(m_document.extensionStyleSheets().injectedAuthorStyleSheets());
result.appendVector(m_document.extensionStyleSheets().authorStyleSheetsForTesting());
for (auto& styleSheet : m_styleSheetsForStyleSheetList) {
if (!is<CSSStyleSheet>(*styleSheet))
continue;
CSSStyleSheet& sheet = downcast<CSSStyleSheet>(*styleSheet);
if (sheet.disabled())
continue;
result.append(&sheet);
}
return result;
}
bool AuthorStyleSheets::activeStyleSheetsContains(const CSSStyleSheet* sheet) const
{
if (!m_weakCopyOfActiveStyleSheetListForFastLookup) {
m_weakCopyOfActiveStyleSheetListForFastLookup = std::make_unique<HashSet<const CSSStyleSheet*>>();
for (auto& activeStyleSheet : m_activeStyleSheets)
m_weakCopyOfActiveStyleSheetListForFastLookup->add(activeStyleSheet.get());
}
return m_weakCopyOfActiveStyleSheetListForFastLookup->contains(sheet);
}
}