blob: f02ca7988266b05604b3586d24e26ab06827d352 [file] [log] [blame]
/*
* 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