| /* |
| * CSS Media Query Evaluator |
| * |
| * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>. |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * Copyright (C) 2013 Intel Corporation. All rights reserved. |
| * |
| * 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 "third_party/blink/renderer/core/css/media_query_evaluator.h" |
| |
| #include "third_party/blink/public/common/css/forced_colors.h" |
| #include "third_party/blink/public/common/css/navigation_controls.h" |
| #include "third_party/blink/renderer/core/css/media_values.h" |
| |
| #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiable_token_builder.h" |
| #include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h" |
| #include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom-blink.h" |
| #include "third_party/blink/renderer/core/css/css_primitive_value.h" |
| #include "third_party/blink/renderer/core/css/css_resolution_units.h" |
| #include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h" |
| #include "third_party/blink/renderer/core/css/media_feature_names.h" |
| #include "third_party/blink/renderer/core/css/media_features.h" |
| #include "third_party/blink/renderer/core/css/media_list.h" |
| #include "third_party/blink/renderer/core/css/media_query.h" |
| #include "third_party/blink/renderer/core/css/media_values_dynamic.h" |
| #include "third_party/blink/renderer/core/css/resolver/media_query_result.h" |
| #include "third_party/blink/renderer/core/css_value_keywords.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/media_type_names.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/platform/geometry/float_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/color_space_gamut.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_map.h" |
| |
| namespace blink { |
| |
| using mojom::blink::HoverType; |
| using mojom::blink::PointerType; |
| |
| namespace { |
| |
| void RecordMediaQueryResult(Document* doc, |
| const MediaQueryExp& expr, |
| bool result) { |
| IdentifiableTokenBuilder input_builder; |
| input_builder.AddToken(IdentifiabilityBenignStringToken(expr.MediaFeature())); |
| input_builder.AddToken( |
| IdentifiabilityBenignStringToken(expr.ExpValue().CssText())); |
| IdentifiableSurface surface = IdentifiableSurface::FromTypeAndToken( |
| IdentifiableSurface::Type::kMediaQuery, input_builder.GetToken()); |
| |
| IdentifiabilityMetricBuilder(doc->UkmSourceID()) |
| .Set(surface, result) |
| .Record(doc->UkmRecorder()); |
| } |
| |
| } // namespace |
| |
| enum MediaFeaturePrefix { kMinPrefix, kMaxPrefix, kNoPrefix }; |
| |
| using EvalFunc = bool (*)(const MediaQueryExpValue&, |
| MediaFeaturePrefix, |
| const MediaValues&); |
| using FunctionMap = HashMap<StringImpl*, EvalFunc>; |
| static FunctionMap* g_function_map; |
| |
| MediaQueryEvaluator::MediaQueryEvaluator(const char* accepted_media_type) |
| : media_type_(accepted_media_type) {} |
| |
| MediaQueryEvaluator::MediaQueryEvaluator(LocalFrame* frame) |
| : media_values_(MediaValues::CreateDynamicIfFrameExists(frame)) {} |
| |
| MediaQueryEvaluator::MediaQueryEvaluator(const MediaValues& media_values) |
| : media_values_(media_values.Copy()) {} |
| |
| MediaQueryEvaluator::~MediaQueryEvaluator() = default; |
| |
| void MediaQueryEvaluator::Trace(Visitor* visitor) const { |
| visitor->Trace(media_values_); |
| } |
| |
| const String MediaQueryEvaluator::MediaType() const { |
| // If a static mediaType was given by the constructor, we use it here. |
| if (!media_type_.IsEmpty()) |
| return media_type_; |
| // Otherwise, we get one from mediaValues (which may be dynamic or cached). |
| if (media_values_) |
| return media_values_->MediaType(); |
| return g_null_atom; |
| } |
| |
| bool MediaQueryEvaluator::MediaTypeMatch( |
| const String& media_type_to_match) const { |
| return media_type_to_match.IsEmpty() || |
| EqualIgnoringASCIICase(media_type_to_match, media_type_names::kAll) || |
| EqualIgnoringASCIICase(media_type_to_match, MediaType()); |
| } |
| |
| static bool ApplyRestrictor(MediaQuery::RestrictorType r, bool value) { |
| return r == MediaQuery::kNot ? !value : value; |
| } |
| |
| bool MediaQueryEvaluator::Eval( |
| const MediaQuery& query, |
| MediaQueryResultList* viewport_dependent_media_query_results, |
| MediaQueryResultList* device_dependent_media_query_results) const { |
| if (!MediaTypeMatch(query.MediaType())) |
| return ApplyRestrictor(query.Restrictor(), false); |
| |
| const ExpressionHeapVector& expressions = query.Expressions(); |
| // Iterate through expressions, stop if any of them eval to false (AND |
| // semantics). |
| wtf_size_t i = 0; |
| for (; i < expressions.size(); ++i) { |
| bool expr_result = Eval(expressions.at(i)); |
| if (viewport_dependent_media_query_results && |
| expressions.at(i).IsViewportDependent()) { |
| viewport_dependent_media_query_results->push_back( |
| MediaQueryResult(expressions.at(i), expr_result)); |
| } |
| if (device_dependent_media_query_results && |
| expressions.at(i).IsDeviceDependent()) { |
| device_dependent_media_query_results->push_back( |
| MediaQueryResult(expressions.at(i), expr_result)); |
| } |
| if (!expr_result) |
| break; |
| } |
| |
| // Assume true if we are at the end of the list, otherwise assume false. |
| return ApplyRestrictor(query.Restrictor(), expressions.size() == i); |
| } |
| |
| bool MediaQueryEvaluator::Eval( |
| const MediaQuerySet& query_set, |
| MediaQueryResultList* viewport_dependent_media_query_results, |
| MediaQueryResultList* device_dependent_media_query_results) const { |
| const Vector<std::unique_ptr<MediaQuery>>& queries = query_set.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 (wtf_size_t i = 0; i < queries.size() && !result; ++i) |
| result = Eval(*queries[i], viewport_dependent_media_query_results, |
| device_dependent_media_query_results); |
| |
| return result; |
| } |
| |
| bool MediaQueryEvaluator::DidResultsChange( |
| const MediaQueryResultList& results) const { |
| base::AutoReset<bool> skip(&skip_ukm_reporting_, true); |
| for (auto& result : results) { |
| if (Eval(result.Expression()) != result.Result()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool MediaQueryEvaluator::DidResultsChange( |
| const Vector<MediaQuerySetResult>& results) const { |
| base::AutoReset<bool> skip(&skip_ukm_reporting_, true); |
| for (const auto& result : results) { |
| if (result.Result() != Eval(result.MediaQueries())) |
| return true; |
| } |
| return false; |
| } |
| |
| template <typename T> |
| bool CompareValue(T a, T b, MediaFeaturePrefix op) { |
| switch (op) { |
| case kMinPrefix: |
| return a >= b; |
| case kMaxPrefix: |
| return a <= b; |
| case kNoPrefix: |
| return a == b; |
| } |
| return false; |
| } |
| |
| bool CompareDoubleValue(double a, double b, MediaFeaturePrefix op) { |
| const double precision = LayoutUnit::Epsilon(); |
| switch (op) { |
| case kMinPrefix: |
| return a >= (b - precision); |
| case kMaxPrefix: |
| return a <= (b + precision); |
| case kNoPrefix: |
| return std::abs(a - b) <= precision; |
| } |
| return false; |
| } |
| |
| static bool CompareAspectRatioValue(const MediaQueryExpValue& value, |
| int width, |
| int height, |
| MediaFeaturePrefix op) { |
| if (value.is_ratio) { |
| return CompareValue(static_cast<double>(width) * value.denominator, |
| static_cast<double>(height) * value.numerator, op); |
| } |
| return false; |
| } |
| |
| static bool NumberValue(const MediaQueryExpValue& value, float& result) { |
| if (value.is_value && value.unit == CSSPrimitiveValue::UnitType::kNumber) { |
| result = value.value; |
| return true; |
| } |
| return false; |
| } |
| |
| static bool ColorMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| float number; |
| int bits_per_component = media_values.ColorBitsPerComponent(); |
| if (value.IsValid()) |
| return NumberValue(value, number) && |
| CompareValue(bits_per_component, static_cast<int>(number), op); |
| |
| return bits_per_component != 0; |
| } |
| |
| static bool ColorIndexMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues&) { |
| // FIXME: We currently assume that we do not support indexed displays, as it |
| // is unknown how to retrieve the information if the display mode is indexed. |
| // This matches Firefox. |
| if (!value.IsValid()) |
| return false; |
| |
| // Acording to spec, if the device does not use a color lookup table, the |
| // value is zero. |
| float number; |
| return NumberValue(value, number) && |
| CompareValue(0, static_cast<int>(number), op); |
| } |
| |
| static bool MonochromeMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| float number; |
| int bits_per_component = media_values.MonochromeBitsPerComponent(); |
| if (value.IsValid()) { |
| return NumberValue(value, number) && |
| CompareValue(bits_per_component, static_cast<int>(number), op); |
| } |
| return bits_per_component != 0; |
| } |
| |
| static bool DisplayModeMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| // isValid() is false if there is no parameter. Without parameter we should |
| // return true to indicate that displayModeMediaFeature is enabled in the |
| // browser. |
| if (!value.IsValid()) |
| return true; |
| |
| if (!value.is_id) |
| return false; |
| |
| blink::mojom::DisplayMode mode = media_values.DisplayMode(); |
| switch (value.id) { |
| case CSSValueID::kFullscreen: |
| return mode == blink::mojom::DisplayMode::kFullscreen; |
| case CSSValueID::kStandalone: |
| return mode == blink::mojom::DisplayMode::kStandalone; |
| case CSSValueID::kMinimalUi: |
| return mode == blink::mojom::DisplayMode::kMinimalUi; |
| case CSSValueID::kBrowser: |
| return mode == blink::mojom::DisplayMode::kBrowser; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool OrientationMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| int width = media_values.ViewportWidth(); |
| int height = media_values.ViewportHeight(); |
| |
| if (value.is_id) { |
| if (width > height) // Square viewport is portrait. |
| return CSSValueID::kLandscape == value.id; |
| return CSSValueID::kPortrait == value.id; |
| } |
| |
| // Expression (orientation) evaluates to true if width and height >= 0. |
| return height >= 0 && width >= 0; |
| } |
| |
| static bool AspectRatioMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| if (value.IsValid()) |
| return CompareAspectRatioValue(value, media_values.ViewportWidth(), |
| media_values.ViewportHeight(), op); |
| |
| // ({,min-,max-}aspect-ratio) |
| // assume if we have a device, its aspect ratio is non-zero. |
| return true; |
| } |
| |
| static bool DeviceAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| if (value.IsValid()) |
| return CompareAspectRatioValue(value, media_values.DeviceWidth(), |
| media_values.DeviceHeight(), op); |
| |
| // ({,min-,max-}device-aspect-ratio) |
| // assume if we have a device, its aspect ratio is non-zero. |
| return true; |
| } |
| |
| static bool EvalResolution(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| // According to MQ4, only 'screen', 'print' and 'speech' may match. |
| // FIXME: What should speech match? |
| // https://www.w3.org/Style/CSS/Tracker/issues/348 |
| float actual_resolution = 0; |
| |
| // This checks the actual media type applied to the document, and we know |
| // this method only got called if this media type matches the one defined |
| // in the query. Thus, if if the document's media type is "print", the |
| // media type of the query will either be "print" or "all". |
| if (EqualIgnoringASCIICase(media_values.MediaType(), |
| media_type_names::kScreen)) { |
| actual_resolution = clampTo<float>(media_values.DevicePixelRatio()); |
| } else if (EqualIgnoringASCIICase(media_values.MediaType(), |
| media_type_names::kPrint)) { |
| // The resolution of images while printing should not depend on the DPI |
| // of the screen. Until we support proper ways of querying this info |
| // we use 300px which is considered minimum for current printers. |
| actual_resolution = 300 / kCssPixelsPerInch; |
| } |
| |
| if (!value.IsValid()) |
| return !!actual_resolution; |
| |
| if (!value.is_value) |
| return false; |
| |
| if (value.unit == CSSPrimitiveValue::UnitType::kNumber) |
| return CompareValue(actual_resolution, clampTo<float>(value.value), op); |
| |
| if (!CSSPrimitiveValue::IsResolution(value.unit)) |
| return false; |
| |
| double canonical_factor = |
| CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(value.unit); |
| double dppx_factor = CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor( |
| CSSPrimitiveValue::UnitType::kDotsPerPixel); |
| float value_in_dppx = |
| clampTo<float>(value.value * (canonical_factor / dppx_factor)); |
| if (value.unit == CSSPrimitiveValue::UnitType::kDotsPerCentimeter) { |
| // To match DPCM to DPPX values, we limit to 2 decimal points. |
| // The https://drafts.csswg.org/css-values/#absolute-lengths recommends |
| // "that the pixel unit refer to the whole number of device pixels that best |
| // approximates the reference pixel". With that in mind, allowing 2 decimal |
| // point precision seems appropriate. |
| return CompareValue(floorf(0.5 + 100 * actual_resolution) / 100, |
| floorf(0.5 + 100 * value_in_dppx) / 100, op); |
| } |
| |
| return CompareValue(actual_resolution, value_in_dppx, op); |
| } |
| |
| static bool DevicePixelRatioMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| UseCounter::Count(media_values.GetDocument(), |
| WebFeature::kPrefixedDevicePixelRatioMediaFeature); |
| |
| return (!value.IsValid() || |
| value.unit == CSSPrimitiveValue::UnitType::kNumber) && |
| EvalResolution(value, op, media_values); |
| } |
| |
| static bool ResolutionMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| return (!value.IsValid() || CSSPrimitiveValue::IsResolution(value.unit)) && |
| EvalResolution(value, op, media_values); |
| } |
| |
| static bool GridMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues&) { |
| // if output device is bitmap, grid: 0 == true |
| // assume we have bitmap device |
| float number; |
| if (value.IsValid() && NumberValue(value, number)) |
| return CompareValue(static_cast<int>(number), 0, op); |
| return false; |
| } |
| |
| static bool ComputeLength(const MediaQueryExpValue& value, |
| const MediaValues& media_values, |
| double& result) { |
| if (!value.is_value) |
| return false; |
| |
| if (value.unit == CSSPrimitiveValue::UnitType::kNumber) { |
| result = clampTo<int>(value.value); |
| return !media_values.StrictMode() || !result; |
| } |
| |
| if (CSSPrimitiveValue::IsLength(value.unit)) |
| return media_values.ComputeLength(value.value, value.unit, result); |
| return false; |
| } |
| |
| static bool ComputeLengthAndCompare(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values, |
| double compare_to_value) { |
| double length; |
| return ComputeLength(value, media_values, length) && |
| CompareDoubleValue(compare_to_value, length, op); |
| } |
| |
| static bool DeviceHeightMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| if (value.IsValid()) |
| return ComputeLengthAndCompare(value, op, media_values, |
| media_values.DeviceHeight()); |
| |
| // ({,min-,max-}device-height) |
| // assume if we have a device, assume non-zero |
| return true; |
| } |
| |
| static bool DeviceWidthMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| if (value.IsValid()) |
| return ComputeLengthAndCompare(value, op, media_values, |
| media_values.DeviceWidth()); |
| |
| // ({,min-,max-}device-width) |
| // assume if we have a device, assume non-zero |
| return true; |
| } |
| |
| static bool HeightMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| double height = media_values.ViewportHeight(); |
| if (value.IsValid()) |
| return ComputeLengthAndCompare(value, op, media_values, height); |
| |
| return height; |
| } |
| |
| static bool WidthMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| double width = media_values.ViewportWidth(); |
| if (value.IsValid()) |
| return ComputeLengthAndCompare(value, op, media_values, width); |
| |
| return width; |
| } |
| |
| // Rest of the functions are trampolines which set the prefix according to the |
| // media feature expression used. |
| |
| static bool MinColorMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return ColorMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxColorMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return ColorMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinColorIndexMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return ColorIndexMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxColorIndexMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return ColorIndexMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinMonochromeMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return MonochromeMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxMonochromeMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return MonochromeMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return AspectRatioMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return AspectRatioMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinDeviceAspectRatioMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return DeviceAspectRatioMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxDeviceAspectRatioMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return DeviceAspectRatioMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinDevicePixelRatioMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| UseCounter::Count(media_values.GetDocument(), |
| WebFeature::kPrefixedMinDevicePixelRatioMediaFeature); |
| |
| return DevicePixelRatioMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxDevicePixelRatioMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| UseCounter::Count(media_values.GetDocument(), |
| WebFeature::kPrefixedMaxDevicePixelRatioMediaFeature); |
| |
| return DevicePixelRatioMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinHeightMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return HeightMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxHeightMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return HeightMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinWidthMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return WidthMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxWidthMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return WidthMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinDeviceHeightMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return DeviceHeightMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxDeviceHeightMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return DeviceHeightMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinDeviceWidthMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return DeviceWidthMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxDeviceWidthMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return DeviceWidthMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool MinResolutionMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return ResolutionMediaFeatureEval(value, kMinPrefix, media_values); |
| } |
| |
| static bool MaxResolutionMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| return ResolutionMediaFeatureEval(value, kMaxPrefix, media_values); |
| } |
| |
| static bool Transform3dMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| UseCounter::Count(media_values.GetDocument(), |
| WebFeature::kPrefixedTransform3dMediaFeature); |
| |
| bool return_value_if_no_parameter; |
| int have3d_rendering; |
| |
| bool three_d_enabled = media_values.ThreeDEnabled(); |
| |
| return_value_if_no_parameter = three_d_enabled; |
| have3d_rendering = three_d_enabled ? 1 : 0; |
| |
| if (value.IsValid()) { |
| float number; |
| return NumberValue(value, number) && |
| CompareValue(have3d_rendering, static_cast<int>(number), op); |
| } |
| return return_value_if_no_parameter; |
| } |
| |
| static bool ImmersiveMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix op, |
| const MediaValues& media_values) { |
| bool return_value_if_no_parameter; |
| int is_immersive_numeric_value; |
| |
| bool immersive = media_values.InImmersiveMode(); |
| |
| return_value_if_no_parameter = immersive; |
| is_immersive_numeric_value = immersive ? 1 : 0; |
| |
| if (value.IsValid()) { |
| float number; |
| return NumberValue(value, number) && |
| CompareValue(is_immersive_numeric_value, static_cast<int>(number), |
| op); |
| } |
| return return_value_if_no_parameter; |
| } |
| |
| static bool HoverMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| HoverType hover = media_values.PrimaryHoverType(); |
| |
| if (!value.IsValid()) |
| return hover != HoverType::kHoverNone; |
| |
| if (!value.is_id) |
| return false; |
| |
| return (hover == HoverType::kHoverNone && value.id == CSSValueID::kNone) || |
| (hover == HoverType::kHoverHoverType && |
| value.id == CSSValueID::kHover); |
| } |
| |
| static bool AnyHoverMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| int available_hover_types = media_values.AvailableHoverTypes(); |
| |
| if (!value.IsValid()) |
| return available_hover_types & ~static_cast<int>(HoverType::kHoverNone); |
| |
| if (!value.is_id) |
| return false; |
| |
| switch (value.id) { |
| case CSSValueID::kNone: |
| return available_hover_types & static_cast<int>(HoverType::kHoverNone); |
| case CSSValueID::kHover: |
| return available_hover_types & |
| static_cast<int>(HoverType::kHoverHoverType); |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool OriginTrialTestMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| // The test feature only supports a 'no-value' parsing. So if we've gotten |
| // to this point it will always match. |
| DCHECK(!value.IsValid()); |
| return true; |
| } |
| |
| static bool PointerMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| PointerType pointer = media_values.PrimaryPointerType(); |
| |
| if (!value.IsValid()) |
| return pointer != PointerType::kPointerNone; |
| |
| if (!value.is_id) |
| return false; |
| |
| return (pointer == PointerType::kPointerNone && |
| value.id == CSSValueID::kNone) || |
| (pointer == PointerType::kPointerCoarseType && |
| value.id == CSSValueID::kCoarse) || |
| (pointer == PointerType::kPointerFineType && |
| value.id == CSSValueID::kFine); |
| } |
| |
| static bool PrefersReducedMotionMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| // If the value is not valid, this was passed without an argument. In that |
| // case, it implicitly resolves to 'reduce'. |
| if (!value.IsValid()) |
| return media_values.PrefersReducedMotion(); |
| |
| if (!value.is_id) |
| return false; |
| |
| return (value.id == CSSValueID::kNoPreference) ^ |
| media_values.PrefersReducedMotion(); |
| } |
| |
| static bool PrefersReducedDataMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| if (!value.IsValid()) |
| return media_values.PrefersReducedData(); |
| |
| if (!value.is_id) |
| return false; |
| |
| return (value.id == CSSValueID::kNoPreference) ^ |
| media_values.PrefersReducedData(); |
| } |
| |
| static bool AnyPointerMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| int available_pointers = media_values.AvailablePointerTypes(); |
| |
| if (!value.IsValid()) { |
| return available_pointers & ~static_cast<int>(PointerType::kPointerNone); |
| } |
| |
| if (!value.is_id) |
| return false; |
| |
| switch (value.id) { |
| case CSSValueID::kCoarse: |
| return available_pointers & |
| static_cast<int>(PointerType::kPointerCoarseType); |
| case CSSValueID::kFine: |
| return available_pointers & |
| static_cast<int>(PointerType::kPointerFineType); |
| case CSSValueID::kNone: |
| return available_pointers & static_cast<int>(PointerType::kPointerNone); |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool ScanMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| // Scan only applies to 'tv' media. |
| if (!EqualIgnoringASCIICase(media_values.MediaType(), media_type_names::kTv)) |
| return false; |
| |
| if (!value.IsValid()) |
| return true; |
| |
| if (!value.is_id) |
| return false; |
| |
| // If a platform interface supplies progressive/interlace info for TVs in the |
| // future, it needs to be handled here. For now, assume a modern TV with |
| // progressive display. |
| return (value.id == CSSValueID::kProgressive); |
| } |
| |
| static bool ColorGamutMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| // isValid() is false if there is no parameter. Without parameter we should |
| // return true to indicate that colorGamutMediaFeature is enabled in the |
| // browser. |
| if (!value.IsValid()) |
| return true; |
| |
| if (!value.is_id) |
| return false; |
| |
| DCHECK(value.id == CSSValueID::kSRGB || value.id == CSSValueID::kP3 || |
| value.id == CSSValueID::kRec2020); |
| |
| ColorSpaceGamut gamut = media_values.ColorGamut(); |
| switch (gamut) { |
| case ColorSpaceGamut::kUnknown: |
| case ColorSpaceGamut::kLessThanNTSC: |
| case ColorSpaceGamut::NTSC: |
| case ColorSpaceGamut::SRGB: |
| return value.id == CSSValueID::kSRGB; |
| case ColorSpaceGamut::kAlmostP3: |
| case ColorSpaceGamut::P3: |
| case ColorSpaceGamut::kAdobeRGB: |
| case ColorSpaceGamut::kWide: |
| return value.id == CSSValueID::kSRGB || value.id == CSSValueID::kP3; |
| case ColorSpaceGamut::BT2020: |
| case ColorSpaceGamut::kProPhoto: |
| case ColorSpaceGamut::kUltraWide: |
| return value.id == CSSValueID::kSRGB || value.id == CSSValueID::kP3 || |
| value.id == CSSValueID::kRec2020; |
| case ColorSpaceGamut::kEnd: |
| NOTREACHED(); |
| return false; |
| } |
| |
| // This is for some compilers that do not understand that it can't be reached. |
| NOTREACHED(); |
| return false; |
| } |
| |
| static bool PrefersColorSchemeMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| auto preferred_scheme = media_values.GetPreferredColorScheme(); |
| |
| if (!value.IsValid()) |
| return true; |
| |
| if (!value.is_id) |
| return false; |
| |
| return (preferred_scheme == mojom::blink::PreferredColorScheme::kDark && |
| value.id == CSSValueID::kDark) || |
| (preferred_scheme == mojom::blink::PreferredColorScheme::kLight && |
| value.id == CSSValueID::kLight); |
| } |
| |
| static bool PrefersContrastMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| auto preferred_contrast = media_values.GetPreferredContrast(); |
| ForcedColors forced_colors = media_values.GetForcedColors(); |
| |
| if (!value.IsValid()) { |
| return forced_colors != ForcedColors::kNone || |
| preferred_contrast != mojom::blink::PreferredContrast::kNoPreference; |
| } |
| |
| if (!value.is_id) |
| return false; |
| |
| switch (value.id) { |
| case CSSValueID::kForced: |
| return forced_colors == ForcedColors::kActive; |
| case CSSValueID::kMore: |
| return preferred_contrast == mojom::blink::PreferredContrast::kMore; |
| case CSSValueID::kLess: |
| return preferred_contrast == mojom::blink::PreferredContrast::kLess; |
| case CSSValueID::kNoPreference: |
| return forced_colors != ForcedColors::kActive && |
| preferred_contrast == |
| mojom::blink::PreferredContrast::kNoPreference; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool ForcedColorsMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| ForcedColors forced_colors = media_values.GetForcedColors(); |
| |
| if (!value.IsValid()) |
| return forced_colors != ForcedColors::kNone; |
| |
| if (!value.is_id) |
| return false; |
| |
| // Check the forced colors against value.id. |
| return (forced_colors == ForcedColors::kNone && |
| value.id == CSSValueID::kNone) || |
| (forced_colors != ForcedColors::kNone && |
| value.id == CSSValueID::kActive); |
| } |
| |
| static bool NavigationControlsMediaFeatureEval( |
| const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| NavigationControls navigation_controls = media_values.GetNavigationControls(); |
| |
| if (!value.IsValid()) |
| return navigation_controls != NavigationControls::kNone; |
| |
| if (!value.is_id) |
| return false; |
| |
| // Check the navigation controls against value.id. |
| return (navigation_controls == NavigationControls::kNone && |
| value.id == CSSValueID::kNone) || |
| (navigation_controls == NavigationControls::kBackButton && |
| value.id == CSSValueID::kBackButton); |
| } |
| |
| static bool ScreenSpanningMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| ScreenSpanning screen_spanning_mode = media_values.GetScreenSpanning(); |
| |
| if (!value.IsValid()) |
| return screen_spanning_mode != ScreenSpanning::kNone; |
| |
| // We should not have parsed a valid MediaQueryExpValue if the value is not |
| // an identifier. |
| DCHECK(value.is_id); |
| |
| return (screen_spanning_mode == ScreenSpanning::kNone && |
| value.id == CSSValueID::kNone) || |
| (screen_spanning_mode == ScreenSpanning::kSingleFoldVertical && |
| value.id == CSSValueID::kSingleFoldVertical) || |
| (screen_spanning_mode == ScreenSpanning::kSingleFoldHorizontal && |
| value.id == CSSValueID::kSingleFoldHorizontal); |
| } |
| |
| static bool ScreenFoldPostureMediaFeatureEval(const MediaQueryExpValue& value, |
| MediaFeaturePrefix, |
| const MediaValues& media_values) { |
| // isValid() is false if there is no parameter. Without parameter we should |
| // return true to indicate that screenFoldPosture is enabled in the |
| // browser. |
| if (!value.IsValid()) |
| return true; |
| |
| DCHECK(value.is_id); |
| |
| ScreenFoldPosture screen_fold_posture = media_values.GetScreenFoldPosture(); |
| switch (value.id) { |
| case CSSValueID::kNoFold: |
| return screen_fold_posture == ScreenFoldPosture::kNoFold; |
| case CSSValueID::kLaptop: |
| return screen_fold_posture == ScreenFoldPosture::kLaptop; |
| case CSSValueID::kFlat: |
| return screen_fold_posture == ScreenFoldPosture::kFlat; |
| case CSSValueID::kTent: |
| return screen_fold_posture == ScreenFoldPosture::kTent; |
| case CSSValueID::kTablet: |
| return screen_fold_posture == ScreenFoldPosture::kTablet; |
| case CSSValueID::kBook: |
| return screen_fold_posture == ScreenFoldPosture::kBook; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| void MediaQueryEvaluator::Init() { |
| // Create the table. |
| g_function_map = new FunctionMap; |
| #define ADD_TO_FUNCTIONMAP(constantPrefix, methodPrefix) \ |
| g_function_map->Set(constantPrefix##MediaFeature.Impl(), \ |
| methodPrefix##MediaFeatureEval); |
| CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(ADD_TO_FUNCTIONMAP); |
| #undef ADD_TO_FUNCTIONMAP |
| } |
| |
| bool MediaQueryEvaluator::Eval(const MediaQueryExp& expr) const { |
| if (!media_values_ || !media_values_->HasValues()) { |
| // media_values_ should only be nullptr when parsing UA stylesheets. The |
| // only media queries we support in UA stylesheets are media type queries. |
| // If HasValues() return false, it means the document frame is nullptr. |
| NOTREACHED(); |
| return false; |
| } |
| |
| DCHECK(g_function_map); |
| |
| // Call the media feature evaluation function. Assume no prefix and let |
| // trampoline functions override the prefix if prefix is used. |
| EvalFunc func = g_function_map->at(expr.MediaFeature().Impl()); |
| if (func) { |
| bool result = func(expr.ExpValue(), kNoPrefix, *media_values_); |
| Document* doc = nullptr; |
| if (!skip_ukm_reporting_ && (doc = media_values_->GetDocument()) && |
| (IdentifiabilityStudySettings::Get()->ShouldSample( |
| IdentifiableSurface::Type::kMediaQuery))) { |
| RecordMediaQueryResult(doc, expr, result); |
| } |
| |
| return result; |
| } |
| |
| return false; |
| } |
| |
| } // namespace blink |