2010-08-30 Maciej Stachowiak <mjs@apple.com>
[WebKit.git] / WebCore / css / MediaQueryEvaluator.cpp
1 /*
2  * CSS Media Query Evaluator
3  *
4  * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "MediaQueryEvaluator.h"
30
31 #include "Chrome.h"
32 #include "ChromeClient.h"
33 #include "CSSPrimitiveValue.h"
34 #include "CSSStyleSelector.h"
35 #include "CSSValueList.h"
36 #include "FloatRect.h"
37 #include "Frame.h"
38 #include "FrameView.h"
39 #include "IntRect.h"
40 #include "MediaFeatureNames.h"
41 #include "MediaList.h"
42 #include "MediaQuery.h"
43 #include "MediaQueryExp.h"
44 #include "NodeRenderStyle.h"
45 #include "Page.h"
46 #include "RenderView.h"
47 #include "RenderStyle.h"
48 #include "PlatformScreen.h"
49 #include <wtf/HashMap.h>
50
51 #if ENABLE(3D_RENDERING) && USE(ACCELERATED_COMPOSITING)
52 #include "RenderLayerCompositor.h"
53 #endif
54
55 namespace WebCore {
56
57 using namespace MediaFeatureNames;
58
59 enum MediaFeaturePrefix { MinPrefix, MaxPrefix, NoPrefix };
60
61 typedef bool (*EvalFunc)(CSSValue*, RenderStyle*, Frame*, MediaFeaturePrefix);
62 typedef HashMap<AtomicStringImpl*, EvalFunc> FunctionMap;
63 static FunctionMap* gFunctionMap;
64
65 /*
66  * FIXME: following media features are not implemented: color_index, scan, resolution
67  *
68  * color_index, min-color-index, max_color_index: It's unknown how to retrieve
69  * the information if the display mode is indexed
70  * scan: The "scan" media feature describes the scanning process of
71  * tv output devices. It's unknown how to retrieve this information from
72  * the platform
73  * resolution, min-resolution, max-resolution: css parser doesn't seem to
74  * support CSS_DIMENSION
75  */
76
77 MediaQueryEvaluator::MediaQueryEvaluator(bool mediaFeatureResult)
78     : m_frame(0)
79     , m_style(0)
80     , m_expResult(mediaFeatureResult)
81 {
82 }
83
84 MediaQueryEvaluator:: MediaQueryEvaluator(const String& acceptedMediaType, bool mediaFeatureResult)
85     : m_mediaType(acceptedMediaType)
86     , m_frame(0)
87     , m_style(0)
88     , m_expResult(mediaFeatureResult)
89 {
90 }
91
92 MediaQueryEvaluator:: MediaQueryEvaluator(const char* acceptedMediaType, bool mediaFeatureResult)
93     : m_mediaType(acceptedMediaType)
94     , m_frame(0)
95     , m_style(0)
96     , m_expResult(mediaFeatureResult)
97 {
98 }
99
100 MediaQueryEvaluator:: MediaQueryEvaluator(const String& acceptedMediaType, Frame* frame, RenderStyle* style)
101     : m_mediaType(acceptedMediaType)
102     , m_frame(frame)
103     , m_style(style)
104     , m_expResult(false) // doesn't matter when we have m_frame and m_style
105 {
106 }
107
108 MediaQueryEvaluator::~MediaQueryEvaluator()
109 {
110 }
111
112 bool MediaQueryEvaluator::mediaTypeMatch(const String& mediaTypeToMatch) const
113 {
114     return mediaTypeToMatch.isEmpty()
115         || equalIgnoringCase(mediaTypeToMatch, "all")
116         || equalIgnoringCase(mediaTypeToMatch, m_mediaType);
117 }
118
119 bool MediaQueryEvaluator::mediaTypeMatchSpecific(const char* mediaTypeToMatch) const
120 {
121     // Like mediaTypeMatch, but without the special cases for "" and "all".
122     ASSERT(mediaTypeToMatch);
123     ASSERT(mediaTypeToMatch[0] != '\0');
124     ASSERT(!equalIgnoringCase(mediaTypeToMatch, String("all")));
125     return equalIgnoringCase(mediaTypeToMatch, m_mediaType);
126 }
127
128 static bool applyRestrictor(MediaQuery::Restrictor r, bool value)
129 {
130     return r == MediaQuery::Not ? !value : value;
131 }
132
133 bool MediaQueryEvaluator::eval(const MediaList* mediaList, CSSStyleSelector* styleSelector) const
134 {
135     if (!mediaList)
136         return true;
137
138     const Vector<MediaQuery*>& queries = mediaList->mediaQueries();
139     if (!queries.size())
140         return true; // empty query list evaluates to true
141
142     // iterate over queries, stop if any of them eval to true (OR semantics)
143     bool result = false;
144     for (size_t i = 0; i < queries.size() && !result; ++i) {
145         MediaQuery* query = queries.at(i);
146
147         if (query->ignored())
148             continue;
149
150         if (mediaTypeMatch(query->mediaType())) {
151             const Vector<OwnPtr<MediaQueryExp> >* exps = query->expressions();
152             // iterate through expressions, stop if any of them eval to false
153             // (AND semantics)
154             size_t j = 0;
155             for (; j < exps->size(); ++j) {
156                 bool exprResult = eval(exps->at(j).get());
157                 if (styleSelector && exps->at(j)->isViewportDependent())
158                     styleSelector->addViewportDependentMediaQueryResult(exps->at(j).get(), exprResult);
159                 if (!exprResult)
160                     break;
161             }
162
163             // assume true if we are at the end of the list,
164             // otherwise assume false
165             result = applyRestrictor(query->restrictor(), exps->size() == j);
166         } else
167             result = applyRestrictor(query->restrictor(), false);
168     }
169
170     return result;
171 }
172
173 static bool parseAspectRatio(CSSValue* value, int& h, int& v)
174 {
175     if (value->isValueList()) {
176         CSSValueList* valueList = static_cast<CSSValueList*>(value);
177         if (valueList->length() == 3) {
178             CSSValue* i0 = valueList->itemWithoutBoundsCheck(0);
179             CSSValue* i1 = valueList->itemWithoutBoundsCheck(1);
180             CSSValue* i2 = valueList->itemWithoutBoundsCheck(2);
181             if (i0->isPrimitiveValue() && static_cast<CSSPrimitiveValue*>(i0)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER
182                 && i1->isPrimitiveValue() && static_cast<CSSPrimitiveValue*>(i1)->primitiveType() == CSSPrimitiveValue::CSS_STRING
183                 && i2->isPrimitiveValue() && static_cast<CSSPrimitiveValue*>(i2)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) {
184                 String str = static_cast<CSSPrimitiveValue*>(i1)->getStringValue();
185                 if (!str.isNull() && str.length() == 1 && str[0] == '/') {
186                     h = static_cast<CSSPrimitiveValue*>(i0)->getIntValue(CSSPrimitiveValue::CSS_NUMBER);
187                     v = static_cast<CSSPrimitiveValue*>(i2)->getIntValue(CSSPrimitiveValue::CSS_NUMBER);
188                     return true;
189                 }
190             }
191         }
192     }
193     return false;
194 }
195
196 template<typename T>
197 bool compareValue(T a, T b, MediaFeaturePrefix op)
198 {
199     switch (op) {
200     case MinPrefix:
201         return a >= b;
202     case MaxPrefix:
203         return a <= b;
204     case NoPrefix:
205         return a == b;
206     }
207     return false;
208 }
209
210 static bool numberValue(CSSValue* value, float& result)
211 {
212     if (value->isPrimitiveValue()
213         && static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) {
214         result = static_cast<CSSPrimitiveValue*>(value)->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
215         return true;
216     }
217     return false;
218 }
219
220 static bool colorMediaFeatureEval(CSSValue* value, RenderStyle*, Frame* frame, MediaFeaturePrefix op)
221 {
222     int bitsPerComponent = screenDepthPerComponent(frame->page()->mainFrame()->view());
223     float number;
224     if (value)
225         return numberValue(value, number) && compareValue(bitsPerComponent, static_cast<int>(number), op);
226
227     return bitsPerComponent != 0;
228 }
229
230 static bool monochromeMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix op)
231 {
232     if (!screenIsMonochrome(frame->page()->mainFrame()->view())) {
233         if (value) {
234             float number;
235             return numberValue(value, number) && compareValue(0, static_cast<int>(number), op);
236         }
237         return false;
238     }
239
240     return colorMediaFeatureEval(value, style, frame, op);
241 }
242
243 static bool orientationMediaFeatureEval(CSSValue* value, RenderStyle*, Frame* frame, MediaFeaturePrefix)
244 {
245     // A missing parameter should fail
246     if (!value)
247         return false;
248
249     FrameView* view = frame->view();
250     int width = view->layoutWidth();
251     int height = view->layoutHeight();
252     if (width > height) // Square viewport is portrait
253         return "landscape" == static_cast<CSSPrimitiveValue*>(value)->getStringValue();
254     return "portrait" == static_cast<CSSPrimitiveValue*>(value)->getStringValue();
255 }
256
257 static bool aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle*, Frame* frame, MediaFeaturePrefix op)
258 {
259     if (value) {
260         FrameView* view = frame->view();
261         int width = view->layoutWidth();
262         int height = view->layoutHeight();
263         int h = 0;
264         int v = 0;
265         if (parseAspectRatio(value, h, v))
266             return v != 0 && compareValue(width * v, height * h, op);
267         return false;
268     }
269
270     // ({,min-,max-}aspect-ratio)
271     // assume if we have a device, its aspect ratio is non-zero
272     return true;
273 }
274
275 static bool device_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle*, Frame* frame, MediaFeaturePrefix op)
276 {
277     if (value) {
278         FloatRect sg = screenRect(frame->page()->mainFrame()->view());
279         int h = 0;
280         int v = 0;
281         if (parseAspectRatio(value, h, v))
282             return v != 0  && compareValue(static_cast<int>(sg.width()) * v, static_cast<int>(sg.height()) * h, op);
283         return false;
284     }
285
286     // ({,min-,max-}device-aspect-ratio)
287     // assume if we have a device, its aspect ratio is non-zero
288     return true;
289 }
290
291 static bool device_pixel_ratioMediaFeatureEval(CSSValue *value, RenderStyle*, Frame* frame, MediaFeaturePrefix op)
292 {
293     if (value)
294         return value->isPrimitiveValue() && compareValue(frame->page()->chrome()->scaleFactor(), static_cast<CSSPrimitiveValue*>(value)->getFloatValue(), op);
295
296     return frame->page()->chrome()->scaleFactor() != 0;
297 }
298
299 static bool gridMediaFeatureEval(CSSValue* value, RenderStyle*, Frame*, MediaFeaturePrefix op)
300 {
301     // if output device is bitmap, grid: 0 == true
302     // assume we have bitmap device
303     float number;
304     if (value && numberValue(value, number))
305         return compareValue(static_cast<int>(number), 0, op);
306     return false;
307 }
308
309 static bool device_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix op)
310 {
311     if (value) {
312         FloatRect sg = screenRect(frame->page()->mainFrame()->view());
313         RenderStyle* rootStyle = frame->document()->documentElement()->renderStyle();
314         return value->isPrimitiveValue() && compareValue(static_cast<int>(sg.height()), static_cast<CSSPrimitiveValue*>(value)->computeLengthInt(style, rootStyle), op);
315     }
316     // ({,min-,max-}device-height)
317     // assume if we have a device, assume non-zero
318     return true;
319 }
320
321 static bool device_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix op)
322 {
323     if (value) {
324         FloatRect sg = screenRect(frame->page()->mainFrame()->view());
325         RenderStyle* rootStyle = frame->document()->documentElement()->renderStyle();
326         return value->isPrimitiveValue() && compareValue(static_cast<int>(sg.width()), static_cast<CSSPrimitiveValue*>(value)->computeLengthInt(style, rootStyle), op);
327     }
328     // ({,min-,max-}device-width)
329     // assume if we have a device, assume non-zero
330     return true;
331 }
332
333 static bool heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix op)
334 {
335     FrameView* view = frame->view();
336     RenderStyle* rootStyle = frame->document()->documentElement()->renderStyle();
337
338     if (value)
339         return value->isPrimitiveValue() && compareValue(view->layoutHeight(), static_cast<CSSPrimitiveValue*>(value)->computeLengthInt(style, rootStyle), op);
340
341     return view->layoutHeight() != 0;
342 }
343
344 static bool widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix op)
345 {
346     FrameView* view = frame->view();
347     RenderStyle* rootStyle = frame->document()->documentElement()->renderStyle();
348
349     if (value)
350         return value->isPrimitiveValue() && compareValue(view->layoutWidth(), static_cast<CSSPrimitiveValue*>(value)->computeLengthInt(style, rootStyle), op);
351
352     return view->layoutWidth() != 0;
353 }
354
355 // rest of the functions are trampolines which set the prefix according to the media feature expression used
356
357 static bool min_colorMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
358 {
359     return colorMediaFeatureEval(value, style, frame, MinPrefix);
360 }
361
362 static bool max_colorMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
363 {
364     return colorMediaFeatureEval(value, style, frame, MaxPrefix);
365 }
366
367 static bool min_monochromeMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
368 {
369     return monochromeMediaFeatureEval(value, style, frame, MinPrefix);
370 }
371
372 static bool max_monochromeMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
373 {
374     return monochromeMediaFeatureEval(value, style, frame, MaxPrefix);
375 }
376
377 static bool min_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
378 {
379     return aspect_ratioMediaFeatureEval(value, style, frame, MinPrefix);
380 }
381
382 static bool max_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
383 {
384     return aspect_ratioMediaFeatureEval(value, style, frame, MaxPrefix);
385 }
386
387 static bool min_device_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
388 {
389     return device_aspect_ratioMediaFeatureEval(value, style, frame, MinPrefix);
390 }
391
392 static bool max_device_aspect_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
393 {
394     return device_aspect_ratioMediaFeatureEval(value, style, frame, MaxPrefix);
395 }
396
397 static bool min_device_pixel_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
398 {
399     return device_pixel_ratioMediaFeatureEval(value, style, frame, MinPrefix);
400 }
401
402 static bool max_device_pixel_ratioMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
403 {
404     return device_pixel_ratioMediaFeatureEval(value, style, frame, MaxPrefix);
405 }
406
407 static bool min_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
408 {
409     return heightMediaFeatureEval(value, style, frame, MinPrefix);
410 }
411
412 static bool max_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
413 {
414     return heightMediaFeatureEval(value, style, frame, MaxPrefix);
415 }
416
417 static bool min_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
418 {
419     return widthMediaFeatureEval(value, style, frame, MinPrefix);
420 }
421
422 static bool max_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
423 {
424     return widthMediaFeatureEval(value, style, frame, MaxPrefix);
425 }
426
427 static bool min_device_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
428 {
429     return device_heightMediaFeatureEval(value, style, frame, MinPrefix);
430 }
431
432 static bool max_device_heightMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
433 {
434     return device_heightMediaFeatureEval(value, style, frame, MaxPrefix);
435 }
436
437 static bool min_device_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
438 {
439     return device_widthMediaFeatureEval(value, style, frame, MinPrefix);
440 }
441
442 static bool max_device_widthMediaFeatureEval(CSSValue* value, RenderStyle* style, Frame* frame, MediaFeaturePrefix)
443 {
444     return device_widthMediaFeatureEval(value, style, frame, MaxPrefix);
445 }
446
447 static bool animationMediaFeatureEval(CSSValue* value, RenderStyle*, Frame*, MediaFeaturePrefix op)
448 {
449     if (value) {
450         float number;
451         return numberValue(value, number) && compareValue(1, static_cast<int>(number), op);
452     }
453     return true;
454 }
455
456 static bool transitionMediaFeatureEval(CSSValue* value, RenderStyle*, Frame*, MediaFeaturePrefix op)
457 {
458     if (value) {
459         float number;
460         return numberValue(value, number) && compareValue(1, static_cast<int>(number), op);
461     }
462     return true;
463 }
464
465 static bool transform_2dMediaFeatureEval(CSSValue* value, RenderStyle*, Frame*, MediaFeaturePrefix op)
466 {
467     if (value) {
468         float number;
469         return numberValue(value, number) && compareValue(1, static_cast<int>(number), op);
470     }
471     return true;
472 }
473
474 static bool transform_3dMediaFeatureEval(CSSValue* value, RenderStyle*, Frame* frame, MediaFeaturePrefix op)
475 {
476     bool returnValueIfNoParameter;
477     int have3dRendering;
478
479 #if ENABLE(3D_RENDERING)
480     bool threeDEnabled = false;
481 #if USE(ACCELERATED_COMPOSITING)
482     if (RenderView* view = frame->contentRenderer())
483         threeDEnabled = view->compositor()->hasAcceleratedCompositing();
484 #endif
485
486     returnValueIfNoParameter = threeDEnabled;
487     have3dRendering = threeDEnabled ? 1 : 0;
488 #else
489     UNUSED_PARAM(frame);
490     returnValueIfNoParameter = false;
491     have3dRendering = 0;
492 #endif
493
494     if (value) {
495         float number;
496         return numberValue(value, number) && compareValue(have3dRendering, static_cast<int>(number), op);
497     }
498     return returnValueIfNoParameter;
499 }
500
501 static bool view_modeMediaFeatureEval(CSSValue* value, RenderStyle*, Frame* frame, MediaFeaturePrefix op)
502 {
503     UNUSED_PARAM(op);
504     if (!value)
505         return true;
506     return Page::stringToViewMode(static_cast<CSSPrimitiveValue*>(value)->getStringValue()) == frame->page()->viewMode();
507 }
508
509 static void createFunctionMap()
510 {
511     // Create the table.
512     gFunctionMap = new FunctionMap;
513 #define ADD_TO_FUNCTIONMAP(name, str)  \
514     gFunctionMap->set(name##MediaFeature.impl(), name##MediaFeatureEval);
515     CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(ADD_TO_FUNCTIONMAP);
516 #undef ADD_TO_FUNCTIONMAP
517 }
518
519 bool MediaQueryEvaluator::eval(const MediaQueryExp* expr) const
520 {
521     if (!m_frame || !m_style)
522         return m_expResult;
523
524     if (!expr->isValid())
525         return false;
526
527     if (!gFunctionMap)
528         createFunctionMap();
529
530     // call the media feature evaluation function. Assume no prefix
531     // and let trampoline functions override the prefix if prefix is
532     // used
533     EvalFunc func = gFunctionMap->get(expr->mediaFeature().impl());
534     if (func)
535         return func(expr->value(), m_style, m_frame, NoPrefix);
536
537     return false;
538 }
539
540 } // namespace