Open Source/webkit/chromium

[code reading] Media query in webkit(media rule)

소혼 2013. 5. 21. 01:29
반응형

이전에 작성한 linkElement, style element에 이어 media rule을 사용해 media query를 주었을 때 웹킷이 처리하는 과정을 정리해보고자 합니다.


이전글 1 : Media query in webkit(link Element)

이전글 2 : Media query in webkit(style element)


특히 이전글2는 반드시 읽으셔야 합니다.


사용할 예제는 이전글2에서 잠깐 보여드렸던 것과 동일합니다.

### html

<!doctype html>
<head>
<style>
@media screen and (min-width:300px) {
body { background-color:red; }
}
</style>
<body>
hello
</body>


시작 지점은 이전 예제의 밑에서 두번째 코드인 DocumentRuleSets::appendAuthorStyleSheets 입니다.

### c++ ; highlight : 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());
}


현재 styleSheet에 mediaQuery가 있다면, 2번글에서와 같이 eval과정을 거쳐서 addRulesFromSheet를 수행할지 말지를 결정할 것입니다. [9, 21라인]

하지만, 이번 예제에서 stylesheet에는 mediaQuery가 없습니다. 따라서 addRulesFromSheet가 수행됩니다.


addRulesFromSheet는 다음과 같습니다.


### c++

void RuleSet::addRulesFromSheet(StyleSheetContents* sheet, const MediaQueryEvaluator& medium, StyleResolver* resolver, const ContainerNode* scope)
{  
    ASSERT(sheet);                                                                                          
   
    const Vector<RefPtr<StyleRuleImport> >& importRules = sheet->importRules();                             
    for (unsigned i = 0; i < importRules.size(); ++i) {
        StyleRuleImport* importRule = importRules[i].get();
        if (importRule->styleSheet() && (!importRule->mediaQueries() || medium.eval(importRule->mediaQueries(), resolver)))
            addRulesFromSheet(importRule->styleSheet(), medium, resolver, scope);                           
    }                                                                                                       
                                                                                                            
    bool hasDocumentSecurityOrigin = resolver && resolver->document()->securityOrigin()->canRequest(sheet->baseURL());
    AddRuleFlags addRuleFlags = static_cast<AddRuleFlags>((hasDocumentSecurityOrigin ? RuleHasDocumentSecurityOrigin : 0) | (!scope ? RuleCanUseFastCheckSelector : 0));
   
    addChildRules(sheet->childRules(), medium, resolver, scope, hasDocumentSecurityOrigin, addRuleFlags);   
                                                                                                            
    if (m_autoShrinkToFitEnabled)
        shrinkToFit();                                                                                      
}

sheet의 import rule들을 모두 순회하며, import rule의 media query를 확인, 있으면, 해당 import rule의 styleSheet를 위해 addRulesFromSheet를 호출합니다.

여기서는  import rule도 없으므로 지나가면 addChildRules라는 함수에서 실제 childRules들을 반영합니다. 이 함수의 인자에도 medium이 있는 것을 볼 수 있습니다.


### c++ ; highlight : [11, 13]

