Commit a157286b authored by tasak@google.com's avatar tasak@google.com

Avoid parsing css text if there are identical inline style blocks.

(1) StyleEngine has two hashmaps, one is a mapping from css text to StyleSheetContents and the other is a mapping from StyleSheetContents to css text. When destroying StyleSheetContents, using the above mapping to remove css text.
The hashmaps are only for non-quirks document. Because non-quirks document cannot share styles with quirks document.
 
(2) made StyleSheetContents::checkLoaded to invoke new clients' sheetLoaded. To do so,
  (2-1) moved m_loadCompleted flag from StyleSheetContents to CSSStyleSheet,
  (2-2) added more "protect" to StyleSheetContents::checkLoaded to avoid CSSStyleSheet (i.e. client) and ownerNode being deleted by scripts, and
  (2-3) modified StyleSheetContents::loadCompleted() to see whether all clients completed loading.

(3) LinkStyle::setCSSStyleSheet should use StyleSheetContents' checkLoaded. Because now checkLoaded invokes only new clients' sheetLoaded. We don't need to directly invoke LinkStyle::sheetLoaded (this breaks m_loadCompleted flag and causes assertion failure). 

BUG=308781
TEST=all existing tests covers, so the tests should pass.

Review URL: https://codereview.chromium.org/28553005

