Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibWeb: Make :open target color and file inputs with an open picker #3419

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions Libraries/LibWeb/CSS/SelectorEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,20 +417,24 @@ static bool matches_read_write_pseudo_class(DOM::Element const& element)
return element.is_editable_or_editing_host();
}

// https://www.w3.org/TR/selectors-4/#open-state
// https://drafts.csswg.org/selectors-4/#open-state
static bool matches_open_state_pseudo_class(DOM::Element const& element, bool open)
{
// The :open pseudo-class represents an element that has both “open” and “closed” states,
// and which is currently in the “open” state.
// The :closed pseudo-class represents an element that has both “open” and “closed” states,
// and which is currently in the closed state.

// NOTE: Spec specifically suggests supporting <details>, <dialog>, and <select>.
// There may be others we want to treat as open or closed.
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-open
// The :open pseudo-class must match any element falling into one of the following categories:
// - details elements that have an open attribute
// - dialog elements that have an open attribute
if (is<HTML::HTMLDetailsElement>(element) || is<HTML::HTMLDialogElement>(element))
return open == element.has_attribute(HTML::AttributeNames::open);
if (is<HTML::HTMLSelectElement>(element))
return open == static_cast<HTML::HTMLSelectElement const&>(element).is_open();
// - select elements that are a drop-down box and whose drop-down boxes are open
if (auto const* select = as_if<HTML::HTMLSelectElement>(element))
return open == select->is_open();
// - input elements that support a picker and whose pickers are open
if (auto const* input = as_if<HTML::HTMLInputElement>(element))
return open == (input->supports_a_picker() && input->is_open());

return false;
}
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibWeb/DOM/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ enum class ShouldComputeRole {
X(HTMLHyperlinkElementHrefChange) \
X(HTMLIFrameElementGeometryChange) \
X(HTMLInputElementSetChecked) \
X(HTMLInputElementSetIsOpen) \
X(HTMLObjectElementUpdateLayoutAndChildObjects) \
X(HTMLOptionElementSelectedChange) \
X(HTMLSelectElementSetIsOpen) \
Expand Down
32 changes: 30 additions & 2 deletions Libraries/LibWeb/HTML/HTMLInputElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,11 @@ static void show_the_picker_if_applicable(HTMLInputElement& element)
// 3. Consume user activation given element's relevant global object.
relevant_global_object.consume_user_activation();

// 4. If element's type attribute is in the File Upload state, then run these steps in parallel:
// 4. If element does not support a picker, then return.
if (!element.supports_a_picker())
return;

// 5. If element's type attribute is in the File Upload state, then run these steps in parallel:
if (element.type_state() == HTMLInputElement::TypeAttributeState::FileUpload) {
// NOTE: These steps cannot be fully implemented here, and must be done in the PageClient when the response comes back from the PageHost

Expand All @@ -312,18 +316,20 @@ static void show_the_picker_if_applicable(HTMLInputElement& element)
auto allow_multiple_files = element.has_attribute(HTML::AttributeNames::multiple) ? AllowMultipleFiles::Yes : AllowMultipleFiles::No;
auto weak_element = element.make_weak_ptr<HTMLInputElement>();

element.set_is_open(true);
element.document().browsing_context()->top_level_browsing_context()->page().did_request_file_picker(weak_element, move(accepted_file_types), allow_multiple_files);
return;
}

// 5. Otherwise, the user agent should show any relevant user interface for selecting a value for element,
// 6. Otherwise, the user agent should show any relevant user interface for selecting a value for element,
// in the way it normally would when the user interacts with the control. (If no such UI applies to element, then this step does nothing.)
// If such a user interface is shown, it must respect the requirements stated in the relevant parts of the specification for how element
// behaves given its type attribute state. (For example, various sections describe restrictions on the resulting value string.)
// This step can have side effects, such as closing other pickers that were previously shown by this algorithm.
// (If this closes a file selection picker, then per the above that will lead to firing either input and change events, or a cancel event.)
if (element.type_state() == HTMLInputElement::TypeAttributeState::Color) {
auto weak_element = element.make_weak_ptr<HTMLInputElement>();
element.set_is_open(true);
element.document().browsing_context()->top_level_browsing_context()->page().did_request_color_picker(weak_element, Color::from_string(element.value()).value_or(Color(0, 0, 0)));
}
}
Expand Down Expand Up @@ -453,6 +459,8 @@ void HTMLInputElement::did_edit_text_node()

