blob: 02a0ce831ed0d2f038314421c3dd0f401e150ba4 [file] [log] [blame]
/*
* CSS Media Query
*
* Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>.
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
* Copyright (C) 2013 Apple Inc. 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_exp.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
#include "third_party/blink/renderer/core/css/parser/css_property_parser_helpers.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/decimal.h"
#include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
using namespace media_feature_names;
static inline bool FeatureWithValidIdent(const String& media_feature,
CSSValueID ident) {
if (media_feature == kDisplayModeMediaFeature)
return ident == CSSValueFullscreen || ident == CSSValueStandalone ||
ident == CSSValueMinimalUi || ident == CSSValueBrowser;
if (media_feature == kOrientationMediaFeature)
return ident == CSSValuePortrait || ident == CSSValueLandscape;
if (media_feature == kPointerMediaFeature ||
media_feature == kAnyPointerMediaFeature)
return ident == CSSValueNone || ident == CSSValueCoarse ||
ident == CSSValueFine;
if (media_feature == kHoverMediaFeature ||
media_feature == kAnyHoverMediaFeature)
return ident == CSSValueNone || ident == CSSValueHover;
if (media_feature == kScanMediaFeature)
return ident == CSSValueInterlace || ident == CSSValueProgressive;
if (RuntimeEnabledFeatures::MediaQueryShapeEnabled()) {
if (media_feature == kShapeMediaFeature)
return ident == CSSValueRect || ident == CSSValueRound;
}
if (media_feature == kColorGamutMediaFeature) {
return ident == CSSValueSRGB || ident == CSSValueP3 ||
ident == CSSValueRec2020;
}
if (RuntimeEnabledFeatures::MediaQueryPrefersColorSchemeEnabled()) {
if (media_feature == kPrefersColorSchemeMediaFeature) {
return ident == CSSValueNoPreference || ident == CSSValueDark ||
ident == CSSValueLight;
}
}
if (RuntimeEnabledFeatures::MediaQueryPrefersReducedMotionEnabled()) {
if (media_feature == kPrefersReducedMotionMediaFeature) {
return ident == CSSValueNoPreference || ident == CSSValueReduce;
}
}
return false;
}
static inline bool FeatureWithValidPositiveLength(
const String& media_feature,
const CSSPrimitiveValue* value) {
if (!(value->IsLength() ||
(value->IsNumber() && value->GetDoubleValue() == 0)))
return false;
return media_feature == kHeightMediaFeature ||
media_feature == kMaxHeightMediaFeature ||
media_feature == kMinHeightMediaFeature ||
media_feature == kWidthMediaFeature ||
media_feature == kMaxWidthMediaFeature ||
media_feature == kMinWidthMediaFeature ||
media_feature == kDeviceHeightMediaFeature ||
media_feature == kMaxDeviceHeightMediaFeature ||
media_feature == kMinDeviceHeightMediaFeature ||
media_feature == kDeviceWidthMediaFeature ||
media_feature == kMinDeviceWidthMediaFeature ||
media_feature == kMaxDeviceWidthMediaFeature;
}
static inline bool FeatureWithValidDensity(const String& media_feature,
const CSSPrimitiveValue* value) {
if ((value->TypeWithCalcResolved() !=
CSSPrimitiveValue::UnitType::kDotsPerPixel &&
value->TypeWithCalcResolved() !=
CSSPrimitiveValue::UnitType::kDotsPerInch &&
value->TypeWithCalcResolved() !=
CSSPrimitiveValue::UnitType::kDotsPerCentimeter) ||
value->GetDoubleValue() <= 0)
return false;
return media_feature == kResolutionMediaFeature ||
media_feature == kMinResolutionMediaFeature ||
media_feature == kMaxResolutionMediaFeature;
}
static inline bool FeatureExpectingPositiveInteger(
const String& media_feature) {
return media_feature == kColorMediaFeature ||
media_feature == kMaxColorMediaFeature ||
media_feature == kMinColorMediaFeature ||
media_feature == kColorIndexMediaFeature ||
media_feature == kMaxColorIndexMediaFeature ||
media_feature == kMinColorIndexMediaFeature ||
media_feature == kMonochromeMediaFeature ||
media_feature == kMaxMonochromeMediaFeature ||
media_feature == kMinMonochromeMediaFeature ||
media_feature == kImmersiveMediaFeature;
}
static inline bool FeatureWithPositiveInteger(const String& media_feature,
const CSSPrimitiveValue* value) {
if (value->TypeWithCalcResolved() != CSSPrimitiveValue::UnitType::kInteger)
return false;
return FeatureExpectingPositiveInteger(media_feature);
}
static inline bool FeatureWithPositiveNumber(const String& media_feature,
const CSSPrimitiveValue* value) {
if (!value->IsNumber())
return false;
return media_feature == kTransform3dMediaFeature ||
media_feature == kDevicePixelRatioMediaFeature ||
media_feature == kMaxDevicePixelRatioMediaFeature ||
media_feature == kMinDevicePixelRatioMediaFeature;
}
static inline bool FeatureWithZeroOrOne(const String& media_feature,
const CSSPrimitiveValue* value) {
if (value->TypeWithCalcResolved() != CSSPrimitiveValue::UnitType::kInteger ||
!(value->GetDoubleValue() == 1 || !value->GetDoubleValue()))
return false;
return media_feature == kGridMediaFeature;
}
static inline bool FeatureWithAspectRatio(const String& media_feature) {
return media_feature == kAspectRatioMediaFeature ||
media_feature == kDeviceAspectRatioMediaFeature ||
media_feature == kMinAspectRatioMediaFeature ||
media_feature == kMaxAspectRatioMediaFeature ||
media_feature == kMinDeviceAspectRatioMediaFeature ||
media_feature == kMaxDeviceAspectRatioMediaFeature;
}
static inline bool FeatureWithoutValue(const String& media_feature) {
// Media features that are prefixed by min/max cannot be used without a value.
return media_feature == kMonochromeMediaFeature ||
media_feature == kColorMediaFeature ||
media_feature == kColorIndexMediaFeature ||
media_feature == kGridMediaFeature ||
media_feature == kHeightMediaFeature ||
media_feature == kWidthMediaFeature ||
media_feature == kDeviceHeightMediaFeature ||
media_feature == kDeviceWidthMediaFeature ||
media_feature == kOrientationMediaFeature ||
media_feature == kAspectRatioMediaFeature ||
media_feature == kDeviceAspectRatioMediaFeature ||
media_feature == kHoverMediaFeature ||
media_feature == kAnyHoverMediaFeature ||
media_feature == kTransform3dMediaFeature ||
media_feature == kPointerMediaFeature ||
media_feature == kAnyPointerMediaFeature ||
media_feature == kDevicePixelRatioMediaFeature ||
media_feature == kResolutionMediaFeature ||
media_feature == kDisplayModeMediaFeature ||
media_feature == kScanMediaFeature ||
media_feature == kShapeMediaFeature ||
media_feature == kColorGamutMediaFeature ||
media_feature == kImmersiveMediaFeature ||
media_feature == kPrefersColorSchemeMediaFeature ||
(RuntimeEnabledFeatures::MediaQueryPrefersReducedMotionEnabled() &&
media_feature == kPrefersReducedMotionMediaFeature);
}
bool MediaQueryExp::IsViewportDependent() const {
return media_feature_ == kWidthMediaFeature ||
media_feature_ == kHeightMediaFeature ||
media_feature_ == kMinWidthMediaFeature ||
media_feature_ == kMinHeightMediaFeature ||
media_feature_ == kMaxWidthMediaFeature ||
media_feature_ == kMaxHeightMediaFeature ||
media_feature_ == kOrientationMediaFeature ||
media_feature_ == kAspectRatioMediaFeature ||
media_feature_ == kMinAspectRatioMediaFeature ||
media_feature_ == kDevicePixelRatioMediaFeature ||
media_feature_ == kResolutionMediaFeature ||
media_feature_ == kMaxAspectRatioMediaFeature ||
media_feature_ == kMaxDevicePixelRatioMediaFeature ||
media_feature_ == kMinDevicePixelRatioMediaFeature;
}
bool MediaQueryExp::IsDeviceDependent() const {
return media_feature_ == kDeviceAspectRatioMediaFeature ||
media_feature_ == kDeviceWidthMediaFeature ||
media_feature_ == kDeviceHeightMediaFeature ||
media_feature_ == kMinDeviceAspectRatioMediaFeature ||
media_feature_ == kMinDeviceWidthMediaFeature ||
media_feature_ == kMinDeviceHeightMediaFeature ||
media_feature_ == kMaxDeviceAspectRatioMediaFeature ||
media_feature_ == kMaxDeviceWidthMediaFeature ||
media_feature_ == kMaxDeviceHeightMediaFeature ||
media_feature_ == kShapeMediaFeature;
}
MediaQueryExp::MediaQueryExp(const MediaQueryExp& other)
: media_feature_(other.MediaFeature()), exp_value_(other.ExpValue()) {}
MediaQueryExp::MediaQueryExp(const String& media_feature,
const MediaQueryExpValue& exp_value)
: media_feature_(media_feature), exp_value_(exp_value) {}
MediaQueryExp MediaQueryExp::Create(const String& media_feature,
CSSParserTokenRange& range) {
DCHECK(!media_feature.IsNull());
MediaQueryExpValue exp_value;
String lower_media_feature =
AttemptStaticStringCreation(media_feature.LowerASCII());
CSSPrimitiveValue* value =
css_property_parser_helpers::ConsumeInteger(range, 0);
if (!value && !FeatureExpectingPositiveInteger(lower_media_feature) &&
!FeatureWithAspectRatio(lower_media_feature)) {
value = css_property_parser_helpers::ConsumeNumber(range,
kValueRangeNonNegative);
}
if (!value) {
value = css_property_parser_helpers::ConsumeLength(range, kHTMLStandardMode,
kValueRangeNonNegative);
}
if (!value)
value = css_property_parser_helpers::ConsumeResolution(range);
// Create value for media query expression that must have 1 or more values.
if (value) {
if (FeatureWithAspectRatio(lower_media_feature)) {
if (value->TypeWithCalcResolved() !=
CSSPrimitiveValue::UnitType::kInteger ||
value->GetDoubleValue() == 0)
return Invalid();
if (!css_property_parser_helpers::ConsumeSlashIncludingWhitespace(range))
return Invalid();
CSSPrimitiveValue* denominator =
css_property_parser_helpers::ConsumePositiveInteger(range);
if (!denominator)
return Invalid();
exp_value.numerator = clampTo<unsigned>(value->GetDoubleValue());
exp_value.denominator = clampTo<unsigned>(denominator->GetDoubleValue());
exp_value.is_ratio = true;
} else if (FeatureWithValidDensity(lower_media_feature, value) ||
FeatureWithValidPositiveLength(lower_media_feature, value) ||
FeatureWithPositiveInteger(lower_media_feature, value) ||
FeatureWithPositiveNumber(lower_media_feature, value) ||
FeatureWithZeroOrOne(lower_media_feature, value)) {
exp_value.value = value->GetDoubleValue();
if (value->IsNumber())
exp_value.unit = CSSPrimitiveValue::UnitType::kNumber;
else
exp_value.unit = value->TypeWithCalcResolved();
exp_value.is_value = true;
} else {
return Invalid();
}
} else if (CSSIdentifierValue* ident =
css_property_parser_helpers::ConsumeIdent(range)) {
CSSValueID ident_id = ident->GetValueID();
if (!FeatureWithValidIdent(lower_media_feature, ident_id))
return Invalid();
exp_value.id = ident_id;
exp_value.is_id = true;
} else if (FeatureWithoutValue(lower_media_feature)) {
// Valid, creates a MediaQueryExp with an 'invalid' MediaQueryExpValue
} else {
return Invalid();
}
return MediaQueryExp(lower_media_feature, exp_value);
}
MediaQueryExp::~MediaQueryExp() = default;
bool MediaQueryExp::operator==(const MediaQueryExp& other) const {
return (other.media_feature_ == media_feature_) &&
((!other.exp_value_.IsValid() && !exp_value_.IsValid()) ||
(other.exp_value_.IsValid() && exp_value_.IsValid() &&
other.exp_value_.Equals(exp_value_)));
}
String MediaQueryExp::Serialize() const {
StringBuilder result;
result.Append('(');
result.Append(media_feature_.LowerASCII());
if (exp_value_.IsValid()) {
result.Append(": ");
result.Append(exp_value_.CssText());
}
result.Append(')');
return result.ToString();
}
static inline String PrintNumber(double number) {
return Decimal::FromDouble(number).ToString();
}
String MediaQueryExpValue::CssText() const {
StringBuilder output;
if (is_value) {
output.Append(PrintNumber(value));
output.Append(CSSPrimitiveValue::UnitTypeToString(unit));
} else if (is_ratio) {
output.Append(PrintNumber(numerator));
output.Append('/');
output.Append(PrintNumber(denominator));
} else if (is_id) {
output.Append(getValueName(id));
}
return output.ToString();
}
} // namespace blink