이전에 작성한 linkElement에 이어 style element로 media query를 주었을 때 웹킷이 처리하는 과정을 정리해보고자 합니다.
style element에 대한 처리를 알려면, webkit이 style element을 처리하는 전반적인 플로우를 이해해야 합니다.
하지만, 여기서는 되도록 media query에 집중해서 설명하려고 노력할 생각입니다.
이전글 : Media query in webkit(link Element)
다음글 : Media query in webkit(import/media rule)
이번에 다룰 예제는 아래와 같습니다.
### html
<!doctype html>
<head>
<style media="screen and (min-width:300px)">
body { background-color:red; }
</style>
<body>
hello
</body>
HTML specification에서 style element를 보면 attribute로 media를 지정할 수 있습니다.
먼저 이 코드를 크롬으로 로드하여 inspector를 통해 document.styleSheets를 보도록 하겠습니다.
StyleSheetList에 한 개의 stylesheet가 있고, 0번째 CSSStyleSheet에는 cssRules, disabled, href, media, 등등이 있습니다.
MediaList타입의 media 프로퍼티에는 style element의 attribute로 넘긴 media query 정보가 들어 있음을 알 수 있습니다.
CSSRuleList타입의 cssRules에는 1개의 CSSStyleRule이 있고 이 안에 지정한 스타일 정보가 들어있음을 알 수 있습니다.
여기서 표현되는 CSSStyleSheet, CSSRuleList, CSSStyleRul은 웹킷 내부에 동일한 이름의 클래스와 매칭됩니다.
이번 글에서 다루진 않겠지만 참고 삼아, 예제를 조금 고쳐보겠습니다. 이번에는 @media rule을 사용한 경우입니다.
### html
<!doctype html>
<head>
<style>
@media screen and (min-width:300px) {
body { background-color:red; }
}
</style>
<body>
hello
</body>
이렇게 변경하면 CSSStyleSheet가 가진 media 프로퍼티에는 아무런 값이 존재하지 않습니다.
대신 cssRules 가 직접 CSSStyleRule을 갖는 것이 아니라, CSSMediaRule을 갖는 것을 알 수 있습니다. CSSMediaRule이 CSSStyleRule을 갖습니다.
[WebKit Internal]
WebKit에서 Style tag를 담당하는 클래스는 HTMLStyleElement와 StyleElement입니다.
HTMLStyleElement는 StyleElement를 상속받습니다. (class HTMLStyleElement : public HTMLElement, private StyleElement)
SVGStyleElement와 공용으로 쓸 수 있는 기능들이 StyleElement안에 있는듯 합니다.
먼저 </style>태그를 만나 읽게 되면 HTMLElement들은 finishParsingChildren을 호출합니다.
HTMLStyleElement 도 해당 함수의 구현을 갖고 있습니다.
### c++
void HTMLStyleElement::finishParsingChildren()
{
StyleElement::finishParsingChildren(this);
HTMLElement::finishParsingChildren();
}
여기서 관심을 갖는 부분은 StyleElement입니다.
### c++ ; highlight : [29, 34]
void StyleElement::finishParsingChildren(Element* element)
{
ASSERT(element);
process(element);
m_createdByParser = false;
}
void StyleElement::process(Element* e)
{
if (!e || !e->inDocument())
return;
unsigned resultLength = 0;
for (Node* c = e->firstChild(); c; c = c->nextSibling()) {
if (isValidStyleChild(c)) {
unsigned length = c->nodeValue().length();
if (length > std::numeric_limits<unsigned>::max() - resultLength) {
createSheet(e, m_startLineNumber, "");
return;
}
resultLength += length;
}
}
StringBuilder sheetText;
sheetText.reserveCapacity(resultLength);
for (Node* c = e->firstChild(); c; c = c->nextSibling()) {
if (isValidStyleChild(c)) {
sheetText.append(c->nodeValue());
}
}
ASSERT(sheetText.length() == resultLength);
createSheet(e, m_startLineNumber, sheetText.toString());
}
StyleElement::finishParsingChildren가 하는 일도 간단히 process()를 부르는 일이 전부입니다.
process()는
1. parsing이 끝났을때(finishParsingChildren),
2. StyleElement가 document에 insert 되었을 때(insertIntoDocument)
3. 자식 노드들이 바뀌었을 때(childrendChanged)
호출됩니다.
process()안을 보시면, child node 가운데 validStyleChild만 찾아서 해당하는 sheetText에 추가하는 것을 알 수 있습니다.(29 line)
위 예제에서는 하나의 노드(TextNode)만을 가지고
값(nodeValue)은 "
body { background-color:red; }
" 입니다. (<style>부터 </style>까지 이기 때문에 위 예제에서는 앞뒤로 newline이 있습니다.)
### c++
static bool isValidStyleChild(Node* node)
{
ASSERT(node);
Node::NodeType nodeType = node->nodeType();
return nodeType == Node::TEXT_NODE || nodeType == Node::CDATA_SECTION_NODE;
}
validStyleChild는 단순히 TEXT_NODE와 CDATA_SECTION_NODE 인지 여부만 체크합니다.
사실상 핵심은 sheet를 만드는 것입니다.(StyleElement::process의 34 line)
StyleElement::process는 엘리먼트(e)와 시작라인, sheetText 문자열(valid한 child node들의 문자열)을 createSheet에게 넘겨줍니다.
### c++ ; highlight: [6,7,8,9,10, 17]
void StyleElement::createSheet(Element* e, WTF::OrdinalNumber startLineNumber, const String& text)
{
ASSERT(e);
ASSERT(e->inDocument());
Document* document = e->document();
if (m_sheet) {
if (m_sheet->isLoading())
document->styleSheetCollection()->removePendingSheet();
clearSheet();
}
// If type is empty or CSS, this is a CSS style sheet.
const AtomicString& type = this->type();
if (document->contentSecurityPolicy()->allowInlineStyle(e->document()->url(), startLineNumber) && isCSS(e, type)) {
RefPtr<MediaQuerySet> mediaQueries;
if (e->isHTMLElement())
mediaQueries = MediaQuerySet::createAllowingDescriptionSyntax(media());
else
mediaQueries = MediaQuerySet::create(media());
MediaQueryEvaluator screenEval("screen", true);
MediaQueryEvaluator printEval("print", true);
if (screenEval.eval(mediaQueries.get()) || printEval.eval(mediaQueries.get())) {// ... 생략, 아래에서 계속
}
}
if (m_sheet)
m_sheet->contents()->checkLoaded();
}
먼저 기존에 만든 CSSStyleSheet이 있는지 확인하고, 있다면 제거합니다. (6-10 line). 이 때 CSSStyleSheet이 로딩중이면 제거합니다.(8line)
StyleElement가 가진 CSSStyleSheet이 로딩중인 케이스는 import rule을 생각하시면 됩니다.
현재 StyleElement는 HTMLStyleElement 에서 this를 받아왔기 때문에 MediaQuerySet::createAllowingDescriptionSyntax(media())를 호출합니다.(17 line)
Media query in webkit(linkElement)를 읽으신 분들은 MediaQueryEvaluator을 만들때 추가로 요구하는 값들이 있었다는 것을 기억해주시기 바랍니다.
FrameView의 mediaType, frame, documentStyle입니다. 여기서는 FrameView의 media type대신 "screen"과 "print" 두개의 MediaQueryEvaluator를 만들고 있고 다른 값들은 넘겨주지 않습니다.
다른 값들이 없으면 media feature를 확인하는 것이 불가능합니다.
이전 글에서 colorMediaFeatureEval의 구현을 보시면, frame을 이용하여 screenDepth를 구하고 있는 것을 알 수 있습니다.
여기서는 대신 두 번째 인자로 true를 넘겨주고 있습니다. 이 값은 MediaQueryEvaluator의 멤버인 m_expResult의 값이 됩니다.
그리고 MediaQueryEvaluator::eval(MediaQueryExp*) const을 다시 보시면,
### c++ ; hilight : [3, 4]
bool MediaQueryEvaluator::eval(const MediaQueryExp* expr) const
{
if (!m_frame || !m_style)
return m_expResult;
if (!expr->isValid())
return false;
if (!gFunctionMap)
createFunctionMap();
// call the media feature evaluation function. Assume no prefix
// and let trampoline functions override the prefix if prefix is
// used
EvalFunc func = gFunctionMap->get(expr->mediaFeature().impl());
if (func)
return func(expr->value(), m_style.get(), m_frame, NoPrefix);
return false;
}
frame과 style의 값이 없을 때 m_expResult의 값을 반환합니다.(4 line)
즉, StyleElement::createSheet에서는 media type만 확인하고 true를 반환합니다.
### c++ ; highlight : [ 7, 10, 11, 13 ]
void StyleElement::createSheet(Element* e, WTF::OrdinalNumber startLineNumber, const String& text)
{
// ... 생략, 위에서 계속
MediaQueryEvaluator screenEval("screen", true);
MediaQueryEvaluator printEval("print", true);
if (screenEval.eval(mediaQueries.get()) || printEval.eval(mediaQueries.get())) {
document->styleSheetCollection()->addPendingSheet();
m_loading = true;
m_sheet = CSSStyleSheet::createInline(e, KURL(), document->inputEncoding());
m_sheet->setMediaQueries(mediaQueries.release());
m_sheet->setTitle(e->title());
m_sheet->contents()->parseStringAtLine(text, startLineNumber.zeroBasedInt());
m_loading = false;
}
}
if (m_sheet)
m_sheet->contents()->checkLoaded();
}
media type이 screen이나 print를 포함한다면, StyleElement::createSheet의 7-15 line에서 CSSStyleSheet를 만듭니다.
먼저 document에 pending된 스타일 시트가 있음을 알립니다.(7 line). document에 pending된 스타일 시트가 있으면 웹코어는 layout을 주기적으로 스케줄링합니다.
그리고, CSSStyleSheet::createInline을 통해 stylesheet를 만듭니다.(9 line) 이 때, CSSStyleSheet::createInline은 StyleSheetContents를 만드는데 여기가 parsing된 결과물(CSSRule)이 저장되는 곳입니다.,
이 후, media query를 설정후(10 line), 넘겨받은 text data의 파싱을 수행합니다. (11 line)
parsing이 끝나면, checkLoaded를 통해 style을 적용할지를 결정합니다.
### c++ ; highlight : 23
void StyleSheetContents::checkLoaded()
{
if (isLoading())
return;
RefPtr<StyleSheetContents> protect(this);
// Avoid |this| being deleted by scripts that run via
// ScriptableDocumentParser::executeScriptsWaitingForStylesheets().
// See <rdar://problem/6622300>.
RefPtr<StyleSheetContents> protector(this);
StyleSheetContents* parentSheet = parentStyleSheet();
if (parentSheet) {
parentSheet->checkLoaded();
m_loadCompleted = true;
return;
}
RefPtr<Node> ownerNode = singleOwnerNode();
if (!ownerNode) {
m_loadCompleted = true;
return;
}
m_loadCompleted = ownerNode->sheetLoaded();
if (m_loadCompleted)
ownerNode->notifyLoadedSheetAndAllCriticalSubresources(m_didLoadErrorOccur);
}
먼저 자신이 로딩중이면, return합니다 (3 line). 예제에서는 로딩할 것이 없으므로 파싱이 끝나면 로딩도 끝납니다. 다음엔 parentSheet가 있으면 parentSheet를 check하는데 여기서는 parentSheet가 없으므로 ownerNode(StyleElement)의 sheetLoaded를 호출합니다. (23 line)
### c++
bool StyleElement::sheetLoaded(Document* document)
{
ASSERT(document);
if (isLoading())
return false;
document->styleSheetCollection()->removePendingSheet();
return true;
}
pendingSheet를 제거합니다.
### c++ ; highlight : [14, 22]
// This method is called whenever a top-level stylesheet has finished loading.
void DocumentStyleSheetCollection::removePendingSheet(RemovePendingSheetNotificationType notification)
{
// Make sure we knew this sheet was pending, and that our count isn't out of sync.
ASSERT(m_pendingStylesheets > 0);
m_pendingStylesheets--;
#ifdef INSTRUMENT_LAYOUT_SCHEDULING
if (!ownerElement())
printf("Stylesheet loaded at time %d. %d stylesheets still remain.\n", elapsedTime(), m_pendingStylesheets);
#endif
if (m_pendingStylesheets)
return;
if (notification == RemovePendingSheetNotifyLater) {
m_document->setNeedsNotifyRemoveAllPendingStylesheet();
return;
}
m_document->didRemoveAllPendingStylesheet();
}
pendingStyleSheets가 더이상 없다면(14 line), didRemoveAllPendingStylesheet를 호출합니다.
### c++ ; highlight : 5
void Document::didRemoveAllPendingStylesheet()
{
m_needsNotifyRemoveAllPendingStylesheet = false;
styleResolverChanged(RecalcStyleIfNeeded);
if (ScriptableDocumentParser* parser = scriptableDocumentParser())
parser->executeScriptsWaitingForStylesheets();
if (m_gotoAnchorNeededAfterStylesheetsLoad && view())
view()->scrollToFragment(m_url);
}
StyleResolve가 변경되었음을 알려줍니다.(5 line)
### c++ ; highlight : 19
void Document::styleResolverChanged(StyleResolverUpdateFlag updateFlag)
{
// Don't bother updating, since we haven't loaded all our style info yet
// and haven't calculated the style selector for the first time.
if (!attached() || (!m_didCalculateStyleResolver && !haveStylesheetsLoaded())) {
m_styleResolver.clear();
return;
}
m_didCalculateStyleResolver = true;
#ifdef INSTRUMENT_LAYOUT_SCHEDULING
if (!ownerElement())
printf("Beginning update of style selector at time %d.\n", elapsedTime());
#endif
DocumentStyleSheetCollection::UpdateFlag styleSheetUpdate = (updateFlag == RecalcStyleIfNeeded)
? DocumentStyleSheetCollection::OptimizedUpdate
: DocumentStyleSheetCollection::FullUpdate;
bool stylesheetChangeRequiresStyleRecalc = m_styleSheetCollection->updateActiveStyleSheets(styleSheetUpdate);
if (updateFlag == DeferRecalcStyle) {
scheduleForcedStyleRecalc();
return;
}// ...
}
styleResolverChanged는 webkit이 layout을 다시 수행하게 되는 관문격에 해당하는 메소드입니다.
이 메소드에서 updateActiveStyleSheets가 호출되어 스타일 시트들을 업데이트 하게 됩니다. (19 line)
### c++ ; highlight : [16, 22, 35, 38]
bool DocumentStyleSheetCollection::updateActiveStyleSheets(UpdateFlag updateFlag)
{
if (m_document->inStyleRecalc()) {
// SVG <use> element may manage to invalidate style selector in the middle of a style recalc.
// https://bugs.webkit.org/show_bug.cgi?id=54344
// FIXME: This should be fixed in SVG and the call site replaced by ASSERT(!m_inStyleRecalc).
m_needsUpdateActiveStylesheetsOnStyleRecalc = true;
m_document->scheduleForcedStyleRecalc();
return false;
}
if (!m_document->renderer() || !m_document->attached())
return false;
Vector<RefPtr<StyleSheet> > activeStyleSheets;
collectActiveStyleSheets(activeStyleSheets);
Vector<RefPtr<CSSStyleSheet> > activeCSSStyleSheets;
activeCSSStyleSheets.append(injectedAuthorStyleSheets());
activeCSSStyleSheets.append(documentAuthorStyleSheets());
collectActiveCSSStyleSheetsFromSeamlessParents(activeCSSStyleSheets, m_document);
filterEnabledCSSStyleSheets(activeCSSStyleSheets, activeStyleSheets);
StyleResolverUpdateType styleResolverUpdateType;
bool requiresFullStyleRecalc;
analyzeStyleSheetChange(updateFlag, activeCSSStyleSheets, styleResolverUpdateType, requiresFullStyleRecalc);
if (styleResolverUpdateType == Reconstruct)
m_document->clearStyleResolver();
else {
StyleResolver* styleResolver = m_document->styleResolver();
if (styleResolverUpdateType == Reset) {
styleResolver->ruleSets().resetAuthorStyle();
styleResolver->appendAuthorStyleSheets(0, activeCSSStyleSheets);
} else {
ASSERT(styleResolverUpdateType == Additive);
styleResolver->appendAuthorStyleSheets(m_activeAuthorStyleSheets.size(), activeCSSStyleSheets);
}
resetCSSFeatureFlags();
}m_activeAuthorStyleSheets.swap(activeCSSStyleSheets);
m_styleSheetsForStyleSheetList.swap(activeStyleSheets);// ...
updateActiveStyleSheets도 많은 일을 수행하지만 일단, collectActiveStyleSheets를 호출합니다. (16 line)
아래서 코드를 보겠지만, activeStyleSheets를 통해 현재 page의 style sheet를 가져오게 됩니다.
여기에 filterEnabledCSSStyleSheets 함수를 통해 activeStyleSheets중에 사용할 수 있는 것들만 activeCSSStyleSheets로 옮깁니다. ( 22 line)
그리고 styleResolver->appendAuthorStyleSheets를 호출합니다. (35 or 38 line)
### c++ ; highlight : [8, 25, 64, 87]
void DocumentStyleSheetCollection::collectActiveStyleSheets(Vector<RefPtr<StyleSheet> >& sheets)
{
if (m_document->settings() && !m_document->settings()->authorAndUserStylesEnabled())
return;
StyleSheetCandidateListHashSet::iterator begin = m_styleSheetCandidateNodes.begin();
StyleSheetCandidateListHashSet::iterator end = m_styleSheetCandidateNodes.end();
for (StyleSheetCandidateListHashSet::iterator it = begin; it != end; ++it) {
Node* n = *it;
StyleSheet* sheet = 0;
if (n->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
// Processing instruction (XML documents only).
// We don't support linking to embedded CSS stylesheets, see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(n);
sheet = pi->sheet();
#if ENABLE(XSLT)
// Don't apply XSL transforms to already transformed documents -- <rdar://problem/4132806>
if (pi->isXSL() && !m_document->transformSourceDocument()) {
// Don't apply XSL transforms until loading is finished.
if (!m_document->parsing())
m_document->applyXSLTransform(pi);
return;
}
#endif
} else if ((n->isHTMLElement() && (n->hasTagName(linkTag) || n->hasTagName(styleTag)))
#if ENABLE(SVG)
|| (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
#endif
) {
Element* e = static_cast<Element*>(n);
AtomicString title = e->getAttribute(titleAttr);
bool enabledViaScript = false;
if (e->hasLocalName(linkTag)) {
// <LINK> element
HTMLLinkElement* linkElement = static_cast<HTMLLinkElement*>(n);
if (linkElement->isDisabled())
continue;
enabledViaScript = linkElement->isEnabledViaScript();
if (linkElement->styleSheetIsLoading()) {
// it is loading but we should still decide which style sheet set to use
if (!enabledViaScript && !title.isEmpty() && m_preferredStylesheetSetName.isEmpty()) {
const AtomicString& rel = e->getAttribute(relAttr);
if (!rel.contains("alternate")) {
m_preferredStylesheetSetName = title;
m_selectedStylesheetSetName = title;
}
}
continue;
}
if (!linkElement->sheet())
title = nullAtom;
}
// Get the current preferred styleset. This is the
// set of sheets that will be enabled.
#if ENABLE(SVG)
if (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
sheet = static_cast<SVGStyleElement*>(n)->sheet();
else
#endif
if (e->hasLocalName(linkTag))
sheet = static_cast<HTMLLinkElement*>(n)->sheet();
else
// <STYLE> element
sheet = static_cast<HTMLStyleElement*>(n)->sheet();
// Check to see if this sheet belongs to a styleset
// (thus making it PREFERRED or ALTERNATE rather than
// PERSISTENT).
AtomicString rel = e->getAttribute(relAttr);
if (!enabledViaScript && !title.isEmpty()) {
// Yes, we have a title.
if (m_preferredStylesheetSetName.isEmpty()) {
// No preferred set has been established. If
// we are NOT an alternate sheet, then establish
// us as the preferred set. Otherwise, just ignore
// this sheet.
if (e->hasLocalName(styleTag) || !rel.contains("alternate"))
m_preferredStylesheetSetName = m_selectedStylesheetSetName = title;
}
if (title != m_preferredStylesheetSetName)
sheet = 0;
}
if (rel.contains("alternate") && title.isEmpty())
sheet = 0;
}
if (sheet)
sheets.append(sheet);
}
}
CandidateNode들을 돌면서 style element의 sheet를 append합니다.
styleResolver->appendAuthorStyleSheets는 결국 DocumentRuleSets::appendAuthorStyleSheets를 호출합니다.
### c++ ; highlight : [9, 21]
void DocumentRuleSets::appendAuthorStyleSheets(unsigned firstNew, const Vector<RefPtr<CSSStyleSheet> >& styleSheets, MediaQueryEvaluator* medium, InspectorCSSOMWrappers& inspectorCSSOMWrappers, bool isViewSource, StyleResolver* resolver)
{
// This handles sheets added to the end of the stylesheet list only. In other cases the style resolver
// needs to be reconstructed. To handle insertions too the rule order numbers would need to be updated.
unsigned size = styleSheets.size();
for (unsigned i = firstNew; i < size; ++i) {
CSSStyleSheet* cssSheet = styleSheets[i].get();
ASSERT(!cssSheet->disabled());
if (cssSheet->mediaQueries() && !medium->eval(cssSheet->mediaQueries(), resolver))
continue;
StyleSheetContents* sheet = cssSheet->contents();
#if ENABLE(STYLE_SCOPED) || ENABLE(SHADOW_DOM)
if (const ContainerNode* scope = StyleScopeResolver::scopeFor(cssSheet)) {
// FIXME: Remove a dependency to calling a StyleResolver's member function.
// If we can avoid calling resolver->ensureScopeResolver() here, we don't have to include "StyleResolver.h".
// https://bugs.webkit.org/show_bug.cgi?id=108890
resolver->ensureScopeResolver()->ensureRuleSetFor(scope)->addRulesFromSheet(sheet, *medium, resolver, scope);
continue;
}
#endif
m_authorStyle->addRulesFromSheet(sheet, *medium, resolver);
inspectorCSSOMWrappers.collectFromStyleSheetIfNeeded(cssSheet);
}
m_authorStyle->shrinkToFit();
collectFeatures(isViewSource, resolver->scopeResolver());
}
드디어 찾아헤매던 media query가 나왔습니다.( 9 line)
StyleResolver가 갖고 있던 m_medium을 받아 이 값을 각각의 CSSStyleSheet의 media query와 비교하여 매치 되는 스타일 시트만 rule에 추가됩니다. (21 line)
이 때 resolver의 값도 같이 넘어가는 것을 보실 수 있습니다.
resolver의 값이 있기 때문에 이제 viewport dependent여부를 확인할 수 있습니다.
### c++ ; highlight : [25, 26]
bool MediaQueryEvaluator::eval(const MediaQuerySet* querySet, StyleResolver* styleResolver) const
{
if (!querySet)
return true;
const Vector<OwnPtr<MediaQuery> >& queries = querySet->queryVector();
if (!queries.size())
return true; // empty query list evaluates to true
// iterate over queries, stop if any of them eval to true (OR semantics)
bool result = false;
for (size_t i = 0; i < queries.size() && !result; ++i) {
MediaQuery* query = queries[i].get();
if (query->ignored())
continue;
if (mediaTypeMatch(query->mediaType())) {
const Vector<OwnPtr<MediaQueryExp> >* exps = query->expressions();
// iterate through expressions, stop if any of them eval to false
// (AND semantics)
size_t j = 0;
for (; j < exps->size(); ++j) {
bool exprResult = eval(exps->at(j).get());
if (styleResolver && exps->at(j)->isViewportDependent())
styleResolver->addViewportDependentMediaQueryResult(exps->at(j).get(), exprResult);
if (!exprResult)
break;
}
// assume true if we are at the end of the list,
// otherwise assume false
result = applyRestrictor(query->restrictor(), exps->size() == j);
} else
result = applyRestrictor(query->restrictor(), false);
}
return result;
}
Media query expression이 viewport dependent한지 체크합니다. 예제에서 width를 지정했으므로 viewport dependent합니다.
따라서 styleResolver에 해당 mediaQuery를 넘겨주게 따로 저장하고 있게 됩니다. ( 26 line )
이렇게 저장된 정보는 layout과정에서 다시 evaluation의 대상이 될 것입니다.
위에서 스타일 시트를 추가할 때 link element도 고려해서 추가하고 있었던 것을 기억하신다면,
이전글에서 설명한 것 외에 linke element에 대해서도 이 부분에서 한번 더 evaluation과정을 거치게 되고 이 때 viewportDependent도 체크하게 될 것입니다.
'Open Source > webkit과 크로미움' 카테고리의 다른 글
[code reading] Media query in webkit(media rule) (0) | 2013.05.21 |
---|---|
ewebkit package 09/04/22 (0) | 2013.03.25 |
[code reading] Media query in webkit (linkElement) (1) | 2013.03.06 |
webkit관련 URL 모음 (0) | 2011.06.14 |
finstrument를 이용해 GtkLauncher를 실행했을 때 결과. (0) | 2011.06.04 |