/* * CSS Media Query Evaluator * * Copyright (C) 2006 Kimmo Kinnunen . * * 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 THE AUTHOR ``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 COMPUTER, INC. OR * 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 "MediaQueryEvaluator.h" #include "Chrome.h" #include "CSSPrimitiveValue.h" #include "CSSValueList.h" #include "DeprecatedString.h" #include "FloatRect.h" #include "Frame.h" #include "FrameView.h" #include "IntRect.h" #include "MediaFeatureNames.h" #include "MediaList.h" #include "MediaQuery.h" #include "MediaQueryExp.h" #include "Page.h" #include "RenderStyle.h" #include "PlatformScreen.h" #include namespace WebCore { using namespace MediaFeatureNames; enum MediaFeaturePrefix { MinPrefix, MaxPrefix, NoPrefix }; typedef bool (*EvalFunc)(CSSValue*, RenderStyle*, Page*, MediaFeaturePrefix); typedef HashMap FunctionMap; static FunctionMap* gFunctionMap; /* * FIXME: following media features are not implemented: color_index, scan, resolution * * color_index, min-color-index, max_color_index: It's unknown how to retrieve * the information if the display mode is indexed * scan: The "scan" media feature describes the scanning process of * tv output devices. It's unknown how to retrieve this information from * the platform * resolution, min-resolution, max-resolution: css parser doesn't seem to * support CSS_DIMENSION */ MediaQueryEvaluator::MediaQueryEvaluator(bool mediaFeatureResult) : m_page(0) , m_style(0) , m_expResult(mediaFeatureResult) { } MediaQueryEvaluator:: MediaQueryEvaluator(const String& acceptedMediaType, bool mediaFeatureResult) : m_mediaType(acceptedMediaType) , m_page(0) , m_style(0) , m_expResult(mediaFeatureResult) { } MediaQueryEvaluator:: MediaQueryEvaluator(const char* acceptedMediaType, bool mediaFeatureResult) : m_mediaType(acceptedMediaType) , m_page(0) , m_style(0) , m_expResult(mediaFeatureResult) { } MediaQueryEvaluator:: MediaQueryEvaluator(const String& acceptedMediaType, Page* page, RenderStyle* style) : m_mediaType(acceptedMediaType.lower()) , m_page(page) , m_style(style) , m_expResult(false) // doesn't matter when we have m_page and m_style { } MediaQueryEvaluator::~MediaQueryEvaluator() { } bool MediaQueryEvaluator::mediaTypeMatch(const String& mediaTypeToMatch) const { return mediaTypeToMatch.isEmpty() || equalIgnoringCase(mediaTypeToMatch, "all") || equalIgnoringCase(mediaTypeToMatch, m_mediaType); } bool MediaQueryEvaluator::mediaTypeMatchSpecific(const char* mediaTypeToMatch) const { // Like mediaTypeMatch, but without the special cases for "" and "all". ASSERT(mediaTypeToMatch); ASSERT(mediaTypeToMatch[0] != '\0'); ASSERT(!equalIgnoringCase(mediaTypeToMatch, String("all"))); return equalIgnoringCase(mediaTypeToMatch, m_mediaType); } static bool applyRestrictor(MediaQuery::Restrictor r, bool value) { return r == MediaQuery::Not ? !value : value; } bool MediaQueryEvaluator::eval(const MediaList* mediaList) const { if (!mediaList) return true; const Vector* queries = mediaList->mediaQueries(); 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->at(i); if (mediaTypeMatch(query->mediaType())) { const Vector* exps = query->expressions(); // iterate through expressions, stop if any of them eval to false // (AND semantics) size_t j = 0; for (; j < exps->size() && eval(exps->at(j)); ++j) /* empty*/; // 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; } static bool parseAspectRatio(CSSValue* value, int& a, int& b) { if (value->isValueList()){ CSSValueList* valueList = static_cast(value); if (valueList->length() == 3) { CSSValue* i0 = valueList->item(0); CSSValue* i1 = valueList->item(1); CSSValue* i2 = valueList->item(2); if (i0->isPrimitiveValue() && static_cast(i0)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER && i1->isPrimitiveValue() && static_cast(i1)->primitiveType() == CSSPrimitiveValue::CSS_STRING && i2->isPrimitiveValue() && static_cast(i2)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) { String str = static_cast(i1)->getStringValue(); if (!str.isNull() && str.length() == 1 && str[0] == DeprecatedChar('/')) { a = static_cast(i0)->getIntValue(CSSPrimitiveValue::CSS_NUMBER); b = static_cast(i2)->getIntValue(CSSPrimitiveValue::CSS_NUMBER); return true; } } } } return false; } inline bool cmpvalue(int a, int b, MediaFeaturePrefix op) { switch (op) { case MinPrefix: return a >= b; case MaxPrefix: return a <= b; case NoPrefix: return a == b; } return false; } inline bool cmpvalue(float a, float b, MediaFeaturePrefix op) { switch (op) { case MinPrefix: return a >= b; case MaxPrefix: return a <= b; case NoPrefix: return a == b; } return false; } static bool numberValue(CSSValue* value, float& result) { if (value->isPrimitiveValue() && static_cast(value)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) { result = static_cast(value)->getFloatValue(CSSPrimitiveValue::CSS_NUMBER); return true; } return false; } static bool colorMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { int bitsPerComponent = screenDepthPerComponent(page->mainFrame()->view()); float number; if (value) return numberValue(value, number) && cmpvalue(bitsPerComponent, (int)number, op); return bitsPerComponent != 0; } static bool monochromeMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { if (!screenIsMonochrome(page->mainFrame()->view())) return false; return colorMediaFeatureEval(value, style, page, op); } static bool device_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { if (value) { FloatRect sg = screenRect(page->mainFrame()->view()); int a = 0; int b = 0; if (parseAspectRatio(value, a, b)) return b != 0 && cmpvalue(a * (int)sg.height(), b * (int)sg.width(), op); return false; } // ({,min-,max-}device-aspect-ratio) // assume if we have a device, its aspect ratio is non-zero return true; } static bool device_pixel_ratioMediaFeatureEval(CSSValue *value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { if (value) return value->isPrimitiveValue() && cmpvalue(page->chrome()->scaleFactor(), static_cast(value)->getFloatValue(), op); return page->chrome()->scaleFactor() != 0; } static bool gridMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { // if output device is bitmap, grid: 0 == true // assume we have bitmap device float number; if (value && numberValue(value, number)) return cmpvalue((int)number, 0, op); return false; } static bool device_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { if (value) { FloatRect sg = screenRect(page->mainFrame()->view()); return value->isPrimitiveValue() && cmpvalue((int)sg.height(), static_cast(value)->computeLengthInt(style), op); } // ({,min-,max-}device-height) // assume if we have a device, assume non-zero return true; } static bool device_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { if (value) { FloatRect sg = screenRect(page->mainFrame()->view()); return value->isPrimitiveValue() && cmpvalue((int)sg.width(), static_cast(value)->computeLengthInt(style), op); } // ({,min-,max-}device-width) // assume if we have a device, assume non-zero return true; } static bool heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { FrameView* view = page->mainFrame()->view(); if (value) return value->isPrimitiveValue() && cmpvalue(view->visibleHeight(), static_cast(value)->computeLengthInt(style), op); return view->visibleHeight() != 0; } static bool widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix op) { FrameView* view = page->mainFrame()->view(); if (value) return value->isPrimitiveValue() && cmpvalue(view->visibleWidth(), static_cast(value)->computeLengthInt(style), op); return view->visibleWidth() != 0; } // rest of the functions are trampolines which set the prefix according to the media feature expression used static bool min_colorMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return colorMediaFeatureEval(value, style, page, MinPrefix); } static bool max_colorMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return colorMediaFeatureEval(value, style, page, MaxPrefix); } static bool min_monochromeMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return monochromeMediaFeatureEval(value, style, page, MinPrefix); } static bool max_monochromeMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return monochromeMediaFeatureEval(value, style, page, MaxPrefix); } static bool min_device_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_aspect_ratioMediaFeatureEval(value, style, page, MinPrefix); } static bool max_device_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_aspect_ratioMediaFeatureEval(value, style, page, MaxPrefix); } static bool min_device_pixel_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_pixel_ratioMediaFeatureEval(value, style, page, MinPrefix); } static bool max_device_pixel_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_pixel_ratioMediaFeatureEval(value, style, page, MaxPrefix); } static bool min_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return heightMediaFeatureEval(value, style, page, MinPrefix); } static bool max_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return heightMediaFeatureEval(value, style, page, MaxPrefix); } static bool min_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return widthMediaFeatureEval(value, style, page, MinPrefix); } static bool max_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return widthMediaFeatureEval(value, style, page, MaxPrefix); } static bool min_device_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_heightMediaFeatureEval(value, style, page, MinPrefix); } static bool max_device_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_heightMediaFeatureEval(value, style, page, MaxPrefix); } static bool min_device_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_widthMediaFeatureEval(value, style, page, MinPrefix); } static bool max_device_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Page* page, MediaFeaturePrefix /*op*/) { return device_widthMediaFeatureEval(value, style, page, MaxPrefix); } static void createFunctionMap() { // Create the table. gFunctionMap = new FunctionMap; #define ADD_TO_FUNCTIONMAP(name, str) \ gFunctionMap->set(name##MediaFeature.impl(), name##MediaFeatureEval); CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(ADD_TO_FUNCTIONMAP); #undef ADD_TO_FUNCTIONMAP } bool MediaQueryEvaluator::eval(const MediaQueryExp* expr) const { if (!m_page || !m_style) return m_expResult; 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, m_page, NoPrefix); return false; } } // namespace