void HTMLInputElement::did_pick_color(Optional<Color> picked_color, ColorPickerUpdateState state)
{
set_is_open(false);

if (type_state() == TypeAttributeState::Color && picked_color.has_value()) {
// then when the user changes the element's value
m_value = value_sanitization_algorithm(picked_color.value().to_string_without_alpha());
Expand Down Expand Up @@ -481,6 +489,8 @@ void HTMLInputElement::did_pick_color(Optional<Color> picked_color, ColorPickerU

void HTMLInputElement::did_select_files(Span<SelectedFile> selected_files, MultipleHandling multiple_handling)
{
set_is_open(false);

// https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
// 4. If the user dismissed the prompt without changing their selection, then queue an element task on the user
// interaction task source given element to fire an event named cancel at element, with the bubbles attribute
Expand Down Expand Up @@ -2933,4 +2943,22 @@ bool HTMLInputElement::suffering_from_bad_input() const
return false;
}

// https://html.spec.whatwg.org/multipage/input.html#input-support-picker
bool HTMLInputElement::supports_a_picker() const
{
// The input element can support a picker. A picker is a user interface element that allows the end user to choose a value.
// Whether an input element supports a picker depends on the type attribute state and implementation-defined behavior.
// An input element must support a picker when its type attribute is in the File Upload state.
return first_is_one_of(type_state(), TypeAttributeState::FileUpload, TypeAttributeState::Color);
}

void HTMLInputElement::set_is_open(bool is_open)
{
if (is_open == m_is_open)
return;

m_is_open = is_open;
invalidate_style(DOM::StyleInvalidationReason::HTMLInputElementSetIsOpen);
}

}
6 changes: 6 additions & 0 deletions Libraries/LibWeb/HTML/HTMLInputElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ class HTMLInputElement final
bool selection_direction_applies() const;
bool has_selectable_text() const;

bool supports_a_picker() const;
bool is_open() const { return m_is_open; }
void set_is_open(bool);

static bool selection_or_range_applies_for_type_state(TypeAttributeState);

Optional<String> selection_direction_binding() { return selection_direction(); }
Expand Down Expand Up @@ -368,6 +372,8 @@ class HTMLInputElement final
String m_last_src_value;

bool m_has_uncommitted_changes { false };

bool m_is_open { false };
};

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Harness status: OK

Found 3 tests

2 Pass
1 Fail
Pass The dialog element should support :open.
Pass The details element should support :open.
Fail The select element should support :open.
55 changes: 55 additions & 0 deletions Tests/LibWeb/Text/input/wpt-import/css/selectors/open-pseudo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<link rel=author href="mailto:[email protected]">
<link rel=help href="https://drafts.csswg.org/selectors-4/#open-state">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/testdriver.js"></script>
<script src="../../resources/testdriver-vendor.js"></script>

<dialog>dialog</dialog>

<details>details</details>

<select>
<option>one</option>
</select>

<script>
test(() => {
const dialog = document.querySelector('dialog');
assert_false(dialog.matches(':open'),
':open should not match when the dialog is closed.');

dialog.show();
assert_true(dialog.matches(':open'),
':open should match after dialog.open().');

dialog.close();
dialog.showModal();
assert_true(dialog.matches(':open'),
':open should match after dialog.showModal().');

dialog.close();
}, 'The dialog element should support :open.');

test(() => {
const details = document.querySelector('details');
assert_false(details.matches(':open'),
':open should not match when the details is closed.');

details.open = true;
assert_true(details.matches(':open'),
':open should match when the details is open.');
}, 'The details element should support :open.');

promise_test(async () => {
const select = document.querySelector('select');
assert_false(select.matches(':open'),
':open should not match when the select is closed.');

await test_driver.click(select);
await new Promise(requestAnimationFrame);
assert_true(select.matches(':open'),
':open should match when the select is open.');
}, 'The select element should support :open.');
</script>
Loading