git-svn-id: svn://svn.chromium.org/blink/trunk@165139 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent c67ea802
<!doctype html>
<html>
<head>
<script src="../resources/runner.js"></script>
</head>
<body><iframe></iframe>
</body>
<script>
function setup() {
var frame = document.getElementsByTagName("iframe")[0];
var testDoc = frame.contentDocument;
testDoc.body.innerHTML = "<head></head><body><div id='sandbox'></div></body>";
return testDoc;
}
PerfTestRunner.measureTime({run:function() {
var testDoc = setup();
var start = PerfTestRunner.now();
var sandbox = testDoc.getElementById('sandbox');
var templateA = testDoc.getElementById('templateA');
var templateB = testDoc.getElementById('templateB');
for (var i = 0; i < 100; i++) {
var elementA = testDoc.createElement("div");
var shadowRootForA = elementA.createShadowRoot();
shadowRootForA.innerHTML = "<style>@host{:scope{display:block}}ul.canaryisdev li:nth-of-type(4)::after{content:'canary/dev'}ul.canaryisdev li:nth-of-type(5)::after{content:'beta'}ul.canaryisdev li:nth-of-type(6)::after{content:'stable'}ul.canaryisdev li:nth-of-type(7)::after{content:''}ul.betaisdev li:nth-of-type(4)::after{content:'canary'}ul.betaisdev li:nth-of-type(5)::after{content:'dev/beta'}ul.betaisdev li:nth-of-type(6)::after{content:'stable'}ul.betaisdev li:nth-of-type(7)::after{content:''}ul li{cursor:pointer;padding:3px 0}ul li::before{-webkit-transition:all 500ms ease;-moz-transition:all 500ms ease;-o-transition:all 500ms ease;transition:all 500ms ease;content:'';margin-right:5px;border-left:3px solid transparent}ul li::after{font-size:75%;margin-left:10px}ul li:first-of-type,ul li:nth-of-type(2),ul li:nth-of-type(3){font-size:75%;font-style:italic}ul li:nth-of-type(3){border-bottom:1px solid #d4d4d4;padding-bottom:10px;margin-bottom:5px}ul li:nth-of-type(4)::after{content:'canary'}ul li:nth-of-type(5)::after{content:'dev'}ul li:nth-of-type(6)::after{content:'beta'}ul li:nth-of-type(7)::after{content:'stable'}ul li[selected]{font-weight:600;color:#366597}ul li[selected]::before{border-color:#366597}</style><div>A</div>";
sandbox.appendChild(elementA);
var elementB = testDoc.createElement("div");
var shadowRootForB = elementB.createShadowRoot();
shadowRootForB.innerHTML = "<style>@host{:scope{display:block;padding:1px}}.milestone-marker{text-align:right;text-transform:uppercase;margin-top:10px;font-weight:600;font-size:14px;color:#366597}@media only screen and (max-width: 700px){[data-first-of-milestone]:after{font-size:12px;font-weight:normal;top:-22px;opacity:1;text-transform:uppercase}}</style><div>B</div>";
sandbox.appendChild(elementB);
}
return PerfTestRunner.now() - start;
}});
</script>
</html>
......@@ -80,6 +80,12 @@ PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> s
return adoptRef(new CSSStyleSheet(sheet, ownerNode, false, TextPosition::minimumPosition()));
}
PassRefPtr<CSSStyleSheet> CSSStyleSheet::createInline(PassRefPtr<StyleSheetContents> sheet, Node* ownerNode, const TextPosition& startPosition)
{
ASSERT(sheet);
return adoptRef(new CSSStyleSheet(sheet, ownerNode, true, startPosition));
}
PassRefPtr<CSSStyleSheet> CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const TextPosition& startPosition, const String& encoding)
{
CSSParserContext parserContext(ownerNode->document(), baseURL, encoding);
......@@ -94,6 +100,7 @@ CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, CSSImportR
, m_ownerNode(0)
, m_ownerRule(ownerRule)
, m_startPosition(TextPosition::minimumPosition())
, m_loadCompleted(false)
{
m_contents->registerClient(this);
}
......@@ -105,6 +112,7 @@ CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, Node* owne
, m_ownerNode(ownerNode)
, m_ownerRule(0)
, m_startPosition(startPosition)
, m_loadCompleted(false)
{
ASSERT(isAcceptableCSSStyleSheetParent(ownerNode));
m_contents->registerClient(this);
......@@ -132,6 +140,8 @@ void CSSStyleSheet::willMutateRules()
// If we are the only client it is safe to mutate.
if (m_contents->hasOneClient() && !m_contents->isInMemoryCache()) {
m_contents->clearRuleSet();
if (m_contents->maybeCacheable())
StyleEngine::removeSheet(m_contents.get());
m_contents->setMutable();
return;
}
......@@ -381,4 +391,16 @@ void CSSStyleSheet::clearChildRuleCSSOMWrappers()
m_childRuleCSSOMWrappers.clear();
}
bool CSSStyleSheet::sheetLoaded()
{
m_loadCompleted = m_ownerNode->sheetLoaded();
return m_loadCompleted;
}
void CSSStyleSheet::startLoadingDynamicSheet()
{
m_loadCompleted = false;
m_ownerNode->startLoadingDynamicSheet();
}
}
......@@ -51,6 +51,7 @@ public:
static PassRefPtr<CSSStyleSheet> create(PassRefPtr<StyleSheetContents>, CSSImportRule* ownerRule = 0);
static PassRefPtr<CSSStyleSheet> create(PassRefPtr<StyleSheetContents>, Node* ownerNode);
static PassRefPtr<CSSStyleSheet> createInline(Node*, const KURL&, const TextPosition& startPosition = TextPosition::minimumPosition(), const String& encoding = String());
static PassRefPtr<CSSStyleSheet> createInline(PassRefPtr<StyleSheetContents>, Node* ownerNode, const TextPosition& startPosition = TextPosition::minimumPosition());
virtual ~CSSStyleSheet();
......@@ -110,6 +111,10 @@ public:
bool isInline() const { return m_isInlineStylesheet; }
TextPosition startPositionInSource() const { return m_startPosition; }
bool sheetLoaded();
bool loadCompleted() const { return m_loadCompleted; }
void startLoadingDynamicSheet();
private:
CSSStyleSheet(PassRefPtr<StyleSheetContents>, CSSImportRule* ownerRule);
CSSStyleSheet(PassRefPtr<StyleSheetContents>, Node* ownerNode, bool isInlineStylesheet, const TextPosition& startPosition);
......@@ -131,6 +136,7 @@ private:
CSSRule* m_ownerRule;
TextPosition m_startPosition;
bool m_loadCompleted;
mutable RefPtr<MediaList> m_mediaCSSOMWrapper;
mutable Vector<RefPtr<CSSRule> > m_childRuleCSSOMWrappers;
......
......@@ -59,7 +59,6 @@ unsigned StyleSheetContents::estimatedSizeInBytes() const
StyleSheetContents::StyleSheetContents(StyleRuleImport* ownerRule, const String& originalURL, const CSSParserContext& context)
: m_ownerRule(ownerRule)
, m_originalURL(originalURL)
, m_loadCompleted(false)
, m_hasSyntacticallyValidCSSHeader(true)
, m_didLoadErrorOccur(false)
, m_usesRemUnits(false)
......@@ -79,7 +78,6 @@ StyleSheetContents::StyleSheetContents(const StyleSheetContents& o)
, m_importRules(o.m_importRules.size())
, m_childRules(o.m_childRules.size())
, m_namespaces(o.m_namespaces)
, m_loadCompleted(true)
, m_hasSyntacticallyValidCSSHeader(o.m_hasSyntacticallyValidCSSHeader)
, m_didLoadErrorOccur(false)
, m_usesRemUnits(o.m_usesRemUnits)
......@@ -100,10 +98,18 @@ StyleSheetContents::StyleSheetContents(const StyleSheetContents& o)
StyleSheetContents::~StyleSheetContents()
{
StyleEngine::removeSheet(this);
clearRules();
}
bool StyleSheetContents::isCacheable() const
void StyleSheetContents::setHasSyntacticallyValidCSSHeader(bool isValidCss)
{
if (maybeCacheable() && !isValidCss)
StyleEngine::removeSheet(this);
m_hasSyntacticallyValidCSSHeader = isValidCss;
}
bool StyleSheetContents::maybeCacheable() const
{
// FIXME: StyleSheets with media queries can't be cached because their RuleSet
// is processed differently based off the media queries, which might resolve
......@@ -118,9 +124,6 @@ bool StyleSheetContents::isCacheable() const
// FIXME: Support cached stylesheets in import rules.
if (m_ownerRule)
return false;
// This would require dealing with multiple clients for load callbacks.
if (!m_loadCompleted)
return false;
if (m_didLoadErrorOccur)
return false;
// It is not the original sheet anymore.
......@@ -133,6 +136,14 @@ bool StyleSheetContents::isCacheable() const
return true;
}
bool StyleSheetContents::isCacheable() const
{
// This would require dealing with multiple clients for load callbacks.
if (!loadCompleted())
return false;
return maybeCacheable();
}
void StyleSheetContents::parserAppendRule(PassRefPtr<StyleRuleBase> rule)
{
ASSERT(!rule->isCharsetRule());
......@@ -347,6 +358,20 @@ bool StyleSheetContents::isLoading() const
return false;
}
bool StyleSheetContents::loadCompleted() const
{
StyleSheetContents* parentSheet = parentStyleSheet();
if (parentSheet)
return parentSheet->loadCompleted();
StyleSheetContents* root = rootStyleSheet();
for (unsigned i = 0; i < root->m_clients.size(); ++i) {
if (!root->m_clients[i]->loadCompleted())
return false;
}
return true;
}
void StyleSheetContents::checkLoaded()
{
if (isLoading())
......@@ -360,17 +385,26 @@ void StyleSheetContents::checkLoaded()
StyleSheetContents* parentSheet = parentStyleSheet();
if (parentSheet) {
parentSheet->checkLoaded();
m_loadCompleted = true;
return;
}
RefPtr<Node> ownerNode = singleOwnerNode();
if (!ownerNode) {
m_loadCompleted = true;
StyleSheetContents* root = rootStyleSheet();
if (root->m_clients.isEmpty())
return;
Vector<CSSStyleSheet*> clients(root->m_clients);
for (unsigned i = 0; i < clients.size(); ++i) {
// Avoid |CSSSStyleSheet| and |ownerNode| being deleted by scripts that run via
// ScriptableDocumentParser::executeScriptsWaitingForResources().
RefPtr<CSSStyleSheet> protectClient(clients[i]);
if (clients[i]->loadCompleted())
continue;
RefPtr<Node> ownerNode = clients[i]->ownerNode();
if (clients[i]->sheetLoaded())
ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
}
m_loadCompleted = ownerNode->sheetLoaded();
if (m_loadCompleted)
ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
}
void StyleSheetContents::notifyLoadedSheet(const CSSStyleSheetResource* sheet)
......@@ -385,8 +419,9 @@ void StyleSheetContents::notifyLoadedSheet(const CSSStyleSheetResource* sheet)
void StyleSheetContents::startLoadingDynamicSheet()
{
if (Node* owner = singleOwnerNode())
owner->startLoadingDynamicSheet();
StyleSheetContents* root = rootStyleSheet();
for (unsigned i = 0; i < root->m_clients.size(); ++i)
root->m_clients[i]->startLoadingDynamicSheet();
}
StyleSheetContents* StyleSheetContents::rootStyleSheet() const
......
......@@ -69,6 +69,7 @@ public:
bool parseStringAtPosition(const String&, const TextPosition&, bool);
bool isCacheable() const;
bool maybeCacheable() const;
bool isLoading() const;
......@@ -82,12 +83,12 @@ public:
const String& charset() const { return m_parserContext.charset(); }
bool loadCompleted() const { return m_loadCompleted; }
bool loadCompleted() const;
bool hasFailedOrCanceledSubresources() const;
KURL completeURL(const String& url) const;
void setHasSyntacticallyValidCSSHeader(bool b) { m_hasSyntacticallyValidCSSHeader = b; }
void setHasSyntacticallyValidCSSHeader(bool isValidCss);
bool hasSyntacticallyValidCSSHeader() const { return m_hasSyntacticallyValidCSSHeader; }
void setHasFontFaceRule(bool b) { m_hasFontFaceRule = b; }
......@@ -165,7 +166,6 @@ private:
typedef HashMap<AtomicString, AtomicString> PrefixNamespaceURIMap;
PrefixNamespaceURIMap m_namespaces;
bool m_loadCompleted : 1;
bool m_hasSyntacticallyValidCSSHeader : 1;
bool m_didLoadErrorOccur : 1;
bool m_usesRemUnits : 1;
......
......@@ -135,15 +135,10 @@ void StyleElement::createSheet(Element* e, const String& text)
MediaQueryEvaluator screenEval("screen", true);
MediaQueryEvaluator printEval("print", true);
if (screenEval.eval(mediaQueries.get()) || printEval.eval(mediaQueries.get())) {
document.styleEngine()->addPendingSheet();
m_loading = true;
TextPosition startPosition = m_startPosition == TextPosition::belowRangePosition() ? TextPosition::minimumPosition() : m_startPosition;
m_sheet = CSSStyleSheet::createInline(e, KURL(), startPosition, document.inputEncoding());
m_sheet = StyleEngine::createSheet(e, text, startPosition, m_createdByParser);
m_sheet->setMediaQueries(mediaQueries.release());
m_sheet->setTitle(e->title());
m_sheet->contents()->parseStringAtPosition(text, startPosition, m_createdByParser);
m_loading = false;
}
}
......
......@@ -52,6 +52,20 @@ namespace WebCore {
using namespace HTMLNames;
static HashMap<AtomicString, StyleSheetContents*>& textToSheetCache()
{
typedef HashMap<AtomicString, StyleSheetContents*> TextToSheetCache;
DEFINE_STATIC_LOCAL(TextToSheetCache, cache, ());
return cache;
}
static HashMap<StyleSheetContents*, AtomicString>& sheetToTextCache()
{
typedef HashMap<StyleSheetContents*, AtomicString> SheetToTextCache;
DEFINE_STATIC_LOCAL(SheetToTextCache, cache, ());
return cache;
}
StyleEngine::StyleEngine(Document& document)
: m_document(document)
, m_isMaster(HTMLImport::isMaster(&document))
......@@ -574,4 +588,52 @@ void StyleEngine::markDocumentDirty()
m_document.import()->master()->styleEngine()->markDocumentDirty();
}
PassRefPtr<CSSStyleSheet> StyleEngine::createSheet(Element* e, const String& text, TextPosition startPosition, bool createdByParser)
{
RefPtr<CSSStyleSheet> styleSheet;
e->document().styleEngine()->addPendingSheet();
if (!e->document().inQuirksMode()) {
AtomicString textContent(text);
HashMap<AtomicString, StyleSheetContents*>::AddResult result = textToSheetCache().add(textContent, 0);
if (result.isNewEntry || !result.iterator->value) {
styleSheet = StyleEngine::parseSheet(e, text, startPosition, createdByParser);
if (result.isNewEntry && styleSheet->contents()->maybeCacheable()) {
result.iterator->value = styleSheet->contents();
sheetToTextCache().add(styleSheet->contents(), textContent);
}
} else {
ASSERT(result.iterator->value->maybeCacheable());
styleSheet = CSSStyleSheet::createInline(result.iterator->value, e, startPosition);
}
} else {
// FIXME: currently we don't cache StyleSheetContents inQuirksMode.
styleSheet = StyleEngine::parseSheet(e, text, startPosition, createdByParser);
}
ASSERT(styleSheet);
styleSheet->setTitle(e->title());
return styleSheet;
}
PassRefPtr<CSSStyleSheet> StyleEngine::parseSheet(Element* e, const String& text, TextPosition startPosition, bool createdByParser)
{
RefPtr<CSSStyleSheet> styleSheet;
styleSheet = CSSStyleSheet::createInline(e, KURL(), startPosition, e->document().inputEncoding());
styleSheet->contents()->parseStringAtPosition(text, startPosition, createdByParser);
return styleSheet;
}
void StyleEngine::removeSheet(StyleSheetContents* contents)
{
HashMap<StyleSheetContents*, AtomicString>::iterator it = sheetToTextCache().find(contents);
if (it == sheetToTextCache().end())
return;
textToSheetCache().remove(it->value);
sheetToTextCache().remove(contents);
}
}
......@@ -175,6 +175,9 @@ public:
void collectDocumentActiveStyleSheets(StyleSheetCollectionBase&);
void markDocumentDirty();
static PassRefPtr<CSSStyleSheet> createSheet(Element*, const String& text, TextPosition startPosition, bool createdByParser);
static void removeSheet(StyleSheetContents*);
private:
StyleEngine(Document&);
......@@ -197,6 +200,8 @@ private:
void notifyPendingStyleSheetAdded();
void notifyPendingStyleSheetRemoved(RemovePendingSheetNotificationType);
static PassRefPtr<CSSStyleSheet> parseSheet(Element*, const String& text, TextPosition startPosition, bool createdByParser);
Document& m_document;
bool m_isMaster;
......
......@@ -388,8 +388,7 @@ void LinkStyle::setCSSStyleSheet(const String& href, const KURL& baseURL, const
m_sheet->setTitle(m_owner->title());
m_loading = false;
sheetLoaded();
notifyLoadedSheetAndAllCriticalSubresources(false);
restoredSheet->checkLoaded();
return;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment