diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-16 11:45:35 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-17 08:59:23 +0000 |
commit | 552906b0f222c5d5dd11b9fd73829d510980461a (patch) | |
tree | 3a11e6ed0538a81dd83b20cf3a4783e297f26d91 /chromium/third_party/blink/renderer/core/html/forms | |
parent | 1b05827804eaf047779b597718c03e7d38344261 (diff) | |
download | qtwebengine-chromium-552906b0f222c5d5dd11b9fd73829d510980461a.tar.gz |
BASELINE: Update Chromium to 83.0.4103.122
Change-Id: Ie3a82f5bb0076eec2a7c6a6162326b4301ee291e
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/html/forms')
145 files changed, 4872 insertions, 2494 deletions
diff --git a/chromium/third_party/blink/renderer/core/html/forms/DEPS b/chromium/third_party/blink/renderer/core/html/forms/DEPS index 4a90b3a5a0b..95b04a4250f 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/DEPS +++ b/chromium/third_party/blink/renderer/core/html/forms/DEPS @@ -1,5 +1,5 @@ specific_include_rules = { "file_input_type_test.cc": [ "+base/run_loop.h", - ] -}
\ No newline at end of file + ], +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc index c4525556389..6fc142b3b06 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc @@ -74,6 +74,10 @@ bool BaseButtonInputType::ShouldSaveAndRestoreFormControlState() const { void BaseButtonInputType::AppendToFormData(FormData&) const {} +bool BaseButtonInputType::TypeShouldForceLegacyLayout() const { + return true; +} + LayoutObject* BaseButtonInputType::CreateLayoutObject(const ComputedStyle&, LegacyLayout) const { return new LayoutButton(&GetElement()); diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h index e0310279d72..b048c471560 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h @@ -54,6 +54,7 @@ class BaseButtonInputType : public InputType, InputTypeView* CreateView() override; bool ShouldSaveAndRestoreFormControlState() const override; void AppendToFormData(FormData&) const override; + bool TypeShouldForceLegacyLayout() const override; LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) const override; ValueMode GetValueMode() const override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc index 4e007f8fc74..ad6d6a6e29a 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc @@ -104,6 +104,16 @@ String BaseTemporalInputType::RangeUnderflowText(const Decimal& minimum) const { LocalizeValue(Serialize(minimum))); } +String BaseTemporalInputType::RangeInvalidText(const Decimal& minimum, + const Decimal& maximum) const { + DCHECK(minimum > maximum) + << "RangeInvalidText should only be called with minimum>maximum"; + + return GetLocale().QueryString(IDS_FORM_VALIDATION_RANGE_INVALID_DATETIME, + LocalizeValue(Serialize(minimum)), + LocalizeValue(Serialize(maximum))); +} + Decimal BaseTemporalInputType::DefaultValueForStepUp() const { return Decimal::FromDouble( ConvertToLocalTime(base::Time::Now()).InMillisecondsF()); @@ -190,7 +200,11 @@ bool BaseTemporalInputType::ShouldRespectListAttribute() { } bool BaseTemporalInputType::ValueMissing(const String& value) const { - return GetElement().IsRequired() && value.IsEmpty(); + // For text-mode input elements (including dates), the value is missing only + // if it is mutable. + // https://html.spec.whatwg.org/multipage/input.html#the-required-attribute + return GetElement().IsRequired() && value.IsEmpty() && + !GetElement().IsDisabledOrReadOnly(); } bool BaseTemporalInputType::MayTriggerVirtualKeyboard() const { diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h index 13359fabe8b..b1e6cafa63e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h @@ -69,6 +69,7 @@ class BaseTemporalInputType : public InputType { bool has_hour, bool has_minute, bool has_second) const = 0; + virtual String AriaRoleForPickerIndicator() const = 0; protected: BaseTemporalInputType(HTMLInputElement& element) : InputType(element) {} @@ -96,6 +97,8 @@ class BaseTemporalInputType : public InputType { bool ValueMissing(const String&) const override; String RangeOverflowText(const Decimal& maximum) const override; String RangeUnderflowText(const Decimal& minimum) const override; + String RangeInvalidText(const Decimal& minimum, + const Decimal& maximum) const override; Decimal DefaultValueForStepUp() const override; bool IsSteppable() const override; virtual String SerializeWithDate(const base::Optional<base::Time>&) const; diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc index 8fdc4798ee3..0f838dc1992 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc @@ -28,6 +28,7 @@ #include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/platform/heap/heap.h" namespace blink { @@ -88,11 +89,12 @@ bool BaseTextInputType::PatternMismatch(const String& value) const { ScriptRegexp::UTF16)); if (!raw_regexp->IsValid()) { GetElement().GetDocument().AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kRendering, - mojom::ConsoleMessageLevel::kError, - "Pattern attribute value " + raw_pattern + - " is not a valid regular expression: " + - raw_regexp->ExceptionMessage())); + MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kRendering, + mojom::ConsoleMessageLevel::kError, + "Pattern attribute value " + raw_pattern + + " is not a valid regular expression: " + + raw_regexp->ExceptionMessage())); regexp_.reset(raw_regexp.release()); pattern_for_regexp_ = raw_pattern; return false; diff --git a/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h index 1afa8214130..bcedfe36f09 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h @@ -56,12 +56,6 @@ class ClearButtonElement final : public HTMLDivElement { Member<ClearButtonOwner> clear_button_owner_; }; -DEFINE_TYPE_CASTS(ClearButtonElement, - Element, - element, - element->IsClearButtonElement(), - element.IsClearButtonElement()); - template <> struct DowncastTraits<ClearButtonElement> { static bool AllowFrom(const Element& element) { diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h index 87bdbc0d9a5..051e20a4bdd 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h @@ -38,13 +38,17 @@ namespace blink { class AXObject; class Color; +// This interface respresents a UI to choose a color. class CORE_EXPORT ColorChooser : public GarbageCollectedMixin { public: ColorChooser(); virtual ~ColorChooser(); void Trace(Visitor* visitor) override {} + // Call to update the selection in the UI. Used to reflect value changes made + // by JS. virtual void SetSelectedColor(const Color&) {} + // Call to close the UI. virtual void EndChooser() {} // Returns a root AXObject in the ColorChooser if it's available. virtual AXObject* RootAXObject() = 0; diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h index 828b2cfba30..292e216ff7a 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h @@ -42,12 +42,15 @@ namespace blink { class Element; +// This class is the client for the ColorChooser. class CORE_EXPORT ColorChooserClient : public GarbageCollectedMixin { public: virtual ~ColorChooserClient(); void Trace(Visitor* visitor) override {} + // Called when a color is chosen by the user in the ColorChooser UI. virtual void DidChooseColor(const Color&) = 0; + // Called when ColorChooser UI was closed by the user. virtual void DidEndChooser() = 0; virtual Element& OwnerElement() const = 0; virtual IntRect ElementRectRelativeToViewport() const = 0; diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc index 4d48b286df1..f61b77e16fe 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc @@ -25,6 +25,8 @@ #include "third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h" +#include "build/build_config.h" +#include "third_party/blink/public/common/browser_interface_broker_proxy.h" #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/core/frame/local_frame.h" @@ -32,8 +34,10 @@ #include "third_party/blink/renderer/core/html/forms/chooser_resource_loader.h" #include "third_party/blink/renderer/core/html/forms/color_chooser_client.h" #include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/color_page_popup_controller.h" #include "third_party/blink/renderer/core/page/page_popup.h" #include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -53,12 +57,8 @@ ColorChooserPopupUIController::ColorChooserPopupUIController( popup_(nullptr), locale_(Locale::DefaultLocale()) {} -ColorChooserPopupUIController::~ColorChooserPopupUIController() = default; - -void ColorChooserPopupUIController::Dispose() { - // Finalized earlier so as to access chrome_client_ while alive. - CancelPopup(); - // ~ColorChooserUIController calls EndChooser(). +ColorChooserPopupUIController::~ColorChooserPopupUIController() { + DCHECK(!popup_); } void ColorChooserPopupUIController::Trace(Visitor* visitor) { @@ -68,7 +68,7 @@ void ColorChooserPopupUIController::Trace(Visitor* visitor) { void ColorChooserPopupUIController::OpenUI() { if (client_->ShouldShowSuggestions() || - RuntimeEnabledFeatures::FormControlsRefreshEnabled()) + features::IsFormControlsRefreshEnabled()) OpenPopup(); else OpenColorChooser(); @@ -93,7 +93,7 @@ void ColorChooserPopupUIController::WriteDocument(SharedBuffer* data) { void ColorChooserPopupUIController::WriteColorPickerDocument( SharedBuffer* data) { - DCHECK(RuntimeEnabledFeatures::FormControlsRefreshEnabled()); + DCHECK(features::IsFormControlsRefreshEnabled()); IntRect anchor_rect_in_screen = chrome_client_->ViewportToScreen( client_->ElementRectRelativeToViewport(), frame_->View()); @@ -102,7 +102,6 @@ void ColorChooserPopupUIController::WriteColorPickerDocument( "<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data); data->Append(ChooserResourceLoader::GetPickerCommonStyleSheet()); data->Append(ChooserResourceLoader::GetColorPickerStyleSheet()); - PagePopupClient::AddString( "</style></head><body>\n" "<div id='main'>Loading...</div><script>\n" @@ -113,6 +112,11 @@ void ColorChooserPopupUIController::WriteColorPickerDocument( AddProperty("anchorRectInScreen", anchor_rect_in_screen, data); AddProperty("zoomFactor", ScaledZoomFactor(), data); AddProperty("shouldShowColorSuggestionPicker", false, data); + AddProperty("isEyeDropperEnabled", features::IsEyeDropperEnabled(), data); +#if defined(OS_MACOSX) + AddProperty("isBorderTransparent", features::IsFormControlsRefreshEnabled(), + data); +#endif PagePopupClient::AddString("};\n", data); data->Append(ChooserResourceLoader::GetPickerCommonJS()); data->Append(ChooserResourceLoader::GetColorPickerJS()); @@ -134,9 +138,8 @@ void ColorChooserPopupUIController::WriteColorSuggestionPickerDocument( "<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data); data->Append(ChooserResourceLoader::GetPickerCommonStyleSheet()); data->Append(ChooserResourceLoader::GetColorSuggestionPickerStyleSheet()); - if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) + if (features::IsFormControlsRefreshEnabled()) data->Append(ChooserResourceLoader::GetColorPickerStyleSheet()); - PagePopupClient::AddString( "</style></head><body>\n" "<div id='main'>Loading...</div><script>\n" @@ -146,7 +149,7 @@ void ColorChooserPopupUIController::WriteColorSuggestionPickerDocument( PagePopupClient::AddProperty( "otherColorLabel", GetLocale().QueryString(IDS_FORM_OTHER_COLOR_LABEL), data); - if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) { + if (features::IsFormControlsRefreshEnabled()) { PagePopupClient::AddProperty("selectedColor", client_->CurrentColor().Serialized(), data); } @@ -154,11 +157,16 @@ void ColorChooserPopupUIController::WriteColorSuggestionPickerDocument( AddProperty("zoomFactor", ScaledZoomFactor(), data); AddProperty("shouldShowColorSuggestionPicker", true, data); AddProperty("isFormControlsRefreshEnabled", - RuntimeEnabledFeatures::FormControlsRefreshEnabled(), data); + features::IsFormControlsRefreshEnabled(), data); + AddProperty("isEyeDropperEnabled", features::IsEyeDropperEnabled(), data); +#if defined(OS_MACOSX) + AddProperty("isBorderTransparent", features::IsFormControlsRefreshEnabled(), + data); +#endif PagePopupClient::AddString("};\n", data); data->Append(ChooserResourceLoader::GetPickerCommonJS()); data->Append(ChooserResourceLoader::GetColorSuggestionPickerJS()); - if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) + if (features::IsFormControlsRefreshEnabled()) data->Append(ChooserResourceLoader::GetColorPickerJS()); data->Append(ChooserResourceLoader::GetColorPickerCommonJS()); PagePopupClient::AddString("</script></body>\n", data); @@ -176,7 +184,7 @@ void ColorChooserPopupUIController::SetValueAndClosePopup( if (num_value == kColorPickerPopupActionSetValue) SetValue(string_value); if (num_value == kColorPickerPopupActionChooseOtherColor) { - DCHECK(!RuntimeEnabledFeatures::FormControlsRefreshEnabled()); + DCHECK(!features::IsFormControlsRefreshEnabled()); OpenColorChooser(); } CancelPopup(); @@ -216,4 +224,24 @@ void ColorChooserPopupUIController::CancelPopup() { chrome_client_->ClosePagePopup(popup_); } +PagePopupController* ColorChooserPopupUIController::CreatePagePopupController( + PagePopup& popup) { + return MakeGarbageCollected<ColorPagePopupController>(popup, this); +} + +void ColorChooserPopupUIController::EyeDropperResponseHandler(bool success, + uint32_t color) { + // TODO(crbug.com/992297): update the color popup with the chosen value. +} + +void ColorChooserPopupUIController::OpenEyeDropper() { + frame_->GetBrowserInterfaceBroker().GetInterface( + eye_dropper_chooser_.BindNewPipeAndPassReceiver()); + eye_dropper_chooser_.set_disconnect_handler(WTF::Bind( + &ColorChooserPopupUIController::EndChooser, WrapWeakPersistent(this))); + eye_dropper_chooser_->Choose( + WTF::Bind(&ColorChooserPopupUIController::EyeDropperResponseHandler, + WrapWeakPersistent(this))); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h index 736b8697680..fd20189266e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h @@ -35,12 +35,11 @@ namespace blink { class ChromeClient; class ColorChooserClient; class PagePopup; +class PagePopupController; class CORE_EXPORT ColorChooserPopupUIController final : public ColorChooserUIController, public PagePopupClient { - USING_PRE_FINALIZER(ColorChooserPopupUIController, Dispose); - public: ColorChooserPopupUIController(LocalFrame*, ChromeClient*, @@ -57,19 +56,21 @@ class CORE_EXPORT ColorChooserPopupUIController final // PagePopupClient functions: void WriteDocument(SharedBuffer*) override; - void SelectFontsFromOwnerDocument(Document&) override {} Locale& GetLocale() override; void SetValueAndClosePopup(int, const String&) override; void SetValue(const String&) override; void CancelPopup() override; Element& OwnerElement() override; void DidClosePopup() override; + PagePopupController* CreatePagePopupController(PagePopup&) override; + + void OpenEyeDropper(); + void EyeDropperResponseHandler(bool success, uint32_t color); private: ChromeClient& GetChromeClient() override; void OpenPopup(); - void Dispose(); void WriteColorPickerDocument(SharedBuffer*); void WriteColorSuggestionPickerDocument(SharedBuffer*); @@ -77,6 +78,7 @@ class CORE_EXPORT ColorChooserPopupUIController final Member<ChromeClient> chrome_client_; PagePopup* popup_; Locale& locale_; + mojo::Remote<mojom::blink::EyeDropperChooser> eye_dropper_chooser_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc index bd31182d982..8bce77ac6c3 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc @@ -27,6 +27,7 @@ #include "third_party/blink/public/common/browser_interface_broker_proxy.h" #include "third_party/blink/public/web/web_local_frame_client.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" #include "third_party/blink/renderer/core/html/forms/color_chooser_client.h" #include "third_party/blink/renderer/platform/graphics/color.h" @@ -37,20 +38,19 @@ namespace blink { ColorChooserUIController::ColorChooserUIController( LocalFrame* frame, blink::ColorChooserClient* client) - : client_(client), frame_(frame) {} + : client_(client), + frame_(frame), + receiver_(this, frame->DomWindow()->GetExecutionContext()) {} -ColorChooserUIController::~ColorChooserUIController() {} +ColorChooserUIController::~ColorChooserUIController() = default; void ColorChooserUIController::Trace(Visitor* visitor) { + visitor->Trace(receiver_); visitor->Trace(frame_); visitor->Trace(client_); ColorChooser::Trace(visitor); } -void ColorChooserUIController::Dispose() { - receiver_.reset(); -} - void ColorChooserUIController::OpenUI() { OpenColorChooser(); } @@ -80,8 +80,10 @@ void ColorChooserUIController::OpenColorChooser() { color_chooser_factory_.BindNewPipeAndPassReceiver()); color_chooser_factory_->OpenColorChooser( chooser_.BindNewPipeAndPassReceiver(), - receiver_.BindNewPipeAndPassRemote(), client_->CurrentColor().Rgb(), - client_->Suggestions()); + receiver_.BindNewPipeAndPassRemote( + frame_->DomWindow()->GetExecutionContext()->GetTaskRunner( + TaskType::kUserInteraction)), + client_->CurrentColor().Rgb(), client_->Suggestions()); receiver_.set_disconnect_handler(WTF::Bind( &ColorChooserUIController::EndChooser, WrapWeakPersistent(this))); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h index 6d0012ac143..69168e082da 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h @@ -27,12 +27,13 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_UI_CONTROLLER_H_ #include <memory> -#include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "third_party/blink/public/mojom/choosers/color_chooser.mojom-blink.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/html/forms/color_chooser.h" #include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h" +#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" namespace blink { @@ -45,7 +46,6 @@ class CORE_EXPORT ColorChooserUIController public mojom::blink::ColorChooserClient, public ColorChooser { USING_GARBAGE_COLLECTED_MIXIN(ColorChooserUIController); - USING_PRE_FINALIZER(ColorChooserUIController, Dispose); public: ColorChooserUIController(LocalFrame*, blink::ColorChooserClient*); @@ -73,7 +73,9 @@ class CORE_EXPORT ColorChooserUIController private: mojo::Remote<mojom::blink::ColorChooserFactory> color_chooser_factory_; - mojo::Receiver<mojom::blink::ColorChooserClient> receiver_{this}; + HeapMojoReceiver<mojom::blink::ColorChooserClient, + HeapMojoWrapperMode::kWithoutContextObserver> + receiver_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc index 60a03ab4a07..75a2290469c 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc @@ -45,6 +45,7 @@ #include "third_party/blink/renderer/core/html/forms/html_option_element.h" #include "third_party/blink/renderer/core/html/html_div_element.h" #include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_theme.h" #include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" @@ -52,6 +53,7 @@ #include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -150,7 +152,7 @@ void ColorInputType::HandleDOMActivateEvent(Event& event) { return; ChromeClient* chrome_client = GetChromeClient(); - if (chrome_client && !chooser_) { + if (chrome_client && !HasOpenedPopup()) { UseCounter::Count( document, (event.UnderlyingEvent() && event.UnderlyingEvent()->isTrusted()) @@ -158,13 +160,23 @@ void ColorInputType::HandleDOMActivateEvent(Event& event) { : WebFeature::kColorInputTypeChooserByUntrustedClick); chooser_ = chrome_client->OpenColorChooser(document.GetFrame(), this, ValueAsColor()); + if (::features::IsFormControlsRefreshEnabled() && + GetElement().GetLayoutObject()) { + // Invalidate paint to ensure that the focus ring is removed. + GetElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + } } event.SetDefaultHandled(); } void ColorInputType::ClosePopupView() { - EndColorChooser(); + if (chooser_) + chooser_->EndChooser(); +} + +bool ColorInputType::HasOpenedPopup() const { + return chooser_; } bool ColorInputType::ShouldRespectListAttribute() { @@ -176,7 +188,7 @@ bool ColorInputType::TypeMismatchFor(const String& value) const { } void ColorInputType::WarnIfValueIsInvalid(const String& value) const { - if (!DeprecatedEqualIgnoringCase(value, GetElement().SanitizeValue(value))) + if (!EqualIgnoringASCIICase(value, GetElement().SanitizeValue(value))) AddWarningToConsole( "The specified value %s does not conform to the required format. The " "format is \"#rrggbb\" where rr, gg, bb are two-digit hexadecimal " @@ -203,11 +215,11 @@ void ColorInputType::DidEndChooser() { if (LayoutTheme::GetTheme().IsModalColorChooser()) GetElement().EnqueueChangeEvent(); chooser_.Clear(); -} - -void ColorInputType::EndColorChooser() { - if (chooser_) - chooser_->EndChooser(); + if (::features::IsFormControlsRefreshEnabled() && + GetElement().GetLayoutObject()) { + // Invalidate paint to ensure that the focus ring is shown. + GetElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + } } void ColorInputType::UpdateView() { @@ -275,4 +287,5 @@ ColorChooserClient* ColorInputType::GetColorChooserClient() { return this; } + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h index 19cfe68ddb0..2e75cb058aa 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h @@ -72,6 +72,7 @@ class ColorInputType final : public InputType, void DidSetValue(const String&, bool value_changed) override; void HandleDOMActivateEvent(Event&) override; void ClosePopupView() override; + bool HasOpenedPopup() const override; bool ShouldRespectListAttribute() override; bool TypeMismatchFor(const String&) const override; void WarnIfValueIsInvalid(const String&) const override; @@ -79,7 +80,6 @@ class ColorInputType final : public InputType, AXObject* PopupRootAXObject() override; Color ValueAsColor() const; - void EndColorChooser(); HTMLElement* ShadowColorSwatch() const; Member<ColorChooser> chooser_; diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc index 4eb86fe8704..b0008e357be 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc @@ -134,4 +134,8 @@ bool DateInputType::IsValidFormat(bool has_year, return has_year && has_month && has_day; } +String DateInputType::AriaRoleForPickerIndicator() const { + return GetLocale().QueryString(IDS_AX_CALENDAR_SHOW_DATE_PICKER); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h index 24bac539a94..e2758b1e561 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h @@ -60,6 +60,7 @@ class DateInputType final : public BaseTemporalInputType { bool has_hour, bool has_minute, bool has_second) const override; + String AriaRoleForPickerIndicator() const override; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc index ab96006c6cd..beb631c63c6 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc @@ -30,6 +30,7 @@ #include "third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h" +#include "build/build_config.h" #include "third_party/blink/public/mojom/choosers/date_time_chooser.mojom-blink.h" #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/strings/grit/blink_strings.h" @@ -46,6 +47,7 @@ #include "third_party/blink/renderer/platform/language.h" #include "third_party/blink/renderer/platform/text/date_components.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -124,11 +126,11 @@ void DateTimeChooserImpl::WriteDocument(SharedBuffer* data) { AddString("<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data); data->Append(ChooserResourceLoader::GetPickerCommonStyleSheet()); - if (!RuntimeEnabledFeatures::FormControlsRefreshEnabled()) + if (!features::IsFormControlsRefreshEnabled()) data->Append(ChooserResourceLoader::GetPickerButtonStyleSheet()); data->Append(ChooserResourceLoader::GetSuggestionPickerStyleSheet()); data->Append(ChooserResourceLoader::GetCalendarPickerStyleSheet()); - if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) { + if (features::IsFormControlsRefreshEnabled()) { data->Append(ChooserResourceLoader::GetCalendarPickerRefreshStyleSheet()); if (parameters_->type == input_type_names::kTime || parameters_->type == input_type_names::kDatetimeLocal) { @@ -168,6 +170,16 @@ void DateTimeChooserImpl::WriteDocument(SharedBuffer* data) { AddProperty("axShowPreviousMonth", GetLocale().QueryString(IDS_AX_CALENDAR_SHOW_PREVIOUS_MONTH), data); + AddProperty("axHourLabel", GetLocale().QueryString(IDS_AX_HOUR_FIELD_TEXT), + data); + AddProperty("axMinuteLabel", + GetLocale().QueryString(IDS_AX_MINUTE_FIELD_TEXT), data); + AddProperty("axSecondLabel", + GetLocale().QueryString(IDS_AX_SECOND_FIELD_TEXT), data); + AddProperty("axMillisecondLabel", + GetLocale().QueryString(IDS_AX_MILLISECOND_FIELD_TEXT), data); + AddProperty("axAmPmLabel", GetLocale().QueryString(IDS_AX_AM_PM_FIELD_TEXT), + data); AddProperty("weekStartDay", locale_->FirstDayOfWeek(), data); AddProperty("shortMonthLabels", locale_->ShortMonthLabels(), data); AddProperty("dayLabels", locale_->WeekDayShortLabels(), data); @@ -175,7 +187,11 @@ void DateTimeChooserImpl::WriteDocument(SharedBuffer* data) { AddProperty("isLocaleRTL", locale_->IsRTL(), data); AddProperty("isRTL", parameters_->is_anchor_element_rtl, data); AddProperty("isFormControlsRefreshEnabled", - RuntimeEnabledFeatures::FormControlsRefreshEnabled(), data); + features::IsFormControlsRefreshEnabled(), data); +#if defined(OS_MACOSX) + AddProperty("isBorderTransparent", features::IsFormControlsRefreshEnabled(), + data); +#endif AddProperty("mode", parameters_->type.GetString(), data); AddProperty("isAMPMFirst", parameters_->is_ampm_first, data); AddProperty("hasAMPM", parameters_->has_ampm, data); @@ -224,7 +240,7 @@ void DateTimeChooserImpl::WriteDocument(SharedBuffer* data) { data->Append(ChooserResourceLoader::GetPickerCommonJS()); data->Append(ChooserResourceLoader::GetSuggestionPickerJS()); - if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) { + if (features::IsFormControlsRefreshEnabled()) { data->Append(ChooserResourceLoader::GetMonthPickerJS()); if (parameters_->type == input_type_names::kTime) { data->Append(ChooserResourceLoader::GetTimePickerJS()); diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h index 1f9602acd70..22bca47ad27 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h @@ -60,7 +60,6 @@ class CORE_EXPORT DateTimeChooserImpl final : public DateTimeChooser, private: // PagePopupClient functions: void WriteDocument(SharedBuffer*) override; - void SelectFontsFromOwnerDocument(Document&) override {} Locale& GetLocale() override; void SetValueAndClosePopup(int, const String&) override; void SetValue(const String&) override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc index 3e0d42db81c..a22fc3b2088 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc @@ -75,7 +75,7 @@ class DateTimeEditBuilder : private DateTimeFormat::TokenHandler { DateTimeEditElement& EditElement() const; - Member<DateTimeEditElement> edit_element_; + DateTimeEditElement* edit_element_; const DateComponents date_value_; const DateTimeEditElement::LayoutParameters& parameters_; DateTimeNumericFieldElement::Range day_range_; @@ -574,8 +574,7 @@ void DateTimeEditElement::BlurByOwner() { scoped_refptr<ComputedStyle> DateTimeEditElement::CustomStyleForLayoutObject() { // FIXME: This is a kind of layout. We might want to introduce new // layoutObject. - scoped_refptr<ComputedStyle> original_style = OriginalStyleForLayoutObject(); - scoped_refptr<ComputedStyle> style = ComputedStyle::Clone(*original_style); + scoped_refptr<ComputedStyle> style = OriginalStyleForLayoutObject(); float width = 0; for (Node* child = FieldsWrapperElement()->firstChild(); child; child = child->nextSibling()) { @@ -598,12 +597,12 @@ scoped_refptr<ComputedStyle> DateTimeEditElement::CustomStyleForLayoutObject() { return style; } -void DateTimeEditElement::DidBlurFromField(WebFocusType focus_type) { +void DateTimeEditElement::DidBlurFromField(mojom::blink::FocusType focus_type) { if (edit_control_owner_) edit_control_owner_->DidBlurFromControl(focus_type); } -void DateTimeEditElement::DidFocusOnField(WebFocusType focus_type) { +void DateTimeEditElement::DidFocusOnField(mojom::blink::FocusType focus_type) { if (edit_control_owner_) edit_control_owner_->DidFocusOnControl(focus_type); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h index ba1e86be80c..b241d8d00df 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h @@ -27,7 +27,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_EDIT_ELEMENT_H_ #include "base/macros.h" -#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h" #include "third_party/blink/renderer/core/html/forms/date_time_field_element.h" #include "third_party/blink/renderer/core/html/forms/step_range.h" #include "third_party/blink/renderer/platform/text/date_components.h" @@ -54,8 +54,8 @@ class DateTimeEditElement final : public HTMLDivElement, class EditControlOwner : public GarbageCollectedMixin { public: virtual ~EditControlOwner(); - virtual void DidBlurFromControl(WebFocusType) = 0; - virtual void DidFocusOnControl(WebFocusType) = 0; + virtual void DidBlurFromControl(mojom::blink::FocusType) = 0; + virtual void DidFocusOnControl(mojom::blink::FocusType) = 0; virtual void EditControlValueChanged() = 0; virtual String FormatDateTimeFieldsState( const DateTimeFieldsState&) const = 0; @@ -143,8 +143,8 @@ class DateTimeEditElement final : public HTMLDivElement, bool IsDateTimeEditElement() const override; // DateTimeFieldElement::FieldOwner functions. - void DidBlurFromField(WebFocusType) override; - void DidFocusOnField(WebFocusType) override; + void DidBlurFromField(mojom::blink::FocusType) override; + void DidFocusOnField(mojom::blink::FocusType) override; void FieldValueChanged() override; bool FocusOnNextField(const DateTimeFieldElement&) override; bool FocusOnPreviousField(const DateTimeFieldElement&) override; @@ -159,12 +159,6 @@ class DateTimeEditElement final : public HTMLDivElement, DISALLOW_COPY_AND_ASSIGN(DateTimeEditElement); }; -DEFINE_TYPE_CASTS(DateTimeEditElement, - Element, - element, - element->IsDateTimeEditElement(), - element.IsDateTimeEditElement()); - template <> struct DowncastTraits<DateTimeEditElement> { static bool AllowFrom(const Element& element) { diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc index e60c7175803..13dbe846f5b 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc @@ -55,20 +55,19 @@ float DateTimeFieldElement::ComputeTextWidth(const ComputedStyle& style, } void DateTimeFieldElement::DefaultEventHandler(Event& event) { - if (event.IsKeyboardEvent()) { - auto& keyboard_event = ToKeyboardEvent(event); + if (auto* keyboard_event = DynamicTo<KeyboardEvent>(event)) { if (!IsDisabled() && !IsFieldOwnerDisabled() && !IsFieldOwnerReadOnly()) { - HandleKeyboardEvent(keyboard_event); - if (keyboard_event.DefaultHandled()) { + HandleKeyboardEvent(*keyboard_event); + if (keyboard_event->DefaultHandled()) { if (field_owner_) field_owner_->FieldDidChangeValueByKeyboard(); return; } } - DefaultKeyboardEventHandler(keyboard_event); + DefaultKeyboardEventHandler(*keyboard_event); if (field_owner_) field_owner_->FieldDidChangeValueByKeyboard(); - if (keyboard_event.DefaultHandled()) + if (keyboard_event->DefaultHandled()) return; } @@ -130,7 +129,8 @@ void DateTimeFieldElement::DefaultKeyboardEventHandler( } } -void DateTimeFieldElement::SetFocused(bool value, WebFocusType focus_type) { +void DateTimeFieldElement::SetFocused(bool value, + mojom::blink::FocusType focus_type) { if (field_owner_) { if (value) { field_owner_->DidFocusOnField(focus_type); diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h index ca56250f6d2..80abb99baa9 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h @@ -27,7 +27,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENT_H_ #include "base/macros.h" -#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h" #include "third_party/blink/renderer/core/html/html_div_element.h" #include "third_party/blink/renderer/core/html/html_span_element.h" @@ -61,8 +61,8 @@ class DateTimeFieldElement : public HTMLSpanElement { class FieldOwner : public GarbageCollectedMixin { public: virtual ~FieldOwner(); - virtual void DidBlurFromField(WebFocusType) = 0; - virtual void DidFocusOnField(WebFocusType) = 0; + virtual void DidBlurFromField(mojom::blink::FocusType) = 0; + virtual void DidFocusOnField(mojom::blink::FocusType) = 0; virtual void FieldValueChanged() = 0; virtual bool FocusOnNextField(const DateTimeFieldElement&) = 0; virtual bool FocusOnPreviousField(const DateTimeFieldElement&) = 0; @@ -108,7 +108,7 @@ class DateTimeFieldElement : public HTMLSpanElement { virtual int ValueForARIAValueNow() const; // Node functions. - void SetFocused(bool, WebFocusType) override; + void SetFocused(bool, mojom::blink::FocusType) override; private: void DefaultKeyboardEventHandler(KeyboardEvent&); diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc index 1fb0cb7203c..eb50355f5a2 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc @@ -191,4 +191,8 @@ bool DateTimeLocalInputType::IsValidFormat(bool has_year, return has_year && has_month && has_day && has_ampm && has_hour && has_minute; } +String DateTimeLocalInputType::AriaRoleForPickerIndicator() const { + return GetLocale().QueryString(IDS_AX_CALENDAR_SHOW_DATE_TIME_LOCAL_PICKER); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h index 65428f6a7ed..01a9e573efc 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h @@ -67,6 +67,7 @@ class DateTimeLocalInputType final : public BaseTemporalInputType { bool has_hour, bool has_minute, bool has_second) const override; + String AriaRoleForPickerIndicator() const override; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc index 3d15ec0fdce..68cc7e418b7 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc @@ -94,8 +94,9 @@ int DateTimeNumericFieldElement::DefaultValueForStepUp() const { return range_.minimum; } -void DateTimeNumericFieldElement::SetFocused(bool value, - WebFocusType focus_type) { +void DateTimeNumericFieldElement::SetFocused( + bool value, + mojom::blink::FocusType focus_type) { if (!value) { int type_ahead_value = TypeAheadValue(); type_ahead_buffer_.Clear(); diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h index 1f7dd54292e..02c576c9ed0 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h @@ -27,7 +27,6 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_NUMERIC_FIELD_ELEMENT_H_ #include "base/macros.h" -#include "third_party/blink/public/platform/web_focus_type.h" #include "third_party/blink/renderer/core/html/forms/date_time_field_element.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" @@ -95,7 +94,7 @@ class DateTimeNumericFieldElement : public DateTimeFieldElement { String Value() const final; // Node functions. - void SetFocused(bool, WebFocusType) final; + void SetFocused(bool, mojom::blink::FocusType) final; String FormatValue(int) const; int RoundUp(int) const; diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc index 8f61483d746..8ad4132abe3 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc @@ -26,6 +26,7 @@ #include "third_party/blink/renderer/core/html/forms/external_date_time_chooser.h" #include "third_party/blink/public/common/browser_interface_broker_proxy.h" +#include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h" @@ -57,23 +58,18 @@ static ui::mojom::TextInputType ToTextInputType(const AtomicString& source) { ExternalDateTimeChooser::~ExternalDateTimeChooser() = default; void ExternalDateTimeChooser::Trace(Visitor* visitor) { + visitor->Trace(date_time_chooser_); visitor->Trace(client_); DateTimeChooser::Trace(visitor); } ExternalDateTimeChooser::ExternalDateTimeChooser(DateTimeChooserClient* client) - : client_(client) { + : date_time_chooser_(client->OwnerElement().GetExecutionContext()), + client_(client) { DCHECK(!RuntimeEnabledFeatures::InputMultipleFieldsUIEnabled()); DCHECK(client); } -ExternalDateTimeChooser* ExternalDateTimeChooser::Create( - DateTimeChooserClient* client) { - ExternalDateTimeChooser* chooser = - MakeGarbageCollected<ExternalDateTimeChooser>(client); - return chooser; -} - void ExternalDateTimeChooser::OpenDateTimeChooser( LocalFrame* frame, const DateTimeChooserParameters& parameters) { @@ -108,12 +104,15 @@ bool ExternalDateTimeChooser::IsShowingDateTimeChooserUI() const { mojom::blink::DateTimeChooser& ExternalDateTimeChooser::GetDateTimeChooser( LocalFrame* frame) { - if (!date_time_chooser_) { + if (!date_time_chooser_.is_bound()) { frame->GetBrowserInterfaceBroker().GetInterface( - date_time_chooser_.BindNewPipeAndPassReceiver()); + date_time_chooser_.BindNewPipeAndPassReceiver( + // Per the spec, this is a user interaction. + // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events + frame->GetTaskRunner(TaskType::kUserInteraction))); } - DCHECK(date_time_chooser_); + DCHECK(date_time_chooser_.is_bound()); return *date_time_chooser_.get(); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h index ae2a00139fc..af6ff3007cf 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h +++ b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h @@ -26,10 +26,10 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_DATE_TIME_CHOOSER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_DATE_TIME_CHOOSER_H_ -#include "mojo/public/cpp/bindings/remote.h" #include "third_party/blink/public/mojom/choosers/date_time_chooser.mojom-blink.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" +#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h" namespace blink { @@ -38,8 +38,6 @@ class LocalFrame; class CORE_EXPORT ExternalDateTimeChooser final : public DateTimeChooser { public: - static ExternalDateTimeChooser* Create(DateTimeChooserClient*); - explicit ExternalDateTimeChooser(DateTimeChooserClient*); ~ExternalDateTimeChooser() override; void Trace(Visitor*) override; @@ -61,9 +59,9 @@ class CORE_EXPORT ExternalDateTimeChooser final : public DateTimeChooser { mojom::blink::DateTimeChooser& GetDateTimeChooser(LocalFrame* frame); - mojo::Remote<mojom::blink::DateTimeChooser> date_time_chooser_; + HeapMojoRemote<mojom::blink::DateTimeChooser> date_time_chooser_; Member<DateTimeChooserClient> client_; }; -} +} // namespace blink #endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser_test.cc b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser_test.cc index d47d454033b..4f705495a9b 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser_test.cc @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" namespace blink { @@ -64,7 +65,8 @@ TEST_F(ExternalDateTimeChooserTest, EndChooserShouldNotCrash) { auto* document = MakeGarbageCollected<Document>(); auto* element = document->CreateRawElement(html_names::kInputTag); auto* client = MakeGarbageCollected<TestDateTimeChooserClient>(element); - auto* external_date_time_chooser = ExternalDateTimeChooser::Create(client); + auto* external_date_time_chooser = + MakeGarbageCollected<ExternalDateTimeChooser>(client); client->SetDateTimeChooser(external_date_time_chooser); external_date_time_chooser->ResponseHandler(true, 0); } @@ -77,7 +79,7 @@ TEST_F(ExternalDateTimeChooserTest, EndChooserShouldNotCrash) { TEST_F(ExternalDateTimeChooserTest, OpenDateTimeChooserShouldNotCrashWhenLabelAndValueIsTheSame) { ScopedInputMultipleFieldsUIForTest input_multiple_fields_ui(false); - GetDocument().documentElement()->SetInnerHTMLFromString(R"HTML( + GetDocument().documentElement()->setInnerHTML(R"HTML( <input id=test type="date" list="src" /> <datalist id="src"> <option value='2019-12-31'>Hint</option> @@ -86,8 +88,8 @@ TEST_F(ExternalDateTimeChooserTest, // value attribute. </datalist> )HTML"); - GetDocument().View()->UpdateAllLifecyclePhases( - DocumentLifecycle::LifecycleUpdateReason::kTest); + GetDocument().View()->UpdateAllLifecyclePhases(DocumentUpdateReason::kTest); + GetDocument().View()->RunPostLifecycleSteps(); auto* input = To<HTMLInputElement>(GetDocument().getElementById("test")); ASSERT_TRUE(input); @@ -98,7 +100,8 @@ TEST_F(ExternalDateTimeChooserTest, auto* client = MakeGarbageCollected<TestDateTimeChooserClient>( GetDocument().documentElement()); - auto* external_date_time_chooser = ExternalDateTimeChooser::Create(client); + auto* external_date_time_chooser = + MakeGarbageCollected<ExternalDateTimeChooser>(client); client->SetDateTimeChooser(external_date_time_chooser); external_date_time_chooser->OpenDateTimeChooser(GetDocument().GetFrame(), params); diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc index b86f742cbd4..f7aab03b544 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc @@ -31,9 +31,9 @@ #include "third_party/blink/renderer/core/html/forms/external_popup_menu.h" #include "build/build_config.h" +#include "third_party/blink/public/common/input/web_mouse_event.h" #include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/public/platform/web_coalesced_input_event.h" -#include "third_party/blink/public/platform/web_mouse_event.h" #include "third_party/blink/public/platform/web_vector.h" #include "third_party/blink/public/web/web_external_popup_menu.h" #include "third_party/blink/public/web/web_local_frame_client.h" @@ -262,7 +262,7 @@ void ExternalPopupMenu::GetPopupMenuInfo(WebPopupMenuInfo& info, } popup_item.enabled = !item_element.IsDisabledFormControl(); const ComputedStyle& style = *owner_element.ItemComputedStyle(item_element); - popup_item.text_direction = ToWebTextDirection(style.Direction()); + popup_item.text_direction = ToBaseTextDirection(style.Direction()); popup_item.has_text_direction_override = IsOverride(style.GetUnicodeBidi()); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc index 383f6a853c8..a493fa8b0c7 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc @@ -17,7 +17,7 @@ #include "third_party/blink/renderer/core/html/forms/html_select_element.h" #include "third_party/blink/renderer/core/html/forms/popup_menu.h" #include "third_party/blink/renderer/core/html_names.h" -#include "third_party/blink/renderer/core/layout/layout_menu_list.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/testing/page_test_base.h" #include "third_party/blink/renderer/platform/heap/heap.h" @@ -35,12 +35,12 @@ class ExternalPopupMenuDisplayNoneItemsTest : public PageTestBase { PageTestBase::SetUp(); auto* element = MakeGarbageCollected<HTMLSelectElement>(GetDocument()); // Set the 4th an 5th items to have "display: none" property - element->SetInnerHTMLFromString( + element->setInnerHTML( "<option><option><option><option style='display:none;'><option " "style='display:none;'><option><option>"); GetDocument().body()->AppendChild(element, ASSERT_NO_EXCEPTION); owner_element_ = element; - GetDocument().UpdateStyleAndLayout(); + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest); } Persistent<HTMLSelectElement> owner_element_; @@ -117,7 +117,7 @@ class ExternalPopupMenuTest : public testing::Test { frame_test_helpers::LoadFrame(MainFrame(), base_url_ + file_name); WebView()->MainFrameWidget()->Resize(WebSize(800, 600)); WebView()->MainFrameWidget()->UpdateAllLifecyclePhases( - WebWidget::LifecycleUpdateReason::kTest); + DocumentUpdateReason::kTest); } WebViewImpl* WebView() const { return helper_.GetWebView(); } @@ -138,16 +138,16 @@ TEST_F(ExternalPopupMenuTest, PopupAccountsForVisualViewportTransform) { WebView()->MainFrameWidget()->Resize(WebSize(100, 100)); WebView()->MainFrameWidget()->UpdateAllLifecyclePhases( - WebWidget::LifecycleUpdateReason::kTest); + DocumentUpdateReason::kTest); auto* select = To<HTMLSelectElement>( MainFrame()->GetFrame()->GetDocument()->getElementById("select")); - LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); - ASSERT_TRUE(menu_list); + auto* layout_object = select->GetLayoutObject(); + ASSERT_TRUE(layout_object); VisualViewport& visual_viewport = WebView()->GetPage()->GetVisualViewport(); - IntRect rect_in_document = menu_list->AbsoluteBoundingBoxRect(); + IntRect rect_in_document = layout_object->AbsoluteBoundingBoxRect(); constexpr int kScaleFactor = 2; ScrollOffset scroll_delta(20, 30); @@ -171,17 +171,17 @@ TEST_F(ExternalPopupMenuTest, DidAcceptIndex) { auto* select = To<HTMLSelectElement>( MainFrame()->GetFrame()->GetDocument()->getElementById("select")); - LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); - ASSERT_TRUE(menu_list); + auto* layout_object = select->GetLayoutObject(); + ASSERT_TRUE(layout_object); select->ShowPopup(); ASSERT_TRUE(select->PopupIsVisible()); WebExternalPopupMenuClient* client = - static_cast<ExternalPopupMenu*>(select->Popup()); + static_cast<ExternalPopupMenu*>(select->PopupForTesting()); client->DidAcceptIndex(2); EXPECT_FALSE(select->PopupIsVisible()); - ASSERT_EQ("2", menu_list->GetText().Utf8()); + ASSERT_EQ("2", select->InnerElement().innerText().Utf8()); EXPECT_EQ(2, select->selectedIndex()); } @@ -191,19 +191,19 @@ TEST_F(ExternalPopupMenuTest, DidAcceptIndices) { auto* select = To<HTMLSelectElement>( MainFrame()->GetFrame()->GetDocument()->getElementById("select")); - LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); - ASSERT_TRUE(menu_list); + auto* layout_object = select->GetLayoutObject(); + ASSERT_TRUE(layout_object); select->ShowPopup(); ASSERT_TRUE(select->PopupIsVisible()); WebExternalPopupMenuClient* client = - static_cast<ExternalPopupMenu*>(select->Popup()); + static_cast<ExternalPopupMenu*>(select->PopupForTesting()); int indices[] = {2}; WebVector<int> indices_vector(indices, 1); client->DidAcceptIndices(indices_vector); EXPECT_FALSE(select->PopupIsVisible()); - EXPECT_EQ("2", menu_list->GetText()); + EXPECT_EQ("2", select->InnerElement().innerText()); EXPECT_EQ(2, select->selectedIndex()); } @@ -213,14 +213,14 @@ TEST_F(ExternalPopupMenuTest, DidAcceptIndicesClearSelect) { auto* select = To<HTMLSelectElement>( MainFrame()->GetFrame()->GetDocument()->getElementById("select")); - LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); - ASSERT_TRUE(menu_list); + auto* layout_object = select->GetLayoutObject(); + ASSERT_TRUE(layout_object); select->ShowPopup(); ASSERT_TRUE(select->PopupIsVisible()); WebExternalPopupMenuClient* client = - static_cast<ExternalPopupMenu*>(select->Popup()); + static_cast<ExternalPopupMenu*>(select->PopupForTesting()); WebVector<int> indices; client->DidAcceptIndices(indices); EXPECT_FALSE(select->PopupIsVisible()); diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc index 5e07aa092bd..62d3be9b859 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc @@ -24,7 +24,6 @@ #include "third_party/blink/public/platform/file_path_conversion.h" #include "third_party/blink/public/strings/grit/blink_strings.h" -#include "third_party/blink/renderer/core/css/style_change_reason.h" #include "third_party/blink/renderer/core/dom/events/event.h" #include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" @@ -34,10 +33,12 @@ #include "third_party/blink/renderer/core/html/forms/form_controller.h" #include "third_party/blink/renderer/core/html/forms/form_data.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/input_type_names.h" #include "third_party/blink/renderer/core/inspector/console_message.h" -#include "third_party/blink/renderer/core/layout/layout_file_upload_control.h" +#include "third_party/blink/renderer/core/layout/layout_block_flow.h" +#include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/drag_data.h" #include "third_party/blink/renderer/core/probe/core_probes.h" @@ -162,9 +163,9 @@ void FileInputType::HandleDOMActivateEvent(Event& event) { if (!LocalFrame::HasTransientUserActivation(document.GetFrame())) { String message = "File chooser dialog can only be shown with a user activation."; - document.AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kJavaScript, - mojom::ConsoleMessageLevel::kWarning, message)); + document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kWarning, message)); return; } @@ -202,9 +203,18 @@ void FileInputType::HandleDOMActivateEvent(Event& event) { event.SetDefaultHandled(); } -LayoutObject* FileInputType::CreateLayoutObject(const ComputedStyle&, - LegacyLayout) const { - return new LayoutFileUploadControl(&GetElement()); +void FileInputType::CustomStyleForLayoutObject(ComputedStyle& style) { + style.SetShouldIgnoreOverflowPropertyForInlineBlockBaseline(); +} + +bool FileInputType::TypeShouldForceLegacyLayout() const { + return !RuntimeEnabledFeatures::LayoutNGForControlsEnabled(); +} + +LayoutObject* FileInputType::CreateLayoutObject(const ComputedStyle& style, + LegacyLayout legacy) const { + return LayoutObjectFactory::CreateFileUploadControl(GetElement(), style, + legacy); } InputType::ValueMode FileInputType::GetValueMode() const { @@ -249,10 +259,8 @@ void FileInputType::SetValue(const String&, return; file_list_->clear(); - GetElement().SetNeedsStyleRecalc( - kSubtreeStyleChange, - StyleChangeReasonForTracing::Create(style_change_reason::kControlValue)); GetElement().SetNeedsValidityCheck(); + UpdateView(); } FileList* FileInputType::CreateFileList(const FileChooserFileInfoList& files, @@ -317,8 +325,10 @@ void FileInputType::CountUsage() { void FileInputType::CreateShadowSubtree() { DCHECK(IsShadowHost(GetElement())); - auto* button = MakeGarbageCollected<HTMLInputElement>( - GetElement().GetDocument(), CreateElementFlags()); + Document& document = GetElement().GetDocument(); + + auto* button = + MakeGarbageCollected<HTMLInputElement>(document, CreateElementFlags()); button->setType(input_type_names::kButton); button->setAttribute( html_names::kValueAttr, @@ -326,25 +336,43 @@ void FileInputType::CreateShadowSubtree() { GetElement().Multiple() ? IDS_FORM_MULTIPLE_FILES_BUTTON_LABEL : IDS_FORM_FILE_BUTTON_LABEL))); button->SetShadowPseudoId(AtomicString("-webkit-file-upload-button")); + button->setAttribute(html_names::kIdAttr, + shadow_element_names::FileUploadButton()); + button->SetActive(GetElement().CanReceiveDroppedFiles()); GetElement().UserAgentShadowRoot()->AppendChild(button); + + // The following element is used only in LayoutNG. + // See LayoutFileUploadControl::IsChildAllowed(). + auto* span = document.CreateRawElement(html_names::kSpanTag); + // This element is hidden from AX trees for a historical reason. + span->setAttribute(html_names::kAriaHiddenAttr, "true"); + GetElement().UserAgentShadowRoot()->AppendChild(span); + + UpdateView(); +} + +HTMLInputElement* FileInputType::UploadButton() const { + Element* element = GetElement().UserAgentShadowRoot()->getElementById( + shadow_element_names::FileUploadButton()); + CHECK(!element || IsA<HTMLInputElement>(element)); + return To<HTMLInputElement>(element); +} + +Node* FileInputType::FileStatusElement() const { + return GetElement().UserAgentShadowRoot()->lastChild(); } void FileInputType::DisabledAttributeChanged() { DCHECK(IsShadowHost(GetElement())); - CHECK(!GetElement().UserAgentShadowRoot()->firstChild() || - IsA<Element>(GetElement().UserAgentShadowRoot()->firstChild())); - if (Element* button = - To<Element>(GetElement().UserAgentShadowRoot()->firstChild())) + if (Element* button = UploadButton()) { button->SetBooleanAttribute(html_names::kDisabledAttr, GetElement().IsDisabledFormControl()); + } } void FileInputType::MultipleAttributeChanged() { DCHECK(IsShadowHost(GetElement())); - CHECK(!GetElement().UserAgentShadowRoot()->firstChild() || - IsA<Element>(GetElement().UserAgentShadowRoot()->firstChild())); - if (Element* button = - To<Element>(GetElement().UserAgentShadowRoot()->firstChild())) { + if (Element* button = UploadButton()) { button->setAttribute( html_names::kValueAttr, AtomicString(GetLocale().QueryString( @@ -373,10 +401,7 @@ bool FileInputType::SetFiles(FileList* files) { GetElement().NotifyFormStateChanged(); GetElement().SetNeedsValidityCheck(); - - if (GetElement().GetLayoutObject()) - GetElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation(); - + UpdateView(); return files_changed; } @@ -520,4 +545,27 @@ void FileInputType::WillOpenPopup() { } } +String FileInputType::FileStatusText() const { + Locale& locale = GetLocale(); + + if (file_list_->IsEmpty()) + return locale.QueryString(IDS_FORM_FILE_NO_FILE_LABEL); + + if (file_list_->length() == 1) + return LayoutTheme::GetTheme().DisplayNameForFile(*file_list_->item(0)); + + return locale.QueryString( + IDS_FORM_FILE_MULTIPLE_UPLOAD, + locale.ConvertToLocalizedNumber(String::Number(file_list_->length()))); +} + +void FileInputType::UpdateView() { + auto* layout_object = GetElement().GetLayoutObject(); + if (layout_object && layout_object->IsFileUploadControl()) + layout_object->SetShouldDoFullPaintInvalidation(); + + if (auto* span = FileStatusElement()) + span->setTextContent(FileStatusText()); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h index 256a75272dd..425bf6b8226 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h @@ -72,6 +72,8 @@ class CORE_EXPORT FileInputType final : public InputType, bool ValueMissing(const String&) const override; String ValueMissingText() const override; void HandleDOMActivateEvent(Event&) override; + void CustomStyleForLayoutObject(ComputedStyle& style) override; + bool TypeShouldForceLegacyLayout() const override; LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) const override; bool CanSetStringValue() const override; @@ -88,10 +90,13 @@ class CORE_EXPORT FileInputType final : public InputType, bool ReceiveDroppedFiles(const DragData*) override; String DroppedFileSystemId() override; void CreateShadowSubtree() override; + HTMLInputElement* UploadButton() const override; void DisabledAttributeChanged() override; void MultipleAttributeChanged() override; String DefaultToolTip(const InputTypeView&) const override; void CopyNonAttributeProperties(const HTMLInputElement&) override; + String FileStatusText() const override; + void UpdateView() override; // KeyboardClickableInputTypeView overrides. void HandleKeypressEvent(KeyboardEvent&) override; @@ -106,6 +111,7 @@ class CORE_EXPORT FileInputType final : public InputType, void WillOpenPopup() override; void SetFilesFromDirectory(const String&); + Node* FileStatusElement() const; Member<FileList> file_list_; String dropped_file_system_id_; diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc b/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc index 3d30730b162..ebbb62c3b57 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc @@ -138,7 +138,7 @@ TEST(FileInputTypeTest, DropTouchesNoPopupOpeningObserver) { std::make_unique<DummyPageHolder>(IntSize(), &page_clients); Document& doc = page_holder->GetDocument(); - doc.body()->SetInnerHTMLFromString("<input type=file webkitdirectory>"); + doc.body()->setInnerHTML("<input type=file webkitdirectory>"); auto& input = *To<HTMLInputElement>(doc.body()->firstChild()); base::RunLoop run_loop; diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc b/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc index b9b161e40a4..681786d2c40 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc @@ -42,10 +42,6 @@ namespace blink { namespace { -// TODO(crbug.com/1008708): Remove this flag when we're sure the new behavior -// is better than the previous one. -constexpr bool kRestoreOnLoad = true; - inline HTMLFormElement* OwnerFormForState(const ListedElement& control) { // Assume controls with form attribute have no owners because we restore // state during parsing and form owners of such controls might be @@ -62,8 +58,9 @@ const AtomicString& ControlType(const ListedElement& control) { } bool IsDirtyControl(const ListedElement& control) { - if (control.IsFormControlElementWithState()) - return ToHTMLFormControlElementWithState(control).UserHasEditedTheField(); + if (auto* form_control_element = + DynamicTo<HTMLFormControlElementWithState>(control)) + return form_control_element->UserHasEditedTheField(); if (control.IsElementInternals()) { // We have no ways to know the dirtiness of a form-associated custom // element. Assume it is dirty if it has focus. @@ -567,7 +564,7 @@ void FormController::WillDeleteForm(HTMLFormElement* form) { } void FormController::RestoreControlStateFor(ListedElement& control) { - if (kRestoreOnLoad && !document_->HasFinishedParsing()) + if (!document_->HasFinishedParsing()) return; if (OwnerFormForState(control)) return; @@ -575,7 +572,7 @@ void FormController::RestoreControlStateFor(ListedElement& control) { } void FormController::RestoreControlStateIn(HTMLFormElement& form) { - if (kRestoreOnLoad && !document_->HasFinishedParsing()) + if (!document_->HasFinishedParsing()) return; EventQueueScope scope; const ListedElement::List& elements = form.ListedElements(); @@ -619,8 +616,6 @@ void FormController::RestoreControlStateOnUpgrade(ListedElement& control) { } void FormController::ScheduleRestore() { - if (!kRestoreOnLoad) - return; document_->GetTaskRunner(TaskType::kInternalLoading) ->PostTask(FROM_HERE, WTF::Bind(&FormController::RestoreAllControlsInDocumentOrder, @@ -628,8 +623,6 @@ void FormController::ScheduleRestore() { } void FormController::RestoreImmediately() { - if (!kRestoreOnLoad) - return; if (did_restore_all_ || !HasControlStates()) return; RestoreAllControlsInDocumentOrder(); diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_controller_test.cc b/chromium/third_party/blink/renderer/core/html/forms/form_controller_test.cc index b278e95decd..e54eefa6018 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/form_controller_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/form_controller_test.cc @@ -20,7 +20,7 @@ TEST(DocumentStateTest, ToStateVectorConnected) { Element* html = doc.CreateRawElement(html_names::kHTMLTag); doc.appendChild(html); Node* body = html->appendChild(doc.CreateRawElement(html_names::kBodyTag)); - To<Element>(body)->SetInnerHTMLFromString("<select form='ff'></select>"); + To<Element>(body)->setInnerHTML("<select form='ff'></select>"); DocumentState* document_state = doc.GetFormController().ControlStates(); Vector<String> state1 = document_state->ToStateVector(); // <signature>, <control-size>, <form-key>, <name>, <type>, <data-size(0)> diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data.cc b/chromium/third_party/blink/renderer/core/html/forms/form_data.cc index 4ef6dba5b2d..1918cac770a 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/form_data.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.cc @@ -302,7 +302,7 @@ scoped_refptr<EncodedFormData> FormData::EncodeMultiPartFormData() { auto* file = To<File>(entry->GetBlob()); // Do not add the file if the path is empty. if (!file->GetPath().IsEmpty()) - form_data->AppendFile(file->GetPath()); + form_data->AppendFile(file->GetPath(), file->LastModifiedTime()); } else { form_data->AppendBlob(entry->GetBlob()->Uuid(), entry->GetBlob()->GetBlobDataHandle()); diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data.idl b/chromium/third_party/blink/renderer/core/html/forms/form_data.idl index 79c8e499142..cc98012eb62 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/form_data.idl +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.idl @@ -33,10 +33,9 @@ typedef (File or USVString) FormDataEntryValue; [ - Constructor(optional HTMLFormElement form), - RaisesException=Constructor, Exposed=(Window,Worker) ] interface FormData { + [RaisesException] constructor(optional HTMLFormElement form); void append(USVString name, USVString value); [CallWith=ScriptState] void append(USVString name, Blob value, optional USVString filename); [ImplementedAs=deleteEntry] void delete(USVString name); diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc index 9b077df8cc7..3e1d9bf0d70 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc @@ -4,9 +4,9 @@ #include "third_party/blink/renderer/core/html/forms/form_data_event.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_form_data_event_init.h" #include "third_party/blink/renderer/core/event_interface_names.h" #include "third_party/blink/renderer/core/html/forms/form_data.h" -#include "third_party/blink/renderer/core/html/forms/form_data_event_init.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl index 0b74add3f2a..84c7af0cc1e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl @@ -5,9 +5,8 @@ // https://html.spec.whatwg.org/C/#formdataevent [ - Constructor(DOMString type, optional FormDataEventInit eventInitDict), Exposed=Window -] -interface FormDataEvent : Event { +] interface FormDataEvent : Event { + constructor(DOMString type, optional FormDataEventInit eventInitDict = {}); readonly attribute FormData formData; }; diff --git a/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc index ce51f88b4ca..f8fef662ad8 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc @@ -92,7 +92,7 @@ void HiddenInputType::SetValue(const String& sanitized_value, } void HiddenInputType::AppendToFormData(FormData& form_data) const { - if (DeprecatedEqualIgnoringCase(GetElement().GetName(), "_charset_")) { + if (EqualIgnoringASCIICase(GetElement().GetName(), "_charset_")) { form_data.AppendFromElement(GetElement().GetName(), String(form_data.Encoding().GetName())); return; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc index 0171b21b096..e062da100d7 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc @@ -27,7 +27,6 @@ #include "third_party/blink/renderer/core/dom/attribute.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" -#include "third_party/blink/renderer/core/events/mouse_event.h" #include "third_party/blink/renderer/core/html/forms/form_data.h" #include "third_party/blink/renderer/core/html/forms/html_form_element.h" #include "third_party/blink/renderer/core/html_names.h" @@ -45,8 +44,15 @@ void HTMLButtonElement::setType(const AtomicString& type) { setAttribute(html_names::kTypeAttr, type); } -LayoutObject* HTMLButtonElement::CreateLayoutObject(const ComputedStyle&, - LegacyLayout) { +LayoutObject* HTMLButtonElement::CreateLayoutObject(const ComputedStyle& style, + LegacyLayout legacy) { + // https://html.spec.whatwg.org/C/#button-layout + EDisplay display = style.Display(); + if (display == EDisplay::kInlineGrid || display == EDisplay::kGrid || + display == EDisplay::kInlineFlex || display == EDisplay::kFlex || + display == EDisplay::kInlineLayoutCustom || + display == EDisplay::kLayoutCustom) + return HTMLFormControlElement::CreateLayoutObject(style, legacy); return new LayoutButton(this); } @@ -84,9 +90,9 @@ bool HTMLButtonElement::IsPresentationAttribute( void HTMLButtonElement::ParseAttribute( const AttributeModificationParams& params) { if (params.name == html_names::kTypeAttr) { - if (DeprecatedEqualIgnoringCase(params.new_value, "reset")) + if (EqualIgnoringASCIICase(params.new_value, "reset")) type_ = RESET; - else if (DeprecatedEqualIgnoringCase(params.new_value, "button")) + else if (EqualIgnoringASCIICase(params.new_value, "button")) type_ = BUTTON; else type_ = SUBMIT; @@ -101,13 +107,6 @@ void HTMLButtonElement::ParseAttribute( } void HTMLButtonElement::DefaultEventHandler(Event& event) { - DefaultEventHandlerInternal(event); - - if (event.type() == event_type_names::kDOMActivate && formOwner()) - formOwner()->DidActivateSubmitButton(this); -} - -void HTMLButtonElement::DefaultEventHandlerInternal(Event& event) { if (event.type() == event_type_names::kDOMActivate && !IsDisabledFormControl()) { if (Form() && type_ == SUBMIT) { @@ -120,33 +119,8 @@ void HTMLButtonElement::DefaultEventHandlerInternal(Event& event) { } } - if (event.IsKeyboardEvent()) { - if (event.type() == event_type_names::kKeydown && - ToKeyboardEvent(event).key() == " ") { - SetActive(true); - // No setDefaultHandled() - IE dispatches a keypress in this case. - return; - } - if (event.type() == event_type_names::kKeypress) { - switch (ToKeyboardEvent(event).charCode()) { - case '\r': - DispatchSimulatedClick(&event); - event.SetDefaultHandled(); - return; - case ' ': - // Prevent scrolling down the page. - event.SetDefaultHandled(); - return; - } - } - if (event.type() == event_type_names::kKeyup && - ToKeyboardEvent(event).key() == " ") { - if (IsActive()) - DispatchSimulatedClick(&event); - event.SetDefaultHandled(); - return; - } - } + if (HandleKeyboardActivation(event)) + return; HTMLFormControlElement::DefaultEventHandler(event); } @@ -223,16 +197,4 @@ Node::InsertionNotificationRequest HTMLButtonElement::InsertedInto( return request; } -EventDispatchHandlingState* HTMLButtonElement::PreDispatchEventHandler( - Event& event) { - if (Form() && CanBeSuccessfulSubmitButton()) - Form()->WillActivateSubmitButton(this); - return nullptr; -} - -void HTMLButtonElement::DidPreventDefault(const Event& event) { - if (auto* form = formOwner()) - form->DidActivateSubmitButton(this); -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h index 6071e0f1bc4..44437eeda26 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h @@ -78,13 +78,6 @@ class HTMLButtonElement final : public HTMLFormControlElement { int DefaultTabIndex() const override; - // TODO(crbug.com/1013385): Remove PreDispatchEventHandler, DidPreventDefault, - // and DefaultEventHandlerInternal. They are here to temporarily fix form - // double-submit. - EventDispatchHandlingState* PreDispatchEventHandler(Event&) override; - void DidPreventDefault(const Event&) final; - void DefaultEventHandlerInternal(Event&); - Type type_; bool is_activated_submit_; }; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc index d7faa3935f7..e6bfaae178f 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc @@ -52,7 +52,7 @@ HTMLDataListOptionsCollection* HTMLDataListElement::options() { void HTMLDataListElement::ChildrenChanged(const ChildrenChange& change) { HTMLElement::ChildrenChanged(change); - if (!change.by_parser) { + if (!change.ByParser()) { GetTreeScope().GetIdTargetObserverRegistry().NotifyObservers( GetIdAttribute()); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h index 07460b9469e..84fb8779116 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h @@ -32,12 +32,11 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_ELEMENT_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_ELEMENT_H_ +#include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h" #include "third_party/blink/renderer/core/html/html_element.h" namespace blink { -class HTMLDataListOptionsCollection; - class CORE_EXPORT HTMLDataListElement final : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h index 4b68f900289..94d5f68907e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h @@ -7,6 +7,7 @@ #include "third_party/blink/renderer/core/html/forms/html_option_element.h" #include "third_party/blink/renderer/core/html/html_collection.h" +#include "third_party/blink/renderer/platform/wtf/casting.h" namespace blink { @@ -28,11 +29,12 @@ class HTMLDataListOptionsCollection : public HTMLCollection { bool ElementMatches(const HTMLElement&) const; }; -DEFINE_TYPE_CASTS(HTMLDataListOptionsCollection, - LiveNodeListBase, - collection, - collection->GetType() == kDataListOptions, - collection.GetType() == kDataListOptions); +template <> +struct DowncastTraits<HTMLDataListOptionsCollection> { + static bool AllowFrom(const LiveNodeListBase& collection) { + return collection.GetType() == kDataListOptions; + } +}; inline bool HTMLDataListOptionsCollection::ElementMatches( const HTMLElement& element) const { diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc index 4b63cb11b2a..e06b9e85ef3 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc @@ -72,7 +72,7 @@ HTMLFieldSetElement::InvalidateDescendantDisabledStateAndFindFocusedOne( { EventDispatchForbiddenScope event_forbidden; for (HTMLElement& element : Traversal<HTMLElement>::DescendantsOf(base)) { - if (auto* control = ToHTMLFormControlElementOrNull(element)) + if (auto* control = DynamicTo<HTMLFormControlElement>(element)) control->AncestorDisabledStateWasChanged(); else if (element.IsFormAssociatedCustomElement()) element.EnsureElementInternals().AncestorDisabledStateWasChanged(); diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc index 1204ae261b3..7ef8db7a852 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc @@ -202,18 +202,6 @@ const AtomicString& HTMLFormControlElement::autocapitalize() const { return g_empty_atom; } -void HTMLFormControlElement::AttachLayoutTree(AttachContext& context) { - HTMLElement::AttachLayoutTree(context); - - if (!GetLayoutObject()) - return; - - // The call to updateFromElement() needs to go after the call through - // to the base class's attachLayoutTree() because that can sometimes do a - // close on the layoutObject. - GetLayoutObject()->UpdateFromElement(); -} - void HTMLFormControlElement::DidMoveToNewDocument(Document& old_document) { ListedElement::DidMoveToNewDocument(old_document); HTMLElement::DidMoveToNewDocument(old_document); @@ -243,10 +231,6 @@ void HTMLFormControlElement::DidChangeForm() { formOwner()->InvalidateDefaultButtonStyle(); } -void HTMLFormControlElement::DispatchChangeEvent() { - DispatchScopedEvent(*Event::CreateBubble(event_type_names::kChange)); -} - HTMLFormElement* HTMLFormControlElement::formOwner() const { return ListedElement::Form(); } @@ -273,13 +257,6 @@ String HTMLFormControlElement::ResultForDialogSubmit() { return FastGetAttribute(html_names::kValueAttr); } -void HTMLFormControlElement::DidRecalcStyle(const StyleRecalcChange change) { - if (change.ReattachLayoutTree()) - return; - if (LayoutObject* layout_object = GetLayoutObject()) - layout_object->UpdateFromElement(); -} - bool HTMLFormControlElement::SupportsFocus() const { return !IsDisabledFormControl(); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h index 6761900c783..62c7a77395e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h @@ -59,8 +59,6 @@ class CORE_EXPORT HTMLFormControlElement : public HTMLElement, void Reset(); - void DispatchChangeEvent(); - HTMLFormElement* formOwner() const final; bool IsDisabledFormControl() const override; @@ -137,7 +135,6 @@ class CORE_EXPORT HTMLFormControlElement : public HTMLElement, void ParseAttribute(const AttributeModificationParams&) override; virtual void RequiredAttributeChanged(); void DisabledAttributeChanged() override; - void AttachLayoutTree(AttachContext&) override; InsertionNotificationRequest InsertedInto(ContainerNode&) override; void RemovedFrom(ContainerNode&) override; void WillChangeForm() override; @@ -146,9 +143,7 @@ class CORE_EXPORT HTMLFormControlElement : public HTMLElement, bool SupportsFocus() const override; bool IsKeyboardFocusable() const override; - bool ShouldHaveFocusAppearance() const final; - - void DidRecalcStyle(const StyleRecalcChange) override; + bool ShouldHaveFocusAppearance() const override; virtual void ResetImpl() {} @@ -167,22 +162,24 @@ class CORE_EXPORT HTMLFormControlElement : public HTMLElement, bool blocks_form_submission_ : 1; }; -inline bool IsHTMLFormControlElement(const Element& element) { - return element.IsFormControlElement(); +template <> +inline bool IsElementOfType<const HTMLFormControlElement>(const Node& node) { + return IsA<HTMLFormControlElement>(node); } - -DEFINE_HTMLELEMENT_TYPE_CASTS_WITH_FUNCTION(HTMLFormControlElement); - template <> struct DowncastTraits<HTMLFormControlElement> { static bool AllowFrom(const Node& node) { - auto* element = DynamicTo<Element>(node); - return element && element->IsFormControlElement(); + auto* html_element = DynamicTo<HTMLElement>(node); + return html_element && AllowFrom(*html_element); } static bool AllowFrom(const ListedElement& control) { return control.IsFormControlElement(); } + static bool AllowFrom(const HTMLElement& html_element) { + return html_element.IsFormControlElement(); + } }; + } // namespace blink #endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc index da02126b3c2..db9caa6e8b2 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc @@ -133,7 +133,7 @@ TEST_F(HTMLFormControlElementTest, DoNotUpdateLayoutDuringDOMMutation) { // ShowValidationMessage(). So calling it during DOM mutation is // dangerous. This test ensures ShowValidationMessage() is NOT called in // appendChild(). crbug.com/756408 - GetDocument().documentElement()->SetInnerHTMLFromString("<select></select>"); + GetDocument().documentElement()->setInnerHTML("<select></select>"); auto* const select = To<HTMLFormControlElement>(GetDocument().QuerySelector("select")); auto* const optgroup = diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc index 888e83b4120..f8f5337c034 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc @@ -24,6 +24,7 @@ #include "third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h" +#include "third_party/blink/renderer/core/dom/events/event.h" #include "third_party/blink/renderer/core/html/forms/html_form_element.h" #include "third_party/blink/renderer/core/input_type_names.h" @@ -275,6 +276,17 @@ bool HTMLFormControlElementWithState::ShouldSaveAndRestoreFormControlState() return isConnected() && ShouldAutocomplete(); } +void HTMLFormControlElementWithState::DispatchInputEvent() { + // Legacy 'input' event for forms set value and checked. + Event* event = Event::CreateBubble(event_type_names::kInput); + event->SetComposed(true); + DispatchScopedEvent(*event); +} + +void HTMLFormControlElementWithState::DispatchChangeEvent() { + DispatchScopedEvent(*Event::CreateBubble(event_type_names::kChange)); +} + void HTMLFormControlElementWithState::FinishParsingChildren() { HTMLFormControlElement::FinishParsingChildren(); ListedElement::TakeStateAndRestore(); diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h index 6f93afebb70..88b71eccf45 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h @@ -27,6 +27,7 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/platform/wtf/casting.h" namespace blink { @@ -50,6 +51,9 @@ class CORE_EXPORT HTMLFormControlElementWithState // This is only used in tests, to fake the user's action void SetUserHasEditedTheFieldForTest() { user_has_edited_the_field_ = true; } + void DispatchInputEvent(); + void DispatchChangeEvent(); + protected: bool user_has_edited_the_field_ = false; HTMLFormControlElementWithState(const QualifiedName& tag_name, Document&); @@ -58,18 +62,18 @@ class CORE_EXPORT HTMLFormControlElementWithState bool IsFormControlElementWithState() const final; private: - bool TypeShouldForceLegacyLayout() const final { return true; } int DefaultTabIndex() const override; // https://html.spec.whatwg.org/C/#autofill-anchor-mantle bool IsWearingAutofillAnchorMantle() const; }; -DEFINE_TYPE_CASTS(HTMLFormControlElementWithState, - ListedElement, - control, - control->IsFormControlElementWithState(), - control.IsFormControlElementWithState()); +template <> +struct DowncastTraits<HTMLFormControlElementWithState> { + static bool AllowFrom(const ListedElement& control) { + return control.IsFormControlElementWithState(); + } +}; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h index 8a33fc9fb8b..684265f7b26 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h @@ -68,11 +68,6 @@ class HTMLFormControlsCollection final : public HTMLCollection { mutable Member<HTMLElement> cached_element_; mutable unsigned cached_element_offset_in_array_; }; -DEFINE_TYPE_CASTS(HTMLFormControlsCollection, - LiveNodeListBase, - collection, - collection->GetType() == kFormControls, - collection.GetType() == kFormControls); } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc index 0f277cd101a..aa0f521211e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc @@ -28,10 +28,12 @@ #include <limits> #include "base/auto_reset.h" -#include "third_party/blink/public/platform/web_insecure_request_policy.h" +#include "third_party/blink/public/common/security_context/insecure_request_policy.h" +#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h" #include "third_party/blink/renderer/bindings/core/v8/radio_node_list_or_element.h" #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" #include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_submit_event_init.h" #include "third_party/blink/renderer/core/dom/attribute.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/element_traversal.h" @@ -51,6 +53,7 @@ #include "third_party/blink/renderer/core/html/forms/html_form_controls_collection.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/forms/radio_node_list.h" +#include "third_party/blink/renderer/core/html/forms/submit_event.h" #include "third_party/blink/renderer/core/html/html_collection.h" #include "third_party/blink/renderer/core/html/html_dialog_element.h" #include "third_party/blink/renderer/core/html/html_image_element.h" @@ -89,8 +92,6 @@ void HTMLFormElement::Trace(Visitor* visitor) { visitor->Trace(radio_button_group_scope_); visitor->Trace(listed_elements_); visitor->Trace(image_elements_); - visitor->Trace(planned_navigation_); - visitor->Trace(activated_submit_button_); HTMLElement::Trace(visitor); } @@ -180,7 +181,7 @@ HTMLElement* HTMLFormElement::item(unsigned index) { return elements()->item(index); } -void HTMLFormElement::SubmitImplicitly(Event& event, +void HTMLFormElement::SubmitImplicitly(const Event& event, bool from_implicit_submission_trigger) { int submission_trigger_count = 0; bool seen_default_button = false; @@ -222,7 +223,7 @@ bool HTMLFormElement::ValidateInteractively() { // Needs to update layout now because we'd like to call isFocusable(), which // has !layoutObject()->needsLayout() assertion. - GetDocument().UpdateStyleAndLayout(); + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kFocus); // Focus on the first focusable control and show a validation message. for (const auto& unhandled : unhandled_invalid_controls) { @@ -241,31 +242,31 @@ bool HTMLFormElement::ValidateInteractively() { String message( "An invalid form control with name='%name' is not focusable."); message.Replace("%name", unhandled->GetName()); - GetDocument().AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kRendering, - mojom::ConsoleMessageLevel::kError, message)); + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kRendering, + mojom::ConsoleMessageLevel::kError, message)); } } return false; } void HTMLFormElement::PrepareForSubmission( - Event* event, + const Event* event, HTMLFormControlElement* submit_button) { LocalFrame* frame = GetDocument().GetFrame(); if (!frame || is_submitting_ || in_user_js_submit_event_) return; if (!isConnected()) { - GetDocument().AddConsoleMessage(ConsoleMessage::Create( + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kWarning, "Form submission canceled because the form is not connected")); return; } - if (GetDocument().IsSandboxed(WebSandboxFlags::kForms)) { - GetDocument().AddConsoleMessage(ConsoleMessage::Create( + if (GetDocument().IsSandboxed(mojom::blink::WebSandboxFlags::kForms)) { + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kSecurity, mojom::ConsoleMessageLevel::kError, "Blocked form submission to '" + attributes_.Action() + @@ -282,7 +283,7 @@ void HTMLFormElement::PrepareForSubmission( WebFeature::kFormSubmittedWithUnclosedFormControl); if (RuntimeEnabledFeatures::UnclosedFormControlIsInvalidEnabled()) { String tag_name = To<HTMLFormControlElement>(element)->tagName(); - GetDocument().AddConsoleMessage(ConsoleMessage::Create( + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kSecurity, mojom::ConsoleMessageLevel::kError, "Form submission failed, as the <" + tag_name + @@ -314,41 +315,23 @@ void HTMLFormElement::PrepareForSubmission( should_submit = false; } else { frame->Client()->DispatchWillSendSubmitEvent(this); - should_submit = - DispatchEvent(*Event::CreateCancelableBubble( - event_type_names::kSubmit)) == DispatchEventResult::kNotCanceled; + SubmitEventInit* submit_event_init = SubmitEventInit::Create(); + submit_event_init->setBubbles(true); + submit_event_init->setCancelable(true); + submit_event_init->setSubmitter( + submit_button ? &submit_button->ToHTMLElement() : nullptr); + should_submit = DispatchEvent(*MakeGarbageCollected<SubmitEvent>( + event_type_names::kSubmit, submit_event_init)) == + DispatchEventResult::kNotCanceled; } } if (should_submit) { - planned_navigation_ = nullptr; - Submit(event, submit_button); + ScheduleFormSubmission(event, submit_button); } - if (!planned_navigation_ || activated_submit_button_) - return; - base::AutoReset<bool> submit_scope(&is_submitting_, true); - SubmitForm(planned_navigation_); - planned_navigation_ = nullptr; -} - -void HTMLFormElement::WillActivateSubmitButton( - HTMLFormControlElement* element) { - if (!activated_submit_button_) - activated_submit_button_ = element; -} - -void HTMLFormElement::DidActivateSubmitButton(HTMLFormControlElement* element) { - if (activated_submit_button_ != element) - return; - activated_submit_button_ = nullptr; - if (!planned_navigation_) - return; - base::AutoReset<bool> submit_scope(&is_submitting_, true); - SubmitForm(planned_navigation_); - planned_navigation_ = nullptr; } void HTMLFormElement::submitFromJavaScript() { - Submit(nullptr, nullptr); + ScheduleFormSubmission(nullptr, nullptr); } void HTMLFormElement::requestSubmit(ExceptionState& exception_state) { @@ -362,7 +345,7 @@ void HTMLFormElement::requestSubmit(HTMLElement* submitter, // 1. If submitter was given, then: if (submitter) { // 1.1. If submitter is not a submit button, then throw a TypeError. - control = ToHTMLFormControlElementOrNull(submitter); + control = DynamicTo<HTMLFormControlElement>(submitter); // button[type] is a subset of input[type]. So it's ok to compare button's // type and input_type_names. if (!control || (control->type() != input_type_names::kSubmit && @@ -393,8 +376,9 @@ void HTMLFormElement::SubmitDialog(FormSubmission* form_submission) { } } -void HTMLFormElement::Submit(Event* event, - HTMLFormControlElement* submit_button) { +void HTMLFormElement::ScheduleFormSubmission( + const Event* event, + HTMLFormControlElement* submit_button) { LocalFrameView* view = GetDocument().View(); LocalFrame* frame = GetDocument().GetFrame(); if (!view || !frame || !frame->GetPage()) @@ -405,7 +389,7 @@ void HTMLFormElement::Submit(Event* event, // or its active sandboxing flag set has its sandboxed forms browsing // context flag set, then abort these steps without doing anything. if (!isConnected()) { - GetDocument().AddConsoleMessage(ConsoleMessage::Create( + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kWarning, "Form submission canceled because the form is not connected")); @@ -413,11 +397,11 @@ void HTMLFormElement::Submit(Event* event, } if (is_constructing_entry_list_) { - GetDocument().AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kJavaScript, - mojom::ConsoleMessageLevel::kWarning, - "Form submission canceled because the form is " - "constructing entry list")); + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kJavaScript, + mojom::ConsoleMessageLevel::kWarning, + "Form submission canceled because the form is " + "constructing entry list")); return; } @@ -447,25 +431,90 @@ void HTMLFormElement::Submit(Event* event, FormSubmission* form_submission = FormSubmission::Create(this, attributes_, event, submit_button); + Frame* target_frame = form_submission->TargetFrame(); + // 'formdata' event handlers might disconnect the form. if (!isConnected()) { - GetDocument().AddConsoleMessage(ConsoleMessage::Create( + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kWarning, "Form submission canceled because the form is not connected")); return; } + if (form_submission->Method() == FormSubmission::kDialogMethod) { SubmitDialog(form_submission); - } else if (in_user_js_submit_event_ || activated_submit_button_) { - // Need to postpone the submission in order to make this cancelable by - // another submission request. - planned_navigation_ = form_submission; - } else { - // This runs JavaScript code if action attribute value is javascript: - // protocol. - SubmitForm(form_submission); + return; + } + + DCHECK(form_submission->Method() == FormSubmission::kPostMethod || + form_submission->Method() == FormSubmission::kGetMethod); + DCHECK(form_submission->Data()); + DCHECK(form_submission->Form()); + if (form_submission->Action().IsEmpty()) + return; + if (GetDocument().IsSandboxed(mojom::blink::WebSandboxFlags::kForms)) { + // FIXME: This message should be moved off the console once a solution to + // https://bugs.webkit.org/show_bug.cgi?id=103274 exists. + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( + mojom::blink::ConsoleMessageSource::kSecurity, + mojom::blink::ConsoleMessageLevel::kError, + "Blocked form submission to '" + + form_submission->Action().ElidedString() + + "' because the form's frame is sandboxed and the 'allow-forms' " + "permission is not set.")); + return; + } + + if (!GetDocument().GetContentSecurityPolicy()->AllowFormAction( + form_submission->Action())) { + return; + } + + UseCounter::Count(GetDocument(), WebFeature::kFormsSubmitted); + if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(), + form_submission->Action())) { + UseCounter::Count(GetDocument(), WebFeature::kMixedContentFormsSubmitted); + } + if (FastHasAttribute(html_names::kDisabledAttr)) { + UseCounter::Count(GetDocument(), + WebFeature::kFormDisabledAttributePresentAndSubmit); } + + if (!target_frame) + return; + + if (form_submission->Action().ProtocolIsJavaScript()) { + // For javascript urls, don't post a task to execute the form submission + // because we already get another task posted for it in + // Document::ProcessJavascriptUrl. If we post two tasks, the javascript will + // be run too late according to some tests. + form_submission->Navigate(); + return; + } + + FrameScheduler* scheduler = GetDocument().GetFrame()->GetFrameScheduler(); + + if (auto* target_local_frame = DynamicTo<LocalFrame>(target_frame)) { + if (!target_local_frame->IsNavigationAllowed()) + return; + + // Cancel parsing if the form submission is targeted at this frame. + if (target_local_frame == GetDocument().GetFrame() && + !form_submission->Action().ProtocolIsJavaScript()) { + target_local_frame->GetDocument()->CancelParsing(); + } + + // Use the target frame's frame scheduler. If we can't due to targeting a + // RemoteFrame, then use the frame scheduler from the frame this form is in. + scheduler = target_local_frame->GetFrameScheduler(); + + // Cancel pending javascript url navigations for the target frame. This new + // form submission should take precedence over them. + target_local_frame->GetDocument()->CancelPendingJavaScriptUrls(); + } + + target_frame->ScheduleFormSubmission(scheduler, form_submission); } FormData* HTMLFormElement::ConstructEntryList( @@ -496,46 +545,6 @@ FormData* HTMLFormElement::ConstructEntryList( return &form_data; } -// Actually submit the form - navigate now. -void HTMLFormElement::SubmitForm(FormSubmission* submission) { - DCHECK(submission->Method() == FormSubmission::kPostMethod || - submission->Method() == FormSubmission::kGetMethod); - DCHECK(submission->Data()); - DCHECK(submission->Form()); - if (submission->Action().IsEmpty()) - return; - if (!GetDocument().IsActive()) - return; - if (GetDocument().IsSandboxed(WebSandboxFlags::kForms)) { - // FIXME: This message should be moved off the console once a solution to - // https://bugs.webkit.org/show_bug.cgi?id=103274 exists. - GetDocument().AddConsoleMessage(ConsoleMessage::Create( - mojom::ConsoleMessageSource::kSecurity, - mojom::ConsoleMessageLevel::kError, - "Blocked form submission to '" + submission->Action().ElidedString() + - "' because the form's frame is sandboxed and the 'allow-forms' " - "permission is not set.")); - return; - } - - if (!GetDocument().GetContentSecurityPolicy()->AllowFormAction( - submission->Action())) { - return; - } - - UseCounter::Count(GetDocument(), WebFeature::kFormsSubmitted); - if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(), - submission->Action())) { - UseCounter::Count(GetDocument(), WebFeature::kMixedContentFormsSubmitted); - } - if (FastHasAttribute(html_names::kDisabledAttr)) { - UseCounter::Count(GetDocument(), - WebFeature::kFormDisabledAttributePresentAndSubmit); - } - - submission->Navigate(); -} - void HTMLFormElement::reset() { LocalFrame* frame = GetDocument().GetFrame(); if (is_in_reset_function_ || !frame) @@ -573,7 +582,9 @@ void HTMLFormElement::ParseAttribute( // If we're not upgrading insecure requests, and the new action attribute is // pointing to an insecure "action" location from a secure page it is marked // as "passive" mixed content. - if (GetDocument().GetInsecureRequestPolicy() & kUpgradeInsecureRequests) + if ((GetDocument().GetSecurityContext().GetInsecureRequestPolicy() & + mojom::blink::InsecureRequestPolicy::kUpgradeInsecureRequests) != + mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone) return; KURL action_url = GetDocument().CompleteURL( attributes_.Action().IsEmpty() ? GetDocument().Url().GetString() @@ -608,11 +619,6 @@ void HTMLFormElement::Disassociate(ListedElement& e) { listed_elements_are_dirty_ = true; listed_elements_.clear(); RemoveFromPastNamesMap(e.ToHTMLElement()); - - if (activated_submit_button_ != &e) - return; - activated_submit_button_ = nullptr; - planned_navigation_ = nullptr; } bool HTMLFormElement::IsURLAttribute(const Attribute& attribute) const { @@ -835,7 +841,7 @@ void HTMLFormElement::GetNamedElements( } bool HTMLFormElement::ShouldAutocomplete() const { - return !DeprecatedEqualIgnoringCase( + return !EqualIgnoringASCIICase( FastGetAttribute(html_names::kAutocompleteAttr), "off"); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h index 3375fb2eb17..620c05a3cc9 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h @@ -71,13 +71,14 @@ class CORE_EXPORT HTMLFormElement final : public HTMLElement { void Disassociate(HTMLImageElement&); void DidAssociateByParser(); - void PrepareForSubmission(Event*, HTMLFormControlElement* submit_button); + void PrepareForSubmission(const Event*, + HTMLFormControlElement* submit_button); void submitFromJavaScript(); void requestSubmit(ExceptionState& exception_state); void requestSubmit(HTMLElement* submitter, ExceptionState& exception_state); void reset(); - void SubmitImplicitly(Event&, bool from_implicit_submission_trigger); + void SubmitImplicitly(const Event&, bool from_implicit_submission_trigger); String GetName() const; @@ -115,12 +116,6 @@ class CORE_EXPORT HTMLFormElement final : public HTMLElement { unsigned UniqueRendererFormId() const { return unique_renderer_form_id_; } - // TODO(crbug.com/1013385): Remove WillActivateSubmitButton, - // DidActivateSubmitButton, and RemovedAssociatedControlElement. They are - // here temporarily to fix form double-submit. - void WillActivateSubmitButton(HTMLFormControlElement* element); - void DidActivateSubmitButton(HTMLFormControlElement* element); - private: InsertionNotificationRequest InsertedInto(ContainerNode&) override; void RemovedFrom(ContainerNode&) override; @@ -137,9 +132,8 @@ class CORE_EXPORT HTMLFormElement final : public HTMLElement { } void SubmitDialog(FormSubmission*); - void Submit(Event*, HTMLFormControlElement* submit_button); - - void SubmitForm(FormSubmission*); + void ScheduleFormSubmission(const Event*, + HTMLFormControlElement* submit_button); void CollectListedElements(Node& root, ListedElement::List&) const; void CollectImageElements(Node& root, HeapVector<Member<HTMLImageElement>>&); @@ -168,11 +162,6 @@ class CORE_EXPORT HTMLFormElement final : public HTMLElement { // Do not access image_elements_ directly. Use ImageElements() instead. HeapVector<Member<HTMLImageElement>> image_elements_; - // https://html.spec.whatwg.org/C/#planned-navigation - // Unlike the specification, we use this only for web-exposed submit() - // function in 'submit' event handler. - Member<FormSubmission> planned_navigation_; - unsigned unique_renderer_form_id_; bool is_submitting_ = false; @@ -185,8 +174,6 @@ class CORE_EXPORT HTMLFormElement final : public HTMLElement { bool has_elements_associated_by_form_attribute_ : 1; bool did_finish_parsing_children_ : 1; bool is_in_reset_function_ : 1; - - Member<HTMLFormControlElement> activated_submit_button_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc index 2ef0a61f534..ce0b6a33afb 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc @@ -32,7 +32,6 @@ #include "third_party/blink/public/mojom/choosers/date_time_chooser.mojom-blink.h" #include "third_party/blink/public/platform/task_type.h" -#include "third_party/blink/public/platform/web_scroll_into_view_params.h" #include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h" #include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h" @@ -50,6 +49,7 @@ #include "third_party/blink/renderer/core/events/before_text_inserted_event.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" #include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/fileapi/file_list.h" #include "third_party/blink/renderer/core/frame/deprecation.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" @@ -81,6 +81,7 @@ #include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" #include "third_party/blink/renderer/platform/wtf/math_extras.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -185,27 +186,27 @@ bool HTMLInputElement::IsValidValue(const String& value) const { } bool HTMLInputElement::TooLong() const { - return willValidate() && TooLong(value(), kCheckDirtyFlag); + return TooLong(value(), kCheckDirtyFlag); } bool HTMLInputElement::TooShort() const { - return willValidate() && TooShort(value(), kCheckDirtyFlag); + return TooShort(value(), kCheckDirtyFlag); } bool HTMLInputElement::TypeMismatch() const { - return willValidate() && input_type_->TypeMismatch(); + return input_type_->TypeMismatch(); } bool HTMLInputElement::ValueMissing() const { - return willValidate() && input_type_->ValueMissing(value()); + return input_type_->ValueMissing(value()); } bool HTMLInputElement::HasBadInput() const { - return willValidate() && input_type_view_->HasBadInput(); + return input_type_view_->HasBadInput(); } bool HTMLInputElement::PatternMismatch() const { - return willValidate() && input_type_->PatternMismatch(value()); + return input_type_->PatternMismatch(value()); } bool HTMLInputElement::TooLong(const String& value, @@ -219,17 +220,16 @@ bool HTMLInputElement::TooShort(const String& value, } bool HTMLInputElement::RangeUnderflow() const { - return willValidate() && input_type_->RangeUnderflow(value()); + return input_type_->RangeUnderflow(value()); } bool HTMLInputElement::RangeOverflow() const { - return willValidate() && input_type_->RangeOverflow(value()); + return input_type_->RangeOverflow(value()); } String HTMLInputElement::validationMessage() const { if (!willValidate()) return String(); - if (CustomError()) return CustomValidationMessage(); @@ -237,7 +237,7 @@ String HTMLInputElement::validationMessage() const { } String HTMLInputElement::ValidationSubMessage() const { - if (!willValidate() || CustomError()) + if (CustomError()) return String(); return input_type_->ValidationMessage(*input_type_view_).second; } @@ -251,7 +251,7 @@ double HTMLInputElement::Maximum() const { } bool HTMLInputElement::StepMismatch() const { - return willValidate() && input_type_->StepMismatch(value()); + return input_type_->StepMismatch(value()); } bool HTMLInputElement::GetAllowedValueStep(Decimal* step) const { @@ -295,6 +295,16 @@ bool HTMLInputElement::MayTriggerVirtualKeyboard() const { return input_type_->MayTriggerVirtualKeyboard(); } +bool HTMLInputElement::ShouldHaveFocusAppearance() const { + // For FormControlsRefresh don't draw focus ring for an input that has its + // popup open. + if (::features::IsFormControlsRefreshEnabled() && + input_type_view_->HasOpenedPopup()) + return false; + + return TextControlElement::ShouldHaveFocusAppearance(); +} + void HTMLInputElement::UpdateFocusAppearanceWithOptions( SelectionBehaviorOnFocus selection_behavior, const FocusOptions* options) { @@ -312,11 +322,13 @@ void HTMLInputElement::UpdateFocusAppearanceWithOptions( // TODO(tkent): scrollRectToVisible is a workaround of a bug of // FrameSelection::revealSelection(). It doesn't scroll correctly in a // case of RangeSelection. crbug.com/443061. - GetDocument().EnsurePaintLocationDataValidForNode(this); + GetDocument().EnsurePaintLocationDataValidForNode( + this, DocumentUpdateReason::kFocus); if (!options->preventScroll()) { if (GetLayoutObject()) { - GetLayoutObject()->ScrollRectToVisible(BoundingBoxForScrollIntoView(), - WebScrollIntoViewParams()); + GetLayoutObject()->ScrollRectToVisible( + BoundingBoxForScrollIntoView(), + ScrollAlignment::CreateScrollIntoViewParams()); } if (GetDocument().GetFrame()) GetDocument().GetFrame()->Selection().RevealSelection(); @@ -343,7 +355,7 @@ void HTMLInputElement::EndEditing() { void HTMLInputElement::DispatchFocusInEvent( const AtomicString& event_type, Element* old_focused_element, - WebFocusType type, + mojom::blink::FocusType type, InputDeviceCapabilities* source_capabilities) { if (event_type == event_type_names::kDOMFocusIn) input_type_view_->HandleFocusInEvent(old_focused_element, type); @@ -597,6 +609,20 @@ bool HTMLInputElement::CanStartSelection() const { return TextControlElement::CanStartSelection(); } +base::Optional<uint32_t> HTMLInputElement::selectionStartForBinding( + ExceptionState& exception_state) const { + if (!input_type_->SupportsSelectionAPI()) + return base::nullopt; + return TextControlElement::selectionStart(); +} + +base::Optional<uint32_t> HTMLInputElement::selectionEndForBinding( + ExceptionState& exception_state) const { + if (!input_type_->SupportsSelectionAPI()) + return base::nullopt; + return TextControlElement::selectionEnd(); +} + unsigned HTMLInputElement::selectionStartForBinding( bool& is_null, ExceptionState& exception_state) const { @@ -626,6 +652,32 @@ String HTMLInputElement::selectionDirectionForBinding( } void HTMLInputElement::setSelectionStartForBinding( + base::Optional<uint32_t> start, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + TextControlElement::setSelectionStart(start.value_or(0)); +} + +void HTMLInputElement::setSelectionEndForBinding( + base::Optional<uint32_t> end, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + TextControlElement::setSelectionEnd(end.value_or(0)); +} + +void HTMLInputElement::setSelectionStartForBinding( unsigned start, bool is_null, ExceptionState& exception_state) { @@ -751,7 +803,7 @@ void HTMLInputElement::ParseAttribute( AddToRadioButtonGroup(); TextControlElement::ParseAttribute(params); } else if (name == html_names::kAutocompleteAttr) { - if (DeprecatedEqualIgnoringCase(value, "off")) { + if (EqualIgnoringASCIICase(value, "off")) { autocomplete_ = kOff; } else { if (value.IsEmpty()) @@ -802,7 +854,7 @@ void HTMLInputElement::ParseAttribute( size_ = size; if (GetLayoutObject()) { GetLayoutObject() - ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( layout_invalidation_reason::kAttributeChanged); } } @@ -885,6 +937,11 @@ bool HTMLInputElement::LayoutObjectIsNeeded(const ComputedStyle& style) const { TextControlElement::LayoutObjectIsNeeded(style); } +// TODO(crbug.com/1040826): Remove this override. +bool HTMLInputElement::TypeShouldForceLegacyLayout() const { + return input_type_view_->TypeShouldForceLegacyLayout(); +} + LayoutObject* HTMLInputElement::CreateLayoutObject(const ComputedStyle& style, LegacyLayout legacy) { return input_type_view_->CreateLayoutObject(style, legacy); @@ -961,12 +1018,13 @@ bool HTMLInputElement::HasBeenPasswordField() const { } void HTMLInputElement::DispatchChangeEventIfNeeded() { - if (input_type_->ShouldSendChangeEventAfterCheckedChanged()) + if (isConnected() && input_type_->ShouldSendChangeEventAfterCheckedChanged()) DispatchChangeEvent(); } void HTMLInputElement::DispatchInputAndChangeEventIfNeeded() { - if (input_type_->ShouldSendChangeEventAfterCheckedChanged()) { + if (isConnected() && + input_type_->ShouldSendChangeEventAfterCheckedChanged()) { DispatchInputEvent(); DispatchChangeEvent(); } @@ -983,6 +1041,7 @@ void HTMLInputElement::setChecked(bool now_checked, if (checked() == now_checked) return; + input_type_->WillUpdateCheckedness(now_checked); is_checked_ = now_checked; if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) @@ -1069,6 +1128,10 @@ String HTMLInputElement::value() const { return g_empty_string; } +String HTMLInputElement::rawValue() const { + return input_type_view_->RawValue(); +} + String HTMLInputElement::ValueOrDefaultLabel() const { String value = this->value(); if (!value.IsNull()) @@ -1150,23 +1213,6 @@ void HTMLInputElement::setValue(const String& value, if (value_changed) NotifyFormStateChanged(); - - if (isConnected()) { - if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) { - auto* page = GetDocument().GetPage(); - auto* view = GetDocument().View(); - // Run the document lifecycle to ensure AX notifications fire, - // even if the value didn't change. - if (page && view) { - // TODO(aboxhall): add a lifecycle phase for accessibility updates. - if (!view->CanThrottleRendering()) - page->Animator().ScheduleVisualUpdate(GetDocument().GetFrame()); - - GetDocument().Lifecycle().EnsureStateAtMost( - DocumentLifecycle::kVisualUpdatePending); - } - } - } } void HTMLInputElement::SetNonAttributeValue(const String& sanitized_value) { @@ -1214,7 +1260,7 @@ void HTMLInputElement::setValueAsDate(ScriptState* script_state, ExceptionState& exception_state) { UseCounter::Count(GetDocument(), WebFeature::kInputElementValueAsDateSetter); base::Optional<base::Time> date = - NativeValueTraits<IDLDateOrNull>::NativeValue( + NativeValueTraits<IDLNullable<IDLDate>>::NativeValue( script_state->GetIsolate(), value.V8Value(), exception_state); if (exception_state.HadException()) return; @@ -1278,12 +1324,12 @@ EventDispatchHandlingState* HTMLInputElement::PreDispatchEventHandler( } if (event.type() != event_type_names::kClick) return nullptr; - if (!event.IsMouseEvent() || - ToMouseEvent(event).button() != + + auto* mouse_event = DynamicTo<MouseEvent>(event); + if (!mouse_event || + mouse_event->button() != static_cast<int16_t>(WebPointerProperties::Button::kLeft)) return nullptr; - if (formOwner() && CanBeSuccessfulSubmitButton()) - formOwner()->WillActivateSubmitButton(this); return input_type_view_->WillDispatchClick(); } @@ -1296,29 +1342,19 @@ void HTMLInputElement::PostDispatchEventHandler( *static_cast<ClickHandlingState*>(state)); } -void HTMLInputElement::DidPreventDefault(const Event& event) { - if (auto* form = formOwner()) - form->DidActivateSubmitButton(this); -} - -void HTMLInputElement::DefaultEventHandler(Event& event) { - DefaultEventHandlerInternal(event); - - if (event.type() == event_type_names::kDOMActivate && formOwner()) - formOwner()->DidActivateSubmitButton(this); -} - -void HTMLInputElement::DefaultEventHandlerInternal(Event& evt) { - if (evt.IsMouseEvent() && evt.type() == event_type_names::kClick && - ToMouseEvent(evt).button() == +void HTMLInputElement::DefaultEventHandler(Event& evt) { + auto* mouse_event = DynamicTo<MouseEvent>(evt); + if (mouse_event && evt.type() == event_type_names::kClick && + mouse_event->button() == static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { - input_type_view_->HandleClickEvent(ToMouseEvent(evt)); + input_type_view_->HandleClickEvent(To<MouseEvent>(evt)); if (evt.DefaultHandled()) return; } - if (evt.IsKeyboardEvent() && evt.type() == event_type_names::kKeydown) { - input_type_view_->HandleKeydownEvent(ToKeyboardEvent(evt)); + auto* keyboad_event = DynamicTo<KeyboardEvent>(evt); + if (keyboad_event && evt.type() == event_type_names::kKeydown) { + input_type_view_->HandleKeydownEvent(*keyboad_event); if (evt.DefaultHandled()) return; } @@ -1349,14 +1385,14 @@ void HTMLInputElement::DefaultEventHandlerInternal(Event& evt) { // Use key press event here since sending simulated mouse events // on key down blocks the proper sending of the key press event. - if (evt.IsKeyboardEvent() && evt.type() == event_type_names::kKeypress) { - input_type_view_->HandleKeypressEvent(ToKeyboardEvent(evt)); + if (keyboad_event && evt.type() == event_type_names::kKeypress) { + input_type_view_->HandleKeypressEvent(*keyboad_event); if (evt.DefaultHandled()) return; } - if (evt.IsKeyboardEvent() && evt.type() == event_type_names::kKeyup) { - input_type_view_->HandleKeyupEvent(ToKeyboardEvent(evt)); + if (keyboad_event && evt.type() == event_type_names::kKeyup) { + input_type_view_->HandleKeyupEvent(*keyboad_event); if (evt.DefaultHandled()) return; } @@ -1390,8 +1426,8 @@ void HTMLInputElement::DefaultEventHandlerInternal(Event& evt) { static_cast<BeforeTextInsertedEvent&>(evt)); } - if (evt.IsMouseEvent() && evt.type() == event_type_names::kMousedown) { - input_type_view_->HandleMouseDownEvent(ToMouseEvent(evt)); + if (mouse_event && evt.type() == event_type_names::kMousedown) { + input_type_view_->HandleMouseDownEvent(*mouse_event); if (evt.DefaultHandled()) return; } @@ -1540,8 +1576,12 @@ void HTMLInputElement::SetCanReceiveDroppedFiles( if (!!can_receive_dropped_files_ == can_receive_dropped_files) return; can_receive_dropped_files_ = can_receive_dropped_files; - if (GetLayoutObject()) - GetLayoutObject()->UpdateFromElement(); + if (HTMLInputElement* button = UploadButton()) + button->SetActive(can_receive_dropped_files); +} + +HTMLInputElement* HTMLInputElement::UploadButton() const { + return input_type_view_->UploadButton(); } String HTMLInputElement::SanitizeValue(const String& proposed_value) const { @@ -1647,9 +1687,8 @@ void HTMLInputElement::SelectColorInColorChooser(const Color& color) { client->DidChooseColor(color); } -void HTMLInputElement::EndColorChooser() { - if (ColorChooserClient* client = input_type_->GetColorChooserClient()) - client->DidEndChooser(); +void HTMLInputElement::EndColorChooserForTesting() { + input_type_view_->ClosePopupView(); } HTMLElement* HTMLInputElement::list() const { @@ -1779,24 +1818,16 @@ String HTMLInputElement::DefaultToolTip() const { return input_type_->DefaultToolTip(*input_type_view_); } -bool HTMLInputElement::ShouldAppearIndeterminate() const { - return input_type_->ShouldAppearIndeterminate(); +String HTMLInputElement::FileStatusText() const { + return input_type_view_->FileStatusText(); } -bool HTMLInputElement::IsInRequiredRadioButtonGroup() { - // TODO(tkent): Remove type check. - DCHECK_EQ(type(), input_type_names::kRadio); - if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) - return scope->IsInRequiredGroup(this); - return false; +bool HTMLInputElement::ShouldApplyMiddleEllipsis() const { + return files() && files()->length() <= 1; } -HTMLInputElement* HTMLInputElement::CheckedRadioButtonForGroup() { - if (checked()) - return this; - if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) - return scope->CheckedButtonForGroup(GetName()); - return nullptr; +bool HTMLInputElement::ShouldAppearIndeterminate() const { + return input_type_->ShouldAppearIndeterminate(); } RadioButtonGroupScope* HTMLInputElement::GetRadioButtonGroupScope() const { @@ -1962,8 +1993,9 @@ bool HTMLInputElement::IsInteractiveContent() const { } scoped_refptr<ComputedStyle> HTMLInputElement::CustomStyleForLayoutObject() { - return input_type_view_->CustomStyleForLayoutObject( - OriginalStyleForLayoutObject()); + scoped_refptr<ComputedStyle> style = OriginalStyleForLayoutObject(); + input_type_view_->CustomStyleForLayoutObject(*style); + return style; } void HTMLInputElement::DidRecalcStyle(const StyleRecalcChange change) { @@ -2013,4 +2045,8 @@ PaintLayerScrollableArea* HTMLInputElement::GetScrollableArea() const { return Element::GetScrollableArea(); } +bool HTMLInputElement::IsDraggedSlider() const { + return input_type_view_->IsDraggedSlider(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h index 67291f93ee4..9b0f10e05d9 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h @@ -26,6 +26,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ #include "base/gtest_prod_util.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h" #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/create_element_flags.h" @@ -127,6 +128,9 @@ class CORE_EXPORT HTMLInputElement bool ShouldAppearChecked() const; bool ShouldAppearIndeterminate() const override; + // Returns null if this isn't associated with any radio button group. + RadioButtonGroupScope* GetRadioButtonGroupScope() const; + unsigned size() const; bool SizeShouldIncludeDecoration(int& preferred_size) const; @@ -151,6 +155,8 @@ class CORE_EXPORT HTMLInputElement bool IsValidValue(const String&) const; bool HasDirtyValue() const; + String rawValue() const; + String SanitizeValue(const String&) const; String LocalizeValue(const String&) const; @@ -177,11 +183,22 @@ class CORE_EXPORT HTMLInputElement // delay the 'input' event with EventQueueScope. void SetValueFromRenderer(const String&); - unsigned selectionStartForBinding(bool&, ExceptionState&) const; - unsigned selectionEndForBinding(bool&, ExceptionState&) const; + base::Optional<uint32_t> selectionStartForBinding(ExceptionState&) const; + base::Optional<uint32_t> selectionEndForBinding(ExceptionState&) const; + // TODO(crbug.com/1060971): Remove |is_null| version. + unsigned selectionStartForBinding(bool&, + ExceptionState&) const; // DEPRECATED + unsigned selectionEndForBinding(bool&, ExceptionState&) const; // DEPRECATED String selectionDirectionForBinding(ExceptionState&) const; - void setSelectionStartForBinding(unsigned, bool is_null, ExceptionState&); - void setSelectionEndForBinding(unsigned, bool is_null, ExceptionState&); + void setSelectionStartForBinding(base::Optional<uint32_t>, ExceptionState&); + void setSelectionEndForBinding(base::Optional<uint32_t>, ExceptionState&); + // TODO(crbug.com/1060971): Remove |is_null| version. + void setSelectionStartForBinding(unsigned, + bool is_null, + ExceptionState&); // DEPRECATED + void setSelectionEndForBinding(unsigned, + bool is_null, + ExceptionState&); // DEPRECATED void setSelectionDirectionForBinding(const String&, ExceptionState&); void setSelectionRangeForBinding(unsigned start, unsigned end, @@ -232,6 +249,10 @@ class CORE_EXPORT HTMLInputElement bool CanReceiveDroppedFiles() const; void SetCanReceiveDroppedFiles(bool); + // Returns 'Choose File(s)' button in a file control. This returns + // nullptr for other input types. + HTMLInputElement* UploadButton() const; + void OnSearch(); void UpdateClearButtonVisibility(); @@ -245,9 +266,6 @@ class CORE_EXPORT HTMLInputElement // Associated <datalist> options which match to the current INPUT value. HeapVector<Member<HTMLOptionElement>> FilteredDataListOptions() const; - HTMLInputElement* CheckedRadioButtonForGroup(); - bool IsInRequiredRadioButtonGroup(); - // Functions for InputType classes. void SetNonAttributeValue(const String&); void SetNonAttributeValueByUserEdit(const String&); @@ -257,10 +275,18 @@ class CORE_EXPORT HTMLInputElement // For test purposes. void SelectColorInColorChooser(const Color&); - void EndColorChooser(); + void EndColorChooserForTesting(); String DefaultToolTip() const override; + // Type=file only: Text not in the button such as "No file chosen". The string + // is not truncated by ellipsis. + // Return a null string for other types. + String FileStatusText() const; + // Returns true if an ellipsis should be injected at the middle of the text. + // This function is called only if text-overflow:ellipsis is specified. + bool ShouldApplyMiddleEllipsis() const; + unsigned height() const; unsigned width() const; void setHeight(unsigned); @@ -315,6 +341,8 @@ class CORE_EXPORT HTMLInputElement void SetHasBeenPasswordField() { has_been_password_field_ = true; } + bool IsDraggedSlider() const; + protected: void DefaultEventHandler(Event&) override; void CreateShadowSubtree(); @@ -332,6 +360,7 @@ class CORE_EXPORT HTMLInputElement bool HasCustomFocusLogic() const final; bool IsKeyboardFocusable() const final; bool MayTriggerVirtualKeyboard() const final; + bool ShouldHaveFocusAppearance() const final; bool IsEnumeratable() const final; bool IsInteractiveContent() const final; bool IsLabelable() const final; @@ -361,6 +390,7 @@ class CORE_EXPORT HTMLInputElement void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) final; + bool TypeShouldForceLegacyLayout() const final; void AttachLayoutTree(AttachContext&) final; void AppendToFormData(FormData&) final; @@ -372,11 +402,6 @@ class CORE_EXPORT HTMLInputElement EventDispatchHandlingState* PreDispatchEventHandler(Event&) final; void PostDispatchEventHandler(Event&, EventDispatchHandlingState*) final; - // TODO(crbug.com/1013385): Remove DidPreventDefault and - // DefaultEventHandlerInternal. They are here as a temporary fix for form - // double-submit. - void DidPreventDefault(const Event&) final; - void DefaultEventHandlerInternal(Event& evt); bool IsURLAttribute(const Attribute&) const final; bool HasLegalLinkAttribute(const QualifiedName&) const final; @@ -392,7 +417,7 @@ class CORE_EXPORT HTMLInputElement void HandleBlurEvent() final; void DispatchFocusInEvent(const AtomicString& event_type, Element* old_focused_element, - WebFocusType, + mojom::blink::FocusType, InputDeviceCapabilities* source_capabilities) final; bool IsOptionalFormControl() const final { return !IsRequiredFormControl(); } @@ -409,8 +434,6 @@ class CORE_EXPORT HTMLInputElement void SetListAttributeTargetObserver(ListAttributeTargetObserver*); void ResetListAttributeTargetObserver(); - // Returns null if this isn't associated with any radio button group. - RadioButtonGroupScope* GetRadioButtonGroupScope() const; void AddToRadioButtonGroup(); void RemoveFromRadioButtonGroup(); scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl index 48bb6afc2a6..3c153f49fd6 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl @@ -81,6 +81,8 @@ enum SelectionMode { "select", "start", "end", "preserve" }; readonly attribute NodeList labels; + [RuntimeEnabled=InputElementRawValue] readonly attribute DOMString rawValue; + void select(); [RaisesException, ImplementedAs=selectionStartForBinding] attribute unsigned long? selectionStart; [RaisesException, ImplementedAs=selectionEndForBinding] attribute unsigned long? selectionEnd; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc index f14da1cfcd8..baa2c174681 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc @@ -7,9 +7,9 @@ #include <memory> #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_keyboard_event_init.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" -#include "third_party/blink/renderer/core/events/keyboard_event_init.h" #include "third_party/blink/renderer/core/fileapi/file_list.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/visual_viewport.h" @@ -35,16 +35,16 @@ class HTMLInputElementTest : public PageTestBase { }; TEST_F(HTMLInputElementTest, FilteredDataListOptionsNoList) { - GetDocument().documentElement()->SetInnerHTMLFromString("<input id=test>"); + GetDocument().documentElement()->setInnerHTML("<input id=test>"); EXPECT_TRUE(TestElement().FilteredDataListOptions().IsEmpty()); - GetDocument().documentElement()->SetInnerHTMLFromString( + GetDocument().documentElement()->setInnerHTML( "<input id=test list=dl1><datalist id=dl1></datalist>"); EXPECT_TRUE(TestElement().FilteredDataListOptions().IsEmpty()); } TEST_F(HTMLInputElementTest, FilteredDataListOptionsContain) { - GetDocument().documentElement()->SetInnerHTMLFromString( + GetDocument().documentElement()->setInnerHTML( "<input id=test value=BC list=dl2>" "<datalist id=dl2>" "<option>AbC DEF</option>" @@ -56,7 +56,7 @@ TEST_F(HTMLInputElementTest, FilteredDataListOptionsContain) { EXPECT_EQ("AbC DEF", options[0]->value().Utf8()); EXPECT_EQ("ghi", options[1]->value().Utf8()); - GetDocument().documentElement()->SetInnerHTMLFromString( + GetDocument().documentElement()->setInnerHTML( "<input id=test value=i list=dl2>" "<datalist id=dl2>" "<option>I</option>" @@ -70,7 +70,7 @@ TEST_F(HTMLInputElementTest, FilteredDataListOptionsContain) { } TEST_F(HTMLInputElementTest, FilteredDataListOptionsForMultipleEmail) { - GetDocument().documentElement()->SetInnerHTMLFromString(R"HTML( + GetDocument().documentElement()->setInnerHTML(R"HTML( <input id=test value='foo@example.com, tkent' list=dl3 type=email multiple> <datalist id=dl3> @@ -104,7 +104,7 @@ TEST_F(HTMLInputElementTest, NoAssertWhenMovedInNewDocument) { // Create an input element with type "range" inside a document without frame. To<HTMLBodyElement>(html->firstChild()) - ->SetInnerHTMLFromString("<input type='range' />"); + ->setInnerHTML("<input type='range' />"); document_without_frame->AppendChild(html); auto page_holder = std::make_unique<DummyPageHolder>(); @@ -154,7 +154,7 @@ TEST_F(HTMLInputElementTest, ImageTypeCrash) { TEST_F(HTMLInputElementTest, RadioKeyDownDCHECKFailure) { // crbug.com/697286 - GetDocument().body()->SetInnerHTMLFromString( + GetDocument().body()->setInnerHTML( "<input type=radio name=g><input type=radio name=g>"); auto& radio1 = To<HTMLInputElement>(*GetDocument().body()->firstChild()); auto& radio2 = To<HTMLInputElement>(*radio1.nextSibling()); @@ -171,7 +171,7 @@ TEST_F(HTMLInputElementTest, RadioKeyDownDCHECKFailure) { TEST_F(HTMLInputElementTest, DateTimeChooserSizeParamRespectsScale) { GetDocument().SetCompatibilityMode(Document::kQuirksMode); GetDocument().View()->GetFrame().GetPage()->GetVisualViewport().SetScale(2.f); - GetDocument().body()->SetInnerHTMLFromString( + GetDocument().body()->setInnerHTML( "<input type='date' style='width:200px;height:50px' />"); UpdateAllLifecyclePhasesForTest(); auto* input = To<HTMLInputElement>(GetDocument().body()->firstChild()); @@ -195,13 +195,13 @@ TEST_F(HTMLInputElementTest, StepDownOverflow) { } TEST_F(HTMLInputElementTest, CheckboxHasNoShadowRoot) { - GetDocument().body()->SetInnerHTMLFromString("<input type='checkbox' />"); + GetDocument().body()->setInnerHTML("<input type='checkbox' />"); auto* input = To<HTMLInputElement>(GetDocument().body()->firstChild()); EXPECT_EQ(nullptr, input->UserAgentShadowRoot()); } TEST_F(HTMLInputElementTest, ChangingInputTypeCausesShadowRootToBeCreated) { - GetDocument().body()->SetInnerHTMLFromString("<input type='checkbox' />"); + GetDocument().body()->setInnerHTML("<input type='checkbox' />"); auto* input = To<HTMLInputElement>(GetDocument().body()->firstChild()); EXPECT_EQ(nullptr, input->UserAgentShadowRoot()); input->setAttribute(html_names::kTypeAttr, "text"); @@ -209,7 +209,7 @@ TEST_F(HTMLInputElementTest, ChangingInputTypeCausesShadowRootToBeCreated) { } TEST_F(HTMLInputElementTest, RepaintAfterClearingFile) { - GetDocument().body()->SetInnerHTMLFromString("<input type='file' />"); + GetDocument().body()->setInnerHTML("<input type='file' />"); auto* input = To<HTMLInputElement>(GetDocument().body()->firstChild()); FileChooserFileInfoList files; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc index d7f564f3b8f..1e5895b2211 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc @@ -24,6 +24,7 @@ #include "third_party/blink/renderer/core/html/forms/html_label_element.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/element_traversal.h" #include "third_party/blink/renderer/core/editing/editing_utilities.h" @@ -87,7 +88,7 @@ HTMLElement* HTMLLabelElement::control() const { HTMLFormElement* HTMLLabelElement::form() const { if (HTMLElement* control = this->control()) { - if (auto* form_control_element = ToHTMLFormControlElementOrNull(control)) + if (auto* form_control_element = DynamicTo<HTMLFormControlElement>(control)) return form_control_element->Form(); if (control->IsFormAssociatedCustomElement()) return control->EnsureElementInternals().Form(); @@ -169,7 +170,8 @@ void HTMLLabelElement::DefaultEventHandler(Event& evt) { // click event to control element. // Note: check if it is a MouseEvent because a click event may // not be an instance of a MouseEvent if created by document.createEvent(). - if (evt.IsMouseEvent() && ToMouseEvent(evt).HasPosition()) { + auto* mouse_event = DynamicTo<MouseEvent>(evt); + if (mouse_event && mouse_event->HasPosition()) { if (LocalFrame* frame = GetDocument().GetFrame()) { // Check if there is a selection and click is not on the // selection. @@ -188,14 +190,14 @@ void HTMLLabelElement::DefaultEventHandler(Event& evt) { // should pass click event to control element. // Only in case of drag, *neither* we pass the click event, // *nor* we focus the control element. - if (is_label_text_selected && ToMouseEvent(evt).ClickCount() == 1) + if (is_label_text_selected && mouse_event->ClickCount() == 1) return; } } processing_click_ = true; - GetDocument().UpdateStyleAndLayout(); + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kInput); if (element->IsMouseFocusable()) { // If the label is *not* selected, or if the click happened on // selection of label, only then focus the control element. @@ -203,7 +205,7 @@ void HTMLLabelElement::DefaultEventHandler(Event& evt) { // so do not focus the control element. if (!is_label_text_selected) { element->focus(FocusParams(SelectionBehaviorOnFocus::kRestore, - kWebFocusTypeMouse, nullptr)); + mojom::blink::FocusType::kMouse, nullptr)); } } @@ -236,7 +238,7 @@ void HTMLLabelElement::focus(const FocusParams& params) { return; } - if (params.type == blink::kWebFocusTypeAccessKey) + if (params.type == blink::mojom::blink::FocusType::kAccessKey) return; // To match other browsers, always restore previous selection. diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc index 3c09fa5d8c1..ae6b2f65cfd 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc @@ -84,6 +84,30 @@ bool HTMLOptGroupElement::MatchesEnabledPseudoClass() const { return !IsDisabledFormControl(); } +void HTMLOptGroupElement::ChildrenChanged(const ChildrenChange& change) { + HTMLElement::ChildrenChanged(change); + auto* select = OwnerSelectElement(); + if (!select) + return; + if (change.type == ChildrenChangeType::kElementInserted) { + if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) + select->OptionInserted(*option, option->Selected()); + } else if (change.type == ChildrenChangeType::kElementRemoved) { + if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) + select->OptionRemoved(*option); + } else if (change.type == ChildrenChangeType::kAllChildrenRemoved) { + DCHECK(change.removed_nodes); + for (Node* node : *change.removed_nodes) { + if (auto* option = DynamicTo<HTMLOptionElement>(node)) + select->OptionRemoved(*option); + } + } +} + +bool HTMLOptGroupElement::ChildrenChangedAllChildrenRemovedNeedsList() const { + return true; +} + Node::InsertionNotificationRequest HTMLOptGroupElement::InsertedInto( ContainerNode& insertion_point) { HTMLElement::InsertedInto(insertion_point); diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h index 693aa166b9b..a03db9bb975 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h @@ -52,6 +52,8 @@ class CORE_EXPORT HTMLOptGroupElement final : public HTMLElement { ~HTMLOptGroupElement() override; bool SupportsFocus() const override; + void ChildrenChanged(const ChildrenChange& change) override; + bool ChildrenChangedAllChildrenRemovedNeedsList() const override; void ParseAttribute(const AttributeModificationParams&) override; void AccessKeyAction(bool send_mouse_events) override; void DidAddUserAgentShadowRoot(ShadowRoot&) override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc index 01ce55d9a26..bce8b9e77da 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc @@ -182,6 +182,8 @@ void HTMLOptionElement::ParseAttribute( SetSelected(!params.new_value.IsNull()); PseudoStateChanged(CSSSelector::kPseudoDefault); } else if (name == html_names::kLabelAttr) { + if (HTMLSelectElement* select = OwnerSelectElement()) + select->OptionElementChildrenChanged(*this); UpdateLabel(); } else { HTMLElement::ParseAttribute(params); @@ -249,8 +251,7 @@ void HTMLOptionElement::SetSelectedState(bool selected) { // notifications only when it's a listbox (and not a menu list). If // there's no layoutObject, fire them anyway just to be safe (to make sure // the AX tree is in sync). - if (!select->GetLayoutObject() || - select->GetLayoutObject()->IsListBox()) { + if (!select->GetLayoutObject() || !select->UsesMenuList()) { cache->ListboxOptionStateChanged(this); cache->ListboxSelectedChildrenChanged(select); } @@ -338,30 +339,6 @@ String HTMLOptionElement::DefaultToolTip() const { return String(); } -Node::InsertionNotificationRequest HTMLOptionElement::InsertedInto( - ContainerNode& insertion_point) { - HTMLElement::InsertedInto(insertion_point); - if (HTMLSelectElement* select = OwnerSelectElement()) { - if (&insertion_point == select || - (IsA<HTMLOptGroupElement>(insertion_point) && - insertion_point.parentNode() == select)) - select->OptionInserted(*this, is_selected_); - } - return kInsertionDone; -} - -void HTMLOptionElement::RemovedFrom(ContainerNode& insertion_point) { - if (auto* select = DynamicTo<HTMLSelectElement>(insertion_point)) { - if (!parentNode() || IsA<HTMLOptGroupElement>(*parentNode())) - select->OptionRemoved(*this); - } else if (IsA<HTMLOptGroupElement>(insertion_point)) { - select = DynamicTo<HTMLSelectElement>(insertion_point.parentNode()); - if (select) - select->OptionRemoved(*this); - } - HTMLElement::RemovedFrom(insertion_point); -} - String HTMLOptionElement::CollectOptionInnerText() const { StringBuilder text; for (Node* node = firstChild(); node;) { diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h index ce663f6bab0..87e6b6a698e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h @@ -38,6 +38,13 @@ class CORE_EXPORT HTMLOptionElement final : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + static HTMLOptionElement* CreateForJSConstructor( + Document& document, + const String& data, + ExceptionState& exception_state) { + return CreateForJSConstructor(document, data, AtomicString(), false, false, + exception_state); + } static HTMLOptionElement* CreateForJSConstructor(Document&, const String& data, const AtomicString& value, @@ -101,8 +108,6 @@ class CORE_EXPORT HTMLOptionElement final : public HTMLElement { bool MatchesDefaultPseudoClass() const override; bool MatchesEnabledPseudoClass() const override; void ParseAttribute(const AttributeModificationParams&) override; - InsertionNotificationRequest InsertedInto(ContainerNode&) override; - void RemovedFrom(ContainerNode&) override; void AccessKeyAction(bool) override; void ChildrenChanged(const ChildrenChange&) override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl index fd2eb010c43..a243648d99e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl +++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl @@ -21,13 +21,15 @@ // https://html.spec.whatwg.org/C/#the-option-element [ + ConstructorCallWith=Document, Exposed=Window, HTMLConstructor, - NamedConstructor=Option(optional DOMString data = null, - optional DOMString value = null, + NamedConstructor=Option(optional DOMString data = "", + optional DOMString value, optional boolean defaultSelected = false, optional boolean selected = false), - ConstructorCallWith=Document, + NamedConstructor_CallWith=Document, + NamedConstructor_RaisesException, RaisesException=Constructor ] interface HTMLOptionElement : HTMLElement { [CEReactions, Reflect] attribute boolean disabled; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc index 13c3d226f3d..569dfb38923 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc @@ -94,17 +94,17 @@ void HTMLOptionsCollection::setLength(unsigned length, To<HTMLSelectElement>(ownerNode()).setLength(length, exception_state); } -bool HTMLOptionsCollection::AnonymousIndexedSetter( +IndexedPropertySetterResult HTMLOptionsCollection::AnonymousIndexedSetter( unsigned index, HTMLOptionElement* value, ExceptionState& exception_state) { auto& base = To<HTMLSelectElement>(ownerNode()); if (!value) { // undefined or null base.remove(index); - return true; + return IndexedPropertySetterResult::kIntercepted; } base.SetOption(index, value, exception_state); - return true; + return IndexedPropertySetterResult::kIntercepted; } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h index 2f371ef293b..d9e50d98190 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h @@ -26,6 +26,8 @@ #include "third_party/blink/renderer/core/html/forms/html_option_element.h" #include "third_party/blink/renderer/core/html/html_collection.h" +#include "third_party/blink/renderer/platform/bindings/v8_binding.h" +#include "third_party/blink/renderer/platform/wtf/casting.h" namespace blink { @@ -53,7 +55,9 @@ class HTMLOptionsCollection final : public HTMLCollection { void setSelectedIndex(int); void setLength(unsigned, ExceptionState&); - bool AnonymousIndexedSetter(unsigned, HTMLOptionElement*, ExceptionState&); + IndexedPropertySetterResult AnonymousIndexedSetter(unsigned, + HTMLOptionElement*, + ExceptionState&); bool ElementMatches(const HTMLElement&) const; @@ -61,11 +65,12 @@ class HTMLOptionsCollection final : public HTMLCollection { void SupportedPropertyNames(Vector<String>& names) override; }; -DEFINE_TYPE_CASTS(HTMLOptionsCollection, - LiveNodeListBase, - collection, - collection->GetType() == kSelectOptions, - collection.GetType() == kSelectOptions); +template <> +struct DowncastTraits<HTMLOptionsCollection> { + static bool AllowFrom(const LiveNodeListBase& collection) { + return collection.GetType() == kSelectOptions; + } +}; inline bool HTMLOptionsCollection::ElementMatches( const HTMLElement& element) const { diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc index a02e8b059ac..a5f4eb0657c 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc @@ -30,6 +30,7 @@ #include "third_party/blink/renderer/core/html/forms/html_select_element.h" #include "build/build_config.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" #include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/bindings/core/v8/html_element_or_long.h" @@ -39,45 +40,38 @@ #include "third_party/blink/renderer/core/dom/attribute.h" #include "third_party/blink/renderer/core/dom/element_traversal.h" #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" -#include "third_party/blink/renderer/core/dom/mutation_observer.h" -#include "third_party/blink/renderer/core/dom/mutation_observer_init.h" -#include "third_party/blink/renderer/core/dom/mutation_record.h" #include "third_party/blink/renderer/core/dom/node_computed_style.h" #include "third_party/blink/renderer/core/dom/node_lists_node_data.h" #include "third_party/blink/renderer/core/dom/node_traversal.h" -#include "third_party/blink/renderer/core/events/gesture_event.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" -#include "third_party/blink/renderer/core/events/mouse_event.h" -#include "third_party/blink/renderer/core/frame/local_dom_window.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/web_feature.h" #include "third_party/blink/renderer/core/html/forms/form_controller.h" #include "third_party/blink/renderer/core/html/forms/form_data.h" #include "third_party/blink/renderer/core/html/forms/html_form_element.h" #include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h" #include "third_party/blink/renderer/core/html/forms/html_option_element.h" -#include "third_party/blink/renderer/core/html/forms/popup_menu.h" +#include "third_party/blink/renderer/core/html/forms/menu_list_inner_element.h" +#include "third_party/blink/renderer/core/html/forms/select_type.h" #include "third_party/blink/renderer/core/html/html_hr_element.h" #include "third_party/blink/renderer/core/html/html_slot_element.h" #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" #include "third_party/blink/renderer/core/html_names.h" -#include "third_party/blink/renderer/core/input/event_handler.h" -#include "third_party/blink/renderer/core/input/input_device_capabilities.h" #include "third_party/blink/renderer/core/inspector/console_message.h" #include "third_party/blink/renderer/core/layout/hit_test_request.h" #include "third_party/blink/renderer/core/layout/hit_test_result.h" -#include "third_party/blink/renderer/core/layout/layout_list_box.h" -#include "third_party/blink/renderer/core/layout/layout_menu_list.h" +#include "third_party/blink/renderer/core/layout/layout_block_flow.h" +#include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_theme.h" -#include "third_party/blink/renderer/core/page/autoscroll_controller.h" #include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/spatial_navigation.h" +#include "third_party/blink/renderer/core/paint/paint_layer.h" +#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -86,18 +80,21 @@ namespace blink { // signed. static const unsigned kMaxListItems = INT_MAX; +// Default size when the multiple attribute is present but size attribute is +// absent. +const int kDefaultListBoxSize = 4; + HTMLSelectElement::HTMLSelectElement(Document& document) : HTMLFormControlElementWithState(html_names::kSelectTag, document), type_ahead_(this), size_(0), last_on_change_option_(nullptr), is_multiple_(false), - is_in_non_contiguous_selection_(false), - active_selection_state_(false), should_recalc_list_items_(false), is_autofilled_by_preview_(false), - index_to_select_on_cancel_(-1), - popup_is_visible_(false) { + index_to_select_on_cancel_(-1) { + // Make sure SelectType is created after initializing |uses_menu_list_|. + select_type_ = SelectType::Create(*this); SetHasCustomStyleCallbacks(); EnsureUserAgentShadowRoot(); } @@ -106,7 +103,7 @@ HTMLSelectElement::~HTMLSelectElement() = default; // static bool HTMLSelectElement::CanAssignToSelectSlot(const Node& node) { - // Even if options/optgroups are not rendered as children of LayoutMenuList, + // Even if options/optgroups are not rendered as children of menulist SELECT, // we still need to add them to the flat tree through slotting since we need // their ComputedStyle for popup rendering. return node.HasTagName(html_names::kOptionTag) || @@ -163,9 +160,6 @@ String HTMLSelectElement::validationMessage() const { } bool HTMLSelectElement::ValueMissing() const { - if (!willValidate()) - return false; - if (!IsRequired()) return false; @@ -187,21 +181,48 @@ void HTMLSelectElement::SelectMultipleOptionsByPopup( const Vector<int>& list_indices) { DCHECK(UsesMenuList()); DCHECK(IsMultiple()); - for (wtf_size_t i = 0; i < list_indices.size(); ++i) { - bool add_selection_if_not_first = i > 0; - if (HTMLOptionElement* option = OptionAtListIndex(list_indices[i])) - UpdateSelectedState(option, add_selection_if_not_first, false); + + HeapHashSet<Member<HTMLOptionElement>> old_selection; + for (auto* option : GetOptionList()) { + if (option->Selected()) { + old_selection.insert(option); + option->SetSelectedState(false); + } } + + bool has_new_selection = false; + for (int list_index : list_indices) { + if (auto* option = OptionAtListIndex(list_index)) { + option->SetSelectedState(true); + option->SetDirty(true); + auto iter = old_selection.find(option); + if (iter != old_selection.end()) + old_selection.erase(iter); + else + has_new_selection = true; + } + } + SetNeedsValidityCheck(); - // TODO(tkent): Using listBoxOnChange() is very confusing. - ListBoxOnChange(); + if (has_new_selection || !old_selection.IsEmpty()) { + DispatchInputEvent(); + DispatchChangeEvent(); + } } -bool HTMLSelectElement::UsesMenuList() const { - if (LayoutTheme::GetTheme().DelegatesMenuListRendering()) - return true; +unsigned HTMLSelectElement::ListBoxSize() const { + DCHECK(!UsesMenuList()); + const unsigned specified_size = size(); + if (specified_size >= 1) + return specified_size; + return kDefaultListBoxSize; +} - return !is_multiple_ && size_ <= 1; +void HTMLSelectElement::UpdateUsesMenuList() { + if (LayoutTheme::GetTheme().DelegatesMenuListRendering()) + uses_menu_list_ = true; + else + uses_menu_list_ = !is_multiple_ && size_ <= 1; } int HTMLSelectElement::ActiveSelectionEndListIndex() const { @@ -269,8 +290,8 @@ void HTMLSelectElement::setValue(const String& value, bool send_events) { flags |= kDispatchInputAndChangeEventFlag; SelectOption(option, flags); - if (send_events && previous_selected_option != option && !UsesMenuList()) - ListBoxOnChange(); + if (send_events && previous_selected_option != option) + select_type_->ListBoxOnChange(); } String HTMLSelectElement::SuggestedValue() const { @@ -314,9 +335,10 @@ void HTMLSelectElement::ParseAttribute( SetNeedsValidityCheck(); if (size_ != old_size) { ChangeRendering(); + UpdateUserAgentShadowTree(*UserAgentShadowRoot()); ResetToDefaultSelection(); - if (!UsesMenuList()) - SaveListboxActiveSelection(); + select_type_->UpdateTextStyleAndContent(); + select_type_->SaveListboxActiveSelection(); } } else if (params.name == html_names::kMultipleAttr) { ParseMultipleAttribute(params.new_value); @@ -332,15 +354,29 @@ bool HTMLSelectElement::MayTriggerVirtualKeyboard() const { return true; } +bool HTMLSelectElement::ShouldHaveFocusAppearance() const { + // For FormControlsRefresh don't draw focus ring for a select that has its + // popup open. + if (::features::IsFormControlsRefreshEnabled() && PopupIsVisible()) + return false; + + return HTMLFormControlElementWithState::ShouldHaveFocusAppearance(); +} + bool HTMLSelectElement::CanSelectAll() const { return !UsesMenuList(); } -LayoutObject* HTMLSelectElement::CreateLayoutObject(const ComputedStyle&, - LegacyLayout) { +bool HTMLSelectElement::TypeShouldForceLegacyLayout() const { + return UsesMenuList(); +} + +LayoutObject* HTMLSelectElement::CreateLayoutObject( + const ComputedStyle& style, + LegacyLayout legacy_layout) { if (UsesMenuList()) - return new LayoutMenuList(this); - return new LayoutListBox(this); + return LayoutObjectFactory::CreateFlexibleBox(*this, style, legacy_layout); + return LayoutObjectFactory::CreateBlockFlow(*this, style, legacy_layout); } HTMLCollection* HTMLSelectElement::selectedOptions() { @@ -355,9 +391,9 @@ void HTMLSelectElement::OptionElementChildrenChanged( const HTMLOptionElement& option) { SetNeedsValidityCheck(); + if (option.Selected()) + select_type_->UpdateTextStyleAndContent(); if (GetLayoutObject()) { - if (option.Selected() && UsesMenuList()) - GetLayoutObject()->UpdateFromElement(); if (AXObjectCache* cache = GetLayoutObject()->GetDocument().ExistingAXObjectCache()) cache->ChildrenChanged(this); @@ -385,7 +421,7 @@ void HTMLSelectElement::SetOption(unsigned index, // We should check |index >= maxListItems| first to avoid integer overflow. if (index >= kMaxListItems || GetListItems().size() + diff + 1 > kMaxListItems) { - GetDocument().AddConsoleMessage(ConsoleMessage::Create( + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kWarning, String::Format("Blocked to expand the option list and set an option at " @@ -418,7 +454,7 @@ void HTMLSelectElement::setLength(unsigned new_len, // We should check |newLen > maxListItems| first to avoid integer overflow. if (new_len > kMaxListItems || GetListItems().size() + new_len - length() > kMaxListItems) { - GetDocument().AddConsoleMessage(ConsoleMessage::Create( + GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kWarning, String::Format("Blocked to expand the option list to %u items. The " @@ -469,246 +505,19 @@ HTMLOptionElement* HTMLSelectElement::OptionAtListIndex(int list_index) const { return DynamicTo<HTMLOptionElement>(items[list_index].Get()); } -// Returns the 1st valid OPTION |skip| items from |listIndex| in direction -// |direction| if there is one. -// Otherwise, it returns the valid OPTION closest to that boundary which is past -// |listIndex| if there is one. -// Otherwise, it returns nullptr. -// Valid means that it is enabled and visible. -HTMLOptionElement* HTMLSelectElement::NextValidOption(int list_index, - SkipDirection direction, - int skip) const { - DCHECK(direction == kSkipBackwards || direction == kSkipForwards); - const ListItems& list_items = GetListItems(); - HTMLOptionElement* last_good_option = nullptr; - int size = list_items.size(); - for (list_index += direction; list_index >= 0 && list_index < size; - list_index += direction) { - --skip; - HTMLElement* element = list_items[list_index]; - auto* option_element = DynamicTo<HTMLOptionElement>(element); - if (!option_element) - continue; - if (option_element->IsDisplayNone()) - continue; - if (element->IsDisabledFormControl()) - continue; - if (!UsesMenuList() && !element->GetLayoutObject()) - continue; - last_good_option = option_element; - if (skip <= 0) - break; - } - return last_good_option; -} - -HTMLOptionElement* HTMLSelectElement::NextSelectableOption( - HTMLOptionElement* start_option) const { - return NextValidOption(start_option ? start_option->ListIndex() : -1, - kSkipForwards, 1); -} - -HTMLOptionElement* HTMLSelectElement::PreviousSelectableOption( - HTMLOptionElement* start_option) const { - return NextValidOption( - start_option ? start_option->ListIndex() : GetListItems().size(), - kSkipBackwards, 1); -} - -HTMLOptionElement* HTMLSelectElement::FirstSelectableOption() const { - // TODO(tkent): This is not efficient. nextSlectableOption(nullptr) is - // faster. - return NextValidOption(GetListItems().size(), kSkipBackwards, INT_MAX); -} - -HTMLOptionElement* HTMLSelectElement::LastSelectableOption() const { - // TODO(tkent): This is not efficient. previousSlectableOption(nullptr) is - // faster. - return NextValidOption(-1, kSkipForwards, INT_MAX); -} - -// Returns the index of the next valid item one page away from |startIndex| in -// direction |direction|. -HTMLOptionElement* HTMLSelectElement::NextSelectableOptionPageAway( - HTMLOptionElement* start_option, - SkipDirection direction) const { - const ListItems& items = GetListItems(); - // Can't use size_ because LayoutObject forces a minimum size. - int page_size = 0; - if (GetLayoutObject()->IsListBox()) { - // -1 so we still show context. - page_size = ToLayoutListBox(GetLayoutObject())->size() - 1; - } - - // One page away, but not outside valid bounds. - // If there is a valid option item one page away, the index is chosen. - // If there is no exact one page away valid option, returns startIndex or - // the most far index. - int start_index = start_option ? start_option->ListIndex() : -1; - int edge_index = (direction == kSkipForwards) ? 0 : (items.size() - 1); - int skip_amount = - page_size + - ((direction == kSkipForwards) ? start_index : (edge_index - start_index)); - return NextValidOption(edge_index, direction, skip_amount); -} - void HTMLSelectElement::SelectAll() { - DCHECK(!UsesMenuList()); - if (!GetLayoutObject() || !is_multiple_) - return; - - // Save the selection so it can be compared to the new selectAll selection - // when dispatching change events. - SaveLastSelection(); - - active_selection_state_ = true; - SetActiveSelectionAnchor(NextSelectableOption(nullptr)); - SetActiveSelectionEnd(PreviousSelectableOption(nullptr)); - - UpdateListBoxSelection(false, false); - ListBoxOnChange(); - SetNeedsValidityCheck(); -} - -void HTMLSelectElement::SaveLastSelection() { - if (UsesMenuList()) { - last_on_change_option_ = SelectedOption(); - return; - } - - last_on_change_selection_.clear(); - for (auto& element : GetListItems()) { - auto* option_element = DynamicTo<HTMLOptionElement>(element.Get()); - last_on_change_selection_.push_back(option_element && - option_element->Selected()); - } + select_type_->SelectAll(); } void HTMLSelectElement::SetActiveSelectionAnchor(HTMLOptionElement* option) { active_selection_anchor_ = option; - if (!UsesMenuList()) - SaveListboxActiveSelection(); -} - -void HTMLSelectElement::SaveListboxActiveSelection() { - // Cache the selection state so we can restore the old selection as the new - // selection pivots around this anchor index. - // Example: - // 1. Press the mouse button on the second OPTION - // active_selection_anchor_ points the second OPTION. - // 2. Drag the mouse pointer onto the fifth OPTION - // active_selection_end_ points the fifth OPTION, OPTIONs at 1-4 indices - // are selected. - // 3. Drag the mouse pointer onto the fourth OPTION - // active_selection_end_ points the fourth OPTION, OPTIONs at 1-3 indices - // are selected. - // UpdateListBoxSelection needs to clear selection of the fifth OPTION. - cached_state_for_active_selection_.resize(0); - for (auto* const option : GetOptionList()) { - cached_state_for_active_selection_.push_back(option->Selected()); - } + select_type_->SaveListboxActiveSelection(); } void HTMLSelectElement::SetActiveSelectionEnd(HTMLOptionElement* option) { active_selection_end_ = option; } -void HTMLSelectElement::UpdateListBoxSelection(bool deselect_other_options, - bool scroll) { - DCHECK(GetLayoutObject()); - DCHECK(GetLayoutObject()->IsListBox() || is_multiple_); - - int active_selection_anchor_index = - active_selection_anchor_ ? active_selection_anchor_->index() : -1; - int active_selection_end_index = - active_selection_end_ ? active_selection_end_->index() : -1; - int start = - std::min(active_selection_anchor_index, active_selection_end_index); - int end = std::max(active_selection_anchor_index, active_selection_end_index); - - int i = 0; - for (auto* const option : GetOptionList()) { - if (option->IsDisabledFormControl() || !option->GetLayoutObject()) { - ++i; - continue; - } - if (i >= start && i <= end) { - option->SetSelectedState(active_selection_state_); - option->SetDirty(true); - } else if (deselect_other_options || - i >= static_cast<int>( - cached_state_for_active_selection_.size())) { - option->SetSelectedState(false); - option->SetDirty(true); - } else { - option->SetSelectedState(cached_state_for_active_selection_[i]); - } - ++i; - } - - UpdateMultiSelectListBoxFocus(); - SetNeedsValidityCheck(); - if (scroll) - ScrollToSelection(); - NotifyFormStateChanged(); -} - -void HTMLSelectElement::ListBoxOnChange() { - DCHECK(!UsesMenuList() || is_multiple_); - - const ListItems& items = GetListItems(); - - // If the cached selection list is empty, or the size has changed, then fire - // dispatchFormControlChangeEvent, and return early. - // FIXME: Why? This looks unreasonable. - if (last_on_change_selection_.IsEmpty() || - last_on_change_selection_.size() != items.size()) { - DispatchChangeEvent(); - return; - } - - // Update last_on_change_selection_ and fire a 'change' event. - bool fire_on_change = false; - for (unsigned i = 0; i < items.size(); ++i) { - HTMLElement* element = items[i]; - auto* option_element = DynamicTo<HTMLOptionElement>(element); - bool selected = option_element && option_element->Selected(); - if (selected != last_on_change_selection_[i]) - fire_on_change = true; - last_on_change_selection_[i] = selected; - } - - if (fire_on_change) { - DispatchInputEvent(); - DispatchChangeEvent(); - } -} - -void HTMLSelectElement::UpdateMultiSelectListBoxFocus() { - if (!is_multiple_) - return; - - for (auto* const option : GetOptionList()) { - if (option->IsDisabledFormControl() || !option->GetLayoutObject()) - continue; - bool is_focused = - (option == active_selection_end_) && is_in_non_contiguous_selection_; - option->SetMultiSelectFocusedState(is_focused); - } - ScrollToSelection(); -} - -void HTMLSelectElement::DispatchInputAndChangeEventForMenuList() { - DCHECK(UsesMenuList()); - - HTMLOptionElement* selected_option = SelectedOption(); - if (last_on_change_option_.Get() != selected_option) { - last_on_change_option_ = selected_option; - DispatchInputEvent(); - DispatchChangeEvent(); - } -} - void HTMLSelectElement::ScrollToSelection() { if (!IsFinishedParsingChildren()) return; @@ -719,16 +528,6 @@ void HTMLSelectElement::ScrollToSelection() { cache->ListboxActiveIndexChanged(this); } -void HTMLSelectElement::SetOptionsChangedOnLayoutObject() { - if (LayoutObject* layout_object = GetLayoutObject()) { - if (!UsesMenuList()) - return; - ToLayoutMenuList(layout_object) - ->SetNeedsLayoutAndPrefWidthsRecalc( - layout_invalidation_reason::kMenuOptionsChanged); - } -} - const HTMLSelectElement::ListItems& HTMLSelectElement::GetListItems() const { if (should_recalc_list_items_) { RecalcListItems(); @@ -755,7 +554,7 @@ void HTMLSelectElement::SetRecalcListItems() { should_recalc_list_items_ = true; - SetOptionsChangedOnLayoutObject(); + select_type_->MaximumOptionWidthMightBeChanged(); if (!isConnected()) { if (HTMLOptionsCollection* collection = CachedCollection<HTMLOptionsCollection>(kSelectOptions)) @@ -903,12 +702,7 @@ void HTMLSelectElement::SetSuggestedOption(HTMLOptionElement* option) { return; suggested_option_ = option; - if (LayoutObject* layout_object = GetLayoutObject()) { - layout_object->UpdateFromElement(); - ScrollToOption(option); - } - if (PopupIsVisible()) - popup_->UpdateFromElement(PopupMenu::kBySelectionChange); + select_type_->DidSetSuggestedOption(option); } void HTMLSelectElement::ScrollToOption(HTMLOptionElement* option) { @@ -936,11 +730,23 @@ void HTMLSelectElement::ScrollToOptionTask() { // OptionRemoved() makes sure option_to_scroll_to_ doesn't have an option with // another owner. DCHECK_EQ(option->OwnerSelectElement(), this); - GetDocument().UpdateStyleAndLayout(); - if (!GetLayoutObject() || !GetLayoutObject()->IsListBox()) + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kScroll); + if (!GetLayoutObject() || UsesMenuList()) return; PhysicalRect bounds = option->BoundingBoxForScrollIntoView(); - ToLayoutListBox(GetLayoutObject())->ScrollToRect(bounds); + + // The following code will not scroll parent boxes unlike ScrollRectToVisible. + auto* box = GetLayoutBox(); + if (!box->HasOverflowClip()) + return; + DCHECK(box->Layer()); + DCHECK(box->Layer()->GetScrollableArea()); + box->Layer()->GetScrollableArea()->ScrollIntoView( + bounds, + ScrollAlignment::CreateScrollIntoViewParams( + ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(), + mojom::blink::ScrollType::kProgrammatic, false, + mojom::blink::ScrollBehavior::kInstant)); } void HTMLSelectElement::OptionSelectionStateChanged(HTMLOptionElement* option, @@ -954,6 +760,41 @@ void HTMLSelectElement::OptionSelectionStateChanged(HTMLOptionElement* option, ResetToDefaultSelection(); } +void HTMLSelectElement::ChildrenChanged(const ChildrenChange& change) { + HTMLFormControlElementWithState::ChildrenChanged(change); + if (change.type == ChildrenChangeType::kElementInserted) { + if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) { + OptionInserted(*option, option->Selected()); + } else if (auto* optgroup = + DynamicTo<HTMLOptGroupElement>(change.sibling_changed)) { + for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup)) + OptionInserted(option, option.Selected()); + } + } else if (change.type == ChildrenChangeType::kElementRemoved) { + if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) { + OptionRemoved(*option); + } else if (auto* optgroup = + DynamicTo<HTMLOptGroupElement>(change.sibling_changed)) { + for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup)) + OptionRemoved(option); + } + } else if (change.type == ChildrenChangeType::kAllChildrenRemoved) { + DCHECK(change.removed_nodes); + for (Node* node : *change.removed_nodes) { + if (auto* option = DynamicTo<HTMLOptionElement>(node)) { + OptionRemoved(*option); + } else if (auto* optgroup = DynamicTo<HTMLOptGroupElement>(node)) { + for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup)) + OptionRemoved(option); + } + } + } +} + +bool HTMLSelectElement::ChildrenChangedAllChildrenRemovedNeedsList() const { + return true; +} + void HTMLSelectElement::OptionInserted(HTMLOptionElement& option, bool option_is_selected) { DCHECK_EQ(option.OwnerSelectElement(), this); @@ -966,7 +807,7 @@ void HTMLSelectElement::OptionInserted(HTMLOptionElement& option, ResetToDefaultSelection(); } SetNeedsValidityCheck(); - last_on_change_selection_.clear(); + select_type_->ClearLastOnChangeSelection(); if (!GetDocument().IsActive()) return; @@ -997,7 +838,7 @@ void HTMLSelectElement::OptionRemoved(HTMLOptionElement& option) { if (option.Selected()) SetAutofillState(WebAutofillState::kNotFilled); SetNeedsValidityCheck(); - last_on_change_selection_.clear(); + select_type_->ClearLastOnChangeSelection(); if (!GetDocument().IsActive()) return; @@ -1013,12 +854,12 @@ void HTMLSelectElement::OptGroupInsertedOrRemoved( HTMLOptGroupElement& optgroup) { SetRecalcListItems(); SetNeedsValidityCheck(); - last_on_change_selection_.clear(); + select_type_->ClearLastOnChangeSelection(); } void HTMLSelectElement::HrInsertedOrRemoved(HTMLHRElement& hr) { SetRecalcListItems(); - last_on_change_selection_.clear(); + select_type_->ClearLastOnChangeSelection(); } // TODO(tkent): This function is not efficient. It contains multiple O(N) @@ -1057,40 +898,7 @@ void HTMLSelectElement::SelectOption(HTMLOptionElement* element, SetActiveSelectionEnd(element); } - // Need to update last_on_change_option_ before - // LayoutMenuList::UpdateFromElement. - bool should_dispatch_events = false; - if (UsesMenuList()) { - should_dispatch_events = (flags & kDispatchInputAndChangeEventFlag) && - last_on_change_option_ != element; - last_on_change_option_ = element; - } - - // For the menu list case, this is what makes the selected element appear. - if (LayoutObject* layout_object = GetLayoutObject()) - layout_object->UpdateFromElement(); - // PopupMenu::UpdateFromElement() posts an O(N) task. - if (PopupIsVisible() && should_update_popup) - popup_->UpdateFromElement(PopupMenu::kBySelectionChange); - - ScrollToSelection(); - SetNeedsValidityCheck(); - - if (UsesMenuList()) { - if (should_dispatch_events) { - DispatchInputEvent(); - DispatchChangeEvent(); - } - if (LayoutObject* layout_object = GetLayoutObject()) { - // Need to check UsesMenuList() again because event handlers might - // change the status. - if (UsesMenuList()) { - // DidSelectOption() is O(N) because of HTMLOptionElement::index(). - ToLayoutMenuList(layout_object)->DidSelectOption(element); - } - } - } - + select_type_->DidSelectOption(element, flags, should_update_popup); NotifyFormStateChanged(); if (LocalFrame::HasTransientUserActivation(GetDocument().GetFrame()) && @@ -1104,29 +912,22 @@ void HTMLSelectElement::SelectOption(HTMLOptionElement* element, void HTMLSelectElement::DispatchFocusEvent( Element* old_focused_element, - WebFocusType type, + mojom::blink::FocusType type, InputDeviceCapabilities* source_capabilities) { // Save the selection so it can be compared to the new selection when // dispatching change events during blur event dispatch. if (UsesMenuList()) - SaveLastSelection(); + select_type_->SaveLastSelection(); HTMLFormControlElementWithState::DispatchFocusEvent(old_focused_element, type, source_capabilities); } void HTMLSelectElement::DispatchBlurEvent( Element* new_focused_element, - WebFocusType type, + mojom::blink::FocusType type, InputDeviceCapabilities* source_capabilities) { type_ahead_.ResetSession(); - // We only need to fire change events here for menu lists, because we fire - // change events for list boxes whenever the selection change is actually - // made. This matches other browsers' behavior. - if (UsesMenuList()) - DispatchInputAndChangeEventForMenuList(); - last_on_change_selection_.clear(); - if (PopupIsVisible()) - HidePopup(); + select_type_->DidBlur(); HTMLFormControlElementWithState::DispatchBlurEvent(new_focused_element, type, source_capabilities); } @@ -1240,6 +1041,7 @@ void HTMLSelectElement::RestoreFormControlState(const FormControlState& state) { } SetNeedsValidityCheck(); + select_type_->UpdateTextStyleAndContent(); } void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) { @@ -1248,6 +1050,7 @@ void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) { is_multiple_ = !value.IsNull(); SetNeedsValidityCheck(); ChangeRendering(); + UpdateUserAgentShadowTree(*UserAgentShadowRoot()); // Restore selectedIndex after changing the multiple flag to preserve // selection as single-line and multi-line has different defaults. if (old_multiple != is_multiple_) { @@ -1259,6 +1062,7 @@ void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) { else ResetToDefaultSelection(); } + select_type_->UpdateTextStyleAndContent(); } void HTMLSelectElement::AppendToFormData(FormData& form_data) { @@ -1279,228 +1083,12 @@ void HTMLSelectElement::ResetImpl() { option->SetDirty(false); } ResetToDefaultSelection(); + select_type_->UpdateTextStyleAndContent(); SetNeedsValidityCheck(); } -void HTMLSelectElement::HandlePopupOpenKeyboardEvent(Event& event) { - focus(); - // Calling focus() may cause us to lose our layoutObject. Return true so - // that our caller doesn't process the event further, but don't set - // the event as handled. - if (!GetLayoutObject() || !GetLayoutObject()->IsMenuList() || - IsDisabledFormControl()) - return; - // Save the selection so it can be compared to the new selection when - // dispatching change events during selectOption, which gets called from - // selectOptionByPopup, which gets called after the user makes a selection - // from the menu. - SaveLastSelection(); - ShowPopup(); - event.SetDefaultHandled(); - return; -} - -bool HTMLSelectElement::ShouldOpenPopupForKeyDownEvent( - const KeyboardEvent& key_event) { - const String& key = key_event.key(); - LayoutTheme& layout_theme = LayoutTheme::GetTheme(); - - if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) - return false; - - return ((layout_theme.PopsMenuByArrowKeys() && - (key == "ArrowDown" || key == "ArrowUp")) || - (layout_theme.PopsMenuByAltDownUpOrF4Key() && - (key == "ArrowDown" || key == "ArrowUp") && key_event.altKey()) || - (layout_theme.PopsMenuByAltDownUpOrF4Key() && - (!key_event.altKey() && !key_event.ctrlKey() && key == "F4"))); -} - -bool HTMLSelectElement::ShouldOpenPopupForKeyPressEvent( - const KeyboardEvent& event) { - LayoutTheme& layout_theme = LayoutTheme::GetTheme(); - int key_code = event.keyCode(); - - return ((layout_theme.PopsMenuBySpaceKey() && key_code == ' ' && - !type_ahead_.HasActiveSession(event)) || - (layout_theme.PopsMenuByReturnKey() && key_code == '\r')); -} - -void HTMLSelectElement::MenuListDefaultEventHandler(Event& event) { - // We need to make the layout tree up-to-date to have GetLayoutObject() give - // the correct result below. An author event handler may have set display to - // some element to none which will cause a layout tree detach. - GetDocument().UpdateStyleAndLayoutTree(); - - if (event.type() == event_type_names::kKeydown) { - if (!GetLayoutObject() || !event.IsKeyboardEvent()) - return; - - auto& key_event = ToKeyboardEvent(event); - if (ShouldOpenPopupForKeyDownEvent(key_event)) { - HandlePopupOpenKeyboardEvent(event); - return; - } - - // When using spatial navigation, we want to be able to navigate away - // from the select element when the user hits any of the arrow keys, - // instead of changing the selection. - if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) { - if (!active_selection_state_) - return; - } - - // The key handling below shouldn't be used for non spatial navigation - // mode Mac - if (LayoutTheme::GetTheme().PopsMenuByArrowKeys() && - !IsSpatialNavigationEnabled(GetDocument().GetFrame())) - return; - - int ignore_modifiers = WebInputEvent::kShiftKey | - WebInputEvent::kControlKey | WebInputEvent::kAltKey | - WebInputEvent::kMetaKey; - if (key_event.GetModifiers() & ignore_modifiers) - return; - - const String& key = key_event.key(); - bool handled = true; - const ListItems& list_items = GetListItems(); - HTMLOptionElement* option = SelectedOption(); - int list_index = option ? option->ListIndex() : -1; - - if (key == "ArrowDown" || key == "ArrowRight") - option = NextValidOption(list_index, kSkipForwards, 1); - else if (key == "ArrowUp" || key == "ArrowLeft") - option = NextValidOption(list_index, kSkipBackwards, 1); - else if (key == "PageDown") - option = NextValidOption(list_index, kSkipForwards, 3); - else if (key == "PageUp") - option = NextValidOption(list_index, kSkipBackwards, 3); - else if (key == "Home") - option = NextValidOption(-1, kSkipForwards, 1); - else if (key == "End") - option = NextValidOption(list_items.size(), kSkipBackwards, 1); - else - handled = false; - - if (handled && option) { - SelectOption(option, kDeselectOtherOptionsFlag | kMakeOptionDirtyFlag | - kDispatchInputAndChangeEventFlag); - } - - if (handled) - event.SetDefaultHandled(); - } - - if (event.type() == event_type_names::kKeypress) { - if (!GetLayoutObject() || !event.IsKeyboardEvent()) - return; - - int key_code = ToKeyboardEvent(event).keyCode(); - if (key_code == ' ' && - IsSpatialNavigationEnabled(GetDocument().GetFrame())) { - // Use space to toggle arrow key handling for selection change or - // spatial navigation. - active_selection_state_ = !active_selection_state_; - event.SetDefaultHandled(); - return; - } - - auto& key_event = ToKeyboardEvent(event); - if (ShouldOpenPopupForKeyPressEvent(key_event)) { - HandlePopupOpenKeyboardEvent(event); - return; - } - - if (!LayoutTheme::GetTheme().PopsMenuByReturnKey() && key_code == '\r') { - if (Form()) - Form()->SubmitImplicitly(event, false); - DispatchInputAndChangeEventForMenuList(); - event.SetDefaultHandled(); - } - } - - if (event.type() == event_type_names::kMousedown && event.IsMouseEvent() && - ToMouseEvent(event).button() == - static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { - InputDeviceCapabilities* source_capabilities = - GetDocument() - .domWindow() - ->GetInputDeviceCapabilities() - ->FiresTouchEvents(ToMouseEvent(event).FromTouch()); - focus(FocusParams(SelectionBehaviorOnFocus::kRestore, kWebFocusTypeNone, - source_capabilities)); - if (GetLayoutObject() && GetLayoutObject()->IsMenuList() && - !IsDisabledFormControl()) { - if (PopupIsVisible()) { - HidePopup(); - } else { - // Save the selection so it can be compared to the new selection - // when we call onChange during selectOption, which gets called - // from selectOptionByPopup, which gets called after the user - // makes a selection from the menu. - SaveLastSelection(); - // TODO(lanwei): Will check if we need to add - // InputDeviceCapabilities here when select menu list gets - // focus, see https://crbug.com/476530. - ShowPopup(); - } - } - event.SetDefaultHandled(); - } -} - -void HTMLSelectElement::UpdateSelectedState(HTMLOptionElement* clicked_option, - bool multi, - bool shift) { - DCHECK(clicked_option); - // Save the selection so it can be compared to the new selection when - // dispatching change events during mouseup, or after autoscroll finishes. - SaveLastSelection(); - - active_selection_state_ = true; - - bool shift_select = is_multiple_ && shift; - bool multi_select = is_multiple_ && multi && !shift; - - // Keep track of whether an active selection (like during drag selection), - // should select or deselect. - if (clicked_option->Selected() && multi_select) { - active_selection_state_ = false; - clicked_option->SetSelectedState(false); - clicked_option->SetDirty(true); - } - - // If we're not in any special multiple selection mode, then deselect all - // other items, excluding the clicked option. If no option was clicked, then - // this will deselect all items in the list. - if (!shift_select && !multi_select) - DeselectItemsWithoutValidation(clicked_option); - - // If the anchor hasn't been set, and we're doing a single selection or a - // shift selection, then initialize the anchor to the first selected index. - if (!active_selection_anchor_ && !multi_select) - SetActiveSelectionAnchor(SelectedOption()); - - // Set the selection state of the clicked option. - if (!clicked_option->IsDisabledFormControl()) { - clicked_option->SetSelectedState(true); - clicked_option->SetDirty(true); - } - - // If there was no selectedIndex() for the previous initialization, or If - // we're doing a single selection, or a multiple selection (using cmd or - // ctrl), then initialize the anchor index to the listIndex that just got - // clicked. - if (!active_selection_anchor_ || !shift_select) - SetActiveSelectionAnchor(clicked_option); - - SetActiveSelectionEnd(clicked_option); - UpdateListBoxSelection(!multi_select); -} - -HTMLOptionElement* HTMLSelectElement::EventTargetOption(const Event& event) { - return DynamicTo<HTMLOptionElement>(event.target()->ToNode()); +bool HTMLSelectElement::PopupIsVisible() const { + return select_type_->PopupIsVisible(); } int HTMLSelectElement::ListIndexForOption(const HTMLOptionElement& option) { @@ -1519,259 +1107,13 @@ AutoscrollController* HTMLSelectElement::GetAutoscrollController() const { return nullptr; } -void HTMLSelectElement::HandleMouseRelease() { - // We didn't start this click/drag on any options. - if (last_on_change_selection_.IsEmpty()) - return; - ListBoxOnChange(); -} - -void HTMLSelectElement::ListBoxDefaultEventHandler(Event& event) { - if (event.type() == event_type_names::kGesturetap && event.IsGestureEvent()) { - focus(); - // Calling focus() may cause us to lose our layoutObject or change the - // layoutObject type, in which case do not want to handle the event. - if (!GetLayoutObject() || !GetLayoutObject()->IsListBox()) - return; - - // Convert to coords relative to the list box if needed. - auto& gesture_event = ToGestureEvent(event); - if (HTMLOptionElement* option = EventTargetOption(gesture_event)) { - if (!IsDisabledFormControl()) { - UpdateSelectedState(option, true, gesture_event.shiftKey()); - ListBoxOnChange(); - } - event.SetDefaultHandled(); - } - - } else if (event.type() == event_type_names::kMousedown && - event.IsMouseEvent() && - ToMouseEvent(event).button() == - static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { - focus(); - // Calling focus() may cause us to lose our layoutObject, in which case - // do not want to handle the event. - if (!GetLayoutObject() || !GetLayoutObject()->IsListBox() || - IsDisabledFormControl()) - return; - - // Convert to coords relative to the list box if needed. - auto& mouse_event = ToMouseEvent(event); - if (HTMLOptionElement* option = EventTargetOption(mouse_event)) { - if (!option->IsDisabledFormControl()) { -#if defined(OS_MACOSX) - UpdateSelectedState(option, mouse_event.metaKey(), - mouse_event.shiftKey()); -#else - UpdateSelectedState(option, mouse_event.ctrlKey(), - mouse_event.shiftKey()); -#endif - } - if (LocalFrame* frame = GetDocument().GetFrame()) - frame->GetEventHandler().SetMouseDownMayStartAutoscroll(); - - event.SetDefaultHandled(); - } - - } else if (event.type() == event_type_names::kMousemove && - event.IsMouseEvent()) { - auto& mouse_event = ToMouseEvent(event); - if (mouse_event.button() != - static_cast<int16_t>(WebPointerProperties::Button::kLeft) || - !mouse_event.ButtonDown()) - return; - - LayoutObject* layout_object = GetLayoutObject(); - if (layout_object) { - layout_object->GetFrameView()->UpdateAllLifecyclePhasesExceptPaint(); - - if (Page* page = GetDocument().GetPage()) { - page->GetAutoscrollController().StartAutoscrollForSelection( - layout_object); - } - } - // Mousedown didn't happen in this element. - if (last_on_change_selection_.IsEmpty()) - return; - - if (HTMLOptionElement* option = EventTargetOption(mouse_event)) { - if (!IsDisabledFormControl()) { - if (is_multiple_) { - // Only extend selection if there is something selected. - if (!active_selection_anchor_) - return; - - SetActiveSelectionEnd(option); - UpdateListBoxSelection(false); - } else { - SetActiveSelectionAnchor(option); - SetActiveSelectionEnd(option); - UpdateListBoxSelection(true); - } - } - } - - } else if (event.type() == event_type_names::kMouseup && - event.IsMouseEvent() && - ToMouseEvent(event).button() == - static_cast<int16_t>(WebPointerProperties::Button::kLeft) && - GetLayoutObject()) { - if (GetDocument().GetPage() && - GetDocument() - .GetPage() - ->GetAutoscrollController() - .AutoscrollInProgressFor(ToLayoutBox(GetLayoutObject()))) - GetDocument().GetPage()->GetAutoscrollController().StopAutoscroll(); - else - HandleMouseRelease(); - - } else if (event.type() == event_type_names::kKeydown) { - if (!event.IsKeyboardEvent()) - return; - const String& key = ToKeyboardEvent(event).key(); - - bool handled = false; - HTMLOptionElement* end_option = nullptr; - if (!active_selection_end_) { - // Initialize the end index - if (key == "ArrowDown" || key == "PageDown") { - HTMLOptionElement* start_option = LastSelectedOption(); - handled = true; - if (key == "ArrowDown") { - end_option = NextSelectableOption(start_option); - } else { - end_option = - NextSelectableOptionPageAway(start_option, kSkipForwards); - } - } else if (key == "ArrowUp" || key == "PageUp") { - HTMLOptionElement* start_option = SelectedOption(); - handled = true; - if (key == "ArrowUp") { - end_option = PreviousSelectableOption(start_option); - } else { - end_option = - NextSelectableOptionPageAway(start_option, kSkipBackwards); - } - } - } else { - // Set the end index based on the current end index. - if (key == "ArrowDown") { - end_option = NextSelectableOption(active_selection_end_.Get()); - handled = true; - } else if (key == "ArrowUp") { - end_option = PreviousSelectableOption(active_selection_end_.Get()); - handled = true; - } else if (key == "PageDown") { - end_option = NextSelectableOptionPageAway(active_selection_end_.Get(), - kSkipForwards); - handled = true; - } else if (key == "PageUp") { - end_option = NextSelectableOptionPageAway(active_selection_end_.Get(), - kSkipBackwards); - handled = true; - } - } - if (key == "Home") { - end_option = FirstSelectableOption(); - handled = true; - } else if (key == "End") { - end_option = LastSelectableOption(); - handled = true; - } - - if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) { - // Check if the selection moves to the boundary. - if (key == "ArrowLeft" || key == "ArrowRight" || - ((key == "ArrowDown" || key == "ArrowUp") && - end_option == active_selection_end_)) - return; - } - - bool is_control_key = false; -#if defined(OS_MACOSX) - is_control_key = ToKeyboardEvent(event).metaKey(); -#else - is_control_key = ToKeyboardEvent(event).ctrlKey(); -#endif - - if (is_multiple_ && ToKeyboardEvent(event).keyCode() == ' ' && - is_control_key && active_selection_end_) { - // Use ctrl+space to toggle selection change. - ToggleSelection(*active_selection_end_); - event.SetDefaultHandled(); - return; - } - - if (end_option && handled) { - // Save the selection so it can be compared to the new selection - // when dispatching change events immediately after making the new - // selection. - SaveLastSelection(); - - SetActiveSelectionEnd(end_option); - - is_in_non_contiguous_selection_ = is_multiple_ && is_control_key; - bool select_new_item = - !is_multiple_ || ToKeyboardEvent(event).shiftKey() || - (!IsSpatialNavigationEnabled(GetDocument().GetFrame()) && - !is_in_non_contiguous_selection_); - if (select_new_item) - active_selection_state_ = true; - // If the anchor is uninitialized, or if we're going to deselect all - // other options, then set the anchor index equal to the end index. - bool deselect_others = - !is_multiple_ || - (!ToKeyboardEvent(event).shiftKey() && select_new_item); - if (!active_selection_anchor_ || deselect_others) { - if (deselect_others) - DeselectItemsWithoutValidation(); - SetActiveSelectionAnchor(active_selection_end_.Get()); - } - - ScrollToOption(end_option); - if (select_new_item || is_in_non_contiguous_selection_) { - if (select_new_item) { - UpdateListBoxSelection(deselect_others); - ListBoxOnChange(); - } - UpdateMultiSelectListBoxFocus(); - } else { - ScrollToSelection(); - } - - event.SetDefaultHandled(); - } - - } else if (event.type() == event_type_names::kKeypress) { - if (!event.IsKeyboardEvent()) - return; - int key_code = ToKeyboardEvent(event).keyCode(); - - if (key_code == '\r') { - if (Form()) - Form()->SubmitImplicitly(event, false); - event.SetDefaultHandled(); - } else if (is_multiple_ && key_code == ' ' && - (IsSpatialNavigationEnabled(GetDocument().GetFrame()) || - is_in_non_contiguous_selection_)) { - HTMLOptionElement* option = active_selection_end_; - // If there's no active selection, - // act as if "ArrowDown" had been pressed. - if (!option) - option = NextSelectableOption(LastSelectedOption()); - if (option) { - // Use space to toggle selection change. - ToggleSelection(*option); - event.SetDefaultHandled(); - } - } - } +LayoutBox* HTMLSelectElement::AutoscrollBox() { + return !UsesMenuList() ? GetLayoutBox() : nullptr; } -void HTMLSelectElement::ToggleSelection(HTMLOptionElement& option) { - active_selection_state_ = !active_selection_state_; - UpdateSelectedState(&option, true /*multi*/, false /*shift*/); - ListBoxOnChange(); +void HTMLSelectElement::StopAutoscroll() { + if (!IsDisabledFormControl()) + select_type_->HandleMouseRelease(); } void HTMLSelectElement::DefaultEventHandler(Event& event) { @@ -1788,19 +1130,17 @@ void HTMLSelectElement::DefaultEventHandler(Event& event) { return; } - if (UsesMenuList()) - MenuListDefaultEventHandler(event); - else - ListBoxDefaultEventHandler(event); - if (event.DefaultHandled()) + if (select_type_->DefaultEventHandler(event)) { + event.SetDefaultHandled(); return; + } - if (event.type() == event_type_names::kKeypress && event.IsKeyboardEvent()) { - auto& keyboard_event = ToKeyboardEvent(event); - if (!keyboard_event.ctrlKey() && !keyboard_event.altKey() && - !keyboard_event.metaKey() && - WTF::unicode::IsPrintableChar(keyboard_event.charCode())) { - TypeAheadFind(keyboard_event); + auto* keyboard_event = DynamicTo<KeyboardEvent>(event); + if (event.type() == event_type_names::kKeypress && keyboard_event) { + if (!keyboard_event->ctrlKey() && !keyboard_event->altKey() && + !keyboard_event->metaKey() && + WTF::unicode::IsPrintableChar(keyboard_event->charCode())) { + TypeAheadFind(*keyboard_event); event.SetDefaultHandled(); return; } @@ -1843,8 +1183,7 @@ void HTMLSelectElement::TypeAheadFind(const KeyboardEvent& event) { SelectOption(OptionAtListIndex(index), kDeselectOtherOptionsFlag | kMakeOptionDirtyFlag | kDispatchInputAndChangeEventFlag); - if (!UsesMenuList()) - ListBoxOnChange(); + select_type_->ListBoxOnChange(); } void HTMLSelectElement::SelectOptionByAccessKey(HTMLOptionElement* option) { @@ -1870,7 +1209,7 @@ void HTMLSelectElement::SelectOptionByAccessKey(HTMLOptionElement* option) { option->SetDirty(true); if (UsesMenuList()) return; - ListBoxOnChange(); + select_type_->ListBoxOnChange(); ScrollToSelection(); } @@ -1892,16 +1231,16 @@ void HTMLSelectElement::FinishParsingChildren() { cache->ListboxActiveIndexChanged(this); } -bool HTMLSelectElement::AnonymousIndexedSetter( +IndexedPropertySetterResult HTMLSelectElement::AnonymousIndexedSetter( unsigned index, HTMLOptionElement* value, ExceptionState& exception_state) { if (!value) { // undefined or null remove(index); - return true; + return IndexedPropertySetterResult::kIntercepted; } SetOption(index, value, exception_state); - return true; + return IndexedPropertySetterResult::kIntercepted; } bool HTMLSelectElement::IsInteractiveContent() const { @@ -1915,14 +1254,47 @@ void HTMLSelectElement::Trace(Visitor* visitor) { visitor->Trace(active_selection_end_); visitor->Trace(option_to_scroll_to_); visitor->Trace(suggested_option_); - visitor->Trace(popup_); - visitor->Trace(popup_updater_); + visitor->Trace(select_type_); HTMLFormControlElementWithState::Trace(visitor); } void HTMLSelectElement::DidAddUserAgentShadowRoot(ShadowRoot& root) { + // Even if UsesMenuList(), the <slot> is necessary to have ComputedStyles + // for <option>s. LayoutFlexibleBox::IsChildAllowed() rejects all of + // LayoutObject children except for MenuListInnerElement's. root.AppendChild( HTMLSlotElement::CreateUserAgentCustomAssignSlot(GetDocument())); + UpdateUserAgentShadowTree(root); + select_type_->UpdateTextStyleAndContent(); +} + +void HTMLSelectElement::UpdateUserAgentShadowTree(ShadowRoot& root) { + // Remove all children of the ShadowRoot except for <slot>. + Node* node = root.firstChild(); + while (node) { + if (IsA<HTMLSlotElement>(node)) { + node = node->nextSibling(); + } else { + auto* will_be_removed = node; + node = node->nextSibling(); + will_be_removed->remove(); + } + } + if (UsesMenuList()) { + Element* inner_element = + MakeGarbageCollected<MenuListInnerElement>(GetDocument()); + inner_element->setAttribute(html_names::kAriaHiddenAttr, "true"); + // Make sure InnerElement() always has a Text node. + inner_element->appendChild(Text::Create(GetDocument(), g_empty_string)); + root.insertBefore(inner_element, root.firstChild()); + } +} + +Element& HTMLSelectElement::InnerElement() const { + DCHECK(UsesMenuList()); + auto* inner_element = DynamicTo<Element>(UserAgentShadowRoot()->firstChild()); + DCHECK(inner_element); + return *inner_element; } HTMLOptionElement* HTMLSelectElement::SpatialNavigationFocusedOption() { @@ -1930,7 +1302,7 @@ HTMLOptionElement* HTMLSelectElement::SpatialNavigationFocusedOption() { return nullptr; HTMLOptionElement* focused_option = ActiveSelectionEnd(); if (!focused_option) - focused_option = FirstSelectableOption(); + focused_option = select_type_->FirstSelectableOption(); return focused_option; } @@ -1960,42 +1332,44 @@ const ComputedStyle* HTMLSelectElement::ItemComputedStyle( } LayoutUnit HTMLSelectElement::ClientPaddingLeft() const { - if (GetLayoutObject() && GetLayoutObject()->IsMenuList()) - return ToLayoutMenuList(GetLayoutObject())->ClientPaddingLeft(); - return LayoutUnit(); + DCHECK(UsesMenuList()); + auto* this_box = GetLayoutBox(); + if (!this_box || !InnerElement().GetLayoutBox()) + return LayoutUnit(); + LayoutTheme& theme = LayoutTheme::GetTheme(); + const ComputedStyle& style = this_box->StyleRef(); + int inner_padding = + style.IsLeftToRightDirection() + ? theme.PopupInternalPaddingStart(style) + : theme.PopupInternalPaddingEnd(GetDocument().GetFrame(), style); + return this_box->PaddingLeft() + inner_padding; } LayoutUnit HTMLSelectElement::ClientPaddingRight() const { - if (GetLayoutObject() && GetLayoutObject()->IsMenuList()) - return ToLayoutMenuList(GetLayoutObject())->ClientPaddingRight(); - return LayoutUnit(); + DCHECK(UsesMenuList()); + auto* this_box = GetLayoutBox(); + if (!this_box || !InnerElement().GetLayoutBox()) + return LayoutUnit(); + LayoutTheme& theme = LayoutTheme::GetTheme(); + const ComputedStyle& style = this_box->StyleRef(); + int inner_padding = + style.IsLeftToRightDirection() + ? theme.PopupInternalPaddingEnd(GetDocument().GetFrame(), style) + : theme.PopupInternalPaddingStart(style); + return this_box->PaddingRight() + inner_padding; } void HTMLSelectElement::PopupDidHide() { - popup_is_visible_ = false; - UnobserveTreeMutation(); - if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) { - if (GetLayoutObject() && GetLayoutObject()->IsMenuList()) - cache->DidHideMenuListPopup(ToLayoutMenuList(GetLayoutObject())); - } + select_type_->PopupDidHide(); } void HTMLSelectElement::SetIndexToSelectOnCancel(int list_index) { index_to_select_on_cancel_ = list_index; - if (GetLayoutObject()) - GetLayoutObject()->UpdateFromElement(); + select_type_->UpdateTextStyleAndContent(); } -HTMLOptionElement* HTMLSelectElement::OptionToBeShown() const { - if (HTMLOptionElement* option = OptionAtListIndex(index_to_select_on_cancel_)) - return option; - if (suggested_option_) - return suggested_option_; - // TODO(tkent): We should not call optionToBeShown() in isMultiple() case. - if (IsMultiple()) - return SelectedOption(); - DCHECK_EQ(SelectedOption(), last_on_change_option_); - return last_on_change_option_; +HTMLOptionElement* HTMLSelectElement::OptionToBeShownForTesting() const { + return select_type_->OptionToBeShown(); } void HTMLSelectElement::SelectOptionByPopup(int list_index) { @@ -2031,44 +1405,28 @@ void HTMLSelectElement::ProvisionalSelectionChanged(unsigned list_index) { } void HTMLSelectElement::ShowPopup() { - if (PopupIsVisible()) - return; - if (GetDocument().GetPage()->GetChromeClient().HasOpenedPopup()) - return; - if (!GetLayoutObject() || !GetLayoutObject()->IsMenuList()) - return; - if (VisibleBoundsInVisualViewport().IsEmpty()) - return; - - if (!popup_) { - popup_ = GetDocument().GetPage()->GetChromeClient().OpenPopupMenu( - *GetDocument().GetFrame(), *this); - } - if (!popup_) - return; - - popup_is_visible_ = true; - ObserveTreeMutation(); - - LayoutMenuList* menu_list = ToLayoutMenuList(GetLayoutObject()); - popup_->Show(); - if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) - cache->DidShowMenuListPopup(menu_list); + select_type_->ShowPopup(); } void HTMLSelectElement::HidePopup() { - if (popup_) - popup_->Hide(); + select_type_->HidePopup(); +} + +PopupMenu* HTMLSelectElement::PopupForTesting() const { + return select_type_->PopupForTesting(); } void HTMLSelectElement::DidRecalcStyle(const StyleRecalcChange change) { HTMLFormControlElementWithState::DidRecalcStyle(change); - if (!change.ReattachLayoutTree() && PopupIsVisible()) - popup_->UpdateFromElement(PopupMenu::kByStyleChange); + select_type_->DidRecalcStyle(change); } void HTMLSelectElement::AttachLayoutTree(AttachContext& context) { HTMLFormControlElementWithState::AttachLayoutTree(context); + // The call to UpdateTextStyle() needs to go after the call through + // to the base class's AttachLayoutTree() because that can sometimes do a + // close on the LayoutObject. + select_type_->UpdateTextStyle(); if (const ComputedStyle* style = GetComputedStyle()) { if (style->Visibility() != EVisibility::kHidden) { @@ -2082,90 +1440,13 @@ void HTMLSelectElement::AttachLayoutTree(AttachContext& context) { void HTMLSelectElement::DetachLayoutTree(bool performing_reattach) { HTMLFormControlElementWithState::DetachLayoutTree(performing_reattach); - if (popup_) - popup_->DisconnectClient(); - popup_is_visible_ = false; - popup_ = nullptr; - UnobserveTreeMutation(); + select_type_->DidDetachLayoutTree(); } void HTMLSelectElement::ResetTypeAheadSessionForTesting() { type_ahead_.ResetSession(); } -// PopupUpdater notifies updates of the specified SELECT element subtree to -// a PopupMenu object. -class HTMLSelectElement::PopupUpdater : public MutationObserver::Delegate { - public: - explicit PopupUpdater(HTMLSelectElement& select) - : select_(select), observer_(MutationObserver::Create(this)) { - MutationObserverInit* init = MutationObserverInit::Create(); - init->setAttributeOldValue(true); - init->setAttributes(true); - // Observe only attributes which affect popup content. - init->setAttributeFilter({"disabled", "label", "selected", "value"}); - init->setCharacterData(true); - init->setCharacterDataOldValue(true); - init->setChildList(true); - init->setSubtree(true); - observer_->observe(select_, init, ASSERT_NO_EXCEPTION); - } - - ExecutionContext* GetExecutionContext() const override { - return &select_->GetDocument(); - } - - void Deliver(const MutationRecordVector& records, - MutationObserver&) override { - // We disconnect the MutationObserver when a popup is closed. However - // MutationObserver can call back after disconnection. - if (!select_->PopupIsVisible()) - return; - for (const auto& record : records) { - if (record->type() == "attributes") { - const auto& element = *To<Element>(record->target()); - if (record->oldValue() == element.getAttribute(record->attributeName())) - continue; - } else if (record->type() == "characterData") { - if (record->oldValue() == record->target()->nodeValue()) - continue; - } - select_->DidMutateSubtree(); - return; - } - } - - void Dispose() { observer_->disconnect(); } - - void Trace(Visitor* visitor) override { - visitor->Trace(select_); - visitor->Trace(observer_); - MutationObserver::Delegate::Trace(visitor); - } - - private: - Member<HTMLSelectElement> select_; - Member<MutationObserver> observer_; -}; - -void HTMLSelectElement::ObserveTreeMutation() { - DCHECK(!popup_updater_); - popup_updater_ = MakeGarbageCollected<PopupUpdater>(*this); -} - -void HTMLSelectElement::UnobserveTreeMutation() { - if (!popup_updater_) - return; - popup_updater_->Dispose(); - popup_updater_ = nullptr; -} - -void HTMLSelectElement::DidMutateSubtree() { - DCHECK(PopupIsVisible()); - DCHECK(popup_); - popup_->UpdateFromElement(PopupMenu::kByDOMChange); -} - void HTMLSelectElement::CloneNonAttributePropertiesFrom( const Element& source, CloneChildrenFlag flag) { @@ -2175,6 +1456,10 @@ void HTMLSelectElement::CloneNonAttributePropertiesFrom( } void HTMLSelectElement::ChangeRendering() { + select_type_->DidDetachLayoutTree(); + UpdateUsesMenuList(); + select_type_->WillBeDestroyed(); + select_type_ = SelectType::Create(*this); if (!InActiveDocument()) return; // TODO(futhark): SetForceReattachLayoutTree() should be the correct way to @@ -2185,4 +1470,8 @@ void HTMLSelectElement::ChangeRendering() { style_change_reason::kControl)); } +const ComputedStyle* HTMLSelectElement::OptionStyle() const { + return select_type_->OptionStyle(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h index 5bfbc9ccde4..314840a3f45 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h @@ -28,6 +28,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_SELECT_ELEMENT_H_ #include "base/gtest_prod_util.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h" #include "third_party/blink/renderer/core/html/forms/html_options_collection.h" @@ -46,6 +47,7 @@ class HTMLOptionElementOrHTMLOptGroupElement; class HTMLElementOrLong; class LayoutUnit; class PopupMenu; +class SelectType; class CORE_EXPORT HTMLSelectElement final : public HTMLFormControlElementWithState, @@ -74,9 +76,12 @@ class CORE_EXPORT HTMLSelectElement final // TODO(tkent): Rename |size| to |Size|. This is not an implementation of // |size| IDL attribute. unsigned size() const { return size_; } + // The number of items to be shown in the LisBox mode. + // Do not call this in the MenuList mode. + unsigned ListBoxSize() const; bool IsMultiple() const { return is_multiple_; } - bool UsesMenuList() const; + bool UsesMenuList() const { return uses_menu_list_; } void add(const HTMLOptionElementOrHTMLOptGroupElement&, const HTMLElementOrLong&, @@ -122,7 +127,6 @@ class CORE_EXPORT HTMLSelectElement final bool CanSelectAll() const; void SelectAll(); - void ListBoxOnChange(); int ActiveSelectionEndListIndex() const; HTMLOptionElement* ActiveSelectionEnd() const; void SetActiveSelectionAnchor(HTMLOptionElement*); @@ -132,13 +136,14 @@ class CORE_EXPORT HTMLSelectElement final void OptionSelectionStateChanged(HTMLOptionElement*, bool option_is_selected); void OptionInserted(HTMLOptionElement&, bool option_is_selected); void OptionRemoved(HTMLOptionElement&); - bool AnonymousIndexedSetter(unsigned, HTMLOptionElement*, ExceptionState&); + IndexedPropertySetterResult AnonymousIndexedSetter(unsigned, + HTMLOptionElement*, + ExceptionState&); void OptGroupInsertedOrRemoved(HTMLOptGroupElement&); void HrInsertedOrRemoved(HTMLHRElement&); HTMLOptionElement* SpatialNavigationFocusedOption(); - void HandleMouseRelease(); int ListIndexForOption(const HTMLOptionElement&); @@ -159,12 +164,14 @@ class CORE_EXPORT HTMLSelectElement final // Provisional selection is a selection made using arrow keys or type ahead. void ProvisionalSelectionChanged(unsigned); void PopupDidHide(); - bool PopupIsVisible() const { return popup_is_visible_; } - HTMLOptionElement* OptionToBeShown() const; + bool PopupIsVisible() const; + HTMLOptionElement* OptionToBeShownForTesting() const; + // Style of the selected OPTION. This is nullable, and only for + // the menulist mode. + const ComputedStyle* OptionStyle() const; void ShowPopup(); void HidePopup(); - PopupMenu* Popup() const { return popup_.Get(); } - void DidMutateSubtree(); + PopupMenu* PopupForTesting() const; void ResetTypeAheadSessionForTesting(); @@ -177,17 +184,22 @@ class CORE_EXPORT HTMLSelectElement final void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) override; + // This should be called only if UsesMenuList(). + Element& InnerElement() const; + private: const AtomicString& FormControlType() const override; bool MayTriggerVirtualKeyboard() const override; + bool ShouldHaveFocusAppearance() const final; + void DispatchFocusEvent( Element* old_focused_element, - WebFocusType, + mojom::blink::FocusType, InputDeviceCapabilities* source_capabilities) override; void DispatchBlurEvent(Element* new_focused_element, - WebFocusType, + mojom::blink::FocusType, InputDeviceCapabilities* source_capabilities) override; bool CanStartSelection() const override { return false; } @@ -199,9 +211,12 @@ class CORE_EXPORT HTMLSelectElement final FormControlState SaveFormControlState() const override; void RestoreFormControlState(const FormControlState&) override; + void ChildrenChanged(const ChildrenChange& change) override; + bool ChildrenChangedAllChildrenRemovedNeedsList() const override; void ParseAttribute(const AttributeModificationParams&) override; bool IsPresentationAttribute(const QualifiedName&) const override; + bool TypeShouldForceLegacyLayout() const override; LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override; void DidRecalcStyle(const StyleRecalcChange) override; void AttachLayoutTree(AttachContext&) override; @@ -211,15 +226,11 @@ class CORE_EXPORT HTMLSelectElement final void DefaultEventHandler(Event&) override; - void DispatchInputAndChangeEventForMenuList(); - void SetRecalcListItems(); void RecalcListItems() const; enum ResetReason { kResetReasonSelectedOptionRemoved, kResetReasonOthers }; void ResetToDefaultSelection(ResetReason = kResetReasonOthers); void TypeAheadFind(const KeyboardEvent&); - void SaveLastSelection(); - void SaveListboxActiveSelection(); // Returns the first selected OPTION, or nullptr. HTMLOptionElement* SelectedOption() const; @@ -241,37 +252,19 @@ class CORE_EXPORT HTMLSelectElement final HTMLOptionElement* element_to_exclude = nullptr); void ParseMultipleAttribute(const AtomicString&); HTMLOptionElement* LastSelectedOption() const; - void UpdateSelectedState(HTMLOptionElement*, bool multi, bool shift); - void MenuListDefaultEventHandler(Event&); - void HandlePopupOpenKeyboardEvent(Event&); - bool ShouldOpenPopupForKeyDownEvent(const KeyboardEvent&); - bool ShouldOpenPopupForKeyPressEvent(const KeyboardEvent&); - void ListBoxDefaultEventHandler(Event&); - void SetOptionsChangedOnLayoutObject(); wtf_size_t SearchOptionsForValue(const String&, wtf_size_t list_index_start, wtf_size_t list_index_end) const; - void UpdateListBoxSelection(bool deselect_other_options, bool scroll = true); - void UpdateMultiSelectListBoxFocus(); void SetIndexToSelectOnCancel(int list_index); void SetSuggestedOption(HTMLOptionElement*); - void ToggleSelection(HTMLOptionElement&); // Returns nullptr if listIndex is out of bounds, or it doesn't point an // HTMLOptionElement. HTMLOptionElement* OptionAtListIndex(int list_index) const; - enum SkipDirection { kSkipBackwards = -1, kSkipForwards = 1 }; - HTMLOptionElement* NextValidOption(int list_index, - SkipDirection, - int skip) const; - HTMLOptionElement* NextSelectableOption(HTMLOptionElement*) const; - HTMLOptionElement* PreviousSelectableOption(HTMLOptionElement*) const; - HTMLOptionElement* FirstSelectableOption() const; - HTMLOptionElement* LastSelectableOption() const; - HTMLOptionElement* NextSelectableOptionPageAway(HTMLOptionElement*, - SkipDirection) const; - HTMLOptionElement* EventTargetOption(const Event&); + AutoscrollController* GetAutoscrollController() const; + LayoutBox* AutoscrollBox() override; + void StopAutoscroll() override; void ScrollToOptionTask(); bool AreAuthorShadowsAllowed() const override { return false; } @@ -282,18 +275,15 @@ class CORE_EXPORT HTMLSelectElement final int OptionCount() const override; String OptionAtIndex(int index) const override; - void ObserveTreeMutation(); - void UnobserveTreeMutation(); - + void UpdateUsesMenuList(); // Apply changes to rendering as a result of attribute changes (multiple, // size). void ChangeRendering(); + void UpdateUserAgentShadowTree(ShadowRoot& root); // list_items_ contains HTMLOptionElement, HTMLOptGroupElement, and // HTMLHRElement objects. mutable ListItems list_items_; - Vector<bool> last_on_change_selection_; - Vector<bool> cached_state_for_active_selection_; TypeAhead type_ahead_; unsigned size_; Member<HTMLOptionElement> last_on_change_option_; @@ -301,22 +291,18 @@ class CORE_EXPORT HTMLSelectElement final Member<HTMLOptionElement> active_selection_end_; Member<HTMLOptionElement> option_to_scroll_to_; Member<HTMLOptionElement> suggested_option_; + bool uses_menu_list_ = true; bool is_multiple_; - bool is_in_non_contiguous_selection_; - bool active_selection_state_; mutable bool should_recalc_list_items_; bool is_autofilled_by_preview_; - class PopupUpdater; - Member<PopupUpdater> popup_updater_; - Member<PopupMenu> popup_; + Member<SelectType> select_type_; int index_to_select_on_cancel_; - bool popup_is_visible_; - FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, FirstSelectableOption); - FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, LastSelectableOption); - FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, NextSelectableOption); - FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, PreviousSelectableOption); + friend class ListBoxSelectType; + friend class MenuListSelectType; + friend class SelectType; + friend class HTMLSelectElementTest; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc index 8da70996dff..f92a9abb963 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc @@ -9,8 +9,11 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/dom/document.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/html/forms/form_controller.h" #include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/select_type.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" #include "third_party/blink/renderer/core/testing/page_test_base.h" #include "third_party/blink/renderer/platform/heap/heap.h" @@ -19,11 +22,49 @@ namespace blink { class HTMLSelectElementTest : public PageTestBase { protected: void SetUp() override; + void TearDown() override; + + SelectType& GetSelectType(const HTMLSelectElement& select) { + return *select.select_type_; + } + + HTMLOptionElement* FirstSelectableOption(const HTMLSelectElement& select) { + return GetSelectType(select).FirstSelectableOption(); + } + HTMLOptionElement* LastSelectableOption(const HTMLSelectElement& select) { + return GetSelectType(select).LastSelectableOption(); + } + HTMLOptionElement* NextSelectableOption(const HTMLSelectElement& select, + HTMLOptionElement* option) { + return GetSelectType(select).NextSelectableOption(option); + } + HTMLOptionElement* PreviousSelectableOption(const HTMLSelectElement& select, + HTMLOptionElement* option) { + return GetSelectType(select).PreviousSelectableOption(option); + } + + bool FirstSelectIsConnectedAfterSelectMultiple(const Vector<int>& indices) { + auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); + select->focus(); + select->SelectMultipleOptionsByPopup(indices); + return select->isConnected(); + } + + private: + bool original_delegates_flag_; }; void HTMLSelectElementTest::SetUp() { PageTestBase::SetUp(); GetDocument().SetMimeType("text/html"); + original_delegates_flag_ = + LayoutTheme::GetTheme().DelegatesMenuListRendering(); +} + +void HTMLSelectElementTest::TearDown() { + LayoutTheme::GetTheme().SetDelegatesMenuListRenderingForTesting( + original_delegates_flag_); + PageTestBase::TearDown(); } TEST_F(HTMLSelectElementTest, SaveRestoreSelectSingleFormControlState) { @@ -55,6 +96,8 @@ TEST_F(HTMLSelectElementTest, SaveRestoreSelectSingleFormControlState) { EXPECT_EQ(2, To<HTMLSelectElement>(element)->selectedIndex()); EXPECT_FALSE(opt0->Selected()); EXPECT_TRUE(opt2->Selected()); + EXPECT_EQ("!666", + To<HTMLSelectElement>(element)->InnerElement().textContent()); } TEST_F(HTMLSelectElementTest, SaveRestoreSelectMultipleFormControlState) { @@ -121,7 +164,8 @@ TEST_F(HTMLSelectElementTest, RestoreUnmatchedFormControlState) { // Restore select->RestoreFormControlState(select_state); EXPECT_EQ(-1, To<HTMLSelectElement>(element)->selectedIndex()); - EXPECT_EQ(nullptr, To<HTMLSelectElement>(element)->OptionToBeShown()); + EXPECT_EQ(nullptr, + To<HTMLSelectElement>(element)->OptionToBeShownForTesting()); } TEST_F(HTMLSelectElementTest, VisibleBoundsInVisualViewport) { @@ -149,13 +193,13 @@ TEST_F(HTMLSelectElementTest, FirstSelectableOption) { { SetHtmlInnerHTML("<select></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ(nullptr, select->FirstSelectableOption()); + EXPECT_EQ(nullptr, FirstSelectableOption(*select)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->FirstSelectableOption()->FastGetAttribute( + EXPECT_EQ("o1", FirstSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } { @@ -163,7 +207,7 @@ TEST_F(HTMLSelectElementTest, FirstSelectableOption) { "<select><option id=o1 disabled></option><option " "id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->FirstSelectableOption()->FastGetAttribute( + EXPECT_EQ("o2", FirstSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } { @@ -171,7 +215,7 @@ TEST_F(HTMLSelectElementTest, FirstSelectableOption) { "<select><option id=o1 style='display:none'></option><option " "id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->FirstSelectableOption()->FastGetAttribute( + EXPECT_EQ("o2", FirstSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } { @@ -179,7 +223,7 @@ TEST_F(HTMLSelectElementTest, FirstSelectableOption) { "<select><optgroup><option id=o1></option><option " "id=o2></option></optgroup></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->FirstSelectableOption()->FastGetAttribute( + EXPECT_EQ("o1", FirstSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } } @@ -188,13 +232,13 @@ TEST_F(HTMLSelectElementTest, LastSelectableOption) { { SetHtmlInnerHTML("<select></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ(nullptr, select->LastSelectableOption()); + EXPECT_EQ(nullptr, LastSelectableOption(*select)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->LastSelectableOption()->FastGetAttribute( + EXPECT_EQ("o2", LastSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } { @@ -202,7 +246,7 @@ TEST_F(HTMLSelectElementTest, LastSelectableOption) { "<select><option id=o1></option><option id=o2 " "disabled></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->LastSelectableOption()->FastGetAttribute( + EXPECT_EQ("o1", LastSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } { @@ -210,7 +254,7 @@ TEST_F(HTMLSelectElementTest, LastSelectableOption) { "<select><option id=o1></option><option id=o2 " "style='display:none'></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->LastSelectableOption()->FastGetAttribute( + EXPECT_EQ("o1", LastSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } { @@ -218,7 +262,7 @@ TEST_F(HTMLSelectElementTest, LastSelectableOption) { "<select><optgroup><option id=o1></option><option " "id=o2></option></optgroup></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->LastSelectableOption()->FastGetAttribute( + EXPECT_EQ("o2", LastSelectableOption(*select)->FastGetAttribute( html_names::kIdAttr)); } } @@ -227,49 +271,50 @@ TEST_F(HTMLSelectElementTest, NextSelectableOption) { { SetHtmlInnerHTML("<select></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ(nullptr, select->NextSelectableOption(nullptr)); + EXPECT_EQ(nullptr, NextSelectableOption(*select, nullptr)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->NextSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o1", NextSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><option id=o1 disabled></option><option " "id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->NextSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o2", NextSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><option id=o1 style='display:none'></option><option " "id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->NextSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o2", NextSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><optgroup><option id=o1></option><option " "id=o2></option></optgroup></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->NextSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o1", NextSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); auto* option = To<HTMLOptionElement>(GetElementById("o1")); - EXPECT_EQ("o2", select->NextSelectableOption(option)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o2", NextSelectableOption(*select, option) + ->FastGetAttribute(html_names::kIdAttr)); - EXPECT_EQ(nullptr, select->NextSelectableOption( - To<HTMLOptionElement>(GetElementById("o2")))); + EXPECT_EQ(nullptr, + NextSelectableOption( + *select, To<HTMLOptionElement>(GetElementById("o2")))); } { SetHtmlInnerHTML( @@ -277,8 +322,8 @@ TEST_F(HTMLSelectElementTest, NextSelectableOption) { "id=o2></option></optgroup></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); auto* option = To<HTMLOptionElement>(GetElementById("o1")); - EXPECT_EQ("o2", select->NextSelectableOption(option)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o2", NextSelectableOption(*select, option) + ->FastGetAttribute(html_names::kIdAttr)); } } @@ -286,49 +331,50 @@ TEST_F(HTMLSelectElementTest, PreviousSelectableOption) { { SetHtmlInnerHTML("<select></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ(nullptr, select->PreviousSelectableOption(nullptr)); + EXPECT_EQ(nullptr, PreviousSelectableOption(*select, nullptr)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->PreviousSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o2", PreviousSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2 " "disabled></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->PreviousSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o1", PreviousSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2 " "style='display:none'></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o1", select->PreviousSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o1", PreviousSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><optgroup><option id=o1></option><option " "id=o2></option></optgroup></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - EXPECT_EQ("o2", select->PreviousSelectableOption(nullptr)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o2", PreviousSelectableOption(*select, nullptr) + ->FastGetAttribute(html_names::kIdAttr)); } { SetHtmlInnerHTML( "<select><option id=o1></option><option id=o2></option></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); auto* option = To<HTMLOptionElement>(GetElementById("o2")); - EXPECT_EQ("o1", select->PreviousSelectableOption(option)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o1", PreviousSelectableOption(*select, option) + ->FastGetAttribute(html_names::kIdAttr)); - EXPECT_EQ(nullptr, select->PreviousSelectableOption( - To<HTMLOptionElement>(GetElementById("o1")))); + EXPECT_EQ(nullptr, + PreviousSelectableOption( + *select, To<HTMLOptionElement>(GetElementById("o1")))); } { SetHtmlInnerHTML( @@ -336,8 +382,8 @@ TEST_F(HTMLSelectElementTest, PreviousSelectableOption) { "id=o2></option></optgroup></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); auto* option = To<HTMLOptionElement>(GetElementById("o2")); - EXPECT_EQ("o1", select->PreviousSelectableOption(option)->FastGetAttribute( - html_names::kIdAttr)); + EXPECT_EQ("o1", PreviousSelectableOption(*select, option) + ->FastGetAttribute(html_names::kIdAttr)); } } @@ -401,7 +447,7 @@ TEST_F(HTMLSelectElementTest, SetRecalcListItemsByOptgroupRemoval) { "<select><optgroup><option>sub1</option><option>sub2</option></" "optgroup></select>"); auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); - select->SetInnerHTMLFromString(""); + select->setInnerHTML(""); // PASS if setInnerHTML didn't have a check failure. } @@ -415,4 +461,109 @@ TEST_F(HTMLSelectElementTest, ScrollToOptionAfterLayoutCrash) { )HTML"); } +TEST_F(HTMLSelectElementTest, CrashOnAttachingMenuList) { + // crbug.com/1044834 + // This test passes if no crash. + SetHtmlInnerHTML("<select><option selected style='direction:rtl'>o1"); + GetDocument().UpdateStyleAndLayoutTree(); + auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); + ASSERT_TRUE(select->GetLayoutObject()); + + // Detach LayoutMenuList. + select->setAttribute("style", "display:none;"); + GetDocument().UpdateStyleAndLayoutTree(); + ASSERT_FALSE(select->GetLayoutObject()); + + // Attach LayoutMenuList again. It triggered null-dereference in + // LayoutMenuList::AdjustInnerStyle(). + select->removeAttribute("style"); + GetDocument().UpdateStyleAndLayoutTree(); + ASSERT_TRUE(select->GetLayoutObject()); +} + +TEST_F(HTMLSelectElementTest, CrashOnAttachingMenuList2) { + // crbug.com/1065125 + // This test passes if no crash. + SetHtmlInnerHTML("<select><optgroup><option>o1</select>"); + auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild()); + select->setTextContent("foo"); + + // Detach LayoutObject. + select->setAttribute("style", "display:none;"); + GetDocument().UpdateStyleAndLayoutTree(); + + // Attach LayoutObject. It triggered a DCHECK failure in + // MenuListSelectType::OptionToBeShown() + select->removeAttribute("style"); + GetDocument().UpdateStyleAndLayoutTree(); +} + +TEST_F(HTMLSelectElementTest, SlotAssignmentRecalcDuringOptionRemoval) { + // crbug.com/1056094 + // This test passes if no CHECK failure about IsSlotAssignmentRecalcForbidden. + SetHtmlInnerHTML("<div dir=auto><select><option>option0"); + auto* select = GetDocument().body()->firstChild()->firstChild(); + auto* option = select->firstChild(); + select->appendChild(option); + option->remove(); +} + +// crbug.com/1060039 +TEST_F(HTMLSelectElementTest, SelectMultipleOptionsByPopup) { + GetDocument().GetSettings()->SetScriptEnabled(true); + LayoutTheme::GetTheme().SetDelegatesMenuListRenderingForTesting(true); + + // Select the same set of options. + { + SetHtmlInnerHTML( + "<select multiple onchange='this.remove();'>" + "<option>o0</option><option>o1</option></select>"); + EXPECT_TRUE(FirstSelectIsConnectedAfterSelectMultiple(Vector<int>{})) + << "Onchange handler should not be executed."; + } + { + SetHtmlInnerHTML( + "<select multiple onchange='this.remove();'>" + "<option>o0</option><option selected>o1</option></select>"); + EXPECT_TRUE(FirstSelectIsConnectedAfterSelectMultiple(Vector<int>{1})) + << "Onchange handler should not be executed."; + } + + // 0 old selected options -> 1+ selected options + { + SetHtmlInnerHTML( + "<select multiple onchange='this.remove();'>" + "<option>o0</option><option>o1</option></select>"); + EXPECT_FALSE(FirstSelectIsConnectedAfterSelectMultiple(Vector<int>{0})) + << "Onchange handler should be executed."; + } + + // 1+ old selected options -> more selected options + { + SetHtmlInnerHTML( + "<select multiple onchange='this.remove();'>" + "<option>o0</option><option selected>o1</option></select>"); + EXPECT_FALSE(FirstSelectIsConnectedAfterSelectMultiple(Vector<int>{0, 1})) + << "Onchange handler should be executed."; + } + + // 1+ old selected options -> 0 selected options + { + SetHtmlInnerHTML( + "<select multiple onchange='this.remove();'>" + "<option>o0</option><option selected>o1</option></select>"); + EXPECT_FALSE(FirstSelectIsConnectedAfterSelectMultiple(Vector<int>{})) + << "Onchange handler should be executed."; + } + + // Multiple old selected options -> less selected options + { + SetHtmlInnerHTML( + "<select multiple onchange='this.remove();'>" + "<option selected>o0</option><option selected>o1</option></select>"); + EXPECT_FALSE(FirstSelectIsConnectedAfterSelectMultiple(Vector<int>{1})) + << "Onchange handler should be executed."; + } +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc index 4924dfa0d84..d10fd35c9af 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc @@ -37,6 +37,8 @@ #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h" #include "third_party/blink/renderer/core/event_interface_names.h" #include "third_party/blink/renderer/core/events/before_text_inserted_event.h" +#include "third_party/blink/renderer/core/events/drag_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/html/forms/form_controller.h" #include "third_party/blink/renderer/core/html/forms/form_data.h" @@ -69,6 +71,11 @@ static inline unsigned ComputeLengthForAPIValue(const String& text) { return text.length() - crlf_count; } +static inline void ReplaceCRWithNewLine(String& text) { + text.Replace("\r\n", "\n"); + text.Replace('\r', '\n'); +} + HTMLTextAreaElement::HTMLTextAreaElement(Document& document) : TextControlElement(html_names::kTextareaTag, document), rows_(kDefaultRows), @@ -154,7 +161,7 @@ void HTMLTextAreaElement::ParseAttribute( rows_ = rows; if (GetLayoutObject()) { GetLayoutObject() - ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( layout_invalidation_reason::kAttributeChanged); } } @@ -167,7 +174,7 @@ void HTMLTextAreaElement::ParseAttribute( cols_ = cols; if (LayoutObject* layout_object = GetLayoutObject()) { layout_object - ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( layout_invalidation_reason::kAttributeChanged); } } @@ -176,11 +183,11 @@ void HTMLTextAreaElement::ParseAttribute( // deprecated. The soft/hard /off values are a recommendation for HTML 4 // extension by IE and NS 4. WrapMethod wrap; - if (DeprecatedEqualIgnoringCase(value, "physical") || - DeprecatedEqualIgnoringCase(value, "hard") || - DeprecatedEqualIgnoringCase(value, "on")) + if (EqualIgnoringASCIICase(value, "physical") || + EqualIgnoringASCIICase(value, "hard") || + EqualIgnoringASCIICase(value, "on")) wrap = kHardWrap; - else if (DeprecatedEqualIgnoringCase(value, "off")) + else if (EqualIgnoringASCIICase(value, "off")) wrap = kNoWrap; else wrap = kSoftWrap; @@ -188,7 +195,7 @@ void HTMLTextAreaElement::ParseAttribute( wrap_ = wrap; if (LayoutObject* layout_object = GetLayoutObject()) { layout_object - ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( layout_invalidation_reason::kAttributeChanged); } } @@ -214,7 +221,7 @@ void HTMLTextAreaElement::AppendToFormData(FormData& form_data) { if (GetName().IsEmpty()) return; - GetDocument().UpdateStyleAndLayout(); + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kForm); const String& text = (wrap_ == kHardWrap) ? ValueWithHardLineBreaks() : value(); @@ -264,7 +271,7 @@ void HTMLTextAreaElement::UpdateFocusAppearanceWithOptions( void HTMLTextAreaElement::DefaultEventHandler(Event& event) { if (GetLayoutObject() && - (event.IsMouseEvent() || event.IsDragEvent() || + (IsA<MouseEvent>(event) || IsA<DragEvent>(event) || event.HasInterface(event_interface_names::kWheelEvent) || event.type() == event_type_names::kBlur)) { ForwardEvent(event); @@ -332,7 +339,7 @@ void HTMLTextAreaElement::HandleBeforeTextInsertedEvent( if (IsFocused()) { // TODO(editing-dev): Use of UpdateStyleAndLayout // needs to be audited. See http://crbug.com/590369 for more details. - GetDocument().UpdateStyleAndLayout(); + GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kForm); selection_length = ComputeLengthForAPIValue( GetDocument().GetFrame()->Selection().SelectedText()); @@ -397,8 +404,7 @@ void HTMLTextAreaElement::SetValueCommon( // Code elsewhere normalizes line endings added by the user via the keyboard // or pasting. We normalize line endings coming from JavaScript here. String normalized_value = new_value; - normalized_value.Replace("\r\n", "\n"); - normalized_value.Replace('\r', '\n'); + ReplaceCRWithNewLine(normalized_value); // Clear the suggested value. Use the base class version to not trigger a view // update. @@ -445,6 +451,10 @@ void HTMLTextAreaElement::SetValueCommon( DispatchFormControlChangeEvent(); break; + case TextFieldEventBehavior::kDispatchInputEvent: + DispatchInputEvent(); + break; + case TextFieldEventBehavior::kDispatchInputAndChangeEvent: DispatchInputEvent(); DispatchFormControlChangeEvent(); @@ -503,10 +513,12 @@ String HTMLTextAreaElement::validationMessage() const { bool HTMLTextAreaElement::ValueMissing() const { // We should not call value() for performance. - return willValidate() && ValueMissing(nullptr); + return ValueMissing(nullptr); } bool HTMLTextAreaElement::ValueMissing(const String* value) const { + // For textarea elements, the value is missing only if it is mutable. + // https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-required return IsRequiredFormControl() && !IsDisabledOrReadOnly() && (value ? *value : this->value()).IsEmpty(); } @@ -602,7 +614,10 @@ void HTMLTextAreaElement::UpdatePlaceholderText() { IsPlaceholderVisible() ? CSSValueID::kBlock : CSSValueID::kNone, true); UserAgentShadowRoot()->InsertBefore(placeholder, InnerEditorElement()); } - placeholder->setTextContent(placeholder_text); + String normalized_value = placeholder_text; + // https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-placeholder + ReplaceCRWithNewLine(normalized_value); + placeholder->setTextContent(normalized_value); } String HTMLTextAreaElement::GetPlaceholderValue() const { diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h index 4980524e917..8700e494c15 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h @@ -117,6 +117,7 @@ class CORE_EXPORT HTMLTextAreaElement final : public TextControlElement { const QualifiedName&, const AtomicString&, MutableCSSPropertyValueSet*) override; + bool TypeShouldForceLegacyLayout() const override { return true; } LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override; void AppendToFormData(FormData&) override; void ResetImpl() override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc index 53857f6329a..53abd4c58ba 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc @@ -87,12 +87,12 @@ bool ImageInputType::SupportsValidation() const { } static IntPoint ExtractClickLocation(const Event& event) { - if (!event.UnderlyingEvent() || !event.UnderlyingEvent()->IsMouseEvent()) + const auto* mouse_event = DynamicTo<MouseEvent>(event.UnderlyingEvent()); + if (!event.UnderlyingEvent() || !mouse_event) return IntPoint(); - auto& mouse_event = *ToMouseEvent(event.UnderlyingEvent()); - if (!mouse_event.HasPosition()) + if (!mouse_event->HasPosition()) return IntPoint(); - return IntPoint(mouse_event.offsetX(), mouse_event.offsetY()); + return IntPoint(mouse_event->offsetX(), mouse_event->offsetY()); } void ImageInputType::HandleDOMActivateEvent(Event& event) { @@ -104,6 +104,10 @@ void ImageInputType::HandleDOMActivateEvent(Event& event) { event.SetDefaultHandled(); } +bool ImageInputType::TypeShouldForceLegacyLayout() const { + return true; +} + LayoutObject* ImageInputType::CreateLayoutObject(const ComputedStyle& style, LegacyLayout legacy) const { if (use_fallback_content_) @@ -188,7 +192,8 @@ unsigned ImageInputType::Height() const { } } - GetElement().GetDocument().UpdateStyleAndLayout(); + GetElement().GetDocument().UpdateStyleAndLayout( + DocumentUpdateReason::kJavaScript); LayoutBox* box = GetElement().GetLayoutBox(); return box ? AdjustForAbsoluteZoom::AdjustInt(box->ContentHeight().ToInt(), @@ -213,7 +218,8 @@ unsigned ImageInputType::Width() const { } } - GetElement().GetDocument().UpdateStyleAndLayout(); + GetElement().GetDocument().UpdateStyleAndLayout( + DocumentUpdateReason::kJavaScript); LayoutBox* box = GetElement().GetLayoutBox(); return box ? AdjustForAbsoluteZoom::AdjustInt(box->ContentWidth().ToInt(), @@ -277,13 +283,9 @@ void ImageInputType::CreateShadowSubtree() { HTMLImageFallbackHelper::CreateAltTextShadowTree(GetElement()); } -scoped_refptr<ComputedStyle> ImageInputType::CustomStyleForLayoutObject( - scoped_refptr<ComputedStyle> new_style) { - if (!use_fallback_content_) - return new_style; - - return HTMLImageFallbackHelper::CustomStyleForAltText(GetElement(), - std::move(new_style)); +void ImageInputType::CustomStyleForLayoutObject(ComputedStyle& style) { + if (use_fallback_content_) + HTMLImageFallbackHelper::CustomStyleForAltText(GetElement(), style); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h index 01d26bee19d..647923dc7d3 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h @@ -41,8 +41,7 @@ namespace blink { class ImageInputType final : public BaseButtonInputType { public: explicit ImageInputType(HTMLInputElement&); - scoped_refptr<ComputedStyle> CustomStyleForLayoutObject( - scoped_refptr<ComputedStyle>) override; + void CustomStyleForLayoutObject(ComputedStyle& style) override; private: void CountUsage() override; @@ -51,6 +50,7 @@ class ImageInputType final : public BaseButtonInputType { void AppendToFormData(FormData&) const override; String ResultForDialogSubmit() const override; bool SupportsValidation() const override; + bool TypeShouldForceLegacyLayout() const override; LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) const override; void HandleDOMActivateEvent(Event&) override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/input_type.cc index d0b43d21802..dbe99ae5e5b 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type.cc @@ -70,6 +70,7 @@ #include "third_party/blink/renderer/core/layout/layout_theme.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" +#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/json/json_values.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" #include "third_party/blink/renderer/platform/text/text_break_iterator.h" @@ -253,6 +254,8 @@ void InputType::SetValueAsDecimal(const Decimal& new_value, void InputType::ReadingChecked() const {} +void InputType::WillUpdateCheckedness(bool) {} + bool InputType::SupportsValidation() const { return true; } @@ -296,7 +299,15 @@ bool InputType::RangeUnderflow(const String& value) const { if (!numeric_value.IsFinite()) return false; - return numeric_value < CreateStepRange(kRejectAny).Minimum(); + StepRange step_range = CreateStepRange(kRejectAny); + if (step_range.HasReversedRange()) { + // With a reversed range, any value outside of the midnight-crossing valid + // range is considered underflow and overflow. + return numeric_value > step_range.Maximum() && + numeric_value < step_range.Minimum(); + } else { + return numeric_value < step_range.Minimum(); + } } bool InputType::RangeOverflow(const String& value) const { @@ -307,7 +318,15 @@ bool InputType::RangeOverflow(const String& value) const { if (!numeric_value.IsFinite()) return false; - return numeric_value > CreateStepRange(kRejectAny).Maximum(); + StepRange step_range = CreateStepRange(kRejectAny); + if (step_range.HasReversedRange()) { + // With a reversed range, any value outside of the midnight-crossing valid + // range is considered underflow and overflow. + return numeric_value > step_range.Maximum() && + numeric_value < step_range.Minimum(); + } else { + return numeric_value > step_range.Maximum(); + } } Decimal InputType::DefaultValueForStepUp() const { @@ -389,6 +408,17 @@ String InputType::RangeUnderflowText(const Decimal&) const { return String(); } +String InputType::ReversedRangeOutOfRangeText(const Decimal&, + const Decimal&) const { + NOTREACHED(); + return String(); +} + +String InputType::RangeInvalidText(const Decimal&, const Decimal&) const { + NOTREACHED(); + return String(); +} + String InputType::TypeMismatchText() const { return GetLocale().QueryString(IDS_FORM_VALIDATION_TYPE_MISMATCH); } @@ -444,6 +474,20 @@ std::pair<String, String> InputType::ValidationMessage( StepRange step_range(CreateStepRange(kRejectAny)); + if (step_range.Minimum() > step_range.Maximum() && + !step_range.HasReversedRange()) { + return std::make_pair( + RangeInvalidText(step_range.Minimum(), step_range.Maximum()), + g_empty_string); + } + + if (step_range.HasReversedRange() && numeric_value < step_range.Minimum() && + numeric_value > step_range.Maximum()) { + return std::make_pair( + ReversedRangeOutOfRangeText(step_range.Minimum(), step_range.Maximum()), + g_empty_string); + } + if (numeric_value < step_range.Minimum()) return std::make_pair(RangeUnderflowText(step_range.Minimum()), g_empty_string); @@ -589,6 +633,9 @@ void InputType::SetValue(const String& sanitized_value, case TextFieldEventBehavior::kDispatchChangeEvent: GetElement().DispatchFormControlChangeEvent(); break; + case TextFieldEventBehavior::kDispatchInputEvent: + GetElement().DispatchInputEvent(); + break; case TextFieldEventBehavior::kDispatchInputAndChangeEvent: GetElement().DispatchInputEvent(); GetElement().DispatchFormControlChangeEvent(); @@ -765,7 +812,7 @@ void InputType::ApplyStep(const Decimal& current, Decimal new_value = current; const AtomicString& step_string = GetElement().FastGetAttribute(html_names::kStepAttr); - if (!DeprecatedEqualIgnoringCase(step_string, "any") && + if (!EqualIgnoringASCIICase(step_string, "any") && step_range.StepMismatch(current)) { // Snap-to-step / clamping steps // If the current value is not matched to step value: @@ -786,10 +833,10 @@ void InputType::ApplyStep(const Decimal& current, } new_value = new_value + step_range.Step() * Decimal::FromDouble(count); - if (!DeprecatedEqualIgnoringCase(step_string, "any")) + if (!EqualIgnoringASCIICase(step_string, "any")) new_value = step_range.AlignValueForStep(current, new_value); - // 7. If the element has a minimum, and value is less than that minimum, + // 8. If the element has a minimum, and value is less than that minimum, // then set value to the smallest value that, when subtracted from the step // base, is an integral multiple of the allowed value step, and that is more // than or equal to minimum. @@ -800,17 +847,23 @@ void InputType::ApplyStep(const Decimal& current, new_value = aligned_minimum; } - // 8. If the element has a maximum, and value is greater than that maximum, + // 9. If the element has a maximum, and value is greater than that maximum, // then set value to the largest value that, when subtracted from the step // base, is an integral multiple of the allowed value step, and that is less // than or equal to maximum. if (new_value > step_range.Maximum()) new_value = aligned_maximum; - // 9. Let value as string be the result of running the algorithm to convert + // 10. If either the method invoked was the stepDown() method and value is + // greater than valueBeforeStepping, or the method invoked was the stepUp() + // method and value is less than valueBeforeStepping, then return. + if ((count < 0 && current < new_value) || (count > 0 && current > new_value)) + return; + + // 11. Let value as string be the result of running the algorithm to convert // a number to a string, as defined for the input element's type attribute's // current state, on value. - // 10. Set the value of the element to value as string. + // 12. Set the value of the element to value as string. SetValueAsDecimal(new_value, event_behavior, exception_state); if (AXObjectCache* cache = GetElement().GetDocument().ExistingAXObjectCache()) @@ -856,7 +909,7 @@ void InputType::StepUpFromLayoutObject(int n) { // * If 0 is in-range, but not matched to step value // - The value should be the larger matched value nearest to 0 if n > 0 // e.g. <input type=number min=-100 step=3> -> 2 - // - The value should be the smaler matched value nearest to 0 if n < 0 + // - The value should be the smaller matched value nearest to 0 if n < 0 // e.g. <input type=number min=-100 step=3> -> -1 // As for date/datetime-local/month/time/week types, the current value is // assumed as "the current local date/time". @@ -941,12 +994,35 @@ Decimal InputType::FindStepBase(const Decimal& default_value) const { return step_base; } +StepRange InputType::CreateReversibleStepRange( + AnyStepHandling any_step_handling, + const Decimal& step_base_default, + const Decimal& minimum_default, + const Decimal& maximum_default, + const StepRange::StepDescription& step_description) const { + return CreateStepRange(any_step_handling, step_base_default, minimum_default, + maximum_default, step_description, + /*supports_reversed_range=*/true); +} + StepRange InputType::CreateStepRange( AnyStepHandling any_step_handling, const Decimal& step_base_default, const Decimal& minimum_default, const Decimal& maximum_default, const StepRange::StepDescription& step_description) const { + return CreateStepRange(any_step_handling, step_base_default, minimum_default, + maximum_default, step_description, + /*supports_reversed_range=*/false); +} + +StepRange InputType::CreateStepRange( + AnyStepHandling any_step_handling, + const Decimal& step_base_default, + const Decimal& minimum_default, + const Decimal& maximum_default, + const StepRange::StepDescription& step_description, + bool supports_reversed_range) const { bool has_range_limitations = false; const Decimal step_base = FindStepBase(step_base_default); Decimal minimum = @@ -964,17 +1040,20 @@ StepRange InputType::CreateStepRange( const Decimal step = StepRange::ParseStep( any_step_handling, step_description, GetElement().FastGetAttribute(html_names::kStepAttr)); - return StepRange(step_base, minimum, maximum, has_range_limitations, step, - step_description); + bool has_reversed_range = + has_range_limitations && supports_reversed_range && maximum < minimum; + return StepRange(step_base, minimum, maximum, has_range_limitations, + has_reversed_range, step, step_description); } void InputType::AddWarningToConsole(const char* message_format, const String& value) const { - GetElement().GetDocument().AddConsoleMessage(ConsoleMessage::Create( - mojom::ConsoleMessageSource::kRendering, - mojom::ConsoleMessageLevel::kWarning, - String::Format(message_format, - JSONValue::QuoteString(value).Utf8().c_str()))); + GetElement().GetDocument().AddConsoleMessage( + MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kRendering, + mojom::ConsoleMessageLevel::kWarning, + String::Format(message_format, + JSONValue::QuoteString(value).Utf8().c_str()))); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type.h b/chromium/third_party/blink/renderer/core/html/forms/input_type.h index 9f36023abe6..45ec3898704 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type.h @@ -104,7 +104,12 @@ class CORE_EXPORT InputType : public GarbageCollected<InputType> { virtual void SetValueAsDecimal(const Decimal&, TextFieldEventBehavior, ExceptionState&) const; + + // Functions related to 'checked' + virtual void ReadingChecked() const; + // The function is called just before updating checkedness. + virtual void WillUpdateCheckedness(bool new_checked); // Validation functions @@ -140,6 +145,10 @@ class CORE_EXPORT InputType : public GarbageCollected<InputType> { virtual String BadInputText() const; virtual String RangeOverflowText(const Decimal& maximum) const; virtual String RangeUnderflowText(const Decimal& minimum) const; + virtual String ReversedRangeOutOfRangeText(const Decimal& minimum, + const Decimal& maximum) const; + virtual String RangeInvalidText(const Decimal& minimum, + const Decimal& maximum) const; virtual String TypeMismatchText() const; virtual String ValueMissingText() const; virtual bool CanSetStringValue() const; @@ -240,6 +249,11 @@ class CORE_EXPORT InputType : public GarbageCollected<InputType> { const Decimal& minimum_default, const Decimal& maximum_default, const StepRange::StepDescription&) const; + StepRange CreateReversibleStepRange(AnyStepHandling, + const Decimal& step_base_default, + const Decimal& minimum_default, + const Decimal& maximum_default, + const StepRange::StepDescription&) const; void AddWarningToConsole(const char* message_format, const String& value) const; @@ -252,6 +266,13 @@ class CORE_EXPORT InputType : public GarbageCollected<InputType> { TextFieldEventBehavior, ExceptionState&); + StepRange CreateStepRange(AnyStepHandling, + const Decimal& step_base_default, + const Decimal& minimum_default, + const Decimal& maximum_default, + const StepRange::StepDescription&, + bool supports_reversed_range) const; + Member<HTMLInputElement> element_; DISALLOW_COPY_AND_ASSIGN(InputType); diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc index ed41b3c08be..336cea6892e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc @@ -74,28 +74,29 @@ void InputTypeView::DispatchSimulatedClickIfActive(KeyboardEvent& event) const { void InputTypeView::AccessKeyAction(bool) { GetElement().focus(FocusParams(SelectionBehaviorOnFocus::kReset, - kWebFocusTypeNone, nullptr)); + mojom::blink::FocusType::kNone, nullptr)); } bool InputTypeView::ShouldSubmitImplicitly(const Event& event) { - return event.IsKeyboardEvent() && - event.type() == event_type_names::kKeypress && - ToKeyboardEvent(event).charCode() == '\r'; + auto* keyboard_event = DynamicTo<KeyboardEvent>(event); + return keyboard_event && event.type() == event_type_names::kKeypress && + keyboard_event->charCode() == '\r'; } HTMLFormElement* InputTypeView::FormForSubmission() const { return GetElement().Form(); } +bool InputTypeView::TypeShouldForceLegacyLayout() const { + return false; +} + LayoutObject* InputTypeView::CreateLayoutObject(const ComputedStyle& style, LegacyLayout legacy) const { return LayoutObject::CreateObject(&GetElement(), style, legacy); } -scoped_refptr<ComputedStyle> InputTypeView::CustomStyleForLayoutObject( - scoped_refptr<ComputedStyle> original_style) { - return original_style; -} +void InputTypeView::CustomStyleForLayoutObject(ComputedStyle&) {} TextDirection InputTypeView::ComputedTextDirection() { return GetElement().ComputedStyleRef().Direction(); @@ -111,12 +112,16 @@ bool InputTypeView::HasCustomFocusLogic() const { void InputTypeView::HandleBlurEvent() {} -void InputTypeView::HandleFocusInEvent(Element*, WebFocusType) {} +void InputTypeView::HandleFocusInEvent(Element*, mojom::blink::FocusType) {} void InputTypeView::StartResourceLoading() {} void InputTypeView::ClosePopupView() {} +bool InputTypeView::HasOpenedPopup() const { + return false; +} + bool InputTypeView::NeedsShadowSubtree() const { return true; } @@ -128,6 +133,14 @@ void InputTypeView::DestroyShadowSubtree() { root->RemoveChildren(); } +HTMLInputElement* InputTypeView::UploadButton() const { + return nullptr; +} + +String InputTypeView::FileStatusText() const { + return String(); +} + void InputTypeView::AltAttributeChanged() {} void InputTypeView::SrcAttributeChanged() {} @@ -181,6 +194,10 @@ void InputTypeView::RestoreFormControlState(const FormControlState& state) { GetElement().setValue(state[0]); } +bool InputTypeView::IsDraggedSlider() const { + return false; +} + bool InputTypeView::HasBadInput() const { return false; } @@ -190,4 +207,8 @@ void ClickHandlingState::Trace(Visitor* visitor) { EventDispatchHandlingState::Trace(visitor); } +String InputTypeView::RawValue() const { + return g_empty_string; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h index ef47deadfda..cba7ed4d453 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h @@ -34,7 +34,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_VIEW_H_ #include "base/macros.h" -#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/events/event_dispatcher.h" #include "third_party/blink/renderer/platform/heap/handle.h" @@ -91,7 +91,8 @@ class CORE_EXPORT InputTypeView : public GarbageCollectedMixin { virtual bool ShouldSubmitImplicitly(const Event&); virtual HTMLFormElement* FormForSubmission() const; virtual bool HasCustomFocusLogic() const; - virtual void HandleFocusInEvent(Element* old_focused_element, WebFocusType); + virtual void HandleFocusInEvent(Element* old_focused_element, + mojom::blink::FocusType); virtual void HandleBlurEvent(); virtual void HandleDOMActivateEvent(Event&); virtual void AccessKeyAction(bool send_mouse_events); @@ -99,16 +100,23 @@ class CORE_EXPORT InputTypeView : public GarbageCollectedMixin { void DispatchSimulatedClickIfActive(KeyboardEvent&) const; virtual void SubtreeHasChanged(); + virtual bool TypeShouldForceLegacyLayout() const; virtual LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) const; - virtual scoped_refptr<ComputedStyle> CustomStyleForLayoutObject( - scoped_refptr<ComputedStyle>); + virtual void CustomStyleForLayoutObject(ComputedStyle& style); virtual TextDirection ComputedTextDirection(); virtual void StartResourceLoading(); virtual void ClosePopupView(); + virtual bool HasOpenedPopup() const; + + // Functions for shadow trees + virtual bool NeedsShadowSubtree() const; virtual void CreateShadowSubtree(); virtual void DestroyShadowSubtree(); + virtual HTMLInputElement* UploadButton() const; + virtual String FileStatusText() const; + virtual void MinOrMaxAttributeChanged(); virtual void StepAttributeChanged(); virtual void AltAttributeChanged(); @@ -129,10 +137,13 @@ class CORE_EXPORT InputTypeView : public GarbageCollectedMixin { virtual bool HasFallbackContent() const { return false; } virtual FormControlState SaveFormControlState() const; virtual void RestoreFormControlState(const FormControlState&); + virtual bool IsDraggedSlider() const; // Validation functions virtual bool HasBadInput() const; + virtual String RawValue() const; + protected: InputTypeView(HTMLInputElement& element) : element_(&element) {} HTMLInputElement& GetElement() const { return *element_; } diff --git a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc index 5b3fa566f25..360545b4afe 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc @@ -5,9 +5,9 @@ #include "third_party/blink/renderer/core/html/forms/internal_popup_menu.h" #include "build/build_config.h" +#include "third_party/blink/public/common/input/web_mouse_event.h" #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/task_type.h" -#include "third_party/blink/public/platform/web_mouse_event.h" #include "third_party/blink/renderer/core/css/css_font_selector.h" #include "third_party/blink/renderer/core/css/style_engine.h" #include "third_party/blink/renderer/core/dom/element_traversal.h" @@ -31,6 +31,7 @@ #include "third_party/blink/renderer/platform/fonts/font_selector_client.h" #include "third_party/blink/renderer/platform/geometry/int_rect.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -286,6 +287,8 @@ void InternalPopupMenu::WriteDocument(SharedBuffer* data) { AddProperty("scaleFactor", scale_factor, data); bool is_rtl = !owner_style->IsLeftToRightDirection(); AddProperty("isRTL", is_rtl, data); + AddProperty("isFormControlsRefreshEnabled", + features::IsFormControlsRefreshEnabled(), data); AddProperty("paddingStart", is_rtl ? owner_element.ClientPaddingRight().ToDouble() : owner_element.ClientPaddingLeft().ToDouble(), @@ -411,11 +414,11 @@ void InternalPopupMenu::AddSeparator(ItemIterationContext& context, PagePopupClient::AddString("},\n", data); } -void InternalPopupMenu::SelectFontsFromOwnerDocument(Document& document) { +CSSFontSelector* InternalPopupMenu::CreateCSSFontSelector( + Document& popup_document) { Document& owner_document = OwnerElement().GetDocument(); - document.GetStyleEngine().SetFontSelector( - MakeGarbageCollected<PopupMenuCSSFontSelector>( - &document, owner_document.GetStyleEngine().GetFontSelector())); + return MakeGarbageCollected<PopupMenuCSSFontSelector>( + &popup_document, owner_document.GetStyleEngine().GetFontSelector()); } void InternalPopupMenu::SetValueAndClosePopup(int num_value, diff --git a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h index b513ae14683..b6af0908fa0 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h +++ b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h @@ -12,6 +12,7 @@ namespace blink { class ChromeClient; +class CSSFontSelector; class PagePopup; class HTMLElement; class HTMLHRElement; @@ -49,7 +50,7 @@ class CORE_EXPORT InternalPopupMenu final : public PopupMenu, // PagePopupClient functions: void WriteDocument(SharedBuffer*) override; - void SelectFontsFromOwnerDocument(Document&) override; + CSSFontSelector* CreateCSSFontSelector(Document& popup_document) override; void SetValueAndClosePopup(int, const String&) override; void SetValue(const String&) override; void CancelPopup() override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc index 72c34bf2c8f..e74aa17fb04 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc @@ -25,14 +25,13 @@ TEST(InternalPopupMenuTest, WriteDocumentInStyleDirtyTree) { auto dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600)); Document& document = dummy_page_holder_->GetDocument(); - document.body()->SetInnerHTMLFromString(R"HTML( + document.body()->setInnerHTML(R"HTML( <select id="select"> <option value="foo">Foo</option> <option value="bar" style="display:none">Bar</option> </select> )HTML"); - document.View()->UpdateAllLifecyclePhases( - DocumentLifecycle::LifecycleUpdateReason::kTest); + document.View()->UpdateAllLifecyclePhases(DocumentUpdateReason::kTest); auto* select = To<HTMLSelectElement>(document.getElementById("select")); ASSERT_TRUE(select); auto* menu = MakeGarbageCollected<InternalPopupMenu>( @@ -50,7 +49,7 @@ TEST(InternalPopupMenuTest, ShowSelectDisplayNone) { auto dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600)); Document& document = dummy_page_holder_->GetDocument(); - document.body()->SetInnerHTMLFromString(R"HTML( + document.body()->setInnerHTML(R"HTML( <div id="container"> <select id="select"> <option>1</option> @@ -58,8 +57,8 @@ TEST(InternalPopupMenuTest, ShowSelectDisplayNone) { </select> </div> )HTML"); - document.View()->UpdateAllLifecyclePhases( - DocumentLifecycle::LifecycleUpdateReason::kTest); + document.View()->UpdateAllLifecyclePhases(DocumentUpdateReason::kTest); + auto* div = document.getElementById("container"); auto* select = To<HTMLSelectElement>(document.getElementById("select")); ASSERT_TRUE(select); diff --git a/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h index ba26f27e7a7..106dae21b52 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h +++ b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h @@ -25,7 +25,6 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELS_NODE_LIST_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELS_NODE_LIST_H_ -#include "base/memory/scoped_refptr.h" #include "third_party/blink/renderer/core/dom/live_node_list.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc b/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc index 53ef3614d4b..5bfc398d3bb 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc @@ -45,6 +45,7 @@ #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/validation_message_client.h" +#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/text/bidi_text_run.h" #include "third_party/blink/renderer/platform/wtf/functional.h" @@ -314,8 +315,7 @@ void ListedElement::UpdateWillValidateCache() { } bool ListedElement::CustomError() const { - return ToHTMLElement().willValidate() && - !custom_validation_message_.IsEmpty(); + return !custom_validation_message_.IsEmpty(); } bool ListedElement::HasBadInput() const { @@ -371,7 +371,9 @@ void ListedElement::SetCustomValidationMessage(const String& message) { } String ListedElement::validationMessage() const { - return CustomError() ? custom_validation_message_ : String(); + return ToHTMLElement().willValidate() && CustomError() + ? custom_validation_message_ + : String(); } String ListedElement::ValidationSubMessage() const { @@ -493,7 +495,7 @@ bool ListedElement::reportValidity() { // Update layout now before calling IsFocusable(), which has // !LayoutObject()->NeedsLayout() assertion. HTMLElement& element = ToHTMLElement(); - element.GetDocument().UpdateStyleAndLayout(); + element.GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kForm); if (element.IsFocusable()) { ShowValidationMessage(); return false; @@ -503,8 +505,9 @@ bool ListedElement::reportValidity() { "An invalid form control with name='%name' is not focusable."); message.Replace("%name", GetName()); element.GetDocument().AddConsoleMessage( - ConsoleMessage::Create(mojom::ConsoleMessageSource::kRendering, - mojom::ConsoleMessageLevel::kError, message)); + MakeGarbageCollected<ConsoleMessage>( + mojom::ConsoleMessageSource::kRendering, + mojom::ConsoleMessageLevel::kError, message)); } return false; } diff --git a/chromium/third_party/blink/renderer/core/html/forms/menu_list_inner_element.cc b/chromium/third_party/blink/renderer/core/html/forms/menu_list_inner_element.cc new file mode 100644 index 00000000000..abd197d33c8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/menu_list_inner_element.cc @@ -0,0 +1,76 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/menu_list_inner_element.h" + +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/style/computed_style.h" + +namespace blink { + +MenuListInnerElement::MenuListInnerElement(Document& document) + : HTMLDivElement(document) { + SetHasCustomStyleCallbacks(); +} + +scoped_refptr<ComputedStyle> +MenuListInnerElement::CustomStyleForLayoutObject() { + const ComputedStyle& parent_style = OwnerShadowHost()->ComputedStyleRef(); + scoped_refptr<ComputedStyle> style = + ComputedStyle::CreateAnonymousStyleWithDisplay(parent_style, + EDisplay::kBlock); + style->SetFlexGrow(1); + style->SetFlexShrink(1); + // min-width: 0; is needed for correct shrinking. + style->SetMinWidth(Length::Fixed(0)); + style->SetHasLineIfEmpty(true); + style->SetOverflowX(EOverflow::kHidden); + style->SetOverflowY(EOverflow::kHidden); + style->SetShouldIgnoreOverflowPropertyForInlineBlockBaseline(); + style->SetTextOverflow(parent_style.TextOverflow()); + style->SetUserModify(EUserModify::kReadOnly); + + // Use margin:auto instead of align-items:center to get safe centering, i.e. + // when the content overflows, treat it the same as align-items: flex-start. + // But we only do that for the cases where html.css would otherwise use + // center. + if (parent_style.AlignItemsPosition() == ItemPosition::kCenter) { + style->SetMarginTop(Length()); + style->SetMarginBottom(Length()); + style->SetAlignSelfPosition(ItemPosition::kFlexStart); + } + + // We set margin-left/right instead of padding-left/right to clip text by + // 'overflow: hidden'. + LayoutTheme& theme = LayoutTheme::GetTheme(); + Length margin_start = + Length::Fixed(theme.PopupInternalPaddingStart(parent_style)); + Length margin_end = Length::Fixed( + theme.PopupInternalPaddingEnd(GetDocument().GetFrame(), parent_style)); + if (parent_style.IsLeftToRightDirection()) { + style->SetTextAlign(ETextAlign::kLeft); + style->SetMarginLeft(margin_start); + style->SetMarginRight(margin_end); + } else { + style->SetTextAlign(ETextAlign::kRight); + style->SetMarginLeft(margin_end); + style->SetMarginRight(margin_start); + } + style->SetPaddingTop( + Length::Fixed(theme.PopupInternalPaddingTop(parent_style))); + style->SetPaddingBottom( + Length::Fixed(theme.PopupInternalPaddingBottom(parent_style))); + + if (const ComputedStyle* option_style = + To<HTMLSelectElement>(OwnerShadowHost())->OptionStyle()) { + style->SetDirection(option_style->Direction()); + style->SetUnicodeBidi(option_style->GetUnicodeBidi()); + } + + return style; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/menu_list_inner_element.h b/chromium/third_party/blink/renderer/core/html/forms/menu_list_inner_element.h new file mode 100644 index 00000000000..b3f7085cb59 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/menu_list_inner_element.h @@ -0,0 +1,21 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MENU_LIST_INNER_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MENU_LIST_INNER_ELEMENT_H_ + +#include "third_party/blink/renderer/core/html/html_div_element.h" + +namespace blink { + +class MenuListInnerElement : public HTMLDivElement { + public: + explicit MenuListInnerElement(Document& document); + + private: + scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override; +}; + +} // namespace blink +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MENU_LIST_INNER_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc index 807f7e5cafb..e72af81f289 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc @@ -30,6 +30,7 @@ #include "third_party/blink/renderer/core/html/forms/month_input_type.h" +#include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/core/frame/web_feature.h" #include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" @@ -168,4 +169,8 @@ bool MonthInputType::IsValidFormat(bool has_year, return has_year && has_month; } +String MonthInputType::AriaRoleForPickerIndicator() const { + return GetLocale().QueryString(IDS_AX_CALENDAR_SHOW_MONTH_PICKER); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h index 27feb7cf4ae..0bbd850b3be 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h @@ -66,6 +66,7 @@ class MonthInputType final : public BaseTemporalInputType { bool has_hour, bool has_minute, bool has_second) const override; + String AriaRoleForPickerIndicator() const override; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc index 665de490bc9..76092674346 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc @@ -30,6 +30,7 @@ #include "third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" #include "third_party/blink/renderer/core/css/style_change_reason.h" #include "third_party/blink/renderer/core/css_value_keywords.h" #include "third_party/blink/renderer/core/dom/document.h" @@ -56,6 +57,7 @@ #include "third_party/blink/renderer/platform/text/date_time_format.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" #include "third_party/blink/renderer/platform/wtf/date_math.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -162,9 +164,10 @@ ClearButtonElement* MultipleFieldsTemporalInputTypeView::GetClearButtonElement() PickerIndicatorElement* MultipleFieldsTemporalInputTypeView::GetPickerIndicatorElement() const { - return ToPickerIndicatorElementOrDie( - GetElement().UserAgentShadowRoot()->getElementById( - shadow_element_names::PickerIndicator())); + auto* element = GetElement().UserAgentShadowRoot()->getElementById( + shadow_element_names::PickerIndicator()); + CHECK(!element || IsA<PickerIndicatorElement>(element)); + return To<PickerIndicatorElement>(element); } inline bool MultipleFieldsTemporalInputTypeView::ContainsFocusedShadowElement() @@ -174,7 +177,7 @@ inline bool MultipleFieldsTemporalInputTypeView::ContainsFocusedShadowElement() } void MultipleFieldsTemporalInputTypeView::DidBlurFromControl( - WebFocusType focus_type) { + mojom::blink::FocusType focus_type) { // We don't need to call blur(). This function is called when control // lost focus. @@ -188,7 +191,7 @@ void MultipleFieldsTemporalInputTypeView::DidBlurFromControl( } void MultipleFieldsTemporalInputTypeView::DidFocusOnControl( - WebFocusType focus_type) { + mojom::blink::FocusType focus_type) { // We don't need to call focus(). This function is called when control // got focus. @@ -278,8 +281,7 @@ bool MultipleFieldsTemporalInputTypeView:: void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue( const String& value) { if (GetElement().IsValidValue(value)) { - GetElement().setValue(value, - TextFieldEventBehavior::kDispatchInputAndChangeEvent); + GetElement().setValue(value, TextFieldEventBehavior::kDispatchInputEvent); return; } @@ -296,7 +298,6 @@ void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue( if (date.ParseDate(value, 0, end) && end == value.length()) edit->SetOnlyYearMonthDay(date); } - GetElement().DispatchFormControlChangeEvent(); } void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue( @@ -304,11 +305,10 @@ void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue( DCHECK(std::isfinite(value) || std::isnan(value)); if (std::isnan(value)) { GetElement().setValue(g_empty_string, - TextFieldEventBehavior::kDispatchInputAndChangeEvent); + TextFieldEventBehavior::kDispatchInputEvent); } else { - GetElement().setValueAsNumber( - value, ASSERT_NO_EXCEPTION, - TextFieldEventBehavior::kDispatchInputAndChangeEvent); + GetElement().setValueAsNumber(value, ASSERT_NO_EXCEPTION, + TextFieldEventBehavior::kDispatchInputEvent); } } @@ -334,6 +334,14 @@ bool MultipleFieldsTemporalInputTypeView::SetupDateTimeChooserParameters( return GetElement().SetupDateTimeChooserParameters(parameters); } +void MultipleFieldsTemporalInputTypeView::DidEndChooser() { + GetElement().EnqueueChangeEvent(); +} + +String MultipleFieldsTemporalInputTypeView::AriaRoleForPickerIndicator() const { + return input_type_->AriaRoleForPickerIndicator(); +} + MultipleFieldsTemporalInputTypeView::MultipleFieldsTemporalInputTypeView( HTMLInputElement& element, BaseTemporalInputType& input_type) @@ -357,25 +365,17 @@ void MultipleFieldsTemporalInputTypeView::Blur() { ClosePopupView(); } -scoped_refptr<ComputedStyle> -MultipleFieldsTemporalInputTypeView::CustomStyleForLayoutObject( - scoped_refptr<ComputedStyle> original_style) { - EDisplay original_display = original_style->Display(); +void MultipleFieldsTemporalInputTypeView::CustomStyleForLayoutObject( + ComputedStyle& style) { + EDisplay original_display = style.Display(); EDisplay new_display = original_display; if (original_display == EDisplay::kInline || original_display == EDisplay::kInlineBlock) new_display = EDisplay::kInlineFlex; else if (original_display == EDisplay::kBlock) new_display = EDisplay::kFlex; - TextDirection content_direction = ComputedTextDirection(); - if (original_style->Direction() == content_direction && - original_display == new_display) - return original_style; - - scoped_refptr<ComputedStyle> style = ComputedStyle::Clone(*original_style); - style->SetDirection(content_direction); - style->SetDisplay(new_display); - return style; + style.SetDisplay(new_display); + style.SetDirection(ComputedTextDirection()); } void MultipleFieldsTemporalInputTypeView::CreateShadowSubtree() { @@ -388,7 +388,7 @@ void MultipleFieldsTemporalInputTypeView::CreateShadowSubtree() { MakeGarbageCollected<DateTimeEditElement, Document&, DateTimeEditElement::EditControlOwner&>(document, *this)); - if (!RuntimeEnabledFeatures::FormControlsRefreshEnabled()) { + if (!features::IsFormControlsRefreshEnabled()) { GetElement().UpdateView(); container->AppendChild( MakeGarbageCollected<ClearButtonElement, Document&, @@ -441,16 +441,18 @@ void MultipleFieldsTemporalInputTypeView::HandleClickEvent(MouseEvent& event) { void MultipleFieldsTemporalInputTypeView::HandleFocusInEvent( Element* old_focused_element, - WebFocusType type) { + mojom::blink::FocusType type) { DateTimeEditElement* edit = GetDateTimeEditElement(); if (!edit || is_destroying_shadow_subtree_) return; - if (type == kWebFocusTypeBackward) { + if (type == mojom::blink::FocusType::kBackward) { if (GetElement().GetDocument().GetPage()) GetElement().GetDocument().GetPage()->GetFocusController().AdvanceFocus( type); - } else if (type == kWebFocusTypeNone || type == kWebFocusTypeMouse || - type == kWebFocusTypePage || type == kWebFocusTypeAccessKey) { + } else if (type == mojom::blink::FocusType::kNone || + type == mojom::blink::FocusType::kMouse || + type == mojom::blink::FocusType::kPage || + type == mojom::blink::FocusType::kAccessKey) { edit->FocusByOwner(old_focused_element); } else { edit->FocusByOwner(); @@ -488,7 +490,7 @@ void MultipleFieldsTemporalInputTypeView::HandleKeydownEvent( ((event.key() == "ArrowDown" && event.getModifierState("Alt")) || (LayoutTheme::GetTheme().ShouldOpenPickerWithF4Key() && event.key() == "F4") || - (RuntimeEnabledFeatures::FormControlsRefreshEnabled() && + (features::IsFormControlsRefreshEnabled() && (event.key() == "Enter" || event.key() == " ")))) { if (PickerIndicatorElement* element = GetPickerIndicatorElement()) element->OpenPopup(); @@ -606,6 +608,13 @@ void MultipleFieldsTemporalInputTypeView::ClosePopupView() { picker->ClosePopup(); } +bool MultipleFieldsTemporalInputTypeView::HasOpenedPopup() const { + if (PickerIndicatorElement* picker = GetPickerIndicatorElement()) + return picker->HasOpenedPopup(); + + return false; +} + void MultipleFieldsTemporalInputTypeView::ValueAttributeChanged() { if (!GetElement().HasDirtyValue()) UpdateView(); @@ -687,4 +696,8 @@ AXObject* MultipleFieldsTemporalInputTypeView::PopupRootAXObject() { return nullptr; } +String MultipleFieldsTemporalInputTypeView::RawValue() const { + return GetDateTimeEditElement()->innerText(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h index 7ec1c405cce..2f4522ade10 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h +++ b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h @@ -31,7 +31,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MULTIPLE_FIELDS_TEMPORAL_INPUT_TYPE_VIEW_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MULTIPLE_FIELDS_TEMPORAL_INPUT_TYPE_VIEW_H_ -#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h" #include "third_party/blink/renderer/core/html/forms/clear_button_element.h" #include "third_party/blink/renderer/core/html/forms/date_time_edit_element.h" #include "third_party/blink/renderer/core/html/forms/input_type_view.h" @@ -58,10 +58,12 @@ class MultipleFieldsTemporalInputTypeView final ~MultipleFieldsTemporalInputTypeView() override; void Trace(Visitor*) override; + String RawValue() const override; + private: // DateTimeEditElement::EditControlOwner functions - void DidBlurFromControl(WebFocusType) final; - void DidFocusOnControl(WebFocusType) final; + void DidBlurFromControl(mojom::blink::FocusType) final; + void DidFocusOnControl(mojom::blink::FocusType) final; void EditControlValueChanged() final; String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override; bool IsEditControlOwnerDisabled() const final; @@ -84,6 +86,8 @@ class MultipleFieldsTemporalInputTypeView final void PickerIndicatorChooseValue(double) final; Element& PickerOwnerElement() const final; bool SetupDateTimeChooserParameters(DateTimeChooserParameters&) final; + void DidEndChooser() final; + String AriaRoleForPickerIndicator() const final; // ClearButtonElement::ClearButtonOwner functions. void FocusAndSelectClearButtonOwner() override; @@ -93,14 +97,15 @@ class MultipleFieldsTemporalInputTypeView final // InputTypeView functions void Blur() final; void ClosePopupView() override; - scoped_refptr<ComputedStyle> CustomStyleForLayoutObject( - scoped_refptr<ComputedStyle>) override; + bool HasOpenedPopup() const override; + void CustomStyleForLayoutObject(ComputedStyle& style) override; void CreateShadowSubtree() final; void DestroyShadowSubtree() final; void DisabledAttributeChanged() final; void ForwardEvent(Event&) final; void HandleClickEvent(MouseEvent&) final; - void HandleFocusInEvent(Element* old_focused_element, WebFocusType) final; + void HandleFocusInEvent(Element* old_focused_element, + mojom::blink::FocusType) final; void HandleKeydownEvent(KeyboardEvent&) final; bool HasBadInput() const override; bool HasCustomFocusLogic() const final; diff --git a/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc index 996addc47ea..d4548d0aba5 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc @@ -153,7 +153,7 @@ bool NumberInputType::SizeShouldIncludeDecoration(int default_size, const String step_string = GetElement().FastGetAttribute(html_names::kStepAttr); - if (DeprecatedEqualIgnoringCase(step_string, "any")) + if (EqualIgnoringASCIICase(step_string, "any")) return false; const Decimal minimum = ParseToDecimalForNumberType( @@ -246,10 +246,7 @@ void NumberInputType::WarnIfValueIsInvalid(const String& value) const { if (value.IsEmpty() || !GetElement().SanitizeValue(value).IsEmpty()) return; AddWarningToConsole( - "The specified value %s is not a valid number. The value must match to " - "the following regular expression: " - "-?(\\d+|\\d+\\.\\d+|\\.\\d+)([eE][-+]?\\d+)?", - value); + "The specified value %s cannot be parsed, or is out of range.", value); } bool NumberInputType::HasBadInput() const { @@ -283,7 +280,7 @@ void NumberInputType::MinOrMaxAttributeChanged() { if (GetElement().GetLayoutObject()) { GetElement() .GetLayoutObject() - ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( layout_invalidation_reason::kAttributeChanged); } } @@ -294,7 +291,7 @@ void NumberInputType::StepAttributeChanged() { if (GetElement().GetLayoutObject()) { GetElement() .GetLayoutObject() - ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( layout_invalidation_reason::kAttributeChanged); } } diff --git a/chromium/third_party/blink/renderer/core/html/forms/option_list.cc b/chromium/third_party/blink/renderer/core/html/forms/option_list.cc index 1b990eebaed..3fb7638b0ff 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/option_list.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/option_list.cc @@ -27,8 +27,7 @@ void OptionListIterator::Advance(HTMLOptionElement* previous) { current_ = option; return; } - if (IsA<HTMLOptGroupElement>(current) && - current->parentNode() == select_.Get()) { + if (IsA<HTMLOptGroupElement>(current) && current->parentNode() == select_) { if ((current_ = Traversal<HTMLOptionElement>::FirstChild(*current))) return; } diff --git a/chromium/third_party/blink/renderer/core/html/forms/option_list.h b/chromium/third_party/blink/renderer/core/html/forms/option_list.h index cd3ea21d4e5..51f8a5960a5 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/option_list.h +++ b/chromium/third_party/blink/renderer/core/html/forms/option_list.h @@ -18,7 +18,7 @@ class CORE_EXPORT OptionListIterator final { public: explicit OptionListIterator(const HTMLSelectElement* select) - : select_(select) { + : select_(select), current_(nullptr) { if (select_) Advance(nullptr); } @@ -37,8 +37,8 @@ class CORE_EXPORT OptionListIterator final { private: void Advance(HTMLOptionElement* current); - Member<const HTMLSelectElement> select_; - Member<HTMLOptionElement> current_; // nullptr means we reached to the end. + const HTMLSelectElement* select_; + HTMLOptionElement* current_; // nullptr means we reached to the end. }; // OptionList class is a lightweight version of HTMLOptionsCollection. @@ -46,13 +46,13 @@ class OptionList final { STACK_ALLOCATED(); public: - explicit OptionList(const HTMLSelectElement& select) : select_(select) {} + explicit OptionList(const HTMLSelectElement& select) : select_(&select) {} using Iterator = OptionListIterator; Iterator begin() { return Iterator(select_); } Iterator end() { return Iterator(nullptr); } private: - Member<const HTMLSelectElement> select_; + const HTMLSelectElement* select_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc b/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc index 863b8eb67c4..71941a49f9e 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc @@ -41,12 +41,12 @@ TEST_F(OptionListTest, Empty) { } TEST_F(OptionListTest, OptionOnly) { - Select().SetInnerHTMLFromString( + Select().setInnerHTML( "text<input><option id=o1></option><input><option " "id=o2></option><input>"); auto* div = To<HTMLElement>( Select().GetDocument().CreateRawElement(html_names::kDivTag)); - div->SetInnerHTMLFromString("<option id=o3></option>"); + div->setInnerHTML("<option id=o3></option>"); Select().AppendChild(div); OptionList list = Select().GetOptionList(); OptionList::Iterator iter = list.begin(); @@ -59,7 +59,7 @@ TEST_F(OptionListTest, OptionOnly) { } TEST_F(OptionListTest, Optgroup) { - Select().SetInnerHTMLFromString( + Select().setInnerHTML( "<optgroup><option id=g11></option><option id=g12></option></optgroup>" "<optgroup><option id=g21></option></optgroup>" "<optgroup></optgroup>" @@ -80,7 +80,7 @@ TEST_F(OptionListTest, Optgroup) { EXPECT_EQ(list.end(), iter); To<HTMLElement>(Select().firstChild()) - ->SetInnerHTMLFromString( + ->setInnerHTML( "<optgroup><option id=gg11></option></optgroup>" "<option id=g11></option>"); list = Select().GetOptionList(); diff --git a/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc b/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc index 0f8b2d17211..9dc42a72620 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc @@ -51,10 +51,9 @@ class MockInsecureInputService : public mojom::blink::InsecureInputService { TEST(PasswordInputTypeTest, DidEditFieldEvent) { auto page_holder = std::make_unique<DummyPageHolder>(IntSize(2000, 2000)); MockInsecureInputService mock_service(page_holder->GetFrame()); - page_holder->GetDocument().body()->SetInnerHTMLFromString( - "<input type='password'>"); + page_holder->GetDocument().body()->setInnerHTML("<input type='password'>"); page_holder->GetDocument().View()->UpdateAllLifecyclePhases( - DocumentLifecycle::LifecycleUpdateReason::kTest); + DocumentUpdateReason::kTest); blink::test::RunPendingTasks(); EXPECT_EQ(0u, mock_service.DidEditFieldCalls()); // Simulate a text field edit. @@ -77,12 +76,11 @@ TEST(PasswordInputTypeTest, DidEditFieldEventNotSentFromSecureContext) { nullptr /* extra_data */); blink::test::RunPendingTasks(); MockInsecureInputService mock_service(page_holder->GetFrame()); - page_holder->GetDocument().SetSecureContextStateForTesting( - SecureContextState::kSecure); - page_holder->GetDocument().body()->SetInnerHTMLFromString( - "<input type='password'>"); + page_holder->GetDocument().SetSecureContextModeForTesting( + SecureContextMode::kSecureContext); + page_holder->GetDocument().body()->setInnerHTML("<input type='password'>"); page_holder->GetDocument().View()->UpdateAllLifecyclePhases( - DocumentLifecycle::LifecycleUpdateReason::kTest); + DocumentUpdateReason::kTest); // Simulate a text field edit. page_holder->GetDocument().MaybeQueueSendDidEditFieldInInsecureContext(); // No message should have been sent from a secure context. diff --git a/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc index 544b8a6b204..50036ddb04b 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc @@ -40,6 +40,7 @@ #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" #include "third_party/blink/renderer/platform/web_test_support.h" +#include "ui/base/ui_base_features.h" namespace blink { @@ -59,7 +60,7 @@ PickerIndicatorElement::~PickerIndicatorElement() { LayoutObject* PickerIndicatorElement::CreateLayoutObject( const ComputedStyle& style, LegacyLayout legacy) { - if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) + if (features::IsFormControlsRefreshEnabled()) return HTMLDivElement::CreateLayoutObject(style, legacy); return new LayoutDetailsMarker(this); @@ -72,12 +73,12 @@ void PickerIndicatorElement::DefaultEventHandler(Event& event) { picker_indicator_owner_->IsPickerIndicatorOwnerDisabledOrReadOnly()) return; + auto* keyboard_event = DynamicTo<KeyboardEvent>(event); if (event.type() == event_type_names::kClick) { OpenPopup(); event.SetDefaultHandled(); - } else if (event.type() == event_type_names::kKeypress && - event.IsKeyboardEvent()) { - int char_code = ToKeyboardEvent(event).charCode(); + } else if (event.type() == event_type_names::kKeypress && keyboard_event) { + int char_code = keyboard_event->charCode(); if (char_code == ' ' || char_code == '\r') { OpenPopup(); event.SetDefaultHandled(); @@ -109,10 +110,16 @@ void PickerIndicatorElement::DidChooseValue(double value) { void PickerIndicatorElement::DidEndChooser() { chooser_.Clear(); + picker_indicator_owner_->DidEndChooser(); + if (::features::IsFormControlsRefreshEnabled() && + OwnerElement().GetLayoutObject()) { + // Invalidate paint to ensure that the focus ring is shown. + OwnerElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + } } void PickerIndicatorElement::OpenPopup() { - if (chooser_) + if (HasOpenedPopup()) return; if (!GetDocument().GetPage()) return; @@ -123,6 +130,11 @@ void PickerIndicatorElement::OpenPopup() { return; chooser_ = GetDocument().GetPage()->GetChromeClient().OpenDateTimeChooser( GetDocument().GetFrame(), this, parameters); + if (::features::IsFormControlsRefreshEnabled() && + OwnerElement().GetLayoutObject()) { + // Invalidate paint to ensure that the focus ring is removed. + OwnerElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + } } Element& PickerIndicatorElement::OwnerElement() const { @@ -136,6 +148,10 @@ void PickerIndicatorElement::ClosePopup() { chooser_->EndChooser(); } +bool PickerIndicatorElement::HasOpenedPopup() const { + return chooser_; +} + void PickerIndicatorElement::DetachLayoutTree(bool performing_reattach) { ClosePopup(); HTMLDivElement::DetachLayoutTree(performing_reattach); @@ -160,7 +176,8 @@ void PickerIndicatorElement::DidNotifySubtreeInsertionsToDocument() { return; // Don't make this focusable if we are in web tests in order to avoid // breaking existing tests. - // FIXME: We should have a way to disable accessibility in web tests. + // TODO(crbug.com/1054048): We should have a way to disable accessibility in + // web tests. Once we do have it, this early return should be removed. if (WebTestSupport::IsRunningWebTest()) return; setAttribute(html_names::kTabindexAttr, "0"); @@ -168,7 +185,8 @@ void PickerIndicatorElement::DidNotifySubtreeInsertionsToDocument() { setAttribute(html_names::kRoleAttr, "button"); setAttribute( html_names::kAriaLabelAttr, - AtomicString(GetLocale().QueryString(IDS_AX_CALENDAR_SHOW_DATE_PICKER))); + AtomicString( + this->picker_indicator_owner_->AriaRoleForPickerIndicator())); } void PickerIndicatorElement::Trace(Visitor* visitor) { diff --git a/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h index df33806c889..4aa8b3237f8 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h @@ -37,8 +37,6 @@ namespace blink { -class HTMLInputElement; - class PickerIndicatorElement final : public HTMLDivElement, public DateTimeChooserClient { USING_GARBAGE_COLLECTED_MIXIN(PickerIndicatorElement); @@ -55,6 +53,8 @@ class PickerIndicatorElement final : public HTMLDivElement, virtual void PickerIndicatorChooseValue(double) = 0; virtual Element& PickerOwnerElement() const = 0; virtual bool SetupDateTimeChooserParameters(DateTimeChooserParameters&) = 0; + virtual void DidEndChooser() = 0; + virtual String AriaRoleForPickerIndicator() const = 0; }; PickerIndicatorElement(Document&, PickerIndicatorOwner&); @@ -63,6 +63,7 @@ class PickerIndicatorElement final : public HTMLDivElement, void OpenPopup(); void ClosePopup(); + bool HasOpenedPopup() const; bool WillRespondToMouseClickEvents() override; void RemovePickerIndicatorOwner() { picker_indicator_owner_ = nullptr; } AXObject* PopupRootAXObject() const; @@ -81,17 +82,16 @@ class PickerIndicatorElement final : public HTMLDivElement, InsertionNotificationRequest InsertedInto(ContainerNode&) override; void DidNotifySubtreeInsertionsToDocument() override; - HTMLInputElement* HostInput(); - Member<PickerIndicatorOwner> picker_indicator_owner_; Member<DateTimeChooser> chooser_; }; -DEFINE_TYPE_CASTS(PickerIndicatorElement, - Element, - element, - element->IsPickerIndicatorElement(), - element.IsPickerIndicatorElement()); +template <> +struct DowncastTraits<PickerIndicatorElement> { + static bool AllowFrom(const Element& element) { + return element.IsPickerIndicatorElement(); + } +}; } // namespace blink #endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc index 08db5400d8b..9191f0d96a3 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc @@ -201,8 +201,6 @@ void RadioButtonGroup::Trace(Visitor* visitor) { // RadioButtonGroup in the header. RadioButtonGroupScope::RadioButtonGroupScope() = default; -RadioButtonGroupScope::~RadioButtonGroupScope() = default; - void RadioButtonGroupScope::AddButton(HTMLInputElement* element) { DCHECK_EQ(element->type(), input_type_names::kRadio); if (element->GetName().IsEmpty()) diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h index 11399c9d004..a6ba8d9f217 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h @@ -36,7 +36,6 @@ class RadioButtonGroupScope { public: RadioButtonGroupScope(); - ~RadioButtonGroupScope(); void Trace(Visitor*); void AddButton(HTMLInputElement*); void UpdateCheckedState(HTMLInputElement*); diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc index 5b69b334e22..c3846016db6 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc @@ -21,6 +21,7 @@ #include "third_party/blink/renderer/core/html/forms/radio_input_type.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" #include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/element_traversal.h" @@ -56,8 +57,33 @@ const AtomicString& RadioInputType::FormControlType() const { } bool RadioInputType::ValueMissing(const String&) const { - return GetElement().IsInRequiredRadioButtonGroup() && - !GetElement().CheckedRadioButtonForGroup(); + HTMLInputElement& input = GetElement(); + if (auto* scope = input.GetRadioButtonGroupScope()) + return scope->IsInRequiredGroup(&input) && !CheckedRadioButtonForGroup(); + + // This element is not managed by a RadioButtonGroupScope. We need to traverse + // the tree from TreeRoot. + DCHECK(!input.isConnected()); + DCHECK(!input.formOwner()); + const AtomicString& name = input.GetName(); + if (name.IsEmpty()) + return false; + bool is_required = false; + bool is_checked = false; + Node& root = input.TreeRoot(); + for (auto* another = Traversal<HTMLInputElement>::InclusiveFirstWithin(root); + another; another = Traversal<HTMLInputElement>::Next(*another, &root)) { + if (another->type() != input_type_names::kRadio || + another->GetName() != name || another->formOwner()) + continue; + if (another->checked()) + is_checked = true; + if (another->FastHasAttribute(html_names::kRequiredAttr)) + is_required = true; + if (is_checked && is_required) + return false; + } + return is_required && !is_checked; } String RadioInputType::ValueMissingText() const { @@ -110,7 +136,7 @@ void RadioInputType::HandleKeydownEvent(KeyboardEvent& event) { : (key == "ArrowDown" || key == "ArrowRight"); // Force layout for isFocusable() in findNextFocusableRadioButtonInGroup(). - document.UpdateStyleAndLayout(); + document.UpdateStyleAndLayout(DocumentUpdateReason::kInput); // We can only stay within the form's children if the form hasn't been demoted // to a leaf because of malformed HTML. @@ -128,9 +154,9 @@ void RadioInputType::HandleKeydownEvent(KeyboardEvent& event) { } } if (input_element) { - document.SetFocusedElement(input_element, - FocusParams(SelectionBehaviorOnFocus::kRestore, - kWebFocusTypeNone, nullptr)); + document.SetFocusedElement( + input_element, FocusParams(SelectionBehaviorOnFocus::kRestore, + mojom::blink::FocusType::kNone, nullptr)); input_element->DispatchSimulatedClick(&event, kSendNoEvents); event.SetDefaultHandled(); return; @@ -175,7 +201,7 @@ bool RadioInputType::IsKeyboardFocusable() const { // Allow keyboard focus if we're checked or if nothing in the group is // checked. - return GetElement().checked() || !GetElement().CheckedRadioButtonForGroup(); + return GetElement().checked() || !CheckedRadioButtonForGroup(); } bool RadioInputType::ShouldSendChangeEventAfterCheckedChanged() { @@ -197,7 +223,7 @@ ClickHandlingState* RadioInputType::WillDispatchClick() { ClickHandlingState* state = MakeGarbageCollected<ClickHandlingState>(); state->checked = GetElement().checked(); - state->checked_radio_button = GetElement().CheckedRadioButtonForGroup(); + state->checked_radio_button = CheckedRadioButtonForGroup(); GetElement().setChecked(true, TextFieldEventBehavior::kDispatchChangeEvent); is_in_click_handler_ = true; return state; @@ -225,7 +251,7 @@ void RadioInputType::DidDispatchClick(Event& event, } bool RadioInputType::ShouldAppearIndeterminate() const { - return !GetElement().CheckedRadioButtonForGroup(); + return !CheckedRadioButtonForGroup(); } HTMLInputElement* RadioInputType::NextRadioButtonInGroup( @@ -247,4 +273,42 @@ HTMLInputElement* RadioInputType::NextRadioButtonInGroup( return nullptr; } +HTMLInputElement* RadioInputType::CheckedRadioButtonForGroup() const { + HTMLInputElement& input = GetElement(); + if (input.checked()) + return &input; + if (auto* scope = input.GetRadioButtonGroupScope()) + return scope->CheckedButtonForGroup(input.GetName()); + + // This element is not managed by a RadioButtonGroupScope. We need to traverse + // the tree from TreeRoot. + DCHECK(!input.isConnected()); + DCHECK(!input.formOwner()); + const AtomicString& name = input.GetName(); + if (name.IsEmpty()) + return nullptr; + Node& root = input.TreeRoot(); + for (auto* another = Traversal<HTMLInputElement>::InclusiveFirstWithin(root); + another; another = Traversal<HTMLInputElement>::Next(*another, &root)) { + if (another->type() != input_type_names::kRadio || + another->GetName() != name || another->formOwner()) + continue; + if (another->checked()) + return another; + } + return nullptr; +} + +void RadioInputType::WillUpdateCheckedness(bool new_checked) { + if (!new_checked) + return; + if (GetElement().GetRadioButtonGroupScope()) { + // Buttons in RadioButtonGroupScope are handled in + // HTMLInputElement::setChecked(). + return; + } + if (auto* input = CheckedRadioButtonForGroup()) + input->setChecked(false); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h index 5c0b43d43cb..f3050550000 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h @@ -46,6 +46,7 @@ class RadioInputType final : public BaseCheckableInputType { private: void CountUsage() override; const AtomicString& FormControlType() const override; + void WillUpdateCheckedness(bool new_checked) override; bool ValueMissing(const String&) const override; String ValueMissingText() const override; void HandleClickEvent(MouseEvent&) override; @@ -59,6 +60,7 @@ class RadioInputType final : public BaseCheckableInputType { HTMLInputElement* FindNextFocusableRadioButtonInGroup(HTMLInputElement*, bool); + HTMLInputElement* CheckedRadioButtonForGroup() const; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc index fe1eb05d751..0eed4825627 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc @@ -144,8 +144,8 @@ StepRange RangeInputType::CreateStepRange( // minimum/maximum. // https://html.spec.whatwg.org/C/#range-state-(type=range):concept-input-min-default const bool kHasRangeLimitations = true; - return StepRange(step_base, minimum, maximum, kHasRangeLimitations, step, - step_description); + return StepRange(step_base, minimum, maximum, kHasRangeLimitations, + /*has_reversed_range=*/false, step, step_description); } bool RangeInputType::IsSteppable() const { @@ -185,7 +185,7 @@ void RangeInputType::HandleKeydownEvent(KeyboardEvent& event) { // FIXME: We can't use stepUp() for the step value "any". So, we increase // or decrease the value by 1/100 of the value range. Is it reasonable? const Decimal step = - DeprecatedEqualIgnoringCase( + EqualIgnoringASCIICase( GetElement().FastGetAttribute(html_names::kStepAttr), "any") ? (step_range.Maximum() - step_range.Minimum()) / 100 : step_range.Step(); @@ -253,6 +253,10 @@ void RangeInputType::CreateShadowSubtree() { GetElement().UserAgentShadowRoot()->AppendChild(container); } +bool RangeInputType::TypeShouldForceLegacyLayout() const { + return true; +} + LayoutObject* RangeInputType::CreateLayoutObject(const ComputedStyle&, LegacyLayout) const { return new LayoutSlider(&GetElement()); @@ -314,10 +318,7 @@ void RangeInputType::WarnIfValueIsInvalid(const String& value) const { if (value.IsEmpty() || !GetElement().SanitizeValue(value).IsEmpty()) return; AddWarningToConsole( - "The specified value %s is not a valid number. The value must match to " - "the following regular expression: " - "-?(\\d+|\\d+\\.\\d+|\\.\\d+)([eE][-+]?\\d+)?", - value); + "The specified value %s cannot be parsed, or is out of range.", value); } void RangeInputType::DisabledAttributeChanged() { @@ -419,4 +420,8 @@ void RangeInputType::ValueAttributeChanged() { UpdateView(); } +bool RangeInputType::IsDraggedSlider() const { + return GetSliderThumbElement()->IsActive(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h index b4da9bce5a0..013379890dc 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h @@ -63,6 +63,7 @@ class RangeInputType final : public InputType, public InputTypeView { bool IsSteppable() const override; void HandleMouseDownEvent(MouseEvent&) override; void HandleKeydownEvent(KeyboardEvent&) override; + bool TypeShouldForceLegacyLayout() const override; LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) const override; void CreateShadowSubtree() override; @@ -86,6 +87,7 @@ class RangeInputType final : public InputType, public InputTypeView { // InputTypeView function: void UpdateView() override; void ValueAttributeChanged() override; + bool IsDraggedSlider() const override; bool tick_mark_values_dirty_; Vector<Decimal> tick_mark_values_; diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/PRESUBMIT.py b/chromium/third_party/blink/renderer/core/html/forms/resources/PRESUBMIT.py index f28465828fa..26b84f5fb5d 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/PRESUBMIT.py +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/PRESUBMIT.py @@ -2,13 +2,15 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. + def _CheckChangeOnUploadOrCommit(input_api, output_api): - return input_api.canned_checks.CheckPatchFormatted(input_api, output_api, - check_js=True) + return input_api.canned_checks.CheckPatchFormatted( + input_api, output_api, check_js=True) + def CheckChangeOnUpload(input_api, output_api): - return _CheckChangeOnUploadOrCommit(input_api, output_api) + return _CheckChangeOnUploadOrCommit(input_api, output_api) def CheckChangeOnCommit(input_api, output_api): - return _CheckChangeOnUploadOrCommit(input_api, output_api) + return _CheckChangeOnUploadOrCommit(input_api, output_api) diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js index d89a990d557..6380d4631f4 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js @@ -59,6 +59,7 @@ var global = { ], isLocaleRTL: false, isFormControlsRefreshEnabled: false, + isBorderTransparent: false, mode: 'date', isAMPMFirst: false, hasAMPM: false, @@ -304,6 +305,40 @@ Day.prototype.next = function(offset) { }; /** + * Given that 'this' is the Nth day of the month, returns the Nth + * day of the month that is specified by the parameter. + * Clips the date if necessary, e.g. if 'this' Day is October 31st and + * the parameter is a November, returns November 30th. + * @param {!Month} month + * @return {!Day} + */ +Day.prototype.thisRangeInMonth = function(month) { + var newDate = month.startDate(); + var originalMonthInt = newDate.getUTCMonth(); + newDate.setUTCDate(this.date); + if (newDate.getUTCMonth() != originalMonthInt) { + newDate.setUTCDate(0); + } + return Day.createFromDate(newDate); +}; + +/** + * @param {!Month} month + * @return {!boolean} + */ +Day.prototype.overlapsMonth = function(month) { + return (month.firstDay() <= this && month.lastDay() >= this); +}; + +/** + * @param {!Month} month + * @return {!boolean} + */ +Day.prototype.isFullyContainedInMonth = function(month) { + return (month.firstDay() <= this && month.lastDay() >= this); +}; + +/** * @return {!Date} */ Day.prototype.startDate = function() { @@ -569,6 +604,65 @@ Week.prototype.next = function(offset) { }; /** + * Given that 'this' is the Nth week of the month, returns + * the Week that is the Nth week in the month specified + * by the parameter. + * Clips the date if necessary, e.g. if 'this' is the 5th week + * of a month that has 5 weeks and the parameter month only has + * 4 weeks, returns the 4th week of that month. + * @param {!Month} month + * @return {!Week} + */ +Week.prototype.thisRangeInMonth = function(month) { + var firstDateInCurrentMonth = this.startDate(); + firstDateInCurrentMonth.setUTCDate(1); + + var offsetInOriginalMonth = + Week._numberOfWeeksSinceDate(firstDateInCurrentMonth, this.startDate()); + + // Determine the first Monday in the new month (the week control shows weeks + // starting on Monday). + var firstWeekStartInNewMonth = month.startDate(); + firstWeekStartInNewMonth.setUTCDate( + 1 + + ((DaysPerWeek + 1 - firstWeekStartInNewMonth.getUTCDay()) % DaysPerWeek)); + + + // Find the Nth Monday in the month where N == offsetInOriginalMonth. + firstWeekStartInNewMonth.setUTCDate( + firstWeekStartInNewMonth.getUTCDate() + + (DaysPerWeek * offsetInOriginalMonth)); + + if (firstWeekStartInNewMonth.getUTCMonth() != month.month) { + // If we overshot into the next month (can happen if we were + // on the 5th week of the old month), go back to the last week + // of the target month. + firstWeekStartInNewMonth.setUTCDate( + firstWeekStartInNewMonth.getUTCDate() - DaysPerWeek); + } + + return Week.createFromDate(firstWeekStartInNewMonth); +}; + +/** + * @param {!Month} month + * @return {!boolean} + */ +Week.prototype.overlapsMonth = function(month) { + return ( + month.firstDay() <= this.lastDay() && month.lastDay() >= this.firstDay()); +}; + +/** + * @param {!Month} month + * @return {!boolean} + */ +Week.prototype.isFullyContainedInMonth = function(month) { + return ( + month.firstDay() <= this.firstDay() && month.lastDay() >= this.lastDay()); +}; + +/** * @return {!Date} */ Week.prototype.startDate = function() { @@ -706,13 +800,6 @@ Month.createFromToday = function() { }; /** - * @return {!boolean} - */ -Month.prototype.containsDay = function(day) { - return this.year === day.year && this.month === day.month; -}; - -/** * @param {!Month} other * @return {!boolean} */ @@ -768,7 +855,7 @@ Month.prototype.firstDay = function() { * @return {!Day} */ Month.prototype.middleDay = function() { - return new Day(this.year, this.month, this.month === 2 ? 14 : 15); + return new Day(this.year, this.month, this.month === 1 ? 14 : 15); }; /** @@ -1666,10 +1753,17 @@ ListCell.prototype.setSelected = function(selected) { if (this._selected === selected) return; this._selected = selected; - if (this._selected) + if (this._selected) { this.element.classList.add('selected'); - else + if (global.params.isFormControlsRefreshEnabled) { + this.element.setAttribute('aria-selected', true); + } + } else { this.element.classList.remove('selected'); + if (global.params.isFormControlsRefreshEnabled) { + this.element.setAttribute('aria-selected', false); + } + } }; /** @@ -1782,7 +1876,18 @@ ListView.prototype.addCellIfNecessary = function(row) { if (cell) return cell; cell = this.prepareNewCell(row); - cell.attachTo(this.scrollView.contentElement); + + // Ensure that the DOM tree positions of the rows are in increasing + // chronological order. This is needed for correct application of + // the :hover selector for the week control, which spans across multiple + // calendar rows. + var rowIndices = Object.keys(this._cells); + var shouldPrepend = (rowIndices.length) > 0 && (row < rowIndices[0]); + cell.attachTo( + this.scrollView.contentElement, + shouldPrepend ? this.scrollView.contentElement.firstElementChild : + undefined); + cell.setWidth(this._width); cell.setPosition(this.scrollView.contentPositionForContentOffset( this.scrollOffsetForRow(row))); @@ -2160,6 +2265,133 @@ ScrubbyScrollBar.prototype.onScrollTimer = function() { this.scrollView.scrollBy(scrollAmount, false); }; +// Mixin containing utilities for identifying and navigating between +// valid day/week/month ranges. +var DateRangeManager = { + _setValidDateConfig(config) { + this.config = {}; + + this.config.minimum = (typeof config.min !== 'undefined' && config.min) ? + parseDateString(config.min) : + this._dateTypeConstructor.Minimum; + this.config.maximum = (typeof config.max !== 'undefined' && config.max) ? + parseDateString(config.max) : + this._dateTypeConstructor.Maximum; + this.config.minimumValue = this.config.minimum.valueOf(); + this.config.maximumValue = this.config.maximum.valueOf(); + this.config.step = (typeof config.step !== 'undefined') ? + Number(config.step) : + this._dateTypeConstructor.DefaultStep; + this.config.stepBase = (typeof config.stepBase !== 'undefined') ? + Number(config.stepBase) : + this._dateTypeConstructor.DefaultStepBase; + }, + + _isValidForStep(value) { + // nextAllowedValue is the time closest (looking forward) to value that is + // within the interval specified by the step and the stepBase. This may + // be equal to value. + var nextAllowedValue = + (Math.ceil((value - this.config.stepBase) / this.config.step) * + this.config.step) + + this.config.stepBase; + // If the nextAllowedValue is between value and the next nearest possible time + // for this control type (determined by adding the smallest time interval, given + // by DefaultStep, to value) then we consider it to be valid. + return nextAllowedValue < (value + this._dateTypeConstructor.DefaultStep); + }, + + /** + * @param {!number} value + * @return {!boolean} + */ + _outOfRange(value) { + return value < this.config.minimumValue || value > this.config.maximumValue; + }, + + /** + * @param {!DateType} dayOrWeekOrMonth + * @return {!boolean} + */ + isValid(dayOrWeekOrMonth) { + var value = dayOrWeekOrMonth.valueOf(); + return dayOrWeekOrMonth instanceof this._dateTypeConstructor && + !this._outOfRange(value) && this._isValidForStep(value); + }, + + /** + * @param {!DayOrWeekOrMonth} dayOrWeekOrMonth + * @return {?DayOrWeekOrMonth} + */ + getNearestValidRangeLookingForward(dayOrWeekOrMonth) { + if (dayOrWeekOrMonth < this.config.minimumValue) { + // Performance optimization: avoid wasting lots of time in the below + // loop if dayOrWeekOrMonth is significantly less than the min. + dayOrWeekOrMonth = + this._dateTypeConstructor.createFromValue(this.config.minimumValue); + } + + while (!this.isValid(dayOrWeekOrMonth) && + dayOrWeekOrMonth < this.config.maximumValue) { + dayOrWeekOrMonth = dayOrWeekOrMonth.next(); + } + + return this.isValid(dayOrWeekOrMonth) ? dayOrWeekOrMonth : null; + }, + + /** + * @param {!DayOrWeekOrMonth} dayOrWeekOrMonth + * @return {?DayOrWeekOrMonth} + */ + getNearestValidRangeLookingBackward(dayOrWeekOrMonth) { + if (dayOrWeekOrMonth > this.config.maximumValue) { + // Performance optimization: avoid wasting lots of time in the below + // loop if dayOrWeekOrMonth is significantly greater than the max. + dayOrWeekOrMonth = + this._dateTypeConstructor.createFromValue(this.config.maximumValue); + } + + while (!this.isValid(dayOrWeekOrMonth) && + dayOrWeekOrMonth > this.config.minimumValue) { + dayOrWeekOrMonth = dayOrWeekOrMonth.previous(); + } + + return this.isValid(dayOrWeekOrMonth) ? dayOrWeekOrMonth : null; + }, + + /** + * @param {!DayOrWeekOrMonth} dayOrWeekOrMonth + * @param {!boolean} lookForwardFirst + * @return {?DayOrWeekOrMonth} + */ + getNearestValidRange(dayOrWeekOrMonth, lookForwardFirst) { + var result = null; + if (lookForwardFirst) { + if (!(result = + this.getNearestValidRangeLookingForward(dayOrWeekOrMonth))) { + result = this.getNearestValidRangeLookingBackward(dayOrWeekOrMonth); + } + } else { + if (!(result = + this.getNearestValidRangeLookingBackward(dayOrWeekOrMonth))) { + result = this.getNearestValidRangeLookingForward(dayOrWeekOrMonth); + } + } + + return result; + }, + + /** + * @param {!Day} day + * @param {!boolean} lookForwardFirst + * @return {?DayOrWeekOrMonth} + */ + getValidRangeNearestToDay(day, lookForwardFirst) { + var dayOrWeekOrMonth = this._dateTypeConstructor.createFromDay(day); + return this.getNearestValidRange(dayOrWeekOrMonth, lookForwardFirst); + } +}; + /** * @constructor * @extends ListCell @@ -2294,14 +2526,16 @@ YearListCell.prototype.setHeight = function(height) { * @param {!Month} minimumMonth * @param {!Month} maximumMonth */ -function YearListView(minimumMonth, maximumMonth) { +function YearListView(minimumMonth, maximumMonth, config) { ListView.call(this); this.element.classList.add('year-list-view'); /** * @type {?Month} */ - this.highlightedMonth = null; + if (!global.params.isFormControlsRefreshEnabled) { + this.highlightedMonth = null; + } /** * @type {?Month} */ @@ -2350,13 +2584,50 @@ function YearListView(minimumMonth, maximumMonth) { this.scrubbyScrollBar = new ScrubbyScrollBar(this.scrollView); this.scrubbyScrollBar.attachTo(this); - this.element.addEventListener('mouseover', this.onMouseOver, false); - this.element.addEventListener('mouseout', this.onMouseOut, false); this.element.addEventListener('keydown', this.onKeyDown, false); - this.element.addEventListener('touchstart', this.onTouchStart, false); + if (!global.params.isFormControlsRefreshEnabled) { + this.element.addEventListener('mouseover', this.onMouseOver, false); + this.element.addEventListener('mouseout', this.onMouseOut, false); + this.element.addEventListener('touchstart', this.onTouchStart, false); + } + + if (global.params.isFormControlsRefreshEnabled && config && + config.mode == 'month') { + this.type = 'month'; + this._dateTypeConstructor = Month; + + this._setValidDateConfig(config); + + this._hadValidValueWhenOpened = false; + var initialSelection = parseDateString(config.currentValue); + if (initialSelection) { + this._hadValidValueWhenOpened = this.isValid(initialSelection); + this._selectedMonth = this.getNearestValidRange( + initialSelection, /*lookForwardFirst*/ true); + } else { + // Ensure that the next month closest to today is selected to start with so that + // the user can simply submit the popup to choose it. + this._selectedMonth = this.getValidRangeNearestToDay( + this._dateTypeConstructor.createFromToday(), + /*lookForwardFirst*/ true); + } + + this._initialSelectedMonth = this._selectedMonth; + } else if (global.params.isFormControlsRefreshEnabled) { + // This is a month switcher menu embedded in another calendar control. + // Set up our config so that getNearestValidRangeLookingForward(Backward) + // when called on this YearListView will navigate by month. + this.config = {}; + this.config.minimumValue = minimumMonth; + this.config.maximumValue = maximumMonth; + this.config.step = Month.DefaultStep; + this.config.stepBase = Month.DefaultStepBase; + this._dateTypeConstructor = Month; + } } YearListView.prototype = Object.create(ListView.prototype); +Object.assign(YearListView.prototype, DateRangeManager); YearListView._Height = YearListCell._SelectedHeight - 1; YearListView._VisibleYearsRefresh = 4; @@ -2504,9 +2775,11 @@ YearListView.prototype.onClick = function(event) { if (this.selectedRow !== oldSelectedRow) { // Always start with first month when changing the year. const month = new Month(year, 0); - this.highlightMonth(month); - this.dispatchEvent( - YearListView.EventTypeYearListViewDidSelectMonth, this, month); + if (!global.params.isFormControlsRefreshEnabled) { + this.highlightMonth(month); + this.dispatchEvent( + YearListView.EventTypeYearListViewDidSelectMonth, this, month); + } this.scrollView.scrollTo(this.selectedRow * YearListCell.GetHeight(), true); } else { var monthButton = enclosingNodeOrSelfWithClass( @@ -2517,7 +2790,9 @@ YearListView.prototype.onClick = function(event) { this.dispatchEvent( YearListView.EventTypeYearListViewDidSelectMonth, this, new Month(year, month)); - this.hide(); + if (!global.params.isFormControlsRefreshEnabled) { + this.hide(); + } } }; @@ -2588,13 +2863,20 @@ YearListView.prototype.prepareNewCell = function(row) { for (var i = 0; i < cell.monthButtons.length; ++i) { var month = new Month(row + 1, i); cell.monthButtons[i].id = month.toString(); - cell.monthButtons[i].setAttribute( - 'aria-disabled', - this._minimumMonth > month || this._maximumMonth < month ? 'true' : - 'false'); + if (global.params.isFormControlsRefreshEnabled && this.type === 'month') { + cell.monthButtons[i].setAttribute( + 'aria-disabled', this.isValid(month) ? 'false' : 'true'); + } else { + cell.monthButtons[i].setAttribute( + 'aria-disabled', + this._minimumMonth > month || this._maximumMonth < month ? 'true' : + 'false'); + } cell.monthButtons[i].setAttribute('aria-label', month.toLocaleString()); + cell.monthButtons[i].setAttribute('aria-selected', false); } - if (this.highlightedMonth && row === this.highlightedMonth.year - 1) { + if (!global.params.isFormControlsRefreshEnabled && this.highlightedMonth && + row === this.highlightedMonth.year - 1) { var monthButton = cell.monthButtons[this.highlightedMonth.month]; monthButton.classList.add(YearListCell.ClassNameHighlighted); // aria-activedescendant assumes both elements have layoutObjects, and @@ -2607,6 +2889,10 @@ YearListView.prototype.prepareNewCell = function(row) { if (this._selectedMonth && (this._selectedMonth.year - 1) === row) { var monthButton = cell.monthButtons[this._selectedMonth.month]; monthButton.classList.add(YearListCell.ClassNameSelected); + if (global.params.isFormControlsRefreshEnabled) { + this.element.setAttribute('aria-activedescendant', monthButton.id); + monthButton.setAttribute('aria-selected', true); + } } const todayMonth = Month.createFromToday(); if ((todayMonth.year - 1) === row) { @@ -2691,8 +2977,10 @@ YearListView.prototype.select = function(row) { this.selectedRow, YearListView.RowAnimationDirection.Opening); if (selectedCell) selectedCell.setSelected(true); - var month = this.highlightedMonth ? this.highlightedMonth.month : 0; - this.highlightMonth(new Month(this.selectedRow + 1, month)); + if (!global.params.isFormControlsRefreshEnabled) { + var month = this.highlightedMonth ? this.highlightedMonth.month : 0; + this.highlightMonth(new Month(this.selectedRow + 1, month)); + } } this.setNeedsUpdateCells(true); }; @@ -2713,8 +3001,6 @@ YearListView.prototype.selectWithoutAnimating = function(row) { selectedCell.setSelected(true); selectedCell.setHeight(YearListCell.GetSelectedHeight()); } - var month = this.highlightedMonth ? this.highlightedMonth.month : 0; - this.highlightMonth(new Month(this.selectedRow + 1, month)); } this.setNeedsUpdateCells(true); }; @@ -2762,7 +3048,29 @@ YearListView.prototype.highlightMonth = function(month) { }; YearListView.prototype.setSelectedMonth = function(month) { + + var oldMonthButton = this.buttonForMonth(this._selectedMonth); + if (oldMonthButton) { + oldMonthButton.classList.remove(YearListCell.ClassNameSelected); + oldMonthButton.setAttribute('aria-selected', false); + } + this._selectedMonth = month; + + var newMonthButton = this.buttonForMonth(this._selectedMonth); + if (newMonthButton) { + newMonthButton.classList.add(YearListCell.ClassNameSelected); + this.element.setAttribute('aria-activedescendant', newMonthButton.id); + newMonthButton.setAttribute('aria-selected', true); + } +}; + +YearListView.prototype.setSelectedMonthAndUpdateView = function(month) { + this.setSelectedMonth(month); + + this.select(this._selectedMonth.year - 1); + + this.scrollView.scrollTo(this.selectedRow * YearListCell.GetHeight(), true); }; YearListView.prototype.showSelectedMonth = function() { @@ -2780,7 +3088,9 @@ YearListView.prototype.show = function(month) { this.scrollToRow(month.year - 1, false); this.selectWithoutAnimating(month.year - 1); - this.highlightMonth(month); + if (!global.params.isFormControlsRefreshEnabled) { + this.highlightMonth(month); + } this.showSelectedMonth(); }; @@ -2795,8 +3105,11 @@ YearListView.prototype._moveHighlightTo = function(month) { this.highlightMonth(month); this.select(this.highlightedMonth.year - 1); - this.dispatchEvent( - YearListView.EventTypeYearListViewDidSelectMonth, this, month); + if (!global.params.isFormControlsRefreshEnabled) { + this.dispatchEvent( + YearListView.EventTypeYearListViewDidSelectMonth, this, month); + } + this.scrollView.scrollTo(this.selectedRow * YearListCell.GetHeight(), true); return true; }; @@ -2807,9 +3120,66 @@ YearListView.prototype._moveHighlightTo = function(month) { YearListView.prototype.onKeyDown = function(event) { var key = event.key; var eventHandled = false; - if (key == 't') - eventHandled = this._moveHighlightTo(Month.createFromToday()); - else if (this.highlightedMonth) { + if (key == 't') { + if (!global.params.isFormControlsRefreshEnabled) { + eventHandled = this._moveHighlightTo(Month.createFromToday()); + if (global.params.isFormControlsRefreshEnabled) { + this.dispatchEvent( + YearListView.EventTypeYearListViewDidSelectMonth, this, + this.highlightedMonth); + } + } + } else if ( + global.params.isFormControlsRefreshEnabled && this._selectedMonth) { + if (global.params.isLocaleRTL ? key == 'ArrowRight' : key == 'ArrowLeft') { + var newSelection = this.getNearestValidRangeLookingBackward( + this._selectedMonth.previous()); + if (newSelection) { + this.setSelectedMonthAndUpdateView(newSelection); + } + } else if (key == 'ArrowUp') { + var newSelection = this.getNearestValidRangeLookingBackward( + this._selectedMonth.previous(YearListCell.ButtonColumns)); + if (newSelection) { + this.setSelectedMonthAndUpdateView(newSelection); + } + } else if ( + global.params.isLocaleRTL ? key == 'ArrowLeft' : key == 'ArrowRight') { + var newSelection = + this.getNearestValidRangeLookingForward(this._selectedMonth.next()); + if (newSelection) { + this.setSelectedMonthAndUpdateView(newSelection); + } + } else if (key == 'ArrowDown') { + var newSelection = this.getNearestValidRangeLookingForward( + this._selectedMonth.next(YearListCell.ButtonColumns)); + if (newSelection) { + this.setSelectedMonthAndUpdateView(newSelection); + } + } else if (key == 'PageUp') { + var newSelection = this.getNearestValidRangeLookingBackward( + this._selectedMonth.previous(MonthsPerYear)); + if (newSelection) { + this.setSelectedMonthAndUpdateView(newSelection); + } + } else if (key == 'PageDown') { + var newSelection = this.getNearestValidRangeLookingForward( + this._selectedMonth.next(MonthsPerYear)); + if (newSelection) { + this.setSelectedMonthAndUpdateView(newSelection); + } + } else if (this.type !== 'month') { + if (key == 'Enter') { + this.dispatchEvent( + YearListView.EventTypeYearListViewDidSelectMonth, this, + this._selectedMonth); + } else if (key == 'Escape') { + this.hide(); + eventHandled = true; + } + } + } else if ( + !global.params.isFormControlsRefreshEnabled && this.highlightedMonth) { if (global.params.isLocaleRTL ? key == 'ArrowRight' : key == 'ArrowLeft') eventHandled = this._moveHighlightTo(this.highlightedMonth.previous()); else if (key == 'ArrowUp') @@ -2884,7 +3254,15 @@ MonthPopupView.ClassNameMonthPopupView = 'month-popup-view'; MonthPopupView.prototype.show = function(initialMonth, calendarTableRect) { this.isVisible = true; - document.body.appendChild(this.element); + if (global.params.isFormControlsRefreshEnabled && + global.params.mode == 'datetime-local') { + // Place the month popup under the datetimelocal-picker element so that the + // datetimelocal-picker element receives its keyboard and click events. + // For other calendar control types, these events are handled via the body element. + document.querySelector('datetimelocal-picker').appendChild(this.element); + } else { + document.body.appendChild(this.element); + } this.yearListView.setWidth(calendarTableRect.width - 2); this.yearListView.setHeight(YearListView.GetHeight()); if (global.params.isLocaleRTL) @@ -3194,12 +3572,17 @@ CalendarHeaderView.prototype = Object.create(View.prototype); CalendarHeaderView.Height = 24; CalendarHeaderView.BottomMargin = 10; +CalendarHeaderView.ClassNameCalendarNavigationButtonIconRefresh = + 'today-button-icon-refresh'; CalendarHeaderView._ForwardTriangle = '<svg width=\'4\' height=\'7\'><polygon points=\'0,7 0,0, 4,3.5\' style=\'fill:#6e6e6e;\' /></svg>'; -CalendarHeaderView._ForwardTriangleRefresh = - '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\ - <path d=\"M15.3516 8.60156L8 15.9531L0.648438 8.60156L1.35156 7.89844L7.5 14.0469V0H8.5V14.0469L14.6484 7.89844L15.3516 8.60156Z\" fill=\"#101010\"/>\ - </svg>' +CalendarHeaderView._ForwardTriangleRefresh = `<svg class="${ + CalendarHeaderView + .ClassNameCalendarNavigationButtonIconRefresh}" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path class="${ + CalendarHeaderView + .ClassNameCalendarNavigationButtonIconRefresh}" d="M15.3516 8.60156L8 15.9531L0.648438 8.60156L1.35156 7.89844L7.5 14.0469V0H8.5V14.0469L14.6484 7.89844L15.3516 8.60156Z" fill="#101010"/> + </svg>`; CalendarHeaderView.GetForwardTriangle = function() { if (global.params.isFormControlsRefreshEnabled) { return CalendarHeaderView._ForwardTriangleRefresh; @@ -3208,10 +3591,13 @@ CalendarHeaderView.GetForwardTriangle = function() { }; CalendarHeaderView._BackwardTriangle = '<svg width=\'4\' height=\'7\'><polygon points=\'0,3.5 4,7 4,0\' style=\'fill:#6e6e6e;\' /></svg>'; -CalendarHeaderView._BackwardTriangleRefresh = - '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\ - <path d=\"M14.6484 8.10156L8.5 1.95312V16H7.5V1.95312L1.35156 8.10156L0.648438 7.39844L8 0.046875L15.3516 7.39844L14.6484 8.10156Z\" fill=\"#101010\"/>\ - </svg>' +CalendarHeaderView._BackwardTriangleRefresh = `<svg class="${ + CalendarHeaderView + .ClassNameCalendarNavigationButtonIconRefresh}" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path class="${ + CalendarHeaderView + .ClassNameCalendarNavigationButtonIconRefresh}" d="M14.6484 8.10156L8.5 1.95312V16H7.5V1.95312L1.35156 8.10156L0.648438 7.39844L8 0.046875L15.3516 7.39844L14.6484 8.10156Z" fill="#101010"/> + </svg>`; CalendarHeaderView.GetBackwardTriangle = function() { if (global.params.isFormControlsRefreshEnabled) { return CalendarHeaderView._BackwardTriangleRefresh; @@ -3240,15 +3626,17 @@ CalendarHeaderView.prototype.onCurrentMonthChanged = function() { }; CalendarHeaderView.prototype.onNavigationButtonClick = function(sender) { - if (sender === this._previousMonthButton) + if (sender === this._previousMonthButton) { this.calendarPicker.setCurrentMonth( this.calendarPicker.currentMonth().previous(), CalendarPicker.NavigationBehavior.WithAnimation); - else if (sender === this._nextMonthButton) + this.calendarPicker.ensureSelectionIsWithinCurrentMonth(); + } else if (sender === this._nextMonthButton) { this.calendarPicker.setCurrentMonth( this.calendarPicker.currentMonth().next(), CalendarPicker.NavigationBehavior.WithAnimation); - else + this.calendarPicker.ensureSelectionIsWithinCurrentMonth(); + } else this.calendarPicker.selectRangeContainingDay(Day.createFromToday()); }; @@ -3536,6 +3924,7 @@ CalendarTableHeaderView.GetHeight = function() { */ function CalendarRowCell() { ListCell.call(this); + this.element.classList.add(CalendarRowCell.ClassNameCalendarRowCell); this.element.style.height = CalendarRowCell.GetHeight() + 'px'; this.element.setAttribute('role', 'row'); @@ -3678,8 +4067,10 @@ function CalendarTableView(calendarPicker) { this._ignoreMouseOutUntillNextMouseOver = false; this.element.addEventListener('click', this.onClick, false); - this.element.addEventListener('mouseover', this.onMouseOver, false); - this.element.addEventListener('mouseout', this.onMouseOut, false); + if (!global.params.isFormControlsRefreshEnabled) { + this.element.addEventListener('mouseover', this.onMouseOver, false); + this.element.addEventListener('mouseout', this.onMouseOut, false); + } // You shouldn't be able to use the mouse wheel to scroll. this.scrollView.element.removeEventListener( @@ -3891,16 +4282,23 @@ CalendarTableView.prototype.updateCells = function() { var dayCell = this._dayCells[dayString]; var day = dayCell.day; dayCell.setIsToday(Day.createFromToday().equals(day)); - dayCell.setSelected( - day >= firstDayInSelection && day <= lastDayInSelection); - var isHighlighted = day >= firstDayInHighlight && day <= lastDayInHighlight; - dayCell.setHighlighted(isHighlighted); - if (isHighlighted) { - if (firstDayInHighlight == lastDayInHighlight) - activeCell = dayCell; - else if ( - this.calendarPicker.type == 'month' && day == firstDayInHighlight) + var isSelected = (day >= firstDayInSelection && day <= lastDayInSelection); + dayCell.setSelected(isSelected); + if (global.params.isFormControlsRefreshEnabled) { + if (isSelected && firstDayInSelection == lastDayInSelection) { activeCell = dayCell; + } + } else { + var isHighlighted = + day >= firstDayInHighlight && day <= lastDayInHighlight; + dayCell.setHighlighted(isHighlighted); + if (isHighlighted) { + if (firstDayInHighlight == lastDayInHighlight) + activeCell = dayCell; + else if ( + this.calendarPicker.type == 'month' && day == firstDayInHighlight) + activeCell = dayCell; + } } dayCell.setIsInCurrentMonth( day >= firstDayInCurrentMonth && day <= lastDayInCurrentMonth); @@ -3910,11 +4308,18 @@ CalendarTableView.prototype.updateCells = function() { for (var weekString in this._weekNumberCells) { var weekNumberCell = this._weekNumberCells[weekString]; var week = weekNumberCell.week; - var isWeekHighlighted = highlight && highlight.equals(week); - weekNumberCell.setSelected(selection && selection.equals(week)); - weekNumberCell.setHighlighted(isWeekHighlighted); - if (isWeekHighlighted) - activeCell = weekNumberCell; + var isSelected = (selection && selection.equals(week)); + weekNumberCell.setSelected(isSelected); + if (global.params.isFormControlsRefreshEnabled) { + if (isSelected) { + activeCell = weekNumberCell; + } + } else { + var isWeekHighlighted = highlight && highlight.equals(week); + weekNumberCell.setHighlighted(isWeekHighlighted); + if (isWeekHighlighted) + activeCell = weekNumberCell; + } weekNumberCell.setDisabled(!this.calendarPicker.isValid(week)); } } @@ -3976,7 +4381,9 @@ CalendarTableView.prototype.throwAwayWeekNumberCell = function(weekNumberCell) { function CalendarPicker(type, config) { View.call(this, createElement('div', CalendarPicker.ClassNameCalendarPicker)); this.element.classList.add(CalendarPicker.ClassNamePreparing); - + if (global.params.isBorderTransparent) { + this.element.style.borderColor = 'transparent'; + } /** * @type {!string} * @const @@ -3988,12 +4395,13 @@ function CalendarPicker(type, config) { this._dateTypeConstructor = Month; else this._dateTypeConstructor = Day; - /** - * @type {!Object} - * @const - */ - this.config = {}; - this._setConfig(config); + + this._setValidDateConfig(config); + + if (global.params.isFormControlsRefreshEnabled && this.type === 'week') { + this.element.classList.add(CalendarPicker.ClassNameWeekPicker); + } + /** * @type {!Month} * @const @@ -4042,15 +4450,26 @@ function CalendarPicker(type, config) { * @protected */ this._selection = null; + /** * @type {?DateType} * @protected */ - this._highlight = null; + if (!global.params.isFormControlsRefreshEnabled) { + this._highlight = null; + } + this.calendarTableView.element.addEventListener( - 'keydown', this.onCalendarTableKeyDown, false); + 'keydown', + global.params.isFormControlsRefreshEnabled ? + this.onCalendarTableKeyDownRefresh : + this.onCalendarTableKeyDown, + false); + + document.body.addEventListener('click', this.onBodyClick, false); document.body.addEventListener('keydown', this.onBodyKeyDown, false); + window.addEventListener('resize', this.onWindowResize, false); /** @@ -4059,22 +4478,48 @@ function CalendarPicker(type, config) { */ this._height = -1; + this._hadValidValueWhenOpened = false; + var initialSelection = parseDateString(config.currentValue); if (initialSelection) { this.setCurrentMonth( Month.createFromDay(initialSelection.middleDay()), CalendarPicker.NavigationBehavior.None); - this.setSelection(initialSelection); - } else + + if (global.params.isFormControlsRefreshEnabled) { + this._hadValidValueWhenOpened = this.isValid(initialSelection); + this.setSelection(this.getNearestValidRange( + initialSelection, /*lookForwardFirst*/ true)); + } else { + this.setSelection(initialSelection); + } + } else { this.setCurrentMonth( Month.createFromToday(), CalendarPicker.NavigationBehavior.None); + + if (global.params.isFormControlsRefreshEnabled) { + // Ensure that the next date closest to today is selected to start with so that + // the user can simply submit the popup to choose it. + this.setSelection(this.getValidRangeNearestToDay( + this._dateTypeConstructor.createFromToday(), + /*lookForwardFirst*/ true)); + } + } + + /** + * @type {?DateType} + * @protected + */ + this._initialSelection = this._selection; } CalendarPicker.prototype = Object.create(View.prototype); +Object.assign(CalendarPicker.prototype, DateRangeManager); CalendarPicker.Padding = 10; CalendarPicker.BorderWidth = 1; CalendarPicker.ClassNameCalendarPicker = 'calendar-picker'; +CalendarPicker.ClassNameWeekPicker = 'week-picker'; CalendarPicker.ClassNamePreparing = 'preparing'; CalendarPicker.EventTypeCurrentMonthChanged = 'currentMonthChanged'; CalendarPicker.commitDelayMs = 100; @@ -4088,6 +4533,10 @@ CalendarPicker.prototype.onWindowResize = function(event) { window.removeEventListener('resize', this.onWindowResize, false); }; +CalendarPicker.prototype.resetToInitialValue = function() { + this.setSelection(this._initialSelection); +}; + /** * @param {!YearListView} sender */ @@ -4096,6 +4545,7 @@ CalendarPicker.prototype.onYearListViewDidHide = function(sender) { this.calendarHeaderView.setDisabled(false); if (global.params.isFormControlsRefreshEnabled) { this.calendarTableView.element.style.visibility = 'visible'; + this.calendarTableView.element.focus(); } else { this.adjustHeight(); } @@ -4108,6 +4558,11 @@ CalendarPicker.prototype.onYearListViewDidHide = function(sender) { CalendarPicker.prototype.onYearListViewDidSelectMonth = function( sender, month) { this.setCurrentMonth(month, CalendarPicker.NavigationBehavior.None); + + if (global.params.isFormControlsRefreshEnabled) { + this.ensureSelectionIsWithinCurrentMonth(); + this.onYearListViewDidHide(); + } }; /** @@ -4146,23 +4601,6 @@ CalendarPicker.prototype.onMonthPopupButtonClick = function(sender) { } }; -CalendarPicker.prototype._setConfig = function(config) { - this.config.minimum = (typeof config.min !== 'undefined' && config.min) ? - parseDateString(config.min) : - this._dateTypeConstructor.Minimum; - this.config.maximum = (typeof config.max !== 'undefined' && config.max) ? - parseDateString(config.max) : - this._dateTypeConstructor.Maximum; - this.config.minimumValue = this.config.minimum.valueOf(); - this.config.maximumValue = this.config.maximum.valueOf(); - this.config.step = (typeof config.step !== undefined) ? - Number(config.step) : - this._dateTypeConstructor.DefaultStep; - this.config.stepBase = (typeof config.stepBase !== 'undefined') ? - Number(config.stepBase) : - this._dateTypeConstructor.DefaultStepBase; -}; - /** * @return {!Month} */ @@ -4289,6 +4727,13 @@ CalendarPicker.prototype.setSelection = function(dayOrWeekOrMonth) { return; if (this._selection && this._selection.equals(dayOrWeekOrMonth)) return; + if (this._selection && !dayOrWeekOrMonth) { + this._selection = null; + if (!global.params.isFormControlsRefreshEnabled) { + this._setHighlight(null); + } + return; + } var firstDayInSelection = dayOrWeekOrMonth.firstDay(); var lastDayInSelection = dayOrWeekOrMonth.lastDay(); var candidateCurrentMonth = Month.createFromDay(firstDayInSelection); @@ -4320,7 +4765,9 @@ CalendarPicker.prototype.setSelection = function(dayOrWeekOrMonth) { candidateCurrentMonth, CalendarPicker.NavigationBehavior.WithAnimation); } - this._setHighlight(dayOrWeekOrMonth); + if (!global.params.isFormControlsRefreshEnabled) { + this._setHighlight(dayOrWeekOrMonth); + } if (!this.isValid(dayOrWeekOrMonth)) return; this._selection = dayOrWeekOrMonth; @@ -4377,41 +4824,84 @@ CalendarPicker.prototype._setHighlight = function(dayOrWeekOrMonth) { }; /** - * @param {!number} value + * @param {!Day} day * @return {!boolean} */ -CalendarPicker.prototype._stepMismatch = function(value) { - var nextAllowedValue = - Math.ceil((value - this.config.stepBase) / this.config.step) * - this.config.step + - this.config.stepBase; - return nextAllowedValue >= value + this._dateTypeConstructor.DefaultStep; +CalendarPicker.prototype.isValidDay = function(day) { + return this.isValid(this._dateTypeConstructor.createFromDay(day)); }; /** - * @param {!number} value - * @return {!boolean} + * If the selection is not inside the month currently shown in the control, + * adjust the selection so that it is within the current month. + * The new selection value is determined in the following manner: + * 1) If the old selection is on the Nth day of the month, try to place it + * on the Nth day of the new month. + * 2) If the Nth day of the new month is not valid, choose the closest + * valid date that is within the new month. + * 3) If the next and previous valid date are equidistant and both within + * the new month, arbitrarily choose the older date. */ -CalendarPicker.prototype._outOfRange = function(value) { - return value < this.config.minimumValue || value > this.config.maximumValue; -}; +CalendarPicker.prototype.ensureSelectionIsWithinCurrentMonth = function() { + if (!this._selection) + return; + if (this._selection.isFullyContainedInMonth(this.currentMonth())) + return; -/** - * @param {!DateType} dayOrWeekOrMonth - * @return {!boolean} - */ -CalendarPicker.prototype.isValid = function(dayOrWeekOrMonth) { - var value = dayOrWeekOrMonth.valueOf(); - return dayOrWeekOrMonth instanceof this._dateTypeConstructor && - !this._outOfRange(value) && !this._stepMismatch(value); -}; + var newSelection = null; + var currentRangeInNewMonth = + this._selection.thisRangeInMonth(this.currentMonth()); -/** - * @param {!Day} day - * @return {!boolean} - */ -CalendarPicker.prototype.isValidDay = function(day) { - return this.isValid(this._dateTypeConstructor.createFromDay(day)); + if (this.isValid(currentRangeInNewMonth)) { + newSelection = currentRangeInNewMonth; + } else { + var validRangeLookingBackward = + this.getNearestValidRangeLookingBackward(currentRangeInNewMonth); + var validRangeLookingForward = + this.getNearestValidRangeLookingForward(currentRangeInNewMonth); + if (validRangeLookingBackward && validRangeLookingForward) { + var newMonthIsForwardOfSelection = + (currentRangeInNewMonth.firstDay() > this._selection.firstDay()); + var [validRangeInDirectionOfAdvancement, validRangeAgainstDirectionOfAdvancement] = + newMonthIsForwardOfSelection ? + [validRangeLookingForward, validRangeLookingBackward] : + [validRangeLookingBackward, validRangeLookingForward]; + + if (!validRangeAgainstDirectionOfAdvancement.overlapsMonth( + this.currentMonth())) { + // If the range going against our direction of movement is not + // entirely within the new month, go with the range in the + // other direction to ensure we that we don't backtrack. + newSelection = validRangeInDirectionOfAdvancement; + } else if (!validRangeInDirectionOfAdvancement.overlapsMonth( + this.currentMonth())) { + newSelection = validRangeAgainstDirectionOfAdvancement; + } else { + // If both of the ranges are in the new month, select the closest one + // to the target date in the new month. + var diffFromForwardRange = Math.abs( + currentRangeInNewMonth.valueOf() - + validRangeLookingForward.valueOf()); + var diffFromBackwardRange = Math.abs( + currentRangeInNewMonth.valueOf() - + validRangeLookingBackward.valueOf()); + if (diffFromForwardRange < diffFromBackwardRange) { + newSelection = validRangeLookingForward; + } else { // In a tie, arbitrarily choose older date + newSelection = validRangeLookingBackward; + } + } + } else if (!validRangeLookingForward) { + newSelection = validRangeLookingBackward; + } else { // !validRangeLookingBackward + newSelection = validRangeLookingForward; + } // No additional clause because they can't both be null; we have a + // selection so there's at least one valid date. + } + + if (newSelection) { + this.setSelection(newSelection); + } }; /** @@ -4435,9 +4925,79 @@ CalendarPicker.prototype._moveHighlight = function(dateRange) { /** * @param {?Event} event */ +CalendarPicker.prototype.onCalendarTableKeyDownRefresh = function(event) { + var key = event.key; + var offset = 0; + + if (!event.target.matches('.today-button-refresh') && this._selection) { + switch (key) { + case 'PageUp': + var previousMonth = this.currentMonth().previous(); + if (previousMonth && previousMonth >= this.config.minimumValue) { + this.setCurrentMonth( + previousMonth, CalendarPicker.NavigationBehavior.WithAnimation); + this.ensureSelectionIsWithinCurrentMonth(); + } + break; + case 'PageDown': + var nextMonth = this.currentMonth().next(); + if (nextMonth && nextMonth >= this.config.minimumValue) { + this.setCurrentMonth( + nextMonth, CalendarPicker.NavigationBehavior.WithAnimation); + this.ensureSelectionIsWithinCurrentMonth(); + } + break; + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + var upOrDownArrowStepSize = + this.type === 'date' || this.type === 'datetime-local' ? + DaysPerWeek : + 1; + if (global.params.isLocaleRTL ? key == 'ArrowRight' : + key == 'ArrowLeft') { + var newSelection = this.getNearestValidRangeLookingBackward( + this._selection.previous()); + if (newSelection) { + this.setSelection(newSelection); + } + } else if (key == 'ArrowUp') { + var newSelection = this.getNearestValidRangeLookingBackward( + this._selection.previous(upOrDownArrowStepSize)); + if (newSelection) { + this.setSelection(newSelection); + } + } else if ( + global.params.isLocaleRTL ? key == 'ArrowLeft' : + key == 'ArrowRight') { + var newSelection = + this.getNearestValidRangeLookingForward(this._selection.next()); + if (newSelection) { + this.setSelection(newSelection); + } + } else if (key == 'ArrowDown') { + var newSelection = this.getNearestValidRangeLookingForward( + this._selection.next(upOrDownArrowStepSize)); + if (newSelection) { + this.setSelection(newSelection); + } + } + break; + }; + } + // else if there is no selection it must be the case that there are no + // valid values (because min >= max). Otherwise we would have set the selection + // during initialization. In this case there's nothing to do. +}; + +/** + * @param {?Event} event + */ CalendarPicker.prototype.onCalendarTableKeyDown = function(event) { var key = event.key; var eventHandled = false; + if (key == 't') { this.selectRangeContainingDay(Day.createFromToday()); eventHandled = true; @@ -4456,19 +5016,20 @@ CalendarPicker.prototype.onCalendarTableKeyDown = function(event) { eventHandled = true; } } else if (this._highlight) { + var upOrDownArrowStepSize = + this.type === 'date' || this.type === 'datetime-local' ? DaysPerWeek : + 1; if (global.params.isLocaleRTL ? key == 'ArrowRight' : key == 'ArrowLeft') { eventHandled = this._moveHighlight(this._highlight.previous()); } else if (key == 'ArrowUp') { - eventHandled = this._moveHighlight(this._highlight.previous( - this.type === 'date' || this.type === 'datetime-local' ? DaysPerWeek : - 1)); + eventHandled = + this._moveHighlight(this._highlight.previous(upOrDownArrowStepSize)); } else if ( global.params.isLocaleRTL ? key == 'ArrowLeft' : key == 'ArrowRight') { eventHandled = this._moveHighlight(this._highlight.next()); } else if (key == 'ArrowDown') { - eventHandled = this._moveHighlight(this._highlight.next( - this.type === 'date' || this.type === 'datetime-local' ? DaysPerWeek : - 1)); + eventHandled = + this._moveHighlight(this._highlight.next(upOrDownArrowStepSize)); } else if (key == 'Enter') { this.setSelectionAndCommit(this._highlight); } @@ -4520,42 +5081,115 @@ CalendarPicker.prototype.setHeight = function(height) { /** * @param {?Event} event */ +CalendarPicker.prototype.onBodyClick = function(event) { + if (global.params.isFormControlsRefreshEnabled && + this.type !== 'datetime-local') { + if (event.target.matches( + '.calendar-navigation-button, .today-button-icon-refresh, .month-button')) { + window.pagePopupController.setValue(this.getSelectedValue()); + } + } +}; + +/** + * @param {?Event} event + */ CalendarPicker.prototype.onBodyKeyDown = function(event) { var key = event.key; var eventHandled = false; var offset = 0; switch (key) { case 'Escape': - window.pagePopupController.closePopup(); - eventHandled = true; + // The datetime-local control handles submission/cancellation at + // the top level, so if we're in a datetime-local let event bubble + // up instead of handling it here. + if (global.params.isFormControlsRefreshEnabled) { + if (this.type !== 'datetime-local') { + if (!this._selection || + (this._selection.equals(this._initialSelection))) { + window.pagePopupController.closePopup(); + } else { + this.resetToInitialValue(); + window.pagePopupController.setValue( + this._hadValidValueWhenOpened ? + this._initialSelection.toString() : + ''); + } + } + } else { + window.pagePopupController.closePopup(); + eventHandled = true; + } + break; + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + case 'PageUp': + case 'PageDown': + if (global.params.isFormControlsRefreshEnabled && + this.type !== 'datetime-local' && + event.target.matches('.calendar-table-view') && this._selection) { + window.pagePopupController.setValue(this.getSelectedValue()); + } + break; + case 'Enter': + // Submit the popup for an Enter keypress except when the user is + // hitting Enter to activate the month switcher button, Today button, + // or previous/next month arrows. + if (global.params.isFormControlsRefreshEnabled && + this.type !== 'datetime-local') { + if (!event.target.matches( + '.calendar-navigation-button, .month-popup-button, .year-list-view')) { + if (this._selection) { + window.pagePopupController.setValueAndClosePopup( + 0, this.getSelectedValue()); + } else { + // If there is no selection it must be the case that there are no + // valid values (because min >= max). There's nothing useful to do + // with the popup in this case so just close on Enter. + window.pagePopupController.closePopup(); + } + } else if (event.target.matches( + '.calendar-navigation-button, .year-list-view')) { + // Navigating with the previous/next arrows may change selection, + // so push this change to the in-page control but don't + // close the popup. + window.pagePopupController.setValue(this.getSelectedValue()); + } + } break; case 'm': case 'M': - offset = offset || 1; // Fall-through. + offset = offset || 1; + // Fall-through. case 'y': case 'Y': - offset = offset || MonthsPerYear; // Fall-through. + offset = offset || MonthsPerYear; + // Fall-through. case 'd': case 'D': - offset = offset || MonthsPerYear * 10; - var oldFirstVisibleRow = - this.calendarTableView - .columnAndRowForDay(this.currentMonth().firstDay()) - .row; - this.setCurrentMonth( - event.shiftKey ? this.currentMonth().previous(offset) : - this.currentMonth().next(offset), - CalendarPicker.NavigationBehavior.WithAnimation); - var newFirstVisibleRow = - this.calendarTableView - .columnAndRowForDay(this.currentMonth().firstDay()) - .row; - if (this._highlight) { - var highlightMiddleDay = this._highlight.middleDay(); - this.highlightRangeContainingDay(highlightMiddleDay.next( - (newFirstVisibleRow - oldFirstVisibleRow) * DaysPerWeek)); + if (!global.params.isFormControlsRefreshEnabled) { + offset = offset || MonthsPerYear * 10; + var oldFirstVisibleRow = + this.calendarTableView + .columnAndRowForDay(this.currentMonth().firstDay()) + .row; + this.setCurrentMonth( + event.shiftKey ? this.currentMonth().previous(offset) : + this.currentMonth().next(offset), + CalendarPicker.NavigationBehavior.WithAnimation); + var newFirstVisibleRow = + this.calendarTableView + .columnAndRowForDay(this.currentMonth().firstDay()) + .row; + if (this._highlight) { + var highlightMiddleDay = this._highlight.middleDay(); + this.highlightRangeContainingDay(highlightMiddleDay.next( + (newFirstVisibleRow - oldFirstVisibleRow) * DaysPerWeek)); + } + eventHandled = true; } - eventHandled = true; break; } if (eventHandled) { diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/calendar_picker_refresh.css b/chromium/third_party/blink/renderer/core/html/forms/resources/calendar_picker_refresh.css index 7ba4b2ba739..69a6fc615e6 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/calendar_picker_refresh.css +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/calendar_picker_refresh.css @@ -4,7 +4,7 @@ */ body { - font: 12px sans-serif; + font-size: 12px; } .calendar-picker { @@ -41,7 +41,7 @@ body { } .calendar-navigation-button:hover { - background-color: #E5E5E5; + background-color: rgba(0, 117, 255, 0.3); } .calendar-navigation-button:disabled { @@ -92,7 +92,7 @@ body { border: 1px solid transparent !important; border-radius: 2px; color: #767676; - padding: 1px; + padding: 1px !important; text-align: center; } @@ -100,31 +100,61 @@ body { color: #101010; } -.day-cell.highlighted, -.month-button.highlighted, -.week-number-cell.highlighted { - background-color: #E5E5E5; +.week-number-cell, +.day-cell { + transition: color 0s; +} + +/* +* Highlight-when-hovered for cells in the month picker menu and standalone +* month control +*/ +.month-button:hover { + background-color: rgba(0, 117, 255, 0.3); +} + +/* +* Highlight-when-hovered for day cells except if this is a week picker +*/ +:not(.week-picker) > .calendar-table-view > .scroll-view > .scroll-view-content + > .calendar-row-cell > .day-cell:not(.selected):hover { + background-color: rgba(0, 117, 255, 0.3); +} + +/* +* Highlight-when-hovered for week picker, in 3 parts: +* 1. Highlight all cells in the hovered row except for Monday, because it +* belongs to the previous week. +* 2. Highlight Monday of the row after the hovered row, because it belongs to +* this week +* 3. Highlight the week number cell for the hovered week +*/ +.week-picker .calendar-row-cell:hover + .day-cell:not(.selected):not(.disabled):not(:nth-child(2)), +.week-picker .calendar-row-cell:hover + .calendar-row-cell + .day-cell:not(.selected):not(.disabled):nth-child(2), +.calendar-row-cell:hover .week-number-cell:not(.selected):not(.disabled) { + background-color: rgba(0, 117, 255, 0.3); } .day-cell.selected, .month-button.selected, .week-number-cell.selected { - background-color: #CECECE; + background-color: #0075FF; + color: #FFFFFF; font-weight: bold; } -.day-cell.highlighted.today, -.day-cell.today, -.month-button.today { - background-color: #6E6E6E; - border: 0; - color: #FFFFFF; - font-weight: bold; +.calendar-table-view:focus .day-cell.selected, +.year-list-view:focus .month-button.selected, +.calendar-table-view:focus .week-number-cell.selected { + outline: solid 2px -webkit-focus-ring-color; + outline-offset: -2px; } -.day-cell.selected.today, -.month-button.selected.today { - border: 2px solid #CECECE !important; +.day-cell.today, +.month-button.today { + border-color: #767676 !important; } .day-cell.disabled, @@ -148,7 +178,7 @@ body { } .today-button-refresh:hover { - background-color: #E5E5E5; + background-color: rgba(0, 117, 255, 0.3); } .today-button-refresh:disabled { @@ -255,11 +285,39 @@ body { color: WindowText; } - .day-cell.highlighted, - .month-button.highlighted, - .week-number-cell.highlighted { - background-color: Window; + /* + * Highlight-when-hovered for cells in the month picker menu and standalone + * month control + */ + .month-button:not(.selected):hover { + background-color: Window !important; + border-color: Highlight !important; + } + + /* + * Highlight-when-hovered for day cells except if this is a week picker + */ + :not(.week-picker) > .calendar-table-view > .scroll-view > .scroll-view-content + > .calendar-row-cell > .day-cell:not(.selected):hover { + background-color: Window !important; + border-color: Highlight !important; + } + + /* + * Highlight-when-hovered for week picker, in 3 parts: + * 1. Highlight all cells in the hovered row except for Monday, because it + * belongs to the previous week. + * 2. Highlight Monday of the row after the hovered row, because it belongs to + * this week + * 3. Highlight the week number cell for the hovered week + */ + .week-picker .calendar-row-cell:hover + .day-cell:not(.selected):not(.disabled):not(:nth-child(2)), + .week-picker .calendar-row-cell:hover + .calendar-row-cell + .day-cell:not(.selected):not(.disabled):nth-child(2), + .calendar-row-cell:hover .week-number-cell:not(.selected):not(.disabled) { border-color: Highlight !important; + background-color: Window !important; } .day-cell.selected, @@ -269,17 +327,15 @@ body { color: Window; } - .day-cell.highlighted.today, - .day-cell.today, - .month-button.today { - background-color: Highlight; - border: 2px solid Window !important; - color: Window; + .calendar-table-view:focus .day-cell.selected, + .year-list-view:focus .month-button.selected, + .calendar-table-view:focus .week-number-cell.selected { + outline: none; } - .day-cell.selected.today, - .month-button.selected.today { - border: 1px solid Window !important; + .day-cell.today, + .month-button.today { + border-color: WindowText !important; } .day-cell.disabled, diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css index dfa50b795cb..5c7e7d331d9 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css @@ -53,7 +53,7 @@ body.controls-refresh { } .controls-refresh .color-suggestion-picker-main { - border: 0; + border: 1px solid #bfbfbf; box-shadow: none; padding: 8px 8px 4px 8px; } @@ -104,6 +104,11 @@ body.controls-refresh { width: 4px; } +.controls-refresh .color-swatch:focus { + outline: solid 2px -webkit-focus-ring-color; + outline-offset: -2px; +} + .other-color { width: 100%; margin: 4px 0 0 0; @@ -114,7 +119,6 @@ body.controls-refresh { border-color: transparent; border-radius: 2px; color: #000000; - font-family: sans-serif; font-size: 12px; line-height: 16px; margin: 0; @@ -136,7 +140,7 @@ body.controls-refresh { } .controls-refresh .color-swatch:focus { - border-color: Highlight; + outline-color: Highlight; } .controls-refresh .color-swatch-container { diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.css index ed9cc009f44..467b28893ee 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.css +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.css @@ -3,10 +3,6 @@ * found in the LICENSE file. */ -body { - font-family: sans-serif; -} - .color-picker-main { border: 0; padding: 0; @@ -14,14 +10,15 @@ body { color-picker { background: #FFFFFF; + border: 1px solid #bfbfbf; display: flex; flex-direction: column; - height: 304px; + height: 250px; width: 232px; } visual-color-picker { - height: 59%; + height: 71.5%; min-height: 0; } @@ -48,12 +45,33 @@ hue-slider { } eye-dropper { + border-radius: 2px; height: 32px; margin-left: 2%; position: relative; width: 32px; } +eye-dropper.hidden { + visibility: hidden; +} + +eye-dropper:not(.selected):hover { + background-color: #F7F7F7; +} + +eye-dropper.selected { + background-color: #CECECE; +} + +eye-dropper > svg { + height: 16px; + left: 25%; + position: absolute; + top: 25%; + width: 16px; +} + color-viewer { border: 1px solid rgba(0, 0, 0, 0.19); border-radius: 50%; @@ -70,7 +88,7 @@ color-well > canvas { hue-slider > canvas { border-radius: 2px; - height: 11px; + height: 12px; margin-top: 7%; width: 100%; } @@ -83,6 +101,15 @@ color-selection-ring { position: absolute; } +color-selection-ring:focus { + /* Simulate the outline using box-shadow because it follows the border radius + * (unlike outline). + */ + box-shadow: 0px 0px 0px 2px; + color: -webkit-focus-ring-color; + outline: none; +} + color-well > color-selection-ring { height: 12px; width: 12px; @@ -126,7 +153,7 @@ input { } color-value-container > input:not(:first-child) { - margin-left: 1%; + margin-left: 4%; } format-toggler { @@ -167,31 +194,6 @@ channel-label { width: 172px; } -submission-controls { - align-items: center; - border-top: 1px solid #CECECE; - display: flex; - flex-direction: row; - height: 13%; - min-height: 0; -} - -#submission-controls-padding { - height: 100%; - width: 84%; -} - -submission-button { - padding: 3%; - text-align: center; - width: 8%; -} - -submission-button:hover { - background-color: #F3F3F3; - border-radius: 2px; -} - @media (forced-colors: active) { color-viewer { forced-color-adjust: none; diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.js index a91dcc38d09..f7396a8596b 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker.js @@ -424,15 +424,16 @@ class ColorPicker extends HTMLElement { constructor(initialColor) { super(); + if (global.params.isBorderTransparent) { + this.style.borderColor = 'transparent'; + } + this.selectedColor_ = initialColor; + this.colorWhenOpened_ = initialColor; this.visualColorPicker_ = new VisualColorPicker(initialColor); this.manualColorPicker_ = new ManualColorPicker(initialColor); - this.submissionControls_ = new SubmissionControls( - this.onSubmitButtonClick_, this.onCancelButtonClick_); - this.append( - this.visualColorPicker_, this.manualColorPicker_, - this.submissionControls_); + this.append(this.visualColorPicker_, this.manualColorPicker_); this.visualColorPicker_.addEventListener( 'visual-color-picker-initialized', this.initializeListeners_); @@ -447,6 +448,8 @@ class ColorPicker extends HTMLElement { this.addEventListener('format-change', this.updateFocusableElements_); document.documentElement.addEventListener('keydown', this.onKeyDown_); + + window.addEventListener('resize', this.onWindowResize_, {once: true}); } get selectedColor() { @@ -480,8 +483,11 @@ class ColorPicker extends HTMLElement { this.processingManualColorChange_ = true; this.visualColorPicker_.color = newColor; this.processingManualColorChange_ = false; + + const selectedValue = newColor.asHex(); + window.pagePopupController.setValue(selectedValue); } - } + }; /** * @param {!Event} event @@ -492,24 +498,33 @@ class ColorPicker extends HTMLElement { if (!this.processingManualColorChange_) { this.selectedColor = newColor; this.manualColorPicker_.color = newColor; + + const selectedValue = newColor.asHex(); + window.pagePopupController.setValue(selectedValue); } else { // We are making a visual color change in response to a manual color // change. So we do not overwrite the manually specified values and do // not change the selected color. } } - } + }; /** * @param {!Event} event */ onKeyDown_ = (event) => { - switch(event.key) { + switch (event.key) { case 'Enter': - this.submissionControls_.submitButton.click(); + window.pagePopupController.closePopup(); break; case 'Escape': - this.submissionControls_.cancelButton.click(); + if (this.selectedColor.equals(this.colorWhenOpened_)) { + window.pagePopupController.closePopup(); + } else { + this.manualColorPicker_.dispatchEvent(new CustomEvent( + 'manual-color-change', + {bubbles: true, detail: {color: this.colorWhenOpened_}})); + } break; case 'Tab': event.preventDefault(); @@ -522,9 +537,8 @@ class ColorPicker extends HTMLElement { this.focusableElements_.indexOf(document.activeElement); let nextFocusIndex; if (event.shiftKey) { - nextFocusIndex = (currentFocusIndex > 0) ? - currentFocusIndex - 1 : - length - 1; + nextFocusIndex = + (currentFocusIndex > 0) ? currentFocusIndex - 1 : length - 1; } else { nextFocusIndex = (currentFocusIndex + 1) % length; } @@ -532,27 +546,20 @@ class ColorPicker extends HTMLElement { } break; } - } + }; updateFocusableElements_ = () => { this.focusableElements_ = Array.from(this.querySelectorAll( 'color-value-container:not(.hidden-color-value-container) > input,' + '[tabindex]:not([tabindex=\'-1\'])')); - } - - static get COMMIT_DELAY_MS() { - return 100; - } - - onSubmitButtonClick_ = () => { - const selectedValue = this.selectedColor_.asHex(); - window.setTimeout(function() { - window.pagePopupController.setValueAndClosePopup(0, selectedValue); - }, ColorPicker.COMMIT_DELAY_MS); }; - onCancelButtonClick_ = () => { - window.pagePopupController.closePopup(); + onWindowResize_ = () => { + // Set focus on the first focusable element. + if (this.focusableElements_ === undefined) { + this.updateFocusableElements_(); + } + this.focusableElements_[0].focus({preventScroll: true}); }; } window.customElements.define('color-picker', ColorPicker); @@ -602,6 +609,15 @@ class VisualColorPicker extends HTMLElement { document.documentElement .addEventListener('mousemove', this.onMouseMove_); document.documentElement.addEventListener('mouseup', this.onMouseUp_); + this.colorWell_ + .addEventListener('touchstart', this.onColorWellTouchStart_); + this.hueSlider_ + .addEventListener('touchstart', this.onHueSliderTouchStart_); + document.documentElement + .addEventListener('touchstart', this.onTouchStart_); + document.documentElement + .addEventListener('touchmove', this.onTouchMove_); + document.documentElement.addEventListener('touchend', this.onTouchEnd_); document.documentElement.addEventListener('keydown', this.onKeyDown_); this.dispatchEvent(new CustomEvent('visual-color-picker-initialized')); @@ -626,7 +642,7 @@ class VisualColorPicker extends HTMLElement { event.preventDefault(); event.stopPropagation(); this.hueSlider_.focused = false; - this.colorWell_.mouseDown(new Point(event.clientX, event.clientY)); + this.colorWell_.pointerDown(new Point(event.clientX, event.clientY)); } /** @@ -636,7 +652,7 @@ class VisualColorPicker extends HTMLElement { event.preventDefault(); event.stopPropagation(); this.colorWell_.focused = false; - this.hueSlider_.mouseDown(new Point(event.clientX, event.clientY)); + this.hueSlider_.pointerDown(new Point(event.clientX, event.clientY)); } onMouseDown_ = () => { @@ -649,13 +665,52 @@ class VisualColorPicker extends HTMLElement { */ onMouseMove_ = (event) => { var point = new Point(event.clientX, event.clientY); - this.colorWell_.mouseMove(point); - this.hueSlider_.mouseMove(point); + this.colorWell_.pointerMove(point); + this.hueSlider_.pointerMove(point); } onMouseUp_ = () => { - this.colorWell_.mouseUp(); - this.hueSlider_.mouseUp(); + this.colorWell_.pointerUp(); + this.hueSlider_.pointerUp(); + } + + /** + * @param {!Event} event + */ + onColorWellTouchStart_ = (event) => { + event.preventDefault(); + event.stopPropagation(); + this.hueSlider_.focused = false; + this.colorWell_.pointerDown(new Point(event.touches[0].clientX, event.touches[0].clientY)); + } + + /** + * @param {!Event} event + */ + onHueSliderTouchStart_ = (event) => { + event.preventDefault(); + event.stopPropagation(); + this.colorWell_.focused = false; + this.hueSlider_.pointerDown(new Point(event.touches[0].clientX, event.touches[0].clientY)); + } + + onTouchStart_ = () => { + this.colorWell_.focused = false; + this.hueSlider_.focused = false; + } + + /** + * @param {!Event} event + */ + onTouchMove_ = (event) => { + var point = new Point(event.touches[0].clientX, event.touches[0].clientY); + this.colorWell_.pointerMove(point); + this.hueSlider_.pointerMove(point); + } + + onTouchEnd_ = () => { + this.colorWell_.pointerUp(); + this.hueSlider_.pointerUp(); } /** @@ -700,7 +755,88 @@ window.customElements.define('visual-color-picker', VisualColorPicker); * implementation.) * TODO(http://crbug.com/992297): Implement eye dropper */ -class EyeDropper extends HTMLElement {} +class EyeDropper extends HTMLElement { + constructor() { + super(); + + if (!global.params.isEyeDropperEnabled) { + this.classList.add('hidden'); + return; + } + + this.setAttribute('tabIndex', 0); + this.innerHTML = + '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" ' + + 'xmlns="http://www.w3.org/2000/svg"><path d="M13.7344 0C14.0469 0 ' + + '14.3411 0.0598958 14.6172 0.179688C14.8932 0.299479 15.1328 ' + + '0.460938 15.3359 0.664062C15.5391 0.867188 15.7005 1.10677 15.8203 ' + + '1.38281C15.9401 1.65885 16 1.95312 16 2.26562C16 2.56771 15.9427 ' + + '2.85938 15.8281 3.14062C15.7135 3.41667 15.5495 3.66146 15.3359 ' + + '3.875L13.4609 5.75C13.6328 5.91667 13.7656 6.10677 13.8594 ' + + '6.32031C13.9531 6.52865 14 6.75521 14 7C14 7.23958 13.9531 7.46354 ' + + '13.8594 7.67188C13.7708 7.88021 13.6432 8.06771 13.4766 ' + + '8.23438L12.25 9.46094L11 8.20312L4.71094 14.4922L4.50781 ' + + '14.5C4.24219 14.5104 4.01302 14.5547 3.82031 14.6328C3.63281 ' + + '14.7109 3.46615 14.8073 3.32031 14.9219C3.17969 15.0312 3.04948 ' + + '15.1484 2.92969 15.2734C2.8151 15.3984 2.69271 15.5156 2.5625 ' + + '15.625C2.43229 15.7344 2.28906 15.8255 2.13281 15.8984C1.97656 ' + + '15.9661 1.78646 16 1.5625 16C1.34896 16 1.14583 15.9583 0.953125 ' + + '15.875C0.765625 15.7917 0.601562 15.6797 0.460938 15.5391C0.320312 ' + + '15.3984 0.208333 15.2344 0.125 15.0469C0.0416667 14.8542 0 14.651 0 ' + + '14.4375C0 14.2135 0.0338542 14.0234 0.101562 13.8672C0.174479 ' + + '13.7057 0.265625 13.5625 0.375 13.4375C0.484375 13.3073 0.601562 ' + + '13.1849 0.726562 13.0703C0.851562 12.9505 0.96875 12.8203 1.07812 ' + + '12.6797C1.19271 12.5339 1.28906 12.3672 1.36719 12.1797C1.44531 ' + + '11.9922 1.48958 11.763 1.5 11.4922L1.50781 11.2891L7.79688 ' + + '5L6.53906 3.75L7.76562 2.52344C7.93229 2.35677 8.11979 2.22917 ' + + '8.32812 2.14062C8.53646 2.04688 8.76042 2 9 2C9.24479 2 9.47135 ' + + '2.04688 9.67969 2.14062C9.89323 2.23438 10.0833 2.36719 10.25 ' + + '2.53906L12.125 0.664062C12.3385 0.450521 12.5833 0.286458 12.8594 ' + + '0.171875C13.1406 0.0572917 13.4323 0 13.7344 0ZM10.2891 7.5L8.5 ' + + '5.71094L2.49219 11.7188C2.46615 11.9844 2.41667 12.2214 2.34375 ' + + '12.4297C2.27083 12.638 2.17708 12.8333 2.0625 13.0156C1.94792 ' + + '13.1927 1.8125 13.3646 1.65625 13.5312C1.50521 13.6927 1.34115 ' + + '13.8646 1.16406 14.0469C1.05469 14.1562 1 14.2891 1 14.4453C1 ' + + '14.5964 1.05469 14.7266 1.16406 14.8359C1.27344 14.9453 1.40365 15 ' + + '1.55469 15C1.71094 15 1.84375 14.9453 1.95312 14.8359C2.13542 ' + + '14.6589 2.3099 14.4948 2.47656 14.3438C2.64323 14.1875 2.8151 ' + + '14.0521 2.99219 13.9375C3.16927 13.8229 3.36198 13.7292 3.57031 ' + + '13.6562C3.77865 13.5833 4.01562 13.5339 4.28125 13.5078L10.2891 ' + + '7.5ZM14.625 3.16406C14.875 2.91406 15 2.61719 15 2.27344C15 2.10156 ' + + '14.9661 1.9375 14.8984 1.78125C14.8307 1.625 14.7396 1.48958 14.625 ' + + '1.375C14.5104 1.26042 14.375 1.16927 14.2188 1.10156C14.0625 ' + + '1.03385 13.8984 1 13.7266 1C13.3828 1 13.0859 1.125 12.8359 ' + + '1.375L10.25 3.95312L9.51562 3.21875C9.36979 3.07292 9.19792 3 9 ' + + '3C8.89062 3 8.78646 3.02604 8.6875 3.07812C8.59375 3.13021 8.5026 ' + + '3.19531 8.41406 3.27344C8.33073 3.35156 8.25 3.4349 8.17188 ' + + '3.52344C8.09375 3.60677 8.02083 3.68229 7.95312 3.75L12.25 ' + + '8.04688L12.7812 7.51562C12.9271 7.36979 13 7.19792 13 7C13 6.89583 ' + + '12.9792 6.80208 12.9375 6.71875C12.901 6.63021 12.8464 6.54948 ' + + '12.7734 6.47656L12.0469 5.75L14.625 3.16406Z" fill="WindowText"/> ' + + '</svg>'; + + this.addEventListener('click', this.onClick_); + this.addEventListener('keydown', this.onKeyDown_); + } + + onClick_ = () => { + event.preventDefault(); + event.stopPropagation(); + this.classList.add('selected'); + window.pagePopupController.openEyeDropper(); + }; + + /** + * @param {!Event} event + */ + onKeyDown_ = (event) => { + switch (event.key) { + case 'Enter': + this.onClick_(); + break; + } + }; +} window.customElements.define('eye-dropper', EyeDropper); /** @@ -766,7 +902,7 @@ class ColorSelectionArea extends HTMLElement { /** * @param {!Point} point */ - mouseDown(point) { + pointerDown(point) { this.colorSelectionRing_.focus({preventScroll: true}); this.colorSelectionRing_.drag = true; this.moveColorSelectionRingTo_(point); @@ -775,13 +911,13 @@ class ColorSelectionArea extends HTMLElement { /** * @param {!Point} point */ - mouseMove(point) { + pointerMove(point) { if (this.colorSelectionRing_.drag) { this.moveColorSelectionRingTo_(point); } } - mouseUp() { + pointerUp() { this.colorSelectionRing_.drag = false; } @@ -1011,6 +1147,7 @@ class ColorSelectionRing extends HTMLElement { initialize() { this.set(this.backingColorPalette_.left, this.backingColorPalette_.top); + this.onPositionChange_(); } /** @@ -1873,65 +2010,3 @@ class ChannelLabel extends HTMLElement { } } window.customElements.define('channel-label', ChannelLabel); - -/** - * SubmissionControls: Provides functionality to submit or discard a change. - */ -class SubmissionControls extends HTMLElement { - /** - * @param {function} submitCallback executed if the submit button is clicked - * @param {function} cancelCallback executed if the cancel button is clicked - */ - constructor(submitCallback, cancelCallback) { - super(); - - const padding = document.createElement('span'); - padding.setAttribute('id', 'submission-controls-padding'); - this.append(padding); - - this.submitButton_ = new SubmissionButton( - submitCallback, - '<svg width="14" height="10" viewBox="0 0 14 10" fill="none" ' + - 'xmlns="http://www.w3.org/2000/svg"><path d="M13.3516 ' + - '1.35156L5 9.71094L0.648438 5.35156L1.35156 4.64844L5 ' + - '8.28906L12.6484 0.648438L13.3516 1.35156Z" fill="WindowText"/></svg>'); - this.cancelButton_ = new SubmissionButton( - cancelCallback, - '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" ' + - 'xmlns="http://www.w3.org/2000/svg"><path d="M7.71094 7L13.1016 ' + - '12.3984L12.3984 13.1016L7 7.71094L1.60156 13.1016L0.898438 ' + - '12.3984L6.28906 7L0.898438 1.60156L1.60156 0.898438L7 ' + - '6.28906L12.3984 0.898438L13.1016 1.60156L7.71094 7Z" ' + - 'fill="WindowText"/></svg>'); - this.append(this.submitButton_, this.cancelButton_); - } - - get submitButton() { - return this.submitButton_; - } - - get cancelButton() { - return this.cancelButton_; - } -} -window.customElements.define('submission-controls', SubmissionControls); - -/** - * SubmissionButton: Button with a custom look that can be clicked for - * a submission action. - */ -class SubmissionButton extends HTMLElement { - /** - * @param {function} clickCallback executed when the button is clicked - * @param {string} htmlString custom look for the button - */ - constructor(clickCallback, htmlString) { - super(); - - this.setAttribute('tabIndex', '0'); - this.innerHTML = htmlString; - - this.addEventListener('click', clickCallback); - } -} -window.customElements.define('submission-button', SubmissionButton); diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker_common.js b/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker_common.js index bdd671d2cf0..907aa31debe 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker_common.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/color_picker_common.js @@ -43,6 +43,9 @@ function initialize(args) { document.body.classList.add('controls-refresh'); } main.classList.add('color-suggestion-picker-main'); + if (global.params.isBorderTransparent) { + main.style.borderColor = 'transparent'; + } errorString = validateColorSuggestionPickerArguments(args); } else { main.classList.add('color-picker-main'); diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/datetimelocal_picker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/datetimelocal_picker.js index 64f219fcaad..414bfde715b 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/datetimelocal_picker.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/datetimelocal_picker.js @@ -19,10 +19,9 @@ function initializeDateTimeLocalPicker(config) { /** * DateTimeLocalPicker: Custom element providing a datetime-local picker implementation. - * DateTimeLocalPicker contains 3 parts: + * DateTimeLocalPicker contains 2 parts: * - date picker * - time picker - * - submission controls */ class DateTimeLocalPicker extends HTMLElement { constructor(config) { @@ -34,34 +33,72 @@ class DateTimeLocalPicker extends HTMLElement { this.timePicker_ = new TimePicker(config); this.append(this.datePicker_.element, this.timePicker_); - this.submissionControls_ = new SubmissionControls( - this.onSubmitButtonClick_, this.onCancelButtonClick_); - this.append(this.submissionControls_); + this.hadValidValueWhenOpened_ = + (config.currentValue !== '') && (this.datePicker_.selection() != null); + this.initialSelectedValue_ = this.selectedValue; + this.addEventListener('keydown', this.onKeyDown_); + this.addEventListener('click', this.onClick_); window.addEventListener('resize', this.onWindowResize_, {once: true}); }; - onSubmitButtonClick_ = () => { - const selectedValue = this.datePicker_.getSelectedValue() + 'T' + - this.timePicker_.selectedValue; - window.setTimeout(function() { - window.pagePopupController.setValueAndClosePopup(0, selectedValue); - }, 100); - }; - - onCancelButtonClick_ = () => { - window.pagePopupController.closePopup(); - }; - onKeyDown_ = (event) => { switch (event.key) { case 'Enter': - this.submissionControls_.submitButton.click(); + // Submit the popup for an Enter keypress except when the user is + // hitting Enter to activate the month switcher button, Today button, + // or previous/next month arrows. + if (!event.target.matches( + '.calendar-navigation-button, .month-popup-button, .year-list-view')) { + window.pagePopupController.setValueAndClosePopup( + 0, this.selectedValue); + } else if (event.target.matches( + '.calendar-navigation-button, .year-list-view')) { + // Navigating with the previous/next arrows may change selection, + // so push this change to the in-page control but don't + // close the popup. + window.pagePopupController.setValue(this.selectedValue); + } break; case 'Escape': - this.submissionControls_.cancelButton.click(); + if (this.selectedValue === this.initialSelectedValue_) { + window.pagePopupController.closePopup(); + } else { + this.datePicker_.resetToInitialValue(); + this.timePicker_.resetToInitialValue(); + window.pagePopupController.setValue( + this.hadValidValueWhenOpened_ ? this.initialSelectedValue_ : ''); + } + break; + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + case 'PageUp': + case 'PageDown': + if (event.target.matches('.calendar-table-view, .time-column') && + this.hasSelectedDate) { + window.pagePopupController.setValue(this.selectedValue); + } + // Stop the native scrolling behavior; the Time picker needs to manage + // its own scroll position. + event.preventDefault(); break; + case 'Home': + case 'End': + // Prevent an attempt to scroll to the end of + // of an infinitely looping time picker column. + event.preventDefault(); + break; + } + }; + + onClick_ = (event) => { + if (event.target.matches( + '.day-cell, .time-cell, .today-button-refresh, .calendar-navigation-button, .year-list-view, .calendar-navigation-button, .today-button-icon-refresh, .month-button') && + this.hasSelectedDate) { + window.pagePopupController.setValue(this.selectedValue); } }; @@ -69,6 +106,19 @@ class DateTimeLocalPicker extends HTMLElement { this.datePicker_.calendarTableView.element.focus(); }; + // This will be false if neither the initial value of the + // control nor today's date are within a valid date range defined + // by the 'step', 'min', and 'max' attributes of the control. + get hasSelectedDate() { + return (this.datePicker_.selection() != null); + } + + get selectedValue() { + return this.hasSelectedDate ? (this.datePicker_.getSelectedValue() + 'T' + + this.timePicker_.selectedValue) : + ''; + } + get height() { return DateTimeLocalPicker.Height; } @@ -77,10 +127,6 @@ class DateTimeLocalPicker extends HTMLElement { return this.datePicker_.width() + this.timePicker_.width; } - get submissionControls() { - return this.submissionControls_; - } - get datePicker() { return this.datePicker_; } @@ -90,5 +136,5 @@ class DateTimeLocalPicker extends HTMLElement { } } DateTimeLocalPicker.ClassName = 'datetimelocal-picker'; -DateTimeLocalPicker.Height = 320; +DateTimeLocalPicker.Height = 280; window.customElements.define('datetimelocal-picker', DateTimeLocalPicker); diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css index d190f9dab12..be88651d93a 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css @@ -16,3 +16,13 @@ option, optgroup { .wrap option { white-space: pre-wrap; } + +.controls-refresh select { + border-radius: 0px; + outline: none; +} + +.controls-refresh option:checked:enabled { + outline: solid 2px -webkit-focus-ring-color; + outline-offset: -2px; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js index 8294f0b6d7e..0709fa0ee21 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js @@ -21,6 +21,9 @@ function initialize(args) { global.params = args; var main = $('main'); main.innerHTML = ''; + if (global.params.isFormControlsRefreshEnabled) { + document.body.classList.add('controls-refresh'); + } global.picker = new ListPicker(main, args); } @@ -37,7 +40,6 @@ function handleArgumentsTimeout() { */ function ListPicker(element, config) { Picker.call(this, element, config); - window.pagePopupController.selectFontsFromOwnerDocument(document); this._selectElement = createElement('select'); this._selectElement.size = 20; this._element.appendChild(this._selectElement); diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/month_picker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/month_picker.js index 47cf5ee0c4a..5ebf0d6b6b3 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/month_picker.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/month_picker.js @@ -10,7 +10,10 @@ function initializeMonthPicker(config) { global.picker = new MonthPicker(config); main.append(global.picker); - main.style.border = '1px solid transparent'; + main.style.border = '1px solid #bfbfbf'; + if (global.params.isBorderTransparent) { + main.style.borderColor = 'transparent'; + } main.style.height = (MonthPicker.Height - 2) + 'px'; main.style.width = (MonthPicker.Width - 2) + 'px'; resizeWindow(MonthPicker.Width, MonthPicker.Height); @@ -29,7 +32,7 @@ class MonthPicker extends HTMLElement { this.initializeFromConfig_(config); this.yearListView_ = - new YearListView(this.minimumMonth_, this.maximumMonth_); + new YearListView(this.minimumMonth_, this.maximumMonth_, config); this.append(this.yearListView_.element); this.initializeYearListView_(); @@ -38,6 +41,7 @@ class MonthPicker extends HTMLElement { this.initializeTodayButton_(); window.addEventListener('resize', this.onWindowResize_); + this.addEventListener('keydown', this.onKeyDown_); } initializeFromConfig_ = (config) => { @@ -49,20 +53,6 @@ class MonthPicker extends HTMLElement { Month.Maximum; this.minimumMonth_ = Month.createFromDay(minimum.firstDay()); this.maximumMonth_ = Month.createFromDay(maximum.lastDay()); - - const initialSelection = parseDateString(config.currentValue); - const initialSelectedMonth = initialSelection ? - Month.createFromDay(initialSelection.middleDay()) : - Month.createFromToday(); - this.initialValidSelection_ = false; - if (initialSelectedMonth < this.minimumMonth_) { - this.selectedMonth_ = this.minimumMonth_; - } else if (initialSelectedMonth > this.maximumMonth_) { - this.selectedMonth_ = this.maximumMonth_; - } else { - this.selectedMonth_ = initialSelectedMonth; - this.initialValidSelection_ = initialSelection != null; - } }; initializeYearListView_ = () => { @@ -78,26 +68,21 @@ class MonthPicker extends HTMLElement { MonthPicker.YearWidth + 'px'; } this.yearListView_.element.style.top = MonthPicker.YearPadding + 'px'; - if (this.initialValidSelection_) { - this.yearListView_.setSelectedMonth(this.selectedMonth_); - } - this.yearListView_.show(this.selectedMonth_); + + let yearForInitialScroll = this.selectedMonth ? + this.selectedMonth.year - 1 : + Month.createFromToday().year - 1; + this.yearListView_.scrollToRow(yearForInitialScroll, false); + this.yearListView_.selectWithoutAnimating(yearForInitialScroll); + this.yearListView_.on( YearListView.EventTypeYearListViewDidSelectMonth, this.onYearListViewDidSelectMonth_); - this.yearListView_.on( - YearListView.EventTypeYearListViewDidHide, this.onYearListViewDidHide_); - }; - - onYearListViewDidHide_ = (sender) => { - const selectedValue = this.selectedMonth_.toString(); - window.setTimeout(function() { - window.pagePopupController.setValueAndClosePopup(0, selectedValue); - }, 100); }; onYearListViewDidSelectMonth_ = (sender, month) => { - this.selectedMonth_ = month; + const selectedValue = month.toString(); + window.pagePopupController.setValueAndClosePopup(0, selectedValue); }; initializeTodayButton_ = () => { @@ -107,8 +92,7 @@ class MonthPicker extends HTMLElement { this.todayButton_.element.classList.add(MonthPicker.ClassNameTodayButton); const monthContainingToday = Month.createFromToday(); this.todayButton_.setDisabled( - monthContainingToday < this.minimumMonth_ || - monthContainingToday > this.maximumMonth_); + !this.yearListView_.isValid(monthContainingToday)); this.todayButton_.on( CalendarNavigationButton.EventTypeButtonClick, this.onTodayButtonClick_); @@ -116,15 +100,68 @@ class MonthPicker extends HTMLElement { onTodayButtonClick_ = (sender) => { const selectedValue = Month.createFromToday().toString(); - window.setTimeout(function() { - window.pagePopupController.setValueAndClosePopup(0, selectedValue); - }, 100); + window.pagePopupController.setValueAndClosePopup(0, selectedValue); + }; + + onKeyDown_ = (event) => { + switch (event.key) { + case 'Enter': + // Don't do anything here if user has hit Enter on 'This month' + // button. We'll handle that in this.onTodayButtonClick_. + if (!event.target.matches('.calendar-navigation-button')) { + if (this.selectedMonth) { + window.pagePopupController.setValueAndClosePopup( + 0, this.selectedMonth.toString()); + } else { + window.pagePopupController.closePopup(); + } + } + break; + case 'Escape': + if (!this.selectedMonth || + (this.selectedMonth.equals(this.initialSelectedMonth))) { + window.pagePopupController.closePopup(); + } else { + this.resetToInitialValue_(); + window.pagePopupController.setValue( + this.hadValidValueWhenOpened ? + this.initialSelectedMonth.toString() : + ''); + } + break; + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + case 'PageUp': + case 'PageDown': + if (this.selectedMonth) { + window.pagePopupController.setValue(this.selectedMonth.toString()); + } + break; + } + }; + + resetToInitialValue_ = () => { + this.yearListView_.setSelectedMonthAndUpdateView(this.initialSelectedMonth); }; onWindowResize_ = (event) => { window.removeEventListener('resize', this.onWindowResize_); this.yearListView_.element.focus(); }; + + get selectedMonth() { + return this.yearListView_._selectedMonth; + }; + + get initialSelectedMonth() { + return this.yearListView_._initialSelectedMonth; + }; + + get hadValidValueWhenOpened() { + return this.yearListView_._hadValidValueWhenOpened; + }; } MonthPicker.Width = 232; MonthPicker.YearWidth = 194; diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css index 860e2a397f0..011130295f3 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css @@ -56,7 +56,7 @@ } .controls-refresh .suggestion-list { - border-color: transparent; + border-color: #bfbfbf; padding: 4px; } @@ -69,6 +69,8 @@ .controls-refresh .suggestion-list-entry:focus { background-color: #E5E5E5; + outline: solid 2px -webkit-focus-ring-color; + outline-offset: -2px; } .controls-refresh .suggestion-list-entry .title { diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js index e42d5424a39..760c04e1022 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js @@ -200,6 +200,9 @@ SuggestionPicker.prototype._layout = function() { if (this._config.isLocaleRTL) this._element.classList.add('locale-rtl'); this._containerElement = createElement('ul', 'suggestion-list'); + if (global.params.isBorderTransparent) { + this._containerElement.style.borderColor = 'transparent'; + } this._containerElement.addEventListener( 'click', this._handleEntryClick.bind(this), false); for (var i = 0; i < this._config.suggestionValues.length; ++i) { diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.css index ebd30d88d0c..2a460119fea 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.css +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.css @@ -3,16 +3,12 @@ * found in the LICENSE file. */ -body { - font-family: sans-serif; -} - .time-picker { background: #FFFFFF; - border: 1px solid transparent; + border: 1px solid #bfbfbf; display: flex; flex-direction: column; - height: 298px; + height: 258px; } .time-columns { @@ -31,7 +27,7 @@ body { outline: none; overflow: scroll; padding: 0; - scroll-snap-type: y mandatory; + position: relative; width: 52px; } @@ -46,57 +42,23 @@ body { font-size: 14px; height: 32px; line-height: 32px; - scroll-snap-align: start; + position: relative; text-align: center; width: 48px; } .time-cell:hover { - background: #E5E5E5; + background-color: rgba(0, 117, 255, 0.3); } .time-cell.selected { - background-color: #CECECE; + background-color: #0075FF; + color: #FFFFFF; font-weight: bold; } .time-column:focus .time-cell.selected { - border-color: highlight; -} - -.submission-controls { - align-items: center; - border-top: 1px solid #CECECE; - bottom: 0px; - display: flex; - flex-direction: row; - height: 40px; - position: absolute; - width: 100%; -} - -#submission-controls-padding { - height: 100%; - width: 84%; -} - -.submission-button { - background-color: #FFFFFF; - border: 2px solid transparent; - border-radius: 2px; - height: 32px; - margin: 4px; - padding: 8px; - width: 32px; -} - -.submission-button:hover { - background-color: #E5E5E5; -} - -.submission-button:focus { - border-color: highlight; - outline: none; + border-color: #101010; } @media (forced-colors: active) { @@ -119,22 +81,4 @@ body { .time-column:focus .time-cell.selected { border-color: WindowText; } - - .submission-button { - background-color: Window; - forced-color-adjust: none; - } - - .submission-button:hover { - background-color: Window; - border-color: Highlight; - } - - .submission-button:focus { - border-color: WindowText; - } - - .submission-button path { - fill: WindowText; - } } diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.js index 8959e6bc4cc..62280830b9d 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.js +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/time_picker.js @@ -27,7 +27,6 @@ const TimeColumnType = { AMPM: 5, }; - /** * Supported label types. * @enum {number} @@ -63,7 +62,7 @@ class Time { this.minute_ = minute; this.second_ = second; this.millisecond_ = millisecond; - } + }; next = (columnType) => { switch (columnType) { @@ -210,15 +209,15 @@ DateTime.ISOStringRegExp = /^(\d+)-(\d+)-(\d+)T(\d+):(\d+):?(\d*).?(\d*)/; /** * TimePicker: Custom element providing a time picker implementation. - * TimePicker contains 2 parts: - * - column container - * - submission controls */ class TimePicker extends HTMLElement { constructor(config) { super(); this.className = TimePicker.ClassName; + if (global.params.isBorderTransparent) { + this.style.borderColor = 'transparent'; + } this.initializeFromConfig_(config); this.timeColumns_ = new TimeColumns(this); @@ -226,10 +225,8 @@ class TimePicker extends HTMLElement { if (config.mode == 'time') { // TimePicker doesn't handle the submission when used for non-time types. - this.submissionControls_ = new SubmissionControls( - this.onSubmitButtonClick_, this.onCancelButtonClick_); - this.append(this.submissionControls_); this.addEventListener('keydown', this.onKeyDown_); + this.addEventListener('click', this.onClick_); } window.addEventListener('resize', this.onWindowResize_, {once: true}); @@ -237,48 +234,70 @@ class TimePicker extends HTMLElement { initializeFromConfig_ = (config) => { const initialSelection = parseDateTimeString(config.currentValue, 'time'); - this.selectedTime_ = + this.initialSelectedTime_ = initialSelection ? initialSelection : Time.currentTime(); + this.hadValidValueWhenOpened_ = (initialSelection != null); this.hasSecond_ = config.hasSecond; this.hasMillisecond_ = config.hasMillisecond; this.hasAMPM_ = config.hasAMPM; }; - onSubmitButtonClick_ = () => { - const selectedValue = this.selectedValue; - window.setTimeout(function() { - window.pagePopupController.setValueAndClosePopup(0, selectedValue); - }, 100); - }; - - onCancelButtonClick_ = () => { - window.pagePopupController.closePopup(); - }; - onWindowResize_ = (event) => { - // Scroll columns to the second half to allow scrolling up. - this.timeColumns_.scrollColumnsToMiddle(); + this.timeColumns_.scrollColumnsToSelectedCells(); this.timeColumns_.firstChild.focus(); }; onKeyDown_ = (event) => { switch (event.key) { case 'Enter': - this.submissionControls_.submitButton.click(); + window.pagePopupController.setValueAndClosePopup(0, this.selectedValue); break; case 'Escape': - this.submissionControls_.cancelButton.click(); + if (this.selectedValue === + this.initialSelectedTime.toString( + this.hasSecond, this.hasMillisecond)) { + window.pagePopupController.closePopup(); + } else { + this.resetToInitialValue(); + window.pagePopupController.setValue( + this.hadValidValueWhenOpened ? this.selectedValue : ''); + } + break; + case 'ArrowUp': + case 'ArrowDown': + window.pagePopupController.setValue(this.selectedValue); + event.stopPropagation(); + event.preventDefault(); + break; + case 'Home': + case 'End': + // Prevent an attempt to scroll to the end of + // of an infinitely looping column. + event.preventDefault(); break; } }; + onClick_ = (event) => { + window.pagePopupController.setValue(this.selectedValue); + }; + + resetToInitialValue = () => { + this.timeColumns_.resetToInitialValues(); + this.timeColumns_.scrollColumnsToSelectedCells(); + } + get selectedValue() { return this.timeColumns_.selectedValue().toString( this.hasSecond, this.hasMillisecond); } - get selectedTime() { - return this.selectedTime_; + get initialSelectedTime() { + return this.initialSelectedTime_; + } + + get hadValidValueWhenOpened() { + return this.hadValidValueWhenOpened_; } get hasSecond() { @@ -304,13 +323,9 @@ class TimePicker extends HTMLElement { get timeColumns() { return this.timeColumns_; } - - get submissionControls() { - return this.submissionControls_; - } } TimePicker.ClassName = 'time-picker'; -TimePicker.Height = 300; +TimePicker.Height = 260; TimePicker.ColumnWidth = 56; TimePicker.BorderWidth = 1; window.customElements.define('time-picker', TimePicker); @@ -379,15 +394,21 @@ class TimeColumns extends HTMLElement { return new Time(hour, minute, second, millisecond); }; - scrollColumnsToMiddle = () => { - this.hourColumn_.scrollTop = this.hourColumn_.scrollHeight / 2; - this.minuteColumn_.scrollTop = this.minuteColumn_.scrollHeight / 2; + resetToInitialValues = + () => { + Array.prototype.forEach.call(this.children, (column) => { + column.resetToInitialValue(); + }); + } + + scrollColumnsToSelectedCells = () => { + this.hourColumn_.scrollToSelectedCell(); + this.minuteColumn_.scrollToSelectedCell(); if (this.secondColumn_) { - this.secondColumn_.scrollTop = this.secondColumn_.scrollHeight / 2; + this.secondColumn_.scrollToSelectedCell(); } if (this.millisecondColumn_) { - this.millisecondColumn_.scrollTop = - this.millisecondColumn_.scrollHeight / 2; + this.millisecondColumn_.scrollToSelectedCell(); } } } @@ -405,10 +426,24 @@ class TimeColumn extends HTMLUListElement { this.className = TimeColumn.ClassName; this.tabIndex = 0; this.columnType_ = columnType; + this.setAttribute('role', 'listbox'); + if (this.columnType_ === TimeColumnType.HOUR) { + this.setAttribute('aria-label', global.params.axHourLabel); + } else if (this.columnType_ === TimeColumnType.MINUTE) { + this.setAttribute('aria-label', global.params.axMinuteLabel); + } else if (this.columnType_ === TimeColumnType.SECOND) { + this.setAttribute('aria-label', global.params.axSecondLabel); + } else if (this.columnType_ === TimeColumnType.MILLISECOND) { + this.setAttribute('aria-label', global.params.axMillisecondLabel); + } else { + this.setAttribute('aria-label', global.params.axAmPmLabel); + } + if (this.columnType_ == TimeColumnType.AMPM) { this.createAndInitializeAMPMCells_(timePicker); } else { this.createAndInitializeCells_(timePicker); + this.setupScrollHandler_(); } this.addEventListener('click', this.onClick_); @@ -417,23 +452,156 @@ class TimeColumn extends HTMLUListElement { createAndInitializeCells_ = (timePicker) => { const totalCells = Time.numberOfValues(this.columnType_, timePicker.hasAMPM); - let currentTime = timePicker.selectedTime.clone(); + let currentTime = timePicker.initialSelectedTime.clone(); + + // The granularity of millisecond cells is once cell per 100ms. + // But, we want to have a cell with the exact millisecond value of the + // in-page control, so we'll replace the millisecond cell closest to that + // value with the exact value. We do that by figuring out here which of + // the cells will be the closest one here, and then matching against that + // one in the subsequent loop. + let roundedMillisecondValue = 0; + if (this.columnType_ === TimeColumnType.MILLISECOND) { + let millisecondValue = + currentTime.value(TimeColumnType.MILLISECOND, timePicker.hasAMPM); + roundedMillisecondValue = + (100 * Math.floor((Number(millisecondValue) + 50.0) / 100.0)) % 1000; + } + + let time = new Time(1, 1, 1, 100); let cells = []; - let duplicateCells = []; - // In order to support a continuous looping navigation for up/down arrows, - // the initial list of cells is doubled and middleTimeCell is kept - // to inform where the duplicated cells begin. + let initialCellIndex = -1; for (let i = 0; i < totalCells; i++) { - let value = currentTime.value(this.columnType_, timePicker.hasAMPM); + let value = time.value(this.columnType_, timePicker.hasAMPM); + + if (this.columnType_ === TimeColumnType.MILLISECOND && + Number(value) === roundedMillisecondValue) { + // Set this cell to the exact ms value of the in-page control + value = + currentTime.value(TimeColumnType.MILLISECOND, timePicker.hasAMPM); + initialCellIndex = i; + } else if ( + time.value(this.columnType_, timePicker.hasAMPM) === + currentTime.value(this.columnType_, timePicker.hasAMPM)) { + initialCellIndex = i; + } + let timeCell = new TimeCell(value, localizeNumber(value)); - let duplicatedTimeCell = new TimeCell(value, localizeNumber(value)); cells.push(timeCell); - duplicateCells.push(duplicatedTimeCell); - currentTime.next(this.columnType_); + + timeCell.initialOffsetTop = TimeColumn.CELL_HEIGHT * i; + timeCell.style.top = `${TimeColumn.SCROLL_OFFSET}px`; + + time.next(this.columnType_); + } + this.selectedTimeCell = this.initialTimeCell_ = cells[initialCellIndex]; + this.cellsInLayoutOrder = cells; + this.append(...cells); + }; + + /* + * Create a scroll handler that implements infinite looping scroll by + * rotating TimeCells up/down so that there is always at least one cell + * offscreen in the direction of the scroll. This activity should be + * invisible to the user. + */ + setupScrollHandler_ = () => { + let lastScrollPosition = 0; + let upcomingSnapToCellEdge = null; + this.addEventListener('scroll', (event) => { + let isGoingDown = (this.scrollTop > lastScrollPosition); + lastScrollPosition = this.scrollTop; + + // Rotate cells down until there is one cell beyond the bottom + // of the visible scroller area. + while (this.cellsInLayoutOrder[this.cellsInLayoutOrder.length - 1] + .offsetTop - + this.scrollTop - this.clientHeight < + TimeColumn.CELL_HEIGHT) { + this.rotateCells_( + /*topToBottom*/ true); + } + + // Rotate cells up until there is one cell beyond the top + // of the visible scroller area. + while (this.scrollTop - this.cellsInLayoutOrder[0].offsetTop < + TimeColumn.CELL_HEIGHT * 2) { + this.rotateCells_( + /*topToBottom*/ false); + } + + // Snap the scroll amount to the nearest TimeCell top edge 1 second + // after the user has stopped scrolling. This would be done with + // CSS scroll-snap-align, but it interferes with this scroll handler + // and causes jittery scrolling. + window.clearTimeout(upcomingSnapToCellEdge); + upcomingSnapToCellEdge = + window.setTimeout(() => {this.snapToCellEdge_(isGoingDown)}, 1000); + }); + }; + + /* + * Scroll the column so that the top is aligned with the top edge of the + * nearest TimeCell in the given direction. + */ + snapToCellEdge_ = (isGoingDown) => { + let offsetFromCellEdge = + (this.cellsInLayoutOrder[this.cellsInLayoutOrder.length - 1].offsetTop - + this.scrollTop) % + TimeColumn.CELL_HEIGHT; + if (isGoingDown) { + this.scrollTop += offsetFromCellEdge; + } else { + if (offsetFromCellEdge != 0) { + this.scrollTop -= TimeColumn.CELL_HEIGHT - offsetFromCellEdge; + } + } + }; + + // Ideally we would have truly infinite scrolling in both directions. + // However, the platform does not allow scrolling into negative scroll + // offsets. So, we start the column at a large positive scroll so that + // the column will be unlikely to hit the top during normal use. + static SCROLL_OFFSET = 100000; + static CELL_HEIGHT = 36; // Height of one TimeCell, including border + + // Using position:absolute for TimeCells seems like the natural choice, + // but absolutely positioned children don't cause the TimeColumn scroll + // container to expand to hold the cells, so they fall off the end of + // the popup. Instead, we use relative positioning and use these + // helpers to convert to an "absolute" position that is easier to reason + // about when manipulating the layout position of the TimeCells. + static getCellAbsolutePosition = (cell) => { + let cellOffset = parseInt(cell.style.top.substring( + 0, cell.style.top.length - 2)); // Chop off the 'px' + return (cellOffset + cell.initialOffsetTop); + }; + static setCellAbsolutePosition = (cell, absolutePosition) => { + cell.style.top = `${absolutePosition - cell.initialOffsetTop}px`; + }; + + // Take the top/bottom TimeCell in this column and move it to the + // bottom/top. This should only be done for offscreen cells so that + // it is invisible to the user -- but it ensures that the cells will + // always be visible wherever the user scrolls. + rotateCells_ = (topToBottom) => { + if (topToBottom) { + let topCell = this.cellsInLayoutOrder.shift(); + let bottomCell = + this.cellsInLayoutOrder[this.cellsInLayoutOrder.length - 1]; + let bottomCellAbsoluteOffset = + TimeColumn.getCellAbsolutePosition(bottomCell); + TimeColumn.setCellAbsolutePosition( + topCell, bottomCellAbsoluteOffset + TimeColumn.CELL_HEIGHT); + this.cellsInLayoutOrder.push(topCell); + } else { + let topCell = this.cellsInLayoutOrder[0]; + let bottomCell = this.cellsInLayoutOrder.pop(); + let absoluteTopCellOffset = TimeColumn.getCellAbsolutePosition(topCell); + TimeColumn.setCellAbsolutePosition( + bottomCell, absoluteTopCellOffset - TimeColumn.CELL_HEIGHT); + this.cellsInLayoutOrder.unshift(bottomCell); } - this.selectedTimeCell = duplicateCells[0]; - this.middleTimeCell_ = duplicateCells[0]; - this.append(...cells, ...duplicateCells); }; createAndInitializeAMPMCells_ = (timePicker) => { @@ -444,7 +612,7 @@ class TimeColumn extends HTMLUListElement { cells.push(timeCell); } - if (timePicker.selectedTime.isAM()) { + if (timePicker.initialSelectedTime.isAM()) { this.append(cells[Label.AM], cells[Label.PM]); this.selectedTimeCell = cells[Label.AM]; } else { @@ -458,40 +626,53 @@ class TimeColumn extends HTMLUListElement { }; /** - * Continuous looping navigation for up/down arrows is supported by: - * - moving for ArrowUp to previous cell and for topmost cell which - * has no previous, we are moving to the last cell from the first list - * - moving for ArrowDown to next cell and for the last duplicated cell - * which has no next, we are moving to the first cell from the duplicated list + * Continuous looping navigation for up/down arrows and scrolling is + * supported by rotating the layout positions of the TimeCells. This + * is done in a scroll event handler and the following keydown handler. + * Cells are rotated in before they are reached by the visible part of + * the scroller, so the user just sees an infinitely looping column. */ onKeyDown_ = (event) => { - let eventHandled = false; switch (event.key) { case 'ArrowUp': - const previousTimeCell = this.selectedTimeCell.previousSibling; - if (previousTimeCell) { - this.selectedTimeCell = previousTimeCell; - previousTimeCell.scrollIntoViewIfNeeded(false); - } else if (this.columnType != TimeColumnType.AMPM) { - // move from the topmost cell to the last cell (the last cell is - // the first one before the duplicated list). - this.selectedTimeCell = this.middleTimeCell.previousSibling; - this.selectedTimeCell.scrollIntoView(); + const previousTimeCell = this.selectedTimeCell.previousSibling ? + this.selectedTimeCell.previousSibling : + this.lastElementChild; + + if (this.scrollTop === 0 && previousTimeCell.offsetTop <= 0) { + // If the user somehow made it all the way to the top of the + // scroller, stop going up and rotating cells into negative + // offsets. This should not be a normal scenario. + break; } - eventHandled = true; + + // Ensure that we don't run out of cells ahead of the selected cell in + // the event that the scroll event handler can't keep up. This can + // happen e.g. if the user holds down the arrow key. + if (this.columnType_ !== TimeColumnType.AMPM && + this.selectedTimeCell === this.cellsInLayoutOrder[0]) { + this.rotateCells_(/*topToBottom*/ false); + } + + this.selectedTimeCell = previousTimeCell; + this.selectedTimeCell.scrollIntoViewIfNeeded(false); break; case 'ArrowDown': - const nextTimeCell = this.selectedTimeCell.nextSibling; - if (nextTimeCell) { - this.selectedTimeCell = nextTimeCell; - nextTimeCell.scrollIntoViewIfNeeded(false); - } else if (this.columnType != TimeColumnType.AMPM) { - // move from the last duplicated cell to the first cell - // of the duplicated list. - this.selectedTimeCell = this.middleTimeCell; - this.selectedTimeCell.scrollIntoView(false); + const nextTimeCell = this.selectedTimeCell.nextSibling ? + this.selectedTimeCell.nextSibling : + this.firstElementChild; + + // Ensure that we don't run out of cells ahead of the selected cell in + // the event that the scroll event handler can't keep up. This can + // happen e.g. if the user holds down the arrow key. + if (this.columnType_ !== TimeColumnType.AMPM && + this.selectedTimeCell === + this.cellsInLayoutOrder[this.cellsInLayoutOrder.length - 1]) { + this.rotateCells_(/*topToBottom*/ true); } - eventHandled = true; + + this.selectedTimeCell = nextTimeCell; + this.selectedTimeCell.scrollIntoViewIfNeeded(false); break; case 'ArrowLeft': const previousTimeColumn = this.previousSibling; @@ -506,12 +687,14 @@ class TimeColumn extends HTMLUListElement { } break; } + }; - if (eventHandled) { - event.stopPropagation(); - event.preventDefault(); + scrollToSelectedCell = (cell) => { + while(this.cellsInLayoutOrder[1] != this.selectedTimeCell) { + this.rotateCells_(/*topToBottom*/true); } - }; + this.scrollTop = this.selectedTimeCell.offsetTop; + } get selectedTimeCell() { return this.selectedTimeCell_; @@ -520,14 +703,21 @@ class TimeColumn extends HTMLUListElement { set selectedTimeCell(timeCell) { if (this.selectedTimeCell_) { this.selectedTimeCell_.classList.remove('selected'); + this.selectedTimeCell_.removeAttribute('aria-selected'); } this.selectedTimeCell_ = timeCell; + this.setAttribute('aria-activedescendant', timeCell.id); this.selectedTimeCell_.classList.add('selected'); + this.selectedTimeCell_.setAttribute('aria-selected', 'true'); } - get middleTimeCell() { - return this.middleTimeCell_; - } + resetToInitialValue = () => { + if (this.columnType_ == TimeColumnType.AMPM) { + this.selectedTimeCell = this.firstChild; + } else { + this.selectedTimeCell = this.initialTimeCell_; + } + }; get columnType() { return this.columnType_; @@ -546,65 +736,16 @@ class TimeCell extends HTMLLIElement { this.className = TimeCell.ClassName; this.textContent = localizedValue; this.value = value; - }; -} -TimeCell.ClassName = 'time-cell'; -window.customElements.define('time-cell', TimeCell, {extends: 'li'}); - -/** - * SubmissionControls: Provides functionality to submit or discard a change. - */ -class SubmissionControls extends HTMLElement { - constructor(submitCallback, cancelCallback) { - super(); - - const padding = document.createElement('span'); - padding.setAttribute('id', 'submission-controls-padding'); - this.append(padding); - - this.className = SubmissionControls.ClassName; - - this.submitButton_ = new SubmissionButton( - submitCallback, - '<svg width="14" height="10" viewBox="0 0 14 10" fill="none" ' + - 'xmlns="http://www.w3.org/2000/svg"><path d="M13.3516 ' + - '1.35156L5 9.71094L0.648438 5.35156L1.35156 4.64844L5 ' + - '8.28906L12.6484 0.648438L13.3516 1.35156Z" fill="black"/></svg>'); - this.cancelButton_ = new SubmissionButton( - cancelCallback, - '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" ' + - 'xmlns="http://www.w3.org/2000/svg"><path d="M7.71094 7L13.1016 ' + - '12.3984L12.3984 13.1016L7 7.71094L1.60156 13.1016L0.898438 ' + - '12.3984L6.28906 7L0.898438 1.60156L1.60156 0.898438L7 ' + - '6.28906L12.3984 0.898438L13.1016 1.60156L7.71094 7Z" ' + - 'fill="black"/></svg>'); - this.append(this.submitButton_, this.cancelButton_); - } - get submitButton() { - return this.submitButton_; - } + this.setAttribute('role', 'option'); + this.id = TimeCell.getNextUniqueId(); + }; - get cancelButton() { - return this.cancelButton_; + static getNextUniqueId() { + return `timeCell${TimeCell.idCount++}`; } -} -SubmissionControls.ClassName = 'submission-controls'; -window.customElements.define('submission-controls', SubmissionControls); - -/** - * SubmissionButton: Button with a custom look that can be clicked for - * a submission action. - */ -class SubmissionButton extends HTMLButtonElement { - constructor(clickCallback, htmlString) { - super(); - this.className = SubmissionButton.ClassName; - this.innerHTML = htmlString; - this.addEventListener('click', clickCallback); - } + static idCount = 0; } -SubmissionButton.ClassName = 'submission-button'; -window.customElements.define( - 'submission-button', SubmissionButton, {extends: 'button'}); +TimeCell.ClassName = 'time-cell'; +window.customElements.define('time-cell', TimeCell, {extends: 'li'}); diff --git a/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc index 5b964f18b85..74fff77ea69 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc @@ -31,6 +31,9 @@ #include "third_party/blink/renderer/core/html/forms/search_input_type.h" #include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/core/css/css_property_names.h" +#include "third_party/blink/renderer/core/css_value_keywords.h" +#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" #include "third_party/blink/renderer/core/frame/web_feature.h" @@ -39,7 +42,6 @@ #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/input_type_names.h" -#include "third_party/blink/renderer/core/layout/layout_search_field.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/heap/heap.h" @@ -56,11 +58,6 @@ void SearchInputType::CountUsage() { CountUsageIfVisible(WebFeature::kInputTypeSearch); } -LayoutObject* SearchInputType::CreateLayoutObject(const ComputedStyle&, - LegacyLayout) const { - return new LayoutSearchField(&GetElement()); -} - const AtomicString& SearchInputType::FormControlType() const { return input_type_names::kSearch; } diff --git a/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h index 34a6f6b89df..dd1ff43512a 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h @@ -42,8 +42,6 @@ class SearchInputType final : public BaseTextInputType { private: void CountUsage() override; - LayoutObject* CreateLayoutObject(const ComputedStyle&, - LegacyLayout) const override; const AtomicString& FormControlType() const override; bool NeedsContainer() const override; void CreateShadowSubtree() override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/select_type.cc b/chromium/third_party/blink/renderer/core/html/forms/select_type.cc new file mode 100644 index 00000000000..345cbeb2682 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/select_type.cc @@ -0,0 +1,1252 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights + * reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "third_party/blink/renderer/core/html/forms/select_type.h" + +#include "build/build_config.h" +#include "third_party/blink/public/strings/grit/blink_strings.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_mutation_observer_init.h" +#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/mutation_observer.h" +#include "third_party/blink/renderer/core/dom/mutation_record.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/events/gesture_event.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/html/forms/popup_menu.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/input/input_device_capabilities.h" +#include "third_party/blink/renderer/core/layout/layout_box.h" +#include "third_party/blink/renderer/core/page/autoscroll_controller.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/page/spatial_navigation.h" +#include "third_party/blink/renderer/core/paint/paint_layer.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "ui/base/ui_base_features.h" + +namespace blink { + +class PopupUpdater; + +namespace { + +HTMLOptionElement* EventTargetOption(const Event& event) { + return DynamicTo<HTMLOptionElement>(event.target()->ToNode()); +} + +} // anonymous namespace + +class MenuListSelectType final : public SelectType { + public: + explicit MenuListSelectType(HTMLSelectElement& select) : SelectType(select) {} + void Trace(Visitor* visitor) override; + + bool DefaultEventHandler(const Event& event) override; + void DidSelectOption(HTMLOptionElement* element, + HTMLSelectElement::SelectOptionFlags flags, + bool should_update_popup) override; + void DidBlur() override; + void DidDetachLayoutTree() override; + void DidRecalcStyle(const StyleRecalcChange change) override; + void DidSetSuggestedOption(HTMLOptionElement* option) override; + void SaveLastSelection() override; + + void UpdateTextStyle() override { UpdateTextStyleInternal(); } + void UpdateTextStyleAndContent() override; + HTMLOptionElement* OptionToBeShown() const override; + const ComputedStyle* OptionStyle() const override { + return option_style_.get(); + } + void MaximumOptionWidthMightBeChanged() const override; + + void ShowPopup() override; + void HidePopup() override; + void PopupDidHide() override; + bool PopupIsVisible() const override; + PopupMenu* PopupForTesting() const override; + + void DidMutateSubtree(); + + private: + bool ShouldOpenPopupForKeyDownEvent(const KeyboardEvent& event); + bool ShouldOpenPopupForKeyPressEvent(const KeyboardEvent& event); + // Returns true if this function handled the event. + bool HandlePopupOpenKeyboardEvent(); + void SetPopupIsVisible(bool popup_is_visible); + void DispatchEventsIfSelectedOptionChanged(); + String UpdateTextStyleInternal(); + void DidUpdateActiveOption(HTMLOptionElement* option); + void ObserveTreeMutation(); + void UnobserveTreeMutation(); + + Member<PopupMenu> popup_; + Member<PopupUpdater> popup_updater_; + scoped_refptr<const ComputedStyle> option_style_; + int ax_menulist_last_active_index_ = -1; + bool has_updated_menulist_active_option_ = false; + bool popup_is_visible_ = false; + bool snav_arrow_key_selection_ = false; +}; + +void MenuListSelectType::Trace(Visitor* visitor) { + visitor->Trace(popup_); + visitor->Trace(popup_updater_); + SelectType::Trace(visitor); +} + +bool MenuListSelectType::DefaultEventHandler(const Event& event) { + // We need to make the layout tree up-to-date to have GetLayoutObject() give + // the correct result below. An author event handler may have set display to + // some element to none which will cause a layout tree detach. + select_->GetDocument().UpdateStyleAndLayoutTree(); + + const auto* key_event = DynamicTo<KeyboardEvent>(event); + if (event.type() == event_type_names::kKeydown) { + if (!select_->GetLayoutObject() || !key_event) + return false; + + if (ShouldOpenPopupForKeyDownEvent(*key_event)) + return HandlePopupOpenKeyboardEvent(); + + // When using spatial navigation, we want to be able to navigate away + // from the select element when the user hits any of the arrow keys, + // instead of changing the selection. + if (IsSpatialNavigationEnabled(select_->GetDocument().GetFrame())) { + if (!snav_arrow_key_selection_) + return false; + } + + // The key handling below shouldn't be used for non spatial navigation + // mode Mac + if (LayoutTheme::GetTheme().PopsMenuByArrowKeys() && + !IsSpatialNavigationEnabled(select_->GetDocument().GetFrame())) + return false; + + int ignore_modifiers = WebInputEvent::kShiftKey | + WebInputEvent::kControlKey | WebInputEvent::kAltKey | + WebInputEvent::kMetaKey; + if (key_event->GetModifiers() & ignore_modifiers) + return false; + + const String& key = key_event->key(); + bool handled = true; + HTMLOptionElement* option = select_->SelectedOption(); + int list_index = option ? option->ListIndex() : -1; + + if (key == "ArrowDown" || key == "ArrowRight") { + option = NextValidOption(list_index, kSkipForwards, 1); + } else if (key == "ArrowUp" || key == "ArrowLeft") { + option = NextValidOption(list_index, kSkipBackwards, 1); + } else if (key == "PageDown") { + option = NextValidOption(list_index, kSkipForwards, 3); + } else if (key == "PageUp") { + option = NextValidOption(list_index, kSkipBackwards, 3); + } else if (key == "Home") { + option = FirstSelectableOption(); + } else if (key == "End") { + option = LastSelectableOption(); + } else { + handled = false; + } + + if (handled && option) { + select_->SelectOption( + option, HTMLSelectElement::kDeselectOtherOptionsFlag | + HTMLSelectElement::kMakeOptionDirtyFlag | + HTMLSelectElement::kDispatchInputAndChangeEventFlag); + } + return handled; + } + + if (event.type() == event_type_names::kKeypress) { + if (!select_->GetLayoutObject() || !key_event) + return false; + + int key_code = key_event->keyCode(); + if (key_code == ' ' && + IsSpatialNavigationEnabled(select_->GetDocument().GetFrame())) { + // Use space to toggle arrow key handling for selection change or + // spatial navigation. + snav_arrow_key_selection_ = !snav_arrow_key_selection_; + return true; + } + + if (ShouldOpenPopupForKeyPressEvent(*key_event)) + return HandlePopupOpenKeyboardEvent(); + + if (!LayoutTheme::GetTheme().PopsMenuByReturnKey() && key_code == '\r') { + if (HTMLFormElement* form = select_->Form()) + form->SubmitImplicitly(event, false); + DispatchEventsIfSelectedOptionChanged(); + return true; + } + return false; + } + + const auto* mouse_event = DynamicTo<MouseEvent>(event); + if (event.type() == event_type_names::kMousedown && mouse_event && + mouse_event->button() == + static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { + InputDeviceCapabilities* source_capabilities = + select_->GetDocument() + .domWindow() + ->GetInputDeviceCapabilities() + ->FiresTouchEvents(mouse_event->FromTouch()); + select_->focus(FocusParams(SelectionBehaviorOnFocus::kRestore, + mojom::blink::FocusType::kNone, + source_capabilities)); + if (select_->GetLayoutObject() && !will_be_destroyed_ && + !select_->IsDisabledFormControl()) { + if (PopupIsVisible()) { + HidePopup(); + } else { + // Save the selection so it can be compared to the new selection + // when we call onChange during selectOption, which gets called + // from selectOptionByPopup, which gets called after the user + // makes a selection from the menu. + SaveLastSelection(); + // TODO(lanwei): Will check if we need to add + // InputDeviceCapabilities here when select menu list gets + // focus, see https://crbug.com/476530. + ShowPopup(); + } + } + return true; + } + return false; +} + +bool MenuListSelectType::ShouldOpenPopupForKeyDownEvent( + const KeyboardEvent& event) { + const String& key = event.key(); + LayoutTheme& layout_theme = LayoutTheme::GetTheme(); + + if (IsSpatialNavigationEnabled(select_->GetDocument().GetFrame())) + return false; + + return ((layout_theme.PopsMenuByArrowKeys() && + (key == "ArrowDown" || key == "ArrowUp")) || + (layout_theme.PopsMenuByAltDownUpOrF4Key() && + (key == "ArrowDown" || key == "ArrowUp") && event.altKey()) || + (layout_theme.PopsMenuByAltDownUpOrF4Key() && + (!event.altKey() && !event.ctrlKey() && key == "F4"))); +} + +bool MenuListSelectType::ShouldOpenPopupForKeyPressEvent( + const KeyboardEvent& event) { + LayoutTheme& layout_theme = LayoutTheme::GetTheme(); + int key_code = event.keyCode(); + + return ((layout_theme.PopsMenuBySpaceKey() && key_code == ' ' && + !select_->type_ahead_.HasActiveSession(event)) || + (layout_theme.PopsMenuByReturnKey() && key_code == '\r')); +} + +bool MenuListSelectType::HandlePopupOpenKeyboardEvent() { + select_->focus(); + // Calling focus() may cause us to lose our LayoutObject. Return true so + // that our caller doesn't process the event further, but don't set + // the event as handled. + if (!select_->GetLayoutObject() || will_be_destroyed_ || + select_->IsDisabledFormControl()) + return false; + // Save the selection so it can be compared to the new selection when + // dispatching change events during SelectOption, which gets called from + // SelectOptionByPopup, which gets called after the user makes a selection + // from the menu. + SaveLastSelection(); + ShowPopup(); + return true; +} + +void MenuListSelectType::ShowPopup() { + if (PopupIsVisible()) + return; + Document& document = select_->GetDocument(); + if (document.GetPage()->GetChromeClient().HasOpenedPopup()) + return; + if (!select_->GetLayoutObject()) + return; + if (select_->VisibleBoundsInVisualViewport().IsEmpty()) + return; + + if (!popup_) { + popup_ = document.GetPage()->GetChromeClient().OpenPopupMenu( + *document.GetFrame(), *select_); + } + if (!popup_) + return; + + SetPopupIsVisible(true); + ObserveTreeMutation(); + + popup_->Show(); + if (AXObjectCache* cache = document.ExistingAXObjectCache()) + cache->DidShowMenuListPopup(select_->GetLayoutObject()); +} + +void MenuListSelectType::HidePopup() { + if (popup_) + popup_->Hide(); +} + +void MenuListSelectType::PopupDidHide() { + SetPopupIsVisible(false); + UnobserveTreeMutation(); + if (AXObjectCache* cache = select_->GetDocument().ExistingAXObjectCache()) { + if (auto* layout_object = select_->GetLayoutObject()) + cache->DidHideMenuListPopup(layout_object); + } +} + +bool MenuListSelectType::PopupIsVisible() const { + return popup_is_visible_; +} + +void MenuListSelectType::SetPopupIsVisible(bool popup_is_visible) { + popup_is_visible_ = popup_is_visible; + if (!::features::IsFormControlsRefreshEnabled()) + return; + if (auto* layout_object = select_->GetLayoutObject()) { + // Invalidate paint to ensure that the focus ring is updated. + layout_object->SetShouldDoFullPaintInvalidation(); + } +} + +PopupMenu* MenuListSelectType::PopupForTesting() const { + return popup_.Get(); +} + +void MenuListSelectType::DidSelectOption( + HTMLOptionElement* element, + HTMLSelectElement::SelectOptionFlags flags, + bool should_update_popup) { + // Need to update last_on_change_option_ before UpdateFromElement(). + const bool should_dispatch_events = + (flags & HTMLSelectElement::kDispatchInputAndChangeEventFlag) && + select_->last_on_change_option_ != element; + select_->last_on_change_option_ = element; + + UpdateTextStyleAndContent(); + // PopupMenu::UpdateFromElement() posts an O(N) task. + if (PopupIsVisible() && should_update_popup) + popup_->UpdateFromElement(PopupMenu::kBySelectionChange); + + SelectType::DidSelectOption(element, flags, should_update_popup); + + if (should_dispatch_events) { + select_->DispatchInputEvent(); + select_->DispatchChangeEvent(); + } + if (select_->GetLayoutObject()) { + // Need to check will_be_destroyed_ because event handlers might + // disassociate |this| and select_. + if (!will_be_destroyed_) { + // DidUpdateActiveOption() is O(N) because of HTMLOptionElement::index(). + DidUpdateActiveOption(element); + } + } +} + +void MenuListSelectType::DispatchEventsIfSelectedOptionChanged() { + HTMLOptionElement* selected_option = select_->SelectedOption(); + if (select_->last_on_change_option_.Get() != selected_option) { + select_->last_on_change_option_ = selected_option; + select_->DispatchInputEvent(); + select_->DispatchChangeEvent(); + } +} + +void MenuListSelectType::DidBlur() { + // We only need to fire change events here for menu lists, because we fire + // change events for list boxes whenever the selection change is actually + // made. This matches other browsers' behavior. + DispatchEventsIfSelectedOptionChanged(); + if (PopupIsVisible()) + HidePopup(); +} + +void MenuListSelectType::DidSetSuggestedOption(HTMLOptionElement*) { + UpdateTextStyleAndContent(); + if (PopupIsVisible()) + popup_->UpdateFromElement(PopupMenu::kBySelectionChange); +} + +void MenuListSelectType::SaveLastSelection() { + select_->last_on_change_option_ = select_->SelectedOption(); +} + +void MenuListSelectType::DidDetachLayoutTree() { + if (popup_) + popup_->DisconnectClient(); + SetPopupIsVisible(false); + popup_ = nullptr; + UnobserveTreeMutation(); +} + +void MenuListSelectType::DidRecalcStyle(const StyleRecalcChange change) { + if (change.ReattachLayoutTree()) + return; + UpdateTextStyle(); + if (PopupIsVisible()) + popup_->UpdateFromElement(PopupMenu::kByStyleChange); +} + +String MenuListSelectType::UpdateTextStyleInternal() { + HTMLOptionElement* option = OptionToBeShown(); + String text = g_empty_string; + const ComputedStyle* option_style = nullptr; + + if (select_->IsMultiple()) { + unsigned selected_count = 0; + HTMLOptionElement* selected_option_element = nullptr; + for (auto* const option : select_->GetOptionList()) { + if (option->Selected()) { + if (++selected_count == 1) + selected_option_element = option; + } + } + + if (selected_count == 1) { + text = selected_option_element->TextIndentedToRespectGroupLabel(); + option_style = selected_option_element->GetComputedStyle(); + } else { + Locale& locale = select_->GetLocale(); + String localized_number_string = + locale.ConvertToLocalizedNumber(String::Number(selected_count)); + text = locale.QueryString(IDS_FORM_SELECT_MENU_LIST_TEXT, + localized_number_string); + DCHECK(!option_style); + } + } else { + if (option) { + text = option->TextIndentedToRespectGroupLabel(); + option_style = option->GetComputedStyle(); + } + } + option_style_ = option_style; + + auto& inner_element = select_->InnerElement(); + const ComputedStyle* inner_style = inner_element.GetComputedStyle(); + if (inner_style && option_style && + ((option_style->Direction() != inner_style->Direction() || + option_style->GetUnicodeBidi() != inner_style->GetUnicodeBidi()))) { + scoped_refptr<ComputedStyle> cloned_style = + ComputedStyle::Clone(*inner_style); + cloned_style->SetDirection(option_style->Direction()); + cloned_style->SetUnicodeBidi(option_style->GetUnicodeBidi()); + if (auto* inner_layout = inner_element.GetLayoutObject()) { + inner_layout->SetModifiedStyleOutsideStyleRecalc( + std::move(cloned_style), LayoutObject::ApplyStyleChanges::kYes); + } else { + inner_element.SetComputedStyle(std::move(cloned_style)); + } + } + if (select_->GetLayoutObject()) + DidUpdateActiveOption(option); + + return text.StripWhiteSpace(); +} + +void MenuListSelectType::UpdateTextStyleAndContent() { + select_->InnerElement().firstChild()->setNodeValue(UpdateTextStyleInternal()); + if (auto* box = select_->GetLayoutBox()) { + if (auto* cache = select_->GetDocument().ExistingAXObjectCache()) + cache->TextChanged(box); + } +} + +void MenuListSelectType::DidUpdateActiveOption(HTMLOptionElement* option) { + Document& document = select_->GetDocument(); + if (!document.ExistingAXObjectCache()) + return; + + int option_index = option ? option->index() : -1; + if (ax_menulist_last_active_index_ == option_index) + return; + ax_menulist_last_active_index_ = option_index; + + // We skip sending accessiblity notifications for the very first option, + // otherwise we get extra focus and select events that are undesired. + if (!has_updated_menulist_active_option_) { + has_updated_menulist_active_option_ = true; + return; + } + + document.ExistingAXObjectCache()->HandleUpdateActiveMenuOption( + select_->GetLayoutObject(), option_index); +} + +HTMLOptionElement* MenuListSelectType::OptionToBeShown() const { + if (auto* option = + select_->OptionAtListIndex(select_->index_to_select_on_cancel_)) + return option; + if (select_->suggested_option_) + return select_->suggested_option_; + // TODO(tkent): We should not call OptionToBeShown() in IsMultiple() case. + if (select_->IsMultiple()) + return select_->SelectedOption(); + DCHECK_EQ(select_->SelectedOption(), select_->last_on_change_option_); + return select_->last_on_change_option_; +} + +void MenuListSelectType::MaximumOptionWidthMightBeChanged() const { + if (LayoutObject* layout_object = select_->GetLayoutObject()) { + layout_object->SetNeedsLayoutAndIntrinsicWidthsRecalc( + layout_invalidation_reason::kMenuOptionsChanged); + } +} + +// PopupUpdater notifies updates of the specified SELECT element subtree to +// a PopupMenu object. +class PopupUpdater : public MutationObserver::Delegate { + public: + explicit PopupUpdater(MenuListSelectType& select_type, + HTMLSelectElement& select) + : select_type_(select_type), + select_(select), + observer_(MutationObserver::Create(this)) { + MutationObserverInit* init = MutationObserverInit::Create(); + init->setAttributeOldValue(true); + init->setAttributes(true); + // Observe only attributes which affect popup content. + init->setAttributeFilter({"disabled", "label", "selected", "value"}); + init->setCharacterData(true); + init->setCharacterDataOldValue(true); + init->setChildList(true); + init->setSubtree(true); + observer_->observe(select_, init, ASSERT_NO_EXCEPTION); + } + + ExecutionContext* GetExecutionContext() const override { + return select_->GetExecutionContext(); + } + + void Deliver(const MutationRecordVector& records, + MutationObserver&) override { + // We disconnect the MutationObserver when a popup is closed. However + // MutationObserver can call back after disconnection. + if (!select_type_->PopupIsVisible()) + return; + for (const auto& record : records) { + if (record->type() == "attributes") { + const auto& element = *To<Element>(record->target()); + if (record->oldValue() == element.getAttribute(record->attributeName())) + continue; + } else if (record->type() == "characterData") { + if (record->oldValue() == record->target()->nodeValue()) + continue; + } + select_type_->DidMutateSubtree(); + return; + } + } + + void Dispose() { observer_->disconnect(); } + + void Trace(Visitor* visitor) override { + visitor->Trace(select_type_); + visitor->Trace(select_); + visitor->Trace(observer_); + MutationObserver::Delegate::Trace(visitor); + } + + private: + Member<MenuListSelectType> select_type_; + Member<HTMLSelectElement> select_; + Member<MutationObserver> observer_; +}; + +void MenuListSelectType::ObserveTreeMutation() { + DCHECK(!popup_updater_); + popup_updater_ = MakeGarbageCollected<PopupUpdater>(*this, *select_); +} + +void MenuListSelectType::UnobserveTreeMutation() { + if (!popup_updater_) + return; + popup_updater_->Dispose(); + popup_updater_ = nullptr; +} + +void MenuListSelectType::DidMutateSubtree() { + DCHECK(PopupIsVisible()); + DCHECK(popup_); + popup_->UpdateFromElement(PopupMenu::kByDOMChange); +} + +// ============================================================================ + +class ListBoxSelectType final : public SelectType { + public: + explicit ListBoxSelectType(HTMLSelectElement& select) : SelectType(select) {} + bool DefaultEventHandler(const Event& event) override; + void DidBlur() override; + void DidSetSuggestedOption(HTMLOptionElement* option) override; + void SaveLastSelection() override; + void SelectAll() override; + void SaveListboxActiveSelection() override; + void HandleMouseRelease() override; + void ListBoxOnChange() override; + void ClearLastOnChangeSelection() override; + + private: + HTMLOptionElement* NextSelectableOptionPageAway(HTMLOptionElement*, + SkipDirection) const; + // Update :-internal-multi-select-focus state of selected OPTIONs. + void UpdateMultiSelectFocus(); + void ToggleSelection(HTMLOptionElement& option); + enum class SelectionMode { + kDeselectOthers, + kRange, + kNotChangeOthers, + }; + void UpdateSelectedState(HTMLOptionElement* clicked_option, + SelectionMode mode); + void UpdateListBoxSelection(bool deselect_other_options, bool scroll = true); + + Vector<bool> cached_state_for_active_selection_; + Vector<bool> last_on_change_selection_; + bool is_in_non_contiguous_selection_ = false; + bool active_selection_state_ = false; +}; + +bool ListBoxSelectType::DefaultEventHandler(const Event& event) { + const auto* mouse_event = DynamicTo<MouseEvent>(event); + const auto* gesture_event = DynamicTo<GestureEvent>(event); + if (event.type() == event_type_names::kGesturetap && gesture_event) { + select_->focus(); + // Calling focus() may cause us to lose our layoutObject or change the + // layoutObject type, in which case do not want to handle the event. + if (!select_->GetLayoutObject() || will_be_destroyed_) + return false; + + // Convert to coords relative to the list box if needed. + if (HTMLOptionElement* option = EventTargetOption(*gesture_event)) { + if (!select_->IsDisabledFormControl()) { + UpdateSelectedState(option, gesture_event->shiftKey() + ? SelectionMode::kRange + : SelectionMode::kNotChangeOthers); + ListBoxOnChange(); + } + return true; + } + return false; + } + + if (event.type() == event_type_names::kMousedown && mouse_event && + mouse_event->button() == + static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { + select_->focus(); + // Calling focus() may cause us to lose our layoutObject, in which case + // do not want to handle the event. + if (!select_->GetLayoutObject() || will_be_destroyed_ || + select_->IsDisabledFormControl()) + return false; + + // Convert to coords relative to the list box if needed. + if (HTMLOptionElement* option = EventTargetOption(*mouse_event)) { + if (!option->IsDisabledFormControl()) { +#if defined(OS_MACOSX) + const bool meta_or_ctrl = mouse_event->metaKey(); +#else + const bool meta_or_ctrl = mouse_event->ctrlKey(); +#endif + UpdateSelectedState(option, mouse_event->shiftKey() + ? SelectionMode::kRange + : meta_or_ctrl + ? SelectionMode::kNotChangeOthers + : SelectionMode::kDeselectOthers); + } + if (LocalFrame* frame = select_->GetDocument().GetFrame()) + frame->GetEventHandler().SetMouseDownMayStartAutoscroll(); + + return true; + } + return false; + } + + if (event.type() == event_type_names::kMousemove && mouse_event) { + if (mouse_event->button() != + static_cast<int16_t>(WebPointerProperties::Button::kLeft) || + !mouse_event->ButtonDown()) + return false; + + if (auto* layout_object = select_->GetLayoutObject()) { + layout_object->GetFrameView()->UpdateAllLifecyclePhasesExceptPaint( + DocumentUpdateReason::kScroll); + + if (Page* page = select_->GetDocument().GetPage()) { + page->GetAutoscrollController().StartAutoscrollForSelection( + layout_object); + } + } + // Mousedown didn't happen in this element. + if (last_on_change_selection_.IsEmpty()) + return false; + + if (HTMLOptionElement* option = EventTargetOption(*mouse_event)) { + if (!select_->IsDisabledFormControl()) { + if (select_->is_multiple_) { + // Only extend selection if there is something selected. + if (!select_->active_selection_anchor_) + return false; + + select_->SetActiveSelectionEnd(option); + UpdateListBoxSelection(false); + } else { + select_->SetActiveSelectionAnchor(option); + select_->SetActiveSelectionEnd(option); + UpdateListBoxSelection(true); + } + } + } + return false; + } + + if (event.type() == event_type_names::kMouseup && mouse_event && + mouse_event->button() == + static_cast<int16_t>(WebPointerProperties::Button::kLeft) && + select_->GetLayoutObject()) { + auto* page = select_->GetDocument().GetPage(); + if (page && page->GetAutoscrollController().AutoscrollInProgressFor( + select_->GetLayoutBox())) + page->GetAutoscrollController().StopAutoscroll(); + else + HandleMouseRelease(); + return false; + } + + if (event.type() == event_type_names::kKeydown) { + const auto* keyboard_event = DynamicTo<KeyboardEvent>(event); + if (!keyboard_event) + return false; + const String& key = keyboard_event->key(); + + bool handled = false; + HTMLOptionElement* end_option = nullptr; + if (!select_->active_selection_end_) { + // Initialize the end index + if (key == "ArrowDown" || key == "PageDown") { + HTMLOptionElement* start_option = select_->LastSelectedOption(); + handled = true; + if (key == "ArrowDown") { + end_option = NextSelectableOption(start_option); + } else { + end_option = + NextSelectableOptionPageAway(start_option, kSkipForwards); + } + } else if (key == "ArrowUp" || key == "PageUp") { + HTMLOptionElement* start_option = select_->SelectedOption(); + handled = true; + if (key == "ArrowUp") { + end_option = PreviousSelectableOption(start_option); + } else { + end_option = + NextSelectableOptionPageAway(start_option, kSkipBackwards); + } + } + } else { + // Set the end index based on the current end index. + if (key == "ArrowDown") { + end_option = NextSelectableOption(select_->active_selection_end_.Get()); + handled = true; + } else if (key == "ArrowUp") { + end_option = + PreviousSelectableOption(select_->active_selection_end_.Get()); + handled = true; + } else if (key == "PageDown") { + end_option = NextSelectableOptionPageAway( + select_->active_selection_end_.Get(), kSkipForwards); + handled = true; + } else if (key == "PageUp") { + end_option = NextSelectableOptionPageAway( + select_->active_selection_end_.Get(), kSkipBackwards); + handled = true; + } + } + if (key == "Home") { + end_option = FirstSelectableOption(); + handled = true; + } else if (key == "End") { + end_option = LastSelectableOption(); + handled = true; + } + + if (IsSpatialNavigationEnabled(select_->GetDocument().GetFrame())) { + // Check if the selection moves to the boundary. + if (key == "ArrowLeft" || key == "ArrowRight" || + ((key == "ArrowDown" || key == "ArrowUp") && + end_option == select_->active_selection_end_)) + return false; + } + + bool is_control_key = false; +#if defined(OS_MACOSX) + is_control_key = keyboard_event->metaKey(); +#else + is_control_key = keyboard_event->ctrlKey(); +#endif + + if (select_->is_multiple_ && keyboard_event->keyCode() == ' ' && + is_control_key && select_->active_selection_end_) { + // Use ctrl+space to toggle selection change. + ToggleSelection(*select_->active_selection_end_); + return true; + } + + if (end_option && handled) { + // Save the selection so it can be compared to the new selection + // when dispatching change events immediately after making the new + // selection. + SaveLastSelection(); + + select_->SetActiveSelectionEnd(end_option); + + is_in_non_contiguous_selection_ = select_->is_multiple_ && is_control_key; + bool select_new_item = + !select_->is_multiple_ || keyboard_event->shiftKey() || + (!IsSpatialNavigationEnabled(select_->GetDocument().GetFrame()) && + !is_in_non_contiguous_selection_); + if (select_new_item) + active_selection_state_ = true; + // If the anchor is uninitialized, or if we're going to deselect all + // other options, then set the anchor index equal to the end index. + bool deselect_others = !select_->is_multiple_ || + (!keyboard_event->shiftKey() && select_new_item); + if (!select_->active_selection_anchor_ || deselect_others) { + if (deselect_others) + select_->DeselectItemsWithoutValidation(); + select_->SetActiveSelectionAnchor(select_->active_selection_end_.Get()); + } + + select_->ScrollToOption(end_option); + if (select_new_item || is_in_non_contiguous_selection_) { + if (select_new_item) { + UpdateListBoxSelection(deselect_others); + ListBoxOnChange(); + } + UpdateMultiSelectFocus(); + } else { + select_->ScrollToSelection(); + } + + return true; + } + return false; + } + + if (event.type() == event_type_names::kKeypress) { + auto* keyboard_event = DynamicTo<KeyboardEvent>(event); + if (!keyboard_event) + return false; + int key_code = keyboard_event->keyCode(); + + if (key_code == '\r') { + if (HTMLFormElement* form = select_->Form()) + form->SubmitImplicitly(event, false); + return true; + } else if (select_->is_multiple_ && key_code == ' ' && + (IsSpatialNavigationEnabled(select_->GetDocument().GetFrame()) || + is_in_non_contiguous_selection_)) { + HTMLOptionElement* option = select_->active_selection_end_; + // If there's no active selection, + // act as if "ArrowDown" had been pressed. + if (!option) + option = NextSelectableOption(select_->LastSelectedOption()); + if (option) { + // Use space to toggle selection change. + ToggleSelection(*option); + return true; + } + } + return false; + } + return false; +} + +void ListBoxSelectType::DidBlur() { + ClearLastOnChangeSelection(); +} + +void ListBoxSelectType::DidSetSuggestedOption(HTMLOptionElement* option) { + if (select_->GetLayoutObject()) + select_->ScrollToOption(option); +} + +void ListBoxSelectType::SaveLastSelection() { + last_on_change_selection_.clear(); + for (auto& element : select_->GetListItems()) { + auto* option_element = DynamicTo<HTMLOptionElement>(element.Get()); + last_on_change_selection_.push_back(option_element && + option_element->Selected()); + } +} + +void ListBoxSelectType::UpdateMultiSelectFocus() { + if (!select_->is_multiple_) + return; + + for (auto* const option : select_->GetOptionList()) { + if (option->IsDisabledFormControl() || !option->GetLayoutObject()) + continue; + bool is_focused = (option == select_->active_selection_end_) && + is_in_non_contiguous_selection_; + option->SetMultiSelectFocusedState(is_focused); + } + select_->ScrollToSelection(); +} + +void ListBoxSelectType::SelectAll() { + if (!select_->GetLayoutObject() || !select_->is_multiple_) + return; + + // Save the selection so it can be compared to the new selectAll selection + // when dispatching change events. + SaveLastSelection(); + + active_selection_state_ = true; + select_->SetActiveSelectionAnchor(NextSelectableOption(nullptr)); + select_->SetActiveSelectionEnd(PreviousSelectableOption(nullptr)); + + UpdateListBoxSelection(false, false); + ListBoxOnChange(); + select_->SetNeedsValidityCheck(); +} + +// Returns the index of the next valid item one page away from |start_option| +// in direction |direction|. +HTMLOptionElement* ListBoxSelectType::NextSelectableOptionPageAway( + HTMLOptionElement* start_option, + SkipDirection direction) const { + const auto& items = select_->GetListItems(); + // -1 so we still show context. + int page_size = select_->ListBoxSize() - 1; + + // One page away, but not outside valid bounds. + // If there is a valid option item one page away, the index is chosen. + // If there is no exact one page away valid option, returns start_index or + // the most far index. + int start_index = start_option ? start_option->ListIndex() : -1; + int edge_index = (direction == kSkipForwards) ? 0 : (items.size() - 1); + int skip_amount = + page_size + + ((direction == kSkipForwards) ? start_index : (edge_index - start_index)); + return NextValidOption(edge_index, direction, skip_amount); +} + +void ListBoxSelectType::ToggleSelection(HTMLOptionElement& option) { + active_selection_state_ = !active_selection_state_; + UpdateSelectedState(&option, SelectionMode::kNotChangeOthers); + ListBoxOnChange(); +} + +void ListBoxSelectType::UpdateSelectedState(HTMLOptionElement* clicked_option, + SelectionMode mode) { + DCHECK(clicked_option); + // Save the selection so it can be compared to the new selection when + // dispatching change events during mouseup, or after autoscroll finishes. + SaveLastSelection(); + + active_selection_state_ = true; + + if (!select_->is_multiple_) + mode = SelectionMode::kDeselectOthers; + + // Keep track of whether an active selection (like during drag selection), + // should select or deselect. + if (clicked_option->Selected() && mode == SelectionMode::kNotChangeOthers) { + active_selection_state_ = false; + clicked_option->SetSelectedState(false); + clicked_option->SetDirty(true); + } + + // If we're not in any special multiple selection mode, then deselect all + // other items, excluding the clicked OPTION. If no option was clicked, then + // this will deselect all items in the list. + if (mode == SelectionMode::kDeselectOthers) + select_->DeselectItemsWithoutValidation(clicked_option); + + // If the anchor hasn't been set, and we're doing kDeselectOthers or kRange, + // then initialize the anchor to the first selected OPTION. + if (!select_->active_selection_anchor_ && + mode != SelectionMode::kNotChangeOthers) + select_->SetActiveSelectionAnchor(select_->SelectedOption()); + + // Set the selection state of the clicked OPTION. + if (!clicked_option->IsDisabledFormControl()) { + clicked_option->SetSelectedState(true); + clicked_option->SetDirty(true); + } + + // If there was no selectedIndex() for the previous initialization, or if + // we're doing kDeselectOthers, or kNotChangeOthers (using cmd or ctrl), + // then initialize the anchor OPTION to the clicked OPTION. + if (!select_->active_selection_anchor_ || mode != SelectionMode::kRange) + select_->SetActiveSelectionAnchor(clicked_option); + + select_->SetActiveSelectionEnd(clicked_option); + UpdateListBoxSelection(mode != SelectionMode::kNotChangeOthers); +} + +void ListBoxSelectType::UpdateListBoxSelection(bool deselect_other_options, + bool scroll) { + DCHECK(select_->GetLayoutObject()); + HTMLOptionElement* const anchor_option = select_->active_selection_anchor_; + HTMLOptionElement* const end_option = select_->active_selection_end_; + const int anchor_index = anchor_option ? anchor_option->index() : -1; + const int end_index = end_option ? end_option->index() : -1; + const int start = std::min(anchor_index, end_index); + const int end = std::max(anchor_index, end_index); + + int i = 0; + for (auto* const option : select_->GetOptionList()) { + if (option->IsDisabledFormControl() || !option->GetLayoutObject()) { + ++i; + continue; + } + if (i >= start && i <= end) { + option->SetSelectedState(active_selection_state_); + option->SetDirty(true); + } else if (deselect_other_options || + i >= static_cast<int>( + cached_state_for_active_selection_.size())) { + option->SetSelectedState(false); + option->SetDirty(true); + } else { + option->SetSelectedState(cached_state_for_active_selection_[i]); + } + ++i; + } + + UpdateMultiSelectFocus(); + select_->SetNeedsValidityCheck(); + if (scroll) + select_->ScrollToSelection(); + select_->NotifyFormStateChanged(); +} + +void ListBoxSelectType::SaveListboxActiveSelection() { + // Cache the selection state so we can restore the old selection as the new + // selection pivots around this anchor index. + // Example: + // 1. Press the mouse button on the second OPTION + // active_selection_anchor_ points the second OPTION. + // 2. Drag the mouse pointer onto the fifth OPTION + // active_selection_end_ points the fifth OPTION, OPTIONs at 1-4 indices + // are selected. + // 3. Drag the mouse pointer onto the fourth OPTION + // active_selection_end_ points the fourth OPTION, OPTIONs at 1-3 indices + // are selected. + // UpdateListBoxSelection needs to clear selection of the fifth OPTION. + cached_state_for_active_selection_.resize(0); + for (auto* const option : select_->GetOptionList()) { + cached_state_for_active_selection_.push_back(option->Selected()); + } +} + +void ListBoxSelectType::HandleMouseRelease() { + // We didn't start this click/drag on any options. + if (last_on_change_selection_.IsEmpty()) + return; + ListBoxOnChange(); +} + +void ListBoxSelectType::ListBoxOnChange() { + const auto& items = select_->GetListItems(); + + // If the cached selection list is empty, or the size has changed, then fire + // 'change' event, and return early. + // FIXME: Why? This looks unreasonable. + if (last_on_change_selection_.IsEmpty() || + last_on_change_selection_.size() != items.size()) { + select_->DispatchChangeEvent(); + return; + } + + // Update last_on_change_selection_ and fire a 'change' event. + bool fire_on_change = false; + for (unsigned i = 0; i < items.size(); ++i) { + HTMLElement* element = items[i]; + auto* option_element = DynamicTo<HTMLOptionElement>(element); + bool selected = option_element && option_element->Selected(); + if (selected != last_on_change_selection_[i]) + fire_on_change = true; + last_on_change_selection_[i] = selected; + } + + if (fire_on_change) { + select_->DispatchInputEvent(); + select_->DispatchChangeEvent(); + } +} + +void ListBoxSelectType::ClearLastOnChangeSelection() { + last_on_change_selection_.clear(); +} + +// ============================================================================ + +SelectType::SelectType(HTMLSelectElement& select) : select_(select) {} + +SelectType* SelectType::Create(HTMLSelectElement& select) { + if (select.UsesMenuList()) + return MakeGarbageCollected<MenuListSelectType>(select); + else + return MakeGarbageCollected<ListBoxSelectType>(select); +} + +void SelectType::WillBeDestroyed() { + will_be_destroyed_ = true; +} + +void SelectType::Trace(Visitor* visitor) { + visitor->Trace(select_); +} + +void SelectType::DidSelectOption(HTMLOptionElement*, + HTMLSelectElement::SelectOptionFlags, + bool) { + select_->ScrollToSelection(); + select_->SetNeedsValidityCheck(); +} + +void SelectType::DidDetachLayoutTree() {} + +void SelectType::DidRecalcStyle(const StyleRecalcChange) {} + +void SelectType::UpdateTextStyle() {} + +void SelectType::UpdateTextStyleAndContent() {} + +HTMLOptionElement* SelectType::OptionToBeShown() const { + NOTREACHED(); + return nullptr; +} + +const ComputedStyle* SelectType::OptionStyle() const { + NOTREACHED(); + return nullptr; +} + +void SelectType::MaximumOptionWidthMightBeChanged() const {} + +void SelectType::SelectAll() { + NOTREACHED(); +} + +void SelectType::SaveListboxActiveSelection() {} + +void SelectType::HandleMouseRelease() {} + +void SelectType::ListBoxOnChange() {} + +void SelectType::ClearLastOnChangeSelection() {} + +void SelectType::ShowPopup() { + NOTREACHED(); +} + +void SelectType::HidePopup() { + NOTREACHED(); +} + +void SelectType::PopupDidHide() { + NOTREACHED(); +} + +bool SelectType::PopupIsVisible() const { + return false; +} + +PopupMenu* SelectType::PopupForTesting() const { + NOTREACHED(); + return nullptr; +} + +// Returns the 1st valid OPTION |skip| items from |list_index| in direction +// |direction| if there is one. +// Otherwise, it returns the valid OPTION closest to that boundary which is past +// |list_index| if there is one. +// Otherwise, it returns nullptr. +// Valid means that it is enabled and visible. +HTMLOptionElement* SelectType::NextValidOption(int list_index, + SkipDirection direction, + int skip) const { + DCHECK(direction == kSkipBackwards || direction == kSkipForwards); + const auto& list_items = select_->GetListItems(); + HTMLOptionElement* last_good_option = nullptr; + int size = list_items.size(); + for (list_index += direction; list_index >= 0 && list_index < size; + list_index += direction) { + --skip; + HTMLElement* element = list_items[list_index]; + auto* option_element = DynamicTo<HTMLOptionElement>(element); + if (!option_element) + continue; + if (option_element->IsDisplayNone()) + continue; + if (element->IsDisabledFormControl()) + continue; + if (!select_->UsesMenuList() && !element->GetLayoutObject()) + continue; + last_good_option = option_element; + if (skip <= 0) + break; + } + return last_good_option; +} + +HTMLOptionElement* SelectType::NextSelectableOption( + HTMLOptionElement* start_option) const { + return NextValidOption(start_option ? start_option->ListIndex() : -1, + kSkipForwards, 1); +} + +HTMLOptionElement* SelectType::PreviousSelectableOption( + HTMLOptionElement* start_option) const { + return NextValidOption( + start_option ? start_option->ListIndex() : select_->GetListItems().size(), + kSkipBackwards, 1); +} + +HTMLOptionElement* SelectType::FirstSelectableOption() const { + return NextValidOption(-1, kSkipForwards, 1); +} + +HTMLOptionElement* SelectType::LastSelectableOption() const { + return NextValidOption(select_->GetListItems().size(), kSkipBackwards, 1); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/select_type.h b/chromium/third_party/blink/renderer/core/html/forms/select_type.h new file mode 100644 index 00000000000..2ce4e1bcb56 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/select_type.h @@ -0,0 +1,84 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SELECT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SELECT_TYPE_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +// SelectType class is an abstraction of the MenuList behavior and the ListBox +// behavior of HTMLSelectElement. +class SelectType : public GarbageCollected<SelectType> { + public: + // Creates an instance of a SelectType subclass depending on the current mode + // of |select|. + static SelectType* Create(HTMLSelectElement& select); + void WillBeDestroyed(); + virtual void Trace(Visitor* visitor); + + // Returns true if the event is handled. + virtual bool DefaultEventHandler(const Event& event) = 0; + + virtual void DidSelectOption(HTMLOptionElement* element, + HTMLSelectElement::SelectOptionFlags flags, + bool should_update_popup); + + virtual void DidBlur() = 0; + virtual void DidDetachLayoutTree(); + virtual void DidRecalcStyle(const StyleRecalcChange change); + virtual void DidSetSuggestedOption(HTMLOptionElement* option) = 0; + virtual void SaveLastSelection() = 0; + + // Update style of text in the CSS box on style or selected OPTION change. + virtual void UpdateTextStyle(); + + // Update style of text in the CSS box on style or selected OPTION change, + // and update the text. + virtual void UpdateTextStyleAndContent(); + + virtual HTMLOptionElement* OptionToBeShown() const; + virtual const ComputedStyle* OptionStyle() const; + virtual void MaximumOptionWidthMightBeChanged() const; + + virtual void SelectAll(); + virtual void SaveListboxActiveSelection(); + virtual void HandleMouseRelease(); + virtual void ListBoxOnChange(); + // Clear OPTION selection information saved by SaveLastSelection(). + // This is for ListBoxes. + virtual void ClearLastOnChangeSelection(); + + virtual void ShowPopup(); + virtual void HidePopup(); + virtual void PopupDidHide(); + virtual bool PopupIsVisible() const; + virtual PopupMenu* PopupForTesting() const; + + enum SkipDirection { kSkipBackwards = -1, kSkipForwards = 1 }; + CORE_EXPORT HTMLOptionElement* NextSelectableOption(HTMLOptionElement*) const; + CORE_EXPORT HTMLOptionElement* PreviousSelectableOption( + HTMLOptionElement*) const; + CORE_EXPORT HTMLOptionElement* FirstSelectableOption() const; + CORE_EXPORT HTMLOptionElement* LastSelectableOption() const; + + protected: + explicit SelectType(HTMLSelectElement& select); + HTMLOptionElement* NextValidOption(int list_index, + SkipDirection direction, + int skip) const; + + const Member<HTMLSelectElement> select_; + bool will_be_destroyed_ = false; + + private: + SelectType(const SelectType&) = delete; + SelectType& operator=(const SelectType&) = delete; +}; + +} // namespace blink +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SELECT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc index a1cd68864ae..4c987d13ff6 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc @@ -47,13 +47,10 @@ #include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_slider_container.h" #include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "ui/base/ui_base_features.h" namespace blink { -inline static bool HasVerticalAppearance(HTMLInputElement* input) { - return input->ComputedStyleRef().EffectiveAppearance() == kSliderVerticalPart; -} - SliderThumbElement::SliderThumbElement(Document& document) : HTMLDivElement(document), in_drag_mode_(false) { SetHasCustomStyleCallbacks(); @@ -67,7 +64,7 @@ void SliderThumbElement::SetPositionFromValue() { if (GetLayoutObject()) { GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation( layout_invalidation_reason::kSliderValueChanged); - if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) { + if (features::IsFormControlsRefreshEnabled()) { HTMLInputElement* input(HostInput()); if (input && input->GetLayoutObject()) { // the slider track selected value needs to be updated. @@ -116,7 +113,7 @@ void SliderThumbElement::SetPositionFromPoint(const LayoutPoint& point) { PhysicalOffset point_in_track = track_box->AbsoluteToLocalPoint(PhysicalOffsetToBeNoop(point)); - bool is_vertical = HasVerticalAppearance(input); + const bool is_vertical = !thumb_box->StyleRef().IsHorizontalWritingMode(); bool is_left_to_right_direction = thumb_box->StyleRef().IsLeftToRightDirection(); LayoutUnit track_size; @@ -199,13 +196,13 @@ void SliderThumbElement::StopDragging() { } void SliderThumbElement::DefaultEventHandler(Event& event) { - if (event.IsPointerEvent() && + if (IsA<PointerEvent>(event) && event.type() == event_type_names::kLostpointercapture) { StopDragging(); return; } - if (!event.IsMouseEvent()) { + if (!IsA<MouseEvent>(event)) { HTMLDivElement::DefaultEventHandler(event); return; } @@ -220,7 +217,7 @@ void SliderThumbElement::DefaultEventHandler(Event& event) { return; } - auto& mouse_event = ToMouseEvent(event); + auto& mouse_event = To<MouseEvent>(event); bool is_left_button = mouse_event.button() == static_cast<int16_t>(WebPointerProperties::Button::kLeft); @@ -348,8 +345,8 @@ LayoutObject* SliderContainerElement::CreateLayoutObject(const ComputedStyle&, } void SliderContainerElement::DefaultEventHandler(Event& event) { - if (event.IsTouchEvent()) { - HandleTouchEvent(ToTouchEvent(&event)); + if (auto* touch_event = DynamicTo<TouchEvent>(event)) { + HandleTouchEvent(touch_event); return; } } @@ -430,10 +427,9 @@ bool SliderContainerElement::CanSlide() { } } } - if ((sliding_direction_ == kVertical && - slider_style->EffectiveAppearance() == kSliderHorizontalPart) || - (sliding_direction_ == kHorizontal && - slider_style->EffectiveAppearance() == kSliderVerticalPart)) { + bool is_horizontal = GetComputedStyle()->IsHorizontalWritingMode(); + if ((sliding_direction_ == kVertical && is_horizontal) || + (sliding_direction_ == kHorizontal && !is_horizontal)) { return false; } return true; @@ -486,14 +482,4 @@ void SliderContainerElement::RemoveAllEventListeners() { has_touch_event_handler_ = false; } -scoped_refptr<ComputedStyle> -SliderContainerElement::CustomStyleForLayoutObject() { - HTMLInputElement* input = HostInput(); - DCHECK(input); - scoped_refptr<ComputedStyle> style = OriginalStyleForLayoutObject(); - style->SetFlexDirection(HasVerticalAppearance(input) ? EFlexDirection::kColumn - : EFlexDirection::kRow); - return style; -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h index 82977b7378d..cc4dd4d78f1 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h @@ -104,7 +104,6 @@ class SliderContainerElement final : public HTMLDivElement { private: LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override; - scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() final; const AtomicString& ShadowPseudoId() const override; Direction GetDirection(LayoutPoint&, LayoutPoint&); bool CanSlide(); diff --git a/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc index 7dbfef38a01..24ea89a2682 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc @@ -62,7 +62,8 @@ void SpinButtonElement::DetachLayoutTree(bool performing_reattach) { } void SpinButtonElement::DefaultEventHandler(Event& event) { - if (!event.IsMouseEvent()) { + auto* mouse_event = DynamicTo<MouseEvent>(event); + if (!mouse_event) { if (!event.DefaultHandled()) HTMLDivElement::DefaultEventHandler(event); return; @@ -81,11 +82,10 @@ void SpinButtonElement::DefaultEventHandler(Event& event) { return; } - auto& mouse_event = ToMouseEvent(event); IntPoint local = RoundedIntPoint(box->AbsoluteToLocalFloatPoint( - FloatPoint(mouse_event.AbsoluteLocation()))); - if (mouse_event.type() == event_type_names::kMousedown && - mouse_event.button() == + FloatPoint(mouse_event->AbsoluteLocation()))); + if (mouse_event->type() == event_type_names::kMousedown && + mouse_event->button() == static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { if (box->PixelSnappedBorderBoxRect().Contains(local)) { if (spin_button_owner_) @@ -114,8 +114,8 @@ void SpinButtonElement::DefaultEventHandler(Event& event) { } event.SetDefaultHandled(); } - } else if (mouse_event.type() == event_type_names::kMouseup && - mouse_event.button() == + } else if (mouse_event->type() == event_type_names::kMouseup && + mouse_event->button() == static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { ReleaseCapture(); } else if (event.type() == event_type_names::kMousemove) { @@ -152,7 +152,7 @@ void SpinButtonElement::ForwardEvent(Event& event) { if (!spin_button_owner_->ShouldSpinButtonRespondToWheelEvents()) return; - DoStepAction(ToWheelEvent(event).wheelDeltaY()); + DoStepAction(To<WheelEvent>(event).wheelDeltaY()); event.SetDefaultHandled(); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/step_range.cc b/chromium/third_party/blink/renderer/core/html/forms/step_range.cc index d8ea6ddb19f..b4bcb5d9ebc 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/step_range.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/step_range.cc @@ -34,7 +34,8 @@ StepRange::StepRange() step_(1), step_base_(0), has_step_(false), - has_range_limitations_(false) {} + has_range_limitations_(false), + supports_reversed_range_(false) {} StepRange::StepRange(const StepRange& step_range) = default; @@ -42,6 +43,7 @@ StepRange::StepRange(const Decimal& step_base, const Decimal& minimum, const Decimal& maximum, bool has_range_limitations, + bool supports_reversed_range, const Decimal& step, const StepDescription& step_description) : maximum_(maximum), @@ -50,7 +52,8 @@ StepRange::StepRange(const Decimal& step_base, step_base_(step_base.IsFinite() ? step_base : 1), step_description_(step_description), has_step_(step.IsFinite()), - has_range_limitations_(has_range_limitations) { + has_range_limitations_(has_range_limitations), + supports_reversed_range_(supports_reversed_range) { DCHECK(maximum_.IsFinite()); DCHECK(minimum_.IsFinite()); DCHECK(step_.IsFinite()); @@ -100,7 +103,7 @@ Decimal StepRange::ParseStep(AnyStepHandling any_step_handling, if (step_string.IsEmpty()) return step_description.DefaultValue(); - if (DeprecatedEqualIgnoringCase(step_string, "any")) { + if (EqualIgnoringASCIICase(step_string, "any")) { switch (any_step_handling) { case kRejectAny: return Decimal::Nan(); @@ -183,4 +186,9 @@ Decimal StepRange::StepSnappedMaximum() const { return aligned_maximum; } +// https://html.spec.whatwg.org/C/#has-a-reversed-range +bool StepRange::HasReversedRange() const { + return supports_reversed_range_ && Maximum() < Minimum(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/step_range.h b/chromium/third_party/blink/renderer/core/html/forms/step_range.h index 796a496c438..70f2005da08 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/step_range.h +++ b/chromium/third_party/blink/renderer/core/html/forms/step_range.h @@ -74,6 +74,7 @@ class CORE_EXPORT StepRange { const Decimal& minimum, const Decimal& maximum, bool has_range_limitations, + bool supports_reversed_range, const Decimal& step, const StepDescription&); @@ -85,6 +86,8 @@ class CORE_EXPORT StepRange { Decimal Minimum() const { return minimum_; } // https://html.spec.whatwg.org/C/#have-range-limitations bool HasRangeLimitations() const { return has_range_limitations_; } + // https://html.spec.whatwg.org/C/#has-a-reversed-range + bool HasReversedRange() const; static Decimal ParseStep(AnyStepHandling, const StepDescription&, const String&); @@ -123,6 +126,7 @@ class CORE_EXPORT StepRange { const StepDescription step_description_; const bool has_step_; const bool has_range_limitations_; + const bool supports_reversed_range_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc b/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc index eddc58b9b28..9916c50d8b7 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc @@ -11,7 +11,8 @@ namespace blink { TEST(StepRangeTest, ClampValueWithOutStepMatchedValue) { // <input type=range value=200 min=0 max=100 step=1000> StepRange step_range(Decimal(200), Decimal(0), Decimal(100), true, - Decimal(1000), StepRange::StepDescription()); + /*has_reversed_range=*/false, Decimal(1000), + StepRange::StepDescription()); EXPECT_EQ(Decimal(100), step_range.ClampValue(Decimal(200))); EXPECT_EQ(Decimal(0), step_range.ClampValue(Decimal(-100))); @@ -20,7 +21,8 @@ TEST(StepRangeTest, ClampValueWithOutStepMatchedValue) { TEST(StepRangeTest, StepSnappedMaximum) { // <input type=number value="1110" max=100 step="20"> StepRange step_range(Decimal::FromDouble(1110), Decimal(0), Decimal(100), - true, Decimal(20), StepRange::StepDescription()); + true, /*has_reversed_range=*/false, Decimal(20), + StepRange::StepDescription()); EXPECT_EQ(Decimal(90), step_range.StepSnappedMaximum()); // crbug.com/617809 @@ -28,9 +30,34 @@ TEST(StepRangeTest, StepSnappedMaximum) { // value="8624024784918570374158793713225864658725102756338798521486349461900449498315865014065406918592181034633618363349807887404915072776534917803019477033072906290735591367789665757384135591225430117374220731087966" // min=0 max=100 step="18446744073709551575"> StepRange step_range2(Decimal::FromDouble(8.62402e+207), Decimal(0), - Decimal(100), true, Decimal::FromDouble(1.84467e+19), + Decimal(100), true, /*has_reversed_range=*/false, + Decimal::FromDouble(1.84467e+19), StepRange::StepDescription()); EXPECT_FALSE(step_range2.StepSnappedMaximum().IsFinite()); } +TEST(StepRangeTest, ReversedRange) { + // <input type=time min="23:00" max="01:00"> + StepRange reversed_time_range( + /*step_base=*/Decimal::FromDouble(82800000), + /*minimum=*/Decimal::FromDouble(82800000), + /*maximum=*/Decimal::FromDouble(3600000), + /*has_range_limitations=*/true, + /*supports_reversed_range=*/true, + /*step=*/Decimal::FromDouble(60000), + /*step_description=*/StepRange::StepDescription()); + EXPECT_TRUE(reversed_time_range.HasReversedRange()); + + // <input type=time min="01:00" max="23:00"> + StepRange regular_time_range( + /*step_base=*/Decimal::FromDouble(3600000), + /*minimum=*/Decimal::FromDouble(3600000), + /*maximum=*/Decimal::FromDouble(82800000), + /*has_range_limitations=*/true, + /*supports_reversed_range=*/true, + /*step=*/Decimal::FromDouble(60000), + /*step_description=*/StepRange::StepDescription()); + EXPECT_FALSE(regular_time_range.HasReversedRange()); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_event.cc b/chromium/third_party/blink/renderer/core/html/forms/submit_event.cc new file mode 100644 index 00000000000..04c84a91404 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/submit_event.cc @@ -0,0 +1,32 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/submit_event.h" + +#include "third_party/blink/renderer/bindings/core/v8/v8_submit_event_init.h" +#include "third_party/blink/renderer/core/event_interface_names.h" +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +SubmitEvent::SubmitEvent(const AtomicString& type, + const SubmitEventInit* event_init) + : Event(type, event_init), + submitter_(event_init ? event_init->submitter() : nullptr) {} + +SubmitEvent* SubmitEvent::Create(const AtomicString& type, + const SubmitEventInit* event_init) { + return MakeGarbageCollected<SubmitEvent>(type, event_init); +} + +void SubmitEvent::Trace(Visitor* visitor) { + visitor->Trace(submitter_); + Event::Trace(visitor); +} + +const AtomicString& SubmitEvent::InterfaceName() const { + return event_interface_names::kSubmitEvent; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_event.h b/chromium/third_party/blink/renderer/core/html/forms/submit_event.h new file mode 100644 index 00000000000..2e242d3a791 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/submit_event.h @@ -0,0 +1,33 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_EVENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_EVENT_H_ + +#include "third_party/blink/renderer/core/dom/events/event.h" + +namespace blink { + +class HTMLElement; +class SubmitEventInit; + +class SubmitEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + static SubmitEvent* Create(const AtomicString& type, + const SubmitEventInit* event_init); + SubmitEvent(const AtomicString& type, const SubmitEventInit* event_init); + + void Trace(Visitor* visitor) override; + HTMLElement* submitter() const { return submitter_.Get(); } + const AtomicString& InterfaceName() const override; + + private: + Member<HTMLElement> submitter_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_EVENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_event.idl b/chromium/third_party/blink/renderer/core/html/forms/submit_event.idl new file mode 100644 index 00000000000..06de5b4286c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/submit_event.idl @@ -0,0 +1,12 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://html.spec.whatwg.org/C/#submitevent + +[ + Exposed=Window +] interface SubmitEvent : Event { + constructor(DOMString type, optional SubmitEventInit eventInitDict = {}); + readonly attribute HTMLElement? submitter; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_event_init.idl b/chromium/third_party/blink/renderer/core/html/forms/submit_event_init.idl new file mode 100644 index 00000000000..e38c93d7680 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/submit_event_init.idl @@ -0,0 +1,9 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://html.spec.whatwg.org/C/#submitevent + +dictionary SubmitEventInit : EventInit { + HTMLElement? submitter = null; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc index 418baf6f877..3792c0037a5 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc @@ -24,6 +24,7 @@ #include "third_party/blink/renderer/core/html/forms/text_control_element.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" #include "third_party/blink/renderer/core/css/style_change_reason.h" #include "third_party/blink/renderer/core/dom/document.h" @@ -82,7 +83,7 @@ TextControlElement::~TextControlElement() = default; void TextControlElement::DispatchFocusEvent( Element* old_focused_element, - WebFocusType type, + mojom::blink::FocusType type, InputDeviceCapabilities* source_capabilities) { if (SupportsPlaceholder()) UpdatePlaceholderVisibility(); @@ -93,7 +94,7 @@ void TextControlElement::DispatchFocusEvent( void TextControlElement::DispatchBlurEvent( Element* new_focused_element, - WebFocusType type, + mojom::blink::FocusType type, InputDeviceCapabilities* source_capabilities) { if (SupportsPlaceholder()) UpdatePlaceholderVisibility(); @@ -222,8 +223,8 @@ void TextControlElement::select() { setSelectionRangeForBinding(0, std::numeric_limits<unsigned>::max()); // Avoid SelectionBehaviorOnFocus::Restore, which scrolls containers to show // the selection. - focus( - FocusParams(SelectionBehaviorOnFocus::kNone, kWebFocusTypeNone, nullptr)); + focus(FocusParams(SelectionBehaviorOnFocus::kNone, + mojom::blink::FocusType::kNone, nullptr)); RestoreCachedSelection(); } @@ -246,7 +247,8 @@ void TextControlElement::ClearValueBeforeFirstUserEdit() { value_before_first_user_edit_ = String(); } -void TextControlElement::SetFocused(bool flag, WebFocusType focus_type) { +void TextControlElement::SetFocused(bool flag, + mojom::blink::FocusType focus_type) { HTMLFormControlElementWithState::SetFocused(flag, focus_type); if (!flag) @@ -972,11 +974,11 @@ String TextControlElement::DirectionForFormData() const { if (dir_attribute_value.IsNull()) continue; - if (DeprecatedEqualIgnoringCase(dir_attribute_value, "rtl") || - DeprecatedEqualIgnoringCase(dir_attribute_value, "ltr")) + if (EqualIgnoringASCIICase(dir_attribute_value, "rtl") || + EqualIgnoringASCIICase(dir_attribute_value, "ltr")) return dir_attribute_value; - if (DeprecatedEqualIgnoringCase(dir_attribute_value, "auto")) { + if (EqualIgnoringASCIICase(dir_attribute_value, "auto")) { bool is_auto; TextDirection text_direction = element->DirectionalityIfhasDirAutoAttribute(is_auto); diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h index 47c266d0474..740cab80101 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h @@ -27,7 +27,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_ELEMENT_H_ #include "base/gtest_prod_util.h" -#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/editing/forward.h" #include "third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h" @@ -45,6 +45,7 @@ enum TextFieldSelectionDirection { enum class TextFieldEventBehavior { kDispatchNoEvent, kDispatchChangeEvent, + kDispatchInputEvent, kDispatchInputAndChangeEvent }; @@ -65,7 +66,7 @@ class CORE_EXPORT TextControlElement : public HTMLFormControlElementWithState { void ForwardEvent(Event&); - void SetFocused(bool, WebFocusType) override; + void SetFocused(bool, mojom::blink::FocusType) override; // The derived class should return true if placeholder processing is needed. virtual bool IsPlaceholderVisible() const = 0; @@ -186,10 +187,10 @@ class CORE_EXPORT TextControlElement : public HTMLFormControlElementWithState { static unsigned IndexForPosition(HTMLElement* inner_editor, const Position&); void DispatchFocusEvent(Element* old_focused_element, - WebFocusType, + mojom::blink::FocusType, InputDeviceCapabilities* source_capabilities) final; void DispatchBlurEvent(Element* new_focused_element, - WebFocusType, + mojom::blink::FocusType, InputDeviceCapabilities* source_capabilities) final; void ScheduleSelectEvent(); void DisabledOrReadonlyAttributeChanged(const QualifiedName&); @@ -202,7 +203,8 @@ class CORE_EXPORT TextControlElement : public HTMLFormControlElementWithState { bool IsEmptySuggestedValue() const { return SuggestedValue().IsEmpty(); } // Called in dispatchFocusEvent(), after placeholder process, before calling // parent's dispatchFocusEvent(). - virtual void HandleFocusEvent(Element* /* oldFocusedNode */, WebFocusType) {} + virtual void HandleFocusEvent(Element* /* oldFocusedNode */, + mojom::blink::FocusType) {} // Called in dispatchBlurEvent(), after placeholder process, before calling // parent's dispatchBlurEvent(). virtual void HandleBlurEvent() {} diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc index 64536a9caa5..e5f900301bc 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc @@ -30,8 +30,7 @@ class TextControlElementTest : public testing::Test { HTMLInputElement& Input() const { return *input_; } void UpdateAllLifecyclePhases() { - GetDocument().View()->UpdateAllLifecyclePhases( - DocumentLifecycle::LifecycleUpdateReason::kTest); + GetDocument().View()->UpdateAllLifecyclePhases(DocumentUpdateReason::kTest); } private: @@ -49,7 +48,7 @@ void TextControlElementTest::SetUp() { std::make_unique<DummyPageHolder>(IntSize(800, 600), &page_clients); document_ = &dummy_page_holder_->GetDocument(); - document_->documentElement()->SetInnerHTMLFromString( + document_->documentElement()->setInnerHTML( "<body><textarea id=textarea></textarea><input id=input /></body>"); UpdateAllLifecyclePhases(); text_control_ = ToTextControl(document_->getElementById("textarea")); diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc index 88b66536810..c1f64e3421c 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc @@ -39,19 +39,6 @@ namespace blink { -TextControlInnerContainer::TextControlInnerContainer(Document& document) - : HTMLDivElement(document) { - setAttribute(html_names::kIdAttr, shadow_element_names::TextFieldContainer()); -} - -LayoutObject* TextControlInnerContainer::CreateLayoutObject( - const ComputedStyle&, - LegacyLayout) { - return new LayoutTextControlInnerContainer(this); -} - -// --------------------------- - EditingViewPortElement::EditingViewPortElement(Document& document) : HTMLDivElement(document) { SetHasCustomStyleCallbacks(); @@ -100,6 +87,17 @@ void TextControlInnerEditorElement::DefaultEventHandler(Event& event) { if (shadow_ancestor) shadow_ancestor->DefaultEventHandler(event); } + + if (event.type() == event_type_names::kScroll) { + // The scroller for a text control is inside of a shadow tree but the + // scroll event won't bubble past the shadow root and authors cannot add + // an event listener to it. Fire the scroll event at the shadow host so + // that the page can hear about the scroll. + Element* shadow_ancestor = OwnerShadowHost(); + if (shadow_ancestor) + shadow_ancestor->DispatchEvent(event); + } + if (!event.DefaultHandled()) HTMLDivElement::DefaultEventHandler(event); } @@ -148,6 +146,8 @@ TextControlInnerEditorElement::CreateInnerEditorStyle() const { ? EUserModify::kReadOnly : EUserModify::kReadWritePlaintextOnly); text_block_style->SetDisplay(EDisplay::kBlock); + text_block_style->SetHasLineIfEmpty(true); + text_block_style->SetShouldIgnoreOverflowPropertyForInlineBlockBaseline(); if (!IsA<HTMLTextAreaElement>(host)) { text_block_style->SetWhiteSpace(EWhiteSpace::kPre); @@ -204,6 +204,7 @@ SearchFieldCancelButtonElement::SearchFieldCancelButtonElement( void SearchFieldCancelButtonElement::DefaultEventHandler(Event& event) { // If the element is visible, on mouseup, clear the value, and set selection + auto* mouse_event = DynamicTo<MouseEvent>(event); auto* input = To<HTMLInputElement>(OwnerShadowHost()); if (!input || input->IsDisabledOrReadOnly()) { if (!event.DefaultHandled()) @@ -211,8 +212,8 @@ void SearchFieldCancelButtonElement::DefaultEventHandler(Event& event) { return; } - if (event.type() == event_type_names::kClick && event.IsMouseEvent() && - ToMouseEvent(event).button() == + if (event.type() == event_type_names::kClick && mouse_event && + mouse_event->button() == static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { input->SetValueForUser(""); input->SetAutofillState(WebAutofillState::kNotFilled); @@ -250,7 +251,7 @@ void PasswordRevealButtonElement::DefaultEventHandler(Event& event) { } // Toggle the should-reveal-password state when clicked. - if (event.type() == event_type_names::kClick && event.IsMouseEvent()) { + if (event.type() == event_type_names::kClick && IsA<MouseEvent>(event)) { bool shouldRevealPassword = !input->ShouldRevealPassword(); input->SetShouldRevealPassword(shouldRevealPassword); diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h index cd0ad1b92fa..f800144a200 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h @@ -32,15 +32,6 @@ namespace blink { -class TextControlInnerContainer final : public HTMLDivElement { - public: - explicit TextControlInnerContainer(Document&); - - protected: - LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override; - bool TypeShouldForceLegacyLayout() const final { return true; } -}; - class EditingViewPortElement final : public HTMLDivElement { public: explicit EditingViewPortElement(Document&); diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc index 5953d45d82b..efb2e1ef525 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc @@ -35,7 +35,9 @@ #include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/events/before_text_inserted_event.h" +#include "third_party/blink/renderer/core/events/drag_event.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" #include "third_party/blink/renderer/core/events/text_event.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/html/forms/form_data.h" @@ -134,7 +136,10 @@ bool TextFieldInputType::IsTextField() const { } bool TextFieldInputType::ValueMissing(const String& value) const { - return GetElement().IsRequired() && value.IsEmpty(); + // For text-mode input elements, the value is missing only if it is mutable. + // https://html.spec.whatwg.org/multipage/input.html#the-required-attribute + return GetElement().IsRequired() && value.IsEmpty() && + !GetElement().IsDisabledOrReadOnly(); } bool TextFieldInputType::CanSetSuggestedValue() { @@ -177,11 +182,14 @@ void TextFieldInputType::SetValue(const String& sanitized_value, GetElement().DispatchFormControlChangeEvent(); break; - case TextFieldEventBehavior::kDispatchInputAndChangeEvent: { + case TextFieldEventBehavior::kDispatchInputEvent: + GetElement().DispatchInputEvent(); + break; + + case TextFieldEventBehavior::kDispatchInputAndChangeEvent: GetElement().DispatchInputEvent(); GetElement().DispatchFormControlChangeEvent(); break; - } case TextFieldEventBehavior::kDispatchNoEvent: break; @@ -226,12 +234,12 @@ void TextFieldInputType::ForwardEvent(Event& event) { // input element. if (GetElement().GetLayoutObject() && !GetElement().GetForceReattachLayoutTree() && - (event.IsMouseEvent() || event.IsDragEvent() || + (IsA<MouseEvent>(event) || IsA<DragEvent>(event) || event.HasInterface(event_interface_names::kWheelEvent) || event.type() == event_type_names::kBlur || event.type() == event_type_names::kFocus)) { - LayoutTextControlSingleLine* layout_text_control = - ToLayoutTextControlSingleLine(GetElement().GetLayoutObject()); + auto* layout_text_control = + To<LayoutTextControlSingleLine>(GetElement().GetLayoutObject()); if (event.type() == event_type_names::kBlur) { if (LayoutBox* inner_editor_layout_object = GetElement().InnerEditorElement()->GetLayoutBox()) { @@ -239,8 +247,8 @@ void TextFieldInputType::ForwardEvent(Event& event) { if (PaintLayer* inner_layer = inner_editor_layout_object->Layer()) { if (PaintLayerScrollableArea* inner_scrollable_area = inner_layer->GetScrollableArea()) { - inner_scrollable_area->SetScrollOffset(ScrollOffset(0, 0), - kProgrammaticScroll); + inner_scrollable_area->SetScrollOffset( + ScrollOffset(0, 0), mojom::blink::ScrollType::kProgrammatic); } } } @@ -268,6 +276,16 @@ bool TextFieldInputType::ShouldSubmitImplicitly(const Event& event) { InputTypeView::ShouldSubmitImplicitly(event); } +void TextFieldInputType::CustomStyleForLayoutObject(ComputedStyle& style) { + // The flag is necessary in order that a text field <input> with non-'visible' + // overflow property doesn't change its baseline. + style.SetShouldIgnoreOverflowPropertyForInlineBlockBaseline(); +} + +bool TextFieldInputType::TypeShouldForceLegacyLayout() const { + return true; +} + LayoutObject* TextFieldInputType::CreateLayoutObject(const ComputedStyle&, LegacyLayout) const { return new LayoutTextControlSingleLine(&GetElement()); @@ -294,7 +312,8 @@ void TextFieldInputType::CreateShadowSubtree() { return; } - auto* container = MakeGarbageCollected<TextControlInnerContainer>(document); + auto* container = MakeGarbageCollected<HTMLDivElement>(document); + container->SetIdAttribute(shadow_element_names::TextFieldContainer()); container->SetShadowPseudoId( AtomicString("-webkit-textfield-decoration-container")); shadow_root->AppendChild(container); @@ -352,8 +371,8 @@ void TextFieldInputType::ListAttributeTargetChanged() { // FIXME: The following code is similar to createShadowSubtree(), // but they are different. We should simplify the code by making // containerElement mandatory. - auto* rp_container = - MakeGarbageCollected<TextControlInnerContainer>(document); + auto* rp_container = MakeGarbageCollected<HTMLDivElement>(document); + rp_container->SetIdAttribute(shadow_element_names::TextFieldContainer()); rp_container->SetShadowPseudoId( AtomicString("-webkit-textfield-decoration-container")); Element* inner_editor = GetElement().InnerEditorElement(); @@ -429,7 +448,8 @@ void TextFieldInputType::HandleBeforeTextInsertedEvent( if (GetElement().IsFocused()) { // TODO(editing-dev): Use of UpdateStyleAndLayout // needs to be audited. See http://crbug.com/590369 for more details. - GetElement().GetDocument().UpdateStyleAndLayout(); + GetElement().GetDocument().UpdateStyleAndLayout( + DocumentUpdateReason::kEditing); selection_length = GetElement() .GetDocument() @@ -573,4 +593,8 @@ void TextFieldInputType::SpinButtonDidReleaseMouseCapture( GetElement().DispatchFormControlChangeEvent(); } +String TextFieldInputType::RawValue() const { + return GetElement().InnerEditorElement()->innerText(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h index 595bed48801..0e7994ca51a 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h @@ -48,6 +48,8 @@ class TextFieldInputType : public InputType, void Trace(Visitor*) override; using InputType::GetElement; + String RawValue() const override; + protected: TextFieldInputType(HTMLInputElement&); ~TextFieldInputType() override; @@ -68,6 +70,8 @@ class TextFieldInputType : public InputType, TextFieldEventBehavior, TextControlSetValueSelection) override; void UpdateView() override; + void CustomStyleForLayoutObject(ComputedStyle& style) override; + bool TypeShouldForceLegacyLayout() const override; LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) const override; diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc index b9d42cef516..a611a0333df 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc @@ -42,9 +42,9 @@ void TextInputType::CountUsage() { CountUsageIfVisible(WebFeature::kInputTypeTextMaxLength); const AtomicString& type = GetElement().FastGetAttribute(html_names::kTypeAttr); - if (DeprecatedEqualIgnoringCase(type, input_type_names::kDatetime)) + if (EqualIgnoringASCIICase(type, input_type_names::kDatetime)) CountUsageIfVisible(WebFeature::kInputTypeDateTimeFallback); - else if (DeprecatedEqualIgnoringCase(type, input_type_names::kWeek)) + else if (EqualIgnoringASCIICase(type, input_type_names::kWeek)) CountUsageIfVisible(WebFeature::kInputTypeWeekFallback); } diff --git a/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc index bbf3ccfc8d6..e56c81b5b43 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc @@ -30,6 +30,7 @@ #include "third_party/blink/renderer/core/html/forms/time_input_type.h" +#include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/frame/web_feature.h" #include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" @@ -76,7 +77,7 @@ StepRange TimeInputType::CreateStepRange( (kTimeDefaultStep, kTimeDefaultStepBase, kTimeStepScaleFactor, StepRange::kScaledStepValueShouldBeInteger)); - return InputType::CreateStepRange( + return InputType::CreateReversibleStepRange( any_step_handling, kTimeDefaultStepBase, Decimal::FromDouble(DateComponents::MinimumTime()), Decimal::FromDouble(DateComponents::MaximumTime()), step_description); @@ -173,4 +174,16 @@ bool TimeInputType::IsValidFormat(bool has_year, return has_hour && has_minute && has_ampm; } +String TimeInputType::AriaRoleForPickerIndicator() const { + return GetLocale().QueryString(IDS_AX_CALENDAR_SHOW_TIME_PICKER); +} + +String TimeInputType::ReversedRangeOutOfRangeText( + const Decimal& minimum, + const Decimal& maximum) const { + return GetLocale().QueryString( + IDS_FORM_VALIDATION_REVERSED_RANGE_OUT_OF_RANGE_TIME, + LocalizeValue(Serialize(minimum)), LocalizeValue(Serialize(maximum))); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h index 58711ec43f5..2b520a8899d 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h @@ -62,6 +62,9 @@ class TimeInputType final : public BaseTemporalInputType { bool has_hour, bool has_minute, bool has_second) const override; + String AriaRoleForPickerIndicator() const override; + String ReversedRangeOutOfRangeText(const Decimal& minimum, + const Decimal& maximum) const override; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/type_ahead_test.cc b/chromium/third_party/blink/renderer/core/html/forms/type_ahead_test.cc index 5077a9152e2..dc43dbf7bb4 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/type_ahead_test.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/type_ahead_test.cc @@ -6,7 +6,7 @@ #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/public/platform/web_keyboard_event.h" +#include "third_party/blink/public/common/input/web_keyboard_event.h" #include "third_party/blink/renderer/core/events/keyboard_event.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc index 3c53ffb9098..0501230b551 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc +++ b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc @@ -30,6 +30,7 @@ #include "third_party/blink/renderer/core/html/forms/week_input_type.h" +#include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/core/frame/web_feature.h" #include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" @@ -125,4 +126,8 @@ bool WeekInputType::IsValidFormat(bool has_year, return has_year && has_week; } +String WeekInputType::AriaRoleForPickerIndicator() const { + return GetLocale().QueryString(IDS_AX_CALENDAR_SHOW_WEEK_PICKER); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h index 783aeff2766..b8b176bbaad 100644 --- a/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h +++ b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h @@ -61,6 +61,7 @@ class WeekInputType final : public BaseTemporalInputType { bool has_hour, bool has_minute, bool has_second) const override; + String AriaRoleForPickerIndicator() const override; }; } // namespace blink |