void RuleSet::addChildRules(const Vector<RefPtr<StyleRuleBase> >& rules, const MediaQueryEvaluator& medium, StyleResolver* resolver, const ContainerNode* scope, bool hasDocumentSecurityOrigin, AddRuleFlags addRuleFlags)
{
    for (unsigned i = 0; i < rules.size(); ++i) {
        StyleRuleBase* rule = rules[i].get();

        if (rule->isStyleRule()) {
            StyleRule* styleRule = static_cast<StyleRule*>(rule);
            addStyleRule(styleRule, addRuleFlags);
        } else if (rule->isPageRule())
            addPageRule(static_cast<StyleRulePage*>(rule));
        else if (rule->isMediaRule()) {
            StyleRuleMedia* mediaRule = static_cast<StyleRuleMedia*>(rule);
            if ((!mediaRule->mediaQueries() || medium.eval(mediaRule->mediaQueries(), resolver)))
                addChildRules(mediaRule->childRules(), medium, resolver, scope, hasDocumentSecurityOrigin, addRuleFlags);
        } else if (rule->isFontFaceRule() && resolver) {
            // Add this font face to our set.
            // FIXME(BUG 72461): We don't add @font-face rules of scoped style sheets for the moment.
            if (scope)
                continue;
            const StyleRuleFontFace* fontFaceRule = static_cast<StyleRuleFontFace*>(rule);
            resolver->fontSelector()->addFontFaceRule(fontFaceRule);
            resolver->invalidateMatchedPropertiesCache();
        } else if (rule->isKeyframesRule() && resolver) {
            // FIXME (BUG 72462): We don't add @keyframe rules of scoped style sheets for the moment.
            if (scope)
                continue;
            resolver->addKeyframeStyle(static_cast<StyleRuleKeyframes*>(rule));
        }
#if ENABLE(CSS_REGIONS)
        else if (rule->isRegionRule() && resolver) {
            // FIXME (BUG 72472): We don't add @-webkit-region rules of scoped style sheets for the moment.
            if (scope)
                continue;
            addRegionRule(static_cast<StyleRuleRegion*>(rule), hasDocumentSecurityOrigin);
        }
#endif
#if ENABLE(SHADOW_DOM)
        else if (rule->isHostRule())
            resolver->addHostRule(static_cast<StyleRuleHost*>(rule), hasDocumentSecurityOrigin, scope);
#endif
#if ENABLE(CSS_DEVICE_ADAPTATION)
        else if (rule->isViewportRule() && resolver) {
            // @viewport should not be scoped.
            if (scope)
                continue;
            resolver->viewportStyleResolver()->addViewportRule(static_cast<StyleRuleViewport*>(rule));
        }
#endif
#if ENABLE(CSS3_CONDITIONAL_RULES)
        else if (rule->isSupportsRule() && static_cast<StyleRuleSupports*>(rule)->conditionIsSupported())
            addChildRules(static_cast<StyleRuleSupports*>(rule)->childRules(), medium, resolver, scope, hasDocumentSecurityOrigin, addRuleFlags);

#endif

    }

}

다양한 CSS rule들을 처리하는 코드 가운데 media rule에 관한 코드를 발견하실 수 있습니다. [11라인]



media query를 발생시키는 코드들은 다 살펴보았지만, 2번글 마지막에서 잠깐 언급했던 것과 같이, viewport와 관련된 media query는 화면의 크기등이 바뀌었을 때 다시 계산할 필요가 있습니다.


예를 들어 webview를 resize하는 경우, FrameView의 size가 변경되어 FrameView::setFrameRect가 호출될 것입니다.


### c++ ; highlight : [33, 34, 35, 36, 37]

void FrameView::setFrameRect(const IntRect& newRect)
{
    IntRect oldRect = frameRect();
    if (newRect == oldRect)              
        return;
       
#if ENABLE(TEXT_AUTOSIZING)
    // Autosized font sizes depend on the width of the viewing area.
    if (newRect.width() != oldRect.width()) {
        Page* page = m_frame ? m_frame->page() : 0;
        if (page && page->mainFrame() == m_frame && page->settings()->textAutosizingEnabled()) {
            for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext())        
                m_frame->document()->textAutosizer()->recalculateMultipliers();
        }                
    }
#endif

    ScrollView::setFrameRect(newRect);

    updateScrollableAreaSet();

    RenderView* renderView = this->renderView();

#if USE(ACCELERATED_COMPOSITING)
    if (renderView) {
        if (renderView->usesCompositing())
            renderView->compositor()->frameViewDidChangeSize();
    }
#endif

    Document* document = m_frame ? m_frame->document() : 0;

    // Viewport-dependent media queries may cause us to need completely different style information.
    if (document && document->styleResolverIfExists() && document->styleResolverIfExists()->affectedByViewportChange()) {
        document->styleResolverChanged(DeferRecalcStyle);
        InspectorInstrumentation::mediaQueryResultChanged(document);
    }

    sendResizeEventIfNeeded();
}

document객체에 있는 styleResolver의 affectedByViewportChange를 통해 media query의 결과가 바뀌었는지 보고[34 라인],

바뀌었다면, styleResolverChanged를 다시 불러줍니다.[35라인]


affectedByViewportChange는 아래와 같습니다.


### c++

bool StyleResolver::affectedByViewportChange() const
{
    unsigned s = m_viewportDependentMediaQueryResults.size();
    for (unsigned i = 0; i < s; i++) {
        if (m_medium->eval(&m_viewportDependentMediaQueryResults[i]->m_expression) != m_viewportDependentMediaQueryResults[i]->m_result)
            return true;
    }
    return false;
}


반응형