Skip to content

Commit bf67389

Browse files
committed
Bug 1965029 - Accept CSS color syntax in input type=color r=emilio
The alpha and colorspace part of the https://html.spec.whatwg.org/#serialize-a-color-well-control-color is largely skipped in this patch, as we don't support them yet in `<input type=color>`, and thus the value is always in the "HTML compatible" form. Changing the existing WPT color.tentative.html to color.html to specifically cover implementations without `alpha` and `colorspace` support, because otherwise reading the test result of `color.window.js` is overly confusing. (Ideally the two tests should share the same test data, but for now I'm skipping that as I'm not confident yet to fill the display-p3 fields. That can be done when we actually implement `colorspace` support.) Introducing Servo_ComputeColorWellControlColor so that the colorspace conversion can happen before losing number precision, following the spec. Differential Revision: https://phabricator.services.mozilla.com/D258654
1 parent 0686fc8 commit bf67389

File tree

13 files changed

+211
-145
lines changed

13 files changed

+211
-145
lines changed

dom/canvas/CanvasRenderingContext2D.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1309,9 +1309,10 @@ CanvasRenderingContext2D::ParseColorSlow(const nsACString& aString) {
13091309

13101310
PresShell* presShell = GetPresShell();
13111311
ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr;
1312+
const StylePerDocumentStyleData* data = set ? set->RawData() : nullptr;
13121313
bool wasCurrentColor = false;
13131314
nscolor color;
1314-
if (ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, &color,
1315+
if (ServoCSSParser::ComputeColor(data, NS_RGB(0, 0, 0), aString, &color,
13151316
&wasCurrentColor, loader)) {
13161317
result.mWasCurrentColor = wasCurrentColor;
13171318
result.mColor.emplace(color);

dom/html/HTMLInputElement.cpp

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "mozilla/dom/BlobImpl.h"
1616
#include "mozilla/dom/CustomEvent.h"
1717
#include "mozilla/dom/Directory.h"
18+
#include "mozilla/dom/DocumentInlines.h"
1819
#include "mozilla/dom/DocumentOrShadowRoot.h"
1920
#include "mozilla/dom/ElementBinding.h"
2021
#include "mozilla/dom/FileSystemUtils.h"
@@ -33,6 +34,7 @@
3334
#include "mozilla/Maybe.h"
3435
#include "mozilla/MouseEvents.h"
3536
#include "mozilla/PresShell.h"
37+
#include "mozilla/ServoCSSParser.h"
3638
#include "mozilla/StaticPrefs_dom.h"
3739
#include "mozilla/StaticPrefs_signon.h"
3840
#include "mozilla/TextUtils.h"
@@ -724,6 +726,41 @@ static bool IsPickerBlocked(Document* aDoc) {
724726
return true;
725727
}
726728

729+
/**
730+
* Parse a CSS color string and convert it to the target colorspace if it
731+
* succeeds.
732+
* https://html.spec.whatwg.org/#update-a-color-well-control-color
733+
*
734+
* @param aValue the string to be parsed
735+
* @return the parsed result as a HTML compatible form
736+
*/
737+
static Maybe<StyleAbsoluteColor> MaybeComputeColor(Document* aDocument,
738+
const nsAString& aValue) {
739+
// A few steps are ignored given we don't support alpha and colorspace. See
740+
// bug 1919718.
741+
return ServoCSSParser::ComputeColorWellControlColor(
742+
aDocument->EnsureStyleSet().RawData(), NS_ConvertUTF16toUTF8(aValue),
743+
StyleColorSpace::Srgb);
744+
}
745+
746+
/**
747+
* https://html.spec.whatwg.org/#serialize-a-color-well-control-color
748+
* https://drafts.csswg.org/css-color/#color-serialization-html-compatible-serialization-is-requested
749+
*
750+
* @param aColor The parsed color
751+
* @param aResult The result in the form of #ffffff.
752+
*/
753+
static void SerializeColorForHTMLCompatibility(const StyleAbsoluteColor& aColor,
754+
nsAString& aResult) {
755+
// Raw StyleAbsoluteColor can have floats outside of 0-1 range e.g. when
756+
// display-p3 color is converted to srgb, and ToColor guarantees to fit the
757+
// values within the range.
758+
nscolor color = aColor.ToColor();
759+
aResult.Truncate();
760+
aResult.AppendPrintf("#%02x%02x%02x", NS_GET_R(color), NS_GET_G(color),
761+
NS_GET_B(color));
762+
}
763+
727764
nsTArray<nsString> HTMLInputElement::GetColorsFromList() {
728765
RefPtr<HTMLDataListElement> dataList = GetList();
729766
if (!dataList) {
@@ -740,10 +777,15 @@ nsTArray<nsString> HTMLInputElement::GetColorsFromList() {
740777
continue;
741778
}
742779

743-
nsString value;
780+
nsAutoString value;
744781
option->GetValue(value);
745-
if (IsValidSimpleColor(value)) {
746-
ToLowerCase(value);
782+
// https://html.spec.whatwg.org/#update-a-color-well-control-color
783+
// https://html.spec.whatwg.org/#serialize-a-color-well-control-color
784+
if (Maybe<StyleAbsoluteColor> result =
785+
MaybeComputeColor(OwnerDoc(), value)) {
786+
// Serialization step 6: If htmlCompatible is true, then do so with
787+
// HTML-compatible serialization requested.
788+
SerializeColorForHTMLCompatibility(*result, value);
747789
colors.AppendElement(value);
748790
}
749791
}
@@ -4981,13 +5023,15 @@ void HTMLInputElement::SanitizeValue(nsAString& aValue,
49815023
}
49825024
} break;
49835025
case FormControlType::InputColor: {
4984-
if (IsValidSimpleColor(aValue)) {
4985-
ToLowerCase(aValue);
4986-
} else {
4987-
// Set default (black) color, if aValue wasn't parsed correctly.
4988-
aValue.AssignLiteral("#000000");
4989-
}
4990-
} break;
5026+
// https://html.spec.whatwg.org/#update-a-color-well-control-color
5027+
// https://html.spec.whatwg.org/#serialize-a-color-well-control-color
5028+
StyleAbsoluteColor color = MaybeComputeColor(OwnerDoc(), aValue)
5029+
.valueOr(StyleAbsoluteColor::BLACK);
5030+
// Serialization step 6: If htmlCompatible is true, then do so with
5031+
// HTML-compatible serialization requested.
5032+
SerializeColorForHTMLCompatibility(color, aValue);
5033+
break;
5034+
}
49915035
default:
49925036
break;
49935037
}
@@ -5009,20 +5053,6 @@ Maybe<nscolor> HTMLInputElement::ParseSimpleColor(const nsAString& aColor) {
50095053
return Some(color);
50105054
}
50115055

5012-
bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const {
5013-
if (aValue.Length() != 7 || aValue.First() != '#') {
5014-
return false;
5015-
}
5016-
5017-
for (int i = 1; i < 7; ++i) {
5018-
if (!IsAsciiDigit(aValue[i]) && !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
5019-
!(aValue[i] >= 'A' && aValue[i] <= 'F')) {
5020-
return false;
5021-
}
5022-
}
5023-
return true;
5024-
}
5025-
50265056
bool HTMLInputElement::IsLeapYear(uint32_t aYear) const {
50275057
if ((aYear % 4 == 0 && aYear % 100 != 0) || (aYear % 400 == 0)) {
50285058
return true;

dom/html/HTMLInputElement.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,14 +1127,6 @@ class HTMLInputElement final : public TextControlElement,
11271127
*/
11281128
RadioGroupContainer* FindTreeRadioGroupContainer() const;
11291129

1130-
/**
1131-
* Parse a color string of the form #XXXXXX where X should be hexa characters
1132-
* @param the string to be parsed.
1133-
* @return whether the string is a valid simple color.
1134-
* Note : this function does not consider the empty string as valid.
1135-
*/
1136-
bool IsValidSimpleColor(const nsAString& aValue) const;
1137-
11381130
/**
11391131
* Parse a week string of the form yyyy-Www
11401132
* @param the string to be parsed.

dom/html/test/forms/test_input_color_picker_datalist.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
MockColorPicker.init(window);
1515

1616
MockColorPicker.showCallback = (picker) => {
17-
is(picker.defaultColors.length, 2);
17+
is(picker.defaultColors.length, 3);
1818
is(picker.defaultColors[0], "#112233");
19-
is(picker.defaultColors[1], "#00ffaa");
19+
is(picker.defaultColors[1], "#000000");
20+
is(picker.defaultColors[2], "#00ffaa");
2021

2122
MockColorPicker.cleanup();
2223
SimpleTest.finish();
@@ -33,7 +34,7 @@
3334
<input type="color" list="color-list">
3435
<datalist id="color-list">
3536
<option value="#112233"></option>
36-
<option value="black"></option> <!-- invalid -->
37+
<option value="black"></option>
3738
<option value="#000000" disabled></option>
3839
<option value="#00FFAA"></option>
3940
<option></option>

dom/html/test/forms/test_input_sanitization.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* other actions here so that this test only tests the value sanitization
3030
* algorithm for the various input types.
3131
*
32-
* XXXjwatt splitting out testing of the value sanitization algorithm and
32+
* XXXjwatt splitting out testing of the value sanitization algorithm and
3333
* "other things" that affect .value makes it harder to know what we're testing
3434
* and what we've missed, because what's included in the value sanitization
3535
* algorithm and what's not is different from input type to input type. It
@@ -280,6 +280,10 @@
280280
case "datetime-local":
281281
return sanitizeDateTimeLocal(aValue);
282282
case "color":
283+
switch (aValue) {
284+
case "red": return "#ff0000";
285+
case "#0f0": return "#00ff00";
286+
}
283287
return /^#[0-9A-Fa-f]{6}$/.exec(aValue) ? aValue.toLowerCase() : "#000000";
284288
default:
285289
return aValue;

layout/inspector/InspectorUtils.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ void InspectorUtils::RgbToColorName(GlobalObject&, uint8_t aR, uint8_t aG,
749749
void InspectorUtils::ColorToRGBA(GlobalObject& aGlobal,
750750
const nsACString& aColorString,
751751
Nullable<InspectorRGBATuple>& aResult) {
752-
auto* styleSet = [&]() -> ServoStyleSet* {
752+
const auto* styleData = [&]() -> const StylePerDocumentStyleData* {
753753
nsCOMPtr<nsIGlobalObject> global =
754754
do_QueryInterface(aGlobal.GetAsSupports());
755755
if (!global) {
@@ -767,11 +767,11 @@ void InspectorUtils::ColorToRGBA(GlobalObject& aGlobal,
767767
if (!ps) {
768768
return nullptr;
769769
}
770-
return ps->StyleSet();
770+
return ps->StyleSet()->RawData();
771771
}();
772772

773773
nscolor color = NS_RGB(0, 0, 0);
774-
if (!ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0), aColorString,
774+
if (!ServoCSSParser::ComputeColor(styleData, NS_RGB(0, 0, 0), aColorString,
775775
&color)) {
776776
aResult.SetNull();
777777
return;

layout/style/ServoCSSParser.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,27 @@ bool ServoCSSParser::IsValidCSSColor(const nsACString& aValue) {
2222
}
2323

2424
/* static */
25-
bool ServoCSSParser::ComputeColor(ServoStyleSet* aStyleSet,
25+
bool ServoCSSParser::ComputeColor(const StylePerDocumentStyleData* aStyleData,
2626
nscolor aCurrentColor,
2727
const nsACString& aValue,
2828
nscolor* aResultColor, bool* aWasCurrentColor,
2929
css::Loader* aLoader) {
30-
return Servo_ComputeColor(aStyleSet ? aStyleSet->RawData() : nullptr,
31-
aCurrentColor, &aValue, aResultColor,
30+
return Servo_ComputeColor(aStyleData, aCurrentColor, &aValue, aResultColor,
3231
aWasCurrentColor, aLoader);
3332
}
3433

34+
/* static */
35+
Maybe<StyleAbsoluteColor> ServoCSSParser::ComputeColorWellControlColor(
36+
const StylePerDocumentStyleData* aStyleData, const nsACString& aValue,
37+
StyleColorSpace aToColorSpace) {
38+
StyleAbsoluteColor color{};
39+
if (Servo_ComputeColorWellControlColor(aStyleData, &aValue, aToColorSpace,
40+
&color)) {
41+
return Some(color);
42+
}
43+
return Nothing();
44+
}
45+
3546
/* static */
3647
bool ServoCSSParser::ColorTo(const nsACString& aFromColor,
3748
const nsACString& aToColorSpace,

layout/style/ServoCSSParser.h

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ namespace mozilla {
2525
struct AnimatedPropertyID;
2626
class ServoStyleSet;
2727
struct URLExtraData;
28+
struct StyleAbsoluteColor;
2829
struct StyleFontFamilyList;
2930
struct StyleFontStretch;
3031
struct StyleFontWeight;
3132
struct StyleFontStyle;
3233
struct StyleLockedDeclarationBlock;
3334
struct StyleParsingMode;
35+
struct StylePerDocumentStyleData;
3436
union StyleComputedFontStyleDescriptor;
37+
enum class StyleColorSpace : uint8_t;
3538

3639
template <typename Integer, typename Number, typename LinearStops>
3740
struct StyleTimingFunction;
@@ -62,8 +65,8 @@ class ServoCSSParser {
6265
/**
6366
* Computes an nscolor from the given CSS <color> value.
6467
*
65-
* @param aStyleSet The style set whose nsPresContext will be used to
66-
* compute system colors and other special color values.
68+
* @param aStyleData The style data to compute system colors and other special
69+
* color values.
6770
* @param aCurrentColor The color value that currentcolor should compute to.
6871
* @param aValue The CSS <color> value.
6972
* @param aResultColor The resulting computed color value.
@@ -74,11 +77,28 @@ class ServoCSSParser {
7477
* won't be reported to the console.
7578
* @return Whether aValue was successfully parsed and aResultColor was set.
7679
*/
77-
static bool ComputeColor(ServoStyleSet* aStyleSet, nscolor aCurrentColor,
78-
const nsACString& aValue, nscolor* aResultColor,
80+
static bool ComputeColor(const StylePerDocumentStyleData* aStyleData,
81+
nscolor aCurrentColor, const nsACString& aValue,
82+
nscolor* aResultColor,
7983
bool* aWasCurrentColor = nullptr,
8084
css::Loader* aLoader = nullptr);
8185

86+
/**
87+
* Computes a StyleAbsoluteColor from the given CSS <color> value, following
88+
* the HTML spec:
89+
* https://html.spec.whatwg.org/#update-a-color-well-control-color
90+
*
91+
* @param aStyleData The style data to compute system colors and other special
92+
* color values.
93+
* @param aValue The CSS <color> value.
94+
* @param aToColorSpace The color space to convert the color into.
95+
* @return The resulting computed color value. For invalid color value,
96+
* Nothing() will be returned.
97+
*/
98+
static Maybe<StyleAbsoluteColor> ComputeColorWellControlColor(
99+
const StylePerDocumentStyleData* aStyleData, const nsACString& aValue,
100+
StyleColorSpace aToColorSpace);
101+
82102
/**
83103
* Takes a CSS <color> and convert it to another color space.
84104
*

servo/ports/geckolib/glue.rs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8371,15 +8371,17 @@ pub unsafe extern "C" fn Servo_IsValidCSSColor(value: &nsACString) -> bool {
83718371
specified::Color::is_valid(&context, &mut input)
83728372
}
83738373

8374-
#[no_mangle]
8375-
pub unsafe extern "C" fn Servo_ComputeColor(
8374+
struct ComputeColorResult {
8375+
result_color: AbsoluteColor,
8376+
was_current_color: bool,
8377+
}
8378+
8379+
unsafe fn compute_color(
83768380
raw_data: Option<&PerDocumentStyleData>,
8377-
current_color: structs::nscolor,
8381+
current_color: &AbsoluteColor,
83788382
value: &nsACString,
8379-
result_color: &mut structs::nscolor,
8380-
was_current_color: *mut bool,
83818383
loader: *mut Loader,
8382-
) -> bool {
8384+
) -> Option<ComputeColorResult> {
83838385
let mut input = ParserInput::new(value.as_str_unchecked());
83848386
let mut input = Parser::new(&mut input);
83858387
let reporter = loader.as_mut().and_then(|loader| {
@@ -8407,21 +8409,55 @@ pub unsafe extern "C" fn Servo_ComputeColor(
84078409
None => None,
84088410
};
84098411

8410-
let computed = match specified::Color::parse_and_compute(&context, &mut input, device) {
8411-
Some(c) => c,
8412-
None => return false,
8413-
};
8412+
let computed = specified::Color::parse_and_compute(&context, &mut input, device)?;
84148413

8414+
let result_color = computed.resolve_to_absolute(current_color);
8415+
let was_current_color = computed.is_currentcolor();
8416+
8417+
Some(ComputeColorResult {
8418+
result_color,
8419+
was_current_color,
8420+
})
8421+
}
8422+
8423+
#[no_mangle]
8424+
pub unsafe extern "C" fn Servo_ComputeColor(
8425+
raw_data: Option<&PerDocumentStyleData>,
8426+
current_color: structs::nscolor,
8427+
value: &nsACString,
8428+
result_color: &mut structs::nscolor,
8429+
was_current_color: *mut bool,
8430+
loader: *mut Loader,
8431+
) -> bool {
84158432
let current_color = style::gecko::values::convert_nscolor_to_absolute_color(current_color);
8433+
let Some(result) = compute_color(raw_data, &current_color, value, loader) else {
8434+
return false;
8435+
};
8436+
8437+
*result_color = style::gecko::values::convert_absolute_color_to_nscolor(&result.result_color);
84168438
if !was_current_color.is_null() {
8417-
*was_current_color = computed.is_currentcolor();
8439+
*was_current_color = result.was_current_color
84188440
}
8419-
8420-
let rgba = computed.resolve_to_absolute(&current_color);
8421-
*result_color = style::gecko::values::convert_absolute_color_to_nscolor(&rgba);
84228441
true
84238442
}
84248443

8444+
// This implements https://html.spec.whatwg.org/#update-a-color-well-control-color,
8445+
// except the actual serialization steps in step 6 of "serialize a color well control color".
8446+
#[no_mangle]
8447+
pub unsafe extern "C" fn Servo_ComputeColorWellControlColor(
8448+
raw_data: Option<&PerDocumentStyleData>,
8449+
value: &nsACString,
8450+
to_color_space: ColorSpace,
8451+
result_color: &mut AbsoluteColor,
8452+
) -> bool {
8453+
if let Some(color) = compute_color(raw_data, &AbsoluteColor::BLACK, value, ptr::null_mut()) {
8454+
*result_color = color.result_color.to_color_space(to_color_space);
8455+
true
8456+
} else {
8457+
false
8458+
}
8459+
}
8460+
84258461
#[no_mangle]
84268462
pub unsafe extern "C" fn Servo_ColorTo(
84278463
from_color: &nsACString,

0 commit comments

Comments
 (0)