summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/html/forms
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:20:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:28:57 +0000
commitd17ea114e5ef69ad5d5d7413280a13e6428098aa (patch)
tree2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/third_party/blink/renderer/core/html/forms
parent8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff)
downloadqtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/html/forms')
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/OWNERS5
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc102
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h70
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.cc124
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.h77
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc208
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h108
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc123
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.h66
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/button_input_type.cc53
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/button_input_type.h51
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc100
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.h56
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc148
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h73
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/clear_button_element.cc91
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h67
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser.cc38
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser.h55
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.cc36
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h61
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc158
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h87
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc89
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h86
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc279
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/color_input_type.h91
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc141
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_input_type.h69
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.cc37
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.h83
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.cc37
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.h57
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc246
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h82
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc871
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h167
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc234
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h113
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.cc794
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.h330
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.cc109
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.h103
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc198
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h75
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc240
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h116
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.cc176
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h89
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/email_input_type.cc322
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/email_input_type.h70
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/email_input_type_test.cc79
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc164
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h66
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc318
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.h99
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc227
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/file_chooser.cc74
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/file_chooser.h98
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc456
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/file_input_type.h109
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc111
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_associated.h22
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_controller.cc587
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_controller.h128
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_data.cc333
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_data.h139
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_data.idl52
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc28
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_data_event.h33
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl13
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/form_data_test.cc45
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc104
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.h70
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc222
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_button_element.h88
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_button_element.idl45
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc73
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h59
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.idl35
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h47
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc134
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.h66
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.idl37
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc682
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h231
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc153
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc96
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h87
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.cc229
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h80
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.idl29
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc899
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_element.h190
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_form_element.idl47
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc1950
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_input_element.h441
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl110
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc206
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc250
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_label_element.h66
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_label_element.idl27
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_legend_element.cc82
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_legend_element.h53
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_legend_element.idl29
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc165
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h68
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.idl25
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc412
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_option_element.h124
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl41
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc110
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h85
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_options_collection.idl36
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_output_element.cc131
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_output_element.h81
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_output_element.idl45
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_output_element_test.cc32
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc2086
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_select_element.h319
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_select_element.idl59
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc449
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc636
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h151
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.idl67
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_text_area_element_test.cc36
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc293
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/image_input_type.h84
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/input_type.cc910
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/input_type.h260
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/input_type_names.json532
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc194
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/input_type_view.h146
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc562
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h69
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.cc80
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h54
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/labelable_element.cc50
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/labelable_element.h66
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/labels_node_list.cc49
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h51
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/listed_element.cc326
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/listed_element.h137
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc173
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/month_input_type.h75
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc652
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h141
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc310
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/number_input_type.h86
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/option_list.cc40
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/option_list.h60
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc90
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/password_input_type.cc88
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/password_input_type.h57
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc353
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc181
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h98
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/popup_menu.h46
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc302
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h56
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc246
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h65
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/radio_node_list.cc130
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/radio_node_list.h68
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/radio_node_list.idl37
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc418
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/range_input_type.h94
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/reset_input_type.cc69
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/reset_input_type.h53
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/.clang-format8
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.css324
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js4085
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css71
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.js185
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/input_alert.svg22
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css18
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js456
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/pickerButton.css71
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.css36
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.js334
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css56
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js331
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/resources/validation_bubble.css148
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc170
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/search_input_type.h66
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc477
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h113
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc257
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/spin_button_element.h112
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/step_range.cc188
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/step_range.h130
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc36
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/submit_input_type.cc89
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/submit_input_type.h56
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.cc50
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.h50
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc1006
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_control_element.h265
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc90
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc213
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h89
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc559
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h108
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc64
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/text_input_type.h51
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc180
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/time_input_type.h71
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/type_ahead.cc128
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/type_ahead.h76
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/url_input_type.cc77
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/url_input_type.h55
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/validity_state.cc76
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/validity_state.h71
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/validity_state.idl37
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc132
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/week_input_type.h70
216 files changed, 41225 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/core/html/forms/OWNERS b/chromium/third_party/blink/renderer/core/html/forms/OWNERS
new file mode 100644
index 00000000000..53439ec8720
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/OWNERS
@@ -0,0 +1,5 @@
+keishi@chromium.org
+tkent@chromium.org
+
+# TEAM: dom-dev@chromium.org
+# COMPONENT: Blink>Forms
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
new file mode 100644
index 00000000000..83d3d68fd88
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h"
+
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_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/layout/layout_button.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+BaseButtonInputType::BaseButtonInputType(HTMLInputElement& element)
+ : InputType(element), KeyboardClickableInputTypeView(element) {}
+
+void BaseButtonInputType::Trace(blink::Visitor* visitor) {
+ KeyboardClickableInputTypeView::Trace(visitor);
+ InputType::Trace(visitor);
+}
+
+InputTypeView* BaseButtonInputType::CreateView() {
+ return this;
+}
+
+void BaseButtonInputType::CreateShadowSubtree() {
+ DCHECK(GetElement().UserAgentShadowRoot());
+ GetElement().UserAgentShadowRoot()->AppendChild(
+ Text::Create(GetElement().GetDocument(), DisplayValue()));
+}
+
+void BaseButtonInputType::ValueAttributeChanged() {
+ ToTextOrDie(GetElement().UserAgentShadowRoot()->firstChild())
+ ->setData(DisplayValue());
+}
+
+String BaseButtonInputType::DisplayValue() const {
+ return GetElement().ValueOrDefaultLabel().RemoveCharacters(IsHTMLLineBreak);
+}
+
+bool BaseButtonInputType::ShouldSaveAndRestoreFormControlState() const {
+ return false;
+}
+
+void BaseButtonInputType::AppendToFormData(FormData&) const {}
+
+LayoutObject* BaseButtonInputType::CreateLayoutObject(
+ const ComputedStyle&) const {
+ return new LayoutButton(&GetElement());
+}
+
+InputType::ValueMode BaseButtonInputType::GetValueMode() const {
+ return ValueMode::kDefault;
+}
+
+void BaseButtonInputType::SetValue(const String& sanitized_value,
+ bool,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) {
+ GetElement().setAttribute(valueAttr, AtomicString(sanitized_value));
+}
+
+bool BaseButtonInputType::MatchesDefaultPseudoClass() {
+ // HTMLFormElement::findDefaultButton() traverses the tree. So we check
+ // canBeSuccessfulSubmitButton() first for early return.
+ return CanBeSuccessfulSubmitButton() && GetElement().Form() &&
+ GetElement().Form()->FindDefaultButton() == &GetElement();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..829e130cbce
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_BUTTON_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_BUTTON_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h"
+
+namespace blink {
+
+// Base of button, image, reset, and submit types.
+class BaseButtonInputType : public InputType,
+ public KeyboardClickableInputTypeView {
+ USING_GARBAGE_COLLECTED_MIXIN(BaseButtonInputType);
+
+ public:
+ void Trace(blink::Visitor*) override;
+ using InputType::GetElement;
+
+ protected:
+ explicit BaseButtonInputType(HTMLInputElement&);
+ void ValueAttributeChanged() override;
+ void CreateShadowSubtree() override;
+
+ private:
+ InputTypeView* CreateView() override;
+ bool ShouldSaveAndRestoreFormControlState() const override;
+ void AppendToFormData(FormData&) const override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) const override;
+ ValueMode GetValueMode() const override;
+ void SetValue(const String&,
+ bool,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) override;
+ bool MatchesDefaultPseudoClass() override;
+
+ String DisplayValue() const;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_BUTTON_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.cc
new file mode 100644
index 00000000000..4b4a558300f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.cc
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/base_checkable_input_type.h"
+
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/frame/use_counter.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_input_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+void BaseCheckableInputType::Trace(blink::Visitor* visitor) {
+ InputTypeView::Trace(visitor);
+ InputType::Trace(visitor);
+}
+
+InputTypeView* BaseCheckableInputType::CreateView() {
+ return this;
+}
+
+FormControlState BaseCheckableInputType::SaveFormControlState() const {
+ return FormControlState(GetElement().checked() ? "on" : "off");
+}
+
+void BaseCheckableInputType::RestoreFormControlState(
+ const FormControlState& state) {
+ GetElement().setChecked(state[0] == "on");
+}
+
+void BaseCheckableInputType::AppendToFormData(FormData& form_data) const {
+ if (GetElement().checked())
+ form_data.append(GetElement().GetName(), GetElement().value());
+}
+
+void BaseCheckableInputType::HandleKeydownEvent(KeyboardEvent* event) {
+ const String& key = event->key();
+ if (key == " ") {
+ GetElement().SetActive(true);
+ // No setDefaultHandled(), because IE dispatches a keypress in this case
+ // and the caller will only dispatch a keypress if we don't call
+ // setDefaultHandled().
+ }
+}
+
+void BaseCheckableInputType::HandleKeypressEvent(KeyboardEvent* event) {
+ if (event->charCode() == ' ') {
+ // Prevent scrolling down the page.
+ event->SetDefaultHandled();
+ }
+}
+
+bool BaseCheckableInputType::CanSetStringValue() const {
+ return false;
+}
+
+// FIXME: Could share this with KeyboardClickableInputTypeView and
+// RangeInputType if we had a common base class.
+void BaseCheckableInputType::AccessKeyAction(bool send_mouse_events) {
+ InputTypeView::AccessKeyAction(send_mouse_events);
+
+ GetElement().DispatchSimulatedClick(
+ nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents);
+}
+
+bool BaseCheckableInputType::MatchesDefaultPseudoClass() {
+ return GetElement().FastHasAttribute(checkedAttr);
+}
+
+InputType::ValueMode BaseCheckableInputType::GetValueMode() const {
+ return ValueMode::kDefaultOn;
+}
+
+void BaseCheckableInputType::SetValue(const String& sanitized_value,
+ bool,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) {
+ GetElement().setAttribute(valueAttr, AtomicString(sanitized_value));
+}
+
+void BaseCheckableInputType::ReadingChecked() const {
+ if (is_in_click_handler_) {
+ UseCounter::Count(GetElement().GetDocument(),
+ WebFeature::kReadingCheckedInClickHandler);
+ }
+}
+
+bool BaseCheckableInputType::IsCheckable() {
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.h
new file mode 100644
index 00000000000..95d84ae6052
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_CHECKABLE_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_CHECKABLE_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/input_type_view.h"
+
+namespace blink {
+
+// Base of checkbox and radio types.
+class BaseCheckableInputType : public InputType, public InputTypeView {
+ USING_GARBAGE_COLLECTED_MIXIN(BaseCheckableInputType);
+
+ public:
+ void Trace(blink::Visitor*) override;
+ using InputType::GetElement;
+
+ protected:
+ BaseCheckableInputType(HTMLInputElement& element)
+ : InputType(element),
+ InputTypeView(element),
+ is_in_click_handler_(false) {}
+ void HandleKeydownEvent(KeyboardEvent*) override;
+ bool NeedsShadowSubtree() const override { return false; }
+
+ bool is_in_click_handler_;
+
+ private:
+ InputTypeView* CreateView() override;
+ FormControlState SaveFormControlState() const final;
+ void RestoreFormControlState(const FormControlState&) final;
+ void AppendToFormData(FormData&) const final;
+ void HandleKeypressEvent(KeyboardEvent*) final;
+ bool CanSetStringValue() const final;
+ void AccessKeyAction(bool send_mouse_events) final;
+ bool MatchesDefaultPseudoClass() override;
+ ValueMode GetValueMode() const override;
+ void SetValue(const String&,
+ bool,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) final;
+ void ReadingChecked() const final;
+ bool IsCheckable() final;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_CHECKABLE_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..6305600fa10
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+
+#include <limits>
+#include "third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/date_math.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/time.h"
+
+namespace blink {
+
+using blink::WebLocalizedString;
+using namespace HTMLNames;
+
+static const int kMsecPerMinute = 60 * 1000;
+static const int kMsecPerSecond = 1000;
+
+String BaseTemporalInputType::BadInputText() const {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationBadInputForDateTime);
+}
+
+InputTypeView* BaseTemporalInputType::CreateView() {
+ if (RuntimeEnabledFeatures::InputMultipleFieldsUIEnabled())
+ return MultipleFieldsTemporalInputTypeView::Create(GetElement(), *this);
+ return ChooserOnlyTemporalInputTypeView::Create(GetElement(), *this);
+}
+
+InputType::ValueMode BaseTemporalInputType::GetValueMode() const {
+ return ValueMode::kValue;
+}
+
+double BaseTemporalInputType::ValueAsDate() const {
+ return ValueAsDouble();
+}
+
+void BaseTemporalInputType::SetValueAsDate(double value,
+ ExceptionState&) const {
+ GetElement().setValue(SerializeWithMilliseconds(value));
+}
+
+double BaseTemporalInputType::ValueAsDouble() const {
+ const Decimal value = ParseToNumber(GetElement().value(), Decimal::Nan());
+ return value.IsFinite() ? value.ToDouble()
+ : DateComponents::InvalidMilliseconds();
+}
+
+void BaseTemporalInputType::SetValueAsDouble(
+ double new_value,
+ TextFieldEventBehavior event_behavior,
+ ExceptionState& exception_state) const {
+ SetValueAsDecimal(Decimal::FromDouble(new_value), event_behavior,
+ exception_state);
+}
+
+bool BaseTemporalInputType::TypeMismatchFor(const String& value) const {
+ return !value.IsEmpty() && !ParseToDateComponents(value, nullptr);
+}
+
+bool BaseTemporalInputType::TypeMismatch() const {
+ return TypeMismatchFor(GetElement().value());
+}
+
+String BaseTemporalInputType::RangeOverflowText(const Decimal& maximum) const {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationRangeOverflowDateTime,
+ LocalizeValue(Serialize(maximum)));
+}
+
+String BaseTemporalInputType::RangeUnderflowText(const Decimal& minimum) const {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationRangeUnderflowDateTime,
+ LocalizeValue(Serialize(minimum)));
+}
+
+Decimal BaseTemporalInputType::DefaultValueForStepUp() const {
+ return Decimal::FromDouble(ConvertToLocalTime(CurrentTimeMS()));
+}
+
+bool BaseTemporalInputType::IsSteppable() const {
+ return true;
+}
+
+Decimal BaseTemporalInputType::ParseToNumber(
+ const String& source,
+ const Decimal& default_value) const {
+ DateComponents date;
+ if (!ParseToDateComponents(source, &date))
+ return default_value;
+ double msec = date.MillisecondsSinceEpoch();
+ DCHECK(std::isfinite(msec));
+ return Decimal::FromDouble(msec);
+}
+
+bool BaseTemporalInputType::ParseToDateComponents(const String& source,
+ DateComponents* out) const {
+ if (source.IsEmpty())
+ return false;
+ DateComponents ignored_result;
+ if (!out)
+ out = &ignored_result;
+ return ParseToDateComponentsInternal(source, out);
+}
+
+String BaseTemporalInputType::Serialize(const Decimal& value) const {
+ if (!value.IsFinite())
+ return String();
+ DateComponents date;
+ if (!SetMillisecondToDateComponents(value.ToDouble(), &date))
+ return String();
+ return SerializeWithComponents(date);
+}
+
+String BaseTemporalInputType::SerializeWithComponents(
+ const DateComponents& date) const {
+ Decimal step;
+ if (!GetElement().GetAllowedValueStep(&step))
+ return date.ToString();
+ if (step.Remainder(kMsecPerMinute).IsZero())
+ return date.ToString(DateComponents::kNone);
+ if (step.Remainder(kMsecPerSecond).IsZero())
+ return date.ToString(DateComponents::kSecond);
+ return date.ToString(DateComponents::kMillisecond);
+}
+
+String BaseTemporalInputType::SerializeWithMilliseconds(double value) const {
+ return Serialize(Decimal::FromDouble(value));
+}
+
+String BaseTemporalInputType::LocalizeValue(
+ const String& proposed_value) const {
+ DateComponents date;
+ if (!ParseToDateComponents(proposed_value, &date))
+ return proposed_value;
+
+ String localized = GetElement().GetLocale().FormatDateTime(date);
+ return localized.IsEmpty() ? proposed_value : localized;
+}
+
+String BaseTemporalInputType::VisibleValue() const {
+ return LocalizeValue(GetElement().value());
+}
+
+String BaseTemporalInputType::SanitizeValue(
+ const String& proposed_value) const {
+ return TypeMismatchFor(proposed_value) ? g_empty_string : proposed_value;
+}
+
+bool BaseTemporalInputType::SupportsReadOnly() const {
+ return true;
+}
+
+bool BaseTemporalInputType::ShouldRespectListAttribute() {
+ return true;
+}
+
+bool BaseTemporalInputType::ValueMissing(const String& value) const {
+ return GetElement().IsRequired() && value.IsEmpty();
+}
+
+bool BaseTemporalInputType::ShouldShowFocusRingOnMouseFocus() const {
+ return true;
+}
+
+bool BaseTemporalInputType::ShouldHaveSecondField(
+ const DateComponents& date) const {
+ StepRange step_range = CreateStepRange(kAnyIsDefaultStep);
+ return date.Second() || date.Millisecond() ||
+ !step_range.Minimum()
+ .Remainder(static_cast<int>(kMsPerMinute))
+ .IsZero() ||
+ !step_range.Step().Remainder(static_cast<int>(kMsPerMinute)).IsZero();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..72617e3af31
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEMPORAL_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEMPORAL_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/date_time_edit_element.h"
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+
+namespace blink {
+
+class ExceptionState;
+
+// A super class of date, datetime, datetime-local, month, time, and week types.
+// TODO(tkent): A single temporal input type creates two InputTypeView instances
+// unnecessarily. One is ChooserOnlyTemporalInputTypeView or
+// MultipleFieldsTemporalInputType, and another is BaseTemporalInputType, which
+// inherits from InputTypeView through InputType. The latter is not used.
+class BaseTemporalInputType : public InputType {
+ public:
+ String VisibleValue() const override;
+ String SanitizeValue(const String&) const override;
+ // Parses the specified string for this InputType, and returns true if it
+ // is successfully parsed. An instance pointed by the DateComponents*
+ // parameter will have parsed values and be modified even if the parsing
+ // fails. The DateComponents* parameter may be 0.
+ bool ParseToDateComponents(const String&, DateComponents*) const;
+ virtual bool SetMillisecondToDateComponents(double,
+ DateComponents*) const = 0;
+
+ // Provide some helpers for MultipleFieldsTemporalInputTypeView.
+ virtual String FormatDateTimeFieldsState(
+ const DateTimeFieldsState&) const = 0;
+ virtual void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&,
+ const DateComponents&) const = 0;
+ virtual bool IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const = 0;
+
+ protected:
+ BaseTemporalInputType(HTMLInputElement& element) : InputType(element) {}
+ Decimal ParseToNumber(const String&, const Decimal&) const override;
+ String Serialize(const Decimal&) const override;
+ String SerializeWithComponents(const DateComponents&) const;
+ bool ShouldHaveSecondField(const DateComponents&) const;
+
+ private:
+ virtual bool ParseToDateComponentsInternal(const String&,
+ DateComponents*) const = 0;
+
+ String BadInputText() const override;
+ InputTypeView* CreateView() override;
+ ValueMode GetValueMode() const override;
+ double ValueAsDate() const override;
+ void SetValueAsDate(double, ExceptionState&) const override;
+ double ValueAsDouble() const override;
+ void SetValueAsDouble(double,
+ TextFieldEventBehavior,
+ ExceptionState&) const override;
+ bool TypeMismatchFor(const String&) const override;
+ bool TypeMismatch() const override;
+ bool ValueMissing(const String&) const override;
+ String RangeOverflowText(const Decimal& maximum) const override;
+ String RangeUnderflowText(const Decimal& minimum) const override;
+ Decimal DefaultValueForStepUp() const override;
+ bool IsSteppable() const override;
+ virtual String SerializeWithMilliseconds(double) const;
+ String LocalizeValue(const String&) const override;
+ bool SupportsReadOnly() const override;
+ bool ShouldRespectListAttribute() override;
+ bool ShouldShowFocusRingOnMouseFocus() const override;
+};
+
+} // namespace blink
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEMPORAL_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..5f167ebf233
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc
@@ -0,0 +1,123 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com>
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * 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/base_text_input_type.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#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"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+BaseTextInputType::BaseTextInputType(HTMLInputElement& element)
+ : TextFieldInputType(element) {}
+
+BaseTextInputType::~BaseTextInputType() = default;
+
+int BaseTextInputType::MaxLength() const {
+ return GetElement().maxLength();
+}
+
+int BaseTextInputType::MinLength() const {
+ return GetElement().minLength();
+}
+
+bool BaseTextInputType::TooLong(
+ const String& value,
+ TextControlElement::NeedsToCheckDirtyFlag check) const {
+ int max = GetElement().maxLength();
+ if (max < 0)
+ return false;
+ if (check == TextControlElement::kCheckDirtyFlag) {
+ // Return false for the default value or a value set by a script even if
+ // it is longer than maxLength.
+ if (!GetElement().HasDirtyValue() || !GetElement().LastChangeWasUserEdit())
+ return false;
+ }
+ return value.length() > static_cast<unsigned>(max);
+}
+
+bool BaseTextInputType::TooShort(
+ const String& value,
+ TextControlElement::NeedsToCheckDirtyFlag check) const {
+ int min = GetElement().minLength();
+ if (min <= 0)
+ return false;
+ if (check == TextControlElement::kCheckDirtyFlag) {
+ // Return false for the default value or a value set by a script even if
+ // it is shorter than minLength.
+ if (!GetElement().HasDirtyValue() || !GetElement().LastChangeWasUserEdit())
+ return false;
+ }
+ // An empty string is excluded from minlength check.
+ unsigned len = value.length();
+ return len > 0 && len < static_cast<unsigned>(min);
+}
+
+bool BaseTextInputType::PatternMismatch(const String& value) const {
+ const AtomicString& raw_pattern = GetElement().FastGetAttribute(patternAttr);
+ // Empty values can't be mismatched
+ if (raw_pattern.IsNull() || value.IsEmpty())
+ return false;
+ if (!regexp_ || pattern_for_regexp_ != raw_pattern) {
+ std::unique_ptr<ScriptRegexp> raw_regexp(
+ new ScriptRegexp(raw_pattern, kTextCaseSensitive, kMultilineDisabled,
+ ScriptRegexp::UTF16));
+ if (!raw_regexp->IsValid()) {
+ GetElement().GetDocument().AddConsoleMessage(
+ ConsoleMessage::Create(kRenderingMessageSource, kErrorMessageLevel,
+ "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;
+ }
+ String pattern = "^(?:" + raw_pattern + ")$";
+ regexp_.reset(new ScriptRegexp(pattern, kTextCaseSensitive,
+ kMultilineDisabled, ScriptRegexp::UTF16));
+ pattern_for_regexp_ = raw_pattern;
+ } else if (!regexp_->IsValid()) {
+ return false;
+ }
+
+ int match_length = 0;
+ int value_length = value.length();
+ int match_offset = regexp_->Match(value, 0, &match_length);
+ bool mismatched = match_offset != 0 || match_length != value_length;
+ return mismatched;
+}
+
+bool BaseTextInputType::SupportsPlaceholder() const {
+ return true;
+}
+
+bool BaseTextInputType::SupportsSelectionAPI() const {
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.h
new file mode 100644
index 00000000000..2d252058a9f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEXT_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEXT_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/text_field_input_type.h"
+
+namespace blink {
+
+class ScriptRegexp;
+
+// Base of email, password, search, tel, text, and URL types.
+// They support maxlength, selection functions, and so on.
+class BaseTextInputType : public TextFieldInputType {
+ protected:
+ BaseTextInputType(HTMLInputElement&);
+ ~BaseTextInputType() override;
+
+ private:
+ bool TooLong(const String&,
+ TextControlElement::NeedsToCheckDirtyFlag) const final;
+ bool TooShort(const String&,
+ TextControlElement::NeedsToCheckDirtyFlag) const final;
+ int MaxLength() const final;
+ int MinLength() const final;
+ bool PatternMismatch(const String&) const final;
+ bool SupportsPlaceholder() const final;
+ bool SupportsSelectionAPI() const override;
+
+ // m_regexp and m_patternForRegexp are mutable because they are kinds of
+ // cache.
+ mutable std::unique_ptr<ScriptRegexp> regexp_;
+ mutable AtomicString pattern_for_regexp_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEXT_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/button_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.cc
new file mode 100644
index 00000000000..7c58447907d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/button_input_type.h"
+
+#include "third_party/blink/renderer/core/input_type_names.h"
+
+namespace blink {
+
+InputType* ButtonInputType::Create(HTMLInputElement& element) {
+ return new ButtonInputType(element);
+}
+
+const AtomicString& ButtonInputType::FormControlType() const {
+ return InputTypeNames::button;
+}
+
+bool ButtonInputType::SupportsValidation() const {
+ return false;
+}
+
+bool ButtonInputType::IsTextButton() const {
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/button_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.h
new file mode 100644
index 00000000000..479bd80655c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BUTTON_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BUTTON_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h"
+
+namespace blink {
+
+class ButtonInputType final : public BaseButtonInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ ButtonInputType(HTMLInputElement& element) : BaseButtonInputType(element) {}
+ const AtomicString& FormControlType() const override;
+ bool SupportsValidation() const override;
+ bool IsTextButton() const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BUTTON_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc
new file mode 100644
index 00000000000..db7b034b596
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/checkbox_input_type.h"
+
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+InputType* CheckboxInputType::Create(HTMLInputElement& element) {
+ return new CheckboxInputType(element);
+}
+
+const AtomicString& CheckboxInputType::FormControlType() const {
+ return InputTypeNames::checkbox;
+}
+
+bool CheckboxInputType::ValueMissing(const String&) const {
+ return GetElement().IsRequired() && !GetElement().checked();
+}
+
+String CheckboxInputType::ValueMissingText() const {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationValueMissingForCheckbox);
+}
+
+void CheckboxInputType::HandleKeyupEvent(KeyboardEvent* event) {
+ const String& key = event->key();
+ if (key != " ")
+ return;
+ DispatchSimulatedClickIfActive(event);
+}
+
+ClickHandlingState* CheckboxInputType::WillDispatchClick() {
+ // An event handler can use preventDefault or "return false" to reverse the
+ // checking we do here. The ClickHandlingState object contains what we need
+ // to undo what we did here in didDispatchClick.
+
+ ClickHandlingState* state = new ClickHandlingState;
+
+ state->checked = GetElement().checked();
+ state->indeterminate = GetElement().indeterminate();
+
+ if (state->indeterminate)
+ GetElement().setIndeterminate(false);
+
+ GetElement().setChecked(!state->checked, kDispatchChangeEvent);
+ is_in_click_handler_ = true;
+ return state;
+}
+
+void CheckboxInputType::DidDispatchClick(Event* event,
+ const ClickHandlingState& state) {
+ if (event->defaultPrevented() || event->DefaultHandled()) {
+ GetElement().setIndeterminate(state.indeterminate);
+ GetElement().setChecked(state.checked);
+ } else {
+ GetElement().DispatchInputAndChangeEventIfNeeded();
+ }
+ is_in_click_handler_ = false;
+ // The work we did in willDispatchClick was default handling.
+ event->SetDefaultHandled();
+}
+
+bool CheckboxInputType::ShouldAppearIndeterminate() const {
+ return GetElement().indeterminate();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.h
new file mode 100644
index 00000000000..ae088c656ef
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHECKBOX_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHECKBOX_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_checkable_input_type.h"
+
+namespace blink {
+
+class CheckboxInputType final : public BaseCheckableInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ CheckboxInputType(HTMLInputElement& element)
+ : BaseCheckableInputType(element) {}
+ const AtomicString& FormControlType() const override;
+ bool ValueMissing(const String&) const override;
+ String ValueMissingText() const override;
+ void HandleKeyupEvent(KeyboardEvent*) override;
+ ClickHandlingState* WillDispatchClick() override;
+ void DidDispatchClick(Event*, const ClickHandlingState&) override;
+ bool ShouldAppearIndeterminate() const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHECKBOX_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc
new file mode 100644
index 00000000000..785db8e8066
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/dom/document.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/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/core/page/page.h"
+
+namespace blink {
+
+ChooserOnlyTemporalInputTypeView::ChooserOnlyTemporalInputTypeView(
+ HTMLInputElement& element,
+ BaseTemporalInputType& input_type)
+ : KeyboardClickableInputTypeView(element), input_type_(input_type) {}
+
+ChooserOnlyTemporalInputTypeView* ChooserOnlyTemporalInputTypeView::Create(
+ HTMLInputElement& element,
+ BaseTemporalInputType& input_type) {
+ return new ChooserOnlyTemporalInputTypeView(element, input_type);
+}
+
+ChooserOnlyTemporalInputTypeView::~ChooserOnlyTemporalInputTypeView() {
+ DCHECK(!date_time_chooser_);
+}
+
+void ChooserOnlyTemporalInputTypeView::Trace(blink::Visitor* visitor) {
+ visitor->Trace(input_type_);
+ visitor->Trace(date_time_chooser_);
+ InputTypeView::Trace(visitor);
+ DateTimeChooserClient::Trace(visitor);
+}
+
+void ChooserOnlyTemporalInputTypeView::HandleDOMActivateEvent(Event* event) {
+ Document& document = GetElement().GetDocument();
+ if (GetElement().IsDisabledOrReadOnly() || !GetElement().GetLayoutObject() ||
+ !Frame::HasTransientUserActivation(document.GetFrame()) ||
+ GetElement().OpenShadowRoot())
+ return;
+
+ if (date_time_chooser_)
+ return;
+ if (!document.IsActive())
+ return;
+ DateTimeChooserParameters parameters;
+ if (!GetElement().SetupDateTimeChooserParameters(parameters))
+ return;
+ UseCounter::Count(
+ document,
+ (event->UnderlyingEvent() && event->UnderlyingEvent()->isTrusted())
+ ? WebFeature::kTemporalInputTypeChooserByTrustedClick
+ : WebFeature::kTemporalInputTypeChooserByUntrustedClick);
+ date_time_chooser_ =
+ document.GetPage()->GetChromeClient().OpenDateTimeChooser(this,
+ parameters);
+}
+
+void ChooserOnlyTemporalInputTypeView::CreateShadowSubtree() {
+ DEFINE_STATIC_LOCAL(AtomicString, value_container_pseudo,
+ ("-webkit-date-and-time-value"));
+
+ HTMLDivElement* value_container =
+ HTMLDivElement::Create(GetElement().GetDocument());
+ value_container->SetShadowPseudoId(value_container_pseudo);
+ GetElement().UserAgentShadowRoot()->AppendChild(value_container);
+ UpdateView();
+}
+
+void ChooserOnlyTemporalInputTypeView::UpdateView() {
+ Node* node = GetElement().UserAgentShadowRoot()->firstChild();
+ if (!node || !node->IsHTMLElement())
+ return;
+ String display_value;
+ if (!GetElement().SuggestedValue().IsNull())
+ display_value = GetElement().SuggestedValue();
+ else
+ display_value = input_type_->VisibleValue();
+ if (display_value.IsEmpty()) {
+ // Need to put something to keep text baseline.
+ display_value = " ";
+ }
+ ToHTMLElement(node)->setTextContent(display_value);
+}
+
+void ChooserOnlyTemporalInputTypeView::DidSetValue(const String& value,
+ bool value_changed) {
+ if (value_changed)
+ UpdateView();
+}
+
+void ChooserOnlyTemporalInputTypeView::ClosePopupView() {
+ CloseDateTimeChooser();
+}
+
+Element& ChooserOnlyTemporalInputTypeView::OwnerElement() const {
+ return GetElement();
+}
+
+void ChooserOnlyTemporalInputTypeView::DidChooseValue(const String& value) {
+ GetElement().setValue(value, kDispatchInputAndChangeEvent);
+}
+
+void ChooserOnlyTemporalInputTypeView::DidChooseValue(double value) {
+ DCHECK(std::isfinite(value) || std::isnan(value));
+ if (std::isnan(value))
+ GetElement().setValue(g_empty_string, kDispatchInputAndChangeEvent);
+ else
+ GetElement().setValueAsNumber(value, ASSERT_NO_EXCEPTION,
+ kDispatchInputAndChangeEvent);
+}
+
+void ChooserOnlyTemporalInputTypeView::DidEndChooser() {
+ date_time_chooser_.Clear();
+}
+
+void ChooserOnlyTemporalInputTypeView::CloseDateTimeChooser() {
+ if (date_time_chooser_)
+ date_time_chooser_->EndChooser();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h
new file mode 100644
index 00000000000..9dbfb0ae2fe
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHOOSER_ONLY_TEMPORAL_INPUT_TYPE_VIEW_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHOOSER_ONLY_TEMPORAL_INPUT_TYPE_VIEW_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h"
+#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+
+namespace blink {
+
+class ChooserOnlyTemporalInputTypeView final
+ : public GarbageCollectedFinalized<ChooserOnlyTemporalInputTypeView>,
+ public KeyboardClickableInputTypeView,
+ public DateTimeChooserClient {
+ USING_GARBAGE_COLLECTED_MIXIN(ChooserOnlyTemporalInputTypeView);
+ USING_PRE_FINALIZER(ChooserOnlyTemporalInputTypeView, CloseDateTimeChooser);
+
+ public:
+ static ChooserOnlyTemporalInputTypeView* Create(HTMLInputElement&,
+ BaseTemporalInputType&);
+ ~ChooserOnlyTemporalInputTypeView() override;
+ void Trace(blink::Visitor*) override;
+
+ private:
+ ChooserOnlyTemporalInputTypeView(HTMLInputElement&, BaseTemporalInputType&);
+ void CloseDateTimeChooser();
+
+ // InputTypeView functions:
+ void CreateShadowSubtree() override;
+ void ClosePopupView() override;
+ void DidSetValue(const String&, bool value_changed) override;
+ void HandleDOMActivateEvent(Event*) override;
+ void UpdateView() override;
+
+ // DateTimeChooserClient functions:
+ Element& OwnerElement() const override;
+ void DidChooseValue(const String&) override;
+ void DidChooseValue(double) override;
+ void DidEndChooser() override;
+
+ Member<BaseTemporalInputType> input_type_;
+ Member<DateTimeChooser> date_time_chooser_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHOOSER_ONLY_TEMPORAL_INPUT_TYPE_VIEW_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.cc b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.cc
new file mode 100644
index 00000000000..7e92b7e68e5
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.cc
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/clear_button_element.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/shadow/shadow_element_names.h"
+#include "third_party/blink/renderer/core/input/event_handler.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline ClearButtonElement::ClearButtonElement(
+ Document& document,
+ ClearButtonOwner& clear_button_owner)
+ : HTMLDivElement(document), clear_button_owner_(&clear_button_owner) {}
+
+ClearButtonElement* ClearButtonElement::Create(
+ Document& document,
+ ClearButtonOwner& clear_button_owner) {
+ ClearButtonElement* element =
+ new ClearButtonElement(document, clear_button_owner);
+ element->SetShadowPseudoId(AtomicString("-webkit-clear-button"));
+ element->setAttribute(idAttr, ShadowElementNames::ClearButton());
+ return element;
+}
+
+void ClearButtonElement::DetachLayoutTree(const AttachContext& context) {
+ HTMLDivElement::DetachLayoutTree(context);
+}
+
+void ClearButtonElement::DefaultEventHandler(Event* event) {
+ if (!clear_button_owner_) {
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ if (!clear_button_owner_->ShouldClearButtonRespondToMouseEvents()) {
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ if (event->type() == EventTypeNames::click) {
+ if (GetLayoutObject() && GetLayoutObject()->VisibleToHitTesting()) {
+ clear_button_owner_->FocusAndSelectClearButtonOwner();
+ clear_button_owner_->ClearValue();
+ event->SetDefaultHandled();
+ }
+ }
+
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+}
+
+bool ClearButtonElement::IsClearButtonElement() const {
+ return true;
+}
+
+void ClearButtonElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(clear_button_owner_);
+ HTMLDivElement::Trace(visitor);
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..4698422d545
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CLEAR_BUTTON_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CLEAR_BUTTON_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+class ClearButtonElement final : public HTMLDivElement {
+ public:
+ class ClearButtonOwner : public GarbageCollectedMixin {
+ public:
+ virtual ~ClearButtonOwner() = default;
+ virtual void FocusAndSelectClearButtonOwner() = 0;
+ virtual bool ShouldClearButtonRespondToMouseEvents() = 0;
+ virtual void ClearValue() = 0;
+ };
+
+ static ClearButtonElement* Create(Document&, ClearButtonOwner&);
+ void RemoveClearButtonOwner() { clear_button_owner_ = nullptr; }
+
+ void Trace(blink::Visitor*) override;
+
+ private:
+ ClearButtonElement(Document&, ClearButtonOwner&);
+ void DetachLayoutTree(const AttachContext& = AttachContext()) override;
+ bool IsMouseFocusable() const override { return false; }
+ void DefaultEventHandler(Event*) override;
+ bool IsClearButtonElement() const override;
+
+ Member<ClearButtonOwner> clear_button_owner_;
+};
+
+DEFINE_TYPE_CASTS(ClearButtonElement,
+ Element,
+ element,
+ element->IsClearButtonElement(),
+ element.IsClearButtonElement());
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CLEAR_BUTTON_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.cc
new file mode 100644
index 00000000000..051b0ecf604
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Google, Inc. ("Google") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "third_party/blink/renderer/core/html/forms/color_chooser.h"
+
+namespace blink {
+
+ColorChooser::ColorChooser() = default;
+
+ColorChooser::~ColorChooser() = default;
+
+} // namespace blink
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
new file mode 100644
index 00000000000..72012762a3d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+
+namespace blink {
+
+class AXObject;
+class Color;
+
+class CORE_EXPORT ColorChooser : public GarbageCollectedMixin {
+ public:
+ ColorChooser();
+ virtual ~ColorChooser();
+ void Trace(blink::Visitor* visitor) override {}
+
+ virtual void SetSelectedColor(const Color&) {}
+ virtual void EndChooser() {}
+ // Returns a root AXObject in the ColorChooser if it's available.
+ virtual AXObject* RootAXObject() = 0;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.cc
new file mode 100644
index 00000000000..4033d4622d5
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.cc
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Google, Inc. ("Google") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h"
+
+namespace blink {
+
+ColorChooserClient::~ColorChooserClient() = default;
+
+} // namespace blink
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
new file mode 100644
index 00000000000..845c5cca368
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_CLIENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_CLIENT_H_
+
+#include "third_party/blink/public/mojom/color_chooser/color_chooser.mojom-blink.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/geometry/int_rect.h"
+#include "third_party/blink/renderer/platform/graphics/color.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class Element;
+
+class CORE_EXPORT ColorChooserClient : public GarbageCollectedMixin {
+ public:
+ virtual ~ColorChooserClient();
+ void Trace(blink::Visitor* visitor) override {}
+
+ virtual void DidChooseColor(const Color&) = 0;
+ virtual void DidEndChooser() = 0;
+ virtual Element& OwnerElement() const = 0;
+ virtual IntRect ElementRectRelativeToViewport() const = 0;
+ virtual Color CurrentColor() = 0;
+ virtual bool ShouldShowSuggestions() const = 0;
+ virtual Vector<mojom::blink::ColorSuggestionPtr> Suggestions() const = 0;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_CLIENT_H_
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
new file mode 100644
index 00000000000..129853be04b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h"
+
+#include "third_party/blink/public/platform/platform.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/html/forms/color_chooser_client.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/core/page/page_popup.h"
+#include "third_party/blink/renderer/platform/geometry/int_rect.h"
+
+namespace blink {
+
+// Keep in sync with Actions in colorSuggestionPicker.js.
+enum ColorPickerPopupAction {
+ kColorPickerPopupActionChooseOtherColor = -2,
+ kColorPickerPopupActionCancel = -1,
+ kColorPickerPopupActionSetValue = 0
+};
+
+ColorChooserPopupUIController::ColorChooserPopupUIController(
+ LocalFrame* frame,
+ ChromeClient* chrome_client,
+ blink::ColorChooserClient* client)
+ : ColorChooserUIController(frame, client),
+ chrome_client_(chrome_client),
+ popup_(nullptr),
+ locale_(Locale::DefaultLocale()) {}
+
+ColorChooserPopupUIController::~ColorChooserPopupUIController() = default;
+
+void ColorChooserPopupUIController::Dispose() {
+ // Finalized earlier so as to access m_chromeClient while alive.
+ ClosePopup();
+ // ~ColorChooserUIController calls endChooser().
+}
+
+void ColorChooserPopupUIController::Trace(blink::Visitor* visitor) {
+ visitor->Trace(chrome_client_);
+ ColorChooserUIController::Trace(visitor);
+}
+
+void ColorChooserPopupUIController::OpenUI() {
+ if (client_->ShouldShowSuggestions())
+ OpenPopup();
+ else
+ OpenColorChooser();
+}
+
+void ColorChooserPopupUIController::EndChooser() {
+ ColorChooserUIController::EndChooser();
+ ClosePopup();
+}
+
+AXObject* ColorChooserPopupUIController::RootAXObject() {
+ return popup_ ? popup_->RootAXObject() : nullptr;
+}
+
+void ColorChooserPopupUIController::WriteDocument(SharedBuffer* data) {
+ Vector<String> suggestion_values;
+ for (auto& suggestion : client_->Suggestions())
+ suggestion_values.push_back(suggestion->label);
+ IntRect anchor_rect_in_screen = chrome_client_->ViewportToScreen(
+ client_->ElementRectRelativeToViewport(), frame_->View());
+
+ PagePopupClient::AddString(
+ "<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data);
+ data->Append(Platform::Current()->GetDataResource("pickerCommon.css"));
+ data->Append(
+ Platform::Current()->GetDataResource("colorSuggestionPicker.css"));
+ PagePopupClient::AddString(
+ "</style></head><body><div id=main>Loading...</div><script>\n"
+ "window.dialogArguments = {\n",
+ data);
+ PagePopupClient::AddProperty("values", suggestion_values, data);
+ PagePopupClient::AddProperty(
+ "otherColorLabel",
+ GetLocale().QueryString(WebLocalizedString::kOtherColorLabel), data);
+ AddProperty("anchorRectInScreen", anchor_rect_in_screen, data);
+ AddProperty("zoomFactor", ZoomFactor(), data);
+ PagePopupClient::AddString("};\n", data);
+ data->Append(Platform::Current()->GetDataResource("pickerCommon.js"));
+ data->Append(
+ Platform::Current()->GetDataResource("colorSuggestionPicker.js"));
+ PagePopupClient::AddString("</script></body>\n", data);
+}
+
+Locale& ColorChooserPopupUIController::GetLocale() {
+ return locale_;
+}
+
+void ColorChooserPopupUIController::SetValueAndClosePopup(
+ int num_value,
+ const String& string_value) {
+ DCHECK(popup_);
+ DCHECK(client_);
+ if (num_value == kColorPickerPopupActionSetValue)
+ SetValue(string_value);
+ if (num_value == kColorPickerPopupActionChooseOtherColor)
+ OpenColorChooser();
+ ClosePopup();
+}
+
+void ColorChooserPopupUIController::SetValue(const String& value) {
+ DCHECK(client_);
+ Color color;
+ bool success = color.SetFromString(value);
+ DCHECK(success);
+ client_->DidChooseColor(color);
+}
+
+void ColorChooserPopupUIController::DidClosePopup() {
+ popup_ = nullptr;
+
+ if (!chooser_)
+ EndChooser();
+}
+
+Element& ColorChooserPopupUIController::OwnerElement() {
+ return client_->OwnerElement();
+}
+
+void ColorChooserPopupUIController::OpenPopup() {
+ DCHECK(!popup_);
+ popup_ = chrome_client_->OpenPagePopup(this);
+}
+
+void ColorChooserPopupUIController::ClosePopup() {
+ if (!popup_)
+ return;
+ chrome_client_->ClosePagePopup(popup_);
+}
+
+} // 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
new file mode 100644
index 00000000000..53d750e78d5
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_POPUP_UI_CONTROLLER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_POPUP_UI_CONTROLLER_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h"
+#include "third_party/blink/renderer/core/page/page_popup_client.h"
+
+namespace blink {
+
+class ChromeClient;
+class ColorChooserClient;
+class PagePopup;
+
+class CORE_EXPORT ColorChooserPopupUIController final
+ : public ColorChooserUIController,
+ public PagePopupClient {
+ USING_PRE_FINALIZER(ColorChooserPopupUIController, Dispose);
+
+ public:
+ static ColorChooserPopupUIController* Create(
+ LocalFrame* frame,
+ ChromeClient* chrome_client,
+ blink::ColorChooserClient* client) {
+ return new ColorChooserPopupUIController(frame, chrome_client, client);
+ }
+
+ ~ColorChooserPopupUIController() override;
+ void Trace(blink::Visitor*) override;
+
+ // ColorChooserUIController functions:
+ void OpenUI() override;
+
+ // ColorChooser functions
+ void EndChooser() override;
+ AXObject* RootAXObject() override;
+
+ // 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 ClosePopup() override;
+ Element& OwnerElement() override;
+ void DidClosePopup() override;
+
+ private:
+ ColorChooserPopupUIController(LocalFrame*,
+ ChromeClient*,
+ blink::ColorChooserClient*);
+
+ void OpenPopup();
+ void Dispose();
+
+ Member<ChromeClient> chrome_client_;
+ PagePopup* popup_;
+ Locale& locale_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_POPUP_UI_CONTROLLER_H_
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
new file mode 100644
index 00000000000..8ea5c8429ad
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h"
+
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "third_party/blink/public/platform/web_color.h"
+#include "third_party/blink/public/web/web_frame_client.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"
+
+namespace blink {
+
+ColorChooserUIController::ColorChooserUIController(
+ LocalFrame* frame,
+ blink::ColorChooserClient* client)
+ : client_(client), frame_(frame), binding_(this) {}
+
+ColorChooserUIController::~ColorChooserUIController() {}
+
+void ColorChooserUIController::Trace(blink::Visitor* visitor) {
+ visitor->Trace(frame_);
+ visitor->Trace(client_);
+ ColorChooser::Trace(visitor);
+}
+
+void ColorChooserUIController::Dispose() {
+ binding_.Close();
+}
+
+void ColorChooserUIController::OpenUI() {
+ OpenColorChooser();
+}
+
+void ColorChooserUIController::SetSelectedColor(const Color& color) {
+ // Color can be set via JS before mojo OpenColorChooser completes.
+ if (chooser_)
+ chooser_->SetSelectedColor(color.Rgb());
+}
+
+void ColorChooserUIController::EndChooser() {
+ chooser_.reset();
+ client_->DidEndChooser();
+}
+
+AXObject* ColorChooserUIController::RootAXObject() {
+ return nullptr;
+}
+
+void ColorChooserUIController::DidChooseColor(uint32_t color) {
+ client_->DidChooseColor(color);
+}
+
+void ColorChooserUIController::OpenColorChooser() {
+ DCHECK(!chooser_);
+ frame_->GetInterfaceProvider().GetInterface(&color_chooser_factory_);
+ mojom::blink::ColorChooserClientPtr mojo_client;
+ binding_.Bind(mojo::MakeRequest(&mojo_client));
+ binding_.set_connection_error_handler(WTF::Bind(
+ &ColorChooserUIController::EndChooser, WrapWeakPersistent(this)));
+ color_chooser_factory_->OpenColorChooser(
+ mojo::MakeRequest(&chooser_), std::move(mojo_client),
+ client_->CurrentColor().Rgb(), client_->Suggestions());
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..311c2a26b2b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_UI_CONTROLLER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_UI_CONTROLLER_H_
+
+#include <memory>
+#include "mojo/public/cpp/bindings/binding.h"
+#include "third_party/blink/public/mojom/color_chooser/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/text/platform_locale.h"
+
+namespace blink {
+
+class ColorChooserClient;
+class LocalFrame;
+
+class CORE_EXPORT ColorChooserUIController
+ : public GarbageCollectedFinalized<ColorChooserUIController>,
+ public mojom::blink::ColorChooserClient,
+ public ColorChooser {
+ USING_GARBAGE_COLLECTED_MIXIN(ColorChooserUIController);
+ USING_PRE_FINALIZER(ColorChooserUIController, Dispose);
+
+ public:
+ static ColorChooserUIController* Create(LocalFrame* frame,
+ blink::ColorChooserClient* client) {
+ return new ColorChooserUIController(frame, client);
+ }
+
+ ~ColorChooserUIController() override;
+ void Trace(blink::Visitor*) override;
+
+ void Dispose();
+
+ virtual void OpenUI();
+
+ // ColorChooser functions:
+ void SetSelectedColor(const Color&) final;
+ void EndChooser() override;
+ AXObject* RootAXObject() override;
+
+ // mojom::blink::ColorChooserClient functions:
+ void DidChooseColor(uint32_t color) final;
+
+ protected:
+ ColorChooserUIController(LocalFrame*, blink::ColorChooserClient*);
+
+ void OpenColorChooser();
+ mojom::blink::ColorChooserPtr chooser_;
+ Member<blink::ColorChooserClient> client_;
+
+ Member<LocalFrame> frame_;
+
+ private:
+ mojom::blink::ColorChooserFactoryPtr color_chooser_factory_;
+ mojo::Binding<mojom::blink::ColorChooserClient> binding_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_UI_CONTROLLER_H_
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
new file mode 100644
index 00000000000..ab0b2d5e477
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/color_input_type.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
+#include "third_party/blink/renderer/core/css_property_names.h"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/events/mouse_event.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/html/forms/color_chooser.h"
+#include "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/forms/html_input_element.h"
+#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_theme.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/platform/graphics/color.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+// Upper limit of number of datalist suggestions shown.
+static const unsigned kMaxSuggestions = 1000;
+// Upper limit for the length of the labels for datalist suggestions.
+static const unsigned kMaxSuggestionLabelLength = 1000;
+
+static bool IsValidColorString(const String& value) {
+ if (value.IsEmpty())
+ return false;
+ if (value[0] != '#')
+ return false;
+
+ // We don't accept #rgb and #aarrggbb formats.
+ if (value.length() != 7)
+ return false;
+ Color color;
+ return color.SetFromString(value) && !color.HasAlpha();
+}
+
+ColorInputType::ColorInputType(HTMLInputElement& element)
+ : InputType(element), KeyboardClickableInputTypeView(element) {}
+
+InputType* ColorInputType::Create(HTMLInputElement& element) {
+ return new ColorInputType(element);
+}
+
+ColorInputType::~ColorInputType() = default;
+
+void ColorInputType::Trace(blink::Visitor* visitor) {
+ visitor->Trace(chooser_);
+ KeyboardClickableInputTypeView::Trace(visitor);
+ ColorChooserClient::Trace(visitor);
+ InputType::Trace(visitor);
+}
+
+InputTypeView* ColorInputType::CreateView() {
+ return this;
+}
+
+InputType::ValueMode ColorInputType::GetValueMode() const {
+ return ValueMode::kValue;
+}
+
+void ColorInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeColor);
+}
+
+const AtomicString& ColorInputType::FormControlType() const {
+ return InputTypeNames::color;
+}
+
+bool ColorInputType::SupportsRequired() const {
+ return false;
+}
+
+String ColorInputType::SanitizeValue(const String& proposed_value) const {
+ if (!IsValidColorString(proposed_value))
+ return "#000000";
+ return proposed_value.DeprecatedLower();
+}
+
+Color ColorInputType::ValueAsColor() const {
+ Color color;
+ bool success = color.SetFromString(GetElement().value());
+ DCHECK(success);
+ return color;
+}
+
+void ColorInputType::CreateShadowSubtree() {
+ DCHECK(IsShadowHost(GetElement()));
+
+ Document& document = GetElement().GetDocument();
+ HTMLDivElement* wrapper_element = HTMLDivElement::Create(document);
+ wrapper_element->SetShadowPseudoId(
+ AtomicString("-webkit-color-swatch-wrapper"));
+ HTMLDivElement* color_swatch = HTMLDivElement::Create(document);
+ color_swatch->SetShadowPseudoId(AtomicString("-webkit-color-swatch"));
+ wrapper_element->AppendChild(color_swatch);
+ GetElement().UserAgentShadowRoot()->AppendChild(wrapper_element);
+
+ GetElement().UpdateView();
+}
+
+void ColorInputType::DidSetValue(const String&, bool value_changed) {
+ if (!value_changed)
+ return;
+ GetElement().UpdateView();
+ if (chooser_)
+ chooser_->SetSelectedColor(ValueAsColor());
+}
+
+void ColorInputType::HandleDOMActivateEvent(Event* event) {
+ if (GetElement().IsDisabledFormControl())
+ return;
+
+ Document& document = GetElement().GetDocument();
+ if (!Frame::HasTransientUserActivation(document.GetFrame()))
+ return;
+
+ ChromeClient* chrome_client = GetChromeClient();
+ if (chrome_client && !chooser_) {
+ UseCounter::Count(
+ document,
+ (event->UnderlyingEvent() && event->UnderlyingEvent()->isTrusted())
+ ? WebFeature::kColorInputTypeChooserByTrustedClick
+ : WebFeature::kColorInputTypeChooserByUntrustedClick);
+ chooser_ = chrome_client->OpenColorChooser(document.GetFrame(), this,
+ ValueAsColor());
+ }
+
+ event->SetDefaultHandled();
+}
+
+void ColorInputType::ClosePopupView() {
+ EndColorChooser();
+}
+
+bool ColorInputType::ShouldRespectListAttribute() {
+ return true;
+}
+
+bool ColorInputType::TypeMismatchFor(const String& value) const {
+ return !IsValidColorString(value);
+}
+
+void ColorInputType::WarnIfValueIsInvalid(const String& value) const {
+ if (!DeprecatedEqualIgnoringCase(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 "
+ "numbers.",
+ value);
+}
+
+void ColorInputType::ValueAttributeChanged() {
+ if (!GetElement().HasDirtyValue())
+ GetElement().UpdateView();
+}
+
+void ColorInputType::DidChooseColor(const Color& color) {
+ if (GetElement().IsDisabledFormControl() || color == ValueAsColor())
+ return;
+ EventQueueScope scope;
+ GetElement().SetValueFromRenderer(color.Serialized());
+ GetElement().UpdateView();
+ if (!LayoutTheme::GetTheme().IsModalColorChooser())
+ GetElement().DispatchFormControlChangeEvent();
+}
+
+void ColorInputType::DidEndChooser() {
+ if (LayoutTheme::GetTheme().IsModalColorChooser())
+ GetElement().EnqueueChangeEvent();
+ chooser_.Clear();
+}
+
+void ColorInputType::EndColorChooser() {
+ if (chooser_)
+ chooser_->EndChooser();
+}
+
+void ColorInputType::UpdateView() {
+ HTMLElement* color_swatch = ShadowColorSwatch();
+ if (!color_swatch)
+ return;
+
+ color_swatch->SetInlineStyleProperty(CSSPropertyBackgroundColor,
+ GetElement().value());
+}
+
+HTMLElement* ColorInputType::ShadowColorSwatch() const {
+ ShadowRoot* shadow = GetElement().UserAgentShadowRoot();
+ return shadow ? ToHTMLElementOrDie(shadow->firstChild()->firstChild())
+ : nullptr;
+}
+
+Element& ColorInputType::OwnerElement() const {
+ return GetElement();
+}
+
+IntRect ColorInputType::ElementRectRelativeToViewport() const {
+ return GetElement().GetDocument().View()->ContentsToViewport(
+ GetElement().PixelSnappedBoundingBox());
+}
+
+Color ColorInputType::CurrentColor() {
+ return ValueAsColor();
+}
+
+bool ColorInputType::ShouldShowSuggestions() const {
+ return GetElement().FastHasAttribute(listAttr);
+}
+
+Vector<mojom::blink::ColorSuggestionPtr> ColorInputType::Suggestions() const {
+ Vector<mojom::blink::ColorSuggestionPtr> suggestions;
+ HTMLDataListElement* data_list = GetElement().DataList();
+ if (data_list) {
+ HTMLDataListOptionsCollection* options = data_list->options();
+ for (unsigned i = 0; HTMLOptionElement* option = options->Item(i); i++) {
+ if (option->IsDisabledFormControl() || option->value().IsEmpty())
+ continue;
+ if (!GetElement().IsValidValue(option->value()))
+ continue;
+ Color color;
+ if (!color.SetFromString(option->value()))
+ continue;
+ suggestions.push_back(mojom::blink::ColorSuggestion::New(
+ color.Rgb(), option->label().Left(kMaxSuggestionLabelLength)));
+ if (suggestions.size() >= kMaxSuggestions)
+ break;
+ }
+ }
+ return suggestions;
+}
+
+AXObject* ColorInputType::PopupRootAXObject() {
+ return chooser_ ? chooser_->RootAXObject() : nullptr;
+}
+
+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
new file mode 100644
index 00000000000..d6356adc4d2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h"
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h"
+
+namespace blink {
+
+class ColorChooser;
+
+class ColorInputType final : public InputType,
+ public KeyboardClickableInputTypeView,
+ public ColorChooserClient {
+ USING_GARBAGE_COLLECTED_MIXIN(ColorInputType);
+
+ public:
+ static InputType* Create(HTMLInputElement&);
+ ~ColorInputType() override;
+ void Trace(blink::Visitor*) override;
+ using InputType::GetElement;
+
+ // ColorChooserClient implementation.
+ void DidChooseColor(const Color&) override;
+ void DidEndChooser() override;
+ Element& OwnerElement() const override;
+ IntRect ElementRectRelativeToViewport() const override;
+ Color CurrentColor() override;
+ bool ShouldShowSuggestions() const override;
+ Vector<mojom::blink::ColorSuggestionPtr> Suggestions() const override;
+ ColorChooserClient* GetColorChooserClient() override;
+
+ private:
+ explicit ColorInputType(HTMLInputElement&);
+ InputTypeView* CreateView() override;
+ ValueMode GetValueMode() const override;
+ void ValueAttributeChanged() override;
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ bool SupportsRequired() const override;
+ String SanitizeValue(const String&) const override;
+ void CreateShadowSubtree() override;
+ void DidSetValue(const String&, bool value_changed) override;
+ void HandleDOMActivateEvent(Event*) override;
+ void ClosePopupView() override;
+ bool ShouldRespectListAttribute() override;
+ bool TypeMismatchFor(const String&) const override;
+ void WarnIfValueIsInvalid(const String&) const override;
+ void UpdateView() override;
+ AXObject* PopupRootAXObject() override;
+
+ Color ValueAsColor() const;
+ void EndColorChooser();
+ HTMLElement* ShadowColorSwatch() const;
+
+ Member<ColorChooser> chooser_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..36be3e524ae
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_input_type.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"
+#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/input_type_names.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+using blink::WebLocalizedString;
+using namespace HTMLNames;
+
+static const int kDateDefaultStep = 1;
+static const int kDateDefaultStepBase = 0;
+static const int kDateStepScaleFactor = 86400000;
+
+inline DateInputType::DateInputType(HTMLInputElement& element)
+ : BaseTemporalInputType(element) {}
+
+InputType* DateInputType::Create(HTMLInputElement& element) {
+ return new DateInputType(element);
+}
+
+void DateInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeDate);
+}
+
+const AtomicString& DateInputType::FormControlType() const {
+ return InputTypeNames::date;
+}
+
+StepRange DateInputType::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ DEFINE_STATIC_LOCAL(
+ const StepRange::StepDescription, step_description,
+ (kDateDefaultStep, kDateDefaultStepBase, kDateStepScaleFactor,
+ StepRange::kParsedStepValueShouldBeInteger));
+
+ return InputType::CreateStepRange(
+ any_step_handling, kDateDefaultStepBase,
+ Decimal::FromDouble(DateComponents::MinimumDate()),
+ Decimal::FromDouble(DateComponents::MaximumDate()), step_description);
+}
+
+bool DateInputType::ParseToDateComponentsInternal(const String& string,
+ DateComponents* out) const {
+ DCHECK(out);
+ unsigned end;
+ return out->ParseDate(string, 0, end) && end == string.length();
+}
+
+bool DateInputType::SetMillisecondToDateComponents(double value,
+ DateComponents* date) const {
+ DCHECK(date);
+ return date->SetMillisecondsSinceEpochForDate(value);
+}
+
+void DateInputType::WarnIfValueIsInvalid(const String& value) const {
+ if (value != GetElement().SanitizeValue(value))
+ AddWarningToConsole(
+ "The specified value %s does not conform to the required format, "
+ "\"yyyy-MM-dd\".",
+ value);
+}
+
+String DateInputType::FormatDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) const {
+ if (!date_time_fields_state.HasDayOfMonth() ||
+ !date_time_fields_state.HasMonth() || !date_time_fields_state.HasYear())
+ return g_empty_string;
+
+ return String::Format("%04u-%02u-%02u", date_time_fields_state.Year(),
+ date_time_fields_state.Month(),
+ date_time_fields_state.DayOfMonth());
+}
+
+void DateInputType::SetupLayoutParameters(
+ DateTimeEditElement::LayoutParameters& layout_parameters,
+ const DateComponents& date) const {
+ layout_parameters.date_time_format = layout_parameters.locale.DateFormat();
+ layout_parameters.fallback_date_time_format = "yyyy-MM-dd";
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr),
+ &layout_parameters.minimum))
+ layout_parameters.minimum = DateComponents();
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr),
+ &layout_parameters.maximum))
+ layout_parameters.maximum = DateComponents();
+ layout_parameters.placeholder_for_day = GetLocale().QueryString(
+ WebLocalizedString::kPlaceholderForDayOfMonthField);
+ layout_parameters.placeholder_for_month =
+ GetLocale().QueryString(WebLocalizedString::kPlaceholderForMonthField);
+ layout_parameters.placeholder_for_year =
+ GetLocale().QueryString(WebLocalizedString::kPlaceholderForYearField);
+}
+
+bool DateInputType::IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const {
+ return has_year && has_month && has_day;
+}
+
+} // 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
new file mode 100644
index 00000000000..cbae403e3b4
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+
+namespace blink {
+
+class DateInputType final : public BaseTemporalInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ explicit DateInputType(HTMLInputElement&);
+
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ StepRange CreateStepRange(AnyStepHandling) const override;
+ bool ParseToDateComponentsInternal(const String&,
+ DateComponents*) const override;
+ bool SetMillisecondToDateComponents(double, DateComponents*) const override;
+ void WarnIfValueIsInvalid(const String&) const override;
+
+ // BaseTemporalInputType functions
+ String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override;
+ void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&,
+ const DateComponents&) const override;
+ bool IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.cc
new file mode 100644
index 00000000000..11bffb1716d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h"
+
+namespace blink {
+
+DateTimeChooser::~DateTimeChooser() = default;
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.h
new file mode 100644
index 00000000000..e1ba1f136db
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/geometry/int_rect.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class AXObject;
+
+struct DateTimeSuggestion {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+ double value;
+ String localized_value;
+ String label;
+};
+
+struct DateTimeChooserParameters {
+ DISALLOW_NEW();
+ AtomicString type;
+ IntRect anchor_rect_in_screen;
+ // Locale name for which the chooser should be localized. This
+ // might be an invalid name because it comes from HTML lang
+ // attributes.
+ AtomicString locale;
+ double double_value;
+ Vector<DateTimeSuggestion> suggestions;
+ double minimum;
+ double maximum;
+ double step;
+ double step_base;
+ bool required;
+ bool is_anchor_element_rtl;
+};
+
+// For pickers like color pickers and date pickers.
+class CORE_EXPORT DateTimeChooser
+ : public GarbageCollectedFinalized<DateTimeChooser> {
+ public:
+ virtual ~DateTimeChooser();
+
+ virtual void EndChooser() = 0;
+ // Returns a root AXObject in the DateTimeChooser if it's available.
+ virtual AXObject* RootAXObject() = 0;
+
+ virtual void Trace(blink::Visitor* visitor) {}
+};
+
+} // namespace blink
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.cc
new file mode 100644
index 00000000000..9dd1679f8d8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h"
+
+namespace blink {
+
+DateTimeChooserClient::~DateTimeChooserClient() = default;
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.h
new file mode 100644
index 00000000000..5c4478cd04a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_CLIENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_CLIENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+class Element;
+
+class CORE_EXPORT DateTimeChooserClient : public GarbageCollectedMixin {
+ public:
+ virtual ~DateTimeChooserClient();
+ void Trace(blink::Visitor* visitor) override {}
+
+ virtual Element& OwnerElement() const = 0;
+ // Called when user picked a value.
+ virtual void DidChooseValue(const String&) = 0;
+ // Called when user picked a value.
+ virtual void DidChooseValue(double) = 0;
+ // Called when chooser has ended.
+ virtual void DidEndChooser() = 0;
+};
+
+} // namespace blink
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_CLIENT_H_
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
new file mode 100644
index 00000000000..d4127eaea68
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h"
+
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h"
+#include "third_party/blink/renderer/core/input_type_names.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/core/page/page_popup.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#include "third_party/blink/renderer/platform/language.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+DateTimeChooserImpl::DateTimeChooserImpl(
+ ChromeClient* chrome_client,
+ DateTimeChooserClient* client,
+ const DateTimeChooserParameters& parameters)
+ : chrome_client_(chrome_client),
+ client_(client),
+ popup_(nullptr),
+ parameters_(parameters),
+ locale_(Locale::Create(parameters.locale)) {
+ DCHECK(RuntimeEnabledFeatures::InputMultipleFieldsUIEnabled());
+ DCHECK(chrome_client_);
+ DCHECK(client_);
+ popup_ = chrome_client_->OpenPagePopup(this);
+}
+
+DateTimeChooserImpl* DateTimeChooserImpl::Create(
+ ChromeClient* chrome_client,
+ DateTimeChooserClient* client,
+ const DateTimeChooserParameters& parameters) {
+ return new DateTimeChooserImpl(chrome_client, client, parameters);
+}
+
+DateTimeChooserImpl::~DateTimeChooserImpl() = default;
+
+void DateTimeChooserImpl::Trace(blink::Visitor* visitor) {
+ visitor->Trace(chrome_client_);
+ visitor->Trace(client_);
+ DateTimeChooser::Trace(visitor);
+}
+
+void DateTimeChooserImpl::EndChooser() {
+ if (!popup_)
+ return;
+ chrome_client_->ClosePagePopup(popup_);
+}
+
+AXObject* DateTimeChooserImpl::RootAXObject() {
+ return popup_ ? popup_->RootAXObject() : nullptr;
+}
+
+static String ValueToDateTimeString(double value, AtomicString type) {
+ DateComponents components;
+ if (type == InputTypeNames::date)
+ components.SetMillisecondsSinceEpochForDate(value);
+ else if (type == InputTypeNames::datetime_local)
+ components.SetMillisecondsSinceEpochForDateTimeLocal(value);
+ else if (type == InputTypeNames::month)
+ components.SetMonthsSinceEpoch(value);
+ else if (type == InputTypeNames::time)
+ components.SetMillisecondsSinceMidnight(value);
+ else if (type == InputTypeNames::week)
+ components.SetMillisecondsSinceEpochForWeek(value);
+ else
+ NOTREACHED();
+ return components.GetType() == DateComponents::kInvalid
+ ? String()
+ : components.ToString();
+}
+
+void DateTimeChooserImpl::WriteDocument(SharedBuffer* data) {
+ String step_string = String::Number(parameters_.step);
+ String step_base_string = String::Number(parameters_.step_base, 11);
+ String today_label_string;
+ String other_date_label_string;
+ if (parameters_.type == InputTypeNames::month) {
+ today_label_string =
+ GetLocale().QueryString(WebLocalizedString::kThisMonthButtonLabel);
+ other_date_label_string =
+ GetLocale().QueryString(WebLocalizedString::kOtherMonthLabel);
+ } else if (parameters_.type == InputTypeNames::week) {
+ today_label_string =
+ GetLocale().QueryString(WebLocalizedString::kThisWeekButtonLabel);
+ other_date_label_string =
+ GetLocale().QueryString(WebLocalizedString::kOtherWeekLabel);
+ } else {
+ today_label_string =
+ GetLocale().QueryString(WebLocalizedString::kCalendarToday);
+ other_date_label_string =
+ GetLocale().QueryString(WebLocalizedString::kOtherDateLabel);
+ }
+
+ AddString("<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data);
+ data->Append(Platform::Current()->GetDataResource("pickerCommon.css"));
+ data->Append(Platform::Current()->GetDataResource("pickerButton.css"));
+ data->Append(Platform::Current()->GetDataResource("suggestionPicker.css"));
+ data->Append(Platform::Current()->GetDataResource("calendarPicker.css"));
+ AddString(
+ "</style></head><body><div id=main>Loading...</div><script>\n"
+ "window.dialogArguments = {\n",
+ data);
+ AddProperty("anchorRectInScreen", parameters_.anchor_rect_in_screen, data);
+ float scale_factor = chrome_client_->WindowToViewportScalar(1.0f);
+ AddProperty("zoomFactor", ZoomFactor() / scale_factor, data);
+ AddProperty("min",
+ ValueToDateTimeString(parameters_.minimum, parameters_.type),
+ data);
+ AddProperty("max",
+ ValueToDateTimeString(parameters_.maximum, parameters_.type),
+ data);
+ AddProperty("step", step_string, data);
+ AddProperty("stepBase", step_base_string, data);
+ AddProperty("required", parameters_.required, data);
+ AddProperty("currentValue",
+ ValueToDateTimeString(parameters_.double_value, parameters_.type),
+ data);
+ AddProperty("locale", parameters_.locale.GetString(), data);
+ AddProperty("todayLabel", today_label_string, data);
+ AddProperty("clearLabel",
+ GetLocale().QueryString(WebLocalizedString::kCalendarClear),
+ data);
+ AddProperty("weekLabel",
+ GetLocale().QueryString(WebLocalizedString::kWeekNumberLabel),
+ data);
+ AddProperty(
+ "axShowMonthSelector",
+ GetLocale().QueryString(WebLocalizedString::kAXCalendarShowMonthSelector),
+ data);
+ AddProperty(
+ "axShowNextMonth",
+ GetLocale().QueryString(WebLocalizedString::kAXCalendarShowNextMonth),
+ data);
+ AddProperty(
+ "axShowPreviousMonth",
+ GetLocale().QueryString(WebLocalizedString::kAXCalendarShowPreviousMonth),
+ data);
+ AddProperty("weekStartDay", locale_->FirstDayOfWeek(), data);
+ AddProperty("shortMonthLabels", locale_->ShortMonthLabels(), data);
+ AddProperty("dayLabels", locale_->WeekDayShortLabels(), data);
+ AddProperty("isLocaleRTL", locale_->IsRTL(), data);
+ AddProperty("isRTL", parameters_.is_anchor_element_rtl, data);
+ AddProperty("mode", parameters_.type.GetString(), data);
+ if (parameters_.suggestions.size()) {
+ Vector<String> suggestion_values;
+ Vector<String> localized_suggestion_values;
+ Vector<String> suggestion_labels;
+ for (unsigned i = 0; i < parameters_.suggestions.size(); i++) {
+ suggestion_values.push_back(ValueToDateTimeString(
+ parameters_.suggestions[i].value, parameters_.type));
+ localized_suggestion_values.push_back(
+ parameters_.suggestions[i].localized_value);
+ suggestion_labels.push_back(parameters_.suggestions[i].label);
+ }
+ AddProperty("suggestionValues", suggestion_values, data);
+ AddProperty("localizedSuggestionValues", localized_suggestion_values, data);
+ AddProperty("suggestionLabels", suggestion_labels, data);
+ AddProperty(
+ "inputWidth",
+ static_cast<unsigned>(parameters_.anchor_rect_in_screen.Width()), data);
+ AddProperty(
+ "showOtherDateEntry",
+ LayoutTheme::GetTheme().SupportsCalendarPicker(parameters_.type), data);
+ AddProperty("otherDateLabel", other_date_label_string, data);
+ AddProperty("suggestionHighlightColor",
+ LayoutTheme::GetTheme()
+ .ActiveListBoxSelectionBackgroundColor()
+ .Serialized(),
+ data);
+ AddProperty("suggestionHighlightTextColor",
+ LayoutTheme::GetTheme()
+ .ActiveListBoxSelectionForegroundColor()
+ .Serialized(),
+ data);
+ }
+ AddString("}\n", data);
+
+ data->Append(Platform::Current()->GetDataResource("pickerCommon.js"));
+ data->Append(Platform::Current()->GetDataResource("suggestionPicker.js"));
+ data->Append(Platform::Current()->GetDataResource("calendarPicker.js"));
+ AddString("</script></body>\n", data);
+}
+
+Element& DateTimeChooserImpl::OwnerElement() {
+ return client_->OwnerElement();
+}
+
+Locale& DateTimeChooserImpl::GetLocale() {
+ return *locale_;
+}
+
+void DateTimeChooserImpl::SetValueAndClosePopup(int num_value,
+ const String& string_value) {
+ if (num_value >= 0)
+ SetValue(string_value);
+ EndChooser();
+}
+
+void DateTimeChooserImpl::SetValue(const String& value) {
+ client_->DidChooseValue(value);
+}
+
+void DateTimeChooserImpl::ClosePopup() {
+ EndChooser();
+}
+
+void DateTimeChooserImpl::DidClosePopup() {
+ DCHECK(client_);
+ popup_ = nullptr;
+ client_->DidEndChooser();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..92cae9e460f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_IMPL_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_IMPL_H_
+
+#include <memory>
+#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/core/page/page_popup_client.h"
+
+namespace blink {
+
+class ChromeClient;
+class DateTimeChooserClient;
+class PagePopup;
+
+class CORE_EXPORT DateTimeChooserImpl final : public DateTimeChooser,
+ public PagePopupClient {
+ public:
+ static DateTimeChooserImpl* Create(ChromeClient*,
+ DateTimeChooserClient*,
+ const DateTimeChooserParameters&);
+ ~DateTimeChooserImpl() override;
+
+ // DateTimeChooser functions:
+ void EndChooser() override;
+ AXObject* RootAXObject() override;
+
+ void Trace(blink::Visitor*) override;
+
+ private:
+ DateTimeChooserImpl(ChromeClient*,
+ DateTimeChooserClient*,
+ const DateTimeChooserParameters&);
+ // 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 ClosePopup() override;
+ Element& OwnerElement() override;
+ void DidClosePopup() override;
+
+ Member<ChromeClient> chrome_client_;
+ Member<DateTimeChooserClient> client_;
+ PagePopup* popup_;
+ DateTimeChooserParameters parameters_;
+ std::unique_ptr<Locale> locale_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_IMPL_H_
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
new file mode 100644
index 00000000000..fd88a27f3d9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
@@ -0,0 +1,871 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_edit_element.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/css/style_change_reason.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/events/mouse_event.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_field_elements.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.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/style/computed_style.h"
+#include "third_party/blink/renderer/platform/fonts/font_cache.h"
+#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"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+class DateTimeEditBuilder : private DateTimeFormat::TokenHandler {
+ public:
+ // The argument objects must be alive until this object dies.
+ DateTimeEditBuilder(DateTimeEditElement&,
+ const DateTimeEditElement::LayoutParameters&,
+ const DateComponents&);
+
+ bool Build(const String&);
+
+ private:
+ bool NeedMillisecondField() const;
+ bool ShouldAMPMFieldDisabled() const;
+ bool ShouldDayOfMonthFieldDisabled() const;
+ bool ShouldHourFieldDisabled() const;
+ bool ShouldMillisecondFieldDisabled() const;
+ bool ShouldMinuteFieldDisabled() const;
+ bool ShouldSecondFieldDisabled() const;
+ bool ShouldYearFieldDisabled() const;
+ inline const StepRange& GetStepRange() const {
+ return parameters_.step_range;
+ }
+ DateTimeNumericFieldElement::Step CreateStep(double ms_per_field_unit,
+ double ms_per_field_size) const;
+
+ // DateTimeFormat::TokenHandler functions.
+ void VisitField(DateTimeFormat::FieldType, int) final;
+ void VisitLiteral(const String&) final;
+
+ DateTimeEditElement& EditElement() const;
+
+ Member<DateTimeEditElement> edit_element_;
+ const DateComponents date_value_;
+ const DateTimeEditElement::LayoutParameters& parameters_;
+ DateTimeNumericFieldElement::Range day_range_;
+ DateTimeNumericFieldElement::Range hour23_range_;
+ DateTimeNumericFieldElement::Range minute_range_;
+ DateTimeNumericFieldElement::Range second_range_;
+ DateTimeNumericFieldElement::Range millisecond_range_;
+};
+
+DateTimeEditBuilder::DateTimeEditBuilder(
+ DateTimeEditElement& element,
+ const DateTimeEditElement::LayoutParameters& layout_parameters,
+ const DateComponents& date_value)
+ : edit_element_(&element),
+ date_value_(date_value),
+ parameters_(layout_parameters),
+ day_range_(1, 31),
+ hour23_range_(0, 23),
+ minute_range_(0, 59),
+ second_range_(0, 59),
+ millisecond_range_(0, 999) {
+ if (date_value_.GetType() == DateComponents::kDate ||
+ date_value_.GetType() == DateComponents::kDateTimeLocal) {
+ if (parameters_.minimum.GetType() != DateComponents::kInvalid &&
+ parameters_.maximum.GetType() != DateComponents::kInvalid &&
+ parameters_.minimum.FullYear() == parameters_.maximum.FullYear() &&
+ parameters_.minimum.Month() == parameters_.maximum.Month() &&
+ parameters_.minimum.MonthDay() <= parameters_.maximum.MonthDay()) {
+ day_range_.minimum = parameters_.minimum.MonthDay();
+ day_range_.maximum = parameters_.maximum.MonthDay();
+ }
+ }
+
+ if (date_value_.GetType() == DateComponents::kTime ||
+ day_range_.IsSingleton()) {
+ if (parameters_.minimum.GetType() != DateComponents::kInvalid &&
+ parameters_.maximum.GetType() != DateComponents::kInvalid &&
+ parameters_.minimum.Hour() <= parameters_.maximum.Hour()) {
+ hour23_range_.minimum = parameters_.minimum.Hour();
+ hour23_range_.maximum = parameters_.maximum.Hour();
+ }
+ }
+
+ if (hour23_range_.IsSingleton() &&
+ parameters_.minimum.Minute() <= parameters_.maximum.Minute()) {
+ minute_range_.minimum = parameters_.minimum.Minute();
+ minute_range_.maximum = parameters_.maximum.Minute();
+ }
+ if (minute_range_.IsSingleton() &&
+ parameters_.minimum.Second() <= parameters_.maximum.Second()) {
+ second_range_.minimum = parameters_.minimum.Second();
+ second_range_.maximum = parameters_.maximum.Second();
+ }
+ if (second_range_.IsSingleton() &&
+ parameters_.minimum.Millisecond() <= parameters_.maximum.Millisecond()) {
+ millisecond_range_.minimum = parameters_.minimum.Millisecond();
+ millisecond_range_.maximum = parameters_.maximum.Millisecond();
+ }
+}
+
+bool DateTimeEditBuilder::Build(const String& format_string) {
+ EditElement().ResetFields();
+ return DateTimeFormat::Parse(format_string, *this);
+}
+
+bool DateTimeEditBuilder::NeedMillisecondField() const {
+ return date_value_.Millisecond() ||
+ !GetStepRange()
+ .Minimum()
+ .Remainder(static_cast<int>(kMsPerSecond))
+ .IsZero() ||
+ !GetStepRange()
+ .Step()
+ .Remainder(static_cast<int>(kMsPerSecond))
+ .IsZero();
+}
+
+void DateTimeEditBuilder::VisitField(DateTimeFormat::FieldType field_type,
+ int count) {
+ const int kCountForAbbreviatedMonth = 3;
+ const int kCountForFullMonth = 4;
+ const int kCountForNarrowMonth = 5;
+ Document& document = EditElement().GetDocument();
+
+ switch (field_type) {
+ case DateTimeFormat::kFieldTypeDayOfMonth: {
+ DateTimeFieldElement* field = DateTimeDayFieldElement::Create(
+ document, EditElement(), parameters_.placeholder_for_day, day_range_);
+ EditElement().AddField(field);
+ if (ShouldDayOfMonthFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeHour11: {
+ DateTimeNumericFieldElement::Step step =
+ CreateStep(kMsPerHour, kMsPerHour * 12);
+ DateTimeFieldElement* field = DateTimeHour11FieldElement::Create(
+ document, EditElement(), hour23_range_, step);
+ EditElement().AddField(field);
+ if (ShouldHourFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeHour12: {
+ DateTimeNumericFieldElement::Step step =
+ CreateStep(kMsPerHour, kMsPerHour * 12);
+ DateTimeFieldElement* field = DateTimeHour12FieldElement::Create(
+ document, EditElement(), hour23_range_, step);
+ EditElement().AddField(field);
+ if (ShouldHourFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeHour23: {
+ DateTimeNumericFieldElement::Step step =
+ CreateStep(kMsPerHour, kMsPerDay);
+ DateTimeFieldElement* field = DateTimeHour23FieldElement::Create(
+ document, EditElement(), hour23_range_, step);
+ EditElement().AddField(field);
+ if (ShouldHourFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeHour24: {
+ DateTimeNumericFieldElement::Step step =
+ CreateStep(kMsPerHour, kMsPerDay);
+ DateTimeFieldElement* field = DateTimeHour24FieldElement::Create(
+ document, EditElement(), hour23_range_, step);
+ EditElement().AddField(field);
+ if (ShouldHourFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeMinute: {
+ DateTimeNumericFieldElement::Step step =
+ CreateStep(kMsPerMinute, kMsPerHour);
+ DateTimeNumericFieldElement* field = DateTimeMinuteFieldElement::Create(
+ document, EditElement(), minute_range_, step);
+ EditElement().AddField(field);
+ if (ShouldMinuteFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeMonth: // Fallthrough.
+ case DateTimeFormat::kFieldTypeMonthStandAlone: {
+ int min_month = 0, max_month = 11;
+ if (parameters_.minimum.GetType() != DateComponents::kInvalid &&
+ parameters_.maximum.GetType() != DateComponents::kInvalid &&
+ parameters_.minimum.FullYear() == parameters_.maximum.FullYear() &&
+ parameters_.minimum.Month() <= parameters_.maximum.Month()) {
+ min_month = parameters_.minimum.Month();
+ max_month = parameters_.maximum.Month();
+ }
+ DateTimeFieldElement* field;
+ switch (count) {
+ case kCountForNarrowMonth: // Fallthrough.
+ case kCountForAbbreviatedMonth:
+ field = DateTimeSymbolicMonthFieldElement::Create(
+ document, EditElement(),
+ field_type == DateTimeFormat::kFieldTypeMonth
+ ? parameters_.locale.ShortMonthLabels()
+ : parameters_.locale.ShortStandAloneMonthLabels(),
+ min_month, max_month);
+ break;
+ case kCountForFullMonth:
+ field = DateTimeSymbolicMonthFieldElement::Create(
+ document, EditElement(),
+ field_type == DateTimeFormat::kFieldTypeMonth
+ ? parameters_.locale.MonthLabels()
+ : parameters_.locale.StandAloneMonthLabels(),
+ min_month, max_month);
+ break;
+ default:
+ field = DateTimeMonthFieldElement::Create(
+ document, EditElement(), parameters_.placeholder_for_month,
+ DateTimeNumericFieldElement::Range(min_month + 1, max_month + 1));
+ break;
+ }
+ EditElement().AddField(field);
+ if (min_month == max_month && min_month == date_value_.Month() &&
+ date_value_.GetType() != DateComponents::kMonth) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypePeriod: {
+ DateTimeFieldElement* field = DateTimeAMPMFieldElement::Create(
+ document, EditElement(), parameters_.locale.TimeAMPMLabels());
+ EditElement().AddField(field);
+ if (ShouldAMPMFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeSecond: {
+ DateTimeNumericFieldElement::Step step =
+ CreateStep(kMsPerSecond, kMsPerMinute);
+ DateTimeNumericFieldElement* field = DateTimeSecondFieldElement::Create(
+ document, EditElement(), second_range_, step);
+ EditElement().AddField(field);
+ if (ShouldSecondFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+
+ if (NeedMillisecondField()) {
+ VisitLiteral(parameters_.locale.LocalizedDecimalSeparator());
+ VisitField(DateTimeFormat::kFieldTypeFractionalSecond, 3);
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeFractionalSecond: {
+ DateTimeNumericFieldElement::Step step = CreateStep(1, kMsPerSecond);
+ DateTimeNumericFieldElement* field =
+ DateTimeMillisecondFieldElement::Create(document, EditElement(),
+ millisecond_range_, step);
+ EditElement().AddField(field);
+ if (ShouldMillisecondFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeWeekOfYear: {
+ DateTimeNumericFieldElement::Range range(
+ DateComponents::kMinimumWeekNumber,
+ DateComponents::kMaximumWeekNumber);
+ if (parameters_.minimum.GetType() != DateComponents::kInvalid &&
+ parameters_.maximum.GetType() != DateComponents::kInvalid &&
+ parameters_.minimum.FullYear() == parameters_.maximum.FullYear() &&
+ parameters_.minimum.Week() <= parameters_.maximum.Week()) {
+ range.minimum = parameters_.minimum.Week();
+ range.maximum = parameters_.maximum.Week();
+ }
+ EditElement().AddField(
+ DateTimeWeekFieldElement::Create(document, EditElement(), range));
+ return;
+ }
+
+ case DateTimeFormat::kFieldTypeYear: {
+ DateTimeYearFieldElement::Parameters year_params;
+ if (parameters_.minimum.GetType() == DateComponents::kInvalid) {
+ year_params.minimum_year = DateComponents::MinimumYear();
+ year_params.min_is_specified = false;
+ } else {
+ year_params.minimum_year = parameters_.minimum.FullYear();
+ year_params.min_is_specified = true;
+ }
+ if (parameters_.maximum.GetType() == DateComponents::kInvalid) {
+ year_params.maximum_year = DateComponents::MaximumYear();
+ year_params.max_is_specified = false;
+ } else {
+ year_params.maximum_year = parameters_.maximum.FullYear();
+ year_params.max_is_specified = true;
+ }
+ if (year_params.minimum_year > year_params.maximum_year) {
+ std::swap(year_params.minimum_year, year_params.maximum_year);
+ std::swap(year_params.min_is_specified, year_params.max_is_specified);
+ }
+ year_params.placeholder = parameters_.placeholder_for_year;
+ DateTimeFieldElement* field = DateTimeYearFieldElement::Create(
+ document, EditElement(), year_params);
+ EditElement().AddField(field);
+ if (ShouldYearFieldDisabled()) {
+ field->SetValueAsDate(date_value_);
+ field->SetDisabled();
+ }
+ return;
+ }
+
+ default:
+ return;
+ }
+}
+
+bool DateTimeEditBuilder::ShouldAMPMFieldDisabled() const {
+ return ShouldHourFieldDisabled() ||
+ (hour23_range_.minimum < 12 && hour23_range_.maximum < 12 &&
+ date_value_.Hour() < 12) ||
+ (hour23_range_.minimum >= 12 && hour23_range_.maximum >= 12 &&
+ date_value_.Hour() >= 12);
+}
+
+bool DateTimeEditBuilder::ShouldDayOfMonthFieldDisabled() const {
+ return day_range_.IsSingleton() &&
+ day_range_.minimum == date_value_.MonthDay() &&
+ date_value_.GetType() != DateComponents::kDate;
+}
+
+bool DateTimeEditBuilder::ShouldHourFieldDisabled() const {
+ if (hour23_range_.IsSingleton() &&
+ hour23_range_.minimum == date_value_.Hour() &&
+ !(ShouldMinuteFieldDisabled() && ShouldSecondFieldDisabled() &&
+ ShouldMillisecondFieldDisabled()))
+ return true;
+
+ if (date_value_.GetType() == DateComponents::kTime)
+ return false;
+ DCHECK_EQ(date_value_.GetType(), DateComponents::kDateTimeLocal);
+
+ if (ShouldDayOfMonthFieldDisabled()) {
+ DCHECK_EQ(parameters_.minimum.FullYear(), parameters_.maximum.FullYear());
+ DCHECK_EQ(parameters_.minimum.Month(), parameters_.maximum.Month());
+ return false;
+ }
+
+ const Decimal decimal_ms_per_day(static_cast<int>(kMsPerDay));
+ Decimal hour_part_of_minimum =
+ (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_day) /
+ static_cast<int>(kMsPerHour))
+ .Floor();
+ return hour_part_of_minimum == date_value_.Hour() &&
+ GetStepRange().Step().Remainder(decimal_ms_per_day).IsZero();
+}
+
+bool DateTimeEditBuilder::ShouldMillisecondFieldDisabled() const {
+ if (millisecond_range_.IsSingleton() &&
+ millisecond_range_.minimum == date_value_.Millisecond())
+ return true;
+
+ const Decimal decimal_ms_per_second(static_cast<int>(kMsPerSecond));
+ return GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_second) ==
+ date_value_.Millisecond() &&
+ GetStepRange().Step().Remainder(decimal_ms_per_second).IsZero();
+}
+
+bool DateTimeEditBuilder::ShouldMinuteFieldDisabled() const {
+ if (minute_range_.IsSingleton() &&
+ minute_range_.minimum == date_value_.Minute())
+ return true;
+
+ const Decimal decimal_ms_per_hour(static_cast<int>(kMsPerHour));
+ Decimal minute_part_of_minimum =
+ (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_hour) /
+ static_cast<int>(kMsPerMinute))
+ .Floor();
+ return minute_part_of_minimum == date_value_.Minute() &&
+ GetStepRange().Step().Remainder(decimal_ms_per_hour).IsZero();
+}
+
+bool DateTimeEditBuilder::ShouldSecondFieldDisabled() const {
+ if (second_range_.IsSingleton() &&
+ second_range_.minimum == date_value_.Second())
+ return true;
+
+ const Decimal decimal_ms_per_minute(static_cast<int>(kMsPerMinute));
+ Decimal second_part_of_minimum =
+ (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_minute) /
+ static_cast<int>(kMsPerSecond))
+ .Floor();
+ return second_part_of_minimum == date_value_.Second() &&
+ GetStepRange().Step().Remainder(decimal_ms_per_minute).IsZero();
+}
+
+bool DateTimeEditBuilder::ShouldYearFieldDisabled() const {
+ return parameters_.minimum.GetType() != DateComponents::kInvalid &&
+ parameters_.maximum.GetType() != DateComponents::kInvalid &&
+ parameters_.minimum.FullYear() == parameters_.maximum.FullYear() &&
+ parameters_.minimum.FullYear() == date_value_.FullYear();
+}
+
+void DateTimeEditBuilder::VisitLiteral(const String& text) {
+ DEFINE_STATIC_LOCAL(AtomicString, text_pseudo_id,
+ ("-webkit-datetime-edit-text"));
+ DCHECK_GT(text.length(), 0u);
+ HTMLDivElement* element = HTMLDivElement::Create(EditElement().GetDocument());
+ element->SetShadowPseudoId(text_pseudo_id);
+ if (parameters_.locale.IsRTL() && text.length()) {
+ WTF::Unicode::CharDirection dir = WTF::Unicode::Direction(text[0]);
+ if (dir == WTF::Unicode::kSegmentSeparator ||
+ dir == WTF::Unicode::kWhiteSpaceNeutral ||
+ dir == WTF::Unicode::kOtherNeutral)
+ element->AppendChild(Text::Create(EditElement().GetDocument(),
+ String(&kRightToLeftMarkCharacter, 1)));
+ }
+ element->AppendChild(Text::Create(EditElement().GetDocument(), text));
+ EditElement().FieldsWrapperElement()->AppendChild(element);
+}
+
+DateTimeEditElement& DateTimeEditBuilder::EditElement() const {
+ return *edit_element_;
+}
+
+DateTimeNumericFieldElement::Step DateTimeEditBuilder::CreateStep(
+ double ms_per_field_unit,
+ double ms_per_field_size) const {
+ const Decimal ms_per_field_unit_decimal(static_cast<int>(ms_per_field_unit));
+ const Decimal ms_per_field_size_decimal(static_cast<int>(ms_per_field_size));
+ Decimal step_milliseconds = GetStepRange().Step();
+ DCHECK(!ms_per_field_unit_decimal.IsZero());
+ DCHECK(!ms_per_field_size_decimal.IsZero());
+ DCHECK(!step_milliseconds.IsZero());
+
+ DateTimeNumericFieldElement::Step step(1, 0);
+
+ if (step_milliseconds.Remainder(ms_per_field_size_decimal).IsZero())
+ step_milliseconds = ms_per_field_size_decimal;
+
+ if (ms_per_field_size_decimal.Remainder(step_milliseconds).IsZero() &&
+ step_milliseconds.Remainder(ms_per_field_unit_decimal).IsZero()) {
+ step.step = static_cast<int>(
+ (step_milliseconds / ms_per_field_unit_decimal).ToDouble());
+ step.step_base = static_cast<int>(
+ (GetStepRange().StepBase() / ms_per_field_unit_decimal)
+ .Floor()
+ .Remainder(ms_per_field_size_decimal / ms_per_field_unit_decimal)
+ .ToDouble());
+ }
+ return step;
+}
+
+// ----------------------------
+
+DateTimeEditElement::EditControlOwner::~EditControlOwner() = default;
+
+DateTimeEditElement::DateTimeEditElement(Document& document,
+ EditControlOwner& edit_control_owner)
+ : HTMLDivElement(document), edit_control_owner_(&edit_control_owner) {
+ SetHasCustomStyleCallbacks();
+}
+
+DateTimeEditElement::~DateTimeEditElement() = default;
+
+void DateTimeEditElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(fields_);
+ visitor->Trace(edit_control_owner_);
+ HTMLDivElement::Trace(visitor);
+}
+
+inline Element* DateTimeEditElement::FieldsWrapperElement() const {
+ DCHECK(firstChild());
+ return ToElementOrDie(firstChild());
+}
+
+void DateTimeEditElement::AddField(DateTimeFieldElement* field) {
+ if (fields_.size() >= kMaximumNumberOfFields)
+ return;
+ fields_.push_back(field);
+ FieldsWrapperElement()->AppendChild(field);
+}
+
+bool DateTimeEditElement::AnyEditableFieldsHaveValues() const {
+ for (const auto& field : fields_) {
+ if (!field->IsDisabled() && field->HasValue())
+ return true;
+ }
+ return false;
+}
+
+void DateTimeEditElement::BlurByOwner() {
+ if (DateTimeFieldElement* field = FocusedField())
+ field->blur();
+}
+
+DateTimeEditElement* DateTimeEditElement::Create(
+ Document& document,
+ EditControlOwner& edit_control_owner) {
+ DateTimeEditElement* container =
+ new DateTimeEditElement(document, edit_control_owner);
+ container->SetShadowPseudoId(AtomicString("-webkit-datetime-edit"));
+ container->setAttribute(idAttr, ShadowElementNames::DateTimeEdit());
+ return container;
+}
+
+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);
+ float width = 0;
+ for (Node* child = FieldsWrapperElement()->firstChild(); child;
+ child = child->nextSibling()) {
+ if (!child->IsElementNode())
+ continue;
+ Element* child_element = ToElement(child);
+ if (child_element->IsDateTimeFieldElement()) {
+ // We need to pass the ComputedStyle of this element because child
+ // elements can't resolve inherited style at this timing.
+ width += static_cast<DateTimeFieldElement*>(child_element)
+ ->MaximumWidth(*style);
+ } else {
+ // ::-webkit-datetime-edit-text case. It has no
+ // border/padding/margin in html.css.
+ width += DateTimeFieldElement::ComputeTextWidth(
+ *style, child_element->textContent());
+ }
+ }
+ style->SetWidth(Length(ceilf(width), kFixed));
+ style->SetUnique();
+ return style;
+}
+
+void DateTimeEditElement::DidBlurFromField(WebFocusType focus_type) {
+ if (edit_control_owner_)
+ edit_control_owner_->DidBlurFromControl(focus_type);
+}
+
+void DateTimeEditElement::DidFocusOnField(WebFocusType focus_type) {
+ if (edit_control_owner_)
+ edit_control_owner_->DidFocusOnControl(focus_type);
+}
+
+void DateTimeEditElement::DisabledStateChanged() {
+ UpdateUIState();
+}
+
+DateTimeFieldElement* DateTimeEditElement::FieldAt(size_t field_index) const {
+ return field_index < fields_.size() ? fields_[field_index].Get() : nullptr;
+}
+
+size_t DateTimeEditElement::FieldIndexOf(
+ const DateTimeFieldElement& field) const {
+ for (size_t field_index = 0; field_index < fields_.size(); ++field_index) {
+ if (fields_[field_index] == &field)
+ return field_index;
+ }
+ return kInvalidFieldIndex;
+}
+
+void DateTimeEditElement::FocusIfNoFocus() {
+ if (FocusedFieldIndex() != kInvalidFieldIndex)
+ return;
+ FocusOnNextFocusableField(0);
+}
+
+void DateTimeEditElement::FocusByOwner(Element* old_focused_element) {
+ if (old_focused_element && old_focused_element->IsDateTimeFieldElement()) {
+ DateTimeFieldElement* old_focused_field =
+ static_cast<DateTimeFieldElement*>(old_focused_element);
+ size_t index = FieldIndexOf(*old_focused_field);
+ GetDocument().UpdateStyleAndLayoutTreeForNode(old_focused_field);
+ if (index != kInvalidFieldIndex && old_focused_field->IsFocusable()) {
+ old_focused_field->focus();
+ return;
+ }
+ }
+ FocusOnNextFocusableField(0);
+}
+
+DateTimeFieldElement* DateTimeEditElement::FocusedField() const {
+ return FieldAt(FocusedFieldIndex());
+}
+
+size_t DateTimeEditElement::FocusedFieldIndex() const {
+ Element* const focused_field_element = GetDocument().FocusedElement();
+ for (size_t field_index = 0; field_index < fields_.size(); ++field_index) {
+ if (fields_[field_index] == focused_field_element)
+ return field_index;
+ }
+ return kInvalidFieldIndex;
+}
+
+void DateTimeEditElement::FieldValueChanged() {
+ if (edit_control_owner_)
+ edit_control_owner_->EditControlValueChanged();
+}
+
+bool DateTimeEditElement::FocusOnNextFocusableField(size_t start_index) {
+ GetDocument().UpdateStyleAndLayoutTreeIgnorePendingStylesheets();
+ for (size_t field_index = start_index; field_index < fields_.size();
+ ++field_index) {
+ if (fields_[field_index]->IsFocusable()) {
+ fields_[field_index]->focus();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool DateTimeEditElement::FocusOnNextField(const DateTimeFieldElement& field) {
+ const size_t start_field_index = FieldIndexOf(field);
+ if (start_field_index == kInvalidFieldIndex)
+ return false;
+ return FocusOnNextFocusableField(start_field_index + 1);
+}
+
+bool DateTimeEditElement::FocusOnPreviousField(
+ const DateTimeFieldElement& field) {
+ const size_t start_field_index = FieldIndexOf(field);
+ if (start_field_index == kInvalidFieldIndex)
+ return false;
+ GetDocument().UpdateStyleAndLayoutTreeIgnorePendingStylesheets();
+ size_t field_index = start_field_index;
+ while (field_index > 0) {
+ --field_index;
+ if (fields_[field_index]->IsFocusable()) {
+ fields_[field_index]->focus();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool DateTimeEditElement::IsDateTimeEditElement() const {
+ return true;
+}
+
+bool DateTimeEditElement::IsDisabled() const {
+ return edit_control_owner_ &&
+ edit_control_owner_->IsEditControlOwnerDisabled();
+}
+
+bool DateTimeEditElement::IsFieldOwnerDisabled() const {
+ return IsDisabled();
+}
+
+bool DateTimeEditElement::IsFieldOwnerReadOnly() const {
+ return IsReadOnly();
+}
+
+bool DateTimeEditElement::IsReadOnly() const {
+ return edit_control_owner_ &&
+ edit_control_owner_->IsEditControlOwnerReadOnly();
+}
+
+void DateTimeEditElement::GetLayout(const LayoutParameters& layout_parameters,
+ const DateComponents& date_value) {
+ // TODO(tkent): We assume this function never dispatches events. However this
+ // can dispatch 'blur' event in Node::removeChild().
+
+ DEFINE_STATIC_LOCAL(AtomicString, fields_wrapper_pseudo_id,
+ ("-webkit-datetime-edit-fields-wrapper"));
+ if (!HasChildren()) {
+ HTMLDivElement* element = HTMLDivElement::Create(GetDocument());
+ element->SetShadowPseudoId(fields_wrapper_pseudo_id);
+ AppendChild(element);
+ }
+ Element* fields_wrapper = FieldsWrapperElement();
+
+ size_t focused_field_index = FocusedFieldIndex();
+ DateTimeFieldElement* const focused_field = FieldAt(focused_field_index);
+ const AtomicString focused_field_id =
+ focused_field ? focused_field->ShadowPseudoId() : g_null_atom;
+
+ DateTimeEditBuilder builder(*this, layout_parameters, date_value);
+ Node* last_child_to_be_removed = fields_wrapper->lastChild();
+ if (!builder.Build(layout_parameters.date_time_format) || fields_.IsEmpty()) {
+ last_child_to_be_removed = fields_wrapper->lastChild();
+ builder.Build(layout_parameters.fallback_date_time_format);
+ }
+
+ if (focused_field_index != kInvalidFieldIndex) {
+ for (size_t field_index = 0; field_index < fields_.size(); ++field_index) {
+ if (fields_[field_index]->ShadowPseudoId() == focused_field_id) {
+ focused_field_index = field_index;
+ break;
+ }
+ }
+ if (DateTimeFieldElement* field =
+ FieldAt(std::min(focused_field_index, fields_.size() - 1)))
+ field->focus();
+ }
+
+ if (last_child_to_be_removed) {
+ for (Node* child_node = fields_wrapper->firstChild(); child_node;
+ child_node = fields_wrapper->firstChild()) {
+ fields_wrapper->RemoveChild(child_node);
+ if (child_node == last_child_to_be_removed)
+ break;
+ }
+ SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::Create(StyleChangeReason::kControl));
+ }
+}
+
+AtomicString DateTimeEditElement::LocaleIdentifier() const {
+ return edit_control_owner_ ? edit_control_owner_->LocaleIdentifier()
+ : g_null_atom;
+}
+
+void DateTimeEditElement::FieldDidChangeValueByKeyboard() {
+ if (edit_control_owner_)
+ edit_control_owner_->EditControlDidChangeValueByKeyboard();
+}
+
+void DateTimeEditElement::ReadOnlyStateChanged() {
+ UpdateUIState();
+}
+
+void DateTimeEditElement::ResetFields() {
+ for (const auto& field : fields_)
+ field->RemoveEventHandler();
+ fields_.Shrink(0);
+}
+
+void DateTimeEditElement::DefaultEventHandler(Event* event) {
+ // In case of control owner forward event to control, e.g. DOM
+ // dispatchEvent method.
+ if (DateTimeFieldElement* field = FocusedField()) {
+ field->DefaultEventHandler(event);
+ if (event->DefaultHandled())
+ return;
+ }
+
+ HTMLDivElement::DefaultEventHandler(event);
+}
+
+void DateTimeEditElement::SetValueAsDate(
+ const LayoutParameters& layout_parameters,
+ const DateComponents& date) {
+ GetLayout(layout_parameters, date);
+ for (const auto& field : fields_)
+ field->SetValueAsDate(date);
+}
+
+void DateTimeEditElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ for (const auto& field : fields_)
+ field->SetValueAsDateTimeFieldsState(date_time_fields_state);
+}
+
+void DateTimeEditElement::SetEmptyValue(
+ const LayoutParameters& layout_parameters,
+ const DateComponents& date_for_read_only_field) {
+ GetLayout(layout_parameters, date_for_read_only_field);
+ for (const auto& field : fields_)
+ field->SetEmptyValue(DateTimeFieldElement::kDispatchNoEvent);
+}
+
+bool DateTimeEditElement::HasFocusedField() {
+ return FocusedFieldIndex() != kInvalidFieldIndex;
+}
+
+void DateTimeEditElement::SetOnlyYearMonthDay(const DateComponents& date) {
+ DCHECK_EQ(date.GetType(), DateComponents::kDate);
+
+ if (!edit_control_owner_)
+ return;
+
+ DateTimeFieldsState date_time_fields_state = ValueAsDateTimeFieldsState();
+ date_time_fields_state.SetYear(date.FullYear());
+ date_time_fields_state.SetMonth(date.Month() + 1);
+ date_time_fields_state.SetDayOfMonth(date.MonthDay());
+ SetValueAsDateTimeFieldsState(date_time_fields_state);
+ edit_control_owner_->EditControlValueChanged();
+}
+
+void DateTimeEditElement::StepDown() {
+ if (DateTimeFieldElement* const field = FocusedField())
+ field->StepDown();
+}
+
+void DateTimeEditElement::StepUp() {
+ if (DateTimeFieldElement* const field = FocusedField())
+ field->StepUp();
+}
+
+void DateTimeEditElement::UpdateUIState() {
+ if (IsDisabled()) {
+ if (DateTimeFieldElement* field = FocusedField())
+ field->blur();
+ }
+}
+
+String DateTimeEditElement::Value() const {
+ if (!edit_control_owner_)
+ return g_empty_string;
+ return edit_control_owner_->FormatDateTimeFieldsState(
+ ValueAsDateTimeFieldsState());
+}
+
+DateTimeFieldsState DateTimeEditElement::ValueAsDateTimeFieldsState() const {
+ DateTimeFieldsState date_time_fields_state;
+ for (const auto& field : fields_)
+ field->PopulateDateTimeFieldsState(date_time_fields_state);
+ return date_time_fields_state;
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..68bea3cff35
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_EDIT_ELEMENT_H_
+#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/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/date_components.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+class DateTimeFieldsState;
+class Locale;
+class StepRange;
+
+// DateTimeEditElement class contains numberic field and symbolc field for
+// representing date and time, such as
+// - Year, Month, Day Of Month
+// - Hour, Minute, Second, Millisecond, AM/PM
+class DateTimeEditElement final : public HTMLDivElement,
+ public DateTimeFieldElement::FieldOwner {
+ USING_GARBAGE_COLLECTED_MIXIN(DateTimeEditElement);
+
+ public:
+ // EditControlOwner implementer must call removeEditControlOwner when
+ // it doesn't handle event, e.g. at destruction.
+ class EditControlOwner : public GarbageCollectedMixin {
+ public:
+ virtual ~EditControlOwner();
+ virtual void DidBlurFromControl(WebFocusType) = 0;
+ virtual void DidFocusOnControl(WebFocusType) = 0;
+ virtual void EditControlValueChanged() = 0;
+ virtual String FormatDateTimeFieldsState(
+ const DateTimeFieldsState&) const = 0;
+ virtual bool IsEditControlOwnerDisabled() const = 0;
+ virtual bool IsEditControlOwnerReadOnly() const = 0;
+ virtual AtomicString LocaleIdentifier() const = 0;
+ virtual void EditControlDidChangeValueByKeyboard() = 0;
+ };
+
+ struct LayoutParameters {
+ STACK_ALLOCATED();
+ String date_time_format;
+ String fallback_date_time_format;
+ Locale& locale;
+ const StepRange step_range;
+ DateComponents minimum;
+ DateComponents maximum;
+ String placeholder_for_day;
+ String placeholder_for_month;
+ String placeholder_for_year;
+
+ LayoutParameters(Locale& locale, const StepRange& step_range)
+ : locale(locale), step_range(step_range) {}
+ };
+
+ static DateTimeEditElement* Create(Document&, EditControlOwner&);
+
+ ~DateTimeEditElement() override;
+ void Trace(blink::Visitor*) override;
+
+ void AddField(DateTimeFieldElement*);
+ bool AnyEditableFieldsHaveValues() const;
+ void BlurByOwner();
+ void DefaultEventHandler(Event*) override;
+ void DisabledStateChanged();
+ Element* FieldsWrapperElement() const;
+ void FocusIfNoFocus();
+ // If oldFocusedNode is one of sub-fields, focus on it. Otherwise focus on
+ // the first sub-field.
+ void FocusByOwner(Element* old_focused_element = nullptr);
+ bool HasFocusedField();
+ void ReadOnlyStateChanged();
+ void RemoveEditControlOwner() { edit_control_owner_ = nullptr; }
+ void ResetFields();
+ void SetEmptyValue(const LayoutParameters&,
+ const DateComponents& date_for_read_only_field);
+ void SetValueAsDate(const LayoutParameters&, const DateComponents&);
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&);
+ void SetOnlyYearMonthDay(const DateComponents&);
+ void StepDown();
+ void StepUp();
+ String Value() const;
+ DateTimeFieldsState ValueAsDateTimeFieldsState() const;
+
+ private:
+ static const size_t kInvalidFieldIndex = static_cast<size_t>(-1);
+
+ // Datetime can be represent at most five fields, such as
+ // 1. year
+ // 2. month
+ // 3. day-of-month
+ // 4. hour
+ // 5. minute
+ // 6. second
+ // 7. millisecond
+ // 8. AM/PM
+ static const int kMaximumNumberOfFields = 8;
+
+ DateTimeEditElement(Document&, EditControlOwner&);
+
+ DateTimeFieldElement* FieldAt(size_t) const;
+ size_t FieldIndexOf(const DateTimeFieldElement&) const;
+ DateTimeFieldElement* FocusedField() const;
+ size_t FocusedFieldIndex() const;
+ bool FocusOnNextFocusableField(size_t start_index);
+ bool IsDisabled() const;
+ bool IsReadOnly() const;
+ void GetLayout(const LayoutParameters&, const DateComponents&);
+ void UpdateUIState();
+
+ // Element function.
+ scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override;
+ bool IsDateTimeEditElement() const override;
+
+ // DateTimeFieldElement::FieldOwner functions.
+ void DidBlurFromField(WebFocusType) override;
+ void DidFocusOnField(WebFocusType) override;
+ void FieldValueChanged() override;
+ bool FocusOnNextField(const DateTimeFieldElement&) override;
+ bool FocusOnPreviousField(const DateTimeFieldElement&) override;
+ bool IsFieldOwnerDisabled() const override;
+ bool IsFieldOwnerReadOnly() const override;
+ AtomicString LocaleIdentifier() const override;
+ void FieldDidChangeValueByKeyboard() override;
+
+ HeapVector<Member<DateTimeFieldElement>, kMaximumNumberOfFields> fields_;
+ Member<EditControlOwner> edit_control_owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeEditElement);
+};
+
+DEFINE_TYPE_CASTS(DateTimeEditElement,
+ Element,
+ element,
+ element->IsDateTimeEditElement(),
+ element.IsDateTimeEditElement());
+
+} // namespace blink
+
+#endif
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
new file mode 100644
index 00000000000..6f2bfbaf0d2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_field_element.h"
+
+#include "third_party/blink/renderer/core/css/style_change_reason.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/layout/text_run_constructor.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+DateTimeFieldElement::FieldOwner::~FieldOwner() = default;
+
+DateTimeFieldElement::DateTimeFieldElement(Document& document,
+ FieldOwner& field_owner)
+ : HTMLSpanElement(document), field_owner_(&field_owner) {}
+
+void DateTimeFieldElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(field_owner_);
+ HTMLSpanElement::Trace(visitor);
+}
+
+float DateTimeFieldElement::ComputeTextWidth(const ComputedStyle& style,
+ const String& text) {
+ return style.GetFont().Width(ConstructTextRun(style.GetFont(), text, style));
+}
+
+void DateTimeFieldElement::DefaultEventHandler(Event* event) {
+ if (event->IsKeyboardEvent()) {
+ KeyboardEvent* keyboard_event = ToKeyboardEvent(event);
+ if (!IsDisabled() && !IsFieldOwnerDisabled() && !IsFieldOwnerReadOnly()) {
+ HandleKeyboardEvent(keyboard_event);
+ if (keyboard_event->DefaultHandled()) {
+ if (field_owner_)
+ field_owner_->FieldDidChangeValueByKeyboard();
+ return;
+ }
+ }
+ DefaultKeyboardEventHandler(keyboard_event);
+ if (field_owner_)
+ field_owner_->FieldDidChangeValueByKeyboard();
+ if (keyboard_event->DefaultHandled())
+ return;
+ }
+
+ HTMLElement::DefaultEventHandler(event);
+}
+
+void DateTimeFieldElement::DefaultKeyboardEventHandler(
+ KeyboardEvent* keyboard_event) {
+ if (keyboard_event->type() != EventTypeNames::keydown)
+ return;
+
+ if (IsDisabled() || IsFieldOwnerDisabled())
+ return;
+
+ const String& key = keyboard_event->key();
+
+ if (key == "ArrowLeft") {
+ if (!field_owner_)
+ return;
+ // FIXME: We'd like to use FocusController::advanceFocus(FocusDirectionLeft,
+ // ...) but it doesn't work for shadow nodes. webkit.org/b/104650
+ if (!LocaleForOwner().IsRTL() && field_owner_->FocusOnPreviousField(*this))
+ keyboard_event->SetDefaultHandled();
+ return;
+ }
+
+ if (key == "ArrowRight") {
+ if (!field_owner_)
+ return;
+ // FIXME: We'd like to use
+ // FocusController::advanceFocus(FocusDirectionRight, ...)
+ // but it doesn't work for shadow nodes. webkit.org/b/104650
+ if (!LocaleForOwner().IsRTL() && field_owner_->FocusOnNextField(*this))
+ keyboard_event->SetDefaultHandled();
+ return;
+ }
+
+ if (IsFieldOwnerReadOnly())
+ return;
+
+ if (key == "ArrowDown") {
+ if (keyboard_event->getModifierState("Alt"))
+ return;
+ keyboard_event->SetDefaultHandled();
+ StepDown();
+ return;
+ }
+
+ if (key == "ArrowUp") {
+ keyboard_event->SetDefaultHandled();
+ StepUp();
+ return;
+ }
+
+ if (key == "Backspace" || key == "Delete") {
+ keyboard_event->SetDefaultHandled();
+ SetEmptyValue(kDispatchEvent);
+ return;
+ }
+}
+
+void DateTimeFieldElement::SetFocused(bool value, WebFocusType focus_type) {
+ if (field_owner_) {
+ if (value) {
+ field_owner_->DidFocusOnField(focus_type);
+ } else {
+ field_owner_->DidBlurFromField(focus_type);
+ }
+ }
+
+ ContainerNode::SetFocused(value, focus_type);
+}
+
+void DateTimeFieldElement::FocusOnNextField() {
+ if (!field_owner_)
+ return;
+ field_owner_->FocusOnNextField(*this);
+}
+
+void DateTimeFieldElement::Initialize(const AtomicString& pseudo,
+ const String& ax_help_text,
+ int ax_minimum,
+ int ax_maximum) {
+ // On accessibility, DateTimeFieldElement acts like spin button.
+ setAttribute(roleAttr, AtomicString("spinbutton"));
+ setAttribute(aria_valuetextAttr, AtomicString(VisibleValue()));
+ setAttribute(aria_valueminAttr, AtomicString::Number(ax_minimum));
+ setAttribute(aria_valuemaxAttr, AtomicString::Number(ax_maximum));
+
+ setAttribute(aria_helpAttr, AtomicString(ax_help_text));
+ SetShadowPseudoId(pseudo);
+ AppendChild(Text::Create(GetDocument(), VisibleValue()));
+}
+
+bool DateTimeFieldElement::IsDateTimeFieldElement() const {
+ return true;
+}
+
+bool DateTimeFieldElement::IsFieldOwnerDisabled() const {
+ return field_owner_ && field_owner_->IsFieldOwnerDisabled();
+}
+
+bool DateTimeFieldElement::IsFieldOwnerReadOnly() const {
+ return field_owner_ && field_owner_->IsFieldOwnerReadOnly();
+}
+
+bool DateTimeFieldElement::IsDisabled() const {
+ return FastHasAttribute(disabledAttr);
+}
+
+Locale& DateTimeFieldElement::LocaleForOwner() const {
+ return GetDocument().GetCachedLocale(LocaleIdentifier());
+}
+
+AtomicString DateTimeFieldElement::LocaleIdentifier() const {
+ return field_owner_ ? field_owner_->LocaleIdentifier() : g_null_atom;
+}
+
+float DateTimeFieldElement::MaximumWidth(const ComputedStyle&) {
+ const float kPaddingLeftAndRight = 2; // This should match to html.css.
+ return kPaddingLeftAndRight;
+}
+
+void DateTimeFieldElement::SetDisabled() {
+ // Set HTML attribute disabled to change apperance.
+ SetBooleanAttribute(disabledAttr, true);
+ SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::CreateWithExtraData(
+ StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_disabled));
+}
+
+bool DateTimeFieldElement::SupportsFocus() const {
+ return !IsDisabled() && !IsFieldOwnerDisabled();
+}
+
+void DateTimeFieldElement::UpdateVisibleValue(EventBehavior event_behavior) {
+ Text* const text_node = ToText(firstChild());
+ const String new_visible_value = VisibleValue();
+ DCHECK_GT(new_visible_value.length(), 0u);
+
+ if (text_node->wholeText() == new_visible_value)
+ return;
+
+ text_node->ReplaceWholeText(new_visible_value);
+ if (HasValue()) {
+ setAttribute(aria_valuenowAttr,
+ AtomicString::Number(ValueForARIAValueNow()));
+ } else {
+ removeAttribute(aria_valuenowAttr);
+ }
+ setAttribute(aria_valuetextAttr, AtomicString(new_visible_value));
+
+ if (event_behavior == kDispatchEvent && field_owner_)
+ field_owner_->FieldValueChanged();
+}
+
+int DateTimeFieldElement::ValueForARIAValueNow() const {
+ return ValueAsInteger();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..94e41be73c7
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENT_H_
+#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/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/core/html/html_span_element.h"
+
+namespace blink {
+
+class DateComponents;
+class DateTimeFieldsState;
+
+// DateTimeFieldElement is base class of date time field element.
+class DateTimeFieldElement : public HTMLSpanElement {
+ public:
+ enum EventBehavior {
+ kDispatchNoEvent,
+ kDispatchEvent,
+ };
+
+ // FieldOwner implementer must call removeEventHandler when
+ // it doesn't handle event, e.g. at destruction.
+ class FieldOwner : public GarbageCollectedMixin {
+ public:
+ virtual ~FieldOwner();
+ virtual void DidBlurFromField(WebFocusType) = 0;
+ virtual void DidFocusOnField(WebFocusType) = 0;
+ virtual void FieldValueChanged() = 0;
+ virtual bool FocusOnNextField(const DateTimeFieldElement&) = 0;
+ virtual bool FocusOnPreviousField(const DateTimeFieldElement&) = 0;
+ virtual bool IsFieldOwnerDisabled() const = 0;
+ virtual bool IsFieldOwnerReadOnly() const = 0;
+ virtual AtomicString LocaleIdentifier() const = 0;
+ virtual void FieldDidChangeValueByKeyboard() = 0;
+ };
+
+ void DefaultEventHandler(Event*) override;
+ virtual bool HasValue() const = 0;
+ bool IsDisabled() const;
+ virtual float MaximumWidth(const ComputedStyle&);
+ virtual void PopulateDateTimeFieldsState(DateTimeFieldsState&) = 0;
+ void RemoveEventHandler() { field_owner_ = nullptr; }
+ void SetDisabled();
+ virtual void SetEmptyValue(EventBehavior = kDispatchNoEvent) = 0;
+ virtual void SetValueAsDate(const DateComponents&) = 0;
+ virtual void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) = 0;
+ virtual void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) = 0;
+ virtual void StepDown() = 0;
+ virtual void StepUp() = 0;
+ virtual String Value() const = 0;
+ virtual String VisibleValue() const = 0;
+ void Trace(blink::Visitor*) override;
+
+ static float ComputeTextWidth(const ComputedStyle&, const String&);
+
+ protected:
+ DateTimeFieldElement(Document&, FieldOwner&);
+ void FocusOnNextField();
+ virtual void HandleKeyboardEvent(KeyboardEvent*) = 0;
+ void Initialize(const AtomicString& pseudo,
+ const String& ax_help_text,
+ int ax_minimum,
+ int ax_maximum);
+ Locale& LocaleForOwner() const;
+ AtomicString LocaleIdentifier() const;
+ void UpdateVisibleValue(EventBehavior);
+ virtual int ValueAsInteger() const = 0;
+ virtual int ValueForARIAValueNow() const;
+
+ // Node functions.
+ void SetFocused(bool, WebFocusType) override;
+
+ private:
+ void DefaultKeyboardEventHandler(KeyboardEvent*);
+ bool IsDateTimeFieldElement() const final;
+ bool IsFieldOwnerDisabled() const;
+ bool IsFieldOwnerReadOnly() const;
+ bool SupportsFocus() const final;
+
+ Member<FieldOwner> field_owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeFieldElement);
+};
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.cc
new file mode 100644
index 00000000000..bfb4b4b71ab
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.cc
@@ -0,0 +1,794 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_field_elements.h"
+
+#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/date_math.h"
+#include "third_party/blink/renderer/platform/wtf/time.h"
+
+namespace blink {
+
+using blink::WebLocalizedString;
+
+static String QueryString(WebLocalizedString::Name name) {
+ return Locale::DefaultLocale().QueryString(name);
+}
+
+DateTimeAMPMFieldElement::DateTimeAMPMFieldElement(
+ Document& document,
+ FieldOwner& field_owner,
+ const Vector<String>& ampm_labels)
+ : DateTimeSymbolicFieldElement(document, field_owner, ampm_labels, 0, 1) {}
+
+DateTimeAMPMFieldElement* DateTimeAMPMFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Vector<String>& ampm_labels) {
+ DEFINE_STATIC_LOCAL(AtomicString, ampm_pseudo_id,
+ ("-webkit-datetime-edit-ampm-field"));
+ DateTimeAMPMFieldElement* field =
+ new DateTimeAMPMFieldElement(document, field_owner, ampm_labels);
+ field->Initialize(ampm_pseudo_id,
+ QueryString(WebLocalizedString::kAXAMPMFieldText));
+ return field;
+}
+
+void DateTimeAMPMFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ if (HasValue())
+ date_time_fields_state.SetAMPM(ValueAsInteger()
+ ? DateTimeFieldsState::kAMPMValuePM
+ : DateTimeFieldsState::kAMPMValueAM);
+ else
+ date_time_fields_state.SetAMPM(DateTimeFieldsState::kAMPMValueEmpty);
+}
+
+void DateTimeAMPMFieldElement::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.Hour() >= 12 ? 1 : 0);
+}
+
+void DateTimeAMPMFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (date_time_fields_state.HasAMPM())
+ SetValueAsInteger(date_time_fields_state.Ampm());
+ else
+ SetEmptyValue();
+}
+
+// ----------------------------
+
+DateTimeDayFieldElement::DateTimeDayFieldElement(Document& document,
+ FieldOwner& field_owner,
+ const String& placeholder,
+ const Range& range)
+ : DateTimeNumericFieldElement(document,
+ field_owner,
+ range,
+ Range(1, 31),
+ placeholder) {}
+
+DateTimeDayFieldElement* DateTimeDayFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const String& placeholder,
+ const Range& range) {
+ DEFINE_STATIC_LOCAL(AtomicString, day_pseudo_id,
+ ("-webkit-datetime-edit-day-field"));
+ DateTimeDayFieldElement* field = new DateTimeDayFieldElement(
+ document, field_owner, placeholder.IsEmpty() ? "--" : placeholder, range);
+ field->Initialize(day_pseudo_id,
+ QueryString(WebLocalizedString::kAXDayOfMonthFieldText));
+ return field;
+}
+
+void DateTimeDayFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetDayOfMonth(
+ HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeDayFieldElement::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.MonthDay());
+}
+
+void DateTimeDayFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasDayOfMonth()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.DayOfMonth();
+ if (GetRange().IsInRange(static_cast<int>(value))) {
+ SetValueAsInteger(value);
+ return;
+ }
+
+ SetEmptyValue();
+}
+
+// ----------------------------
+
+DateTimeHourFieldElementBase::DateTimeHourFieldElementBase(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Range& hard_limits,
+ const Step& step)
+ : DateTimeNumericFieldElement(document,
+ field_owner,
+ range,
+ hard_limits,
+ "--",
+ step) {}
+
+void DateTimeHourFieldElementBase::Initialize() {
+ DEFINE_STATIC_LOCAL(AtomicString, hour_pseudo_id,
+ ("-webkit-datetime-edit-hour-field"));
+ DateTimeNumericFieldElement::Initialize(
+ hour_pseudo_id, QueryString(WebLocalizedString::kAXHourFieldText));
+}
+
+void DateTimeHourFieldElementBase::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.Hour());
+}
+
+void DateTimeHourFieldElementBase::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasHour()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const int hour12 = date_time_fields_state.Hour();
+ if (hour12 < 1 || hour12 > 12) {
+ SetEmptyValue();
+ return;
+ }
+
+ const int hour11 = hour12 == 12 ? 0 : hour12;
+ const int hour23 =
+ date_time_fields_state.Ampm() == DateTimeFieldsState::kAMPMValuePM
+ ? hour11 + 12
+ : hour11;
+ SetValueAsInteger(hour23);
+}
+// ----------------------------
+
+DateTimeHour11FieldElement::DateTimeHour11FieldElement(Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step)
+ : DateTimeHourFieldElementBase(document,
+ field_owner,
+ range,
+ Range(0, 11),
+ step) {}
+
+DateTimeHour11FieldElement* DateTimeHour11FieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& hour23_range,
+ const Step& step) {
+ DCHECK_GE(hour23_range.minimum, 0);
+ DCHECK_LE(hour23_range.maximum, 23);
+ DCHECK_LE(hour23_range.minimum, hour23_range.maximum);
+ Range range(0, 11);
+ if (hour23_range.maximum < 12) {
+ range = hour23_range;
+ } else if (hour23_range.minimum >= 12) {
+ range.minimum = hour23_range.minimum - 12;
+ range.maximum = hour23_range.maximum - 12;
+ }
+
+ DateTimeHour11FieldElement* field =
+ new DateTimeHour11FieldElement(document, field_owner, range, step);
+ field->Initialize();
+ return field;
+}
+
+void DateTimeHour11FieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ if (!HasValue()) {
+ date_time_fields_state.SetHour(DateTimeFieldsState::kEmptyValue);
+ return;
+ }
+ const int value = ValueAsInteger();
+ date_time_fields_state.SetHour(value ? value : 12);
+}
+
+void DateTimeHour11FieldElement::SetValueAsInteger(
+ int value,
+ EventBehavior event_behavior) {
+ value = Range(0, 23).ClampValue(value) % 12;
+ DateTimeNumericFieldElement::SetValueAsInteger(value, event_behavior);
+}
+
+// ----------------------------
+
+DateTimeHour12FieldElement::DateTimeHour12FieldElement(Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step)
+ : DateTimeHourFieldElementBase(document,
+ field_owner,
+ range,
+ Range(1, 12),
+ step) {}
+
+DateTimeHour12FieldElement* DateTimeHour12FieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& hour23_range,
+ const Step& step) {
+ DCHECK_GE(hour23_range.minimum, 0);
+ DCHECK_LE(hour23_range.maximum, 23);
+ DCHECK_LE(hour23_range.minimum, hour23_range.maximum);
+ Range range(1, 12);
+ if (hour23_range.maximum < 12) {
+ range = hour23_range;
+ } else if (hour23_range.minimum >= 12) {
+ range.minimum = hour23_range.minimum - 12;
+ range.maximum = hour23_range.maximum - 12;
+ }
+ if (!range.minimum)
+ range.minimum = 12;
+ if (!range.maximum)
+ range.maximum = 12;
+ if (range.minimum > range.maximum) {
+ range.minimum = 1;
+ range.maximum = 12;
+ }
+ DateTimeHour12FieldElement* field =
+ new DateTimeHour12FieldElement(document, field_owner, range, step);
+ field->Initialize();
+ return field;
+}
+
+void DateTimeHour12FieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetHour(HasValue() ? ValueAsInteger()
+ : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeHour12FieldElement::SetValueAsInteger(
+ int value,
+ EventBehavior event_behavior) {
+ value = Range(0, 24).ClampValue(value) % 12;
+ DateTimeNumericFieldElement::SetValueAsInteger(value ? value : 12,
+ event_behavior);
+}
+
+// ----------------------------
+
+DateTimeHour23FieldElement::DateTimeHour23FieldElement(Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step)
+ : DateTimeHourFieldElementBase(document,
+ field_owner,
+ range,
+ Range(0, 23),
+ step) {}
+
+DateTimeHour23FieldElement* DateTimeHour23FieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& hour23_range,
+ const Step& step) {
+ DCHECK_GE(hour23_range.minimum, 0);
+ DCHECK_LE(hour23_range.maximum, 23);
+ DCHECK_LE(hour23_range.minimum, hour23_range.maximum);
+ DateTimeHour23FieldElement* field =
+ new DateTimeHour23FieldElement(document, field_owner, hour23_range, step);
+ field->Initialize();
+ return field;
+}
+
+void DateTimeHour23FieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ if (!HasValue()) {
+ date_time_fields_state.SetHour(DateTimeFieldsState::kEmptyValue);
+ return;
+ }
+
+ const int value = ValueAsInteger();
+
+ date_time_fields_state.SetHour(value % 12 ? value % 12 : 12);
+ date_time_fields_state.SetAMPM(value >= 12
+ ? DateTimeFieldsState::kAMPMValuePM
+ : DateTimeFieldsState::kAMPMValueAM);
+}
+
+void DateTimeHour23FieldElement::SetValueAsInteger(
+ int value,
+ EventBehavior event_behavior) {
+ value = Range(0, 23).ClampValue(value);
+ DateTimeNumericFieldElement::SetValueAsInteger(value, event_behavior);
+}
+
+// ----------------------------
+
+DateTimeHour24FieldElement::DateTimeHour24FieldElement(Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step)
+ : DateTimeHourFieldElementBase(document,
+ field_owner,
+ range,
+ Range(1, 24),
+ step) {}
+
+DateTimeHour24FieldElement* DateTimeHour24FieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& hour23_range,
+ const Step& step) {
+ DCHECK_GE(hour23_range.minimum, 0);
+ DCHECK_LE(hour23_range.maximum, 23);
+ DCHECK_LE(hour23_range.minimum, hour23_range.maximum);
+ Range range(hour23_range.minimum ? hour23_range.minimum : 24,
+ hour23_range.maximum ? hour23_range.maximum : 24);
+ if (range.minimum > range.maximum) {
+ range.minimum = 1;
+ range.maximum = 24;
+ }
+
+ DateTimeHour24FieldElement* field =
+ new DateTimeHour24FieldElement(document, field_owner, range, step);
+ field->Initialize();
+ return field;
+}
+
+void DateTimeHour24FieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ if (!HasValue()) {
+ date_time_fields_state.SetHour(DateTimeFieldsState::kEmptyValue);
+ return;
+ }
+
+ const int value = ValueAsInteger();
+
+ if (value == 24) {
+ date_time_fields_state.SetHour(12);
+ date_time_fields_state.SetAMPM(DateTimeFieldsState::kAMPMValueAM);
+ } else {
+ date_time_fields_state.SetHour(value == 12 ? 12 : value % 12);
+ date_time_fields_state.SetAMPM(value >= 12
+ ? DateTimeFieldsState::kAMPMValuePM
+ : DateTimeFieldsState::kAMPMValueAM);
+ }
+}
+
+void DateTimeHour24FieldElement::SetValueAsInteger(
+ int value,
+ EventBehavior event_behavior) {
+ value = Range(0, 24).ClampValue(value);
+ DateTimeNumericFieldElement::SetValueAsInteger(value ? value : 24,
+ event_behavior);
+}
+
+// ----------------------------
+
+DateTimeMillisecondFieldElement::DateTimeMillisecondFieldElement(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step)
+ : DateTimeNumericFieldElement(document,
+ field_owner,
+ range,
+ Range(0, 999),
+ "---",
+ step) {}
+
+DateTimeMillisecondFieldElement* DateTimeMillisecondFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step) {
+ DEFINE_STATIC_LOCAL(AtomicString, millisecond_pseudo_id,
+ ("-webkit-datetime-edit-millisecond-field"));
+ DateTimeMillisecondFieldElement* field =
+ new DateTimeMillisecondFieldElement(document, field_owner, range, step);
+ field->Initialize(millisecond_pseudo_id,
+ QueryString(WebLocalizedString::kAXMillisecondFieldText));
+ return field;
+}
+
+void DateTimeMillisecondFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetMillisecond(
+ HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeMillisecondFieldElement::SetValueAsDate(
+ const DateComponents& date) {
+ SetValueAsInteger(date.Millisecond());
+}
+
+void DateTimeMillisecondFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasMillisecond()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.Millisecond();
+ if (value > static_cast<unsigned>(Maximum())) {
+ SetEmptyValue();
+ return;
+ }
+
+ SetValueAsInteger(value);
+}
+
+// ----------------------------
+
+DateTimeMinuteFieldElement::DateTimeMinuteFieldElement(Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step)
+ : DateTimeNumericFieldElement(document,
+ field_owner,
+ range,
+ Range(0, 59),
+ "--",
+ step) {}
+
+DateTimeMinuteFieldElement* DateTimeMinuteFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step) {
+ DEFINE_STATIC_LOCAL(AtomicString, minute_pseudo_id,
+ ("-webkit-datetime-edit-minute-field"));
+ DateTimeMinuteFieldElement* field =
+ new DateTimeMinuteFieldElement(document, field_owner, range, step);
+ field->Initialize(minute_pseudo_id,
+ QueryString(WebLocalizedString::kAXMinuteFieldText));
+ return field;
+}
+
+void DateTimeMinuteFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetMinute(
+ HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeMinuteFieldElement::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.Minute());
+}
+
+void DateTimeMinuteFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasMinute()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.Minute();
+ if (value > static_cast<unsigned>(Maximum())) {
+ SetEmptyValue();
+ return;
+ }
+
+ SetValueAsInteger(value);
+}
+
+// ----------------------------
+
+DateTimeMonthFieldElement::DateTimeMonthFieldElement(Document& document,
+ FieldOwner& field_owner,
+ const String& placeholder,
+ const Range& range)
+ : DateTimeNumericFieldElement(document,
+ field_owner,
+ range,
+ Range(1, 12),
+ placeholder) {}
+
+DateTimeMonthFieldElement* DateTimeMonthFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const String& placeholder,
+ const Range& range) {
+ DEFINE_STATIC_LOCAL(AtomicString, month_pseudo_id,
+ ("-webkit-datetime-edit-month-field"));
+ DateTimeMonthFieldElement* field = new DateTimeMonthFieldElement(
+ document, field_owner, placeholder.IsEmpty() ? "--" : placeholder, range);
+ field->Initialize(month_pseudo_id,
+ QueryString(WebLocalizedString::kAXMonthFieldText));
+ return field;
+}
+
+void DateTimeMonthFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetMonth(
+ HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeMonthFieldElement::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.Month() + 1);
+}
+
+void DateTimeMonthFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasMonth()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.Month();
+ if (GetRange().IsInRange(static_cast<int>(value))) {
+ SetValueAsInteger(value);
+ return;
+ }
+
+ SetEmptyValue();
+}
+
+// ----------------------------
+
+DateTimeSecondFieldElement::DateTimeSecondFieldElement(Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step)
+ : DateTimeNumericFieldElement(document,
+ field_owner,
+ range,
+ Range(0, 59),
+ "--",
+ step) {}
+
+DateTimeSecondFieldElement* DateTimeSecondFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Step& step) {
+ DEFINE_STATIC_LOCAL(AtomicString, second_pseudo_id,
+ ("-webkit-datetime-edit-second-field"));
+ DateTimeSecondFieldElement* field =
+ new DateTimeSecondFieldElement(document, field_owner, range, step);
+ field->Initialize(second_pseudo_id,
+ QueryString(WebLocalizedString::kAXSecondFieldText));
+ return field;
+}
+
+void DateTimeSecondFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetSecond(
+ HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeSecondFieldElement::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.Second());
+}
+
+void DateTimeSecondFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasSecond()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.Second();
+ if (value > static_cast<unsigned>(Maximum())) {
+ SetEmptyValue();
+ return;
+ }
+
+ SetValueAsInteger(value);
+}
+
+// ----------------------------
+
+DateTimeSymbolicMonthFieldElement::DateTimeSymbolicMonthFieldElement(
+ Document& document,
+ FieldOwner& field_owner,
+ const Vector<String>& labels,
+ int minimum,
+ int maximum)
+ : DateTimeSymbolicFieldElement(document,
+ field_owner,
+ labels,
+ minimum,
+ maximum) {}
+
+DateTimeSymbolicMonthFieldElement* DateTimeSymbolicMonthFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Vector<String>& labels,
+ int minimum,
+ int maximum) {
+ DEFINE_STATIC_LOCAL(AtomicString, month_pseudo_id,
+ ("-webkit-datetime-edit-month-field"));
+ DateTimeSymbolicMonthFieldElement* field =
+ new DateTimeSymbolicMonthFieldElement(document, field_owner, labels,
+ minimum, maximum);
+ field->Initialize(month_pseudo_id,
+ QueryString(WebLocalizedString::kAXMonthFieldText));
+ return field;
+}
+
+void DateTimeSymbolicMonthFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ if (!HasValue())
+ date_time_fields_state.SetMonth(DateTimeFieldsState::kEmptyValue);
+ DCHECK_LT(ValueAsInteger(), static_cast<int>(SymbolsSize()));
+ date_time_fields_state.SetMonth(ValueAsInteger() + 1);
+}
+
+void DateTimeSymbolicMonthFieldElement::SetValueAsDate(
+ const DateComponents& date) {
+ SetValueAsInteger(date.Month());
+}
+
+void DateTimeSymbolicMonthFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasMonth()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.Month() - 1;
+ if (value >= SymbolsSize()) {
+ SetEmptyValue();
+ return;
+ }
+
+ SetValueAsInteger(value);
+}
+
+// ----------------------------
+
+DateTimeWeekFieldElement::DateTimeWeekFieldElement(Document& document,
+ FieldOwner& field_owner,
+ const Range& range)
+ : DateTimeNumericFieldElement(document,
+ field_owner,
+ range,
+ Range(DateComponents::kMinimumWeekNumber,
+ DateComponents::kMaximumWeekNumber),
+ "--") {}
+
+DateTimeWeekFieldElement* DateTimeWeekFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& range) {
+ DEFINE_STATIC_LOCAL(AtomicString, week_pseudo_id,
+ ("-webkit-datetime-edit-week-field"));
+ DateTimeWeekFieldElement* field =
+ new DateTimeWeekFieldElement(document, field_owner, range);
+ field->Initialize(week_pseudo_id,
+ QueryString(WebLocalizedString::kAXWeekOfYearFieldText));
+ return field;
+}
+
+void DateTimeWeekFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetWeekOfYear(
+ HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeWeekFieldElement::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.Week());
+}
+
+void DateTimeWeekFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasWeekOfYear()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.WeekOfYear();
+ if (GetRange().IsInRange(static_cast<int>(value))) {
+ SetValueAsInteger(value);
+ return;
+ }
+
+ SetEmptyValue();
+}
+
+// ----------------------------
+
+DateTimeYearFieldElement::DateTimeYearFieldElement(
+ Document& document,
+ FieldOwner& field_owner,
+ const DateTimeYearFieldElement::Parameters& parameters)
+ : DateTimeNumericFieldElement(
+ document,
+ field_owner,
+ Range(parameters.minimum_year, parameters.maximum_year),
+ Range(DateComponents::MinimumYear(), DateComponents::MaximumYear()),
+ parameters.placeholder.IsEmpty() ? "----" : parameters.placeholder),
+ min_is_specified_(parameters.min_is_specified),
+ max_is_specified_(parameters.max_is_specified) {
+ DCHECK_GE(parameters.minimum_year, DateComponents::MinimumYear());
+ DCHECK_LE(parameters.maximum_year, DateComponents::MaximumYear());
+}
+
+DateTimeYearFieldElement* DateTimeYearFieldElement::Create(
+ Document& document,
+ FieldOwner& field_owner,
+ const DateTimeYearFieldElement::Parameters& parameters) {
+ DEFINE_STATIC_LOCAL(AtomicString, year_pseudo_id,
+ ("-webkit-datetime-edit-year-field"));
+ DateTimeYearFieldElement* field =
+ new DateTimeYearFieldElement(document, field_owner, parameters);
+ field->Initialize(year_pseudo_id,
+ QueryString(WebLocalizedString::kAXYearFieldText));
+ return field;
+}
+
+static int CurrentFullYear() {
+ DateComponents date;
+ date.SetMillisecondsSinceEpochForMonth(ConvertToLocalTime(CurrentTimeMS()));
+ return date.FullYear();
+}
+
+int DateTimeYearFieldElement::DefaultValueForStepDown() const {
+ return max_is_specified_
+ ? DateTimeNumericFieldElement::DefaultValueForStepDown()
+ : CurrentFullYear();
+}
+
+int DateTimeYearFieldElement::DefaultValueForStepUp() const {
+ return min_is_specified_
+ ? DateTimeNumericFieldElement::DefaultValueForStepUp()
+ : CurrentFullYear();
+}
+
+void DateTimeYearFieldElement::PopulateDateTimeFieldsState(
+ DateTimeFieldsState& date_time_fields_state) {
+ date_time_fields_state.SetYear(HasValue() ? ValueAsInteger()
+ : DateTimeFieldsState::kEmptyValue);
+}
+
+void DateTimeYearFieldElement::SetValueAsDate(const DateComponents& date) {
+ SetValueAsInteger(date.FullYear());
+}
+
+void DateTimeYearFieldElement::SetValueAsDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) {
+ if (!date_time_fields_state.HasYear()) {
+ SetEmptyValue();
+ return;
+ }
+
+ const unsigned value = date_time_fields_state.Year();
+ if (GetRange().IsInRange(static_cast<int>(value))) {
+ SetValueAsInteger(value);
+ return;
+ }
+
+ SetEmptyValue();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.h
new file mode 100644
index 00000000000..758f9a71d56
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENTS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENTS_H_
+
+#include "base/macros.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+class DateTimeAMPMFieldElement final : public DateTimeSymbolicFieldElement {
+ public:
+ static DateTimeAMPMFieldElement* Create(Document&,
+ FieldOwner&,
+ const Vector<String>&);
+
+ private:
+ DateTimeAMPMFieldElement(Document&, FieldOwner&, const Vector<String>&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeAMPMFieldElement);
+};
+
+class DateTimeDayFieldElement final : public DateTimeNumericFieldElement {
+ public:
+ static DateTimeDayFieldElement* Create(Document&,
+ FieldOwner&,
+ const String& placeholder,
+ const Range&);
+
+ private:
+ DateTimeDayFieldElement(Document&,
+ FieldOwner&,
+ const String& placeholder,
+ const Range&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeDayFieldElement);
+};
+
+class DateTimeHourFieldElementBase : public DateTimeNumericFieldElement {
+ protected:
+ DateTimeHourFieldElementBase(Document&,
+ FieldOwner&,
+ const Range&,
+ const Range& hard_limits,
+ const Step&);
+ void Initialize();
+
+ private:
+ // DateTimeFieldElement functions.
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeHourFieldElementBase);
+};
+
+class DateTimeHour11FieldElement final : public DateTimeHourFieldElementBase {
+ public:
+ static DateTimeHour11FieldElement* Create(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ private:
+ DateTimeHour11FieldElement(Document&,
+ FieldOwner&,
+ const Range& hour23_range,
+ const Step&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeHour11FieldElement);
+};
+
+class DateTimeHour12FieldElement final : public DateTimeHourFieldElementBase {
+ public:
+ static DateTimeHour12FieldElement* Create(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ private:
+ DateTimeHour12FieldElement(Document&,
+ FieldOwner&,
+ const Range& hour23_range,
+ const Step&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeHour12FieldElement);
+};
+
+class DateTimeHour23FieldElement final : public DateTimeHourFieldElementBase {
+ public:
+ static DateTimeHour23FieldElement* Create(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ private:
+ DateTimeHour23FieldElement(Document&,
+ FieldOwner&,
+ const Range& hour23_range,
+ const Step&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeHour23FieldElement);
+};
+
+class DateTimeHour24FieldElement final : public DateTimeHourFieldElementBase {
+ public:
+ static DateTimeHour24FieldElement* Create(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ private:
+ DateTimeHour24FieldElement(Document&,
+ FieldOwner&,
+ const Range& hour23_range,
+ const Step&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeHour24FieldElement);
+};
+
+class DateTimeMillisecondFieldElement final
+ : public DateTimeNumericFieldElement {
+ public:
+ static DateTimeMillisecondFieldElement* Create(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ private:
+ DateTimeMillisecondFieldElement(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeMillisecondFieldElement);
+};
+
+class DateTimeMinuteFieldElement final : public DateTimeNumericFieldElement {
+ public:
+ static DateTimeMinuteFieldElement* Create(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ private:
+ DateTimeMinuteFieldElement(Document&, FieldOwner&, const Range&, const Step&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeMinuteFieldElement);
+};
+
+class DateTimeMonthFieldElement final : public DateTimeNumericFieldElement {
+ public:
+ static DateTimeMonthFieldElement* Create(Document&,
+ FieldOwner&,
+ const String& placeholder,
+ const Range&);
+
+ private:
+ DateTimeMonthFieldElement(Document&,
+ FieldOwner&,
+ const String& placeholder,
+ const Range&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeMonthFieldElement);
+};
+
+class DateTimeSecondFieldElement final : public DateTimeNumericFieldElement {
+ public:
+ static DateTimeSecondFieldElement* Create(Document&,
+ FieldOwner&,
+ const Range&,
+ const Step&);
+
+ private:
+ DateTimeSecondFieldElement(Document&, FieldOwner&, const Range&, const Step&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeSecondFieldElement);
+};
+
+class DateTimeSymbolicMonthFieldElement final
+ : public DateTimeSymbolicFieldElement {
+ public:
+ static DateTimeSymbolicMonthFieldElement* Create(Document&,
+ FieldOwner&,
+ const Vector<String>&,
+ int minimum,
+ int maximum);
+
+ private:
+ DateTimeSymbolicMonthFieldElement(Document&,
+ FieldOwner&,
+ const Vector<String>&,
+ int minimum,
+ int maximum);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeSymbolicMonthFieldElement);
+};
+
+class DateTimeWeekFieldElement final : public DateTimeNumericFieldElement {
+ public:
+ static DateTimeWeekFieldElement* Create(Document&, FieldOwner&, const Range&);
+
+ private:
+ DateTimeWeekFieldElement(Document&, FieldOwner&, const Range&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeWeekFieldElement);
+};
+
+class DateTimeYearFieldElement final : public DateTimeNumericFieldElement {
+ public:
+ struct Parameters {
+ STACK_ALLOCATED();
+ int minimum_year;
+ int maximum_year;
+ bool min_is_specified;
+ bool max_is_specified;
+ String placeholder;
+
+ Parameters()
+ : minimum_year(-1),
+ maximum_year(-1),
+ min_is_specified(false),
+ max_is_specified(false) {}
+ };
+
+ static DateTimeYearFieldElement* Create(Document&,
+ FieldOwner&,
+ const Parameters&);
+
+ private:
+ DateTimeYearFieldElement(Document&, FieldOwner&, const Parameters&);
+
+ // DateTimeFieldElement functions.
+ void PopulateDateTimeFieldsState(DateTimeFieldsState&) override;
+ void SetValueAsDate(const DateComponents&) override;
+ void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override;
+
+ // DateTimeNumericFieldElement functions.
+ int DefaultValueForStepDown() const override;
+ int DefaultValueForStepUp() const override;
+
+ bool min_is_specified_;
+ bool max_is_specified_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeYearFieldElement);
+};
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.cc
new file mode 100644
index 00000000000..9896a7f9d0c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h"
+
+#include "third_party/blink/renderer/core/html/forms/form_controller.h"
+
+namespace blink {
+
+const unsigned DateTimeFieldsState::kEmptyValue = static_cast<unsigned>(-1);
+
+static unsigned GetNumberFromFormControlState(const FormControlState& state,
+ size_t index) {
+ if (index >= state.ValueSize())
+ return DateTimeFieldsState::kEmptyValue;
+ bool parsed;
+ unsigned const value = state[index].ToUInt(&parsed);
+ return parsed ? value : DateTimeFieldsState::kEmptyValue;
+}
+
+static DateTimeFieldsState::AMPMValue GetAMPMFromFormControlState(
+ const FormControlState& state,
+ size_t index) {
+ if (index >= state.ValueSize())
+ return DateTimeFieldsState::kAMPMValueEmpty;
+ const String value = state[index];
+ if (value == "A")
+ return DateTimeFieldsState::kAMPMValueAM;
+ if (value == "P")
+ return DateTimeFieldsState::kAMPMValuePM;
+ return DateTimeFieldsState::kAMPMValueEmpty;
+}
+
+DateTimeFieldsState::DateTimeFieldsState()
+ : year_(kEmptyValue),
+ month_(kEmptyValue),
+ day_of_month_(kEmptyValue),
+ hour_(kEmptyValue),
+ minute_(kEmptyValue),
+ second_(kEmptyValue),
+ millisecond_(kEmptyValue),
+ week_of_year_(kEmptyValue),
+ ampm_(kAMPMValueEmpty) {}
+
+unsigned DateTimeFieldsState::Hour23() const {
+ if (!HasHour() || !HasAMPM())
+ return kEmptyValue;
+ return (hour_ % 12) + (ampm_ == kAMPMValuePM ? 12 : 0);
+}
+
+DateTimeFieldsState DateTimeFieldsState::RestoreFormControlState(
+ const FormControlState& state) {
+ DateTimeFieldsState date_time_fields_state;
+ date_time_fields_state.SetYear(GetNumberFromFormControlState(state, 0));
+ date_time_fields_state.SetMonth(GetNumberFromFormControlState(state, 1));
+ date_time_fields_state.SetDayOfMonth(GetNumberFromFormControlState(state, 2));
+ date_time_fields_state.SetHour(GetNumberFromFormControlState(state, 3));
+ date_time_fields_state.SetMinute(GetNumberFromFormControlState(state, 4));
+ date_time_fields_state.SetSecond(GetNumberFromFormControlState(state, 5));
+ date_time_fields_state.SetMillisecond(
+ GetNumberFromFormControlState(state, 6));
+ date_time_fields_state.SetWeekOfYear(GetNumberFromFormControlState(state, 7));
+ date_time_fields_state.SetAMPM(GetAMPMFromFormControlState(state, 8));
+ return date_time_fields_state;
+}
+
+FormControlState DateTimeFieldsState::SaveFormControlState() const {
+ FormControlState state;
+ state.Append(HasYear() ? String::Number(year_) : g_empty_string);
+ state.Append(HasMonth() ? String::Number(month_) : g_empty_string);
+ state.Append(HasDayOfMonth() ? String::Number(day_of_month_)
+ : g_empty_string);
+ state.Append(HasHour() ? String::Number(hour_) : g_empty_string);
+ state.Append(HasMinute() ? String::Number(minute_) : g_empty_string);
+ state.Append(HasSecond() ? String::Number(second_) : g_empty_string);
+ state.Append(HasMillisecond() ? String::Number(millisecond_)
+ : g_empty_string);
+ state.Append(HasWeekOfYear() ? String::Number(week_of_year_)
+ : g_empty_string);
+ if (HasAMPM())
+ state.Append(ampm_ == kAMPMValueAM ? "A" : "P");
+ else
+ state.Append(g_empty_string);
+ return state;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.h
new file mode 100644
index 00000000000..36020b2a3ed
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELDS_STATE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELDS_STATE_H_
+
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+class FormControlState;
+
+// DateTimeFieldsState represents fields in date/time for form state
+// save/restore for input type "date", "datetime", "datetime-local", "month",
+// "time", and "week" with multiple fields input UI.
+//
+// Each field can contain invalid value for date, e.g. day of month field can
+// be 30 even if month field is February.
+class DateTimeFieldsState {
+ STACK_ALLOCATED();
+
+ public:
+ enum AMPMValue {
+ kAMPMValueEmpty = -1,
+ kAMPMValueAM,
+ kAMPMValuePM,
+ };
+
+ static const unsigned kEmptyValue;
+
+ DateTimeFieldsState();
+
+ static DateTimeFieldsState RestoreFormControlState(const FormControlState&);
+ FormControlState SaveFormControlState() const;
+
+ AMPMValue Ampm() const { return ampm_; }
+ unsigned DayOfMonth() const { return day_of_month_; }
+ unsigned Hour() const { return hour_; }
+ unsigned Hour23() const;
+ unsigned Millisecond() const { return millisecond_; }
+ unsigned Minute() const { return minute_; }
+ unsigned Month() const { return month_; }
+ unsigned Second() const { return second_; }
+ unsigned WeekOfYear() const { return week_of_year_; }
+ unsigned Year() const { return year_; }
+
+ bool HasAMPM() const { return ampm_ != kAMPMValueEmpty; }
+ bool HasDayOfMonth() const { return day_of_month_ != kEmptyValue; }
+ bool HasHour() const { return hour_ != kEmptyValue; }
+ bool HasMillisecond() const { return millisecond_ != kEmptyValue; }
+ bool HasMinute() const { return minute_ != kEmptyValue; }
+ bool HasMonth() const { return month_ != kEmptyValue; }
+ bool HasSecond() const { return second_ != kEmptyValue; }
+ bool HasWeekOfYear() const { return week_of_year_ != kEmptyValue; }
+ bool HasYear() const { return year_ != kEmptyValue; }
+
+ void SetAMPM(AMPMValue ampm) { ampm_ = ampm; }
+ void SetDayOfMonth(unsigned day_of_month) { day_of_month_ = day_of_month; }
+ void SetHour(unsigned hour12) { hour_ = hour12; }
+ void SetMillisecond(unsigned millisecond) { millisecond_ = millisecond; }
+ void SetMinute(unsigned minute) { minute_ = minute; }
+ void SetMonth(unsigned month) { month_ = month; }
+ void SetSecond(unsigned second) { second_ = second; }
+ void SetWeekOfYear(unsigned week_of_year) { week_of_year_ = week_of_year; }
+ void SetYear(unsigned year) { year_ = year; }
+
+ private:
+ unsigned year_;
+ unsigned month_; // 1 to 12.
+ unsigned day_of_month_;
+ unsigned hour_; // 1 to 12.
+ unsigned minute_;
+ unsigned second_;
+ unsigned millisecond_;
+ unsigned week_of_year_;
+ AMPMValue ampm_;
+};
+
+} // namespace blink
+
+#endif
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
new file mode 100644
index 00000000000..083cd0b302f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_local_input_type.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.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"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+using blink::WebLocalizedString;
+using namespace HTMLNames;
+
+static const int kDateTimeLocalDefaultStep = 60;
+static const int kDateTimeLocalDefaultStepBase = 0;
+static const int kDateTimeLocalStepScaleFactor = 1000;
+
+InputType* DateTimeLocalInputType::Create(HTMLInputElement& element) {
+ return new DateTimeLocalInputType(element);
+}
+
+void DateTimeLocalInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeDateTimeLocal);
+}
+
+const AtomicString& DateTimeLocalInputType::FormControlType() const {
+ return InputTypeNames::datetime_local;
+}
+
+double DateTimeLocalInputType::ValueAsDate() const {
+ // valueAsDate doesn't work for the datetime-local type according to the
+ // standard.
+ return DateComponents::InvalidMilliseconds();
+}
+
+void DateTimeLocalInputType::SetValueAsDate(
+ double value,
+ ExceptionState& exception_state) const {
+ // valueAsDate doesn't work for the datetime-local type according to the
+ // standard.
+ InputType::SetValueAsDate(value, exception_state);
+}
+
+StepRange DateTimeLocalInputType::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ DEFINE_STATIC_LOCAL(const StepRange::StepDescription, step_description,
+ (kDateTimeLocalDefaultStep, kDateTimeLocalDefaultStepBase,
+ kDateTimeLocalStepScaleFactor,
+ StepRange::kScaledStepValueShouldBeInteger));
+
+ return InputType::CreateStepRange(
+ any_step_handling, kDateTimeLocalDefaultStepBase,
+ Decimal::FromDouble(DateComponents::MinimumDateTime()),
+ Decimal::FromDouble(DateComponents::MaximumDateTime()), step_description);
+}
+
+bool DateTimeLocalInputType::ParseToDateComponentsInternal(
+ const String& string,
+ DateComponents* out) const {
+ DCHECK(out);
+ unsigned end;
+ return out->ParseDateTimeLocal(string, 0, end) && end == string.length();
+}
+
+bool DateTimeLocalInputType::SetMillisecondToDateComponents(
+ double value,
+ DateComponents* date) const {
+ DCHECK(date);
+ return date->SetMillisecondsSinceEpochForDateTimeLocal(value);
+}
+
+String DateTimeLocalInputType::LocalizeValue(
+ const String& proposed_value) const {
+ DateComponents date;
+ if (!ParseToDateComponents(proposed_value, &date))
+ return proposed_value;
+
+ Locale::FormatType format_type = ShouldHaveSecondField(date)
+ ? Locale::kFormatTypeMedium
+ : Locale::kFormatTypeShort;
+ String localized = GetElement().GetLocale().FormatDateTime(date, format_type);
+ return localized.IsEmpty() ? proposed_value : localized;
+}
+
+void DateTimeLocalInputType::WarnIfValueIsInvalid(const String& value) const {
+ if (value != GetElement().SanitizeValue(value))
+ AddWarningToConsole(
+ "The specified value %s does not conform to the required format. The "
+ "format is \"yyyy-MM-ddThh:mm\" followed by optional \":ss\" or "
+ "\":ss.SSS\".",
+ value);
+}
+
+String DateTimeLocalInputType::FormatDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) const {
+ if (!date_time_fields_state.HasDayOfMonth() ||
+ !date_time_fields_state.HasMonth() || !date_time_fields_state.HasYear() ||
+ !date_time_fields_state.HasHour() ||
+ !date_time_fields_state.HasMinute() || !date_time_fields_state.HasAMPM())
+ return g_empty_string;
+
+ if (date_time_fields_state.HasMillisecond() &&
+ date_time_fields_state.Millisecond()) {
+ return String::Format(
+ "%04u-%02u-%02uT%02u:%02u:%02u.%03u", date_time_fields_state.Year(),
+ date_time_fields_state.Month(), date_time_fields_state.DayOfMonth(),
+ date_time_fields_state.Hour23(), date_time_fields_state.Minute(),
+ date_time_fields_state.HasSecond() ? date_time_fields_state.Second()
+ : 0,
+ date_time_fields_state.Millisecond());
+ }
+
+ if (date_time_fields_state.HasSecond() && date_time_fields_state.Second()) {
+ return String::Format(
+ "%04u-%02u-%02uT%02u:%02u:%02u", date_time_fields_state.Year(),
+ date_time_fields_state.Month(), date_time_fields_state.DayOfMonth(),
+ date_time_fields_state.Hour23(), date_time_fields_state.Minute(),
+ date_time_fields_state.Second());
+ }
+
+ return String::Format(
+ "%04u-%02u-%02uT%02u:%02u", date_time_fields_state.Year(),
+ date_time_fields_state.Month(), date_time_fields_state.DayOfMonth(),
+ date_time_fields_state.Hour23(), date_time_fields_state.Minute());
+}
+
+void DateTimeLocalInputType::SetupLayoutParameters(
+ DateTimeEditElement::LayoutParameters& layout_parameters,
+ const DateComponents& date) const {
+ if (ShouldHaveSecondField(date)) {
+ layout_parameters.date_time_format =
+ layout_parameters.locale.DateTimeFormatWithSeconds();
+ layout_parameters.fallback_date_time_format = "yyyy-MM-dd'T'HH:mm:ss";
+ } else {
+ layout_parameters.date_time_format =
+ layout_parameters.locale.DateTimeFormatWithoutSeconds();
+ layout_parameters.fallback_date_time_format = "yyyy-MM-dd'T'HH:mm";
+ }
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr),
+ &layout_parameters.minimum))
+ layout_parameters.minimum = DateComponents();
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr),
+ &layout_parameters.maximum))
+ layout_parameters.maximum = DateComponents();
+ layout_parameters.placeholder_for_day = GetLocale().QueryString(
+ WebLocalizedString::kPlaceholderForDayOfMonthField);
+ layout_parameters.placeholder_for_month =
+ GetLocale().QueryString(WebLocalizedString::kPlaceholderForMonthField);
+ layout_parameters.placeholder_for_year =
+ GetLocale().QueryString(WebLocalizedString::kPlaceholderForYearField);
+}
+
+bool DateTimeLocalInputType::IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const {
+ return has_year && has_month && has_day && has_ampm && has_hour && has_minute;
+}
+
+} // 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
new file mode 100644
index 00000000000..102cdc3e239
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_LOCAL_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_LOCAL_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+
+namespace blink {
+
+class ExceptionState;
+
+class DateTimeLocalInputType final : public BaseTemporalInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ explicit DateTimeLocalInputType(HTMLInputElement& element)
+ : BaseTemporalInputType(element) {}
+
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ double ValueAsDate() const override;
+ void SetValueAsDate(double, ExceptionState&) const override;
+ StepRange CreateStepRange(AnyStepHandling) const override;
+ bool ParseToDateComponentsInternal(const String&,
+ DateComponents*) const override;
+ bool SetMillisecondToDateComponents(double, DateComponents*) const override;
+ String LocalizeValue(const String&) const override;
+ void WarnIfValueIsInvalid(const String&) const override;
+
+ // BaseTemporalInputType functions
+ String FormatDateTimeFieldsState(const DateTimeFieldsState&) const final;
+ void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&,
+ const DateComponents&) const final;
+ bool IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_LOCAL_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..33268c41aa8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h"
+
+#include "third_party/blink/renderer/core/css_property_names.h"
+#include "third_party/blink/renderer/core/css_value_keywords.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+
+namespace blink {
+
+int DateTimeNumericFieldElement::Range::ClampValue(int value) const {
+ return std::min(std::max(value, minimum), maximum);
+}
+
+bool DateTimeNumericFieldElement::Range::IsInRange(int value) const {
+ return value >= minimum && value <= maximum;
+}
+
+// ----------------------------
+
+DateTimeNumericFieldElement::DateTimeNumericFieldElement(
+ Document& document,
+ FieldOwner& field_owner,
+ const Range& range,
+ const Range& hard_limits,
+ const String& placeholder,
+ const DateTimeNumericFieldElement::Step& step)
+ : DateTimeFieldElement(document, field_owner),
+ placeholder_(placeholder),
+ range_(range),
+ hard_limits_(hard_limits),
+ step_(step),
+ value_(0),
+ has_value_(false) {
+ DCHECK_NE(step_.step, 0);
+ DCHECK_LE(range_.minimum, range_.maximum);
+ DCHECK_LE(hard_limits_.minimum, hard_limits_.maximum);
+
+ // We show a direction-neutral string such as "--" as a placeholder. It
+ // should follow the direction of numeric values.
+ if (LocaleForOwner().IsRTL()) {
+ WTF::Unicode::CharDirection dir =
+ WTF::Unicode::Direction(FormatValue(Maximum())[0]);
+ if (dir == WTF::Unicode::kLeftToRight ||
+ dir == WTF::Unicode::kEuropeanNumber ||
+ dir == WTF::Unicode::kArabicNumber) {
+ SetInlineStyleProperty(CSSPropertyUnicodeBidi, CSSValueBidiOverride);
+ SetInlineStyleProperty(CSSPropertyDirection, CSSValueLtr);
+ }
+ }
+}
+
+float DateTimeNumericFieldElement::MaximumWidth(const ComputedStyle& style) {
+ float maximum_width = ComputeTextWidth(style, placeholder_);
+ maximum_width =
+ std::max(maximum_width, ComputeTextWidth(style, FormatValue(Maximum())));
+ maximum_width = std::max(maximum_width, ComputeTextWidth(style, Value()));
+ return maximum_width + DateTimeFieldElement::MaximumWidth(style);
+}
+
+int DateTimeNumericFieldElement::DefaultValueForStepDown() const {
+ return range_.maximum;
+}
+
+int DateTimeNumericFieldElement::DefaultValueForStepUp() const {
+ return range_.minimum;
+}
+
+void DateTimeNumericFieldElement::SetFocused(bool value,
+ WebFocusType focus_type) {
+ if (!value) {
+ int value = TypeAheadValue();
+ type_ahead_buffer_.Clear();
+ if (value >= 0)
+ SetValueAsInteger(value, kDispatchEvent);
+ }
+ DateTimeFieldElement::SetFocused(value, focus_type);
+}
+
+String DateTimeNumericFieldElement::FormatValue(int value) const {
+ Locale& locale = LocaleForOwner();
+ if (hard_limits_.maximum > 999)
+ return locale.ConvertToLocalizedNumber(String::Format("%04d", value));
+ if (hard_limits_.maximum > 99)
+ return locale.ConvertToLocalizedNumber(String::Format("%03d", value));
+ return locale.ConvertToLocalizedNumber(String::Format("%02d", value));
+}
+
+void DateTimeNumericFieldElement::HandleKeyboardEvent(
+ KeyboardEvent* keyboard_event) {
+ DCHECK(!IsDisabled());
+ if (keyboard_event->type() != EventTypeNames::keypress)
+ return;
+
+ UChar char_code = static_cast<UChar>(keyboard_event->charCode());
+ String number =
+ LocaleForOwner().ConvertFromLocalizedNumber(String(&char_code, 1));
+ const int digit = number[0] - '0';
+ if (digit < 0 || digit > 9)
+ return;
+
+ unsigned maximum_length =
+ DateTimeNumericFieldElement::FormatValue(range_.maximum).length();
+ if (type_ahead_buffer_.length() >= maximum_length) {
+ String current = type_ahead_buffer_.ToString();
+ type_ahead_buffer_.Clear();
+ unsigned desired_length = maximum_length - 1;
+ type_ahead_buffer_.Append(current, current.length() - desired_length,
+ desired_length);
+ }
+ type_ahead_buffer_.Append(number);
+ int new_value = TypeAheadValue();
+ if (new_value >= hard_limits_.minimum) {
+ SetValueAsInteger(new_value, kDispatchEvent);
+ } else {
+ has_value_ = false;
+ UpdateVisibleValue(kDispatchEvent);
+ }
+
+ if (type_ahead_buffer_.length() >= maximum_length ||
+ new_value * 10 > range_.maximum)
+ FocusOnNextField();
+
+ keyboard_event->SetDefaultHandled();
+}
+
+bool DateTimeNumericFieldElement::HasValue() const {
+ return has_value_;
+}
+
+void DateTimeNumericFieldElement::Initialize(const AtomicString& pseudo,
+ const String& ax_help_text) {
+ DateTimeFieldElement::Initialize(pseudo, ax_help_text, range_.minimum,
+ range_.maximum);
+}
+
+int DateTimeNumericFieldElement::Maximum() const {
+ return range_.maximum;
+}
+
+void DateTimeNumericFieldElement::SetEmptyValue(EventBehavior event_behavior) {
+ if (IsDisabled())
+ return;
+
+ has_value_ = false;
+ value_ = 0;
+ type_ahead_buffer_.Clear();
+ UpdateVisibleValue(event_behavior);
+}
+
+void DateTimeNumericFieldElement::SetValueAsInteger(
+ int value,
+ EventBehavior event_behavior) {
+ value_ = hard_limits_.ClampValue(value);
+ has_value_ = true;
+ UpdateVisibleValue(event_behavior);
+}
+
+void DateTimeNumericFieldElement::StepDown() {
+ int new_value =
+ RoundDown(has_value_ ? value_ - 1 : DefaultValueForStepDown());
+ if (!range_.IsInRange(new_value))
+ new_value = RoundDown(range_.maximum);
+ type_ahead_buffer_.Clear();
+ SetValueAsInteger(new_value, kDispatchEvent);
+}
+
+void DateTimeNumericFieldElement::StepUp() {
+ int new_value = RoundUp(has_value_ ? value_ + 1 : DefaultValueForStepUp());
+ if (!range_.IsInRange(new_value))
+ new_value = RoundUp(range_.minimum);
+ type_ahead_buffer_.Clear();
+ SetValueAsInteger(new_value, kDispatchEvent);
+}
+
+String DateTimeNumericFieldElement::Value() const {
+ return has_value_ ? FormatValue(value_) : g_empty_string;
+}
+
+int DateTimeNumericFieldElement::ValueAsInteger() const {
+ return has_value_ ? value_ : -1;
+}
+
+int DateTimeNumericFieldElement::TypeAheadValue() const {
+ if (type_ahead_buffer_.length())
+ return type_ahead_buffer_.ToString().ToInt();
+ return -1;
+}
+
+String DateTimeNumericFieldElement::VisibleValue() const {
+ if (type_ahead_buffer_.length())
+ return FormatValue(TypeAheadValue());
+ return has_value_ ? Value() : placeholder_;
+}
+
+int DateTimeNumericFieldElement::RoundDown(int n) const {
+ n -= step_.step_base;
+ if (n >= 0)
+ n = n / step_.step * step_.step;
+ else
+ n = -((-n + step_.step - 1) / step_.step * step_.step);
+ return n + step_.step_base;
+}
+
+int DateTimeNumericFieldElement::RoundUp(int n) const {
+ n -= step_.step_base;
+ if (n >= 0)
+ n = (n + step_.step - 1) / step_.step * step_.step;
+ else
+ n = -(-n / step_.step * step_.step);
+ return n + step_.step_base;
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..eeb5fb5d978
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_NUMERIC_FIELD_ELEMENT_H_
+#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.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+// DateTimeNumericFieldElement represents numeric field of date time format,
+// such as:
+// - hour
+// - minute
+// - millisecond
+// - second
+// - year
+class DateTimeNumericFieldElement : public DateTimeFieldElement {
+ public:
+ struct Step {
+ DISALLOW_NEW();
+ Step(int step = 1, int step_base = 0) : step(step), step_base(step_base) {}
+ int step;
+ int step_base;
+ };
+
+ struct Range {
+ DISALLOW_NEW();
+ Range(int minimum, int maximum) : minimum(minimum), maximum(maximum) {}
+ int ClampValue(int) const;
+ bool IsInRange(int) const;
+ bool IsSingleton() const { return minimum == maximum; }
+
+ int minimum;
+ int maximum;
+ };
+
+ protected:
+ DateTimeNumericFieldElement(Document&,
+ FieldOwner&,
+ const Range&,
+ const Range& hard_limits,
+ const String& placeholder,
+ const Step& = Step());
+
+ int ClampValue(int value) const { return range_.ClampValue(value); }
+ virtual int DefaultValueForStepDown() const;
+ virtual int DefaultValueForStepUp() const;
+ const Range& GetRange() const { return range_; }
+
+ // DateTimeFieldElement functions.
+ bool HasValue() const final;
+ void Initialize(const AtomicString& pseudo, const String& ax_help_text);
+ int Maximum() const;
+ void SetEmptyValue(EventBehavior = kDispatchNoEvent) final;
+ void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override;
+ int ValueAsInteger() const final;
+ String VisibleValue() const final;
+
+ private:
+ // DateTimeFieldElement functions.
+ void HandleKeyboardEvent(KeyboardEvent*) final;
+ float MaximumWidth(const ComputedStyle&) override;
+ void StepDown() final;
+ void StepUp() final;
+ String Value() const final;
+
+ // Node functions.
+ void SetFocused(bool, WebFocusType) final;
+
+ String FormatValue(int) const;
+ int RoundUp(int) const;
+ int RoundDown(int) const;
+ int TypeAheadValue() const;
+
+ const String placeholder_;
+ const Range range_;
+ const Range hard_limits_;
+ const Step step_;
+ int value_;
+ bool has_value_;
+ mutable StringBuilder type_ahead_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeNumericFieldElement);
+};
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.cc
new file mode 100644
index 00000000000..5039df9f369
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.cc
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h"
+
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
+#include "third_party/blink/renderer/platform/text/text_run.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/unicode.h"
+
+namespace blink {
+
+static AtomicString MakeVisibleEmptyValue(const Vector<String>& symbols) {
+ unsigned maximum_length = 0;
+ for (unsigned index = 0; index < symbols.size(); ++index)
+ maximum_length =
+ std::max(maximum_length, NumGraphemeClusters(symbols[index]));
+ StringBuilder builder;
+ builder.ReserveCapacity(maximum_length);
+ for (unsigned length = 0; length < maximum_length; ++length)
+ builder.Append('-');
+ return builder.ToAtomicString();
+}
+
+DateTimeSymbolicFieldElement::DateTimeSymbolicFieldElement(
+ Document& document,
+ FieldOwner& field_owner,
+ const Vector<String>& symbols,
+ int minimum,
+ int maximum)
+ : DateTimeFieldElement(document, field_owner),
+ symbols_(symbols),
+ visible_empty_value_(MakeVisibleEmptyValue(symbols)),
+ selected_index_(-1),
+ type_ahead_(this),
+ minimum_index_(minimum),
+ maximum_index_(maximum) {
+ DCHECK(!symbols.IsEmpty());
+ DCHECK_GE(minimum_index_, 0);
+ SECURITY_DCHECK(maximum_index_ < static_cast<int>(symbols_.size()));
+ DCHECK_LE(minimum_index_, maximum_index_);
+}
+
+float DateTimeSymbolicFieldElement::MaximumWidth(const ComputedStyle& style) {
+ float maximum_width = ComputeTextWidth(style, VisibleEmptyValue());
+ for (unsigned index = 0; index < symbols_.size(); ++index)
+ maximum_width =
+ std::max(maximum_width, ComputeTextWidth(style, symbols_[index]));
+ return maximum_width + DateTimeFieldElement::MaximumWidth(style);
+}
+
+void DateTimeSymbolicFieldElement::HandleKeyboardEvent(
+ KeyboardEvent* keyboard_event) {
+ if (keyboard_event->type() != EventTypeNames::keypress)
+ return;
+
+ const UChar char_code = WTF::Unicode::ToLower(keyboard_event->charCode());
+ if (char_code < ' ')
+ return;
+
+ keyboard_event->SetDefaultHandled();
+
+ int index = type_ahead_.HandleEvent(
+ keyboard_event, TypeAhead::kMatchPrefix | TypeAhead::kCycleFirstChar |
+ TypeAhead::kMatchIndex);
+ if (index < 0)
+ return;
+ SetValueAsInteger(index, kDispatchEvent);
+}
+
+bool DateTimeSymbolicFieldElement::HasValue() const {
+ return selected_index_ >= 0;
+}
+
+void DateTimeSymbolicFieldElement::Initialize(const AtomicString& pseudo,
+ const String& ax_help_text) {
+ // The minimum and maximum below are exposed to users, and 1-based numbers
+ // are natural for symbolic fields. For example, the minimum value of a
+ // month field should be 1, not 0.
+ DateTimeFieldElement::Initialize(pseudo, ax_help_text, minimum_index_ + 1,
+ maximum_index_ + 1);
+}
+
+void DateTimeSymbolicFieldElement::SetEmptyValue(EventBehavior event_behavior) {
+ if (IsDisabled())
+ return;
+ selected_index_ = kInvalidIndex;
+ UpdateVisibleValue(event_behavior);
+}
+
+void DateTimeSymbolicFieldElement::SetValueAsInteger(
+ int new_selected_index,
+ EventBehavior event_behavior) {
+ selected_index_ = std::max(
+ 0, std::min(new_selected_index, static_cast<int>(symbols_.size() - 1)));
+ UpdateVisibleValue(event_behavior);
+}
+
+void DateTimeSymbolicFieldElement::StepDown() {
+ if (HasValue()) {
+ if (!IndexIsInRange(--selected_index_))
+ selected_index_ = maximum_index_;
+ } else {
+ selected_index_ = maximum_index_;
+ }
+ UpdateVisibleValue(kDispatchEvent);
+}
+
+void DateTimeSymbolicFieldElement::StepUp() {
+ if (HasValue()) {
+ if (!IndexIsInRange(++selected_index_))
+ selected_index_ = minimum_index_;
+ } else {
+ selected_index_ = minimum_index_;
+ }
+ UpdateVisibleValue(kDispatchEvent);
+}
+
+String DateTimeSymbolicFieldElement::Value() const {
+ return HasValue() ? symbols_[selected_index_] : g_empty_string;
+}
+
+int DateTimeSymbolicFieldElement::ValueAsInteger() const {
+ return selected_index_;
+}
+
+int DateTimeSymbolicFieldElement::ValueForARIAValueNow() const {
+ // Synchronize with minimum/maximum adjustment in initialize().
+ return selected_index_ + 1;
+}
+
+String DateTimeSymbolicFieldElement::VisibleEmptyValue() const {
+ return visible_empty_value_;
+}
+
+String DateTimeSymbolicFieldElement::VisibleValue() const {
+ return HasValue() ? symbols_[selected_index_] : VisibleEmptyValue();
+}
+
+int DateTimeSymbolicFieldElement::IndexOfSelectedOption() const {
+ return selected_index_;
+}
+
+int DateTimeSymbolicFieldElement::OptionCount() const {
+ return symbols_.size();
+}
+
+String DateTimeSymbolicFieldElement::OptionAtIndex(int index) const {
+ return symbols_[index];
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h
new file mode 100644
index 00000000000..8f515406d1e
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_SYMBOLIC_FIELD_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_SYMBOLIC_FIELD_ELEMENT_H_
+
+#include "base/macros.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_field_element.h"
+#include "third_party/blink/renderer/core/html/forms/type_ahead.h"
+
+namespace blink {
+
+// DateTimeSymbolicFieldElement represents non-numeric field of data time
+// format, such as: AM/PM, and month.
+class DateTimeSymbolicFieldElement : public DateTimeFieldElement,
+ public TypeAheadDataSource {
+ protected:
+ DateTimeSymbolicFieldElement(Document&,
+ FieldOwner&,
+ const Vector<String>&,
+ int minimum,
+ int maximum);
+ size_t SymbolsSize() const { return symbols_.size(); }
+ bool HasValue() const final;
+ void Initialize(const AtomicString& pseudo, const String& ax_help_text);
+ void SetEmptyValue(EventBehavior = kDispatchNoEvent) final;
+ void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) final;
+ int ValueAsInteger() const final;
+
+ private:
+ static const int kInvalidIndex = -1;
+
+ String VisibleEmptyValue() const;
+ bool IndexIsInRange(int index) const {
+ return index >= minimum_index_ && index <= maximum_index_;
+ }
+
+ // DateTimeFieldElement functions.
+ void HandleKeyboardEvent(KeyboardEvent*) final;
+ float MaximumWidth(const ComputedStyle&) override;
+ void StepDown() final;
+ void StepUp() final;
+ String Value() const final;
+ int ValueForARIAValueNow() const final;
+ String VisibleValue() const final;
+
+ // TypeAheadDataSource functions.
+ int IndexOfSelectedOption() const override;
+ int OptionCount() const override;
+ String OptionAtIndex(int index) const override;
+
+ const Vector<String> symbols_;
+
+ // We use AtomicString to share visible empty value among multiple
+ // DateTimeEditElements in the page.
+ const AtomicString visible_empty_value_;
+ int selected_index_;
+ TypeAhead type_ahead_;
+ const int minimum_index_;
+ const int maximum_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeSymbolicFieldElement);
+};
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/email_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.cc
new file mode 100644
index 00000000000..4955ba9d605
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.cc
@@ -0,0 +1,322 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com>
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * 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/email_input_type.h"
+
+#include <unicode/idna.h>
+#include <unicode/unistr.h>
+#include <unicode/uvernum.h>
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+#if U_ICU_VERSION_MAJOR_NUM >= 59
+#include <unicode/char16ptr.h>
+#endif
+
+namespace blink {
+
+using blink::WebLocalizedString;
+
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
+static const char kLocalPartCharacters[] =
+ "abcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+/=?^_`{|}~.-";
+static const char kEmailPattern[] =
+ "[a-z0-9!#$%&'*+/=?^_`{|}~.-]+" // local part
+ "@"
+ "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?" // domain part
+ "(?:\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*";
+
+// RFC5321 says the maximum total length of a domain name is 255 octets.
+static const int32_t kMaximumDomainNameLength = 255;
+// Use the same option as in url/url_canon_icu.cc
+static const int32_t kIdnaConversionOption = UIDNA_CHECK_BIDI;
+
+std::unique_ptr<ScriptRegexp> EmailInputType::CreateEmailRegexp() {
+ return std::make_unique<ScriptRegexp>(kEmailPattern,
+ kTextCaseUnicodeInsensitive);
+}
+
+String EmailInputType::ConvertEmailAddressToASCII(const ScriptRegexp& regexp,
+ const String& address) {
+ if (address.ContainsOnlyASCII())
+ return address;
+
+ size_t at_position = address.find('@');
+ if (at_position == kNotFound)
+ return address;
+ String host = address.Substring(at_position + 1);
+
+ // UnicodeString ctor for copy-on-write does not work reliably (in debug
+ // build.) TODO(jshin): In an unlikely case this is a perf-issue, treat
+ // 8bit and non-8bit strings separately.
+ host.Ensure16Bit();
+ icu::UnicodeString idn_domain_name(host.Characters16(), host.length());
+ icu::UnicodeString domain_name;
+
+ // Leak |idna| at the end.
+ UErrorCode error_code = U_ZERO_ERROR;
+ static icu::IDNA* idna =
+ icu::IDNA::createUTS46Instance(kIdnaConversionOption, error_code);
+ DCHECK(idna);
+ icu::IDNAInfo idna_info;
+ idna->nameToASCII(idn_domain_name, domain_name, idna_info, error_code);
+ if (U_FAILURE(error_code) || idna_info.hasErrors() ||
+ domain_name.length() > kMaximumDomainNameLength)
+ return address;
+
+ StringBuilder builder;
+ builder.Append(address, 0, at_position + 1);
+#if U_ICU_VERSION_MAJOR_NUM >= 59
+ builder.Append(icu::toUCharPtr(domain_name.getBuffer()), domain_name.length());
+#else
+ builder.Append(domain_name.getBuffer(), domain_name.length());
+#endif
+ String ascii_email = builder.ToString();
+ return IsValidEmailAddress(regexp, ascii_email) ? ascii_email : address;
+}
+
+String EmailInputType::ConvertEmailAddressToUnicode(
+ const String& address) const {
+ if (!address.ContainsOnlyASCII())
+ return address;
+
+ size_t at_position = address.find('@');
+ if (at_position == kNotFound)
+ return address;
+
+ if (address.Find("xn--", at_position + 1) == kNotFound)
+ return address;
+
+ String unicode_host = Platform::Current()->ConvertIDNToUnicode(
+ address.Substring(at_position + 1));
+ StringBuilder builder;
+ builder.Append(address, 0, at_position + 1);
+ builder.Append(unicode_host);
+ return builder.ToString();
+}
+
+static bool IsInvalidLocalPartCharacter(UChar ch) {
+ if (!IsASCII(ch))
+ return true;
+ DEFINE_STATIC_LOCAL(const String, valid_characters, (kLocalPartCharacters));
+ return valid_characters.find(ToASCIILower(ch)) == kNotFound;
+}
+
+static bool IsInvalidDomainCharacter(UChar ch) {
+ if (!IsASCII(ch))
+ return true;
+ return !IsASCIILower(ch) && !IsASCIIUpper(ch) && !IsASCIIDigit(ch) &&
+ ch != '.' && ch != '-';
+}
+
+static bool CheckValidDotUsage(const String& domain) {
+ if (domain.IsEmpty())
+ return true;
+ if (domain[0] == '.' || domain[domain.length() - 1] == '.')
+ return false;
+ return domain.Find("..") == kNotFound;
+}
+
+bool EmailInputType::IsValidEmailAddress(const ScriptRegexp& regexp,
+ const String& address) {
+ int address_length = address.length();
+ if (!address_length)
+ return false;
+
+ int match_length;
+ int match_offset = regexp.Match(address, 0, &match_length);
+
+ return !match_offset && match_length == address_length;
+}
+
+EmailInputType::EmailInputType(HTMLInputElement& element)
+ : BaseTextInputType(element) {}
+
+InputType* EmailInputType::Create(HTMLInputElement& element) {
+ return new EmailInputType(element);
+}
+
+void EmailInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeEmail);
+ bool has_max_length = GetElement().FastHasAttribute(HTMLNames::maxlengthAttr);
+ if (has_max_length)
+ CountUsageIfVisible(WebFeature::kInputTypeEmailMaxLength);
+ if (GetElement().Multiple()) {
+ CountUsageIfVisible(WebFeature::kInputTypeEmailMultiple);
+ if (has_max_length)
+ CountUsageIfVisible(WebFeature::kInputTypeEmailMultipleMaxLength);
+ }
+}
+
+const AtomicString& EmailInputType::FormControlType() const {
+ return InputTypeNames::email;
+}
+
+ScriptRegexp& EmailInputType::EnsureEmailRegexp() const {
+ if (!email_regexp_)
+ email_regexp_ = CreateEmailRegexp();
+ return *email_regexp_;
+}
+
+// The return value is an invalid email address string if the specified string
+// contains an invalid email address. Otherwise, null string is returned.
+// If an empty string is returned, it means empty address is specified.
+// e.g. "foo@example.com,,bar@example.com" for multiple case.
+String EmailInputType::FindInvalidAddress(const String& value) const {
+ if (value.IsEmpty())
+ return String();
+ if (!GetElement().Multiple())
+ return IsValidEmailAddress(EnsureEmailRegexp(), value) ? String() : value;
+ Vector<String> addresses;
+ value.Split(',', true, addresses);
+ for (const auto& address : addresses) {
+ String stripped = StripLeadingAndTrailingHTMLSpaces(address);
+ if (!IsValidEmailAddress(EnsureEmailRegexp(), stripped))
+ return stripped;
+ }
+ return String();
+}
+
+bool EmailInputType::TypeMismatchFor(const String& value) const {
+ return !FindInvalidAddress(value).IsNull();
+}
+
+bool EmailInputType::TypeMismatch() const {
+ return TypeMismatchFor(GetElement().value());
+}
+
+String EmailInputType::TypeMismatchText() const {
+ String invalid_address = FindInvalidAddress(GetElement().value());
+ DCHECK(!invalid_address.IsNull());
+ if (invalid_address.IsEmpty())
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmailEmpty);
+ String at_sign = String("@");
+ size_t at_index = invalid_address.find('@');
+ if (at_index == kNotFound)
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmailNoAtSign, at_sign,
+ invalid_address);
+ // We check validity against an ASCII value because of difficulty to check
+ // invalid characters. However we should show Unicode value.
+ String unicode_address = ConvertEmailAddressToUnicode(invalid_address);
+ String local_part = invalid_address.Left(at_index);
+ String domain = invalid_address.Substring(at_index + 1);
+ if (local_part.IsEmpty())
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmailEmptyLocal, at_sign,
+ unicode_address);
+ if (domain.IsEmpty())
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmailEmptyDomain, at_sign,
+ unicode_address);
+ size_t invalid_char_index = local_part.Find(IsInvalidLocalPartCharacter);
+ if (invalid_char_index != kNotFound) {
+ unsigned char_length = U_IS_LEAD(local_part[invalid_char_index]) ? 2 : 1;
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmailInvalidLocal,
+ at_sign, local_part.Substring(invalid_char_index, char_length));
+ }
+ invalid_char_index = domain.Find(IsInvalidDomainCharacter);
+ if (invalid_char_index != kNotFound) {
+ unsigned char_length = U_IS_LEAD(domain[invalid_char_index]) ? 2 : 1;
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmailInvalidDomain,
+ at_sign, domain.Substring(invalid_char_index, char_length));
+ }
+ if (!CheckValidDotUsage(domain)) {
+ size_t at_index_in_unicode = unicode_address.find('@');
+ DCHECK_NE(at_index_in_unicode, kNotFound);
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmailInvalidDots,
+ String("."), unicode_address.Substring(at_index_in_unicode + 1));
+ }
+ if (GetElement().Multiple())
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForMultipleEmail);
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForEmail);
+}
+
+bool EmailInputType::SupportsSelectionAPI() const {
+ return false;
+}
+
+String EmailInputType::SanitizeValue(const String& proposed_value) const {
+ String no_line_break_value = proposed_value.RemoveCharacters(IsHTMLLineBreak);
+ if (!GetElement().Multiple())
+ return StripLeadingAndTrailingHTMLSpaces(no_line_break_value);
+ Vector<String> addresses;
+ no_line_break_value.Split(',', true, addresses);
+ StringBuilder stripped_value;
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ if (i > 0)
+ stripped_value.Append(',');
+ stripped_value.Append(StripLeadingAndTrailingHTMLSpaces(addresses[i]));
+ }
+ return stripped_value.ToString();
+}
+
+String EmailInputType::ConvertFromVisibleValue(
+ const String& visible_value) const {
+ String sanitized_value = SanitizeValue(visible_value);
+ if (!GetElement().Multiple())
+ return ConvertEmailAddressToASCII(EnsureEmailRegexp(), sanitized_value);
+ Vector<String> addresses;
+ sanitized_value.Split(',', true, addresses);
+ StringBuilder builder;
+ builder.ReserveCapacity(sanitized_value.length());
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ if (i > 0)
+ builder.Append(',');
+ builder.Append(
+ ConvertEmailAddressToASCII(EnsureEmailRegexp(), addresses[i]));
+ }
+ return builder.ToString();
+}
+
+String EmailInputType::VisibleValue() const {
+ String value = GetElement().value();
+ if (!GetElement().Multiple())
+ return ConvertEmailAddressToUnicode(value);
+
+ Vector<String> addresses;
+ value.Split(',', true, addresses);
+ StringBuilder builder;
+ builder.ReserveCapacity(value.length());
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ if (i > 0)
+ builder.Append(',');
+ builder.Append(ConvertEmailAddressToUnicode(addresses[i]));
+ }
+ return builder.ToString();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/email_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.h
new file mode 100644
index 00000000000..58a6e745bb0
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EMAIL_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EMAIL_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h"
+
+namespace blink {
+
+class EmailInputType final : public BaseTextInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ // They are public for unit testing.
+ CORE_EXPORT static String ConvertEmailAddressToASCII(const ScriptRegexp&,
+ const String&);
+ CORE_EXPORT static bool IsValidEmailAddress(const ScriptRegexp&,
+ const String&);
+ CORE_EXPORT static std::unique_ptr<ScriptRegexp> CreateEmailRegexp();
+
+ private:
+ explicit EmailInputType(HTMLInputElement&);
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ bool TypeMismatchFor(const String&) const override;
+ bool TypeMismatch() const override;
+ String TypeMismatchText() const override;
+ bool SupportsSelectionAPI() const override;
+ String SanitizeValue(const String&) const override;
+ String ConvertFromVisibleValue(const String&) const override;
+ String VisibleValue() const override;
+
+ ScriptRegexp& EnsureEmailRegexp() const;
+ String ConvertEmailAddressToUnicode(const String&) const;
+ String FindInvalidAddress(const String&) const;
+
+ mutable std::unique_ptr<ScriptRegexp> email_regexp_;
+};
+
+} // namespace blink
+
+#endif // ButtonInputType_h
diff --git a/chromium/third_party/blink/renderer/core/html/forms/email_input_type_test.cc b/chromium/third_party/blink/renderer/core/html/forms/email_input_type_test.cc
new file mode 100644
index 00000000000..b702e425cc4
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/email_input_type_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2015 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/email_input_type.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h"
+
+namespace blink {
+
+namespace {
+
+void ExpectToSucceed(const String& source) {
+ std::unique_ptr<ScriptRegexp> email_regexp =
+ EmailInputType::CreateEmailRegexp();
+ String result =
+ EmailInputType::ConvertEmailAddressToASCII(*email_regexp, source);
+ EXPECT_NE(source, result);
+ EXPECT_TRUE(EmailInputType::IsValidEmailAddress(*email_regexp, result));
+}
+
+void ExpectToFail(const String& source) {
+ std::unique_ptr<ScriptRegexp> email_regexp =
+ EmailInputType::CreateEmailRegexp();
+ // Conversion failed. The resultant value might contains non-ASCII
+ // characters, and not a valid email address.
+ EXPECT_FALSE(EmailInputType::IsValidEmailAddress(
+ *email_regexp,
+ EmailInputType::ConvertEmailAddressToASCII(*email_regexp, source)));
+}
+
+} // namespace
+
+TEST(EmailInputTypeTest, ConvertEmailAddressToASCII) {
+ // U+043C U+043E U+0439 . U+0434 U+043E U+043C U+0435 U+043D
+ ExpectToFail(
+ String::FromUTF8("user@\xD0\xBC\xD0\xBE\xD0\xB9."
+ "\xD0\xB4\xD0\xBE\xD0\xBC\xD0\xB5\xD0\xBD@"));
+ ExpectToFail(
+ String::FromUTF8("user@\xD0\xBC\xD0\xBE\xD0\xB9. "
+ "\xD0\xB4\xD0\xBE\xD0\xBC\xD0\xB5\xD0\xBD"));
+ ExpectToFail(
+ String::FromUTF8("user@\xD0\xBC\xD0\xBE\xD0\xB9."
+ "\t\xD0\xB4\xD0\xBE\xD0\xBC\xD0\xB5\xD0\xBD"));
+}
+
+TEST(EmailInputTypeTest, ConvertEmailAddressToASCIIUTS46) {
+ // http://unicode.org/reports/tr46/#Table_IDNA_Comparisons
+
+ // U+00E0
+ ExpectToSucceed(String::FromUTF8("foo@\xC3\xA0.com"));
+ // U+FF01
+ ExpectToFail(String::FromUTF8("foo@\xEF\xBC\x81.com"));
+
+ // U+2132
+ ExpectToFail(String::FromUTF8("foo@\xE2\x84\xB2.com"));
+ // U+2F868
+ ExpectToFail(String::FromUTF8("foo@\xF0\xAF\xA1\xA8.com"));
+
+ // U+00C0
+ ExpectToSucceed(String::FromUTF8("foo@\xC3\x80.com"));
+ // U+2665
+ ExpectToSucceed(String::FromUTF8("foo@\xE2\x99\xA5.com"));
+ // U+00DF
+ ExpectToSucceed(String::FromUTF8("foo@\xC3\x9F.com"));
+
+ // U+0221
+ ExpectToSucceed(String::FromUTF8("foo@\xC8\xA1.com"));
+ // U+0662
+ ExpectToFail(String::FromUTF8("foo@\xD8\x82.com"));
+
+ // U+2615
+ ExpectToSucceed(String::FromUTF8("foo@\xE2\x98\x95.com"));
+ // U+023A
+ ExpectToSucceed(String::FromUTF8("foo@\xC8\xBA.com"));
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..a50c2db2d8c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/external_date_time_chooser.h"
+
+#include "third_party/blink/public/web/web_date_time_chooser_completion.h"
+#include "third_party/blink/public/web/web_date_time_chooser_params.h"
+#include "third_party/blink/public/web/web_view_client.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+
+namespace blink {
+
+class WebDateTimeChooserCompletionImpl : public WebDateTimeChooserCompletion {
+ public:
+ WebDateTimeChooserCompletionImpl(ExternalDateTimeChooser* chooser)
+ : chooser_(chooser) {}
+
+ private:
+ void DidChooseValue(double value) override {
+ chooser_->DidChooseValue(value);
+ delete this;
+ }
+
+ void DidCancelChooser() override {
+ chooser_->DidCancelChooser();
+ delete this;
+ }
+
+ Persistent<ExternalDateTimeChooser> chooser_;
+};
+
+ExternalDateTimeChooser::~ExternalDateTimeChooser() = default;
+
+void ExternalDateTimeChooser::Trace(blink::Visitor* visitor) {
+ visitor->Trace(client_);
+ DateTimeChooser::Trace(visitor);
+}
+
+ExternalDateTimeChooser::ExternalDateTimeChooser(DateTimeChooserClient* client)
+ : client_(client) {
+ DCHECK(!RuntimeEnabledFeatures::InputMultipleFieldsUIEnabled());
+ DCHECK(client);
+}
+
+ExternalDateTimeChooser* ExternalDateTimeChooser::Create(
+ ChromeClient* chrome_client,
+ WebViewClient* web_view_client,
+ DateTimeChooserClient* client,
+ const DateTimeChooserParameters& parameters) {
+ DCHECK(chrome_client);
+ ExternalDateTimeChooser* chooser = new ExternalDateTimeChooser(client);
+ if (!chooser->OpenDateTimeChooser(chrome_client, web_view_client, parameters))
+ chooser = nullptr;
+ return chooser;
+}
+
+static WebDateTimeInputType ToWebDateTimeInputType(const AtomicString& source) {
+ if (source == InputTypeNames::date)
+ return kWebDateTimeInputTypeDate;
+ if (source == InputTypeNames::datetime)
+ return kWebDateTimeInputTypeDateTime;
+ if (source == InputTypeNames::datetime_local)
+ return kWebDateTimeInputTypeDateTimeLocal;
+ if (source == InputTypeNames::month)
+ return kWebDateTimeInputTypeMonth;
+ if (source == InputTypeNames::time)
+ return kWebDateTimeInputTypeTime;
+ if (source == InputTypeNames::week)
+ return kWebDateTimeInputTypeWeek;
+ return kWebDateTimeInputTypeNone;
+}
+
+bool ExternalDateTimeChooser::OpenDateTimeChooser(
+ ChromeClient* chrome_client,
+ WebViewClient* web_view_client,
+ const DateTimeChooserParameters& parameters) {
+ if (!web_view_client)
+ return false;
+
+ WebDateTimeChooserParams web_params;
+ web_params.type = ToWebDateTimeInputType(parameters.type);
+ web_params.anchor_rect_in_screen = parameters.anchor_rect_in_screen;
+ web_params.double_value = parameters.double_value;
+ web_params.suggestions = parameters.suggestions;
+ web_params.minimum = parameters.minimum;
+ web_params.maximum = parameters.maximum;
+ web_params.step = parameters.step;
+ web_params.step_base = parameters.step_base;
+ web_params.is_required = parameters.required;
+ web_params.is_anchor_element_rtl = parameters.is_anchor_element_rtl;
+
+ WebDateTimeChooserCompletion* completion =
+ new WebDateTimeChooserCompletionImpl(this);
+ if (web_view_client->OpenDateTimeChooser(web_params, completion))
+ return true;
+ // We can't open a chooser. Calling
+ // WebDateTimeChooserCompletionImpl::didCancelChooser to delete the
+ // WebDateTimeChooserCompletionImpl object and deref this.
+ completion->DidCancelChooser();
+ return false;
+}
+
+void ExternalDateTimeChooser::DidChooseValue(const WebString& value) {
+ if (client_)
+ client_->DidChooseValue(value);
+ // didChooseValue might run JavaScript code, and endChooser() might be
+ // called. However DateTimeChooserCompletionImpl still has one reference to
+ // this object.
+ if (client_)
+ client_->DidEndChooser();
+}
+
+void ExternalDateTimeChooser::DidChooseValue(double value) {
+ if (client_)
+ client_->DidChooseValue(value);
+ // didChooseValue might run JavaScript code, and endChooser() might be
+ // called. However DateTimeChooserCompletionImpl still has one reference to
+ // this object.
+ if (client_)
+ client_->DidEndChooser();
+}
+
+void ExternalDateTimeChooser::DidCancelChooser() {
+ if (client_)
+ client_->DidEndChooser();
+}
+
+void ExternalDateTimeChooser::EndChooser() {
+ DateTimeChooserClient* client = client_;
+ client_ = nullptr;
+ client->DidEndChooser();
+}
+
+AXObject* ExternalDateTimeChooser::RootAXObject() {
+ return nullptr;
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..19d512520a1
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#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 "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h"
+
+namespace blink {
+
+class ChromeClient;
+class DateTimeChooserClient;
+class WebString;
+class WebViewClient;
+
+class CORE_EXPORT ExternalDateTimeChooser final : public DateTimeChooser {
+ public:
+ static ExternalDateTimeChooser* Create(ChromeClient*,
+ WebViewClient*,
+ DateTimeChooserClient*,
+ const DateTimeChooserParameters&);
+ ~ExternalDateTimeChooser() override;
+ void Trace(blink::Visitor*) override;
+
+ // The following functions are for DateTimeChooserCompletion.
+ void DidChooseValue(const WebString&);
+ void DidChooseValue(double);
+ void DidCancelChooser();
+
+ private:
+ ExternalDateTimeChooser(DateTimeChooserClient*);
+ bool OpenDateTimeChooser(ChromeClient*,
+ WebViewClient*,
+ const DateTimeChooserParameters&);
+
+ // DateTimeChooser function:
+ void EndChooser() override;
+ AXObject* RootAXObject() override;
+
+ Member<DateTimeChooserClient> client_;
+};
+}
+#endif
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
new file mode 100644
index 00000000000..3e5f3525d29
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/external_popup_menu.h"
+
+#include "build/build_config.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_frame_client.h"
+#include "third_party/blink/public/web/web_menu_item_info.h"
+#include "third_party/blink/public/web/web_popup_menu_info.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/events/current_input_event.h"
+#include "third_party/blink/renderer/core/exported/web_view_impl.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_local_frame_impl.h"
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
+#include "third_party/blink/renderer/core/layout/layout_box.h"
+#include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/platform/geometry/float_quad.h"
+#include "third_party/blink/renderer/platform/geometry/int_point.h"
+#include "third_party/blink/renderer/platform/text/text_direction.h"
+#if defined(OS_MACOSX)
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#endif
+
+namespace blink {
+
+ExternalPopupMenu::ExternalPopupMenu(LocalFrame& frame,
+ HTMLSelectElement& owner_element,
+ WebView& web_view)
+ : owner_element_(owner_element),
+ local_frame_(frame),
+ web_view_(web_view),
+ dispatch_event_timer_(frame.GetTaskRunner(TaskType::kUnspecedTimer),
+ this,
+ &ExternalPopupMenu::DispatchEvent),
+ web_external_popup_menu_(nullptr) {}
+
+ExternalPopupMenu::~ExternalPopupMenu() = default;
+
+void ExternalPopupMenu::Trace(blink::Visitor* visitor) {
+ visitor->Trace(owner_element_);
+ visitor->Trace(local_frame_);
+ PopupMenu::Trace(visitor);
+}
+
+bool ExternalPopupMenu::ShowInternal() {
+ // Blink core reuses the PopupMenu of an element. For simplicity, we do
+ // recreate the actual external popup everytime.
+ if (web_external_popup_menu_) {
+ web_external_popup_menu_->Close();
+ web_external_popup_menu_ = nullptr;
+ }
+
+ WebPopupMenuInfo info;
+ GetPopupMenuInfo(info, *owner_element_);
+ if (info.items.empty())
+ return false;
+ WebLocalFrameImpl* webframe =
+ WebLocalFrameImpl::FromFrame(local_frame_.Get());
+ web_external_popup_menu_ =
+ webframe->Client()->CreateExternalPopupMenu(info, this);
+ if (web_external_popup_menu_) {
+ LayoutObject* layout_object = owner_element_->GetLayoutObject();
+ if (!layout_object || !layout_object->IsBox())
+ return false;
+ FloatQuad quad(ToLayoutBox(layout_object)
+ ->LocalToAbsoluteQuad(FloatQuad(
+ ToLayoutBox(layout_object)->BorderBoundingBox())));
+ IntRect rect(quad.EnclosingBoundingBox());
+ IntRect rect_in_viewport = local_frame_->View()->ContentsToViewport(rect);
+ web_external_popup_menu_->Show(rect_in_viewport);
+ return true;
+ }
+
+ // The client might refuse to create a popup (when there is already one
+ // pending to be shown for example).
+ DidCancel();
+ return false;
+}
+
+void ExternalPopupMenu::Show() {
+ if (!ShowInternal())
+ return;
+#if defined(OS_MACOSX)
+ const WebInputEvent* current_event = CurrentInputEvent::Get();
+ if (current_event && current_event->GetType() == WebInputEvent::kMouseDown) {
+ synthetic_event_ = std::make_unique<WebMouseEvent>();
+ *synthetic_event_ = *static_cast<const WebMouseEvent*>(current_event);
+ synthetic_event_->SetType(WebInputEvent::kMouseUp);
+ dispatch_event_timer_.StartOneShot(TimeDelta(), FROM_HERE);
+ // FIXME: show() is asynchronous. If preparing a popup is slow and a
+ // user released the mouse button before showing the popup, mouseup and
+ // click events are correctly dispatched. Dispatching the synthetic
+ // mouseup event is redundant in this case.
+ }
+#endif
+}
+
+void ExternalPopupMenu::DispatchEvent(TimerBase*) {
+ web_view_.HandleInputEvent(blink::WebCoalescedInputEvent(*synthetic_event_));
+ synthetic_event_.reset();
+}
+
+void ExternalPopupMenu::Hide() {
+ if (owner_element_)
+ owner_element_->PopupDidHide();
+ if (!web_external_popup_menu_)
+ return;
+ web_external_popup_menu_->Close();
+ web_external_popup_menu_ = nullptr;
+}
+
+void ExternalPopupMenu::UpdateFromElement(UpdateReason reason) {
+ switch (reason) {
+ case kBySelectionChange:
+ case kByDOMChange:
+ if (needs_update_)
+ return;
+ needs_update_ = true;
+ owner_element_->GetDocument()
+ .GetTaskRunner(TaskType::kUserInteraction)
+ ->PostTask(FROM_HERE, WTF::Bind(&ExternalPopupMenu::Update,
+ WrapPersistent(this)));
+ break;
+
+ case kByStyleChange:
+ // TODO(tkent): We should update the popup location/content in some
+ // cases. e.g. Updating ComputedStyle of the SELECT element affects
+ // popup position and OPTION style.
+ break;
+ }
+}
+
+void ExternalPopupMenu::Update() {
+ if (!web_external_popup_menu_ || !owner_element_)
+ return;
+ owner_element_->GetDocument().UpdateStyleAndLayoutTree();
+ // disconnectClient() might have been called.
+ if (!owner_element_)
+ return;
+ needs_update_ = false;
+
+ if (ShowInternal())
+ return;
+ // We failed to show a popup. Notify it to the owner.
+ Hide();
+}
+
+void ExternalPopupMenu::DisconnectClient() {
+ Hide();
+ owner_element_ = nullptr;
+}
+
+void ExternalPopupMenu::DidChangeSelection(int index) {}
+
+void ExternalPopupMenu::DidAcceptIndex(int index) {
+ // Calling methods on the HTMLSelectElement might lead to this object being
+ // derefed. This ensures it does not get deleted while we are running this
+ // method.
+ int popup_menu_item_index = ToPopupMenuItemIndex(index, *owner_element_);
+
+ if (owner_element_) {
+ owner_element_->PopupDidHide();
+ owner_element_->SelectOptionByPopup(popup_menu_item_index);
+ }
+ web_external_popup_menu_ = nullptr;
+}
+
+// Android uses this function even for single SELECT.
+void ExternalPopupMenu::DidAcceptIndices(const WebVector<int>& indices) {
+ if (!owner_element_) {
+ web_external_popup_menu_ = nullptr;
+ return;
+ }
+
+ HTMLSelectElement* owner_element = owner_element_;
+ owner_element->PopupDidHide();
+
+ if (indices.size() == 0) {
+ owner_element->SelectOptionByPopup(-1);
+ } else if (!owner_element->IsMultiple()) {
+ owner_element->SelectOptionByPopup(
+ ToPopupMenuItemIndex(indices[indices.size() - 1], *owner_element));
+ } else {
+ Vector<int> list_indices;
+ list_indices.ReserveCapacity(indices.size());
+ for (size_t i = 0; i < indices.size(); ++i)
+ list_indices.push_back(ToPopupMenuItemIndex(indices[i], *owner_element));
+ owner_element->SelectMultipleOptionsByPopup(list_indices);
+ }
+
+ web_external_popup_menu_ = nullptr;
+}
+
+void ExternalPopupMenu::DidCancel() {
+ if (owner_element_)
+ owner_element_->PopupDidHide();
+ web_external_popup_menu_ = nullptr;
+}
+
+void ExternalPopupMenu::GetPopupMenuInfo(WebPopupMenuInfo& info,
+ HTMLSelectElement& owner_element) {
+ const HeapVector<Member<HTMLElement>>& list_items =
+ owner_element.GetListItems();
+ size_t item_count = list_items.size();
+ size_t count = 0;
+ Vector<WebMenuItemInfo> items(item_count);
+ for (size_t i = 0; i < item_count; ++i) {
+ if (owner_element.ItemIsDisplayNone(*list_items[i]))
+ continue;
+
+ Element& item_element = *list_items[i];
+ WebMenuItemInfo& popup_item = items[count++];
+ popup_item.label = owner_element.ItemText(item_element);
+ popup_item.tool_tip = item_element.title();
+ popup_item.checked = false;
+ if (IsHTMLHRElement(item_element)) {
+ popup_item.type = WebMenuItemInfo::kSeparator;
+ } else if (IsHTMLOptGroupElement(item_element)) {
+ popup_item.type = WebMenuItemInfo::kGroup;
+ } else {
+ popup_item.type = WebMenuItemInfo::kOption;
+ popup_item.checked = ToHTMLOptionElement(item_element).Selected();
+ }
+ popup_item.enabled = !item_element.IsDisabledFormControl();
+ const ComputedStyle& style = *owner_element.ItemComputedStyle(item_element);
+ popup_item.text_direction = ToWebTextDirection(style.Direction());
+ popup_item.has_text_direction_override = IsOverride(style.GetUnicodeBidi());
+ }
+
+ const ComputedStyle& menu_style = owner_element.GetComputedStyle()
+ ? *owner_element.GetComputedStyle()
+ : *owner_element.EnsureComputedStyle();
+ const SimpleFontData* font_data = menu_style.GetFont().PrimaryFont();
+ DCHECK(font_data);
+ info.item_height = font_data ? font_data->GetFontMetrics().Height() : 0;
+ info.item_font_size = static_cast<int>(
+ menu_style.GetFont().GetFontDescription().ComputedSize());
+ info.selected_index = ToExternalPopupMenuItemIndex(
+ owner_element.SelectedListIndex(), owner_element);
+ info.right_aligned = menu_style.Direction() == TextDirection::kRtl;
+ info.allow_multiple_selection = owner_element.IsMultiple();
+ if (count < item_count)
+ items.Shrink(count);
+ info.items = items;
+}
+
+int ExternalPopupMenu::ToPopupMenuItemIndex(int external_popup_menu_item_index,
+ HTMLSelectElement& owner_element) {
+ if (external_popup_menu_item_index < 0)
+ return external_popup_menu_item_index;
+
+ int index_tracker = 0;
+ const HeapVector<Member<HTMLElement>>& items = owner_element.GetListItems();
+ for (int i = 0; i < static_cast<int>(items.size()); ++i) {
+ if (owner_element.ItemIsDisplayNone(*items[i]))
+ continue;
+ if (index_tracker++ == external_popup_menu_item_index)
+ return i;
+ }
+ return -1;
+}
+
+int ExternalPopupMenu::ToExternalPopupMenuItemIndex(
+ int popup_menu_item_index,
+ HTMLSelectElement& owner_element) {
+ if (popup_menu_item_index < 0)
+ return popup_menu_item_index;
+
+ size_t index_tracker = 0;
+ const HeapVector<Member<HTMLElement>>& items = owner_element.GetListItems();
+ for (int i = 0; i < static_cast<int>(items.size()); ++i) {
+ if (owner_element.ItemIsDisplayNone(*items[i]))
+ continue;
+ if (popup_menu_item_index == i)
+ return index_tracker;
+ ++index_tracker;
+ }
+ return -1;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.h b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.h
new file mode 100644
index 00000000000..742f8ddd661
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_POPUP_MENU_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_POPUP_MENU_H_
+
+#include <memory>
+#include "third_party/blink/public/platform/web_canvas.h"
+#include "third_party/blink/public/platform/web_scrollbar.h"
+#include "third_party/blink/public/web/web_external_popup_menu_client.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/popup_menu.h"
+#include "third_party/blink/renderer/platform/timer.h"
+#include "third_party/blink/renderer/platform/wtf/compiler.h"
+
+namespace blink {
+
+class HTMLSelectElement;
+class LocalFrame;
+class WebExternalPopupMenu;
+class WebMouseEvent;
+class WebView;
+struct WebPopupMenuInfo;
+
+// The ExternalPopupMenu is a PopupMenu implementation for macOS and Android.
+// It uses a OS-native menu implementation.
+class CORE_EXPORT ExternalPopupMenu final : public PopupMenu,
+ public WebExternalPopupMenuClient {
+ public:
+ ExternalPopupMenu(LocalFrame&, HTMLSelectElement&, WebView&);
+ ~ExternalPopupMenu() override;
+
+ // Fills |info| with the popup menu information contained in the
+ // PopupMenuClient associated with this ExternalPopupMenu.
+ // FIXME: public only for test access. Need to revert once gtest
+ // helpers from chromium are available for blink.
+ static void GetPopupMenuInfo(WebPopupMenuInfo&, HTMLSelectElement&);
+ static int ToPopupMenuItemIndex(int index, HTMLSelectElement&);
+ static int ToExternalPopupMenuItemIndex(int index, HTMLSelectElement&);
+
+ void Trace(blink::Visitor*) override;
+
+ private:
+ // PopupMenu methods:
+ void Show() override;
+ void Hide() override;
+ void UpdateFromElement(UpdateReason) override;
+ void DisconnectClient() override;
+
+ // WebExternalPopupClient methods:
+ void DidChangeSelection(int index) override;
+ void DidAcceptIndex(int index) override;
+ void DidAcceptIndices(const WebVector<int>& indices) override;
+ void DidCancel() override;
+
+ bool ShowInternal();
+ void DispatchEvent(TimerBase*);
+ void Update();
+
+ Member<HTMLSelectElement> owner_element_;
+ Member<LocalFrame> local_frame_;
+ WebView& web_view_;
+ std::unique_ptr<WebMouseEvent> synthetic_event_;
+ TaskRunnerTimer<ExternalPopupMenu> dispatch_event_timer_;
+ // The actual implementor of the show menu.
+ WebExternalPopupMenu* web_external_popup_menu_;
+ bool needs_update_ = false;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_POPUP_MENU_H_
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
new file mode 100644
index 00000000000..d9084519e22
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc
@@ -0,0 +1,227 @@
+// Copyright (c) 2014 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/external_popup_menu.h"
+
+#include <memory>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
+#include "third_party/blink/public/web/web_external_popup_menu.h"
+#include "third_party/blink/public/web/web_popup_menu_info.h"
+#include "third_party/blink/public/web/web_settings.h"
+#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
+#include "third_party/blink/renderer/core/frame/visual_viewport.h"
+#include "third_party/blink/renderer/core/frame/web_local_frame_impl.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/html_names.h"
+#include "third_party/blink/renderer/core/layout/layout_menu_list.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/testing/unit_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
+
+namespace blink {
+
+class ExternalPopupMenuDisplayNoneItemsTest : public PageTestBase {
+ public:
+ ExternalPopupMenuDisplayNoneItemsTest() = default;
+
+ protected:
+ void SetUp() override {
+ PageTestBase::SetUp();
+ HTMLSelectElement* element = HTMLSelectElement::Create(GetDocument());
+ // Set the 4th an 5th items to have "display: none" property
+ element->SetInnerHTMLFromString(
+ "<option><option><option><option style='display:none;'><option "
+ "style='display:none;'><option><option>");
+ GetDocument().body()->AppendChild(element, ASSERT_NO_EXCEPTION);
+ owner_element_ = element;
+ GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+ }
+
+ Persistent<HTMLSelectElement> owner_element_;
+};
+
+TEST_F(ExternalPopupMenuDisplayNoneItemsTest, PopupMenuInfoSizeTest) {
+ WebPopupMenuInfo info;
+ ExternalPopupMenu::GetPopupMenuInfo(info, *owner_element_);
+ EXPECT_EQ(5U, info.items.size());
+}
+
+TEST_F(ExternalPopupMenuDisplayNoneItemsTest, IndexMappingTest) {
+ // 6th indexed item in popupmenu would be the 4th item in ExternalPopupMenu,
+ // and vice-versa.
+ EXPECT_EQ(
+ 4, ExternalPopupMenu::ToExternalPopupMenuItemIndex(6, *owner_element_));
+ EXPECT_EQ(6, ExternalPopupMenu::ToPopupMenuItemIndex(4, *owner_element_));
+
+ // Invalid index, methods should return -1.
+ EXPECT_EQ(
+ -1, ExternalPopupMenu::ToExternalPopupMenuItemIndex(8, *owner_element_));
+ EXPECT_EQ(-1, ExternalPopupMenu::ToPopupMenuItemIndex(8, *owner_element_));
+}
+
+class ExternalPopupMenuWebFrameClient
+ : public FrameTestHelpers::TestWebFrameClient {
+ public:
+ WebExternalPopupMenu* CreateExternalPopupMenu(
+ const WebPopupMenuInfo&,
+ WebExternalPopupMenuClient*) override {
+ return &mock_web_external_popup_menu_;
+ }
+ WebRect ShownBounds() const {
+ return mock_web_external_popup_menu_.ShownBounds();
+ }
+
+ private:
+ class MockWebExternalPopupMenu : public WebExternalPopupMenu {
+ void Show(const WebRect& bounds) override { shown_bounds_ = bounds; }
+ void Close() override {}
+
+ public:
+ WebRect ShownBounds() const { return shown_bounds_; }
+
+ private:
+ WebRect shown_bounds_;
+ };
+ WebRect shown_bounds_;
+ MockWebExternalPopupMenu mock_web_external_popup_menu_;
+};
+
+class ExternalPopupMenuTest : public testing::Test {
+ public:
+ ExternalPopupMenuTest() : base_url_("http://www.test.com") {}
+
+ protected:
+ void SetUp() override {
+ helper_.Initialize(&web_frame_client_);
+ WebView()->SetUseExternalPopupMenus(true);
+ }
+ void TearDown() override {
+ Platform::Current()
+ ->GetURLLoaderMockFactory()
+ ->UnregisterAllURLsAndClearMemoryCache();
+ }
+
+ void RegisterMockedURLLoad(const std::string& file_name) {
+ URLTestHelpers::RegisterMockedURLLoadFromBase(
+ WebString::FromUTF8(base_url_), test::CoreTestDataPath("popup"),
+ WebString::FromUTF8(file_name), WebString::FromUTF8("text/html"));
+ }
+
+ void LoadFrame(const std::string& file_name) {
+ FrameTestHelpers::LoadFrame(MainFrame(), base_url_ + file_name);
+ WebView()->Resize(WebSize(800, 600));
+ WebView()->UpdateAllLifecyclePhases();
+ }
+
+ WebViewImpl* WebView() const { return helper_.GetWebView(); }
+ const ExternalPopupMenuWebFrameClient& Client() const {
+ return web_frame_client_;
+ }
+ WebLocalFrameImpl* MainFrame() const { return helper_.LocalMainFrame(); }
+
+ private:
+ std::string base_url_;
+ ExternalPopupMenuWebFrameClient web_frame_client_;
+ FrameTestHelpers::WebViewHelper helper_;
+};
+
+TEST_F(ExternalPopupMenuTest, PopupAccountsForVisualViewportTransform) {
+ RegisterMockedURLLoad("select_mid_screen.html");
+ LoadFrame("select_mid_screen.html");
+
+ WebView()->Resize(WebSize(100, 100));
+ WebView()->UpdateAllLifecyclePhases();
+
+ HTMLSelectElement* select = ToHTMLSelectElement(
+ MainFrame()->GetFrame()->GetDocument()->getElementById("select"));
+ LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject());
+ ASSERT_TRUE(menu_list);
+
+ VisualViewport& visual_viewport = WebView()->GetPage()->GetVisualViewport();
+
+ IntRect rect_in_document = menu_list->AbsoluteBoundingBoxRect();
+
+ constexpr int kScaleFactor = 2;
+ ScrollOffset scroll_delta(20, 30);
+
+ const int expected_x =
+ (rect_in_document.X() - scroll_delta.Width()) * kScaleFactor;
+ const int expected_y =
+ (rect_in_document.Y() - scroll_delta.Height()) * kScaleFactor;
+
+ WebView()->SetPageScaleFactor(kScaleFactor);
+ visual_viewport.Move(scroll_delta);
+ select->ShowPopup();
+
+ EXPECT_EQ(expected_x, Client().ShownBounds().x);
+ EXPECT_EQ(expected_y, Client().ShownBounds().y);
+}
+
+TEST_F(ExternalPopupMenuTest, DidAcceptIndex) {
+ RegisterMockedURLLoad("select.html");
+ LoadFrame("select.html");
+
+ HTMLSelectElement* select = ToHTMLSelectElement(
+ MainFrame()->GetFrame()->GetDocument()->getElementById("select"));
+ LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject());
+ ASSERT_TRUE(menu_list);
+
+ select->ShowPopup();
+ ASSERT_TRUE(select->PopupIsVisible());
+
+ WebExternalPopupMenuClient* client =
+ static_cast<ExternalPopupMenu*>(select->Popup());
+ client->DidAcceptIndex(2);
+ EXPECT_FALSE(select->PopupIsVisible());
+ ASSERT_STREQ("2", menu_list->GetText().Utf8().data());
+ EXPECT_EQ(2, select->selectedIndex());
+}
+
+TEST_F(ExternalPopupMenuTest, DidAcceptIndices) {
+ RegisterMockedURLLoad("select.html");
+ LoadFrame("select.html");
+
+ HTMLSelectElement* select = ToHTMLSelectElement(
+ MainFrame()->GetFrame()->GetDocument()->getElementById("select"));
+ LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject());
+ ASSERT_TRUE(menu_list);
+
+ select->ShowPopup();
+ ASSERT_TRUE(select->PopupIsVisible());
+
+ WebExternalPopupMenuClient* client =
+ static_cast<ExternalPopupMenu*>(select->Popup());
+ int indices[] = {2};
+ WebVector<int> indices_vector(indices, 1);
+ client->DidAcceptIndices(indices_vector);
+ EXPECT_FALSE(select->PopupIsVisible());
+ EXPECT_STREQ("2", menu_list->GetText().Utf8().data());
+ EXPECT_EQ(2, select->selectedIndex());
+}
+
+TEST_F(ExternalPopupMenuTest, DidAcceptIndicesClearSelect) {
+ RegisterMockedURLLoad("select.html");
+ LoadFrame("select.html");
+
+ HTMLSelectElement* select = ToHTMLSelectElement(
+ MainFrame()->GetFrame()->GetDocument()->getElementById("select"));
+ LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject());
+ ASSERT_TRUE(menu_list);
+
+ select->ShowPopup();
+ ASSERT_TRUE(select->PopupIsVisible());
+
+ WebExternalPopupMenuClient* client =
+ static_cast<ExternalPopupMenu*>(select->Popup());
+ WebVector<int> indices;
+ client->DidAcceptIndices(indices);
+ EXPECT_FALSE(select->PopupIsVisible());
+ EXPECT_EQ(-1, select->selectedIndex());
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.cc
new file mode 100644
index 00000000000..ebc3146a21d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/file_chooser.h"
+
+namespace blink {
+
+FileChooserClient::~FileChooserClient() = default;
+
+FileChooser* FileChooserClient::NewFileChooser(
+ const WebFileChooserParams& params) {
+ if (chooser_)
+ chooser_->DisconnectClient();
+
+ chooser_ = FileChooser::Create(this, params);
+ return chooser_.get();
+}
+
+inline FileChooser::FileChooser(FileChooserClient* client,
+ const WebFileChooserParams& params)
+ : client_(client), params_(params) {}
+
+scoped_refptr<FileChooser> FileChooser::Create(
+ FileChooserClient* client,
+ const WebFileChooserParams& params) {
+ return base::AdoptRef(new FileChooser(client, params));
+}
+
+FileChooser::~FileChooser() = default;
+
+void FileChooser::ChooseFiles(const Vector<FileChooserFileInfo>& files) {
+ // FIXME: This is inelegant. We should not be looking at params_ here.
+ if (params_.selected_files.size() == files.size()) {
+ bool was_changed = false;
+ for (unsigned i = 0; i < files.size(); ++i) {
+ if (String(params_.selected_files[i]) != files[i].path) {
+ was_changed = true;
+ break;
+ }
+ }
+ if (!was_changed)
+ return;
+ }
+
+ if (client_)
+ client_->FilesChosen(files);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.h
new file mode 100644
index 00000000000..b0d4ce85213
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_CHOOSER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_CHOOSER_H_
+
+#include "third_party/blink/public/web/web_file_chooser_params.h"
+#include "third_party/blink/renderer/platform/file_metadata.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class FileChooser;
+
+struct FileChooserFileInfo {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+ FileChooserFileInfo(const String& path, const String& display_name = String())
+ : path(path), display_name(display_name) {}
+
+ FileChooserFileInfo(const KURL& file_system_url, const FileMetadata metadata)
+ : file_system_url(file_system_url), metadata(metadata) {}
+
+ // Members for native files.
+ const String path;
+ const String display_name;
+
+ // Members for file system API files.
+ const KURL file_system_url;
+ const FileMetadata metadata;
+};
+
+class FileChooserClient : public GarbageCollectedMixin {
+ public:
+ virtual void FilesChosen(const Vector<FileChooserFileInfo>&) = 0;
+ virtual ~FileChooserClient();
+
+ protected:
+ FileChooser* NewFileChooser(const WebFileChooserParams&);
+
+ private:
+ scoped_refptr<FileChooser> chooser_;
+};
+
+class FileChooser : public RefCounted<FileChooser> {
+ public:
+ static scoped_refptr<FileChooser> Create(FileChooserClient*,
+ const WebFileChooserParams&);
+ ~FileChooser();
+
+ void DisconnectClient() { client_ = nullptr; }
+
+ // FIXME: We should probably just pass file paths that could be virtual paths
+ // with proper display names rather than passing structs.
+ void ChooseFiles(const Vector<FileChooserFileInfo>& files);
+
+ const WebFileChooserParams& Params() const { return params_; }
+
+ private:
+ FileChooser(FileChooserClient*, const WebFileChooserParams&);
+
+ WeakPersistent<FileChooserClient> client_;
+ WebFileChooserParams params_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_CHOOSER_H_
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
new file mode 100644
index 00000000000..ac9f83feee2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All
+ * rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * 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/file_input_type.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.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"
+#include "third_party/blink/renderer/core/fileapi/file.h"
+#include "third_party/blink/renderer/core/fileapi/file_list.h"
+#include "third_party/blink/renderer/core/frame/use_counter.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_input_element.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_file_upload_control.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/platform/file_metadata.h"
+#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/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+using blink::WebLocalizedString;
+using namespace HTMLNames;
+
+namespace {
+
+WebVector<WebString> CollectAcceptTypes(const HTMLInputElement& input) {
+ Vector<String> mime_types = input.AcceptMIMETypes();
+ Vector<String> extensions = input.AcceptFileExtensions();
+
+ Vector<String> accept_types;
+ accept_types.ReserveCapacity(mime_types.size() + extensions.size());
+ accept_types.AppendVector(mime_types);
+ accept_types.AppendVector(extensions);
+ return accept_types;
+}
+
+} // namespace
+
+inline FileInputType::FileInputType(HTMLInputElement& element)
+ : InputType(element),
+ KeyboardClickableInputTypeView(element),
+ file_list_(FileList::Create()) {}
+
+InputType* FileInputType::Create(HTMLInputElement& element) {
+ return new FileInputType(element);
+}
+
+void FileInputType::Trace(blink::Visitor* visitor) {
+ visitor->Trace(file_list_);
+ KeyboardClickableInputTypeView::Trace(visitor);
+ InputType::Trace(visitor);
+}
+
+InputTypeView* FileInputType::CreateView() {
+ return this;
+}
+
+Vector<FileChooserFileInfo> FileInputType::FilesFromFormControlState(
+ const FormControlState& state) {
+ Vector<FileChooserFileInfo> files;
+ for (size_t i = 0; i < state.ValueSize(); i += 2) {
+ if (!state[i + 1].IsEmpty())
+ files.push_back(FileChooserFileInfo(state[i], state[i + 1]));
+ else
+ files.push_back(FileChooserFileInfo(state[i]));
+ }
+ return files;
+}
+
+const AtomicString& FileInputType::FormControlType() const {
+ return InputTypeNames::file;
+}
+
+FormControlState FileInputType::SaveFormControlState() const {
+ if (file_list_->IsEmpty())
+ return FormControlState();
+ FormControlState state;
+ unsigned num_files = file_list_->length();
+ for (unsigned i = 0; i < num_files; ++i) {
+ if (file_list_->item(i)->HasBackingFile()) {
+ state.Append(file_list_->item(i)->GetPath());
+ state.Append(file_list_->item(i)->name());
+ }
+ // FIXME: handle Blob-backed File instances, see http://crbug.com/394948
+ }
+ return state;
+}
+
+void FileInputType::RestoreFormControlState(const FormControlState& state) {
+ if (state.ValueSize() % 2)
+ return;
+ FilesChosen(FilesFromFormControlState(state));
+}
+
+void FileInputType::AppendToFormData(FormData& form_data) const {
+ FileList* file_list = GetElement().files();
+ unsigned num_files = file_list->length();
+ if (num_files == 0) {
+ form_data.append(GetElement().GetName(), File::Create(""));
+ return;
+ }
+
+ for (unsigned i = 0; i < num_files; ++i)
+ form_data.append(GetElement().GetName(), file_list->item(i));
+}
+
+bool FileInputType::ValueMissing(const String& value) const {
+ return GetElement().IsRequired() && value.IsEmpty();
+}
+
+String FileInputType::ValueMissingText() const {
+ return GetLocale().QueryString(
+ GetElement().Multiple()
+ ? WebLocalizedString::kValidationValueMissingForMultipleFile
+ : WebLocalizedString::kValidationValueMissingForFile);
+}
+
+void FileInputType::HandleDOMActivateEvent(Event* event) {
+ if (GetElement().IsDisabledFormControl())
+ return;
+
+ if (!Frame::HasTransientUserActivation(GetElement().GetDocument().GetFrame()))
+ return;
+
+ if (ChromeClient* chrome_client = GetChromeClient()) {
+ WebFileChooserParams params;
+ HTMLInputElement& input = GetElement();
+ Document& document = input.GetDocument();
+ bool is_directory = input.FastHasAttribute(webkitdirectoryAttr);
+ params.directory = is_directory;
+ params.need_local_path = is_directory;
+ params.multi_select = is_directory || input.FastHasAttribute(multipleAttr);
+ params.accept_types = CollectAcceptTypes(input);
+ params.selected_files = file_list_->PathsForUserVisibleFiles();
+ params.use_media_capture = RuntimeEnabledFeatures::MediaCaptureEnabled() &&
+ input.FastHasAttribute(captureAttr);
+ params.requestor = document.Url();
+
+ UseCounter::Count(
+ document, document.IsSecureContext()
+ ? WebFeature::kInputTypeFileSecureOriginOpenChooser
+ : WebFeature::kInputTypeFileInsecureOriginOpenChooser);
+
+ chrome_client->OpenFileChooser(document.GetFrame(), NewFileChooser(params));
+ }
+ event->SetDefaultHandled();
+}
+
+LayoutObject* FileInputType::CreateLayoutObject(const ComputedStyle&) const {
+ return new LayoutFileUploadControl(&GetElement());
+}
+
+InputType::ValueMode FileInputType::GetValueMode() const {
+ return ValueMode::kFilename;
+}
+
+bool FileInputType::CanSetStringValue() const {
+ return false;
+}
+
+FileList* FileInputType::Files() {
+ return file_list_.Get();
+}
+
+bool FileInputType::CanSetValue(const String& value) {
+ // For security reasons, we don't allow setting the filename, but we do allow
+ // clearing it. The HTML5 spec (as of the 10/24/08 working draft) says that
+ // the value attribute isn't applicable to the file upload control at all, but
+ // for now we are keeping this behavior to avoid breaking existing websites
+ // that may be relying on this.
+ return value.IsEmpty();
+}
+
+String FileInputType::ValueInFilenameValueMode() const {
+ if (file_list_->IsEmpty())
+ return String();
+
+ // HTML5 tells us that we're supposed to use this goofy value for
+ // file input controls. Historically, browsers revealed the real
+ // file path, but that's a privacy problem. Code on the web
+ // decided to try to parse the value by looking for backslashes
+ // (because that's what Windows file paths use). To be compatible
+ // with that code, we make up a fake path for the file.
+ return "C:\\fakepath\\" + file_list_->item(0)->name();
+}
+
+void FileInputType::SetValue(const String&,
+ bool value_changed,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) {
+ if (!value_changed)
+ return;
+
+ file_list_->clear();
+ GetElement().SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
+ GetElement().SetNeedsValidityCheck();
+}
+
+FileList* FileInputType::CreateFileList(
+ const Vector<FileChooserFileInfo>& files,
+ bool has_webkit_directory_attr) {
+ FileList* file_list(FileList::Create());
+ size_t size = files.size();
+
+ // If a directory is being selected, the UI allows a directory to be chosen
+ // and the paths provided here share a root directory somewhere up the tree;
+ // we want to store only the relative paths from that point.
+ if (size && has_webkit_directory_attr) {
+ // Find the common root path.
+ String root_path = DirectoryName(files[0].path);
+ for (size_t i = 1; i < size; ++i) {
+ while (!files[i].path.StartsWith(root_path))
+ root_path = DirectoryName(root_path);
+ }
+ root_path = DirectoryName(root_path);
+ DCHECK(root_path.length());
+ int root_length = root_path.length();
+ if (root_path[root_length - 1] != '\\' && root_path[root_length - 1] != '/')
+ root_length += 1;
+ for (const auto& file : files) {
+ // Normalize backslashes to slashes before exposing the relative path to
+ // script.
+ String relative_path =
+ file.path.Substring(root_length).Replace('\\', '/');
+ file_list->Append(File::CreateWithRelativePath(file.path, relative_path));
+ }
+ return file_list;
+ }
+
+ for (const auto& file : files) {
+ if (file.file_system_url.IsEmpty()) {
+ file_list->Append(
+ File::CreateForUserProvidedFile(file.path, file.display_name));
+ } else {
+ file_list->Append(File::CreateForFileSystemFile(
+ file.file_system_url, file.metadata, File::kIsUserVisible));
+ }
+ }
+ return file_list;
+}
+
+void FileInputType::CountUsage() {
+ Document* document = &GetElement().GetDocument();
+ if (document->IsSecureContext())
+ UseCounter::Count(*document, WebFeature::kInputTypeFileInsecureOrigin);
+ else
+ UseCounter::Count(*document, WebFeature::kInputTypeFileSecureOrigin);
+}
+
+void FileInputType::CreateShadowSubtree() {
+ DCHECK(IsShadowHost(GetElement()));
+ auto* button = HTMLInputElement::Create(GetElement().GetDocument(),
+ CreateElementFlags());
+ button->setType(InputTypeNames::button);
+ button->setAttribute(
+ valueAttr,
+ AtomicString(GetLocale().QueryString(
+ GetElement().Multiple()
+ ? WebLocalizedString::kFileButtonChooseMultipleFilesLabel
+ : WebLocalizedString::kFileButtonChooseFileLabel)));
+ button->SetShadowPseudoId(AtomicString("-webkit-file-upload-button"));
+ GetElement().UserAgentShadowRoot()->AppendChild(button);
+}
+
+void FileInputType::DisabledAttributeChanged() {
+ DCHECK(IsShadowHost(GetElement()));
+ if (Element* button =
+ ToElementOrDie(GetElement().UserAgentShadowRoot()->firstChild()))
+ button->SetBooleanAttribute(disabledAttr,
+ GetElement().IsDisabledFormControl());
+}
+
+void FileInputType::MultipleAttributeChanged() {
+ DCHECK(IsShadowHost(GetElement()));
+ if (Element* button =
+ ToElementOrDie(GetElement().UserAgentShadowRoot()->firstChild()))
+ button->setAttribute(
+ valueAttr,
+ AtomicString(GetLocale().QueryString(
+ GetElement().Multiple()
+ ? WebLocalizedString::kFileButtonChooseMultipleFilesLabel
+ : WebLocalizedString::kFileButtonChooseFileLabel)));
+}
+
+void FileInputType::SetFiles(FileList* files) {
+ if (!files)
+ return;
+
+ bool files_changed = false;
+ if (files->length() != file_list_->length()) {
+ files_changed = true;
+ } else {
+ for (unsigned i = 0; i < files->length(); ++i) {
+ if (!files->item(i)->HasSameSource(*file_list_->item(i))) {
+ files_changed = true;
+ break;
+ }
+ }
+ }
+
+ file_list_ = files;
+
+ GetElement().NotifyFormStateChanged();
+ GetElement().SetNeedsValidityCheck();
+
+ if (GetElement().GetLayoutObject())
+ GetElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation();
+
+ if (files_changed) {
+ // This call may cause destruction of this instance.
+ // input instance is safe since it is ref-counted.
+ GetElement().DispatchInputEvent();
+ GetElement().DispatchChangeEvent();
+ }
+}
+
+void FileInputType::FilesChosen(const Vector<FileChooserFileInfo>& files) {
+ SetFiles(CreateFileList(files,
+ GetElement().FastHasAttribute(webkitdirectoryAttr)));
+}
+
+void FileInputType::SetFilesFromDirectory(const String& path) {
+ if (ChromeClient* chrome_client = GetChromeClient()) {
+ Vector<String> files;
+ files.push_back(path);
+ WebFileChooserParams params;
+ params.directory = true;
+ params.multi_select = true;
+ params.selected_files = files;
+ params.accept_types = CollectAcceptTypes(GetElement());
+ params.requestor = GetElement().GetDocument().Url();
+ chrome_client->EnumerateChosenDirectory(NewFileChooser(params));
+ }
+}
+
+void FileInputType::SetFilesFromPaths(const Vector<String>& paths) {
+ if (paths.IsEmpty())
+ return;
+
+ HTMLInputElement& input = GetElement();
+ if (input.FastHasAttribute(webkitdirectoryAttr)) {
+ SetFilesFromDirectory(paths[0]);
+ return;
+ }
+
+ Vector<FileChooserFileInfo> files;
+ for (const auto& path : paths)
+ files.push_back(FileChooserFileInfo(path));
+
+ if (input.FastHasAttribute(multipleAttr)) {
+ FilesChosen(files);
+ } else {
+ Vector<FileChooserFileInfo> first_file_only;
+ first_file_only.push_back(files[0]);
+ FilesChosen(first_file_only);
+ }
+}
+
+bool FileInputType::ReceiveDroppedFiles(const DragData* drag_data) {
+ Vector<String> paths;
+ drag_data->AsFilePaths(paths);
+ if (paths.IsEmpty())
+ return false;
+
+ if (!GetElement().FastHasAttribute(webkitdirectoryAttr)) {
+ dropped_file_system_id_ = drag_data->DroppedFileSystemId();
+ }
+ SetFilesFromPaths(paths);
+ return true;
+}
+
+String FileInputType::DroppedFileSystemId() {
+ return dropped_file_system_id_;
+}
+
+String FileInputType::DefaultToolTip(const InputTypeView&) const {
+ FileList* file_list = file_list_.Get();
+ unsigned list_size = file_list->length();
+ if (!list_size) {
+ return GetLocale().QueryString(
+ WebLocalizedString::kFileButtonNoFileSelectedLabel);
+ }
+
+ StringBuilder names;
+ for (size_t i = 0; i < list_size; ++i) {
+ names.Append(file_list->item(i)->name());
+ if (i != list_size - 1)
+ names.Append('\n');
+ }
+ return names.ToString();
+}
+
+void FileInputType::CopyNonAttributeProperties(const HTMLInputElement& source) {
+ DCHECK(file_list_->IsEmpty());
+ const FileList* source_list = source.files();
+ for (unsigned i = 0; i < source_list->length(); ++i)
+ file_list_->Append(source_list->item(i)->Clone());
+}
+
+void FileInputType::HandleKeypressEvent(KeyboardEvent* event) {
+ if (GetElement().FastHasAttribute(webkitdirectoryAttr)) {
+ // Override to invoke the action on Enter key up (not press) to avoid
+ // repeats committing the file chooser.
+ const String& key = event->key();
+ if (key == "Enter") {
+ event->SetDefaultHandled();
+ return;
+ }
+ }
+ KeyboardClickableInputTypeView::HandleKeypressEvent(event);
+}
+
+void FileInputType::HandleKeyupEvent(KeyboardEvent* event) {
+ if (GetElement().FastHasAttribute(webkitdirectoryAttr)) {
+ // Override to invoke the action on Enter key up (not press) to avoid
+ // repeats committing the file chooser.
+ if (event->key() == "Enter") {
+ GetElement().DispatchSimulatedClick(event);
+ event->SetDefaultHandled();
+ return;
+ }
+ }
+ KeyboardClickableInputTypeView::HandleKeyupEvent(event);
+}
+
+} // 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
new file mode 100644
index 00000000000..25aa374372f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_INPUT_TYPE_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/file_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+
+namespace blink {
+
+class DragData;
+class FileList;
+
+class CORE_EXPORT FileInputType final : public InputType,
+ public KeyboardClickableInputTypeView,
+ private FileChooserClient {
+ USING_GARBAGE_COLLECTED_MIXIN(FileInputType);
+
+ public:
+ static InputType* Create(HTMLInputElement&);
+ void Trace(blink::Visitor*) override;
+ using InputType::GetElement;
+ static Vector<FileChooserFileInfo> FilesFromFormControlState(
+ const FormControlState&);
+ static FileList* CreateFileList(const Vector<FileChooserFileInfo>& files,
+ bool has_webkit_directory_attr);
+
+ void CountUsage() override;
+
+ void SetFilesFromPaths(const Vector<String>&) override;
+
+ private:
+ FileInputType(HTMLInputElement&);
+ InputTypeView* CreateView() override;
+ const AtomicString& FormControlType() const override;
+ FormControlState SaveFormControlState() const override;
+ void RestoreFormControlState(const FormControlState&) override;
+ void AppendToFormData(FormData&) const override;
+ bool ValueMissing(const String&) const override;
+ String ValueMissingText() const override;
+ void HandleDOMActivateEvent(Event*) override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) const override;
+ bool CanSetStringValue() const override;
+ FileList* Files() override;
+ void SetFiles(FileList*) override;
+ ValueMode GetValueMode() const override;
+ bool CanSetValue(const String&) override;
+ String ValueInFilenameValueMode() const override;
+ void SetValue(const String&,
+ bool value_changed,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) override;
+ bool ReceiveDroppedFiles(const DragData*) override;
+ String DroppedFileSystemId() override;
+ void CreateShadowSubtree() override;
+ void DisabledAttributeChanged() override;
+ void MultipleAttributeChanged() override;
+ String DefaultToolTip(const InputTypeView&) const override;
+ void CopyNonAttributeProperties(const HTMLInputElement&) override;
+
+ // KeyboardClickableInputTypeView overrides.
+ void HandleKeypressEvent(KeyboardEvent*) override;
+ void HandleKeyupEvent(KeyboardEvent*) override;
+
+ // FileChooserClient implementation.
+ void FilesChosen(const Vector<FileChooserFileInfo>&) override;
+
+ void SetFilesFromDirectory(const String&);
+
+ Member<FileList> file_list_;
+ String dropped_file_system_id_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..9ea88efd7b3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 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/file_input_type.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/clipboard/data_object.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/fileapi/file_list.h"
+#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/page/drag_data.h"
+#include "third_party/blink/renderer/platform/wtf/date_math.h"
+
+namespace blink {
+
+TEST(FileInputTypeTest, createFileList) {
+ Vector<FileChooserFileInfo> files;
+
+ // Native file.
+ files.push_back(
+ FileChooserFileInfo("/native/path/native-file", "display-name"));
+
+ // Non-native file.
+ KURL url("filesystem:http://example.com/isolated/hash/non-native-file");
+ FileMetadata metadata;
+ metadata.length = 64;
+ metadata.modification_time = 1.0 * kMsPerDay + 3;
+ files.push_back(FileChooserFileInfo(url, metadata));
+
+ FileList* list = FileInputType::CreateFileList(files, false);
+ ASSERT_TRUE(list);
+ ASSERT_EQ(2u, list->length());
+
+ EXPECT_EQ("/native/path/native-file", list->item(0)->GetPath());
+ EXPECT_EQ("display-name", list->item(0)->name());
+ EXPECT_TRUE(list->item(0)->FileSystemURL().IsEmpty());
+
+ EXPECT_TRUE(list->item(1)->GetPath().IsEmpty());
+ EXPECT_EQ("non-native-file", list->item(1)->name());
+ EXPECT_EQ(url, list->item(1)->FileSystemURL());
+ EXPECT_EQ(64u, list->item(1)->size());
+ EXPECT_EQ(1.0 * kMsPerDay + 3, list->item(1)->lastModified());
+}
+
+TEST(FileInputTypeTest, ignoreDroppedNonNativeFiles) {
+ Document* document = Document::CreateForTest();
+ auto* input = HTMLInputElement::Create(*document, CreateElementFlags());
+ InputType* file_input = FileInputType::Create(*input);
+
+ DataObject* native_file_raw_drag_data = DataObject::Create();
+ const DragData native_file_drag_data(native_file_raw_drag_data, IntPoint(),
+ IntPoint(), kDragOperationCopy);
+ native_file_drag_data.PlatformData()->Add(File::Create("/native/path"));
+ native_file_drag_data.PlatformData()->SetFilesystemId("fileSystemId");
+ file_input->ReceiveDroppedFiles(&native_file_drag_data);
+ EXPECT_EQ("fileSystemId", file_input->DroppedFileSystemId());
+ ASSERT_EQ(1u, file_input->Files()->length());
+ EXPECT_EQ(String("/native/path"), file_input->Files()->item(0)->GetPath());
+
+ DataObject* non_native_file_raw_drag_data = DataObject::Create();
+ const DragData non_native_file_drag_data(non_native_file_raw_drag_data,
+ IntPoint(), IntPoint(),
+ kDragOperationCopy);
+ FileMetadata metadata;
+ metadata.length = 1234;
+ const KURL url("filesystem:http://example.com/isolated/hash/non-native-file");
+ non_native_file_drag_data.PlatformData()->Add(
+ File::CreateForFileSystemFile(url, metadata, File::kIsUserVisible));
+ non_native_file_drag_data.PlatformData()->SetFilesystemId("fileSystemId");
+ file_input->ReceiveDroppedFiles(&non_native_file_drag_data);
+ // Dropping non-native files should not change the existing files.
+ EXPECT_EQ("fileSystemId", file_input->DroppedFileSystemId());
+ ASSERT_EQ(1u, file_input->Files()->length());
+ EXPECT_EQ(String("/native/path"), file_input->Files()->item(0)->GetPath());
+}
+
+TEST(FileInputTypeTest, setFilesFromPaths) {
+ Document* document = Document::CreateForTest();
+ auto* input = HTMLInputElement::Create(*document, CreateElementFlags());
+ InputType* file_input = FileInputType::Create(*input);
+ Vector<String> paths;
+ paths.push_back("/native/path");
+ paths.push_back("/native/path2");
+ file_input->SetFilesFromPaths(paths);
+ ASSERT_EQ(1u, file_input->Files()->length());
+ EXPECT_EQ(String("/native/path"), file_input->Files()->item(0)->GetPath());
+
+ // Try to upload multiple files without multipleAttr
+ paths.clear();
+ paths.push_back("/native/path1");
+ paths.push_back("/native/path2");
+ file_input->SetFilesFromPaths(paths);
+ ASSERT_EQ(1u, file_input->Files()->length());
+ EXPECT_EQ(String("/native/path1"), file_input->Files()->item(0)->GetPath());
+
+ // Try to upload multiple files with multipleAttr
+ input->SetBooleanAttribute(HTMLNames::multipleAttr, true);
+ paths.clear();
+ paths.push_back("/native/real/path1");
+ paths.push_back("/native/real/path2");
+ file_input->SetFilesFromPaths(paths);
+ ASSERT_EQ(2u, file_input->Files()->length());
+ EXPECT_EQ(String("/native/real/path1"),
+ file_input->Files()->item(0)->GetPath());
+ EXPECT_EQ(String("/native/real/path2"),
+ file_input->Files()->item(1)->GetPath());
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_associated.h b/chromium/third_party/blink/renderer/core/html/forms/form_associated.h
new file mode 100644
index 00000000000..ee155b92cc9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_associated.h
@@ -0,0 +1,22 @@
+// Copyright 2016 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_FORM_ASSOCIATED_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_ASSOCIATED_H_
+
+namespace blink {
+
+class HTMLFormElement;
+
+// Contains code to associate form with a form associated element
+// https://html.spec.whatwg.org/multipage/forms.html#form-associated-element
+class FormAssociated {
+ public:
+ // HTMLFormElement can be null
+ virtual void AssociateWith(HTMLFormElement*) = 0;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_ASSOCIATED_H_
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
new file mode 100644
index 00000000000..acdb61a4eef
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2006, 2008, 2009, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010, 2011, 2012 Google Inc. All rights reserved.
+ *
+ * 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/form_controller.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#include "third_party/blink/renderer/core/html/forms/file_chooser.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_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/platform/wtf/deque.h"
+#include "third_party/blink/renderer/platform/wtf/hash_table_deleted_value_type.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+static inline HTMLFormElement* OwnerFormForState(
+ const HTMLFormControlElementWithState& control) {
+ // Assume controls with form attribute have no owners because we restore
+ // state during parsing and form owners of such controls might be
+ // indeterminate.
+ return control.FastHasAttribute(formAttr) ? nullptr : control.Form();
+}
+
+// ----------------------------------------------------------------------------
+
+// Serilized form of FormControlState:
+// (',' means strings around it are separated in stateVector.)
+//
+// SerializedControlState ::= SkipState | RestoreState
+// SkipState ::= '0'
+// RestoreState ::= UnsignedNumber, ControlValue+
+// UnsignedNumber ::= [0-9]+
+// ControlValue ::= arbitrary string
+//
+// RestoreState has a sequence of ControlValues. The length of the
+// sequence is represented by UnsignedNumber.
+
+void FormControlState::SerializeTo(Vector<String>& state_vector) const {
+ DCHECK(!IsFailure());
+ state_vector.push_back(String::Number(values_.size()));
+ for (const auto& value : values_)
+ state_vector.push_back(value.IsNull() ? g_empty_string : value);
+}
+
+FormControlState FormControlState::Deserialize(
+ const Vector<String>& state_vector,
+ size_t& index) {
+ if (index >= state_vector.size())
+ return FormControlState(kTypeFailure);
+ size_t value_size = state_vector[index++].ToUInt();
+ if (!value_size)
+ return FormControlState();
+ if (index + value_size > state_vector.size())
+ return FormControlState(kTypeFailure);
+ FormControlState state;
+ state.values_.ReserveCapacity(value_size);
+ for (size_t i = 0; i < value_size; ++i)
+ state.Append(state_vector[index++]);
+ return state;
+}
+
+// ----------------------------------------------------------------------------
+
+class FormElementKey {
+ public:
+ FormElementKey(StringImpl* = nullptr, StringImpl* = nullptr);
+ ~FormElementKey();
+ FormElementKey(const FormElementKey&);
+ FormElementKey& operator=(const FormElementKey&);
+
+ StringImpl* GetName() const { return name_; }
+ StringImpl* GetType() const { return type_; }
+
+ // Hash table deleted values, which are only constructed and never copied or
+ // destroyed.
+ FormElementKey(WTF::HashTableDeletedValueType)
+ : name_(HashTableDeletedValue()) {}
+ bool IsHashTableDeletedValue() const {
+ return name_ == HashTableDeletedValue();
+ }
+
+ private:
+ void Ref() const;
+ void Deref() const;
+
+ static StringImpl* HashTableDeletedValue() {
+ return reinterpret_cast<StringImpl*>(-1);
+ }
+
+ StringImpl* name_;
+ StringImpl* type_;
+};
+
+FormElementKey::FormElementKey(StringImpl* name, StringImpl* type)
+ : name_(name), type_(type) {
+ Ref();
+}
+
+FormElementKey::~FormElementKey() {
+ Deref();
+}
+
+FormElementKey::FormElementKey(const FormElementKey& other)
+ : name_(other.GetName()), type_(other.GetType()) {
+ Ref();
+}
+
+FormElementKey& FormElementKey::operator=(const FormElementKey& other) {
+ other.Ref();
+ Deref();
+ name_ = other.GetName();
+ type_ = other.GetType();
+ return *this;
+}
+
+void FormElementKey::Ref() const {
+ if (GetName())
+ GetName()->AddRef();
+ if (GetType())
+ GetType()->AddRef();
+}
+
+void FormElementKey::Deref() const {
+ if (GetName())
+ GetName()->Release();
+ if (GetType())
+ GetType()->Release();
+}
+
+inline bool operator==(const FormElementKey& a, const FormElementKey& b) {
+ return a.GetName() == b.GetName() && a.GetType() == b.GetType();
+}
+
+struct FormElementKeyHash {
+ static unsigned GetHash(const FormElementKey&);
+ static bool Equal(const FormElementKey& a, const FormElementKey& b) {
+ return a == b;
+ }
+ static const bool safe_to_compare_to_empty_or_deleted = true;
+};
+
+unsigned FormElementKeyHash::GetHash(const FormElementKey& key) {
+ return StringHasher::HashMemory<sizeof(FormElementKey)>(&key);
+}
+
+struct FormElementKeyHashTraits : WTF::GenericHashTraits<FormElementKey> {
+ static void ConstructDeletedValue(FormElementKey& slot, bool) {
+ new (NotNull, &slot) FormElementKey(WTF::kHashTableDeletedValue);
+ }
+ static bool IsDeletedValue(const FormElementKey& value) {
+ return value.IsHashTableDeletedValue();
+ }
+};
+
+// ----------------------------------------------------------------------------
+
+class SavedFormState {
+ USING_FAST_MALLOC(SavedFormState);
+
+ public:
+ static std::unique_ptr<SavedFormState> Create();
+ static std::unique_ptr<SavedFormState> Deserialize(const Vector<String>&,
+ size_t& index);
+ void SerializeTo(Vector<String>&) const;
+ bool IsEmpty() const { return state_for_new_form_elements_.IsEmpty(); }
+ void AppendControlState(const AtomicString& name,
+ const AtomicString& type,
+ const FormControlState&);
+ FormControlState TakeControlState(const AtomicString& name,
+ const AtomicString& type);
+
+ Vector<String> GetReferencedFilePaths() const;
+
+ private:
+ SavedFormState() : control_state_count_(0) {}
+
+ using FormElementStateMap = HashMap<FormElementKey,
+ Deque<FormControlState>,
+ FormElementKeyHash,
+ FormElementKeyHashTraits>;
+ FormElementStateMap state_for_new_form_elements_;
+ size_t control_state_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(SavedFormState);
+};
+
+std::unique_ptr<SavedFormState> SavedFormState::Create() {
+ return base::WrapUnique(new SavedFormState);
+}
+
+static bool IsNotFormControlTypeCharacter(UChar ch) {
+ return ch != '-' && (ch > 'z' || ch < 'a');
+}
+
+std::unique_ptr<SavedFormState> SavedFormState::Deserialize(
+ const Vector<String>& state_vector,
+ size_t& index) {
+ if (index >= state_vector.size())
+ return nullptr;
+ // FIXME: We need String::toSizeT().
+ size_t item_count = state_vector[index++].ToUInt();
+ if (!item_count)
+ return nullptr;
+ std::unique_ptr<SavedFormState> saved_form_state =
+ base::WrapUnique(new SavedFormState);
+ while (item_count--) {
+ if (index + 1 >= state_vector.size())
+ return nullptr;
+ String name = state_vector[index++];
+ String type = state_vector[index++];
+ FormControlState state = FormControlState::Deserialize(state_vector, index);
+ if (type.IsEmpty() ||
+ type.Find(IsNotFormControlTypeCharacter) != kNotFound ||
+ state.IsFailure())
+ return nullptr;
+ saved_form_state->AppendControlState(AtomicString(name), AtomicString(type),
+ state);
+ }
+ return saved_form_state;
+}
+
+void SavedFormState::SerializeTo(Vector<String>& state_vector) const {
+ state_vector.push_back(String::Number(control_state_count_));
+ for (const auto& form_control : state_for_new_form_elements_) {
+ const FormElementKey& key = form_control.key;
+ const Deque<FormControlState>& queue = form_control.value;
+ for (const FormControlState& form_control_state : queue) {
+ state_vector.push_back(key.GetName());
+ state_vector.push_back(key.GetType());
+ form_control_state.SerializeTo(state_vector);
+ }
+ }
+}
+
+void SavedFormState::AppendControlState(const AtomicString& name,
+ const AtomicString& type,
+ const FormControlState& state) {
+ FormElementKey key(name.Impl(), type.Impl());
+ FormElementStateMap::iterator it = state_for_new_form_elements_.find(key);
+ if (it != state_for_new_form_elements_.end()) {
+ it->value.push_back(state);
+ } else {
+ Deque<FormControlState> state_list;
+ state_list.push_back(state);
+ state_for_new_form_elements_.Set(key, state_list);
+ }
+ control_state_count_++;
+}
+
+FormControlState SavedFormState::TakeControlState(const AtomicString& name,
+ const AtomicString& type) {
+ if (state_for_new_form_elements_.IsEmpty())
+ return FormControlState();
+ FormElementStateMap::iterator it = state_for_new_form_elements_.find(
+ FormElementKey(name.Impl(), type.Impl()));
+ if (it == state_for_new_form_elements_.end())
+ return FormControlState();
+ DCHECK_GT(it->value.size(), 0u);
+ FormControlState state = it->value.TakeFirst();
+ control_state_count_--;
+ if (!it->value.size())
+ state_for_new_form_elements_.erase(it);
+ return state;
+}
+
+Vector<String> SavedFormState::GetReferencedFilePaths() const {
+ Vector<String> to_return;
+ for (const auto& form_control : state_for_new_form_elements_) {
+ const FormElementKey& key = form_control.key;
+ if (!Equal(key.GetType(), "file", 4))
+ continue;
+ const Deque<FormControlState>& queue = form_control.value;
+ for (const FormControlState& form_control_state : queue) {
+ const Vector<FileChooserFileInfo>& selected_files =
+ HTMLInputElement::FilesFromFileInputFormControlState(
+ form_control_state);
+ for (const auto& file : selected_files)
+ to_return.push_back(file.path);
+ }
+ }
+ return to_return;
+}
+
+// ----------------------------------------------------------------------------
+
+class FormKeyGenerator final
+ : public GarbageCollectedFinalized<FormKeyGenerator> {
+
+ public:
+ static FormKeyGenerator* Create() { return new FormKeyGenerator; }
+ void Trace(blink::Visitor* visitor) { visitor->Trace(form_to_key_map_); }
+ const AtomicString& FormKey(const HTMLFormControlElementWithState&);
+ void WillDeleteForm(HTMLFormElement*);
+
+ private:
+ FormKeyGenerator() = default;
+
+ using FormToKeyMap = HeapHashMap<Member<HTMLFormElement>, AtomicString>;
+ using FormSignatureToNextIndexMap = HashMap<String, unsigned>;
+ FormToKeyMap form_to_key_map_;
+ FormSignatureToNextIndexMap form_signature_to_next_index_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(FormKeyGenerator);
+};
+
+static inline void RecordFormStructure(const HTMLFormElement& form,
+ StringBuilder& builder) {
+ // 2 is enough to distinguish forms in webkit.org/b/91209#c0
+ const size_t kNamedControlsToBeRecorded = 2;
+ const ListedElement::List& controls = form.ListedElements();
+ builder.Append(" [");
+ for (size_t i = 0, named_controls = 0;
+ i < controls.size() && named_controls < kNamedControlsToBeRecorded;
+ ++i) {
+ if (!controls[i]->IsFormControlElementWithState())
+ continue;
+ HTMLFormControlElementWithState* control =
+ ToHTMLFormControlElementWithState(controls[i]);
+ if (!OwnerFormForState(*control))
+ continue;
+ AtomicString name = control->GetName();
+ if (name.IsEmpty())
+ continue;
+ named_controls++;
+ builder.Append(name);
+ builder.Append(' ');
+ }
+ builder.Append(']');
+}
+
+static inline String FormSignature(const HTMLFormElement& form) {
+ KURL action_url = form.GetURLAttribute(actionAttr);
+ // Remove the query part because it might contain volatile parameters such
+ // as a session key.
+ if (!action_url.IsEmpty())
+ action_url.SetQuery(String());
+
+ StringBuilder builder;
+ if (!action_url.IsEmpty())
+ builder.Append(action_url.GetString());
+
+ RecordFormStructure(form, builder);
+ return builder.ToString();
+}
+
+const AtomicString& FormKeyGenerator::FormKey(
+ const HTMLFormControlElementWithState& control) {
+ HTMLFormElement* form = OwnerFormForState(control);
+ if (!form) {
+ DEFINE_STATIC_LOCAL(const AtomicString, form_key_for_no_owner,
+ ("No owner"));
+ return form_key_for_no_owner;
+ }
+ FormToKeyMap::const_iterator it = form_to_key_map_.find(form);
+ if (it != form_to_key_map_.end())
+ return it->value;
+
+ String signature = FormSignature(*form);
+ DCHECK(!signature.IsNull());
+ FormSignatureToNextIndexMap::AddResult result =
+ form_signature_to_next_index_map_.insert(signature, 0);
+ unsigned next_index = result.stored_value->value++;
+
+ StringBuilder form_key_builder;
+ form_key_builder.Append(signature);
+ form_key_builder.Append(" #");
+ form_key_builder.AppendNumber(next_index);
+ FormToKeyMap::AddResult add_form_keyresult =
+ form_to_key_map_.insert(form, form_key_builder.ToAtomicString());
+ return add_form_keyresult.stored_value->value;
+}
+
+void FormKeyGenerator::WillDeleteForm(HTMLFormElement* form) {
+ DCHECK(form);
+ form_to_key_map_.erase(form);
+}
+
+// ----------------------------------------------------------------------------
+
+DocumentState* DocumentState::Create() {
+ return new DocumentState;
+}
+
+void DocumentState::Trace(blink::Visitor* visitor) {
+ visitor->Trace(form_controls_);
+}
+
+void DocumentState::AddControl(HTMLFormControlElementWithState* control) {
+ DCHECK(!control->Next() && !control->Prev());
+ form_controls_.Append(control);
+}
+
+void DocumentState::RemoveControl(HTMLFormControlElementWithState* control) {
+ form_controls_.Remove(control);
+ control->SetPrev(nullptr);
+ control->SetNext(nullptr);
+}
+
+static String FormStateSignature() {
+ // In the legacy version of serialized state, the first item was a name
+ // attribute value of a form control. The following string literal should
+ // contain some characters which are rarely used for name attribute values.
+ DEFINE_STATIC_LOCAL(String, signature,
+ ("\n\r?% Blink serialized form state version 9 \n\r=&"));
+ return signature;
+}
+
+Vector<String> DocumentState::ToStateVector() {
+ FormKeyGenerator* key_generator = FormKeyGenerator::Create();
+ std::unique_ptr<SavedFormStateMap> state_map =
+ base::WrapUnique(new SavedFormStateMap);
+ for (HTMLFormControlElementWithState* control = form_controls_.Head();
+ control; control = control->Next()) {
+ DCHECK(control->isConnected());
+ if (!control->ShouldSaveAndRestoreFormControlState())
+ continue;
+ SavedFormStateMap::AddResult result =
+ state_map->insert(key_generator->FormKey(*control), nullptr);
+ if (result.is_new_entry)
+ result.stored_value->value = SavedFormState::Create();
+ result.stored_value->value->AppendControlState(
+ control->GetName(), control->type(), control->SaveFormControlState());
+ }
+
+ Vector<String> state_vector;
+ state_vector.ReserveInitialCapacity(form_controls_.size() * 4);
+ state_vector.push_back(FormStateSignature());
+ for (const auto& saved_form_state : *state_map) {
+ state_vector.push_back(saved_form_state.key);
+ saved_form_state.value->SerializeTo(state_vector);
+ }
+ bool has_only_signature = state_vector.size() == 1;
+ if (has_only_signature)
+ state_vector.clear();
+ return state_vector;
+}
+
+// ----------------------------------------------------------------------------
+
+FormController::FormController() : document_state_(DocumentState::Create()) {}
+
+FormController::~FormController() = default;
+
+void FormController::Trace(blink::Visitor* visitor) {
+ visitor->Trace(document_state_);
+ visitor->Trace(form_key_generator_);
+}
+
+DocumentState* FormController::FormElementsState() const {
+ return document_state_.Get();
+}
+
+void FormController::SetStateForNewFormElements(
+ const Vector<String>& state_vector) {
+ FormStatesFromStateVector(state_vector, saved_form_state_map_);
+}
+
+bool FormController::HasFormStates() const {
+ return !saved_form_state_map_.IsEmpty();
+}
+
+FormControlState FormController::TakeStateForFormElement(
+ const HTMLFormControlElementWithState& control) {
+ if (saved_form_state_map_.IsEmpty())
+ return FormControlState();
+ if (!form_key_generator_)
+ form_key_generator_ = FormKeyGenerator::Create();
+ SavedFormStateMap::iterator it =
+ saved_form_state_map_.find(form_key_generator_->FormKey(control));
+ if (it == saved_form_state_map_.end())
+ return FormControlState();
+ FormControlState state =
+ it->value->TakeControlState(control.GetName(), control.type());
+ if (it->value->IsEmpty())
+ saved_form_state_map_.erase(it);
+ return state;
+}
+
+void FormController::FormStatesFromStateVector(
+ const Vector<String>& state_vector,
+ SavedFormStateMap& map) {
+ map.clear();
+
+ size_t i = 0;
+ if (state_vector.size() < 1 || state_vector[i++] != FormStateSignature())
+ return;
+
+ while (i + 1 < state_vector.size()) {
+ AtomicString form_key = AtomicString(state_vector[i++]);
+ std::unique_ptr<SavedFormState> state =
+ SavedFormState::Deserialize(state_vector, i);
+ if (!state) {
+ i = 0;
+ break;
+ }
+ map.insert(form_key, std::move(state));
+ }
+ if (i != state_vector.size())
+ map.clear();
+}
+
+void FormController::WillDeleteForm(HTMLFormElement* form) {
+ if (form_key_generator_)
+ form_key_generator_->WillDeleteForm(form);
+}
+
+void FormController::RestoreControlStateFor(
+ HTMLFormControlElementWithState& control) {
+ // We don't save state of a control with
+ // shouldSaveAndRestoreFormControlState() == false. But we need to skip
+ // restoring process too because a control in another form might have the same
+ // pair of name and type and saved its state.
+ if (!control.ShouldSaveAndRestoreFormControlState())
+ return;
+ if (OwnerFormForState(control))
+ return;
+ FormControlState state = TakeStateForFormElement(control);
+ if (state.ValueSize() > 0)
+ control.RestoreFormControlState(state);
+}
+
+void FormController::RestoreControlStateIn(HTMLFormElement& form) {
+ EventQueueScope scope;
+ const ListedElement::List& elements = form.ListedElements();
+ for (const auto& element : elements) {
+ if (!element->IsFormControlElementWithState())
+ continue;
+ HTMLFormControlElementWithState* control =
+ ToHTMLFormControlElementWithState(element);
+ if (!control->ShouldSaveAndRestoreFormControlState())
+ continue;
+ if (OwnerFormForState(*control) != &form)
+ continue;
+ FormControlState state = TakeStateForFormElement(*control);
+ if (state.ValueSize() > 0) {
+ // restoreFormControlState might dispatch input/change events.
+ control->RestoreFormControlState(state);
+ }
+ }
+}
+
+Vector<String> FormController::GetReferencedFilePaths(
+ const Vector<String>& state_vector) {
+ Vector<String> to_return;
+ SavedFormStateMap map;
+ FormStatesFromStateVector(state_vector, map);
+ for (const auto& saved_form_state : map)
+ to_return.AppendVector(saved_form_state.value->GetReferencedFilePaths());
+ return to_return;
+}
+
+void FormController::RegisterStatefulFormControl(
+ HTMLFormControlElementWithState& control) {
+ document_state_->AddControl(&control);
+}
+
+void FormController::UnregisterStatefulFormControl(
+ HTMLFormControlElementWithState& control) {
+ document_state_->RemoveControl(&control);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_controller.h b/chromium/third_party/blink/renderer/core/html/forms/form_controller.h
new file mode 100644
index 00000000000..19c6a2c7e75
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_controller.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights
+ * reserved.
+ * Copyright (C) 2010, 2011, 2012 Google Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_CONTROLLER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_CONTROLLER_H_
+
+#include <memory>
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class FormKeyGenerator;
+class HTMLFormControlElementWithState;
+class HTMLFormElement;
+class SavedFormState;
+
+class FormControlState {
+ DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+
+ public:
+ FormControlState() : type_(kTypeSkip) {}
+ explicit FormControlState(const String& value) : type_(kTypeRestore) {
+ values_.push_back(value);
+ }
+ static FormControlState Deserialize(const Vector<String>& state_vector,
+ size_t& index);
+ FormControlState(const FormControlState& another) = default;
+ FormControlState& operator=(const FormControlState&);
+
+ bool IsFailure() const { return type_ == kTypeFailure; }
+ size_t ValueSize() const { return values_.size(); }
+ const String& operator[](size_t i) const { return values_[i]; }
+ void Append(const String&);
+ void SerializeTo(Vector<String>& state_vector) const;
+
+ private:
+ enum Type { kTypeSkip, kTypeRestore, kTypeFailure };
+ explicit FormControlState(Type type) : type_(type) {}
+
+ Type type_;
+ Vector<String> values_;
+};
+
+inline FormControlState& FormControlState::operator=(
+ const FormControlState& another) = default;
+
+inline void FormControlState::Append(const String& value) {
+ type_ = kTypeRestore;
+ values_.push_back(value);
+}
+
+using SavedFormStateMap =
+ HashMap<AtomicString, std::unique_ptr<SavedFormState>>;
+
+class DocumentState final : public GarbageCollected<DocumentState> {
+ public:
+ static DocumentState* Create();
+ void Trace(blink::Visitor*);
+
+ void AddControl(HTMLFormControlElementWithState*);
+ void RemoveControl(HTMLFormControlElementWithState*);
+ Vector<String> ToStateVector();
+
+ private:
+ using FormElementList = HeapDoublyLinkedList<HTMLFormControlElementWithState>;
+ FormElementList form_controls_;
+};
+
+class FormController final : public GarbageCollectedFinalized<FormController> {
+ public:
+ static FormController* Create() { return new FormController; }
+ ~FormController();
+ void Trace(blink::Visitor*);
+
+ void RegisterStatefulFormControl(HTMLFormControlElementWithState&);
+ void UnregisterStatefulFormControl(HTMLFormControlElementWithState&);
+ // This should be callled only by Document::formElementsState().
+ DocumentState* FormElementsState() const;
+ // This should be callled only by Document::setStateForNewFormElements().
+ void SetStateForNewFormElements(const Vector<String>&);
+ // Returns true if saved state is set to this object and there are entries
+ // which are not consumed yet.
+ bool HasFormStates() const;
+ void WillDeleteForm(HTMLFormElement*);
+ void RestoreControlStateFor(HTMLFormControlElementWithState&);
+ void RestoreControlStateIn(HTMLFormElement&);
+
+ static Vector<String> GetReferencedFilePaths(
+ const Vector<String>& state_vector);
+
+ private:
+ FormController();
+ FormControlState TakeStateForFormElement(
+ const HTMLFormControlElementWithState&);
+ static void FormStatesFromStateVector(const Vector<String>&,
+ SavedFormStateMap&);
+
+ Member<DocumentState> document_state_;
+ SavedFormStateMap saved_form_state_map_;
+ Member<FormKeyGenerator> form_key_generator_;
+};
+
+} // namespace blink
+#endif
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
new file mode 100644
index 00000000000..877f254cc77
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.cc
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/form_data.h"
+
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/fileapi/blob.h"
+#include "third_party/blink/renderer/core/fileapi/file.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/network/form_data_encoder.h"
+#include "third_party/blink/renderer/platform/text/line_ending.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+namespace {
+
+class FormDataIterationSource final
+ : public PairIterable<String, FormDataEntryValue>::IterationSource {
+ public:
+ FormDataIterationSource(FormData* form_data)
+ : form_data_(form_data), current_(0) {}
+
+ bool Next(ScriptState* script_state,
+ String& name,
+ FormDataEntryValue& value,
+ ExceptionState& exception_state) override {
+ if (current_ >= form_data_->size())
+ return false;
+
+ const FormData::Entry& entry = *form_data_->Entries()[current_++];
+ name = form_data_->Decode(entry.name());
+ if (entry.IsString()) {
+ value.SetUSVString(form_data_->Decode(entry.Value()));
+ } else {
+ DCHECK(entry.isFile());
+ value.SetFile(entry.GetFile());
+ }
+ return true;
+ }
+
+ void Trace(blink::Visitor* visitor) override {
+ visitor->Trace(form_data_);
+ PairIterable<String, FormDataEntryValue>::IterationSource::Trace(visitor);
+ }
+
+ private:
+ const Member<FormData> form_data_;
+ size_t current_;
+};
+
+} // namespace
+
+FormData::FormData(const WTF::TextEncoding& encoding) : encoding_(encoding) {}
+
+FormData::FormData(HTMLFormElement* form) : encoding_(UTF8Encoding()) {
+ if (form)
+ form->ConstructFormDataSet(nullptr, *this);
+}
+
+void FormData::Trace(blink::Visitor* visitor) {
+ visitor->Trace(entries_);
+ ScriptWrappable::Trace(visitor);
+}
+
+void FormData::append(const String& name, const String& value) {
+ entries_.push_back(
+ new Entry(EncodeAndNormalize(name), EncodeAndNormalize(value)));
+}
+
+void FormData::append(ScriptState* script_state,
+ const String& name,
+ Blob* blob,
+ const String& filename) {
+ if (!blob) {
+ UseCounter::Count(ExecutionContext::From(script_state),
+ WebFeature::kFormDataAppendNull);
+ }
+ append(name, blob, filename);
+}
+
+void FormData::deleteEntry(const String& name) {
+ const CString encoded_name = EncodeAndNormalize(name);
+ size_t i = 0;
+ while (i < entries_.size()) {
+ if (entries_[i]->name() == encoded_name) {
+ entries_.EraseAt(i);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void FormData::get(const String& name, FormDataEntryValue& result) {
+ const CString encoded_name = EncodeAndNormalize(name);
+ for (const auto& entry : Entries()) {
+ if (entry->name() == encoded_name) {
+ if (entry->IsString()) {
+ result.SetUSVString(Decode(entry->Value()));
+ } else {
+ DCHECK(entry->isFile());
+ result.SetFile(entry->GetFile());
+ }
+ return;
+ }
+ }
+}
+
+HeapVector<FormDataEntryValue> FormData::getAll(const String& name) {
+ HeapVector<FormDataEntryValue> results;
+
+ const CString encoded_name = EncodeAndNormalize(name);
+ for (const auto& entry : Entries()) {
+ if (entry->name() != encoded_name)
+ continue;
+ FormDataEntryValue value;
+ if (entry->IsString()) {
+ value.SetUSVString(Decode(entry->Value()));
+ } else {
+ DCHECK(entry->isFile());
+ value.SetFile(entry->GetFile());
+ }
+ results.push_back(value);
+ }
+ return results;
+}
+
+bool FormData::has(const String& name) {
+ const CString encoded_name = EncodeAndNormalize(name);
+ for (const auto& entry : Entries()) {
+ if (entry->name() == encoded_name)
+ return true;
+ }
+ return false;
+}
+
+void FormData::set(const String& name, const String& value) {
+ SetEntry(new Entry(EncodeAndNormalize(name), EncodeAndNormalize(value)));
+}
+
+void FormData::set(const String& name, Blob* blob, const String& filename) {
+ SetEntry(new Entry(EncodeAndNormalize(name), blob, filename));
+}
+
+void FormData::SetEntry(const Entry* entry) {
+ DCHECK(entry);
+ const CString encoded_name = entry->name();
+ bool found = false;
+ size_t i = 0;
+ while (i < entries_.size()) {
+ if (entries_[i]->name() != encoded_name) {
+ ++i;
+ } else if (found) {
+ entries_.EraseAt(i);
+ } else {
+ found = true;
+ entries_[i] = entry;
+ ++i;
+ }
+ }
+ if (!found)
+ entries_.push_back(entry);
+}
+
+void FormData::append(const String& name, int value) {
+ append(name, String::Number(value));
+}
+
+void FormData::append(const String& name, Blob* blob, const String& filename) {
+ entries_.push_back(new Entry(EncodeAndNormalize(name), blob, filename));
+}
+
+CString FormData::EncodeAndNormalize(const String& string) const {
+ CString encoded_string =
+ encoding_.Encode(string, WTF::kEntitiesForUnencodables);
+ return NormalizeLineEndingsToCRLF(encoded_string);
+}
+
+String FormData::Decode(const CString& data) const {
+ return Encoding().Decode(data.data(), data.length());
+}
+
+scoped_refptr<EncodedFormData> FormData::EncodeFormData(
+ EncodedFormData::EncodingType encoding_type) {
+ scoped_refptr<EncodedFormData> form_data = EncodedFormData::Create();
+ Vector<char> encoded_data;
+ for (const auto& entry : Entries()) {
+ FormDataEncoder::AddKeyValuePairAsFormData(
+ encoded_data, entry->name(),
+ entry->isFile() ? EncodeAndNormalize(entry->GetFile()->name())
+ : entry->Value(),
+ encoding_type);
+ }
+ form_data->AppendData(encoded_data.data(), encoded_data.size());
+ return form_data;
+}
+
+scoped_refptr<EncodedFormData> FormData::EncodeMultiPartFormData() {
+ scoped_refptr<EncodedFormData> form_data = EncodedFormData::Create();
+ form_data->SetBoundary(FormDataEncoder::GenerateUniqueBoundaryString());
+ Vector<char> encoded_data;
+ for (const auto& entry : Entries()) {
+ Vector<char> header;
+ FormDataEncoder::BeginMultiPartHeader(header, form_data->Boundary().data(),
+ entry->name());
+
+ // If the current type is blob, then we also need to include the
+ // filename.
+ if (entry->GetBlob()) {
+ String name;
+ if (entry->GetBlob()->IsFile()) {
+ File* file = ToFile(entry->GetBlob());
+ // For file blob, use the filename (or relative path if it is
+ // present) as the name.
+ name = file->webkitRelativePath().IsEmpty()
+ ? file->name()
+ : file->webkitRelativePath();
+
+ // If a filename is passed in FormData.append(), use it instead
+ // of the file blob's name.
+ if (!entry->Filename().IsNull())
+ name = entry->Filename();
+ } else {
+ // For non-file blob, use the filename if it is passed in
+ // FormData.append().
+ if (!entry->Filename().IsNull())
+ name = entry->Filename();
+ else
+ name = "blob";
+ }
+
+ // We have to include the filename=".." part in the header, even if
+ // the filename is empty.
+ FormDataEncoder::AddFilenameToMultiPartHeader(header, Encoding(), name);
+
+ // Add the content type if available, or "application/octet-stream"
+ // otherwise (RFC 1867).
+ String content_type;
+ if (entry->GetBlob()->type().IsEmpty())
+ content_type = "application/octet-stream";
+ else
+ content_type = entry->GetBlob()->type();
+ FormDataEncoder::AddContentTypeToMultiPartHeader(header,
+ content_type.Latin1());
+ }
+
+ FormDataEncoder::FinishMultiPartHeader(header);
+
+ // Append body
+ form_data->AppendData(header.data(), header.size());
+ if (entry->GetBlob()) {
+ if (entry->GetBlob()->HasBackingFile()) {
+ File* file = ToFile(entry->GetBlob());
+ // Do not add the file if the path is empty.
+ if (!file->GetPath().IsEmpty())
+ form_data->AppendFile(file->GetPath());
+ } else {
+ form_data->AppendBlob(entry->GetBlob()->Uuid(),
+ entry->GetBlob()->GetBlobDataHandle());
+ }
+ } else {
+ form_data->AppendData(entry->Value().data(), entry->Value().length());
+ }
+ form_data->AppendData("\r\n", 2);
+ }
+ FormDataEncoder::AddBoundaryToMultiPartHeader(
+ encoded_data, form_data->Boundary().data(), true);
+ form_data->AppendData(encoded_data.data(), encoded_data.size());
+ return form_data;
+}
+
+PairIterable<String, FormDataEntryValue>::IterationSource*
+FormData::StartIteration(ScriptState*, ExceptionState&) {
+ return new FormDataIterationSource(this);
+}
+
+// ----------------------------------------------------------------
+
+void FormData::Entry::Trace(blink::Visitor* visitor) {
+ visitor->Trace(blob_);
+}
+
+File* FormData::Entry::GetFile() const {
+ DCHECK(GetBlob());
+ // The spec uses the passed filename when inserting entries into the list.
+ // Here, we apply the filename (if present) as an override when extracting
+ // entries.
+ // FIXME: Consider applying the name during insertion.
+
+ if (GetBlob()->IsFile()) {
+ File* file = ToFile(GetBlob());
+ if (Filename().IsNull())
+ return file;
+ return file->Clone(Filename());
+ }
+
+ String filename = filename_;
+ if (filename.IsNull())
+ filename = "blob";
+ return File::Create(filename, CurrentTimeMS(),
+ GetBlob()->GetBlobDataHandle());
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data.h b/chromium/third_party/blink/renderer/core/html/forms/form_data.h
new file mode 100644
index 00000000000..9e0ed37b2d2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_
+
+#include "third_party/blink/renderer/bindings/core/v8/file_or_usv_string.h"
+#include "third_party/blink/renderer/bindings/core/v8/iterable.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/network/encoded_form_data.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
+
+namespace blink {
+
+class Blob;
+class HTMLFormElement;
+class ScriptState;
+
+// Typedef from FormData.idl:
+typedef FileOrUSVString FormDataEntryValue;
+
+class CORE_EXPORT FormData final
+ : public ScriptWrappable,
+ public PairIterable<String, FormDataEntryValue> {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static FormData* Create(HTMLFormElement* form = nullptr) {
+ return new FormData(form);
+ }
+
+ static FormData* Create(const WTF::TextEncoding& encoding) {
+ return new FormData(encoding);
+ }
+ void Trace(blink::Visitor*);
+
+ // FormData IDL interface.
+ void append(const String& name, const String& value);
+ void append(ScriptState*,
+ const String& name,
+ Blob*,
+ const String& filename = String());
+ void deleteEntry(const String& name);
+ void get(const String& name, FormDataEntryValue& result);
+ HeapVector<FormDataEntryValue> getAll(const String& name);
+ bool has(const String& name);
+ void set(const String& name, const String& value);
+ void set(const String& name, Blob*, const String& filename = String());
+
+ // Internal functions.
+
+ const WTF::TextEncoding& Encoding() const { return encoding_; }
+ class Entry;
+ const HeapVector<Member<const Entry>>& Entries() const { return entries_; }
+ size_t size() const { return entries_.size(); }
+ void append(const String& name, int value);
+ void append(const String& name, Blob*, const String& filename = String());
+ String Decode(const CString& data) const;
+
+ // This flag is true if this FormData is created with a <form>, and its
+ // associated elements contain a non-empty password field.
+ bool ContainsPasswordData() const { return contains_password_data_; }
+ void SetContainsPasswordData(bool flag) { contains_password_data_ = flag; }
+
+ scoped_refptr<EncodedFormData> EncodeFormData(
+ EncodedFormData::EncodingType = EncodedFormData::kFormURLEncoded);
+ scoped_refptr<EncodedFormData> EncodeMultiPartFormData();
+
+ private:
+ explicit FormData(const WTF::TextEncoding&);
+ explicit FormData(HTMLFormElement*);
+ void SetEntry(const Entry*);
+ CString EncodeAndNormalize(const String& key) const;
+ IterationSource* StartIteration(ScriptState*, ExceptionState&) override;
+
+ WTF::TextEncoding encoding_;
+ // Entry pointers in m_entries never be nullptr.
+ HeapVector<Member<const Entry>> entries_;
+ bool contains_password_data_ = false;
+};
+
+// Represents entry, which is a pair of a name and a value.
+// https://xhr.spec.whatwg.org/#concept-formdata-entry
+// Entry objects are immutable.
+class FormData::Entry : public GarbageCollectedFinalized<FormData::Entry> {
+ public:
+ Entry(const CString& name, const CString& value)
+ : name_(name), value_(value) {}
+ Entry(const CString& name, Blob* blob, const String& filename)
+ : name_(name), blob_(blob), filename_(filename) {}
+ void Trace(blink::Visitor*);
+
+ bool IsString() const { return !blob_; }
+ bool isFile() const { return blob_; }
+ const CString& name() const { return name_; }
+ const CString& Value() const { return value_; }
+ Blob* GetBlob() const { return blob_.Get(); }
+ File* GetFile() const;
+ const String& Filename() const { return filename_; }
+
+ private:
+ const CString name_;
+ const CString value_;
+ const Member<Blob> blob_;
+ const String filename_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_
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
new file mode 100644
index 00000000000..2fcaf4b3ce9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.idl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// https://xhr.spec.whatwg.org/#interface-formdata
+
+typedef (File or USVString) FormDataEntryValue;
+
+// TODO(foolip): Remove LegacyInterfaceTypeChecking, which allows for
+// `append('name', null, 'filename')` and `set('name', null, 'filename')` to
+// append/set null values instead of throwing. https://crbug.com/561338
+[
+ Constructor(optional HTMLFormElement form),
+ Exposed=(Window,Worker),
+ LegacyInterfaceTypeChecking
+] interface FormData {
+ void append(USVString name, USVString value);
+ [CallWith=ScriptState] void append(USVString name, Blob value, optional USVString filename);
+ [ImplementedAs=deleteEntry] void delete(USVString name);
+ FormDataEntryValue? get(USVString name);
+ sequence<FormDataEntryValue> getAll(USVString name);
+ boolean has(USVString name);
+ void set(USVString name, USVString value);
+ void set(USVString name, Blob value, optional USVString filename);
+ iterable<USVString, FormDataEntryValue>;
+};
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
new file mode 100644
index 00000000000..6fe89dd1a08
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc
@@ -0,0 +1,28 @@
+// Copyright 2018 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/form_data_event.h"
+
+#include "third_party/blink/renderer/core/html/forms/form_data.h"
+
+namespace blink {
+
+FormDataEvent::FormDataEvent(FormData& form_data)
+ : Event(EventTypeNames::formdata, Bubbles::kYes, Cancelable::kNo),
+ form_data_(form_data) {}
+
+FormDataEvent* FormDataEvent::Create(FormData& form_data) {
+ return new FormDataEvent(form_data);
+}
+
+void FormDataEvent::Trace(Visitor* visitor) {
+ visitor->Trace(form_data_);
+ Event::Trace(visitor);
+}
+
+const AtomicString& FormDataEvent::InterfaceName() const {
+ return EventNames::FormDataEvent;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.h b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.h
new file mode 100644
index 00000000000..a3a31a506cd
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.h
@@ -0,0 +1,33 @@
+// Copyright 2018 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_FORM_DATA_EVENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_EVENT_H_
+
+#include "third_party/blink/renderer/core/dom/events/event.h"
+
+namespace blink {
+
+class FormData;
+
+class FormDataEvent : public Event {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static FormDataEvent* Create(FormData& form_data);
+ void Trace(Visitor* visitor) override;
+
+ FormData* formData() const { return form_data_; };
+
+ const AtomicString& InterfaceName() const override;
+
+ private:
+ FormDataEvent(FormData& form_data);
+
+ Member<FormData> form_data_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_EVENT_H_
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
new file mode 100644
index 00000000000..1b7c1bca272
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl
@@ -0,0 +1,13 @@
+// Copyright 2018 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://docs.google.com/document/d/1JO8puctCSpW-ZYGU8lF-h4FWRIDQNDVexzHoOQ2iQmY/edit?pli=1#heading=h.je8c7y5qpgki
+
+[
+ Exposed=Window,
+ RuntimeEnabled=FormDataEvent
+]
+interface FormDataEvent : Event {
+ readonly attribute FormData formData;
+};
diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_test.cc b/chromium/third_party/blink/renderer/core/html/forms/form_data_test.cc
new file mode 100644
index 00000000000..4b6ad726a37
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_test.cc
@@ -0,0 +1,45 @@
+// Copyright 2015 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/form_data.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+TEST(FormDataTest, get) {
+ FormData* fd = FormData::Create(UTF8Encoding());
+ fd->append("name1", "value1");
+
+ FileOrUSVString result;
+ fd->get("name1", result);
+ EXPECT_TRUE(result.IsUSVString());
+ EXPECT_EQ("value1", result.GetAsUSVString());
+
+ const FormData::Entry& entry = *fd->Entries()[0];
+ EXPECT_STREQ("name1", entry.name().data());
+ EXPECT_STREQ("value1", entry.Value().data());
+}
+
+TEST(FormDataTest, getAll) {
+ FormData* fd = FormData::Create(UTF8Encoding());
+ fd->append("name1", "value1");
+
+ HeapVector<FormDataEntryValue> results = fd->getAll("name1");
+ EXPECT_EQ(1u, results.size());
+ EXPECT_TRUE(results[0].IsUSVString());
+ EXPECT_EQ("value1", results[0].GetAsUSVString());
+
+ EXPECT_EQ(1u, fd->size());
+}
+
+TEST(FormDataTest, has) {
+ FormData* fd = FormData::Create(UTF8Encoding());
+ fd->append("name1", "value1");
+
+ EXPECT_TRUE(fd->has("name1"));
+ EXPECT_EQ(1u, fd->size());
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..02bdd554181
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/hidden_input_type.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_input_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+InputType* HiddenInputType::Create(HTMLInputElement& element) {
+ return new HiddenInputType(element);
+}
+
+void HiddenInputType::Trace(blink::Visitor* visitor) {
+ InputTypeView::Trace(visitor);
+ InputType::Trace(visitor);
+}
+
+InputTypeView* HiddenInputType::CreateView() {
+ return this;
+}
+
+const AtomicString& HiddenInputType::FormControlType() const {
+ return InputTypeNames::hidden;
+}
+
+bool HiddenInputType::ShouldSaveAndRestoreFormControlState() const {
+ return false;
+}
+
+bool HiddenInputType::SupportsValidation() const {
+ return false;
+}
+
+LayoutObject* HiddenInputType::CreateLayoutObject(const ComputedStyle&) const {
+ NOTREACHED();
+ return nullptr;
+}
+
+void HiddenInputType::AccessKeyAction(bool) {}
+
+bool HiddenInputType::LayoutObjectIsNeeded() {
+ return false;
+}
+
+InputType::ValueMode HiddenInputType::GetValueMode() const {
+ return ValueMode::kDefault;
+}
+
+void HiddenInputType::SetValue(const String& sanitized_value,
+ bool,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) {
+ GetElement().setAttribute(valueAttr, AtomicString(sanitized_value));
+}
+
+void HiddenInputType::AppendToFormData(FormData& form_data) const {
+ if (DeprecatedEqualIgnoringCase(GetElement().GetName(), "_charset_")) {
+ form_data.append(GetElement().GetName(),
+ String(form_data.Encoding().GetName()));
+ return;
+ }
+ InputType::AppendToFormData(form_data);
+}
+
+bool HiddenInputType::ShouldRespectHeightAndWidthAttributes() {
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.h
new file mode 100644
index 00000000000..ba7b9fc22bb
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HIDDEN_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HIDDEN_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/input_type_view.h"
+
+namespace blink {
+
+class HiddenInputType final : public InputType, private InputTypeView {
+ USING_GARBAGE_COLLECTED_MIXIN(HiddenInputType);
+
+ public:
+ static InputType* Create(HTMLInputElement&);
+ void Trace(blink::Visitor*) override;
+ using InputType::GetElement;
+
+ private:
+ HiddenInputType(HTMLInputElement& element)
+ : InputType(element), InputTypeView(element) {}
+ InputTypeView* CreateView() override;
+ const AtomicString& FormControlType() const override;
+ bool ShouldSaveAndRestoreFormControlState() const override;
+ bool SupportsValidation() const override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) const override;
+ void AccessKeyAction(bool send_mouse_events) override;
+ bool LayoutObjectIsNeeded() override;
+ ValueMode GetValueMode() const override;
+ bool IsInteractiveContent() const override { return false; }
+ bool ShouldRespectHeightAndWidthAttributes() override;
+ void SetValue(const String&,
+ bool,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) override;
+ void AppendToFormData(FormData&) const override;
+ bool NeedsShadowSubtree() const override { return false; }
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HIDDEN_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..6e1ba1efce9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc
@@ -0,0 +1,222 @@
+/*
+ * 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, 2010 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
+ * Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
+ *
+ * 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/html_button_element.h"
+
+#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/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"
+#include "third_party/blink/renderer/core/layout/layout_button.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline HTMLButtonElement::HTMLButtonElement(Document& document)
+ : HTMLFormControlElement(buttonTag, document),
+ type_(SUBMIT),
+ is_activated_submit_(false) {}
+
+HTMLButtonElement* HTMLButtonElement::Create(Document& document) {
+ return new HTMLButtonElement(document);
+}
+
+void HTMLButtonElement::setType(const AtomicString& type) {
+ setAttribute(typeAttr, type);
+}
+
+LayoutObject* HTMLButtonElement::CreateLayoutObject(const ComputedStyle&) {
+ return new LayoutButton(this);
+}
+
+const AtomicString& HTMLButtonElement::FormControlType() const {
+ switch (type_) {
+ case SUBMIT: {
+ DEFINE_STATIC_LOCAL(const AtomicString, submit, ("submit"));
+ return submit;
+ }
+ case BUTTON: {
+ DEFINE_STATIC_LOCAL(const AtomicString, button, ("button"));
+ return button;
+ }
+ case RESET: {
+ DEFINE_STATIC_LOCAL(const AtomicString, reset, ("reset"));
+ return reset;
+ }
+ }
+
+ NOTREACHED();
+ return g_empty_atom;
+}
+
+bool HTMLButtonElement::IsPresentationAttribute(
+ const QualifiedName& name) const {
+ if (name == alignAttr) {
+ // Don't map 'align' attribute. This matches what Firefox and IE do, but
+ // not Opera. See http://bugs.webkit.org/show_bug.cgi?id=12071
+ return false;
+ }
+
+ return HTMLFormControlElement::IsPresentationAttribute(name);
+}
+
+void HTMLButtonElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ if (params.name == typeAttr) {
+ if (DeprecatedEqualIgnoringCase(params.new_value, "reset"))
+ type_ = RESET;
+ else if (DeprecatedEqualIgnoringCase(params.new_value, "button"))
+ type_ = BUTTON;
+ else
+ type_ = SUBMIT;
+ SetNeedsWillValidateCheck();
+ if (formOwner() && isConnected())
+ formOwner()->InvalidateDefaultButtonStyle();
+ } else {
+ if (params.name == formactionAttr)
+ LogUpdateAttributeIfIsolatedWorldAndInDocument("button", params);
+ HTMLFormControlElement::ParseAttribute(params);
+ }
+}
+
+void HTMLButtonElement::DefaultEventHandler(Event* event) {
+ if (event->type() == EventTypeNames::DOMActivate &&
+ !IsDisabledFormControl()) {
+ if (Form() && type_ == SUBMIT) {
+ Form()->PrepareForSubmission(event, this);
+ event->SetDefaultHandled();
+ }
+ if (Form() && type_ == RESET) {
+ Form()->reset();
+ event->SetDefaultHandled();
+ }
+ }
+
+ if (event->IsKeyboardEvent()) {
+ if (event->type() == EventTypeNames::keydown &&
+ ToKeyboardEvent(event)->key() == " ") {
+ SetActive(true);
+ // No setDefaultHandled() - IE dispatches a keypress in this case.
+ return;
+ }
+ if (event->type() == EventTypeNames::keypress) {
+ switch (ToKeyboardEvent(event)->charCode()) {
+ case '\r':
+ DispatchSimulatedClick(event);
+ event->SetDefaultHandled();
+ return;
+ case ' ':
+ // Prevent scrolling down the page.
+ event->SetDefaultHandled();
+ return;
+ }
+ }
+ if (event->type() == EventTypeNames::keyup &&
+ ToKeyboardEvent(event)->key() == " ") {
+ if (IsActive())
+ DispatchSimulatedClick(event);
+ event->SetDefaultHandled();
+ return;
+ }
+ }
+
+ HTMLFormControlElement::DefaultEventHandler(event);
+}
+
+bool HTMLButtonElement::HasActivationBehavior() const {
+ return true;
+}
+
+bool HTMLButtonElement::WillRespondToMouseClickEvents() {
+ if (!IsDisabledFormControl() && Form() && (type_ == SUBMIT || type_ == RESET))
+ return true;
+ return HTMLFormControlElement::WillRespondToMouseClickEvents();
+}
+
+bool HTMLButtonElement::CanBeSuccessfulSubmitButton() const {
+ return type_ == SUBMIT;
+}
+
+bool HTMLButtonElement::IsActivatedSubmit() const {
+ return is_activated_submit_;
+}
+
+void HTMLButtonElement::SetActivatedSubmit(bool flag) {
+ is_activated_submit_ = flag;
+}
+
+void HTMLButtonElement::AppendToFormData(FormData& form_data) {
+ if (type_ == SUBMIT && !GetName().IsEmpty() && is_activated_submit_)
+ form_data.append(GetName(), Value());
+}
+
+void HTMLButtonElement::AccessKeyAction(bool send_mouse_events) {
+ focus();
+
+ DispatchSimulatedClick(
+ nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents);
+}
+
+bool HTMLButtonElement::IsURLAttribute(const Attribute& attribute) const {
+ return attribute.GetName() == formactionAttr ||
+ HTMLFormControlElement::IsURLAttribute(attribute);
+}
+
+const AtomicString& HTMLButtonElement::Value() const {
+ return getAttribute(valueAttr);
+}
+
+bool HTMLButtonElement::RecalcWillValidate() const {
+ return type_ == SUBMIT && HTMLFormControlElement::RecalcWillValidate();
+}
+
+bool HTMLButtonElement::IsInteractiveContent() const {
+ return true;
+}
+
+bool HTMLButtonElement::SupportsAutofocus() const {
+ return true;
+}
+
+bool HTMLButtonElement::MatchesDefaultPseudoClass() const {
+ // HTMLFormElement::findDefaultButton() traverses the tree. So we check
+ // canBeSuccessfulSubmitButton() first for early return.
+ return CanBeSuccessfulSubmitButton() && Form() &&
+ Form()->FindDefaultButton() == this;
+}
+
+Node::InsertionNotificationRequest HTMLButtonElement::InsertedInto(
+ ContainerNode* insertion_point) {
+ InsertionNotificationRequest request =
+ HTMLFormControlElement::InsertedInto(insertion_point);
+ LogAddElementIfIsolatedWorldAndInDocument("button", typeAttr, formmethodAttr,
+ formactionAttr);
+ return request;
+}
+
+} // 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
new file mode 100644
index 00000000000..23da1dfd32d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+
+namespace blink {
+
+class HTMLButtonElement final : public HTMLFormControlElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLButtonElement* Create(Document&);
+
+ void setType(const AtomicString&);
+
+ const AtomicString& Value() const;
+
+ bool WillRespondToMouseClickEvents() override;
+
+ private:
+ explicit HTMLButtonElement(Document&);
+
+ enum Type { SUBMIT, RESET, BUTTON };
+
+ const AtomicString& FormControlType() const override;
+
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+
+ // HTMLFormControlElement always creates one, but buttons don't need it.
+ bool AlwaysCreateUserAgentShadowRoot() const override { return false; }
+
+ Node::InsertionNotificationRequest InsertedInto(ContainerNode*) override;
+ void ParseAttribute(const AttributeModificationParams&) override;
+ bool IsPresentationAttribute(const QualifiedName&) const override;
+ void DefaultEventHandler(Event*) override;
+ bool HasActivationBehavior() const override;
+
+ void AppendToFormData(FormData&) override;
+
+ bool IsEnumeratable() const override { return true; }
+ bool SupportLabels() const override { return true; }
+ bool ShouldForceLegacyLayout() const final { return true; }
+ bool IsInteractiveContent() const override;
+ bool SupportsAutofocus() const override;
+ bool MatchesDefaultPseudoClass() const override;
+
+ bool CanBeSuccessfulSubmitButton() const override;
+ bool IsActivatedSubmit() const override;
+ void SetActivatedSubmit(bool flag) override;
+
+ void AccessKeyAction(bool send_mouse_events) override;
+ bool IsURLAttribute(const Attribute&) const override;
+
+ bool CanStartSelection() const override { return false; }
+
+ bool IsOptionalFormControl() const override { return true; }
+ bool RecalcWillValidate() const override;
+
+ Type type_;
+ bool is_activated_submit_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.idl
new file mode 100644
index 00000000000..a56286740c9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.idl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.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.
+ */
+
+// https://html.spec.whatwg.org/#the-button-element
+[HTMLConstructor]
+interface HTMLButtonElement : HTMLElement {
+ [CEReactions, Reflect] attribute boolean autofocus;
+ [CEReactions, Reflect] attribute boolean disabled;
+ [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form;
+ [CEReactions] attribute DOMString formAction;
+ [CEReactions] attribute DOMString formEnctype;
+ [CEReactions] attribute DOMString formMethod;
+ [CEReactions, Reflect] attribute boolean formNoValidate;
+ [CEReactions, Reflect] attribute DOMString formTarget;
+ [CEReactions, Reflect] attribute DOMString name;
+ [CEReactions] attribute DOMString type;
+ [CEReactions, Reflect] attribute DOMString value;
+ // FIXME: attribute HTMLMenuElement? menu;
+
+ readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ readonly attribute DOMString validationMessage;
+ boolean checkValidity();
+ boolean reportValidity();
+ void setCustomValidity(DOMString error);
+
+ readonly attribute NodeList labels;
+};
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
new file mode 100644
index 00000000000..7ded38a407f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ * Copyright (C) 2010 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h"
+
+#include "third_party/blink/renderer/core/dom/id_target_observer_registry.h"
+#include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h"
+#include "third_party/blink/renderer/core/html_names.h"
+
+namespace blink {
+
+inline HTMLDataListElement::HTMLDataListElement(Document& document)
+ : HTMLElement(HTMLNames::datalistTag, document) {}
+
+HTMLDataListElement* HTMLDataListElement::Create(Document& document) {
+ UseCounter::Count(document, WebFeature::kDataListElement);
+ return new HTMLDataListElement(document);
+}
+
+HTMLDataListOptionsCollection* HTMLDataListElement::options() {
+ return EnsureCachedCollection<HTMLDataListOptionsCollection>(
+ kDataListOptions);
+}
+
+void HTMLDataListElement::ChildrenChanged(const ChildrenChange& change) {
+ HTMLElement::ChildrenChanged(change);
+ if (!change.by_parser) {
+ GetTreeScope().GetIdTargetObserverRegistry().NotifyObservers(
+ GetIdAttribute());
+ }
+}
+
+void HTMLDataListElement::FinishParsingChildren() {
+ GetTreeScope().GetIdTargetObserverRegistry().NotifyObservers(
+ GetIdAttribute());
+}
+
+void HTMLDataListElement::OptionElementChildrenChanged() {
+ GetTreeScope().GetIdTargetObserverRegistry().NotifyObservers(
+ GetIdAttribute());
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..abb676f53ae
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009, Google Inc. All rights reserved.
+ * Copyright (C) 2010 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#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/html_element.h"
+
+namespace blink {
+
+class HTMLDataListOptionsCollection;
+
+class CORE_EXPORT HTMLDataListElement final : public HTMLElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLDataListElement* Create(Document&);
+
+ HTMLDataListOptionsCollection* options();
+
+ void OptionElementChildrenChanged();
+
+ private:
+ HTMLDataListElement(Document&);
+ void ChildrenChanged(const ChildrenChange&) override;
+ void FinishParsingChildren() override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.idl
new file mode 100644
index 00000000000..46de4a0a335
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.idl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2009, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// https://html.spec.whatwg.org/#the-datalist-element
+[HTMLConstructor]
+interface HTMLDataListElement : HTMLElement {
+ readonly attribute HTMLCollection options;
+};
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
new file mode 100644
index 00000000000..cc7a21b476f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h
@@ -0,0 +1,47 @@
+// Copyright 2014 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_HTML_DATA_LIST_OPTIONS_COLLECTION_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_OPTIONS_COLLECTION_H_
+
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/html_collection.h"
+
+namespace blink {
+
+class HTMLDataListOptionsCollection : public HTMLCollection {
+ public:
+ static HTMLDataListOptionsCollection* Create(ContainerNode& owner_node,
+ CollectionType type) {
+ DCHECK_EQ(type, kDataListOptions);
+ return new HTMLDataListOptionsCollection(owner_node);
+ }
+
+ HTMLOptionElement* Item(unsigned offset) const {
+ return ToHTMLOptionElement(HTMLCollection::item(offset));
+ }
+
+ bool ElementMatches(const HTMLElement&) const;
+
+ private:
+ explicit HTMLDataListOptionsCollection(ContainerNode& owner_node)
+ : HTMLCollection(owner_node,
+ kDataListOptions,
+ kDoesNotOverrideItemAfter) {}
+};
+
+DEFINE_TYPE_CASTS(HTMLDataListOptionsCollection,
+ LiveNodeListBase,
+ collection,
+ collection->GetType() == kDataListOptions,
+ collection.GetType() == kDataListOptions);
+
+inline bool HTMLDataListOptionsCollection::ElementMatches(
+ const HTMLElement& element) const {
+ return IsHTMLOptionElement(element);
+}
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_OPTIONS_COLLECTION_H_
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
new file mode 100644
index 00000000000..34b273b7a15
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc
@@ -0,0 +1,134 @@
+/*
+ * 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, 2010 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/html_field_set_element.h"
+
+#include "third_party/blink/renderer/core/dom/element_traversal.h"
+#include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
+#include "third_party/blink/renderer/core/html/forms/html_legend_element.h"
+#include "third_party/blink/renderer/core/html/html_collection.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/layout/layout_fieldset.h"
+#include "third_party/blink/renderer/platform/event_dispatch_forbidden_scope.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline HTMLFieldSetElement::HTMLFieldSetElement(Document& document)
+ : HTMLFormControlElement(fieldsetTag, document) {}
+
+HTMLFieldSetElement* HTMLFieldSetElement::Create(Document& document) {
+ return new HTMLFieldSetElement(document);
+}
+
+bool HTMLFieldSetElement::MatchesValidityPseudoClasses() const {
+ return true;
+}
+
+bool HTMLFieldSetElement::IsValidElement() {
+ for (Element* element : *elements()) {
+ if (element->IsFormControlElement()) {
+ if (!ToHTMLFormControlElement(element)->checkValidity(
+ nullptr, kCheckValidityDispatchNoEvent))
+ return false;
+ }
+ }
+ return true;
+}
+
+bool HTMLFieldSetElement::IsSubmittableElement() {
+ return false;
+}
+
+// Returns a disabled focused element if it's in descendants of |base|.
+Element*
+HTMLFieldSetElement::InvalidateDescendantDisabledStateAndFindFocusedOne(
+ Element& base) {
+ Element* focused_element = AdjustedFocusedElementInTreeScope();
+ bool should_blur = false;
+ {
+ EventDispatchForbiddenScope event_forbidden;
+ for (HTMLFormControlElement& element :
+ Traversal<HTMLFormControlElement>::DescendantsOf(base)) {
+ element.AncestorDisabledStateWasChanged();
+ if (focused_element == &element && element.IsDisabledFormControl())
+ should_blur = true;
+ }
+ }
+ return should_blur ? focused_element : nullptr;
+}
+
+void HTMLFieldSetElement::DisabledAttributeChanged() {
+ // This element must be updated before the style of nodes in its subtree gets
+ // recalculated.
+ HTMLFormControlElement::DisabledAttributeChanged();
+ if (Element* focused_element =
+ InvalidateDescendantDisabledStateAndFindFocusedOne(*this))
+ focused_element->blur();
+}
+
+void HTMLFieldSetElement::ChildrenChanged(const ChildrenChange& change) {
+ HTMLFormControlElement::ChildrenChanged(change);
+ Element* focused_element = nullptr;
+ {
+ EventDispatchForbiddenScope event_forbidden;
+ for (HTMLLegendElement& legend :
+ Traversal<HTMLLegendElement>::ChildrenOf(*this)) {
+ if (Element* element =
+ InvalidateDescendantDisabledStateAndFindFocusedOne(legend))
+ focused_element = element;
+ }
+ }
+ if (focused_element)
+ focused_element->blur();
+}
+
+bool HTMLFieldSetElement::SupportsFocus() const {
+ return HTMLElement::SupportsFocus() && !IsDisabledFormControl();
+}
+
+const AtomicString& HTMLFieldSetElement::FormControlType() const {
+ DEFINE_STATIC_LOCAL(const AtomicString, fieldset, ("fieldset"));
+ return fieldset;
+}
+
+LayoutObject* HTMLFieldSetElement::CreateLayoutObject(const ComputedStyle&) {
+ return new LayoutFieldset(this);
+}
+
+HTMLLegendElement* HTMLFieldSetElement::Legend() const {
+ return Traversal<HTMLLegendElement>::FirstChild(*this);
+}
+
+HTMLCollection* HTMLFieldSetElement::elements() {
+ return EnsureCachedCollection<HTMLCollection>(kFormControls);
+}
+
+int HTMLFieldSetElement::tabIndex() const {
+ return HTMLElement::tabIndex();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.h
new file mode 100644
index 00000000000..3020e62468a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FIELD_SET_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FIELD_SET_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+
+namespace blink {
+
+class HTMLCollection;
+
+class CORE_EXPORT HTMLFieldSetElement final : public HTMLFormControlElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLFieldSetElement* Create(Document&);
+ HTMLLegendElement* Legend() const;
+ HTMLCollection* elements();
+
+ protected:
+ void DisabledAttributeChanged() override;
+
+ private:
+ explicit HTMLFieldSetElement(Document&);
+
+ bool IsEnumeratable() const override { return true; }
+ bool SupportsFocus() const override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ const AtomicString& FormControlType() const override;
+ bool RecalcWillValidate() const override { return false; }
+ int tabIndex() const final;
+ bool MatchesValidityPseudoClasses() const final;
+ bool IsValidElement() final;
+ void ChildrenChanged(const ChildrenChange&) override;
+ bool AreAuthorShadowsAllowed() const override { return false; }
+ bool IsSubmittableElement() override;
+ bool AlwaysCreateUserAgentShadowRoot() const override { return false; }
+
+ Element* InvalidateDescendantDisabledStateAndFindFocusedOne(Element& base);
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FIELD_SET_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.idl
new file mode 100644
index 00000000000..69b3aff69ec
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.idl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006 Apple Computer, Inc.
+ *
+ * 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.
+ */
+
+// https://html.spec.whatwg.org/#the-fieldset-element
+[HTMLConstructor]
+interface HTMLFieldSetElement : HTMLElement {
+ [CEReactions, Reflect] attribute boolean disabled;
+ [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form;
+ [CEReactions, Reflect] attribute DOMString name;
+
+ readonly attribute DOMString type;
+
+ [Measure] readonly attribute HTMLCollection elements;
+
+ readonly attribute boolean willValidate;
+ [SameObject] readonly attribute ValidityState validity;
+ readonly attribute DOMString validationMessage;
+ boolean checkValidity();
+ boolean reportValidity();
+ void setCustomValidity(DOMString error);
+};
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
new file mode 100644
index 00000000000..1a3f8837b09
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
@@ -0,0 +1,682 @@
+/*
+ * 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 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/html_form_control_element.h"
+
+#include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/renderer/core/dom/ax_object_cache.h"
+#include "third_party/blink/renderer/core/dom/element_traversal.h"
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_field_set_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_legend_element.h"
+#include "third_party/blink/renderer/core/html/forms/validity_state.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
+#include "third_party/blink/renderer/core/inspector/console_message.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/page/validation_message_client.h"
+#include "third_party/blink/renderer/platform/event_dispatch_forbidden_scope.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
+#include "third_party/blink/renderer/platform/text/bidi_text_run.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+HTMLFormControlElement::HTMLFormControlElement(const QualifiedName& tag_name,
+ Document& document)
+ : LabelableElement(tag_name, document),
+ ancestor_disabled_state_(kAncestorDisabledStateUnknown),
+ data_list_ancestor_state_(kUnknown),
+ may_have_field_set_ancestor_(true),
+ is_autofilled_(false),
+ has_validation_message_(false),
+ will_validate_initialized_(false),
+ will_validate_(true),
+ is_valid_(true),
+ validity_is_dirty_(false),
+ blocks_form_submission_(false) {
+ SetHasCustomStyleCallbacks();
+}
+
+HTMLFormControlElement::~HTMLFormControlElement() = default;
+
+void HTMLFormControlElement::Trace(blink::Visitor* visitor) {
+ ListedElement::Trace(visitor);
+ LabelableElement::Trace(visitor);
+}
+
+String HTMLFormControlElement::formAction() const {
+ const AtomicString& action = FastGetAttribute(formactionAttr);
+ if (action.IsEmpty())
+ return GetDocument().Url();
+ return GetDocument().CompleteURL(StripLeadingAndTrailingHTMLSpaces(action));
+}
+
+void HTMLFormControlElement::setFormAction(const AtomicString& value) {
+ setAttribute(formactionAttr, value);
+}
+
+String HTMLFormControlElement::formEnctype() const {
+ const AtomicString& form_enctype_attr = FastGetAttribute(formenctypeAttr);
+ if (form_enctype_attr.IsNull())
+ return g_empty_string;
+ return FormSubmission::Attributes::ParseEncodingType(form_enctype_attr);
+}
+
+void HTMLFormControlElement::setFormEnctype(const AtomicString& value) {
+ setAttribute(formenctypeAttr, value);
+}
+
+String HTMLFormControlElement::formMethod() const {
+ const AtomicString& form_method_attr = FastGetAttribute(formmethodAttr);
+ if (form_method_attr.IsNull())
+ return g_empty_string;
+ return FormSubmission::Attributes::MethodString(
+ FormSubmission::Attributes::ParseMethodType(form_method_attr));
+}
+
+void HTMLFormControlElement::setFormMethod(const AtomicString& value) {
+ setAttribute(formmethodAttr, value);
+}
+
+bool HTMLFormControlElement::FormNoValidate() const {
+ return FastHasAttribute(formnovalidateAttr);
+}
+
+void HTMLFormControlElement::UpdateAncestorDisabledState() const {
+ if (!may_have_field_set_ancestor_) {
+ ancestor_disabled_state_ = kAncestorDisabledStateEnabled;
+ return;
+ }
+ may_have_field_set_ancestor_ = false;
+ // <fieldset> element of which |disabled| attribute affects |this| element.
+ HTMLFieldSetElement* disabled_fieldset_ancestor = nullptr;
+ ContainerNode* last_legend_ancestor = nullptr;
+ for (HTMLElement* ancestor = Traversal<HTMLElement>::FirstAncestor(*this);
+ ancestor; ancestor = Traversal<HTMLElement>::FirstAncestor(*ancestor)) {
+ if (IsHTMLLegendElement(*ancestor))
+ last_legend_ancestor = ancestor;
+ if (IsHTMLFieldSetElement(*ancestor)) {
+ may_have_field_set_ancestor_ = true;
+ if (ancestor->IsDisabledFormControl()) {
+ auto* fieldset = ToHTMLFieldSetElement(ancestor);
+ if (last_legend_ancestor && last_legend_ancestor == fieldset->Legend())
+ continue;
+ disabled_fieldset_ancestor = fieldset;
+ break;
+ }
+ }
+ }
+ ancestor_disabled_state_ = disabled_fieldset_ancestor
+ ? kAncestorDisabledStateDisabled
+ : kAncestorDisabledStateEnabled;
+}
+
+void HTMLFormControlElement::AncestorDisabledStateWasChanged() {
+ ancestor_disabled_state_ = kAncestorDisabledStateUnknown;
+ DisabledAttributeChanged();
+}
+
+void HTMLFormControlElement::Reset() {
+ SetAutofilled(false);
+ ResetImpl();
+}
+
+void HTMLFormControlElement::AttributeChanged(
+ const AttributeModificationParams& params) {
+ HTMLElement::AttributeChanged(params);
+ if (params.name == disabledAttr &&
+ params.old_value.IsNull() != params.new_value.IsNull()) {
+ DisabledAttributeChanged();
+ if (params.reason == AttributeModificationReason::kDirectly &&
+ IsDisabledFormControl() && AdjustedFocusedElementInTreeScope() == this)
+ blur();
+ }
+}
+
+void HTMLFormControlElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ const QualifiedName& name = params.name;
+ if (name == formAttr) {
+ FormAttributeChanged();
+ UseCounter::Count(GetDocument(), WebFeature::kFormAttribute);
+ } else if (name == readonlyAttr) {
+ if (params.old_value.IsNull() != params.new_value.IsNull()) {
+ SetNeedsWillValidateCheck();
+ PseudoStateChanged(CSSSelector::kPseudoReadOnly);
+ PseudoStateChanged(CSSSelector::kPseudoReadWrite);
+ if (LayoutObject* o = GetLayoutObject())
+ o->InvalidateIfControlStateChanged(kReadOnlyControlState);
+ }
+ } else if (name == requiredAttr) {
+ if (params.old_value.IsNull() != params.new_value.IsNull())
+ RequiredAttributeChanged();
+ UseCounter::Count(GetDocument(), WebFeature::kRequiredAttribute);
+ } else if (name == autofocusAttr) {
+ HTMLElement::ParseAttribute(params);
+ UseCounter::Count(GetDocument(), WebFeature::kAutoFocusAttribute);
+ } else {
+ HTMLElement::ParseAttribute(params);
+ }
+}
+
+void HTMLFormControlElement::DisabledAttributeChanged() {
+ // Don't blur in this function because this is called for descendants of
+ // <fieldset> while tree traversal.
+ EventDispatchForbiddenScope event_forbidden;
+
+ SetNeedsWillValidateCheck();
+ PseudoStateChanged(CSSSelector::kPseudoDisabled);
+ PseudoStateChanged(CSSSelector::kPseudoEnabled);
+ if (LayoutObject* o = GetLayoutObject())
+ o->InvalidateIfControlStateChanged(kEnabledControlState);
+
+ // TODO(dmazzoni): http://crbug.com/699438.
+ // Replace |CheckedStateChanged| with a generic tree changed event.
+ if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
+ cache->CheckedStateChanged(this);
+}
+
+void HTMLFormControlElement::RequiredAttributeChanged() {
+ SetNeedsValidityCheck();
+ PseudoStateChanged(CSSSelector::kPseudoRequired);
+ PseudoStateChanged(CSSSelector::kPseudoOptional);
+ // TODO(dmazzoni): http://crbug.com/699438.
+ // Replace |CheckedStateChanged| with a generic tree changed event.
+ if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
+ cache->CheckedStateChanged(this);
+}
+
+bool HTMLFormControlElement::IsReadOnly() const {
+ return FastHasAttribute(HTMLNames::readonlyAttr);
+}
+
+bool HTMLFormControlElement::IsDisabledOrReadOnly() const {
+ return IsDisabledFormControl() || IsReadOnly();
+}
+
+bool HTMLFormControlElement::SupportsAutofocus() const {
+ return false;
+}
+
+bool HTMLFormControlElement::IsAutofocusable() const {
+ return FastHasAttribute(autofocusAttr) && SupportsAutofocus();
+}
+
+void HTMLFormControlElement::SetAutofilled(bool autofilled) {
+ if (autofilled == is_autofilled_)
+ return;
+
+ is_autofilled_ = autofilled;
+ PseudoStateChanged(CSSSelector::kPseudoAutofill);
+}
+
+const AtomicString& HTMLFormControlElement::autocapitalize() const {
+ if (!FastGetAttribute(autocapitalizeAttr).IsEmpty())
+ return HTMLElement::autocapitalize();
+
+ // If the form control itself does not have the autocapitalize attribute set,
+ // but the form owner is non-null and does have the autocapitalize attribute
+ // set, we inherit from the form owner.
+ if (HTMLFormElement* form = Form())
+ return form->autocapitalize();
+
+ return g_empty_atom;
+}
+
+static bool ShouldAutofocusOnAttach(const HTMLFormControlElement* element) {
+ if (!element->IsAutofocusable())
+ return false;
+ if (element->GetDocument().IsSandboxed(kSandboxAutomaticFeatures)) {
+ // FIXME: This message should be moved off the console once a solution to
+ // https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
+ element->GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kSecurityMessageSource, kErrorMessageLevel,
+ "Blocked autofocusing on a form control because the form's frame is "
+ "sandboxed and the 'allow-scripts' permission is not set."));
+ return false;
+ }
+
+ return true;
+}
+
+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();
+
+ // FIXME: Autofocus handling should be moved to insertedInto according to
+ // the standard.
+ if (ShouldAutofocusOnAttach(this))
+ GetDocument().SetAutofocusElement(this);
+}
+
+void HTMLFormControlElement::DidMoveToNewDocument(Document& old_document) {
+ ListedElement::DidMoveToNewDocument(old_document);
+ HTMLElement::DidMoveToNewDocument(old_document);
+}
+
+Node::InsertionNotificationRequest HTMLFormControlElement::InsertedInto(
+ ContainerNode* insertion_point) {
+ ancestor_disabled_state_ = kAncestorDisabledStateUnknown;
+ // Force traversal to find ancestor
+ may_have_field_set_ancestor_ = true;
+ data_list_ancestor_state_ = kUnknown;
+ SetNeedsWillValidateCheck();
+ HTMLElement::InsertedInto(insertion_point);
+ ListedElement::InsertedInto(insertion_point);
+ FieldSetAncestorsSetNeedsValidityCheck(insertion_point);
+
+ // Trigger for elements outside of forms.
+ if (!formOwner() && insertion_point->isConnected())
+ GetDocument().DidAssociateFormControl(this);
+
+ return kInsertionDone;
+}
+
+void HTMLFormControlElement::RemovedFrom(ContainerNode* insertion_point) {
+ FieldSetAncestorsSetNeedsValidityCheck(insertion_point);
+ HideVisibleValidationMessage();
+ has_validation_message_ = false;
+ ancestor_disabled_state_ = kAncestorDisabledStateUnknown;
+ data_list_ancestor_state_ = kUnknown;
+ SetNeedsWillValidateCheck();
+ HTMLElement::RemovedFrom(insertion_point);
+ ListedElement::RemovedFrom(insertion_point);
+}
+
+void HTMLFormControlElement::WillChangeForm() {
+ ListedElement::WillChangeForm();
+ FormOwnerSetNeedsValidityCheck();
+ if (formOwner() && CanBeSuccessfulSubmitButton())
+ formOwner()->InvalidateDefaultButtonStyle();
+}
+
+void HTMLFormControlElement::DidChangeForm() {
+ ListedElement::DidChangeForm();
+ FormOwnerSetNeedsValidityCheck();
+ if (formOwner() && isConnected() && CanBeSuccessfulSubmitButton())
+ formOwner()->InvalidateDefaultButtonStyle();
+}
+
+void HTMLFormControlElement::FormOwnerSetNeedsValidityCheck() {
+ if (HTMLFormElement* form = formOwner()) {
+ form->PseudoStateChanged(CSSSelector::kPseudoValid);
+ form->PseudoStateChanged(CSSSelector::kPseudoInvalid);
+ }
+}
+
+void HTMLFormControlElement::FieldSetAncestorsSetNeedsValidityCheck(
+ Node* node) {
+ if (!node)
+ return;
+ if (!may_have_field_set_ancestor_)
+ return;
+ for (HTMLFieldSetElement* field_set =
+ Traversal<HTMLFieldSetElement>::FirstAncestorOrSelf(*node);
+ field_set;
+ field_set = Traversal<HTMLFieldSetElement>::FirstAncestor(*field_set)) {
+ field_set->PseudoStateChanged(CSSSelector::kPseudoValid);
+ field_set->PseudoStateChanged(CSSSelector::kPseudoInvalid);
+ }
+}
+
+void HTMLFormControlElement::DispatchChangeEvent() {
+ DispatchScopedEvent(Event::CreateBubble(EventTypeNames::change));
+}
+
+HTMLFormElement* HTMLFormControlElement::formOwner() const {
+ return ListedElement::Form();
+}
+
+bool HTMLFormControlElement::IsDisabledFormControl() const {
+ if (FastHasAttribute(disabledAttr))
+ return true;
+
+ // Since the MHTML is loaded in sandboxing mode with form submission and
+ // script execution disabled, we should gray out all form control elements
+ // to indicate that the form cannot be worked on.
+ if (GetDocument().Fetcher()->Archive())
+ return true;
+
+ if (ancestor_disabled_state_ == kAncestorDisabledStateUnknown)
+ UpdateAncestorDisabledState();
+ return ancestor_disabled_state_ == kAncestorDisabledStateDisabled;
+}
+
+bool HTMLFormControlElement::MatchesEnabledPseudoClass() const {
+ return !IsDisabledFormControl();
+}
+
+bool HTMLFormControlElement::IsRequired() const {
+ return FastHasAttribute(requiredAttr);
+}
+
+String HTMLFormControlElement::ResultForDialogSubmit() {
+ return FastGetAttribute(valueAttr);
+}
+
+void HTMLFormControlElement::DidRecalcStyle(StyleRecalcChange) {
+ if (LayoutObject* layout_object = GetLayoutObject())
+ layout_object->UpdateFromElement();
+}
+
+bool HTMLFormControlElement::SupportsFocus() const {
+ return !IsDisabledFormControl();
+}
+
+bool HTMLFormControlElement::IsKeyboardFocusable() const {
+ // Skip tabIndex check in a parent class.
+ return IsFocusable();
+}
+
+bool HTMLFormControlElement::ShouldShowFocusRingOnMouseFocus() const {
+ return false;
+}
+
+bool HTMLFormControlElement::ShouldHaveFocusAppearance() const {
+ return !WasFocusedByMouse() || ShouldShowFocusRingOnMouseFocus();
+}
+
+void HTMLFormControlElement::WillCallDefaultEventHandler(const Event& event) {
+ if (!WasFocusedByMouse())
+ return;
+ if (!event.IsKeyboardEvent() || event.type() != EventTypeNames::keydown)
+ return;
+
+ bool old_should_have_focus_appearance = ShouldHaveFocusAppearance();
+ SetWasFocusedByMouse(false);
+
+ // Changes to WasFocusedByMouse may affect ShouldHaveFocusAppearance() and
+ // LayoutTheme::IsFocused(). Inform LayoutTheme if
+ // ShouldHaveFocusAppearance() changes.
+ if (old_should_have_focus_appearance != ShouldHaveFocusAppearance() &&
+ GetLayoutObject()) {
+ GetLayoutObject()->InvalidateIfControlStateChanged(kFocusControlState);
+ }
+}
+
+int HTMLFormControlElement::tabIndex() const {
+ // Skip the supportsFocus check in HTMLElement.
+ return Element::tabIndex();
+}
+
+bool HTMLFormControlElement::RecalcWillValidate() const {
+ if (data_list_ancestor_state_ == kUnknown) {
+ if (Traversal<HTMLDataListElement>::FirstAncestor(*this))
+ data_list_ancestor_state_ = kInsideDataList;
+ else
+ data_list_ancestor_state_ = kNotInsideDataList;
+ }
+ return data_list_ancestor_state_ == kNotInsideDataList &&
+ !IsDisabledOrReadOnly();
+}
+
+bool HTMLFormControlElement::willValidate() const {
+ if (!will_validate_initialized_ || data_list_ancestor_state_ == kUnknown) {
+ const_cast<HTMLFormControlElement*>(this)->SetNeedsWillValidateCheck();
+ } else {
+ // If the following assertion fails, setNeedsWillValidateCheck() is not
+ // called correctly when something which changes recalcWillValidate() result
+ // is updated.
+ DCHECK_EQ(will_validate_, RecalcWillValidate());
+ }
+ return will_validate_;
+}
+
+void HTMLFormControlElement::SetNeedsWillValidateCheck() {
+ // We need to recalculate willValidate immediately because willValidate change
+ // can causes style change.
+ bool new_will_validate = RecalcWillValidate();
+ if (will_validate_initialized_ && will_validate_ == new_will_validate)
+ return;
+ will_validate_initialized_ = true;
+ will_validate_ = new_will_validate;
+ // Needs to force setNeedsValidityCheck() to invalidate validity state of
+ // FORM/FIELDSET. If this element updates willValidate twice and
+ // isValidElement() is not called between them, the second call of this
+ // function still has m_validityIsDirty==true, which means
+ // setNeedsValidityCheck() doesn't invalidate validity state of
+ // FORM/FIELDSET.
+ validity_is_dirty_ = false;
+ SetNeedsValidityCheck();
+ // No need to trigger style recalculation here because
+ // setNeedsValidityCheck() does it in the right away. This relies on
+ // the assumption that valid() is always true if willValidate() is false.
+
+ if (!will_validate_)
+ HideVisibleValidationMessage();
+}
+
+void HTMLFormControlElement::FindCustomValidationMessageTextDirection(
+ const String& message,
+ TextDirection& message_dir,
+ String& sub_message,
+ TextDirection& sub_message_dir) {
+ message_dir = DetermineDirectionality(message);
+ if (!sub_message.IsEmpty())
+ sub_message_dir = GetLayoutObject()->Style()->Direction();
+}
+
+void HTMLFormControlElement::UpdateVisibleValidationMessage() {
+ Page* page = GetDocument().GetPage();
+ if (!page || !page->IsPageVisible() || GetDocument().UnloadStarted())
+ return;
+ if (page->Paused())
+ return;
+ String message;
+ if (GetLayoutObject() && willValidate())
+ message = validationMessage().StripWhiteSpace();
+
+ has_validation_message_ = true;
+ ValidationMessageClient* client = &page->GetValidationMessageClient();
+ TextDirection message_dir = TextDirection::kLtr;
+ TextDirection sub_message_dir = TextDirection::kLtr;
+ String sub_message = ValidationSubMessage().StripWhiteSpace();
+ if (message.IsEmpty()) {
+ client->HideValidationMessage(*this);
+ } else {
+ FindCustomValidationMessageTextDirection(message, message_dir, sub_message,
+ sub_message_dir);
+ }
+ client->ShowValidationMessage(*this, message, message_dir, sub_message,
+ sub_message_dir);
+}
+
+void HTMLFormControlElement::HideVisibleValidationMessage() {
+ if (!has_validation_message_)
+ return;
+
+ if (ValidationMessageClient* client = GetValidationMessageClient())
+ client->HideValidationMessage(*this);
+}
+
+bool HTMLFormControlElement::IsValidationMessageVisible() const {
+ if (!has_validation_message_)
+ return false;
+
+ ValidationMessageClient* client = GetValidationMessageClient();
+ if (!client)
+ return false;
+
+ return client->IsValidationMessageVisible(*this);
+}
+
+ValidationMessageClient* HTMLFormControlElement::GetValidationMessageClient()
+ const {
+ Page* page = GetDocument().GetPage();
+ if (!page)
+ return nullptr;
+
+ return &page->GetValidationMessageClient();
+}
+
+bool HTMLFormControlElement::checkValidity(
+ HeapVector<Member<HTMLFormControlElement>>* unhandled_invalid_controls,
+ CheckValidityEventBehavior event_behavior) {
+ if (!willValidate())
+ return true;
+ if (IsValidElement())
+ return true;
+ if (event_behavior != kCheckValidityDispatchInvalidEvent)
+ return false;
+ Document* original_document = &GetDocument();
+ DispatchEventResult dispatch_result =
+ DispatchEvent(Event::CreateCancelable(EventTypeNames::invalid));
+ if (dispatch_result == DispatchEventResult::kNotCanceled &&
+ unhandled_invalid_controls && isConnected() &&
+ original_document == GetDocument())
+ unhandled_invalid_controls->push_back(this);
+ return false;
+}
+
+void HTMLFormControlElement::ShowValidationMessage() {
+ scrollIntoViewIfNeeded(false);
+ focus();
+ UpdateVisibleValidationMessage();
+}
+
+bool HTMLFormControlElement::reportValidity() {
+ HeapVector<Member<HTMLFormControlElement>> unhandled_invalid_controls;
+ bool is_valid = checkValidity(&unhandled_invalid_controls,
+ kCheckValidityDispatchInvalidEvent);
+ if (is_valid || unhandled_invalid_controls.IsEmpty())
+ return is_valid;
+ DCHECK_EQ(unhandled_invalid_controls.size(), 1u);
+ DCHECK_EQ(unhandled_invalid_controls[0].Get(), this);
+ // Update layout now before calling isFocusable(), which has
+ // !layoutObject()->needsLayout() assertion.
+ GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+ if (IsFocusable()) {
+ ShowValidationMessage();
+ return false;
+ }
+ if (GetDocument().GetFrame()) {
+ String message(
+ "An invalid form control with name='%name' is not focusable.");
+ message.Replace("%name", GetName());
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kRenderingMessageSource, kErrorMessageLevel, message));
+ }
+ return false;
+}
+
+bool HTMLFormControlElement::MatchesValidityPseudoClasses() const {
+ return willValidate();
+}
+
+bool HTMLFormControlElement::IsValidElement() {
+ if (validity_is_dirty_) {
+ is_valid_ = !willValidate() || Valid();
+ validity_is_dirty_ = false;
+ } else {
+ // If the following assertion fails, setNeedsValidityCheck() is not
+ // called correctly when something which changes validity is updated.
+ DCHECK_EQ(is_valid_, (!willValidate() || Valid()));
+ }
+ return is_valid_;
+}
+
+void HTMLFormControlElement::SetNeedsValidityCheck() {
+ if (!validity_is_dirty_) {
+ validity_is_dirty_ = true;
+ FormOwnerSetNeedsValidityCheck();
+ FieldSetAncestorsSetNeedsValidityCheck(parentNode());
+ PseudoStateChanged(CSSSelector::kPseudoValid);
+ PseudoStateChanged(CSSSelector::kPseudoInvalid);
+ }
+
+ // Updates only if this control already has a validation message.
+ if (IsValidationMessageVisible()) {
+ // Calls UpdateVisibleValidationMessage() even if is_valid_ is not
+ // changed because a validation message can be changed.
+ GetDocument()
+ .GetTaskRunner(TaskType::kDOMManipulation)
+ ->PostTask(
+ FROM_HERE,
+ WTF::Bind(&HTMLFormControlElement::UpdateVisibleValidationMessage,
+ WrapPersistent(this)));
+ }
+}
+
+void HTMLFormControlElement::setCustomValidity(const String& error) {
+ ListedElement::setCustomValidity(error);
+ SetNeedsValidityCheck();
+}
+
+void HTMLFormControlElement::DispatchBlurEvent(
+ Element* new_focused_element,
+ WebFocusType type,
+ InputDeviceCapabilities* source_capabilities) {
+ HTMLElement::DispatchBlurEvent(new_focused_element, type,
+ source_capabilities);
+ HideVisibleValidationMessage();
+}
+
+bool HTMLFormControlElement::IsSuccessfulSubmitButton() const {
+ return CanBeSuccessfulSubmitButton() && !IsDisabledFormControl();
+}
+
+// static
+const HTMLFormControlElement*
+HTMLFormControlElement::EnclosingFormControlElement(const Node* node) {
+ if (!node)
+ return nullptr;
+ return Traversal<HTMLFormControlElement>::FirstAncestorOrSelf(*node);
+}
+
+String HTMLFormControlElement::NameForAutofill() const {
+ String full_name = GetName();
+ String trimmed_name = full_name.StripWhiteSpace();
+ if (!trimmed_name.IsEmpty())
+ return trimmed_name;
+ full_name = GetIdAttribute();
+ trimmed_name = full_name.StripWhiteSpace();
+ return trimmed_name;
+}
+
+void HTMLFormControlElement::CloneNonAttributePropertiesFrom(
+ const Element& source,
+ CloneChildrenFlag flag) {
+ HTMLElement::CloneNonAttributePropertiesFrom(source, flag);
+ SetNeedsValidityCheck();
+}
+
+void HTMLFormControlElement::AssociateWith(HTMLFormElement* form) {
+ AssociateByParser(form);
+};
+
+} // namespace blink
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
new file mode 100644
index 00000000000..e85ee8d44d0
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
+ * reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/form_associated.h"
+#include "third_party/blink/renderer/core/html/forms/labelable_element.h"
+#include "third_party/blink/renderer/core/html/forms/listed_element.h"
+
+namespace blink {
+
+class HTMLFormElement;
+class ValidationMessageClient;
+
+enum CheckValidityEventBehavior {
+ kCheckValidityDispatchNoEvent,
+ kCheckValidityDispatchInvalidEvent
+};
+
+// HTMLFormControlElement is the default implementation of
+// ListedElement, and listed element implementations should use
+// HTMLFormControlElement unless there is a special reason.
+class CORE_EXPORT HTMLFormControlElement : public LabelableElement,
+ public ListedElement,
+ public FormAssociated {
+ USING_GARBAGE_COLLECTED_MIXIN(HTMLFormControlElement);
+
+ public:
+ ~HTMLFormControlElement() override;
+ void Trace(blink::Visitor*) override;
+
+ String formAction() const;
+ void setFormAction(const AtomicString&);
+ String formEnctype() const;
+ void setFormEnctype(const AtomicString&);
+ String formMethod() const;
+ void setFormMethod(const AtomicString&);
+ bool FormNoValidate() const;
+
+ void AncestorDisabledStateWasChanged();
+
+ void Reset();
+
+ void DispatchChangeEvent();
+
+ HTMLFormElement* formOwner() const final;
+
+ bool IsDisabledFormControl() const override;
+
+ bool MatchesEnabledPseudoClass() const override;
+
+ bool IsEnumeratable() const override { return false; }
+
+ bool IsRequired() const;
+
+ const AtomicString& type() const { return FormControlType(); }
+
+ virtual const AtomicString& FormControlType() const = 0;
+
+ virtual bool CanTriggerImplicitSubmission() const { return false; }
+
+ virtual bool IsSubmittableElement() { return true; }
+
+ virtual String ResultForDialogSubmit();
+
+ // Return true if this control type can be a submit button. This doesn't
+ // check |disabled|, and this doesn't check if this is the first submit
+ // button.
+ virtual bool CanBeSuccessfulSubmitButton() const { return false; }
+ // Return true if this control can submit a form.
+ // i.e. canBeSuccessfulSubmitButton() && !isDisabledFormControl().
+ bool IsSuccessfulSubmitButton() const;
+ virtual bool IsActivatedSubmit() const { return false; }
+ virtual void SetActivatedSubmit(bool) {}
+
+ bool willValidate() const override;
+
+ void UpdateVisibleValidationMessage();
+ void HideVisibleValidationMessage();
+ bool checkValidity(
+ HeapVector<Member<HTMLFormControlElement>>* unhandled_invalid_controls =
+ nullptr,
+ CheckValidityEventBehavior = kCheckValidityDispatchInvalidEvent);
+ bool reportValidity();
+ // This must be called only after the caller check the element is focusable.
+ void ShowValidationMessage();
+ bool IsValidationMessageVisible() const;
+ // This must be called when a validation constraint or control value is
+ // changed.
+ void SetNeedsValidityCheck();
+ void setCustomValidity(const String&) final;
+ void FindCustomValidationMessageTextDirection(const String& message,
+ TextDirection& message_dir,
+ String& sub_message,
+ TextDirection& sub_message_dir);
+
+ bool IsReadOnly() const;
+ bool IsDisabledOrReadOnly() const;
+
+ bool IsAutofocusable() const;
+
+ virtual bool ShouldShowFocusRingOnMouseFocus() const;
+
+ bool IsAutofilled() const { return is_autofilled_; }
+ void SetAutofilled(bool = true);
+
+ const AtomicString& autocapitalize() const final;
+
+ static const HTMLFormControlElement* EnclosingFormControlElement(const Node*);
+
+ String NameForAutofill() const;
+
+ void CloneNonAttributePropertiesFrom(const Element&,
+ CloneChildrenFlag) override;
+
+ FormAssociated* ToFormAssociatedOrNull() override { return this; };
+ void AssociateWith(HTMLFormElement*) override;
+
+ bool BlocksFormSubmission() const { return blocks_form_submission_; }
+ void SetBlocksFormSubmission(bool value) { blocks_form_submission_ = value; }
+
+ protected:
+ HTMLFormControlElement(const QualifiedName& tag_name, Document&);
+
+ void AttributeChanged(const AttributeModificationParams&) override;
+ void ParseAttribute(const AttributeModificationParams&) override;
+ virtual void RequiredAttributeChanged();
+ virtual void DisabledAttributeChanged();
+ void AttachLayoutTree(AttachContext&) override;
+ InsertionNotificationRequest InsertedInto(ContainerNode*) override;
+ void RemovedFrom(ContainerNode*) override;
+ void WillChangeForm() override;
+ void DidChangeForm() override;
+ void DidMoveToNewDocument(Document& old_document) override;
+
+ bool SupportsFocus() const override;
+ bool IsKeyboardFocusable() const override;
+ bool ShouldHaveFocusAppearance() const final;
+ void DispatchBlurEvent(Element* new_focused_element,
+ WebFocusType,
+ InputDeviceCapabilities* source_capabilities) override;
+ void WillCallDefaultEventHandler(const Event&) final;
+
+ void DidRecalcStyle(StyleRecalcChange) override;
+
+ // This must be called any time the result of willValidate() has changed.
+ void SetNeedsWillValidateCheck();
+ virtual bool RecalcWillValidate() const;
+
+ virtual void ResetImpl() {}
+ virtual bool SupportsAutofocus() const;
+
+ private:
+ bool IsFormControlElement() const final { return true; }
+ bool AlwaysCreateUserAgentShadowRoot() const override { return true; }
+
+ int tabIndex() const override;
+
+ bool IsValidElement() override;
+ bool MatchesValidityPseudoClasses() const override;
+ void UpdateAncestorDisabledState() const;
+
+ ValidationMessageClient* GetValidationMessageClient() const;
+
+ // Requests validity recalc for the form owner, if one exists.
+ void FormOwnerSetNeedsValidityCheck();
+ // Requests validity recalc for all ancestor fieldsets, if exist.
+ void FieldSetAncestorsSetNeedsValidityCheck(Node*);
+
+ enum AncestorDisabledState {
+ kAncestorDisabledStateUnknown,
+ kAncestorDisabledStateEnabled,
+ kAncestorDisabledStateDisabled
+ };
+ mutable AncestorDisabledState ancestor_disabled_state_;
+ enum DataListAncestorState { kUnknown, kInsideDataList, kNotInsideDataList };
+ mutable enum DataListAncestorState data_list_ancestor_state_;
+ mutable bool may_have_field_set_ancestor_ : 1;
+
+ bool is_autofilled_ : 1;
+ bool has_validation_message_ : 1;
+ // The initial value of m_willValidate depends on the derived class. We can't
+ // initialize it with a virtual function in the constructor. m_willValidate
+ // is not deterministic as long as m_willValidateInitialized is false.
+ mutable bool will_validate_initialized_ : 1;
+ mutable bool will_validate_ : 1;
+
+ // Cache of valid().
+ bool is_valid_ : 1;
+ bool validity_is_dirty_ : 1;
+
+ bool blocks_form_submission_ : 1;
+};
+
+inline bool IsHTMLFormControlElement(const Element& element) {
+ return element.IsFormControlElement();
+}
+
+DEFINE_HTMLELEMENT_TYPE_CASTS_WITH_FUNCTION(HTMLFormControlElement);
+DEFINE_TYPE_CASTS(HTMLFormControlElement,
+ ListedElement,
+ control,
+ control->IsFormControlElement(),
+ control.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
new file mode 100644
index 00000000000..96133c5afce
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc
@@ -0,0 +1,153 @@
+// Copyright 2014 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/html_form_control_element.h"
+
+#include <memory>
+#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/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
+#include "third_party/blink/renderer/core/page/scoped_page_pauser.h"
+#include "third_party/blink/renderer/core/page/validation_message_client.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+
+namespace blink {
+
+namespace {
+class MockFormValidationMessageClient
+ : public GarbageCollectedFinalized<MockFormValidationMessageClient>,
+ public ValidationMessageClient {
+ USING_GARBAGE_COLLECTED_MIXIN(MockFormValidationMessageClient);
+
+ public:
+ void ShowValidationMessage(const Element& anchor,
+ const String&,
+ TextDirection,
+ const String&,
+ TextDirection) override {
+ anchor_ = anchor;
+ ++operation_count_;
+ }
+
+ void HideValidationMessage(const Element& anchor) override {
+ if (anchor_ == &anchor)
+ anchor_ = nullptr;
+ ++operation_count_;
+ }
+
+ bool IsValidationMessageVisible(const Element& anchor) override {
+ return anchor_ == &anchor;
+ }
+
+ void DocumentDetached(const Document&) override {}
+ void WillBeDestroyed() override {}
+ void Trace(blink::Visitor* visitor) override {
+ visitor->Trace(anchor_);
+ ValidationMessageClient::Trace(visitor);
+ }
+
+ // The number of calls of ShowValidationMessage() and HideValidationMessage().
+ int OperationCount() const { return operation_count_; }
+
+ private:
+ Member<const Element> anchor_;
+ int operation_count_ = 0;
+};
+} // namespace
+
+class HTMLFormControlElementTest : public PageTestBase {
+ protected:
+ void SetUp() override;
+};
+
+void HTMLFormControlElementTest::SetUp() {
+ Page::PageClients page_clients;
+ FillWithEmptyClients(page_clients);
+ SetupPageWithClients(&page_clients);
+ GetDocument().SetMimeType("text/html");
+}
+
+TEST_F(HTMLFormControlElementTest, customValidationMessageTextDirection) {
+ SetHtmlInnerHTML("<body><input pattern='abc' value='def' id=input></body>");
+
+ HTMLInputElement* input = ToHTMLInputElement(GetElementById("input"));
+ input->setCustomValidity(
+ String::FromUTF8("\xD8\xB9\xD8\xB1\xD8\xA8\xD9\x89"));
+ input->setAttribute(
+ HTMLNames::titleAttr,
+ AtomicString::FromUTF8("\xD8\xB9\xD8\xB1\xD8\xA8\xD9\x89"));
+
+ String message = input->validationMessage().StripWhiteSpace();
+ String sub_message = input->ValidationSubMessage().StripWhiteSpace();
+ TextDirection message_dir = TextDirection::kRtl;
+ TextDirection sub_message_dir = TextDirection::kLtr;
+
+ input->FindCustomValidationMessageTextDirection(message, message_dir,
+ sub_message, sub_message_dir);
+ EXPECT_EQ(TextDirection::kRtl, message_dir);
+ EXPECT_EQ(TextDirection::kLtr, sub_message_dir);
+
+ scoped_refptr<ComputedStyle> rtl_style =
+ ComputedStyle::Clone(input->GetLayoutObject()->StyleRef());
+ rtl_style->SetDirection(TextDirection::kRtl);
+ input->GetLayoutObject()->SetStyle(std::move(rtl_style));
+ input->FindCustomValidationMessageTextDirection(message, message_dir,
+ sub_message, sub_message_dir);
+ EXPECT_EQ(TextDirection::kRtl, message_dir);
+ EXPECT_EQ(TextDirection::kLtr, sub_message_dir);
+
+ input->setCustomValidity(String::FromUTF8("Main message."));
+ message = input->validationMessage().StripWhiteSpace();
+ sub_message = input->ValidationSubMessage().StripWhiteSpace();
+ input->FindCustomValidationMessageTextDirection(message, message_dir,
+ sub_message, sub_message_dir);
+ EXPECT_EQ(TextDirection::kLtr, message_dir);
+ EXPECT_EQ(TextDirection::kLtr, sub_message_dir);
+
+ input->setCustomValidity(String());
+ message = input->validationMessage().StripWhiteSpace();
+ sub_message = input->ValidationSubMessage().StripWhiteSpace();
+ input->FindCustomValidationMessageTextDirection(message, message_dir,
+ sub_message, sub_message_dir);
+ EXPECT_EQ(TextDirection::kLtr, message_dir);
+ EXPECT_EQ(TextDirection::kRtl, sub_message_dir);
+}
+
+TEST_F(HTMLFormControlElementTest, UpdateValidationMessageSkippedIfPrinting) {
+ SetHtmlInnerHTML("<body><input required id=input></body>");
+ ValidationMessageClient* validation_message_client =
+ new MockFormValidationMessageClient();
+ GetPage().SetValidationMessageClient(validation_message_client);
+ Page::OrdinaryPages().insert(&GetPage());
+
+ HTMLInputElement* input = ToHTMLInputElement(GetElementById("input"));
+ ScopedPagePauser pauser; // print() pauses the page.
+ input->reportValidity();
+ EXPECT_FALSE(validation_message_client->IsValidationMessageVisible(*input));
+}
+
+TEST_F(HTMLFormControlElementTest, DoNotUpdateLayoutDuringDOMMutation) {
+ // The real ValidationMessageClient has UpdateStyleAndLayout*() in
+ // 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>");
+ HTMLFormControlElement* const select =
+ ToHTMLFormControlElement(GetDocument().QuerySelector("select"));
+ auto* const optgroup = GetDocument().CreateRawElement(HTMLNames::optgroupTag);
+ auto* validation_client = new MockFormValidationMessageClient();
+ GetDocument().GetPage()->SetValidationMessageClient(validation_client);
+
+ select->setCustomValidity("foobar");
+ select->reportValidity();
+ int start_operation_count = validation_client->OperationCount();
+ select->appendChild(optgroup);
+ EXPECT_EQ(start_operation_count, validation_client->OperationCount())
+ << "DOM mutation should not handle validation message UI in it.";
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..430f7bc185b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc
@@ -0,0 +1,96 @@
+/*
+ * 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 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/html_form_control_element_with_state.h"
+
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/local_frame_client.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/page/chrome_client.h"
+
+namespace blink {
+
+HTMLFormControlElementWithState::HTMLFormControlElementWithState(
+ const QualifiedName& tag_name,
+ Document& doc)
+ : HTMLFormControlElement(tag_name, doc) {}
+
+HTMLFormControlElementWithState::~HTMLFormControlElementWithState() = default;
+
+Node::InsertionNotificationRequest
+HTMLFormControlElementWithState::InsertedInto(ContainerNode* insertion_point) {
+ if (insertion_point->isConnected() && !ContainingShadowRoot())
+ GetDocument().GetFormController().RegisterStatefulFormControl(*this);
+ return HTMLFormControlElement::InsertedInto(insertion_point);
+}
+
+void HTMLFormControlElementWithState::RemovedFrom(
+ ContainerNode* insertion_point) {
+ if (insertion_point->isConnected() && !ContainingShadowRoot() &&
+ !insertion_point->ContainingShadowRoot())
+ GetDocument().GetFormController().UnregisterStatefulFormControl(*this);
+ HTMLFormControlElement::RemovedFrom(insertion_point);
+}
+
+bool HTMLFormControlElementWithState::ShouldAutocomplete() const {
+ if (!Form())
+ return true;
+ return Form()->ShouldAutocomplete();
+}
+
+void HTMLFormControlElementWithState::NotifyFormStateChanged() {
+ // This can be called during fragment parsing as a result of option
+ // selection before the document is active (or even in a frame).
+ if (!GetDocument().IsActive())
+ return;
+ GetDocument().GetFrame()->Client()->DidUpdateCurrentHistoryItem();
+}
+
+bool HTMLFormControlElementWithState::ShouldSaveAndRestoreFormControlState()
+ const {
+ // We don't save/restore control state in a form with autocomplete=off.
+ return isConnected() && ShouldAutocomplete();
+}
+
+FormControlState HTMLFormControlElementWithState::SaveFormControlState() const {
+ return FormControlState();
+}
+
+void HTMLFormControlElementWithState::FinishParsingChildren() {
+ HTMLFormControlElement::FinishParsingChildren();
+ GetDocument().GetFormController().RestoreControlStateFor(*this);
+}
+
+bool HTMLFormControlElementWithState::IsFormControlElementWithState() const {
+ return true;
+}
+
+void HTMLFormControlElementWithState::Trace(Visitor* visitor) {
+ visitor->Trace(prev_);
+ visitor->Trace(next_);
+ HTMLFormControlElement::Trace(visitor);
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..fab6339d451
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
+ * reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_WITH_STATE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_WITH_STATE_H_
+
+#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/doubly_linked_list.h"
+
+namespace blink {
+
+class FormControlState;
+
+class HTMLFormControlElementWithState;
+// Cannot use eager tracing as HTMLFormControlElementWithState objects are part
+// of a HeapDoublyLinkedList and have pointers to the previous and next elements
+// in the list, which can cause very deep stacks in eager tracing.
+WILL_NOT_BE_EAGERLY_TRACED_CLASS(HTMLFormControlElementWithState);
+
+class CORE_EXPORT HTMLFormControlElementWithState
+ : public HTMLFormControlElement,
+ public DoublyLinkedListNode<HTMLFormControlElementWithState> {
+ public:
+ ~HTMLFormControlElementWithState() override;
+
+ bool CanContainRangeEndPoint() const final { return false; }
+
+ virtual bool ShouldAutocomplete() const;
+ virtual bool ShouldSaveAndRestoreFormControlState() const;
+ virtual FormControlState SaveFormControlState() const;
+ // The specified FormControlState must have at least one string value.
+ virtual void RestoreFormControlState(const FormControlState&) {}
+ void NotifyFormStateChanged();
+
+ void Trace(Visitor*) override;
+
+ protected:
+ HTMLFormControlElementWithState(const QualifiedName& tag_name, Document&);
+
+ void FinishParsingChildren() override;
+ InsertionNotificationRequest InsertedInto(ContainerNode*) override;
+ void RemovedFrom(ContainerNode*) override;
+ bool IsFormControlElementWithState() const final;
+
+ private:
+ bool ShouldForceLegacyLayout() const final { return true; }
+
+ // Pointers for DoublyLinkedListNode<HTMLFormControlElementWithState>. This
+ // is used for adding an instance to a list of form controls stored in
+ // DocumentState. Each instance is only added to its containing document's
+ // DocumentState list.
+ friend class WTF::DoublyLinkedListNode<HTMLFormControlElementWithState>;
+ Member<HTMLFormControlElementWithState> prev_;
+ Member<HTMLFormControlElementWithState> next_;
+};
+
+DEFINE_TYPE_CASTS(HTMLFormControlElementWithState,
+ ListedElement,
+ control,
+ control->IsFormControlElementWithState(),
+ control.IsFormControlElementWithState());
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.cc
new file mode 100644
index 00000000000..8f0d4784c76
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.cc
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010, 2011, 2012 Apple Inc. All
+ * rights reserved.
+ * Copyright (C) 2014 Samsung Electronics. All rights reserved.
+ *
+ * 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/html_form_controls_collection.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/radio_node_list_or_element.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/html_image_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+// Since the collections are to be "live", we have to do the
+// calculation every time if anything has changed.
+
+HTMLFormControlsCollection::HTMLFormControlsCollection(
+ ContainerNode& owner_node)
+ : HTMLCollection(owner_node, kFormControls, kOverridesItemAfter),
+ cached_element_(nullptr),
+ cached_element_offset_in_array_(0) {
+ DCHECK(IsHTMLFormElement(owner_node));
+}
+
+HTMLFormControlsCollection* HTMLFormControlsCollection::Create(
+ ContainerNode& owner_node,
+ CollectionType type) {
+ DCHECK_EQ(type, kFormControls);
+ return new HTMLFormControlsCollection(owner_node);
+}
+
+HTMLFormControlsCollection::~HTMLFormControlsCollection() = default;
+
+const ListedElement::List& HTMLFormControlsCollection::ListedElements() const {
+ return ToHTMLFormElement(ownerNode()).ListedElements();
+}
+
+const HeapVector<Member<HTMLImageElement>>&
+HTMLFormControlsCollection::FormImageElements() const {
+ return ToHTMLFormElement(ownerNode()).ImageElements();
+}
+
+static unsigned FindListedElement(const ListedElement::List& listed_elements,
+ Element* element) {
+ unsigned i = 0;
+ for (; i < listed_elements.size(); ++i) {
+ ListedElement* listed_element = listed_elements[i];
+ if (listed_element->IsEnumeratable() &&
+ ToHTMLElement(listed_element) == element)
+ break;
+ }
+ return i;
+}
+
+HTMLElement* HTMLFormControlsCollection::VirtualItemAfter(
+ Element* previous) const {
+ const ListedElement::List& listed_elements = ListedElements();
+ unsigned offset;
+ if (!previous)
+ offset = 0;
+ else if (cached_element_ == previous)
+ offset = cached_element_offset_in_array_ + 1;
+ else
+ offset = FindListedElement(listed_elements, previous) + 1;
+
+ for (unsigned i = offset; i < listed_elements.size(); ++i) {
+ ListedElement* listed_element = listed_elements[i];
+ if (listed_element->IsEnumeratable()) {
+ cached_element_ = ToHTMLElement(listed_element);
+ cached_element_offset_in_array_ = i;
+ return cached_element_;
+ }
+ }
+ return nullptr;
+}
+
+void HTMLFormControlsCollection::InvalidateCache(Document* old_document) const {
+ HTMLCollection::InvalidateCache(old_document);
+ cached_element_ = nullptr;
+ cached_element_offset_in_array_ = 0;
+}
+
+static HTMLElement* FirstNamedItem(const ListedElement::List& elements_array,
+ const QualifiedName& attr_name,
+ const String& name) {
+ DCHECK(attr_name == idAttr || attr_name == nameAttr);
+
+ for (const auto& listed_element : elements_array) {
+ HTMLElement* element = ToHTMLElement(listed_element);
+ if (listed_element->IsEnumeratable() &&
+ element->FastGetAttribute(attr_name) == name)
+ return element;
+ }
+ return nullptr;
+}
+
+HTMLElement* HTMLFormControlsCollection::namedItem(
+ const AtomicString& name) const {
+ // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
+ // This method first searches for an object with a matching id
+ // attribute. If a match is not found, the method then searches for an
+ // object with a matching name attribute, but only on those elements
+ // that are allowed a name attribute.
+ if (HTMLElement* item = FirstNamedItem(ListedElements(), idAttr, name))
+ return item;
+ return FirstNamedItem(ListedElements(), nameAttr, name);
+}
+
+void HTMLFormControlsCollection::UpdateIdNameCache() const {
+ if (HasValidIdNameCache())
+ return;
+
+ NamedItemCache* cache = NamedItemCache::Create();
+ HashSet<StringImpl*> found_input_elements;
+
+ for (const auto& listed_element : ListedElements()) {
+ if (listed_element->IsEnumeratable()) {
+ HTMLElement* element = ToHTMLElement(listed_element);
+ const AtomicString& id_attr_val = element->GetIdAttribute();
+ const AtomicString& name_attr_val = element->GetNameAttribute();
+ if (!id_attr_val.IsEmpty()) {
+ cache->AddElementWithId(id_attr_val, element);
+ found_input_elements.insert(id_attr_val.Impl());
+ }
+ if (!name_attr_val.IsEmpty() && id_attr_val != name_attr_val) {
+ cache->AddElementWithName(name_attr_val, element);
+ found_input_elements.insert(name_attr_val.Impl());
+ }
+ }
+ }
+
+ // HTMLFormControlsCollection doesn't support named getter for IMG
+ // elements. However we still need to handle IMG elements here because
+ // HTMLFormElement named getter relies on this.
+ for (const auto& element : FormImageElements()) {
+ const AtomicString& id_attr_val = element->GetIdAttribute();
+ const AtomicString& name_attr_val = element->GetNameAttribute();
+ if (!id_attr_val.IsEmpty() &&
+ !found_input_elements.Contains(id_attr_val.Impl()))
+ cache->AddElementWithId(id_attr_val, element);
+ if (!name_attr_val.IsEmpty() && id_attr_val != name_attr_val &&
+ !found_input_elements.Contains(name_attr_val.Impl()))
+ cache->AddElementWithName(name_attr_val, element);
+ }
+
+ // Set the named item cache last as traversing the tree may cause cache
+ // invalidation.
+ SetNamedItemCache(cache);
+}
+
+void HTMLFormControlsCollection::namedGetter(
+ const AtomicString& name,
+ RadioNodeListOrElement& return_value) {
+ HeapVector<Member<Element>> named_items;
+ NamedItems(name, named_items);
+
+ if (named_items.IsEmpty())
+ return;
+
+ if (named_items.size() == 1) {
+ if (!IsHTMLImageElement(*named_items[0]))
+ return_value.SetElement(named_items.at(0));
+ return;
+ }
+
+ // This path never returns a RadioNodeList for <img> because
+ // onlyMatchingImgElements flag is false by default.
+ return_value.SetRadioNodeList(ownerNode().GetRadioNodeList(name));
+}
+
+void HTMLFormControlsCollection::SupportedPropertyNames(Vector<String>& names) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#htmlformcontrolscollection-0:
+ // The supported property names consist of the non-empty values of all the id
+ // and name attributes of all the elements represented by the collection, in
+ // tree order, ignoring later duplicates, with the id of an element preceding
+ // its name if it contributes both, they differ from each other, and neither
+ // is the duplicate of an earlier entry.
+ HashSet<AtomicString> existing_names;
+ unsigned length = this->length();
+ for (unsigned i = 0; i < length; ++i) {
+ HTMLElement* element = item(i);
+ DCHECK(element);
+ const AtomicString& id_attribute = element->GetIdAttribute();
+ if (!id_attribute.IsEmpty()) {
+ HashSet<AtomicString>::AddResult add_result =
+ existing_names.insert(id_attribute);
+ if (add_result.is_new_entry)
+ names.push_back(id_attribute);
+ }
+ const AtomicString& name_attribute = element->GetNameAttribute();
+ if (!name_attribute.IsEmpty()) {
+ HashSet<AtomicString>::AddResult add_result =
+ existing_names.insert(name_attribute);
+ if (add_result.is_new_entry)
+ names.push_back(name_attribute);
+ }
+ }
+}
+
+void HTMLFormControlsCollection::Trace(blink::Visitor* visitor) {
+ visitor->Trace(cached_element_);
+ HTMLCollection::Trace(visitor);
+}
+
+} // 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
new file mode 100644
index 00000000000..7f9c999cca6
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights
+ * reserved.
+ * Copyright (C) 2014 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROLS_COLLECTION_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROLS_COLLECTION_H_
+
+#include "third_party/blink/renderer/core/html/forms/listed_element.h"
+#include "third_party/blink/renderer/core/html/forms/radio_node_list.h"
+#include "third_party/blink/renderer/core/html/html_collection.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+
+namespace blink {
+
+class HTMLImageElement;
+class RadioNodeListOrElement;
+
+// This class is just a big hack to find form elements even in malformed HTML
+// elements. The famous <table><tr><form><td> problem.
+
+class HTMLFormControlsCollection final : public HTMLCollection {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLFormControlsCollection* Create(ContainerNode&, CollectionType);
+
+ ~HTMLFormControlsCollection() override;
+
+ HTMLElement* item(unsigned offset) const {
+ return ToHTMLElement(HTMLCollection::item(offset));
+ }
+
+ HTMLElement* namedItem(const AtomicString& name) const override;
+ void namedGetter(const AtomicString& name, RadioNodeListOrElement&);
+
+ void Trace(blink::Visitor*) override;
+
+ private:
+ explicit HTMLFormControlsCollection(ContainerNode&);
+
+ void UpdateIdNameCache() const override;
+ void SupportedPropertyNames(Vector<String>& names) override;
+
+ const ListedElement::List& ListedElements() const;
+ const HeapVector<Member<HTMLImageElement>>& FormImageElements() const;
+ HTMLElement* VirtualItemAfter(Element*) const override;
+ void InvalidateCache(Document* old_document = nullptr) const override;
+
+ 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
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROLS_COLLECTION_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.idl b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.idl
new file mode 100644
index 00000000000..674ee55225b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.idl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2006, 2007, 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
+ * Copyright (C) 2014 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ */
+
+// https://html.spec.whatwg.org/#the-htmlformcontrolscollection-interface
+
+interface HTMLFormControlsCollection : HTMLCollection {
+ // inherits length and item()
+ [ImplementedAs=namedGetter] getter (RadioNodeList or Element)? namedItem(DOMString name); // shadows inherited namedItem()
+ // FIXME: This getter is not in the spec.
+ [ImplementedAs=item] getter Node (unsigned long index);
+};
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
new file mode 100644
index 00000000000..699de8293eb
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc
@@ -0,0 +1,899 @@
+/*
+ * 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, 2008, 2009 Apple Inc. All rights
+ * reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/html_form_element.h"
+
+#include <limits>
+#include "third_party/blink/public/platform/web_insecure_request_policy.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/core/dom/attribute.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/dom/events/event.h"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
+#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h"
+#include "third_party/blink/renderer/core/frame/csp/content_security_policy.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_client.h"
+#include "third_party/blink/renderer/core/frame/remote_frame.h"
+#include "third_party/blink/renderer/core/frame/use_counter.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/form_data_event.h"
+#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/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"
+#include "third_party/blink/renderer/core/html/html_object_element.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_object.h"
+#include "third_party/blink/renderer/core/loader/form_submission.h"
+#include "third_party/blink/renderer/core/loader/mixed_content_checker.h"
+#include "third_party/blink/renderer/core/loader/navigation_scheduler.h"
+#include "third_party/blink/renderer/platform/wtf/auto_reset.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+HTMLFormElement::HTMLFormElement(Document& document)
+ : HTMLElement(formTag, document),
+ listed_elements_are_dirty_(false),
+ image_elements_are_dirty_(false),
+ has_elements_associated_by_parser_(false),
+ has_elements_associated_by_form_attribute_(false),
+ did_finish_parsing_children_(false),
+ is_in_reset_function_(false),
+ was_demoted_(false) {}
+
+HTMLFormElement* HTMLFormElement::Create(Document& document) {
+ UseCounter::Count(document, WebFeature::kFormElement);
+ return new HTMLFormElement(document);
+}
+
+HTMLFormElement::~HTMLFormElement() = default;
+
+void HTMLFormElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(past_names_map_);
+ visitor->Trace(radio_button_group_scope_);
+ visitor->Trace(listed_elements_);
+ visitor->Trace(image_elements_);
+ visitor->Trace(planned_navigation_);
+ HTMLElement::Trace(visitor);
+}
+
+bool HTMLFormElement::MatchesValidityPseudoClasses() const {
+ return true;
+}
+
+bool HTMLFormElement::IsValidElement() {
+ return !CheckInvalidControlsAndCollectUnhandled(
+ nullptr, kCheckValidityDispatchNoEvent);
+}
+
+bool HTMLFormElement::LayoutObjectIsNeeded(const ComputedStyle& style) const {
+ if (!was_demoted_)
+ return HTMLElement::LayoutObjectIsNeeded(style);
+
+ ContainerNode* node = parentNode();
+ if (!node || !node->GetLayoutObject())
+ return HTMLElement::LayoutObjectIsNeeded(style);
+ LayoutObject* parent_layout_object = node->GetLayoutObject();
+ // FIXME: Shouldn't we also check for table caption (see |formIsTablePart|
+ // below).
+ // FIXME: This check is not correct for Shadow DOM.
+ bool parent_is_table_element_part =
+ (parent_layout_object->IsTable() && IsHTMLTableElement(*node)) ||
+ (parent_layout_object->IsTableRow() && IsHTMLTableRowElement(*node)) ||
+ (parent_layout_object->IsTableSection() && node->HasTagName(tbodyTag)) ||
+ (parent_layout_object->IsLayoutTableCol() && node->HasTagName(colTag)) ||
+ (parent_layout_object->IsTableCell() && IsHTMLTableRowElement(*node));
+
+ if (!parent_is_table_element_part)
+ return true;
+
+ EDisplay display = style.Display();
+ bool form_is_table_part =
+ display == EDisplay::kTable || display == EDisplay::kInlineTable ||
+ display == EDisplay::kTableRowGroup ||
+ display == EDisplay::kTableHeaderGroup ||
+ display == EDisplay::kTableFooterGroup ||
+ display == EDisplay::kTableRow ||
+ display == EDisplay::kTableColumnGroup ||
+ display == EDisplay::kTableColumn || display == EDisplay::kTableCell ||
+ display == EDisplay::kTableCaption;
+
+ return form_is_table_part;
+}
+
+Node::InsertionNotificationRequest HTMLFormElement::InsertedInto(
+ ContainerNode* insertion_point) {
+ HTMLElement::InsertedInto(insertion_point);
+ LogAddElementIfIsolatedWorldAndInDocument("form", methodAttr, actionAttr);
+ if (insertion_point->isConnected())
+ GetDocument().DidAssociateFormControl(this);
+ return kInsertionDone;
+}
+
+template <class T>
+void NotifyFormRemovedFromTree(const T& elements, Node& root) {
+ for (const auto& element : elements)
+ element->FormRemovedFromTree(root);
+}
+
+void HTMLFormElement::RemovedFrom(ContainerNode* insertion_point) {
+ // We don't need to take care of form association by 'form' content
+ // attribute becuse IdTargetObserver handles it.
+ if (has_elements_associated_by_parser_) {
+ Node& root = NodeTraversal::HighestAncestorOrSelf(*this);
+ if (!listed_elements_are_dirty_) {
+ ListedElement::List elements(ListedElements());
+ NotifyFormRemovedFromTree(elements, root);
+ } else {
+ ListedElement::List elements;
+ CollectListedElements(
+ NodeTraversal::HighestAncestorOrSelf(*insertion_point), elements);
+ NotifyFormRemovedFromTree(elements, root);
+ CollectListedElements(root, elements);
+ NotifyFormRemovedFromTree(elements, root);
+ }
+
+ if (!image_elements_are_dirty_) {
+ HeapVector<Member<HTMLImageElement>> images(ImageElements());
+ NotifyFormRemovedFromTree(images, root);
+ } else {
+ HeapVector<Member<HTMLImageElement>> images;
+ CollectImageElements(
+ NodeTraversal::HighestAncestorOrSelf(*insertion_point), images);
+ NotifyFormRemovedFromTree(images, root);
+ CollectImageElements(root, images);
+ NotifyFormRemovedFromTree(images, root);
+ }
+ }
+ GetDocument().GetFormController().WillDeleteForm(this);
+ HTMLElement::RemovedFrom(insertion_point);
+}
+
+void HTMLFormElement::HandleLocalEvents(Event& event) {
+ Node* target_node = event.target()->ToNode();
+ if (event.eventPhase() != Event::kCapturingPhase && target_node &&
+ target_node != this &&
+ (event.type() == EventTypeNames::submit ||
+ event.type() == EventTypeNames::reset)) {
+ event.stopPropagation();
+ return;
+ }
+ HTMLElement::HandleLocalEvents(event);
+}
+
+unsigned HTMLFormElement::length() const {
+ unsigned len = 0;
+ for (const auto& element : ListedElements()) {
+ if (element->IsEnumeratable())
+ ++len;
+ }
+ return len;
+}
+
+HTMLElement* HTMLFormElement::item(unsigned index) {
+ return elements()->item(index);
+}
+
+void HTMLFormElement::SubmitImplicitly(Event* event,
+ bool from_implicit_submission_trigger) {
+ int submission_trigger_count = 0;
+ bool seen_default_button = false;
+ for (const auto& element : ListedElements()) {
+ if (!element->IsFormControlElement())
+ continue;
+ HTMLFormControlElement* control = ToHTMLFormControlElement(element);
+ if (!seen_default_button && control->CanBeSuccessfulSubmitButton()) {
+ if (from_implicit_submission_trigger)
+ seen_default_button = true;
+ if (control->IsSuccessfulSubmitButton()) {
+ control->DispatchSimulatedClick(event);
+ return;
+ }
+ if (from_implicit_submission_trigger) {
+ // Default (submit) button is not activated; no implicit submission.
+ return;
+ }
+ } else if (control->CanTriggerImplicitSubmission()) {
+ ++submission_trigger_count;
+ }
+ }
+ if (from_implicit_submission_trigger && submission_trigger_count == 1)
+ PrepareForSubmission(event, nullptr);
+}
+
+bool HTMLFormElement::ValidateInteractively() {
+ UseCounter::Count(GetDocument(), WebFeature::kFormValidationStarted);
+ for (const auto& element : ListedElements()) {
+ if (element->IsFormControlElement())
+ ToHTMLFormControlElement(element)->HideVisibleValidationMessage();
+ }
+
+ HeapVector<Member<HTMLFormControlElement>> unhandled_invalid_controls;
+ if (!CheckInvalidControlsAndCollectUnhandled(
+ &unhandled_invalid_controls, kCheckValidityDispatchInvalidEvent))
+ return true;
+ UseCounter::Count(GetDocument(),
+ WebFeature::kFormValidationAbortedSubmission);
+ // Because the form has invalid controls, we abort the form submission and
+ // show a validation message on a focusable form control.
+
+ // Needs to update layout now because we'd like to call isFocusable(), which
+ // has !layoutObject()->needsLayout() assertion.
+ GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+
+ // Focus on the first focusable control and show a validation message.
+ for (const auto& unhandled : unhandled_invalid_controls) {
+ if (unhandled->IsFocusable()) {
+ unhandled->ShowValidationMessage();
+ UseCounter::Count(GetDocument(),
+ WebFeature::kFormValidationShowedMessage);
+ break;
+ }
+ }
+ // Warn about all of unfocusable controls.
+ if (GetDocument().GetFrame()) {
+ for (const auto& unhandled : unhandled_invalid_controls) {
+ if (unhandled->IsFocusable())
+ continue;
+ String message(
+ "An invalid form control with name='%name' is not focusable.");
+ message.Replace("%name", unhandled->GetName());
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kRenderingMessageSource, kErrorMessageLevel, message));
+ }
+ }
+ return false;
+}
+
+void HTMLFormElement::PrepareForSubmission(
+ 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(
+ kJSMessageSource, kWarningMessageLevel,
+ "Form submission canceled because the form is not connected"));
+ return;
+ }
+
+ if (GetDocument().IsSandboxed(kSandboxForms)) {
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kSecurityMessageSource, kErrorMessageLevel,
+ "Blocked form submission to '" + attributes_.Action() +
+ "' because the form's frame is sandboxed and the 'allow-forms' "
+ "permission is not set."));
+ return;
+ }
+
+ // https://github.com/whatwg/html/issues/2253
+ for (const auto& element : ListedElements()) {
+ if (element->IsFormControlElement() &&
+ ToHTMLFormControlElement(element)->BlocksFormSubmission()) {
+ UseCounter::Count(GetDocument(),
+ WebFeature::kFormSubmittedWithUnclosedFormControl);
+ if (RuntimeEnabledFeatures::UnclosedFormControlIsInvalidEnabled()) {
+ String tag_name = ToHTMLFormControlElement(element)->tagName();
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kSecurityMessageSource, kErrorMessageLevel,
+ "Form submission failed, as the <" + tag_name +
+ "> element named "
+ "'" +
+ element->GetName() +
+ "' was implicitly closed by reaching "
+ "the end of the file. Please add an explicit end tag "
+ "('</" +
+ tag_name + ">')"));
+ DispatchEvent(Event::Create(EventTypeNames::error));
+ return;
+ }
+ }
+ }
+
+ bool skip_validation = !GetDocument().GetPage() || NoValidate();
+ DCHECK(event);
+ if (submit_button && submit_button->FormNoValidate())
+ skip_validation = true;
+
+ UseCounter::Count(GetDocument(), WebFeature::kFormSubmissionStarted);
+ // Interactive validation must be done before dispatching the submit event.
+ if (!skip_validation && !ValidateInteractively())
+ return;
+
+ bool should_submit;
+ {
+ AutoReset<bool> submit_event_handler_scope(&in_user_js_submit_event_, true);
+ frame->Client()->DispatchWillSendSubmitEvent(this);
+ should_submit =
+ DispatchEvent(Event::CreateCancelableBubble(EventTypeNames::submit)) ==
+ DispatchEventResult::kNotCanceled;
+ }
+ if (should_submit) {
+ planned_navigation_ = nullptr;
+ Submit(event, submit_button);
+ }
+ if (!planned_navigation_)
+ return;
+ AutoReset<bool> submit_scope(&is_submitting_, true);
+ ScheduleFormSubmission(planned_navigation_);
+ planned_navigation_ = nullptr;
+}
+
+void HTMLFormElement::submitFromJavaScript() {
+ Submit(nullptr, nullptr);
+}
+
+void HTMLFormElement::SubmitDialog(FormSubmission* form_submission) {
+ for (Node* node = this; node; node = node->ParentOrShadowHostNode()) {
+ if (auto* dialog = ToHTMLDialogElementOrNull(*node)) {
+ dialog->close(form_submission->Result());
+ return;
+ }
+ }
+}
+
+void HTMLFormElement::Submit(Event* event,
+ HTMLFormControlElement* submit_button) {
+ LocalFrameView* view = GetDocument().View();
+ LocalFrame* frame = GetDocument().GetFrame();
+ if (!view || !frame || !frame->GetPage())
+ return;
+
+ // https://html.spec.whatwg.org/multipage/forms.html#form-submission-algorithm
+ // 2. If form document is not connected, has no associated browsing context,
+ // 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(
+ kJSMessageSource, kWarningMessageLevel,
+ "Form submission canceled because the form is not connected"));
+ return;
+ }
+
+ if (is_submitting_)
+ return;
+
+ // Delay dispatching 'close' to dialog until done submitting.
+ EventQueueScope scope_for_dialog_close;
+ AutoReset<bool> submit_scope(&is_submitting_, true);
+
+ if (event && !submit_button) {
+ // In a case of implicit submission without a submit button, 'submit'
+ // event handler might add a submit button. We search for a submit
+ // button again.
+ // TODO(tkent): Do we really need to activate such submit button?
+ for (const auto& listed_element : ListedElements()) {
+ if (!listed_element->IsFormControlElement())
+ continue;
+ HTMLFormControlElement* control =
+ ToHTMLFormControlElement(listed_element);
+ DCHECK(!control->IsActivatedSubmit());
+ if (control->IsSuccessfulSubmitButton()) {
+ submit_button = control;
+ break;
+ }
+ }
+ }
+
+ FormSubmission* form_submission =
+ FormSubmission::Create(this, attributes_, event, submit_button);
+ // 'formdata' event handlers might disconnect the form.
+ if (RuntimeEnabledFeatures::FormDataEventEnabled() && !isConnected()) {
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kJSMessageSource, kWarningMessageLevel,
+ "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_) {
+ // 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.
+ ScheduleFormSubmission(form_submission);
+ }
+}
+
+void HTMLFormElement::ConstructFormDataSet(
+ HTMLFormControlElement* submit_button,
+ FormData& form_data) {
+ // TODO(tkent): We might move the event dispatching later than the
+ // ListedElements iteration.
+ if (RuntimeEnabledFeatures::FormDataEventEnabled())
+ DispatchEvent(FormDataEvent::Create(form_data));
+
+ if (submit_button)
+ submit_button->SetActivatedSubmit(true);
+ for (ListedElement* control : ListedElements()) {
+ DCHECK(control);
+ HTMLElement& element = ToHTMLElement(*control);
+ if (!element.IsDisabledFormControl())
+ control->AppendToFormData(form_data);
+ if (auto* input = ToHTMLInputElementOrNull(element)) {
+ if (input->type() == InputTypeNames::password &&
+ !input->value().IsEmpty())
+ form_data.SetContainsPasswordData(true);
+ }
+ }
+ if (submit_button)
+ submit_button->SetActivatedSubmit(false);
+}
+
+void HTMLFormElement::ScheduleFormSubmission(FormSubmission* submission) {
+ DCHECK(submission->Method() == FormSubmission::kPostMethod ||
+ submission->Method() == FormSubmission::kGetMethod);
+ DCHECK(submission->Data());
+ DCHECK(submission->Form());
+ if (submission->Action().IsEmpty())
+ return;
+ if (GetDocument().IsSandboxed(kSandboxForms)) {
+ // 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(
+ kSecurityMessageSource, kErrorMessageLevel,
+ "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;
+ }
+
+ if (submission->Action().ProtocolIsJavaScript()) {
+ GetDocument()
+ .GetFrame()
+ ->GetScriptController()
+ .ExecuteScriptIfJavaScriptURL(submission->Action(), this);
+ return;
+ }
+
+ Frame* target_frame = GetDocument().GetFrame()->FindFrameForNavigation(
+ submission->Target(), *GetDocument().GetFrame(),
+ submission->RequestURL());
+ if (!target_frame) {
+ target_frame = GetDocument().GetFrame();
+ } else {
+ submission->ClearTarget();
+ }
+ if (!target_frame->GetPage())
+ return;
+
+ UseCounter::Count(GetDocument(), WebFeature::kFormsSubmitted);
+ if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(),
+ submission->Action())) {
+ UseCounter::Count(GetDocument().GetFrame(),
+ WebFeature::kMixedContentFormsSubmitted);
+ }
+
+ // TODO(lukasza): Investigate if the code below can uniformly handle remote
+ // and local frames (i.e. by calling virtual Frame::navigate from a timer).
+ // See also https://goo.gl/95d2KA.
+ if (target_frame->IsLocalFrame()) {
+ ToLocalFrame(target_frame)
+ ->GetNavigationScheduler()
+ .ScheduleFormSubmission(&GetDocument(), submission);
+ } else {
+ FrameLoadRequest frame_load_request =
+ submission->CreateFrameLoadRequest(&GetDocument());
+ frame_load_request.GetResourceRequest().SetHasUserGesture(
+ Frame::HasTransientUserActivation(GetDocument().GetFrame()));
+ ToRemoteFrame(target_frame)->Navigate(frame_load_request);
+ }
+}
+
+void HTMLFormElement::reset() {
+ LocalFrame* frame = GetDocument().GetFrame();
+ if (is_in_reset_function_ || !frame)
+ return;
+
+ is_in_reset_function_ = true;
+
+ if (DispatchEvent(Event::CreateCancelableBubble(EventTypeNames::reset)) !=
+ DispatchEventResult::kNotCanceled) {
+ is_in_reset_function_ = false;
+ return;
+ }
+
+ // Copy the element list because |reset()| implementation can update DOM
+ // structure.
+ ListedElement::List elements(ListedElements());
+ for (const auto& element : elements) {
+ if (element->IsFormControlElement())
+ ToHTMLFormControlElement(element)->Reset();
+ }
+
+ is_in_reset_function_ = false;
+}
+
+void HTMLFormElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ const QualifiedName& name = params.name;
+ if (name == actionAttr) {
+ attributes_.ParseAction(params.new_value);
+ LogUpdateAttributeIfIsolatedWorldAndInDocument("form", params);
+
+ // 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)
+ return;
+ KURL action_url = GetDocument().CompleteURL(
+ attributes_.Action().IsEmpty() ? GetDocument().Url().GetString()
+ : attributes_.Action());
+ if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(),
+ action_url)) {
+ UseCounter::Count(GetDocument().GetFrame(),
+ WebFeature::kMixedContentFormPresent);
+ }
+ } else if (name == targetAttr) {
+ attributes_.SetTarget(params.new_value);
+ } else if (name == methodAttr) {
+ attributes_.UpdateMethodType(params.new_value);
+ } else if (name == enctypeAttr) {
+ attributes_.UpdateEncodingType(params.new_value);
+ } else if (name == accept_charsetAttr) {
+ attributes_.SetAcceptCharset(params.new_value);
+ } else {
+ HTMLElement::ParseAttribute(params);
+ }
+}
+
+void HTMLFormElement::Associate(ListedElement& e) {
+ listed_elements_are_dirty_ = true;
+ listed_elements_.clear();
+ if (ToHTMLElement(e).FastHasAttribute(formAttr))
+ has_elements_associated_by_form_attribute_ = true;
+}
+
+void HTMLFormElement::Disassociate(ListedElement& e) {
+ listed_elements_are_dirty_ = true;
+ listed_elements_.clear();
+ RemoveFromPastNamesMap(ToHTMLElement(e));
+}
+
+bool HTMLFormElement::IsURLAttribute(const Attribute& attribute) const {
+ return attribute.GetName() == actionAttr ||
+ HTMLElement::IsURLAttribute(attribute);
+}
+
+bool HTMLFormElement::HasLegalLinkAttribute(const QualifiedName& name) const {
+ return name == actionAttr || HTMLElement::HasLegalLinkAttribute(name);
+}
+
+void HTMLFormElement::Associate(HTMLImageElement& e) {
+ image_elements_are_dirty_ = true;
+ image_elements_.clear();
+}
+
+void HTMLFormElement::Disassociate(HTMLImageElement& e) {
+ image_elements_are_dirty_ = true;
+ image_elements_.clear();
+ RemoveFromPastNamesMap(e);
+}
+
+void HTMLFormElement::DidAssociateByParser() {
+ if (!did_finish_parsing_children_)
+ return;
+ has_elements_associated_by_parser_ = true;
+ UseCounter::Count(GetDocument(), WebFeature::kFormAssociationByParser);
+}
+
+HTMLFormControlsCollection* HTMLFormElement::elements() {
+ return EnsureCachedCollection<HTMLFormControlsCollection>(kFormControls);
+}
+
+void HTMLFormElement::CollectListedElements(
+ Node& root,
+ ListedElement::List& elements) const {
+ elements.clear();
+ for (HTMLElement& element : Traversal<HTMLElement>::StartsAfter(root)) {
+ ListedElement* listed_element = nullptr;
+ if (element.IsFormControlElement())
+ listed_element = ToHTMLFormControlElement(&element);
+ else if (auto* object = ToHTMLObjectElementOrNull(element))
+ listed_element = object;
+ else
+ continue;
+ if (listed_element->Form() == this)
+ elements.push_back(listed_element);
+ }
+}
+
+// This function should be const conceptually. However we update some fields
+// because of lazy evaluation.
+const ListedElement::List& HTMLFormElement::ListedElements() const {
+ if (!listed_elements_are_dirty_)
+ return listed_elements_;
+ HTMLFormElement* mutable_this = const_cast<HTMLFormElement*>(this);
+ Node* scope = mutable_this;
+ if (has_elements_associated_by_parser_)
+ scope = &NodeTraversal::HighestAncestorOrSelf(*mutable_this);
+ if (isConnected() && has_elements_associated_by_form_attribute_)
+ scope = &GetTreeScope().RootNode();
+ DCHECK(scope);
+ CollectListedElements(*scope, mutable_this->listed_elements_);
+ mutable_this->listed_elements_are_dirty_ = false;
+ return listed_elements_;
+}
+
+void HTMLFormElement::CollectImageElements(
+ Node& root,
+ HeapVector<Member<HTMLImageElement>>& elements) {
+ elements.clear();
+ for (HTMLImageElement& image :
+ Traversal<HTMLImageElement>::StartsAfter(root)) {
+ if (image.formOwner() == this)
+ elements.push_back(&image);
+ }
+}
+
+const HeapVector<Member<HTMLImageElement>>& HTMLFormElement::ImageElements() {
+ if (!image_elements_are_dirty_)
+ return image_elements_;
+ CollectImageElements(has_elements_associated_by_parser_
+ ? NodeTraversal::HighestAncestorOrSelf(*this)
+ : *this,
+ image_elements_);
+ image_elements_are_dirty_ = false;
+ return image_elements_;
+}
+
+String HTMLFormElement::GetName() const {
+ return GetNameAttribute();
+}
+
+bool HTMLFormElement::NoValidate() const {
+ return FastHasAttribute(novalidateAttr);
+}
+
+String HTMLFormElement::action() const {
+ Document& document = GetDocument();
+ KURL action_url = document.CompleteURL(attributes_.Action().IsEmpty()
+ ? document.Url().GetString()
+ : attributes_.Action());
+ return action_url.GetString();
+}
+
+void HTMLFormElement::setAction(const AtomicString& value) {
+ setAttribute(actionAttr, value);
+}
+
+void HTMLFormElement::setEnctype(const AtomicString& value) {
+ setAttribute(enctypeAttr, value);
+}
+
+String HTMLFormElement::method() const {
+ return FormSubmission::Attributes::MethodString(attributes_.Method());
+}
+
+void HTMLFormElement::setMethod(const AtomicString& value) {
+ setAttribute(methodAttr, value);
+}
+
+HTMLFormControlElement* HTMLFormElement::FindDefaultButton() const {
+ for (const auto& element : ListedElements()) {
+ if (!element->IsFormControlElement())
+ continue;
+ HTMLFormControlElement* control = ToHTMLFormControlElement(element);
+ if (control->CanBeSuccessfulSubmitButton())
+ return control;
+ }
+ return nullptr;
+}
+
+bool HTMLFormElement::checkValidity() {
+ return !CheckInvalidControlsAndCollectUnhandled(
+ nullptr, kCheckValidityDispatchInvalidEvent);
+}
+
+bool HTMLFormElement::CheckInvalidControlsAndCollectUnhandled(
+ HeapVector<Member<HTMLFormControlElement>>* unhandled_invalid_controls,
+ CheckValidityEventBehavior event_behavior) {
+ // Copy listedElements because event handlers called from
+ // HTMLFormControlElement::checkValidity() might change listedElements.
+ const ListedElement::List& listed_elements = ListedElements();
+ HeapVector<Member<ListedElement>> elements;
+ elements.ReserveCapacity(listed_elements.size());
+ for (const auto& element : listed_elements)
+ elements.push_back(element);
+ int invalid_controls_count = 0;
+ for (const auto& element : elements) {
+ if (element->Form() == this && element->IsFormControlElement()) {
+ HTMLFormControlElement* control = ToHTMLFormControlElement(element);
+ if (control->IsSubmittableElement() &&
+ !control->checkValidity(unhandled_invalid_controls, event_behavior) &&
+ control->formOwner() == this) {
+ ++invalid_controls_count;
+ if (!unhandled_invalid_controls &&
+ event_behavior == kCheckValidityDispatchNoEvent)
+ return true;
+ }
+ }
+ }
+ return invalid_controls_count;
+}
+
+bool HTMLFormElement::reportValidity() {
+ return ValidateInteractively();
+}
+
+Element* HTMLFormElement::ElementFromPastNamesMap(
+ const AtomicString& past_name) {
+ if (past_name.IsEmpty() || !past_names_map_)
+ return nullptr;
+ Element* element = past_names_map_->at(past_name);
+#if DCHECK_IS_ON()
+ if (!element)
+ return nullptr;
+ SECURITY_DCHECK(ToHTMLElement(element)->formOwner() == this);
+ if (IsHTMLImageElement(*element)) {
+ SECURITY_DCHECK(ImageElements().Find(element) != kNotFound);
+ } else if (IsHTMLObjectElement(*element)) {
+ SECURITY_DCHECK(ListedElements().Find(ToHTMLObjectElement(element)) !=
+ kNotFound);
+ } else {
+ SECURITY_DCHECK(ListedElements().Find(ToHTMLFormControlElement(element)) !=
+ kNotFound);
+ }
+#endif
+ return element;
+}
+
+void HTMLFormElement::AddToPastNamesMap(Element* element,
+ const AtomicString& past_name) {
+ if (past_name.IsEmpty())
+ return;
+ if (!past_names_map_)
+ past_names_map_ = new PastNamesMap;
+ past_names_map_->Set(past_name, element);
+}
+
+void HTMLFormElement::RemoveFromPastNamesMap(HTMLElement& element) {
+ if (!past_names_map_)
+ return;
+ for (auto& it : *past_names_map_) {
+ if (it.value == &element) {
+ it.value = nullptr;
+ // Keep looping. Single element can have multiple names.
+ }
+ }
+}
+
+void HTMLFormElement::GetNamedElements(
+ const AtomicString& name,
+ HeapVector<Member<Element>>& named_items) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem
+ elements()->NamedItems(name, named_items);
+
+ Element* element_from_past = ElementFromPastNamesMap(name);
+ if (named_items.size() && named_items.front() != element_from_past) {
+ AddToPastNamesMap(named_items.front().Get(), name);
+ } else if (element_from_past && named_items.IsEmpty()) {
+ named_items.push_back(element_from_past);
+ UseCounter::Count(GetDocument(),
+ WebFeature::kFormNameAccessForPastNamesMap);
+ }
+}
+
+bool HTMLFormElement::ShouldAutocomplete() const {
+ return !DeprecatedEqualIgnoringCase(FastGetAttribute(autocompleteAttr),
+ "off");
+}
+
+void HTMLFormElement::FinishParsingChildren() {
+ HTMLElement::FinishParsingChildren();
+ GetDocument().GetFormController().RestoreControlStateIn(*this);
+ did_finish_parsing_children_ = true;
+}
+
+void HTMLFormElement::CloneNonAttributePropertiesFrom(const Element& source,
+ CloneChildrenFlag flag) {
+ was_demoted_ = ToHTMLFormElement(source).was_demoted_;
+ HTMLElement::CloneNonAttributePropertiesFrom(source, flag);
+}
+
+void HTMLFormElement::AnonymousNamedGetter(
+ const AtomicString& name,
+ RadioNodeListOrElement& return_value) {
+ // Call getNamedElements twice, first time check if it has a value
+ // and let HTMLFormElement update its cache.
+ // See issue: 867404
+ {
+ HeapVector<Member<Element>> elements;
+ GetNamedElements(name, elements);
+ if (elements.IsEmpty())
+ return;
+ }
+
+ // Second call may return different results from the first call,
+ // but if the first the size cannot be zero.
+ HeapVector<Member<Element>> elements;
+ GetNamedElements(name, elements);
+ DCHECK(!elements.IsEmpty());
+
+ bool only_match_img =
+ !elements.IsEmpty() && IsHTMLImageElement(*elements.front());
+ if (only_match_img) {
+ UseCounter::Count(GetDocument(),
+ WebFeature::kFormNameAccessForImageElement);
+ // The following code has performance impact, but it should be small
+ // because <img> access via <form> name getter is rarely used.
+ for (auto& element : elements) {
+ if (IsHTMLImageElement(*element) && !element->IsDescendantOf(this)) {
+ UseCounter::Count(
+ GetDocument(),
+ WebFeature::kFormNameAccessForNonDescendantImageElement);
+ break;
+ }
+ }
+ }
+ if (elements.size() == 1) {
+ return_value.SetElement(elements.at(0));
+ return;
+ }
+
+ return_value.SetRadioNodeList(GetRadioNodeList(name, only_match_img));
+}
+
+void HTMLFormElement::SetDemoted(bool demoted) {
+ if (demoted)
+ UseCounter::Count(GetDocument(), WebFeature::kDemotedFormElement);
+ was_demoted_ = demoted;
+}
+
+void HTMLFormElement::InvalidateDefaultButtonStyle() const {
+ for (const auto& control : ListedElements()) {
+ if (!control->IsFormControlElement())
+ continue;
+ if (ToHTMLFormControlElement(control)->CanBeSuccessfulSubmitButton()) {
+ ToHTMLFormControlElement(control)->PseudoStateChanged(
+ CSSSelector::kPseudoDefault);
+ }
+ }
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..6ba89f9f2fc
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
+ * reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_
+
+#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/core/html/forms/radio_button_group_scope.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/core/loader/form_submission.h"
+
+namespace blink {
+
+class Event;
+class ListedElement;
+class HTMLFormControlElement;
+class HTMLFormControlsCollection;
+class HTMLImageElement;
+class RadioNodeListOrElement;
+
+class CORE_EXPORT HTMLFormElement final : public HTMLElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLFormElement* Create(Document&);
+ ~HTMLFormElement() override;
+ void Trace(blink::Visitor*) override;
+
+ HTMLFormControlsCollection* elements();
+ void GetNamedElements(const AtomicString&, HeapVector<Member<Element>>&);
+
+ unsigned length() const;
+ HTMLElement* item(unsigned index);
+
+ String action() const;
+ void setAction(const AtomicString&);
+
+ String enctype() const { return attributes_.EncodingType(); }
+ void setEnctype(const AtomicString&);
+
+ String encoding() const { return attributes_.EncodingType(); }
+ void setEncoding(const AtomicString& value) { setEnctype(value); }
+
+ bool ShouldAutocomplete() const;
+
+ void Associate(ListedElement&);
+ void Disassociate(ListedElement&);
+ void Associate(HTMLImageElement&);
+ void Disassociate(HTMLImageElement&);
+ void DidAssociateByParser();
+
+ void PrepareForSubmission(Event*, HTMLFormControlElement* submit_button);
+ void submitFromJavaScript();
+ void reset();
+
+ void SetDemoted(bool);
+
+ void SubmitImplicitly(Event*, bool from_implicit_submission_trigger);
+
+ String GetName() const;
+
+ bool NoValidate() const;
+
+ const AtomicString& Action() const;
+
+ String method() const;
+ void setMethod(const AtomicString&);
+
+ // Find the 'default button.'
+ // https://html.spec.whatwg.org/multipage/forms.html#default-button
+ HTMLFormControlElement* FindDefaultButton() const;
+
+ bool checkValidity();
+ bool reportValidity();
+ bool MatchesValidityPseudoClasses() const final;
+ bool IsValidElement() final;
+
+ RadioButtonGroupScope& GetRadioButtonGroupScope() {
+ return radio_button_group_scope_;
+ }
+
+ const ListedElement::List& ListedElements() const;
+ const HeapVector<Member<HTMLImageElement>>& ImageElements();
+
+ void AnonymousNamedGetter(const AtomicString& name, RadioNodeListOrElement&);
+ void InvalidateDefaultButtonStyle() const;
+
+ // 'construct the form data set'
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set
+ void ConstructFormDataSet(HTMLFormControlElement* submit_button,
+ FormData& form_data);
+
+ private:
+ explicit HTMLFormElement(Document&);
+
+ bool LayoutObjectIsNeeded(const ComputedStyle&) const override;
+ InsertionNotificationRequest InsertedInto(ContainerNode*) override;
+ void RemovedFrom(ContainerNode*) override;
+ void FinishParsingChildren() override;
+
+ void HandleLocalEvents(Event&) override;
+
+ void ParseAttribute(const AttributeModificationParams&) override;
+ bool IsURLAttribute(const Attribute&) const override;
+ bool HasLegalLinkAttribute(const QualifiedName&) const override;
+
+ NamedItemType GetNamedItemType() const override {
+ return NamedItemType::kName;
+ }
+
+ void CloneNonAttributePropertiesFrom(const Element&,
+ CloneChildrenFlag) override;
+
+ void SubmitDialog(FormSubmission*);
+ void Submit(Event*, HTMLFormControlElement* submit_button);
+
+ void ScheduleFormSubmission(FormSubmission*);
+
+ void CollectListedElements(Node& root, ListedElement::List&) const;
+ void CollectImageElements(Node& root, HeapVector<Member<HTMLImageElement>>&);
+
+ // Returns true if the submission should proceed.
+ bool ValidateInteractively();
+
+ // Validates each of the controls, and stores controls of which 'invalid'
+ // event was not canceled to the specified vector. Returns true if there
+ // are any invalid controls in this form.
+ bool CheckInvalidControlsAndCollectUnhandled(
+ HeapVector<Member<HTMLFormControlElement>>*,
+ CheckValidityEventBehavior);
+
+ Element* ElementFromPastNamesMap(const AtomicString&);
+ void AddToPastNamesMap(Element*, const AtomicString& past_name);
+ void RemoveFromPastNamesMap(HTMLElement&);
+
+ typedef HeapHashMap<AtomicString, Member<Element>> PastNamesMap;
+
+ FormSubmission::Attributes attributes_;
+ Member<PastNamesMap> past_names_map_;
+
+ RadioButtonGroupScope radio_button_group_scope_;
+
+ // Do not access m_listedElements directly. Use listedElements()
+ // instead.
+ ListedElement::List listed_elements_;
+ // Do not access m_imageElements directly. Use imageElements() instead.
+ HeapVector<Member<HTMLImageElement>> image_elements_;
+
+ // https://html.spec.whatwg.org/multipage/forms.html#planned-navigation
+ // Unlike the specification, we use this only for web-exposed submit()
+ // function in 'submit' event handler.
+ Member<FormSubmission> planned_navigation_;
+
+ bool is_submitting_ = false;
+ bool in_user_js_submit_event_ = false;
+
+ bool listed_elements_are_dirty_ : 1;
+ bool image_elements_are_dirty_ : 1;
+ bool has_elements_associated_by_parser_ : 1;
+ bool has_elements_associated_by_form_attribute_ : 1;
+ bool did_finish_parsing_children_ : 1;
+ bool is_in_reset_function_ : 1;
+ bool was_demoted_ : 1;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.idl
new file mode 100644
index 00000000000..8a830e253b2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.idl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.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.
+ */
+
+// https://html.spec.whatwg.org/#the-form-element
+
+[
+ HTMLConstructor,
+ OverrideBuiltins
+] interface HTMLFormElement : HTMLElement {
+ [CEReactions, Reflect=accept_charset] attribute DOMString acceptCharset;
+ [CEReactions, URL] attribute DOMString action;
+ [CEReactions, Reflect, ReflectOnly=("on","off"), ReflectMissing="on", ReflectInvalid="on"] attribute DOMString autocomplete;
+ [CEReactions, CustomElementCallbacks] attribute DOMString enctype;
+ [CEReactions, CustomElementCallbacks] attribute DOMString encoding;
+ [CEReactions, CustomElementCallbacks] attribute DOMString method;
+ [CEReactions, Reflect] attribute DOMString name;
+ [CEReactions, Reflect] attribute boolean noValidate;
+ [CEReactions, Reflect] attribute DOMString target;
+
+ readonly attribute HTMLFormControlsCollection elements;
+ readonly attribute long length;
+ [ImplementedAs=item] getter Element (unsigned long index);
+ // FIXME: This getter should not have [NotEnumerable].
+ [NotEnumerable] getter (RadioNodeList or Element) (DOMString name);
+
+ [ImplementedAs=submitFromJavaScript] void submit();
+ [CEReactions, CustomElementCallbacks] void reset();
+ boolean checkValidity();
+ boolean reportValidity();
+};
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
new file mode 100644
index 00000000000..cb0f2b5c862
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -0,0 +1,1950 @@
+/*
+ * 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, 2008, 2009, 2010, 2011 Apple Inc. All
+ * rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
+ * Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2008 Torch Mobile Inc. All rights reserved.
+ * (http://www.torchmobile.com/)
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * 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/html_input_element.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/renderer/bindings/core/v8/exception_messages.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h"
+#include "third_party/blink/renderer/core/css/style_change_reason.h"
+#include "third_party/blink/renderer/core/css_property_names.h"
+#include "third_party/blink/renderer/core/dom/ax_object_cache.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#include "third_party/blink/renderer/core/dom/id_target_observer.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/dom/sync_reattach_context.h"
+#include "third_party/blink/renderer/core/dom/v0_insertion_point.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h"
+#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/frame/deprecation.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/use_counter.h"
+#include "third_party/blink/renderer/core/html/forms/color_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/file_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/form_controller.h"
+#include "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/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/search_input_type.h"
+#include "third_party/blink/renderer/core/html/html_collection.h"
+#include "third_party/blink/renderer/core/html/html_image_loader.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_type_names.h"
+#include "third_party/blink/renderer/core/layout/layout_object.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/platform/language.h"
+#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"
+
+namespace blink {
+
+using ValueMode = InputType::ValueMode;
+using namespace HTMLNames;
+
+class ListAttributeTargetObserver : public IdTargetObserver {
+ public:
+ static ListAttributeTargetObserver* Create(const AtomicString& id,
+ HTMLInputElement*);
+ void Trace(blink::Visitor*) override;
+ void IdTargetChanged() override;
+
+ private:
+ ListAttributeTargetObserver(const AtomicString& id, HTMLInputElement*);
+
+ Member<HTMLInputElement> element_;
+};
+
+const int kDefaultSize = 20;
+
+HTMLInputElement::HTMLInputElement(Document& document,
+ const CreateElementFlags flags)
+ : TextControlElement(inputTag, document),
+ size_(kDefaultSize),
+ has_dirty_value_(false),
+ is_checked_(false),
+ dirty_checkedness_(false),
+ is_indeterminate_(false),
+ is_activated_submit_(false),
+ autocomplete_(kUninitialized),
+ has_non_empty_list_(false),
+ state_restored_(false),
+ parsing_in_progress_(flags.IsCreatedByParser()),
+ can_receive_dropped_files_(false),
+ should_reveal_password_(false),
+ needs_to_update_view_value_(true),
+ is_placeholder_visible_(false),
+ has_been_password_field_(false),
+ // |input_type_| is lazily created when constructed by the parser to avoid
+ // constructing unnecessarily a text InputType and its shadow subtree,
+ // just to destroy them when the |type| attribute gets set by the parser
+ // to something else than 'text'.
+ input_type_(flags.IsCreatedByParser() ? nullptr
+ : InputType::CreateText(*this)),
+ input_type_view_(input_type_ ? input_type_->CreateView() : nullptr) {
+ SetHasCustomStyleCallbacks();
+}
+
+HTMLInputElement* HTMLInputElement::Create(Document& document,
+ const CreateElementFlags flags) {
+ auto* input_element = new HTMLInputElement(document, flags);
+ if (!flags.IsCreatedByParser()) {
+ DCHECK(input_element->input_type_view_->NeedsShadowSubtree());
+ input_element->CreateUserAgentShadowRoot();
+ input_element->CreateShadowSubtree();
+ }
+ return input_element;
+}
+
+void HTMLInputElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(input_type_);
+ visitor->Trace(input_type_view_);
+ visitor->Trace(list_attribute_target_observer_);
+ visitor->Trace(image_loader_);
+ TextControlElement::Trace(visitor);
+}
+
+bool HTMLInputElement::HasPendingActivity() const {
+ return ImageLoader() && ImageLoader()->HasPendingActivity();
+}
+
+HTMLImageLoader& HTMLInputElement::EnsureImageLoader() {
+ if (!image_loader_)
+ image_loader_ = HTMLImageLoader::Create(this);
+ return *image_loader_;
+}
+
+HTMLInputElement::~HTMLInputElement() = default;
+
+const AtomicString& HTMLInputElement::GetName() const {
+ return name_.IsNull() ? g_empty_atom : name_;
+}
+
+Vector<FileChooserFileInfo>
+HTMLInputElement::FilesFromFileInputFormControlState(
+ const FormControlState& state) {
+ return FileInputType::FilesFromFormControlState(state);
+}
+
+bool HTMLInputElement::ShouldAutocomplete() const {
+ if (autocomplete_ != kUninitialized)
+ return autocomplete_ == kOn;
+ return TextControlElement::ShouldAutocomplete();
+}
+
+bool HTMLInputElement::IsValidValue(const String& value) const {
+ if (!input_type_->CanSetStringValue()) {
+ NOTREACHED();
+ return false;
+ }
+ return !input_type_->TypeMismatchFor(value) &&
+ !input_type_->StepMismatch(value) &&
+ !input_type_->RangeUnderflow(value) &&
+ !input_type_->RangeOverflow(value) &&
+ !TooLong(value, kIgnoreDirtyFlag) &&
+ !TooShort(value, kIgnoreDirtyFlag) &&
+ !input_type_->PatternMismatch(value) &&
+ !input_type_->ValueMissing(value);
+}
+
+bool HTMLInputElement::TooLong() const {
+ return willValidate() && TooLong(value(), kCheckDirtyFlag);
+}
+
+bool HTMLInputElement::TooShort() const {
+ return willValidate() && TooShort(value(), kCheckDirtyFlag);
+}
+
+bool HTMLInputElement::TypeMismatch() const {
+ return willValidate() && input_type_->TypeMismatch();
+}
+
+bool HTMLInputElement::ValueMissing() const {
+ return willValidate() && input_type_->ValueMissing(value());
+}
+
+bool HTMLInputElement::HasBadInput() const {
+ return willValidate() && input_type_view_->HasBadInput();
+}
+
+bool HTMLInputElement::PatternMismatch() const {
+ return willValidate() && input_type_->PatternMismatch(value());
+}
+
+bool HTMLInputElement::TooLong(const String& value,
+ NeedsToCheckDirtyFlag check) const {
+ return input_type_->TooLong(value, check);
+}
+
+bool HTMLInputElement::TooShort(const String& value,
+ NeedsToCheckDirtyFlag check) const {
+ return input_type_->TooShort(value, check);
+}
+
+bool HTMLInputElement::RangeUnderflow() const {
+ return willValidate() && input_type_->RangeUnderflow(value());
+}
+
+bool HTMLInputElement::RangeOverflow() const {
+ return willValidate() && input_type_->RangeOverflow(value());
+}
+
+String HTMLInputElement::validationMessage() const {
+ if (!willValidate())
+ return String();
+
+ if (CustomError())
+ return CustomValidationMessage();
+
+ return input_type_->ValidationMessage(*input_type_view_).first;
+}
+
+String HTMLInputElement::ValidationSubMessage() const {
+ if (!willValidate() || CustomError())
+ return String();
+ return input_type_->ValidationMessage(*input_type_view_).second;
+}
+
+double HTMLInputElement::Minimum() const {
+ return input_type_->Minimum();
+}
+
+double HTMLInputElement::Maximum() const {
+ return input_type_->Maximum();
+}
+
+bool HTMLInputElement::StepMismatch() const {
+ return willValidate() && input_type_->StepMismatch(value());
+}
+
+bool HTMLInputElement::GetAllowedValueStep(Decimal* step) const {
+ return input_type_->GetAllowedValueStep(step);
+}
+
+StepRange HTMLInputElement::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ return input_type_->CreateStepRange(any_step_handling);
+}
+
+Decimal HTMLInputElement::FindClosestTickMarkValue(const Decimal& value) {
+ return input_type_->FindClosestTickMarkValue(value);
+}
+
+void HTMLInputElement::stepUp(int n, ExceptionState& exception_state) {
+ input_type_->StepUp(n, exception_state);
+}
+
+void HTMLInputElement::stepDown(int n, ExceptionState& exception_state) {
+ input_type_->StepUp(-1.0 * n, exception_state);
+}
+
+void HTMLInputElement::blur() {
+ input_type_view_->Blur();
+}
+
+void HTMLInputElement::DefaultBlur() {
+ TextControlElement::blur();
+}
+
+bool HTMLInputElement::HasCustomFocusLogic() const {
+ return input_type_view_->HasCustomFocusLogic();
+}
+
+bool HTMLInputElement::IsKeyboardFocusable() const {
+ return input_type_->IsKeyboardFocusable();
+}
+
+bool HTMLInputElement::ShouldShowFocusRingOnMouseFocus() const {
+ return input_type_->ShouldShowFocusRingOnMouseFocus();
+}
+
+void HTMLInputElement::UpdateFocusAppearanceWithOptions(
+ SelectionBehaviorOnFocus selection_behavior,
+ const FocusOptions& options) {
+ if (IsTextField()) {
+ switch (selection_behavior) {
+ case SelectionBehaviorOnFocus::kReset:
+ select();
+ break;
+ case SelectionBehaviorOnFocus::kRestore:
+ RestoreCachedSelection();
+ break;
+ case SelectionBehaviorOnFocus::kNone:
+ return;
+ }
+ // 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);
+ if (!options.preventScroll()) {
+ if (GetLayoutObject()) {
+ GetLayoutObject()->ScrollRectToVisible(BoundingBoxForScrollIntoView(),
+ WebScrollIntoViewParams());
+ }
+ if (GetDocument().GetFrame())
+ GetDocument().GetFrame()->Selection().RevealSelection();
+ }
+ } else {
+ TextControlElement::UpdateFocusAppearanceWithOptions(selection_behavior,
+ options);
+ }
+}
+
+void HTMLInputElement::EndEditing() {
+ DCHECK(GetDocument().IsActive());
+ if (!GetDocument().IsActive())
+ return;
+
+ if (!IsTextField())
+ return;
+
+ LocalFrame* frame = GetDocument().GetFrame();
+ frame->GetSpellChecker().DidEndEditingOnTextField(this);
+ frame->GetPage()->GetChromeClient().DidEndEditingOnTextField(*this);
+}
+
+void HTMLInputElement::DispatchFocusInEvent(
+ const AtomicString& event_type,
+ Element* old_focused_element,
+ WebFocusType type,
+ InputDeviceCapabilities* source_capabilities) {
+ if (event_type == EventTypeNames::DOMFocusIn)
+ input_type_view_->HandleFocusInEvent(old_focused_element, type);
+ HTMLFormControlElementWithState::DispatchFocusInEvent(
+ event_type, old_focused_element, type, source_capabilities);
+}
+
+void HTMLInputElement::HandleBlurEvent() {
+ input_type_view_->HandleBlurEvent();
+}
+
+void HTMLInputElement::setType(const AtomicString& type) {
+ setAttribute(typeAttr, type);
+}
+
+void HTMLInputElement::InitializeTypeInParsing() {
+ DCHECK(parsing_in_progress_);
+ DCHECK(!input_type_);
+ DCHECK(!input_type_view_);
+
+ const AtomicString& new_type_name =
+ InputType::NormalizeTypeName(FastGetAttribute(typeAttr));
+ input_type_ = InputType::Create(*this, new_type_name);
+ input_type_view_ = input_type_->CreateView();
+ String default_value = FastGetAttribute(valueAttr);
+ if (input_type_->GetValueMode() == ValueMode::kValue)
+ non_attribute_value_ = SanitizeValue(default_value);
+ has_been_password_field_ |= new_type_name == InputTypeNames::password;
+
+ if (input_type_view_->NeedsShadowSubtree()) {
+ CreateUserAgentShadowRoot();
+ CreateShadowSubtree();
+ }
+
+ SetNeedsWillValidateCheck();
+
+ if (!default_value.IsNull())
+ input_type_->WarnIfValueIsInvalid(default_value);
+
+ input_type_view_->UpdateView();
+}
+
+void HTMLInputElement::UpdateType() {
+ DCHECK(input_type_);
+ DCHECK(input_type_view_);
+
+ const AtomicString& new_type_name =
+ InputType::NormalizeTypeName(FastGetAttribute(typeAttr));
+ if (input_type_->FormControlType() == new_type_name)
+ return;
+
+ InputType* new_type = InputType::Create(*this, new_type_name);
+ RemoveFromRadioButtonGroup();
+
+ ValueMode old_value_mode = input_type_->GetValueMode();
+ bool did_respect_height_and_width =
+ input_type_->ShouldRespectHeightAndWidthAttributes();
+ bool could_be_successful_submit_button = CanBeSuccessfulSubmitButton();
+
+ input_type_view_->DestroyShadowSubtree();
+ DropInnerEditorElement();
+ LazyReattachIfAttached();
+
+ if (input_type_->SupportsRequired() != new_type->SupportsRequired() &&
+ IsRequired()) {
+ PseudoStateChanged(CSSSelector::kPseudoRequired);
+ PseudoStateChanged(CSSSelector::kPseudoOptional);
+ }
+ if (input_type_->SupportsReadOnly() != new_type->SupportsReadOnly()) {
+ PseudoStateChanged(CSSSelector::kPseudoReadOnly);
+ PseudoStateChanged(CSSSelector::kPseudoReadWrite);
+ }
+ if (input_type_->IsCheckable() != new_type->IsCheckable()) {
+ PseudoStateChanged(CSSSelector::kPseudoChecked);
+ }
+ PseudoStateChanged(CSSSelector::kPseudoIndeterminate);
+ if (input_type_->IsSteppable() || new_type->IsSteppable()) {
+ PseudoStateChanged(CSSSelector::kPseudoInRange);
+ PseudoStateChanged(CSSSelector::kPseudoOutOfRange);
+ }
+
+ bool placeholder_changed =
+ input_type_->SupportsPlaceholder() != new_type->SupportsPlaceholder();
+
+ has_been_password_field_ |= new_type_name == InputTypeNames::password;
+
+ input_type_ = new_type;
+ input_type_view_ = input_type_->CreateView();
+ if (input_type_view_->NeedsShadowSubtree()) {
+ EnsureUserAgentShadowRoot();
+ CreateShadowSubtree();
+ }
+
+ SetNeedsWillValidateCheck();
+
+ if (placeholder_changed) {
+ // We need to update the UA shadow and then the placeholder visibility flag
+ // here. Otherwise it would happen as part of attaching the layout tree
+ // which would be too late in order to make style invalidation work for
+ // the upcoming frame.
+ UpdatePlaceholderText();
+ UpdatePlaceholderVisibility();
+ PseudoStateChanged(CSSSelector::kPseudoPlaceholderShown);
+ }
+
+ ValueMode new_value_mode = input_type_->GetValueMode();
+
+ // https://html.spec.whatwg.org/multipage/forms.html#input-type-change
+ //
+ // 1. If the previous state of the element's type attribute put the value IDL
+ // attribute in the value mode, and the element's value is not the empty
+ // string, and the new state of the element's type attribute puts the value
+ // IDL attribute in either the default mode or the default/on mode, then set
+ // the element's value content attribute to the element's value.
+ if (old_value_mode == ValueMode::kValue &&
+ (new_value_mode == ValueMode::kDefault ||
+ new_value_mode == ValueMode::kDefaultOn)) {
+ if (HasDirtyValue())
+ setAttribute(valueAttr, AtomicString(non_attribute_value_));
+ non_attribute_value_ = String();
+ has_dirty_value_ = false;
+ }
+ // 2. Otherwise, if the previous state of the element's type attribute put the
+ // value IDL attribute in any mode other than the value mode, and the new
+ // state of the element's type attribute puts the value IDL attribute in the
+ // value mode, then set the value of the element to the value of the value
+ // content attribute, if there is one, or the empty string otherwise, and then
+ // set the control's dirty value flag to false.
+ else if (old_value_mode != ValueMode::kValue &&
+ new_value_mode == ValueMode::kValue) {
+ AtomicString value_string = FastGetAttribute(valueAttr);
+ input_type_->WarnIfValueIsInvalid(value_string);
+ non_attribute_value_ = SanitizeValue(value_string);
+ has_dirty_value_ = false;
+ }
+ // 3. Otherwise, if the previous state of the element's type attribute put the
+ // value IDL attribute in any mode other than the filename mode, and the new
+ // state of the element's type attribute puts the value IDL attribute in the
+ // filename mode, then set the value of the element to the empty string.
+ else if (old_value_mode != ValueMode::kFilename &&
+ new_value_mode == ValueMode::kFilename) {
+ non_attribute_value_ = String();
+ has_dirty_value_ = false;
+
+ } else {
+ // ValueMode wasn't changed, or kDefault <-> kDefaultOn.
+ if (!HasDirtyValue()) {
+ String default_value = FastGetAttribute(valueAttr);
+ if (!default_value.IsNull())
+ input_type_->WarnIfValueIsInvalid(default_value);
+ }
+
+ if (new_value_mode == ValueMode::kValue) {
+ String new_value = SanitizeValue(non_attribute_value_);
+ if (!EqualIgnoringNullity(new_value, non_attribute_value_)) {
+ if (HasDirtyValue())
+ setValue(new_value);
+ else
+ SetNonDirtyValue(new_value);
+ }
+ }
+ }
+
+ needs_to_update_view_value_ = true;
+ input_type_view_->UpdateView();
+
+ if (did_respect_height_and_width !=
+ input_type_->ShouldRespectHeightAndWidthAttributes()) {
+ DCHECK(GetElementData());
+ AttributeCollection attributes = AttributesWithoutUpdate();
+ if (const Attribute* height = attributes.Find(heightAttr)) {
+ TextControlElement::AttributeChanged(AttributeModificationParams(
+ heightAttr, height->Value(), height->Value(),
+ AttributeModificationReason::kDirectly));
+ }
+ if (const Attribute* width = attributes.Find(widthAttr)) {
+ TextControlElement::AttributeChanged(
+ AttributeModificationParams(widthAttr, width->Value(), width->Value(),
+ AttributeModificationReason::kDirectly));
+ }
+ if (const Attribute* align = attributes.Find(alignAttr)) {
+ TextControlElement::AttributeChanged(
+ AttributeModificationParams(alignAttr, align->Value(), align->Value(),
+ AttributeModificationReason::kDirectly));
+ }
+ }
+
+ // UA Shadow tree was recreated. We need to set selection again. We do it
+ // later in order to avoid force layout.
+ if (GetDocument().FocusedElement() == this)
+ GetDocument().UpdateFocusAppearanceLater();
+
+ // TODO(tkent): Should we dispatch a change event?
+ ClearValueBeforeFirstUserEdit();
+
+ AddToRadioButtonGroup();
+
+ SetNeedsValidityCheck();
+ if ((could_be_successful_submit_button || CanBeSuccessfulSubmitButton()) &&
+ formOwner() && isConnected())
+ formOwner()->InvalidateDefaultButtonStyle();
+ NotifyFormStateChanged();
+}
+
+void HTMLInputElement::SubtreeHasChanged() {
+ input_type_view_->SubtreeHasChanged();
+ // When typing in an input field, childrenChanged is not called, so we need to
+ // force the directionality check.
+ CalculateAndAdjustDirectionality();
+}
+
+const AtomicString& HTMLInputElement::FormControlType() const {
+ return input_type_->FormControlType();
+}
+
+bool HTMLInputElement::ShouldSaveAndRestoreFormControlState() const {
+ if (!input_type_->ShouldSaveAndRestoreFormControlState())
+ return false;
+ return TextControlElement::ShouldSaveAndRestoreFormControlState();
+}
+
+FormControlState HTMLInputElement::SaveFormControlState() const {
+ return input_type_view_->SaveFormControlState();
+}
+
+void HTMLInputElement::RestoreFormControlState(const FormControlState& state) {
+ input_type_view_->RestoreFormControlState(state);
+ state_restored_ = true;
+}
+
+bool HTMLInputElement::CanStartSelection() const {
+ if (!IsTextField())
+ return false;
+ return TextControlElement::CanStartSelection();
+}
+
+unsigned HTMLInputElement::selectionStartForBinding(
+ bool& is_null,
+ ExceptionState& exception_state) const {
+ if (!input_type_->SupportsSelectionAPI()) {
+ is_null = true;
+ return 0;
+ }
+ return TextControlElement::selectionStart();
+}
+
+unsigned HTMLInputElement::selectionEndForBinding(
+ bool& is_null,
+ ExceptionState& exception_state) const {
+ if (!input_type_->SupportsSelectionAPI()) {
+ is_null = true;
+ return 0;
+ }
+ return TextControlElement::selectionEnd();
+}
+
+String HTMLInputElement::selectionDirectionForBinding(
+ ExceptionState& exception_state) const {
+ if (!input_type_->SupportsSelectionAPI()) {
+ return String();
+ }
+ return TextControlElement::selectionDirection();
+}
+
+void HTMLInputElement::setSelectionStartForBinding(
+ unsigned start,
+ bool is_null,
+ ExceptionState& exception_state) {
+ if (!input_type_->SupportsSelectionAPI()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "The input element's type ('" +
+ input_type_->FormControlType() +
+ "') does not support selection.");
+ return;
+ }
+ TextControlElement::setSelectionStart(start);
+}
+
+void HTMLInputElement::setSelectionEndForBinding(
+ unsigned end,
+ bool is_null,
+ ExceptionState& exception_state) {
+ if (!input_type_->SupportsSelectionAPI()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "The input element's type ('" +
+ input_type_->FormControlType() +
+ "') does not support selection.");
+ return;
+ }
+ TextControlElement::setSelectionEnd(end);
+}
+
+void HTMLInputElement::setSelectionDirectionForBinding(
+ const String& direction,
+ ExceptionState& exception_state) {
+ if (!input_type_->SupportsSelectionAPI()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "The input element's type ('" +
+ input_type_->FormControlType() +
+ "') does not support selection.");
+ return;
+ }
+ TextControlElement::setSelectionDirection(direction);
+}
+
+void HTMLInputElement::setSelectionRangeForBinding(
+ unsigned start,
+ unsigned end,
+ ExceptionState& exception_state) {
+ if (!input_type_->SupportsSelectionAPI()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "The input element's type ('" +
+ input_type_->FormControlType() +
+ "') does not support selection.");
+ return;
+ }
+ TextControlElement::setSelectionRangeForBinding(start, end);
+}
+
+void HTMLInputElement::setSelectionRangeForBinding(
+ unsigned start,
+ unsigned end,
+ const String& direction,
+ ExceptionState& exception_state) {
+ if (!input_type_->SupportsSelectionAPI()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "The input element's type ('" +
+ input_type_->FormControlType() +
+ "') does not support selection.");
+ return;
+ }
+ TextControlElement::setSelectionRangeForBinding(start, end, direction);
+}
+
+void HTMLInputElement::AccessKeyAction(bool send_mouse_events) {
+ input_type_view_->AccessKeyAction(send_mouse_events);
+}
+
+bool HTMLInputElement::IsPresentationAttribute(
+ const QualifiedName& name) const {
+ // FIXME: Remove type check.
+ if (name == vspaceAttr || name == hspaceAttr || name == alignAttr ||
+ name == widthAttr || name == heightAttr ||
+ (name == borderAttr && type() == InputTypeNames::image))
+ return true;
+ return TextControlElement::IsPresentationAttribute(name);
+}
+
+void HTMLInputElement::CollectStyleForPresentationAttribute(
+ const QualifiedName& name,
+ const AtomicString& value,
+ MutableCSSPropertyValueSet* style) {
+ if (name == vspaceAttr) {
+ AddHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
+ AddHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
+ } else if (name == hspaceAttr) {
+ AddHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
+ AddHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
+ } else if (name == alignAttr) {
+ if (input_type_->ShouldRespectAlignAttribute())
+ ApplyAlignmentAttributeToStyle(value, style);
+ } else if (name == widthAttr) {
+ if (input_type_->ShouldRespectHeightAndWidthAttributes())
+ AddHTMLLengthToStyle(style, CSSPropertyWidth, value);
+ } else if (name == heightAttr) {
+ if (input_type_->ShouldRespectHeightAndWidthAttributes())
+ AddHTMLLengthToStyle(style, CSSPropertyHeight, value);
+ } else if (name == borderAttr &&
+ type() == InputTypeNames::image) { // FIXME: Remove type check.
+ ApplyBorderAttributeToStyle(value, style);
+ } else {
+ TextControlElement::CollectStyleForPresentationAttribute(name, value,
+ style);
+ }
+}
+
+void HTMLInputElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ DCHECK(input_type_);
+ DCHECK(input_type_view_);
+ const QualifiedName& name = params.name;
+ const AtomicString& value = params.new_value;
+
+ if (name == nameAttr) {
+ RemoveFromRadioButtonGroup();
+ name_ = value;
+ AddToRadioButtonGroup();
+ TextControlElement::ParseAttribute(params);
+ } else if (name == autocompleteAttr) {
+ if (DeprecatedEqualIgnoringCase(value, "off")) {
+ autocomplete_ = kOff;
+ } else {
+ if (value.IsEmpty())
+ autocomplete_ = kUninitialized;
+ else
+ autocomplete_ = kOn;
+ }
+ } else if (name == typeAttr) {
+ UpdateType();
+ } else if (name == valueAttr) {
+ // We only need to setChanged if the form is looking at the default value
+ // right now.
+ if (!HasDirtyValue()) {
+ if (input_type_->GetValueMode() == ValueMode::kValue)
+ non_attribute_value_ = SanitizeValue(value);
+ UpdatePlaceholderVisibility();
+ SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::FromAttribute(valueAttr));
+ }
+ needs_to_update_view_value_ = true;
+ SetNeedsValidityCheck();
+ input_type_->WarnIfValueIsInvalidAndElementIsVisible(value);
+ input_type_->InRangeChanged();
+ input_type_view_->ValueAttributeChanged();
+ } else if (name == checkedAttr) {
+ // Another radio button in the same group might be checked by state
+ // restore. We shouldn't call setChecked() even if this has the checked
+ // attribute. So, delay the setChecked() call until
+ // finishParsingChildren() is called if parsing is in progress.
+ if ((!parsing_in_progress_ ||
+ !GetDocument().GetFormController().HasFormStates()) &&
+ !dirty_checkedness_) {
+ setChecked(!value.IsNull());
+ dirty_checkedness_ = false;
+ }
+ PseudoStateChanged(CSSSelector::kPseudoDefault);
+ } else if (name == maxlengthAttr) {
+ SetNeedsValidityCheck();
+ } else if (name == minlengthAttr) {
+ SetNeedsValidityCheck();
+ } else if (name == sizeAttr) {
+ unsigned size = 0;
+ if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, size) ||
+ size == 0 || size > 0x7fffffffu)
+ size = kDefaultSize;
+ if (size_ != size) {
+ size_ = size;
+ if (GetLayoutObject())
+ GetLayoutObject()
+ ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
+ LayoutInvalidationReason::kAttributeChanged);
+ }
+ } else if (name == altAttr) {
+ input_type_view_->AltAttributeChanged();
+ } else if (name == srcAttr) {
+ input_type_view_->SrcAttributeChanged();
+ } else if (name == usemapAttr || name == accesskeyAttr) {
+ // FIXME: ignore for the moment
+ } else if (name == onsearchAttr) {
+ // Search field and slider attributes all just cause updateFromElement to be
+ // called through style recalcing.
+ SetAttributeEventListener(
+ EventTypeNames::search,
+ CreateAttributeEventListener(this, name, value, EventParameterName()));
+ } else if (name == incrementalAttr) {
+ UseCounter::Count(GetDocument(), WebFeature::kIncrementalAttribute);
+ } else if (name == minAttr) {
+ input_type_view_->MinOrMaxAttributeChanged();
+ input_type_->SanitizeValueInResponseToMinOrMaxAttributeChange();
+ input_type_->InRangeChanged();
+ SetNeedsValidityCheck();
+ UseCounter::Count(GetDocument(), WebFeature::kMinAttribute);
+ } else if (name == maxAttr) {
+ input_type_view_->MinOrMaxAttributeChanged();
+ input_type_->SanitizeValueInResponseToMinOrMaxAttributeChange();
+ input_type_->InRangeChanged();
+ SetNeedsValidityCheck();
+ UseCounter::Count(GetDocument(), WebFeature::kMaxAttribute);
+ } else if (name == multipleAttr) {
+ input_type_view_->MultipleAttributeChanged();
+ SetNeedsValidityCheck();
+ } else if (name == stepAttr) {
+ input_type_view_->StepAttributeChanged();
+ SetNeedsValidityCheck();
+ UseCounter::Count(GetDocument(), WebFeature::kStepAttribute);
+ } else if (name == patternAttr) {
+ SetNeedsValidityCheck();
+ UseCounter::Count(GetDocument(), WebFeature::kPatternAttribute);
+ } else if (name == readonlyAttr) {
+ TextControlElement::ParseAttribute(params);
+ input_type_view_->ReadonlyAttributeChanged();
+ } else if (name == listAttr) {
+ has_non_empty_list_ = !value.IsEmpty();
+ if (has_non_empty_list_) {
+ ResetListAttributeTargetObserver();
+ ListAttributeTargetChanged();
+ }
+ UseCounter::Count(GetDocument(), WebFeature::kListAttribute);
+ } else if (name == webkitdirectoryAttr) {
+ TextControlElement::ParseAttribute(params);
+ UseCounter::Count(GetDocument(), WebFeature::kPrefixedDirectoryAttribute);
+ } else {
+ if (name == formactionAttr)
+ LogUpdateAttributeIfIsolatedWorldAndInDocument("input", params);
+ TextControlElement::ParseAttribute(params);
+ }
+ input_type_view_->AttributeChanged();
+}
+
+void HTMLInputElement::ParserDidSetAttributes() {
+ DCHECK(parsing_in_progress_);
+ InitializeTypeInParsing();
+}
+
+void HTMLInputElement::FinishParsingChildren() {
+ parsing_in_progress_ = false;
+ DCHECK(input_type_);
+ DCHECK(input_type_view_);
+ TextControlElement::FinishParsingChildren();
+ if (!state_restored_) {
+ bool checked = hasAttribute(checkedAttr);
+ if (checked)
+ setChecked(checked);
+ dirty_checkedness_ = false;
+ }
+}
+
+bool HTMLInputElement::LayoutObjectIsNeeded(const ComputedStyle& style) const {
+ return input_type_->LayoutObjectIsNeeded() &&
+ TextControlElement::LayoutObjectIsNeeded(style);
+}
+
+LayoutObject* HTMLInputElement::CreateLayoutObject(const ComputedStyle& style) {
+ return input_type_view_->CreateLayoutObject(style);
+}
+
+void HTMLInputElement::AttachLayoutTree(AttachContext& context) {
+ SyncReattachContext reattach_context(context);
+ TextControlElement::AttachLayoutTree(context);
+ if (GetLayoutObject()) {
+ input_type_->OnAttachWithLayoutObject();
+ }
+
+ input_type_view_->StartResourceLoading();
+ input_type_->CountUsage();
+}
+
+void HTMLInputElement::DetachLayoutTree(const AttachContext& context) {
+ if (GetLayoutObject()) {
+ input_type_->OnDetachWithLayoutObject();
+ }
+ TextControlElement::DetachLayoutTree(context);
+ needs_to_update_view_value_ = true;
+ input_type_view_->ClosePopupView();
+}
+
+String HTMLInputElement::AltText() const {
+ // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
+ // also heavily discussed by Hixie on bugzilla
+ // note this is intentionally different to HTMLImageElement::altText()
+ String alt = FastGetAttribute(altAttr);
+ // fall back to title attribute
+ if (alt.IsNull())
+ alt = FastGetAttribute(titleAttr);
+ if (alt.IsNull())
+ alt = FastGetAttribute(valueAttr);
+ if (alt.IsNull())
+ alt = GetLocale().QueryString(WebLocalizedString::kInputElementAltText);
+ return alt;
+}
+
+bool HTMLInputElement::CanBeSuccessfulSubmitButton() const {
+ return input_type_->CanBeSuccessfulSubmitButton();
+}
+
+bool HTMLInputElement::IsActivatedSubmit() const {
+ return is_activated_submit_;
+}
+
+void HTMLInputElement::SetActivatedSubmit(bool flag) {
+ is_activated_submit_ = flag;
+}
+
+void HTMLInputElement::AppendToFormData(FormData& form_data) {
+ if (input_type_->IsFormDataAppendable())
+ input_type_->AppendToFormData(form_data);
+}
+
+String HTMLInputElement::ResultForDialogSubmit() {
+ return input_type_->ResultForDialogSubmit();
+}
+
+void HTMLInputElement::ResetImpl() {
+ if (input_type_->GetValueMode() == ValueMode::kValue) {
+ SetNonDirtyValue(DefaultValue());
+ SetNeedsValidityCheck();
+ } else if (input_type_->GetValueMode() == ValueMode::kFilename) {
+ SetNonDirtyValue(String());
+ SetNeedsValidityCheck();
+ }
+
+ setChecked(hasAttribute(checkedAttr));
+ dirty_checkedness_ = false;
+}
+
+bool HTMLInputElement::IsTextField() const {
+ return input_type_->IsTextField();
+}
+
+bool HTMLInputElement::HasBeenPasswordField() const {
+ return has_been_password_field_;
+}
+
+void HTMLInputElement::DispatchChangeEventIfNeeded() {
+ if (isConnected() && input_type_->ShouldSendChangeEventAfterCheckedChanged())
+ DispatchChangeEvent();
+}
+
+void HTMLInputElement::DispatchInputAndChangeEventIfNeeded() {
+ if (isConnected() &&
+ input_type_->ShouldSendChangeEventAfterCheckedChanged()) {
+ DispatchInputEvent();
+ DispatchChangeEvent();
+ }
+}
+
+bool HTMLInputElement::checked() const {
+ input_type_->ReadingChecked();
+ return is_checked_;
+}
+
+void HTMLInputElement::setChecked(bool now_checked,
+ TextFieldEventBehavior event_behavior) {
+ dirty_checkedness_ = true;
+ if (checked() == now_checked)
+ return;
+
+ is_checked_ = now_checked;
+
+ if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope())
+ scope->UpdateCheckedState(this);
+ if (LayoutObject* o = GetLayoutObject())
+ o->InvalidateIfControlStateChanged(kCheckedControlState);
+ SetNeedsValidityCheck();
+
+ // Ideally we'd do this from the layout tree (matching
+ // LayoutTextView), but it's not possible to do it at the moment
+ // because of the way the code is structured.
+ if (GetLayoutObject()) {
+ if (AXObjectCache* cache =
+ GetLayoutObject()->GetDocument().ExistingAXObjectCache())
+ cache->CheckedStateChanged(this);
+ }
+
+ // Only send a change event for items in the document (avoid firing during
+ // parsing) and don't send a change event for a radio button that's getting
+ // unchecked to match other browsers. DOM is not a useful standard for this
+ // because it says only to fire change events at "lose focus" time, which is
+ // definitely wrong in practice for these types of elements.
+ if (event_behavior == kDispatchInputAndChangeEvent && isConnected() &&
+ input_type_->ShouldSendChangeEventAfterCheckedChanged()) {
+ DispatchInputEvent();
+ }
+
+ PseudoStateChanged(CSSSelector::kPseudoChecked);
+}
+
+void HTMLInputElement::setIndeterminate(bool new_value) {
+ if (indeterminate() == new_value)
+ return;
+
+ is_indeterminate_ = new_value;
+
+ PseudoStateChanged(CSSSelector::kPseudoIndeterminate);
+
+ if (LayoutObject* o = GetLayoutObject())
+ o->InvalidateIfControlStateChanged(kCheckedControlState);
+}
+
+unsigned HTMLInputElement::size() const {
+ return size_;
+}
+
+bool HTMLInputElement::SizeShouldIncludeDecoration(int& preferred_size) const {
+ return input_type_view_->SizeShouldIncludeDecoration(kDefaultSize,
+ preferred_size);
+}
+
+void HTMLInputElement::CloneNonAttributePropertiesFrom(const Element& source,
+ CloneChildrenFlag flag) {
+ const HTMLInputElement& source_element = ToHTMLInputElement(source);
+
+ non_attribute_value_ = source_element.non_attribute_value_;
+ has_dirty_value_ = source_element.has_dirty_value_;
+ setChecked(source_element.is_checked_);
+ dirty_checkedness_ = source_element.dirty_checkedness_;
+ is_indeterminate_ = source_element.is_indeterminate_;
+ input_type_->CopyNonAttributeProperties(source_element);
+
+ TextControlElement::CloneNonAttributePropertiesFrom(source, flag);
+
+ needs_to_update_view_value_ = true;
+ input_type_view_->UpdateView();
+}
+
+String HTMLInputElement::value() const {
+ switch (input_type_->GetValueMode()) {
+ case ValueMode::kFilename:
+ return input_type_->ValueInFilenameValueMode();
+ case ValueMode::kDefault:
+ return FastGetAttribute(valueAttr);
+ case ValueMode::kDefaultOn: {
+ AtomicString value_string = FastGetAttribute(valueAttr);
+ return value_string.IsNull() ? "on" : value_string;
+ }
+ case ValueMode::kValue:
+ return non_attribute_value_;
+ }
+ NOTREACHED();
+ return g_empty_string;
+}
+
+String HTMLInputElement::ValueOrDefaultLabel() const {
+ String value = this->value();
+ if (!value.IsNull())
+ return value;
+ return input_type_->DefaultLabel();
+}
+
+void HTMLInputElement::SetValueForUser(const String& value) {
+ // Call setValue and make it send a change event.
+ setValue(value, kDispatchChangeEvent);
+}
+
+void HTMLInputElement::SetSuggestedValue(const String& value) {
+ if (!input_type_->CanSetSuggestedValue())
+ return;
+ needs_to_update_view_value_ = true;
+ TextControlElement::SetSuggestedValue(SanitizeValue(value));
+ SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
+ input_type_view_->UpdateView();
+}
+
+void HTMLInputElement::SetEditingValue(const String& value) {
+ if (!GetLayoutObject() || !IsTextField())
+ return;
+ SetInnerEditorValue(value);
+ SubtreeHasChanged();
+
+ unsigned max = value.length();
+ SetSelectionRange(max, max);
+ DispatchInputEvent();
+}
+
+void HTMLInputElement::SetInnerEditorValue(const String& value) {
+ TextControlElement::SetInnerEditorValue(value);
+ needs_to_update_view_value_ = false;
+}
+
+void HTMLInputElement::setValue(const String& value,
+ ExceptionState& exception_state,
+ TextFieldEventBehavior event_behavior) {
+ // FIXME: Remove type check.
+ if (type() == InputTypeNames::file && !value.IsEmpty()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "This input element accepts a filename, "
+ "which may only be programmatically set "
+ "to the empty string.");
+ return;
+ }
+ setValue(value, event_behavior);
+}
+
+void HTMLInputElement::setValue(const String& value,
+ TextFieldEventBehavior event_behavior,
+ TextControlSetValueSelection selection) {
+ input_type_->WarnIfValueIsInvalidAndElementIsVisible(value);
+ if (!input_type_->CanSetValue(value))
+ return;
+
+ // Clear the suggested value. Use the base class version to not trigger a view
+ // update.
+ TextControlElement::SetSuggestedValue(String());
+
+ EventQueueScope scope;
+ String sanitized_value = SanitizeValue(value);
+ bool value_changed = sanitized_value != this->value();
+
+ SetLastChangeWasNotUserEdit();
+ needs_to_update_view_value_ = true;
+
+ input_type_->SetValue(sanitized_value, value_changed, event_behavior,
+ selection);
+ input_type_view_->DidSetValue(sanitized_value, value_changed);
+
+ if (value_changed)
+ NotifyFormStateChanged();
+}
+
+void HTMLInputElement::SetNonAttributeValue(const String& sanitized_value) {
+ // This is a common code for ValueMode::kValue.
+ DCHECK_EQ(input_type_->GetValueMode(), ValueMode::kValue);
+ non_attribute_value_ = sanitized_value;
+ has_dirty_value_ = true;
+ SetNeedsValidityCheck();
+ input_type_->InRangeChanged();
+}
+
+void HTMLInputElement::SetNonAttributeValueByUserEdit(
+ const String& sanitized_value) {
+ SetValueBeforeFirstUserEditIfNotSet();
+ SetNonAttributeValue(sanitized_value);
+ CheckIfValueWasReverted(sanitized_value);
+}
+
+void HTMLInputElement::SetNonDirtyValue(const String& new_value) {
+ setValue(new_value);
+ has_dirty_value_ = false;
+}
+
+bool HTMLInputElement::HasDirtyValue() const {
+ return has_dirty_value_;
+}
+
+void HTMLInputElement::UpdateView() {
+ input_type_view_->UpdateView();
+}
+
+double HTMLInputElement::valueAsDate(bool& is_null) const {
+ double date = input_type_->ValueAsDate();
+ is_null = !std::isfinite(date);
+ return date;
+}
+
+void HTMLInputElement::setValueAsDate(double value,
+ bool is_null,
+ ExceptionState& exception_state) {
+ input_type_->SetValueAsDate(value, exception_state);
+}
+
+double HTMLInputElement::valueAsNumber() const {
+ return input_type_->ValueAsDouble();
+}
+
+void HTMLInputElement::setValueAsNumber(double new_value,
+ ExceptionState& exception_state,
+ TextFieldEventBehavior event_behavior) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#dom-input-valueasnumber
+ // On setting, if the new value is infinite, then throw a TypeError exception.
+ if (std::isinf(new_value)) {
+ exception_state.ThrowTypeError(
+ ExceptionMessages::NotAFiniteNumber(new_value));
+ return;
+ }
+ input_type_->SetValueAsDouble(new_value, event_behavior, exception_state);
+}
+
+void HTMLInputElement::SetValueFromRenderer(const String& value) {
+ // File upload controls will never use this.
+ DCHECK_NE(type(), InputTypeNames::file);
+
+ // Clear the suggested value. Use the base class version to not trigger a view
+ // update.
+ TextControlElement::SetSuggestedValue(String());
+
+ // Renderer and our event handler are responsible for sanitizing values.
+ DCHECK(value == input_type_->SanitizeUserInputValue(value) ||
+ input_type_->SanitizeUserInputValue(value).IsEmpty());
+
+ DCHECK(!value.IsNull());
+ SetValueBeforeFirstUserEditIfNotSet();
+ non_attribute_value_ = value;
+ has_dirty_value_ = true;
+ needs_to_update_view_value_ = false;
+ CheckIfValueWasReverted(value);
+
+ // Input event is fired by the Node::defaultEventHandler for editable
+ // controls.
+ if (!IsTextField())
+ DispatchInputEvent();
+ NotifyFormStateChanged();
+
+ SetNeedsValidityCheck();
+
+ // Clear autofill flag (and yellow background) on user edit.
+ SetAutofilled(false);
+}
+
+EventDispatchHandlingState* HTMLInputElement::PreDispatchEventHandler(
+ Event* event) {
+ if (event->type() == EventTypeNames::textInput &&
+ input_type_view_->ShouldSubmitImplicitly(event)) {
+ event->stopPropagation();
+ return nullptr;
+ }
+ if (event->type() != EventTypeNames::click)
+ return nullptr;
+ if (!event->IsMouseEvent() ||
+ ToMouseEvent(event)->button() !=
+ static_cast<short>(WebPointerProperties::Button::kLeft))
+ return nullptr;
+ return input_type_view_->WillDispatchClick();
+}
+
+void HTMLInputElement::PostDispatchEventHandler(
+ Event* event,
+ EventDispatchHandlingState* state) {
+ if (!state)
+ return;
+ input_type_view_->DidDispatchClick(event,
+ *static_cast<ClickHandlingState*>(state));
+}
+
+void HTMLInputElement::DefaultEventHandler(Event* evt) {
+ if (evt->IsMouseEvent() && evt->type() == EventTypeNames::click &&
+ ToMouseEvent(evt)->button() ==
+ static_cast<short>(WebPointerProperties::Button::kLeft)) {
+ input_type_view_->HandleClickEvent(ToMouseEvent(evt));
+ if (evt->DefaultHandled())
+ return;
+ }
+
+ if (evt->IsKeyboardEvent() && evt->type() == EventTypeNames::keydown) {
+ input_type_view_->HandleKeydownEvent(ToKeyboardEvent(evt));
+ if (evt->DefaultHandled())
+ return;
+ }
+
+ // Call the base event handler before any of our own event handling for almost
+ // all events in text fields. Makes editing keyboard handling take precedence
+ // over the keydown and keypress handling in this function.
+ bool call_base_class_early =
+ IsTextField() && (evt->type() == EventTypeNames::keydown ||
+ evt->type() == EventTypeNames::keypress);
+ if (call_base_class_early) {
+ TextControlElement::DefaultEventHandler(evt);
+ if (evt->DefaultHandled())
+ return;
+ }
+
+ // DOMActivate events cause the input to be "activated" - in the case of image
+ // and submit inputs, this means actually submitting the form. For reset
+ // inputs, the form is reset. These events are sent when the user clicks on
+ // the element, or presses enter while it is the active element. JavaScript
+ // code wishing to activate the element must dispatch a DOMActivate event - a
+ // click event will not do the job.
+ if (evt->type() == EventTypeNames::DOMActivate) {
+ input_type_view_->HandleDOMActivateEvent(evt);
+ if (evt->DefaultHandled())
+ return;
+ }
+
+ // 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() == EventTypeNames::keypress) {
+ input_type_view_->HandleKeypressEvent(ToKeyboardEvent(evt));
+ if (evt->DefaultHandled())
+ return;
+ }
+
+ if (evt->IsKeyboardEvent() && evt->type() == EventTypeNames::keyup) {
+ input_type_view_->HandleKeyupEvent(ToKeyboardEvent(evt));
+ if (evt->DefaultHandled())
+ return;
+ }
+
+ if (input_type_view_->ShouldSubmitImplicitly(evt)) {
+ // FIXME: Remove type check.
+ if (type() == InputTypeNames::search) {
+ GetDocument()
+ .GetTaskRunner(TaskType::kUserInteraction)
+ ->PostTask(FROM_HERE, WTF::Bind(&HTMLInputElement::OnSearch,
+ WrapPersistent(this)));
+ }
+ // Form submission finishes editing, just as loss of focus does.
+ // If there was a change, send the event now.
+ DispatchFormControlChangeEvent();
+
+ HTMLFormElement* form_for_submission =
+ input_type_view_->FormForSubmission();
+ // Form may never have been present, or may have been destroyed by code
+ // responding to the change event.
+ if (form_for_submission) {
+ form_for_submission->SubmitImplicitly(evt,
+ CanTriggerImplicitSubmission());
+ }
+ evt->SetDefaultHandled();
+ return;
+ }
+
+ if (evt->IsBeforeTextInsertedEvent()) {
+ input_type_view_->HandleBeforeTextInsertedEvent(
+ static_cast<BeforeTextInsertedEvent*>(evt));
+ }
+
+ if (evt->IsMouseEvent() && evt->type() == EventTypeNames::mousedown) {
+ input_type_view_->HandleMouseDownEvent(ToMouseEvent(evt));
+ if (evt->DefaultHandled())
+ return;
+ }
+
+ input_type_view_->ForwardEvent(evt);
+
+ if (!call_base_class_early && !evt->DefaultHandled())
+ TextControlElement::DefaultEventHandler(evt);
+}
+
+void HTMLInputElement::CreateShadowSubtree() {
+ input_type_view_->CreateShadowSubtree();
+}
+
+bool HTMLInputElement::HasActivationBehavior() const {
+ return true;
+}
+
+bool HTMLInputElement::WillRespondToMouseClickEvents() {
+ // FIXME: Consider implementing willRespondToMouseClickEvents() in InputType
+ // if more accurate results are necessary.
+ if (!IsDisabledFormControl())
+ return true;
+
+ return TextControlElement::WillRespondToMouseClickEvents();
+}
+
+bool HTMLInputElement::IsURLAttribute(const Attribute& attribute) const {
+ return attribute.GetName() == srcAttr ||
+ attribute.GetName() == formactionAttr ||
+ TextControlElement::IsURLAttribute(attribute);
+}
+
+bool HTMLInputElement::HasLegalLinkAttribute(const QualifiedName& name) const {
+ return input_type_->HasLegalLinkAttribute(name) ||
+ TextControlElement::HasLegalLinkAttribute(name);
+}
+
+const QualifiedName& HTMLInputElement::SubResourceAttributeName() const {
+ return input_type_->SubResourceAttributeName();
+}
+
+const AtomicString& HTMLInputElement::DefaultValue() const {
+ return FastGetAttribute(valueAttr);
+}
+
+static inline bool IsRFC2616TokenCharacter(UChar ch) {
+ return IsASCII(ch) && ch > ' ' && ch != '"' && ch != '(' && ch != ')' &&
+ ch != ',' && ch != '/' && (ch < ':' || ch > '@') &&
+ (ch < '[' || ch > ']') && ch != '{' && ch != '}' && ch != 0x7f;
+}
+
+static bool IsValidMIMEType(const String& type) {
+ size_t slash_position = type.find('/');
+ if (slash_position == kNotFound || !slash_position ||
+ slash_position == type.length() - 1)
+ return false;
+ for (size_t i = 0; i < type.length(); ++i) {
+ if (!IsRFC2616TokenCharacter(type[i]) && i != slash_position)
+ return false;
+ }
+ return true;
+}
+
+static bool IsValidFileExtension(const String& type) {
+ if (type.length() < 2)
+ return false;
+ return type[0] == '.';
+}
+
+static Vector<String> ParseAcceptAttribute(const String& accept_string,
+ bool (*predicate)(const String&)) {
+ Vector<String> types;
+ if (accept_string.IsEmpty())
+ return types;
+
+ Vector<String> split_types;
+ accept_string.Split(',', false, split_types);
+ for (const String& split_type : split_types) {
+ String trimmed_type = StripLeadingAndTrailingHTMLSpaces(split_type);
+ if (trimmed_type.IsEmpty())
+ continue;
+ if (!predicate(trimmed_type))
+ continue;
+ types.push_back(trimmed_type.DeprecatedLower());
+ }
+
+ return types;
+}
+
+Vector<String> HTMLInputElement::AcceptMIMETypes() const {
+ return ParseAcceptAttribute(FastGetAttribute(acceptAttr), IsValidMIMEType);
+}
+
+Vector<String> HTMLInputElement::AcceptFileExtensions() const {
+ return ParseAcceptAttribute(FastGetAttribute(acceptAttr),
+ IsValidFileExtension);
+}
+
+const AtomicString& HTMLInputElement::Alt() const {
+ return FastGetAttribute(altAttr);
+}
+
+bool HTMLInputElement::Multiple() const {
+ return FastHasAttribute(multipleAttr);
+}
+
+void HTMLInputElement::setSize(unsigned size, ExceptionState& exception_state) {
+ if (size == 0) {
+ exception_state.ThrowDOMException(
+ kIndexSizeError, "The value provided is 0, which is an invalid size.");
+ } else {
+ SetUnsignedIntegralAttribute(sizeAttr, size ? size : kDefaultSize,
+ kDefaultSize);
+ }
+}
+
+KURL HTMLInputElement::Src() const {
+ return GetDocument().CompleteURL(FastGetAttribute(srcAttr));
+}
+
+FileList* HTMLInputElement::files() const {
+ return input_type_->Files();
+}
+
+void HTMLInputElement::setFiles(FileList* files) {
+ input_type_->SetFiles(files);
+}
+
+bool HTMLInputElement::ReceiveDroppedFiles(const DragData* drag_data) {
+ return input_type_->ReceiveDroppedFiles(drag_data);
+}
+
+String HTMLInputElement::DroppedFileSystemId() {
+ return input_type_->DroppedFileSystemId();
+}
+
+bool HTMLInputElement::CanReceiveDroppedFiles() const {
+ return can_receive_dropped_files_;
+}
+
+void HTMLInputElement::SetCanReceiveDroppedFiles(
+ bool can_receive_dropped_files) {
+ if (!!can_receive_dropped_files_ == can_receive_dropped_files)
+ return;
+ can_receive_dropped_files_ = can_receive_dropped_files;
+ if (GetLayoutObject())
+ GetLayoutObject()->UpdateFromElement();
+}
+
+String HTMLInputElement::SanitizeValue(const String& proposed_value) const {
+ return input_type_->SanitizeValue(proposed_value);
+}
+
+String HTMLInputElement::LocalizeValue(const String& proposed_value) const {
+ if (proposed_value.IsNull())
+ return proposed_value;
+ return input_type_->LocalizeValue(proposed_value);
+}
+
+bool HTMLInputElement::IsInRange() const {
+ return willValidate() && input_type_->IsInRange(value());
+}
+
+bool HTMLInputElement::IsOutOfRange() const {
+ return willValidate() && input_type_->IsOutOfRange(value());
+}
+
+bool HTMLInputElement::IsRequiredFormControl() const {
+ return input_type_->SupportsRequired() && IsRequired();
+}
+
+bool HTMLInputElement::MatchesReadOnlyPseudoClass() const {
+ return input_type_->SupportsReadOnly() && IsReadOnly();
+}
+
+bool HTMLInputElement::MatchesReadWritePseudoClass() const {
+ return input_type_->SupportsReadOnly() && !IsReadOnly();
+}
+
+void HTMLInputElement::OnSearch() {
+ input_type_->DispatchSearchEvent();
+}
+
+void HTMLInputElement::UpdateClearButtonVisibility() {
+ input_type_view_->UpdateClearButtonVisibility();
+}
+
+void HTMLInputElement::WillChangeForm() {
+ if (input_type_)
+ RemoveFromRadioButtonGroup();
+ TextControlElement::WillChangeForm();
+}
+
+void HTMLInputElement::DidChangeForm() {
+ TextControlElement::DidChangeForm();
+ if (input_type_)
+ AddToRadioButtonGroup();
+}
+
+Node::InsertionNotificationRequest HTMLInputElement::InsertedInto(
+ ContainerNode* insertion_point) {
+ TextControlElement::InsertedInto(insertion_point);
+ if (insertion_point->isConnected() && !Form())
+ AddToRadioButtonGroup();
+ ResetListAttributeTargetObserver();
+ LogAddElementIfIsolatedWorldAndInDocument("input", typeAttr, formactionAttr);
+ return kInsertionShouldCallDidNotifySubtreeInsertions;
+}
+
+void HTMLInputElement::RemovedFrom(ContainerNode* insertion_point) {
+ input_type_view_->ClosePopupView();
+ if (insertion_point->isConnected() && !Form())
+ RemoveFromRadioButtonGroup();
+ TextControlElement::RemovedFrom(insertion_point);
+ DCHECK(!isConnected());
+ ResetListAttributeTargetObserver();
+}
+
+void HTMLInputElement::DidMoveToNewDocument(Document& old_document) {
+ if (ImageLoader())
+ ImageLoader()->ElementDidMoveToNewDocument();
+
+ // FIXME: Remove type check.
+ if (type() == InputTypeNames::radio)
+ GetTreeScope().GetRadioButtonGroupScope().RemoveButton(this);
+
+ TextControlElement::DidMoveToNewDocument(old_document);
+}
+
+bool HTMLInputElement::RecalcWillValidate() const {
+ return input_type_->SupportsValidation() &&
+ TextControlElement::RecalcWillValidate();
+}
+
+void HTMLInputElement::RequiredAttributeChanged() {
+ TextControlElement::RequiredAttributeChanged();
+ if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope())
+ scope->RequiredAttributeChanged(this);
+ input_type_view_->RequiredAttributeChanged();
+}
+
+void HTMLInputElement::DisabledAttributeChanged() {
+ TextControlElement::DisabledAttributeChanged();
+ input_type_view_->DisabledAttributeChanged();
+}
+
+void HTMLInputElement::SelectColorInColorChooser(const Color& color) {
+ if (ColorChooserClient* client = input_type_->GetColorChooserClient())
+ client->DidChooseColor(color);
+}
+
+void HTMLInputElement::EndColorChooser() {
+ if (ColorChooserClient* client = input_type_->GetColorChooserClient())
+ client->DidEndChooser();
+}
+
+HTMLElement* HTMLInputElement::list() const {
+ return DataList();
+}
+
+HTMLDataListElement* HTMLInputElement::DataList() const {
+ if (!has_non_empty_list_)
+ return nullptr;
+
+ if (!input_type_->ShouldRespectListAttribute())
+ return nullptr;
+
+ return ToHTMLDataListElementOrNull(
+ GetTreeScope().getElementById(FastGetAttribute(listAttr)));
+}
+
+bool HTMLInputElement::HasValidDataListOptions() const {
+ HTMLDataListElement* data_list = DataList();
+ if (!data_list)
+ return false;
+ HTMLDataListOptionsCollection* options = data_list->options();
+ for (unsigned i = 0; HTMLOptionElement* option = options->Item(i); ++i) {
+ if (!option->value().IsEmpty() && !option->IsDisabledFormControl() &&
+ IsValidValue(option->value()))
+ return true;
+ }
+ return false;
+}
+
+HeapVector<Member<HTMLOptionElement>>
+HTMLInputElement::FilteredDataListOptions() const {
+ HeapVector<Member<HTMLOptionElement>> filtered;
+ HTMLDataListElement* data_list = DataList();
+ if (!data_list)
+ return filtered;
+
+ String value = InnerEditorValue();
+ if (Multiple() && type() == InputTypeNames::email) {
+ Vector<String> emails;
+ value.Split(',', true, emails);
+ if (!emails.IsEmpty())
+ value = emails.back().StripWhiteSpace();
+ }
+
+ HTMLDataListOptionsCollection* options = data_list->options();
+ filtered.ReserveCapacity(options->length());
+ value = value.FoldCase();
+ for (unsigned i = 0; i < options->length(); ++i) {
+ HTMLOptionElement* option = options->Item(i);
+ DCHECK(option);
+ if (!value.IsEmpty()) {
+ // Firefox shows OPTIONs with matched labels, Edge shows OPTIONs
+ // with matches values. We show both.
+ if (option->value().FoldCase().Find(value) == kNotFound &&
+ option->label().FoldCase().Find(value) == kNotFound)
+ continue;
+ }
+ // TODO(tkent): Should allow invalid strings. crbug.com/607097.
+ if (option->value().IsEmpty() || option->IsDisabledFormControl() ||
+ !IsValidValue(option->value()))
+ continue;
+ filtered.push_back(option);
+ }
+ return filtered;
+}
+
+void HTMLInputElement::SetListAttributeTargetObserver(
+ ListAttributeTargetObserver* new_observer) {
+ if (list_attribute_target_observer_)
+ list_attribute_target_observer_->Unregister();
+ list_attribute_target_observer_ = new_observer;
+}
+
+void HTMLInputElement::ResetListAttributeTargetObserver() {
+ const AtomicString& value = FastGetAttribute(listAttr);
+ if (!value.IsNull() && isConnected()) {
+ SetListAttributeTargetObserver(
+ ListAttributeTargetObserver::Create(value, this));
+ } else {
+ SetListAttributeTargetObserver(nullptr);
+ }
+}
+
+void HTMLInputElement::ListAttributeTargetChanged() {
+ input_type_view_->ListAttributeTargetChanged();
+}
+
+bool HTMLInputElement::IsSteppable() const {
+ return input_type_->IsSteppable();
+}
+
+bool HTMLInputElement::IsTextButton() const {
+ return input_type_->IsTextButton();
+}
+
+bool HTMLInputElement::IsEnumeratable() const {
+ return input_type_->IsEnumeratable();
+}
+
+bool HTMLInputElement::SupportLabels() const {
+ return input_type_->IsInteractiveContent();
+}
+
+bool HTMLInputElement::MatchesDefaultPseudoClass() const {
+ return input_type_->MatchesDefaultPseudoClass();
+}
+
+bool HTMLInputElement::ShouldAppearChecked() const {
+ return checked() && input_type_->IsCheckable();
+}
+
+void HTMLInputElement::SetPlaceholderVisibility(bool visible) {
+ is_placeholder_visible_ = visible;
+}
+
+bool HTMLInputElement::SupportsPlaceholder() const {
+ return input_type_->SupportsPlaceholder();
+}
+
+void HTMLInputElement::UpdatePlaceholderText() {
+ return input_type_view_->UpdatePlaceholderText();
+}
+
+String HTMLInputElement::GetPlaceholderValue() const {
+ return !SuggestedValue().IsEmpty() ? SuggestedValue() : StrippedPlaceholder();
+}
+
+String HTMLInputElement::DefaultToolTip() const {
+ return input_type_->DefaultToolTip(*input_type_view_);
+}
+
+bool HTMLInputElement::ShouldAppearIndeterminate() const {
+ return input_type_->ShouldAppearIndeterminate();
+}
+
+bool HTMLInputElement::IsInRequiredRadioButtonGroup() {
+ // TODO(tkent): Remove type check.
+ DCHECK_EQ(type(), InputTypeNames::radio);
+ if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope())
+ return scope->IsInRequiredGroup(this);
+ return false;
+}
+
+HTMLInputElement* HTMLInputElement::CheckedRadioButtonForGroup() {
+ if (checked())
+ return this;
+ if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope())
+ return scope->CheckedButtonForGroup(GetName());
+ return nullptr;
+}
+
+RadioButtonGroupScope* HTMLInputElement::GetRadioButtonGroupScope() const {
+ // FIXME: Remove type check.
+ if (type() != InputTypeNames::radio)
+ return nullptr;
+ if (HTMLFormElement* form_element = Form())
+ return &form_element->GetRadioButtonGroupScope();
+ if (isConnected())
+ return &GetTreeScope().GetRadioButtonGroupScope();
+ return nullptr;
+}
+
+unsigned HTMLInputElement::SizeOfRadioGroup() const {
+ RadioButtonGroupScope* scope = GetRadioButtonGroupScope();
+ if (!scope)
+ return 0;
+ return scope->GroupSizeFor(this);
+}
+
+inline void HTMLInputElement::AddToRadioButtonGroup() {
+ if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope())
+ scope->AddButton(this);
+}
+
+inline void HTMLInputElement::RemoveFromRadioButtonGroup() {
+ if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope())
+ scope->RemoveButton(this);
+}
+
+unsigned HTMLInputElement::height() const {
+ return input_type_->Height();
+}
+
+unsigned HTMLInputElement::width() const {
+ return input_type_->Width();
+}
+
+void HTMLInputElement::setHeight(unsigned height) {
+ SetUnsignedIntegralAttribute(heightAttr, height);
+}
+
+void HTMLInputElement::setWidth(unsigned width) {
+ SetUnsignedIntegralAttribute(widthAttr, width);
+}
+
+ListAttributeTargetObserver* ListAttributeTargetObserver::Create(
+ const AtomicString& id,
+ HTMLInputElement* element) {
+ return new ListAttributeTargetObserver(id, element);
+}
+
+ListAttributeTargetObserver::ListAttributeTargetObserver(
+ const AtomicString& id,
+ HTMLInputElement* element)
+ : IdTargetObserver(element->GetTreeScope().GetIdTargetObserverRegistry(),
+ id),
+ element_(element) {}
+
+void ListAttributeTargetObserver::Trace(blink::Visitor* visitor) {
+ visitor->Trace(element_);
+ IdTargetObserver::Trace(visitor);
+}
+
+void ListAttributeTargetObserver::IdTargetChanged() {
+ element_->ListAttributeTargetChanged();
+}
+
+void HTMLInputElement::setRangeText(const String& replacement,
+ ExceptionState& exception_state) {
+ if (!input_type_->SupportsSelectionAPI()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "The input element's type ('" +
+ input_type_->FormControlType() +
+ "') does not support selection.");
+ return;
+ }
+
+ TextControlElement::setRangeText(replacement, exception_state);
+}
+
+void HTMLInputElement::setRangeText(const String& replacement,
+ unsigned start,
+ unsigned end,
+ const String& selection_mode,
+ ExceptionState& exception_state) {
+ if (!input_type_->SupportsSelectionAPI()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "The input element's type ('" +
+ input_type_->FormControlType() +
+ "') does not support selection.");
+ return;
+ }
+
+ TextControlElement::setRangeText(replacement, start, end, selection_mode,
+ exception_state);
+}
+
+bool HTMLInputElement::SetupDateTimeChooserParameters(
+ DateTimeChooserParameters& parameters) {
+ if (!GetDocument().View())
+ return false;
+
+ parameters.type = type();
+ parameters.minimum = Minimum();
+ parameters.maximum = Maximum();
+ parameters.required = IsRequired();
+ if (!RuntimeEnabledFeatures::LangAttributeAwareFormControlUIEnabled()) {
+ parameters.locale = DefaultLanguage();
+ } else {
+ AtomicString computed_locale = ComputeInheritedLanguage();
+ parameters.locale =
+ computed_locale.IsEmpty() ? DefaultLanguage() : computed_locale;
+ }
+
+ StepRange step_range = CreateStepRange(kRejectAny);
+ if (step_range.HasStep()) {
+ parameters.step = step_range.Step().ToDouble();
+ parameters.step_base = step_range.StepBase().ToDouble();
+ } else {
+ parameters.step = 1.0;
+ parameters.step_base = 0;
+ }
+
+ parameters.anchor_rect_in_screen =
+ GetDocument().View()->ContentsToScreen(PixelSnappedBoundingBox());
+ parameters.double_value = input_type_->ValueAsDouble();
+ parameters.is_anchor_element_rtl =
+ input_type_view_->ComputedTextDirection() == TextDirection::kRtl;
+ if (HTMLDataListElement* data_list = DataList()) {
+ HTMLDataListOptionsCollection* options = data_list->options();
+ for (unsigned i = 0; HTMLOptionElement* option = options->Item(i); ++i) {
+ if (option->value().IsEmpty() || option->IsDisabledFormControl() ||
+ !IsValidValue(option->value()))
+ continue;
+ DateTimeSuggestion suggestion;
+ suggestion.value =
+ input_type_->ParseToNumber(option->value(), Decimal::Nan())
+ .ToDouble();
+ if (std::isnan(suggestion.value))
+ continue;
+ suggestion.localized_value = LocalizeValue(option->value());
+ suggestion.label =
+ option->value() == option->label() ? String() : option->label();
+ parameters.suggestions.push_back(suggestion);
+ }
+ }
+ return true;
+}
+
+bool HTMLInputElement::SupportsInputModeAttribute() const {
+ return input_type_->SupportsInputModeAttribute();
+}
+
+void HTMLInputElement::SetShouldRevealPassword(bool value) {
+ if (!!should_reveal_password_ == value)
+ return;
+ should_reveal_password_ = value;
+ LazyReattachIfAttached();
+}
+
+bool HTMLInputElement::IsInteractiveContent() const {
+ return input_type_->IsInteractiveContent();
+}
+
+bool HTMLInputElement::SupportsAutofocus() const {
+ return input_type_->IsInteractiveContent();
+}
+
+scoped_refptr<ComputedStyle> HTMLInputElement::CustomStyleForLayoutObject() {
+ return input_type_view_->CustomStyleForLayoutObject(
+ OriginalStyleForLayoutObject());
+}
+
+void HTMLInputElement::DidNotifySubtreeInsertionsToDocument() {
+ ListAttributeTargetChanged();
+}
+
+AXObject* HTMLInputElement::PopupRootAXObject() {
+ return input_type_view_->PopupRootAXObject();
+}
+
+void HTMLInputElement::EnsureFallbackContent() {
+ input_type_view_->EnsureFallbackContent();
+}
+
+void HTMLInputElement::EnsurePrimaryContent() {
+ input_type_view_->EnsurePrimaryContent();
+}
+
+bool HTMLInputElement::HasFallbackContent() const {
+ return input_type_view_->HasFallbackContent();
+}
+
+void HTMLInputElement::SetFilesFromPaths(const Vector<String>& paths) {
+ return input_type_->SetFilesFromPaths(paths);
+}
+
+void HTMLInputElement::ChildrenChanged(const ChildrenChange& change) {
+ // Some input types only need shadow roots to hide any children that may
+ // have been appended by script. For such types, shadow roots are lazily
+ // created when children are added for the first time.
+ EnsureUserAgentShadowRoot();
+ ContainerNode::ChildrenChanged(change);
+}
+
+} // 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
new file mode 100644
index 00000000000..06effcc35eb
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_
+
+#include "base/gtest_prod_util.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"
+#include "third_party/blink/renderer/core/html/forms/file_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/step_range.h"
+#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
+
+namespace blink {
+
+class AXObject;
+class DragData;
+class ExceptionState;
+class FileList;
+class HTMLDataListElement;
+class HTMLImageLoader;
+class InputType;
+class InputTypeView;
+class KURL;
+class ListAttributeTargetObserver;
+class RadioButtonGroupScope;
+struct DateTimeChooserParameters;
+
+class CORE_EXPORT HTMLInputElement
+ : public TextControlElement,
+ public ActiveScriptWrappable<HTMLInputElement> {
+ DEFINE_WRAPPERTYPEINFO();
+ USING_GARBAGE_COLLECTED_MIXIN(HTMLInputElement);
+
+ public:
+ static HTMLInputElement* Create(Document&, const CreateElementFlags);
+ ~HTMLInputElement() override;
+ void Trace(blink::Visitor*) override;
+
+ bool HasPendingActivity() const final;
+
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(webkitspeechchange);
+
+ bool ShouldAutocomplete() const final;
+
+ // For ValidityState
+ bool HasBadInput() const final;
+ bool PatternMismatch() const final;
+ bool RangeUnderflow() const final;
+ bool RangeOverflow() const final;
+ bool StepMismatch() const final;
+ bool TooLong() const final;
+ bool TooShort() const final;
+ bool TypeMismatch() const final;
+ bool ValueMissing() const final;
+ String validationMessage() const final;
+ String ValidationSubMessage() const final;
+
+ // Returns the minimum value for type=date, number, or range. Don't call this
+ // for other types.
+ double Minimum() const;
+ // Returns the maximum value for type=date, number, or range. Don't call this
+ // for other types. This always returns a value which is >= minimum().
+ double Maximum() const;
+ // Sets the "allowed value step" defined in the HTML spec to the specified
+ // double pointer. Returns false if there is no "allowed value step."
+ bool GetAllowedValueStep(Decimal*) const;
+ StepRange CreateStepRange(AnyStepHandling) const;
+
+ Decimal FindClosestTickMarkValue(const Decimal&);
+
+ // Implementations of HTMLInputElement::stepUp() and stepDown().
+ void stepUp(int, ExceptionState&);
+ void stepDown(int, ExceptionState&);
+ // stepUp()/stepDown() for user-interaction.
+ bool IsSteppable() const;
+
+ // Returns true if the type is button, reset, or submit.
+ bool IsTextButton() const;
+ // Returns true if the type is email, number, password, search, tel, text,
+ // or url.
+ bool IsTextField() const;
+ // Do not add type check predicates for concrete input types; e.g. isImage,
+ // isRadio, isFile. If you want to check the input type, you may use
+ // |input->type() == InputTypeNames::image|, etc.
+
+ // Returns whether this field is or has ever been a password field so that
+ // its value can be protected from memorization by autofill or keyboards.
+ bool HasBeenPasswordField() const;
+
+ bool checked() const;
+ void setChecked(bool, TextFieldEventBehavior = kDispatchNoEvent);
+ void DispatchChangeEventIfNeeded();
+ void DispatchInputAndChangeEventIfNeeded();
+
+ // 'indeterminate' is a state independent of the checked state that causes the
+ // control to draw in a way that hides the actual state.
+ bool indeterminate() const { return is_indeterminate_; }
+ void setIndeterminate(bool);
+ // shouldAppearChecked is used by the layout tree/CSS while checked() is used
+ // by JS to determine checked state
+ bool ShouldAppearChecked() const;
+ bool ShouldAppearIndeterminate() const override;
+
+ unsigned size() const;
+ bool SizeShouldIncludeDecoration(int& preferred_size) const;
+
+ void setType(const AtomicString&);
+
+ String value() const override;
+ void setValue(const String&,
+ ExceptionState&,
+ TextFieldEventBehavior = kDispatchNoEvent);
+ void setValue(const String&,
+ TextFieldEventBehavior = kDispatchNoEvent,
+ TextControlSetValueSelection =
+ TextControlSetValueSelection::kSetSelectionToEnd) override;
+ void SetValueForUser(const String&);
+ // Update the value, and clear hasDirtyValue() flag.
+ void SetNonDirtyValue(const String&);
+ // Checks if the specified string would be a valid value.
+ // We should not call this for types with no string value such as CHECKBOX and
+ // RADIO.
+ bool IsValidValue(const String&) const;
+ bool HasDirtyValue() const;
+
+ String SanitizeValue(const String&) const;
+
+ String LocalizeValue(const String&) const;
+
+ void SetSuggestedValue(const String& value) override;
+
+ void SetEditingValue(const String&);
+
+ double valueAsDate(bool& is_null) const;
+ void setValueAsDate(double, bool is_null, ExceptionState&);
+
+ double valueAsNumber() const;
+ void setValueAsNumber(double,
+ ExceptionState&,
+ TextFieldEventBehavior = kDispatchNoEvent);
+
+ String ValueOrDefaultLabel() const;
+
+ // This function dispatches 'input' event for non-textfield types. Callers
+ // need to handle any DOM structure changes by event handlers, or need to
+ // delay the 'input' event with EventQueueScope.
+ void SetValueFromRenderer(const String&);
+
+ unsigned selectionStartForBinding(bool&, ExceptionState&) const;
+ unsigned selectionEndForBinding(bool&, ExceptionState&) const;
+ String selectionDirectionForBinding(ExceptionState&) const;
+ void setSelectionStartForBinding(unsigned, bool is_null, ExceptionState&);
+ void setSelectionEndForBinding(unsigned, bool is_null, ExceptionState&);
+ void setSelectionDirectionForBinding(const String&, ExceptionState&);
+ void setSelectionRangeForBinding(unsigned start,
+ unsigned end,
+ ExceptionState&);
+ void setSelectionRangeForBinding(unsigned start,
+ unsigned end,
+ const String& direction,
+ ExceptionState&);
+
+ bool LayoutObjectIsNeeded(const ComputedStyle&) const final;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ void DetachLayoutTree(const AttachContext& = AttachContext()) final;
+ void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus,
+ const FocusOptions&) final;
+
+ // FIXME: For isActivatedSubmit and setActivatedSubmit, we should use the
+ // NVI-idiom here by making it private virtual in all classes and expose a
+ // public method in HTMLFormControlElement to call
+ // the private virtual method.
+ bool IsActivatedSubmit() const final;
+ void SetActivatedSubmit(bool flag) final;
+
+ String AltText() const final;
+
+ const AtomicString& DefaultValue() const;
+
+ Vector<String> AcceptMIMETypes() const;
+ Vector<String> AcceptFileExtensions() const;
+ const AtomicString& Alt() const;
+
+ void setSize(unsigned, ExceptionState&);
+
+ KURL Src() const;
+ bool Multiple() const;
+
+ FileList* files() const;
+ void setFiles(FileList*);
+
+ void SetFilesFromPaths(const Vector<String>&);
+
+ // Returns true if the given DragData has more than one dropped files.
+ bool ReceiveDroppedFiles(const DragData*);
+
+ String DroppedFileSystemId();
+
+ // These functions are used for laying out the input active during a
+ // drag-and-drop operation.
+ bool CanReceiveDroppedFiles() const;
+ void SetCanReceiveDroppedFiles(bool);
+
+ void OnSearch();
+
+ void UpdateClearButtonVisibility();
+
+ bool WillRespondToMouseClickEvents() override;
+
+ HTMLElement* list() const;
+ HTMLDataListElement* DataList() const;
+ bool HasValidDataListOptions() const;
+ void ListAttributeTargetChanged();
+ // 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&);
+ void UpdateView();
+ bool NeedsToUpdateViewValue() const { return needs_to_update_view_value_; }
+ void SetInnerEditorValue(const String&) override;
+
+ // For test purposes.
+ void SelectColorInColorChooser(const Color&);
+ void EndColorChooser();
+
+ String DefaultToolTip() const override;
+
+ unsigned height() const;
+ unsigned width() const;
+ void setHeight(unsigned);
+ void setWidth(unsigned);
+
+ void blur() final;
+ void DefaultBlur();
+
+ const AtomicString& GetName() const final;
+
+ void EndEditing();
+
+ static Vector<FileChooserFileInfo> FilesFromFileInputFormControlState(
+ const FormControlState&);
+
+ bool MatchesReadOnlyPseudoClass() const final;
+ bool MatchesReadWritePseudoClass() const final;
+ void setRangeText(const String& replacement, ExceptionState&) final;
+ void setRangeText(const String& replacement,
+ unsigned start,
+ unsigned end,
+ const String& selection_mode,
+ ExceptionState&) final;
+
+ HTMLImageLoader* ImageLoader() const { return image_loader_.Get(); }
+ HTMLImageLoader& EnsureImageLoader();
+
+ bool SetupDateTimeChooserParameters(DateTimeChooserParameters&);
+
+ bool SupportsInputModeAttribute() const;
+
+ void SetShouldRevealPassword(bool value);
+ bool ShouldRevealPassword() const { return should_reveal_password_; }
+ AXObject* PopupRootAXObject();
+ void DidNotifySubtreeInsertionsToDocument() override;
+
+ virtual void EnsureFallbackContent();
+ virtual void EnsurePrimaryContent();
+ bool HasFallbackContent() const;
+
+ bool IsPlaceholderVisible() const override { return is_placeholder_visible_; }
+ void SetPlaceholderVisibility(bool) override;
+
+ unsigned SizeOfRadioGroup() const;
+
+ bool SupportsPlaceholder() const final;
+ String GetPlaceholderValue() const final;
+
+ void ChildrenChanged(const ChildrenChange&) override;
+
+ protected:
+ HTMLInputElement(Document&, const CreateElementFlags);
+
+ void DefaultEventHandler(Event*) override;
+ void CreateShadowSubtree();
+
+ private:
+ enum AutoCompleteSetting { kUninitialized, kOn, kOff };
+
+ void WillChangeForm() final;
+ void DidChangeForm() final;
+ InsertionNotificationRequest InsertedInto(ContainerNode*) override;
+ void RemovedFrom(ContainerNode*) final;
+ void DidMoveToNewDocument(Document& old_document) final;
+ bool HasActivationBehavior() const override;
+
+ bool HasCustomFocusLogic() const final;
+ bool IsKeyboardFocusable() const final;
+ bool ShouldShowFocusRingOnMouseFocus() const final;
+ bool IsEnumeratable() const final;
+ bool IsInteractiveContent() const final;
+ bool SupportLabels() const final;
+ bool MatchesDefaultPseudoClass() const override;
+
+ bool IsTextControl() const final { return IsTextField(); }
+
+ bool CanTriggerImplicitSubmission() const final { return IsTextField(); }
+
+ const AtomicString& FormControlType() const final;
+
+ bool ShouldSaveAndRestoreFormControlState() const final;
+ FormControlState SaveFormControlState() const final;
+ void RestoreFormControlState(const FormControlState&) final;
+
+ bool CanStartSelection() const final;
+
+ void AccessKeyAction(bool send_mouse_events) final;
+
+ void ParseAttribute(const AttributeModificationParams&) override;
+ bool IsPresentationAttribute(const QualifiedName&) const final;
+ void CollectStyleForPresentationAttribute(const QualifiedName&,
+ const AtomicString&,
+ MutableCSSPropertyValueSet*) final;
+ void FinishParsingChildren() final;
+ void ParserDidSetAttributes() final;
+
+ void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) final;
+
+ void AttachLayoutTree(AttachContext&) final;
+
+ void AppendToFormData(FormData&) final;
+ String ResultForDialogSubmit() final;
+
+ bool CanBeSuccessfulSubmitButton() const final;
+
+ void ResetImpl() final;
+ bool SupportsAutofocus() const final;
+
+ EventDispatchHandlingState* PreDispatchEventHandler(Event*) final;
+ void PostDispatchEventHandler(Event*, EventDispatchHandlingState*) final;
+
+ bool IsURLAttribute(const Attribute&) const final;
+ bool HasLegalLinkAttribute(const QualifiedName&) const final;
+ const QualifiedName& SubResourceAttributeName() const final;
+ bool IsInRange() const final;
+ bool IsOutOfRange() const final;
+
+ bool TooLong(const String&, NeedsToCheckDirtyFlag) const;
+ bool TooShort(const String&, NeedsToCheckDirtyFlag) const;
+
+ void UpdatePlaceholderText() final;
+ bool IsEmptyValue() const final { return InnerEditorValue().IsEmpty(); }
+ void HandleBlurEvent() final;
+ void DispatchFocusInEvent(const AtomicString& event_type,
+ Element* old_focused_element,
+ WebFocusType,
+ InputDeviceCapabilities* source_capabilities) final;
+
+ bool IsOptionalFormControl() const final { return !IsRequiredFormControl(); }
+ bool IsRequiredFormControl() const final;
+ bool RecalcWillValidate() const final;
+ void RequiredAttributeChanged() final;
+ void DisabledAttributeChanged() final;
+
+ void InitializeTypeInParsing();
+ void UpdateType();
+
+ void SubtreeHasChanged() final;
+
+ void SetListAttributeTargetObserver(ListAttributeTargetObserver*);
+ void ResetListAttributeTargetObserver();
+ void ParseMaxLengthAttribute(const AtomicString&);
+ void ParseMinLengthAttribute(const AtomicString&);
+
+ // Returns null if this isn't associated with any radio button group.
+ RadioButtonGroupScope* GetRadioButtonGroupScope() const;
+ void AddToRadioButtonGroup();
+ void RemoveFromRadioButtonGroup();
+ scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override;
+
+ AtomicString name_;
+ // The value string in |value| value mode.
+ String non_attribute_value_;
+ unsigned size_;
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-input-value-dirty-flag
+ unsigned has_dirty_value_ : 1;
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-checked
+ unsigned is_checked_ : 1;
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-input-checked-dirty-flag
+ unsigned dirty_checkedness_ : 1;
+ unsigned is_indeterminate_ : 1;
+ unsigned is_activated_submit_ : 1;
+ unsigned autocomplete_ : 2; // AutoCompleteSetting
+ unsigned has_non_empty_list_ : 1;
+ unsigned state_restored_ : 1;
+ unsigned parsing_in_progress_ : 1;
+ unsigned can_receive_dropped_files_ : 1;
+ unsigned should_reveal_password_ : 1;
+ unsigned needs_to_update_view_value_ : 1;
+ unsigned is_placeholder_visible_ : 1;
+ unsigned has_been_password_field_ : 1;
+ Member<InputType> input_type_;
+ Member<InputTypeView> input_type_view_;
+ // The ImageLoader must be owned by this element because the loader code
+ // assumes that it lives as long as its owning element lives. If we move the
+ // loader into the ImageInput object we may delete the loader while this
+ // element lives on.
+ Member<HTMLImageLoader> image_loader_;
+ Member<ListAttributeTargetObserver> list_attribute_target_observer_;
+
+ FRIEND_TEST_ALL_PREFIXES(HTMLInputElementTest, RadioKeyDownDCHECKFailure);
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_
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
new file mode 100644
index 00000000000..099fed4b526
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ */
+
+// https://html.spec.whatwg.org/#the-input-element
+
+enum SelectionMode { "select", "start", "end", "preserve" };
+
+[
+ HTMLConstructor,
+ ActiveScriptWrappable
+] interface HTMLInputElement : HTMLElement {
+ [CEReactions, Reflect] attribute DOMString accept;
+ [CEReactions, Reflect] attribute DOMString alt;
+ [CEReactions, Reflect] attribute DOMString autocomplete;
+ [CEReactions, Reflect] attribute boolean autofocus;
+ [CEReactions, Reflect=checked] attribute boolean defaultChecked;
+ attribute boolean checked;
+ [CEReactions, Reflect] attribute DOMString dirName;
+ [CEReactions, Reflect] attribute boolean disabled;
+ [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form;
+ // The 'files' attribute is intentionally not readonly.
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=22682
+ attribute FileList? files;
+ [CEReactions] attribute DOMString formAction;
+ [CEReactions, CustomElementCallbacks] attribute DOMString formEnctype;
+ [CEReactions, CustomElementCallbacks] attribute DOMString formMethod;
+ [CEReactions, Reflect] attribute boolean formNoValidate;
+ [CEReactions, Reflect] attribute DOMString formTarget;
+ [CEReactions, CustomElementCallbacks] attribute unsigned long height;
+ attribute boolean indeterminate;
+ readonly attribute HTMLElement? list;
+ [CEReactions, Reflect] attribute DOMString max;
+ [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute long maxLength;
+ [CEReactions, Reflect] attribute DOMString min;
+ [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute long minLength;
+ [CEReactions, Reflect] attribute boolean multiple;
+ [CEReactions, Reflect] attribute DOMString name;
+ [CEReactions, Reflect] attribute DOMString pattern;
+ [CEReactions, Reflect] attribute DOMString placeholder;
+ [CEReactions, Reflect] attribute boolean readOnly;
+ [CEReactions, Reflect] attribute boolean required;
+ [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute unsigned long size;
+ [CEReactions, Reflect, URL] attribute DOMString src;
+ [CEReactions, Reflect] attribute DOMString step;
+ [CEReactions, CustomElementCallbacks] attribute DOMString type;
+ [CEReactions, Reflect=value, CustomElementCallbacks] attribute DOMString defaultValue;
+ [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute [TreatNullAs=EmptyString] DOMString value;
+ [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute Date? valueAsDate;
+ [RaisesException=Setter, CustomElementCallbacks] attribute unrestricted double valueAsNumber;
+ // Note: The spec has valueLow and valueHigh for two-valued range controls.
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=13154
+ [CEReactions, CustomElementCallbacks] attribute unsigned long width;
+
+ [RaisesException, CustomElementCallbacks] void stepUp(optional long n = 1);
+ [RaisesException, CustomElementCallbacks] void stepDown(optional long n = 1);
+
+ readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ readonly attribute DOMString validationMessage;
+ boolean checkValidity();
+ boolean reportValidity();
+ void setCustomValidity(DOMString error);
+
+ readonly attribute NodeList labels;
+
+ void select();
+ [RaisesException, ImplementedAs=selectionStartForBinding] attribute unsigned long? selectionStart;
+ [RaisesException, ImplementedAs=selectionEndForBinding] attribute unsigned long? selectionEnd;
+ [RaisesException, ImplementedAs=selectionDirectionForBinding] attribute DOMString? selectionDirection;
+ [RaisesException] void setRangeText(DOMString replacement);
+ [RaisesException] void setRangeText(DOMString replacement,
+ unsigned long start,
+ unsigned long end,
+ optional SelectionMode selectionMode = "preserve");
+ [RaisesException, ImplementedAs=setSelectionRangeForBinding]
+ void setSelectionRange(unsigned long start,
+ unsigned long end,
+ optional DOMString direction);
+
+ // obsolete members
+ // https://html.spec.whatwg.org/#HTMLInputElement-partial
+ [CEReactions, Reflect] attribute DOMString align;
+ [CEReactions, Reflect] attribute DOMString useMap;
+
+ // HTML Media Capture
+ // https://w3c.github.io/html-media-capture/#the-capture-attribute
+ [Measure, RuntimeEnabled=MediaCapture, Reflect] attribute DOMString capture;
+
+ // Non-standard APIs
+ [Reflect, MeasureAs=PrefixedDirectoryAttribute] attribute boolean webkitdirectory;
+ [Reflect, MeasureAs=IncrementalAttribute] attribute boolean incremental;
+};
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
new file mode 100644
index 00000000000..6bbe2a3568e
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc
@@ -0,0 +1,206 @@
+// Copyright 2014 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/html_input_element.h"
+
+#include <memory>
+#include "testing/gtest/include/gtest/gtest.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/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/frame/visual_viewport.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/html_body_element.h"
+#include "third_party/blink/renderer/core/html/html_html_element.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+
+namespace blink {
+
+class HTMLInputElementTest : public PageTestBase {
+ protected:
+ HTMLInputElement& TestElement() {
+ Element* element = GetDocument().getElementById("test");
+ DCHECK(element);
+ return ToHTMLInputElement(*element);
+ }
+};
+
+TEST_F(HTMLInputElementTest, FilteredDataListOptionsNoList) {
+ GetDocument().documentElement()->SetInnerHTMLFromString("<input id=test>");
+ EXPECT_TRUE(TestElement().FilteredDataListOptions().IsEmpty());
+
+ GetDocument().documentElement()->SetInnerHTMLFromString(
+ "<input id=test list=dl1><datalist id=dl1></datalist>");
+ EXPECT_TRUE(TestElement().FilteredDataListOptions().IsEmpty());
+}
+
+TEST_F(HTMLInputElementTest, FilteredDataListOptionsContain) {
+ GetDocument().documentElement()->SetInnerHTMLFromString(
+ "<input id=test value=BC list=dl2>"
+ "<datalist id=dl2>"
+ "<option>AbC DEF</option>"
+ "<option>VAX</option>"
+ "<option value=ghi>abc</option>" // Match to label, not value.
+ "</datalist>");
+ auto options = TestElement().FilteredDataListOptions();
+ EXPECT_EQ(2u, options.size());
+ EXPECT_EQ("AbC DEF", options[0]->value().Utf8());
+ EXPECT_EQ("ghi", options[1]->value().Utf8());
+
+ GetDocument().documentElement()->SetInnerHTMLFromString(
+ "<input id=test value=i list=dl2>"
+ "<datalist id=dl2>"
+ "<option>I</option>"
+ "<option>&#x0130;</option>" // LATIN CAPITAL LETTER I WITH DOT ABOVE
+ "<option>&#xFF49;</option>" // FULLWIDTH LATIN SMALL LETTER I
+ "</datalist>");
+ options = TestElement().FilteredDataListOptions();
+ EXPECT_EQ(2u, options.size());
+ EXPECT_EQ("I", options[0]->value().Utf8());
+ EXPECT_EQ(0x0130, options[1]->value()[0]);
+}
+
+TEST_F(HTMLInputElementTest, FilteredDataListOptionsForMultipleEmail) {
+ GetDocument().documentElement()->SetInnerHTMLFromString(R"HTML(
+ <input id=test value='foo@example.com, tkent' list=dl3 type=email
+ multiple>
+ <datalist id=dl3>
+ <option>keishi@chromium.org</option>
+ <option>tkent@chromium.org</option>
+ </datalist>
+ )HTML");
+ auto options = TestElement().FilteredDataListOptions();
+ EXPECT_EQ(1u, options.size());
+ EXPECT_EQ("tkent@chromium.org", options[0]->value().Utf8());
+}
+
+TEST_F(HTMLInputElementTest, create) {
+ auto* input = HTMLInputElement::Create(GetDocument(),
+ CreateElementFlags::ByCreateElement());
+ EXPECT_NE(nullptr, input->UserAgentShadowRoot());
+
+ input =
+ HTMLInputElement::Create(GetDocument(), CreateElementFlags::ByParser());
+ EXPECT_EQ(nullptr, input->UserAgentShadowRoot());
+ input->ParserSetAttributes(Vector<Attribute>());
+ EXPECT_NE(nullptr, input->UserAgentShadowRoot());
+}
+
+TEST_F(HTMLInputElementTest, NoAssertWhenMovedInNewDocument) {
+ Document* document_without_frame = Document::CreateForTest();
+ EXPECT_EQ(nullptr, document_without_frame->GetPage());
+ HTMLHtmlElement* html = HTMLHtmlElement::Create(*document_without_frame);
+ html->AppendChild(HTMLBodyElement::Create(*document_without_frame));
+
+ // Create an input element with type "range" inside a document without frame.
+ ToHTMLBodyElement(html->firstChild())
+ ->SetInnerHTMLFromString("<input type='range' />");
+ document_without_frame->AppendChild(html);
+
+ std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create();
+ auto& document = page_holder->GetDocument();
+ EXPECT_NE(nullptr, document.GetPage());
+
+ // Put the input element inside a document with frame.
+ document.body()->AppendChild(document_without_frame->body()->firstChild());
+
+ // Remove the input element and all refs to it so it gets deleted before the
+ // document.
+ // The assert in |EventHandlerRegistry::updateEventHandlerTargets()| should
+ // not be triggered.
+ document.body()->RemoveChild(document.body()->firstChild());
+}
+
+TEST_F(HTMLInputElementTest, DefaultToolTip) {
+ auto* input_without_form =
+ HTMLInputElement::Create(GetDocument(), CreateElementFlags());
+ input_without_form->SetBooleanAttribute(HTMLNames::requiredAttr, true);
+ GetDocument().body()->AppendChild(input_without_form);
+ EXPECT_EQ("<<ValidationValueMissing>>", input_without_form->DefaultToolTip());
+
+ HTMLFormElement* form = HTMLFormElement::Create(GetDocument());
+ GetDocument().body()->AppendChild(form);
+ auto* input_with_form =
+ HTMLInputElement::Create(GetDocument(), CreateElementFlags());
+ input_with_form->SetBooleanAttribute(HTMLNames::requiredAttr, true);
+ form->AppendChild(input_with_form);
+ EXPECT_EQ("<<ValidationValueMissing>>", input_with_form->DefaultToolTip());
+
+ form->SetBooleanAttribute(HTMLNames::novalidateAttr, true);
+ EXPECT_EQ(String(), input_with_form->DefaultToolTip());
+}
+
+// crbug.com/589838
+TEST_F(HTMLInputElementTest, ImageTypeCrash) {
+ auto* input = HTMLInputElement::Create(GetDocument(), CreateElementFlags());
+ input->setAttribute(HTMLNames::typeAttr, "image");
+ input->EnsureFallbackContent();
+ // Make sure ensurePrimaryContent() recreates UA shadow tree, and updating
+ // |value| doesn't crash.
+ input->EnsurePrimaryContent();
+ input->setAttribute(HTMLNames::valueAttr, "aaa");
+}
+
+TEST_F(HTMLInputElementTest, RadioKeyDownDCHECKFailure) {
+ // crbug.com/697286
+ GetDocument().body()->SetInnerHTMLFromString(
+ "<input type=radio name=g><input type=radio name=g>");
+ HTMLInputElement& radio1 =
+ ToHTMLInputElement(*GetDocument().body()->firstChild());
+ HTMLInputElement& radio2 = ToHTMLInputElement(*radio1.nextSibling());
+ radio1.focus();
+ // Make layout-dirty.
+ radio2.setAttribute(HTMLNames::styleAttr, "position:fixed");
+ KeyboardEventInit init;
+ init.setKey("ArrowRight");
+ radio1.DefaultEventHandler(new KeyboardEvent("keydown", init));
+ EXPECT_EQ(GetDocument().ActiveElement(), &radio2);
+}
+
+TEST_F(HTMLInputElementTest, DateTimeChooserSizeParamRespectsScale) {
+ GetDocument().SetCompatibilityMode(Document::kQuirksMode);
+ GetDocument().View()->GetFrame().GetPage()->GetVisualViewport().SetScale(2.f);
+ GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='date' style='width:200px;height:50px' />");
+ GetDocument().View()->UpdateAllLifecyclePhases();
+ HTMLInputElement* input =
+ ToHTMLInputElement(GetDocument().body()->firstChild());
+
+ DateTimeChooserParameters params;
+ bool success = input->SetupDateTimeChooserParameters(params);
+ EXPECT_TRUE(success);
+ EXPECT_EQ("date", params.type);
+ EXPECT_EQ(IntRect(16, 16, 400, 100), params.anchor_rect_in_screen);
+}
+
+TEST_F(HTMLInputElementTest, StepDownOverflow) {
+ auto* input = HTMLInputElement::Create(GetDocument(), CreateElementFlags());
+ input->setAttribute(HTMLNames::typeAttr, "date");
+ input->setAttribute(HTMLNames::minAttr, "2010-02-10");
+ input->setAttribute(HTMLNames::stepAttr, "9223372036854775556");
+ // InputType::applyStep() should not pass an out-of-range value to
+ // setValueAsDecimal, and WTF::msToYear() should not cause a DCHECK failure.
+ input->stepDown(1, ASSERT_NO_EXCEPTION);
+}
+
+TEST_F(HTMLInputElementTest, CheckboxHasNoShadowRoot) {
+ GetDocument().body()->SetInnerHTMLFromString("<input type='checkbox' />");
+ HTMLInputElement* input =
+ ToHTMLInputElement(GetDocument().body()->firstChild());
+ EXPECT_EQ(nullptr, input->UserAgentShadowRoot());
+}
+
+TEST_F(HTMLInputElementTest, ChangingInputTypeCausesShadowRootToBeCreated) {
+ GetDocument().body()->SetInnerHTMLFromString("<input type='checkbox' />");
+ HTMLInputElement* input =
+ ToHTMLInputElement(GetDocument().body()->firstChild());
+ EXPECT_EQ(nullptr, input->UserAgentShadowRoot());
+ input->setAttribute(HTMLNames::typeAttr, "text");
+ EXPECT_NE(nullptr, input->UserAgentShadowRoot());
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..ea36f0bd3c8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc
@@ -0,0 +1,250 @@
+/*
+ * 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, 2010 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/html_label_element.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"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/selection_controller.h"
+#include "third_party/blink/renderer/core/editing/visible_selection.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/frame/use_counter.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+#include "third_party/blink/renderer/core/html/forms/listed_element.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/layout/layout_object.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline HTMLLabelElement::HTMLLabelElement(Document& document)
+ : HTMLElement(labelTag, document), processing_click_(false) {}
+
+HTMLLabelElement* HTMLLabelElement::Create(Document& document) {
+ return new HTMLLabelElement(document);
+}
+
+LabelableElement* HTMLLabelElement::control() const {
+ const AtomicString& control_id = getAttribute(forAttr);
+ if (control_id.IsNull()) {
+ // Search the children and descendants of the label element for a form
+ // element.
+ // per http://dev.w3.org/html5/spec/Overview.html#the-label-element
+ // the form element must be "labelable form-associated element".
+ for (LabelableElement& element :
+ Traversal<LabelableElement>::DescendantsOf(*this)) {
+ if (element.SupportLabels()) {
+ if (!element.IsFormControlElement()) {
+ UseCounter::Count(
+ GetDocument(),
+ WebFeature::kHTMLLabelElementControlForNonFormAssociatedElement);
+ }
+ return &element;
+ }
+ }
+ return nullptr;
+ }
+
+ if (!IsInTreeScope())
+ return nullptr;
+
+ if (Element* element = GetTreeScope().getElementById(control_id)) {
+ if (IsLabelableElement(*element) &&
+ ToLabelableElement(*element).SupportLabels()) {
+ if (!element->IsFormControlElement()) {
+ UseCounter::Count(
+ GetDocument(),
+ WebFeature::kHTMLLabelElementControlForNonFormAssociatedElement);
+ }
+ return ToLabelableElement(element);
+ }
+ }
+
+ return nullptr;
+}
+
+HTMLFormElement* HTMLLabelElement::form() const {
+ if (LabelableElement* control = this->control()) {
+ return control->IsFormControlElement()
+ ? ToHTMLFormControlElement(control)->Form()
+ : nullptr;
+ }
+ return nullptr;
+}
+
+void HTMLLabelElement::SetActive(bool down) {
+ if (down != IsActive()) {
+ // Update our status first.
+ HTMLElement::SetActive(down);
+ }
+
+ // Also update our corresponding control.
+ HTMLElement* control_element = control();
+ if (control_element && control_element->IsActive() != IsActive())
+ control_element->SetActive(IsActive());
+}
+
+void HTMLLabelElement::SetHovered(bool over) {
+ if (over != IsHovered()) {
+ // Update our status first.
+ HTMLElement::SetHovered(over);
+ }
+
+ // Also update our corresponding control.
+ HTMLElement* element = control();
+ if (element && element->IsHovered() != IsHovered())
+ element->SetHovered(IsHovered());
+}
+
+bool HTMLLabelElement::IsInteractiveContent() const {
+ return true;
+}
+
+bool HTMLLabelElement::IsInInteractiveContent(Node* node) const {
+ if (!IsShadowIncludingInclusiveAncestorOf(node))
+ return false;
+ while (node && this != node) {
+ if (node->IsHTMLElement() && ToHTMLElement(node)->IsInteractiveContent())
+ return true;
+ node = node->ParentOrShadowHostNode();
+ }
+ return false;
+}
+
+void HTMLLabelElement::DefaultEventHandler(Event* evt) {
+ if (evt->type() == EventTypeNames::click && !processing_click_) {
+ HTMLElement* element = control();
+
+ // If we can't find a control or if the control received the click
+ // event, then there's no need for us to do anything.
+ if (!element ||
+ (evt->target() && element->IsShadowIncludingInclusiveAncestorOf(
+ evt->target()->ToNode())))
+ return;
+
+ if (evt->target() && IsInInteractiveContent(evt->target()->ToNode()))
+ return;
+
+ // Behaviour of label element is as follows:
+ // - If there is double click, two clicks will be passed to control
+ // element. Control element will *not* be focused.
+ // - If there is selection of label element by dragging, no click
+ // event is passed. Also, no focus on control element.
+ // - If there is already a selection on label element and then label
+ // is clicked, then click event is passed to control element and
+ // control element is focused.
+
+ bool is_label_text_selected = false;
+
+ // If the click is not simulated and the text of the label element
+ // is selected by dragging over it, then return without passing the
+ // 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()) {
+ if (LocalFrame* frame = GetDocument().GetFrame()) {
+ // Check if there is a selection and click is not on the
+ // selection.
+ if (GetLayoutObject() && GetLayoutObject()->IsSelectable() &&
+ frame->Selection()
+ .ComputeVisibleSelectionInDOMTreeDeprecated()
+ .IsRange() &&
+ !frame->GetEventHandler()
+ .GetSelectionController()
+ .MouseDownWasSingleClickInSelection() &&
+ evt->target()->ToNode()->CanStartSelection())
+ is_label_text_selected = true;
+ // If selection is there and is single click i.e. text is
+ // selected by dragging over label text, then return.
+ // Click count >=2, meaning double click or triple click,
+ // 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)
+ return;
+ }
+ }
+
+ processing_click_ = true;
+
+ GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+ if (element->IsMouseFocusable()) {
+ // If the label is *not* selected, or if the click happened on
+ // selection of label, only then focus the control element.
+ // In case of double click or triple click, selection will be there,
+ // so do not focus the control element.
+ if (!is_label_text_selected) {
+ element->focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
+ kWebFocusTypeMouse, nullptr));
+ }
+ }
+
+ // Click the corresponding control.
+ element->DispatchSimulatedClick(evt);
+
+ processing_click_ = false;
+
+ evt->SetDefaultHandled();
+ }
+
+ HTMLElement::DefaultEventHandler(evt);
+}
+
+bool HTMLLabelElement::HasActivationBehavior() const {
+ return true;
+}
+
+bool HTMLLabelElement::WillRespondToMouseClickEvents() {
+ if (control() && control()->WillRespondToMouseClickEvents())
+ return true;
+
+ return HTMLElement::WillRespondToMouseClickEvents();
+}
+
+void HTMLLabelElement::focus(const FocusParams& params) {
+ GetDocument().UpdateStyleAndLayoutTreeForNode(this);
+ if (IsFocusable()) {
+ HTMLElement::focus(params);
+ return;
+ }
+ // To match other browsers, always restore previous selection.
+ if (HTMLElement* element = control()) {
+ element->focus(FocusParams(SelectionBehaviorOnFocus::kRestore, params.type,
+ params.source_capabilities, params.options));
+ }
+}
+
+void HTMLLabelElement::AccessKeyAction(bool send_mouse_events) {
+ if (HTMLElement* element = control())
+ element->AccessKeyAction(send_mouse_events);
+ else
+ HTMLElement::AccessKeyAction(send_mouse_events);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_label_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.h
new file mode 100644
index 00000000000..9bfb702444a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LABEL_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LABEL_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+
+namespace blink {
+
+class LabelableElement;
+
+class CORE_EXPORT HTMLLabelElement final : public HTMLElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLLabelElement* Create(Document&);
+ LabelableElement* control() const;
+ HTMLFormElement* form() const;
+
+ bool WillRespondToMouseClickEvents() override;
+
+ private:
+ explicit HTMLLabelElement(Document&);
+ bool IsInInteractiveContent(Node*) const;
+
+ bool IsInteractiveContent() const override;
+ void AccessKeyAction(bool send_mouse_events) override;
+
+ // Overridden to update the hover/active state of the corresponding control.
+ void SetActive(bool = true) override;
+ void SetHovered(bool = true) override;
+
+ // Overridden to either click() or focus() the corresponding control.
+ void DefaultEventHandler(Event*) override;
+ bool HasActivationBehavior() const override;
+
+ void focus(const FocusParams&) override;
+
+ bool processing_click_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LABEL_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_label_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.idl
new file mode 100644
index 00000000000..254cbd9a6e8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.idl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.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.
+ */
+
+// https://html.spec.whatwg.org/#the-label-element
+[HTMLConstructor]
+interface HTMLLabelElement : HTMLElement {
+ readonly attribute HTMLFormElement? form;
+ [CEReactions, Reflect=for] attribute DOMString htmlFor;
+ readonly attribute HTMLElement? control;
+};
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.cc
new file mode 100644
index 00000000000..4317588c878
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.cc
@@ -0,0 +1,82 @@
+/*
+ * 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, 2010 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/html_legend_element.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/html/forms/html_field_set_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline HTMLLegendElement::HTMLLegendElement(Document& document)
+ : HTMLElement(legendTag, document) {}
+
+DEFINE_NODE_FACTORY(HTMLLegendElement)
+
+HTMLFormControlElement* HTMLLegendElement::AssociatedControl() {
+ // Check if there's a fieldset belonging to this legend.
+ HTMLFieldSetElement* fieldset =
+ Traversal<HTMLFieldSetElement>::FirstAncestor(*this);
+ if (!fieldset)
+ return nullptr;
+
+ // Find first form element inside the fieldset that is not a legend element.
+ // FIXME: Should we consider tabindex?
+ return Traversal<HTMLFormControlElement>::Next(*fieldset, fieldset);
+}
+
+void HTMLLegendElement::focus(const FocusParams& params) {
+ GetDocument().UpdateStyleAndLayoutTreeForNode(this);
+ if (IsFocusable()) {
+ Element::focus(params);
+ return;
+ }
+
+ // To match other browsers' behavior, never restore previous selection.
+ if (HTMLFormControlElement* control = AssociatedControl()) {
+ control->focus(FocusParams(SelectionBehaviorOnFocus::kReset, params.type,
+ params.source_capabilities, params.options));
+ }
+}
+
+void HTMLLegendElement::AccessKeyAction(bool send_mouse_events) {
+ if (HTMLFormControlElement* control = AssociatedControl())
+ control->AccessKeyAction(send_mouse_events);
+}
+
+HTMLFormElement* HTMLLegendElement::form() const {
+ // According to the specification, If the legend has a fieldset element as
+ // its parent, then the form attribute must return the same value as the
+ // form attribute on that fieldset element. Otherwise, it must return null.
+ if (auto* fieldset = ToHTMLFieldSetElementOrNull(parentNode()))
+ return fieldset->formOwner();
+ return nullptr;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.h
new file mode 100644
index 00000000000..c69a718b330
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LEGEND_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LEGEND_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/html/html_element.h"
+
+namespace blink {
+
+class HTMLFormControlElement;
+
+class HTMLLegendElement final : public HTMLElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ DECLARE_NODE_FACTORY(HTMLLegendElement);
+
+ HTMLFormElement* form() const;
+
+ private:
+ explicit HTMLLegendElement(Document&);
+
+ // Control in the legend's fieldset that gets focus and access key.
+ HTMLFormControlElement* AssociatedControl();
+
+ void AccessKeyAction(bool send_mouse_events) override;
+ void focus(const FocusParams&) override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LEGEND_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.idl
new file mode 100644
index 00000000000..909b3ab17f3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.idl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.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.
+ */
+
+// https://html.spec.whatwg.org/#the-legend-element
+[HTMLConstructor]
+interface HTMLLegendElement : HTMLElement {
+ readonly attribute HTMLFormElement? form;
+
+ // obsolete members
+ // https://html.spec.whatwg.org/#HTMLLegendElement-partial
+ [CEReactions, Reflect] attribute DOMString align;
+};
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
new file mode 100644
index 00000000000..37bebed4f77
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc
@@ -0,0 +1,165 @@
+/*
+ * 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, 2008, 2010 Apple Inc. All rights
+ * reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/html_opt_group_element.h"
+
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/editing/editing_utilities.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/core/html/html_slot_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/platform/wtf/std_lib_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline HTMLOptGroupElement::HTMLOptGroupElement(Document& document)
+ : HTMLElement(optgroupTag, document) {}
+
+// An explicit empty destructor should be in HTMLOptGroupElement.cpp, because
+// if an implicit destructor is used or an empty destructor is defined in
+// HTMLOptGroupElement.h, when including HTMLOptGroupElement.h,
+// msvc tries to expand the destructor and causes
+// a compile error because of lack of ComputedStyle definition.
+HTMLOptGroupElement::~HTMLOptGroupElement() = default;
+
+HTMLOptGroupElement* HTMLOptGroupElement::Create(Document& document) {
+ HTMLOptGroupElement* opt_group_element = new HTMLOptGroupElement(document);
+ opt_group_element->EnsureUserAgentShadowRoot();
+ return opt_group_element;
+}
+
+// static
+bool HTMLOptGroupElement::CanAssignToOptGroupSlot(const Node& node) {
+ return node.HasTagName(optionTag) || node.HasTagName(hrTag);
+}
+
+bool HTMLOptGroupElement::IsDisabledFormControl() const {
+ return FastHasAttribute(disabledAttr);
+}
+
+void HTMLOptGroupElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ HTMLElement::ParseAttribute(params);
+
+ if (params.name == disabledAttr) {
+ PseudoStateChanged(CSSSelector::kPseudoDisabled);
+ PseudoStateChanged(CSSSelector::kPseudoEnabled);
+ } else if (params.name == labelAttr) {
+ UpdateGroupLabel();
+ }
+}
+
+bool HTMLOptGroupElement::SupportsFocus() const {
+ HTMLSelectElement* select = OwnerSelectElement();
+ if (select && select->UsesMenuList())
+ return false;
+ return HTMLElement::SupportsFocus();
+}
+
+bool HTMLOptGroupElement::MatchesEnabledPseudoClass() const {
+ return !IsDisabledFormControl();
+}
+
+Node::InsertionNotificationRequest HTMLOptGroupElement::InsertedInto(
+ ContainerNode* insertion_point) {
+ HTMLElement::InsertedInto(insertion_point);
+ if (HTMLSelectElement* select = OwnerSelectElement()) {
+ if (insertion_point == select)
+ select->OptGroupInsertedOrRemoved(*this);
+ }
+ return kInsertionDone;
+}
+
+void HTMLOptGroupElement::RemovedFrom(ContainerNode* insertion_point) {
+ if (auto* select = ToHTMLSelectElementOrNull(*insertion_point)) {
+ if (!parentNode())
+ select->OptGroupInsertedOrRemoved(*this);
+ }
+ HTMLElement::RemovedFrom(insertion_point);
+}
+
+String HTMLOptGroupElement::GroupLabelText() const {
+ String item_text = getAttribute(labelAttr);
+
+ // In WinIE, leading and trailing whitespace is ignored in options and
+ // optgroups. We match this behavior.
+ item_text = item_text.StripWhiteSpace();
+ // We want to collapse our whitespace too. This will match other browsers.
+ item_text = item_text.SimplifyWhiteSpace();
+
+ return item_text;
+}
+
+HTMLSelectElement* HTMLOptGroupElement::OwnerSelectElement() const {
+ // TODO(tkent): We should return only the parent <select>.
+ return Traversal<HTMLSelectElement>::FirstAncestor(*this);
+}
+
+String HTMLOptGroupElement::DefaultToolTip() const {
+ if (HTMLSelectElement* select = OwnerSelectElement())
+ return select->DefaultToolTip();
+ return String();
+}
+
+void HTMLOptGroupElement::AccessKeyAction(bool) {
+ HTMLSelectElement* select = OwnerSelectElement();
+ // send to the parent to bring focus to the list box
+ if (select && !select->IsFocused())
+ select->AccessKeyAction(false);
+}
+
+void HTMLOptGroupElement::DidAddUserAgentShadowRoot(ShadowRoot& root) {
+ DEFINE_STATIC_LOCAL(AtomicString, label_padding, ("0 2px 1px 2px"));
+ DEFINE_STATIC_LOCAL(AtomicString, label_min_height, ("1.2em"));
+ HTMLDivElement* label = HTMLDivElement::Create(GetDocument());
+ label->setAttribute(roleAttr, AtomicString("group"));
+ label->setAttribute(aria_labelAttr, AtomicString());
+ label->SetInlineStyleProperty(CSSPropertyPadding, label_padding);
+ label->SetInlineStyleProperty(CSSPropertyMinHeight, label_min_height);
+ label->SetIdAttribute(ShadowElementNames::OptGroupLabel());
+ root.AppendChild(label);
+
+ root.AppendChild(
+ HTMLSlotElement::CreateUserAgentCustomAssignSlot(GetDocument()));
+}
+
+void HTMLOptGroupElement::UpdateGroupLabel() {
+ const String& label_text = GroupLabelText();
+ HTMLDivElement& label = OptGroupLabelElement();
+ label.setTextContent(label_text);
+ label.setAttribute(aria_labelAttr, AtomicString(label_text));
+}
+
+HTMLDivElement& HTMLOptGroupElement::OptGroupLabelElement() const {
+ return *ToHTMLDivElementOrDie(UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::OptGroupLabel()));
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..52cb07a2a04
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPT_GROUP_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPT_GROUP_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+
+namespace blink {
+
+class HTMLSelectElement;
+class HTMLDivElement;
+
+class CORE_EXPORT HTMLOptGroupElement final : public HTMLElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLOptGroupElement* Create(Document&);
+
+ bool IsDisabledFormControl() const override;
+ String DefaultToolTip() const override;
+ HTMLSelectElement* OwnerSelectElement() const;
+
+ String GroupLabelText() const;
+ HTMLDivElement& OptGroupLabelElement() const;
+
+ // Used for slot assignment.
+ static bool CanAssignToOptGroupSlot(const Node&);
+
+ private:
+ explicit HTMLOptGroupElement(Document&);
+ ~HTMLOptGroupElement() override;
+
+ bool SupportsFocus() const override;
+ void ParseAttribute(const AttributeModificationParams&) override;
+ void AccessKeyAction(bool send_mouse_events) override;
+ void DidAddUserAgentShadowRoot(ShadowRoot&) override;
+ bool MatchesEnabledPseudoClass() const override;
+ InsertionNotificationRequest InsertedInto(ContainerNode*) override;
+ void RemovedFrom(ContainerNode*) override;
+
+ void UpdateGroupLabel();
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPT_GROUP_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.idl
new file mode 100644
index 00000000000..91f7a64eff6
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.idl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+// https://html.spec.whatwg.org/#the-optgroup-element
+[HTMLConstructor]
+interface HTMLOptGroupElement : HTMLElement {
+ [CEReactions, Reflect] attribute boolean disabled;
+ [CEReactions, Reflect] attribute DOMString label;
+};
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
new file mode 100644
index 00000000000..c12a3710774
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2001 Dirk Mueller (mueller@kde.org)
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
+ * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Motorola Mobility, Inc. All rights reserved.
+ *
+ * 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/html_option_element.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/dom/ax_object_cache.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/dom/node_traversal.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_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/layout/layout_theme.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+HTMLOptionElement::HTMLOptionElement(Document& document)
+ : HTMLElement(optionTag, document), is_selected_(false) {}
+
+// An explicit empty destructor should be in HTMLOptionElement.cpp, because
+// if an implicit destructor is used or an empty destructor is defined in
+// HTMLOptionElement.h, when including HTMLOptionElement.h,
+// msvc tries to expand the destructor and causes
+// a compile error because of lack of ComputedStyle definition.
+HTMLOptionElement::~HTMLOptionElement() = default;
+
+HTMLOptionElement* HTMLOptionElement::Create(Document& document) {
+ HTMLOptionElement* option = new HTMLOptionElement(document);
+ option->EnsureUserAgentShadowRoot();
+ return option;
+}
+
+HTMLOptionElement* HTMLOptionElement::CreateForJSConstructor(
+ Document& document,
+ const String& data,
+ const AtomicString& value,
+ bool default_selected,
+ bool selected,
+ ExceptionState& exception_state) {
+ HTMLOptionElement* element = new HTMLOptionElement(document);
+ element->EnsureUserAgentShadowRoot();
+ if (!data.IsEmpty()) {
+ element->AppendChild(Text::Create(document, data), exception_state);
+ if (exception_state.HadException())
+ return nullptr;
+ }
+
+ if (!value.IsNull())
+ element->setValue(value);
+ if (default_selected)
+ element->setAttribute(selectedAttr, g_empty_atom);
+ element->SetSelected(selected);
+
+ return element;
+}
+
+void HTMLOptionElement::AttachLayoutTree(AttachContext& context) {
+ AttachContext option_context(context);
+ if (!GetNonAttachedStyle() && ParentComputedStyle()) {
+ if (HTMLSelectElement* select = OwnerSelectElement())
+ select->UpdateListOnLayoutObject();
+ SetNonAttachedStyle(StyleForLayoutObject());
+ }
+ HTMLElement::AttachLayoutTree(option_context);
+}
+
+bool HTMLOptionElement::SupportsFocus() const {
+ HTMLSelectElement* select = OwnerSelectElement();
+ if (select && select->UsesMenuList())
+ return false;
+ return HTMLElement::SupportsFocus();
+}
+
+bool HTMLOptionElement::MatchesDefaultPseudoClass() const {
+ return FastHasAttribute(selectedAttr);
+}
+
+bool HTMLOptionElement::MatchesEnabledPseudoClass() const {
+ return !IsDisabledFormControl();
+}
+
+String HTMLOptionElement::DisplayLabel() const {
+ Document& document = GetDocument();
+ String text;
+
+ // WinIE does not use the label attribute, so as a quirk, we ignore it.
+ if (!document.InQuirksMode())
+ text = FastGetAttribute(labelAttr);
+
+ // FIXME: The following treats an element with the label attribute set to
+ // the empty string the same as an element with no label attribute at all.
+ // Is that correct? If it is, then should the label function work the same
+ // way?
+ if (text.IsEmpty())
+ text = CollectOptionInnerText();
+
+ return text.StripWhiteSpace(IsHTMLSpace<UChar>)
+ .SimplifyWhiteSpace(IsHTMLSpace<UChar>);
+}
+
+String HTMLOptionElement::text() const {
+ return CollectOptionInnerText()
+ .StripWhiteSpace(IsHTMLSpace<UChar>)
+ .SimplifyWhiteSpace(IsHTMLSpace<UChar>);
+}
+
+void HTMLOptionElement::setText(const String& text) {
+ // Changing the text causes a recalc of a select's items, which will reset the
+ // selected index to the first item if the select is single selection with a
+ // menu list. We attempt to preserve the selected item.
+ HTMLSelectElement* select = OwnerSelectElement();
+ bool select_is_menu_list = select && select->UsesMenuList();
+ int old_selected_index = select_is_menu_list ? select->selectedIndex() : -1;
+
+ setTextContent(text);
+
+ if (select_is_menu_list && select->selectedIndex() != old_selected_index)
+ select->setSelectedIndex(old_selected_index);
+}
+
+void HTMLOptionElement::AccessKeyAction(bool) {
+ if (HTMLSelectElement* select = OwnerSelectElement())
+ select->SelectOptionByAccessKey(this);
+}
+
+int HTMLOptionElement::index() const {
+ // It would be faster to cache the index, but harder to get it right in all
+ // cases.
+
+ HTMLSelectElement* select_element = OwnerSelectElement();
+ if (!select_element)
+ return 0;
+
+ int option_index = 0;
+ for (auto* const option : select_element->GetOptionList()) {
+ if (option == this)
+ return option_index;
+ ++option_index;
+ }
+
+ return 0;
+}
+
+int HTMLOptionElement::ListIndex() const {
+ if (HTMLSelectElement* select_element = OwnerSelectElement())
+ return select_element->ListIndexForOption(*this);
+ return -1;
+}
+
+void HTMLOptionElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ const QualifiedName& name = params.name;
+ if (name == valueAttr) {
+ if (HTMLDataListElement* data_list = OwnerDataListElement())
+ data_list->OptionElementChildrenChanged();
+ } else if (name == disabledAttr) {
+ if (params.old_value.IsNull() != params.new_value.IsNull()) {
+ PseudoStateChanged(CSSSelector::kPseudoDisabled);
+ PseudoStateChanged(CSSSelector::kPseudoEnabled);
+ if (LayoutObject* o = GetLayoutObject())
+ o->InvalidateIfControlStateChanged(kEnabledControlState);
+ }
+ } else if (name == selectedAttr) {
+ if (params.old_value.IsNull() != params.new_value.IsNull() && !is_dirty_)
+ SetSelected(!params.new_value.IsNull());
+ PseudoStateChanged(CSSSelector::kPseudoDefault);
+ } else if (name == labelAttr) {
+ UpdateLabel();
+ } else {
+ HTMLElement::ParseAttribute(params);
+ }
+}
+
+String HTMLOptionElement::value() const {
+ const AtomicString& value = FastGetAttribute(valueAttr);
+ if (!value.IsNull())
+ return value;
+ return CollectOptionInnerText()
+ .StripWhiteSpace(IsHTMLSpace<UChar>)
+ .SimplifyWhiteSpace(IsHTMLSpace<UChar>);
+}
+
+void HTMLOptionElement::setValue(const AtomicString& value) {
+ setAttribute(valueAttr, value);
+}
+
+bool HTMLOptionElement::Selected() const {
+ return is_selected_;
+}
+
+void HTMLOptionElement::SetSelected(bool selected) {
+ if (is_selected_ == selected)
+ return;
+
+ SetSelectedState(selected);
+
+ if (HTMLSelectElement* select = OwnerSelectElement())
+ select->OptionSelectionStateChanged(this, selected);
+}
+
+bool HTMLOptionElement::selectedForBinding() const {
+ return Selected();
+}
+
+void HTMLOptionElement::setSelectedForBinding(bool selected) {
+ bool was_selected = is_selected_;
+ SetSelected(selected);
+
+ // As of December 2015, the HTML specification says the dirtiness becomes
+ // true by |selected| setter unconditionally. However it caused a real bug,
+ // crbug.com/570367, and is not compatible with other browsers.
+ // Firefox seems not to set dirtiness if an option is owned by a select
+ // element and selectedness is not changed.
+ if (OwnerSelectElement() && was_selected == is_selected_)
+ return;
+
+ is_dirty_ = true;
+}
+
+void HTMLOptionElement::SetSelectedState(bool selected) {
+ if (is_selected_ == selected)
+ return;
+
+ is_selected_ = selected;
+ PseudoStateChanged(CSSSelector::kPseudoChecked);
+
+ if (HTMLSelectElement* select = OwnerSelectElement()) {
+ select->InvalidateSelectedItems();
+
+ if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
+ // If there is a layoutObject (most common), fire accessibility
+ // 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()) {
+ cache->ListboxOptionStateChanged(this);
+ cache->ListboxSelectedChildrenChanged(select);
+ }
+ }
+ }
+}
+
+void HTMLOptionElement::SetDirty(bool value) {
+ is_dirty_ = value;
+}
+
+void HTMLOptionElement::ChildrenChanged(const ChildrenChange& change) {
+ if (HTMLDataListElement* data_list = OwnerDataListElement())
+ data_list->OptionElementChildrenChanged();
+ else if (HTMLSelectElement* select = OwnerSelectElement())
+ select->OptionElementChildrenChanged(*this);
+ UpdateLabel();
+ HTMLElement::ChildrenChanged(change);
+}
+
+HTMLDataListElement* HTMLOptionElement::OwnerDataListElement() const {
+ return Traversal<HTMLDataListElement>::FirstAncestor(*this);
+}
+
+HTMLSelectElement* HTMLOptionElement::OwnerSelectElement() const {
+ if (!parentNode())
+ return nullptr;
+ if (auto* select = ToHTMLSelectElementOrNull(*parentNode()))
+ return select;
+ if (IsHTMLOptGroupElement(*parentNode()))
+ return ToHTMLSelectElementOrNull(parentNode()->parentNode());
+ return nullptr;
+}
+
+String HTMLOptionElement::label() const {
+ const AtomicString& label = FastGetAttribute(labelAttr);
+ if (!label.IsNull())
+ return label;
+ return CollectOptionInnerText()
+ .StripWhiteSpace(IsHTMLSpace<UChar>)
+ .SimplifyWhiteSpace(IsHTMLSpace<UChar>);
+}
+
+void HTMLOptionElement::setLabel(const AtomicString& label) {
+ setAttribute(labelAttr, label);
+}
+
+String HTMLOptionElement::TextIndentedToRespectGroupLabel() const {
+ ContainerNode* parent = parentNode();
+ if (parent && IsHTMLOptGroupElement(*parent))
+ return " " + DisplayLabel();
+ return DisplayLabel();
+}
+
+bool HTMLOptionElement::OwnElementDisabled() const {
+ return FastHasAttribute(disabledAttr);
+}
+
+bool HTMLOptionElement::IsDisabledFormControl() const {
+ if (OwnElementDisabled())
+ return true;
+ if (Element* parent = parentElement())
+ return IsHTMLOptGroupElement(*parent) && parent->IsDisabledFormControl();
+ return false;
+}
+
+String HTMLOptionElement::DefaultToolTip() const {
+ if (HTMLSelectElement* select = OwnerSelectElement())
+ return select->DefaultToolTip();
+ return String();
+}
+
+Node::InsertionNotificationRequest HTMLOptionElement::InsertedInto(
+ ContainerNode* insertion_point) {
+ HTMLElement::InsertedInto(insertion_point);
+ if (HTMLSelectElement* select = OwnerSelectElement()) {
+ if (insertion_point == select || (IsHTMLOptGroupElement(*insertion_point) &&
+ insertion_point->parentNode() == select))
+ select->OptionInserted(*this, is_selected_);
+ }
+ return kInsertionDone;
+}
+
+void HTMLOptionElement::RemovedFrom(ContainerNode* insertion_point) {
+ if (auto* select = ToHTMLSelectElementOrNull(*insertion_point)) {
+ if (!parentNode() || IsHTMLOptGroupElement(*parentNode()))
+ select->OptionRemoved(*this);
+ } else if (IsHTMLOptGroupElement(*insertion_point)) {
+ if (auto* select = ToHTMLSelectElementOrNull(insertion_point->parentNode()))
+ select->OptionRemoved(*this);
+ }
+ HTMLElement::RemovedFrom(insertion_point);
+}
+
+String HTMLOptionElement::CollectOptionInnerText() const {
+ StringBuilder text;
+ for (Node* node = firstChild(); node;) {
+ if (node->IsTextNode())
+ text.Append(node->nodeValue());
+ // Text nodes inside script elements are not part of the option text.
+ if (node->IsElementNode() && ToElement(node)->IsScriptElement())
+ node = NodeTraversal::NextSkippingChildren(*node, this);
+ else
+ node = NodeTraversal::Next(*node, this);
+ }
+ return text.ToString();
+}
+
+HTMLFormElement* HTMLOptionElement::form() const {
+ if (HTMLSelectElement* select_element = OwnerSelectElement())
+ return select_element->formOwner();
+
+ return nullptr;
+}
+
+void HTMLOptionElement::DidAddUserAgentShadowRoot(ShadowRoot& root) {
+ UpdateLabel();
+}
+
+void HTMLOptionElement::UpdateLabel() {
+ if (ShadowRoot* root = UserAgentShadowRoot())
+ root->setTextContent(DisplayLabel());
+}
+
+bool HTMLOptionElement::SpatialNavigationFocused() const {
+ HTMLSelectElement* select = OwnerSelectElement();
+ if (!select || !select->IsFocused())
+ return false;
+ return select->SpatialNavigationFocusedOption() == this;
+}
+
+bool HTMLOptionElement::IsDisplayNone() const {
+ const ComputedStyle* style = GetComputedStyle();
+ return !style || style->Display() == EDisplay::kNone;
+}
+
+String HTMLOptionElement::innerText() {
+ // A workaround for crbug.com/424578. We add ShadowRoot to an OPTION, but
+ // innerText behavior for Shadow DOM is unclear. We just return the same
+ // string before adding ShadowRoot.
+ return textContent();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..3ef0a2e9ab3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2010, 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTION_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTION_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+
+namespace blink {
+
+class ExceptionState;
+class HTMLDataListElement;
+class HTMLSelectElement;
+
+class CORE_EXPORT HTMLOptionElement final : public HTMLElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLOptionElement* Create(Document&);
+ static HTMLOptionElement* CreateForJSConstructor(Document&,
+ const String& data,
+ const AtomicString& value,
+ bool default_selected,
+ bool selected,
+ ExceptionState&);
+
+ // A text to be shown to users. The difference from |label()| is |label()|
+ // returns an empty string if |label| content attribute is empty.
+ // |displayLabel()| returns the value string in that case.
+ String DisplayLabel() const;
+
+ // |text| IDL attribute implementations.
+ String text() const;
+ void setText(const String&);
+
+ int index() const;
+
+ String value() const;
+ void setValue(const AtomicString&);
+
+ bool Selected() const;
+ void SetSelected(bool);
+ bool selectedForBinding() const;
+ void setSelectedForBinding(bool);
+
+ HTMLDataListElement* OwnerDataListElement() const;
+ HTMLSelectElement* OwnerSelectElement() const;
+
+ String label() const;
+ void setLabel(const AtomicString&);
+
+ bool OwnElementDisabled() const;
+
+ bool IsDisabledFormControl() const override;
+ String DefaultToolTip() const override;
+
+ String TextIndentedToRespectGroupLabel() const;
+
+ // Update 'selectedness'.
+ void SetSelectedState(bool);
+ // Update 'dirtiness'.
+ void SetDirty(bool);
+
+ HTMLFormElement* form() const;
+ bool SpatialNavigationFocused() const;
+
+ bool IsDisplayNone() const;
+
+ int ListIndex() const;
+
+ private:
+ explicit HTMLOptionElement(Document&);
+ ~HTMLOptionElement() override;
+
+ bool SupportsFocus() const override;
+ bool MatchesDefaultPseudoClass() const override;
+ bool MatchesEnabledPseudoClass() const override;
+ void AttachLayoutTree(AttachContext&) override;
+ void ParseAttribute(const AttributeModificationParams&) override;
+ InsertionNotificationRequest InsertedInto(ContainerNode*) override;
+ void RemovedFrom(ContainerNode*) override;
+ void AccessKeyAction(bool) override;
+ void ChildrenChanged(const ChildrenChange&) override;
+ String innerText() override;
+
+ void DidAddUserAgentShadowRoot(ShadowRoot&) override;
+
+ String CollectOptionInnerText() const;
+
+ void UpdateLabel();
+
+ // Represents 'selectedness'.
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-option-selectedness
+ bool is_selected_;
+ // Represents 'dirtiness'.
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-option-dirtiness
+ bool is_dirty_ = false;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTION_ELEMENT_H_
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
new file mode 100644
index 00000000000..39c2fe25650
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2006, 2007, 2010 Apple, Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.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.
+ */
+
+// https://html.spec.whatwg.org/#the-option-element
+
+[
+ HTMLConstructor,
+ NamedConstructor=Option(optional DOMString data = null,
+ optional DOMString value = null,
+ optional boolean defaultSelected = false,
+ optional boolean selected = false),
+ ConstructorCallWith=Document,
+ RaisesException=Constructor
+] interface HTMLOptionElement : HTMLElement {
+ [CEReactions, Reflect] attribute boolean disabled;
+ readonly attribute HTMLFormElement? form;
+ [CEReactions] attribute DOMString label;
+ [CEReactions, Reflect=selected] attribute boolean defaultSelected;
+ [ImplementedAs=selectedForBinding] attribute boolean selected;
+ [CEReactions] attribute DOMString value;
+
+ [CEReactions] attribute DOMString text;
+ readonly attribute long index;
+};
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
new file mode 100644
index 00000000000..e195f8aad68
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2006, 2011, 2012 Apple Computer, Inc.
+ * Copyright (C) 2014 Samsung Electronics. All rights reserved.
+ *
+ * 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/html_options_collection.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/bindings/core/v8/html_element_or_long.h"
+#include "third_party/blink/renderer/bindings/core/v8/html_option_element_or_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/html_select_element.h"
+
+namespace blink {
+
+HTMLOptionsCollection::HTMLOptionsCollection(ContainerNode& select)
+ : HTMLCollection(select, kSelectOptions, kDoesNotOverrideItemAfter) {
+ DCHECK(IsHTMLSelectElement(select));
+}
+
+void HTMLOptionsCollection::SupportedPropertyNames(Vector<String>& names) {
+ // As per
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#htmloptionscollection:
+ // The supported property names consist of the non-empty values of all the id
+ // and name attributes of all the elements represented by the collection, in
+ // tree order, ignoring later duplicates, with the id of an element preceding
+ // its name if it contributes both, they differ from each other, and neither
+ // is the duplicate of an earlier entry.
+ HashSet<AtomicString> existing_names;
+ unsigned length = this->length();
+ for (unsigned i = 0; i < length; ++i) {
+ Element* element = item(i);
+ DCHECK(element);
+ const AtomicString& id_attribute = element->GetIdAttribute();
+ if (!id_attribute.IsEmpty()) {
+ HashSet<AtomicString>::AddResult add_result =
+ existing_names.insert(id_attribute);
+ if (add_result.is_new_entry)
+ names.push_back(id_attribute);
+ }
+ const AtomicString& name_attribute = element->GetNameAttribute();
+ if (!name_attribute.IsEmpty()) {
+ HashSet<AtomicString>::AddResult add_result =
+ existing_names.insert(name_attribute);
+ if (add_result.is_new_entry)
+ names.push_back(name_attribute);
+ }
+ }
+}
+
+HTMLOptionsCollection* HTMLOptionsCollection::Create(ContainerNode& select,
+ CollectionType) {
+ return new HTMLOptionsCollection(select);
+}
+
+void HTMLOptionsCollection::add(
+ const HTMLOptionElementOrHTMLOptGroupElement& element,
+ const HTMLElementOrLong& before,
+ ExceptionState& exception_state) {
+ ToHTMLSelectElement(ownerNode()).add(element, before, exception_state);
+}
+
+void HTMLOptionsCollection::remove(int index) {
+ ToHTMLSelectElement(ownerNode()).remove(index);
+}
+
+int HTMLOptionsCollection::selectedIndex() const {
+ return ToHTMLSelectElement(ownerNode()).selectedIndex();
+}
+
+void HTMLOptionsCollection::setSelectedIndex(int index) {
+ ToHTMLSelectElement(ownerNode()).setSelectedIndex(index);
+}
+
+void HTMLOptionsCollection::setLength(unsigned length,
+ ExceptionState& exception_state) {
+ ToHTMLSelectElement(ownerNode()).setLength(length, exception_state);
+}
+
+bool HTMLOptionsCollection::AnonymousIndexedSetter(
+ unsigned index,
+ HTMLOptionElement* value,
+ ExceptionState& exception_state) {
+ HTMLSelectElement& base = ToHTMLSelectElement(ownerNode());
+ if (!value) { // undefined or null
+ base.remove(index);
+ return true;
+ }
+ base.SetOption(index, value, exception_state);
+ return true;
+}
+
+} // 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
new file mode 100644
index 00000000000..665d0eb431d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTIONS_COLLECTION_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTIONS_COLLECTION_H_
+
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/html_collection.h"
+
+namespace blink {
+
+class ExceptionState;
+class HTMLOptionElementOrHTMLOptGroupElement;
+class HTMLElementOrLong;
+
+class HTMLOptionsCollection final : public HTMLCollection {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLOptionsCollection* Create(ContainerNode&, CollectionType);
+
+ HTMLOptionElement* item(unsigned offset) const {
+ return ToHTMLOptionElement(HTMLCollection::item(offset));
+ }
+
+ void add(const HTMLOptionElementOrHTMLOptGroupElement&,
+ const HTMLElementOrLong&,
+ ExceptionState&);
+ void remove(int index);
+
+ int selectedIndex() const;
+ void setSelectedIndex(int);
+
+ void setLength(unsigned, ExceptionState&);
+ bool AnonymousIndexedSetter(unsigned, HTMLOptionElement*, ExceptionState&);
+
+ bool ElementMatches(const HTMLElement&) const;
+
+ private:
+ explicit HTMLOptionsCollection(ContainerNode&);
+
+ void SupportedPropertyNames(Vector<String>& names) override;
+};
+
+DEFINE_TYPE_CASTS(HTMLOptionsCollection,
+ LiveNodeListBase,
+ collection,
+ collection->GetType() == kSelectOptions,
+ collection.GetType() == kSelectOptions);
+
+inline bool HTMLOptionsCollection::ElementMatches(
+ const HTMLElement& element) const {
+ if (!IsHTMLOptionElement(element))
+ return false;
+ Node* parent = element.parentNode();
+ if (!parent)
+ return false;
+ if (parent == &RootNode())
+ return true;
+ return IsHTMLOptGroupElement(*parent) && parent->parentNode() == &RootNode();
+}
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTIONS_COLLECTION_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.idl b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.idl
new file mode 100644
index 00000000000..2d020678172
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.idl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
+ * Copyright (C) 2013, 2014 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ */
+
+// https://html.spec.whatwg.org/#the-htmloptionscollection-interface
+
+interface HTMLOptionsCollection : HTMLCollection {
+ // Inherits item() and namedItem()
+ [CEReactions, RaisesException=Setter] attribute unsigned long length; // shadows inherited length
+ [CEReactions, RaisesException] setter void (unsigned long index, HTMLOptionElement? option);
+ [CEReactions, RaisesException] void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null);
+ [CEReactions] void remove(long index);
+ attribute long selectedIndex;
+
+ // TODO(tkent): We need to declare these getters because our IDL compiler
+ // doesn't support inheritance of anonymous getters. crbug.com/752877
+ [ImplementedAs=item] getter Element? (unsigned long index);
+ [ImplementedAs=namedItem] getter Element? (DOMString name);
+};
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.cc
new file mode 100644
index 00000000000..69fa1c1768b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.cc
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/html_output_element.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/html_names.h"
+
+namespace blink {
+
+inline HTMLOutputElement::HTMLOutputElement(Document& document)
+ : HTMLFormControlElement(HTMLNames::outputTag, document),
+ is_default_value_mode_(true),
+ default_value_(""),
+ tokens_(DOMTokenList::Create(*this, HTMLNames::forAttr)) {}
+
+HTMLOutputElement::~HTMLOutputElement() = default;
+
+HTMLOutputElement* HTMLOutputElement::Create(Document& document) {
+ return new HTMLOutputElement(document);
+}
+
+const AtomicString& HTMLOutputElement::FormControlType() const {
+ DEFINE_STATIC_LOCAL(const AtomicString, output, ("output"));
+ return output;
+}
+
+bool HTMLOutputElement::IsDisabledFormControl() const {
+ return false;
+}
+
+bool HTMLOutputElement::MatchesEnabledPseudoClass() const {
+ return false;
+}
+
+bool HTMLOutputElement::SupportsFocus() const {
+ return HTMLElement::SupportsFocus();
+}
+
+void HTMLOutputElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ if (params.name == HTMLNames::forAttr)
+ tokens_->DidUpdateAttributeValue(params.old_value, params.new_value);
+ else
+ HTMLFormControlElement::ParseAttribute(params);
+}
+
+DOMTokenList* HTMLOutputElement::htmlFor() const {
+ return tokens_.Get();
+}
+
+void HTMLOutputElement::ChildrenChanged(const ChildrenChange& change) {
+ HTMLFormControlElement::ChildrenChanged(change);
+
+ if (is_default_value_mode_)
+ default_value_ = textContent();
+}
+
+void HTMLOutputElement::ResetImpl() {
+ // The reset algorithm for output elements is to set the element's
+ // value mode flag to "default" and then to set the element's textContent
+ // attribute to the default value.
+ if (default_value_ == value())
+ return;
+ setTextContent(default_value_);
+ is_default_value_mode_ = true;
+}
+
+String HTMLOutputElement::value() const {
+ return textContent();
+}
+
+void HTMLOutputElement::setValue(const String& value) {
+ // The value mode flag set to "value" when the value attribute is set.
+ is_default_value_mode_ = false;
+ if (value == this->value())
+ return;
+ setTextContent(value);
+}
+
+String HTMLOutputElement::defaultValue() const {
+ return default_value_;
+}
+
+void HTMLOutputElement::setDefaultValue(const String& value) {
+ if (default_value_ == value)
+ return;
+ default_value_ = value;
+ // The spec requires the value attribute set to the default value
+ // when the element's value mode flag to "default".
+ if (is_default_value_mode_)
+ setTextContent(value);
+}
+
+int HTMLOutputElement::tabIndex() const {
+ return HTMLElement::tabIndex();
+}
+
+void HTMLOutputElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(tokens_);
+ HTMLFormControlElement::Trace(visitor);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.h
new file mode 100644
index 00000000000..2c322a0a70f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OUTPUT_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OUTPUT_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/dom/dom_token_list.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+
+namespace blink {
+
+class CORE_EXPORT HTMLOutputElement final : public HTMLFormControlElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLOutputElement* Create(Document&);
+ ~HTMLOutputElement() override;
+
+ bool willValidate() const override { return false; }
+
+ String value() const;
+ void setValue(const String&);
+ String defaultValue() const;
+ void setDefaultValue(const String&);
+ DOMTokenList* htmlFor() const;
+
+ bool CanContainRangeEndPoint() const override {
+ return is_default_value_mode_;
+ }
+
+ void Trace(blink::Visitor*) override;
+
+ private:
+ explicit HTMLOutputElement(Document&);
+
+ void ParseAttribute(const AttributeModificationParams&) override;
+ const AtomicString& FormControlType() const override;
+ bool IsDisabledFormControl() const override;
+ bool MatchesEnabledPseudoClass() const override;
+ bool IsEnumeratable() const override { return true; }
+ bool SupportLabels() const override { return true; }
+ bool SupportsFocus() const override;
+ void ChildrenChanged(const ChildrenChange&) override;
+ void ResetImpl() override;
+ int tabIndex() const override;
+
+ bool is_default_value_mode_;
+ String default_value_;
+ Member<DOMTokenList> tokens_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OUTPUT_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.idl
new file mode 100644
index 00000000000..e6471696564
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.idl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+// https://html.spec.whatwg.org/#the-output-element
+[HTMLConstructor]
+interface HTMLOutputElement : HTMLElement {
+ [PutForwards=value] readonly attribute DOMTokenList htmlFor;
+ [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form;
+ [CEReactions, Reflect] attribute DOMString name;
+
+ readonly attribute DOMString type;
+ [CEReactions] attribute DOMString defaultValue;
+ [CEReactions] attribute DOMString value;
+
+ readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ readonly attribute DOMString validationMessage;
+ boolean checkValidity();
+ boolean reportValidity();
+ void setCustomValidity(DOMString error);
+
+ readonly attribute NodeList labels;
+};
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_output_element_test.cc
new file mode 100644
index 00000000000..fdb5cbe5979
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element_test.cc
@@ -0,0 +1,32 @@
+// Copyright 2015 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/html_output_element.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/dom_token_list.h"
+#include "third_party/blink/renderer/core/html_names.h"
+
+namespace blink {
+
+TEST(HTMLLinkElementSizesAttributeTest,
+ setHTMLForProperty_updatesForAttribute) {
+ Document* document = Document::CreateForTest();
+ HTMLOutputElement* element = HTMLOutputElement::Create(*document);
+ EXPECT_EQ(g_null_atom, element->getAttribute(HTMLNames::forAttr));
+ element->htmlFor()->setValue(" strawberry ");
+ EXPECT_EQ(" strawberry ", element->getAttribute(HTMLNames::forAttr));
+}
+
+TEST(HTMLOutputElementTest, setForAttribute_updatesHTMLForPropertyValue) {
+ Document* document = Document::CreateForTest();
+ HTMLOutputElement* element = HTMLOutputElement::Create(*document);
+ DOMTokenList* for_tokens = element->htmlFor();
+ EXPECT_EQ(g_null_atom, for_tokens->value());
+ element->setAttribute(HTMLNames::forAttr, "orange grape");
+ EXPECT_EQ("orange grape", for_tokens->value());
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..66338a64dd8
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -0,0 +1,2086 @@
+/*
+ * 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/html_select_element.h"
+
+#include "build/build_config.h"
+#include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/bindings/core/v8/html_element_or_long.h"
+#include "third_party/blink/renderer/bindings/core/v8/html_option_element_or_html_opt_group_element.h"
+#include "third_party/blink/renderer/core/dom/attribute.h"
+#include "third_party/blink/renderer/core/dom/ax_object_cache.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_frame.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.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/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_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/platform/instrumentation/tracing/trace_event.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+// Upper limit of m_listItems. According to the HTML standard, options larger
+// than this limit doesn't work well because |selectedIndex| IDL attribute is
+// signed.
+static const unsigned kMaxListItems = INT_MAX;
+
+HTMLSelectElement::HTMLSelectElement(Document& document)
+ : HTMLFormControlElementWithState(selectTag, document),
+ type_ahead_(this),
+ size_(0),
+ last_on_change_option_(nullptr),
+ is_multiple_(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) {
+ SetHasCustomStyleCallbacks();
+}
+
+HTMLSelectElement* HTMLSelectElement::Create(Document& document) {
+ HTMLSelectElement* select = new HTMLSelectElement(document);
+ select->EnsureUserAgentShadowRoot();
+ return select;
+}
+
+HTMLSelectElement::~HTMLSelectElement() = default;
+
+// static
+bool HTMLSelectElement::CanAssignToSelectSlot(const Node& node) {
+ return node.HasTagName(optionTag) || node.HasTagName(optgroupTag) ||
+ node.HasTagName(hrTag);
+}
+
+const AtomicString& HTMLSelectElement::FormControlType() const {
+ DEFINE_STATIC_LOCAL(const AtomicString, select_multiple, ("select-multiple"));
+ DEFINE_STATIC_LOCAL(const AtomicString, select_one, ("select-one"));
+ return is_multiple_ ? select_multiple : select_one;
+}
+
+bool HTMLSelectElement::HasPlaceholderLabelOption() const {
+ // The select element has no placeholder label option if it has an attribute
+ // "multiple" specified or a display size of non-1.
+ //
+ // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec
+ // 3, 2010. "size() != 1" is correct. Using "size() > 1" here because
+ // size() may be 0 in WebKit. See the discussion at
+ // https://bugs.webkit.org/show_bug.cgi?id=43887
+ //
+ // "0 size()" happens when an attribute "size" is absent or an invalid size
+ // attribute is specified. In this case, the display size should be assumed
+ // as the default. The default display size is 1 for non-multiple select
+ // elements, and 4 for multiple select elements.
+ //
+ // Finally, if size() == 0 and non-multiple, the display size can be assumed
+ // as 1.
+ if (IsMultiple() || size() > 1)
+ return false;
+
+ // TODO(tkent): This function is called in CSS selector matching. Using
+ // listItems() might have performance impact.
+ if (GetListItems().size() == 0 || !IsHTMLOptionElement(GetListItems()[0]))
+ return false;
+ return ToHTMLOptionElement(GetListItems()[0])->value().IsEmpty();
+}
+
+String HTMLSelectElement::validationMessage() const {
+ if (!willValidate())
+ return String();
+ if (CustomError())
+ return CustomValidationMessage();
+ if (ValueMissing()) {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationValueMissingForSelect);
+ }
+ return String();
+}
+
+bool HTMLSelectElement::ValueMissing() const {
+ if (!willValidate())
+ return false;
+
+ if (!IsRequired())
+ return false;
+
+ int first_selection_index = selectedIndex();
+
+ // If a non-placeholer label option is selected (firstSelectionIndex > 0),
+ // it's not value-missing.
+ return first_selection_index < 0 ||
+ (!first_selection_index && HasPlaceholderLabelOption());
+}
+
+String HTMLSelectElement::DefaultToolTip() const {
+ if (Form() && Form()->NoValidate())
+ return String();
+ return validationMessage();
+}
+
+void HTMLSelectElement::SelectMultipleOptionsByPopup(
+ const Vector<int>& list_indices) {
+ DCHECK(UsesMenuList());
+ DCHECK(IsMultiple());
+ for (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);
+ }
+ SetNeedsValidityCheck();
+ // TODO(tkent): Using listBoxOnChange() is very confusing.
+ ListBoxOnChange();
+}
+
+bool HTMLSelectElement::UsesMenuList() const {
+ if (LayoutTheme::GetTheme().DelegatesMenuListRendering())
+ return true;
+
+ return !is_multiple_ && size_ <= 1;
+}
+
+int HTMLSelectElement::ActiveSelectionEndListIndex() const {
+ HTMLOptionElement* option = ActiveSelectionEnd();
+ return option ? option->ListIndex() : -1;
+}
+
+HTMLOptionElement* HTMLSelectElement::ActiveSelectionEnd() const {
+ if (active_selection_end_)
+ return active_selection_end_.Get();
+ return LastSelectedOption();
+}
+
+void HTMLSelectElement::add(
+ const HTMLOptionElementOrHTMLOptGroupElement& element,
+ const HTMLElementOrLong& before,
+ ExceptionState& exception_state) {
+ HTMLElement* element_to_insert;
+ DCHECK(!element.IsNull());
+ if (element.IsHTMLOptionElement())
+ element_to_insert = element.GetAsHTMLOptionElement();
+ else
+ element_to_insert = element.GetAsHTMLOptGroupElement();
+
+ HTMLElement* before_element;
+ if (before.IsHTMLElement())
+ before_element = before.GetAsHTMLElement();
+ else if (before.IsLong())
+ before_element = options()->item(before.GetAsLong());
+ else
+ before_element = nullptr;
+
+ InsertBefore(element_to_insert, before_element, exception_state);
+ SetNeedsValidityCheck();
+}
+
+void HTMLSelectElement::remove(int option_index) {
+ if (HTMLOptionElement* option = item(option_index))
+ option->remove(IGNORE_EXCEPTION_FOR_TESTING);
+}
+
+String HTMLSelectElement::value() const {
+ if (HTMLOptionElement* option = SelectedOption())
+ return option->value();
+ return "";
+}
+
+void HTMLSelectElement::setValue(const String& value, bool send_events) {
+ HTMLOptionElement* option = nullptr;
+ // Find the option with value() matching the given parameter and make it the
+ // current selection.
+ for (auto* const item : GetOptionList()) {
+ if (item->value() == value) {
+ option = item;
+ break;
+ }
+ }
+
+ HTMLOptionElement* previous_selected_option = SelectedOption();
+ SetSuggestedOption(nullptr);
+ if (is_autofilled_by_preview_)
+ SetAutofilled(false);
+ SelectOptionFlags flags = kDeselectOtherOptions | kMakeOptionDirty;
+ if (send_events)
+ flags |= kDispatchInputAndChangeEvent;
+ SelectOption(option, flags);
+
+ if (send_events && previous_selected_option != option && !UsesMenuList())
+ ListBoxOnChange();
+}
+
+String HTMLSelectElement::SuggestedValue() const {
+ return suggested_option_ ? suggested_option_->value() : "";
+}
+
+void HTMLSelectElement::SetSuggestedValue(const String& value) {
+ if (value.IsNull()) {
+ SetSuggestedOption(nullptr);
+ return;
+ }
+
+ for (auto* const option : GetOptionList()) {
+ if (option->value() == value) {
+ SetSuggestedOption(option);
+ is_autofilled_by_preview_ = true;
+ return;
+ }
+ }
+
+ SetSuggestedOption(nullptr);
+}
+
+bool HTMLSelectElement::IsPresentationAttribute(
+ const QualifiedName& name) const {
+ if (name == alignAttr) {
+ // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
+ // See http://bugs.webkit.org/show_bug.cgi?id=12072
+ return false;
+ }
+
+ return HTMLFormControlElementWithState::IsPresentationAttribute(name);
+}
+
+void HTMLSelectElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ if (params.name == sizeAttr) {
+ unsigned old_size = size_;
+ if (!ParseHTMLNonNegativeInteger(params.new_value, size_))
+ size_ = 0;
+ SetNeedsValidityCheck();
+ if (size_ != old_size) {
+ if (InActiveDocument())
+ LazyReattachIfAttached();
+ ResetToDefaultSelection();
+ if (!UsesMenuList())
+ SaveListboxActiveSelection();
+ }
+ } else if (params.name == multipleAttr) {
+ ParseMultipleAttribute(params.new_value);
+ } else if (params.name == accesskeyAttr) {
+ // FIXME: ignore for the moment.
+ //
+ } else {
+ HTMLFormControlElementWithState::ParseAttribute(params);
+ }
+}
+
+bool HTMLSelectElement::ShouldShowFocusRingOnMouseFocus() const {
+ return true;
+}
+
+bool HTMLSelectElement::CanSelectAll() const {
+ return !UsesMenuList();
+}
+
+LayoutObject* HTMLSelectElement::CreateLayoutObject(const ComputedStyle&) {
+ if (UsesMenuList())
+ return new LayoutMenuList(this);
+ return new LayoutListBox(this);
+}
+
+HTMLCollection* HTMLSelectElement::selectedOptions() {
+ return EnsureCachedCollection<HTMLCollection>(kSelectedOptions);
+}
+
+HTMLOptionsCollection* HTMLSelectElement::options() {
+ return EnsureCachedCollection<HTMLOptionsCollection>(kSelectOptions);
+}
+
+void HTMLSelectElement::OptionElementChildrenChanged(
+ const HTMLOptionElement& option) {
+ SetNeedsValidityCheck();
+
+ if (GetLayoutObject()) {
+ if (option.Selected() && UsesMenuList())
+ GetLayoutObject()->UpdateFromElement();
+ if (AXObjectCache* cache =
+ GetLayoutObject()->GetDocument().ExistingAXObjectCache())
+ cache->ChildrenChanged(this);
+ }
+}
+
+void HTMLSelectElement::AccessKeyAction(bool send_mouse_events) {
+ focus();
+ DispatchSimulatedClick(
+ nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents);
+}
+
+Element* HTMLSelectElement::namedItem(const AtomicString& name) {
+ return options()->namedItem(name);
+}
+
+HTMLOptionElement* HTMLSelectElement::item(unsigned index) {
+ return options()->item(index);
+}
+
+void HTMLSelectElement::SetOption(unsigned index,
+ HTMLOptionElement* option,
+ ExceptionState& exception_state) {
+ int diff = index - length();
+ // We should check |index >= maxListItems| first to avoid integer overflow.
+ if (index >= kMaxListItems ||
+ GetListItems().size() + diff + 1 > kMaxListItems) {
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kJSMessageSource, kWarningMessageLevel,
+ String::Format("Blocked to expand the option list and set an option at "
+ "index=%u. The maximum list length is %u.",
+ index, kMaxListItems)));
+ return;
+ }
+ HTMLOptionElementOrHTMLOptGroupElement element;
+ element.SetHTMLOptionElement(option);
+ HTMLElementOrLong before;
+ // Out of array bounds? First insert empty dummies.
+ if (diff > 0) {
+ setLength(index, exception_state);
+ // Replace an existing entry?
+ } else if (diff < 0) {
+ before.SetHTMLElement(options()->item(index + 1));
+ remove(index);
+ }
+ if (exception_state.HadException())
+ return;
+ // Finally add the new element.
+ EventQueueScope scope;
+ add(element, before, exception_state);
+ if (diff >= 0 && option->Selected())
+ OptionSelectionStateChanged(option, true);
+}
+
+void HTMLSelectElement::setLength(unsigned new_len,
+ ExceptionState& exception_state) {
+ // We should check |newLen > maxListItems| first to avoid integer overflow.
+ if (new_len > kMaxListItems ||
+ GetListItems().size() + new_len - length() > kMaxListItems) {
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kJSMessageSource, kWarningMessageLevel,
+ String::Format("Blocked to expand the option list to %u items. The "
+ "maximum list length is %u.",
+ new_len, kMaxListItems)));
+ return;
+ }
+ int diff = length() - new_len;
+
+ if (diff < 0) { // Add dummy elements.
+ do {
+ AppendChild(HTMLOptionElement::Create(GetDocument()), exception_state);
+ if (exception_state.HadException())
+ break;
+ } while (++diff);
+ } else {
+ // Removing children fires mutation events, which might mutate the DOM
+ // further, so we first copy out a list of elements that we intend to
+ // remove then attempt to remove them one at a time.
+ HeapVector<Member<HTMLOptionElement>> items_to_remove;
+ size_t option_index = 0;
+ for (auto* const option : GetOptionList()) {
+ if (option_index++ >= new_len) {
+ DCHECK(option->parentNode());
+ items_to_remove.push_back(option);
+ }
+ }
+
+ for (auto& item : items_to_remove) {
+ if (item->parentNode())
+ item->parentNode()->RemoveChild(item.Get(), exception_state);
+ }
+ }
+ SetNeedsValidityCheck();
+}
+
+bool HTMLSelectElement::IsRequiredFormControl() const {
+ return IsRequired();
+}
+
+HTMLOptionElement* HTMLSelectElement::OptionAtListIndex(int list_index) const {
+ if (list_index < 0)
+ return nullptr;
+ const ListItems& items = GetListItems();
+ if (static_cast<size_t>(list_index) >= items.size())
+ return nullptr;
+ return ToHTMLOptionElementOrNull(items[list_index]);
+}
+
+// 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];
+ if (!IsHTMLOptionElement(*element))
+ continue;
+ if (ToHTMLOptionElement(*element).IsDisplayNone())
+ continue;
+ if (element->IsDisabledFormControl())
+ continue;
+ if (!UsesMenuList() && !element->GetLayoutObject())
+ continue;
+ last_good_option = ToHTMLOptionElement(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 m_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()) {
+ last_on_change_selection_.push_back(
+ IsHTMLOptionElement(*element) &&
+ ToHTMLOptionElement(element)->Selected());
+ }
+}
+
+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
+ // m_activeSelectionAnchorIndex = 1
+ // 2. Drag the mouse pointer onto the fifth OPTION
+ // m_activeSelectionEndIndex = 4, options at 1-4 indices are selected.
+ // 3. Drag the mouse pointer onto the fourth OPTION
+ // m_activeSelectionEndIndex = 3, 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());
+ }
+}
+
+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;
+ }
+
+ 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 m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
+ bool fire_on_change = false;
+ for (unsigned i = 0; i < items.size(); ++i) {
+ HTMLElement* element = items[i];
+ bool selected = IsHTMLOptionElement(*element) &&
+ ToHTMLOptionElement(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::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;
+ if (UsesMenuList())
+ return;
+ ScrollToOption(ActiveSelectionEnd());
+ if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
+ cache->ListboxActiveIndexChanged(this);
+}
+
+void HTMLSelectElement::SetOptionsChangedOnLayoutObject() {
+ if (LayoutObject* layout_object = GetLayoutObject()) {
+ if (!UsesMenuList())
+ return;
+ ToLayoutMenuList(layout_object)
+ ->SetNeedsLayoutAndPrefWidthsRecalc(
+ LayoutInvalidationReason::kMenuOptionsChanged);
+ }
+}
+
+const HTMLSelectElement::ListItems& HTMLSelectElement::GetListItems() const {
+ if (should_recalc_list_items_) {
+ RecalcListItems();
+ } else {
+#if DCHECK_IS_ON()
+ HeapVector<Member<HTMLElement>> items = list_items_;
+ RecalcListItems();
+ DCHECK(items == list_items_);
+#endif
+ }
+
+ return list_items_;
+}
+
+void HTMLSelectElement::InvalidateSelectedItems() {
+ if (HTMLCollection* collection =
+ CachedCollection<HTMLCollection>(kSelectedOptions))
+ collection->InvalidateCache();
+}
+
+void HTMLSelectElement::SetRecalcListItems() {
+ // FIXME: This function does a bunch of confusing things depending on if it
+ // is in the document or not.
+
+ should_recalc_list_items_ = true;
+
+ SetOptionsChangedOnLayoutObject();
+ if (!isConnected()) {
+ if (HTMLOptionsCollection* collection =
+ CachedCollection<HTMLOptionsCollection>(kSelectOptions))
+ collection->InvalidateCache();
+ InvalidateSelectedItems();
+ }
+
+ if (GetLayoutObject()) {
+ if (AXObjectCache* cache =
+ GetLayoutObject()->GetDocument().ExistingAXObjectCache())
+ cache->ChildrenChanged(this);
+ }
+}
+
+void HTMLSelectElement::RecalcListItems() const {
+ TRACE_EVENT0("blink", "HTMLSelectElement::recalcListItems");
+ list_items_.resize(0);
+
+ should_recalc_list_items_ = false;
+
+ for (Element* current_element = ElementTraversal::FirstWithin(*this);
+ current_element && list_items_.size() < kMaxListItems;) {
+ if (!current_element->IsHTMLElement()) {
+ current_element =
+ ElementTraversal::NextSkippingChildren(*current_element, this);
+ continue;
+ }
+ HTMLElement& current = ToHTMLElement(*current_element);
+
+ // We should ignore nested optgroup elements. The HTML parser flatten
+ // them. However we need to ignore nested optgroups built by DOM APIs.
+ // This behavior matches to IE and Firefox.
+ if (IsHTMLOptGroupElement(current)) {
+ if (current.parentNode() != this) {
+ current_element = ElementTraversal::NextSkippingChildren(current, this);
+ continue;
+ }
+ list_items_.push_back(&current);
+ if (Element* next_element = ElementTraversal::FirstWithin(current)) {
+ current_element = next_element;
+ continue;
+ }
+ }
+
+ if (IsHTMLOptionElement(current))
+ list_items_.push_back(&current);
+
+ if (IsHTMLHRElement(current))
+ list_items_.push_back(&current);
+
+ // In conforming HTML code, only <optgroup> and <option> will be found
+ // within a <select>. We call NodeTraversal::nextSkippingChildren so
+ // that we only step into those tags that we choose to. For web-compat,
+ // we should cope with the case where odd tags like a <div> have been
+ // added but we handle this because such tags have already been removed
+ // from the <select>'s subtree at this point.
+ current_element =
+ ElementTraversal::NextSkippingChildren(*current_element, this);
+ }
+}
+
+void HTMLSelectElement::ResetToDefaultSelection(ResetReason reason) {
+ // https://html.spec.whatwg.org/multipage/forms.html#ask-for-a-reset
+ if (IsMultiple())
+ return;
+ HTMLOptionElement* first_enabled_option = nullptr;
+ HTMLOptionElement* last_selected_option = nullptr;
+ bool did_change = false;
+ int option_index = 0;
+ // We can't use HTMLSelectElement::options here because this function is
+ // called in Node::insertedInto and Node::removedFrom before invalidating
+ // node collections.
+ for (auto* const option : GetOptionList()) {
+ if (option->Selected()) {
+ if (last_selected_option) {
+ last_selected_option->SetSelectedState(false);
+ did_change = true;
+ }
+ last_selected_option = option;
+ }
+ if (!first_enabled_option && !option->IsDisabledFormControl()) {
+ first_enabled_option = option;
+ if (reason == kResetReasonSelectedOptionRemoved) {
+ // There must be no selected OPTIONs.
+ break;
+ }
+ }
+ ++option_index;
+ }
+ if (!last_selected_option && size_ <= 1 &&
+ (!first_enabled_option ||
+ (first_enabled_option && !first_enabled_option->Selected()))) {
+ SelectOption(first_enabled_option,
+ reason == kResetReasonSelectedOptionRemoved
+ ? 0
+ : kDeselectOtherOptions);
+ last_selected_option = first_enabled_option;
+ did_change = true;
+ }
+ if (did_change)
+ SetNeedsValidityCheck();
+ last_on_change_option_ = last_selected_option;
+}
+
+HTMLOptionElement* HTMLSelectElement::SelectedOption() const {
+ for (auto* const option : GetOptionList()) {
+ if (option->Selected())
+ return option;
+ }
+ return nullptr;
+}
+
+int HTMLSelectElement::selectedIndex() const {
+ unsigned index = 0;
+
+ // Return the number of the first option selected.
+ for (auto* const option : GetOptionList()) {
+ if (option->Selected())
+ return index;
+ ++index;
+ }
+
+ return -1;
+}
+
+void HTMLSelectElement::setSelectedIndex(int index) {
+ SelectOption(item(index), kDeselectOtherOptions | kMakeOptionDirty);
+}
+
+int HTMLSelectElement::SelectedListIndex() const {
+ int index = 0;
+ for (const auto& item : GetListItems()) {
+ if (IsHTMLOptionElement(item) && ToHTMLOptionElement(item)->Selected())
+ return index;
+ ++index;
+ }
+ return -1;
+}
+
+void HTMLSelectElement::SetSuggestedOption(HTMLOptionElement* option) {
+ if (suggested_option_ == option)
+ return;
+ suggested_option_ = option;
+
+ if (LayoutObject* layout_object = GetLayoutObject()) {
+ layout_object->UpdateFromElement();
+ ScrollToOption(option);
+ }
+ if (PopupIsVisible())
+ popup_->UpdateFromElement(PopupMenu::kBySelectionChange);
+}
+
+void HTMLSelectElement::ScrollToOption(HTMLOptionElement* option) {
+ if (!option)
+ return;
+ if (UsesMenuList())
+ return;
+ bool has_pending_task = option_to_scroll_to_;
+ // We'd like to keep an HTMLOptionElement reference rather than the index of
+ // the option because the task should work even if unselected option is
+ // inserted before executing scrollToOptionTask().
+ option_to_scroll_to_ = option;
+ if (!has_pending_task) {
+ GetDocument()
+ .GetTaskRunner(TaskType::kUserInteraction)
+ ->PostTask(FROM_HERE, WTF::Bind(&HTMLSelectElement::ScrollToOptionTask,
+ WrapPersistent(this)));
+ }
+}
+
+void HTMLSelectElement::ScrollToOptionTask() {
+ HTMLOptionElement* option = option_to_scroll_to_.Release();
+ if (!option || !isConnected())
+ return;
+ // optionRemoved() makes sure m_optionToScrollTo doesn't have an option with
+ // another owner.
+ DCHECK_EQ(option->OwnerSelectElement(), this);
+ GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+ if (!GetLayoutObject() || !GetLayoutObject()->IsListBox())
+ return;
+ LayoutRect bounds = option->BoundingBoxForScrollIntoView();
+ ToLayoutListBox(GetLayoutObject())->ScrollToRect(bounds);
+}
+
+void HTMLSelectElement::OptionSelectionStateChanged(HTMLOptionElement* option,
+ bool option_is_selected) {
+ DCHECK_EQ(option->OwnerSelectElement(), this);
+ if (option_is_selected)
+ SelectOption(option, IsMultiple() ? 0 : kDeselectOtherOptions);
+ else if (!UsesMenuList() || IsMultiple())
+ SelectOption(nullptr, IsMultiple() ? 0 : kDeselectOtherOptions);
+ else
+ ResetToDefaultSelection();
+}
+
+void HTMLSelectElement::OptionInserted(HTMLOptionElement& option,
+ bool option_is_selected) {
+ DCHECK_EQ(option.OwnerSelectElement(), this);
+ SetRecalcListItems();
+ if (option_is_selected) {
+ SelectOption(&option, IsMultiple() ? 0 : kDeselectOtherOptions);
+ } else {
+ // No need to reset if we already have a selected option.
+ if (!last_on_change_option_)
+ ResetToDefaultSelection();
+ }
+ SetNeedsValidityCheck();
+ last_on_change_selection_.clear();
+
+ if (!GetDocument().IsActive())
+ return;
+
+ GetDocument()
+ .GetFrame()
+ ->GetPage()
+ ->GetChromeClient()
+ .SelectFieldOptionsChanged(*this);
+}
+
+void HTMLSelectElement::OptionRemoved(HTMLOptionElement& option) {
+ SetRecalcListItems();
+ if (option.Selected())
+ ResetToDefaultSelection(kResetReasonSelectedOptionRemoved);
+ else if (!last_on_change_option_)
+ ResetToDefaultSelection();
+ if (last_on_change_option_ == &option)
+ last_on_change_option_.Clear();
+ if (option_to_scroll_to_ == &option)
+ option_to_scroll_to_.Clear();
+ if (active_selection_anchor_ == &option)
+ active_selection_anchor_.Clear();
+ if (active_selection_end_ == &option)
+ active_selection_end_.Clear();
+ if (suggested_option_ == &option)
+ SetSuggestedOption(nullptr);
+ if (option.Selected())
+ SetAutofilled(false);
+ SetNeedsValidityCheck();
+ last_on_change_selection_.clear();
+
+ if (!GetDocument().IsActive())
+ return;
+
+ GetDocument()
+ .GetFrame()
+ ->GetPage()
+ ->GetChromeClient()
+ .SelectFieldOptionsChanged(*this);
+}
+
+void HTMLSelectElement::OptGroupInsertedOrRemoved(
+ HTMLOptGroupElement& optgroup) {
+ SetRecalcListItems();
+ SetNeedsValidityCheck();
+ last_on_change_selection_.clear();
+}
+
+void HTMLSelectElement::HrInsertedOrRemoved(HTMLHRElement& hr) {
+ SetRecalcListItems();
+ last_on_change_selection_.clear();
+}
+
+// TODO(tkent): This function is not efficient. It contains multiple O(N)
+// operations. crbug.com/577989.
+void HTMLSelectElement::SelectOption(HTMLOptionElement* element,
+ SelectOptionFlags flags) {
+ TRACE_EVENT0("blink", "HTMLSelectElement::selectOption");
+
+ bool should_update_popup = false;
+
+ // selectedOption() is O(N).
+ if (IsAutofilled() && SelectedOption() != element)
+ SetAutofilled(false);
+
+ if (element) {
+ if (!element->Selected())
+ should_update_popup = true;
+ element->SetSelectedState(true);
+ if (flags & kMakeOptionDirty)
+ element->SetDirty(true);
+ }
+
+ // deselectItemsWithoutValidation() is O(N).
+ if (flags & kDeselectOtherOptions)
+ should_update_popup |= DeselectItemsWithoutValidation(element);
+
+ // We should update active selection after finishing OPTION state change
+ // because setActiveSelectionAnchorIndex() stores OPTION's selection state.
+ if (element) {
+ // setActiveSelectionAnchor is O(N).
+ if (!active_selection_anchor_ || !IsMultiple() ||
+ flags & kDeselectOtherOptions)
+ SetActiveSelectionAnchor(element);
+ if (!active_selection_end_ || !IsMultiple() ||
+ flags & kDeselectOtherOptions)
+ SetActiveSelectionEnd(element);
+ }
+
+ // Need to update m_lastOnChangeOption before
+ // LayoutMenuList::updateFromElement.
+ bool should_dispatch_events = false;
+ if (UsesMenuList()) {
+ should_dispatch_events = (flags & kDispatchInputAndChangeEvent) &&
+ 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);
+ }
+ }
+ }
+
+ NotifyFormStateChanged();
+
+ if (Frame::HasTransientUserActivation(GetDocument().GetFrame()) &&
+ GetDocument().IsActive()) {
+ GetDocument()
+ .GetPage()
+ ->GetChromeClient()
+ .DidChangeSelectionInSelectControl(*this);
+ }
+}
+
+void HTMLSelectElement::DispatchFocusEvent(
+ Element* old_focused_element,
+ WebFocusType 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();
+ HTMLFormControlElementWithState::DispatchFocusEvent(old_focused_element, type,
+ source_capabilities);
+}
+
+void HTMLSelectElement::DispatchBlurEvent(
+ Element* new_focused_element,
+ WebFocusType 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();
+ HTMLFormControlElementWithState::DispatchBlurEvent(new_focused_element, type,
+ source_capabilities);
+}
+
+// Returns true if selection state of any OPTIONs is changed.
+bool HTMLSelectElement::DeselectItemsWithoutValidation(
+ HTMLOptionElement* exclude_element) {
+ if (!IsMultiple() && UsesMenuList() && last_on_change_option_ &&
+ last_on_change_option_ != exclude_element) {
+ last_on_change_option_->SetSelectedState(false);
+ return true;
+ }
+ bool did_update_selection = false;
+ for (auto* const option : GetOptionList()) {
+ if (option != exclude_element) {
+ if (option->Selected())
+ did_update_selection = true;
+ option->SetSelectedState(false);
+ }
+ }
+ return did_update_selection;
+}
+
+FormControlState HTMLSelectElement::SaveFormControlState() const {
+ const ListItems& items = GetListItems();
+ size_t length = items.size();
+ FormControlState state;
+ for (unsigned i = 0; i < length; ++i) {
+ if (!IsHTMLOptionElement(*items[i]))
+ continue;
+ HTMLOptionElement* option = ToHTMLOptionElement(items[i]);
+ if (!option->Selected())
+ continue;
+ state.Append(option->value());
+ state.Append(String::Number(i));
+ if (!IsMultiple())
+ break;
+ }
+ return state;
+}
+
+size_t HTMLSelectElement::SearchOptionsForValue(const String& value,
+ size_t list_index_start,
+ size_t list_index_end) const {
+ const ListItems& items = GetListItems();
+ size_t loop_end_index = std::min(items.size(), list_index_end);
+ for (size_t i = list_index_start; i < loop_end_index; ++i) {
+ if (!IsHTMLOptionElement(items[i]))
+ continue;
+ if (ToHTMLOptionElement(items[i])->value() == value)
+ return i;
+ }
+ return kNotFound;
+}
+
+void HTMLSelectElement::RestoreFormControlState(const FormControlState& state) {
+ RecalcListItems();
+
+ const ListItems& items = GetListItems();
+ size_t items_size = items.size();
+ if (items_size == 0)
+ return;
+
+ SelectOption(nullptr, kDeselectOtherOptions);
+
+ // The saved state should have at least one value and an index.
+ DCHECK_GE(state.ValueSize(), 2u);
+ if (!IsMultiple()) {
+ size_t index = state[1].ToUInt();
+ if (index < items_size && IsHTMLOptionElement(items[index]) &&
+ ToHTMLOptionElement(items[index])->value() == state[0]) {
+ ToHTMLOptionElement(items[index])->SetSelectedState(true);
+ ToHTMLOptionElement(items[index])->SetDirty(true);
+ last_on_change_option_ = ToHTMLOptionElement(items[index]);
+ } else {
+ size_t found_index = SearchOptionsForValue(state[0], 0, items_size);
+ if (found_index != kNotFound) {
+ ToHTMLOptionElement(items[found_index])->SetSelectedState(true);
+ ToHTMLOptionElement(items[found_index])->SetDirty(true);
+ last_on_change_option_ = ToHTMLOptionElement(items[found_index]);
+ }
+ }
+ } else {
+ size_t start_index = 0;
+ for (size_t i = 0; i < state.ValueSize(); i += 2) {
+ const String& value = state[i];
+ const size_t index = state[i + 1].ToUInt();
+ if (index < items_size && IsHTMLOptionElement(items[index]) &&
+ ToHTMLOptionElement(items[index])->value() == value) {
+ ToHTMLOptionElement(items[index])->SetSelectedState(true);
+ ToHTMLOptionElement(items[index])->SetDirty(true);
+ start_index = index + 1;
+ } else {
+ size_t found_index =
+ SearchOptionsForValue(value, start_index, items_size);
+ if (found_index == kNotFound)
+ found_index = SearchOptionsForValue(value, 0, start_index);
+ if (found_index == kNotFound)
+ continue;
+ ToHTMLOptionElement(items[found_index])->SetSelectedState(true);
+ ToHTMLOptionElement(items[found_index])->SetDirty(true);
+ start_index = found_index + 1;
+ }
+ }
+ }
+
+ SetNeedsValidityCheck();
+}
+
+void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) {
+ bool old_multiple = is_multiple_;
+ HTMLOptionElement* old_selected_option = SelectedOption();
+ is_multiple_ = !value.IsNull();
+ SetNeedsValidityCheck();
+ LazyReattachIfAttached();
+ // Restore selectedIndex after changing the multiple flag to preserve
+ // selection as single-line and multi-line has different defaults.
+ if (old_multiple != is_multiple_) {
+ // Preserving the first selection is compatible with Firefox and
+ // WebKit. However Edge seems to "ask for a reset" simply. As of 2016
+ // March, the HTML specification says nothing about this.
+ if (old_selected_option)
+ SelectOption(old_selected_option, kDeselectOtherOptions);
+ else
+ ResetToDefaultSelection();
+ }
+}
+
+void HTMLSelectElement::AppendToFormData(FormData& form_data) {
+ const AtomicString& name = GetName();
+ if (name.IsEmpty())
+ return;
+
+ for (auto* const option : GetOptionList()) {
+ if (option->Selected() && !option->IsDisabledFormControl())
+ form_data.append(name, option->value());
+ }
+}
+
+void HTMLSelectElement::ResetImpl() {
+ for (auto* const option : GetOptionList()) {
+ option->SetSelectedState(option->FastHasAttribute(selectedAttr));
+ option->SetDirty(false);
+ }
+ ResetToDefaultSelection();
+ 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(
+ 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(KeyboardEvent* event) {
+ LayoutTheme& layout_theme = LayoutTheme::GetTheme();
+ int key_code = event->keyCode();
+
+ return ((layout_theme.PopsMenuBySpaceKey() && event->keyCode() == ' ' &&
+ !type_ahead_.HasActiveSession(event)) ||
+ (layout_theme.PopsMenuByReturnKey() && key_code == '\r'));
+}
+
+void HTMLSelectElement::MenuListDefaultEventHandler(Event* event) {
+ if (event->type() == EventTypeNames::keydown) {
+ if (!GetLayoutObject() || !event->IsKeyboardEvent())
+ return;
+
+ KeyboardEvent* 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, kDeselectOtherOptions | kMakeOptionDirty |
+ kDispatchInputAndChangeEvent);
+ }
+
+ if (handled)
+ event->SetDefaultHandled();
+ }
+
+ if (event->type() == EventTypeNames::keypress) {
+ 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;
+ }
+
+ KeyboardEvent* 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() == EventTypeNames::mousedown && event->IsMouseEvent() &&
+ ToMouseEvent(event)->button() ==
+ static_cast<short>(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) {
+ Node* target_node = event.target()->ToNode();
+ if (!target_node || !IsHTMLOptionElement(*target_node))
+ return nullptr;
+ return ToHTMLOptionElement(target_node);
+}
+
+int HTMLSelectElement::ListIndexForOption(const HTMLOptionElement& option) {
+ const ListItems& items = GetListItems();
+ size_t length = items.size();
+ for (size_t i = 0; i < length; ++i) {
+ if (items[i].Get() == &option)
+ return i;
+ }
+ return -1;
+}
+
+AutoscrollController* HTMLSelectElement::GetAutoscrollController() const {
+ if (Page* page = GetDocument().GetPage())
+ return &page->GetAutoscrollController();
+ 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() == EventTypeNames::gesturetap && 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.
+ GestureEvent& 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() == EventTypeNames::mousedown &&
+ event->IsMouseEvent() &&
+ ToMouseEvent(event)->button() ==
+ static_cast<short>(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.
+ MouseEvent* 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() == EventTypeNames::mousemove &&
+ event->IsMouseEvent()) {
+ MouseEvent* mouse_event = ToMouseEvent(event);
+ if (mouse_event->button() !=
+ static_cast<short>(WebPointerProperties::Button::kLeft) ||
+ !mouse_event->ButtonDown())
+ return;
+
+ if (LayoutObject* object = GetLayoutObject())
+ object->GetFrameView()->UpdateAllLifecyclePhasesExceptPaint();
+
+ if (Page* page = GetDocument().GetPage()) {
+ page->GetAutoscrollController().StartAutoscrollForSelection(
+ GetLayoutObject());
+ }
+ // 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() == EventTypeNames::mouseup &&
+ event->IsMouseEvent() &&
+ ToMouseEvent(event)->button() ==
+ static_cast<short>(WebPointerProperties::Button::kLeft) &&
+ GetLayoutObject()) {
+ if (GetDocument().GetPage() &&
+ GetDocument()
+ .GetPage()
+ ->GetAutoscrollController()
+ .AutoscrollInProgressFor(ToLayoutBox(GetLayoutObject())))
+ GetDocument().GetPage()->GetAutoscrollController().StopAutoscroll();
+ else
+ HandleMouseRelease();
+
+ } else if (event->type() == EventTypeNames::keydown) {
+ 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;
+ }
+
+ 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);
+
+ bool select_new_item =
+ !is_multiple_ || ToKeyboardEvent(event)->shiftKey() ||
+ !IsSpatialNavigationEnabled(GetDocument().GetFrame());
+ if (select_new_item)
+ active_selection_state_ = true;
+ // If the anchor is unitialized, 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) {
+ UpdateListBoxSelection(deselect_others);
+ ListBoxOnChange();
+ } else {
+ ScrollToSelection();
+ }
+
+ event->SetDefaultHandled();
+ }
+
+ } else if (event->type() == EventTypeNames::keypress) {
+ 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())) {
+ // Use space to toggle selection change.
+ active_selection_state_ = !active_selection_state_;
+ UpdateSelectedState(active_selection_end_.Get(), true /*multi*/,
+ false /*shift*/);
+ ListBoxOnChange();
+ event->SetDefaultHandled();
+ }
+ }
+}
+
+void HTMLSelectElement::DefaultEventHandler(Event* event) {
+ if (!GetLayoutObject())
+ return;
+
+ if (IsDisabledFormControl()) {
+ HTMLFormControlElementWithState::DefaultEventHandler(event);
+ return;
+ }
+
+ if (UsesMenuList())
+ MenuListDefaultEventHandler(event);
+ else
+ ListBoxDefaultEventHandler(event);
+ if (event->DefaultHandled())
+ return;
+
+ if (event->type() == EventTypeNames::keypress && event->IsKeyboardEvent()) {
+ KeyboardEvent* keyboard_event = ToKeyboardEvent(event);
+ if (!keyboard_event->ctrlKey() && !keyboard_event->altKey() &&
+ !keyboard_event->metaKey() &&
+ WTF::Unicode::IsPrintableChar(keyboard_event->charCode())) {
+ TypeAheadFind(keyboard_event);
+ event->SetDefaultHandled();
+ return;
+ }
+ }
+ HTMLFormControlElementWithState::DefaultEventHandler(event);
+}
+
+HTMLOptionElement* HTMLSelectElement::LastSelectedOption() const {
+ const ListItems& items = GetListItems();
+ for (size_t i = items.size(); i;) {
+ if (HTMLOptionElement* option = OptionAtListIndex(--i)) {
+ if (option->Selected())
+ return option;
+ }
+ }
+ return nullptr;
+}
+
+int HTMLSelectElement::IndexOfSelectedOption() const {
+ return SelectedListIndex();
+}
+
+int HTMLSelectElement::OptionCount() const {
+ return GetListItems().size();
+}
+
+String HTMLSelectElement::OptionAtIndex(int index) const {
+ if (HTMLOptionElement* option = OptionAtListIndex(index)) {
+ if (!option->IsDisabledFormControl())
+ return option->DisplayLabel();
+ }
+ return String();
+}
+
+void HTMLSelectElement::TypeAheadFind(KeyboardEvent* event) {
+ int index = type_ahead_.HandleEvent(
+ event, TypeAhead::kMatchPrefix | TypeAhead::kCycleFirstChar);
+ if (index < 0)
+ return;
+ SelectOption(OptionAtListIndex(index), kDeselectOtherOptions |
+ kMakeOptionDirty |
+ kDispatchInputAndChangeEvent);
+ if (!UsesMenuList())
+ ListBoxOnChange();
+}
+
+void HTMLSelectElement::SelectOptionByAccessKey(HTMLOptionElement* option) {
+ // First bring into focus the list box.
+ if (!IsFocused())
+ AccessKeyAction(false);
+
+ if (!option || option->OwnerSelectElement() != this)
+ return;
+ EventQueueScope scope;
+ // If this index is already selected, unselect. otherwise update the
+ // selected index.
+ SelectOptionFlags flags =
+ kDispatchInputAndChangeEvent | (IsMultiple() ? 0 : kDeselectOtherOptions);
+ if (option->Selected()) {
+ if (UsesMenuList())
+ SelectOption(nullptr, flags);
+ else
+ option->SetSelectedState(false);
+ } else {
+ SelectOption(option, flags);
+ }
+ option->SetDirty(true);
+ if (UsesMenuList())
+ return;
+ ListBoxOnChange();
+ ScrollToSelection();
+}
+
+unsigned HTMLSelectElement::length() const {
+ unsigned options = 0;
+ for (auto* const option : GetOptionList()) {
+ ALLOW_UNUSED_LOCAL(option);
+ ++options;
+ }
+ return options;
+}
+
+void HTMLSelectElement::FinishParsingChildren() {
+ HTMLFormControlElementWithState::FinishParsingChildren();
+ if (UsesMenuList())
+ return;
+ ScrollToOption(SelectedOption());
+ if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
+ cache->ListboxActiveIndexChanged(this);
+}
+
+bool HTMLSelectElement::AnonymousIndexedSetter(
+ unsigned index,
+ HTMLOptionElement* value,
+ ExceptionState& exception_state) {
+ if (!value) { // undefined or null
+ remove(index);
+ return true;
+ }
+ SetOption(index, value, exception_state);
+ return true;
+}
+
+bool HTMLSelectElement::IsInteractiveContent() const {
+ return true;
+}
+
+bool HTMLSelectElement::SupportsAutofocus() const {
+ return true;
+}
+
+void HTMLSelectElement::UpdateListOnLayoutObject() {
+ SetOptionsChangedOnLayoutObject();
+}
+
+void HTMLSelectElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(list_items_);
+ visitor->Trace(last_on_change_option_);
+ visitor->Trace(active_selection_anchor_);
+ visitor->Trace(active_selection_end_);
+ visitor->Trace(option_to_scroll_to_);
+ visitor->Trace(suggested_option_);
+ visitor->Trace(popup_);
+ visitor->Trace(popup_updater_);
+ HTMLFormControlElementWithState::Trace(visitor);
+}
+
+void HTMLSelectElement::DidAddUserAgentShadowRoot(ShadowRoot& root) {
+ root.AppendChild(
+ HTMLSlotElement::CreateUserAgentCustomAssignSlot(GetDocument()));
+}
+
+HTMLOptionElement* HTMLSelectElement::SpatialNavigationFocusedOption() {
+ if (!IsSpatialNavigationEnabled(GetDocument().GetFrame()))
+ return nullptr;
+ HTMLOptionElement* focused_option = ActiveSelectionEnd();
+ if (!focused_option)
+ focused_option = FirstSelectableOption();
+ return focused_option;
+}
+
+String HTMLSelectElement::ItemText(const Element& element) const {
+ String item_string;
+ if (auto* optgroup = ToHTMLOptGroupElementOrNull(element))
+ item_string = optgroup->GroupLabelText();
+ else if (auto* option = ToHTMLOptionElementOrNull(element))
+ item_string = option->TextIndentedToRespectGroupLabel();
+
+ if (GetLayoutObject())
+ ApplyTextTransform(GetLayoutObject()->Style(), item_string, ' ');
+ return item_string;
+}
+
+bool HTMLSelectElement::ItemIsDisplayNone(Element& element) const {
+ if (auto* option = ToHTMLOptionElementOrNull(element))
+ return option->IsDisplayNone();
+ const ComputedStyle* style = ItemComputedStyle(element);
+ return !style || style->Display() == EDisplay::kNone;
+}
+
+const ComputedStyle* HTMLSelectElement::ItemComputedStyle(
+ Element& element) const {
+ return element.GetComputedStyle() ? element.GetComputedStyle()
+ : element.EnsureComputedStyle();
+}
+
+LayoutUnit HTMLSelectElement::ClientPaddingLeft() const {
+ if (GetLayoutObject() && GetLayoutObject()->IsMenuList())
+ return ToLayoutMenuList(GetLayoutObject())->ClientPaddingLeft();
+ return LayoutUnit();
+}
+
+LayoutUnit HTMLSelectElement::ClientPaddingRight() const {
+ if (GetLayoutObject() && GetLayoutObject()->IsMenuList())
+ return ToLayoutMenuList(GetLayoutObject())->ClientPaddingRight();
+ return LayoutUnit();
+}
+
+void HTMLSelectElement::PopupDidHide() {
+ popup_is_visible_ = false;
+ UnobserveTreeMutation();
+ if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
+ if (GetLayoutObject() && GetLayoutObject()->IsMenuList())
+ cache->DidHideMenuListPopup(ToLayoutMenuList(GetLayoutObject()));
+ }
+}
+
+void HTMLSelectElement::SetIndexToSelectOnCancel(int list_index) {
+ index_to_select_on_cancel_ = list_index;
+ if (GetLayoutObject())
+ GetLayoutObject()->UpdateFromElement();
+}
+
+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_;
+}
+
+void HTMLSelectElement::SelectOptionByPopup(int list_index) {
+ DCHECK(UsesMenuList());
+ // Check to ensure a page navigation has not occurred while the popup was
+ // up.
+ Document& doc = GetDocument();
+ if (&doc != doc.GetFrame()->GetDocument())
+ return;
+
+ SetIndexToSelectOnCancel(-1);
+
+ HTMLOptionElement* option = OptionAtListIndex(list_index);
+ // Bail out if this index is already the selected one, to avoid running
+ // unnecessary JavaScript that can mess up autofill when there is no actual
+ // change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and
+ // <rdar://7467917>). The selectOption function does not behave this way,
+ // possibly because other callers need a change event even in cases where
+ // the selected option is not change.
+ if (option == SelectedOption())
+ return;
+ SelectOption(option, kDeselectOtherOptions | kMakeOptionDirty |
+ kDispatchInputAndChangeEvent);
+}
+
+void HTMLSelectElement::PopupDidCancel() {
+ if (index_to_select_on_cancel_ >= 0)
+ SelectOptionByPopup(index_to_select_on_cancel_);
+}
+
+void HTMLSelectElement::ProvisionalSelectionChanged(unsigned list_index) {
+ SetIndexToSelectOnCancel(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);
+}
+
+void HTMLSelectElement::HidePopup() {
+ if (popup_)
+ popup_->Hide();
+}
+
+void HTMLSelectElement::DidRecalcStyle(StyleRecalcChange change) {
+ HTMLFormControlElementWithState::DidRecalcStyle(change);
+ if (PopupIsVisible())
+ popup_->UpdateFromElement(PopupMenu::kByStyleChange);
+}
+
+void HTMLSelectElement::DetachLayoutTree(const AttachContext& context) {
+ HTMLFormControlElementWithState::DetachLayoutTree(context);
+ if (popup_)
+ popup_->DisconnectClient();
+ popup_is_visible_ = false;
+ popup_ = nullptr;
+ UnobserveTreeMutation();
+}
+
+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;
+ 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 Element& element = *ToElement(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(blink::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_ = new 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);
+}
+
+} // 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
new file mode 100644
index 00000000000..76a8880e2f3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h
@@ -0,0 +1,319 @@
+/*
+ * 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) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights
+ * reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_SELECT_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_SELECT_ELEMENT_H_
+
+#include "base/gtest_prod_util.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"
+#include "third_party/blink/renderer/core/html/forms/option_list.h"
+#include "third_party/blink/renderer/core/html/forms/type_ahead.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class AutoscrollController;
+class ExceptionState;
+class HTMLHRElement;
+class HTMLOptGroupElement;
+class HTMLOptionElement;
+class HTMLOptionElementOrHTMLOptGroupElement;
+class HTMLElementOrLong;
+class PopupMenu;
+
+class CORE_EXPORT HTMLSelectElement final
+ : public HTMLFormControlElementWithState,
+ private TypeAheadDataSource {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLSelectElement* Create(Document&);
+ ~HTMLSelectElement() override;
+
+ int selectedIndex() const;
+ void setSelectedIndex(int);
+ // `listIndex' version of |selectedIndex|.
+ int SelectedListIndex() const;
+
+ // For ValidityState
+ String validationMessage() const override;
+ bool ValueMissing() const override;
+
+ String DefaultToolTip() const override;
+ void ResetImpl() override;
+
+ unsigned length() const;
+ void setLength(unsigned, ExceptionState&);
+
+ // TODO(tkent): Rename |size| to |Size|. This is not an implementation of
+ // |size| IDL attribute.
+ unsigned size() const { return size_; }
+ bool IsMultiple() const { return is_multiple_; }
+
+ bool UsesMenuList() const;
+
+ void add(const HTMLOptionElementOrHTMLOptGroupElement&,
+ const HTMLElementOrLong&,
+ ExceptionState&);
+
+ using Node::remove;
+ void remove(int index);
+
+ String value() const;
+ void setValue(const String&, bool send_events = false);
+ String SuggestedValue() const;
+ void SetSuggestedValue(const String&);
+
+ // |options| and |selectedOptions| are not safe to be used in in
+ // HTMLOptionElement::removedFrom() and insertedInto() because their cache
+ // is inconsistent in these functions.
+ HTMLOptionsCollection* options();
+ HTMLCollection* selectedOptions();
+
+ // This is similar to |options| HTMLCollection. But this is safe in
+ // HTMLOptionElement::removedFrom() and insertedInto().
+ // OptionList supports only forward iteration.
+ OptionList GetOptionList() const { return OptionList(*this); }
+
+ void OptionElementChildrenChanged(const HTMLOptionElement&);
+
+ void InvalidateSelectedItems();
+
+ using ListItems = HeapVector<Member<HTMLElement>>;
+ // We prefer |optionList()| to |listItems()|.
+ const ListItems& GetListItems() const;
+
+ void AccessKeyAction(bool send_mouse_events) override;
+ void SelectOptionByAccessKey(HTMLOptionElement*);
+
+ void SetOption(unsigned index, HTMLOptionElement*, ExceptionState&);
+
+ Element* namedItem(const AtomicString& name);
+ HTMLOptionElement* item(unsigned index);
+
+ void ScrollToSelection();
+ void ScrollToOption(HTMLOptionElement*);
+
+ bool CanSelectAll() const;
+ void SelectAll();
+ void ListBoxOnChange();
+ int ActiveSelectionEndListIndex() const;
+ HTMLOptionElement* ActiveSelectionEnd() const;
+ void SetActiveSelectionAnchor(HTMLOptionElement*);
+ void SetActiveSelectionEnd(HTMLOptionElement*);
+
+ // For use in the implementation of HTMLOptionElement.
+ void OptionSelectionStateChanged(HTMLOptionElement*, bool option_is_selected);
+ void OptionInserted(HTMLOptionElement&, bool option_is_selected);
+ void OptionRemoved(HTMLOptionElement&);
+ bool AnonymousIndexedSetter(unsigned, HTMLOptionElement*, ExceptionState&);
+
+ void OptGroupInsertedOrRemoved(HTMLOptGroupElement&);
+ void HrInsertedOrRemoved(HTMLHRElement&);
+
+ void UpdateListOnLayoutObject();
+
+ HTMLOptionElement* SpatialNavigationFocusedOption();
+ void HandleMouseRelease();
+
+ int ListIndexForOption(const HTMLOptionElement&);
+
+ // Helper functions for popup menu implementations.
+ String ItemText(const Element&) const;
+ bool ItemIsDisplayNone(Element&) const;
+ // itemComputedStyle() returns nullptr only if the owner Document is not
+ // active. So, It returns a valid object when we open a popup.
+ const ComputedStyle* ItemComputedStyle(Element&) const;
+ // Text starting offset in LTR.
+ LayoutUnit ClientPaddingLeft() const;
+ // Text starting offset in RTL.
+ LayoutUnit ClientPaddingRight() const;
+ void SelectOptionByPopup(int list_index);
+ void SelectMultipleOptionsByPopup(const Vector<int>& list_indices);
+ // A popup is canceled when the popup was hidden without selecting an item.
+ void PopupDidCancel();
+ // 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;
+ void ShowPopup();
+ void HidePopup();
+ PopupMenu* Popup() const { return popup_.Get(); }
+ void DidMutateSubtree();
+
+ void ResetTypeAheadSessionForTesting();
+
+ // Used for slot assignment.
+ static bool CanAssignToSelectSlot(const Node&);
+
+ bool HasNonInBodyInsertionMode() const override { return true; }
+
+ void Trace(blink::Visitor*) override;
+
+ protected:
+ explicit HTMLSelectElement(Document&);
+
+ private:
+ const AtomicString& FormControlType() const override;
+
+ bool ShouldShowFocusRingOnMouseFocus() const override;
+
+ void DispatchFocusEvent(
+ Element* old_focused_element,
+ WebFocusType,
+ InputDeviceCapabilities* source_capabilities) override;
+ void DispatchBlurEvent(Element* new_focused_element,
+ WebFocusType,
+ InputDeviceCapabilities* source_capabilities) override;
+
+ bool CanStartSelection() const override { return false; }
+
+ bool IsEnumeratable() const override { return true; }
+ bool IsInteractiveContent() const override;
+ bool SupportsAutofocus() const override;
+ bool SupportLabels() const override { return true; }
+
+ FormControlState SaveFormControlState() const override;
+ void RestoreFormControlState(const FormControlState&) override;
+
+ void ParseAttribute(const AttributeModificationParams&) override;
+ bool IsPresentationAttribute(const QualifiedName&) const override;
+
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ void DidRecalcStyle(StyleRecalcChange) override;
+ void DetachLayoutTree(const AttachContext& = AttachContext()) override;
+ void AppendToFormData(FormData&) override;
+ void DidAddUserAgentShadowRoot(ShadowRoot&) override;
+
+ void DefaultEventHandler(Event*) override;
+
+ void DispatchInputAndChangeEventForMenuList();
+
+ void SetRecalcListItems();
+ void RecalcListItems() const;
+ enum ResetReason { kResetReasonSelectedOptionRemoved, kResetReasonOthers };
+ void ResetToDefaultSelection(ResetReason = kResetReasonOthers);
+ void TypeAheadFind(KeyboardEvent*);
+ void SaveLastSelection();
+ void SaveListboxActiveSelection();
+ // Returns the first selected OPTION, or nullptr.
+ HTMLOptionElement* SelectedOption() const;
+
+ bool IsOptionalFormControl() const override {
+ return !IsRequiredFormControl();
+ }
+ bool IsRequiredFormControl() const override;
+
+ bool HasPlaceholderLabelOption() const;
+
+ enum SelectOptionFlag {
+ kDeselectOtherOptions = 1 << 0,
+ kDispatchInputAndChangeEvent = 1 << 1,
+ kMakeOptionDirty = 1 << 2,
+ };
+ typedef unsigned SelectOptionFlags;
+ void SelectOption(HTMLOptionElement*, SelectOptionFlags);
+ bool DeselectItemsWithoutValidation(
+ 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(KeyboardEvent*);
+ bool ShouldOpenPopupForKeyPressEvent(KeyboardEvent*);
+ void ListBoxDefaultEventHandler(Event*);
+ void SetOptionsChangedOnLayoutObject();
+ size_t SearchOptionsForValue(const String&,
+ size_t list_index_start,
+ size_t list_index_end) const;
+ void UpdateListBoxSelection(bool deselect_other_options, bool scroll = true);
+ void SetIndexToSelectOnCancel(int list_index);
+ void SetSuggestedOption(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;
+ void ScrollToOptionTask();
+
+ bool AreAuthorShadowsAllowed() const override { return false; }
+ void FinishParsingChildren() override;
+
+ // TypeAheadDataSource functions.
+ int IndexOfSelectedOption() const override;
+ int OptionCount() const override;
+ String OptionAtIndex(int index) const override;
+
+ void ObserveTreeMutation();
+ void UnobserveTreeMutation();
+
+ // m_listItems 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_;
+ Member<HTMLOptionElement> active_selection_anchor_;
+ Member<HTMLOptionElement> active_selection_end_;
+ Member<HTMLOptionElement> option_to_scroll_to_;
+ Member<HTMLOptionElement> suggested_option_;
+ bool is_multiple_;
+ bool active_selection_state_;
+ mutable bool should_recalc_list_items_;
+ bool is_autofilled_by_preview_;
+
+ class PopupUpdater;
+ Member<PopupUpdater> popup_updater_;
+ Member<PopupMenu> popup_;
+ 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);
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_SELECT_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.idl
new file mode 100644
index 00000000000..05e4a3703fd
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.idl
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
+ * Copyright (C) 2013 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ */
+
+// https://html.spec.whatwg.org/#the-select-element
+[HTMLConstructor]
+interface HTMLSelectElement : HTMLElement {
+ [CEReactions, Reflect] attribute DOMString autocomplete;
+ [CEReactions, Reflect] attribute boolean autofocus;
+ [CEReactions, Reflect] attribute boolean disabled;
+ [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form;
+ [CEReactions, Reflect] attribute boolean multiple;
+ [CEReactions, Reflect] attribute DOMString name;
+ [CEReactions, Reflect] attribute boolean required;
+ [CEReactions, Reflect] attribute unsigned long size;
+
+ readonly attribute DOMString type;
+
+ readonly attribute HTMLOptionsCollection options;
+ // TODO(foolip): The length setter should never throw.
+ [CEReactions, RaisesException=Setter] attribute unsigned long length;
+ getter Element? item(unsigned long index);
+ HTMLOptionElement? namedItem(DOMString name);
+ [CEReactions, RaisesException] void add((HTMLOptionElement or HTMLOptGroupElement) element,
+ optional (HTMLElement or long)? before = null);
+ [CEReactions, RaisesException] void remove(); // ChildNode overload
+ [CEReactions] void remove(long index);
+ [CEReactions, RaisesException] setter void (unsigned long index, HTMLOptionElement? option);
+
+ readonly attribute HTMLCollection selectedOptions;
+ attribute long selectedIndex;
+ attribute DOMString value;
+
+ readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ readonly attribute DOMString validationMessage;
+ boolean checkValidity();
+ boolean reportValidity();
+ void setCustomValidity(DOMString error);
+
+ readonly attribute NodeList labels;
+};
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
new file mode 100644
index 00000000000..0b21a2d6330
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc
@@ -0,0 +1,449 @@
+// Copyright 2014 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/html_select_element.h"
+
+#include <memory>
+#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/html/forms/form_controller.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+
+namespace blink {
+
+class HTMLSelectElementTest : public PageTestBase {
+ protected:
+ void SetUp() override;
+};
+
+void HTMLSelectElementTest::SetUp() {
+ Page::PageClients page_clients;
+ FillWithEmptyClients(page_clients);
+ PageTestBase::SetupPageWithClients(&page_clients);
+ GetDocument().SetMimeType("text/html");
+}
+
+TEST_F(HTMLSelectElementTest, SaveRestoreSelectSingleFormControlState) {
+ SetHtmlInnerHTML(
+ "<!DOCTYPE HTML><select id='sel'>"
+ "<option value='111' id='0'>111</option>"
+ "<option value='222'>222</option>"
+ "<option value='111' selected id='2'>!666</option>"
+ "<option value='999'>999</option></select>");
+ Element* element = GetElementById("sel");
+ HTMLFormControlElementWithState* select = ToHTMLSelectElement(element);
+ HTMLOptionElement* opt0 = ToHTMLOptionElement(GetElementById("0"));
+ HTMLOptionElement* opt2 = ToHTMLOptionElement(GetElementById("2"));
+
+ // Save the select element state, and then restore again.
+ // Test passes if the restored state is not changed.
+ EXPECT_EQ(2, ToHTMLSelectElement(element)->selectedIndex());
+ EXPECT_FALSE(opt0->Selected());
+ EXPECT_TRUE(opt2->Selected());
+ FormControlState select_state = select->SaveFormControlState();
+ EXPECT_EQ(2U, select_state.ValueSize());
+
+ // Clear the selected state, to be restored by restoreFormControlState.
+ ToHTMLSelectElement(select)->setSelectedIndex(-1);
+ ASSERT_FALSE(opt2->Selected());
+
+ // Restore
+ select->RestoreFormControlState(select_state);
+ EXPECT_EQ(2, ToHTMLSelectElement(element)->selectedIndex());
+ EXPECT_FALSE(opt0->Selected());
+ EXPECT_TRUE(opt2->Selected());
+}
+
+TEST_F(HTMLSelectElementTest, SaveRestoreSelectMultipleFormControlState) {
+ SetHtmlInnerHTML(
+ "<!DOCTYPE HTML><select id='sel' multiple>"
+ "<option value='111' id='0'>111</option>"
+ "<option value='222'>222</option>"
+ "<option value='111' selected id='2'>!666</option>"
+ "<option value='999' selected id='3'>999</option></select>");
+ HTMLFormControlElementWithState* select =
+ ToHTMLSelectElement(GetElementById("sel"));
+
+ HTMLOptionElement* opt0 = ToHTMLOptionElement(GetElementById("0"));
+ HTMLOptionElement* opt2 = ToHTMLOptionElement(GetElementById("2"));
+ HTMLOptionElement* opt3 = ToHTMLOptionElement(GetElementById("3"));
+
+ // Save the select element state, and then restore again.
+ // Test passes if the selected options are not changed.
+ EXPECT_FALSE(opt0->Selected());
+ EXPECT_TRUE(opt2->Selected());
+ EXPECT_TRUE(opt3->Selected());
+ FormControlState select_state = select->SaveFormControlState();
+ EXPECT_EQ(4U, select_state.ValueSize());
+
+ // Clear the selected state, to be restored by restoreFormControlState.
+ opt2->SetSelected(false);
+ opt3->SetSelected(false);
+ ASSERT_FALSE(opt2->Selected());
+ ASSERT_FALSE(opt3->Selected());
+
+ // Restore
+ select->RestoreFormControlState(select_state);
+ EXPECT_FALSE(opt0->Selected());
+ EXPECT_TRUE(opt2->Selected());
+ EXPECT_TRUE(opt3->Selected());
+}
+
+TEST_F(HTMLSelectElementTest, RestoreUnmatchedFormControlState) {
+ // We had a bug that selectedOption() and m_lastOnChangeOption were
+ // mismatched in optionToBeShown(). It happened when
+ // restoreFormControlState() couldn't find matched OPTIONs.
+ // crbug.com/627833.
+
+ SetHtmlInnerHTML(R"HTML(
+ <select id='sel'>
+ <option selected>Default</option>
+ <option id='2'>222</option>
+ </select>
+ )HTML");
+ Element* element = GetElementById("sel");
+ HTMLFormControlElementWithState* select = ToHTMLSelectElement(element);
+ HTMLOptionElement* opt2 = ToHTMLOptionElement(GetElementById("2"));
+
+ ToHTMLSelectElement(element)->setSelectedIndex(1);
+ // Save the current state.
+ FormControlState select_state = select->SaveFormControlState();
+ EXPECT_EQ(2U, select_state.ValueSize());
+
+ // Reset the status.
+ select->Reset();
+ ASSERT_FALSE(opt2->Selected());
+ element->RemoveChild(opt2);
+
+ // Restore
+ select->RestoreFormControlState(select_state);
+ EXPECT_EQ(-1, ToHTMLSelectElement(element)->selectedIndex());
+ EXPECT_EQ(nullptr, ToHTMLSelectElement(element)->OptionToBeShown());
+}
+
+TEST_F(HTMLSelectElementTest, VisibleBoundsInVisualViewport) {
+ SetHtmlInnerHTML(
+ "<select style='position:fixed; top:12.3px; height:24px; "
+ "-webkit-appearance:none;'><option>o1</select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ ASSERT_NE(select, nullptr);
+ IntRect bounds = select->VisibleBoundsInVisualViewport();
+ EXPECT_EQ(24, bounds.Height());
+}
+
+TEST_F(HTMLSelectElementTest, PopupIsVisible) {
+ SetHtmlInnerHTML("<select><option>o1</option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ ASSERT_NE(select, nullptr);
+ EXPECT_FALSE(select->PopupIsVisible());
+ select->ShowPopup();
+ EXPECT_TRUE(select->PopupIsVisible());
+ GetDocument().Shutdown();
+ EXPECT_FALSE(select->PopupIsVisible());
+}
+
+TEST_F(HTMLSelectElementTest, FirstSelectableOption) {
+ {
+ SetHtmlInnerHTML("<select></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ(nullptr, select->FirstSelectableOption());
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->FirstSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1 disabled></option><option "
+ "id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->FirstSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1 style='display:none'></option><option "
+ "id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->FirstSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><optgroup><option id=o1></option><option "
+ "id=o2></option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->FirstSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+}
+
+TEST_F(HTMLSelectElementTest, LastSelectableOption) {
+ {
+ SetHtmlInnerHTML("<select></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ(nullptr, select->LastSelectableOption());
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->LastSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2 "
+ "disabled></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->LastSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2 "
+ "style='display:none'></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->LastSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><optgroup><option id=o1></option><option "
+ "id=o2></option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->LastSelectableOption()->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+}
+
+TEST_F(HTMLSelectElementTest, NextSelectableOption) {
+ {
+ SetHtmlInnerHTML("<select></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ(nullptr, select->NextSelectableOption(nullptr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->NextSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1 disabled></option><option "
+ "id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->NextSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1 style='display:none'></option><option "
+ "id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->NextSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><optgroup><option id=o1></option><option "
+ "id=o2></option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->NextSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o1"));
+ EXPECT_EQ("o2", select->NextSelectableOption(option)->FastGetAttribute(
+ HTMLNames::idAttr));
+
+ EXPECT_EQ(nullptr, select->NextSelectableOption(
+ ToHTMLOptionElement(GetElementById("o2"))));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><optgroup><option "
+ "id=o2></option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o1"));
+ EXPECT_EQ("o2", select->NextSelectableOption(option)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+}
+
+TEST_F(HTMLSelectElementTest, PreviousSelectableOption) {
+ {
+ SetHtmlInnerHTML("<select></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ(nullptr, select->PreviousSelectableOption(nullptr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->PreviousSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2 "
+ "disabled></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->PreviousSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2 "
+ "style='display:none'></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o1", select->PreviousSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><optgroup><option id=o1></option><option "
+ "id=o2></option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ EXPECT_EQ("o2", select->PreviousSelectableOption(nullptr)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><option id=o2></option></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o2"));
+ EXPECT_EQ("o1", select->PreviousSelectableOption(option)->FastGetAttribute(
+ HTMLNames::idAttr));
+
+ EXPECT_EQ(nullptr, select->PreviousSelectableOption(
+ ToHTMLOptionElement(GetElementById("o1"))));
+ }
+ {
+ SetHtmlInnerHTML(
+ "<select><option id=o1></option><optgroup><option "
+ "id=o2></option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o2"));
+ EXPECT_EQ("o1", select->PreviousSelectableOption(option)->FastGetAttribute(
+ HTMLNames::idAttr));
+ }
+}
+
+TEST_F(HTMLSelectElementTest, ActiveSelectionEndAfterOptionRemoval) {
+ SetHtmlInnerHTML(
+ "<select><optgroup><option selected>o1</option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ HTMLOptionElement* option =
+ ToHTMLOptionElement(select->firstChild()->firstChild());
+ EXPECT_EQ(1, select->ActiveSelectionEndListIndex());
+ select->firstChild()->removeChild(option);
+ EXPECT_EQ(-1, select->ActiveSelectionEndListIndex());
+ select->AppendChild(option);
+ EXPECT_EQ(1, select->ActiveSelectionEndListIndex());
+}
+
+TEST_F(HTMLSelectElementTest, DefaultToolTip) {
+ SetHtmlInnerHTML(
+ "<select size=4><option value="
+ ">Placeholder</option><optgroup><option>o2</option></optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ Element* option = ToElement(select->firstChild());
+ Element* optgroup = ToElement(option->nextSibling());
+
+ EXPECT_EQ(String(), select->DefaultToolTip())
+ << "defaultToolTip for SELECT without FORM and without required "
+ "attribute should return null string.";
+ EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip());
+ EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip());
+
+ select->SetBooleanAttribute(HTMLNames::requiredAttr, true);
+ EXPECT_EQ("<<ValidationValueMissingForSelect>>", select->DefaultToolTip())
+ << "defaultToolTip for SELECT without FORM and with required attribute "
+ "should return a valueMissing message.";
+ EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip());
+ EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip());
+
+ HTMLFormElement* form = HTMLFormElement::Create(GetDocument());
+ GetDocument().body()->AppendChild(form);
+ form->AppendChild(select);
+ EXPECT_EQ("<<ValidationValueMissingForSelect>>", select->DefaultToolTip())
+ << "defaultToolTip for SELECT with FORM and required attribute should "
+ "return a valueMissing message.";
+ EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip());
+ EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip());
+
+ form->SetBooleanAttribute(HTMLNames::novalidateAttr, true);
+ EXPECT_EQ(String(), select->DefaultToolTip())
+ << "defaultToolTip for SELECT with FORM[novalidate] and required "
+ "attribute should return null string.";
+ EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip());
+ EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip());
+
+ option->remove();
+ optgroup->remove();
+ EXPECT_EQ(String(), option->DefaultToolTip());
+ EXPECT_EQ(String(), optgroup->DefaultToolTip());
+}
+
+TEST_F(HTMLSelectElementTest, SetRecalcListItemsByOptgroupRemoval) {
+ SetHtmlInnerHTML(
+ "<select><optgroup><option>sub1</option><option>sub2</option></"
+ "optgroup></select>");
+ HTMLSelectElement* select =
+ ToHTMLSelectElement(GetDocument().body()->firstChild());
+ select->SetInnerHTMLFromString("");
+ // PASS if setInnerHTML didn't have a check failure.
+}
+
+TEST_F(HTMLSelectElementTest, ScrollToOptionAfterLayoutCrash) {
+ // crbug.com/737447
+ // This test passes if no crash.
+ SetHtmlInnerHTML(R"HTML(
+ <style>*:checked { position:fixed; }</style>
+ <select multiple><<option>o1</option><option
+ selected>o2</option></select>
+ )HTML");
+}
+
+} // 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
new file mode 100644
index 00000000000..1e76096534f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
@@ -0,0 +1,636 @@
+/*
+ * 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, 2008, 2010 Apple Inc. All rights
+ * reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
+ * Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
+ *
+ * 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/html_text_area_element.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.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"
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/dom/exception_code.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
+#include "third_party/blink/renderer/core/events/before_text_inserted_event.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/use_counter.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/text_control_inner_elements.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.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/layout/layout_text_control_multi_line.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/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+static const unsigned kDefaultRows = 2;
+static const unsigned kDefaultCols = 20;
+
+static inline unsigned ComputeLengthForAPIValue(const String& text) {
+ unsigned length = text.length();
+ unsigned crlf_count = 0;
+ for (unsigned i = 0; i < length; ++i) {
+ if (text[i] == '\r' && i + 1 < length && text[i + 1] == '\n')
+ crlf_count++;
+ }
+ return text.length() - crlf_count;
+}
+
+HTMLTextAreaElement::HTMLTextAreaElement(Document& document)
+ : TextControlElement(textareaTag, document),
+ rows_(kDefaultRows),
+ cols_(kDefaultCols),
+ wrap_(kSoftWrap),
+ is_dirty_(false),
+ is_placeholder_visible_(false) {}
+
+HTMLTextAreaElement* HTMLTextAreaElement::Create(Document& document) {
+ HTMLTextAreaElement* text_area = new HTMLTextAreaElement(document);
+ text_area->EnsureUserAgentShadowRoot();
+ return text_area;
+}
+
+void HTMLTextAreaElement::DidAddUserAgentShadowRoot(ShadowRoot& root) {
+ root.AppendChild(CreateInnerEditorElement());
+}
+
+const AtomicString& HTMLTextAreaElement::FormControlType() const {
+ DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea"));
+ return textarea;
+}
+
+FormControlState HTMLTextAreaElement::SaveFormControlState() const {
+ return is_dirty_ ? FormControlState(value()) : FormControlState();
+}
+
+void HTMLTextAreaElement::RestoreFormControlState(
+ const FormControlState& state) {
+ setValue(state[0]);
+}
+
+void HTMLTextAreaElement::ChildrenChanged(const ChildrenChange& change) {
+ HTMLElement::ChildrenChanged(change);
+ SetLastChangeWasNotUserEdit();
+ if (is_dirty_)
+ SetInnerEditorValue(value());
+ else
+ SetNonDirtyValue(defaultValue());
+}
+
+bool HTMLTextAreaElement::IsPresentationAttribute(
+ const QualifiedName& name) const {
+ if (name == alignAttr) {
+ // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
+ // See http://bugs.webkit.org/show_bug.cgi?id=7075
+ return false;
+ }
+
+ if (name == wrapAttr)
+ return true;
+ return TextControlElement::IsPresentationAttribute(name);
+}
+
+void HTMLTextAreaElement::CollectStyleForPresentationAttribute(
+ const QualifiedName& name,
+ const AtomicString& value,
+ MutableCSSPropertyValueSet* style) {
+ if (name == wrapAttr) {
+ if (ShouldWrapText()) {
+ AddPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace,
+ CSSValuePreWrap);
+ AddPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap,
+ CSSValueBreakWord);
+ } else {
+ AddPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace,
+ CSSValuePre);
+ AddPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap,
+ CSSValueNormal);
+ }
+ } else {
+ TextControlElement::CollectStyleForPresentationAttribute(name, value,
+ style);
+ }
+}
+
+void HTMLTextAreaElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ const QualifiedName& name = params.name;
+ const AtomicString& value = params.new_value;
+ if (name == rowsAttr) {
+ unsigned rows = 0;
+ if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, rows) ||
+ rows <= 0 || rows > 0x7fffffffu)
+ rows = kDefaultRows;
+ if (rows_ != rows) {
+ rows_ = rows;
+ if (GetLayoutObject()) {
+ GetLayoutObject()
+ ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
+ LayoutInvalidationReason::kAttributeChanged);
+ }
+ }
+ } else if (name == colsAttr) {
+ unsigned cols = 0;
+ if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, cols) ||
+ cols <= 0 || cols > 0x7fffffffu)
+ cols = kDefaultCols;
+ if (cols_ != cols) {
+ cols_ = cols;
+ if (LayoutObject* layout_object = GetLayoutObject()) {
+ layout_object
+ ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
+ LayoutInvalidationReason::kAttributeChanged);
+ }
+ }
+ } else if (name == wrapAttr) {
+ // The virtual/physical values were a Netscape extension of HTML 3.0, now
+ // 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"))
+ wrap = kHardWrap;
+ else if (DeprecatedEqualIgnoringCase(value, "off"))
+ wrap = kNoWrap;
+ else
+ wrap = kSoftWrap;
+ if (wrap != wrap_) {
+ wrap_ = wrap;
+ if (LayoutObject* layout_object = GetLayoutObject()) {
+ layout_object
+ ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
+ LayoutInvalidationReason::kAttributeChanged);
+ }
+ }
+ } else if (name == accesskeyAttr) {
+ // ignore for the moment
+ } else if (name == maxlengthAttr) {
+ UseCounter::Count(GetDocument(), WebFeature::kTextAreaMaxLength);
+ SetNeedsValidityCheck();
+ } else if (name == minlengthAttr) {
+ UseCounter::Count(GetDocument(), WebFeature::kTextAreaMinLength);
+ SetNeedsValidityCheck();
+ } else {
+ TextControlElement::ParseAttribute(params);
+ }
+}
+
+LayoutObject* HTMLTextAreaElement::CreateLayoutObject(const ComputedStyle&) {
+ return new LayoutTextControlMultiLine(this);
+}
+
+void HTMLTextAreaElement::AppendToFormData(FormData& form_data) {
+ if (GetName().IsEmpty())
+ return;
+
+ GetDocument().UpdateStyleAndLayout();
+
+ const String& text =
+ (wrap_ == kHardWrap) ? ValueWithHardLineBreaks() : value();
+ form_data.append(GetName(), text);
+
+ const AtomicString& dirname_attr_value = FastGetAttribute(dirnameAttr);
+ if (!dirname_attr_value.IsNull())
+ form_data.append(dirname_attr_value, DirectionForFormData());
+}
+
+void HTMLTextAreaElement::ResetImpl() {
+ SetNonDirtyValue(defaultValue());
+}
+
+bool HTMLTextAreaElement::HasCustomFocusLogic() const {
+ return true;
+}
+
+bool HTMLTextAreaElement::IsKeyboardFocusable() const {
+ // If a given text area can be focused at all, then it will always be keyboard
+ // focusable.
+ return IsFocusable();
+}
+
+bool HTMLTextAreaElement::ShouldShowFocusRingOnMouseFocus() const {
+ return true;
+}
+
+void HTMLTextAreaElement::UpdateFocusAppearanceWithOptions(
+ SelectionBehaviorOnFocus selection_behavior,
+ const FocusOptions& options) {
+ switch (selection_behavior) {
+ case SelectionBehaviorOnFocus::kReset: // Fallthrough.
+ case SelectionBehaviorOnFocus::kRestore:
+ RestoreCachedSelection();
+ break;
+ case SelectionBehaviorOnFocus::kNone:
+ return;
+ }
+ if (!options.preventScroll()) {
+ if (GetDocument().GetFrame())
+ GetDocument().GetFrame()->Selection().RevealSelection();
+ }
+}
+
+void HTMLTextAreaElement::DefaultEventHandler(Event* event) {
+ if (GetLayoutObject() && (event->IsMouseEvent() || event->IsDragEvent() ||
+ event->HasInterface(EventNames::WheelEvent) ||
+ event->type() == EventTypeNames::blur))
+ ForwardEvent(event);
+ else if (GetLayoutObject() && event->IsBeforeTextInsertedEvent())
+ HandleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
+
+ TextControlElement::DefaultEventHandler(event);
+}
+
+void HTMLTextAreaElement::SubtreeHasChanged() {
+#if DCHECK_IS_ON()
+ // The innerEditor should have either Text nodes or a placeholder break
+ // element. If we see other nodes, it's a bug in editing code and we should
+ // fix it.
+ Element* inner_editor = InnerEditorElement();
+ for (Node& node : NodeTraversal::DescendantsOf(*inner_editor)) {
+ if (node.IsTextNode())
+ continue;
+ DCHECK(IsHTMLBRElement(node));
+ DCHECK_EQ(&node, inner_editor->lastChild());
+ }
+#endif
+ AddPlaceholderBreakElementIfNecessary();
+ SetValueBeforeFirstUserEditIfNotSet();
+ UpdateValue();
+ CheckIfValueWasReverted(value());
+ SetNeedsValidityCheck();
+ SetAutofilled(false);
+ UpdatePlaceholderVisibility();
+
+ if (!IsFocused())
+ return;
+
+ // When typing in a textarea, childrenChanged is not called, so we need to
+ // force the directionality check.
+ CalculateAndAdjustDirectionality();
+
+ DCHECK(GetDocument().IsActive());
+ GetDocument().GetPage()->GetChromeClient().DidChangeValueInTextField(*this);
+}
+
+void HTMLTextAreaElement::HandleBeforeTextInsertedEvent(
+ BeforeTextInsertedEvent* event) const {
+ DCHECK(event);
+ DCHECK(GetLayoutObject());
+ int signed_max_length = maxLength();
+ if (signed_max_length < 0)
+ return;
+ unsigned unsigned_max_length = static_cast<unsigned>(signed_max_length);
+
+ const String& current_value = InnerEditorValue();
+ unsigned current_length = ComputeLengthForAPIValue(current_value);
+ if (current_length + ComputeLengthForAPIValue(event->GetText()) <
+ unsigned_max_length)
+ return;
+
+ // selectionLength represents the selection length of this text field to be
+ // removed by this insertion.
+ // If the text field has no focus, we don't need to take account of the
+ // selection length. The selection is the source of text drag-and-drop in
+ // that case, and nothing in the text field will be removed.
+ unsigned selection_length = 0;
+ if (IsFocused()) {
+ // TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets
+ // needs to be audited. See http://crbug.com/590369 for more details.
+ GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+
+ selection_length = ComputeLengthForAPIValue(
+ GetDocument().GetFrame()->Selection().SelectedText());
+ }
+ DCHECK_GE(current_length, selection_length);
+ unsigned base_length = current_length - selection_length;
+ unsigned appendable_length =
+ unsigned_max_length > base_length ? unsigned_max_length - base_length : 0;
+ event->SetText(SanitizeUserInputValue(event->GetText(), appendable_length));
+}
+
+String HTMLTextAreaElement::SanitizeUserInputValue(const String& proposed_value,
+ unsigned max_length) {
+ unsigned submission_length = 0;
+ unsigned i = 0;
+ for (; i < proposed_value.length(); ++i) {
+ if (proposed_value[i] == '\r' && i + 1 < proposed_value.length() &&
+ proposed_value[i + 1] == '\n')
+ continue;
+ ++submission_length;
+ if (submission_length == max_length) {
+ ++i;
+ break;
+ }
+ if (submission_length > max_length)
+ break;
+ }
+ if (i > 0 && U16_IS_LEAD(proposed_value[i - 1]))
+ --i;
+ return proposed_value.Left(i);
+}
+
+void HTMLTextAreaElement::UpdateValue() {
+ value_ = InnerEditorValue();
+ NotifyFormStateChanged();
+ is_dirty_ = true;
+ UpdatePlaceholderVisibility();
+}
+
+String HTMLTextAreaElement::value() const {
+ return value_;
+}
+
+void HTMLTextAreaElement::setValue(const String& value,
+ TextFieldEventBehavior event_behavior,
+ TextControlSetValueSelection selection) {
+ SetValueCommon(value, event_behavior, selection);
+ is_dirty_ = true;
+}
+
+void HTMLTextAreaElement::SetNonDirtyValue(const String& value) {
+ SetValueCommon(value, kDispatchNoEvent,
+ TextControlSetValueSelection::kSetSelectionToEnd);
+ is_dirty_ = false;
+}
+
+void HTMLTextAreaElement::SetValueCommon(
+ const String& new_value,
+ TextFieldEventBehavior event_behavior,
+ TextControlSetValueSelection selection) {
+ // 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.IsNull() ? "" : new_value;
+ normalized_value.Replace("\r\n", "\n");
+ normalized_value.Replace('\r', '\n');
+
+ // Clear the suggested value. Use the base class version to not trigger a view
+ // update.
+ TextControlElement::SetSuggestedValue(String());
+
+ // Return early because we don't want to trigger other side effects when the
+ // value isn't changing. This is interoperable.
+ if (normalized_value == value())
+ return;
+
+ if (event_behavior != kDispatchNoEvent)
+ SetValueBeforeFirstUserEditIfNotSet();
+ value_ = normalized_value;
+ SetInnerEditorValue(value_);
+ if (event_behavior == kDispatchNoEvent)
+ SetLastChangeWasNotUserEdit();
+ else
+ CheckIfValueWasReverted(value_);
+ UpdatePlaceholderVisibility();
+ SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
+ SetNeedsValidityCheck();
+ if (IsFinishedParsingChildren() &&
+ selection == TextControlSetValueSelection::kSetSelectionToEnd) {
+ // Set the caret to the end of the text value except for initialize.
+ unsigned end_of_string = value_.length();
+ SetSelectionRange(end_of_string, end_of_string);
+ }
+
+ NotifyFormStateChanged();
+ switch (event_behavior) {
+ case kDispatchChangeEvent:
+ DispatchFormControlChangeEvent();
+ break;
+
+ case kDispatchInputAndChangeEvent:
+ DispatchInputEvent();
+ DispatchFormControlChangeEvent();
+ break;
+
+ case kDispatchNoEvent:
+ break;
+ }
+}
+
+String HTMLTextAreaElement::defaultValue() const {
+ StringBuilder value;
+
+ // Since there may be comments, ignore nodes other than text nodes.
+ for (Node* n = firstChild(); n; n = n->nextSibling()) {
+ if (n->IsTextNode())
+ value.Append(ToText(n)->data());
+ }
+
+ return value.ToString();
+}
+
+void HTMLTextAreaElement::setDefaultValue(const String& default_value) {
+ // To preserve comments, remove only the text nodes, then add a single text
+ // node.
+ HeapVector<Member<Node>> text_nodes;
+ for (Node* n = firstChild(); n; n = n->nextSibling()) {
+ if (n->IsTextNode())
+ text_nodes.push_back(n);
+ }
+ for (const auto& text : text_nodes)
+ RemoveChild(text.Get(), IGNORE_EXCEPTION_FOR_TESTING);
+
+ // Normalize line endings.
+ String value = default_value;
+ value.Replace("\r\n", "\n");
+ value.Replace('\r', '\n');
+
+ InsertBefore(GetDocument().createTextNode(value), firstChild(),
+ IGNORE_EXCEPTION_FOR_TESTING);
+
+ if (!is_dirty_)
+ SetNonDirtyValue(value);
+}
+
+void HTMLTextAreaElement::SetSuggestedValue(const String& value) {
+ TextControlElement::SetSuggestedValue(value);
+ SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
+}
+
+String HTMLTextAreaElement::validationMessage() const {
+ if (!willValidate())
+ return String();
+
+ if (CustomError())
+ return CustomValidationMessage();
+
+ if (ValueMissing())
+ return GetLocale().QueryString(WebLocalizedString::kValidationValueMissing);
+
+ if (TooLong()) {
+ return GetLocale().ValidationMessageTooLongText(value().length(),
+ maxLength());
+ }
+
+ if (TooShort()) {
+ return GetLocale().ValidationMessageTooShortText(value().length(),
+ minLength());
+ }
+
+ return String();
+}
+
+bool HTMLTextAreaElement::ValueMissing() const {
+ // We should not call value() for performance.
+ return willValidate() && ValueMissing(nullptr);
+}
+
+bool HTMLTextAreaElement::ValueMissing(const String* value) const {
+ return IsRequiredFormControl() && !IsDisabledOrReadOnly() &&
+ (value ? *value : this->value()).IsEmpty();
+}
+
+bool HTMLTextAreaElement::TooLong() const {
+ // We should not call value() for performance.
+ return willValidate() && TooLong(nullptr, kCheckDirtyFlag);
+}
+
+bool HTMLTextAreaElement::TooShort() const {
+ // We should not call value() for performance.
+ return willValidate() && TooShort(nullptr, kCheckDirtyFlag);
+}
+
+bool HTMLTextAreaElement::TooLong(const String* value,
+ NeedsToCheckDirtyFlag check) const {
+ // Return false for the default value or value set by script even if it is
+ // longer than maxLength.
+ if (check == kCheckDirtyFlag && !LastChangeWasUserEdit())
+ return false;
+
+ int max = maxLength();
+ if (max < 0)
+ return false;
+ unsigned len =
+ value ? ComputeLengthForAPIValue(*value) : this->value().length();
+ return len > static_cast<unsigned>(max);
+}
+
+bool HTMLTextAreaElement::TooShort(const String* value,
+ NeedsToCheckDirtyFlag check) const {
+ // Return false for the default value or value set by script even if it is
+ // shorter than minLength.
+ if (check == kCheckDirtyFlag && !LastChangeWasUserEdit())
+ return false;
+
+ int min = minLength();
+ if (min <= 0)
+ return false;
+ // An empty string is excluded from minlength check.
+ unsigned len =
+ value ? ComputeLengthForAPIValue(*value) : this->value().length();
+ return len > 0 && len < static_cast<unsigned>(min);
+}
+
+bool HTMLTextAreaElement::IsValidValue(const String& candidate) const {
+ return !ValueMissing(&candidate) && !TooLong(&candidate, kIgnoreDirtyFlag) &&
+ !TooShort(&candidate, kIgnoreDirtyFlag);
+}
+
+void HTMLTextAreaElement::AccessKeyAction(bool) {
+ focus();
+}
+
+void HTMLTextAreaElement::setCols(unsigned cols) {
+ SetUnsignedIntegralAttribute(colsAttr, cols ? cols : kDefaultCols,
+ kDefaultCols);
+}
+
+void HTMLTextAreaElement::setRows(unsigned rows) {
+ SetUnsignedIntegralAttribute(rowsAttr, rows ? rows : kDefaultRows,
+ kDefaultRows);
+}
+
+bool HTMLTextAreaElement::MatchesReadOnlyPseudoClass() const {
+ return IsReadOnly();
+}
+
+bool HTMLTextAreaElement::MatchesReadWritePseudoClass() const {
+ return !IsReadOnly();
+}
+
+void HTMLTextAreaElement::SetPlaceholderVisibility(bool visible) {
+ is_placeholder_visible_ = visible;
+}
+
+void HTMLTextAreaElement::UpdatePlaceholderText() {
+ HTMLElement* placeholder = PlaceholderElement();
+ const String placeholder_text = GetPlaceholderValue();
+ if (placeholder_text.IsEmpty()) {
+ if (placeholder)
+ UserAgentShadowRoot()->RemoveChild(placeholder);
+ return;
+ }
+ if (!placeholder) {
+ HTMLDivElement* new_element = HTMLDivElement::Create(GetDocument());
+ placeholder = new_element;
+ placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder"));
+ placeholder->setAttribute(idAttr, ShadowElementNames::Placeholder());
+ placeholder->SetInlineStyleProperty(
+ CSSPropertyDisplay,
+ IsPlaceholderVisible() ? CSSValueBlock : CSSValueNone, true);
+ UserAgentShadowRoot()->InsertBefore(placeholder, InnerEditorElement());
+ }
+ placeholder->setTextContent(placeholder_text);
+}
+
+String HTMLTextAreaElement::GetPlaceholderValue() const {
+ return !SuggestedValue().IsEmpty() ? SuggestedValue()
+ : FastGetAttribute(placeholderAttr);
+}
+
+bool HTMLTextAreaElement::IsInteractiveContent() const {
+ return true;
+}
+
+bool HTMLTextAreaElement::SupportsAutofocus() const {
+ return true;
+}
+
+void HTMLTextAreaElement::CloneNonAttributePropertiesFrom(
+ const Element& source,
+ CloneChildrenFlag flag) {
+ const HTMLTextAreaElement& source_element = ToHTMLTextAreaElement(source);
+ SetValueCommon(source_element.value(), kDispatchNoEvent,
+ TextControlSetValueSelection::kSetSelectionToEnd);
+ is_dirty_ = source_element.is_dirty_;
+ TextControlElement::CloneNonAttributePropertiesFrom(source, flag);
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..9c4347d1733
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_TEXT_AREA_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_TEXT_AREA_ELEMENT_H_
+
+#include "base/gtest_prod_util.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
+
+namespace blink {
+
+class BeforeTextInsertedEvent;
+
+class CORE_EXPORT HTMLTextAreaElement final : public TextControlElement {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static HTMLTextAreaElement* Create(Document&);
+
+ unsigned cols() const { return cols_; }
+ unsigned rows() const { return rows_; }
+
+ bool ShouldWrapText() const { return wrap_ != kNoWrap; }
+
+ String value() const override;
+ void setValue(const String&,
+ TextFieldEventBehavior = kDispatchNoEvent,
+ TextControlSetValueSelection =
+ TextControlSetValueSelection::kSetSelectionToEnd) override;
+ String defaultValue() const;
+ void setDefaultValue(const String&);
+ int textLength() const { return value().length(); }
+
+ void SetSuggestedValue(const String& value) override;
+
+ // For ValidityState
+ String validationMessage() const override;
+ bool ValueMissing() const override;
+ bool TooLong() const override;
+ bool TooShort() const override;
+ bool IsValidValue(const String&) const;
+
+ void setCols(unsigned);
+ void setRows(unsigned);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HTMLTextAreaElementTest, SanitizeUserInputValue);
+ explicit HTMLTextAreaElement(Document&);
+
+ enum WrapMethod { kNoWrap, kSoftWrap, kHardWrap };
+
+ void DidAddUserAgentShadowRoot(ShadowRoot&) override;
+ // FIXME: Author shadows should be allowed
+ // https://bugs.webkit.org/show_bug.cgi?id=92608
+ bool AreAuthorShadowsAllowed() const override { return false; }
+
+ void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) const;
+ static String SanitizeUserInputValue(const String&, unsigned max_length);
+ void UpdateValue();
+ void SetNonDirtyValue(const String&);
+ void SetValueCommon(const String&,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection);
+
+ bool IsPlaceholderVisible() const override { return is_placeholder_visible_; }
+ void SetPlaceholderVisibility(bool) override;
+ bool SupportsPlaceholder() const override { return true; }
+ String GetPlaceholderValue() const final;
+ void UpdatePlaceholderText() override;
+ bool IsEmptyValue() const override { return value().IsEmpty(); }
+
+ bool IsOptionalFormControl() const override {
+ return !IsRequiredFormControl();
+ }
+ bool IsRequiredFormControl() const override { return IsRequired(); }
+
+ void DefaultEventHandler(Event*) override;
+
+ void SubtreeHasChanged() override;
+
+ bool IsEnumeratable() const override { return true; }
+ bool IsInteractiveContent() const override;
+ bool SupportsAutofocus() const override;
+ bool SupportLabels() const override { return true; }
+
+ const AtomicString& FormControlType() const override;
+
+ FormControlState SaveFormControlState() const override;
+ void RestoreFormControlState(const FormControlState&) override;
+
+ bool IsTextControl() const override { return true; }
+
+ void ChildrenChanged(const ChildrenChange&) override;
+ void ParseAttribute(const AttributeModificationParams&) override;
+ bool IsPresentationAttribute(const QualifiedName&) const override;
+ void CollectStyleForPresentationAttribute(
+ const QualifiedName&,
+ const AtomicString&,
+ MutableCSSPropertyValueSet*) override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ void AppendToFormData(FormData&) override;
+ void ResetImpl() override;
+ bool HasCustomFocusLogic() const override;
+ bool ShouldShowFocusRingOnMouseFocus() const override;
+ bool IsKeyboardFocusable() const override;
+ void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus,
+ const FocusOptions&) override;
+
+ void AccessKeyAction(bool send_mouse_events) override;
+
+ bool MatchesReadOnlyPseudoClass() const override;
+ bool MatchesReadWritePseudoClass() const override;
+ void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) final;
+
+ // If the String* argument is 0, apply value().
+ bool ValueMissing(const String*) const;
+ bool TooLong(const String*, NeedsToCheckDirtyFlag) const;
+ bool TooShort(const String*, NeedsToCheckDirtyFlag) const;
+
+ unsigned rows_;
+ unsigned cols_;
+ WrapMethod wrap_;
+ mutable String value_;
+ mutable bool is_dirty_;
+ unsigned is_placeholder_visible_ : 1;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_TEXT_AREA_ELEMENT_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.idl
new file mode 100644
index 00000000000..5fc9a25ec0c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.idl
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
+ * Copyright (C) 2011 Motorola Mobility, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+// https://html.spec.whatwg.org/#the-textarea-element
+[HTMLConstructor]
+interface HTMLTextAreaElement : HTMLElement {
+ [CEReactions, Reflect] attribute DOMString autocomplete;
+ [CEReactions, Reflect] attribute boolean autofocus;
+ [CEReactions] attribute unsigned long cols;
+ [CEReactions, Reflect] attribute DOMString dirName;
+ [CEReactions, Reflect] attribute boolean disabled;
+ [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form;
+ [CEReactions, RaisesException=Setter] attribute long maxLength;
+ [CEReactions, RaisesException=Setter] attribute long minLength;
+ [CEReactions, Reflect] attribute DOMString name;
+ [CEReactions, Reflect] attribute DOMString placeholder;
+ [CEReactions, Reflect] attribute boolean readOnly;
+ [CEReactions, Reflect] attribute boolean required;
+ [CEReactions] attribute unsigned long rows;
+ [CEReactions, Reflect] attribute DOMString wrap;
+
+ readonly attribute DOMString type;
+ [CEReactions] attribute DOMString defaultValue;
+ [CEReactions] attribute [TreatNullAs=NullString] DOMString value;
+ readonly attribute unsigned long textLength;
+
+ readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ readonly attribute DOMString validationMessage;
+ boolean checkValidity();
+ boolean reportValidity();
+ void setCustomValidity(DOMString error);
+
+ readonly attribute NodeList labels;
+
+ void select();
+ attribute unsigned long selectionStart;
+ attribute unsigned long selectionEnd;
+ attribute DOMString selectionDirection;
+ [RaisesException] void setRangeText(DOMString replacement);
+ [RaisesException] void setRangeText(DOMString replacement,
+ unsigned long start,
+ unsigned long end,
+ optional SelectionMode selectionMode = "preserve");
+ [ImplementedAs=setSelectionRangeForBinding]
+ void setSelectionRange(unsigned long start,
+ unsigned long end,
+ optional DOMString direction);
+};
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element_test.cc
new file mode 100644
index 00000000000..1c2b730cd23
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2016 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/html_text_area_element.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+TEST(HTMLTextAreaElementTest, SanitizeUserInputValue) {
+ UChar kLeadSurrogate = 0xD800;
+ EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("", 0));
+ EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("a", 0));
+ EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("\n", 0));
+ StringBuilder builder;
+ builder.Append(kLeadSurrogate);
+ String lead_surrogate = builder.ToString();
+ EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue(lead_surrogate, 0));
+
+ EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("", 1));
+ EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue(lead_surrogate, 1));
+ EXPECT_EQ("a", HTMLTextAreaElement::SanitizeUserInputValue("a", 1));
+ EXPECT_EQ("\n", HTMLTextAreaElement::SanitizeUserInputValue("\n", 1));
+ EXPECT_EQ("\n", HTMLTextAreaElement::SanitizeUserInputValue("\n", 2));
+
+ EXPECT_EQ("abc", HTMLTextAreaElement::SanitizeUserInputValue(
+ String("abc") + lead_surrogate, 4));
+ EXPECT_EQ("a\ncd", HTMLTextAreaElement::SanitizeUserInputValue("a\ncdef", 4));
+ EXPECT_EQ("a\rcd", HTMLTextAreaElement::SanitizeUserInputValue("a\rcdef", 4));
+ EXPECT_EQ("a\r\ncd",
+ HTMLTextAreaElement::SanitizeUserInputValue("a\r\ncdef", 4));
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..fed797033fd
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All
+ * rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * 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/image_input_type.h"
+
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/dom/sync_reattach_context.h"
+#include "third_party/blink/renderer/core/events/mouse_event.h"
+#include "third_party/blink/renderer/core/frame/deprecation.h"
+#include "third_party/blink/renderer/core/frame/use_counter.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_input_element.h"
+#include "third_party/blink/renderer/core/html/html_image_fallback_helper.h"
+#include "third_party/blink/renderer/core/html/html_image_loader.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_type_names.h"
+#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/layout_image.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline ImageInputType::ImageInputType(HTMLInputElement& element)
+ : BaseButtonInputType(element), use_fallback_content_(false) {}
+
+InputType* ImageInputType::Create(HTMLInputElement& element) {
+ return new ImageInputType(element);
+}
+
+const AtomicString& ImageInputType::FormControlType() const {
+ return InputTypeNames::image;
+}
+
+bool ImageInputType::IsFormDataAppendable() const {
+ return true;
+}
+
+void ImageInputType::AppendToFormData(FormData& form_data) const {
+ if (!GetElement().IsActivatedSubmit())
+ return;
+ const AtomicString& name = GetElement().GetName();
+ if (name.IsEmpty()) {
+ form_data.append("x", click_location_.X());
+ form_data.append("y", click_location_.Y());
+ return;
+ }
+
+ DEFINE_STATIC_LOCAL(String, dot_x_string, (".x"));
+ DEFINE_STATIC_LOCAL(String, dot_y_string, (".y"));
+ form_data.append(name + dot_x_string, click_location_.X());
+ form_data.append(name + dot_y_string, click_location_.Y());
+
+ if (!GetElement().value().IsEmpty()) {
+ Deprecation::CountDeprecation(
+ GetElement().GetDocument(),
+ WebFeature::kImageInputTypeFormDataWithNonEmptyValue);
+ form_data.append(name, GetElement().value());
+ }
+}
+
+String ImageInputType::ResultForDialogSubmit() const {
+ StringBuilder result;
+ result.AppendNumber(click_location_.X());
+ result.Append(',');
+ result.AppendNumber(click_location_.Y());
+ return result.ToString();
+}
+
+bool ImageInputType::SupportsValidation() const {
+ return false;
+}
+
+static IntPoint ExtractClickLocation(Event* event) {
+ if (!event->UnderlyingEvent() || !event->UnderlyingEvent()->IsMouseEvent())
+ return IntPoint();
+ MouseEvent* mouse_event = ToMouseEvent(event->UnderlyingEvent());
+ if (!mouse_event->HasPosition())
+ return IntPoint();
+ return IntPoint(mouse_event->offsetX(), mouse_event->offsetY());
+}
+
+void ImageInputType::HandleDOMActivateEvent(Event* event) {
+ if (GetElement().IsDisabledFormControl() || !GetElement().Form())
+ return;
+ click_location_ = ExtractClickLocation(event);
+ GetElement().Form()->PrepareForSubmission(
+ event, &GetElement()); // Event handlers can run.
+ event->SetDefaultHandled();
+}
+
+LayoutObject* ImageInputType::CreateLayoutObject(
+ const ComputedStyle& style) const {
+ if (use_fallback_content_)
+ return new LayoutBlockFlow(&GetElement());
+ LayoutImage* image = new LayoutImage(&GetElement());
+ image->SetImageResource(LayoutImageResource::Create());
+ return image;
+}
+
+void ImageInputType::AltAttributeChanged() {
+ if (GetElement().UserAgentShadowRoot()) {
+ Element* text =
+ GetElement().UserAgentShadowRoot()->getElementById("alttext");
+ String value = GetElement().AltText();
+ if (text && text->textContent() != value)
+ text->setTextContent(GetElement().AltText());
+ }
+}
+
+void ImageInputType::SrcAttributeChanged() {
+ if (!GetElement().GetLayoutObject())
+ return;
+ GetElement().EnsureImageLoader().UpdateFromElement(
+ ImageLoader::kUpdateIgnorePreviousError);
+}
+
+void ImageInputType::ValueAttributeChanged() {
+ if (use_fallback_content_)
+ return;
+ BaseButtonInputType::ValueAttributeChanged();
+}
+
+void ImageInputType::StartResourceLoading() {
+ BaseButtonInputType::StartResourceLoading();
+
+ HTMLImageLoader& image_loader = GetElement().EnsureImageLoader();
+ image_loader.UpdateFromElement();
+
+ LayoutObject* layout_object = GetElement().GetLayoutObject();
+ if (!layout_object || !layout_object->IsLayoutImage())
+ return;
+
+ LayoutImageResource* image_resource =
+ ToLayoutImage(layout_object)->ImageResource();
+ image_resource->SetImageResource(image_loader.GetContent());
+}
+
+bool ImageInputType::ShouldRespectAlignAttribute() {
+ return true;
+}
+
+bool ImageInputType::CanBeSuccessfulSubmitButton() {
+ return true;
+}
+
+bool ImageInputType::IsEnumeratable() {
+ return false;
+}
+
+bool ImageInputType::ShouldRespectHeightAndWidthAttributes() {
+ return true;
+}
+
+unsigned ImageInputType::Height() const {
+ if (!GetElement().GetLayoutObject()) {
+ // Check the attribute first for an explicit pixel value.
+ unsigned height;
+ if (ParseHTMLNonNegativeInteger(GetElement().FastGetAttribute(heightAttr),
+ height))
+ return height;
+
+ // If the image is available, use its height.
+ HTMLImageLoader* image_loader = GetElement().ImageLoader();
+ if (image_loader && image_loader->GetContent()) {
+ return image_loader->GetContent()
+ ->IntrinsicSize(LayoutObject::ShouldRespectImageOrientation(nullptr))
+ .Height();
+ }
+ }
+
+ GetElement().GetDocument().UpdateStyleAndLayout();
+
+ LayoutBox* box = GetElement().GetLayoutBox();
+ return box ? AdjustForAbsoluteZoom::AdjustInt(box->ContentHeight().ToInt(),
+ box)
+ : 0;
+}
+
+unsigned ImageInputType::Width() const {
+ if (!GetElement().GetLayoutObject()) {
+ // Check the attribute first for an explicit pixel value.
+ unsigned width;
+ if (ParseHTMLNonNegativeInteger(GetElement().FastGetAttribute(widthAttr),
+ width))
+ return width;
+
+ // If the image is available, use its width.
+ HTMLImageLoader* image_loader = GetElement().ImageLoader();
+ if (image_loader && image_loader->GetContent()) {
+ return image_loader->GetContent()
+ ->IntrinsicSize(LayoutObject::ShouldRespectImageOrientation(nullptr))
+ .Width();
+ }
+ }
+
+ GetElement().GetDocument().UpdateStyleAndLayout();
+
+ LayoutBox* box = GetElement().GetLayoutBox();
+ return box ? AdjustForAbsoluteZoom::AdjustInt(box->ContentWidth().ToInt(),
+ box)
+ : 0;
+}
+
+bool ImageInputType::HasLegalLinkAttribute(const QualifiedName& name) const {
+ return name == srcAttr || BaseButtonInputType::HasLegalLinkAttribute(name);
+}
+
+const QualifiedName& ImageInputType::SubResourceAttributeName() const {
+ return srcAttr;
+}
+
+void ImageInputType::EnsureFallbackContent() {
+ if (use_fallback_content_)
+ return;
+ SetUseFallbackContent();
+ ReattachFallbackContent();
+}
+
+void ImageInputType::SetUseFallbackContent() {
+ if (use_fallback_content_)
+ return;
+ use_fallback_content_ = true;
+ if (GetElement().GetDocument().InStyleRecalc())
+ return;
+ if (ShadowRoot* root = GetElement().UserAgentShadowRoot())
+ root->RemoveChildren();
+ CreateShadowSubtree();
+}
+
+void ImageInputType::EnsurePrimaryContent() {
+ if (!use_fallback_content_)
+ return;
+ use_fallback_content_ = false;
+ if (ShadowRoot* root = GetElement().UserAgentShadowRoot())
+ root->RemoveChildren();
+ CreateShadowSubtree();
+ ReattachFallbackContent();
+}
+
+void ImageInputType::ReattachFallbackContent() {
+ if (GetElement().GetDocument().InStyleRecalc()) {
+ // This can happen inside of AttachLayoutTree() in the middle of a
+ // RebuildLayoutTree, so we need to reattach synchronously here.
+ GetElement().ReattachLayoutTree(
+ SyncReattachContext::CurrentAttachContext());
+ } else {
+ GetElement().LazyReattachIfAttached();
+ }
+}
+
+void ImageInputType::CreateShadowSubtree() {
+ if (!use_fallback_content_) {
+ BaseButtonInputType::CreateShadowSubtree();
+ return;
+ }
+ 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));
+}
+
+} // 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
new file mode 100644
index 00000000000..13f721c24e0
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_IMAGE_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_IMAGE_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h"
+#include "third_party/blink/renderer/platform/geometry/int_point.h"
+
+namespace blink {
+
+class ImageInputType final : public BaseButtonInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+ scoped_refptr<ComputedStyle> CustomStyleForLayoutObject(
+ scoped_refptr<ComputedStyle>) override;
+
+ private:
+ ImageInputType(HTMLInputElement&);
+ const AtomicString& FormControlType() const override;
+ bool IsFormDataAppendable() const override;
+ void AppendToFormData(FormData&) const override;
+ String ResultForDialogSubmit() const override;
+ bool SupportsValidation() const override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) const override;
+ void HandleDOMActivateEvent(Event*) override;
+ void AltAttributeChanged() override;
+ void SrcAttributeChanged() override;
+ void ValueAttributeChanged() override;
+ void StartResourceLoading() override;
+ bool ShouldRespectAlignAttribute() override;
+ bool CanBeSuccessfulSubmitButton() override;
+ bool IsEnumeratable() override;
+ bool ShouldRespectHeightAndWidthAttributes() override;
+ unsigned Height() const override;
+ unsigned Width() const override;
+ bool HasLegalLinkAttribute(const QualifiedName&) const override;
+ const QualifiedName& SubResourceAttributeName() const override;
+ void EnsureFallbackContent() override;
+ void EnsurePrimaryContent() override;
+ void CreateShadowSubtree() override;
+
+ void ReattachFallbackContent();
+ void SetUseFallbackContent();
+ bool HasFallbackContent() const override { return use_fallback_content_; }
+
+ // Valid only during HTMLFormElement::prepareForSubmission().
+ IntPoint click_location_;
+
+ bool use_fallback_content_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_IMAGE_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..6e0ad2c33b7
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/input_type.cc
@@ -0,0 +1,910 @@
+/*
+ * 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, 2008, 2009, 2010, 2011 Apple Inc. All
+ * rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
+ * Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
+ * Copyright (C) 2009, 2010, 2011, 2012 Google Inc. All rights reserved.
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * 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/input_type.h"
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/dom/ax_object_cache.h"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#include "third_party/blink/renderer/core/dom/exception_code.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/fileapi/file_list.h"
+#include "third_party/blink/renderer/core/html/forms/button_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/checkbox_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/color_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/color_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/date_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_local_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/email_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/file_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/form_data.h"
+#include "third_party/blink/renderer/core/html/forms/hidden_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/image_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/month_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/number_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/password_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/radio_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/range_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/reset_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/search_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/submit_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/telephone_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/text_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/time_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/url_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/week_input_type.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.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_theme.h"
+#include "third_party/blink/renderer/core/page/page.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"
+
+namespace blink {
+
+using blink::WebLocalizedString;
+using namespace HTMLNames;
+
+using InputTypeFactoryFunction = InputType* (*)(HTMLInputElement&);
+using InputTypeFactoryMap = HashMap<AtomicString, InputTypeFactoryFunction>;
+
+static std::unique_ptr<InputTypeFactoryMap> CreateInputTypeFactoryMap() {
+ std::unique_ptr<InputTypeFactoryMap> map =
+ std::make_unique<InputTypeFactoryMap>();
+ map->insert(InputTypeNames::button, ButtonInputType::Create);
+ map->insert(InputTypeNames::checkbox, CheckboxInputType::Create);
+ map->insert(InputTypeNames::color, ColorInputType::Create);
+ map->insert(InputTypeNames::date, DateInputType::Create);
+ map->insert(InputTypeNames::datetime_local, DateTimeLocalInputType::Create);
+ map->insert(InputTypeNames::email, EmailInputType::Create);
+ map->insert(InputTypeNames::file, FileInputType::Create);
+ map->insert(InputTypeNames::hidden, HiddenInputType::Create);
+ map->insert(InputTypeNames::image, ImageInputType::Create);
+ map->insert(InputTypeNames::month, MonthInputType::Create);
+ map->insert(InputTypeNames::number, NumberInputType::Create);
+ map->insert(InputTypeNames::password, PasswordInputType::Create);
+ map->insert(InputTypeNames::radio, RadioInputType::Create);
+ map->insert(InputTypeNames::range, RangeInputType::Create);
+ map->insert(InputTypeNames::reset, ResetInputType::Create);
+ map->insert(InputTypeNames::search, SearchInputType::Create);
+ map->insert(InputTypeNames::submit, SubmitInputType::Create);
+ map->insert(InputTypeNames::tel, TelephoneInputType::Create);
+ map->insert(InputTypeNames::time, TimeInputType::Create);
+ map->insert(InputTypeNames::url, URLInputType::Create);
+ map->insert(InputTypeNames::week, WeekInputType::Create);
+ // No need to register "text" because it is the default type.
+ return map;
+}
+
+static const InputTypeFactoryMap* FactoryMap() {
+ static const InputTypeFactoryMap* factory_map =
+ CreateInputTypeFactoryMap().release();
+ return factory_map;
+}
+
+InputType* InputType::Create(HTMLInputElement& element,
+ const AtomicString& type_name) {
+ InputTypeFactoryFunction factory =
+ type_name.IsEmpty() ? nullptr : FactoryMap()->at(type_name);
+ if (!factory)
+ factory = TextInputType::Create;
+ return factory(element);
+}
+
+InputType* InputType::CreateText(HTMLInputElement& element) {
+ return TextInputType::Create(element);
+}
+
+const AtomicString& InputType::NormalizeTypeName(
+ const AtomicString& type_name) {
+ if (type_name.IsEmpty())
+ return InputTypeNames::text;
+ InputTypeFactoryMap::const_iterator it =
+ FactoryMap()->find(type_name.LowerASCII());
+ return it == FactoryMap()->end() ? InputTypeNames::text : it->key;
+}
+
+InputType::~InputType() = default;
+
+void InputType::Trace(blink::Visitor* visitor) {
+ visitor->Trace(element_);
+}
+
+bool InputType::IsTextField() const {
+ return false;
+}
+
+bool InputType::ShouldSaveAndRestoreFormControlState() const {
+ return true;
+}
+
+bool InputType::IsFormDataAppendable() const {
+ // There is no form data unless there's a name for non-image types.
+ return !GetElement().GetName().IsEmpty();
+}
+
+void InputType::AppendToFormData(FormData& form_data) const {
+ form_data.append(GetElement().GetName(), GetElement().value());
+}
+
+String InputType::ResultForDialogSubmit() const {
+ return GetElement().FastGetAttribute(valueAttr);
+}
+
+double InputType::ValueAsDate() const {
+ return DateComponents::InvalidMilliseconds();
+}
+
+void InputType::SetValueAsDate(double, ExceptionState& exception_state) const {
+ exception_state.ThrowDOMException(
+ kInvalidStateError, "This input element does not support Date values.");
+}
+
+double InputType::ValueAsDouble() const {
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+void InputType::SetValueAsDouble(double double_value,
+ TextFieldEventBehavior event_behavior,
+ ExceptionState& exception_state) const {
+ exception_state.ThrowDOMException(
+ kInvalidStateError, "This input element does not support Number values.");
+}
+
+void InputType::SetValueAsDecimal(const Decimal& new_value,
+ TextFieldEventBehavior event_behavior,
+ ExceptionState&) const {
+ GetElement().setValue(Serialize(new_value), event_behavior);
+}
+
+void InputType::ReadingChecked() const {}
+
+bool InputType::SupportsValidation() const {
+ return true;
+}
+
+bool InputType::TypeMismatchFor(const String&) const {
+ return false;
+}
+
+bool InputType::TypeMismatch() const {
+ return false;
+}
+
+bool InputType::SupportsRequired() const {
+ // Almost all validatable types support @required.
+ return SupportsValidation();
+}
+
+bool InputType::ValueMissing(const String&) const {
+ return false;
+}
+
+bool InputType::TooLong(const String&,
+ TextControlElement::NeedsToCheckDirtyFlag) const {
+ return false;
+}
+
+bool InputType::TooShort(const String&,
+ TextControlElement::NeedsToCheckDirtyFlag) const {
+ return false;
+}
+
+bool InputType::PatternMismatch(const String&) const {
+ return false;
+}
+
+bool InputType::RangeUnderflow(const String& value) const {
+ if (!IsSteppable())
+ return false;
+
+ const Decimal numeric_value = ParseToNumberOrNaN(value);
+ if (!numeric_value.IsFinite())
+ return false;
+
+ return numeric_value < CreateStepRange(kRejectAny).Minimum();
+}
+
+bool InputType::RangeOverflow(const String& value) const {
+ if (!IsSteppable())
+ return false;
+
+ const Decimal numeric_value = ParseToNumberOrNaN(value);
+ if (!numeric_value.IsFinite())
+ return false;
+
+ return numeric_value > CreateStepRange(kRejectAny).Maximum();
+}
+
+Decimal InputType::DefaultValueForStepUp() const {
+ return 0;
+}
+
+double InputType::Minimum() const {
+ return CreateStepRange(kRejectAny).Minimum().ToDouble();
+}
+
+double InputType::Maximum() const {
+ return CreateStepRange(kRejectAny).Maximum().ToDouble();
+}
+
+bool InputType::IsInRange(const String& value) const {
+ if (!IsSteppable())
+ return false;
+
+ // This function should return true if both of validity.rangeUnderflow and
+ // validity.rangeOverflow are false.
+ // If the INPUT has no value, they are false.
+ const Decimal numeric_value = ParseToNumberOrNaN(value);
+ if (!numeric_value.IsFinite())
+ return true;
+
+ StepRange step_range(CreateStepRange(kRejectAny));
+ return step_range.HasRangeLimitations() &&
+ numeric_value >= step_range.Minimum() &&
+ numeric_value <= step_range.Maximum();
+}
+
+bool InputType::IsOutOfRange(const String& value) const {
+ if (!IsSteppable())
+ return false;
+
+ // This function should return true if either validity.rangeUnderflow or
+ // validity.rangeOverflow are true.
+ // If the INPUT has no value, they are false.
+ const Decimal numeric_value = ParseToNumberOrNaN(value);
+ if (!numeric_value.IsFinite())
+ return false;
+
+ StepRange step_range(CreateStepRange(kRejectAny));
+ return step_range.HasRangeLimitations() &&
+ (numeric_value < step_range.Minimum() ||
+ numeric_value > step_range.Maximum());
+}
+
+void InputType::InRangeChanged() const {
+ if (IsSteppable()) {
+ GetElement().PseudoStateChanged(CSSSelector::kPseudoInRange);
+ GetElement().PseudoStateChanged(CSSSelector::kPseudoOutOfRange);
+ }
+}
+
+bool InputType::StepMismatch(const String& value) const {
+ if (!IsSteppable())
+ return false;
+
+ const Decimal numeric_value = ParseToNumberOrNaN(value);
+ if (!numeric_value.IsFinite())
+ return false;
+
+ return CreateStepRange(kRejectAny).StepMismatch(numeric_value);
+}
+
+String InputType::BadInputText() const {
+ NOTREACHED();
+ return GetLocale().QueryString(WebLocalizedString::kValidationTypeMismatch);
+}
+
+String InputType::RangeOverflowText(const Decimal&) const {
+ NOTREACHED();
+ return String();
+}
+
+String InputType::RangeUnderflowText(const Decimal&) const {
+ NOTREACHED();
+ return String();
+}
+
+String InputType::TypeMismatchText() const {
+ return GetLocale().QueryString(WebLocalizedString::kValidationTypeMismatch);
+}
+
+String InputType::ValueMissingText() const {
+ return GetLocale().QueryString(WebLocalizedString::kValidationValueMissing);
+}
+
+std::pair<String, String> InputType::ValidationMessage(
+ const InputTypeView& input_type_view) const {
+ const String value = GetElement().value();
+
+ // The order of the following checks is meaningful. e.g. We'd like to show the
+ // badInput message even if the control has other validation errors.
+ if (input_type_view.HasBadInput())
+ return std::make_pair(BadInputText(), g_empty_string);
+
+ if (ValueMissing(value))
+ return std::make_pair(ValueMissingText(), g_empty_string);
+
+ if (TypeMismatch())
+ return std::make_pair(TypeMismatchText(), g_empty_string);
+
+ if (PatternMismatch(value)) {
+ // https://html.spec.whatwg.org/multipage/forms.html#attr-input-pattern
+ // When an input element has a pattern attribute specified, authors
+ // should include a title attribute to give a description of the
+ // pattern. User agents may use the contents of this attribute, if it
+ // is present, when informing the user that the pattern is not matched
+ return std::make_pair(
+ GetLocale().QueryString(WebLocalizedString::kValidationPatternMismatch),
+ GetElement().FastGetAttribute(titleAttr).GetString());
+ }
+
+ if (GetElement().TooLong()) {
+ return std::make_pair(GetLocale().ValidationMessageTooLongText(
+ value.length(), GetElement().maxLength()),
+ g_empty_string);
+ }
+
+ if (GetElement().TooShort()) {
+ return std::make_pair(GetLocale().ValidationMessageTooShortText(
+ value.length(), GetElement().minLength()),
+ g_empty_string);
+ }
+
+ if (!IsSteppable())
+ return std::make_pair(g_empty_string, g_empty_string);
+
+ const Decimal numeric_value = ParseToNumberOrNaN(value);
+ if (!numeric_value.IsFinite())
+ return std::make_pair(g_empty_string, g_empty_string);
+
+ StepRange step_range(CreateStepRange(kRejectAny));
+
+ if (numeric_value < step_range.Minimum())
+ return std::make_pair(RangeUnderflowText(step_range.Minimum()),
+ g_empty_string);
+
+ if (numeric_value > step_range.Maximum())
+ return std::make_pair(RangeOverflowText(step_range.Maximum()),
+ g_empty_string);
+
+ if (step_range.StepMismatch(numeric_value)) {
+ DCHECK(step_range.HasStep());
+ Decimal candidate1 = step_range.ClampValue(numeric_value);
+ String localized_candidate1 = LocalizeValue(Serialize(candidate1));
+ Decimal candidate2 = candidate1 < numeric_value
+ ? candidate1 + step_range.Step()
+ : candidate1 - step_range.Step();
+ if (!candidate2.IsFinite() || candidate2 < step_range.Minimum() ||
+ candidate2 > step_range.Maximum()) {
+ return std::make_pair(
+ GetLocale().QueryString(
+ WebLocalizedString::kValidationStepMismatchCloseToLimit,
+ localized_candidate1),
+ g_empty_string);
+ }
+ String localized_candidate2 = LocalizeValue(Serialize(candidate2));
+ if (candidate1 < candidate2) {
+ return std::make_pair(
+ GetLocale().QueryString(WebLocalizedString::kValidationStepMismatch,
+ localized_candidate1, localized_candidate2),
+ g_empty_string);
+ }
+ return std::make_pair(
+ GetLocale().QueryString(WebLocalizedString::kValidationStepMismatch,
+ localized_candidate2, localized_candidate1),
+ g_empty_string);
+ }
+
+ return std::make_pair(g_empty_string, g_empty_string);
+}
+
+Decimal InputType::ParseToNumber(const String&,
+ const Decimal& default_value) const {
+ NOTREACHED();
+ return default_value;
+}
+
+Decimal InputType::ParseToNumberOrNaN(const String& string) const {
+ return ParseToNumber(string, Decimal::Nan());
+}
+
+String InputType::Serialize(const Decimal&) const {
+ NOTREACHED();
+ return String();
+}
+
+ChromeClient* InputType::GetChromeClient() const {
+ if (Page* page = GetElement().GetDocument().GetPage())
+ return &page->GetChromeClient();
+ return nullptr;
+}
+
+Locale& InputType::GetLocale() const {
+ return GetElement().GetLocale();
+}
+
+bool InputType::CanSetStringValue() const {
+ return true;
+}
+
+bool InputType::IsKeyboardFocusable() const {
+ return GetElement().IsFocusable();
+}
+
+bool InputType::ShouldShowFocusRingOnMouseFocus() const {
+ return false;
+}
+
+void InputType::CountUsage() {}
+
+bool InputType::ShouldRespectAlignAttribute() {
+ return false;
+}
+
+void InputType::SanitizeValueInResponseToMinOrMaxAttributeChange() {}
+
+bool InputType::CanBeSuccessfulSubmitButton() {
+ return false;
+}
+
+bool InputType::MatchesDefaultPseudoClass() {
+ return false;
+}
+
+bool InputType::LayoutObjectIsNeeded() {
+ return true;
+}
+
+FileList* InputType::Files() {
+ return nullptr;
+}
+
+void InputType::SetFiles(FileList*) {}
+
+void InputType::SetFilesFromPaths(const Vector<String>& paths) {}
+
+String InputType::ValueInFilenameValueMode() const {
+ NOTREACHED();
+ return String();
+}
+
+String InputType::DefaultLabel() const {
+ return String();
+}
+
+bool InputType::CanSetSuggestedValue() {
+ return false;
+}
+
+bool InputType::ShouldSendChangeEventAfterCheckedChanged() {
+ return true;
+}
+
+void InputType::DispatchSearchEvent() {}
+
+void InputType::SetValue(const String& sanitized_value,
+ bool value_changed,
+ TextFieldEventBehavior event_behavior,
+ TextControlSetValueSelection) {
+ // This setValue() implementation is used only for ValueMode::kValue except
+ // TextFieldInputType. That is to say, type=color, type=range, and temporal
+ // input types.
+ DCHECK_EQ(GetValueMode(), ValueMode::kValue);
+ if (event_behavior == kDispatchNoEvent)
+ GetElement().SetNonAttributeValue(sanitized_value);
+ else
+ GetElement().SetNonAttributeValueByUserEdit(sanitized_value);
+ if (!value_changed)
+ return;
+ switch (event_behavior) {
+ case kDispatchChangeEvent:
+ GetElement().DispatchFormControlChangeEvent();
+ break;
+ case kDispatchInputAndChangeEvent:
+ GetElement().DispatchInputEvent();
+ GetElement().DispatchFormControlChangeEvent();
+ break;
+ case kDispatchNoEvent:
+ break;
+ }
+}
+
+bool InputType::CanSetValue(const String&) {
+ return true;
+}
+
+String InputType::LocalizeValue(const String& proposed_value) const {
+ return proposed_value;
+}
+
+String InputType::VisibleValue() const {
+ return GetElement().value();
+}
+
+String InputType::SanitizeValue(const String& proposed_value) const {
+ return proposed_value;
+}
+
+String InputType::SanitizeUserInputValue(const String& proposed_value) const {
+ return SanitizeValue(proposed_value);
+}
+
+void InputType::WarnIfValueIsInvalidAndElementIsVisible(
+ const String& value) const {
+ // Don't warn if the value is set in Modernizr.
+ const ComputedStyle* style = GetElement().GetComputedStyle();
+ if (style && style->Visibility() != EVisibility::kHidden)
+ WarnIfValueIsInvalid(value);
+}
+
+void InputType::WarnIfValueIsInvalid(const String&) const {}
+
+bool InputType::ReceiveDroppedFiles(const DragData*) {
+ NOTREACHED();
+ return false;
+}
+
+String InputType::DroppedFileSystemId() {
+ NOTREACHED();
+ return String();
+}
+
+bool InputType::ShouldRespectListAttribute() {
+ return false;
+}
+
+bool InputType::IsTextButton() const {
+ return false;
+}
+
+bool InputType::IsInteractiveContent() const {
+ return true;
+}
+
+bool InputType::IsEnumeratable() {
+ return true;
+}
+
+bool InputType::IsCheckable() {
+ return false;
+}
+
+bool InputType::IsSteppable() const {
+ return false;
+}
+
+bool InputType::ShouldRespectHeightAndWidthAttributes() {
+ return false;
+}
+
+int InputType::MaxLength() const {
+ return -1;
+}
+
+int InputType::MinLength() const {
+ return 0;
+}
+
+bool InputType::SupportsPlaceholder() const {
+ return false;
+}
+
+bool InputType::SupportsReadOnly() const {
+ return false;
+}
+
+String InputType::DefaultToolTip(const InputTypeView& input_type_view) const {
+ if (GetElement().Form() && GetElement().Form()->NoValidate())
+ return String();
+ return ValidationMessage(input_type_view).first;
+}
+
+Decimal InputType::FindClosestTickMarkValue(const Decimal&) {
+ NOTREACHED();
+ return Decimal::Nan();
+}
+
+bool InputType::HasLegalLinkAttribute(const QualifiedName&) const {
+ return false;
+}
+
+const QualifiedName& InputType::SubResourceAttributeName() const {
+ return QualifiedName::Null();
+}
+
+void InputType::CopyNonAttributeProperties(const HTMLInputElement&) {}
+
+void InputType::OnAttachWithLayoutObject() {}
+
+void InputType::OnDetachWithLayoutObject() {}
+
+bool InputType::ShouldAppearIndeterminate() const {
+ return false;
+}
+
+bool InputType::SupportsInputModeAttribute() const {
+ return false;
+}
+
+bool InputType::SupportsSelectionAPI() const {
+ return false;
+}
+
+unsigned InputType::Height() const {
+ return 0;
+}
+
+unsigned InputType::Width() const {
+ return 0;
+}
+
+ColorChooserClient* InputType::GetColorChooserClient() {
+ return nullptr;
+}
+
+void InputType::ApplyStep(const Decimal& current,
+ double count,
+ AnyStepHandling any_step_handling,
+ TextFieldEventBehavior event_behavior,
+ ExceptionState& exception_state) {
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-stepup
+
+ StepRange step_range(CreateStepRange(any_step_handling));
+ // 2. If the element has no allowed value step, then throw an
+ // InvalidStateError exception, and abort these steps.
+ if (!step_range.HasStep()) {
+ exception_state.ThrowDOMException(
+ kInvalidStateError,
+ "This form element does not have an allowed value step.");
+ return;
+ }
+
+ // 3. If the element has a minimum and a maximum and the minimum is greater
+ // than the maximum, then abort these steps.
+ if (step_range.Minimum() > step_range.Maximum())
+ return;
+
+ // 4. If the element has a minimum and a maximum and there is no value
+ // greater than or equal to the element's minimum and less than or equal to
+ // the element's maximum that, when subtracted from the step base, is an
+ // integral multiple of the allowed value step, then abort these steps.
+ Decimal aligned_maximum = step_range.StepSnappedMaximum();
+ if (!aligned_maximum.IsFinite())
+ return;
+
+ Decimal base = step_range.StepBase();
+ Decimal step = step_range.Step();
+ EventQueueScope scope;
+ Decimal new_value = current;
+ const AtomicString& step_string = GetElement().FastGetAttribute(stepAttr);
+ if (!DeprecatedEqualIgnoringCase(step_string, "any") &&
+ step_range.StepMismatch(current)) {
+ // Snap-to-step / clamping steps
+ // If the current value is not matched to step value:
+ // - The value should be the larger matched value nearest to 0 if count > 0
+ // e.g. <input type=number value=3 min=-100 step=3> -> 5
+ // - The value should be the smaller matched value nearest to 0 if count < 0
+ // e.g. <input type=number value=3 min=-100 step=3> -> 2
+ //
+
+ DCHECK(!step.IsZero());
+ if (count < 0) {
+ new_value = base + ((new_value - base) / step).Floor() * step;
+ ++count;
+ } else if (count > 0) {
+ new_value = base + ((new_value - base) / step).Ceil() * step;
+ --count;
+ }
+ }
+ new_value = new_value + step_range.Step() * Decimal::FromDouble(count);
+
+ if (!DeprecatedEqualIgnoringCase(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,
+ // 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.
+ if (new_value < step_range.Minimum()) {
+ const Decimal aligned_minimum =
+ base + ((step_range.Minimum() - base) / step).Ceil() * step;
+ DCHECK_GE(aligned_minimum, step_range.Minimum());
+ new_value = aligned_minimum;
+ }
+
+ // 8. 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
+ // 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.
+ SetValueAsDecimal(new_value, event_behavior, exception_state);
+
+ if (AXObjectCache* cache = GetElement().GetDocument().ExistingAXObjectCache())
+ cache->HandleValueChanged(&GetElement());
+}
+
+bool InputType::GetAllowedValueStep(Decimal* step) const {
+ StepRange step_range(CreateStepRange(kRejectAny));
+ *step = step_range.Step();
+ return step_range.HasStep();
+}
+
+StepRange InputType::CreateStepRange(AnyStepHandling) const {
+ NOTREACHED();
+ return StepRange();
+}
+
+void InputType::StepUp(double n, ExceptionState& exception_state) {
+ if (!IsSteppable()) {
+ exception_state.ThrowDOMException(kInvalidStateError,
+ "This form element is not steppable.");
+ return;
+ }
+ const Decimal current = ParseToNumber(GetElement().value(), 0);
+ ApplyStep(current, n, kRejectAny, kDispatchNoEvent, exception_state);
+}
+
+void InputType::StepUpFromLayoutObject(int n) {
+ // The only difference from stepUp()/stepDown() is the extra treatment
+ // of the current value before applying the step:
+ //
+ // If the current value is not a number, including empty, the current value is
+ // assumed as 0.
+ // * If 0 is in-range, and matches to step value
+ // - The value should be the +step if n > 0
+ // - The value should be the -step if n < 0
+ // If -step or +step is out of range, new value should be 0.
+ // * If 0 is smaller than the minimum value
+ // - The value should be the minimum value for any n
+ // * If 0 is larger than the maximum value
+ // - The value should be the maximum value for any 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
+ // 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".
+ // As for datetime type, the current value is assumed as "the current
+ // date/time in UTC".
+ // If the current value is smaller than the minimum value:
+ // - The value should be the minimum value if n > 0
+ // - Nothing should happen if n < 0
+ // If the current value is larger than the maximum value:
+ // - The value should be the maximum value if n < 0
+ // - Nothing should happen if n > 0
+ //
+ // n is assumed as -n if step < 0.
+
+ DCHECK(IsSteppable());
+ if (!IsSteppable())
+ return;
+ DCHECK(n);
+ if (!n)
+ return;
+
+ StepRange step_range(CreateStepRange(kAnyIsDefaultStep));
+
+ // FIXME: Not any changes after stepping, even if it is an invalid value, may
+ // be better.
+ // (e.g. Stepping-up for <input type="number" value="foo" step="any" /> =>
+ // "foo")
+ if (!step_range.HasStep())
+ return;
+
+ EventQueueScope scope;
+ const Decimal step = step_range.Step();
+
+ int sign;
+ if (step > 0)
+ sign = n;
+ else if (step < 0)
+ sign = -n;
+ else
+ sign = 0;
+
+ Decimal current = ParseToNumberOrNaN(GetElement().value());
+ if (!current.IsFinite()) {
+ current = DefaultValueForStepUp();
+ const Decimal next_diff = step * n;
+ if (current < step_range.Minimum() - next_diff)
+ current = step_range.Minimum() - next_diff;
+ if (current > step_range.Maximum() - next_diff)
+ current = step_range.Maximum() - next_diff;
+ SetValueAsDecimal(current, kDispatchNoEvent, IGNORE_EXCEPTION_FOR_TESTING);
+ }
+ if ((sign > 0 && current < step_range.Minimum()) ||
+ (sign < 0 && current > step_range.Maximum())) {
+ SetValueAsDecimal(sign > 0 ? step_range.Minimum() : step_range.Maximum(),
+ kDispatchChangeEvent, IGNORE_EXCEPTION_FOR_TESTING);
+ return;
+ }
+ if ((sign > 0 && current >= step_range.Maximum()) ||
+ (sign < 0 && current <= step_range.Minimum()))
+ return;
+ ApplyStep(current, n, kAnyIsDefaultStep, kDispatchChangeEvent,
+ IGNORE_EXCEPTION_FOR_TESTING);
+}
+
+void InputType::CountUsageIfVisible(WebFeature feature) const {
+ if (const ComputedStyle* style = GetElement().GetComputedStyle()) {
+ if (style->Visibility() != EVisibility::kHidden)
+ UseCounter::Count(GetElement().GetDocument(), feature);
+ }
+}
+
+Decimal InputType::FindStepBase(const Decimal& default_value) const {
+ Decimal step_base =
+ ParseToNumber(GetElement().FastGetAttribute(minAttr), Decimal::Nan());
+ if (!step_base.IsFinite())
+ step_base =
+ ParseToNumber(GetElement().FastGetAttribute(valueAttr), default_value);
+ return step_base;
+}
+
+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 {
+ bool has_range_limitations = false;
+ const Decimal step_base = FindStepBase(step_base_default);
+ Decimal minimum = ParseToNumberOrNaN(GetElement().FastGetAttribute(minAttr));
+ if (minimum.IsFinite())
+ has_range_limitations = true;
+ else
+ minimum = minimum_default;
+ Decimal maximum = ParseToNumberOrNaN(GetElement().FastGetAttribute(maxAttr));
+ if (maximum.IsFinite())
+ has_range_limitations = true;
+ else
+ maximum = maximum_default;
+ const Decimal step =
+ StepRange::ParseStep(any_step_handling, step_description,
+ GetElement().FastGetAttribute(stepAttr));
+ return StepRange(step_base, minimum, maximum, has_range_limitations, step,
+ step_description);
+}
+
+void InputType::AddWarningToConsole(const char* message_format,
+ const String& value) const {
+ GetElement().GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kRenderingMessageSource, kWarningMessageLevel,
+ String::Format(message_format,
+ JSONValue::QuoteString(value).Utf8().data())));
+}
+
+} // 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
new file mode 100644
index 00000000000..5b844c9057d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/input_type.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_H_
+
+#include "base/macros.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/frame/web_feature_forward.h"
+#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h"
+#include "third_party/blink/renderer/core/html/forms/step_range.h"
+#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
+
+namespace blink {
+
+class ChromeClient;
+class DragData;
+class ExceptionState;
+class FileList;
+class FormData;
+class InputTypeView;
+
+// An InputType object represents the type-specific part of an HTMLInputElement.
+// Do not expose instances of InputType and classes derived from it to classes
+// other than HTMLInputElement.
+class CORE_EXPORT InputType : public GarbageCollectedFinalized<InputType> {
+ public:
+ static InputType* Create(HTMLInputElement&, const AtomicString&);
+ static InputType* CreateText(HTMLInputElement&);
+ static const AtomicString& NormalizeTypeName(const AtomicString&);
+ virtual ~InputType();
+ virtual void Trace(blink::Visitor*);
+
+ virtual InputTypeView* CreateView() = 0;
+ virtual const AtomicString& FormControlType() const = 0;
+
+ // Type query functions
+
+ // Any time we are using one of these functions it's best to refactor
+ // to add a virtual function to allow the input type object to do the
+ // work instead, or at least make a query function that asks a higher
+ // level question. These functions make the HTMLInputElement class
+ // inflexible because it's harder to add new input types if there is
+ // scattered code with special cases for various types.
+
+ virtual bool IsInteractiveContent() const;
+ virtual bool IsTextButton() const;
+ virtual bool IsTextField() const;
+
+ // Form value functions
+
+ virtual bool ShouldSaveAndRestoreFormControlState() const;
+ virtual bool IsFormDataAppendable() const;
+ virtual void AppendToFormData(FormData&) const;
+ virtual String ResultForDialogSubmit() const;
+
+ // DOM property functions
+
+ // Returns a string value in ValueMode::kFilename.
+ virtual String ValueInFilenameValueMode() const;
+ // Default string to be used for showing button and form submission if |value|
+ // is missing.
+ virtual String DefaultLabel() const;
+
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value
+ enum class ValueMode { kValue, kDefault, kDefaultOn, kFilename };
+ virtual ValueMode GetValueMode() const = 0;
+
+ virtual double ValueAsDate() const;
+ virtual void SetValueAsDate(double, ExceptionState&) const;
+ virtual double ValueAsDouble() const;
+ virtual void SetValueAsDouble(double,
+ TextFieldEventBehavior,
+ ExceptionState&) const;
+ virtual void SetValueAsDecimal(const Decimal&,
+ TextFieldEventBehavior,
+ ExceptionState&) const;
+ virtual void ReadingChecked() const;
+
+ // Validation functions
+
+ // Returns a validation message as .first, and title attribute value as
+ // .second if patternMismatch.
+ std::pair<String, String> ValidationMessage(const InputTypeView&) const;
+ virtual bool SupportsValidation() const;
+ virtual bool TypeMismatchFor(const String&) const;
+ // Type check for the current input value. We do nothing for some types
+ // though typeMismatchFor() does something for them because of value
+ // sanitization.
+ virtual bool TypeMismatch() const;
+ virtual bool SupportsRequired() const;
+ virtual bool ValueMissing(const String&) const;
+ virtual bool PatternMismatch(const String&) const;
+ virtual bool TooLong(const String&,
+ TextControlElement::NeedsToCheckDirtyFlag) const;
+ virtual bool TooShort(const String&,
+ TextControlElement::NeedsToCheckDirtyFlag) const;
+ bool RangeUnderflow(const String&) const;
+ bool RangeOverflow(const String&) const;
+ bool IsInRange(const String&) const;
+ bool IsOutOfRange(const String&) const;
+ void InRangeChanged() const;
+ virtual Decimal DefaultValueForStepUp() const;
+ double Minimum() const;
+ double Maximum() const;
+ bool StepMismatch(const String&) const;
+ virtual bool GetAllowedValueStep(Decimal*) const;
+ virtual StepRange CreateStepRange(AnyStepHandling) const;
+ virtual void StepUp(double, ExceptionState&);
+ virtual void StepUpFromLayoutObject(int);
+ virtual String BadInputText() const;
+ virtual String RangeOverflowText(const Decimal& maximum) const;
+ virtual String RangeUnderflowText(const Decimal& minimum) const;
+ virtual String TypeMismatchText() const;
+ virtual String ValueMissingText() const;
+ virtual bool CanSetStringValue() const;
+ virtual String LocalizeValue(const String&) const;
+ virtual String VisibleValue() const;
+ // Returing the null string means "use the default value."
+ // This function must be called only by HTMLInputElement::sanitizeValue().
+ virtual String SanitizeValue(const String&) const;
+ virtual String SanitizeUserInputValue(const String&) const;
+ virtual void WarnIfValueIsInvalid(const String&) const;
+ void WarnIfValueIsInvalidAndElementIsVisible(const String&) const;
+
+ virtual bool IsKeyboardFocusable() const;
+ virtual bool ShouldShowFocusRingOnMouseFocus() const;
+ virtual bool CanBeSuccessfulSubmitButton();
+ virtual bool MatchesDefaultPseudoClass();
+
+ // Miscellaneous functions
+
+ virtual bool LayoutObjectIsNeeded();
+ virtual void CountUsage();
+ virtual void SanitizeValueInResponseToMinOrMaxAttributeChange();
+ virtual bool ShouldRespectAlignAttribute();
+ virtual FileList* Files();
+ virtual void SetFiles(FileList*);
+ virtual void SetFilesFromPaths(const Vector<String>&);
+ // Should return true if the given DragData has more than one dropped files.
+ virtual bool ReceiveDroppedFiles(const DragData*);
+ virtual String DroppedFileSystemId();
+ // Should return true if the corresponding layoutObject for a type can display
+ // a suggested value.
+ virtual bool CanSetSuggestedValue();
+ virtual bool ShouldSendChangeEventAfterCheckedChanged();
+ virtual bool CanSetValue(const String&);
+ virtual void SetValue(const String&,
+ bool value_changed,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection);
+ virtual bool ShouldRespectListAttribute();
+ virtual bool IsEnumeratable();
+ virtual bool IsCheckable();
+ virtual bool IsSteppable() const;
+ virtual bool ShouldRespectHeightAndWidthAttributes();
+ virtual int MaxLength() const;
+ virtual int MinLength() const;
+ virtual bool SupportsPlaceholder() const;
+ virtual bool SupportsReadOnly() const;
+ virtual String DefaultToolTip(const InputTypeView&) const;
+ virtual Decimal FindClosestTickMarkValue(const Decimal&);
+ virtual bool HasLegalLinkAttribute(const QualifiedName&) const;
+ virtual const QualifiedName& SubResourceAttributeName() const;
+ virtual void CopyNonAttributeProperties(const HTMLInputElement&);
+ virtual void OnAttachWithLayoutObject();
+ virtual void OnDetachWithLayoutObject();
+
+ // Parses the specified string for the type, and return
+ // the Decimal value for the parsing result if the parsing
+ // succeeds; Returns defaultValue otherwise. This function can
+ // return NaN or Infinity only if defaultValue is NaN or Infinity.
+ virtual Decimal ParseToNumber(const String&,
+ const Decimal& default_value) const;
+
+ // Create a string representation of the specified Decimal value for the
+ // input type. If NaN or Infinity is specified, this returns an empty
+ // string. This should not be called for types without valueAsNumber.
+ virtual String Serialize(const Decimal&) const;
+
+ virtual bool ShouldAppearIndeterminate() const;
+
+ virtual bool SupportsInputModeAttribute() const;
+
+ virtual bool SupportsSelectionAPI() const;
+
+ // Gets width and height of the input element if the type of the
+ // element is image. It returns 0 if the element is not image type.
+ virtual unsigned Height() const;
+ virtual unsigned Width() const;
+
+ virtual void DispatchSearchEvent();
+
+ // For test purpose
+ virtual ColorChooserClient* GetColorChooserClient();
+
+ protected:
+ InputType(HTMLInputElement& element) : element_(element) {}
+ HTMLInputElement& GetElement() const { return *element_; }
+ ChromeClient* GetChromeClient() const;
+ Locale& GetLocale() const;
+ Decimal ParseToNumberOrNaN(const String&) const;
+ void CountUsageIfVisible(WebFeature) const;
+
+ // Derive the step base, following the HTML algorithm steps.
+ Decimal FindStepBase(const Decimal&) const;
+
+ StepRange CreateStepRange(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;
+
+ private:
+ // Helper for stepUp()/stepDown(). Adds step value * count to the current
+ // value.
+ void ApplyStep(const Decimal&,
+ double count,
+ AnyStepHandling,
+ TextFieldEventBehavior,
+ ExceptionState&);
+
+ Member<HTMLInputElement> element_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputType);
+};
+
+} // namespace blink
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type_names.json5 b/chromium/third_party/blink/renderer/core/html/forms/input_type_names.json5
new file mode 100644
index 00000000000..73487a149d6
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_names.json5
@@ -0,0 +1,32 @@
+{
+ metadata: {
+ namespace: "InputType",
+ export: "CORE_EXPORT",
+ },
+
+ data: [
+ "button",
+ "checkbox",
+ "color",
+ "date",
+ "datetime",
+ "datetime-local",
+ "email",
+ "file",
+ "hidden",
+ "image",
+ "month",
+ "number",
+ "password",
+ "radio",
+ "range",
+ "reset",
+ "search",
+ "submit",
+ "tel",
+ "text",
+ "time",
+ "url",
+ "week",
+ ],
+}
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
new file mode 100644
index 00000000000..273cf3f5172
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc
@@ -0,0 +1,194 @@
+/*
+ * 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, 2008, 2009, 2010, 2011 Apple Inc. All
+ * rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
+ * Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
+ * Copyright (C) 2009, 2010, 2011, 2012 Google Inc. All rights reserved.
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * 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/input_type_view.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/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/html_input_element.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
+
+namespace blink {
+
+InputTypeView::~InputTypeView() = default;
+
+void InputTypeView::Trace(blink::Visitor* visitor) {
+ visitor->Trace(element_);
+}
+
+bool InputTypeView::SizeShouldIncludeDecoration(int,
+ int& preferred_size) const {
+ preferred_size = GetElement().size();
+ return false;
+}
+
+void InputTypeView::HandleClickEvent(MouseEvent*) {}
+
+void InputTypeView::HandleMouseDownEvent(MouseEvent*) {}
+
+void InputTypeView::HandleKeydownEvent(KeyboardEvent*) {}
+
+void InputTypeView::HandleKeypressEvent(KeyboardEvent*) {}
+
+void InputTypeView::HandleKeyupEvent(KeyboardEvent*) {}
+
+void InputTypeView::HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) {}
+
+void InputTypeView::HandleDOMActivateEvent(Event*) {}
+
+void InputTypeView::ForwardEvent(Event*) {}
+
+void InputTypeView::DispatchSimulatedClickIfActive(KeyboardEvent* event) const {
+ if (GetElement().IsActive())
+ GetElement().DispatchSimulatedClick(event);
+ event->SetDefaultHandled();
+}
+
+void InputTypeView::AccessKeyAction(bool) {
+ GetElement().focus(FocusParams(SelectionBehaviorOnFocus::kReset,
+ kWebFocusTypeNone, nullptr));
+}
+
+bool InputTypeView::ShouldSubmitImplicitly(Event* event) {
+ return event->IsKeyboardEvent() &&
+ event->type() == EventTypeNames::keypress &&
+ ToKeyboardEvent(event)->charCode() == '\r';
+}
+
+HTMLFormElement* InputTypeView::FormForSubmission() const {
+ return GetElement().Form();
+}
+
+LayoutObject* InputTypeView::CreateLayoutObject(
+ const ComputedStyle& style) const {
+ return LayoutObject::CreateObject(&GetElement(), style);
+}
+
+scoped_refptr<ComputedStyle> InputTypeView::CustomStyleForLayoutObject(
+ scoped_refptr<ComputedStyle> original_style) {
+ return original_style;
+}
+
+TextDirection InputTypeView::ComputedTextDirection() {
+ return GetElement().EnsureComputedStyle()->Direction();
+}
+
+void InputTypeView::Blur() {
+ GetElement().DefaultBlur();
+}
+
+bool InputTypeView::HasCustomFocusLogic() const {
+ return true;
+}
+
+void InputTypeView::HandleBlurEvent() {}
+
+void InputTypeView::HandleFocusInEvent(Element*, WebFocusType) {}
+
+void InputTypeView::StartResourceLoading() {}
+
+void InputTypeView::ClosePopupView() {}
+
+bool InputTypeView::NeedsShadowSubtree() const {
+ return true;
+}
+
+void InputTypeView::CreateShadowSubtree() {}
+
+void InputTypeView::DestroyShadowSubtree() {
+ if (ShadowRoot* root = GetElement().UserAgentShadowRoot())
+ root->RemoveChildren();
+}
+
+void InputTypeView::AltAttributeChanged() {}
+
+void InputTypeView::SrcAttributeChanged() {}
+
+void InputTypeView::MinOrMaxAttributeChanged() {}
+
+void InputTypeView::StepAttributeChanged() {}
+
+ClickHandlingState* InputTypeView::WillDispatchClick() {
+ return nullptr;
+}
+
+void InputTypeView::DidDispatchClick(Event*, const ClickHandlingState&) {}
+
+void InputTypeView::UpdateView() {}
+
+void InputTypeView::AttributeChanged() {}
+
+void InputTypeView::MultipleAttributeChanged() {}
+
+void InputTypeView::DisabledAttributeChanged() {}
+
+void InputTypeView::ReadonlyAttributeChanged() {}
+
+void InputTypeView::RequiredAttributeChanged() {}
+
+void InputTypeView::ValueAttributeChanged() {}
+
+void InputTypeView::DidSetValue(const String&, bool) {}
+
+void InputTypeView::SubtreeHasChanged() {
+ NOTREACHED();
+}
+
+void InputTypeView::ListAttributeTargetChanged() {}
+
+void InputTypeView::UpdateClearButtonVisibility() {}
+
+void InputTypeView::UpdatePlaceholderText() {}
+
+AXObject* InputTypeView::PopupRootAXObject() {
+ return nullptr;
+}
+
+FormControlState InputTypeView::SaveFormControlState() const {
+ String current_value = GetElement().value();
+ if (current_value == GetElement().DefaultValue())
+ return FormControlState();
+ return FormControlState(current_value);
+}
+
+void InputTypeView::RestoreFormControlState(const FormControlState& state) {
+ GetElement().setValue(state[0]);
+}
+
+bool InputTypeView::HasBadInput() const {
+ return false;
+}
+
+void ClickHandlingState::Trace(blink::Visitor* visitor) {
+ visitor->Trace(checked_radio_button);
+ EventDispatchHandlingState::Trace(visitor);
+}
+
+} // 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
new file mode 100644
index 00000000000..f741beb93ed
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2012 Samsung Electronics. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_VIEW_H_
+#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/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"
+#include "third_party/blink/renderer/platform/text/text_direction.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+class AXObject;
+class BeforeTextInsertedEvent;
+class Element;
+class Event;
+class FormControlState;
+class HTMLFormElement;
+class HTMLInputElement;
+class KeyboardEvent;
+class MouseEvent;
+class LayoutObject;
+class ComputedStyle;
+
+class ClickHandlingState final : public EventDispatchHandlingState {
+ public:
+ void Trace(blink::Visitor*) override;
+
+ bool checked;
+ bool indeterminate;
+ Member<HTMLInputElement> checked_radio_button;
+};
+
+// An InputTypeView object represents the UI-specific part of an
+// HTMLInputElement. Do not expose instances of InputTypeView and classes
+// derived from it to classes other than HTMLInputElement.
+class CORE_EXPORT InputTypeView : public GarbageCollectedMixin {
+ public:
+ virtual ~InputTypeView();
+ void Trace(blink::Visitor*) override;
+
+ virtual bool SizeShouldIncludeDecoration(int default_size,
+ int& preferred_size) const;
+
+ // Event handling functions
+
+ virtual void HandleClickEvent(MouseEvent*);
+ virtual void HandleMouseDownEvent(MouseEvent*);
+ virtual ClickHandlingState* WillDispatchClick();
+ virtual void DidDispatchClick(Event*, const ClickHandlingState&);
+ virtual void HandleKeydownEvent(KeyboardEvent*);
+ virtual void HandleKeypressEvent(KeyboardEvent*);
+ virtual void HandleKeyupEvent(KeyboardEvent*);
+ virtual void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*);
+ virtual void ForwardEvent(Event*);
+ virtual bool ShouldSubmitImplicitly(Event*);
+ virtual HTMLFormElement* FormForSubmission() const;
+ virtual bool HasCustomFocusLogic() const;
+ virtual void HandleFocusInEvent(Element* old_focused_element, WebFocusType);
+ virtual void HandleBlurEvent();
+ virtual void HandleDOMActivateEvent(Event*);
+ virtual void AccessKeyAction(bool send_mouse_events);
+ virtual void Blur();
+ void DispatchSimulatedClickIfActive(KeyboardEvent*) const;
+
+ virtual void SubtreeHasChanged();
+ virtual LayoutObject* CreateLayoutObject(const ComputedStyle&) const;
+ virtual scoped_refptr<ComputedStyle> CustomStyleForLayoutObject(
+ scoped_refptr<ComputedStyle>);
+ virtual TextDirection ComputedTextDirection();
+ virtual void StartResourceLoading();
+ virtual void ClosePopupView();
+ virtual bool NeedsShadowSubtree() const;
+ virtual void CreateShadowSubtree();
+ virtual void DestroyShadowSubtree();
+ virtual void MinOrMaxAttributeChanged();
+ virtual void StepAttributeChanged();
+ virtual void AltAttributeChanged();
+ virtual void SrcAttributeChanged();
+ virtual void UpdateView();
+ virtual void AttributeChanged();
+ virtual void MultipleAttributeChanged();
+ virtual void DisabledAttributeChanged();
+ virtual void ReadonlyAttributeChanged();
+ virtual void RequiredAttributeChanged();
+ virtual void ValueAttributeChanged();
+ virtual void DidSetValue(const String&, bool value_changed);
+ virtual void ListAttributeTargetChanged();
+ virtual void UpdateClearButtonVisibility();
+ virtual void UpdatePlaceholderText();
+ virtual AXObject* PopupRootAXObject();
+ virtual void EnsureFallbackContent() {}
+ virtual void EnsurePrimaryContent() {}
+ virtual bool HasFallbackContent() const { return false; }
+ virtual FormControlState SaveFormControlState() const;
+ virtual void RestoreFormControlState(const FormControlState&);
+
+ // Validation functions
+ virtual bool HasBadInput() const;
+
+ protected:
+ InputTypeView(HTMLInputElement& element) : element_(&element) {}
+ HTMLInputElement& GetElement() const { return *element_; }
+
+ private:
+ Member<HTMLInputElement> element_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputTypeView);
+};
+
+} // namespace blink
+#endif
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
new file mode 100644
index 00000000000..4e66eb00022
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc
@@ -0,0 +1,562 @@
+// Copyright (c) 2014 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/internal_popup_menu.h"
+
+#include "build/build_config.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"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/exported/web_view_impl.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/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/html_select_element.h"
+#include "third_party/blink/renderer/core/html/html_hr_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/layout/layout_theme.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/core/page/page_popup.h"
+#include "third_party/blink/renderer/platform/fonts/font_selector.h"
+#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"
+
+namespace blink {
+
+namespace {
+
+// TODO crbug.com/516675 Add stretch to serialization
+
+const char* FontStyleToString(FontSelectionValue slope) {
+ if (slope == ItalicSlopeValue())
+ return "italic";
+ return "normal";
+}
+
+const char* TextTransformToString(ETextTransform transform) {
+ switch (transform) {
+ case ETextTransform::kCapitalize:
+ return "capitalize";
+ case ETextTransform::kUppercase:
+ return "uppercase";
+ case ETextTransform::kLowercase:
+ return "lowercase";
+ case ETextTransform::kNone:
+ return "none";
+ }
+ NOTREACHED();
+ return "";
+}
+
+} // anonymous namespace
+
+class PopupMenuCSSFontSelector : public CSSFontSelector,
+ private FontSelectorClient {
+ USING_GARBAGE_COLLECTED_MIXIN(PopupMenuCSSFontSelector);
+
+ public:
+ static PopupMenuCSSFontSelector* Create(
+ Document* document,
+ CSSFontSelector* owner_font_selector) {
+ return new PopupMenuCSSFontSelector(document, owner_font_selector);
+ }
+
+ ~PopupMenuCSSFontSelector() override;
+
+ // We don't override willUseFontData() for now because the old PopupListBox
+ // only worked with fonts loaded when opening the popup.
+ scoped_refptr<FontData> GetFontData(const FontDescription&,
+ const AtomicString&) override;
+
+ void Trace(blink::Visitor*) override;
+
+ private:
+ PopupMenuCSSFontSelector(Document*, CSSFontSelector*);
+
+ void FontsNeedUpdate(FontSelector*) override;
+
+ Member<CSSFontSelector> owner_font_selector_;
+};
+
+PopupMenuCSSFontSelector::PopupMenuCSSFontSelector(
+ Document* document,
+ CSSFontSelector* owner_font_selector)
+ : CSSFontSelector(document), owner_font_selector_(owner_font_selector) {
+ owner_font_selector_->RegisterForInvalidationCallbacks(this);
+}
+
+PopupMenuCSSFontSelector::~PopupMenuCSSFontSelector() = default;
+
+scoped_refptr<FontData> PopupMenuCSSFontSelector::GetFontData(
+ const FontDescription& description,
+ const AtomicString& name) {
+ return owner_font_selector_->GetFontData(description, name);
+}
+
+void PopupMenuCSSFontSelector::FontsNeedUpdate(FontSelector* font_selector) {
+ DispatchInvalidationCallbacks();
+}
+
+void PopupMenuCSSFontSelector::Trace(blink::Visitor* visitor) {
+ visitor->Trace(owner_font_selector_);
+ CSSFontSelector::Trace(visitor);
+ FontSelectorClient::Trace(visitor);
+}
+
+// ----------------------------------------------------------------
+
+class InternalPopupMenu::ItemIterationContext {
+ STACK_ALLOCATED();
+
+ public:
+ ItemIterationContext(const ComputedStyle& style, SharedBuffer* buffer)
+ : base_style_(style),
+ background_color_(
+ style.VisitedDependentColor(GetCSSPropertyBackgroundColor())),
+ list_index_(0),
+ is_in_group_(false),
+ buffer_(buffer) {
+ DCHECK(buffer_);
+#if defined(OS_LINUX)
+ // On other platforms, the <option> background color is the same as the
+ // <select> background color. On Linux, that makes the <option>
+ // background color very dark, so by default, try to use a lighter
+ // background color for <option>s.
+ if (LayoutTheme::GetTheme().SystemColor(CSSValueButtonface) ==
+ background_color_)
+ background_color_ = LayoutTheme::GetTheme().SystemColor(CSSValueMenu);
+#endif
+ }
+
+ void SerializeBaseStyle() {
+ DCHECK(!is_in_group_);
+ PagePopupClient::AddString("baseStyle: {", buffer_);
+ AddProperty("backgroundColor", background_color_.Serialized(), buffer_);
+ AddProperty(
+ "color",
+ BaseStyle().VisitedDependentColor(GetCSSPropertyColor()).Serialized(),
+ buffer_);
+ AddProperty("textTransform",
+ String(TextTransformToString(BaseStyle().TextTransform())),
+ buffer_);
+ AddProperty("fontSize", BaseFont().ComputedPixelSize(), buffer_);
+ AddProperty("fontStyle", String(FontStyleToString(BaseFont().Style())),
+ buffer_);
+ AddProperty("fontVariant",
+ BaseFont().VariantCaps() == FontDescription::kSmallCaps
+ ? String("small-caps")
+ : String(),
+ buffer_);
+
+ PagePopupClient::AddString("fontFamily: [", buffer_);
+ for (const FontFamily* f = &BaseFont().Family(); f; f = f->Next()) {
+ AddJavaScriptString(f->Family().GetString(), buffer_);
+ if (f->Next())
+ PagePopupClient::AddString(",", buffer_);
+ }
+ PagePopupClient::AddString("]", buffer_);
+ PagePopupClient::AddString("},\n", buffer_);
+ }
+
+ Color BackgroundColor() const {
+ return is_in_group_ ? group_style_->VisitedDependentColor(
+ GetCSSPropertyBackgroundColor())
+ : background_color_;
+ }
+ // Do not use baseStyle() for background-color, use backgroundColor()
+ // instead.
+ const ComputedStyle& BaseStyle() {
+ return is_in_group_ ? *group_style_ : base_style_;
+ }
+ const FontDescription& BaseFont() {
+ return is_in_group_ ? group_style_->GetFontDescription()
+ : base_style_.GetFontDescription();
+ }
+ void StartGroupChildren(const ComputedStyle& group_style) {
+ DCHECK(!is_in_group_);
+ PagePopupClient::AddString("children: [", buffer_);
+ is_in_group_ = true;
+ group_style_ = &group_style;
+ }
+ void FinishGroupIfNecessary() {
+ if (!is_in_group_)
+ return;
+ PagePopupClient::AddString("],},\n", buffer_);
+ is_in_group_ = false;
+ group_style_ = nullptr;
+ }
+
+ const ComputedStyle& base_style_;
+ Color background_color_;
+ const ComputedStyle* group_style_;
+
+ unsigned list_index_;
+ bool is_in_group_;
+ SharedBuffer* buffer_;
+};
+
+// ----------------------------------------------------------------
+
+InternalPopupMenu* InternalPopupMenu::Create(ChromeClient* chrome_client,
+ HTMLSelectElement& owner_element) {
+ return new InternalPopupMenu(chrome_client, owner_element);
+}
+
+InternalPopupMenu::InternalPopupMenu(ChromeClient* chrome_client,
+ HTMLSelectElement& owner_element)
+ : chrome_client_(chrome_client),
+ owner_element_(owner_element),
+ popup_(nullptr),
+ needs_update_(false) {}
+
+InternalPopupMenu::~InternalPopupMenu() {
+ DCHECK(!popup_);
+}
+
+void InternalPopupMenu::Trace(blink::Visitor* visitor) {
+ visitor->Trace(chrome_client_);
+ visitor->Trace(owner_element_);
+ PopupMenu::Trace(visitor);
+}
+
+void InternalPopupMenu::WriteDocument(SharedBuffer* data) {
+ HTMLSelectElement& owner_element = *owner_element_;
+ IntRect anchor_rect_in_screen = chrome_client_->ViewportToScreen(
+ owner_element.VisibleBoundsInVisualViewport(),
+ owner_element.GetDocument().View());
+
+ float scale_factor = chrome_client_->WindowToViewportScalar(1.f);
+ PagePopupClient::AddString(
+ "<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data);
+ data->Append(Platform::Current()->GetDataResource("pickerCommon.css"));
+ data->Append(Platform::Current()->GetDataResource("listPicker.css"));
+ if (!RuntimeEnabledFeatures::ForceTallerSelectPopupEnabled())
+ PagePopupClient::AddString("@media (any-pointer:coarse) {", data);
+ int padding = static_cast<int>(roundf(4 * scale_factor));
+ int min_height = static_cast<int>(roundf(24 * scale_factor));
+ PagePopupClient::AddString(String::Format("option, optgroup {"
+ "padding-top: %dpx;"
+ "}\n"
+ "option {"
+ "padding-bottom: %dpx;"
+ "min-height: %dpx;"
+ "display: flex;"
+ "align-items: center;"
+ "}",
+ padding, padding, min_height),
+ data);
+ if (!RuntimeEnabledFeatures::ForceTallerSelectPopupEnabled()) {
+ // Closes @media.
+ PagePopupClient::AddString("}", data);
+ }
+
+ PagePopupClient::AddString(
+ "</style></head><body><div id=main>Loading...</div><script>\n"
+ "window.dialogArguments = {\n",
+ data);
+ AddProperty("selectedIndex", owner_element.SelectedListIndex(), data);
+ const ComputedStyle* owner_style = owner_element.GetComputedStyle();
+ ItemIterationContext context(*owner_style, data);
+ context.SerializeBaseStyle();
+ PagePopupClient::AddString("children: [\n", data);
+ const HeapVector<Member<HTMLElement>>& items = owner_element.GetListItems();
+ for (; context.list_index_ < items.size(); ++context.list_index_) {
+ Element& child = *items[context.list_index_];
+ if (!IsHTMLOptGroupElement(child.parentNode()))
+ context.FinishGroupIfNecessary();
+ if (auto* option = ToHTMLOptionElementOrNull(child))
+ AddOption(context, *option);
+ else if (auto* optgroup = ToHTMLOptGroupElementOrNull(child))
+ AddOptGroup(context, *optgroup);
+ else if (auto* hr = ToHTMLHRElementOrNull(child))
+ AddSeparator(context, *hr);
+ }
+ context.FinishGroupIfNecessary();
+ PagePopupClient::AddString("],\n", data);
+
+ AddProperty("anchorRectInScreen", anchor_rect_in_screen, data);
+ AddProperty("zoomFactor", 1, data);
+ AddProperty("scaleFactor", scale_factor, data);
+ bool is_rtl = !owner_style->IsLeftToRightDirection();
+ AddProperty("isRTL", is_rtl, data);
+ AddProperty("paddingStart",
+ is_rtl ? owner_element.ClientPaddingRight().ToDouble()
+ : owner_element.ClientPaddingLeft().ToDouble(),
+ data);
+ PagePopupClient::AddString("};\n", data);
+ data->Append(Platform::Current()->GetDataResource("pickerCommon.js"));
+ data->Append(Platform::Current()->GetDataResource("listPicker.js"));
+ PagePopupClient::AddString("</script></body>\n", data);
+}
+
+void InternalPopupMenu::AddElementStyle(ItemIterationContext& context,
+ HTMLElement& element) {
+ const ComputedStyle* style = owner_element_->ItemComputedStyle(element);
+ DCHECK(style);
+ SharedBuffer* data = context.buffer_;
+ // TODO(tkent): We generate unnecessary "style: {\n},\n" even if no
+ // additional style.
+ PagePopupClient::AddString("style: {\n", data);
+ if (style->Visibility() == EVisibility::kHidden)
+ AddProperty("visibility", String("hidden"), data);
+ if (style->Display() == EDisplay::kNone)
+ AddProperty("display", String("none"), data);
+ const ComputedStyle& base_style = context.BaseStyle();
+ if (base_style.Direction() != style->Direction()) {
+ AddProperty(
+ "direction",
+ String(style->Direction() == TextDirection::kRtl ? "rtl" : "ltr"),
+ data);
+ }
+ if (IsOverride(style->GetUnicodeBidi()))
+ AddProperty("unicodeBidi", String("bidi-override"), data);
+ Color foreground_color = style->VisitedDependentColor(GetCSSPropertyColor());
+ if (base_style.VisitedDependentColor(GetCSSPropertyColor()) !=
+ foreground_color)
+ AddProperty("color", foreground_color.Serialized(), data);
+ Color background_color =
+ style->VisitedDependentColor(GetCSSPropertyBackgroundColor());
+ if (context.BackgroundColor() != background_color &&
+ background_color != Color::kTransparent)
+ AddProperty("backgroundColor", background_color.Serialized(), data);
+ const FontDescription& base_font = context.BaseFont();
+ const FontDescription& font_description =
+ style->GetFont().GetFontDescription();
+ if (base_font.ComputedPixelSize() != font_description.ComputedPixelSize()) {
+ // We don't use FontDescription::specifiedSize() because this element
+ // might have its own zoom level.
+ AddProperty("fontSize", font_description.ComputedPixelSize(), data);
+ }
+ // Our UA stylesheet has font-weight:normal for OPTION.
+ if (NormalWeightValue() != font_description.Weight()) {
+ AddProperty("fontWeight", String::Number(font_description.Weight()), data);
+ }
+ if (base_font.Family() != font_description.Family()) {
+ PagePopupClient::AddString("fontFamily: [\n", data);
+ for (const FontFamily* f = &font_description.Family(); f; f = f->Next()) {
+ AddJavaScriptString(f->Family().GetString(), data);
+ if (f->Next())
+ PagePopupClient::AddString(",\n", data);
+ }
+ PagePopupClient::AddString("],\n", data);
+ }
+ if (base_font.Style() != font_description.Style()) {
+ AddProperty("fontStyle",
+ String(FontStyleToString(font_description.Style())), data);
+ }
+
+ if (base_font.VariantCaps() != font_description.VariantCaps() &&
+ font_description.VariantCaps() == FontDescription::kSmallCaps)
+ AddProperty("fontVariant", String("small-caps"), data);
+
+ if (base_style.TextTransform() != style->TextTransform()) {
+ AddProperty("textTransform",
+ String(TextTransformToString(style->TextTransform())), data);
+ }
+
+ PagePopupClient::AddString("},\n", data);
+}
+
+void InternalPopupMenu::AddOption(ItemIterationContext& context,
+ HTMLOptionElement& element) {
+ SharedBuffer* data = context.buffer_;
+ PagePopupClient::AddString("{", data);
+ AddProperty("label", element.DisplayLabel(), data);
+ AddProperty("value", context.list_index_, data);
+ if (!element.title().IsEmpty())
+ AddProperty("title", element.title(), data);
+ const AtomicString& aria_label =
+ element.FastGetAttribute(HTMLNames::aria_labelAttr);
+ if (!aria_label.IsEmpty())
+ AddProperty("ariaLabel", aria_label, data);
+ if (element.IsDisabledFormControl())
+ AddProperty("disabled", true, data);
+ AddElementStyle(context, element);
+ PagePopupClient::AddString("},", data);
+}
+
+void InternalPopupMenu::AddOptGroup(ItemIterationContext& context,
+ HTMLOptGroupElement& element) {
+ SharedBuffer* data = context.buffer_;
+ PagePopupClient::AddString("{\n", data);
+ PagePopupClient::AddString("type: \"optgroup\",\n", data);
+ AddProperty("label", element.GroupLabelText(), data);
+ AddProperty("title", element.title(), data);
+ AddProperty("ariaLabel", element.FastGetAttribute(HTMLNames::aria_labelAttr),
+ data);
+ AddProperty("disabled", element.IsDisabledFormControl(), data);
+ AddElementStyle(context, element);
+ context.StartGroupChildren(*owner_element_->ItemComputedStyle(element));
+ // We should call ItemIterationContext::finishGroupIfNecessary() later.
+}
+
+void InternalPopupMenu::AddSeparator(ItemIterationContext& context,
+ HTMLHRElement& element) {
+ SharedBuffer* data = context.buffer_;
+ PagePopupClient::AddString("{\n", data);
+ PagePopupClient::AddString("type: \"separator\",\n", data);
+ AddProperty("title", element.title(), data);
+ AddProperty("ariaLabel", element.FastGetAttribute(HTMLNames::aria_labelAttr),
+ data);
+ AddProperty("disabled", element.IsDisabledFormControl(), data);
+ AddElementStyle(context, element);
+ PagePopupClient::AddString("},\n", data);
+}
+
+void InternalPopupMenu::SelectFontsFromOwnerDocument(Document& document) {
+ Document& owner_document = OwnerElement().GetDocument();
+ document.GetStyleEngine().SetFontSelector(PopupMenuCSSFontSelector::Create(
+ &document, owner_document.GetStyleEngine().GetFontSelector()));
+}
+
+void InternalPopupMenu::SetValueAndClosePopup(int num_value,
+ const String& string_value) {
+ DCHECK(popup_);
+ DCHECK(owner_element_);
+ if (!string_value.IsEmpty()) {
+ bool success;
+ int list_index = string_value.ToInt(&success);
+ DCHECK(success);
+
+ EventQueueScope scope;
+ owner_element_->SelectOptionByPopup(list_index);
+ if (popup_)
+ chrome_client_->ClosePagePopup(popup_);
+ // 'change' event is dispatched here. For compatbility with
+ // Angular 1.2, we need to dispatch a change event before
+ // mouseup/click events.
+ } else {
+ if (popup_)
+ chrome_client_->ClosePagePopup(popup_);
+ }
+ // We dispatch events on the owner element to match the legacy behavior.
+ // Other browsers dispatch click events before and after showing the popup.
+ if (owner_element_) {
+ // TODO(dtapuska): Why is this event positionless?
+ WebMouseEvent event;
+ event.SetFrameScale(1);
+ Element* owner = &OwnerElement();
+ owner->DispatchMouseEvent(event, EventTypeNames::mouseup);
+ owner->DispatchMouseEvent(event, EventTypeNames::click);
+ }
+}
+
+void InternalPopupMenu::SetValue(const String& value) {
+ DCHECK(owner_element_);
+ bool success;
+ int list_index = value.ToInt(&success);
+ DCHECK(success);
+ owner_element_->ProvisionalSelectionChanged(list_index);
+}
+
+void InternalPopupMenu::DidClosePopup() {
+ // Clearing m_popup first to prevent from trying to close the popup again.
+ popup_ = nullptr;
+ if (owner_element_)
+ owner_element_->PopupDidHide();
+}
+
+Element& InternalPopupMenu::OwnerElement() {
+ return *owner_element_;
+}
+
+Locale& InternalPopupMenu::GetLocale() {
+ return Locale::DefaultLocale();
+}
+
+void InternalPopupMenu::ClosePopup() {
+ if (popup_)
+ chrome_client_->ClosePagePopup(popup_);
+ if (owner_element_)
+ owner_element_->PopupDidCancel();
+}
+
+void InternalPopupMenu::Dispose() {
+ if (popup_)
+ chrome_client_->ClosePagePopup(popup_);
+}
+
+void InternalPopupMenu::Show() {
+ DCHECK(!popup_);
+ popup_ = chrome_client_->OpenPagePopup(this);
+}
+
+void InternalPopupMenu::Hide() {
+ ClosePopup();
+}
+
+void InternalPopupMenu::UpdateFromElement(UpdateReason) {
+ if (needs_update_)
+ return;
+ needs_update_ = true;
+ OwnerElement()
+ .GetDocument()
+ .GetTaskRunner(TaskType::kUserInteraction)
+ ->PostTask(FROM_HERE,
+ WTF::Bind(&InternalPopupMenu::Update, WrapPersistent(this)));
+}
+
+void InternalPopupMenu::Update() {
+ if (!popup_ || !owner_element_)
+ return;
+ OwnerElement().GetDocument().UpdateStyleAndLayoutTree();
+ // disconnectClient() might have been called.
+ if (!owner_element_)
+ return;
+ needs_update_ = false;
+
+ if (!OwnerElement()
+ .GetDocument()
+ .GetFrame()
+ ->View()
+ ->VisibleContentRect()
+ .Intersects(OwnerElement().PixelSnappedBoundingBox())) {
+ Hide();
+ return;
+ }
+
+ scoped_refptr<SharedBuffer> data = SharedBuffer::Create();
+ PagePopupClient::AddString("window.updateData = {\n", data.get());
+ PagePopupClient::AddString("type: \"update\",\n", data.get());
+ ItemIterationContext context(*owner_element_->GetComputedStyle(), data.get());
+ context.SerializeBaseStyle();
+ PagePopupClient::AddString("children: [", data.get());
+ const HeapVector<Member<HTMLElement>>& items = owner_element_->GetListItems();
+ for (; context.list_index_ < items.size(); ++context.list_index_) {
+ Element& child = *items[context.list_index_];
+ if (!IsHTMLOptGroupElement(child.parentNode()))
+ context.FinishGroupIfNecessary();
+ if (auto* option = ToHTMLOptionElementOrNull(child))
+ AddOption(context, *option);
+ else if (auto* optgroup = ToHTMLOptGroupElementOrNull(child))
+ AddOptGroup(context, *optgroup);
+ else if (auto* hr = ToHTMLHRElementOrNull(child))
+ AddSeparator(context, *hr);
+ }
+ context.FinishGroupIfNecessary();
+ PagePopupClient::AddString("],\n", data.get());
+ IntRect anchor_rect_in_screen = chrome_client_->ViewportToScreen(
+ owner_element_->VisibleBoundsInVisualViewport(),
+ OwnerElement().GetDocument().View());
+ AddProperty("anchorRectInScreen", anchor_rect_in_screen, data.get());
+ PagePopupClient::AddString("}\n", data.get());
+ popup_->PostMessageToPopup(String::FromUTF8(data->Data(), data->size()));
+}
+
+void InternalPopupMenu::DisconnectClient() {
+ owner_element_ = nullptr;
+ // Cannot be done during finalization, so instead done when the
+ // layout object is destroyed and disconnected.
+ Dispose();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..e3e7a69d08e
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2014 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_INTERNAL_POPUP_MENU_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INTERNAL_POPUP_MENU_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/popup_menu.h"
+#include "third_party/blink/renderer/core/page/page_popup_client.h"
+
+namespace blink {
+
+class ChromeClient;
+class PagePopup;
+class HTMLElement;
+class HTMLHRElement;
+class HTMLOptGroupElement;
+class HTMLOptionElement;
+class HTMLSelectElement;
+
+// InternalPopupMenu is a PopupMenu implementation for platforms other than
+// macOS and Android. The UI is built with an HTML page inside a PagePopup.
+class CORE_EXPORT InternalPopupMenu final : public PopupMenu,
+ public PagePopupClient {
+ public:
+ static InternalPopupMenu* Create(ChromeClient*, HTMLSelectElement&);
+ ~InternalPopupMenu() override;
+ void Trace(blink::Visitor*) override;
+
+ void Update();
+
+ void Dispose();
+
+ private:
+ InternalPopupMenu(ChromeClient*, HTMLSelectElement&);
+
+ class ItemIterationContext;
+ void AddOption(ItemIterationContext&, HTMLOptionElement&);
+ void AddOptGroup(ItemIterationContext&, HTMLOptGroupElement&);
+ void AddSeparator(ItemIterationContext&, HTMLHRElement&);
+ void AddElementStyle(ItemIterationContext&, HTMLElement&);
+
+ // PopupMenu functions:
+ void Show() override;
+ void Hide() override;
+ void DisconnectClient() override;
+ void UpdateFromElement(UpdateReason) override;
+
+ // PagePopupClient functions:
+ void WriteDocument(SharedBuffer*) override;
+ void SelectFontsFromOwnerDocument(Document&) override;
+ void SetValueAndClosePopup(int, const String&) override;
+ void SetValue(const String&) override;
+ void ClosePopup() override;
+ Element& OwnerElement() override;
+ float ZoomFactor() override { return 1.0; }
+ Locale& GetLocale() override;
+ void DidClosePopup() override;
+
+ Member<ChromeClient> chrome_client_;
+ Member<HTMLSelectElement> owner_element_;
+ PagePopup* popup_;
+ bool needs_update_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INTERNAL_POPUP_MENU_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.cc
new file mode 100644
index 00000000000..848859cea96
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.cc
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010, 2012 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h"
+
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+void KeyboardClickableInputTypeView::HandleKeydownEvent(KeyboardEvent* event) {
+ const String& key = event->key();
+ if (key == " ") {
+ GetElement().SetActive(true);
+ // No setDefaultHandled(), because IE dispatches a keypress in this case
+ // and the caller will only dispatch a keypress if we don't call
+ // setDefaultHandled().
+ }
+}
+
+void KeyboardClickableInputTypeView::HandleKeypressEvent(KeyboardEvent* event) {
+ const String& key = event->key();
+ if (key == "Enter") {
+ GetElement().DispatchSimulatedClick(event);
+ event->SetDefaultHandled();
+ return;
+ }
+ if (key == " ") {
+ // Prevent scrolling down the page.
+ event->SetDefaultHandled();
+ }
+}
+
+void KeyboardClickableInputTypeView::HandleKeyupEvent(KeyboardEvent* event) {
+ const String& key = event->key();
+ if (key != " ")
+ return;
+ // Simulate mouse click for spacebar for button types.
+ DispatchSimulatedClickIfActive(event);
+}
+
+// FIXME: Could share this with BaseCheckableInputType and RangeInputType if we
+// had a common base class.
+void KeyboardClickableInputTypeView::AccessKeyAction(bool send_mouse_events) {
+ InputTypeView::AccessKeyAction(send_mouse_events);
+ GetElement().DispatchSimulatedClick(
+ nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h
new file mode 100644
index 00000000000..1dc03b1e7ac
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_KEYBOARD_CLICKABLE_INPUT_TYPE_VIEW_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_KEYBOARD_CLICKABLE_INPUT_TYPE_VIEW_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/input_type_view.h"
+
+namespace blink {
+
+// An InputTypeView class that dispatches a simulated click on space/return key.
+class CORE_EXPORT KeyboardClickableInputTypeView : public InputTypeView {
+ protected:
+ KeyboardClickableInputTypeView(HTMLInputElement& element)
+ : InputTypeView(element) {}
+
+ protected:
+ void HandleKeydownEvent(KeyboardEvent*) override;
+ void HandleKeypressEvent(KeyboardEvent*) override;
+ void HandleKeyupEvent(KeyboardEvent*) override;
+ void AccessKeyAction(bool send_mouse_events) override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_KEYBOARD_CLICKABLE_INPUT_TYPE_VIEW_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/labelable_element.cc b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.cc
new file mode 100644
index 00000000000..a8641324673
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.cc
@@ -0,0 +1,50 @@
+/*
+ * 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 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/labelable_element.h"
+
+#include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
+#include "third_party/blink/renderer/core/dom/node_rare_data.h"
+#include "third_party/blink/renderer/core/html/forms/labels_node_list.h"
+
+namespace blink {
+
+LabelableElement::LabelableElement(const QualifiedName& tag_name,
+ Document& document)
+ : HTMLElement(tag_name, document) {}
+
+LabelableElement::~LabelableElement() = default;
+
+LabelsNodeList* LabelableElement::labels() {
+ if (!SupportLabels())
+ return nullptr;
+
+ return EnsureCachedCollection<LabelsNodeList>(kLabelsNodeListType);
+}
+
+void LabelableElement::Trace(blink::Visitor* visitor) {
+ HTMLElement::Trace(visitor);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/labelable_element.h b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.h
new file mode 100644
index 00000000000..71fb17d6fa3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELABLE_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELABLE_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+
+namespace blink {
+
+class LabelsNodeList;
+
+// LabelableElement represents "labelable element" defined in the HTML
+// specification, and provides the implementation of the "labels" attribute.
+class CORE_EXPORT LabelableElement : public HTMLElement {
+ public:
+ ~LabelableElement() override;
+ LabelsNodeList* labels();
+ virtual bool SupportLabels() const { return false; }
+
+ void Trace(blink::Visitor*) override;
+
+ protected:
+ LabelableElement(const QualifiedName& tag_name, Document&);
+
+ private:
+ bool IsLabelable() const final { return true; }
+};
+
+inline bool IsLabelableElement(const HTMLElement& element) {
+ return element.IsLabelable();
+}
+
+DEFINE_HTMLELEMENT_TYPE_CASTS_WITH_FUNCTION(LabelableElement);
+
+} // namespace blink
+
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.cc b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.cc
new file mode 100644
index 00000000000..e2ff16dda4a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.cc
@@ -0,0 +1,49 @@
+/**
+ * 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, 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Nokia Inc. All rights reserved.
+ *
+ * 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/labels_node_list.h"
+
+#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/node_rare_data.h"
+#include "third_party/blink/renderer/core/html/forms/html_label_element.h"
+#include "third_party/blink/renderer/core/html/forms/labelable_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+LabelsNodeList::LabelsNodeList(ContainerNode& owner_node)
+ : LiveNodeList(owner_node,
+ kLabelsNodeListType,
+ kInvalidateForFormControls,
+ NodeListSearchRoot::kTreeScope) {}
+
+LabelsNodeList::~LabelsNodeList() = default;
+
+bool LabelsNodeList::ElementMatches(const Element& element) const {
+ return IsHTMLLabelElement(element) &&
+ ToHTMLLabelElement(element).control() == ownerNode();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..fa5b33993ce
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h
@@ -0,0 +1,51 @@
+/*
+ * 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, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Nokia Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#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 {
+
+class LabelsNodeList final : public LiveNodeList {
+ public:
+ static LabelsNodeList* Create(ContainerNode& owner_node,
+ CollectionType type) {
+ DCHECK_EQ(type, kLabelsNodeListType);
+ return new LabelsNodeList(owner_node);
+ }
+
+ ~LabelsNodeList() override;
+
+ protected:
+ explicit LabelsNodeList(ContainerNode&);
+
+ bool ElementMatches(const Element&) const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELS_NODE_LIST_H_
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
new file mode 100644
index 00000000000..dee6e10f57b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc
@@ -0,0 +1,326 @@
+/*
+ * 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 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/listed_element.h"
+
+#include "third_party/blink/renderer/core/dom/id_target_observer.h"
+#include "third_party/blink/renderer/core/dom/node_traversal.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/validity_state.h"
+#include "third_party/blink/renderer/core/html/html_object_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+class FormAttributeTargetObserver : public IdTargetObserver {
+ public:
+ static FormAttributeTargetObserver* Create(const AtomicString& id,
+ ListedElement*);
+ void Trace(blink::Visitor*) override;
+ void IdTargetChanged() override;
+
+ private:
+ FormAttributeTargetObserver(const AtomicString& id, ListedElement*);
+
+ Member<ListedElement> element_;
+};
+
+ListedElement::ListedElement() : form_was_set_by_parser_(false) {}
+
+ListedElement::~ListedElement() {
+ // We can't call setForm here because it contains virtual calls.
+}
+
+void ListedElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(form_attribute_target_observer_);
+ visitor->Trace(form_);
+ visitor->Trace(validity_state_);
+}
+
+ValidityState* ListedElement::validity() {
+ if (!validity_state_)
+ validity_state_ = ValidityState::Create(this);
+
+ return validity_state_.Get();
+}
+
+void ListedElement::DidMoveToNewDocument(Document& old_document) {
+ HTMLElement* element = ToHTMLElement(this);
+ if (element->FastHasAttribute(formAttr))
+ SetFormAttributeTargetObserver(nullptr);
+}
+
+void ListedElement::InsertedInto(ContainerNode* insertion_point) {
+ if (!form_was_set_by_parser_ || !form_ ||
+ NodeTraversal::HighestAncestorOrSelf(*insertion_point) !=
+ NodeTraversal::HighestAncestorOrSelf(*form_.Get()))
+ ResetFormOwner();
+
+ if (!insertion_point->isConnected())
+ return;
+
+ HTMLElement* element = ToHTMLElement(this);
+ if (element->FastHasAttribute(formAttr))
+ ResetFormAttributeTargetObserver();
+}
+
+void ListedElement::RemovedFrom(ContainerNode* insertion_point) {
+ HTMLElement* element = ToHTMLElement(this);
+ if (insertion_point->isConnected() && element->FastHasAttribute(formAttr)) {
+ SetFormAttributeTargetObserver(nullptr);
+ ResetFormOwner();
+ return;
+ }
+ // If the form and element are both in the same tree, preserve the connection
+ // to the form. Otherwise, null out our form and remove ourselves from the
+ // form's list of elements.
+ if (form_ && NodeTraversal::HighestAncestorOrSelf(*element) !=
+ NodeTraversal::HighestAncestorOrSelf(*form_.Get()))
+ ResetFormOwner();
+}
+
+HTMLFormElement* ListedElement::FindAssociatedForm(
+ const HTMLElement* element,
+ const AtomicString& form_id,
+ HTMLFormElement* form_ancestor) {
+ // 3. If the element is reassociateable, has a form content attribute, and
+ // is itself in a Document, then run these substeps:
+ if (!form_id.IsNull() && element->isConnected()) {
+ // 3.1. If the first element in the Document to have an ID that is
+ // case-sensitively equal to the element's form content attribute's
+ // value is a form element, then associate the form-associated element
+ // with that form element.
+ // 3.2. Abort the "reset the form owner" steps.
+ Element* new_form_candidate =
+ element->GetTreeScope().getElementById(form_id);
+ return ToHTMLFormElementOrNull(new_form_candidate);
+ }
+ // 4. Otherwise, if the form-associated element in question has an ancestor
+ // form element, then associate the form-associated element with the nearest
+ // such ancestor form element.
+ return form_ancestor;
+}
+
+void ListedElement::FormRemovedFromTree(const Node& form_root) {
+ DCHECK(form_);
+ if (NodeTraversal::HighestAncestorOrSelf(ToHTMLElement(*this)) == form_root)
+ return;
+ ResetFormOwner();
+}
+
+void ListedElement::AssociateByParser(HTMLFormElement* form) {
+ if (form && form->isConnected()) {
+ form_was_set_by_parser_ = true;
+ SetForm(form);
+ form->DidAssociateByParser();
+ }
+}
+
+void ListedElement::SetForm(HTMLFormElement* new_form) {
+ if (form_.Get() == new_form)
+ return;
+ WillChangeForm();
+ if (form_)
+ form_->Disassociate(*this);
+ if (new_form) {
+ form_ = new_form;
+ form_->Associate(*this);
+ } else {
+ form_ = nullptr;
+ }
+ DidChangeForm();
+}
+
+void ListedElement::WillChangeForm() {}
+
+void ListedElement::DidChangeForm() {
+ if (!form_was_set_by_parser_ && form_ && form_->isConnected()) {
+ HTMLElement* element = ToHTMLElement(this);
+ element->GetDocument().DidAssociateFormControl(element);
+ }
+}
+
+void ListedElement::ResetFormOwner() {
+ form_was_set_by_parser_ = false;
+ HTMLElement* element = ToHTMLElement(this);
+ const AtomicString& form_id(element->FastGetAttribute(formAttr));
+ HTMLFormElement* nearest_form = element->FindFormAncestor();
+ // 1. If the element's form owner is not null, and either the element is not
+ // reassociateable or its form content attribute is not present, and the
+ // element's form owner is its nearest form element ancestor after the
+ // change to the ancestor chain, then do nothing, and abort these steps.
+ if (form_ && form_id.IsNull() && form_.Get() == nearest_form)
+ return;
+
+ SetForm(FindAssociatedForm(element, form_id, nearest_form));
+}
+
+void ListedElement::FormAttributeChanged() {
+ ResetFormOwner();
+ ResetFormAttributeTargetObserver();
+}
+
+bool ListedElement::CustomError() const {
+ const HTMLElement* element = ToHTMLElement(this);
+ return element->willValidate() && !custom_validation_message_.IsEmpty();
+}
+
+bool ListedElement::HasBadInput() const {
+ return false;
+}
+
+bool ListedElement::PatternMismatch() const {
+ return false;
+}
+
+bool ListedElement::RangeOverflow() const {
+ return false;
+}
+
+bool ListedElement::RangeUnderflow() const {
+ return false;
+}
+
+bool ListedElement::StepMismatch() const {
+ return false;
+}
+
+bool ListedElement::TooLong() const {
+ return false;
+}
+
+bool ListedElement::TooShort() const {
+ return false;
+}
+
+bool ListedElement::TypeMismatch() const {
+ return false;
+}
+
+bool ListedElement::Valid() const {
+ bool some_error = TypeMismatch() || StepMismatch() || RangeUnderflow() ||
+ RangeOverflow() || TooLong() || TooShort() ||
+ PatternMismatch() || ValueMissing() || HasBadInput() ||
+ CustomError();
+ return !some_error;
+}
+
+bool ListedElement::ValueMissing() const {
+ return false;
+}
+
+String ListedElement::CustomValidationMessage() const {
+ return custom_validation_message_;
+}
+
+String ListedElement::validationMessage() const {
+ return CustomError() ? custom_validation_message_ : String();
+}
+
+String ListedElement::ValidationSubMessage() const {
+ return String();
+}
+
+void ListedElement::setCustomValidity(const String& error) {
+ custom_validation_message_ = error;
+}
+
+void ListedElement::SetFormAttributeTargetObserver(
+ FormAttributeTargetObserver* new_observer) {
+ if (form_attribute_target_observer_)
+ form_attribute_target_observer_->Unregister();
+ form_attribute_target_observer_ = new_observer;
+}
+
+void ListedElement::ResetFormAttributeTargetObserver() {
+ HTMLElement* element = ToHTMLElement(this);
+ const AtomicString& form_id(element->FastGetAttribute(formAttr));
+ if (!form_id.IsNull() && element->isConnected()) {
+ SetFormAttributeTargetObserver(
+ FormAttributeTargetObserver::Create(form_id, this));
+ } else {
+ SetFormAttributeTargetObserver(nullptr);
+ }
+}
+
+void ListedElement::FormAttributeTargetChanged() {
+ ResetFormOwner();
+}
+
+const AtomicString& ListedElement::GetName() const {
+ const AtomicString& name = ToHTMLElement(this)->GetNameAttribute();
+ return name.IsNull() ? g_empty_atom : name;
+}
+
+bool ListedElement::IsFormControlElementWithState() const {
+ return false;
+}
+
+const HTMLElement& ToHTMLElement(const ListedElement& listed_element) {
+ if (listed_element.IsFormControlElement())
+ return ToHTMLFormControlElement(listed_element);
+ return ToHTMLObjectElementFromListedElement(listed_element);
+}
+
+const HTMLElement* ToHTMLElement(const ListedElement* listed_element) {
+ DCHECK(listed_element);
+ return &ToHTMLElement(*listed_element);
+}
+
+HTMLElement* ToHTMLElement(ListedElement* listed_element) {
+ return const_cast<HTMLElement*>(
+ ToHTMLElement(static_cast<const ListedElement*>(listed_element)));
+}
+
+HTMLElement& ToHTMLElement(ListedElement& listed_element) {
+ return const_cast<HTMLElement&>(
+ ToHTMLElement(static_cast<const ListedElement&>(listed_element)));
+}
+
+FormAttributeTargetObserver* FormAttributeTargetObserver::Create(
+ const AtomicString& id,
+ ListedElement* element) {
+ return new FormAttributeTargetObserver(id, element);
+}
+
+FormAttributeTargetObserver::FormAttributeTargetObserver(const AtomicString& id,
+ ListedElement* element)
+ : IdTargetObserver(
+ ToHTMLElement(element)->GetTreeScope().GetIdTargetObserverRegistry(),
+ id),
+ element_(element) {}
+
+void FormAttributeTargetObserver::Trace(blink::Visitor* visitor) {
+ visitor->Trace(element_);
+ IdTargetObserver::Trace(visitor);
+}
+
+void FormAttributeTargetObserver::IdTargetChanged() {
+ element_->FormAttributeTargetChanged();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/listed_element.h b/chromium/third_party/blink/renderer/core/html/forms/listed_element.h
new file mode 100644
index 00000000000..891cb564d2c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/listed_element.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
+ * reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LISTED_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LISTED_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class ContainerNode;
+class Document;
+class FormAttributeTargetObserver;
+class FormData;
+class HTMLElement;
+class HTMLFormElement;
+class Node;
+class ValidityState;
+
+// https://html.spec.whatwg.org/multipage/forms.html#category-listed
+class CORE_EXPORT ListedElement : public GarbageCollectedMixin {
+ public:
+ virtual ~ListedElement();
+
+ static HTMLFormElement* FindAssociatedForm(const HTMLElement*,
+ const AtomicString& form_id,
+ HTMLFormElement* form_ancestor);
+ HTMLFormElement* Form() const { return form_.Get(); }
+ ValidityState* validity();
+
+ virtual bool IsFormControlElement() const = 0;
+ virtual bool IsFormControlElementWithState() const;
+ virtual bool IsEnumeratable() const = 0;
+
+ // Returns the 'name' attribute value. If this element has no name
+ // attribute, it returns an empty string instead of null string.
+ // Note that the 'name' IDL attribute doesn't use this function.
+ virtual const AtomicString& GetName() const;
+
+ // Override in derived classes to get the encoded name=value pair for
+ // submitting.
+ virtual void AppendToFormData(FormData&) {}
+
+ void ResetFormOwner();
+
+ void FormRemovedFromTree(const Node& form_root);
+
+ // ValidityState attribute implementations
+ bool CustomError() const;
+
+ // Override functions for patterMismatch, rangeOverflow, rangerUnderflow,
+ // stepMismatch, tooLong, tooShort and valueMissing must call willValidate
+ // method.
+ virtual bool HasBadInput() const;
+ virtual bool PatternMismatch() const;
+ virtual bool RangeOverflow() const;
+ virtual bool RangeUnderflow() const;
+ virtual bool StepMismatch() const;
+ virtual bool TooLong() const;
+ virtual bool TooShort() const;
+ virtual bool TypeMismatch() const;
+ virtual bool ValueMissing() const;
+ virtual String validationMessage() const;
+ virtual String ValidationSubMessage() const;
+ bool Valid() const;
+ virtual void setCustomValidity(const String&);
+
+ void FormAttributeTargetChanged();
+
+ typedef HeapVector<Member<ListedElement>> List;
+
+ void Trace(blink::Visitor*) override;
+
+ protected:
+ ListedElement();
+
+ void InsertedInto(ContainerNode*);
+ void RemovedFrom(ContainerNode*);
+ void DidMoveToNewDocument(Document& old_document);
+
+ // FIXME: Remove usage of setForm. resetFormOwner should be enough, and
+ // setForm is confusing.
+ void SetForm(HTMLFormElement*);
+ void AssociateByParser(HTMLFormElement*);
+ void FormAttributeChanged();
+
+ // If you add an override of willChangeForm() or didChangeForm() to a class
+ // derived from this one, you will need to add a call to setForm(0) to the
+ // destructor of that class.
+ virtual void WillChangeForm();
+ virtual void DidChangeForm();
+
+ String CustomValidationMessage() const;
+
+ private:
+ void SetFormAttributeTargetObserver(FormAttributeTargetObserver*);
+ void ResetFormAttributeTargetObserver();
+
+ Member<FormAttributeTargetObserver> form_attribute_target_observer_;
+ Member<HTMLFormElement> form_;
+ Member<ValidityState> validity_state_;
+ String custom_validation_message_;
+ // If m_formWasSetByParser is true, m_form is always non-null.
+ bool form_was_set_by_parser_;
+};
+
+CORE_EXPORT HTMLElement* ToHTMLElement(ListedElement*);
+CORE_EXPORT HTMLElement& ToHTMLElement(ListedElement&);
+CORE_EXPORT const HTMLElement* ToHTMLElement(const ListedElement*);
+CORE_EXPORT const HTMLElement& ToHTMLElement(const ListedElement&);
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LISTED_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
new file mode 100644
index 00000000000..d36dfd4de74
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/month_input_type.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"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/date_math.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/time.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+static const int kMonthDefaultStep = 1;
+static const int kMonthDefaultStepBase = 0;
+static const int kMonthStepScaleFactor = 1;
+
+InputType* MonthInputType::Create(HTMLInputElement& element) {
+ return new MonthInputType(element);
+}
+
+void MonthInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeMonth);
+}
+
+const AtomicString& MonthInputType::FormControlType() const {
+ return InputTypeNames::month;
+}
+
+double MonthInputType::ValueAsDate() const {
+ DateComponents date;
+ if (!ParseToDateComponents(GetElement().value(), &date))
+ return DateComponents::InvalidMilliseconds();
+ double msec = date.MillisecondsSinceEpoch();
+ DCHECK(std::isfinite(msec));
+ return msec;
+}
+
+String MonthInputType::SerializeWithMilliseconds(double value) const {
+ DateComponents date;
+ if (!date.SetMillisecondsSinceEpochForMonth(value))
+ return String();
+ return SerializeWithComponents(date);
+}
+
+Decimal MonthInputType::DefaultValueForStepUp() const {
+ DateComponents date;
+ date.SetMillisecondsSinceEpochForMonth(ConvertToLocalTime(CurrentTimeMS()));
+ double months = date.MonthsSinceEpoch();
+ DCHECK(std::isfinite(months));
+ return Decimal::FromDouble(months);
+}
+
+StepRange MonthInputType::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ DEFINE_STATIC_LOCAL(
+ const StepRange::StepDescription, step_description,
+ (kMonthDefaultStep, kMonthDefaultStepBase, kMonthStepScaleFactor,
+ StepRange::kParsedStepValueShouldBeInteger));
+
+ return InputType::CreateStepRange(
+ any_step_handling, Decimal::FromDouble(kMonthDefaultStepBase),
+ Decimal::FromDouble(DateComponents::MinimumMonth()),
+ Decimal::FromDouble(DateComponents::MaximumMonth()), step_description);
+}
+
+Decimal MonthInputType::ParseToNumber(const String& src,
+ const Decimal& default_value) const {
+ DateComponents date;
+ if (!ParseToDateComponents(src, &date))
+ return default_value;
+ double months = date.MonthsSinceEpoch();
+ DCHECK(std::isfinite(months));
+ return Decimal::FromDouble(months);
+}
+
+bool MonthInputType::ParseToDateComponentsInternal(const String& string,
+ DateComponents* out) const {
+ DCHECK(out);
+ unsigned end;
+ return out->ParseMonth(string, 0, end) && end == string.length();
+}
+
+bool MonthInputType::SetMillisecondToDateComponents(
+ double value,
+ DateComponents* date) const {
+ DCHECK(date);
+ return date->SetMonthsSinceEpoch(value);
+}
+
+bool MonthInputType::CanSetSuggestedValue() {
+ return true;
+}
+
+void MonthInputType::WarnIfValueIsInvalid(const String& value) const {
+ if (value != GetElement().SanitizeValue(value))
+ AddWarningToConsole(
+ "The specified value %s does not conform to the required format. The "
+ "format is \"yyyy-MM\" where yyyy is year in four or more digits, and "
+ "MM is 01-12.",
+ value);
+}
+
+String MonthInputType::FormatDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) const {
+ if (!date_time_fields_state.HasMonth() || !date_time_fields_state.HasYear())
+ return g_empty_string;
+ return String::Format("%04u-%02u", date_time_fields_state.Year(),
+ date_time_fields_state.Month());
+}
+
+void MonthInputType::SetupLayoutParameters(
+ DateTimeEditElement::LayoutParameters& layout_parameters,
+ const DateComponents& date) const {
+ layout_parameters.date_time_format = layout_parameters.locale.MonthFormat();
+ layout_parameters.fallback_date_time_format = "yyyy-MM";
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr),
+ &layout_parameters.minimum))
+ layout_parameters.minimum = DateComponents();
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr),
+ &layout_parameters.maximum))
+ layout_parameters.maximum = DateComponents();
+ layout_parameters.placeholder_for_month = "--";
+ layout_parameters.placeholder_for_year = "----";
+}
+
+bool MonthInputType::IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const {
+ return has_year && has_month;
+}
+
+} // 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
new file mode 100644
index 00000000000..402bc58849d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MONTH_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MONTH_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+
+namespace blink {
+
+class MonthInputType final : public BaseTemporalInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ explicit MonthInputType(HTMLInputElement& element)
+ : BaseTemporalInputType(element) {}
+
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ double ValueAsDate() const override;
+ String SerializeWithMilliseconds(double) const override;
+ Decimal ParseToNumber(const String&, const Decimal&) const override;
+ Decimal DefaultValueForStepUp() const override;
+ StepRange CreateStepRange(AnyStepHandling) const override;
+ bool ParseToDateComponentsInternal(const String&,
+ DateComponents*) const override;
+ bool SetMillisecondToDateComponents(double, DateComponents*) const override;
+ bool CanSetSuggestedValue() override;
+ void WarnIfValueIsInvalid(const String&) const override;
+
+ // BaseTemporalInputType functions
+ String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override;
+ void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&,
+ const DateComponents&) const override;
+ bool IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MONTH_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..3481a05ca5d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.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/events/scoped_event_queue.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/events/mouse_event.h"
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h"
+#include "third_party/blink/renderer/core/html/forms/form_controller.h"
+#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
+#include "third_party/blink/renderer/core/layout/layout_theme.h"
+#include "third_party/blink/renderer/core/page/focus_controller.h"
+#include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#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"
+
+namespace blink {
+
+class DateTimeFormatValidator : public DateTimeFormat::TokenHandler {
+ public:
+ DateTimeFormatValidator()
+ : has_year_(false),
+ has_month_(false),
+ has_week_(false),
+ has_day_(false),
+ has_ampm_(false),
+ has_hour_(false),
+ has_minute_(false),
+ has_second_(false) {}
+
+ void VisitField(DateTimeFormat::FieldType, int) final;
+ void VisitLiteral(const String&) final {}
+
+ bool ValidateFormat(const String& format, const BaseTemporalInputType&);
+
+ private:
+ bool has_year_;
+ bool has_month_;
+ bool has_week_;
+ bool has_day_;
+ bool has_ampm_;
+ bool has_hour_;
+ bool has_minute_;
+ bool has_second_;
+};
+
+void DateTimeFormatValidator::VisitField(DateTimeFormat::FieldType field_type,
+ int) {
+ switch (field_type) {
+ case DateTimeFormat::kFieldTypeYear:
+ has_year_ = true;
+ break;
+ case DateTimeFormat::kFieldTypeMonth: // Fallthrough.
+ case DateTimeFormat::kFieldTypeMonthStandAlone:
+ has_month_ = true;
+ break;
+ case DateTimeFormat::kFieldTypeWeekOfYear:
+ has_week_ = true;
+ break;
+ case DateTimeFormat::kFieldTypeDayOfMonth:
+ has_day_ = true;
+ break;
+ case DateTimeFormat::kFieldTypePeriod:
+ has_ampm_ = true;
+ break;
+ case DateTimeFormat::kFieldTypeHour11: // Fallthrough.
+ case DateTimeFormat::kFieldTypeHour12:
+ has_hour_ = true;
+ break;
+ case DateTimeFormat::kFieldTypeHour23: // Fallthrough.
+ case DateTimeFormat::kFieldTypeHour24:
+ has_hour_ = true;
+ has_ampm_ = true;
+ break;
+ case DateTimeFormat::kFieldTypeMinute:
+ has_minute_ = true;
+ break;
+ case DateTimeFormat::kFieldTypeSecond:
+ has_second_ = true;
+ break;
+ default:
+ break;
+ }
+}
+
+bool DateTimeFormatValidator::ValidateFormat(
+ const String& format,
+ const BaseTemporalInputType& input_type) {
+ if (!DateTimeFormat::Parse(format, *this))
+ return false;
+ return input_type.IsValidFormat(has_year_, has_month_, has_week_, has_day_,
+ has_ampm_, has_hour_, has_minute_,
+ has_second_);
+}
+
+DateTimeEditElement*
+MultipleFieldsTemporalInputTypeView::GetDateTimeEditElement() const {
+ return ToDateTimeEditElementOrDie(
+ GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::DateTimeEdit()));
+}
+
+SpinButtonElement* MultipleFieldsTemporalInputTypeView::GetSpinButtonElement()
+ const {
+ return ToSpinButtonElementOrDie(
+ GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::SpinButton()));
+}
+
+ClearButtonElement* MultipleFieldsTemporalInputTypeView::GetClearButtonElement()
+ const {
+ return ToClearButtonElementOrDie(
+ GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::ClearButton()));
+}
+
+PickerIndicatorElement*
+MultipleFieldsTemporalInputTypeView::GetPickerIndicatorElement() const {
+ return ToPickerIndicatorElementOrDie(
+ GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::PickerIndicator()));
+}
+
+inline bool MultipleFieldsTemporalInputTypeView::ContainsFocusedShadowElement()
+ const {
+ return GetElement().UserAgentShadowRoot()->contains(
+ GetElement().GetDocument().FocusedElement());
+}
+
+void MultipleFieldsTemporalInputTypeView::DidBlurFromControl(
+ WebFocusType focus_type) {
+ // We don't need to call blur(). This function is called when control
+ // lost focus.
+
+ if (ContainsFocusedShadowElement())
+ return;
+ EventQueueScope scope;
+ // Remove focus ring by CSS "focus" pseudo class.
+ GetElement().SetFocused(false, focus_type);
+ if (SpinButtonElement* spin_button = GetSpinButtonElement())
+ spin_button->ReleaseCapture();
+}
+
+void MultipleFieldsTemporalInputTypeView::DidFocusOnControl(
+ WebFocusType focus_type) {
+ // We don't need to call focus(). This function is called when control
+ // got focus.
+
+ if (!ContainsFocusedShadowElement())
+ return;
+ // Add focus ring by CSS "focus" pseudo class.
+ // FIXME: Setting the focus flag to non-focused element is too tricky.
+ GetElement().SetFocused(true, focus_type);
+}
+
+void MultipleFieldsTemporalInputTypeView::EditControlValueChanged() {
+ String old_value = GetElement().value();
+ String new_value =
+ input_type_->SanitizeValue(GetDateTimeEditElement()->Value());
+ // Even if oldValue is null and newValue is "", we should assume they are
+ // same.
+ if ((old_value.IsEmpty() && new_value.IsEmpty()) || old_value == new_value) {
+ GetElement().SetNeedsValidityCheck();
+ } else {
+ GetElement().SetNonAttributeValueByUserEdit(new_value);
+ GetElement().SetNeedsStyleRecalc(
+ kSubtreeStyleChange,
+ StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
+ GetElement().DispatchInputEvent();
+ }
+ GetElement().NotifyFormStateChanged();
+ GetElement().UpdateClearButtonVisibility();
+}
+
+String MultipleFieldsTemporalInputTypeView::FormatDateTimeFieldsState(
+ const DateTimeFieldsState& state) const {
+ return input_type_->FormatDateTimeFieldsState(state);
+}
+
+bool MultipleFieldsTemporalInputTypeView::HasCustomFocusLogic() const {
+ return false;
+}
+
+bool MultipleFieldsTemporalInputTypeView::IsEditControlOwnerDisabled() const {
+ return GetElement().IsDisabledFormControl();
+}
+
+bool MultipleFieldsTemporalInputTypeView::IsEditControlOwnerReadOnly() const {
+ return GetElement().IsReadOnly();
+}
+
+void MultipleFieldsTemporalInputTypeView::FocusAndSelectSpinButtonOwner() {
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ edit->FocusIfNoFocus();
+}
+
+bool MultipleFieldsTemporalInputTypeView::
+ ShouldSpinButtonRespondToMouseEvents() {
+ return !GetElement().IsDisabledOrReadOnly();
+}
+
+bool MultipleFieldsTemporalInputTypeView::
+ ShouldSpinButtonRespondToWheelEvents() {
+ if (!ShouldSpinButtonRespondToMouseEvents())
+ return false;
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ return edit->HasFocusedField();
+ return false;
+}
+
+void MultipleFieldsTemporalInputTypeView::SpinButtonStepDown() {
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ edit->StepDown();
+}
+
+void MultipleFieldsTemporalInputTypeView::SpinButtonStepUp() {
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ edit->StepUp();
+}
+
+void MultipleFieldsTemporalInputTypeView::SpinButtonDidReleaseMouseCapture(
+ SpinButtonElement::EventDispatch event_dispatch) {
+ if (event_dispatch == SpinButtonElement::kEventDispatchAllowed)
+ GetElement().DispatchFormControlChangeEvent();
+}
+
+bool MultipleFieldsTemporalInputTypeView::
+ IsPickerIndicatorOwnerDisabledOrReadOnly() const {
+ return GetElement().IsDisabledOrReadOnly();
+}
+
+void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue(
+ const String& value) {
+ if (GetElement().IsValidValue(value)) {
+ GetElement().setValue(value, kDispatchInputAndChangeEvent);
+ return;
+ }
+
+ DateTimeEditElement* edit = GetDateTimeEditElement();
+ if (!edit)
+ return;
+ EventQueueScope scope;
+ DateComponents date;
+ unsigned end;
+ if (date.ParseDate(value, 0, end) && end == value.length())
+ edit->SetOnlyYearMonthDay(date);
+ GetElement().DispatchFormControlChangeEvent();
+}
+
+void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue(
+ double value) {
+ DCHECK(std::isfinite(value) || std::isnan(value));
+ if (std::isnan(value))
+ GetElement().setValue(g_empty_string, kDispatchInputAndChangeEvent);
+ else
+ GetElement().setValueAsNumber(value, ASSERT_NO_EXCEPTION,
+ kDispatchInputAndChangeEvent);
+}
+
+Element& MultipleFieldsTemporalInputTypeView::PickerOwnerElement() const {
+ return GetElement();
+}
+
+bool MultipleFieldsTemporalInputTypeView::SetupDateTimeChooserParameters(
+ DateTimeChooserParameters& parameters) {
+ return GetElement().SetupDateTimeChooserParameters(parameters);
+}
+
+MultipleFieldsTemporalInputTypeView::MultipleFieldsTemporalInputTypeView(
+ HTMLInputElement& element,
+ BaseTemporalInputType& input_type)
+ : InputTypeView(element),
+ input_type_(input_type),
+ is_destroying_shadow_subtree_(false),
+ picker_indicator_is_visible_(false),
+ picker_indicator_is_always_visible_(false) {}
+
+MultipleFieldsTemporalInputTypeView*
+MultipleFieldsTemporalInputTypeView::Create(HTMLInputElement& element,
+ BaseTemporalInputType& input_type) {
+ return new MultipleFieldsTemporalInputTypeView(element, input_type);
+}
+
+MultipleFieldsTemporalInputTypeView::~MultipleFieldsTemporalInputTypeView() =
+ default;
+
+void MultipleFieldsTemporalInputTypeView::Trace(blink::Visitor* visitor) {
+ visitor->Trace(input_type_);
+ InputTypeView::Trace(visitor);
+}
+
+void MultipleFieldsTemporalInputTypeView::Blur() {
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ edit->BlurByOwner();
+}
+
+scoped_refptr<ComputedStyle>
+MultipleFieldsTemporalInputTypeView::CustomStyleForLayoutObject(
+ scoped_refptr<ComputedStyle> original_style) {
+ EDisplay original_display = original_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);
+ style->SetUnique();
+ return style;
+}
+
+void MultipleFieldsTemporalInputTypeView::CreateShadowSubtree() {
+ DCHECK(IsShadowHost(GetElement()));
+
+ // Element must not have a layoutObject here, because if it did
+ // DateTimeEditElement::customStyleForLayoutObject() is called in
+ // appendChild() before the field wrapper element is created.
+ // FIXME: This code should not depend on such craziness.
+ DCHECK(!GetElement().GetLayoutObject());
+
+ Document& document = GetElement().GetDocument();
+ ContainerNode* container = GetElement().UserAgentShadowRoot();
+
+ container->AppendChild(DateTimeEditElement::Create(document, *this));
+ GetElement().UpdateView();
+ container->AppendChild(ClearButtonElement::Create(document, *this));
+ container->AppendChild(SpinButtonElement::Create(document, *this));
+
+ if (LayoutTheme::GetTheme().SupportsCalendarPicker(
+ input_type_->FormControlType()))
+ picker_indicator_is_always_visible_ = true;
+ container->AppendChild(PickerIndicatorElement::Create(document, *this));
+ picker_indicator_is_visible_ = true;
+ UpdatePickerIndicatorVisibility();
+}
+
+void MultipleFieldsTemporalInputTypeView::DestroyShadowSubtree() {
+ DCHECK(!is_destroying_shadow_subtree_);
+ is_destroying_shadow_subtree_ = true;
+ if (SpinButtonElement* element = GetSpinButtonElement())
+ element->RemoveSpinButtonOwner();
+ if (ClearButtonElement* element = GetClearButtonElement())
+ element->RemoveClearButtonOwner();
+ if (DateTimeEditElement* element = GetDateTimeEditElement())
+ element->RemoveEditControlOwner();
+ if (PickerIndicatorElement* element = GetPickerIndicatorElement())
+ element->RemovePickerIndicatorOwner();
+
+ // If a field element has focus, set focus back to the <input> itself before
+ // deleting the field. This prevents unnecessary focusout/blur events.
+ if (ContainsFocusedShadowElement())
+ GetElement().focus();
+
+ InputTypeView::DestroyShadowSubtree();
+ is_destroying_shadow_subtree_ = false;
+}
+
+void MultipleFieldsTemporalInputTypeView::HandleClickEvent(MouseEvent* event) {
+ if (!event->isTrusted()) {
+ UseCounter::Count(GetElement().GetDocument(),
+ WebFeature::kTemporalInputTypeIgnoreUntrustedClick);
+ }
+}
+
+void MultipleFieldsTemporalInputTypeView::HandleFocusInEvent(
+ Element* old_focused_element,
+ WebFocusType type) {
+ DateTimeEditElement* edit = GetDateTimeEditElement();
+ if (!edit || is_destroying_shadow_subtree_)
+ return;
+ if (type == kWebFocusTypeBackward) {
+ if (GetElement().GetDocument().GetPage())
+ GetElement().GetDocument().GetPage()->GetFocusController().AdvanceFocus(
+ type);
+ } else if (type == kWebFocusTypeNone || type == kWebFocusTypeMouse ||
+ type == kWebFocusTypePage) {
+ edit->FocusByOwner(old_focused_element);
+ } else {
+ edit->FocusByOwner();
+ }
+}
+
+void MultipleFieldsTemporalInputTypeView::ForwardEvent(Event* event) {
+ if (SpinButtonElement* element = GetSpinButtonElement()) {
+ element->ForwardEvent(event);
+ if (event->DefaultHandled())
+ return;
+ }
+
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ edit->DefaultEventHandler(event);
+}
+
+void MultipleFieldsTemporalInputTypeView::DisabledAttributeChanged() {
+ EventQueueScope scope;
+ GetSpinButtonElement()->ReleaseCapture();
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ edit->DisabledStateChanged();
+}
+
+void MultipleFieldsTemporalInputTypeView::RequiredAttributeChanged() {
+ UpdateClearButtonVisibility();
+}
+
+void MultipleFieldsTemporalInputTypeView::HandleKeydownEvent(
+ KeyboardEvent* event) {
+ if (!GetElement().IsFocused())
+ return;
+ if (picker_indicator_is_visible_ &&
+ ((event->key() == "ArrowDown" && event->getModifierState("Alt")) ||
+ (LayoutTheme::GetTheme().ShouldOpenPickerWithF4Key() &&
+ event->key() == "F4"))) {
+ if (PickerIndicatorElement* element = GetPickerIndicatorElement())
+ element->OpenPopup();
+ event->SetDefaultHandled();
+ } else {
+ ForwardEvent(event);
+ }
+}
+
+bool MultipleFieldsTemporalInputTypeView::HasBadInput() const {
+ DateTimeEditElement* edit = GetDateTimeEditElement();
+ return GetElement().value().IsEmpty() && edit &&
+ edit->AnyEditableFieldsHaveValues();
+}
+
+AtomicString MultipleFieldsTemporalInputTypeView::LocaleIdentifier() const {
+ return GetElement().ComputeInheritedLanguage();
+}
+
+void MultipleFieldsTemporalInputTypeView::
+ EditControlDidChangeValueByKeyboard() {
+ GetElement().DispatchFormControlChangeEvent();
+}
+
+void MultipleFieldsTemporalInputTypeView::MinOrMaxAttributeChanged() {
+ UpdateView();
+}
+
+void MultipleFieldsTemporalInputTypeView::ReadonlyAttributeChanged() {
+ EventQueueScope scope;
+ GetSpinButtonElement()->ReleaseCapture();
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ edit->ReadOnlyStateChanged();
+}
+
+void MultipleFieldsTemporalInputTypeView::RestoreFormControlState(
+ const FormControlState& state) {
+ DateTimeEditElement* edit = GetDateTimeEditElement();
+ if (!edit)
+ return;
+ DateTimeFieldsState date_time_fields_state =
+ DateTimeFieldsState::RestoreFormControlState(state);
+ edit->SetValueAsDateTimeFieldsState(date_time_fields_state);
+ GetElement().SetNonAttributeValue(input_type_->SanitizeValue(edit->Value()));
+ UpdateClearButtonVisibility();
+}
+
+FormControlState MultipleFieldsTemporalInputTypeView::SaveFormControlState()
+ const {
+ if (DateTimeEditElement* edit = GetDateTimeEditElement())
+ return edit->ValueAsDateTimeFieldsState().SaveFormControlState();
+ return FormControlState();
+}
+
+void MultipleFieldsTemporalInputTypeView::DidSetValue(
+ const String& sanitized_value,
+ bool value_changed) {
+ DateTimeEditElement* edit = GetDateTimeEditElement();
+ if (value_changed || (sanitized_value.IsEmpty() && edit &&
+ edit->AnyEditableFieldsHaveValues())) {
+ GetElement().UpdateView();
+ GetElement().SetNeedsValidityCheck();
+ }
+}
+
+void MultipleFieldsTemporalInputTypeView::StepAttributeChanged() {
+ UpdateView();
+}
+
+void MultipleFieldsTemporalInputTypeView::UpdateView() {
+ DateTimeEditElement* edit = GetDateTimeEditElement();
+ if (!edit)
+ return;
+
+ DateTimeEditElement::LayoutParameters layout_parameters(
+ GetElement().GetLocale(),
+ input_type_->CreateStepRange(kAnyIsDefaultStep));
+
+ DateComponents date;
+ bool has_value = false;
+ if (!GetElement().SuggestedValue().IsNull())
+ has_value = input_type_->ParseToDateComponents(
+ GetElement().SuggestedValue(), &date);
+ else
+ has_value = input_type_->ParseToDateComponents(GetElement().value(), &date);
+ if (!has_value)
+ input_type_->SetMillisecondToDateComponents(
+ layout_parameters.step_range.Minimum().ToDouble(), &date);
+
+ input_type_->SetupLayoutParameters(layout_parameters, date);
+
+ DEFINE_STATIC_LOCAL(AtomicString, datetimeformat_attr, ("datetimeformat"));
+ edit->setAttribute(datetimeformat_attr,
+ AtomicString(layout_parameters.date_time_format),
+ ASSERT_NO_EXCEPTION);
+ const AtomicString pattern = edit->FastGetAttribute(HTMLNames::patternAttr);
+ if (!pattern.IsEmpty())
+ layout_parameters.date_time_format = pattern;
+
+ if (!DateTimeFormatValidator().ValidateFormat(
+ layout_parameters.date_time_format, *input_type_))
+ layout_parameters.date_time_format =
+ layout_parameters.fallback_date_time_format;
+
+ if (has_value)
+ edit->SetValueAsDate(layout_parameters, date);
+ else
+ edit->SetEmptyValue(layout_parameters, date);
+ UpdateClearButtonVisibility();
+}
+
+void MultipleFieldsTemporalInputTypeView::ClosePopupView() {
+ if (PickerIndicatorElement* picker = GetPickerIndicatorElement())
+ picker->ClosePopup();
+}
+
+void MultipleFieldsTemporalInputTypeView::ValueAttributeChanged() {
+ if (!GetElement().HasDirtyValue())
+ UpdateView();
+}
+
+void MultipleFieldsTemporalInputTypeView::ListAttributeTargetChanged() {
+ UpdatePickerIndicatorVisibility();
+}
+
+void MultipleFieldsTemporalInputTypeView::UpdatePickerIndicatorVisibility() {
+ if (picker_indicator_is_always_visible_) {
+ ShowPickerIndicator();
+ return;
+ }
+ if (GetElement().HasValidDataListOptions())
+ ShowPickerIndicator();
+ else
+ HidePickerIndicator();
+}
+
+void MultipleFieldsTemporalInputTypeView::HidePickerIndicator() {
+ if (!picker_indicator_is_visible_)
+ return;
+ picker_indicator_is_visible_ = false;
+ DCHECK(GetPickerIndicatorElement());
+ GetPickerIndicatorElement()->SetInlineStyleProperty(CSSPropertyDisplay,
+ CSSValueNone);
+}
+
+void MultipleFieldsTemporalInputTypeView::ShowPickerIndicator() {
+ if (picker_indicator_is_visible_)
+ return;
+ picker_indicator_is_visible_ = true;
+ DCHECK(GetPickerIndicatorElement());
+ GetPickerIndicatorElement()->RemoveInlineStyleProperty(CSSPropertyDisplay);
+}
+
+void MultipleFieldsTemporalInputTypeView::FocusAndSelectClearButtonOwner() {
+ GetElement().focus();
+}
+
+bool MultipleFieldsTemporalInputTypeView::
+ ShouldClearButtonRespondToMouseEvents() {
+ return !GetElement().IsDisabledOrReadOnly() && !GetElement().IsRequired();
+}
+
+void MultipleFieldsTemporalInputTypeView::ClearValue() {
+ GetElement().setValue("", kDispatchInputAndChangeEvent);
+ GetElement().UpdateClearButtonVisibility();
+}
+
+void MultipleFieldsTemporalInputTypeView::UpdateClearButtonVisibility() {
+ ClearButtonElement* clear_button = GetClearButtonElement();
+ if (!clear_button)
+ return;
+
+ if (GetElement().IsRequired() ||
+ !GetDateTimeEditElement()->AnyEditableFieldsHaveValues()) {
+ clear_button->SetInlineStyleProperty(CSSPropertyOpacity, 0.0,
+ CSSPrimitiveValue::UnitType::kNumber);
+ clear_button->SetInlineStyleProperty(CSSPropertyPointerEvents,
+ CSSValueNone);
+ } else {
+ clear_button->RemoveInlineStyleProperty(CSSPropertyOpacity);
+ clear_button->RemoveInlineStyleProperty(CSSPropertyPointerEvents);
+ }
+}
+
+TextDirection MultipleFieldsTemporalInputTypeView::ComputedTextDirection() {
+ return GetElement().GetLocale().IsRTL() ? TextDirection::kRtl
+ : TextDirection::kLtr;
+}
+
+AXObject* MultipleFieldsTemporalInputTypeView::PopupRootAXObject() {
+ if (PickerIndicatorElement* picker = GetPickerIndicatorElement())
+ return picker->PopupRootAXObject();
+ return nullptr;
+}
+
+} // 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
new file mode 100644
index 00000000000..b60ffbe75fc
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#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/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"
+#include "third_party/blink/renderer/core/html/forms/picker_indicator_element.h"
+#include "third_party/blink/renderer/core/html/forms/spin_button_element.h"
+
+namespace blink {
+
+class BaseTemporalInputType;
+struct DateTimeChooserParameters;
+
+class MultipleFieldsTemporalInputTypeView final
+ : public GarbageCollectedFinalized<MultipleFieldsTemporalInputTypeView>,
+ public InputTypeView,
+ protected DateTimeEditElement::EditControlOwner,
+ protected PickerIndicatorElement::PickerIndicatorOwner,
+ protected SpinButtonElement::SpinButtonOwner,
+ protected ClearButtonElement::ClearButtonOwner {
+ USING_GARBAGE_COLLECTED_MIXIN(MultipleFieldsTemporalInputTypeView);
+
+ public:
+ static MultipleFieldsTemporalInputTypeView* Create(HTMLInputElement&,
+ BaseTemporalInputType&);
+ ~MultipleFieldsTemporalInputTypeView() override;
+ void Trace(blink::Visitor*) override;
+
+ private:
+ MultipleFieldsTemporalInputTypeView(HTMLInputElement&,
+ BaseTemporalInputType&);
+
+ // DateTimeEditElement::EditControlOwner functions
+ void DidBlurFromControl(WebFocusType) final;
+ void DidFocusOnControl(WebFocusType) final;
+ void EditControlValueChanged() final;
+ String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override;
+ bool IsEditControlOwnerDisabled() const final;
+ bool IsEditControlOwnerReadOnly() const final;
+ AtomicString LocaleIdentifier() const final;
+ void EditControlDidChangeValueByKeyboard() final;
+
+ // SpinButtonElement::SpinButtonOwner functions.
+ void FocusAndSelectSpinButtonOwner() override;
+ bool ShouldSpinButtonRespondToMouseEvents() override;
+ bool ShouldSpinButtonRespondToWheelEvents() override;
+ void SpinButtonStepDown() override;
+ void SpinButtonStepUp() override;
+ void SpinButtonDidReleaseMouseCapture(
+ SpinButtonElement::EventDispatch) override;
+
+ // PickerIndicatorElement::PickerIndicatorOwner functions
+ bool IsPickerIndicatorOwnerDisabledOrReadOnly() const final;
+ void PickerIndicatorChooseValue(const String&) final;
+ void PickerIndicatorChooseValue(double) final;
+ Element& PickerOwnerElement() const final;
+ bool SetupDateTimeChooserParameters(DateTimeChooserParameters&) final;
+
+ // ClearButtonElement::ClearButtonOwner functions.
+ void FocusAndSelectClearButtonOwner() override;
+ bool ShouldClearButtonRespondToMouseEvents() override;
+ void ClearValue() override;
+
+ // InputTypeView functions
+ void Blur() final;
+ void ClosePopupView() override;
+ scoped_refptr<ComputedStyle> CustomStyleForLayoutObject(
+ scoped_refptr<ComputedStyle>) 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 HandleKeydownEvent(KeyboardEvent*) final;
+ bool HasBadInput() const override;
+ bool HasCustomFocusLogic() const final;
+ void MinOrMaxAttributeChanged() final;
+ void ReadonlyAttributeChanged() final;
+ void RequiredAttributeChanged() final;
+ void RestoreFormControlState(const FormControlState&) final;
+ FormControlState SaveFormControlState() const final;
+ void DidSetValue(const String&, bool value_changed) final;
+ void StepAttributeChanged() final;
+ void UpdateView() final;
+ void ValueAttributeChanged() override;
+ void ListAttributeTargetChanged() final;
+ void UpdateClearButtonVisibility() final;
+ TextDirection ComputedTextDirection() final;
+ AXObject* PopupRootAXObject() final;
+
+ DateTimeEditElement* GetDateTimeEditElement() const;
+ SpinButtonElement* GetSpinButtonElement() const;
+ ClearButtonElement* GetClearButtonElement() const;
+ PickerIndicatorElement* GetPickerIndicatorElement() const;
+ bool ContainsFocusedShadowElement() const;
+ void ShowPickerIndicator();
+ void HidePickerIndicator();
+ void UpdatePickerIndicatorVisibility();
+
+ Member<BaseTemporalInputType> input_type_;
+ bool is_destroying_shadow_subtree_;
+ bool picker_indicator_is_visible_;
+ bool picker_indicator_is_always_visible_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MULTIPLE_FIELDS_TEMPORAL_INPUT_TYPE_VIEW_H_
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
new file mode 100644
index 00000000000..e7bfdd2c5bd
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/number_input_type.h"
+
+#include <limits>
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#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/frame/web_feature.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_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_type_names.h"
+#include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
+
+namespace blink {
+
+using blink::WebLocalizedString;
+using namespace HTMLNames;
+
+static const int kNumberDefaultStep = 1;
+static const int kNumberDefaultStepBase = 0;
+static const int kNumberStepScaleFactor = 1;
+
+struct RealNumberRenderSize {
+ unsigned size_before_decimal_point;
+ unsigned size_afte_decimal_point;
+
+ RealNumberRenderSize(unsigned before, unsigned after)
+ : size_before_decimal_point(before), size_afte_decimal_point(after) {}
+
+ RealNumberRenderSize Max(const RealNumberRenderSize& other) const {
+ return RealNumberRenderSize(
+ std::max(size_before_decimal_point, other.size_before_decimal_point),
+ std::max(size_afte_decimal_point, other.size_afte_decimal_point));
+ }
+};
+
+static RealNumberRenderSize CalculateRenderSize(const Decimal& value) {
+ DCHECK(value.IsFinite());
+ const unsigned size_of_digits =
+ String::Number(value.Value().Coefficient()).length();
+ const unsigned size_of_sign = value.IsNegative() ? 1 : 0;
+ const int exponent = value.Exponent();
+ if (exponent >= 0)
+ return RealNumberRenderSize(size_of_sign + size_of_digits, 0);
+
+ const int size_before_decimal_point = exponent + size_of_digits;
+ if (size_before_decimal_point > 0) {
+ // In case of "123.456"
+ return RealNumberRenderSize(size_of_sign + size_before_decimal_point,
+ size_of_digits - size_before_decimal_point);
+ }
+
+ // In case of "0.00012345"
+ const unsigned kSizeOfZero = 1;
+ const unsigned number_of_zero_after_decimal_point =
+ -size_before_decimal_point;
+ return RealNumberRenderSize(
+ size_of_sign + kSizeOfZero,
+ number_of_zero_after_decimal_point + size_of_digits);
+}
+
+InputType* NumberInputType::Create(HTMLInputElement& element) {
+ return new NumberInputType(element);
+}
+
+void NumberInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeNumber);
+}
+
+const AtomicString& NumberInputType::FormControlType() const {
+ return InputTypeNames::number;
+}
+
+void NumberInputType::SetValue(const String& sanitized_value,
+ bool value_changed,
+ TextFieldEventBehavior event_behavior,
+ TextControlSetValueSelection selection) {
+ if (!value_changed && sanitized_value.IsEmpty() &&
+ !GetElement().InnerEditorValue().IsEmpty())
+ GetElement().UpdateView();
+ TextFieldInputType::SetValue(sanitized_value, value_changed, event_behavior,
+ selection);
+}
+
+double NumberInputType::ValueAsDouble() const {
+ return ParseToDoubleForNumberType(GetElement().value());
+}
+
+void NumberInputType::SetValueAsDouble(double new_value,
+ TextFieldEventBehavior event_behavior,
+ ExceptionState& exception_state) const {
+ GetElement().setValue(SerializeForNumberType(new_value), event_behavior);
+}
+
+void NumberInputType::SetValueAsDecimal(const Decimal& new_value,
+ TextFieldEventBehavior event_behavior,
+ ExceptionState& exception_state) const {
+ GetElement().setValue(SerializeForNumberType(new_value), event_behavior);
+}
+
+bool NumberInputType::TypeMismatchFor(const String& value) const {
+ return !value.IsEmpty() && !std::isfinite(ParseToDoubleForNumberType(value));
+}
+
+bool NumberInputType::TypeMismatch() const {
+ DCHECK(!TypeMismatchFor(GetElement().value()));
+ return false;
+}
+
+StepRange NumberInputType::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ DEFINE_STATIC_LOCAL(
+ const StepRange::StepDescription, step_description,
+ (kNumberDefaultStep, kNumberDefaultStepBase, kNumberStepScaleFactor));
+ const Decimal double_max =
+ Decimal::FromDouble(std::numeric_limits<double>::max());
+ return InputType::CreateStepRange(any_step_handling, kNumberDefaultStepBase,
+ -double_max, double_max, step_description);
+}
+
+bool NumberInputType::SizeShouldIncludeDecoration(int default_size,
+ int& preferred_size) const {
+ preferred_size = default_size;
+
+ const String step_string = GetElement().FastGetAttribute(stepAttr);
+ if (DeprecatedEqualIgnoringCase(step_string, "any"))
+ return false;
+
+ const Decimal minimum =
+ ParseToDecimalForNumberType(GetElement().FastGetAttribute(minAttr));
+ if (!minimum.IsFinite())
+ return false;
+
+ const Decimal maximum =
+ ParseToDecimalForNumberType(GetElement().FastGetAttribute(maxAttr));
+ if (!maximum.IsFinite())
+ return false;
+
+ const Decimal step = ParseToDecimalForNumberType(step_string, 1);
+ DCHECK(step.IsFinite());
+
+ RealNumberRenderSize size = CalculateRenderSize(minimum).Max(
+ CalculateRenderSize(maximum).Max(CalculateRenderSize(step)));
+
+ preferred_size = size.size_before_decimal_point +
+ size.size_afte_decimal_point +
+ (size.size_afte_decimal_point ? 1 : 0);
+
+ return true;
+}
+
+bool NumberInputType::IsSteppable() const {
+ return true;
+}
+
+void NumberInputType::HandleKeydownEvent(KeyboardEvent* event) {
+ EventQueueScope scope;
+ HandleKeydownEventForSpinButton(event);
+ if (!event->DefaultHandled())
+ TextFieldInputType::HandleKeydownEvent(event);
+}
+
+void NumberInputType::HandleBeforeTextInsertedEvent(
+ BeforeTextInsertedEvent* event) {
+ event->SetText(GetLocale().StripInvalidNumberCharacters(event->GetText(),
+ "0123456789.Ee-+"));
+}
+
+Decimal NumberInputType::ParseToNumber(const String& src,
+ const Decimal& default_value) const {
+ return ParseToDecimalForNumberType(src, default_value);
+}
+
+String NumberInputType::Serialize(const Decimal& value) const {
+ if (!value.IsFinite())
+ return String();
+ return SerializeForNumberType(value);
+}
+
+static bool IsE(UChar ch) {
+ return ch == 'e' || ch == 'E';
+}
+
+String NumberInputType::LocalizeValue(const String& proposed_value) const {
+ if (proposed_value.IsEmpty())
+ return proposed_value;
+ // We don't localize scientific notations.
+ if (proposed_value.Find(IsE) != kNotFound)
+ return proposed_value;
+ return GetElement().GetLocale().ConvertToLocalizedNumber(proposed_value);
+}
+
+String NumberInputType::VisibleValue() const {
+ return LocalizeValue(GetElement().value());
+}
+
+String NumberInputType::ConvertFromVisibleValue(
+ const String& visible_value) const {
+ if (visible_value.IsEmpty())
+ return visible_value;
+ // We don't localize scientific notations.
+ if (visible_value.Find(IsE) != kNotFound)
+ return visible_value;
+ return GetElement().GetLocale().ConvertFromLocalizedNumber(visible_value);
+}
+
+String NumberInputType::SanitizeValue(const String& proposed_value) const {
+ if (proposed_value.IsEmpty())
+ return proposed_value;
+ return std::isfinite(ParseToDoubleForNumberType(proposed_value))
+ ? proposed_value
+ : g_empty_string;
+}
+
+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);
+}
+
+bool NumberInputType::HasBadInput() const {
+ String standard_value =
+ ConvertFromVisibleValue(GetElement().InnerEditorValue());
+ return !standard_value.IsEmpty() &&
+ !std::isfinite(ParseToDoubleForNumberType(standard_value));
+}
+
+String NumberInputType::BadInputText() const {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationBadInputForNumber);
+}
+
+String NumberInputType::RangeOverflowText(const Decimal& maximum) const {
+ return GetLocale().QueryString(WebLocalizedString::kValidationRangeOverflow,
+ LocalizeValue(Serialize(maximum)));
+}
+
+String NumberInputType::RangeUnderflowText(const Decimal& minimum) const {
+ return GetLocale().QueryString(WebLocalizedString::kValidationRangeUnderflow,
+ LocalizeValue(Serialize(minimum)));
+}
+
+bool NumberInputType::SupportsPlaceholder() const {
+ return true;
+}
+
+void NumberInputType::MinOrMaxAttributeChanged() {
+ TextFieldInputType::MinOrMaxAttributeChanged();
+
+ if (GetElement().GetLayoutObject())
+ GetElement()
+ .GetLayoutObject()
+ ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
+ LayoutInvalidationReason::kAttributeChanged);
+}
+
+void NumberInputType::StepAttributeChanged() {
+ TextFieldInputType::StepAttributeChanged();
+
+ if (GetElement().GetLayoutObject())
+ GetElement()
+ .GetLayoutObject()
+ ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
+ LayoutInvalidationReason::kAttributeChanged);
+}
+
+bool NumberInputType::SupportsSelectionAPI() const {
+ return false;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/number_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.h
new file mode 100644
index 00000000000..302539b63a0
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_NUMBER_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_NUMBER_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/text_field_input_type.h"
+
+namespace blink {
+
+class ExceptionState;
+
+class NumberInputType final : public TextFieldInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ NumberInputType(HTMLInputElement& element) : TextFieldInputType(element) {}
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ void SetValue(const String&,
+ bool value_changed,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) override;
+ double ValueAsDouble() const override;
+ void SetValueAsDouble(double,
+ TextFieldEventBehavior,
+ ExceptionState&) const override;
+ void SetValueAsDecimal(const Decimal&,
+ TextFieldEventBehavior,
+ ExceptionState&) const override;
+ bool TypeMismatchFor(const String&) const override;
+ bool TypeMismatch() const override;
+ bool SizeShouldIncludeDecoration(int default_size,
+ int& preferred_size) const override;
+ bool IsSteppable() const override;
+ StepRange CreateStepRange(AnyStepHandling) const override;
+ void HandleKeydownEvent(KeyboardEvent*) override;
+ void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) override;
+ Decimal ParseToNumber(const String&, const Decimal&) const override;
+ String Serialize(const Decimal&) const override;
+ String LocalizeValue(const String&) const override;
+ String VisibleValue() const override;
+ String ConvertFromVisibleValue(const String&) const override;
+ String SanitizeValue(const String&) const override;
+ void WarnIfValueIsInvalid(const String&) const override;
+ bool HasBadInput() const override;
+ String BadInputText() const override;
+ String RangeOverflowText(const Decimal& maxmum) const override;
+ String RangeUnderflowText(const Decimal& minimum) const override;
+ bool SupportsPlaceholder() const override;
+ void MinOrMaxAttributeChanged() override;
+ void StepAttributeChanged() override;
+ bool SupportsSelectionAPI() const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_NUMBER_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..5beee4e934c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/option_list.cc
@@ -0,0 +1,40 @@
+// Copyright 2016 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/option_list.h"
+
+#include "third_party/blink/renderer/core/dom/element_traversal.h"
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
+
+namespace blink {
+
+void OptionListIterator::Advance(HTMLOptionElement* previous) {
+ // This function returns only
+ // - An OPTION child of m_select, or
+ // - An OPTION child of an OPTGROUP child of m_select.
+
+ Element* current;
+ if (previous) {
+ DCHECK_EQ(previous->OwnerSelectElement(), select_);
+ current = ElementTraversal::NextSkippingChildren(*previous, select_);
+ } else {
+ current = ElementTraversal::FirstChild(*select_);
+ }
+ while (current) {
+ if (auto* option = ToHTMLOptionElementOrNull(current)) {
+ current_ = option;
+ return;
+ }
+ if (IsHTMLOptGroupElement(current) &&
+ current->parentNode() == select_.Get()) {
+ if ((current_ = Traversal<HTMLOptionElement>::FirstChild(*current)))
+ return;
+ }
+ current = ElementTraversal::NextSkippingChildren(*current, select_);
+ }
+ current_ = nullptr;
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..cd3ea21d4e5
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/option_list.h
@@ -0,0 +1,60 @@
+// Copyright 2016 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_OPTION_LIST_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_OPTION_LIST_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+
+namespace blink {
+
+class HTMLSelectElement;
+class HTMLOptionElement;
+
+class CORE_EXPORT OptionListIterator final {
+ STACK_ALLOCATED();
+
+ public:
+ explicit OptionListIterator(const HTMLSelectElement* select)
+ : select_(select) {
+ if (select_)
+ Advance(nullptr);
+ }
+ HTMLOptionElement* operator*() { return current_; }
+ void operator++() {
+ if (current_)
+ Advance(current_);
+ }
+ bool operator==(const OptionListIterator& other) const {
+ return current_ == other.current_;
+ }
+ bool operator!=(const OptionListIterator& other) const {
+ return !(*this == other);
+ }
+
+ private:
+ void Advance(HTMLOptionElement* current);
+
+ Member<const HTMLSelectElement> select_;
+ Member<HTMLOptionElement> current_; // nullptr means we reached to the end.
+};
+
+// OptionList class is a lightweight version of HTMLOptionsCollection.
+class OptionList final {
+ STACK_ALLOCATED();
+
+ public:
+ 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_;
+};
+
+} // namespace blink
+
+#endif
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
new file mode 100644
index 00000000000..f10e65be341
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2016 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/option_list.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
+#include "third_party/blink/renderer/core/html/html_document.h"
+
+namespace blink {
+
+namespace {
+
+AtomicString Id(const HTMLOptionElement* option) {
+ return option->FastGetAttribute(HTMLNames::idAttr);
+}
+
+} // namespace
+
+class OptionListTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ HTMLDocument* document = HTMLDocument::CreateForTest();
+ HTMLSelectElement* select = HTMLSelectElement::Create(*document);
+ document->AppendChild(select);
+ select_ = select;
+ }
+ HTMLSelectElement& Select() const { return *select_; }
+
+ private:
+ Persistent<HTMLSelectElement> select_;
+};
+
+TEST_F(OptionListTest, Empty) {
+ OptionList list = Select().GetOptionList();
+ EXPECT_EQ(list.end(), list.begin())
+ << "OptionList should iterate over empty SELECT successfully";
+}
+
+TEST_F(OptionListTest, OptionOnly) {
+ Select().SetInnerHTMLFromString(
+ "text<input><option id=o1></option><input><option "
+ "id=o2></option><input>");
+ auto* div =
+ ToHTMLElement(Select().GetDocument().CreateRawElement(HTMLNames::divTag));
+ div->SetInnerHTMLFromString("<option id=o3></option>");
+ Select().AppendChild(div);
+ OptionList list = Select().GetOptionList();
+ OptionList::Iterator iter = list.begin();
+ EXPECT_EQ("o1", Id(*iter));
+ ++iter;
+ EXPECT_EQ("o2", Id(*iter));
+ ++iter;
+ // No "o3" because it's in DIV.
+ EXPECT_EQ(list.end(), iter);
+}
+
+TEST_F(OptionListTest, Optgroup) {
+ Select().SetInnerHTMLFromString(
+ "<optgroup><option id=g11></option><option id=g12></option></optgroup>"
+ "<optgroup><option id=g21></option></optgroup>"
+ "<optgroup></optgroup>"
+ "<option id=o1></option>"
+ "<optgroup><option id=g41></option></optgroup>");
+ OptionList list = Select().GetOptionList();
+ OptionList::Iterator iter = list.begin();
+ EXPECT_EQ("g11", Id(*iter));
+ ++iter;
+ EXPECT_EQ("g12", Id(*iter));
+ ++iter;
+ EXPECT_EQ("g21", Id(*iter));
+ ++iter;
+ EXPECT_EQ("o1", Id(*iter));
+ ++iter;
+ EXPECT_EQ("g41", Id(*iter));
+ ++iter;
+ EXPECT_EQ(list.end(), iter);
+
+ ToHTMLElement(Select().firstChild())
+ ->SetInnerHTMLFromString(
+ "<optgroup><option id=gg11></option></optgroup>"
+ "<option id=g11></option>");
+ list = Select().GetOptionList();
+ iter = list.begin();
+ EXPECT_EQ("g11", Id(*iter)) << "Nested OPTGROUP should be ignored.";
+}
+
+} // naemespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/password_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.cc
new file mode 100644
index 00000000000..82bcbf68fa6
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/password_input_type.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/frame/local_frame.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/html_input_element.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/core/layout/layout_text_control_single_line.h"
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
+
+namespace blink {
+
+InputType* PasswordInputType::Create(HTMLInputElement& element) {
+ return new PasswordInputType(element);
+}
+
+void PasswordInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypePassword);
+ if (GetElement().FastHasAttribute(HTMLNames::maxlengthAttr))
+ CountUsageIfVisible(WebFeature::kInputTypePasswordMaxLength);
+}
+
+const AtomicString& PasswordInputType::FormControlType() const {
+ return InputTypeNames::password;
+}
+
+bool PasswordInputType::ShouldSaveAndRestoreFormControlState() const {
+ return false;
+}
+
+FormControlState PasswordInputType::SaveFormControlState() const {
+ // Should never save/restore password fields.
+ NOTREACHED();
+ return FormControlState();
+}
+
+void PasswordInputType::RestoreFormControlState(const FormControlState&) {
+ // Should never save/restore password fields.
+ NOTREACHED();
+}
+
+bool PasswordInputType::ShouldRespectListAttribute() {
+ return false;
+}
+
+void PasswordInputType::OnAttachWithLayoutObject() {
+ GetElement().GetDocument().IncrementPasswordCount();
+}
+
+void PasswordInputType::OnDetachWithLayoutObject() {
+ GetElement().GetDocument().DecrementPasswordCount();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/password_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.h
new file mode 100644
index 00000000000..093a7947405
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PASSWORD_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PASSWORD_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h"
+
+namespace blink {
+
+class PasswordInputType final : public BaseTextInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ explicit PasswordInputType(HTMLInputElement& element)
+ : BaseTextInputType(element) {}
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ bool ShouldSaveAndRestoreFormControlState() const override;
+ FormControlState SaveFormControlState() const override;
+ void RestoreFormControlState(const FormControlState&) override;
+ bool ShouldRespectListAttribute() override;
+ void OnAttachWithLayoutObject() override;
+ void OnDetachWithLayoutObject() override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PASSWORD_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..65e28107e7a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc
@@ -0,0 +1,353 @@
+// Copyright 2016 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 <memory>
+#include <utility>
+
+#include "third_party/blink/renderer/core/html/forms/password_input_type.h"
+
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/modules/insecure_input/insecure_input_service.mojom-blink.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.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/testing/unit_test_helpers.h"
+
+namespace blink {
+
+class MockInsecureInputService : public mojom::blink::InsecureInputService {
+ public:
+ explicit MockInsecureInputService(LocalFrame& frame) {
+ service_manager::InterfaceProvider::TestApi test_api(
+ &frame.GetInterfaceProvider());
+ test_api.SetBinderForName(
+ mojom::blink::InsecureInputService::Name_,
+ WTF::BindRepeating(&MockInsecureInputService::BindRequest,
+ WTF::Unretained(this)));
+ }
+
+ ~MockInsecureInputService() override = default;
+
+ void BindRequest(mojo::ScopedMessagePipeHandle handle) {
+ binding_set_.AddBinding(
+ this, mojom::blink::InsecureInputServiceRequest(std::move(handle)));
+ }
+
+ unsigned DidEditFieldCalls() const { return num_did_edit_field_calls_; }
+
+ bool PasswordFieldVisibleCalled() const {
+ return password_field_visible_called_;
+ }
+
+ unsigned NumPasswordFieldsInvisibleCalls() const {
+ return num_password_fields_invisible_calls_;
+ }
+
+ private:
+ // mojom::InsecureInputService
+ void PasswordFieldVisibleInInsecureContext() override {
+ password_field_visible_called_ = true;
+ }
+
+ void AllPasswordFieldsInInsecureContextInvisible() override {
+ ++num_password_fields_invisible_calls_;
+ }
+
+ void DidEditFieldInInsecureContext() override { ++num_did_edit_field_calls_; }
+
+ mojo::BindingSet<InsecureInputService> binding_set_;
+
+ bool password_field_visible_called_ = false;
+ unsigned num_did_edit_field_calls_ = 0;
+ unsigned num_password_fields_invisible_calls_ = 0;
+};
+
+// Tests that a Mojo message is sent when a visible password field
+// appears on the page.
+TEST(PasswordInputTypeTest, PasswordVisibilityEvent) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled());
+}
+
+// Tests that a Mojo message is not sent when a visible password field
+// appears in a secure context.
+TEST(PasswordInputTypeTest, PasswordVisibilityEventInSecureContext) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().SetURL(KURL("https://example.test"));
+ page_holder->GetDocument().SetSecurityOrigin(
+ SecurityOrigin::Create(KURL("https://example.test")));
+ page_holder->GetDocument().SetSecureContextStateForTesting(
+ SecureContextState::kSecure);
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ // No message should have been sent from a secure context.
+ blink::test::RunPendingTasks();
+ EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled());
+}
+
+// Tests that a Mojo message is sent when a previously invisible password field
+// becomes visible.
+TEST(PasswordInputTypeTest, InvisiblePasswordFieldBecomesVisible) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password' style='display:none;'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ // The message should not be sent for a hidden password field.
+ EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled());
+
+ // Now make the input visible.
+ HTMLInputElement* input =
+ ToHTMLInputElement(page_holder->GetDocument().body()->firstChild());
+ input->setAttribute("style", "", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled());
+}
+
+// Tests that a Mojo message is sent when a previously non-password field
+// becomes a password.
+TEST(PasswordInputTypeTest, NonPasswordFieldBecomesPassword) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='text'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ // The message should not be sent for a non-password field.
+ blink::test::RunPendingTasks();
+ EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled());
+
+ // Now make the input a password field.
+ HTMLInputElement* input =
+ ToHTMLInputElement(page_holder->GetDocument().body()->firstChild());
+ input->setType("password");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled());
+}
+
+// Tests that a Mojo message is *not* sent when a previously invisible password
+// field becomes a visible non-password field.
+TEST(PasswordInputTypeTest,
+ InvisiblePasswordFieldBecomesVisibleNonPasswordField) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password' style='display:none;'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ // The message should not be sent for a hidden password field.
+ EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled());
+
+ // Now make the input a visible non-password field.
+ HTMLInputElement* input =
+ ToHTMLInputElement(page_holder->GetDocument().body()->firstChild());
+ input->setType("text");
+ input->setAttribute("style", "", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled());
+}
+
+// Tests that a Mojo message is sent when the only visible password
+// field becomes invisible.
+TEST(PasswordInputTypeTest, VisiblePasswordFieldBecomesInvisible) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled());
+ EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // Now make the input invisible.
+ HTMLInputElement* input =
+ ToHTMLInputElement(page_holder->GetDocument().body()->firstChild());
+ input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls());
+}
+
+// Tests that a Mojo message is sent when all visible password fields
+// become invisible.
+TEST(PasswordInputTypeTest, AllVisiblePasswordFieldBecomeInvisible) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'><input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // Make the first input invisible. There should be no message because
+ // there is still a visible input.
+ HTMLInputElement* input =
+ ToHTMLInputElement(page_holder->GetDocument().body()->firstChild());
+ input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // When all inputs are invisible, then a message should be sent.
+ input = ToHTMLInputElement(page_holder->GetDocument().body()->lastChild());
+ input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // If the count of visible inputs goes positive again and then back to
+ // zero, a message should be sent again.
+ input->setAttribute("style", "", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls());
+ input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(2u, mock_service.NumPasswordFieldsInvisibleCalls());
+}
+
+// Tests that a Mojo message is sent when the containing element of a
+// visible password field becomes invisible.
+TEST(PasswordInputTypeTest, PasswordFieldContainerBecomesInvisible) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<div><input type='password'></div>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // If the containing div becomes invisible, a message should be sent.
+ HTMLElement* div =
+ ToHTMLDivElement(page_holder->GetDocument().body()->firstChild());
+ div->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // If the containing div becomes visible and then invisible again, a message
+ // should be sent.
+ div->setAttribute("style", "", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls());
+ div->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION);
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(2u, mock_service.NumPasswordFieldsInvisibleCalls());
+}
+
+// Tests that a Mojo message is sent when all visible password fields
+// become non-password fields.
+TEST(PasswordInputTypeTest, PasswordFieldsBecomeNonPasswordFields) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'><input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // Make the first input a non-password input. There should be no
+ // message because there is still a visible password input.
+ HTMLInputElement* input =
+ ToHTMLInputElement(page_holder->GetDocument().body()->firstChild());
+ input->setType("text");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls());
+
+ // When all inputs are no longer passwords, then a message should be sent.
+ input = ToHTMLInputElement(page_holder->GetDocument().body()->lastChild());
+ input->setType("text");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls());
+}
+
+// Tests that only one Mojo message is sent for multiple password
+// visibility events within the same task.
+TEST(PasswordInputTypeTest, MultipleEventsInSameTask) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ // Make the password field invisible in the same task.
+ HTMLInputElement* input =
+ ToHTMLInputElement(page_holder->GetDocument().body()->firstChild());
+ input->setType("text");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ // Only a single Mojo message should have been sent, with the latest state of
+ // the page (which is that no password fields are visible).
+ EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls());
+ EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled());
+}
+
+// Tests that a Mojo message is sent when a password field is edited
+// on the page.
+TEST(PasswordInputTypeTest, DidEditFieldEvent) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(0u, mock_service.DidEditFieldCalls());
+ // Simulate a text field edit.
+ page_holder->GetDocument().MaybeQueueSendDidEditFieldInInsecureContext();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.DidEditFieldCalls());
+ // Ensure additional edits do not trigger additional notifications.
+ page_holder->GetDocument().MaybeQueueSendDidEditFieldInInsecureContext();
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(1u, mock_service.DidEditFieldCalls());
+}
+
+// Tests that a Mojo message is not sent when a password field is edited
+// in a secure context.
+TEST(PasswordInputTypeTest, DidEditFieldEventNotSentFromSecureContext) {
+ std::unique_ptr<DummyPageHolder> page_holder =
+ DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr);
+ MockInsecureInputService mock_service(page_holder->GetFrame());
+ page_holder->GetDocument().SetURL(KURL("https://example.test"));
+ page_holder->GetDocument().SetSecurityOrigin(
+ SecurityOrigin::Create(KURL("https://example.test")));
+ page_holder->GetDocument().SetSecureContextStateForTesting(
+ SecureContextState::kSecure);
+ page_holder->GetDocument().body()->SetInnerHTMLFromString(
+ "<input type='password'>");
+ page_holder->GetDocument().View()->UpdateAllLifecyclePhases();
+ // Simulate a text field edit.
+ page_holder->GetDocument().MaybeQueueSendDidEditFieldInInsecureContext();
+ // No message should have been sent from a secure context.
+ blink::test::RunPendingTasks();
+ EXPECT_EQ(0u, mock_service.DidEditFieldCalls());
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..38bc9b09567
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/picker_indicator_element.h"
+
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
+#include "third_party/blink/renderer/core/layout/layout_details_marker.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/platform/layout_test_support.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline PickerIndicatorElement::PickerIndicatorElement(
+ Document& document,
+ PickerIndicatorOwner& picker_indicator_owner)
+ : HTMLDivElement(document),
+ picker_indicator_owner_(&picker_indicator_owner) {}
+
+PickerIndicatorElement* PickerIndicatorElement::Create(
+ Document& document,
+ PickerIndicatorOwner& picker_indicator_owner) {
+ PickerIndicatorElement* element =
+ new PickerIndicatorElement(document, picker_indicator_owner);
+ element->SetShadowPseudoId(AtomicString("-webkit-calendar-picker-indicator"));
+ element->setAttribute(idAttr, ShadowElementNames::PickerIndicator());
+ return element;
+}
+
+PickerIndicatorElement::~PickerIndicatorElement() {
+ DCHECK(!chooser_);
+}
+
+LayoutObject* PickerIndicatorElement::CreateLayoutObject(const ComputedStyle&) {
+ return new LayoutDetailsMarker(this);
+}
+
+void PickerIndicatorElement::DefaultEventHandler(Event* event) {
+ if (!GetLayoutObject())
+ return;
+ if (!picker_indicator_owner_ ||
+ picker_indicator_owner_->IsPickerIndicatorOwnerDisabledOrReadOnly())
+ return;
+
+ if (event->type() == EventTypeNames::click) {
+ OpenPopup();
+ event->SetDefaultHandled();
+ } else if (event->type() == EventTypeNames::keypress &&
+ event->IsKeyboardEvent()) {
+ int char_code = ToKeyboardEvent(event)->charCode();
+ if (char_code == ' ' || char_code == '\r') {
+ OpenPopup();
+ event->SetDefaultHandled();
+ }
+ }
+
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+}
+
+bool PickerIndicatorElement::WillRespondToMouseClickEvents() {
+ if (GetLayoutObject() && picker_indicator_owner_ &&
+ !picker_indicator_owner_->IsPickerIndicatorOwnerDisabledOrReadOnly())
+ return true;
+
+ return HTMLDivElement::WillRespondToMouseClickEvents();
+}
+
+void PickerIndicatorElement::DidChooseValue(const String& value) {
+ if (!picker_indicator_owner_)
+ return;
+ picker_indicator_owner_->PickerIndicatorChooseValue(value);
+}
+
+void PickerIndicatorElement::DidChooseValue(double value) {
+ if (picker_indicator_owner_)
+ picker_indicator_owner_->PickerIndicatorChooseValue(value);
+}
+
+void PickerIndicatorElement::DidEndChooser() {
+ chooser_.Clear();
+}
+
+void PickerIndicatorElement::OpenPopup() {
+ if (chooser_)
+ return;
+ if (!GetDocument().GetPage())
+ return;
+ if (!picker_indicator_owner_)
+ return;
+ DateTimeChooserParameters parameters;
+ if (!picker_indicator_owner_->SetupDateTimeChooserParameters(parameters))
+ return;
+ chooser_ = GetDocument().GetPage()->GetChromeClient().OpenDateTimeChooser(
+ this, parameters);
+}
+
+Element& PickerIndicatorElement::OwnerElement() const {
+ DCHECK(picker_indicator_owner_);
+ return picker_indicator_owner_->PickerOwnerElement();
+}
+
+void PickerIndicatorElement::ClosePopup() {
+ if (!chooser_)
+ return;
+ chooser_->EndChooser();
+}
+
+void PickerIndicatorElement::DetachLayoutTree(const AttachContext& context) {
+ ClosePopup();
+ HTMLDivElement::DetachLayoutTree(context);
+}
+
+AXObject* PickerIndicatorElement::PopupRootAXObject() const {
+ return chooser_ ? chooser_->RootAXObject() : nullptr;
+}
+
+bool PickerIndicatorElement::IsPickerIndicatorElement() const {
+ return true;
+}
+
+Node::InsertionNotificationRequest PickerIndicatorElement::InsertedInto(
+ ContainerNode* insertion_point) {
+ HTMLDivElement::InsertedInto(insertion_point);
+ return kInsertionShouldCallDidNotifySubtreeInsertions;
+}
+
+void PickerIndicatorElement::DidNotifySubtreeInsertionsToDocument() {
+ if (!GetDocument().GetSettings() ||
+ !GetDocument().GetSettings()->GetAccessibilityEnabled())
+ return;
+ // Don't make this focusable if we are in layout tests in order to avoid to
+ // break existing tests.
+ // FIXME: We should have a way to disable accessibility in layout tests.
+ if (LayoutTestSupport::IsRunningLayoutTest())
+ return;
+ setAttribute(tabindexAttr, "0");
+ setAttribute(aria_haspopupAttr, "true");
+ setAttribute(roleAttr, "button");
+}
+
+void PickerIndicatorElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(picker_indicator_owner_);
+ visitor->Trace(chooser_);
+ HTMLDivElement::Trace(visitor);
+ DateTimeChooserClient::Trace(visitor);
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..c0c6fe30e6f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PICKER_INDICATOR_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PICKER_INDICATOR_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h"
+#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+
+namespace blink {
+
+class HTMLInputElement;
+
+class PickerIndicatorElement final : public HTMLDivElement,
+ public DateTimeChooserClient {
+ USING_GARBAGE_COLLECTED_MIXIN(PickerIndicatorElement);
+
+ public:
+ // PickerIndicatorOwner implementer must call removePickerIndicatorOwner when
+ // it doesn't handle event, e.g. at destruction.
+ class PickerIndicatorOwner : public GarbageCollectedMixin {
+ public:
+ virtual ~PickerIndicatorOwner() = default;
+ virtual bool IsPickerIndicatorOwnerDisabledOrReadOnly() const = 0;
+ // FIXME: Remove. Deprecated in favor of double version.
+ virtual void PickerIndicatorChooseValue(const String&) = 0;
+ virtual void PickerIndicatorChooseValue(double) = 0;
+ virtual Element& PickerOwnerElement() const = 0;
+ virtual bool SetupDateTimeChooserParameters(DateTimeChooserParameters&) = 0;
+ };
+
+ static PickerIndicatorElement* Create(Document&, PickerIndicatorOwner&);
+ ~PickerIndicatorElement() override;
+ void Trace(blink::Visitor*) override;
+
+ void OpenPopup();
+ void ClosePopup();
+ bool WillRespondToMouseClickEvents() override;
+ void RemovePickerIndicatorOwner() { picker_indicator_owner_ = nullptr; }
+ AXObject* PopupRootAXObject() const;
+
+ // DateTimeChooserClient implementation.
+ Element& OwnerElement() const override;
+ void DidChooseValue(const String&) override;
+ void DidChooseValue(double) override;
+ void DidEndChooser() override;
+
+ private:
+ PickerIndicatorElement(Document&, PickerIndicatorOwner&);
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ void DefaultEventHandler(Event*) override;
+ void DetachLayoutTree(const AttachContext& = AttachContext()) override;
+ bool IsPickerIndicatorElement() const override;
+ 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());
+
+} // namespace blink
+#endif
diff --git a/chromium/third_party/blink/renderer/core/html/forms/popup_menu.h b/chromium/third_party/blink/renderer/core/html/forms/popup_menu.h
new file mode 100644
index 00000000000..c2175a18ada
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/popup_menu.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_POPUP_MENU_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_POPUP_MENU_H_
+
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+
+namespace blink {
+
+class PopupMenu : public GarbageCollectedFinalized<PopupMenu> {
+ public:
+ virtual ~PopupMenu() = default;
+ virtual void Trace(blink::Visitor* visitor) {}
+ virtual void Show() = 0;
+ virtual void Hide() = 0;
+ enum UpdateReason {
+ kBySelectionChange,
+ kByStyleChange,
+ kByDOMChange,
+ };
+ virtual void UpdateFromElement(UpdateReason) = 0;
+ virtual void DisconnectClient() = 0;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_POPUP_MENU_H_
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
new file mode 100644
index 00000000000..9f997f23b71
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved.
+ *
+ * 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/radio_button_group_scope.h"
+
+#include "third_party/blink/renderer/core/dom/ax_object_cache.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+
+namespace blink {
+
+class RadioButtonGroup : public GarbageCollected<RadioButtonGroup> {
+ public:
+ static RadioButtonGroup* Create();
+ bool IsEmpty() const { return members_.IsEmpty(); }
+ bool IsRequired() const { return required_count_; }
+ HTMLInputElement* CheckedButton() const { return checked_button_; }
+ void Add(HTMLInputElement*);
+ void UpdateCheckedState(HTMLInputElement*);
+ void RequiredAttributeChanged(HTMLInputElement*);
+ void Remove(HTMLInputElement*);
+ bool Contains(HTMLInputElement*) const;
+ unsigned size() const;
+
+ void Trace(blink::Visitor*);
+
+ private:
+ RadioButtonGroup();
+ void SetNeedsValidityCheckForAllButtons();
+ bool IsValid() const;
+ void SetCheckedButton(HTMLInputElement*);
+
+ // The map records the 'required' state of each (button) element.
+ using Members = HeapHashMap<Member<HTMLInputElement>, bool>;
+
+ using MemberKeyValue = WTF::KeyValuePair<Member<HTMLInputElement>, bool>;
+
+ void UpdateRequiredButton(MemberKeyValue&, bool is_required);
+
+ Members members_;
+ Member<HTMLInputElement> checked_button_;
+ size_t required_count_;
+};
+
+RadioButtonGroup::RadioButtonGroup()
+ : checked_button_(nullptr), required_count_(0) {}
+
+RadioButtonGroup* RadioButtonGroup::Create() {
+ return new RadioButtonGroup;
+}
+
+inline bool RadioButtonGroup::IsValid() const {
+ return !IsRequired() || checked_button_;
+}
+
+void RadioButtonGroup::SetCheckedButton(HTMLInputElement* button) {
+ HTMLInputElement* old_checked_button = checked_button_;
+ if (old_checked_button == button)
+ return;
+ checked_button_ = button;
+ if (old_checked_button)
+ old_checked_button->setChecked(false);
+}
+
+void RadioButtonGroup::UpdateRequiredButton(MemberKeyValue& it,
+ bool is_required) {
+ if (it.value == is_required)
+ return;
+
+ it.value = is_required;
+ if (is_required) {
+ required_count_++;
+ } else {
+ DCHECK_GT(required_count_, 0u);
+ required_count_--;
+ }
+}
+
+void RadioButtonGroup::Add(HTMLInputElement* button) {
+ DCHECK_EQ(button->type(), InputTypeNames::radio);
+ auto add_result = members_.insert(button, false);
+ if (!add_result.is_new_entry)
+ return;
+ bool group_was_valid = IsValid();
+ UpdateRequiredButton(*add_result.stored_value, button->IsRequired());
+ if (button->checked())
+ SetCheckedButton(button);
+
+ bool group_is_valid = IsValid();
+ if (group_was_valid != group_is_valid) {
+ SetNeedsValidityCheckForAllButtons();
+ } else if (!group_is_valid) {
+ // A radio button not in a group is always valid. We need to make it
+ // invalid only if the group is invalid.
+ button->SetNeedsValidityCheck();
+ }
+}
+
+void RadioButtonGroup::UpdateCheckedState(HTMLInputElement* button) {
+ DCHECK_EQ(button->type(), InputTypeNames::radio);
+ DCHECK(members_.Contains(button));
+ bool was_valid = IsValid();
+ if (button->checked()) {
+ SetCheckedButton(button);
+ } else {
+ if (checked_button_ == button)
+ checked_button_ = nullptr;
+ }
+ if (was_valid != IsValid())
+ SetNeedsValidityCheckForAllButtons();
+ for (auto& member : members_) {
+ HTMLInputElement* const input_element = member.key;
+ input_element->PseudoStateChanged(CSSSelector::kPseudoIndeterminate);
+ }
+}
+
+void RadioButtonGroup::RequiredAttributeChanged(HTMLInputElement* button) {
+ DCHECK_EQ(button->type(), InputTypeNames::radio);
+ auto it = members_.find(button);
+ DCHECK_NE(it, members_.end());
+ bool was_valid = IsValid();
+ // Synchronize the 'required' flag for the button, along with
+ // updating the overall count.
+ UpdateRequiredButton(*it, button->IsRequired());
+ if (was_valid != IsValid())
+ SetNeedsValidityCheckForAllButtons();
+}
+
+void RadioButtonGroup::Remove(HTMLInputElement* button) {
+ DCHECK_EQ(button->type(), InputTypeNames::radio);
+ auto it = members_.find(button);
+ if (it == members_.end())
+ return;
+ bool was_valid = IsValid();
+ DCHECK_EQ(it->value, button->IsRequired());
+ UpdateRequiredButton(*it, false);
+ members_.erase(it);
+ if (checked_button_ == button)
+ checked_button_ = nullptr;
+
+ if (members_.IsEmpty()) {
+ DCHECK(!required_count_);
+ DCHECK(!checked_button_);
+ } else if (was_valid != IsValid()) {
+ SetNeedsValidityCheckForAllButtons();
+ }
+ if (!was_valid) {
+ // A radio button not in a group is always valid. We need to make it
+ // valid only if the group was invalid.
+ button->SetNeedsValidityCheck();
+ }
+
+ // Send notification to update AX attributes for AXObjects which radiobutton
+ // group has.
+ if (!members_.IsEmpty()) {
+ HTMLInputElement* input = members_.begin()->key;
+ if (AXObjectCache* cache = input->GetDocument().ExistingAXObjectCache())
+ cache->RadiobuttonRemovedFromGroup(input);
+ }
+}
+
+void RadioButtonGroup::SetNeedsValidityCheckForAllButtons() {
+ for (auto& element : members_) {
+ HTMLInputElement* const button = element.key;
+ DCHECK_EQ(button->type(), InputTypeNames::radio);
+ button->SetNeedsValidityCheck();
+ }
+}
+
+bool RadioButtonGroup::Contains(HTMLInputElement* button) const {
+ return members_.Contains(button);
+}
+
+unsigned RadioButtonGroup::size() const {
+ return members_.size();
+}
+
+void RadioButtonGroup::Trace(blink::Visitor* visitor) {
+ visitor->Trace(members_);
+ visitor->Trace(checked_button_);
+}
+
+// ----------------------------------------------------------------
+
+// Explicity define empty constructor and destructor in order to prevent the
+// compiler from generating them as inlines. So we don't need to to define
+// RadioButtonGroup in the header.
+RadioButtonGroupScope::RadioButtonGroupScope() = default;
+
+RadioButtonGroupScope::~RadioButtonGroupScope() = default;
+
+void RadioButtonGroupScope::AddButton(HTMLInputElement* element) {
+ DCHECK_EQ(element->type(), InputTypeNames::radio);
+ if (element->GetName().IsEmpty())
+ return;
+
+ if (!name_to_group_map_)
+ name_to_group_map_ = new NameToGroupMap;
+
+ auto* key_value =
+ name_to_group_map_->insert(element->GetName(), nullptr).stored_value;
+ if (!key_value->value)
+ key_value->value = RadioButtonGroup::Create();
+ key_value->value->Add(element);
+}
+
+void RadioButtonGroupScope::UpdateCheckedState(HTMLInputElement* element) {
+ DCHECK_EQ(element->type(), InputTypeNames::radio);
+ if (element->GetName().IsEmpty())
+ return;
+ DCHECK(name_to_group_map_);
+ if (!name_to_group_map_)
+ return;
+ RadioButtonGroup* group = name_to_group_map_->at(element->GetName());
+ DCHECK(group);
+ group->UpdateCheckedState(element);
+}
+
+void RadioButtonGroupScope::RequiredAttributeChanged(
+ HTMLInputElement* element) {
+ DCHECK_EQ(element->type(), InputTypeNames::radio);
+ if (element->GetName().IsEmpty())
+ return;
+ DCHECK(name_to_group_map_);
+ if (!name_to_group_map_)
+ return;
+ RadioButtonGroup* group = name_to_group_map_->at(element->GetName());
+ DCHECK(group);
+ group->RequiredAttributeChanged(element);
+}
+
+HTMLInputElement* RadioButtonGroupScope::CheckedButtonForGroup(
+ const AtomicString& name) const {
+ if (!name_to_group_map_)
+ return nullptr;
+ RadioButtonGroup* group = name_to_group_map_->at(name);
+ return group ? group->CheckedButton() : nullptr;
+}
+
+bool RadioButtonGroupScope::IsInRequiredGroup(HTMLInputElement* element) const {
+ DCHECK_EQ(element->type(), InputTypeNames::radio);
+ if (element->GetName().IsEmpty())
+ return false;
+ if (!name_to_group_map_)
+ return false;
+ RadioButtonGroup* group = name_to_group_map_->at(element->GetName());
+ return group && group->IsRequired() && group->Contains(element);
+}
+
+unsigned RadioButtonGroupScope::GroupSizeFor(
+ const HTMLInputElement* element) const {
+ if (!name_to_group_map_)
+ return 0;
+
+ RadioButtonGroup* group = name_to_group_map_->at(element->GetName());
+ if (!group)
+ return 0;
+ return group->size();
+}
+
+void RadioButtonGroupScope::RemoveButton(HTMLInputElement* element) {
+ DCHECK_EQ(element->type(), InputTypeNames::radio);
+ if (element->GetName().IsEmpty())
+ return;
+ if (!name_to_group_map_)
+ return;
+
+ RadioButtonGroup* group = name_to_group_map_->at(element->GetName());
+ if (!group)
+ return;
+ group->Remove(element);
+ if (group->IsEmpty()) {
+ // We don't remove an empty RadioButtonGroup from m_nameToGroupMap for
+ // better performance.
+ DCHECK(!group->IsRequired());
+ SECURITY_DCHECK(!group->CheckedButton());
+ }
+}
+
+void RadioButtonGroupScope::Trace(blink::Visitor* visitor) {
+ visitor->Trace(name_to_group_map_);
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..8c8c9a20a87
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_BUTTON_GROUP_SCOPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_BUTTON_GROUP_SCOPE_H_
+
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
+
+namespace blink {
+
+class HTMLInputElement;
+class RadioButtonGroup;
+
+class RadioButtonGroupScope {
+ DISALLOW_NEW();
+
+ public:
+ RadioButtonGroupScope();
+ ~RadioButtonGroupScope();
+ void Trace(blink::Visitor*);
+ void AddButton(HTMLInputElement*);
+ void UpdateCheckedState(HTMLInputElement*);
+ void RequiredAttributeChanged(HTMLInputElement*);
+ void RemoveButton(HTMLInputElement*);
+ HTMLInputElement* CheckedButtonForGroup(const AtomicString& group_name) const;
+ bool IsInRequiredGroup(HTMLInputElement*) const;
+ unsigned GroupSizeFor(const HTMLInputElement*) const;
+
+ private:
+ using NameToGroupMap = HeapHashMap<AtomicString, Member<RadioButtonGroup>>;
+ Member<NameToGroupMap> name_to_group_map_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_BUTTON_GROUP_SCOPE_H_
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
new file mode 100644
index 00000000000..0ca386d492d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2005, 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * 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/radio_input_type.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/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/events/mouse_event.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#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/input_type_names.h"
+#include "third_party/blink/renderer/core/page/spatial_navigation.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+namespace {
+
+HTMLInputElement* NextInputElement(const HTMLInputElement& element,
+ const HTMLFormElement* stay_within,
+ bool forward) {
+ return forward ? Traversal<HTMLInputElement>::Next(element, stay_within)
+ : Traversal<HTMLInputElement>::Previous(element, stay_within);
+}
+
+} // namespace
+
+using namespace HTMLNames;
+
+InputType* RadioInputType::Create(HTMLInputElement& element) {
+ return new RadioInputType(element);
+}
+
+const AtomicString& RadioInputType::FormControlType() const {
+ return InputTypeNames::radio;
+}
+
+bool RadioInputType::ValueMissing(const String&) const {
+ return GetElement().IsInRequiredRadioButtonGroup() &&
+ !GetElement().CheckedRadioButtonForGroup();
+}
+
+String RadioInputType::ValueMissingText() const {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationValueMissingForRadio);
+}
+
+void RadioInputType::HandleClickEvent(MouseEvent* event) {
+ event->SetDefaultHandled();
+}
+
+HTMLInputElement* RadioInputType::FindNextFocusableRadioButtonInGroup(
+ HTMLInputElement* current_element,
+ bool forward) {
+ for (HTMLInputElement* input_element =
+ NextRadioButtonInGroup(current_element, forward);
+ input_element;
+ input_element = NextRadioButtonInGroup(input_element, forward)) {
+ if (input_element->IsFocusable())
+ return input_element;
+ }
+ return nullptr;
+}
+
+void RadioInputType::HandleKeydownEvent(KeyboardEvent* event) {
+ // TODO(tkent): We should return more earlier.
+ if (!GetElement().GetLayoutObject())
+ return;
+ BaseCheckableInputType::HandleKeydownEvent(event);
+ if (event->DefaultHandled())
+ return;
+ const String& key = event->key();
+ if (key != "ArrowUp" && key != "ArrowDown" && key != "ArrowLeft" &&
+ key != "ArrowRight")
+ return;
+
+ if (event->ctrlKey() || event->metaKey() || event->altKey())
+ return;
+
+ // Left and up mean "previous radio button".
+ // Right and down mean "next radio button".
+ // Tested in WinIE, and even for RTL, left still means previous radio button
+ // (and so moves to the right). Seems strange, but we'll match it. However,
+ // when using Spatial Navigation, we need to be able to navigate without
+ // changing the selection.
+ Document& document = GetElement().GetDocument();
+ if (IsSpatialNavigationEnabled(document.GetFrame()))
+ return;
+ bool forward = ComputedTextDirection() == TextDirection::kRtl
+ ? (key == "ArrowDown" || key == "ArrowLeft")
+ : (key == "ArrowDown" || key == "ArrowRight");
+
+ // Force layout for isFocusable() in findNextFocusableRadioButtonInGroup().
+ document.UpdateStyleAndLayoutIgnorePendingStylesheets();
+
+ // We can only stay within the form's children if the form hasn't been demoted
+ // to a leaf because of malformed HTML.
+ HTMLInputElement* input_element =
+ FindNextFocusableRadioButtonInGroup(&GetElement(), forward);
+ if (!input_element) {
+ // Traverse in reverse direction till last or first radio button
+ forward = !(forward);
+ HTMLInputElement* next_input_element =
+ FindNextFocusableRadioButtonInGroup(&GetElement(), forward);
+ while (next_input_element) {
+ input_element = next_input_element;
+ next_input_element =
+ FindNextFocusableRadioButtonInGroup(next_input_element, forward);
+ }
+ }
+ if (input_element) {
+ document.SetFocusedElement(input_element,
+ FocusParams(SelectionBehaviorOnFocus::kRestore,
+ kWebFocusTypeNone, nullptr));
+ input_element->DispatchSimulatedClick(event, kSendNoEvents);
+ event->SetDefaultHandled();
+ return;
+ }
+}
+
+void RadioInputType::HandleKeyupEvent(KeyboardEvent* event) {
+ const String& key = event->key();
+ if (key != " ")
+ return;
+ // If an unselected radio is tabbed into (because the entire group has nothing
+ // checked, or because of some explicit .focus() call), then allow space to
+ // check it.
+ if (GetElement().checked())
+ return;
+ DispatchSimulatedClickIfActive(event);
+}
+
+bool RadioInputType::IsKeyboardFocusable() const {
+ if (!InputType::IsKeyboardFocusable())
+ return false;
+
+ // When using Spatial Navigation, every radio button should be focusable.
+ if (IsSpatialNavigationEnabled(GetElement().GetDocument().GetFrame()))
+ return true;
+
+ // Never allow keyboard tabbing to leave you in the same radio group. Always
+ // skip any other elements in the group.
+ Element* current_focused_element =
+ GetElement().GetDocument().FocusedElement();
+ if (auto* focused_input = ToHTMLInputElementOrNull(current_focused_element)) {
+ if (focused_input->type() == InputTypeNames::radio &&
+ focused_input->Form() == GetElement().Form() &&
+ focused_input->GetName() == GetElement().GetName())
+ return false;
+ }
+
+ // Allow keyboard focus if we're checked or if nothing in the group is
+ // checked.
+ return GetElement().checked() || !GetElement().CheckedRadioButtonForGroup();
+}
+
+bool RadioInputType::ShouldSendChangeEventAfterCheckedChanged() {
+ // Don't send a change event for a radio button that's getting unchecked.
+ // This was done to match the behavior of other browsers.
+ return GetElement().checked();
+}
+
+ClickHandlingState* RadioInputType::WillDispatchClick() {
+ // An event handler can use preventDefault or "return false" to reverse the
+ // selection we do here. The ClickHandlingState object contains what we need
+ // to undo what we did here in didDispatchClick.
+
+ // We want radio groups to end up in sane states, i.e., to have something
+ // checked. Therefore if nothing is currently selected, we won't allow the
+ // upcoming action to be "undone", since we want some object in the radio
+ // group to actually get selected.
+
+ ClickHandlingState* state = new ClickHandlingState;
+
+ state->checked = GetElement().checked();
+ state->checked_radio_button = GetElement().CheckedRadioButtonForGroup();
+ GetElement().setChecked(true, kDispatchChangeEvent);
+ is_in_click_handler_ = true;
+ return state;
+}
+
+void RadioInputType::DidDispatchClick(Event* event,
+ const ClickHandlingState& state) {
+ if (event->defaultPrevented() || event->DefaultHandled()) {
+ // Restore the original selected radio button if possible.
+ // Make sure it is still a radio button and only do the restoration if it
+ // still belongs to our group.
+ HTMLInputElement* checked_radio_button = state.checked_radio_button.Get();
+ if (!checked_radio_button)
+ GetElement().setChecked(false);
+ else if (checked_radio_button->type() == InputTypeNames::radio &&
+ checked_radio_button->Form() == GetElement().Form() &&
+ checked_radio_button->GetName() == GetElement().GetName())
+ checked_radio_button->setChecked(true);
+ } else if (state.checked != GetElement().checked()) {
+ GetElement().DispatchInputAndChangeEventIfNeeded();
+ }
+ is_in_click_handler_ = false;
+ // The work we did in willDispatchClick was default handling.
+ event->SetDefaultHandled();
+}
+
+bool RadioInputType::ShouldAppearIndeterminate() const {
+ return !GetElement().CheckedRadioButtonForGroup();
+}
+
+HTMLInputElement* RadioInputType::NextRadioButtonInGroup(
+ HTMLInputElement* current,
+ bool forward) {
+ // TODO(tkent): Staying within form() is incorrect. This code ignore input
+ // elements associated by |form| content attribute.
+ // TODO(tkent): Comparing name() with == is incorrect. It should be
+ // case-insensitive.
+ for (HTMLInputElement* input_element =
+ NextInputElement(*current, current->Form(), forward);
+ input_element; input_element = NextInputElement(
+ *input_element, current->Form(), forward)) {
+ if (current->Form() == input_element->Form() &&
+ input_element->type() == InputTypeNames::radio &&
+ input_element->GetName() == current->GetName())
+ return input_element;
+ }
+ return nullptr;
+}
+
+} // 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
new file mode 100644
index 00000000000..7c1740d85da
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/forms/base_checkable_input_type.h"
+
+namespace blink {
+
+class RadioInputType final : public BaseCheckableInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+ CORE_EXPORT static HTMLInputElement* NextRadioButtonInGroup(HTMLInputElement*,
+ bool forward);
+
+ private:
+ RadioInputType(HTMLInputElement& element) : BaseCheckableInputType(element) {}
+ const AtomicString& FormControlType() const override;
+ bool ValueMissing(const String&) const override;
+ String ValueMissingText() const override;
+ void HandleClickEvent(MouseEvent*) override;
+ void HandleKeydownEvent(KeyboardEvent*) override;
+ void HandleKeyupEvent(KeyboardEvent*) override;
+ bool IsKeyboardFocusable() const override;
+ bool ShouldSendChangeEventAfterCheckedChanged() override;
+ ClickHandlingState* WillDispatchClick() override;
+ void DidDispatchClick(Event*, const ClickHandlingState&) override;
+ bool ShouldAppearIndeterminate() const override;
+
+ HTMLInputElement* FindNextFocusableRadioButtonInGroup(HTMLInputElement*,
+ bool);
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.cc b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.cc
new file mode 100644
index 00000000000..7170f0e8803
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2012 Motorola Mobility, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MOTOROLA MOBILITY, INC. AND ITS CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY, INC. OR ITS
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/radio_node_list.h"
+
+#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/node_rare_data.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/html_image_element.h"
+#include "third_party/blink/renderer/core/html/html_object_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+RadioNodeList::RadioNodeList(ContainerNode& root_node,
+ const AtomicString& name,
+ CollectionType type)
+ : LiveNodeList(root_node,
+ type,
+ kInvalidateForFormControls,
+ IsHTMLFormElement(root_node)
+ ? NodeListSearchRoot::kTreeScope
+ : NodeListSearchRoot::kOwnerNode),
+ name_(name) {}
+
+RadioNodeList::~RadioNodeList() = default;
+
+static inline HTMLInputElement* ToRadioButtonInputElement(Element& element) {
+ if (!IsHTMLInputElement(element))
+ return nullptr;
+ HTMLInputElement& input_element = ToHTMLInputElement(element);
+ if (input_element.type() != InputTypeNames::radio ||
+ input_element.value().IsEmpty())
+ return nullptr;
+ return &input_element;
+}
+
+String RadioNodeList::value() const {
+ if (ShouldOnlyMatchImgElements())
+ return String();
+ unsigned length = this->length();
+ for (unsigned i = 0; i < length; ++i) {
+ const HTMLInputElement* input_element = ToRadioButtonInputElement(*item(i));
+ if (!input_element || !input_element->checked())
+ continue;
+ return input_element->value();
+ }
+ return String();
+}
+
+void RadioNodeList::setValue(const String& value) {
+ if (ShouldOnlyMatchImgElements())
+ return;
+ unsigned length = this->length();
+ for (unsigned i = 0; i < length; ++i) {
+ HTMLInputElement* input_element = ToRadioButtonInputElement(*item(i));
+ if (!input_element || input_element->value() != value)
+ continue;
+ input_element->setChecked(true);
+ return;
+ }
+}
+
+bool RadioNodeList::MatchesByIdOrName(const Element& test_element) const {
+ return test_element.GetIdAttribute() == name_ ||
+ test_element.GetNameAttribute() == name_;
+}
+
+bool RadioNodeList::CheckElementMatchesRadioNodeListFilter(
+ const Element& test_element) const {
+ DCHECK(!ShouldOnlyMatchImgElements());
+ DCHECK(IsHTMLObjectElement(test_element) ||
+ test_element.IsFormControlElement());
+ if (IsHTMLFormElement(ownerNode())) {
+ HTMLFormElement* form_element = ToHTMLElement(test_element).formOwner();
+ if (!form_element || form_element != ownerNode())
+ return false;
+ }
+
+ return MatchesByIdOrName(test_element);
+}
+
+bool RadioNodeList::ElementMatches(const Element& element) const {
+ if (ShouldOnlyMatchImgElements()) {
+ if (!IsHTMLImageElement(element))
+ return false;
+
+ if (ToHTMLImageElement(element).formOwner() != ownerNode())
+ return false;
+
+ return MatchesByIdOrName(element);
+ }
+
+ if (!IsHTMLObjectElement(element) && !element.IsFormControlElement())
+ return false;
+
+ if (IsHTMLInputElement(element) &&
+ ToHTMLInputElement(element).type() == InputTypeNames::image)
+ return false;
+
+ return CheckElementMatchesRadioNodeListFilter(element);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.h b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.h
new file mode 100644
index 00000000000..81e76d5601c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2012 Motorola Mobility, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MOTOROLA MOBILITY, INC. AND ITS CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY, INC. OR ITS
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_NODE_LIST_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_NODE_LIST_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/dom/live_node_list.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+
+namespace blink {
+
+class RadioNodeList final : public LiveNodeList {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static RadioNodeList* Create(ContainerNode& owner_node,
+ CollectionType type,
+ const AtomicString& name) {
+ DCHECK(type == kRadioNodeListType || type == kRadioImgNodeListType);
+ return new RadioNodeList(owner_node, name, type);
+ }
+
+ ~RadioNodeList() override;
+
+ String value() const;
+ void setValue(const String&);
+
+ private:
+ RadioNodeList(ContainerNode&, const AtomicString& name, CollectionType);
+
+ bool CheckElementMatchesRadioNodeListFilter(const Element&) const;
+
+ bool MatchesByIdOrName(const Element&) const;
+ bool ShouldOnlyMatchImgElements() const {
+ return GetType() == kRadioImgNodeListType;
+ }
+
+ bool ElementMatches(const Element&) const override;
+
+ AtomicString name_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_NODE_LIST_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.idl b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.idl
new file mode 100644
index 00000000000..3f7fde99fdd
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.idl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012 Motorola Mobility, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MOTOROLA MOBILITY, INC. AND ITS CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY, INC. OR ITS
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// https://html.spec.whatwg.org/#radionodelist
+
+[
+ Exposed=Window
+] interface RadioNodeList : NodeList {
+ attribute DOMString value;
+
+ // TODO(tkent): We need to declare this indexed property getter because our
+ // IDL compiler doesn't support inheritance of indexed property
+ // getters. crbug.com/752877
+ [ImplementedAs=item] getter Node? (unsigned long index);
+};
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
new file mode 100644
index 00000000000..771acd0215a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/range_input_type.h"
+
+#include <algorithm>
+#include <limits>
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/dom/ax_object_cache.h"
+#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.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/events/mouse_event.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "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/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/forms/slider_thumb_element.h"
+#include "third_party/blink/renderer/core/html/forms/step_range.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.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/layout/layout_slider.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+static const int kRangeDefaultMinimum = 0;
+static const int kRangeDefaultMaximum = 100;
+static const int kRangeDefaultStep = 1;
+static const int kRangeDefaultStepBase = 0;
+static const int kRangeStepScaleFactor = 1;
+
+static Decimal EnsureMaximum(const Decimal& proposed_value,
+ const Decimal& minimum) {
+ return proposed_value >= minimum ? proposed_value : minimum;
+}
+
+InputType* RangeInputType::Create(HTMLInputElement& element) {
+ return new RangeInputType(element);
+}
+
+RangeInputType::RangeInputType(HTMLInputElement& element)
+ : InputType(element),
+ InputTypeView(element),
+ tick_mark_values_dirty_(true) {}
+
+void RangeInputType::Trace(blink::Visitor* visitor) {
+ InputTypeView::Trace(visitor);
+ InputType::Trace(visitor);
+}
+
+InputTypeView* RangeInputType::CreateView() {
+ return this;
+}
+
+InputType::ValueMode RangeInputType::GetValueMode() const {
+ return ValueMode::kValue;
+}
+
+void RangeInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeRange);
+ if (const ComputedStyle* style = GetElement().GetComputedStyle()) {
+ if (style->Appearance() == kSliderVerticalPart) {
+ UseCounter::Count(GetElement().GetDocument(),
+ WebFeature::kInputTypeRangeVerticalAppearance);
+ }
+ }
+}
+
+const AtomicString& RangeInputType::FormControlType() const {
+ return InputTypeNames::range;
+}
+
+double RangeInputType::ValueAsDouble() const {
+ return ParseToDoubleForNumberType(GetElement().value());
+}
+
+void RangeInputType::SetValueAsDouble(double new_value,
+ TextFieldEventBehavior event_behavior,
+ ExceptionState& exception_state) const {
+ SetValueAsDecimal(Decimal::FromDouble(new_value), event_behavior,
+ exception_state);
+}
+
+bool RangeInputType::TypeMismatchFor(const String& value) const {
+ return !value.IsEmpty() && !std::isfinite(ParseToDoubleForNumberType(value));
+}
+
+bool RangeInputType::SupportsRequired() const {
+ return false;
+}
+
+StepRange RangeInputType::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ DEFINE_STATIC_LOCAL(
+ const StepRange::StepDescription, step_description,
+ (kRangeDefaultStep, kRangeDefaultStepBase, kRangeStepScaleFactor));
+
+ const Decimal step_base = FindStepBase(kRangeDefaultStepBase);
+ const Decimal minimum = ParseToNumber(GetElement().FastGetAttribute(minAttr),
+ kRangeDefaultMinimum);
+ const Decimal maximum =
+ EnsureMaximum(ParseToNumber(GetElement().FastGetAttribute(maxAttr),
+ kRangeDefaultMaximum),
+ minimum);
+
+ const Decimal step =
+ StepRange::ParseStep(any_step_handling, step_description,
+ GetElement().FastGetAttribute(stepAttr));
+ // Range type always has range limitations because it has default
+ // minimum/maximum.
+ // https://html.spec.whatwg.org/multipage/forms.html#range-state-(type=range):concept-input-min-default
+ const bool kHasRangeLimitations = true;
+ return StepRange(step_base, minimum, maximum, kHasRangeLimitations, step,
+ step_description);
+}
+
+bool RangeInputType::IsSteppable() const {
+ return true;
+}
+
+void RangeInputType::HandleMouseDownEvent(MouseEvent* event) {
+ if (GetElement().IsDisabledFormControl())
+ return;
+
+ Node* target_node = event->target()->ToNode();
+ if (event->button() !=
+ static_cast<short>(WebPointerProperties::Button::kLeft) ||
+ !target_node)
+ return;
+ DCHECK(IsShadowHost(GetElement()));
+ if (target_node != GetElement() &&
+ !target_node->IsDescendantOf(GetElement().UserAgentShadowRoot()))
+ return;
+ SliderThumbElement* thumb = GetSliderThumbElement();
+ if (target_node == thumb)
+ return;
+ thumb->DragFrom(LayoutPoint(event->AbsoluteLocation()));
+}
+
+void RangeInputType::HandleKeydownEvent(KeyboardEvent* event) {
+ if (GetElement().IsDisabledFormControl())
+ return;
+
+ const String& key = event->key();
+
+ const Decimal current = ParseToNumberOrNaN(GetElement().value());
+ DCHECK(current.IsFinite());
+
+ StepRange step_range(CreateStepRange(kRejectAny));
+
+ // 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(
+ GetElement().FastGetAttribute(stepAttr), "any")
+ ? (step_range.Maximum() - step_range.Minimum()) / 100
+ : step_range.Step();
+ const Decimal big_step =
+ std::max((step_range.Maximum() - step_range.Minimum()) / 10, step);
+
+ TextDirection dir = TextDirection::kLtr;
+ bool is_vertical = false;
+ if (GetElement().GetLayoutObject()) {
+ dir = ComputedTextDirection();
+ ControlPart part = GetElement().GetLayoutObject()->Style()->Appearance();
+ is_vertical = part == kSliderVerticalPart;
+ }
+
+ Decimal new_value;
+ if (key == "ArrowUp") {
+ new_value = current + step;
+ } else if (key == "ArrowDown") {
+ new_value = current - step;
+ } else if (key == "ArrowLeft") {
+ new_value = (is_vertical || dir == TextDirection::kRtl) ? current + step
+ : current - step;
+ } else if (key == "ArrowRight") {
+ new_value = (is_vertical || dir == TextDirection::kRtl) ? current - step
+ : current + step;
+ } else if (key == "PageUp") {
+ new_value = current + big_step;
+ } else if (key == "PageDown") {
+ new_value = current - big_step;
+ } else if (key == "Home") {
+ new_value = is_vertical ? step_range.Maximum() : step_range.Minimum();
+ } else if (key == "End") {
+ new_value = is_vertical ? step_range.Minimum() : step_range.Maximum();
+ } else {
+ return; // Did not match any key binding.
+ }
+
+ new_value = step_range.ClampValue(new_value);
+
+ if (new_value != current) {
+ EventQueueScope scope;
+ TextFieldEventBehavior event_behavior = kDispatchInputAndChangeEvent;
+ SetValueAsDecimal(new_value, event_behavior, IGNORE_EXCEPTION_FOR_TESTING);
+
+ if (AXObjectCache* cache =
+ GetElement().GetDocument().ExistingAXObjectCache())
+ cache->HandleValueChanged(&GetElement());
+ }
+
+ event->SetDefaultHandled();
+}
+
+void RangeInputType::CreateShadowSubtree() {
+ DCHECK(IsShadowHost(GetElement()));
+
+ Document& document = GetElement().GetDocument();
+ HTMLDivElement* track = HTMLDivElement::Create(document);
+ track->SetShadowPseudoId(AtomicString("-webkit-slider-runnable-track"));
+ track->setAttribute(idAttr, ShadowElementNames::SliderTrack());
+ track->AppendChild(SliderThumbElement::Create(document));
+ HTMLElement* container = SliderContainerElement::Create(document);
+ container->AppendChild(track);
+ GetElement().UserAgentShadowRoot()->AppendChild(container);
+ container->setAttribute(styleAttr, "-webkit-appearance:inherit");
+}
+
+LayoutObject* RangeInputType::CreateLayoutObject(const ComputedStyle&) const {
+ return new LayoutSlider(&GetElement());
+}
+
+Decimal RangeInputType::ParseToNumber(const String& src,
+ const Decimal& default_value) const {
+ return ParseToDecimalForNumberType(src, default_value);
+}
+
+String RangeInputType::Serialize(const Decimal& value) const {
+ if (!value.IsFinite())
+ return String();
+ return SerializeForNumberType(value);
+}
+
+// FIXME: Could share this with KeyboardClickableInputTypeView and
+// BaseCheckableInputType if we had a common base class.
+void RangeInputType::AccessKeyAction(bool send_mouse_events) {
+ InputTypeView::AccessKeyAction(send_mouse_events);
+
+ GetElement().DispatchSimulatedClick(
+ nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents);
+}
+
+void RangeInputType::SanitizeValueInResponseToMinOrMaxAttributeChange() {
+ if (GetElement().HasDirtyValue())
+ GetElement().setValue(GetElement().value());
+ else
+ GetElement().SetNonDirtyValue(GetElement().value());
+ GetElement().UpdateView();
+}
+
+void RangeInputType::StepAttributeChanged() {
+ if (GetElement().HasDirtyValue())
+ GetElement().setValue(GetElement().value());
+ else
+ GetElement().SetNonDirtyValue(GetElement().value());
+ GetElement().UpdateView();
+}
+
+void RangeInputType::DidSetValue(const String&, bool value_changed) {
+ if (value_changed)
+ GetElement().UpdateView();
+}
+
+void RangeInputType::UpdateView() {
+ GetSliderThumbElement()->SetPositionFromValue();
+}
+
+String RangeInputType::SanitizeValue(const String& proposed_value) const {
+ StepRange step_range(CreateStepRange(kRejectAny));
+ const Decimal proposed_numeric_value =
+ ParseToNumber(proposed_value, step_range.DefaultValue());
+ return SerializeForNumberType(step_range.ClampValue(proposed_numeric_value));
+}
+
+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);
+}
+
+void RangeInputType::DisabledAttributeChanged() {
+ if (GetElement().IsDisabledFormControl())
+ GetSliderThumbElement()->StopDragging();
+}
+
+bool RangeInputType::ShouldRespectListAttribute() {
+ return true;
+}
+
+inline SliderThumbElement* RangeInputType::GetSliderThumbElement() const {
+ return ToSliderThumbElementOrDie(
+ GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::SliderThumb()));
+}
+
+inline Element* RangeInputType::SliderTrackElement() const {
+ return GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::SliderTrack());
+}
+
+void RangeInputType::ListAttributeTargetChanged() {
+ tick_mark_values_dirty_ = true;
+ if (GetElement().GetLayoutObject())
+ GetElement()
+ .GetLayoutObject()
+ ->SetShouldDoFullPaintInvalidationIncludingNonCompositingDescendants();
+ Element* slider_track_element = SliderTrackElement();
+ if (slider_track_element->GetLayoutObject())
+ slider_track_element->GetLayoutObject()->SetNeedsLayout(
+ LayoutInvalidationReason::kAttributeChanged);
+}
+
+static bool DecimalCompare(const Decimal& a, const Decimal& b) {
+ return a < b;
+}
+
+void RangeInputType::UpdateTickMarkValues() {
+ if (!tick_mark_values_dirty_)
+ return;
+ tick_mark_values_.clear();
+ tick_mark_values_dirty_ = false;
+ HTMLDataListElement* data_list = GetElement().DataList();
+ if (!data_list)
+ return;
+ HTMLDataListOptionsCollection* options = data_list->options();
+ tick_mark_values_.ReserveCapacity(options->length());
+ for (unsigned i = 0; i < options->length(); ++i) {
+ HTMLOptionElement* option_element = options->Item(i);
+ String option_value = option_element->value();
+ if (option_element->IsDisabledFormControl() || option_value.IsEmpty())
+ continue;
+ if (!GetElement().IsValidValue(option_value))
+ continue;
+ tick_mark_values_.push_back(ParseToNumber(option_value, Decimal::Nan()));
+ }
+ tick_mark_values_.ShrinkToFit();
+ std::sort(tick_mark_values_.begin(), tick_mark_values_.end(), DecimalCompare);
+}
+
+Decimal RangeInputType::FindClosestTickMarkValue(const Decimal& value) {
+ UpdateTickMarkValues();
+ if (!tick_mark_values_.size())
+ return Decimal::Nan();
+
+ size_t left = 0;
+ size_t right = tick_mark_values_.size();
+ size_t middle;
+ while (true) {
+ DCHECK_LE(left, right);
+ middle = left + (right - left) / 2;
+ if (!middle)
+ break;
+ if (middle == tick_mark_values_.size() - 1 &&
+ tick_mark_values_[middle] < value) {
+ middle++;
+ break;
+ }
+ if (tick_mark_values_[middle - 1] <= value &&
+ tick_mark_values_[middle] >= value)
+ break;
+
+ if (tick_mark_values_[middle] < value)
+ left = middle;
+ else
+ right = middle;
+ }
+ const Decimal closest_left = middle ? tick_mark_values_[middle - 1]
+ : Decimal::Infinity(Decimal::kNegative);
+ const Decimal closest_right = middle != tick_mark_values_.size()
+ ? tick_mark_values_[middle]
+ : Decimal::Infinity(Decimal::kPositive);
+ if (closest_right - value < value - closest_left)
+ return closest_right;
+ return closest_left;
+}
+
+} // 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
new file mode 100644
index 00000000000..23c39a870fb
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RANGE_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RANGE_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/input_type_view.h"
+
+namespace blink {
+
+class ExceptionState;
+class SliderThumbElement;
+
+class RangeInputType final : public InputType, public InputTypeView {
+ USING_GARBAGE_COLLECTED_MIXIN(RangeInputType);
+
+ public:
+ static InputType* Create(HTMLInputElement&);
+ void Trace(blink::Visitor*) override;
+ using InputType::GetElement;
+
+ private:
+ RangeInputType(HTMLInputElement&);
+ InputTypeView* CreateView() override;
+ ValueMode GetValueMode() const override;
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ double ValueAsDouble() const override;
+ void SetValueAsDouble(double,
+ TextFieldEventBehavior,
+ ExceptionState&) const override;
+ bool TypeMismatchFor(const String&) const override;
+ bool SupportsRequired() const override;
+ StepRange CreateStepRange(AnyStepHandling) const override;
+ bool IsSteppable() const override;
+ void HandleMouseDownEvent(MouseEvent*) override;
+ void HandleKeydownEvent(KeyboardEvent*) override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) const override;
+ void CreateShadowSubtree() override;
+ Decimal ParseToNumber(const String&, const Decimal&) const override;
+ String Serialize(const Decimal&) const override;
+ void AccessKeyAction(bool send_mouse_events) override;
+ void SanitizeValueInResponseToMinOrMaxAttributeChange() override;
+ void StepAttributeChanged() override;
+ void WarnIfValueIsInvalid(const String&) const override;
+ void DidSetValue(const String&, bool value_changed) override;
+ String SanitizeValue(const String& proposed_value) const override;
+ bool ShouldRespectListAttribute() override;
+ void DisabledAttributeChanged() override;
+ void ListAttributeTargetChanged() override;
+ Decimal FindClosestTickMarkValue(const Decimal&) override;
+
+ SliderThumbElement* GetSliderThumbElement() const;
+ Element* SliderTrackElement() const;
+ void UpdateTickMarkValues();
+
+ // InputTypeView function:
+ void UpdateView() override;
+
+ bool tick_mark_values_dirty_;
+ Vector<Decimal> tick_mark_values_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RANGE_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.cc
new file mode 100644
index 00000000000..b1093eb2d12
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/reset_input_type.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/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+InputType* ResetInputType::Create(HTMLInputElement& element) {
+ return new ResetInputType(element);
+}
+
+const AtomicString& ResetInputType::FormControlType() const {
+ return InputTypeNames::reset;
+}
+
+bool ResetInputType::SupportsValidation() const {
+ return false;
+}
+
+void ResetInputType::HandleDOMActivateEvent(Event* event) {
+ if (GetElement().IsDisabledFormControl() || !GetElement().Form())
+ return;
+ GetElement().Form()->reset();
+ event->SetDefaultHandled();
+}
+
+String ResetInputType::DefaultLabel() const {
+ return GetLocale().QueryString(WebLocalizedString::kResetButtonDefaultLabel);
+}
+
+bool ResetInputType::IsTextButton() const {
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.h
new file mode 100644
index 00000000000..e2c58bc3f47
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RESET_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RESET_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h"
+
+namespace blink {
+
+class ResetInputType final : public BaseButtonInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ ResetInputType(HTMLInputElement& element) : BaseButtonInputType(element) {}
+ const AtomicString& FormControlType() const override;
+ bool SupportsValidation() const override;
+ void HandleDOMActivateEvent(Event*) override;
+ String DefaultLabel() const override;
+ bool IsTextButton() const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RESET_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/.clang-format b/chromium/third_party/blink/renderer/core/html/forms/resources/.clang-format
new file mode 100644
index 00000000000..ea647be8464
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/.clang-format
@@ -0,0 +1,8 @@
+---
+BasedOnStyle: Chromium
+Language: JavaScript
+ColumnLimit: 120
+CommentPragmas: .*\@.*
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+ReflowComments: false
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.css
new file mode 100644
index 00000000000..e5cbb72003b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.css
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+body {
+ -webkit-user-select: none;
+ background-color: white;
+ font: -webkit-control;
+ font-size: 12px;
+}
+
+.rtl {
+ direction: rtl;
+}
+
+.scroll-view {
+ overflow: hidden;
+ width: 0;
+ height: 0;
+}
+
+.list-cell {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 0;
+ height: 0;
+}
+
+.list-cell.hidden {
+ display: none;
+}
+
+.week-number-cell,
+.day-cell {
+ position: static;
+ text-align: center;
+ box-sizing: border-box;
+ display: inline-block;
+ cursor: default;
+ transition: color 1s;
+ padding: 1px;
+}
+
+.week-number-cell {
+ box-sizing: border-box;
+ color: black;
+ padding-right: 0;
+ box-shadow: 1px 0 0 #bfbfbf;
+ margin-right: 1px;
+}
+
+.day-cell {
+ color: #bfbfbf;
+}
+
+.day-cell.highlighted.today,
+.day-cell.today {
+ border: 1px solid #bfbfbf;
+ padding: 0;
+}
+
+.week-number-cell.highlighted,
+.day-cell.highlighted {
+ background-color: #e5ecf8;
+}
+
+.week-number-cell.highlighted.disabled,
+.day-cell.highlighted.disabled {
+ border: 1px solid #e5ecf8;
+ padding: 0;
+}
+
+.week-number-cell.selected,
+.day-cell.selected {
+ background-color: #bccdec;
+}
+
+.week-number-cell.disabled,
+.day-cell.disabled {
+ background-color: #f5f5f5;
+}
+
+.day-cell.current-month {
+ color: #000000;
+}
+
+.calendar-table-view {
+ border: 1px solid #bfbfbf;
+ outline: none;
+}
+
+.week-number-label,
+.week-day-label {
+ text-align: center;
+ display: inline-block;
+ line-height: 23px;
+ padding-top: 1px;
+ box-sizing: padding-box;
+}
+
+.week-number-label {
+ box-sizing: border-box;
+ border-right: 1px solid #bfbfbf;
+}
+
+.calendar-table-header-view {
+ background-color: #f5f5f5;
+ border-bottom: 1px solid #bfbfbf;
+ height: 24px;
+}
+
+.calendar-picker {
+ border: 1px solid #bfbfbf;
+ border-radius: 2px;
+ position: absolute;
+ padding: 10px;
+ background-color: white;
+ overflow: hidden;
+ cursor: default;
+}
+
+.calendar-header-view {
+ margin-bottom: 10px;
+ display: flex;
+ flex-flow: row;
+}
+
+.calendar-title {
+ -webkit-align-self: center;
+ flex: 1;
+ text-align: left;
+}
+
+.rtl .calendar-title {
+ text-align: right;
+}
+
+.month-popup-button,
+.month-popup-button:hover,
+.month-popup-button:disabled {
+ background-color: transparent !important;
+ background-image: none !important;
+ box-shadow: none !important;
+ color: black;
+}
+
+.month-popup-button:disabled {
+ opacity: 0.7;
+}
+
+.month-popup-button {
+ font-size: 12px;
+ padding: 4px;
+ display: inline-block;
+ cursor: default;
+ border: 1px solid transparent !important;
+ height: 24px !important;
+}
+
+.month-popup-button .disclosure-triangle {
+ margin: 0 6px;
+}
+
+.month-popup-button .disclosure-triangle svg {
+ padding-bottom: 2px;
+}
+
+.today-button::after {
+ content: "";
+ display: block;
+ border-radius: 3px;
+ width: 6px;
+ height: 6px;
+ background-color: #6e6e6e;
+ margin: 0 auto;
+}
+
+.calendar-navigation-button {
+ -webkit-align-self: center;
+ width: 24px;
+ height: 24px;
+ min-width: 0 !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ -webkit-margin-start: 4px !important;
+}
+
+.year-list-view {
+ border: 1px solid #bfbfbf;
+ background-color: white;
+ position: absolute;
+}
+
+.year-list-cell {
+ box-sizing: border-box;
+ border-bottom: 1px solid #bfbfbf;
+ background-color: white;
+ overflow: hidden;
+}
+
+.year-list-cell .label {
+ height: 24px;
+ line-height: 24px;
+ -webkit-padding-start: 8px;
+ background-color: #f5f5f5;
+ border-bottom: 1px solid #bfbfbf;
+}
+
+.year-list-cell .month-chooser {
+ padding: 0;
+}
+
+.month-buttons-row {
+ display: flex;
+}
+
+.month-button {
+ flex: 1;
+ height: 32px;
+ line-height: 32px;
+ padding: 0 !important;
+ margin: 0 !important;
+ background-image: none !important;
+ background-color: #ffffff;
+ border-width: 0 !important;
+ box-shadow: none !important;
+ text-align: center;
+}
+
+.month-button.highlighted {
+ background-color: #e5ecf8;
+}
+
+.month-button[aria-disabled="true"] {
+ color: GrayText;
+}
+
+.scrubby-scroll-bar {
+ width: 14px;
+ height: 60px;
+ background-color: white;
+ border-left: 1px solid #bfbfbf;
+ position: absolute;
+ top: 0;
+}
+
+.scrubby-scroll-thumb {
+ width: 10px;
+ margin: 2px;
+ height: 30px;
+ background-color: #d8d8d8;
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+.month-popup-view {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
+.year-list-view .scrubby-scroll-bar {
+ right: 0;
+}
+
+.rtl .year-list-view .scrubby-scroll-bar {
+ left: 0;
+ right: auto;
+ border-left-width: 0;
+ border-right: 1px solid #bfbfbf;
+}
+
+.year-month-button {
+ width: 24px;
+ height: 24px;
+ min-width: 0;
+ padding: 0;
+}
+
+.month-popup-button:focus,
+.year-list-view:focus,
+.calendar-table-view:focus {
+ transition: border-color 200ms;
+ /* We use border color because it follows the border radius (unlike outline).
+ * This is particularly noticeable on mac. */
+ border-color: rgb(77, 144, 254) !important;
+ outline: none;
+}
+
+.preparing button:focus,
+.preparing .year-list-view:focus,
+.preparing .calendar-table-view:focus {
+ transition: none;
+}
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
new file mode 100644
index 00000000000..250b55c6ad7
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js
@@ -0,0 +1,4085 @@
+'use strict';
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/**
+ * @enum {number}
+ */
+var WeekDay = {Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6};
+
+/**
+ * @type {Object}
+ */
+var global = {
+ picker: null,
+ params: {
+ locale: 'en-US',
+ weekStartDay: WeekDay.Sunday,
+ dayLabels: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
+ shortMonthLabels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'],
+ isLocaleRTL: false,
+ mode: 'date',
+ weekLabel: 'Week',
+ anchorRectInScreen: new Rectangle(0, 0, 0, 0),
+ currentValue: null
+ }
+};
+
+// ----------------------------------------------------------------
+// Utility functions
+
+/**
+ * @return {!boolean}
+ */
+function hasInaccuratePointingDevice() {
+ return matchMedia('(any-pointer: coarse)').matches;
+}
+
+/**
+ * @return {!string} lowercase locale name. e.g. "en-us"
+ */
+function getLocale() {
+ return (global.params.locale || 'en-us').toLowerCase();
+}
+
+/**
+ * @return {!string} lowercase language code. e.g. "en"
+ */
+function getLanguage() {
+ var locale = getLocale();
+ var result = locale.match(/^([a-z]+)/);
+ if (!result)
+ return 'en';
+ return result[1];
+}
+
+/**
+ * @param {!number} number
+ * @return {!string}
+ */
+function localizeNumber(number) {
+ return window.pagePopupController.localizeNumberString(number);
+}
+
+/**
+ * @const
+ * @type {number}
+ */
+var ImperialEraLimit = 2087;
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ * @return {!string}
+ */
+function formatJapaneseImperialEra(year, month) {
+ // We don't show an imperial era if it is greater than 99 becase of space
+ // limitation.
+ if (year > ImperialEraLimit)
+ return '';
+ if (year > 1989)
+ return '(\u5e73\u6210' + localizeNumber(year - 1988) + '\u5e74)';
+ if (year == 1989)
+ return '(\u5e73\u6210\u5143\u5e74)';
+ if (year >= 1927)
+ return '(\u662d\u548c' + localizeNumber(year - 1925) + '\u5e74)';
+ if (year > 1912)
+ return '(\u5927\u6b63' + localizeNumber(year - 1911) + '\u5e74)';
+ if (year == 1912 && month >= 7)
+ return '(\u5927\u6b63\u5143\u5e74)';
+ if (year > 1868)
+ return '(\u660e\u6cbb' + localizeNumber(year - 1867) + '\u5e74)';
+ if (year == 1868)
+ return '(\u660e\u6cbb\u5143\u5e74)';
+ return '';
+}
+
+function createUTCDate(year, month, date) {
+ var newDate = new Date(0);
+ newDate.setUTCFullYear(year);
+ newDate.setUTCMonth(month);
+ newDate.setUTCDate(date);
+ return newDate;
+}
+
+/**
+ * @param {string} dateString
+ * @return {?Day|Week|Month}
+ */
+function parseDateString(dateString) {
+ var month = Month.parse(dateString);
+ if (month)
+ return month;
+ var week = Week.parse(dateString);
+ if (week)
+ return week;
+ return Day.parse(dateString);
+}
+
+/**
+ * @const
+ * @type {number}
+ */
+var DaysPerWeek = 7;
+
+/**
+ * @const
+ * @type {number}
+ */
+var MonthsPerYear = 12;
+
+/**
+ * @const
+ * @type {number}
+ */
+var MillisecondsPerDay = 24 * 60 * 60 * 1000;
+
+/**
+ * @const
+ * @type {number}
+ */
+var MillisecondsPerWeek = DaysPerWeek * MillisecondsPerDay;
+
+/**
+ * @constructor
+ */
+function DateType() {
+}
+
+/**
+ * @constructor
+ * @extends DateType
+ * @param {!number} year
+ * @param {!number} month
+ * @param {!number} date
+ */
+function Day(year, month, date) {
+ var dateObject = createUTCDate(year, month, date);
+ if (isNaN(dateObject.valueOf()))
+ throw 'Invalid date';
+ /**
+ * @type {number}
+ * @const
+ */
+ this.year = dateObject.getUTCFullYear();
+ /**
+ * @type {number}
+ * @const
+ */
+ this.month = dateObject.getUTCMonth();
+ /**
+ * @type {number}
+ * @const
+ */
+ this.date = dateObject.getUTCDate();
+};
+
+Day.prototype = Object.create(DateType.prototype);
+
+Day.ISOStringRegExp = /^(\d+)-(\d+)-(\d+)/;
+
+/**
+ * @param {!string} str
+ * @return {?Day}
+ */
+Day.parse = function(str) {
+ var match = Day.ISOStringRegExp.exec(str);
+ if (!match)
+ return null;
+ var year = parseInt(match[1], 10);
+ var month = parseInt(match[2], 10) - 1;
+ var date = parseInt(match[3], 10);
+ return new Day(year, month, date);
+};
+
+/**
+ * @param {!number} value
+ * @return {!Day}
+ */
+Day.createFromValue = function(millisecondsSinceEpoch) {
+ return Day.createFromDate(new Date(millisecondsSinceEpoch))
+};
+
+/**
+ * @param {!Date} date
+ * @return {!Day}
+ */
+Day.createFromDate = function(date) {
+ if (isNaN(date.valueOf()))
+ throw 'Invalid date';
+ return new Day(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
+};
+
+/**
+ * @param {!Day} day
+ * @return {!Day}
+ */
+Day.createFromDay = function(day) {
+ return day;
+};
+
+/**
+ * @return {!Day}
+ */
+Day.createFromToday = function() {
+ var now = new Date();
+ return new Day(now.getFullYear(), now.getMonth(), now.getDate());
+};
+
+/**
+ * @param {!DateType} other
+ * @return {!boolean}
+ */
+Day.prototype.equals = function(other) {
+ return other instanceof Day && this.year === other.year && this.month === other.month && this.date === other.date;
+};
+
+/**
+ * @param {!number=} offset
+ * @return {!Day}
+ */
+Day.prototype.previous = function(offset) {
+ if (typeof offset === 'undefined')
+ offset = 1;
+ return new Day(this.year, this.month, this.date - offset);
+};
+
+/**
+ * @param {!number=} offset
+ * @return {!Day}
+ */
+Day.prototype.next = function(offset) {
+ if (typeof offset === 'undefined')
+ offset = 1;
+ return new Day(this.year, this.month, this.date + offset);
+};
+
+/**
+ * @return {!Date}
+ */
+Day.prototype.startDate = function() {
+ return createUTCDate(this.year, this.month, this.date);
+};
+
+/**
+ * @return {!Date}
+ */
+Day.prototype.endDate = function() {
+ return createUTCDate(this.year, this.month, this.date + 1);
+};
+
+/**
+ * @return {!Day}
+ */
+Day.prototype.firstDay = function() {
+ return this;
+};
+
+/**
+ * @return {!Day}
+ */
+Day.prototype.middleDay = function() {
+ return this;
+};
+
+/**
+ * @return {!Day}
+ */
+Day.prototype.lastDay = function() {
+ return this;
+};
+
+/**
+ * @return {!number}
+ */
+Day.prototype.valueOf = function() {
+ return createUTCDate(this.year, this.month, this.date).getTime();
+};
+
+/**
+ * @return {!WeekDay}
+ */
+Day.prototype.weekDay = function() {
+ return createUTCDate(this.year, this.month, this.date).getUTCDay();
+};
+
+/**
+ * @return {!string}
+ */
+Day.prototype.toString = function() {
+ var yearString = String(this.year);
+ if (yearString.length < 4)
+ yearString = ('000' + yearString).substr(-4, 4);
+ return yearString + '-' + ('0' + (this.month + 1)).substr(-2, 2) + '-' + ('0' + this.date).substr(-2, 2);
+};
+
+/**
+ * @return {!string}
+ */
+Day.prototype.format = function() {
+ if (!Day.formatter) {
+ Day.formatter = new Intl.DateTimeFormat(
+ getLocale(), {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC'});
+ }
+ return Day.formatter.format(this.startDate());
+};
+
+// See WebCore/platform/DateComponents.h.
+Day.Minimum = Day.createFromValue(-62135596800000.0);
+Day.Maximum = Day.createFromValue(8640000000000000.0);
+
+// See WebCore/html/DayInputType.cpp.
+Day.DefaultStep = 86400000;
+Day.DefaultStepBase = 0;
+
+/**
+ * @constructor
+ * @extends DateType
+ * @param {!number} year
+ * @param {!number} week
+ */
+function Week(year, week) {
+ /**
+ * @type {number}
+ * @const
+ */
+ this.year = year;
+ /**
+ * @type {number}
+ * @const
+ */
+ this.week = week;
+ // Number of years per year is either 52 or 53.
+ if (this.week < 1 || (this.week > 52 && this.week > Week.numberOfWeeksInYear(this.year))) {
+ var normalizedWeek = Week.createFromDay(this.firstDay());
+ this.year = normalizedWeek.year;
+ this.week = normalizedWeek.week;
+ }
+}
+
+Week.ISOStringRegExp = /^(\d+)-[wW](\d+)$/;
+
+// See WebCore/platform/DateComponents.h.
+Week.Minimum = new Week(1, 1);
+Week.Maximum = new Week(275760, 37);
+
+// See WebCore/html/WeekInputType.cpp.
+Week.DefaultStep = 604800000;
+Week.DefaultStepBase = -259200000;
+
+Week.EpochWeekDay = createUTCDate(1970, 0, 0).getUTCDay();
+
+/**
+ * @param {!string} str
+ * @return {?Week}
+ */
+Week.parse = function(str) {
+ var match = Week.ISOStringRegExp.exec(str);
+ if (!match)
+ return null;
+ var year = parseInt(match[1], 10);
+ var week = parseInt(match[2], 10);
+ return new Week(year, week);
+};
+
+/**
+ * @param {!number} millisecondsSinceEpoch
+ * @return {!Week}
+ */
+Week.createFromValue = function(millisecondsSinceEpoch) {
+ return Week.createFromDate(new Date(millisecondsSinceEpoch))
+};
+
+/**
+ * @param {!Date} date
+ * @return {!Week}
+ */
+Week.createFromDate = function(date) {
+ if (isNaN(date.valueOf()))
+ throw 'Invalid date';
+ var year = date.getUTCFullYear();
+ if (year <= Week.Maximum.year && Week.weekOneStartDateForYear(year + 1).getTime() <= date.getTime())
+ year++;
+ else if (year > 1 && Week.weekOneStartDateForYear(year).getTime() > date.getTime())
+ year--;
+ var week = 1 + Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), date);
+ return new Week(year, week);
+};
+
+/**
+ * @param {!Day} day
+ * @return {!Week}
+ */
+Week.createFromDay = function(day) {
+ var year = day.year;
+ if (year <= Week.Maximum.year && Week.weekOneStartDayForYear(year + 1) <= day)
+ year++;
+ else if (year > 1 && Week.weekOneStartDayForYear(year) > day)
+ year--;
+ var week = Math.floor(1 + (day.valueOf() - Week.weekOneStartDayForYear(year).valueOf()) / MillisecondsPerWeek);
+ return new Week(year, week);
+};
+
+/**
+ * @return {!Week}
+ */
+Week.createFromToday = function() {
+ var now = new Date();
+ return Week.createFromDate(createUTCDate(now.getFullYear(), now.getMonth(), now.getDate()));
+};
+
+/**
+ * @param {!number} year
+ * @return {!Date}
+ */
+Week.weekOneStartDateForYear = function(year) {
+ if (year < 1)
+ return createUTCDate(1, 0, 1);
+ // The week containing January 4th is week one.
+ var yearStartDay = createUTCDate(year, 0, 4).getUTCDay();
+ return createUTCDate(year, 0, 4 - (yearStartDay + 6) % DaysPerWeek);
+};
+
+/**
+ * @param {!number} year
+ * @return {!Day}
+ */
+Week.weekOneStartDayForYear = function(year) {
+ if (year < 1)
+ return Day.Minimum;
+ // The week containing January 4th is week one.
+ var yearStartDay = createUTCDate(year, 0, 4).getUTCDay();
+ return new Day(year, 0, 4 - (yearStartDay + 6) % DaysPerWeek);
+};
+
+/**
+ * @param {!number} year
+ * @return {!number}
+ */
+Week.numberOfWeeksInYear = function(year) {
+ if (year < 1 || year > Week.Maximum.year)
+ return 0;
+ else if (year === Week.Maximum.year)
+ return Week.Maximum.week;
+ return Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), Week.weekOneStartDateForYear(year + 1));
+};
+
+/**
+ * @param {!Date} baseDate
+ * @param {!Date} date
+ * @return {!number}
+ */
+Week._numberOfWeeksSinceDate = function(baseDate, date) {
+ return Math.floor((date.getTime() - baseDate.getTime()) / MillisecondsPerWeek);
+};
+
+/**
+ * @param {!DateType} other
+ * @return {!boolean}
+ */
+Week.prototype.equals = function(other) {
+ return other instanceof Week && this.year === other.year && this.week === other.week;
+};
+
+/**
+ * @param {!number=} offset
+ * @return {!Week}
+ */
+Week.prototype.previous = function(offset) {
+ if (typeof offset === 'undefined')
+ offset = 1;
+ return new Week(this.year, this.week - offset);
+};
+
+/**
+ * @param {!number=} offset
+ * @return {!Week}
+ */
+Week.prototype.next = function(offset) {
+ if (typeof offset === 'undefined')
+ offset = 1;
+ return new Week(this.year, this.week + offset);
+};
+
+/**
+ * @return {!Date}
+ */
+Week.prototype.startDate = function() {
+ var weekStartDate = Week.weekOneStartDateForYear(this.year);
+ weekStartDate.setUTCDate(weekStartDate.getUTCDate() + (this.week - 1) * 7);
+ return weekStartDate;
+};
+
+/**
+ * @return {!Date}
+ */
+Week.prototype.endDate = function() {
+ if (this.equals(Week.Maximum))
+ return Day.Maximum.startDate();
+ return this.next().startDate();
+};
+
+/**
+ * @return {!Day}
+ */
+Week.prototype.firstDay = function() {
+ var weekOneStartDay = Week.weekOneStartDayForYear(this.year);
+ return weekOneStartDay.next((this.week - 1) * DaysPerWeek);
+};
+
+/**
+ * @return {!Day}
+ */
+Week.prototype.middleDay = function() {
+ return this.firstDay().next(3);
+};
+
+/**
+ * @return {!Day}
+ */
+Week.prototype.lastDay = function() {
+ if (this.equals(Week.Maximum))
+ return Day.Maximum;
+ return this.next().firstDay().previous();
+};
+
+/**
+ * @return {!number}
+ */
+Week.prototype.valueOf = function() {
+ return this.firstDay().valueOf() - createUTCDate(1970, 0, 1).getTime();
+};
+
+/**
+ * @return {!string}
+ */
+Week.prototype.toString = function() {
+ var yearString = String(this.year);
+ if (yearString.length < 4)
+ yearString = ('000' + yearString).substr(-4, 4);
+ return yearString + '-W' + ('0' + this.week).substr(-2, 2);
+};
+
+/**
+ * @constructor
+ * @extends DateType
+ * @param {!number} year
+ * @param {!number} month
+ */
+function Month(year, month) {
+ /**
+ * @type {number}
+ * @const
+ */
+ this.year = year + Math.floor(month / MonthsPerYear);
+ /**
+ * @type {number}
+ * @const
+ */
+ this.month = month % MonthsPerYear < 0 ? month % MonthsPerYear + MonthsPerYear : month % MonthsPerYear;
+};
+
+Month.ISOStringRegExp = /^(\d+)-(\d+)$/;
+
+// See WebCore/platform/DateComponents.h.
+Month.Minimum = new Month(1, 0);
+Month.Maximum = new Month(275760, 8);
+
+// See WebCore/html/MonthInputType.cpp.
+Month.DefaultStep = 1;
+Month.DefaultStepBase = 0;
+
+/**
+ * @param {!string} str
+ * @return {?Month}
+ */
+Month.parse = function(str) {
+ var match = Month.ISOStringRegExp.exec(str);
+ if (!match)
+ return null;
+ var year = parseInt(match[1], 10);
+ var month = parseInt(match[2], 10) - 1;
+ return new Month(year, month);
+};
+
+/**
+ * @param {!number} value
+ * @return {!Month}
+ */
+Month.createFromValue = function(monthsSinceEpoch) {
+ return new Month(1970, monthsSinceEpoch)
+};
+
+/**
+ * @param {!Date} date
+ * @return {!Month}
+ */
+Month.createFromDate = function(date) {
+ if (isNaN(date.valueOf()))
+ throw 'Invalid date';
+ return new Month(date.getUTCFullYear(), date.getUTCMonth());
+};
+
+/**
+ * @param {!Day} day
+ * @return {!Month}
+ */
+Month.createFromDay = function(day) {
+ return new Month(day.year, day.month);
+};
+
+/**
+ * @return {!Month}
+ */
+Month.createFromToday = function() {
+ var now = new Date();
+ return new Month(now.getFullYear(), now.getMonth());
+};
+
+/**
+ * @return {!boolean}
+ */
+Month.prototype.containsDay = function(day) {
+ return this.year === day.year && this.month === day.month;
+};
+
+/**
+ * @param {!Month} other
+ * @return {!boolean}
+ */
+Month.prototype.equals = function(other) {
+ return other instanceof Month && this.year === other.year && this.month === other.month;
+};
+
+/**
+ * @param {!number=} offset
+ * @return {!Month}
+ */
+Month.prototype.previous = function(offset) {
+ if (typeof offset === 'undefined')
+ offset = 1;
+ return new Month(this.year, this.month - offset);
+};
+
+/**
+ * @param {!number=} offset
+ * @return {!Month}
+ */
+Month.prototype.next = function(offset) {
+ if (typeof offset === 'undefined')
+ offset = 1;
+ return new Month(this.year, this.month + offset);
+};
+
+/**
+ * @return {!Date}
+ */
+Month.prototype.startDate = function() {
+ return createUTCDate(this.year, this.month, 1);
+};
+
+/**
+ * @return {!Date}
+ */
+Month.prototype.endDate = function() {
+ if (this.equals(Month.Maximum))
+ return Day.Maximum.startDate();
+ return this.next().startDate();
+};
+
+/**
+ * @return {!Day}
+ */
+Month.prototype.firstDay = function() {
+ return new Day(this.year, this.month, 1);
+};
+
+/**
+ * @return {!Day}
+ */
+Month.prototype.middleDay = function() {
+ return new Day(this.year, this.month, this.month === 2 ? 14 : 15);
+};
+
+/**
+ * @return {!Day}
+ */
+Month.prototype.lastDay = function() {
+ if (this.equals(Month.Maximum))
+ return Day.Maximum;
+ return this.next().firstDay().previous();
+};
+
+/**
+ * @return {!number}
+ */
+Month.prototype.valueOf = function() {
+ return (this.year - 1970) * MonthsPerYear + this.month;
+};
+
+/**
+ * @return {!string}
+ */
+Month.prototype.toString = function() {
+ var yearString = String(this.year);
+ if (yearString.length < 4)
+ yearString = ('000' + yearString).substr(-4, 4);
+ return yearString + '-' + ('0' + (this.month + 1)).substr(-2, 2);
+};
+
+/**
+ * @return {!string}
+ */
+Month.prototype.toLocaleString = function() {
+ if (global.params.locale === 'ja')
+ return '' + this.year + '\u5e74' + formatJapaneseImperialEra(this.year, this.month) + ' ' + (this.month + 1) +
+ '\u6708';
+ return window.pagePopupController.formatMonth(this.year, this.month);
+};
+
+/**
+ * @return {!string}
+ */
+Month.prototype.toShortLocaleString = function() {
+ return window.pagePopupController.formatShortMonth(this.year, this.month);
+};
+
+// ----------------------------------------------------------------
+// Initialization
+
+/**
+ * @param {Event} event
+ */
+function handleMessage(event) {
+ if (global.argumentsReceived)
+ return;
+ global.argumentsReceived = true;
+ initialize(JSON.parse(event.data));
+}
+
+/**
+ * @param {!Object} params
+ */
+function setGlobalParams(params) {
+ var name;
+ for (name in global.params) {
+ if (typeof params[name] === 'undefined')
+ console.warn('Missing argument: ' + name);
+ }
+ for (name in params) {
+ global.params[name] = params[name];
+ }
+};
+
+/**
+ * @param {!Object} args
+ */
+function initialize(args) {
+ setGlobalParams(args);
+ if (global.params.suggestionValues && global.params.suggestionValues.length)
+ openSuggestionPicker();
+ else
+ openCalendarPicker();
+}
+
+function closePicker() {
+ if (global.picker)
+ global.picker.cleanup();
+ var main = $('main');
+ main.innerHTML = '';
+ main.className = '';
+};
+
+function openSuggestionPicker() {
+ closePicker();
+ global.picker = new SuggestionPicker($('main'), global.params);
+};
+
+function openCalendarPicker() {
+ closePicker();
+ global.picker = new CalendarPicker(global.params.mode, global.params);
+ global.picker.attachTo($('main'));
+};
+
+// Parameter t should be a number between 0 and 1.
+var AnimationTimingFunction = {
+ Linear: function(t) {
+ return t;
+ },
+ EaseInOut: function(t) {
+ t *= 2;
+ if (t < 1)
+ return Math.pow(t, 3) / 2;
+ t -= 2;
+ return Math.pow(t, 3) / 2 + 1;
+ }
+};
+
+/**
+ * @constructor
+ * @extends EventEmitter
+ */
+function AnimationManager() {
+ EventEmitter.call(this);
+
+ this._isRunning = false;
+ this._runningAnimatorCount = 0;
+ this._runningAnimators = {};
+ this._animationFrameCallbackBound = this._animationFrameCallback.bind(this);
+}
+
+AnimationManager.prototype = Object.create(EventEmitter.prototype);
+
+AnimationManager.EventTypeAnimationFrameWillFinish = 'animationFrameWillFinish';
+
+AnimationManager.prototype._startAnimation = function() {
+ if (this._isRunning)
+ return;
+ this._isRunning = true;
+ window.requestAnimationFrame(this._animationFrameCallbackBound);
+};
+
+AnimationManager.prototype._stopAnimation = function() {
+ if (!this._isRunning)
+ return;
+ this._isRunning = false;
+};
+
+/**
+ * @param {!Animator} animator
+ */
+AnimationManager.prototype.add = function(animator) {
+ if (this._runningAnimators[animator.id])
+ return;
+ this._runningAnimators[animator.id] = animator;
+ this._runningAnimatorCount++;
+ if (this._needsTimer())
+ this._startAnimation();
+};
+
+/**
+ * @param {!Animator} animator
+ */
+AnimationManager.prototype.remove = function(animator) {
+ if (!this._runningAnimators[animator.id])
+ return;
+ delete this._runningAnimators[animator.id];
+ this._runningAnimatorCount--;
+ if (!this._needsTimer())
+ this._stopAnimation();
+};
+
+AnimationManager.prototype._animationFrameCallback = function(now) {
+ if (this._runningAnimatorCount > 0) {
+ for (var id in this._runningAnimators) {
+ this._runningAnimators[id].onAnimationFrame(now);
+ }
+ }
+ this.dispatchEvent(AnimationManager.EventTypeAnimationFrameWillFinish);
+ if (this._isRunning)
+ window.requestAnimationFrame(this._animationFrameCallbackBound);
+};
+
+/**
+ * @return {!boolean}
+ */
+AnimationManager.prototype._needsTimer = function() {
+ return this._runningAnimatorCount > 0 || this.hasListener(AnimationManager.EventTypeAnimationFrameWillFinish);
+};
+
+/**
+ * @param {!string} type
+ * @param {!Function} callback
+ * @override
+ */
+AnimationManager.prototype.on = function(type, callback) {
+ EventEmitter.prototype.on.call(this, type, callback);
+ if (this._needsTimer())
+ this._startAnimation();
+};
+
+/**
+ * @param {!string} type
+ * @param {!Function} callback
+ * @override
+ */
+AnimationManager.prototype.removeListener = function(type, callback) {
+ EventEmitter.prototype.removeListener.call(this, type, callback);
+ if (!this._needsTimer())
+ this._stopAnimation();
+};
+
+AnimationManager.shared = new AnimationManager();
+
+/**
+ * @constructor
+ * @extends EventEmitter
+ */
+function Animator() {
+ EventEmitter.call(this);
+
+ /**
+ * @type {!number}
+ * @const
+ */
+ this.id = Animator._lastId++;
+ /**
+ * @type {!number}
+ */
+ this.duration = 100;
+ /**
+ * @type {?function}
+ */
+ this.step = null;
+ /**
+ * @type {!boolean}
+ * @protected
+ */
+ this._isRunning = false;
+ /**
+ * @type {!number}
+ */
+ this.currentValue = 0;
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._lastStepTime = 0;
+}
+
+Animator.prototype = Object.create(EventEmitter.prototype);
+
+Animator._lastId = 0;
+
+Animator.EventTypeDidAnimationStop = 'didAnimationStop';
+
+/**
+ * @return {!boolean}
+ */
+Animator.prototype.isRunning = function() {
+ return this._isRunning;
+};
+
+Animator.prototype.start = function() {
+ this._lastStepTime = performance.now();
+ this._isRunning = true;
+ AnimationManager.shared.add(this);
+};
+
+Animator.prototype.stop = function() {
+ if (!this._isRunning)
+ return;
+ this._isRunning = false;
+ AnimationManager.shared.remove(this);
+ this.dispatchEvent(Animator.EventTypeDidAnimationStop, this);
+};
+
+/**
+ * @param {!number} now
+ */
+Animator.prototype.onAnimationFrame = function(now) {
+ this._lastStepTime = now;
+ this.step(this);
+};
+
+/**
+ * @constructor
+ * @extends Animator
+ */
+function TransitionAnimator() {
+ Animator.call(this);
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._from = 0;
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._to = 0;
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._delta = 0;
+ /**
+ * @type {!number}
+ */
+ this.progress = 0.0;
+ /**
+ * @type {!function}
+ */
+ this.timingFunction = AnimationTimingFunction.Linear;
+}
+
+TransitionAnimator.prototype = Object.create(Animator.prototype);
+
+/**
+ * @param {!number} value
+ */
+TransitionAnimator.prototype.setFrom = function(value) {
+ this._from = value;
+ this._delta = this._to - this._from;
+};
+
+TransitionAnimator.prototype.start = function() {
+ console.assert(isFinite(this.duration));
+ this.progress = 0.0;
+ this.currentValue = this._from;
+ Animator.prototype.start.call(this);
+};
+
+/**
+ * @param {!number} value
+ */
+TransitionAnimator.prototype.setTo = function(value) {
+ this._to = value;
+ this._delta = this._to - this._from;
+};
+
+/**
+ * @param {!number} now
+ */
+TransitionAnimator.prototype.onAnimationFrame = function(now) {
+ this.progress += (now - this._lastStepTime) / this.duration;
+ this.progress = Math.min(1.0, this.progress);
+ this._lastStepTime = now;
+ this.currentValue = this.timingFunction(this.progress) * this._delta + this._from;
+ this.step(this);
+ if (this.progress === 1.0) {
+ this.stop();
+ return;
+ }
+};
+
+/**
+ * @constructor
+ * @extends Animator
+ * @param {!number} initialVelocity
+ * @param {!number} initialValue
+ */
+function FlingGestureAnimator(initialVelocity, initialValue) {
+ Animator.call(this);
+ /**
+ * @type {!number}
+ */
+ this.initialVelocity = initialVelocity;
+ /**
+ * @type {!number}
+ */
+ this.initialValue = initialValue;
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._elapsedTime = 0;
+ var startVelocity = Math.abs(this.initialVelocity);
+ if (startVelocity > this._velocityAtTime(0))
+ startVelocity = this._velocityAtTime(0);
+ if (startVelocity < 0)
+ startVelocity = 0;
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._timeOffset = this._timeAtVelocity(startVelocity);
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._positionOffset = this._valueAtTime(this._timeOffset);
+ /**
+ * @type {!number}
+ */
+ this.duration = this._timeAtVelocity(0);
+}
+
+FlingGestureAnimator.prototype = Object.create(Animator.prototype);
+
+// Velocity is subject to exponential decay. These parameters are coefficients
+// that determine the curve.
+FlingGestureAnimator._P0 = -5707.62;
+FlingGestureAnimator._P1 = 0.172;
+FlingGestureAnimator._P2 = 0.0037;
+
+/**
+ * @param {!number} t
+ */
+FlingGestureAnimator.prototype._valueAtTime = function(t) {
+ return FlingGestureAnimator._P0 * Math.exp(-FlingGestureAnimator._P2 * t) - FlingGestureAnimator._P1 * t -
+ FlingGestureAnimator._P0;
+};
+
+/**
+ * @param {!number} t
+ */
+FlingGestureAnimator.prototype._velocityAtTime = function(t) {
+ return -FlingGestureAnimator._P0 * FlingGestureAnimator._P2 * Math.exp(-FlingGestureAnimator._P2 * t) -
+ FlingGestureAnimator._P1;
+};
+
+/**
+ * @param {!number} v
+ */
+FlingGestureAnimator.prototype._timeAtVelocity = function(v) {
+ return -Math.log((v + FlingGestureAnimator._P1) / (-FlingGestureAnimator._P0 * FlingGestureAnimator._P2)) /
+ FlingGestureAnimator._P2;
+};
+
+FlingGestureAnimator.prototype.start = function() {
+ this._lastStepTime = performance.now();
+ Animator.prototype.start.call(this);
+};
+
+/**
+ * @param {!number} now
+ */
+FlingGestureAnimator.prototype.onAnimationFrame = function(now) {
+ this._elapsedTime += now - this._lastStepTime;
+ this._lastStepTime = now;
+ if (this._elapsedTime + this._timeOffset >= this.duration) {
+ this.stop();
+ return;
+ }
+ var position = this._valueAtTime(this._elapsedTime + this._timeOffset) - this._positionOffset;
+ if (this.initialVelocity < 0)
+ position = -position;
+ this.currentValue = position + this.initialValue;
+ this.step(this);
+};
+
+/**
+ * @constructor
+ * @extends EventEmitter
+ * @param {?Element} element
+ * View adds itself as a property on the element so we can access it from Event.target.
+ */
+function View(element) {
+ EventEmitter.call(this);
+ /**
+ * @type {Element}
+ * @const
+ */
+ this.element = element || createElement('div');
+ this.element.$view = this;
+ this.bindCallbackMethods();
+}
+
+View.prototype = Object.create(EventEmitter.prototype);
+
+/**
+ * @param {!Element} ancestorElement
+ * @return {?Object}
+ */
+View.prototype.offsetRelativeTo = function(ancestorElement) {
+ var x = 0;
+ var y = 0;
+ var element = this.element;
+ while (element) {
+ x += element.offsetLeft || 0;
+ y += element.offsetTop || 0;
+ element = element.offsetParent;
+ if (element === ancestorElement)
+ return {x: x, y: y};
+ }
+ return null;
+};
+
+/**
+ * @param {!View|Node} parent
+ * @param {?View|Node=} before
+ */
+View.prototype.attachTo = function(parent, before) {
+ if (parent instanceof View)
+ return this.attachTo(parent.element, before);
+ if (typeof before === 'undefined')
+ before = null;
+ if (before instanceof View)
+ before = before.element;
+ parent.insertBefore(this.element, before);
+};
+
+View.prototype.bindCallbackMethods = function() {
+ for (var methodName in this) {
+ if (!/^on[A-Z]/.test(methodName))
+ continue;
+ if (this.hasOwnProperty(methodName))
+ continue;
+ var method = this[methodName];
+ if (!(method instanceof Function))
+ continue;
+ this[methodName] = method.bind(this);
+ }
+};
+
+/**
+ * @constructor
+ * @extends View
+ */
+function ScrollView() {
+ View.call(this, createElement('div', ScrollView.ClassNameScrollView));
+ /**
+ * @type {Element}
+ * @const
+ */
+ this.contentElement = createElement('div', ScrollView.ClassNameScrollViewContent);
+ this.element.appendChild(this.contentElement);
+ /**
+ * @type {number}
+ */
+ this.minimumContentOffset = -Infinity;
+ /**
+ * @type {number}
+ */
+ this.maximumContentOffset = Infinity;
+ /**
+ * @type {number}
+ * @protected
+ */
+ this._contentOffset = 0;
+ /**
+ * @type {number}
+ * @protected
+ */
+ this._width = 0;
+ /**
+ * @type {number}
+ * @protected
+ */
+ this._height = 0;
+ /**
+ * @type {Animator}
+ * @protected
+ */
+ this._scrollAnimator = null;
+ /**
+ * @type {?Object}
+ */
+ this.delegate = null;
+ /**
+ * @type {!number}
+ */
+ this._lastTouchPosition = 0;
+ /**
+ * @type {!number}
+ */
+ this._lastTouchVelocity = 0;
+ /**
+ * @type {!number}
+ */
+ this._lastTouchTimeStamp = 0;
+
+ this.element.addEventListener('mousewheel', this.onMouseWheel, false);
+ this.element.addEventListener('touchstart', this.onTouchStart, false);
+
+ /**
+ * The content offset is partitioned so the it can go beyond the CSS limit
+ * of 33554433px.
+ * @type {number}
+ * @protected
+ */
+ this._partitionNumber = 0;
+}
+
+ScrollView.prototype = Object.create(View.prototype);
+
+ScrollView.PartitionHeight = 100000;
+ScrollView.ClassNameScrollView = 'scroll-view';
+ScrollView.ClassNameScrollViewContent = 'scroll-view-content';
+
+/**
+ * @param {!Event} event
+ */
+ScrollView.prototype.onTouchStart = function(event) {
+ var touch = event.touches[0];
+ this._lastTouchPosition = touch.clientY;
+ this._lastTouchVelocity = 0;
+ this._lastTouchTimeStamp = event.timeStamp;
+ if (this._scrollAnimator)
+ this._scrollAnimator.stop();
+ window.addEventListener('touchmove', this.onWindowTouchMove, false);
+ window.addEventListener('touchend', this.onWindowTouchEnd, false);
+};
+
+/**
+ * @param {!Event} event
+ */
+ScrollView.prototype.onWindowTouchMove = function(event) {
+ var touch = event.touches[0];
+ var deltaTime = event.timeStamp - this._lastTouchTimeStamp;
+ var deltaY = this._lastTouchPosition - touch.clientY;
+ this.scrollBy(deltaY, false);
+ this._lastTouchVelocity = deltaY / deltaTime;
+ this._lastTouchPosition = touch.clientY;
+ this._lastTouchTimeStamp = event.timeStamp;
+ event.stopPropagation();
+ event.preventDefault();
+};
+
+/**
+ * @param {!Event} event
+ */
+ScrollView.prototype.onWindowTouchEnd = function(event) {
+ if (Math.abs(this._lastTouchVelocity) > 0.01) {
+ this._scrollAnimator = new FlingGestureAnimator(this._lastTouchVelocity, this._contentOffset);
+ this._scrollAnimator.step = this.onFlingGestureAnimatorStep;
+ this._scrollAnimator.start();
+ }
+ window.removeEventListener('touchmove', this.onWindowTouchMove, false);
+ window.removeEventListener('touchend', this.onWindowTouchEnd, false);
+};
+
+/**
+ * @param {!Animator} animator
+ */
+ScrollView.prototype.onFlingGestureAnimatorStep = function(animator) {
+ this.scrollTo(animator.currentValue, false);
+};
+
+/**
+ * @return {!Animator}
+ */
+ScrollView.prototype.scrollAnimator = function() {
+ return this._scrollAnimator;
+};
+
+/**
+ * @param {!number} width
+ */
+ScrollView.prototype.setWidth = function(width) {
+ console.assert(isFinite(width));
+ if (this._width === width)
+ return;
+ this._width = width;
+ this.element.style.width = this._width + 'px';
+};
+
+/**
+ * @return {!number}
+ */
+ScrollView.prototype.width = function() {
+ return this._width;
+};
+
+/**
+ * @param {!number} height
+ */
+ScrollView.prototype.setHeight = function(height) {
+ console.assert(isFinite(height));
+ if (this._height === height)
+ return;
+ this._height = height;
+ this.element.style.height = height + 'px';
+ if (this.delegate)
+ this.delegate.scrollViewDidChangeHeight(this);
+};
+
+/**
+ * @return {!number}
+ */
+ScrollView.prototype.height = function() {
+ return this._height;
+};
+
+/**
+ * @param {!Animator} animator
+ */
+ScrollView.prototype.onScrollAnimatorStep = function(animator) {
+ this.setContentOffset(animator.currentValue);
+};
+
+/**
+ * @param {!number} offset
+ * @param {?boolean} animate
+ */
+ScrollView.prototype.scrollTo = function(offset, animate) {
+ console.assert(isFinite(offset));
+ if (!animate) {
+ this.setContentOffset(offset);
+ return;
+ }
+ if (this._scrollAnimator)
+ this._scrollAnimator.stop();
+ this._scrollAnimator = new TransitionAnimator();
+ this._scrollAnimator.step = this.onScrollAnimatorStep;
+ this._scrollAnimator.setFrom(this._contentOffset);
+ this._scrollAnimator.setTo(offset);
+ this._scrollAnimator.duration = 300;
+ this._scrollAnimator.start();
+};
+
+/**
+ * @param {!number} offset
+ * @param {?boolean} animate
+ */
+ScrollView.prototype.scrollBy = function(offset, animate) {
+ this.scrollTo(this._contentOffset + offset, animate);
+};
+
+/**
+ * @return {!number}
+ */
+ScrollView.prototype.contentOffset = function() {
+ return this._contentOffset;
+};
+
+/**
+ * @param {?Event} event
+ */
+ScrollView.prototype.onMouseWheel = function(event) {
+ this.setContentOffset(this._contentOffset - event.wheelDelta / 30);
+ event.stopPropagation();
+ event.preventDefault();
+};
+
+
+/**
+ * @param {!number} value
+ */
+ScrollView.prototype.setContentOffset = function(value) {
+ console.assert(isFinite(value));
+ value = Math.min(this.maximumContentOffset - this._height, Math.max(this.minimumContentOffset, Math.floor(value)));
+ if (this._contentOffset === value)
+ return;
+ this._contentOffset = value;
+ this._updateScrollContent();
+ if (this.delegate)
+ this.delegate.scrollViewDidChangeContentOffset(this);
+};
+
+ScrollView.prototype._updateScrollContent = function() {
+ var newPartitionNumber = Math.floor(this._contentOffset / ScrollView.PartitionHeight);
+ var partitionChanged = this._partitionNumber !== newPartitionNumber;
+ this._partitionNumber = newPartitionNumber;
+ this.contentElement.style.webkitTransform =
+ 'translate(0, ' + (-this.contentPositionForContentOffset(this._contentOffset)) + 'px)';
+ if (this.delegate && partitionChanged)
+ this.delegate.scrollViewDidChangePartition(this);
+};
+
+/**
+ * @param {!View|Node} parent
+ * @param {?View|Node=} before
+ * @override
+ */
+ScrollView.prototype.attachTo = function(parent, before) {
+ View.prototype.attachTo.call(this, parent, before);
+ this._updateScrollContent();
+};
+
+/**
+ * @param {!number} offset
+ */
+ScrollView.prototype.contentPositionForContentOffset = function(offset) {
+ return offset - this._partitionNumber * ScrollView.PartitionHeight;
+};
+
+/**
+ * @constructor
+ * @extends View
+ */
+function ListCell() {
+ View.call(this, createElement('div', ListCell.ClassNameListCell));
+
+ /**
+ * @type {!number}
+ */
+ this.row = NaN;
+ /**
+ * @type {!number}
+ */
+ this._width = 0;
+ /**
+ * @type {!number}
+ */
+ this._position = 0;
+}
+
+ListCell.prototype = Object.create(View.prototype);
+
+ListCell.DefaultRecycleBinLimit = 64;
+ListCell.ClassNameListCell = 'list-cell';
+ListCell.ClassNameHidden = 'hidden';
+
+/**
+ * @return {!Array} An array to keep thrown away cells.
+ */
+ListCell.prototype._recycleBin = function() {
+ console.assert(false, 'NOT REACHED: ListCell.prototype._recycleBin needs to be overridden.');
+ return [];
+};
+
+ListCell.prototype.throwAway = function() {
+ this.hide();
+ var limit = typeof this.constructor.RecycleBinLimit === 'undefined' ? ListCell.DefaultRecycleBinLimit :
+ this.constructor.RecycleBinLimit;
+ var recycleBin = this._recycleBin();
+ if (recycleBin.length < limit)
+ recycleBin.push(this);
+};
+
+ListCell.prototype.show = function() {
+ this.element.classList.remove(ListCell.ClassNameHidden);
+};
+
+ListCell.prototype.hide = function() {
+ this.element.classList.add(ListCell.ClassNameHidden);
+};
+
+/**
+ * @return {!number} Width in pixels.
+ */
+ListCell.prototype.width = function() {
+ return this._width;
+};
+
+/**
+ * @param {!number} width Width in pixels.
+ */
+ListCell.prototype.setWidth = function(width) {
+ if (this._width === width)
+ return;
+ this._width = width;
+ this.element.style.width = this._width + 'px';
+};
+
+/**
+ * @return {!number} Position in pixels.
+ */
+ListCell.prototype.position = function() {
+ return this._position;
+};
+
+/**
+ * @param {!number} y Position in pixels.
+ */
+ListCell.prototype.setPosition = function(y) {
+ if (this._position === y)
+ return;
+ this._position = y;
+ this.element.style.webkitTransform = 'translate(0, ' + this._position + 'px)';
+};
+
+/**
+ * @param {!boolean} selected
+ */
+ListCell.prototype.setSelected = function(selected) {
+ if (this._selected === selected)
+ return;
+ this._selected = selected;
+ if (this._selected)
+ this.element.classList.add('selected');
+ else
+ this.element.classList.remove('selected');
+};
+
+/**
+ * @constructor
+ * @extends View
+ */
+function ListView() {
+ View.call(this, createElement('div', ListView.ClassNameListView));
+ this.element.tabIndex = 0;
+ this.element.setAttribute('role', 'grid');
+
+ /**
+ * @type {!number}
+ * @private
+ */
+ this._width = 0;
+ /**
+ * @type {!Object}
+ * @private
+ */
+ this._cells = {};
+
+ /**
+ * @type {!number}
+ */
+ this.selectedRow = ListView.NoSelection;
+
+ /**
+ * @type {!ScrollView}
+ */
+ this.scrollView = new ScrollView();
+ this.scrollView.delegate = this;
+ this.scrollView.minimumContentOffset = 0;
+ this.scrollView.setWidth(0);
+ this.scrollView.setHeight(0);
+ this.scrollView.attachTo(this);
+
+ this.element.addEventListener('click', this.onClick, false);
+
+ /**
+ * @type {!boolean}
+ * @private
+ */
+ this._needsUpdateCells = false;
+}
+
+ListView.prototype = Object.create(View.prototype);
+
+ListView.NoSelection = -1;
+ListView.ClassNameListView = 'list-view';
+
+ListView.prototype.onAnimationFrameWillFinish = function() {
+ if (this._needsUpdateCells)
+ this.updateCells();
+};
+
+/**
+ * @param {!boolean} needsUpdateCells
+ */
+ListView.prototype.setNeedsUpdateCells = function(needsUpdateCells) {
+ if (this._needsUpdateCells === needsUpdateCells)
+ return;
+ this._needsUpdateCells = needsUpdateCells;
+ if (this._needsUpdateCells)
+ AnimationManager.shared.on(AnimationManager.EventTypeAnimationFrameWillFinish, this.onAnimationFrameWillFinish);
+ else
+ AnimationManager.shared.removeListener(
+ AnimationManager.EventTypeAnimationFrameWillFinish, this.onAnimationFrameWillFinish);
+};
+
+/**
+ * @param {!number} row
+ * @return {?ListCell}
+ */
+ListView.prototype.cellAtRow = function(row) {
+ return this._cells[row];
+};
+
+/**
+ * @param {!number} offset Scroll offset in pixels.
+ * @return {!number}
+ */
+ListView.prototype.rowAtScrollOffset = function(offset) {
+ console.assert(false, 'NOT REACHED: ListView.prototype.rowAtScrollOffset needs to be overridden.');
+ return 0;
+};
+
+/**
+ * @param {!number} row
+ * @return {!number} Scroll offset in pixels.
+ */
+ListView.prototype.scrollOffsetForRow = function(row) {
+ console.assert(false, 'NOT REACHED: ListView.prototype.scrollOffsetForRow needs to be overridden.');
+ return 0;
+};
+
+/**
+ * @param {!number} row
+ * @return {!ListCell}
+ */
+ListView.prototype.addCellIfNecessary = function(row) {
+ var cell = this._cells[row];
+ if (cell)
+ return cell;
+ cell = this.prepareNewCell(row);
+ cell.attachTo(this.scrollView.contentElement);
+ cell.setWidth(this._width);
+ cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(row)));
+ this._cells[row] = cell;
+ return cell;
+};
+
+/**
+ * @param {!number} row
+ * @return {!ListCell}
+ */
+ListView.prototype.prepareNewCell = function(row) {
+ console.assert(false, 'NOT REACHED: ListView.prototype.prepareNewCell should be overridden.');
+ return new ListCell();
+};
+
+/**
+ * @param {!ListCell} cell
+ */
+ListView.prototype.throwAwayCell = function(cell) {
+ delete this._cells[cell.row];
+ cell.throwAway();
+};
+
+/**
+ * @return {!number}
+ */
+ListView.prototype.firstVisibleRow = function() {
+ return this.rowAtScrollOffset(this.scrollView.contentOffset());
+};
+
+/**
+ * @return {!number}
+ */
+ListView.prototype.lastVisibleRow = function() {
+ return this.rowAtScrollOffset(this.scrollView.contentOffset() + this.scrollView.height() - 1);
+};
+
+/**
+ * @param {!ScrollView} scrollView
+ */
+ListView.prototype.scrollViewDidChangeContentOffset = function(scrollView) {
+ this.setNeedsUpdateCells(true);
+};
+
+/**
+ * @param {!ScrollView} scrollView
+ */
+ListView.prototype.scrollViewDidChangeHeight = function(scrollView) {
+ this.setNeedsUpdateCells(true);
+};
+
+/**
+ * @param {!ScrollView} scrollView
+ */
+ListView.prototype.scrollViewDidChangePartition = function(scrollView) {
+ this.setNeedsUpdateCells(true);
+};
+
+ListView.prototype.updateCells = function() {
+ var firstVisibleRow = this.firstVisibleRow();
+ var lastVisibleRow = this.lastVisibleRow();
+ console.assert(firstVisibleRow <= lastVisibleRow);
+ for (var c in this._cells) {
+ var cell = this._cells[c];
+ if (cell.row < firstVisibleRow || cell.row > lastVisibleRow)
+ this.throwAwayCell(cell);
+ }
+ for (var i = firstVisibleRow; i <= lastVisibleRow; ++i) {
+ var cell = this._cells[i];
+ if (cell)
+ cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(cell.row)));
+ else
+ this.addCellIfNecessary(i);
+ }
+ this.setNeedsUpdateCells(false);
+};
+
+/**
+ * @return {!number} Width in pixels.
+ */
+ListView.prototype.width = function() {
+ return this._width;
+};
+
+/**
+ * @param {!number} width Width in pixels.
+ */
+ListView.prototype.setWidth = function(width) {
+ if (this._width === width)
+ return;
+ this._width = width;
+ this.scrollView.setWidth(this._width);
+ for (var c in this._cells) {
+ this._cells[c].setWidth(this._width);
+ }
+ this.element.style.width = this._width + 'px';
+ this.setNeedsUpdateCells(true);
+};
+
+/**
+ * @return {!number} Height in pixels.
+ */
+ListView.prototype.height = function() {
+ return this.scrollView.height();
+};
+
+/**
+ * @param {!number} height Height in pixels.
+ */
+ListView.prototype.setHeight = function(height) {
+ this.scrollView.setHeight(height);
+};
+
+/**
+ * @param {?Event} event
+ */
+ListView.prototype.onClick = function(event) {
+ var clickedCellElement = enclosingNodeOrSelfWithClass(event.target, ListCell.ClassNameListCell);
+ if (!clickedCellElement)
+ return;
+ var clickedCell = clickedCellElement.$view;
+ if (clickedCell.row !== this.selectedRow)
+ this.select(clickedCell.row);
+};
+
+/**
+ * @param {!number} row
+ */
+ListView.prototype.select = function(row) {
+ if (this.selectedRow === row)
+ return;
+ this.deselect();
+ if (row === ListView.NoSelection)
+ return;
+ this.selectedRow = row;
+ var selectedCell = this._cells[this.selectedRow];
+ if (selectedCell)
+ selectedCell.setSelected(true);
+};
+
+ListView.prototype.deselect = function() {
+ if (this.selectedRow === ListView.NoSelection)
+ return;
+ var selectedCell = this._cells[this.selectedRow];
+ if (selectedCell)
+ selectedCell.setSelected(false);
+ this.selectedRow = ListView.NoSelection;
+};
+
+/**
+ * @param {!number} row
+ * @param {!boolean} animate
+ */
+ListView.prototype.scrollToRow = function(row, animate) {
+ this.scrollView.scrollTo(this.scrollOffsetForRow(row), animate);
+};
+
+/**
+ * @constructor
+ * @extends View
+ * @param {!ScrollView} scrollView
+ */
+function ScrubbyScrollBar(scrollView) {
+ View.call(this, createElement('div', ScrubbyScrollBar.ClassNameScrubbyScrollBar));
+
+ /**
+ * @type {!Element}
+ * @const
+ */
+ this.thumb = createElement('div', ScrubbyScrollBar.ClassNameScrubbyScrollThumb);
+ this.element.appendChild(this.thumb);
+
+ /**
+ * @type {!ScrollView}
+ * @const
+ */
+ this.scrollView = scrollView;
+
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._height = 0;
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._thumbHeight = 0;
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._thumbPosition = 0;
+
+ this.setHeight(0);
+ this.setThumbHeight(ScrubbyScrollBar.ThumbHeight);
+
+ /**
+ * @type {?Animator}
+ * @protected
+ */
+ this._thumbStyleTopAnimator = null;
+
+ /**
+ * @type {?number}
+ * @protected
+ */
+ this._timer = null;
+
+ this.element.addEventListener('mousedown', this.onMouseDown, false);
+ this.element.addEventListener('touchstart', this.onTouchStart, false);
+}
+
+ScrubbyScrollBar.prototype = Object.create(View.prototype);
+
+ScrubbyScrollBar.ScrollInterval = 16;
+ScrubbyScrollBar.ThumbMargin = 2;
+ScrubbyScrollBar.ThumbHeight = 30;
+ScrubbyScrollBar.ClassNameScrubbyScrollBar = 'scrubby-scroll-bar';
+ScrubbyScrollBar.ClassNameScrubbyScrollThumb = 'scrubby-scroll-thumb';
+
+/**
+ * @param {?Event} event
+ */
+ScrubbyScrollBar.prototype.onTouchStart = function(event) {
+ var touch = event.touches[0];
+ this._setThumbPositionFromEventPosition(touch.clientY);
+ if (this._thumbStyleTopAnimator)
+ this._thumbStyleTopAnimator.stop();
+ this._timer = setInterval(this.onScrollTimer, ScrubbyScrollBar.ScrollInterval);
+ window.addEventListener('touchmove', this.onWindowTouchMove, false);
+ window.addEventListener('touchend', this.onWindowTouchEnd, false);
+ event.stopPropagation();
+ event.preventDefault();
+};
+
+/**
+ * @param {?Event} event
+ */
+ScrubbyScrollBar.prototype.onWindowTouchMove = function(event) {
+ var touch = event.touches[0];
+ this._setThumbPositionFromEventPosition(touch.clientY);
+ event.stopPropagation();
+ event.preventDefault();
+};
+
+/**
+ * @param {?Event} event
+ */
+ScrubbyScrollBar.prototype.onWindowTouchEnd = function(event) {
+ this._thumbStyleTopAnimator = new TransitionAnimator();
+ this._thumbStyleTopAnimator.step = this.onThumbStyleTopAnimationStep;
+ this._thumbStyleTopAnimator.setFrom(this.thumb.offsetTop);
+ this._thumbStyleTopAnimator.setTo((this._height - this._thumbHeight) / 2);
+ this._thumbStyleTopAnimator.timingFunction = AnimationTimingFunction.EaseInOut;
+ this._thumbStyleTopAnimator.duration = 100;
+ this._thumbStyleTopAnimator.start();
+
+ window.removeEventListener('touchmove', this.onWindowTouchMove, false);
+ window.removeEventListener('touchend', this.onWindowTouchEnd, false);
+ clearInterval(this._timer);
+};
+
+/**
+ * @return {!number} Height of the view in pixels.
+ */
+ScrubbyScrollBar.prototype.height = function() {
+ return this._height;
+};
+
+/**
+ * @param {!number} height Height of the view in pixels.
+ */
+ScrubbyScrollBar.prototype.setHeight = function(height) {
+ if (this._height === height)
+ return;
+ this._height = height;
+ this.element.style.height = this._height + 'px';
+ this.thumb.style.top = ((this._height - this._thumbHeight) / 2) + 'px';
+ this._thumbPosition = 0;
+};
+
+/**
+ * @param {!number} height Height of the scroll bar thumb in pixels.
+ */
+ScrubbyScrollBar.prototype.setThumbHeight = function(height) {
+ if (this._thumbHeight === height)
+ return;
+ this._thumbHeight = height;
+ this.thumb.style.height = this._thumbHeight + 'px';
+ this.thumb.style.top = ((this._height - this._thumbHeight) / 2) + 'px';
+ this._thumbPosition = 0;
+};
+
+/**
+ * @param {number} position
+ */
+ScrubbyScrollBar.prototype._setThumbPositionFromEventPosition = function(position) {
+ var thumbMin = ScrubbyScrollBar.ThumbMargin;
+ var thumbMax = this._height - this._thumbHeight - ScrubbyScrollBar.ThumbMargin * 2;
+ var y = position - this.element.getBoundingClientRect().top - this.element.clientTop + this.element.scrollTop;
+ var thumbPosition = y - this._thumbHeight / 2;
+ thumbPosition = Math.max(thumbPosition, thumbMin);
+ thumbPosition = Math.min(thumbPosition, thumbMax);
+ this.thumb.style.top = thumbPosition + 'px';
+ this._thumbPosition = 1.0 - (thumbPosition - thumbMin) / (thumbMax - thumbMin) * 2;
+};
+
+/**
+ * @param {?Event} event
+ */
+ScrubbyScrollBar.prototype.onMouseDown = function(event) {
+ this._setThumbPositionFromEventPosition(event.clientY);
+
+ window.addEventListener('mousemove', this.onWindowMouseMove, false);
+ window.addEventListener('mouseup', this.onWindowMouseUp, false);
+ if (this._thumbStyleTopAnimator)
+ this._thumbStyleTopAnimator.stop();
+ this._timer = setInterval(this.onScrollTimer, ScrubbyScrollBar.ScrollInterval);
+ event.stopPropagation();
+ event.preventDefault();
+};
+
+/**
+ * @param {?Event} event
+ */
+ScrubbyScrollBar.prototype.onWindowMouseMove = function(event) {
+ this._setThumbPositionFromEventPosition(event.clientY);
+};
+
+/**
+ * @param {?Event} event
+ */
+ScrubbyScrollBar.prototype.onWindowMouseUp = function(event) {
+ this._thumbStyleTopAnimator = new TransitionAnimator();
+ this._thumbStyleTopAnimator.step = this.onThumbStyleTopAnimationStep;
+ this._thumbStyleTopAnimator.setFrom(this.thumb.offsetTop);
+ this._thumbStyleTopAnimator.setTo((this._height - this._thumbHeight) / 2);
+ this._thumbStyleTopAnimator.timingFunction = AnimationTimingFunction.EaseInOut;
+ this._thumbStyleTopAnimator.duration = 100;
+ this._thumbStyleTopAnimator.start();
+
+ window.removeEventListener('mousemove', this.onWindowMouseMove, false);
+ window.removeEventListener('mouseup', this.onWindowMouseUp, false);
+ clearInterval(this._timer);
+};
+
+/**
+ * @param {!Animator} animator
+ */
+ScrubbyScrollBar.prototype.onThumbStyleTopAnimationStep = function(animator) {
+ this.thumb.style.top = animator.currentValue + 'px';
+};
+
+ScrubbyScrollBar.prototype.onScrollTimer = function() {
+ var scrollAmount = Math.pow(this._thumbPosition, 2) * 10;
+ if (this._thumbPosition > 0)
+ scrollAmount = -scrollAmount;
+ this.scrollView.scrollBy(scrollAmount, false);
+};
+
+/**
+ * @constructor
+ * @extends ListCell
+ * @param {!Array} shortMonthLabels
+ */
+function YearListCell(shortMonthLabels) {
+ ListCell.call(this);
+ this.element.classList.add(YearListCell.ClassNameYearListCell);
+ this.element.style.height = YearListCell.Height + 'px';
+
+ /**
+ * @type {!Element}
+ * @const
+ */
+ this.label = createElement('div', YearListCell.ClassNameLabel, '----');
+ this.element.appendChild(this.label);
+ this.label.style.height = (YearListCell.Height - YearListCell.BorderBottomWidth) + 'px';
+ this.label.style.lineHeight = (YearListCell.Height - YearListCell.BorderBottomWidth) + 'px';
+
+ /**
+ * @type {!Array} Array of the 12 month button elements.
+ * @const
+ */
+ this.monthButtons = [];
+ var monthChooserElement = createElement('div', YearListCell.ClassNameMonthChooser);
+ for (var r = 0; r < YearListCell.ButtonRows; ++r) {
+ var buttonsRow = createElement('div', YearListCell.ClassNameMonthButtonsRow);
+ buttonsRow.setAttribute('role', 'row');
+ for (var c = 0; c < YearListCell.ButtonColumns; ++c) {
+ var month = c + r * YearListCell.ButtonColumns;
+ var button = createElement('div', YearListCell.ClassNameMonthButton, shortMonthLabels[month]);
+ button.setAttribute('role', 'gridcell');
+ button.dataset.month = month;
+ buttonsRow.appendChild(button);
+ this.monthButtons.push(button);
+ }
+ monthChooserElement.appendChild(buttonsRow);
+ }
+ this.element.appendChild(monthChooserElement);
+
+ /**
+ * @type {!boolean}
+ * @private
+ */
+ this._selected = false;
+ /**
+ * @type {!number}
+ * @private
+ */
+ this._height = 0;
+}
+
+YearListCell.prototype = Object.create(ListCell.prototype);
+
+YearListCell.Height = hasInaccuratePointingDevice() ? 31 : 25;
+YearListCell.BorderBottomWidth = 1;
+YearListCell.ButtonRows = 3;
+YearListCell.ButtonColumns = 4;
+YearListCell.SelectedHeight = hasInaccuratePointingDevice() ? 127 : 121;
+YearListCell.ClassNameYearListCell = 'year-list-cell';
+YearListCell.ClassNameLabel = 'label';
+YearListCell.ClassNameMonthChooser = 'month-chooser';
+YearListCell.ClassNameMonthButtonsRow = 'month-buttons-row';
+YearListCell.ClassNameMonthButton = 'month-button';
+YearListCell.ClassNameHighlighted = 'highlighted';
+
+YearListCell._recycleBin = [];
+
+/**
+ * @return {!Array}
+ * @override
+ */
+YearListCell.prototype._recycleBin = function() {
+ return YearListCell._recycleBin;
+};
+
+/**
+ * @param {!number} row
+ */
+YearListCell.prototype.reset = function(row) {
+ this.row = row;
+ this.label.textContent = row + 1;
+ for (var i = 0; i < this.monthButtons.length; ++i) {
+ this.monthButtons[i].classList.remove(YearListCell.ClassNameHighlighted);
+ }
+ this.show();
+};
+
+/**
+ * @return {!number} The height in pixels.
+ */
+YearListCell.prototype.height = function() {
+ return this._height;
+};
+
+/**
+ * @param {!number} height Height in pixels.
+ */
+YearListCell.prototype.setHeight = function(height) {
+ if (this._height === height)
+ return;
+ this._height = height;
+ this.element.style.height = this._height + 'px';
+};
+
+/**
+ * @constructor
+ * @extends ListView
+ * @param {!Month} minimumMonth
+ * @param {!Month} maximumMonth
+ */
+function YearListView(minimumMonth, maximumMonth) {
+ ListView.call(this);
+ this.element.classList.add('year-list-view');
+
+ /**
+ * @type {?Month}
+ */
+ this.highlightedMonth = null;
+ /**
+ * @type {!Month}
+ * @const
+ * @protected
+ */
+ this._minimumMonth = minimumMonth;
+ /**
+ * @type {!Month}
+ * @const
+ * @protected
+ */
+ this._maximumMonth = maximumMonth;
+
+ this.scrollView.minimumContentOffset = (this._minimumMonth.year - 1) * YearListCell.Height;
+ this.scrollView.maximumContentOffset =
+ (this._maximumMonth.year - 1) * YearListCell.Height + YearListCell.SelectedHeight;
+
+ /**
+ * @type {!Object}
+ * @const
+ * @protected
+ */
+ this._runningAnimators = {};
+ /**
+ * @type {!Array}
+ * @const
+ * @protected
+ */
+ this._animatingRows = [];
+ /**
+ * @type {!boolean}
+ * @protected
+ */
+ this._ignoreMouseOutUntillNextMouseOver = false;
+
+ /**
+ * @type {!ScrubbyScrollBar}
+ * @const
+ */
+ 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);
+}
+
+YearListView.prototype = Object.create(ListView.prototype);
+
+YearListView.Height = YearListCell.SelectedHeight - 1;
+YearListView.EventTypeYearListViewDidHide = 'yearListViewDidHide';
+YearListView.EventTypeYearListViewDidSelectMonth = 'yearListViewDidSelectMonth';
+
+/**
+ * @param {?Event} event
+ */
+YearListView.prototype.onTouchStart = function(event) {
+ var touch = event.touches[0];
+ var monthButtonElement = enclosingNodeOrSelfWithClass(touch.target, YearListCell.ClassNameMonthButton);
+ if (!monthButtonElement)
+ return;
+ var cellElement = enclosingNodeOrSelfWithClass(monthButtonElement, YearListCell.ClassNameYearListCell);
+ var cell = cellElement.$view;
+ this.highlightMonth(new Month(cell.row + 1, parseInt(monthButtonElement.dataset.month, 10)));
+};
+
+/**
+ * @param {?Event} event
+ */
+YearListView.prototype.onMouseOver = function(event) {
+ var monthButtonElement = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton);
+ if (!monthButtonElement)
+ return;
+ var cellElement = enclosingNodeOrSelfWithClass(monthButtonElement, YearListCell.ClassNameYearListCell);
+ var cell = cellElement.$view;
+ this.highlightMonth(new Month(cell.row + 1, parseInt(monthButtonElement.dataset.month, 10)));
+ this._ignoreMouseOutUntillNextMouseOver = false;
+};
+
+/**
+ * @param {?Event} event
+ */
+YearListView.prototype.onMouseOut = function(event) {
+ if (this._ignoreMouseOutUntillNextMouseOver)
+ return;
+ var monthButtonElement = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton);
+ if (!monthButtonElement) {
+ this.dehighlightMonth();
+ }
+};
+
+/**
+ * @param {!number} width Width in pixels.
+ * @override
+ */
+YearListView.prototype.setWidth = function(width) {
+ ListView.prototype.setWidth.call(this, width - this.scrubbyScrollBar.element.offsetWidth);
+ this.element.style.width = width + 'px';
+};
+
+/**
+ * @param {!number} height Height in pixels.
+ * @override
+ */
+YearListView.prototype.setHeight = function(height) {
+ ListView.prototype.setHeight.call(this, height);
+ this.scrubbyScrollBar.setHeight(height);
+};
+
+/**
+ * @enum {number}
+ */
+YearListView.RowAnimationDirection = {
+ Opening: 0,
+ Closing: 1
+};
+
+/**
+ * @param {!number} row
+ * @param {!YearListView.RowAnimationDirection} direction
+ */
+YearListView.prototype._animateRow = function(row, direction) {
+ var fromValue =
+ direction === YearListView.RowAnimationDirection.Closing ? YearListCell.SelectedHeight : YearListCell.Height;
+ var oldAnimator = this._runningAnimators[row];
+ if (oldAnimator) {
+ oldAnimator.stop();
+ fromValue = oldAnimator.currentValue;
+ }
+ var cell = this.cellAtRow(row);
+ var animator = new TransitionAnimator();
+ animator.step = this.onCellHeightAnimatorStep;
+ animator.setFrom(fromValue);
+ animator.setTo(
+ direction === YearListView.RowAnimationDirection.Opening ? YearListCell.SelectedHeight : YearListCell.Height);
+ animator.timingFunction = AnimationTimingFunction.EaseInOut;
+ animator.duration = 300;
+ animator.row = row;
+ animator.on(Animator.EventTypeDidAnimationStop, this.onCellHeightAnimatorDidStop);
+ this._runningAnimators[row] = animator;
+ this._animatingRows.push(row);
+ this._animatingRows.sort();
+ animator.start();
+};
+
+/**
+ * @param {?Animator} animator
+ */
+YearListView.prototype.onCellHeightAnimatorDidStop = function(animator) {
+ delete this._runningAnimators[animator.row];
+ var index = this._animatingRows.indexOf(animator.row);
+ this._animatingRows.splice(index, 1);
+};
+
+/**
+ * @param {!Animator} animator
+ */
+YearListView.prototype.onCellHeightAnimatorStep = function(animator) {
+ var cell = this.cellAtRow(animator.row);
+ if (cell)
+ cell.setHeight(animator.currentValue);
+ this.updateCells();
+};
+
+/**
+ * @param {?Event} event
+ */
+YearListView.prototype.onClick = function(event) {
+ var oldSelectedRow = this.selectedRow;
+ ListView.prototype.onClick.call(this, event);
+ var year = this.selectedRow + 1;
+ if (this.selectedRow !== oldSelectedRow) {
+ var month = this.highlightedMonth ? this.highlightedMonth.month : 0;
+ this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, new Month(year, month));
+ this.scrollView.scrollTo(this.selectedRow * YearListCell.Height, true);
+ } else {
+ var monthButton = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton);
+ if (!monthButton || monthButton.getAttribute('aria-disabled') == 'true')
+ return;
+ var month = parseInt(monthButton.dataset.month, 10);
+ this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, new Month(year, month));
+ this.hide();
+ }
+};
+
+/**
+ * @param {!number} scrollOffset
+ * @return {!number}
+ * @override
+ */
+YearListView.prototype.rowAtScrollOffset = function(scrollOffset) {
+ var remainingOffset = scrollOffset;
+ var lastAnimatingRow = 0;
+ var rowsWithIrregularHeight = this._animatingRows.slice();
+ if (this.selectedRow > -1 && !this._runningAnimators[this.selectedRow]) {
+ rowsWithIrregularHeight.push(this.selectedRow);
+ rowsWithIrregularHeight.sort();
+ }
+ for (var i = 0; i < rowsWithIrregularHeight.length; ++i) {
+ var row = rowsWithIrregularHeight[i];
+ var animator = this._runningAnimators[row];
+ var rowHeight = animator ? animator.currentValue : YearListCell.SelectedHeight;
+ if (remainingOffset <= (row - lastAnimatingRow) * YearListCell.Height) {
+ return lastAnimatingRow + Math.floor(remainingOffset / YearListCell.Height);
+ }
+ remainingOffset -= (row - lastAnimatingRow) * YearListCell.Height;
+ if (remainingOffset <= (rowHeight - YearListCell.Height))
+ return row;
+ remainingOffset -= rowHeight - YearListCell.Height;
+ lastAnimatingRow = row;
+ }
+ return lastAnimatingRow + Math.floor(remainingOffset / YearListCell.Height);
+};
+
+/**
+ * @param {!number} row
+ * @return {!number}
+ * @override
+ */
+YearListView.prototype.scrollOffsetForRow = function(row) {
+ var scrollOffset = row * YearListCell.Height;
+ for (var i = 0; i < this._animatingRows.length; ++i) {
+ var animatingRow = this._animatingRows[i];
+ if (animatingRow >= row)
+ break;
+ var animator = this._runningAnimators[animatingRow];
+ scrollOffset += animator.currentValue - YearListCell.Height;
+ }
+ if (this.selectedRow > -1 && this.selectedRow < row && !this._runningAnimators[this.selectedRow]) {
+ scrollOffset += YearListCell.SelectedHeight - YearListCell.Height;
+ }
+ return scrollOffset;
+};
+
+/**
+ * @param {!number} row
+ * @return {!YearListCell}
+ * @override
+ */
+YearListView.prototype.prepareNewCell = function(row) {
+ var cell = YearListCell._recycleBin.pop() || new YearListCell(global.params.shortMonthLabels);
+ cell.reset(row);
+ cell.setSelected(this.selectedRow === 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');
+ cell.monthButtons[i].setAttribute('aria-label', month.toLocaleString());
+ }
+ if (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
+ // |monthButton| might have no layoutObject yet.
+ var element = this.element;
+ setTimeout(function() {
+ element.setAttribute('aria-activedescendant', monthButton.id);
+ }, 0);
+ }
+ var animator = this._runningAnimators[row];
+ if (animator)
+ cell.setHeight(animator.currentValue);
+ else if (row === this.selectedRow)
+ cell.setHeight(YearListCell.SelectedHeight);
+ else
+ cell.setHeight(YearListCell.Height);
+ return cell;
+};
+
+/**
+ * @override
+ */
+YearListView.prototype.updateCells = function() {
+ var firstVisibleRow = this.firstVisibleRow();
+ var lastVisibleRow = this.lastVisibleRow();
+ console.assert(firstVisibleRow <= lastVisibleRow);
+ for (var c in this._cells) {
+ var cell = this._cells[c];
+ if (cell.row < firstVisibleRow || cell.row > lastVisibleRow)
+ this.throwAwayCell(cell);
+ }
+ for (var i = firstVisibleRow; i <= lastVisibleRow; ++i) {
+ var cell = this._cells[i];
+ if (cell)
+ cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(cell.row)));
+ else
+ this.addCellIfNecessary(i);
+ }
+ this.setNeedsUpdateCells(false);
+};
+
+/**
+ * @override
+ */
+YearListView.prototype.deselect = function() {
+ if (this.selectedRow === ListView.NoSelection)
+ return;
+ var selectedCell = this._cells[this.selectedRow];
+ if (selectedCell)
+ selectedCell.setSelected(false);
+ this._animateRow(this.selectedRow, YearListView.RowAnimationDirection.Closing);
+ this.selectedRow = ListView.NoSelection;
+ this.setNeedsUpdateCells(true);
+};
+
+YearListView.prototype.deselectWithoutAnimating = function() {
+ if (this.selectedRow === ListView.NoSelection)
+ return;
+ var selectedCell = this._cells[this.selectedRow];
+ if (selectedCell) {
+ selectedCell.setSelected(false);
+ selectedCell.setHeight(YearListCell.Height);
+ }
+ this.selectedRow = ListView.NoSelection;
+ this.setNeedsUpdateCells(true);
+};
+
+/**
+ * @param {!number} row
+ * @override
+ */
+YearListView.prototype.select = function(row) {
+ if (this.selectedRow === row)
+ return;
+ this.deselect();
+ if (row === ListView.NoSelection)
+ return;
+ this.selectedRow = row;
+ if (this.selectedRow !== ListView.NoSelection) {
+ var selectedCell = this._cells[this.selectedRow];
+ this._animateRow(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));
+ }
+ this.setNeedsUpdateCells(true);
+};
+
+/**
+ * @param {!number} row
+ */
+YearListView.prototype.selectWithoutAnimating = function(row) {
+ if (this.selectedRow === row)
+ return;
+ this.deselectWithoutAnimating();
+ if (row === ListView.NoSelection)
+ return;
+ this.selectedRow = row;
+ if (this.selectedRow !== ListView.NoSelection) {
+ var selectedCell = this._cells[this.selectedRow];
+ if (selectedCell) {
+ selectedCell.setSelected(true);
+ selectedCell.setHeight(YearListCell.SelectedHeight);
+ }
+ var month = this.highlightedMonth ? this.highlightedMonth.month : 0;
+ this.highlightMonth(new Month(this.selectedRow + 1, month));
+ }
+ this.setNeedsUpdateCells(true);
+};
+
+/**
+ * @param {!Month} month
+ * @return {?HTMLDivElement}
+ */
+YearListView.prototype.buttonForMonth = function(month) {
+ if (!month)
+ return null;
+ var row = month.year - 1;
+ var cell = this.cellAtRow(row);
+ if (!cell)
+ return null;
+ return cell.monthButtons[month.month];
+};
+
+YearListView.prototype.dehighlightMonth = function() {
+ if (!this.highlightedMonth)
+ return;
+ var monthButton = this.buttonForMonth(this.highlightedMonth);
+ if (monthButton) {
+ monthButton.classList.remove(YearListCell.ClassNameHighlighted);
+ }
+ this.highlightedMonth = null;
+ this.element.removeAttribute('aria-activedescendant');
+};
+
+/**
+ * @param {!Month} month
+ */
+YearListView.prototype.highlightMonth = function(month) {
+ if (this.highlightedMonth && this.highlightedMonth.equals(month))
+ return;
+ this.dehighlightMonth();
+ this.highlightedMonth = month;
+ if (!this.highlightedMonth)
+ return;
+ var monthButton = this.buttonForMonth(this.highlightedMonth);
+ if (monthButton) {
+ monthButton.classList.add(YearListCell.ClassNameHighlighted);
+ this.element.setAttribute('aria-activedescendant', monthButton.id);
+ }
+};
+
+/**
+ * @param {!Month} month
+ */
+YearListView.prototype.show = function(month) {
+ this._ignoreMouseOutUntillNextMouseOver = true;
+
+ this.scrollToRow(month.year - 1, false);
+ this.selectWithoutAnimating(month.year - 1);
+ this.highlightMonth(month);
+};
+
+YearListView.prototype.hide = function() {
+ this.dispatchEvent(YearListView.EventTypeYearListViewDidHide, this);
+};
+
+/**
+ * @param {!Month} month
+ */
+YearListView.prototype._moveHighlightTo = function(month) {
+ this.highlightMonth(month);
+ this.select(this.highlightedMonth.year - 1);
+
+ this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, month);
+ this.scrollView.scrollTo(this.selectedRow * YearListCell.Height, true);
+ return true;
+};
+
+/**
+ * @param {?Event} event
+ */
+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 (global.params.isLocaleRTL ? key == 'ArrowRight' : key == 'ArrowLeft')
+ eventHandled = this._moveHighlightTo(this.highlightedMonth.previous());
+ else if (key == 'ArrowUp')
+ eventHandled = this._moveHighlightTo(this.highlightedMonth.previous(YearListCell.ButtonColumns));
+ else if (global.params.isLocaleRTL ? key == 'ArrowLeft' : key == 'ArrowRight')
+ eventHandled = this._moveHighlightTo(this.highlightedMonth.next());
+ else if (key == 'ArrowDown')
+ eventHandled = this._moveHighlightTo(this.highlightedMonth.next(YearListCell.ButtonColumns));
+ else if (key == 'PageUp')
+ eventHandled = this._moveHighlightTo(this.highlightedMonth.previous(MonthsPerYear));
+ else if (key == 'PageDown')
+ eventHandled = this._moveHighlightTo(this.highlightedMonth.next(MonthsPerYear));
+ else if (key == 'Enter') {
+ this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, this.highlightedMonth);
+ this.hide();
+ eventHandled = true;
+ }
+ } else if (key == 'ArrowUp') {
+ this.scrollView.scrollBy(-YearListCell.Height, true);
+ eventHandled = true;
+ } else if (key == 'ArrowDown') {
+ this.scrollView.scrollBy(YearListCell.Height, true);
+ eventHandled = true;
+ } else if (key == 'PageUp') {
+ this.scrollView.scrollBy(-this.scrollView.height(), true);
+ eventHandled = true;
+ } else if (key == 'PageDown') {
+ this.scrollView.scrollBy(this.scrollView.height(), true);
+ eventHandled = true;
+ }
+
+ if (eventHandled) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+};
+
+/**
+ * @constructor
+ * @extends View
+ * @param {!Month} minimumMonth
+ * @param {!Month} maximumMonth
+ */
+function MonthPopupView(minimumMonth, maximumMonth) {
+ View.call(this, createElement('div', MonthPopupView.ClassNameMonthPopupView));
+
+ /**
+ * @type {!YearListView}
+ * @const
+ */
+ this.yearListView = new YearListView(minimumMonth, maximumMonth);
+ this.yearListView.attachTo(this);
+
+ /**
+ * @type {!boolean}
+ */
+ this.isVisible = false;
+
+ this.element.addEventListener('click', this.onClick, false);
+}
+
+MonthPopupView.prototype = Object.create(View.prototype);
+
+MonthPopupView.ClassNameMonthPopupView = 'month-popup-view';
+
+MonthPopupView.prototype.show = function(initialMonth, calendarTableRect) {
+ this.isVisible = true;
+ document.body.appendChild(this.element);
+ this.yearListView.setWidth(calendarTableRect.width - 2);
+ this.yearListView.setHeight(YearListView.Height);
+ if (global.params.isLocaleRTL)
+ this.yearListView.element.style.right = calendarTableRect.x + 'px';
+ else
+ this.yearListView.element.style.left = calendarTableRect.x + 'px';
+ this.yearListView.element.style.top = calendarTableRect.y + 'px';
+ this.yearListView.show(initialMonth);
+ this.yearListView.element.focus();
+};
+
+MonthPopupView.prototype.hide = function() {
+ if (!this.isVisible)
+ return;
+ this.isVisible = false;
+ this.element.parentNode.removeChild(this.element);
+ this.yearListView.hide();
+};
+
+/**
+ * @param {?Event} event
+ */
+MonthPopupView.prototype.onClick = function(event) {
+ if (event.target !== this.element)
+ return;
+ this.hide();
+};
+
+/**
+ * @constructor
+ * @extends View
+ * @param {!number} maxWidth Maximum width in pixels.
+ */
+function MonthPopupButton(maxWidth) {
+ View.call(this, createElement('button', MonthPopupButton.ClassNameMonthPopupButton));
+ this.element.setAttribute('aria-label', global.params.axShowMonthSelector);
+
+ /**
+ * @type {!Element}
+ * @const
+ */
+ this.labelElement = createElement('span', MonthPopupButton.ClassNameMonthPopupButtonLabel, '-----');
+ this.element.appendChild(this.labelElement);
+
+ /**
+ * @type {!Element}
+ * @const
+ */
+ this.disclosureTriangleIcon = createElement('span', MonthPopupButton.ClassNameDisclosureTriangle);
+ this.disclosureTriangleIcon.innerHTML =
+ '<svg width=\'7\' height=\'5\'><polygon points=\'0,1 7,1 3.5,5\' style=\'fill:#000000;\' /></svg>';
+ this.element.appendChild(this.disclosureTriangleIcon);
+
+ /**
+ * @type {!boolean}
+ * @protected
+ */
+ this._useShortMonth = this._shouldUseShortMonth(maxWidth);
+ this.element.style.maxWidth = maxWidth + 'px';
+
+ this.element.addEventListener('click', this.onClick, false);
+}
+
+MonthPopupButton.prototype = Object.create(View.prototype);
+
+MonthPopupButton.ClassNameMonthPopupButton = 'month-popup-button';
+MonthPopupButton.ClassNameMonthPopupButtonLabel = 'month-popup-button-label';
+MonthPopupButton.ClassNameDisclosureTriangle = 'disclosure-triangle';
+MonthPopupButton.EventTypeButtonClick = 'buttonClick';
+
+/**
+ * @param {!number} maxWidth Maximum available width in pixels.
+ * @return {!boolean}
+ */
+MonthPopupButton.prototype._shouldUseShortMonth = function(maxWidth) {
+ document.body.appendChild(this.element);
+ var month = Month.Maximum;
+ for (var i = 0; i < MonthsPerYear; ++i) {
+ this.labelElement.textContent = month.toLocaleString();
+ if (this.element.offsetWidth > maxWidth)
+ return true;
+ month = month.previous();
+ }
+ document.body.removeChild(this.element);
+ return false;
+};
+
+/**
+ * @param {!Month} month
+ */
+MonthPopupButton.prototype.setCurrentMonth = function(month) {
+ this.labelElement.textContent = this._useShortMonth ? month.toShortLocaleString() : month.toLocaleString();
+};
+
+/**
+ * @param {?Event} event
+ */
+MonthPopupButton.prototype.onClick = function(event) {
+ this.dispatchEvent(MonthPopupButton.EventTypeButtonClick, this);
+};
+
+/**
+ * @constructor
+ * @extends View
+ */
+function CalendarNavigationButton() {
+ View.call(this, createElement('button', CalendarNavigationButton.ClassNameCalendarNavigationButton));
+ /**
+ * @type {number} Threshold for starting repeating clicks in milliseconds.
+ */
+ this.repeatingClicksStartingThreshold = CalendarNavigationButton.DefaultRepeatingClicksStartingThreshold;
+ /**
+ * @type {number} Interval between reapeating clicks in milliseconds.
+ */
+ this.reapeatingClicksInterval = CalendarNavigationButton.DefaultRepeatingClicksInterval;
+ /**
+ * @type {?number} The ID for the timeout that triggers the repeating clicks.
+ */
+ this._timer = null;
+ this.element.addEventListener('click', this.onClick, false);
+ this.element.addEventListener('mousedown', this.onMouseDown, false);
+ this.element.addEventListener('touchstart', this.onTouchStart, false);
+};
+
+CalendarNavigationButton.prototype = Object.create(View.prototype);
+
+CalendarNavigationButton.DefaultRepeatingClicksStartingThreshold = 600;
+CalendarNavigationButton.DefaultRepeatingClicksInterval = 300;
+CalendarNavigationButton.LeftMargin = 4;
+CalendarNavigationButton.Width = 24;
+CalendarNavigationButton.ClassNameCalendarNavigationButton = 'calendar-navigation-button';
+CalendarNavigationButton.EventTypeButtonClick = 'buttonClick';
+CalendarNavigationButton.EventTypeRepeatingButtonClick = 'repeatingButtonClick';
+
+/**
+ * @param {!boolean} disabled
+ */
+CalendarNavigationButton.prototype.setDisabled = function(disabled) {
+ this.element.disabled = disabled;
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarNavigationButton.prototype.onClick = function(event) {
+ this.dispatchEvent(CalendarNavigationButton.EventTypeButtonClick, this);
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarNavigationButton.prototype.onTouchStart = function(event) {
+ if (this._timer !== null)
+ return;
+ this._timer = setTimeout(this.onRepeatingClick, this.repeatingClicksStartingThreshold);
+ window.addEventListener('touchend', this.onWindowTouchEnd, false);
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarNavigationButton.prototype.onWindowTouchEnd = function(event) {
+ if (this._timer === null)
+ return;
+ clearTimeout(this._timer);
+ this._timer = null;
+ window.removeEventListener('touchend', this.onWindowMouseUp, false);
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarNavigationButton.prototype.onMouseDown = function(event) {
+ if (this._timer !== null)
+ return;
+ this._timer = setTimeout(this.onRepeatingClick, this.repeatingClicksStartingThreshold);
+ window.addEventListener('mouseup', this.onWindowMouseUp, false);
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarNavigationButton.prototype.onWindowMouseUp = function(event) {
+ if (this._timer === null)
+ return;
+ clearTimeout(this._timer);
+ this._timer = null;
+ window.removeEventListener('mouseup', this.onWindowMouseUp, false);
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarNavigationButton.prototype.onRepeatingClick = function(event) {
+ this.dispatchEvent(CalendarNavigationButton.EventTypeRepeatingButtonClick, this);
+ this._timer = setTimeout(this.onRepeatingClick, this.reapeatingClicksInterval);
+};
+
+/**
+ * @constructor
+ * @extends View
+ * @param {!CalendarPicker} calendarPicker
+ */
+function CalendarHeaderView(calendarPicker) {
+ View.call(this, createElement('div', CalendarHeaderView.ClassNameCalendarHeaderView));
+ this.calendarPicker = calendarPicker;
+ this.calendarPicker.on(CalendarPicker.EventTypeCurrentMonthChanged, this.onCurrentMonthChanged);
+
+ var titleElement = createElement('div', CalendarHeaderView.ClassNameCalendarTitle);
+ this.element.appendChild(titleElement);
+
+ /**
+ * @type {!MonthPopupButton}
+ */
+ this.monthPopupButton = new MonthPopupButton(
+ this.calendarPicker.calendarTableView.width() - CalendarTableView.BorderWidth * 2 -
+ CalendarNavigationButton.Width * 3 - CalendarNavigationButton.LeftMargin * 2);
+ this.monthPopupButton.attachTo(titleElement);
+
+ /**
+ * @type {!CalendarNavigationButton}
+ * @const
+ */
+ this._previousMonthButton = new CalendarNavigationButton();
+ this._previousMonthButton.attachTo(this);
+ this._previousMonthButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick);
+ this._previousMonthButton.on(CalendarNavigationButton.EventTypeRepeatingButtonClick, this.onNavigationButtonClick);
+ this._previousMonthButton.element.setAttribute('aria-label', global.params.axShowPreviousMonth);
+
+ /**
+ * @type {!CalendarNavigationButton}
+ * @const
+ */
+ this._todayButton = new CalendarNavigationButton();
+ this._todayButton.attachTo(this);
+ this._todayButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick);
+ this._todayButton.element.classList.add(CalendarHeaderView.ClassNameTodayButton);
+ var monthContainingToday = Month.createFromToday();
+ this._todayButton.setDisabled(
+ monthContainingToday < this.calendarPicker.minimumMonth ||
+ monthContainingToday > this.calendarPicker.maximumMonth);
+ this._todayButton.element.setAttribute('aria-label', global.params.todayLabel);
+
+ /**
+ * @type {!CalendarNavigationButton}
+ * @const
+ */
+ this._nextMonthButton = new CalendarNavigationButton();
+ this._nextMonthButton.attachTo(this);
+ this._nextMonthButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick);
+ this._nextMonthButton.on(CalendarNavigationButton.EventTypeRepeatingButtonClick, this.onNavigationButtonClick);
+ this._nextMonthButton.element.setAttribute('aria-label', global.params.axShowNextMonth);
+
+ if (global.params.isLocaleRTL) {
+ this._nextMonthButton.element.innerHTML = CalendarHeaderView._BackwardTriangle;
+ this._previousMonthButton.element.innerHTML = CalendarHeaderView._ForwardTriangle;
+ } else {
+ this._nextMonthButton.element.innerHTML = CalendarHeaderView._ForwardTriangle;
+ this._previousMonthButton.element.innerHTML = CalendarHeaderView._BackwardTriangle;
+ }
+}
+
+CalendarHeaderView.prototype = Object.create(View.prototype);
+
+CalendarHeaderView.Height = 24;
+CalendarHeaderView.BottomMargin = 10;
+CalendarHeaderView._ForwardTriangle =
+ '<svg width=\'4\' height=\'7\'><polygon points=\'0,7 0,0, 4,3.5\' style=\'fill:#6e6e6e;\' /></svg>';
+CalendarHeaderView._BackwardTriangle =
+ '<svg width=\'4\' height=\'7\'><polygon points=\'0,3.5 4,7 4,0\' style=\'fill:#6e6e6e;\' /></svg>';
+CalendarHeaderView.ClassNameCalendarHeaderView = 'calendar-header-view';
+CalendarHeaderView.ClassNameCalendarTitle = 'calendar-title';
+CalendarHeaderView.ClassNameTodayButton = 'today-button';
+
+CalendarHeaderView.prototype.onCurrentMonthChanged = function() {
+ this.monthPopupButton.setCurrentMonth(this.calendarPicker.currentMonth());
+ this._previousMonthButton.setDisabled(
+ this.disabled || this.calendarPicker.currentMonth() <= this.calendarPicker.minimumMonth);
+ this._nextMonthButton.setDisabled(
+ this.disabled || this.calendarPicker.currentMonth() >= this.calendarPicker.maximumMonth);
+};
+
+CalendarHeaderView.prototype.onNavigationButtonClick = function(sender) {
+ if (sender === this._previousMonthButton)
+ this.calendarPicker.setCurrentMonth(
+ this.calendarPicker.currentMonth().previous(), CalendarPicker.NavigationBehavior.WithAnimation);
+ else if (sender === this._nextMonthButton)
+ this.calendarPicker.setCurrentMonth(
+ this.calendarPicker.currentMonth().next(), CalendarPicker.NavigationBehavior.WithAnimation);
+ else
+ this.calendarPicker.selectRangeContainingDay(Day.createFromToday());
+};
+
+/**
+ * @param {!boolean} disabled
+ */
+CalendarHeaderView.prototype.setDisabled = function(disabled) {
+ this.disabled = disabled;
+ this.monthPopupButton.element.disabled = this.disabled;
+ this._previousMonthButton.setDisabled(
+ this.disabled || this.calendarPicker.currentMonth() <= this.calendarPicker.minimumMonth);
+ this._nextMonthButton.setDisabled(
+ this.disabled || this.calendarPicker.currentMonth() >= this.calendarPicker.maximumMonth);
+ var monthContainingToday = Month.createFromToday();
+ this._todayButton.setDisabled(
+ this.disabled || monthContainingToday < this.calendarPicker.minimumMonth ||
+ monthContainingToday > this.calendarPicker.maximumMonth);
+};
+
+/**
+ * @constructor
+ * @extends ListCell
+ */
+function DayCell() {
+ ListCell.call(this);
+ this.element.classList.add(DayCell.ClassNameDayCell);
+ this.element.style.width = DayCell.Width + 'px';
+ this.element.style.height = DayCell.Height + 'px';
+ this.element.style.lineHeight = (DayCell.Height - DayCell.PaddingSize * 2) + 'px';
+ this.element.setAttribute('role', 'gridcell');
+ /**
+ * @type {?Day}
+ */
+ this.day = null;
+};
+
+DayCell.prototype = Object.create(ListCell.prototype);
+
+DayCell.Width = 34;
+DayCell.Height = hasInaccuratePointingDevice() ? 34 : 20;
+DayCell.PaddingSize = 1;
+DayCell.ClassNameDayCell = 'day-cell';
+DayCell.ClassNameHighlighted = 'highlighted';
+DayCell.ClassNameDisabled = 'disabled';
+DayCell.ClassNameCurrentMonth = 'current-month';
+DayCell.ClassNameToday = 'today';
+
+DayCell._recycleBin = [];
+
+DayCell.recycleOrCreate = function() {
+ return DayCell._recycleBin.pop() || new DayCell();
+};
+
+/**
+ * @return {!Array}
+ * @override
+ */
+DayCell.prototype._recycleBin = function() {
+ return DayCell._recycleBin;
+};
+
+/**
+ * @override
+ */
+DayCell.prototype.throwAway = function() {
+ ListCell.prototype.throwAway.call(this);
+ this.day = null;
+};
+
+/**
+ * @param {!boolean} highlighted
+ */
+DayCell.prototype.setHighlighted = function(highlighted) {
+ if (highlighted) {
+ this.element.classList.add(DayCell.ClassNameHighlighted);
+ this.element.setAttribute('aria-selected', 'true');
+ } else {
+ this.element.classList.remove(DayCell.ClassNameHighlighted);
+ this.element.setAttribute('aria-selected', 'false');
+ }
+};
+
+/**
+ * @param {!boolean} disabled
+ */
+DayCell.prototype.setDisabled = function(disabled) {
+ if (disabled)
+ this.element.classList.add(DayCell.ClassNameDisabled);
+ else
+ this.element.classList.remove(DayCell.ClassNameDisabled);
+};
+
+/**
+ * @param {!boolean} selected
+ */
+DayCell.prototype.setIsInCurrentMonth = function(selected) {
+ if (selected)
+ this.element.classList.add(DayCell.ClassNameCurrentMonth);
+ else
+ this.element.classList.remove(DayCell.ClassNameCurrentMonth);
+};
+
+/**
+ * @param {!boolean} selected
+ */
+DayCell.prototype.setIsToday = function(selected) {
+ if (selected)
+ this.element.classList.add(DayCell.ClassNameToday);
+ else
+ this.element.classList.remove(DayCell.ClassNameToday);
+};
+
+/**
+ * @param {!Day} day
+ */
+DayCell.prototype.reset = function(day) {
+ this.day = day;
+ this.element.textContent = localizeNumber(this.day.date.toString());
+ this.element.setAttribute('aria-label', this.day.format());
+ this.element.id = this.day.toString();
+ this.show();
+};
+
+/**
+ * @constructor
+ * @extends ListCell
+ */
+function WeekNumberCell() {
+ ListCell.call(this);
+ this.element.classList.add(WeekNumberCell.ClassNameWeekNumberCell);
+ this.element.style.width = (WeekNumberCell.Width - WeekNumberCell.SeparatorWidth) + 'px';
+ this.element.style.height = WeekNumberCell.Height + 'px';
+ this.element.style.lineHeight = (WeekNumberCell.Height - WeekNumberCell.PaddingSize * 2) + 'px';
+ /**
+ * @type {?Week}
+ */
+ this.week = null;
+};
+
+WeekNumberCell.prototype = Object.create(ListCell.prototype);
+
+WeekNumberCell.Width = 48;
+WeekNumberCell.Height = DayCell.Height;
+WeekNumberCell.SeparatorWidth = 1;
+WeekNumberCell.PaddingSize = 1;
+WeekNumberCell.ClassNameWeekNumberCell = 'week-number-cell';
+WeekNumberCell.ClassNameHighlighted = 'highlighted';
+WeekNumberCell.ClassNameDisabled = 'disabled';
+
+WeekNumberCell._recycleBin = [];
+
+/**
+ * @return {!Array}
+ * @override
+ */
+WeekNumberCell.prototype._recycleBin = function() {
+ return WeekNumberCell._recycleBin;
+};
+
+/**
+ * @return {!WeekNumberCell}
+ */
+WeekNumberCell.recycleOrCreate = function() {
+ return WeekNumberCell._recycleBin.pop() || new WeekNumberCell();
+};
+
+/**
+ * @param {!Week} week
+ */
+WeekNumberCell.prototype.reset = function(week) {
+ this.week = week;
+ this.element.id = week.toString();
+ this.element.setAttribute('role', 'gridcell');
+ this.element.setAttribute(
+ 'aria-label', window.pagePopupController.formatWeek(week.year, week.week, week.firstDay().format()));
+ this.element.textContent = localizeNumber(this.week.week.toString());
+ this.show();
+};
+
+/**
+ * @override
+ */
+WeekNumberCell.prototype.throwAway = function() {
+ ListCell.prototype.throwAway.call(this);
+ this.week = null;
+};
+
+WeekNumberCell.prototype.setHighlighted = function(highlighted) {
+ if (highlighted) {
+ this.element.classList.add(WeekNumberCell.ClassNameHighlighted);
+ this.element.setAttribute('aria-selected', 'true');
+ } else {
+ this.element.classList.remove(WeekNumberCell.ClassNameHighlighted);
+ this.element.setAttribute('aria-selected', 'false');
+ }
+};
+
+WeekNumberCell.prototype.setDisabled = function(disabled) {
+ if (disabled)
+ this.element.classList.add(WeekNumberCell.ClassNameDisabled);
+ else
+ this.element.classList.remove(WeekNumberCell.ClassNameDisabled);
+};
+
+/**
+ * @constructor
+ * @extends View
+ * @param {!boolean} hasWeekNumberColumn
+ */
+function CalendarTableHeaderView(hasWeekNumberColumn) {
+ View.call(this, createElement('div', 'calendar-table-header-view'));
+ if (hasWeekNumberColumn) {
+ var weekNumberLabelElement = createElement('div', 'week-number-label', global.params.weekLabel);
+ weekNumberLabelElement.style.width = WeekNumberCell.Width + 'px';
+ this.element.appendChild(weekNumberLabelElement);
+ }
+ for (var i = 0; i < DaysPerWeek; ++i) {
+ var weekDayNumber = (global.params.weekStartDay + i) % DaysPerWeek;
+ var labelElement = createElement('div', 'week-day-label', global.params.dayLabels[weekDayNumber]);
+ labelElement.style.width = DayCell.Width + 'px';
+ this.element.appendChild(labelElement);
+ if (getLanguage() === 'ja') {
+ if (weekDayNumber === 0)
+ labelElement.style.color = 'red';
+ else if (weekDayNumber === 6)
+ labelElement.style.color = 'blue';
+ }
+ }
+}
+
+CalendarTableHeaderView.prototype = Object.create(View.prototype);
+
+CalendarTableHeaderView.Height = 25;
+
+/**
+ * @constructor
+ * @extends ListCell
+ */
+function CalendarRowCell() {
+ ListCell.call(this);
+ this.element.classList.add(CalendarRowCell.ClassNameCalendarRowCell);
+ this.element.style.height = CalendarRowCell.Height + 'px';
+ this.element.setAttribute('role', 'row');
+
+ /**
+ * @type {!Array}
+ * @protected
+ */
+ this._dayCells = [];
+ /**
+ * @type {!number}
+ */
+ this.row = 0;
+ /**
+ * @type {?CalendarTableView}
+ */
+ this.calendarTableView = null;
+}
+
+CalendarRowCell.prototype = Object.create(ListCell.prototype);
+
+CalendarRowCell.Height = DayCell.Height;
+CalendarRowCell.ClassNameCalendarRowCell = 'calendar-row-cell';
+
+CalendarRowCell._recycleBin = [];
+
+/**
+ * @return {!Array}
+ * @override
+ */
+CalendarRowCell.prototype._recycleBin = function() {
+ return CalendarRowCell._recycleBin;
+};
+
+/**
+ * @param {!number} row
+ * @param {!CalendarTableView} calendarTableView
+ */
+CalendarRowCell.prototype.reset = function(row, calendarTableView) {
+ this.row = row;
+ this.calendarTableView = calendarTableView;
+ if (this.calendarTableView.hasWeekNumberColumn) {
+ var middleDay = this.calendarTableView.dayAtColumnAndRow(3, row);
+ var week = Week.createFromDay(middleDay);
+ this.weekNumberCell = this.calendarTableView.prepareNewWeekNumberCell(week);
+ this.weekNumberCell.attachTo(this);
+ }
+ var day = calendarTableView.dayAtColumnAndRow(0, row);
+ for (var i = 0; i < DaysPerWeek; ++i) {
+ var dayCell = this.calendarTableView.prepareNewDayCell(day);
+ dayCell.attachTo(this);
+ this._dayCells.push(dayCell);
+ day = day.next();
+ }
+ this.show();
+};
+
+/**
+ * @override
+ */
+CalendarRowCell.prototype.throwAway = function() {
+ ListCell.prototype.throwAway.call(this);
+ if (this.weekNumberCell)
+ this.calendarTableView.throwAwayWeekNumberCell(this.weekNumberCell);
+ this._dayCells.forEach(this.calendarTableView.throwAwayDayCell, this.calendarTableView);
+ this._dayCells.length = 0;
+};
+
+/**
+ * @constructor
+ * @extends ListView
+ * @param {!CalendarPicker} calendarPicker
+ */
+function CalendarTableView(calendarPicker) {
+ ListView.call(this);
+ this.element.classList.add(CalendarTableView.ClassNameCalendarTableView);
+ this.element.tabIndex = 0;
+
+ /**
+ * @type {!boolean}
+ * @const
+ */
+ this.hasWeekNumberColumn = calendarPicker.type === 'week';
+ /**
+ * @type {!CalendarPicker}
+ * @const
+ */
+ this.calendarPicker = calendarPicker;
+ /**
+ * @type {!Object}
+ * @const
+ */
+ this._dayCells = {};
+ var headerView = new CalendarTableHeaderView(this.hasWeekNumberColumn);
+ headerView.attachTo(this, this.scrollView);
+
+ if (this.hasWeekNumberColumn) {
+ this.setWidth(DayCell.Width * DaysPerWeek + WeekNumberCell.Width);
+ /**
+ * @type {?Array}
+ * @const
+ */
+ this._weekNumberCells = [];
+ } else {
+ this.setWidth(DayCell.Width * DaysPerWeek);
+ }
+
+ /**
+ * @type {!boolean}
+ * @protected
+ */
+ this._ignoreMouseOutUntillNextMouseOver = false;
+
+ this.element.addEventListener('click', this.onClick, false);
+ 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('mousewheel', this.scrollView.onMouseWheel, false);
+ // You shouldn't be able to do gesture scroll.
+ this.scrollView.element.removeEventListener('touchstart', this.scrollView.onTouchStart, false);
+}
+
+CalendarTableView.prototype = Object.create(ListView.prototype);
+
+CalendarTableView.BorderWidth = 1;
+CalendarTableView.ClassNameCalendarTableView = 'calendar-table-view';
+
+/**
+ * @param {!number} scrollOffset
+ * @return {!number}
+ */
+CalendarTableView.prototype.rowAtScrollOffset = function(scrollOffset) {
+ return Math.floor(scrollOffset / CalendarRowCell.Height);
+};
+
+/**
+ * @param {!number} row
+ * @return {!number}
+ */
+CalendarTableView.prototype.scrollOffsetForRow = function(row) {
+ return row * CalendarRowCell.Height;
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarTableView.prototype.onClick = function(event) {
+ if (this.hasWeekNumberColumn) {
+ var weekNumberCellElement = enclosingNodeOrSelfWithClass(event.target, WeekNumberCell.ClassNameWeekNumberCell);
+ if (weekNumberCellElement) {
+ var weekNumberCell = weekNumberCellElement.$view;
+ this.calendarPicker.selectRangeContainingDay(weekNumberCell.week.firstDay());
+ return;
+ }
+ }
+ var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell);
+ if (!dayCellElement)
+ return;
+ var dayCell = dayCellElement.$view;
+ this.calendarPicker.selectRangeContainingDay(dayCell.day);
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarTableView.prototype.onMouseOver = function(event) {
+ if (this.hasWeekNumberColumn) {
+ var weekNumberCellElement = enclosingNodeOrSelfWithClass(event.target, WeekNumberCell.ClassNameWeekNumberCell);
+ if (weekNumberCellElement) {
+ var weekNumberCell = weekNumberCellElement.$view;
+ this.calendarPicker.highlightRangeContainingDay(weekNumberCell.week.firstDay());
+ this._ignoreMouseOutUntillNextMouseOver = false;
+ return;
+ }
+ }
+ var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell);
+ if (!dayCellElement)
+ return;
+ var dayCell = dayCellElement.$view;
+ this.calendarPicker.highlightRangeContainingDay(dayCell.day);
+ this._ignoreMouseOutUntillNextMouseOver = false;
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarTableView.prototype.onMouseOut = function(event) {
+ if (this._ignoreMouseOutUntillNextMouseOver)
+ return;
+ var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell);
+ if (!dayCellElement) {
+ this.calendarPicker.highlightRangeContainingDay(null);
+ }
+};
+
+/**
+ * @param {!number} row
+ * @return {!CalendarRowCell}
+ */
+CalendarTableView.prototype.prepareNewCell = function(row) {
+ var cell = CalendarRowCell._recycleBin.pop() || new CalendarRowCell();
+ cell.reset(row, this);
+ return cell;
+};
+
+/**
+ * @return {!number} Height in pixels.
+ */
+CalendarTableView.prototype.height = function() {
+ return this.scrollView.height() + CalendarTableHeaderView.Height + CalendarTableView.BorderWidth * 2;
+};
+
+/**
+ * @param {!number} height Height in pixels.
+ */
+CalendarTableView.prototype.setHeight = function(height) {
+ this.scrollView.setHeight(height - CalendarTableHeaderView.Height - CalendarTableView.BorderWidth * 2);
+};
+
+/**
+ * @param {!Month} month
+ * @param {!boolean} animate
+ */
+CalendarTableView.prototype.scrollToMonth = function(month, animate) {
+ var rowForFirstDayInMonth = this.columnAndRowForDay(month.firstDay()).row;
+ this.scrollView.scrollTo(this.scrollOffsetForRow(rowForFirstDayInMonth), animate);
+};
+
+/**
+ * @param {!number} column
+ * @param {!number} row
+ * @return {!Day}
+ */
+CalendarTableView.prototype.dayAtColumnAndRow = function(column, row) {
+ var daysSinceMinimum = row * DaysPerWeek + column + global.params.weekStartDay - CalendarTableView._MinimumDayWeekDay;
+ return Day.createFromValue(daysSinceMinimum * MillisecondsPerDay + CalendarTableView._MinimumDayValue);
+};
+
+CalendarTableView._MinimumDayValue = Day.Minimum.valueOf();
+CalendarTableView._MinimumDayWeekDay = Day.Minimum.weekDay();
+
+/**
+ * @param {!Day} day
+ * @return {!Object} Object with properties column and row.
+ */
+CalendarTableView.prototype.columnAndRowForDay = function(day) {
+ var daysSinceMinimum = (day.valueOf() - CalendarTableView._MinimumDayValue) / MillisecondsPerDay;
+ var offset = daysSinceMinimum + CalendarTableView._MinimumDayWeekDay - global.params.weekStartDay;
+ var row = Math.floor(offset / DaysPerWeek);
+ var column = offset - row * DaysPerWeek;
+ return {column: column, row: row};
+};
+
+CalendarTableView.prototype.updateCells = function() {
+ ListView.prototype.updateCells.call(this);
+
+ var selection = this.calendarPicker.selection();
+ var firstDayInSelection;
+ var lastDayInSelection;
+ if (selection) {
+ firstDayInSelection = selection.firstDay().valueOf();
+ lastDayInSelection = selection.lastDay().valueOf();
+ } else {
+ firstDayInSelection = Infinity;
+ lastDayInSelection = Infinity;
+ }
+ var highlight = this.calendarPicker.highlight();
+ var firstDayInHighlight;
+ var lastDayInHighlight;
+ if (highlight) {
+ firstDayInHighlight = highlight.firstDay().valueOf();
+ lastDayInHighlight = highlight.lastDay().valueOf();
+ } else {
+ firstDayInHighlight = Infinity;
+ lastDayInHighlight = Infinity;
+ }
+ var currentMonth = this.calendarPicker.currentMonth();
+ var firstDayInCurrentMonth = currentMonth.firstDay().valueOf();
+ var lastDayInCurrentMonth = currentMonth.lastDay().valueOf();
+ var activeCell = null;
+ for (var dayString in this._dayCells) {
+ 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)
+ activeCell = dayCell;
+ }
+ dayCell.setIsInCurrentMonth(day >= firstDayInCurrentMonth && day <= lastDayInCurrentMonth);
+ dayCell.setDisabled(!this.calendarPicker.isValidDay(day));
+ }
+ if (this.hasWeekNumberColumn) {
+ 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;
+ weekNumberCell.setDisabled(!this.calendarPicker.isValid(week));
+ }
+ }
+ if (activeCell) {
+ // Ensure a layoutObject because an element with no layoutObject doesn't post
+ // activedescendant events. This shouldn't run in the above |for| loop
+ // to avoid CSS transition.
+ activeCell.element.offsetLeft;
+ this.element.setAttribute('aria-activedescendant', activeCell.element.id);
+ }
+};
+
+/**
+ * @param {!Day} day
+ * @return {!DayCell}
+ */
+CalendarTableView.prototype.prepareNewDayCell = function(day) {
+ var dayCell = DayCell.recycleOrCreate();
+ dayCell.reset(day);
+ if (this.calendarPicker.type == 'month')
+ dayCell.element.setAttribute('aria-label', Month.createFromDay(day).toLocaleString());
+ this._dayCells[dayCell.day.toString()] = dayCell;
+ return dayCell;
+};
+
+/**
+ * @param {!Week} week
+ * @return {!WeekNumberCell}
+ */
+CalendarTableView.prototype.prepareNewWeekNumberCell = function(week) {
+ var weekNumberCell = WeekNumberCell.recycleOrCreate();
+ weekNumberCell.reset(week);
+ this._weekNumberCells[weekNumberCell.week.toString()] = weekNumberCell;
+ return weekNumberCell;
+};
+
+/**
+ * @param {!DayCell} dayCell
+ */
+CalendarTableView.prototype.throwAwayDayCell = function(dayCell) {
+ delete this._dayCells[dayCell.day.toString()];
+ dayCell.throwAway();
+};
+
+/**
+ * @param {!WeekNumberCell} weekNumberCell
+ */
+CalendarTableView.prototype.throwAwayWeekNumberCell = function(weekNumberCell) {
+ delete this._weekNumberCells[weekNumberCell.week.toString()];
+ weekNumberCell.throwAway();
+};
+
+/**
+ * @constructor
+ * @extends View
+ * @param {!Object} config
+ */
+function CalendarPicker(type, config) {
+ View.call(this, createElement('div', CalendarPicker.ClassNameCalendarPicker));
+ this.element.classList.add(CalendarPicker.ClassNamePreparing);
+
+ /**
+ * @type {!string}
+ * @const
+ */
+ this.type = type;
+ if (this.type === 'week')
+ this._dateTypeConstructor = Week;
+ else if (this.type === 'month')
+ this._dateTypeConstructor = Month;
+ else
+ this._dateTypeConstructor = Day;
+ /**
+ * @type {!Object}
+ * @const
+ */
+ this.config = {};
+ this._setConfig(config);
+ /**
+ * @type {!Month}
+ * @const
+ */
+ this.minimumMonth = Month.createFromDay(this.config.minimum.firstDay());
+ /**
+ * @type {!Month}
+ * @const
+ */
+ this.maximumMonth = Month.createFromDay(this.config.maximum.lastDay());
+ if (global.params.isLocaleRTL)
+ this.element.classList.add('rtl');
+ /**
+ * @type {!CalendarTableView}
+ * @const
+ */
+ this.calendarTableView = new CalendarTableView(this);
+ this.calendarTableView.hasNumberColumn = this.type === 'week';
+ /**
+ * @type {!CalendarHeaderView}
+ * @const
+ */
+ this.calendarHeaderView = new CalendarHeaderView(this);
+ this.calendarHeaderView.monthPopupButton.on(MonthPopupButton.EventTypeButtonClick, this.onMonthPopupButtonClick);
+ /**
+ * @type {!MonthPopupView}
+ * @const
+ */
+ this.monthPopupView = new MonthPopupView(this.minimumMonth, this.maximumMonth);
+ this.monthPopupView.yearListView.on(
+ YearListView.EventTypeYearListViewDidSelectMonth, this.onYearListViewDidSelectMonth);
+ this.monthPopupView.yearListView.on(YearListView.EventTypeYearListViewDidHide, this.onYearListViewDidHide);
+ this.calendarHeaderView.attachTo(this);
+ this.calendarTableView.attachTo(this);
+ /**
+ * @type {!Month}
+ * @protected
+ */
+ this._currentMonth = new Month(NaN, NaN);
+ /**
+ * @type {?DateType}
+ * @protected
+ */
+ this._selection = null;
+ /**
+ * @type {?DateType}
+ * @protected
+ */
+ this._highlight = null;
+ this.calendarTableView.element.addEventListener('keydown', this.onCalendarTableKeyDown, false);
+ document.body.addEventListener('keydown', this.onBodyKeyDown, false);
+
+ window.addEventListener('resize', this.onWindowResize, false);
+
+ /**
+ * @type {!number}
+ * @protected
+ */
+ this._height = -1;
+
+ var initialSelection = parseDateString(config.currentValue);
+ if (initialSelection) {
+ this.setCurrentMonth(Month.createFromDay(initialSelection.middleDay()), CalendarPicker.NavigationBehavior.None);
+ this.setSelection(initialSelection);
+ } else
+ this.setCurrentMonth(Month.createFromToday(), CalendarPicker.NavigationBehavior.None);
+}
+
+CalendarPicker.prototype = Object.create(View.prototype);
+
+CalendarPicker.Padding = 10;
+CalendarPicker.BorderWidth = 1;
+CalendarPicker.ClassNameCalendarPicker = 'calendar-picker';
+CalendarPicker.ClassNamePreparing = 'preparing';
+CalendarPicker.EventTypeCurrentMonthChanged = 'currentMonthChanged';
+CalendarPicker.commitDelayMs = 100;
+
+/**
+ * @param {!Event} event
+ */
+CalendarPicker.prototype.onWindowResize = function(event) {
+ this.element.classList.remove(CalendarPicker.ClassNamePreparing);
+ window.removeEventListener('resize', this.onWindowResize, false);
+};
+
+/**
+ * @param {!YearListView} sender
+ */
+CalendarPicker.prototype.onYearListViewDidHide = function(sender) {
+ this.monthPopupView.hide();
+ this.calendarHeaderView.setDisabled(false);
+ this.adjustHeight();
+};
+
+/**
+ * @param {!YearListView} sender
+ * @param {!Month} month
+ */
+CalendarPicker.prototype.onYearListViewDidSelectMonth = function(sender, month) {
+ this.setCurrentMonth(month, CalendarPicker.NavigationBehavior.None);
+};
+
+/**
+ * @param {!View|Node} parent
+ * @param {?View|Node=} before
+ * @override
+ */
+CalendarPicker.prototype.attachTo = function(parent, before) {
+ View.prototype.attachTo.call(this, parent, before);
+ this.calendarTableView.element.focus();
+};
+
+CalendarPicker.prototype.cleanup = function() {
+ window.removeEventListener('resize', this.onWindowResize, false);
+ this.calendarTableView.element.removeEventListener('keydown', this.onBodyKeyDown, false);
+ // Month popup view might be attached to document.body.
+ this.monthPopupView.hide();
+};
+
+/**
+ * @param {?MonthPopupButton} sender
+ */
+CalendarPicker.prototype.onMonthPopupButtonClick = function(sender) {
+ var clientRect = this.calendarTableView.element.getBoundingClientRect();
+ var calendarTableRect = new Rectangle(
+ clientRect.left + document.body.scrollLeft, clientRect.top + document.body.scrollTop, clientRect.width,
+ clientRect.height);
+ this.monthPopupView.show(this.currentMonth(), calendarTableRect);
+ this.calendarHeaderView.setDisabled(true);
+ this.adjustHeight();
+};
+
+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}
+ */
+CalendarPicker.prototype.currentMonth = function() {
+ return this._currentMonth;
+};
+
+/**
+ * @enum {number}
+ */
+CalendarPicker.NavigationBehavior = {
+ None: 0,
+ WithAnimation: 1
+};
+
+/**
+ * @param {!Month} month
+ * @param {!CalendarPicker.NavigationBehavior} animate
+ */
+CalendarPicker.prototype.setCurrentMonth = function(month, behavior) {
+ if (month > this.maximumMonth)
+ month = this.maximumMonth;
+ else if (month < this.minimumMonth)
+ month = this.minimumMonth;
+ if (this._currentMonth.equals(month))
+ return;
+ this._currentMonth = month;
+ this.calendarTableView.scrollToMonth(
+ this._currentMonth, behavior === CalendarPicker.NavigationBehavior.WithAnimation);
+ this.adjustHeight();
+ this.calendarTableView.setNeedsUpdateCells(true);
+ this.dispatchEvent(CalendarPicker.EventTypeCurrentMonthChanged, {target: this});
+};
+
+CalendarPicker.prototype.adjustHeight = function() {
+ var rowForFirstDayInMonth = this.calendarTableView.columnAndRowForDay(this._currentMonth.firstDay()).row;
+ var rowForLastDayInMonth = this.calendarTableView.columnAndRowForDay(this._currentMonth.lastDay()).row;
+ var numberOfRows = rowForLastDayInMonth - rowForFirstDayInMonth + 1;
+ var calendarTableViewHeight =
+ CalendarTableHeaderView.Height + numberOfRows * DayCell.Height + CalendarTableView.BorderWidth * 2;
+ var height = (this.monthPopupView.isVisible ? YearListView.Height : calendarTableViewHeight) +
+ CalendarHeaderView.Height + CalendarHeaderView.BottomMargin + CalendarPicker.Padding * 2 +
+ CalendarPicker.BorderWidth * 2;
+ this.setHeight(height);
+};
+
+CalendarPicker.prototype.selection = function() {
+ return this._selection;
+};
+
+CalendarPicker.prototype.highlight = function() {
+ return this._highlight;
+};
+
+/**
+ * @return {!Day}
+ */
+CalendarPicker.prototype.firstVisibleDay = function() {
+ var firstVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().firstDay()).row;
+ var firstVisibleDay = this.calendarTableView.dayAtColumnAndRow(0, firstVisibleRow);
+ if (!firstVisibleDay)
+ firstVisibleDay = Day.Minimum;
+ return firstVisibleDay;
+};
+
+/**
+ * @return {!Day}
+ */
+CalendarPicker.prototype.lastVisibleDay = function() {
+ var lastVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().lastDay()).row;
+ var lastVisibleDay = this.calendarTableView.dayAtColumnAndRow(DaysPerWeek - 1, lastVisibleRow);
+ if (!lastVisibleDay)
+ lastVisibleDay = Day.Maximum;
+ return lastVisibleDay;
+};
+
+/**
+ * @param {?Day} day
+ */
+CalendarPicker.prototype.selectRangeContainingDay = function(day) {
+ var selection = day ? this._dateTypeConstructor.createFromDay(day) : null;
+ this.setSelectionAndCommit(selection);
+};
+
+/**
+ * @param {?Day} day
+ */
+CalendarPicker.prototype.highlightRangeContainingDay = function(day) {
+ var highlight = day ? this._dateTypeConstructor.createFromDay(day) : null;
+ this._setHighlight(highlight);
+};
+
+/**
+ * Select the specified date.
+ * @param {?DateType} dayOrWeekOrMonth
+ */
+CalendarPicker.prototype.setSelection = function(dayOrWeekOrMonth) {
+ if (!this._selection && !dayOrWeekOrMonth)
+ return;
+ if (this._selection && this._selection.equals(dayOrWeekOrMonth))
+ return;
+ var firstDayInSelection = dayOrWeekOrMonth.firstDay();
+ var lastDayInSelection = dayOrWeekOrMonth.lastDay();
+ var candidateCurrentMonth = Month.createFromDay(firstDayInSelection);
+ if (this.firstVisibleDay() > lastDayInSelection || this.lastVisibleDay() < firstDayInSelection) {
+ // Change current month if the selection is not visible at all.
+ this.setCurrentMonth(candidateCurrentMonth, CalendarPicker.NavigationBehavior.WithAnimation);
+ } else if (this.firstVisibleDay() < firstDayInSelection || this.lastVisibleDay() > lastDayInSelection) {
+ // If the selection is partly visible, only change the current month if
+ // doing so will make the whole selection visible.
+ var firstVisibleRow = this.calendarTableView.columnAndRowForDay(candidateCurrentMonth.firstDay()).row;
+ var firstVisibleDay = this.calendarTableView.dayAtColumnAndRow(0, firstVisibleRow);
+ var lastVisibleRow = this.calendarTableView.columnAndRowForDay(candidateCurrentMonth.lastDay()).row;
+ var lastVisibleDay = this.calendarTableView.dayAtColumnAndRow(DaysPerWeek - 1, lastVisibleRow);
+ if (firstDayInSelection >= firstVisibleDay && lastDayInSelection <= lastVisibleDay)
+ this.setCurrentMonth(candidateCurrentMonth, CalendarPicker.NavigationBehavior.WithAnimation);
+ }
+ this._setHighlight(dayOrWeekOrMonth);
+ if (!this.isValid(dayOrWeekOrMonth))
+ return;
+ this._selection = dayOrWeekOrMonth;
+ this.calendarTableView.setNeedsUpdateCells(true);
+};
+
+/**
+ * Select the specified date, commit it, and close the popup.
+ * @param {?DateType} dayOrWeekOrMonth
+ */
+CalendarPicker.prototype.setSelectionAndCommit = function(dayOrWeekOrMonth) {
+ this.setSelection(dayOrWeekOrMonth);
+ // Redraw the widget immidiately, and wait for some time to give feedback to
+ // a user.
+ this.element.offsetLeft;
+ var value = this._selection.toString();
+ if (CalendarPicker.commitDelayMs == 0) {
+ // For testing.
+ window.pagePopupController.setValueAndClosePopup(0, value);
+ } else if (CalendarPicker.commitDelayMs < 0) {
+ // For testing.
+ window.pagePopupController.setValue(value);
+ } else {
+ setTimeout(function() {
+ window.pagePopupController.setValueAndClosePopup(0, value);
+ }, CalendarPicker.commitDelayMs);
+ }
+};
+
+/**
+ * @param {?DateType} dayOrWeekOrMonth
+ */
+CalendarPicker.prototype._setHighlight = function(dayOrWeekOrMonth) {
+ if (!this._highlight && !dayOrWeekOrMonth)
+ return;
+ if (!dayOrWeekOrMonth && !this._highlight)
+ return;
+ if (this._highlight && this._highlight.equals(dayOrWeekOrMonth))
+ return;
+ this._highlight = dayOrWeekOrMonth;
+ this.calendarTableView.setNeedsUpdateCells(true);
+};
+
+/**
+ * @param {!number} value
+ * @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;
+};
+
+/**
+ * @param {!number} value
+ * @return {!boolean}
+ */
+CalendarPicker.prototype._outOfRange = function(value) {
+ return value < this.config.minimumValue || value > this.config.maximumValue;
+};
+
+/**
+ * @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);
+};
+
+/**
+ * @param {!Day} day
+ * @return {!boolean}
+ */
+CalendarPicker.prototype.isValidDay = function(day) {
+ return this.isValid(this._dateTypeConstructor.createFromDay(day));
+};
+
+/**
+ * @param {!DateType} dateRange
+ * @return {!boolean} Returns true if the highlight was changed.
+ */
+CalendarPicker.prototype._moveHighlight = function(dateRange) {
+ if (!dateRange)
+ return false;
+ if (this._outOfRange(dateRange.valueOf()))
+ return false;
+ if (this.firstVisibleDay() > dateRange.middleDay() || this.lastVisibleDay() < dateRange.middleDay())
+ this.setCurrentMonth(Month.createFromDay(dateRange.middleDay()), CalendarPicker.NavigationBehavior.WithAnimation);
+ this._setHighlight(dateRange);
+ return true;
+};
+
+/**
+ * @param {?Event} event
+ */
+CalendarPicker.prototype.onCalendarTableKeyDown = function(event) {
+ var key = event.key;
+ var eventHandled = false;
+ if (key == 't') {
+ this.selectRangeContainingDay(Day.createFromToday());
+ eventHandled = true;
+ } else if (key == 'PageUp') {
+ var previousMonth = this.currentMonth().previous();
+ if (previousMonth && previousMonth >= this.config.minimumValue) {
+ this.setCurrentMonth(previousMonth, CalendarPicker.NavigationBehavior.WithAnimation);
+ eventHandled = true;
+ }
+ } else if (key == 'PageDown') {
+ var nextMonth = this.currentMonth().next();
+ if (nextMonth && nextMonth >= this.config.minimumValue) {
+ this.setCurrentMonth(nextMonth, CalendarPicker.NavigationBehavior.WithAnimation);
+ eventHandled = true;
+ }
+ } else if (this._highlight) {
+ 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' ? DaysPerWeek : 1));
+ } 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' ? DaysPerWeek : 1));
+ } else if (key == 'Enter') {
+ this.setSelectionAndCommit(this._highlight);
+ }
+ } else if (key == 'ArrowLeft' || key == 'ArrowUp' || key == 'ArrowRight' || key == 'ArrowDown') {
+ // Highlight range near the middle.
+ this.highlightRangeContainingDay(this.currentMonth().middleDay());
+ eventHandled = true;
+ }
+
+ if (eventHandled) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+};
+
+/**
+ * @return {!number} Width in pixels.
+ */
+CalendarPicker.prototype.width = function() {
+ return this.calendarTableView.width() +
+ (CalendarTableView.BorderWidth + CalendarPicker.BorderWidth + CalendarPicker.Padding) * 2;
+};
+
+/**
+ * @return {!number} Height in pixels.
+ */
+CalendarPicker.prototype.height = function() {
+ return this._height;
+};
+
+/**
+ * @param {!number} height Height in pixels.
+ */
+CalendarPicker.prototype.setHeight = function(height) {
+ if (this._height === height)
+ return;
+ this._height = height;
+ resizeWindow(this.width(), this._height);
+ this.calendarTableView.setHeight(
+ this._height - CalendarHeaderView.Height - CalendarHeaderView.BottomMargin - CalendarPicker.Padding * 2 -
+ CalendarTableView.BorderWidth * 2);
+};
+
+/**
+ * @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;
+ break;
+ case 'm':
+ case 'M':
+ offset = offset || 1; // Fall-through.
+ case 'y':
+ case 'Y':
+ 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));
+ }
+ eventHandled = true;
+ break;
+ }
+ if (eventHandled) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+};
+
+if (window.dialogArguments) {
+ initialize(dialogArguments);
+} else {
+ window.addEventListener('message', handleMessage, false);
+}
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
new file mode 100644
index 00000000000..eda7bbbc068
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+body {
+ -webkit-user-select: none;
+ background-color: white;
+ font: -webkit-small-control;
+ margin: 0;
+ overflow: hidden;
+}
+
+#main {
+ background-color: white;
+ border: solid 1px #8899aa;
+ box-shadow: inset 2px 2px 2px white,
+ inset -2px -2px 1px rgba(0,0,0,0.1);
+ padding: 6px;
+ float: left;
+}
+
+.color-swatch {
+ float: left;
+ width: 20px;
+ height: 20px;
+ margin: 1px;
+ padding: 0;
+ border: 1px solid #e0e0e0;
+ border-radius: 0;
+ box-sizing: content-box;
+}
+
+.color-swatch:focus {
+ border: 1px solid #000000;
+ outline: none;
+}
+
+.color-swatch-container {
+ width: 100%;
+ max-height: 104px;
+ overflow: auto;
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+}
+
+.other-color {
+ width: 100%;
+ margin: 4px 0 0 0;
+}
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.js
new file mode 100644
index 00000000000..aa2477f1381
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.js
@@ -0,0 +1,185 @@
+'use strict';
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+var global = {argumentsReceived: false, params: null};
+
+/**
+ * @param {Event} event
+ */
+function handleMessage(event) {
+ initialize(JSON.parse(event.data));
+ global.argumentsReceived = true;
+}
+
+/**
+ * @param {!Object} args
+ */
+function initialize(args) {
+ global.params = args;
+ var main = $('main');
+ main.innerHTML = '';
+ var errorString = validateArguments(args);
+ if (errorString) {
+ main.textContent = 'Internal error: ' + errorString;
+ resizeWindow(main.offsetWidth, main.offsetHeight);
+ } else
+ new ColorPicker(main, args);
+}
+
+// The DefaultColorPalette is used when the list of values are empty.
+var DefaultColorPalette = [
+ '#000000', '#404040', '#808080', '#c0c0c0', '#ffffff', '#980000', '#ff0000', '#ff9900', '#ffff00', '#00ff00',
+ '#00ffff', '#4a86e8', '#0000ff', '#9900ff', '#ff00ff'
+];
+
+function handleArgumentsTimeout() {
+ if (global.argumentsReceived)
+ return;
+ var args = {values: DefaultColorPalette, otherColorLabel: 'Other...'};
+ initialize(args);
+}
+
+/**
+ * @param {!Object} args
+ * @return {?string} An error message, or null if the argument has no errors.
+ */
+function validateArguments(args) {
+ if (!args.values)
+ return 'No values.';
+ if (!args.otherColorLabel)
+ return 'No otherColorLabel.';
+ return null;
+}
+
+function ColorPicker(element, config) {
+ Picker.call(this, element, config);
+ this._config = config;
+ if (this._config.values.length === 0)
+ this._config.values = DefaultColorPalette;
+ this._container = null;
+ this._layout();
+ document.body.addEventListener('keydown', this._handleKeyDown.bind(this));
+ this._element.addEventListener('mousemove', this._handleMouseMove.bind(this));
+ this._element.addEventListener('mousedown', this._handleMouseDown.bind(this));
+}
+ColorPicker.prototype = Object.create(Picker.prototype);
+
+var SwatchBorderBoxWidth = 24; // keep in sync with CSS
+var SwatchBorderBoxHeight = 24; // keep in sync with CSS
+var SwatchesPerRow = 5;
+var SwatchesMaxRow = 4;
+
+ColorPicker.prototype._layout = function() {
+ var container = createElement('div', 'color-swatch-container');
+ container.addEventListener('click', this._handleSwatchClick.bind(this), false);
+ for (var i = 0; i < this._config.values.length; ++i) {
+ var swatch = createElement('button', 'color-swatch');
+ swatch.dataset.index = i;
+ swatch.dataset.value = this._config.values[i];
+ swatch.title = this._config.values[i];
+ swatch.style.backgroundColor = this._config.values[i];
+ container.appendChild(swatch);
+ }
+ var containerWidth = SwatchBorderBoxWidth * SwatchesPerRow;
+ if (this._config.values.length > SwatchesPerRow * SwatchesMaxRow)
+ containerWidth += getScrollbarWidth();
+ container.style.width = containerWidth + 'px';
+ container.style.maxHeight = (SwatchBorderBoxHeight * SwatchesMaxRow) + 'px';
+ this._element.appendChild(container);
+ var otherButton = createElement('button', 'other-color', this._config.otherColorLabel);
+ otherButton.addEventListener('click', this.chooseOtherColor.bind(this), false);
+ this._element.appendChild(otherButton);
+ this._container = container;
+ this._otherButton = otherButton;
+ var elementWidth = this._element.offsetWidth;
+ var elementHeight = this._element.offsetHeight;
+ resizeWindow(elementWidth, elementHeight);
+};
+
+ColorPicker.prototype.selectColorAtIndex = function(index) {
+ index = Math.max(Math.min(this._container.childNodes.length - 1, index), 0);
+ this._container.childNodes[index].focus();
+};
+
+ColorPicker.prototype._handleMouseMove = function(event) {
+ if (event.target.classList.contains('color-swatch'))
+ event.target.focus();
+};
+
+ColorPicker.prototype._handleMouseDown = function(event) {
+ // Prevent blur.
+ if (event.target.classList.contains('color-swatch'))
+ event.preventDefault();
+};
+
+ColorPicker.prototype._handleKeyDown = function(event) {
+ var key = event.key;
+ if (key === 'Escape')
+ this.handleCancel();
+ else if (key == 'ArrowLeft' || key == 'ArrowUp' || key == 'ArrowRight' || key == 'ArrowDown') {
+ var selectedElement = document.activeElement;
+ var index = 0;
+ if (selectedElement.classList.contains('other-color')) {
+ if (key != 'ArrowRight' && key != 'ArrowUp')
+ return;
+ index = this._container.childNodes.length - 1;
+ } else if (selectedElement.classList.contains('color-swatch')) {
+ index = parseInt(selectedElement.dataset.index, 10);
+ switch (key) {
+ case 'ArrowLeft':
+ index--;
+ break;
+ case 'ArrowRight':
+ index++;
+ break;
+ case 'ArrowUp':
+ index -= SwatchesPerRow;
+ break;
+ case 'ArrowDown':
+ index += SwatchesPerRow;
+ break;
+ }
+ if (index > this._container.childNodes.length - 1) {
+ this._otherButton.focus();
+ return;
+ }
+ }
+ this.selectColorAtIndex(index);
+ }
+ event.preventDefault();
+};
+
+ColorPicker.prototype._handleSwatchClick = function(event) {
+ if (event.target.classList.contains('color-swatch'))
+ this.submitValue(event.target.dataset.value);
+};
+
+if (window.dialogArguments) {
+ initialize(dialogArguments);
+} else {
+ window.addEventListener('message', handleMessage, false);
+ window.setTimeout(handleArgumentsTimeout, 1000);
+}
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/input_alert.svg b/chromium/third_party/blink/renderer/core/html/forms/resources/input_alert.svg
new file mode 100644
index 00000000000..d0aef2b0469
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/input_alert.svg
@@ -0,0 +1,22 @@
+<!-- No XML declaration intentionally -->
+<!-- Copyright 2017 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. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 47 47" id="icon">
+<rect x="0" y="0" width="47" height="47" rx="4" ry="4" fill="#ffa300"/>
+
+<rect x="19" y="10" width="9" height="15" fill="#ffbc4f" />
+<rect x="20" y="10" width="7" height="15" fill="#ffffff" />
+<rect x="20" y="9" width="7" height="1" fill="#ffb53d" />
+<rect x="20" y="25" width="7" height="1" fill="#ffd6af" />
+<rect x="20" y="26" width="7" height="1" fill="#ff7e00" />
+
+<defs>
+<linearGradient id="g1" x1="0" y1="0" x2="0" y2="1">
+<stop offset="0%" stop-color="#ffb53d" />
+<stop offset="100%" stop-color="#ff7e00" />
+</linearGradient>
+</defs>
+<circle cx="23.5" cy="33" r="4.5" fill="url(#g1)"/>
+<circle cx="23.5" cy="33" r="3.5" fill="#ffffff"/>
+</svg>
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
new file mode 100644
index 00000000000..5cce9ac3bb3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css
@@ -0,0 +1,18 @@
+select {
+ display: block;
+ overflow-y: auto;
+}
+
+select hr {
+ border-style: none;
+ border-bottom: 1px solid black;
+ margin: 0 0.5em;
+}
+
+option, optgroup {
+ -webkit-padding-end: 2px;
+}
+
+.wrap option {
+ white-space: pre-wrap;
+}
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
new file mode 100644
index 00000000000..6ce6d125af6
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js
@@ -0,0 +1,456 @@
+'use strict';
+// Copyright (c) 2014 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.
+
+var global = {argumentsReceived: false, params: null, picker: null};
+
+/**
+ * @param {Event} event
+ */
+function handleMessage(event) {
+ window.removeEventListener('message', handleMessage, false);
+ initialize(JSON.parse(event.data));
+ global.argumentsReceived = true;
+}
+
+/**
+ * @param {!Object} args
+ */
+function initialize(args) {
+ global.params = args;
+ var main = $('main');
+ main.innerHTML = '';
+ global.picker = new ListPicker(main, args);
+}
+
+function handleArgumentsTimeout() {
+ if (global.argumentsReceived)
+ return;
+ initialize({});
+}
+
+/**
+ * @constructor
+ * @param {!Element} element
+ * @param {!Object} config
+ */
+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);
+ this._delayedChildrenConfig = null;
+ this._delayedChildrenConfigIndex = 0;
+ this._layout();
+ this._selectElement.addEventListener('mouseup', this._handleMouseUp.bind(this), false);
+ this._selectElement.addEventListener('touchstart', this._handleTouchStart.bind(this), false);
+ this._selectElement.addEventListener('keydown', this._handleKeyDown.bind(this), false);
+ this._selectElement.addEventListener('change', this._handleChange.bind(this), false);
+ window.addEventListener('message', this._handleWindowMessage.bind(this), false);
+ window.addEventListener('mousemove', this._handleWindowMouseMove.bind(this), false);
+ this._handleWindowTouchMoveBound = this._handleWindowTouchMove.bind(this);
+ this._handleWindowTouchEndBound = this._handleWindowTouchEnd.bind(this);
+ this._handleTouchSelectModeScrollBound = this._handleTouchSelectModeScroll.bind(this);
+ this.lastMousePositionX = Infinity;
+ this.lastMousePositionY = Infinity;
+ this._selectionSetByMouseHover = false;
+
+ this._trackingTouchId = null;
+
+ this._handleWindowDidHide();
+ this._selectElement.focus();
+ this._selectElement.value = this._config.selectedIndex;
+}
+ListPicker.prototype = Object.create(Picker.prototype);
+
+ListPicker.prototype._handleWindowDidHide = function() {
+ this._fixWindowSize();
+ var selectedOption = this._selectElement.options[this._selectElement.selectedIndex];
+ if (selectedOption)
+ selectedOption.scrollIntoView(false);
+ window.removeEventListener('didHide', this._handleWindowDidHideBound, false);
+};
+
+ListPicker.prototype._handleWindowMessage = function(event) {
+ eval(event.data);
+ if (window.updateData.type === 'update') {
+ this._config.baseStyle = window.updateData.baseStyle;
+ this._config.children = window.updateData.children;
+ this._update();
+ if (this._config.anchorRectInScreen.x !== window.updateData.anchorRectInScreen.x ||
+ this._config.anchorRectInScreen.y !== window.updateData.anchorRectInScreen.y ||
+ this._config.anchorRectInScreen.width !== window.updateData.anchorRectInScreen.width ||
+ this._config.anchorRectInScreen.height !== window.updateData.anchorRectInScreen.height) {
+ this._config.anchorRectInScreen = window.updateData.anchorRectInScreen;
+ this._fixWindowSize();
+ }
+ }
+ delete window.updateData;
+};
+
+// This should be matched to the border width of the internal listbox
+// SELECT. See listPicker.css and html.css.
+ListPicker.ListboxSelectBorder = 1;
+
+ListPicker.prototype._handleWindowMouseMove = function(event) {
+ var visibleTop = ListPicker.ListboxSelectBorder;
+ var visibleBottom = this._selectElement.offsetHeight - ListPicker.ListboxSelectBorder;
+ var optionBounds = event.target.getBoundingClientRect();
+ if (optionBounds.height >= 1.0) {
+ // If the height of the visible part of event.target is less than 1px,
+ // ignore this event because it may be an error by sub-pixel layout.
+ if (optionBounds.top < visibleTop) {
+ if (optionBounds.bottom - visibleTop < 1.0)
+ return;
+ } else if (optionBounds.bottom > visibleBottom) {
+ if (visibleBottom - optionBounds.top < 1.0)
+ return;
+ }
+ }
+ this.lastMousePositionX = event.clientX;
+ this.lastMousePositionY = event.clientY;
+ this._highlightOption(event.target);
+ this._selectionSetByMouseHover = true;
+ // Prevent the select element from firing change events for mouse input.
+ event.preventDefault();
+};
+
+ListPicker.prototype._handleMouseUp = function(event) {
+ if (event.target.tagName !== 'OPTION')
+ return;
+ window.pagePopupController.setValueAndClosePopup(0, this._selectElement.value);
+};
+
+ListPicker.prototype._handleTouchStart = function(event) {
+ if (this._trackingTouchId !== null)
+ return;
+ // Enter touch select mode. In touch select mode the highlight follows the
+ // finger and on touchend the highlighted item is selected.
+ var touch = event.touches[0];
+ this._trackingTouchId = touch.identifier;
+ this._highlightOption(touch.target);
+ this._selectionSetByMouseHover = false;
+ this._selectElement.addEventListener('scroll', this._handleTouchSelectModeScrollBound, false);
+ window.addEventListener('touchmove', this._handleWindowTouchMoveBound, false);
+ window.addEventListener('touchend', this._handleWindowTouchEndBound, false);
+};
+
+ListPicker.prototype._handleTouchSelectModeScroll = function(event) {
+ this._exitTouchSelectMode();
+};
+
+ListPicker.prototype._exitTouchSelectMode = function(event) {
+ this._trackingTouchId = null;
+ this._selectElement.removeEventListener('scroll', this._handleTouchSelectModeScrollBound, false);
+ window.removeEventListener('touchmove', this._handleWindowTouchMoveBound, false);
+ window.removeEventListener('touchend', this._handleWindowTouchEndBound, false);
+};
+
+ListPicker.prototype._handleWindowTouchMove = function(event) {
+ if (this._trackingTouchId === null)
+ return;
+ var touch = this._getTouchForId(event.touches, this._trackingTouchId);
+ if (!touch)
+ return;
+ this._highlightOption(document.elementFromPoint(touch.clientX, touch.clientY));
+ this._selectionSetByMouseHover = false;
+};
+
+ListPicker.prototype._handleWindowTouchEnd = function(event) {
+ if (this._trackingTouchId === null)
+ return;
+ var touch = this._getTouchForId(event.changedTouches, this._trackingTouchId);
+ if (!touch)
+ return;
+ var target = document.elementFromPoint(touch.clientX, touch.clientY);
+ if (target.tagName === 'OPTION' && !target.disabled)
+ window.pagePopupController.setValueAndClosePopup(0, this._selectElement.value);
+ this._exitTouchSelectMode();
+};
+
+ListPicker.prototype._getTouchForId = function(touchList, id) {
+ for (var i = 0; i < touchList.length; i++) {
+ if (touchList[i].identifier === id)
+ return touchList[i];
+ }
+ return null;
+};
+
+ListPicker.prototype._highlightOption = function(target) {
+ if (target.tagName !== 'OPTION' || target.selected || target.disabled)
+ return;
+ var savedScrollTop = this._selectElement.scrollTop;
+ // TODO(tkent): Updating HTMLOptionElement::selected is not efficient. We
+ // should optimize it, or use an alternative way.
+ target.selected = true;
+ this._selectElement.scrollTop = savedScrollTop;
+};
+
+ListPicker.prototype._handleChange = function(event) {
+ window.pagePopupController.setValue(this._selectElement.value);
+ this._selectionSetByMouseHover = false;
+};
+
+ListPicker.prototype._handleKeyDown = function(event) {
+ var key = event.key;
+ if (key === 'Escape') {
+ window.pagePopupController.closePopup();
+ event.preventDefault();
+ } else if (key === 'Tab' || key === 'Enter') {
+ window.pagePopupController.setValueAndClosePopup(0, this._selectElement.value);
+ event.preventDefault();
+ } else if (event.altKey && (key === 'ArrowDown' || key === 'ArrowUp')) {
+ // We need to add a delay here because, if we do it immediately the key
+ // press event will be handled by HTMLSelectElement and this popup will
+ // be reopened.
+ setTimeout(function() {
+ window.pagePopupController.closePopup();
+ }, 0);
+ event.preventDefault();
+ }
+};
+
+ListPicker.prototype._fixWindowSize = function() {
+ this._selectElement.style.height = '';
+ var scale = this._config.scaleFactor;
+ var maxHeight = this._selectElement.offsetHeight;
+ var noScrollHeight = (this._calculateScrollHeight() + ListPicker.ListboxSelectBorder * 2);
+ var scrollbarWidth = getScrollbarWidth();
+ var elementOffsetWidth = this._selectElement.offsetWidth;
+ var desiredWindowHeight = noScrollHeight;
+ var desiredWindowWidth = elementOffsetWidth;
+ // If we already have a vertical scrollbar, subtract it out, it will get re-added below.
+ if (this._selectElement.scrollHeight > this._selectElement.clientHeight)
+ desiredWindowWidth -= scrollbarWidth;
+ var expectingScrollbar = false;
+ if (desiredWindowHeight > maxHeight) {
+ desiredWindowHeight = maxHeight;
+ // Setting overflow to auto does not increase width for the scrollbar
+ // so we need to do it manually.
+ desiredWindowWidth += scrollbarWidth;
+ expectingScrollbar = true;
+ }
+ // Screen coordinate for anchorRectInScreen and windowRect is DIP.
+ desiredWindowWidth = Math.max(this._config.anchorRectInScreen.width * scale, desiredWindowWidth);
+ var windowRect =
+ adjustWindowRect(desiredWindowWidth / scale, desiredWindowHeight / scale, elementOffsetWidth / scale, 0);
+ // If the available screen space is smaller than maxHeight, we will get an unexpected scrollbar.
+ if (!expectingScrollbar && windowRect.height < noScrollHeight / scale) {
+ desiredWindowWidth = windowRect.width * scale + scrollbarWidth;
+ windowRect = adjustWindowRect(desiredWindowWidth / scale, windowRect.height, windowRect.width, windowRect.height);
+ }
+ this._selectElement.style.width = (windowRect.width * scale) + 'px';
+ this._selectElement.style.height = (windowRect.height * scale) + 'px';
+ this._element.style.height = (windowRect.height * scale) + 'px';
+ setWindowRect(windowRect);
+};
+
+ListPicker.prototype._calculateScrollHeight = function() {
+ // Element.scrollHeight returns an integer value but this calculate the
+ // actual fractional value.
+ // TODO(tkent): This can be too large? crbug.com/579863
+ var top = Infinity;
+ var bottom = -Infinity;
+ for (var i = 0; i < this._selectElement.children.length; i++) {
+ var rect = this._selectElement.children[i].getBoundingClientRect();
+ // Skip hidden elements.
+ if (rect.width === 0 && rect.height === 0)
+ continue;
+ top = Math.min(top, rect.top);
+ bottom = Math.max(bottom, rect.bottom);
+ }
+ return Math.max(bottom - top, 0);
+};
+
+ListPicker.prototype._listItemCount = function() {
+ return this._selectElement.querySelectorAll('option,optgroup,hr').length;
+};
+
+ListPicker.prototype._layout = function() {
+ if (this._config.isRTL)
+ this._element.classList.add('rtl');
+ this._selectElement.style.backgroundColor = this._config.baseStyle.backgroundColor;
+ this._selectElement.style.color = this._config.baseStyle.color;
+ this._selectElement.style.textTransform = this._config.baseStyle.textTransform;
+ this._selectElement.style.fontSize = this._config.baseStyle.fontSize + 'px';
+ this._selectElement.style.fontFamily = this._config.baseStyle.fontFamily.map(s => '"' + s + '"').join(',');
+ this._selectElement.style.fontStyle = this._config.baseStyle.fontStyle;
+ this._selectElement.style.fontVariant = this._config.baseStyle.fontVariant;
+ this._updateChildren(this._selectElement, this._config);
+};
+
+ListPicker.prototype._update = function() {
+ var scrollPosition = this._selectElement.scrollTop;
+ var oldValue = this._selectElement.value;
+ this._layout();
+ this._selectElement.value = this._config.selectedIndex;
+ this._selectElement.scrollTop = scrollPosition;
+ var optionUnderMouse = null;
+ if (this._selectionSetByMouseHover) {
+ var elementUnderMouse = document.elementFromPoint(this.lastMousePositionX, this.lastMousePositionY);
+ optionUnderMouse = elementUnderMouse && elementUnderMouse.closest('option');
+ }
+ if (optionUnderMouse)
+ optionUnderMouse.selected = true;
+ else
+ this._selectElement.value = oldValue;
+ this._selectElement.scrollTop = scrollPosition;
+ this.dispatchEvent('didUpdate');
+};
+
+ListPicker.DelayedLayoutThreshold = 1000;
+
+/**
+ * @param {!Element} parent Select element or optgroup element.
+ * @param {!Object} config
+ */
+ListPicker.prototype._updateChildren = function(parent, config) {
+ var outOfDateIndex = 0;
+ var fragment = null;
+ var inGroup = parent.tagName === 'OPTGROUP';
+ var lastListIndex = -1;
+ var limit = Math.max(this._config.selectedIndex, ListPicker.DelayedLayoutThreshold);
+ var i;
+ for (i = 0; i < config.children.length; ++i) {
+ if (!inGroup && lastListIndex >= limit)
+ break;
+ var childConfig = config.children[i];
+ var item = this._findReusableItem(parent, childConfig, outOfDateIndex) || this._createItemElement(childConfig);
+ this._configureItem(item, childConfig, inGroup);
+ lastListIndex = item.value ? Number(item.value) : -1;
+ if (outOfDateIndex < parent.children.length) {
+ parent.insertBefore(item, parent.children[outOfDateIndex]);
+ } else {
+ if (!fragment)
+ fragment = document.createDocumentFragment();
+ fragment.appendChild(item);
+ }
+ outOfDateIndex++;
+ }
+ if (fragment) {
+ parent.appendChild(fragment);
+ } else {
+ var unused = parent.children.length - outOfDateIndex;
+ for (var j = 0; j < unused; j++) {
+ parent.removeChild(parent.lastElementChild);
+ }
+ }
+ if (i < config.children.length) {
+ // We don't bind |config.children| and |i| to _updateChildrenLater
+ // because config.children can get invalid before _updateChildrenLater
+ // is called.
+ this._delayedChildrenConfig = config.children;
+ this._delayedChildrenConfigIndex = i;
+ // Needs some amount of delay to kick the first paint.
+ setTimeout(this._updateChildrenLater.bind(this), 100);
+ }
+};
+
+ListPicker.prototype._updateChildrenLater = function(timeStamp) {
+ if (!this._delayedChildrenConfig)
+ return;
+ var fragment = document.createDocumentFragment();
+ var startIndex = this._delayedChildrenConfigIndex;
+ for (; this._delayedChildrenConfigIndex < this._delayedChildrenConfig.length; ++this._delayedChildrenConfigIndex) {
+ var childConfig = this._delayedChildrenConfig[this._delayedChildrenConfigIndex];
+ var item = this._createItemElement(childConfig);
+ this._configureItem(item, childConfig, false);
+ fragment.appendChild(item);
+ }
+ this._selectElement.appendChild(fragment);
+ this._selectElement.classList.add('wrap');
+ this._delayedChildrenConfig = null;
+};
+
+ListPicker.prototype._findReusableItem = function(parent, config, startIndex) {
+ if (startIndex >= parent.children.length)
+ return null;
+ var tagName = 'OPTION';
+ if (config.type === 'optgroup')
+ tagName = 'OPTGROUP';
+ else if (config.type === 'separator')
+ tagName = 'HR';
+ for (var i = startIndex; i < parent.children.length; i++) {
+ var child = parent.children[i];
+ if (tagName === child.tagName) {
+ return child;
+ }
+ }
+ return null;
+};
+
+ListPicker.prototype._createItemElement = function(config) {
+ var element;
+ if (!config.type || config.type === 'option')
+ element = createElement('option');
+ else if (config.type === 'optgroup')
+ element = createElement('optgroup');
+ else if (config.type === 'separator')
+ element = createElement('hr');
+ return element;
+};
+
+ListPicker.prototype._applyItemStyle = function(element, styleConfig) {
+ if (!styleConfig)
+ return;
+ var style = element.style;
+ style.visibility = styleConfig.visibility ? styleConfig.visibility : '';
+ style.display = styleConfig.display ? styleConfig.display : '';
+ style.direction = styleConfig.direction ? styleConfig.direction : '';
+ style.unicodeBidi = styleConfig.unicodeBidi ? styleConfig.unicodeBidi : '';
+ style.color = styleConfig.color ? styleConfig.color : '';
+ style.backgroundColor = styleConfig.backgroundColor ? styleConfig.backgroundColor : '';
+ style.fontSize = styleConfig.fontSize ? styleConfig.fontSize + 'px' : '';
+ style.fontWeight = styleConfig.fontWeight ? styleConfig.fontWeight : '';
+ style.fontFamily = styleConfig.fontFamily ? styleConfig.fontFamily.map(s => '"' + s + '"').join(',') : '';
+ style.fontStyle = styleConfig.fontStyle ? styleConfig.fontStyle : '';
+ style.fontVariant = styleConfig.fontVariant ? styleConfig.fontVariant : '';
+ style.textTransform = styleConfig.textTransform ? styleConfig.textTransform : '';
+};
+
+ListPicker.prototype._configureItem = function(element, config, inGroup) {
+ if (!config.type || config.type === 'option') {
+ element.label = config.label;
+ element.value = config.value;
+ if (config.title)
+ element.title = config.title;
+ else
+ element.removeAttribute('title');
+ element.disabled = !!config.disabled
+ if (config.ariaLabel)
+ element.setAttribute('aria-label', config.ariaLabel);
+ else element.removeAttribute('aria-label');
+ element.style.webkitPaddingStart = this._config.paddingStart + 'px';
+ if (inGroup) {
+ element.style.webkitMarginStart = (-this._config.paddingStart) + 'px';
+ // Should be synchronized with padding-end in listPicker.css.
+ element.style.webkitMarginEnd = '-2px';
+ }
+ } else if (config.type === 'optgroup') {
+ element.label = config.label;
+ element.title = config.title;
+ element.disabled = config.disabled;
+ element.setAttribute('aria-label', config.ariaLabel);
+ this._updateChildren(element, config);
+ element.style.webkitPaddingStart = this._config.paddingStart + 'px';
+ } else if (config.type === 'separator') {
+ element.title = config.title;
+ element.disabled = config.disabled;
+ element.setAttribute('aria-label', config.ariaLabel);
+ if (inGroup) {
+ element.style.webkitMarginStart = (-this._config.paddingStart) + 'px';
+ // Should be synchronized with padding-end in listPicker.css.
+ element.style.webkitMarginEnd = '-2px';
+ }
+ }
+ this._applyItemStyle(element, config.style);
+};
+
+if (window.dialogArguments) {
+ initialize(dialogArguments);
+} else {
+ window.addEventListener('message', handleMessage, false);
+ window.setTimeout(handleArgumentsTimeout, 1000);
+}
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/pickerButton.css b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerButton.css
new file mode 100644
index 00000000000..c0c6ff1fbab
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerButton.css
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+input[type='button'],
+button {
+ -webkit-appearance: none;
+ -webkit-user-select: none;
+ background-image: linear-gradient(#ededed, #ededed 38%, #dedede);
+ border: 1px solid rgba(0, 0, 0, 0.25);
+ border-radius: 2px;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
+ inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #444;
+ font: inherit;
+ text-shadow: 0 1px 0 rgb(240, 240, 240);
+ min-height: 2em;
+ min-width: 4em;
+ -webkit-padding-end: 10px;
+ -webkit-padding-start: 10px;
+ margin: 0;
+}
+
+:enabled:hover:-webkit-any(button, input[type='button']) {
+ background-image: linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+ border-color: rgba(0, 0, 0, 0.3);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12), inset 0 1px 2px rgba(255, 255, 255, 0.95);
+ color: black;
+}
+
+:enabled:active:-webkit-any(button, input[type='button']) {
+ background-image: linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+ box-shadow: none;
+ text-shadow: none;
+}
+
+:disabled:-webkit-any(button, input[type='button']) {
+ background-image: linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+ border-color: rgba(80, 80, 80, 0.2);
+ box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #aaa;
+}
+
+:enabled:focus:-webkit-any(button, input[type='button']) {
+ transition: border-color 200ms;
+ /* We use border color because it follows the border radius (unlike outline).
+ * This is particularly noticeable on mac. */
+ border-color: rgb(77, 144, 254);
+ outline: none;
+}
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.css b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.css
new file mode 100644
index 00000000000..b08f7538fae
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+body {
+ -webkit-user-select: none;
+ background-color: white;
+ font: -webkit-small-control;
+ margin: 0;
+ overflow: hidden;
+}
+
+.rtl {
+ direction: rtl;
+}
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.js b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.js
new file mode 100644
index 00000000000..d4b6856b22f
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.js
@@ -0,0 +1,334 @@
+'use strict';
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+/**
+ * @param {!string} id
+ */
+function $(id) {
+ return document.getElementById(id);
+}
+
+/**
+ * @param {!string} tagName
+ * @param {string=} opt_class
+ * @param {string=} opt_text
+ * @return {!Element}
+ */
+function createElement(tagName, opt_class, opt_text) {
+ var element = document.createElement(tagName);
+ if (opt_class)
+ element.setAttribute('class', opt_class);
+ if (opt_text)
+ element.appendChild(document.createTextNode(opt_text));
+ return element;
+}
+
+/**
+ * @constructor
+ * @param {!number|Rectangle|Object} xOrRect
+ * @param {!number} y
+ * @param {!number} width
+ * @param {!number} height
+ */
+function Rectangle(xOrRect, y, width, height) {
+ if (typeof xOrRect === 'object') {
+ y = xOrRect.y;
+ width = xOrRect.width;
+ height = xOrRect.height;
+ xOrRect = xOrRect.x;
+ }
+ this.x = xOrRect;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+}
+
+Rectangle.prototype = {
+ get maxX() {
+ return this.x + this.width;
+ },
+ get maxY() {
+ return this.y + this.height;
+ },
+ toString: function() {
+ return 'Rectangle(' + this.x + ',' + this.y + ',' + this.width + ',' + this.height + ')';
+ }
+};
+
+/**
+ * @param {!Rectangle} rect1
+ * @param {!Rectangle} rect2
+ * @return {?Rectangle}
+ */
+Rectangle.intersection = function(rect1, rect2) {
+ var x = Math.max(rect1.x, rect2.x);
+ var maxX = Math.min(rect1.maxX, rect2.maxX);
+ var y = Math.max(rect1.y, rect2.y);
+ var maxY = Math.min(rect1.maxY, rect2.maxY);
+ var width = maxX - x;
+ var height = maxY - y;
+ if (width < 0 || height < 0)
+ return null;
+ return new Rectangle(x, y, width, height);
+};
+
+/**
+ * @param {!number} width in CSS pixel
+ * @param {!number} height in CSS pixel
+ */
+function resizeWindow(width, height) {
+ var zoom = global.params.zoomFactor ? global.params.zoomFactor : 1;
+ setWindowRect(adjustWindowRect(width * zoom, height * zoom, width * zoom, height * zoom));
+}
+
+/**
+ * @param {!number} width in DIP
+ * @param {!number} height in DIP
+ * @param {?number} minWidth in DIP
+ * @param {?number} minHeight in DIP
+ * @return {!Rectangle} Adjusted rectangle in DIP
+ */
+function adjustWindowRect(width, height, minWidth, minHeight) {
+ if (typeof minWidth !== 'number')
+ minWidth = 0;
+ if (typeof minHeight !== 'number')
+ minHeight = 0;
+
+ var windowRect = new Rectangle(0, 0, Math.ceil(width), Math.ceil(height));
+
+ if (!global.params.anchorRectInScreen)
+ return windowRect;
+
+ var anchorRect = new Rectangle(global.params.anchorRectInScreen);
+ var availRect = new Rectangle(
+ window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
+
+ _adjustWindowRectVertically(windowRect, availRect, anchorRect, minHeight);
+ _adjustWindowRectHorizontally(windowRect, availRect, anchorRect, minWidth);
+
+ return windowRect;
+}
+
+/**
+ * Arguments are DIPs.
+ */
+function _adjustWindowRectVertically(windowRect, availRect, anchorRect, minHeight) {
+ var availableSpaceAbove = anchorRect.y - availRect.y;
+ availableSpaceAbove = Math.max(0, Math.min(availRect.height, availableSpaceAbove));
+
+ var availableSpaceBelow = availRect.maxY - anchorRect.maxY;
+ availableSpaceBelow = Math.max(0, Math.min(availRect.height, availableSpaceBelow));
+ if (windowRect.height > availableSpaceBelow && availableSpaceBelow < availableSpaceAbove) {
+ windowRect.height = Math.min(windowRect.height, availableSpaceAbove);
+ windowRect.height = Math.max(windowRect.height, minHeight);
+ windowRect.y = anchorRect.y - windowRect.height;
+ } else {
+ windowRect.height = Math.min(windowRect.height, availableSpaceBelow);
+ windowRect.height = Math.max(windowRect.height, minHeight);
+ windowRect.y = anchorRect.maxY;
+ }
+}
+
+/**
+ * Arguments are DIPs.
+ */
+function _adjustWindowRectHorizontally(windowRect, availRect, anchorRect, minWidth) {
+ windowRect.width = Math.min(windowRect.width, availRect.width);
+ windowRect.width = Math.max(windowRect.width, minWidth);
+ windowRect.x = anchorRect.x;
+ // If we are getting clipped, we want to switch alignment to the right side
+ // of the anchor rect as long as doing so will make the popup not clipped.
+ var rightAlignedX = windowRect.x + anchorRect.width - windowRect.width;
+ if (rightAlignedX >= availRect.x && (windowRect.maxX > availRect.maxX || global.params.isRTL))
+ windowRect.x = rightAlignedX;
+}
+
+/**
+ * @param {!Rectangle} rect Window position and size in DIP.
+ */
+function setWindowRect(rect) {
+ if (window.frameElement) {
+ window.frameElement.style.width = rect.width + 'px';
+ window.frameElement.style.height = rect.height + 'px';
+ } else {
+ window.pagePopupController.setWindowRect(rect.x, rect.y, rect.width, rect.height);
+ }
+}
+
+function hideWindow() {
+ setWindowRect(adjustWindowRect(1, 1, 1, 1));
+}
+
+/**
+ * @return {!boolean}
+ */
+function isWindowHidden() {
+ // window.innerWidth and innerHeight are zoom-adjusted values. If we call
+ // setWindowRect with width=100 and the zoom-level is 2.0, innerWidth will
+ // return 50.
+ return window.innerWidth <= 1 && window.innerHeight <= 1;
+}
+
+window.addEventListener('resize', function() {
+ if (isWindowHidden())
+ window.dispatchEvent(new CustomEvent('didHide'));
+ else
+ window.dispatchEvent(new CustomEvent('didOpenPicker'));
+}, false);
+
+/**
+ * @return {!number}
+ */
+function getScrollbarWidth() {
+ if (typeof window.scrollbarWidth === 'undefined') {
+ var scrollDiv = document.createElement('div');
+ scrollDiv.style.opacity = '0';
+ scrollDiv.style.overflow = 'scroll';
+ scrollDiv.style.width = '50px';
+ scrollDiv.style.height = '50px';
+ document.body.appendChild(scrollDiv);
+ window.scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+ scrollDiv.parentNode.removeChild(scrollDiv);
+ }
+ return window.scrollbarWidth;
+}
+
+/**
+ * @param {!string} className
+ * @return {?Element}
+ */
+function enclosingNodeOrSelfWithClass(selfNode, className) {
+ for (var node = selfNode; node && node !== selfNode.ownerDocument; node = node.parentNode) {
+ if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
+ return node;
+ }
+ return null;
+}
+
+/**
+ * @constructor
+ */
+function EventEmitter(){};
+
+/**
+ * @param {!string} type
+ * @param {!function({...*})} callback
+ */
+EventEmitter.prototype.on = function(type, callback) {
+ console.assert(callback instanceof Function);
+ if (!this._callbacks)
+ this._callbacks = {};
+ if (!this._callbacks[type])
+ this._callbacks[type] = [];
+ this._callbacks[type].push(callback);
+};
+
+EventEmitter.prototype.hasListener = function(type) {
+ if (!this._callbacks)
+ return false;
+ var callbacksForType = this._callbacks[type];
+ if (!callbacksForType)
+ return false;
+ return callbacksForType.length > 0;
+};
+
+/**
+ * @param {!string} type
+ * @param {!function(Object)} callback
+ */
+EventEmitter.prototype.removeListener = function(type, callback) {
+ if (!this._callbacks)
+ return;
+ var callbacksForType = this._callbacks[type];
+ if (!callbacksForType)
+ return;
+ callbacksForType.splice(callbacksForType.indexOf(callback), 1);
+ if (callbacksForType.length === 0)
+ delete this._callbacks[type];
+};
+
+/**
+ * @param {!string} type
+ * @param {...*} var_args
+ */
+EventEmitter.prototype.dispatchEvent = function(type) {
+ if (!this._callbacks)
+ return;
+ var callbacksForType = this._callbacks[type];
+ if (!callbacksForType)
+ return;
+ callbacksForType = callbacksForType.slice(0);
+ for (var i = 0; i < callbacksForType.length; ++i) {
+ callbacksForType[i].apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+};
+
+/**
+ * @constructor
+ * @extends EventEmitter
+ * @param {!Element} element
+ * @param {!Object} config
+ */
+function Picker(element, config) {
+ this._element = element;
+ this._config = config;
+}
+
+Picker.prototype = Object.create(EventEmitter.prototype);
+
+/**
+ * @enum {number}
+ */
+Picker.Actions = {
+ SetValue: 0,
+ Cancel: -1,
+ ChooseOtherColor: -2
+};
+
+/**
+ * @param {!string} value
+ */
+Picker.prototype.submitValue = function(value) {
+ window.pagePopupController.setValue(value);
+ window.pagePopupController.closePopup();
+};
+
+Picker.prototype.handleCancel = function() {
+ window.pagePopupController.closePopup();
+};
+
+Picker.prototype.chooseOtherColor = function() {
+ window.pagePopupController.setValueAndClosePopup(Picker.Actions.ChooseOtherColor, '');
+};
+
+Picker.prototype.cleanup = function() {};
+
+window.addEventListener('keyup', function(event) {
+ // JAWS dispatches extra Alt events and unless we handle them they move the
+ // focus and close the popup.
+ if (event.key === 'Alt')
+ event.preventDefault();
+}, true);
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
new file mode 100644
index 00000000000..0281d0df5bf
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css
@@ -0,0 +1,56 @@
+.suggestion-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ font: -webkit-small-control;
+ border: 1px solid #7f9db9;
+ background-color: white;
+ overflow: hidden;
+}
+
+.suggestion-list-entry {
+ white-space: nowrap;
+ height: 1.73em;
+ line-height: 1.73em;
+ -webkit-select: none;
+ cursor: default;
+}
+
+.suggestion-list-entry:focus {
+ outline: none;
+}
+
+.suggestion-list-entry .content {
+ padding: 0 4px;
+}
+
+.suggestion-list-entry .label {
+ text-align: right;
+ color: #737373;
+ float: right;
+ padding: 0 4px 0 20px;
+}
+
+.rtl .suggestion-list-entry .label {
+ float: left;
+ padding: 0 20px 0 4px;
+}
+
+.suggestion-list-entry .title {
+ direction: ltr;
+ display: inline-block;
+}
+
+.locale-rtl .suggestion-list-entry .title {
+ direction: rtl;
+}
+
+.measuring-width .suggestion-list-entry .label {
+ float: none;
+ margin-right: 0;
+}
+
+.suggestion-list .separator {
+ border-top: 1px solid #dcdcdc;
+ height: 0;
+}
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
new file mode 100644
index 00000000000..e56b93ea0e7
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js
@@ -0,0 +1,331 @@
+'use strict';
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+/**
+ * @constructor
+ * @param {!Element} element
+ * @param {!Object} config
+ */
+function SuggestionPicker(element, config) {
+ Picker.call(this, element, config);
+ this._isFocusByMouse = false;
+ this._containerElement = null;
+ this._setColors();
+ this._layout();
+ this._fixWindowSize();
+ this._handleBodyKeyDownBound = this._handleBodyKeyDown.bind(this);
+ document.body.addEventListener('keydown', this._handleBodyKeyDownBound);
+ this._element.addEventListener('mouseout', this._handleMouseOut.bind(this), false);
+}
+SuggestionPicker.prototype = Object.create(Picker.prototype);
+
+SuggestionPicker.NumberOfVisibleEntries = 20;
+
+// An entry needs to be at least this many pixels visible for it to be a visible entry.
+SuggestionPicker.VisibleEntryThresholdHeight = 4;
+
+SuggestionPicker.ActionNames = {
+ OpenCalendarPicker: 'openCalendarPicker'
+};
+
+SuggestionPicker.ListEntryClass = 'suggestion-list-entry';
+
+SuggestionPicker.validateConfig = function(config) {
+ if (config.showOtherDateEntry && !config.otherDateLabel)
+ return 'No otherDateLabel.';
+ if (config.suggestionHighlightColor && !config.suggestionHighlightColor)
+ return 'No suggestionHighlightColor.';
+ if (config.suggestionHighlightTextColor && !config.suggestionHighlightTextColor)
+ return 'No suggestionHighlightTextColor.';
+ if (config.suggestionValues.length !== config.localizedSuggestionValues.length)
+ return 'localizedSuggestionValues.length must equal suggestionValues.length.';
+ if (config.suggestionValues.length !== config.suggestionLabels.length)
+ return 'suggestionLabels.length must equal suggestionValues.length.';
+ if (typeof config.inputWidth === 'undefined')
+ return 'No inputWidth.';
+ return null;
+};
+
+SuggestionPicker.prototype._setColors = function() {
+ var text = '.' + SuggestionPicker.ListEntryClass + ':focus {\
+ background-color: ' +
+ this._config.suggestionHighlightColor + ';\
+ color: ' +
+ this._config.suggestionHighlightTextColor + '; }';
+ text += '.' + SuggestionPicker.ListEntryClass +
+ ':focus .label { color: ' + this._config.suggestionHighlightTextColor + '; }';
+ document.head.appendChild(createElement('style', null, text));
+};
+
+SuggestionPicker.prototype.cleanup = function() {
+ document.body.removeEventListener('keydown', this._handleBodyKeyDownBound, false);
+};
+
+/**
+ * @param {!string} title
+ * @param {!string} label
+ * @param {!string} value
+ * @return {!Element}
+ */
+SuggestionPicker.prototype._createSuggestionEntryElement = function(title, label, value) {
+ var entryElement = createElement('li', SuggestionPicker.ListEntryClass);
+ entryElement.tabIndex = 0;
+ entryElement.dataset.value = value;
+ var content = createElement('span', 'content');
+ entryElement.appendChild(content);
+ var titleElement = createElement('span', 'title', title);
+ content.appendChild(titleElement);
+ if (label) {
+ var labelElement = createElement('span', 'label', label);
+ content.appendChild(labelElement);
+ }
+ entryElement.addEventListener('mouseover', this._handleEntryMouseOver.bind(this), false);
+ return entryElement;
+};
+
+/**
+ * @param {!string} title
+ * @param {!string} actionName
+ * @return {!Element}
+ */
+SuggestionPicker.prototype._createActionEntryElement = function(title, actionName) {
+ var entryElement = createElement('li', SuggestionPicker.ListEntryClass);
+ entryElement.tabIndex = 0;
+ entryElement.dataset.action = actionName;
+ var content = createElement('span', 'content');
+ entryElement.appendChild(content);
+ var titleElement = createElement('span', 'title', title);
+ content.appendChild(titleElement);
+ entryElement.addEventListener('mouseover', this._handleEntryMouseOver.bind(this), false);
+ return entryElement;
+};
+
+/**
+* @return {!number}
+*/
+SuggestionPicker.prototype._measureMaxContentWidth = function() {
+ // To measure the required width, we first set the class to "measuring-width" which
+ // left aligns all the content including label.
+ this._containerElement.classList.add('measuring-width');
+ var maxContentWidth = 0;
+ var contentElements = this._containerElement.getElementsByClassName('content');
+ for (var i = 0; i < contentElements.length; ++i) {
+ maxContentWidth = Math.max(maxContentWidth, contentElements[i].getBoundingClientRect().width);
+ }
+ this._containerElement.classList.remove('measuring-width');
+ return maxContentWidth;
+};
+
+SuggestionPicker.prototype._fixWindowSize = function() {
+ var ListBorder = 2;
+ var zoom = this._config.zoomFactor;
+ var desiredWindowWidth = (this._measureMaxContentWidth() + ListBorder) * zoom;
+ if (typeof this._config.inputWidth === 'number')
+ desiredWindowWidth = Math.max(this._config.inputWidth, desiredWindowWidth);
+ var totalHeight = ListBorder;
+ var maxHeight = 0;
+ var entryCount = 0;
+ for (var i = 0; i < this._containerElement.childNodes.length; ++i) {
+ var node = this._containerElement.childNodes[i];
+ if (node.classList.contains(SuggestionPicker.ListEntryClass))
+ entryCount++;
+ totalHeight += node.offsetHeight;
+ if (maxHeight === 0 && entryCount == SuggestionPicker.NumberOfVisibleEntries)
+ maxHeight = totalHeight;
+ }
+ var desiredWindowHeight = totalHeight * zoom;
+ if (maxHeight !== 0 && totalHeight > maxHeight * zoom) {
+ this._containerElement.style.maxHeight = (maxHeight - ListBorder) + 'px';
+ desiredWindowWidth += getScrollbarWidth() * zoom;
+ desiredWindowHeight = maxHeight * zoom;
+ this._containerElement.style.overflowY = 'scroll';
+ }
+ var windowRect = adjustWindowRect(desiredWindowWidth, desiredWindowHeight, desiredWindowWidth, 0);
+ this._containerElement.style.height = (windowRect.height / zoom - ListBorder) + 'px';
+ setWindowRect(windowRect);
+};
+
+SuggestionPicker.prototype._layout = function() {
+ if (this._config.isRTL)
+ this._element.classList.add('rtl');
+ if (this._config.isLocaleRTL)
+ this._element.classList.add('locale-rtl');
+ this._containerElement = createElement('ul', 'suggestion-list');
+ this._containerElement.addEventListener('click', this._handleEntryClick.bind(this), false);
+ for (var i = 0; i < this._config.suggestionValues.length; ++i) {
+ this._containerElement.appendChild(this._createSuggestionEntryElement(
+ this._config.localizedSuggestionValues[i], this._config.suggestionLabels[i], this._config.suggestionValues[i]));
+ }
+ if (this._config.showOtherDateEntry) {
+ // Add separator
+ var separator = createElement('div', 'separator');
+ this._containerElement.appendChild(separator);
+
+ // Add "Other..." entry
+ var otherEntry =
+ this._createActionEntryElement(this._config.otherDateLabel, SuggestionPicker.ActionNames.OpenCalendarPicker);
+ this._containerElement.appendChild(otherEntry);
+ }
+ this._element.appendChild(this._containerElement);
+};
+
+/**
+ * @param {!Element} entry
+ */
+SuggestionPicker.prototype.selectEntry = function(entry) {
+ if (typeof entry.dataset.value !== 'undefined') {
+ this.submitValue(entry.dataset.value);
+ } else if (entry.dataset.action === SuggestionPicker.ActionNames.OpenCalendarPicker) {
+ window.addEventListener('didHide', SuggestionPicker._handleWindowDidHide, false);
+ hideWindow();
+ }
+};
+
+SuggestionPicker._handleWindowDidHide = function() {
+ openCalendarPicker();
+ window.removeEventListener('didHide', SuggestionPicker._handleWindowDidHide);
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleEntryClick = function(event) {
+ var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass);
+ if (!entry)
+ return;
+ this.selectEntry(entry);
+ event.preventDefault();
+};
+
+/**
+ * @return {?Element}
+ */
+SuggestionPicker.prototype._findFirstVisibleEntry = function() {
+ var scrollTop = this._containerElement.scrollTop;
+ var childNodes = this._containerElement.childNodes;
+ for (var i = 0; i < childNodes.length; ++i) {
+ var node = childNodes[i];
+ if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass))
+ continue;
+ if (node.offsetTop + node.offsetHeight - scrollTop > SuggestionPicker.VisibleEntryThresholdHeight)
+ return node;
+ }
+ return null;
+};
+
+/**
+ * @return {?Element}
+ */
+SuggestionPicker.prototype._findLastVisibleEntry = function() {
+ var scrollBottom = this._containerElement.scrollTop + this._containerElement.offsetHeight;
+ var childNodes = this._containerElement.childNodes;
+ for (var i = childNodes.length - 1; i >= 0; --i) {
+ var node = childNodes[i];
+ if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass))
+ continue;
+ if (scrollBottom - node.offsetTop > SuggestionPicker.VisibleEntryThresholdHeight)
+ return node;
+ }
+ return null;
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleBodyKeyDown = function(event) {
+ var eventHandled = false;
+ var key = event.key;
+ if (key === 'Escape') {
+ this.handleCancel();
+ eventHandled = true;
+ } else if (key == 'ArrowUp') {
+ if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) {
+ for (var node = document.activeElement.previousElementSibling; node; node = node.previousElementSibling) {
+ if (node.classList.contains(SuggestionPicker.ListEntryClass)) {
+ this._isFocusByMouse = false;
+ node.focus();
+ break;
+ }
+ }
+ } else {
+ this._element.querySelector('.' + SuggestionPicker.ListEntryClass + ':last-child').focus();
+ }
+ eventHandled = true;
+ } else if (key == 'ArrowDown') {
+ if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) {
+ for (var node = document.activeElement.nextElementSibling; node; node = node.nextElementSibling) {
+ if (node.classList.contains(SuggestionPicker.ListEntryClass)) {
+ this._isFocusByMouse = false;
+ node.focus();
+ break;
+ }
+ }
+ } else {
+ this._element.querySelector('.' + SuggestionPicker.ListEntryClass + ':first-child').focus();
+ }
+ eventHandled = true;
+ } else if (key === 'Enter') {
+ this.selectEntry(document.activeElement);
+ eventHandled = true;
+ } else if (key === 'PageUp') {
+ this._containerElement.scrollTop -= this._containerElement.clientHeight;
+ // Scrolling causes mouseover event to be called and that tries to move the focus too.
+ // To prevent flickering we won't focus if the current focus was caused by the mouse.
+ if (!this._isFocusByMouse)
+ this._findFirstVisibleEntry().focus();
+ eventHandled = true;
+ } else if (key === 'PageDown') {
+ this._containerElement.scrollTop += this._containerElement.clientHeight;
+ if (!this._isFocusByMouse)
+ this._findLastVisibleEntry().focus();
+ eventHandled = true;
+ }
+ if (eventHandled)
+ event.preventDefault();
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleEntryMouseOver = function(event) {
+ var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass);
+ if (!entry)
+ return;
+ this._isFocusByMouse = true;
+ entry.focus();
+ event.preventDefault();
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleMouseOut = function(event) {
+ if (!document.activeElement.classList.contains(SuggestionPicker.ListEntryClass))
+ return;
+ this._isFocusByMouse = false;
+ document.activeElement.blur();
+ event.preventDefault();
+};
diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/validation_bubble.css b/chromium/third_party/blink/renderer/core/html/forms/resources/validation_bubble.css
new file mode 100644
index 00000000000..a8e2df9765a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/resources/validation_bubble.css
@@ -0,0 +1,148 @@
+/* Copyright 2017 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.
+ */
+
+:root {
+ --bubble-background: white;
+ --bubble-border-color: gray;
+ --arrow-size: 8px;
+}
+
+#container {
+ box-sizing: border-box;
+ display: inline-block;
+ font-family: system-ui;
+ max-width: 50%;
+ opacity: 0;
+ position: absolute;
+ will-change: opacity, transform;
+}
+
+#container.shown-initially {
+ /* If scaleY is smaller than 0.2, an assertion failure occurs in Skia. */
+ transform: scale(0.96, 1);
+}
+
+#container.shown-fully {
+ opacity: 1.0;
+ transform: scale(1, 1);
+ transition: opacity 466.67ms cubic-bezier(0.4, 0, 0.2, 1), transform 1166.67ms cubic-bezier(0.2, 0, 0, 1);
+}
+
+#container.hiding {
+ opacity: 0;
+ /* See ValidationMessageClientImpl::HideValidationMessage too */
+ transition: opacity 133.33ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#container.shown-initially -webkit-any(#icon, #main-message, #sub-message) {
+ opacity: 0;
+}
+
+#container.shown-fully -webkit-any(#icon, #main-message, #sub-message) {
+ opacity: 1;
+ transition: opacity 700ms cubic-bezier(0.3, 0, 0.1, 1);
+}
+
+#container.hiding -webkit-any(#icon, #main-message, #sub-message) {
+ opacity: 0;
+ transition: opacity 116.67ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#bubble-body {
+ background: var(--bubble-background);
+ border-radius: 4px;
+ border: 1px solid var(--bubble-border-color);
+ box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
+ display: grid;
+ padding: 8px;
+}
+
+#spacer-top {
+ display: block;
+ height: var(--arrow-size);
+}
+
+#outer-arrow-top {
+ border-color: transparent transparent var(--bubble-border-color) transparent;
+ border-style: solid;
+ border-width: 0px var(--arrow-size) var(--arrow-size) var(--arrow-size);
+ display: block;
+ left: 10px;
+ margin-top: 0px;
+ position: absolute;
+ width: 0px;
+}
+
+#inner-arrow-top {
+ border-color: transparent transparent var(--bubble-background) transparent;
+ border-style: solid;
+ border-width: 0px var(--arrow-size) var(--arrow-size) var(--arrow-size);
+ display: block;
+ left: 10px;
+ margin-top: 1px;
+ position: absolute;
+ width: 0px;
+}
+
+.bottom-arrow #outer-arrow-top, .bottom-arrow #inner-arrow-top, .bottom-arrow #spacer-top {
+ display: none;
+}
+
+#outer-arrow-bottom, #inner-arrow-bottom, #spacer-bottom {
+ display: none;
+}
+
+.bottom-arrow #spacer-bottom {
+ display: block;
+ height: var(--arrow-size);
+}
+
+.bottom-arrow #outer-arrow-bottom {
+ border-color: var(--bubble-border-color) transparent transparent transparent;
+ border-style: solid;
+ border-width: var(--arrow-size) var(--arrow-size) 0px var(--arrow-size);
+ display: block;
+ left: 10px;
+ margin-top: 0px;
+ position: absolute;
+ width: 0px;
+}
+
+.bottom-arrow #inner-arrow-bottom {
+ border-color: var(--bubble-background) transparent transparent transparent;
+ border-style: solid;
+ border-width: var(--arrow-size) var(--arrow-size) 0px var(--arrow-size);
+ display: block;
+ left: 10px;
+ margin-top: -1px;
+ position: absolute;
+ width: 0px;
+}
+
+#icon {
+ grid-row: 1 / 3;
+ grid-column: 1;
+ -webkit-margin-end: 8px;
+}
+
+#main-message {
+ font-size: 14px;
+ grid-row: 1;
+ grid-column: 2;
+ margin-top: 3px;
+ margin-bottom: 4px;
+}
+
+#sub-message {
+ color: #444;
+ font-size: 13px;
+ grid-row: 2;
+ grid-column: 2;
+}
+
+body {
+ margin: 0;
+ overflow: hidden;
+}
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
new file mode 100644
index 00000000000..a14a67b7dce
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/search_input_type.h"
+
+#include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.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"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.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/layout/layout_search_field.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline SearchInputType::SearchInputType(HTMLInputElement& element)
+ : BaseTextInputType(element),
+ search_event_timer_(
+ element.GetDocument().GetTaskRunner(TaskType::kUserInteraction),
+ this,
+ &SearchInputType::SearchEventTimerFired) {}
+
+InputType* SearchInputType::Create(HTMLInputElement& element) {
+ return new SearchInputType(element);
+}
+
+void SearchInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeSearch);
+}
+
+LayoutObject* SearchInputType::CreateLayoutObject(const ComputedStyle&) const {
+ return new LayoutSearchField(&GetElement());
+}
+
+const AtomicString& SearchInputType::FormControlType() const {
+ return InputTypeNames::search;
+}
+
+bool SearchInputType::NeedsContainer() const {
+ return true;
+}
+
+void SearchInputType::CreateShadowSubtree() {
+ TextFieldInputType::CreateShadowSubtree();
+ Element* container = ContainerElement();
+ Element* view_port = GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::EditingViewPort());
+ DCHECK(container);
+ DCHECK(view_port);
+ container->InsertBefore(
+ SearchFieldCancelButtonElement::Create(GetElement().GetDocument()),
+ view_port->nextSibling());
+}
+
+void SearchInputType::HandleKeydownEvent(KeyboardEvent* event) {
+ if (GetElement().IsDisabledOrReadOnly()) {
+ TextFieldInputType::HandleKeydownEvent(event);
+ return;
+ }
+
+ const String& key = event->key();
+ if (key == "Escape") {
+ GetElement().SetValueForUser("");
+ GetElement().OnSearch();
+ event->SetDefaultHandled();
+ return;
+ }
+ TextFieldInputType::HandleKeydownEvent(event);
+}
+
+void SearchInputType::StartSearchEventTimer() {
+ DCHECK(GetElement().GetLayoutObject());
+ unsigned length = GetElement().InnerEditorValue().length();
+
+ if (!length) {
+ search_event_timer_.Stop();
+ GetElement()
+ .GetDocument()
+ .GetTaskRunner(TaskType::kUserInteraction)
+ ->PostTask(FROM_HERE, WTF::Bind(&HTMLInputElement::OnSearch,
+ WrapPersistent(&GetElement())));
+ return;
+ }
+
+ // After typing the first key, we wait 0.5 seconds.
+ // After the second key, 0.4 seconds, then 0.3, then 0.2 from then on.
+ search_event_timer_.StartOneShot(max(0.2, 0.6 - 0.1 * length), FROM_HERE);
+}
+
+void SearchInputType::DispatchSearchEvent() {
+ search_event_timer_.Stop();
+ GetElement().DispatchEvent(Event::CreateBubble(EventTypeNames::search));
+}
+
+void SearchInputType::SearchEventTimerFired(TimerBase*) {
+ GetElement().OnSearch();
+}
+
+bool SearchInputType::SearchEventsShouldBeDispatched() const {
+ return GetElement().hasAttribute(incrementalAttr);
+}
+
+void SearchInputType::DidSetValueByUserEdit() {
+ UpdateCancelButtonVisibility();
+
+ // If the incremental attribute is set, then dispatch the search event
+ if (SearchEventsShouldBeDispatched())
+ StartSearchEventTimer();
+
+ TextFieldInputType::DidSetValueByUserEdit();
+}
+
+void SearchInputType::UpdateView() {
+ BaseTextInputType::UpdateView();
+ UpdateCancelButtonVisibility();
+}
+
+void SearchInputType::UpdateCancelButtonVisibility() {
+ Element* button = GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::SearchClearButton());
+ if (!button)
+ return;
+ if (GetElement().value().IsEmpty()) {
+ button->SetInlineStyleProperty(CSSPropertyOpacity, 0.0,
+ CSSPrimitiveValue::UnitType::kNumber);
+ button->SetInlineStyleProperty(CSSPropertyPointerEvents, CSSValueNone);
+ } else {
+ button->RemoveInlineStyleProperty(CSSPropertyOpacity);
+ button->RemoveInlineStyleProperty(CSSPropertyPointerEvents);
+ }
+}
+
+bool SearchInputType::SupportsInputModeAttribute() const {
+ return true;
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..45b6abc3505
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SEARCH_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SEARCH_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h"
+#include "third_party/blink/renderer/platform/timer.h"
+
+namespace blink {
+
+class SearchInputType final : public BaseTextInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ SearchInputType(HTMLInputElement&);
+ void CountUsage() override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) const override;
+ const AtomicString& FormControlType() const override;
+ bool NeedsContainer() const override;
+ void CreateShadowSubtree() override;
+ void HandleKeydownEvent(KeyboardEvent*) override;
+ void DidSetValueByUserEdit() override;
+ bool SupportsInputModeAttribute() const override;
+ void UpdateView() override;
+ void DispatchSearchEvent() override;
+
+ void SearchEventTimerFired(TimerBase*);
+ bool SearchEventsShouldBeDispatched() const;
+ void StartSearchEventTimer();
+ void UpdateCancelButtonVisibility();
+
+ TaskRunnerTimer<SearchInputType> search_event_timer_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SEARCH_INPUT_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
new file mode 100644
index 00000000000..3aaeef9dc0e
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/slider_thumb_element.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/mouse_event.h"
+#include "third_party/blink/renderer/core/events/touch_event.h"
+#include "third_party/blink/renderer/core/frame/event_handler_registry.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/step_range.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
+#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
+#include "third_party/blink/renderer/core/input/event_handler.h"
+#include "third_party/blink/renderer/core/layout/layout_slider_container.h"
+#include "third_party/blink/renderer/core/layout/layout_slider_thumb.h"
+#include "third_party/blink/renderer/core/layout/layout_theme.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline static bool HasVerticalAppearance(HTMLInputElement* input) {
+ DCHECK(input->GetLayoutObject());
+ const ComputedStyle& slider_style = input->GetLayoutObject()->StyleRef();
+
+ return slider_style.Appearance() == kSliderVerticalPart;
+}
+
+inline SliderThumbElement::SliderThumbElement(Document& document)
+ : HTMLDivElement(document), in_drag_mode_(false) {}
+
+SliderThumbElement* SliderThumbElement::Create(Document& document) {
+ SliderThumbElement* element = new SliderThumbElement(document);
+ element->setAttribute(idAttr, ShadowElementNames::SliderThumb());
+ return element;
+}
+
+void SliderThumbElement::SetPositionFromValue() {
+ // Since the code to calculate position is in the LayoutSliderThumb layout
+ // path, we don't actually update the value here. Instead, we poke at the
+ // layoutObject directly to trigger layout.
+ if (GetLayoutObject())
+ GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation(
+ LayoutInvalidationReason::kSliderValueChanged);
+}
+
+LayoutObject* SliderThumbElement::CreateLayoutObject(const ComputedStyle&) {
+ return new LayoutSliderThumb(this);
+}
+
+bool SliderThumbElement::IsDisabledFormControl() const {
+ return HostInput() && HostInput()->IsDisabledFormControl();
+}
+
+bool SliderThumbElement::MatchesReadOnlyPseudoClass() const {
+ return HostInput() && HostInput()->MatchesReadOnlyPseudoClass();
+}
+
+bool SliderThumbElement::MatchesReadWritePseudoClass() const {
+ return HostInput() && HostInput()->MatchesReadWritePseudoClass();
+}
+
+const Node* SliderThumbElement::FocusDelegate() const {
+ return HostInput();
+}
+
+void SliderThumbElement::DragFrom(const LayoutPoint& point) {
+ StartDragging();
+ SetPositionFromPoint(point);
+}
+
+void SliderThumbElement::SetPositionFromPoint(const LayoutPoint& point) {
+ HTMLInputElement* input(HostInput());
+ Element* track_element = input->UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::SliderTrack());
+
+ if (!input->GetLayoutObject() || !GetLayoutBox() ||
+ !track_element->GetLayoutBox())
+ return;
+
+ LayoutPoint offset = LayoutPoint(input->GetLayoutObject()->AbsoluteToLocal(
+ FloatPoint(point), kUseTransforms));
+ bool is_vertical = HasVerticalAppearance(input);
+ bool is_left_to_right_direction =
+ GetLayoutBox()->Style()->IsLeftToRightDirection();
+ LayoutUnit track_size;
+ LayoutUnit position;
+ LayoutUnit current_position;
+ // We need to calculate currentPosition from absolute points becaue the
+ // layoutObject for this node is usually on a layer and layoutBox()->x() and
+ // y() are unusable.
+ // FIXME: This should probably respect transforms.
+ LayoutPoint absolute_thumb_origin =
+ GetLayoutBox()->AbsoluteBoundingBoxRectIgnoringTransforms().Location();
+ LayoutPoint absolute_slider_content_origin =
+ LayoutPoint(input->GetLayoutObject()->LocalToAbsolute());
+ IntRect track_bounding_box =
+ track_element->GetLayoutObject()
+ ->AbsoluteBoundingBoxRectIgnoringTransforms();
+ IntRect input_bounding_box =
+ input->GetLayoutObject()->AbsoluteBoundingBoxRectIgnoringTransforms();
+ if (is_vertical) {
+ track_size = track_element->GetLayoutBox()->ContentHeight() -
+ GetLayoutBox()->Size().Height();
+ position = offset.Y() - GetLayoutBox()->Size().Height() / 2 -
+ track_bounding_box.Y() + input_bounding_box.Y() -
+ GetLayoutBox()->MarginBottom();
+ current_position =
+ absolute_thumb_origin.Y() - absolute_slider_content_origin.Y();
+ } else {
+ track_size = track_element->GetLayoutBox()->ContentWidth() -
+ GetLayoutBox()->Size().Width();
+ position = offset.X() - GetLayoutBox()->Size().Width() / 2 -
+ track_bounding_box.X() + input_bounding_box.X();
+ position -= is_left_to_right_direction ? GetLayoutBox()->MarginLeft()
+ : GetLayoutBox()->MarginRight();
+ current_position =
+ absolute_thumb_origin.X() - absolute_slider_content_origin.X();
+ }
+ position = std::min(position, track_size).ClampNegativeToZero();
+ const Decimal ratio =
+ Decimal::FromDouble(static_cast<double>(position) / track_size);
+ const Decimal fraction =
+ is_vertical || !is_left_to_right_direction ? Decimal(1) - ratio : ratio;
+ StepRange step_range(input->CreateStepRange(kRejectAny));
+ Decimal value =
+ step_range.ClampValue(step_range.ValueFromProportion(fraction));
+
+ Decimal closest = input->FindClosestTickMarkValue(value);
+ if (closest.IsFinite()) {
+ double closest_fraction =
+ step_range.ProportionFromValue(closest).ToDouble();
+ double closest_ratio = is_vertical || !is_left_to_right_direction
+ ? 1.0 - closest_fraction
+ : closest_fraction;
+ LayoutUnit closest_position(track_size * closest_ratio);
+ const LayoutUnit snapping_threshold(5);
+ if ((closest_position - position).Abs() <= snapping_threshold)
+ value = closest;
+ }
+
+ String value_string = SerializeForNumberType(value);
+ if (value_string == input->value())
+ return;
+
+ // FIXME: This is no longer being set from renderer. Consider updating the
+ // method name.
+ input->SetValueFromRenderer(value_string);
+ if (GetLayoutObject())
+ GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation(
+ LayoutInvalidationReason::kSliderValueChanged);
+}
+
+void SliderThumbElement::StartDragging() {
+ if (LocalFrame* frame = GetDocument().GetFrame()) {
+ // Note that we get to here only we through mouse event path. The touch
+ // events are implicitly captured to the starting element and will be
+ // handled in handleTouchEvent function.
+ frame->GetEventHandler().SetPointerCapture(PointerEventFactory::kMouseId,
+ this);
+ in_drag_mode_ = true;
+ }
+}
+
+void SliderThumbElement::StopDragging() {
+ if (!in_drag_mode_)
+ return;
+
+ if (LocalFrame* frame = GetDocument().GetFrame()) {
+ frame->GetEventHandler().ReleasePointerCapture(
+ PointerEventFactory::kMouseId, this);
+ }
+ in_drag_mode_ = false;
+ if (GetLayoutObject())
+ GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation(
+ LayoutInvalidationReason::kSliderValueChanged);
+ if (HostInput())
+ HostInput()->DispatchFormControlChangeEvent();
+}
+
+void SliderThumbElement::DefaultEventHandler(Event* event) {
+ if (event->IsPointerEvent() &&
+ event->type() == EventTypeNames::lostpointercapture) {
+ StopDragging();
+ return;
+ }
+
+ if (!event->IsMouseEvent()) {
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ // FIXME: Should handle this readonly/disabled check in more general way.
+ // Missing this kind of check is likely to occur elsewhere if adding it in
+ // each shadow element.
+ HTMLInputElement* input = HostInput();
+ if (!input || input->IsDisabledFormControl()) {
+ StopDragging();
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ MouseEvent* mouse_event = ToMouseEvent(event);
+ bool is_left_button = mouse_event->button() ==
+ static_cast<short>(WebPointerProperties::Button::kLeft);
+ const AtomicString& event_type = event->type();
+
+ // We intentionally do not call event->setDefaultHandled() here because
+ // MediaControlTimelineElement::defaultEventHandler() wants to handle these
+ // mouse events.
+ if (event_type == EventTypeNames::mousedown && is_left_button) {
+ StartDragging();
+ return;
+ }
+ if (event_type == EventTypeNames::mouseup && is_left_button) {
+ StopDragging();
+ return;
+ }
+ if (event_type == EventTypeNames::mousemove) {
+ if (in_drag_mode_)
+ SetPositionFromPoint(LayoutPoint(mouse_event->AbsoluteLocation()));
+ return;
+ }
+
+ HTMLDivElement::DefaultEventHandler(event);
+}
+
+bool SliderThumbElement::WillRespondToMouseMoveEvents() {
+ const HTMLInputElement* input = HostInput();
+ if (input && !input->IsDisabledFormControl() && in_drag_mode_)
+ return true;
+
+ return HTMLDivElement::WillRespondToMouseMoveEvents();
+}
+
+bool SliderThumbElement::WillRespondToMouseClickEvents() {
+ const HTMLInputElement* input = HostInput();
+ if (input && !input->IsDisabledFormControl())
+ return true;
+
+ return HTMLDivElement::WillRespondToMouseClickEvents();
+}
+
+void SliderThumbElement::DetachLayoutTree(const AttachContext& context) {
+ if (in_drag_mode_) {
+ if (LocalFrame* frame = GetDocument().GetFrame())
+ frame->GetEventHandler().SetCapturingMouseEventsNode(nullptr);
+ }
+ HTMLDivElement::DetachLayoutTree(context);
+}
+
+HTMLInputElement* SliderThumbElement::HostInput() const {
+ // Only HTMLInputElement creates SliderThumbElement instances as its shadow
+ // nodes. So, ownerShadowHost() must be an HTMLInputElement.
+ return ToHTMLInputElement(OwnerShadowHost());
+}
+
+static const AtomicString& SliderThumbShadowPartId() {
+ DEFINE_STATIC_LOCAL(const AtomicString, slider_thumb,
+ ("-webkit-slider-thumb"));
+ return slider_thumb;
+}
+
+static const AtomicString& MediaSliderThumbShadowPartId() {
+ DEFINE_STATIC_LOCAL(const AtomicString, media_slider_thumb,
+ ("-webkit-media-slider-thumb"));
+ return media_slider_thumb;
+}
+
+const AtomicString& SliderThumbElement::ShadowPseudoId() const {
+ HTMLInputElement* input = HostInput();
+ if (!input || !input->GetLayoutObject())
+ return SliderThumbShadowPartId();
+
+ const ComputedStyle& slider_style = input->GetLayoutObject()->StyleRef();
+ switch (slider_style.Appearance()) {
+ case kMediaSliderPart:
+ case kMediaSliderThumbPart:
+ case kMediaVolumeSliderPart:
+ case kMediaVolumeSliderThumbPart:
+ return MediaSliderThumbShadowPartId();
+ default:
+ return SliderThumbShadowPartId();
+ }
+}
+
+// --------------------------------
+
+inline SliderContainerElement::SliderContainerElement(Document& document)
+ : HTMLDivElement(document),
+ has_touch_event_handler_(false),
+ touch_started_(false),
+ sliding_direction_(kNoMove) {
+ UpdateTouchEventHandlerRegistry();
+}
+
+DEFINE_NODE_FACTORY(SliderContainerElement)
+
+HTMLInputElement* SliderContainerElement::HostInput() const {
+ return ToHTMLInputElement(OwnerShadowHost());
+}
+
+LayoutObject* SliderContainerElement::CreateLayoutObject(const ComputedStyle&) {
+ return new LayoutSliderContainer(this);
+}
+
+void SliderContainerElement::DefaultEventHandler(Event* event) {
+ if (event->IsTouchEvent()) {
+ HandleTouchEvent(ToTouchEvent(event));
+ return;
+ }
+}
+
+void SliderContainerElement::HandleTouchEvent(TouchEvent* event) {
+ HTMLInputElement* input = HostInput();
+ if (!input || input->IsDisabledFormControl() || !event)
+ return;
+
+ if (event->type() == EventTypeNames::touchend) {
+ // TODO: Also do this for touchcancel?
+ input->DispatchFormControlChangeEvent();
+ event->SetDefaultHandled();
+ sliding_direction_ = kNoMove;
+ touch_started_ = false;
+ return;
+ }
+
+ // The direction of this series of touch actions has been determined, which is
+ // perpendicular to the slider, so no need to adjust the value.
+ if (!CanSlide()) {
+ return;
+ }
+
+ TouchList* touches = event->targetTouches();
+ SliderThumbElement* thumb = ToSliderThumbElement(
+ GetTreeScope().getElementById(ShadowElementNames::SliderThumb()));
+ if (!thumb || !touches)
+ return;
+
+ if (touches->length() == 1) {
+ if (event->type() == EventTypeNames::touchstart) {
+ start_point_ = touches->item(0)->AbsoluteLocation();
+ sliding_direction_ = kNoMove;
+ touch_started_ = true;
+ thumb->SetPositionFromPoint(touches->item(0)->AbsoluteLocation());
+ } else if (touch_started_) {
+ LayoutPoint current_point = touches->item(0)->AbsoluteLocation();
+ if (sliding_direction_ ==
+ kNoMove) { // Still needs to update the direction.
+ sliding_direction_ = GetDirection(current_point, start_point_);
+ }
+
+ // m_slidingDirection has been updated, so check whether it's okay to
+ // slide again.
+ if (CanSlide()) {
+ thumb->SetPositionFromPoint(touches->item(0)->AbsoluteLocation());
+ event->SetDefaultHandled();
+ }
+ }
+ }
+}
+
+SliderContainerElement::Direction SliderContainerElement::GetDirection(
+ LayoutPoint& point1,
+ LayoutPoint& point2) {
+ if (point1 == point2) {
+ return kNoMove;
+ }
+ if ((point1.X() - point2.X()).Abs() >= (point1.Y() - point2.Y()).Abs()) {
+ return kHorizontal;
+ }
+ return kVertical;
+}
+
+bool SliderContainerElement::CanSlide() {
+ if (!HostInput() || !HostInput()->GetLayoutObject() ||
+ !HostInput()->GetLayoutObject()->Style()) {
+ return false;
+ }
+ const ComputedStyle* slider_style = HostInput()->GetLayoutObject()->Style();
+ const TransformOperations& transforms = slider_style->Transform();
+ int transform_size = transforms.size();
+ if (transform_size > 0) {
+ for (int i = 0; i < transform_size; ++i) {
+ if (transforms.at(i)->GetType() == TransformOperation::kRotate) {
+ return true;
+ }
+ }
+ }
+ if ((sliding_direction_ == kVertical &&
+ slider_style->Appearance() == kSliderHorizontalPart) ||
+ (sliding_direction_ == kHorizontal &&
+ slider_style->Appearance() == kSliderVerticalPart)) {
+ return false;
+ }
+ return true;
+}
+
+const AtomicString& SliderContainerElement::ShadowPseudoId() const {
+ DEFINE_STATIC_LOCAL(const AtomicString, media_slider_container,
+ ("-webkit-media-slider-container"));
+ DEFINE_STATIC_LOCAL(const AtomicString, slider_container,
+ ("-webkit-slider-container"));
+
+ if (!OwnerShadowHost() || !OwnerShadowHost()->GetLayoutObject())
+ return slider_container;
+
+ const ComputedStyle& slider_style =
+ OwnerShadowHost()->GetLayoutObject()->StyleRef();
+ switch (slider_style.Appearance()) {
+ case kMediaSliderPart:
+ case kMediaSliderThumbPart:
+ case kMediaVolumeSliderPart:
+ case kMediaVolumeSliderThumbPart:
+ return media_slider_container;
+ default:
+ return slider_container;
+ }
+}
+
+void SliderContainerElement::UpdateTouchEventHandlerRegistry() {
+ if (has_touch_event_handler_) {
+ return;
+ }
+ if (GetDocument().GetPage() &&
+ GetDocument().Lifecycle().GetState() < DocumentLifecycle::kStopping) {
+ EventHandlerRegistry& registry =
+ GetDocument().GetPage()->GetEventHandlerRegistry();
+ registry.DidAddEventHandler(
+ *this, EventHandlerRegistry::kTouchStartOrMoveEventPassive);
+ registry.DidAddEventHandler(*this, EventHandlerRegistry::kPointerEvent);
+ has_touch_event_handler_ = true;
+ }
+}
+
+void SliderContainerElement::DidMoveToNewDocument(Document& old_document) {
+ UpdateTouchEventHandlerRegistry();
+ HTMLElement::DidMoveToNewDocument(old_document);
+}
+
+void SliderContainerElement::RemoveAllEventListeners() {
+ Node::RemoveAllEventListeners();
+ has_touch_event_handler_ = false;
+}
+
+} // 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
new file mode 100644
index 00000000000..e4e692391d4
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SLIDER_THUMB_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SLIDER_THUMB_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+class HTMLInputElement;
+class Event;
+class TouchEvent;
+
+class SliderThumbElement final : public HTMLDivElement {
+ public:
+ static SliderThumbElement* Create(Document&);
+
+ void SetPositionFromValue();
+
+ void DragFrom(const LayoutPoint&);
+ void DefaultEventHandler(Event*) override;
+ bool WillRespondToMouseMoveEvents() override;
+ bool WillRespondToMouseClickEvents() override;
+ void DetachLayoutTree(const AttachContext& = AttachContext()) override;
+ const AtomicString& ShadowPseudoId() const override;
+ HTMLInputElement* HostInput() const;
+ void SetPositionFromPoint(const LayoutPoint&);
+ void StopDragging();
+
+ private:
+ SliderThumbElement(Document&);
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ Element* CloneWithoutAttributesAndChildren(Document&) const override;
+ bool IsDisabledFormControl() const override;
+ bool MatchesReadOnlyPseudoClass() const override;
+ bool MatchesReadWritePseudoClass() const override;
+ const Node* FocusDelegate() const override;
+ void StartDragging();
+
+ bool
+ in_drag_mode_; // Mouse only. Touch is handled by SliderContainerElement.
+};
+
+inline Element* SliderThumbElement::CloneWithoutAttributesAndChildren(
+ Document& factory) const {
+ return Create(factory);
+}
+
+// FIXME: There are no ways to check if a node is a SliderThumbElement.
+DEFINE_ELEMENT_TYPE_CASTS(SliderThumbElement, IsHTMLElement());
+
+class SliderContainerElement final : public HTMLDivElement {
+ public:
+ enum Direction {
+ kHorizontal,
+ kVertical,
+ kNoMove,
+ };
+
+ DECLARE_NODE_FACTORY(SliderContainerElement);
+ HTMLInputElement* HostInput() const;
+ void DefaultEventHandler(Event*) override;
+ void HandleTouchEvent(TouchEvent*);
+ void UpdateTouchEventHandlerRegistry();
+ void DidMoveToNewDocument(Document&) override;
+ void RemoveAllEventListeners() override;
+
+ private:
+ explicit SliderContainerElement(Document&);
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ const AtomicString& ShadowPseudoId() const override;
+ Direction GetDirection(LayoutPoint&, LayoutPoint&);
+ bool CanSlide();
+
+ bool has_touch_event_handler_;
+ bool touch_started_;
+ Direction sliding_direction_;
+ LayoutPoint start_point_;
+};
+
+} // namespace blink
+
+#endif
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
new file mode 100644
index 00000000000..698c5812176
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/spin_button_element.h"
+
+#include "build/build_config.h"
+#include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/renderer/core/events/mouse_event.h"
+#include "third_party/blink/renderer/core/events/wheel_event.h"
+#include "third_party/blink/renderer/core/frame/local_frame.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/event_handler.h"
+#include "third_party/blink/renderer/core/layout/layout_box.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/platform/scroll/scrollbar_theme.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+inline SpinButtonElement::SpinButtonElement(Document& document,
+ SpinButtonOwner& spin_button_owner)
+ : HTMLDivElement(document),
+ spin_button_owner_(&spin_button_owner),
+ capturing_(false),
+ up_down_state_(kIndeterminate),
+ press_starting_state_(kIndeterminate),
+ repeating_timer_(document.GetTaskRunner(TaskType::kInternalAnimation),
+ this,
+ &SpinButtonElement::RepeatingTimerFired) {}
+
+SpinButtonElement* SpinButtonElement::Create(
+ Document& document,
+ SpinButtonOwner& spin_button_owner) {
+ SpinButtonElement* element =
+ new SpinButtonElement(document, spin_button_owner);
+ element->SetShadowPseudoId(AtomicString("-webkit-inner-spin-button"));
+ element->setAttribute(idAttr, ShadowElementNames::SpinButton());
+ return element;
+}
+
+void SpinButtonElement::DetachLayoutTree(const AttachContext& context) {
+ ReleaseCapture(kEventDispatchDisallowed);
+ HTMLDivElement::DetachLayoutTree(context);
+}
+
+void SpinButtonElement::DefaultEventHandler(Event* event) {
+ if (!event->IsMouseEvent()) {
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ LayoutBox* box = GetLayoutBox();
+ if (!box) {
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ if (!ShouldRespondToMouseEvents()) {
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ MouseEvent* mouse_event = ToMouseEvent(event);
+ IntPoint local = RoundedIntPoint(box->AbsoluteToLocal(
+ FloatPoint(mouse_event->AbsoluteLocation()), kUseTransforms));
+ if (mouse_event->type() == EventTypeNames::mousedown &&
+ mouse_event->button() ==
+ static_cast<short>(WebPointerProperties::Button::kLeft)) {
+ if (box->PixelSnappedBorderBoxRect().Contains(local)) {
+ if (spin_button_owner_)
+ spin_button_owner_->FocusAndSelectSpinButtonOwner();
+ if (GetLayoutObject()) {
+ if (up_down_state_ != kIndeterminate) {
+ // A JavaScript event handler called in doStepAction() below
+ // might change the element state and we might need to
+ // cancel the repeating timer by the state change. If we
+ // started the timer after doStepAction(), we would have no
+ // chance to cancel the timer.
+ StartRepeatingTimer();
+ DoStepAction(up_down_state_ == kUp ? 1 : -1);
+ }
+ }
+ event->SetDefaultHandled();
+ }
+ } else if (mouse_event->type() == EventTypeNames::mouseup &&
+ mouse_event->button() ==
+ static_cast<short>(WebPointerProperties::Button::kLeft)) {
+ ReleaseCapture();
+ } else if (event->type() == EventTypeNames::mousemove) {
+ if (box->PixelSnappedBorderBoxRect().Contains(local)) {
+ if (!capturing_) {
+ if (LocalFrame* frame = GetDocument().GetFrame()) {
+ frame->GetEventHandler().SetCapturingMouseEventsNode(this);
+ capturing_ = true;
+ if (Page* page = GetDocument().GetPage())
+ page->GetChromeClient().RegisterPopupOpeningObserver(this);
+ }
+ }
+ UpDownState old_up_down_state = up_down_state_;
+ up_down_state_ = (local.Y() < box->Size().Height() / 2) ? kUp : kDown;
+ if (up_down_state_ != old_up_down_state)
+ GetLayoutObject()->SetShouldDoFullPaintInvalidation();
+ } else {
+ ReleaseCapture();
+ up_down_state_ = kIndeterminate;
+ }
+ }
+
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+}
+
+void SpinButtonElement::WillOpenPopup() {
+ ReleaseCapture();
+ up_down_state_ = kIndeterminate;
+}
+
+void SpinButtonElement::ForwardEvent(Event* event) {
+ if (!GetLayoutBox())
+ return;
+
+ if (!event->HasInterface(EventNames::WheelEvent))
+ return;
+
+ if (!spin_button_owner_)
+ return;
+
+ if (!spin_button_owner_->ShouldSpinButtonRespondToWheelEvents())
+ return;
+
+ DoStepAction(ToWheelEvent(event)->wheelDeltaY());
+ event->SetDefaultHandled();
+}
+
+bool SpinButtonElement::WillRespondToMouseMoveEvents() {
+ if (GetLayoutBox() && ShouldRespondToMouseEvents())
+ return true;
+
+ return HTMLDivElement::WillRespondToMouseMoveEvents();
+}
+
+bool SpinButtonElement::WillRespondToMouseClickEvents() {
+ if (GetLayoutBox() && ShouldRespondToMouseEvents())
+ return true;
+
+ return HTMLDivElement::WillRespondToMouseClickEvents();
+}
+
+void SpinButtonElement::DoStepAction(int amount) {
+ if (!spin_button_owner_)
+ return;
+
+ if (amount > 0)
+ spin_button_owner_->SpinButtonStepUp();
+ else if (amount < 0)
+ spin_button_owner_->SpinButtonStepDown();
+}
+
+void SpinButtonElement::ReleaseCapture(EventDispatch event_dispatch) {
+ StopRepeatingTimer();
+ if (!capturing_)
+ return;
+ if (LocalFrame* frame = GetDocument().GetFrame()) {
+ frame->GetEventHandler().SetCapturingMouseEventsNode(nullptr);
+ capturing_ = false;
+ if (Page* page = GetDocument().GetPage())
+ page->GetChromeClient().UnregisterPopupOpeningObserver(this);
+ }
+ if (spin_button_owner_)
+ spin_button_owner_->SpinButtonDidReleaseMouseCapture(event_dispatch);
+}
+
+bool SpinButtonElement::MatchesReadOnlyPseudoClass() const {
+ return OwnerShadowHost()->MatchesReadOnlyPseudoClass();
+}
+
+bool SpinButtonElement::MatchesReadWritePseudoClass() const {
+ return OwnerShadowHost()->MatchesReadWritePseudoClass();
+}
+
+void SpinButtonElement::StartRepeatingTimer() {
+ press_starting_state_ = up_down_state_;
+ Page* page = GetDocument().GetPage();
+ DCHECK(page);
+ ScrollbarTheme& theme = page->GetScrollbarTheme();
+ repeating_timer_.Start(theme.InitialAutoscrollTimerDelay(),
+ theme.AutoscrollTimerDelay(), FROM_HERE);
+}
+
+void SpinButtonElement::StopRepeatingTimer() {
+ repeating_timer_.Stop();
+}
+
+void SpinButtonElement::Step(int amount) {
+ if (!ShouldRespondToMouseEvents())
+ return;
+// On Mac OS, NSStepper updates the value for the button under the mouse
+// cursor regardless of the button pressed at the beginning. So the
+// following check is not needed for Mac OS.
+#if !defined(OS_MACOSX)
+ if (up_down_state_ != press_starting_state_)
+ return;
+#endif
+ DoStepAction(amount);
+}
+
+void SpinButtonElement::RepeatingTimerFired(TimerBase*) {
+ if (up_down_state_ != kIndeterminate)
+ Step(up_down_state_ == kUp ? 1 : -1);
+}
+
+void SpinButtonElement::SetHovered(bool flag) {
+ if (!flag)
+ up_down_state_ = kIndeterminate;
+ HTMLDivElement::SetHovered(flag);
+}
+
+bool SpinButtonElement::ShouldRespondToMouseEvents() {
+ return !spin_button_owner_ ||
+ spin_button_owner_->ShouldSpinButtonRespondToMouseEvents();
+}
+
+void SpinButtonElement::Trace(blink::Visitor* visitor) {
+ visitor->Trace(spin_button_owner_);
+ HTMLDivElement::Trace(visitor);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.h b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.h
new file mode 100644
index 00000000000..88dc9b64e12
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SPIN_BUTTON_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SPIN_BUTTON_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/core/page/popup_opening_observer.h"
+#include "third_party/blink/renderer/platform/timer.h"
+
+namespace blink {
+
+class CORE_EXPORT SpinButtonElement final : public HTMLDivElement,
+ public PopupOpeningObserver {
+ public:
+ enum UpDownState {
+ kIndeterminate, // Hovered, but the event is not handled.
+ kDown,
+ kUp,
+ };
+ enum EventDispatch {
+ kEventDispatchAllowed,
+ kEventDispatchDisallowed,
+ };
+ class SpinButtonOwner : public GarbageCollectedMixin {
+ public:
+ virtual ~SpinButtonOwner() = default;
+ virtual void FocusAndSelectSpinButtonOwner() = 0;
+ virtual bool ShouldSpinButtonRespondToMouseEvents() = 0;
+ virtual bool ShouldSpinButtonRespondToWheelEvents() = 0;
+ virtual void SpinButtonDidReleaseMouseCapture(EventDispatch) = 0;
+ virtual void SpinButtonStepDown() = 0;
+ virtual void SpinButtonStepUp() = 0;
+ };
+
+ // The owner of SpinButtonElement must call removeSpinButtonOwner
+ // because SpinButtonElement can be outlive SpinButtonOwner
+ // implementation, e.g. during event handling.
+ static SpinButtonElement* Create(Document&, SpinButtonOwner&);
+ UpDownState GetUpDownState() const { return up_down_state_; }
+ void ReleaseCapture(EventDispatch = kEventDispatchAllowed);
+ void RemoveSpinButtonOwner() { spin_button_owner_ = nullptr; }
+
+ void Step(int amount);
+
+ bool WillRespondToMouseMoveEvents() override;
+ bool WillRespondToMouseClickEvents() override;
+
+ void ForwardEvent(Event*);
+
+ void Trace(blink::Visitor*) override;
+
+ private:
+ SpinButtonElement(Document&, SpinButtonOwner&);
+
+ void DetachLayoutTree(const AttachContext&) override;
+ bool IsSpinButtonElement() const override { return true; }
+ bool IsDisabledFormControl() const override {
+ return OwnerShadowHost() && OwnerShadowHost()->IsDisabledFormControl();
+ }
+ bool MatchesReadOnlyPseudoClass() const override;
+ bool MatchesReadWritePseudoClass() const override;
+ void DefaultEventHandler(Event*) override;
+ void WillOpenPopup() override;
+ void DoStepAction(int);
+ void StartRepeatingTimer();
+ void StopRepeatingTimer();
+ void RepeatingTimerFired(TimerBase*);
+ void SetHovered(bool = true) override;
+ bool ShouldRespondToMouseEvents();
+ bool IsMouseFocusable() const override { return false; }
+
+ Member<SpinButtonOwner> spin_button_owner_;
+ bool capturing_;
+ UpDownState up_down_state_;
+ UpDownState press_starting_state_;
+ TaskRunnerTimer<SpinButtonElement> repeating_timer_;
+};
+
+DEFINE_TYPE_CASTS(SpinButtonElement,
+ Node,
+ node,
+ ToElement(node)->IsSpinButtonElement(),
+ ToElement(node).IsSpinButtonElement());
+
+} // namespace blink
+
+#endif
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
new file mode 100644
index 00000000000..63a3a36d0f9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/step_range.cc
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
+ *
+ * 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/step_range.h"
+
+#include <float.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/platform/wtf/math_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+StepRange::StepRange()
+ : maximum_(100),
+ minimum_(0),
+ step_(1),
+ step_base_(0),
+ has_step_(false),
+ has_range_limitations_(false) {}
+
+StepRange::StepRange(const StepRange& step_range) = default;
+
+StepRange::StepRange(const Decimal& step_base,
+ const Decimal& minimum,
+ const Decimal& maximum,
+ bool has_range_limitations,
+ const Decimal& step,
+ const StepDescription& step_description)
+ : maximum_(maximum),
+ minimum_(minimum),
+ step_(step.IsFinite() ? step : 1),
+ step_base_(step_base.IsFinite() ? step_base : 1),
+ step_description_(step_description),
+ has_step_(step.IsFinite()),
+ has_range_limitations_(has_range_limitations) {
+ DCHECK(maximum_.IsFinite());
+ DCHECK(minimum_.IsFinite());
+ DCHECK(step_.IsFinite());
+ DCHECK(step_base_.IsFinite());
+}
+
+Decimal StepRange::AcceptableError() const {
+ // FIXME: We should use DBL_MANT_DIG instead of FLT_MANT_DIG regarding to
+ // HTML5 specification.
+ DEFINE_STATIC_LOCAL(const Decimal, two_power_of_float_mantissa_bits,
+ (Decimal::kPositive, 0, UINT64_C(1) << FLT_MANT_DIG));
+ return step_description_.step_value_should_be == kStepValueShouldBeReal
+ ? step_ / two_power_of_float_mantissa_bits
+ : Decimal(0);
+}
+
+Decimal StepRange::AlignValueForStep(const Decimal& current_value,
+ const Decimal& new_value) const {
+ DEFINE_STATIC_LOCAL(const Decimal, ten_power_of21,
+ (Decimal::kPositive, 21, 1));
+ if (new_value >= ten_power_of21)
+ return new_value;
+
+ return StepMismatch(current_value) ? new_value
+ : RoundByStep(new_value, step_base_);
+}
+
+Decimal StepRange::ClampValue(const Decimal& value) const {
+ const Decimal in_range_value = std::max(minimum_, std::min(value, maximum_));
+ if (!has_step_)
+ return in_range_value;
+ // Rounds inRangeValue to stepBase + N * step.
+ const Decimal rounded_value = RoundByStep(in_range_value, step_base_);
+ const Decimal clamped_value =
+ rounded_value > maximum_
+ ? rounded_value - step_
+ : (rounded_value < minimum_ ? rounded_value + step_ : rounded_value);
+ // clampedValue can be outside of [m_minimum, m_maximum] if m_step is huge.
+ if (clamped_value < minimum_ || clamped_value > maximum_)
+ return in_range_value;
+ return clamped_value;
+}
+
+Decimal StepRange::ParseStep(AnyStepHandling any_step_handling,
+ const StepDescription& step_description,
+ const String& step_string) {
+ if (step_string.IsEmpty())
+ return step_description.DefaultValue();
+
+ if (DeprecatedEqualIgnoringCase(step_string, "any")) {
+ switch (any_step_handling) {
+ case kRejectAny:
+ return Decimal::Nan();
+ case kAnyIsDefaultStep:
+ return step_description.DefaultValue();
+ default:
+ NOTREACHED();
+ }
+ }
+
+ Decimal step = ParseToDecimalForNumberType(step_string);
+ if (!step.IsFinite() || step <= 0)
+ return step_description.DefaultValue();
+
+ switch (step_description.step_value_should_be) {
+ case kStepValueShouldBeReal:
+ step *= step_description.step_scale_factor;
+ break;
+ case kParsedStepValueShouldBeInteger:
+ // For date, month, and week, the parsed value should be an integer for
+ // some types.
+ step = std::max(step.Round(), Decimal(1));
+ step *= step_description.step_scale_factor;
+ break;
+ case kScaledStepValueShouldBeInteger:
+ // For datetime, datetime-local, time, the result should be an integer.
+ step *= step_description.step_scale_factor;
+ step = std::max(step.Round(), Decimal(1));
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ DCHECK_GT(step, 0);
+ return step;
+}
+
+Decimal StepRange::RoundByStep(const Decimal& value,
+ const Decimal& base) const {
+ return base + ((value - base) / step_).Round() * step_;
+}
+
+bool StepRange::StepMismatch(const Decimal& value_for_check) const {
+ if (!has_step_)
+ return false;
+ if (!value_for_check.IsFinite())
+ return false;
+ const Decimal value = (value_for_check - step_base_).Abs();
+ if (!value.IsFinite())
+ return false;
+ // Decimal's fractional part size is DBL_MAN_DIG-bit. If the current value
+ // is greater than step*2^DBL_MANT_DIG, the following computation for
+ // remainder makes no sense.
+ DEFINE_STATIC_LOCAL(const Decimal, two_power_of_double_mantissa_bits,
+ (Decimal::kPositive, 0, UINT64_C(1) << DBL_MANT_DIG));
+ if (value / two_power_of_double_mantissa_bits > step_)
+ return false;
+ // The computation follows HTML5 4.10.7.2.10 `The step attribute' :
+ // ... that number subtracted from the step base is not an integral multiple
+ // of the allowed value step, the element is suffering from a step mismatch.
+ const Decimal remainder = (value - step_ * (value / step_).Round()).Abs();
+ // Accepts errors in lower fractional part which IEEE 754 single-precision
+ // can't represent.
+ const Decimal computed_acceptable_error = AcceptableError();
+ return computed_acceptable_error < remainder &&
+ remainder < (step_ - computed_acceptable_error);
+}
+
+Decimal StepRange::StepSnappedMaximum() const {
+ Decimal base = StepBase();
+ Decimal step = Step();
+ if (base - step == base || !(base / step).IsFinite())
+ return Decimal::Nan();
+ Decimal aligned_maximum = base + ((Maximum() - base) / step).Floor() * step;
+ if (aligned_maximum > Maximum())
+ aligned_maximum -= step;
+ DCHECK_LE(aligned_maximum, Maximum());
+ if (aligned_maximum < Minimum())
+ return Decimal::Nan();
+ return aligned_maximum;
+}
+
+} // 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
new file mode 100644
index 00000000000..b3177715881
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/step_range.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_STEP_RANGE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_STEP_RANGE_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/decimal.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+enum AnyStepHandling { kRejectAny, kAnyIsDefaultStep };
+
+class CORE_EXPORT StepRange {
+ DISALLOW_NEW();
+
+ public:
+ enum StepValueShouldBe {
+ kStepValueShouldBeReal,
+ kParsedStepValueShouldBeInteger,
+ kScaledStepValueShouldBeInteger,
+ };
+
+ struct StepDescription {
+ USING_FAST_MALLOC(StepDescription);
+
+ public:
+ int default_step;
+ int default_step_base;
+ int step_scale_factor;
+ StepValueShouldBe step_value_should_be;
+
+ StepDescription(
+ int default_step,
+ int default_step_base,
+ int step_scale_factor,
+ StepValueShouldBe step_value_should_be = kStepValueShouldBeReal)
+ : default_step(default_step),
+ default_step_base(default_step_base),
+ step_scale_factor(step_scale_factor),
+ step_value_should_be(step_value_should_be) {}
+
+ StepDescription()
+ : default_step(1),
+ default_step_base(0),
+ step_scale_factor(1),
+ step_value_should_be(kStepValueShouldBeReal) {}
+
+ Decimal DefaultValue() const { return default_step * step_scale_factor; }
+ };
+
+ StepRange();
+ StepRange(const StepRange&);
+ StepRange(const Decimal& step_base,
+ const Decimal& minimum,
+ const Decimal& maximum,
+ bool has_range_limitations,
+ const Decimal& step,
+ const StepDescription&);
+
+ Decimal AlignValueForStep(const Decimal& current_value,
+ const Decimal& new_value) const;
+ Decimal ClampValue(const Decimal& value) const;
+ bool HasStep() const { return has_step_; }
+ Decimal Maximum() const { return maximum_; }
+ Decimal Minimum() const { return minimum_; }
+ // https://html.spec.whatwg.org/multipage/forms.html#have-range-limitations
+ bool HasRangeLimitations() const { return has_range_limitations_; }
+ static Decimal ParseStep(AnyStepHandling,
+ const StepDescription&,
+ const String&);
+ Decimal Step() const { return step_; }
+ Decimal StepBase() const { return step_base_; }
+ bool StepMismatch(const Decimal&) const;
+ // Returns the maximum step-matched value between minimum() and
+ // maximum(). If there's no such value, this returns Decimal::nan().
+ Decimal StepSnappedMaximum() const;
+
+ // Clamp the middle value according to the step
+ Decimal DefaultValue() const { return ClampValue((minimum_ + maximum_) / 2); }
+
+ // Map value into 0-1 range
+ Decimal ProportionFromValue(const Decimal& value) const {
+ if (minimum_ == maximum_)
+ return 0;
+
+ return (value - minimum_) / (maximum_ - minimum_);
+ }
+
+ // Map from 0-1 range to value
+ Decimal ValueFromProportion(const Decimal& proportion) const {
+ return minimum_ + proportion * (maximum_ - minimum_);
+ }
+
+ private:
+ StepRange& operator=(const StepRange&) = delete;
+ Decimal AcceptableError() const;
+ Decimal RoundByStep(const Decimal& value, const Decimal& base) const;
+
+ const Decimal maximum_; // maximum must be >= minimum.
+ const Decimal minimum_;
+ const Decimal step_;
+ const Decimal step_base_;
+ const StepDescription step_description_;
+ const bool has_step_;
+ const bool has_range_limitations_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_STEP_RANGE_H_
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
new file mode 100644
index 00000000000..eddc58b9b28
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2015 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/step_range.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+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());
+
+ EXPECT_EQ(Decimal(100), step_range.ClampValue(Decimal(200)));
+ EXPECT_EQ(Decimal(0), step_range.ClampValue(Decimal(-100)));
+}
+
+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());
+ EXPECT_EQ(Decimal(90), step_range.StepSnappedMaximum());
+
+ // crbug.com/617809
+ // <input type=number
+ // 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),
+ StepRange::StepDescription());
+ EXPECT_FALSE(step_range2.StepSnappedMaximum().IsFinite());
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.cc
new file mode 100644
index 00000000000..d4d0a48280a
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.cc
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/submit_input_type.h"
+
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/frame/use_counter.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_input_element.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+InputType* SubmitInputType::Create(HTMLInputElement& element) {
+ UseCounter::Count(element.GetDocument(), WebFeature::kInputTypeSubmit);
+ return new SubmitInputType(element);
+}
+
+const AtomicString& SubmitInputType::FormControlType() const {
+ return InputTypeNames::submit;
+}
+
+void SubmitInputType::AppendToFormData(FormData& form_data) const {
+ if (GetElement().IsActivatedSubmit())
+ form_data.append(GetElement().GetName(),
+ GetElement().ValueOrDefaultLabel());
+}
+
+bool SubmitInputType::SupportsRequired() const {
+ return false;
+}
+
+void SubmitInputType::HandleDOMActivateEvent(Event* event) {
+ if (GetElement().IsDisabledFormControl() || !GetElement().Form())
+ return;
+ GetElement().Form()->PrepareForSubmission(
+ event, &GetElement()); // Event handlers can run.
+ event->SetDefaultHandled();
+}
+
+bool SubmitInputType::CanBeSuccessfulSubmitButton() {
+ return true;
+}
+
+String SubmitInputType::DefaultLabel() const {
+ return GetLocale().QueryString(WebLocalizedString::kSubmitButtonDefaultLabel);
+}
+
+bool SubmitInputType::IsTextButton() const {
+ return true;
+}
+
+void SubmitInputType::ValueAttributeChanged() {
+ UseCounter::Count(GetElement().GetDocument(),
+ WebFeature::kInputTypeSubmitWithValue);
+ BaseButtonInputType::ValueAttributeChanged();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.h
new file mode 100644
index 00000000000..22d94696d35
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h"
+
+namespace blink {
+
+class SubmitInputType final : public BaseButtonInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ SubmitInputType(HTMLInputElement& element) : BaseButtonInputType(element) {}
+ const AtomicString& FormControlType() const override;
+ void AppendToFormData(FormData&) const override;
+ bool SupportsRequired() const override;
+ void HandleDOMActivateEvent(Event*) override;
+ bool CanBeSuccessfulSubmitButton() override;
+ String DefaultLabel() const override;
+ bool IsTextButton() const override;
+ void ValueAttributeChanged() override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.cc
new file mode 100644
index 00000000000..75bf3b1677e
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/telephone_input_type.h"
+
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+
+namespace blink {
+
+InputType* TelephoneInputType::Create(HTMLInputElement& element) {
+ return new TelephoneInputType(element);
+}
+
+void TelephoneInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeTel);
+}
+
+const AtomicString& TelephoneInputType::FormControlType() const {
+ return InputTypeNames::tel;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.h
new file mode 100644
index 00000000000..b9b8ca7131d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TELEPHONE_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TELEPHONE_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h"
+
+namespace blink {
+
+class TelephoneInputType final : public BaseTextInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ TelephoneInputType(HTMLInputElement& element) : BaseTextInputType(element) {}
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TELEPHONE_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..5a4aab14f69
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc
@@ -0,0 +1,1006 @@
+/*
+ * 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 Apple Inc. All rights reserved.
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.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/text_control_element.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h"
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#include "third_party/blink/renderer/core/dom/ax_object_cache.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/dom/events/event.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/editing/editing_utilities.h"
+#include "third_party/blink/renderer/core/editing/editor.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/iterators/character_iterator.h"
+#include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
+#include "third_party/blink/renderer/core/editing/position.h"
+#include "third_party/blink/renderer/core/editing/selection_template.h"
+#include "third_party/blink/renderer/core/editing/serializers/serialization.h"
+#include "third_party/blink/renderer/core/editing/set_selection_options.h"
+#include "third_party/blink/renderer/core/editing/visible_position.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h"
+#include "third_party/blink/renderer/core/html/html_br_element.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.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/layout/layout_block.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/layout_theme.h"
+#include "third_party/blink/renderer/core/page/focus_controller.h"
+#include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+TextControlElement::TextControlElement(const QualifiedName& tag_name,
+ Document& doc)
+ : HTMLFormControlElementWithState(tag_name, doc),
+ last_change_was_user_edit_(false),
+ cached_selection_start_(0),
+ cached_selection_end_(0) {
+ cached_selection_direction_ =
+ doc.GetFrame() && doc.GetFrame()
+ ->GetEditor()
+ .Behavior()
+ .ShouldConsiderSelectionAsDirectional()
+ ? kSelectionHasForwardDirection
+ : kSelectionHasNoDirection;
+}
+
+TextControlElement::~TextControlElement() = default;
+
+void TextControlElement::DispatchFocusEvent(
+ Element* old_focused_element,
+ WebFocusType type,
+ InputDeviceCapabilities* source_capabilities) {
+ if (SupportsPlaceholder())
+ UpdatePlaceholderVisibility();
+ HandleFocusEvent(old_focused_element, type);
+ HTMLFormControlElementWithState::DispatchFocusEvent(old_focused_element, type,
+ source_capabilities);
+}
+
+void TextControlElement::DispatchBlurEvent(
+ Element* new_focused_element,
+ WebFocusType type,
+ InputDeviceCapabilities* source_capabilities) {
+ if (SupportsPlaceholder())
+ UpdatePlaceholderVisibility();
+ HandleBlurEvent();
+ HTMLFormControlElementWithState::DispatchBlurEvent(new_focused_element, type,
+ source_capabilities);
+}
+
+void TextControlElement::DefaultEventHandler(Event* event) {
+ if (event->type() == EventTypeNames::webkitEditableContentChanged &&
+ GetLayoutObject() && GetLayoutObject()->IsTextControl()) {
+ last_change_was_user_edit_ = !GetDocument().IsRunningExecCommand();
+
+ if (IsFocused()) {
+ // Updating the cache in SelectionChanged() isn't enough because
+ // SelectionChanged() is not called if:
+ // - Text nodes in the inner-editor is split to multiple, and
+ // - The caret is on the beginning of a Text node, and its previous node
+ // is updated, or
+ // - The caret is on the end of a text node, and its next node is updated.
+ CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(),
+ ComputeSelectionDirection());
+ }
+
+ SubtreeHasChanged();
+ return;
+ }
+
+ HTMLFormControlElementWithState::DefaultEventHandler(event);
+}
+
+void TextControlElement::ForwardEvent(Event* event) {
+ if (event->type() == EventTypeNames::blur ||
+ event->type() == EventTypeNames::focus)
+ return;
+ InnerEditorElement()->DefaultEventHandler(event);
+}
+
+String TextControlElement::StrippedPlaceholder() const {
+ // According to the HTML5 specification, we need to remove CR and LF from
+ // the attribute value.
+ const AtomicString& attribute_value = FastGetAttribute(placeholderAttr);
+ if (!attribute_value.Contains(kNewlineCharacter) &&
+ !attribute_value.Contains(kCarriageReturnCharacter))
+ return attribute_value;
+
+ StringBuilder stripped;
+ unsigned length = attribute_value.length();
+ stripped.ReserveCapacity(length);
+ for (unsigned i = 0; i < length; ++i) {
+ UChar character = attribute_value[i];
+ if (character == kNewlineCharacter || character == kCarriageReturnCharacter)
+ continue;
+ stripped.Append(character);
+ }
+ return stripped.ToString();
+}
+
+static bool IsNotLineBreak(UChar ch) {
+ return ch != kNewlineCharacter && ch != kCarriageReturnCharacter;
+}
+
+bool TextControlElement::IsPlaceholderEmpty() const {
+ const AtomicString& attribute_value = FastGetAttribute(placeholderAttr);
+ return attribute_value.GetString().Find(IsNotLineBreak) == kNotFound;
+}
+
+bool TextControlElement::PlaceholderShouldBeVisible() const {
+ return SupportsPlaceholder() && InnerEditorValue().IsEmpty() &&
+ !IsPlaceholderEmpty() && SuggestedValue().IsEmpty();
+}
+
+HTMLElement* TextControlElement::PlaceholderElement() const {
+ if (!SupportsPlaceholder())
+ return nullptr;
+ DCHECK(UserAgentShadowRoot());
+ return ToHTMLElementOrDie(
+ UserAgentShadowRoot()->getElementById(ShadowElementNames::Placeholder()));
+}
+
+void TextControlElement::UpdatePlaceholderVisibility() {
+ HTMLElement* placeholder = PlaceholderElement();
+ if (!placeholder) {
+ UpdatePlaceholderText();
+ return;
+ }
+
+ bool place_holder_was_visible = IsPlaceholderVisible();
+ SetPlaceholderVisibility(PlaceholderShouldBeVisible());
+
+ placeholder->SetInlineStyleProperty(
+ CSSPropertyDisplay,
+ IsPlaceholderVisible() || !SuggestedValue().IsEmpty() ? CSSValueBlock
+ : CSSValueNone,
+ true);
+
+ // If there was a visibility change not caused by the suggested value, set
+ // that the pseudo state changed.
+ if (place_holder_was_visible != IsPlaceholderVisible() &&
+ SuggestedValue().IsEmpty()) {
+ PseudoStateChanged(CSSSelector::kPseudoPlaceholderShown);
+ }
+}
+
+void TextControlElement::setSelectionStart(unsigned start) {
+ setSelectionRangeForBinding(start, std::max(start, selectionEnd()),
+ selectionDirection());
+}
+
+void TextControlElement::setSelectionEnd(unsigned end) {
+ setSelectionRangeForBinding(std::min(end, selectionStart()), end,
+ selectionDirection());
+}
+
+void TextControlElement::setSelectionDirection(const String& direction) {
+ setSelectionRangeForBinding(selectionStart(), selectionEnd(), direction);
+}
+
+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));
+ RestoreCachedSelection();
+}
+
+void TextControlElement::SetValueBeforeFirstUserEditIfNotSet() {
+ if (!value_before_first_user_edit_.IsNull())
+ return;
+ String value = this->value();
+ value_before_first_user_edit_ = value.IsNull() ? g_empty_string : value;
+}
+
+void TextControlElement::CheckIfValueWasReverted(const String& value) {
+ DCHECK(!value_before_first_user_edit_.IsNull())
+ << "setValueBeforeFirstUserEditIfNotSet should be called beforehand.";
+ String non_null_value = value.IsNull() ? g_empty_string : value;
+ if (value_before_first_user_edit_ == non_null_value)
+ ClearValueBeforeFirstUserEdit();
+}
+
+void TextControlElement::ClearValueBeforeFirstUserEdit() {
+ value_before_first_user_edit_ = String();
+}
+
+void TextControlElement::SetFocused(bool flag, WebFocusType focus_type) {
+ HTMLFormControlElementWithState::SetFocused(flag, focus_type);
+
+ if (!flag)
+ DispatchFormControlChangeEvent();
+}
+
+void TextControlElement::DispatchFormControlChangeEvent() {
+ if (!value_before_first_user_edit_.IsNull() &&
+ !EqualIgnoringNullity(value_before_first_user_edit_, value())) {
+ ClearValueBeforeFirstUserEdit();
+ DispatchChangeEvent();
+ } else {
+ ClearValueBeforeFirstUserEdit();
+ }
+}
+
+void TextControlElement::EnqueueChangeEvent() {
+ if (!value_before_first_user_edit_.IsNull() &&
+ !EqualIgnoringNullity(value_before_first_user_edit_, value())) {
+ Event* event = Event::CreateBubble(EventTypeNames::change);
+ event->SetTarget(this);
+ GetDocument().EnqueueAnimationFrameEvent(event);
+ }
+ ClearValueBeforeFirstUserEdit();
+}
+
+void TextControlElement::setRangeText(const String& replacement,
+ ExceptionState& exception_state) {
+ setRangeText(replacement, selectionStart(), selectionEnd(), "preserve",
+ exception_state);
+}
+
+void TextControlElement::setRangeText(const String& replacement,
+ unsigned start,
+ unsigned end,
+ const String& selection_mode,
+ ExceptionState& exception_state) {
+ if (start > end) {
+ exception_state.ThrowDOMException(
+ kIndexSizeError, "The provided start value (" + String::Number(start) +
+ ") is larger than the provided end value (" +
+ String::Number(end) + ").");
+ return;
+ }
+ if (OpenShadowRoot())
+ return;
+
+ String text = InnerEditorValue();
+ unsigned text_length = text.length();
+ unsigned replacement_length = replacement.length();
+ unsigned new_selection_start = selectionStart();
+ unsigned new_selection_end = selectionEnd();
+
+ start = std::min(start, text_length);
+ end = std::min(end, text_length);
+
+ if (start < end)
+ text.replace(start, end - start, replacement);
+ else
+ text.insert(replacement, start);
+
+ setValue(text, TextFieldEventBehavior::kDispatchNoEvent,
+ TextControlSetValueSelection::kDoNotSet);
+
+ if (selection_mode == "select") {
+ new_selection_start = start;
+ new_selection_end = start + replacement_length;
+ } else if (selection_mode == "start") {
+ new_selection_start = new_selection_end = start;
+ } else if (selection_mode == "end") {
+ new_selection_start = new_selection_end = start + replacement_length;
+ } else {
+ DCHECK_EQ(selection_mode, "preserve");
+ long delta = replacement_length - (end - start);
+
+ if (new_selection_start > end)
+ new_selection_start += delta;
+ else if (new_selection_start > start)
+ new_selection_start = start;
+
+ if (new_selection_end > end)
+ new_selection_end += delta;
+ else if (new_selection_end > start)
+ new_selection_end = start + replacement_length;
+ }
+
+ setSelectionRangeForBinding(new_selection_start, new_selection_end);
+}
+
+void TextControlElement::setSelectionRangeForBinding(
+ unsigned start,
+ unsigned end,
+ const String& direction_string) {
+ TextFieldSelectionDirection direction = kSelectionHasNoDirection;
+ if (direction_string == "forward")
+ direction = kSelectionHasForwardDirection;
+ else if (direction_string == "backward")
+ direction = kSelectionHasBackwardDirection;
+ if (SetSelectionRange(start, end, direction))
+ ScheduleSelectEvent();
+}
+
+static Position PositionForIndex(HTMLElement* inner_editor, unsigned index) {
+ if (index == 0) {
+ Node* node = NodeTraversal::Next(*inner_editor, inner_editor);
+ if (node && node->IsTextNode())
+ return Position(node, 0);
+ return Position(inner_editor, 0);
+ }
+ unsigned remaining_characters_to_move_forward = index;
+ Node* last_br_or_text = inner_editor;
+ for (Node& node : NodeTraversal::DescendantsOf(*inner_editor)) {
+ if (node.HasTagName(brTag)) {
+ if (remaining_characters_to_move_forward == 0)
+ return Position::BeforeNode(node);
+ --remaining_characters_to_move_forward;
+ last_br_or_text = &node;
+ continue;
+ }
+
+ if (node.IsTextNode()) {
+ Text& text = ToText(node);
+ if (remaining_characters_to_move_forward < text.length())
+ return Position(&text, remaining_characters_to_move_forward);
+ remaining_characters_to_move_forward -= text.length();
+ last_br_or_text = &node;
+ continue;
+ }
+
+ NOTREACHED();
+ }
+ DCHECK(last_br_or_text);
+ return LastPositionInOrAfterNode(*last_br_or_text);
+}
+
+unsigned TextControlElement::IndexForPosition(HTMLElement* inner_editor,
+ const Position& passed_position) {
+ if (!inner_editor || !inner_editor->contains(passed_position.AnchorNode()) ||
+ passed_position.IsNull())
+ return 0;
+
+ if (Position::BeforeNode(*inner_editor) == passed_position)
+ return 0;
+
+ unsigned index = 0;
+ Node* start_node = passed_position.ComputeNodeBeforePosition();
+ if (!start_node)
+ start_node = passed_position.ComputeContainerNode();
+ if (start_node == inner_editor && passed_position.IsAfterAnchor())
+ start_node = inner_editor->lastChild();
+ DCHECK(start_node);
+ DCHECK(inner_editor->contains(start_node));
+
+ for (Node* node = start_node; node;
+ node = NodeTraversal::Previous(*node, inner_editor)) {
+ if (node->IsTextNode()) {
+ int length = ToText(*node).length();
+ if (node == passed_position.ComputeContainerNode())
+ index += std::min(length, passed_position.OffsetInContainerNode());
+ else
+ index += length;
+ } else if (node->HasTagName(brTag)) {
+ ++index;
+ }
+ }
+
+ return index;
+}
+
+bool TextControlElement::SetSelectionRange(
+ unsigned start,
+ unsigned end,
+ TextFieldSelectionDirection direction) {
+ if (OpenShadowRoot() || !IsTextControl())
+ return false;
+ const unsigned editor_value_length = InnerEditorValue().length();
+ end = std::min(end, editor_value_length);
+ start = std::min(start, end);
+ LocalFrame* frame = GetDocument().GetFrame();
+ if (direction == kSelectionHasNoDirection && frame &&
+ frame->GetEditor().Behavior().ShouldConsiderSelectionAsDirectional())
+ direction = kSelectionHasForwardDirection;
+ bool did_change = CacheSelection(start, end, direction);
+
+ if (GetDocument().FocusedElement() != this)
+ return did_change;
+
+ HTMLElement* inner_editor = InnerEditorElement();
+ if (!frame || !inner_editor)
+ return did_change;
+
+ Position start_position = PositionForIndex(inner_editor, start);
+ Position end_position =
+ start == end ? start_position : PositionForIndex(inner_editor, end);
+
+ DCHECK_EQ(start, IndexForPosition(inner_editor, start_position));
+ DCHECK_EQ(end, IndexForPosition(inner_editor, end_position));
+
+#if DCHECK_IS_ON()
+ // startPosition and endPosition can be null position for example when
+ // "-webkit-user-select: none" style attribute is specified.
+ if (start_position.IsNotNull() && end_position.IsNotNull()) {
+ DCHECK_EQ(start_position.AnchorNode()->OwnerShadowHost(), this);
+ DCHECK_EQ(end_position.AnchorNode()->OwnerShadowHost(), this);
+ }
+#endif // DCHECK_IS_ON()
+ frame->Selection().SetSelection(
+ SelectionInDOMTree::Builder()
+ .Collapse(direction == kSelectionHasBackwardDirection
+ ? end_position
+ : start_position)
+ .Extend(direction == kSelectionHasBackwardDirection ? start_position
+ : end_position)
+ .Build(),
+ SetSelectionOptions::Builder()
+ .SetShouldCloseTyping(true)
+ .SetShouldClearTypingStyle(true)
+ .SetDoNotSetFocus(true)
+ .SetIsDirectional(direction != kSelectionHasNoDirection)
+ .Build());
+ return did_change;
+}
+
+bool TextControlElement::CacheSelection(unsigned start,
+ unsigned end,
+ TextFieldSelectionDirection direction) {
+ DCHECK_LE(start, end);
+ bool did_change = cached_selection_start_ != start ||
+ cached_selection_end_ != end ||
+ cached_selection_direction_ != direction;
+ cached_selection_start_ = start;
+ cached_selection_end_ = end;
+ cached_selection_direction_ = direction;
+ return did_change;
+}
+
+VisiblePosition TextControlElement::VisiblePositionForIndex(int index) const {
+ if (index <= 0)
+ return VisiblePosition::FirstPositionInNode(*InnerEditorElement());
+ Position start, end;
+ bool selected = Range::selectNodeContents(InnerEditorElement(), start, end);
+ if (!selected)
+ return VisiblePosition();
+ CharacterIterator it(start, end);
+ it.Advance(index - 1);
+ return CreateVisiblePosition(it.EndPosition(), TextAffinity::kUpstream);
+}
+
+// TODO(yosin): We should move |TextControlElement::indexForVisiblePosition()|
+// to "AXLayoutObject.cpp" since this funciton is used only there.
+int TextControlElement::IndexForVisiblePosition(
+ const VisiblePosition& pos) const {
+ Position index_position = pos.DeepEquivalent().ParentAnchoredEquivalent();
+ if (EnclosingTextControl(index_position) != this)
+ return 0;
+ DCHECK(index_position.IsConnected()) << index_position;
+ return TextIterator::RangeLength(Position(InnerEditorElement(), 0),
+ index_position);
+}
+
+unsigned TextControlElement::selectionStart() const {
+ if (!IsTextControl())
+ return 0;
+ if (GetDocument().FocusedElement() != this)
+ return cached_selection_start_;
+
+ return ComputeSelectionStart();
+}
+
+unsigned TextControlElement::ComputeSelectionStart() const {
+ DCHECK(IsTextControl());
+ LocalFrame* frame = GetDocument().GetFrame();
+ if (!frame)
+ return 0;
+
+ // To avoid regression on speedometer benchmark[1] test, we should not
+ // update layout tree in this code block.
+ // [1] http://browserbench.org/Speedometer/
+ DocumentLifecycle::DisallowTransitionScope disallow_transition(
+ GetDocument().Lifecycle());
+ const SelectionInDOMTree& selection =
+ frame->Selection().GetSelectionInDOMTree();
+ return IndexForPosition(InnerEditorElement(),
+ selection.ComputeStartPosition());
+}
+
+unsigned TextControlElement::selectionEnd() const {
+ if (!IsTextControl())
+ return 0;
+ if (GetDocument().FocusedElement() != this)
+ return cached_selection_end_;
+ return ComputeSelectionEnd();
+}
+
+unsigned TextControlElement::ComputeSelectionEnd() const {
+ DCHECK(IsTextControl());
+ LocalFrame* frame = GetDocument().GetFrame();
+ if (!frame)
+ return 0;
+
+ // To avoid regression on speedometer benchmark[1] test, we should not
+ // update layout tree in this code block.
+ // [1] http://browserbench.org/Speedometer/
+ DocumentLifecycle::DisallowTransitionScope disallow_transition(
+ GetDocument().Lifecycle());
+ const SelectionInDOMTree& selection =
+ frame->Selection().GetSelectionInDOMTree();
+ return IndexForPosition(InnerEditorElement(), selection.ComputeEndPosition());
+}
+
+static const AtomicString& DirectionString(
+ TextFieldSelectionDirection direction) {
+ DEFINE_STATIC_LOCAL(const AtomicString, none, ("none"));
+ DEFINE_STATIC_LOCAL(const AtomicString, forward, ("forward"));
+ DEFINE_STATIC_LOCAL(const AtomicString, backward, ("backward"));
+
+ switch (direction) {
+ case kSelectionHasNoDirection:
+ return none;
+ case kSelectionHasForwardDirection:
+ return forward;
+ case kSelectionHasBackwardDirection:
+ return backward;
+ }
+
+ NOTREACHED();
+ return none;
+}
+
+const AtomicString& TextControlElement::selectionDirection() const {
+ // Ensured by HTMLInputElement::selectionDirectionForBinding().
+ DCHECK(IsTextControl());
+ if (GetDocument().FocusedElement() != this)
+ return DirectionString(cached_selection_direction_);
+ return DirectionString(ComputeSelectionDirection());
+}
+
+TextFieldSelectionDirection TextControlElement::ComputeSelectionDirection()
+ const {
+ DCHECK(IsTextControl());
+ LocalFrame* frame = GetDocument().GetFrame();
+ if (!frame)
+ return kSelectionHasNoDirection;
+
+ // To avoid regression on speedometer benchmark[1] test, we should not
+ // update layout tree in this code block.
+ // [1] http://browserbench.org/Speedometer/
+ DocumentLifecycle::DisallowTransitionScope disallow_transition(
+ GetDocument().Lifecycle());
+ const SelectionInDOMTree& selection =
+ frame->Selection().GetSelectionInDOMTree();
+ const Position& start = selection.ComputeStartPosition();
+ return frame->Selection().IsDirectional()
+ ? (selection.Base() == start ? kSelectionHasForwardDirection
+ : kSelectionHasBackwardDirection)
+ : kSelectionHasNoDirection;
+}
+
+static inline void SetContainerAndOffsetForRange(Node* node,
+ int offset,
+ Node*& container_node,
+ int& offset_in_container) {
+ if (node->IsTextNode()) {
+ container_node = node;
+ offset_in_container = offset;
+ } else {
+ container_node = node->parentNode();
+ offset_in_container = node->NodeIndex() + offset;
+ }
+}
+
+SelectionInDOMTree TextControlElement::Selection() const {
+ if (!GetLayoutObject() || !IsTextControl())
+ return SelectionInDOMTree();
+
+ int start = cached_selection_start_;
+ int end = cached_selection_end_;
+
+ DCHECK_LE(start, end);
+ HTMLElement* inner_text = InnerEditorElement();
+ if (!inner_text)
+ return SelectionInDOMTree();
+
+ if (!inner_text->HasChildren()) {
+ return SelectionInDOMTree::Builder()
+ .Collapse(Position(inner_text, 0))
+ .Build();
+ }
+
+ int offset = 0;
+ Node* start_node = nullptr;
+ Node* end_node = nullptr;
+ for (Node& node : NodeTraversal::DescendantsOf(*inner_text)) {
+ DCHECK(!node.hasChildren());
+ DCHECK(node.IsTextNode() || IsHTMLBRElement(node));
+ int length = node.IsTextNode() ? Position::LastOffsetInNode(node) : 1;
+
+ if (offset <= start && start <= offset + length)
+ SetContainerAndOffsetForRange(&node, start - offset, start_node, start);
+
+ if (offset <= end && end <= offset + length) {
+ SetContainerAndOffsetForRange(&node, end - offset, end_node, end);
+ break;
+ }
+
+ offset += length;
+ }
+
+ if (!start_node || !end_node)
+ return SelectionInDOMTree();
+
+ return SelectionInDOMTree::Builder()
+ .SetBaseAndExtent(Position(start_node, start), Position(end_node, end))
+ .Build();
+}
+
+int TextControlElement::maxLength() const {
+ int value;
+ if (!ParseHTMLInteger(FastGetAttribute(maxlengthAttr), value))
+ return -1;
+ return value >= 0 ? value : -1;
+}
+
+int TextControlElement::minLength() const {
+ int value;
+ if (!ParseHTMLInteger(FastGetAttribute(minlengthAttr), value))
+ return -1;
+ return value >= 0 ? value : -1;
+}
+
+void TextControlElement::setMaxLength(int new_value,
+ ExceptionState& exception_state) {
+ int min = minLength();
+ if (new_value < 0) {
+ exception_state.ThrowDOMException(
+ kIndexSizeError, "The value provided (" + String::Number(new_value) +
+ ") is not positive or 0.");
+ } else if (min >= 0 && new_value < min) {
+ exception_state.ThrowDOMException(
+ kIndexSizeError, ExceptionMessages::IndexExceedsMinimumBound(
+ "maxLength", new_value, min));
+ } else {
+ SetIntegralAttribute(maxlengthAttr, new_value);
+ }
+}
+
+void TextControlElement::setMinLength(int new_value,
+ ExceptionState& exception_state) {
+ int max = maxLength();
+ if (new_value < 0) {
+ exception_state.ThrowDOMException(
+ kIndexSizeError, "The value provided (" + String::Number(new_value) +
+ ") is not positive or 0.");
+ } else if (max >= 0 && new_value > max) {
+ exception_state.ThrowDOMException(
+ kIndexSizeError, ExceptionMessages::IndexExceedsMaximumBound(
+ "minLength", new_value, max));
+ } else {
+ SetIntegralAttribute(minlengthAttr, new_value);
+ }
+}
+
+void TextControlElement::RestoreCachedSelection() {
+ if (SetSelectionRange(cached_selection_start_, cached_selection_end_,
+ cached_selection_direction_))
+ ScheduleSelectEvent();
+}
+
+void TextControlElement::SelectionChanged(bool user_triggered) {
+ if (!GetLayoutObject() || !IsTextControl())
+ return;
+
+ // selectionStart() or selectionEnd() will return cached selection when this
+ // node doesn't have focus.
+ CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(),
+ ComputeSelectionDirection());
+
+ LocalFrame* frame = GetDocument().GetFrame();
+ if (!frame || !user_triggered)
+ return;
+ const SelectionInDOMTree& selection =
+ frame->Selection().GetSelectionInDOMTree();
+ if (selection.Type() != kRangeSelection)
+ return;
+ DispatchEvent(Event::CreateBubble(EventTypeNames::select));
+}
+
+void TextControlElement::ScheduleSelectEvent() {
+ Event* event = Event::CreateBubble(EventTypeNames::select);
+ event->SetTarget(this);
+ GetDocument().EnqueueAnimationFrameEvent(event);
+}
+
+void TextControlElement::ParseAttribute(
+ const AttributeModificationParams& params) {
+ if (params.name == placeholderAttr) {
+ UpdatePlaceholderText();
+ UpdatePlaceholderVisibility();
+ UseCounter::Count(GetDocument(), WebFeature::kPlaceholderAttribute);
+ } else {
+ HTMLFormControlElementWithState::ParseAttribute(params);
+ }
+}
+
+bool TextControlElement::LastChangeWasUserEdit() const {
+ if (!IsTextControl())
+ return false;
+ return last_change_was_user_edit_;
+}
+
+Node* TextControlElement::CreatePlaceholderBreakElement() const {
+ return HTMLBRElement::Create(GetDocument());
+}
+
+void TextControlElement::AddPlaceholderBreakElementIfNecessary() {
+ HTMLElement* inner_editor = InnerEditorElement();
+ if (inner_editor->GetLayoutObject() &&
+ !inner_editor->GetLayoutObject()->Style()->PreserveNewline())
+ return;
+ Node* last_child = inner_editor->lastChild();
+ if (!last_child || !last_child->IsTextNode())
+ return;
+ if (ToText(last_child)->data().EndsWith('\n') ||
+ ToText(last_child)->data().EndsWith('\r'))
+ inner_editor->AppendChild(CreatePlaceholderBreakElement());
+}
+
+void TextControlElement::SetInnerEditorValue(const String& value) {
+ DCHECK(!OpenShadowRoot());
+ if (!IsTextControl() || OpenShadowRoot())
+ return;
+
+ DCHECK(InnerEditorElement());
+
+ bool text_is_changed = value != InnerEditorValue();
+ HTMLElement* inner_editor = InnerEditorElement();
+ if (!text_is_changed && inner_editor->HasChildren())
+ return;
+
+ // If the last child is a trailing <br> that's appended below, remove it
+ // first so as to enable setInnerText() fast path of updating a text node.
+ if (IsHTMLBRElement(inner_editor->lastChild()))
+ inner_editor->RemoveChild(inner_editor->lastChild(), ASSERT_NO_EXCEPTION);
+
+ // We don't use setTextContent. It triggers unnecessary paint.
+ if (value.IsEmpty())
+ inner_editor->RemoveChildren();
+ else
+ ReplaceChildrenWithText(inner_editor, value, ASSERT_NO_EXCEPTION);
+
+ // Add <br> so that we can put the caret at the next line of the last
+ // newline.
+ AddPlaceholderBreakElementIfNecessary();
+
+ if (text_is_changed && GetLayoutObject()) {
+ if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
+ cache->HandleTextFormControlChanged(this);
+ }
+}
+
+String TextControlElement::InnerEditorValue() const {
+ DCHECK(!OpenShadowRoot());
+ HTMLElement* inner_editor = InnerEditorElement();
+ if (!inner_editor || !IsTextControl())
+ return g_empty_string;
+
+ // Typically, innerEditor has 0 or one Text node followed by 0 or one <br>.
+ if (!inner_editor->HasChildren())
+ return g_empty_string;
+ Node& first_child = *inner_editor->firstChild();
+ if (first_child.IsTextNode()) {
+ Node* second_child = first_child.nextSibling();
+ if (!second_child)
+ return ToText(first_child).data();
+ if (!second_child->nextSibling() && IsHTMLBRElement(*second_child))
+ return ToText(first_child).data();
+ } else if (!first_child.nextSibling() && IsHTMLBRElement(first_child)) {
+ return g_empty_string;
+ }
+
+ StringBuilder result;
+ for (Node& node : NodeTraversal::InclusiveDescendantsOf(*inner_editor)) {
+ if (IsHTMLBRElement(node)) {
+ DCHECK_EQ(&node, inner_editor->lastChild());
+ if (&node != inner_editor->lastChild())
+ result.Append(kNewlineCharacter);
+ } else if (node.IsTextNode()) {
+ result.Append(ToText(node).data());
+ }
+ }
+ return result.ToString();
+}
+
+static void GetNextSoftBreak(RootInlineBox*& line,
+ Node*& break_node,
+ unsigned& break_offset) {
+ RootInlineBox* next;
+ for (; line; line = next) {
+ next = line->NextRootBox();
+ if (next && !line->EndsWithBreak()) {
+ DCHECK(line->LineBreakObj());
+ break_node = line->LineBreakObj().GetNode();
+ break_offset = line->LineBreakPos();
+ line = next;
+ return;
+ }
+ }
+ break_node = nullptr;
+ break_offset = 0;
+}
+
+String TextControlElement::ValueWithHardLineBreaks() const {
+ // FIXME: It's not acceptable to ignore the HardWrap setting when there is no
+ // layoutObject. While we have no evidence this has ever been a practical
+ // problem, it would be best to fix it some day.
+ HTMLElement* inner_text = InnerEditorElement();
+ if (!inner_text || !IsTextControl())
+ return value();
+
+ LayoutBlockFlow* layout_object =
+ ToLayoutBlockFlow(inner_text->GetLayoutObject());
+ if (!layout_object)
+ return value();
+
+ DCHECK(CanUseInlineBox(*layout_object));
+ Node* break_node;
+ unsigned break_offset;
+ RootInlineBox* line = layout_object->FirstRootBox();
+ if (!line)
+ return value();
+
+ GetNextSoftBreak(line, break_node, break_offset);
+
+ StringBuilder result;
+ for (Node& node : NodeTraversal::DescendantsOf(*inner_text)) {
+ if (IsHTMLBRElement(node)) {
+ DCHECK_EQ(&node, inner_text->lastChild());
+ if (&node != inner_text->lastChild())
+ result.Append(kNewlineCharacter);
+ } else if (node.IsTextNode()) {
+ String data = ToText(node).data();
+ unsigned length = data.length();
+ unsigned position = 0;
+ while (break_node == node && break_offset <= length) {
+ if (break_offset > position) {
+ result.Append(data, position, break_offset - position);
+ position = break_offset;
+ result.Append(kNewlineCharacter);
+ }
+ GetNextSoftBreak(line, break_node, break_offset);
+ }
+ result.Append(data, position, length - position);
+ }
+ while (break_node == node)
+ GetNextSoftBreak(line, break_node, break_offset);
+ }
+ return result.ToString();
+}
+
+TextControlElement* EnclosingTextControl(const Position& position) {
+ DCHECK(position.IsNull() || position.IsOffsetInAnchor() ||
+ position.ComputeContainerNode() ||
+ !position.AnchorNode()->OwnerShadowHost() ||
+ (position.AnchorNode()->parentNode() &&
+ position.AnchorNode()->parentNode()->IsShadowRoot()));
+ return EnclosingTextControl(position.ComputeContainerNode());
+}
+
+TextControlElement* EnclosingTextControl(const Node* container) {
+ if (!container)
+ return nullptr;
+ Element* ancestor = container->OwnerShadowHost();
+ return ancestor && IsTextControl(*ancestor) &&
+ container->ContainingShadowRoot()->IsUserAgent()
+ ? ToTextControl(ancestor)
+ : nullptr;
+}
+
+String TextControlElement::DirectionForFormData() const {
+ for (const HTMLElement* element = this; element;
+ element = Traversal<HTMLElement>::FirstAncestor(*element)) {
+ const AtomicString& dir_attribute_value =
+ element->FastGetAttribute(dirAttr);
+ if (dir_attribute_value.IsNull())
+ continue;
+
+ if (DeprecatedEqualIgnoringCase(dir_attribute_value, "rtl") ||
+ DeprecatedEqualIgnoringCase(dir_attribute_value, "ltr"))
+ return dir_attribute_value;
+
+ if (DeprecatedEqualIgnoringCase(dir_attribute_value, "auto")) {
+ bool is_auto;
+ TextDirection text_direction =
+ element->DirectionalityIfhasDirAutoAttribute(is_auto);
+ return text_direction == TextDirection::kRtl ? "rtl" : "ltr";
+ }
+ }
+
+ return "ltr";
+}
+
+void TextControlElement::SetAutofillValue(const String& value) {
+ // Set the value trimmed to the max length of the field and dispatch the input
+ // and change events.
+ setValue(value.Substring(0, maxLength()), kDispatchInputAndChangeEvent);
+}
+
+// TODO(crbug.com/772433): Create and use a new suggested-value element instead.
+void TextControlElement::SetSuggestedValue(const String& value) {
+ suggested_value_ = value.Substring(0, maxLength());
+ if (!suggested_value_.IsEmpty() && !InnerEditorValue().IsEmpty()) {
+ // If there is an inner editor value, hide it so the suggested value can be
+ // shown to the user.
+ static_cast<TextControlInnerEditorElement*>(InnerEditorElement())
+ ->SetVisibility(false);
+ } else if (suggested_value_.IsEmpty() && InnerEditorElement()) {
+ // If there is no suggested value and there is an InnerEditorElement, reset
+ // its visibility.
+ static_cast<TextControlInnerEditorElement*>(InnerEditorElement())
+ ->SetVisibility(true);
+ }
+
+ UpdatePlaceholderText();
+
+ HTMLElement* placeholder = PlaceholderElement();
+ if (!placeholder)
+ return;
+
+ UpdatePlaceholderVisibility();
+
+ if (suggested_value_.IsEmpty()) {
+ // Reset the pseudo-id for placeholders to use the appropriated style
+ placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder"));
+ } else {
+ // Set the pseudo-id for suggested values to use the appropriated style.
+ placeholder->SetShadowPseudoId(AtomicString("-internal-input-suggested"));
+ }
+}
+
+HTMLElement* TextControlElement::CreateInnerEditorElement() {
+ DCHECK(!inner_editor_);
+ inner_editor_ = TextControlInnerEditorElement::Create(GetDocument());
+ return inner_editor_;
+}
+
+const String& TextControlElement::SuggestedValue() const {
+ return suggested_value_;
+}
+
+void TextControlElement::Trace(Visitor* visitor) {
+ visitor->Trace(inner_editor_);
+ HTMLFormControlElementWithState::Trace(visitor);
+}
+
+void TextControlElement::CloneNonAttributePropertiesFrom(
+ const Element& source,
+ CloneChildrenFlag flag) {
+ const TextControlElement& source_element =
+ static_cast<const TextControlElement&>(source);
+ last_change_was_user_edit_ = source_element.last_change_was_user_edit_;
+ HTMLFormControlElement::CloneNonAttributePropertiesFrom(source, flag);
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..e7a9ce1cb34
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
+ * reserved.
+ * Copyright (C) 2009, 2010, 2011 Google Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_ELEMENT_H_
+#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/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"
+
+namespace blink {
+
+class ExceptionState;
+
+enum TextFieldSelectionDirection {
+ kSelectionHasNoDirection,
+ kSelectionHasForwardDirection,
+ kSelectionHasBackwardDirection
+};
+enum TextFieldEventBehavior {
+ kDispatchNoEvent,
+ kDispatchChangeEvent,
+ kDispatchInputAndChangeEvent
+};
+
+enum class TextControlSetValueSelection {
+ kSetSelectionToEnd,
+ kDoNotSet,
+};
+
+class CORE_EXPORT TextControlElement : public HTMLFormControlElementWithState {
+ public:
+ // Common flag for HTMLInputElement::tooLong(),
+ // HTMLTextAreaElement::tooLong(),
+ // HTMLInputElement::tooShort() and HTMLTextAreaElement::tooShort().
+ enum NeedsToCheckDirtyFlag { kCheckDirtyFlag, kIgnoreDirtyFlag };
+
+ ~TextControlElement() override;
+
+ void ForwardEvent(Event*);
+
+ void SetFocused(bool, WebFocusType) override;
+
+ // The derived class should return true if placeholder processing is needed.
+ virtual bool IsPlaceholderVisible() const = 0;
+ virtual void SetPlaceholderVisibility(bool) = 0;
+ virtual bool SupportsPlaceholder() const = 0;
+ String StrippedPlaceholder() const;
+ HTMLElement* PlaceholderElement() const;
+ void UpdatePlaceholderVisibility();
+
+ VisiblePosition VisiblePositionForIndex(int) const;
+ int IndexForVisiblePosition(const VisiblePosition&) const;
+ unsigned selectionStart() const;
+ unsigned selectionEnd() const;
+ const AtomicString& selectionDirection() const;
+ void setSelectionStart(unsigned);
+ void setSelectionEnd(unsigned);
+ void setSelectionDirection(const String&);
+ void select();
+ virtual void setRangeText(const String& replacement, ExceptionState&);
+ virtual void setRangeText(const String& replacement,
+ unsigned start,
+ unsigned end,
+ const String& selection_mode,
+ ExceptionState&);
+ // Web-exposed setSelectionRange() function. This schedule to dispatch
+ // 'select' event.
+ void setSelectionRangeForBinding(unsigned start,
+ unsigned end,
+ const String& direction = "none");
+ // Blink-internal version of setSelectionRange(). This translates "none"
+ // direction to "forward" on platforms without "none" direction.
+ // This returns true if it updated cached selection and/or FrameSelection.
+ bool SetSelectionRange(
+ unsigned start,
+ unsigned end,
+ TextFieldSelectionDirection = kSelectionHasNoDirection);
+ SelectionInDOMTree Selection() const;
+
+ int maxLength() const;
+ int minLength() const;
+ void setMaxLength(int, ExceptionState&);
+ void setMinLength(int, ExceptionState&);
+
+ // Dispatch 'change' event if the value is updated.
+ void DispatchFormControlChangeEvent();
+ // Enqueue 'change' event if the value is updated.
+ void EnqueueChangeEvent();
+ // This should be called on every user-input, before the user-input changes
+ // the value.
+ void SetValueBeforeFirstUserEditIfNotSet();
+ // This should be called on every user-input, after the user-input changed the
+ // value. The argument is the updated value.
+ void CheckIfValueWasReverted(const String&);
+ void ClearValueBeforeFirstUserEdit();
+
+ virtual String value() const = 0;
+ virtual void setValue(
+ const String&,
+ TextFieldEventBehavior = kDispatchNoEvent,
+ TextControlSetValueSelection =
+ TextControlSetValueSelection::kSetSelectionToEnd) = 0;
+
+ HTMLElement* InnerEditorElement() const { return inner_editor_; }
+ HTMLElement* CreateInnerEditorElement();
+ void DropInnerEditorElement() { inner_editor_ = nullptr; }
+
+ void SelectionChanged(bool user_triggered);
+ bool LastChangeWasUserEdit() const;
+ virtual void SetInnerEditorValue(const String&);
+ String InnerEditorValue() const;
+ Node* CreatePlaceholderBreakElement() const;
+
+ String DirectionForFormData() const;
+
+ // Set the value trimmed to the max length of the field and dispatch the input
+ // and change events.
+ void SetAutofillValue(const String& value);
+
+ virtual void SetSuggestedValue(const String& value);
+ const String& SuggestedValue() const;
+
+ void Trace(Visitor*) override;
+
+ protected:
+ TextControlElement(const QualifiedName&, Document&);
+ bool IsPlaceholderEmpty() const;
+ virtual void UpdatePlaceholderText() = 0;
+ virtual String GetPlaceholderValue() const = 0;
+
+ void ParseAttribute(const AttributeModificationParams&) override;
+
+ void RestoreCachedSelection();
+
+ void DefaultEventHandler(Event*) override;
+ virtual void SubtreeHasChanged() = 0;
+
+ void SetLastChangeWasNotUserEdit() { last_change_was_user_edit_ = false; }
+ void AddPlaceholderBreakElementIfNecessary();
+ String ValueWithHardLineBreaks() const;
+
+ void CloneNonAttributePropertiesFrom(const Element&,
+ CloneChildrenFlag) override;
+
+ private:
+ unsigned ComputeSelectionStart() const;
+ unsigned ComputeSelectionEnd() const;
+ TextFieldSelectionDirection ComputeSelectionDirection() const;
+ // Returns true if cached values and arguments are not same.
+ bool CacheSelection(unsigned start,
+ unsigned end,
+ TextFieldSelectionDirection);
+ static unsigned IndexForPosition(HTMLElement* inner_editor, const Position&);
+
+ void DispatchFocusEvent(Element* old_focused_element,
+ WebFocusType,
+ InputDeviceCapabilities* source_capabilities) final;
+ void DispatchBlurEvent(Element* new_focused_element,
+ WebFocusType,
+ InputDeviceCapabilities* source_capabilities) final;
+ void ScheduleSelectEvent();
+
+ // Returns true if user-editable value is empty. Used to check placeholder
+ // visibility.
+ virtual bool IsEmptyValue() const = 0;
+ // Returns true if suggested value is empty. Used to check placeholder
+ // visibility.
+ bool IsEmptySuggestedValue() const { return SuggestedValue().IsEmpty(); }
+ // Called in dispatchFocusEvent(), after placeholder process, before calling
+ // parent's dispatchFocusEvent().
+ virtual void HandleFocusEvent(Element* /* oldFocusedNode */, WebFocusType) {}
+ // Called in dispatchBlurEvent(), after placeholder process, before calling
+ // parent's dispatchBlurEvent().
+ virtual void HandleBlurEvent() {}
+
+ // Whether the placeholder attribute value should be visible. Does not
+ // necessarily match the placeholder_element visibility because it can be used
+ // for suggested values too.
+ bool PlaceholderShouldBeVisible() const;
+
+ // Held directly instead of looked up by ID for speed.
+ // Not only is the lookup faster, but for simple text inputs it avoids
+ // creating a number of TreeScope data structures to track elements by ID.
+ Member<HTMLElement> inner_editor_;
+
+ // In m_valueBeforeFirstUserEdit, we distinguish a null String and zero-length
+ // String. Null String means the field doesn't have any data yet, and
+ // zero-length String is a valid data.
+ String value_before_first_user_edit_;
+ bool last_change_was_user_edit_;
+
+ unsigned cached_selection_start_;
+ unsigned cached_selection_end_;
+ TextFieldSelectionDirection cached_selection_direction_;
+
+ String suggested_value_;
+ String value_before_set_suggested_value_;
+
+ FRIEND_TEST_ALL_PREFIXES(TextControlElementTest, IndexForPosition);
+};
+
+inline bool IsTextControl(const Node& node) {
+ return node.IsElementNode() && ToElement(node).IsTextControl();
+}
+inline bool IsTextControl(const Node* node) {
+ return node && IsTextControl(*node);
+}
+
+// We can't use DEFINE_TYPE_CASTS for TextControl because macro
+// names and the destination type name are not matched.
+// e.g. ToTextControl() returns TextControlElement.
+#define DEFINE_TEXT_CONTROL_CASTS(Type, ArgType) \
+ inline Type* ToTextControl(ArgType* node) { \
+ SECURITY_DCHECK(!node || IsTextControl(*node)); \
+ return static_cast<Type*>(node); \
+ } \
+ inline Type& ToTextControl(ArgType& node) { \
+ SECURITY_DCHECK(IsTextControl(node)); \
+ return static_cast<Type&>(node); \
+ } \
+ inline Type* ToTextControlOrNull(ArgType* node) { \
+ return node && IsTextControl(*node) ? static_cast<Type*>(node) : nullptr; \
+ } \
+ inline Type* ToTextControlOrNull(ArgType& node) { \
+ return IsTextControl(node) ? static_cast<Type*>(&node) : nullptr; \
+ } \
+ void ToTextControl(Type*); \
+ void ToTextControl(Type&)
+
+DEFINE_TEXT_CONTROL_CASTS(TextControlElement, Node);
+DEFINE_TEXT_CONTROL_CASTS(const TextControlElement, const Node);
+
+#undef DEFINE_TEXT_CONTROL_CASTS
+
+TextControlElement* EnclosingTextControl(const Position&);
+TextControlElement* EnclosingTextControl(const Node*);
+
+} // namespace blink
+
+#endif
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
new file mode 100644
index 00000000000..e3f8b036562
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 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/text_control_element.h"
+
+#include <memory>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/position.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
+#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+
+namespace blink {
+
+class TextControlElementTest : public testing::Test {
+ protected:
+ void SetUp() override;
+
+ DummyPageHolder& Page() const { return *dummy_page_holder_; }
+ Document& GetDocument() const { return *document_; }
+ TextControlElement& TextControl() const { return *text_control_; }
+ HTMLInputElement& Input() const { return *input_; }
+
+ private:
+ std::unique_ptr<DummyPageHolder> dummy_page_holder_;
+
+ Persistent<Document> document_;
+ Persistent<TextControlElement> text_control_;
+ Persistent<HTMLInputElement> input_;
+};
+
+void TextControlElementTest::SetUp() {
+ Page::PageClients page_clients;
+ FillWithEmptyClients(page_clients);
+ dummy_page_holder_ =
+ DummyPageHolder::Create(IntSize(800, 600), &page_clients);
+
+ document_ = &dummy_page_holder_->GetDocument();
+ document_->documentElement()->SetInnerHTMLFromString(
+ "<body><textarea id=textarea></textarea><input id=input /></body>");
+ document_->View()->UpdateAllLifecyclePhases();
+ text_control_ = ToTextControl(document_->getElementById("textarea"));
+ text_control_->focus();
+ input_ = ToHTMLInputElement(document_->getElementById("input"));
+}
+
+TEST_F(TextControlElementTest, SetSelectionRange) {
+ EXPECT_EQ(0u, TextControl().selectionStart());
+ EXPECT_EQ(0u, TextControl().selectionEnd());
+
+ TextControl().SetInnerEditorValue("Hello, text form.");
+ EXPECT_EQ(0u, TextControl().selectionStart());
+ EXPECT_EQ(0u, TextControl().selectionEnd());
+
+ TextControl().SetSelectionRange(1, 3);
+ EXPECT_EQ(1u, TextControl().selectionStart());
+ EXPECT_EQ(3u, TextControl().selectionEnd());
+}
+
+TEST_F(TextControlElementTest, SetSelectionRangeDoesNotCauseLayout) {
+ Input().focus();
+ Input().setValue("Hello, input form.");
+ Input().SetSelectionRange(1, 1);
+
+ // Force layout if document().updateStyleAndLayoutIgnorePendingStylesheets()
+ // is called.
+ GetDocument().body()->AppendChild(GetDocument().createTextNode("foo"));
+ const int start_layout_count = Page().GetFrameView().LayoutCount();
+ EXPECT_TRUE(GetDocument().NeedsLayoutTreeUpdate());
+ Input().SetSelectionRange(2, 2);
+ EXPECT_EQ(start_layout_count, Page().GetFrameView().LayoutCount());
+}
+
+TEST_F(TextControlElementTest, IndexForPosition) {
+ HTMLInputElement* input =
+ ToHTMLInputElement(GetDocument().getElementById("input"));
+ input->setValue("Hello");
+ HTMLElement* inner_editor = input->InnerEditorElement();
+ EXPECT_EQ(5u, TextControlElement::IndexForPosition(
+ inner_editor,
+ Position(inner_editor, PositionAnchorType::kAfterAnchor)));
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..90890d15156
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h"
+
+#include "third_party/blink/renderer/core/css/resolver/style_adjuster.h"
+#include "third_party/blink/renderer/core/css/style_change_reason.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/dom/user_gesture_indicator.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/events/text_event_input_type.h"
+#include "third_party/blink/renderer/core/frame/local_frame.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/event_handler.h"
+#include "third_party/blink/renderer/core/layout/layout_text_control_single_line.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+TextControlInnerContainer::TextControlInnerContainer(Document& document)
+ : HTMLDivElement(document) {}
+
+TextControlInnerContainer* TextControlInnerContainer::Create(
+ Document& document) {
+ TextControlInnerContainer* element = new TextControlInnerContainer(document);
+ element->setAttribute(idAttr, ShadowElementNames::TextFieldContainer());
+ return element;
+}
+
+LayoutObject* TextControlInnerContainer::CreateLayoutObject(
+ const ComputedStyle&) {
+ return new LayoutTextControlInnerContainer(this);
+}
+
+// ---------------------------
+
+EditingViewPortElement::EditingViewPortElement(Document& document)
+ : HTMLDivElement(document) {
+ SetHasCustomStyleCallbacks();
+}
+
+EditingViewPortElement* EditingViewPortElement::Create(Document& document) {
+ EditingViewPortElement* element = new EditingViewPortElement(document);
+ element->setAttribute(idAttr, ShadowElementNames::EditingViewPort());
+ return element;
+}
+
+scoped_refptr<ComputedStyle>
+EditingViewPortElement::CustomStyleForLayoutObject() {
+ // FXIME: Move these styles to html.css.
+
+ scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
+ style->InheritFrom(OwnerShadowHost()->ComputedStyleRef());
+
+ style->SetFlexGrow(1);
+ style->SetMinWidth(Length(0, kFixed));
+ style->SetDisplay(EDisplay::kBlock);
+ style->SetDirection(TextDirection::kLtr);
+
+ // We don't want the shadow dom to be editable, so we set this block to
+ // read-only in case the input itself is editable.
+ style->SetUserModify(EUserModify::kReadOnly);
+ style->SetUnique();
+
+ return style;
+}
+
+// ---------------------------
+
+inline TextControlInnerEditorElement::TextControlInnerEditorElement(
+ Document& document)
+ : HTMLDivElement(document) {
+ SetHasCustomStyleCallbacks();
+}
+
+TextControlInnerEditorElement* TextControlInnerEditorElement::Create(
+ Document& document) {
+ return new TextControlInnerEditorElement(document);
+}
+
+void TextControlInnerEditorElement::DefaultEventHandler(Event* event) {
+ // FIXME: In the future, we should add a way to have default event listeners.
+ // Then we would add one to the text field's inner div, and we wouldn't need
+ // this subclass.
+ // Or possibly we could just use a normal event listener.
+ if (event->IsBeforeTextInsertedEvent() ||
+ event->type() == EventTypeNames::webkitEditableContentChanged) {
+ Element* shadow_ancestor = OwnerShadowHost();
+ // A TextControlInnerTextElement can have no host if its been detached,
+ // but kept alive by an EditCommand. In this case, an undo/redo can
+ // cause events to be sent to the TextControlInnerTextElement. To
+ // prevent an infinite loop, we must check for this case before sending
+ // the event up the chain.
+ if (shadow_ancestor)
+ shadow_ancestor->DefaultEventHandler(event);
+ }
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+}
+
+void TextControlInnerEditorElement::SetVisibility(bool is_visible) {
+ if (is_visible_ != is_visible) {
+ is_visible_ = is_visible;
+ SetNeedsStyleRecalc(
+ kLocalStyleChange,
+ StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
+ }
+}
+
+LayoutObject* TextControlInnerEditorElement::CreateLayoutObject(
+ const ComputedStyle&) {
+ return new LayoutTextControlInnerEditor(this);
+}
+
+scoped_refptr<ComputedStyle>
+TextControlInnerEditorElement::CustomStyleForLayoutObject() {
+ LayoutObject* parent_layout_object = OwnerShadowHost()->GetLayoutObject();
+ if (!parent_layout_object || !parent_layout_object->IsTextControl())
+ return OriginalStyleForLayoutObject();
+ LayoutTextControl* text_control = ToLayoutTextControl(parent_layout_object);
+ scoped_refptr<ComputedStyle> inner_editor_style =
+ text_control->CreateInnerEditorStyle(text_control->StyleRef());
+ // Using StyleAdjuster::adjustComputedStyle updates unwanted style. We'd like
+ // to apply only editing-related and alignment-related.
+ StyleAdjuster::AdjustStyleForEditing(*inner_editor_style);
+ if (!is_visible_)
+ inner_editor_style->SetOpacity(0);
+ return inner_editor_style;
+}
+
+// ----------------------------
+
+inline SearchFieldCancelButtonElement::SearchFieldCancelButtonElement(
+ Document& document)
+ : HTMLDivElement(document), capturing_(false) {}
+
+SearchFieldCancelButtonElement* SearchFieldCancelButtonElement::Create(
+ Document& document) {
+ SearchFieldCancelButtonElement* element =
+ new SearchFieldCancelButtonElement(document);
+ element->SetShadowPseudoId(AtomicString("-webkit-search-cancel-button"));
+ element->setAttribute(idAttr, ShadowElementNames::SearchClearButton());
+ return element;
+}
+
+void SearchFieldCancelButtonElement::DetachLayoutTree(
+ const AttachContext& context) {
+ if (capturing_) {
+ if (LocalFrame* frame = GetDocument().GetFrame())
+ frame->GetEventHandler().SetCapturingMouseEventsNode(nullptr);
+ }
+ HTMLDivElement::DetachLayoutTree(context);
+}
+
+void SearchFieldCancelButtonElement::DefaultEventHandler(Event* event) {
+ // If the element is visible, on mouseup, clear the value, and set selection
+ HTMLInputElement* input(ToHTMLInputElement(OwnerShadowHost()));
+ if (!input || input->IsDisabledOrReadOnly()) {
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+ return;
+ }
+
+ if (event->type() == EventTypeNames::click && event->IsMouseEvent() &&
+ ToMouseEvent(event)->button() ==
+ static_cast<short>(WebPointerProperties::Button::kLeft)) {
+ input->SetValueForUser("");
+ input->SetAutofilled(false);
+ input->OnSearch();
+ event->SetDefaultHandled();
+ }
+
+ if (!event->DefaultHandled())
+ HTMLDivElement::DefaultEventHandler(event);
+}
+
+bool SearchFieldCancelButtonElement::WillRespondToMouseClickEvents() {
+ const HTMLInputElement* input = ToHTMLInputElement(OwnerShadowHost());
+ if (input && !input->IsDisabledOrReadOnly())
+ return true;
+
+ return HTMLDivElement::WillRespondToMouseClickEvents();
+}
+
+} // namespace blink
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
new file mode 100644
index 00000000000..5f448b6b052
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_INNER_ELEMENTS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_INNER_ELEMENTS_H_
+
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+class TextControlInnerContainer final : public HTMLDivElement {
+ public:
+ static TextControlInnerContainer* Create(Document&);
+
+ protected:
+ explicit TextControlInnerContainer(Document&);
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+};
+
+class EditingViewPortElement final : public HTMLDivElement {
+ public:
+ static EditingViewPortElement* Create(Document&);
+
+ protected:
+ explicit EditingViewPortElement(Document&);
+ scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override;
+
+ private:
+ bool SupportsFocus() const override { return false; }
+};
+
+class TextControlInnerEditorElement final : public HTMLDivElement {
+ public:
+ static TextControlInnerEditorElement* Create(Document&);
+
+ void DefaultEventHandler(Event*) override;
+
+ void SetVisibility(bool is_visible);
+
+ private:
+ explicit TextControlInnerEditorElement(Document&);
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override;
+ scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override;
+ bool SupportsFocus() const override { return false; }
+ bool is_visible_ = true;
+};
+
+class SearchFieldCancelButtonElement final : public HTMLDivElement {
+ public:
+ static SearchFieldCancelButtonElement* Create(Document&);
+
+ void DefaultEventHandler(Event*) override;
+ bool WillRespondToMouseClickEvents() override;
+
+ private:
+ explicit SearchFieldCancelButtonElement(Document&);
+ void DetachLayoutTree(const AttachContext& = AttachContext()) override;
+ bool SupportsFocus() const override { return false; }
+
+ bool capturing_;
+};
+
+} // namespace blink
+
+#endif
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
new file mode 100644
index 00000000000..c5e2fbf13e6
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/text_field_input_type.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
+#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/keyboard_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"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.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/layout/layout_details_marker.h"
+#include "third_party/blink/renderer/core/layout/layout_text_control_single_line.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/core/page/page.h"
+#include "third_party/blink/renderer/core/paint/paint_layer.h"
+#include "third_party/blink/renderer/platform/event_dispatch_forbidden_scope.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+class DataListIndicatorElement final : public HTMLDivElement {
+ private:
+ inline DataListIndicatorElement(Document& document)
+ : HTMLDivElement(document) {}
+ inline HTMLInputElement* HostInput() const {
+ return ToHTMLInputElement(OwnerShadowHost());
+ }
+
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) override {
+ return new LayoutDetailsMarker(this);
+ }
+
+ EventDispatchHandlingState* PreDispatchEventHandler(Event* event) override {
+ // Chromium opens autofill popup in a mousedown event listener
+ // associated to the document. We don't want to open it in this case
+ // because we opens a datalist chooser later.
+ // FIXME: We should dispatch mousedown events even in such case.
+ if (event->type() == EventTypeNames::mousedown)
+ event->stopPropagation();
+ return nullptr;
+ }
+
+ void DefaultEventHandler(Event* event) override {
+ DCHECK(GetDocument().IsActive());
+ if (event->type() != EventTypeNames::click)
+ return;
+ HTMLInputElement* host = HostInput();
+ if (host && !host->IsDisabledOrReadOnly()) {
+ GetDocument().GetPage()->GetChromeClient().OpenTextDataListChooser(*host);
+ event->SetDefaultHandled();
+ }
+ }
+
+ bool WillRespondToMouseClickEvents() override {
+ return HostInput() && !HostInput()->IsDisabledOrReadOnly() &&
+ GetDocument().IsActive();
+ }
+
+ public:
+ static DataListIndicatorElement* Create(Document& document) {
+ DataListIndicatorElement* element = new DataListIndicatorElement(document);
+ element->SetShadowPseudoId(
+ AtomicString("-webkit-calendar-picker-indicator"));
+ element->setAttribute(idAttr, ShadowElementNames::PickerIndicator());
+ return element;
+ }
+};
+
+TextFieldInputType::TextFieldInputType(HTMLInputElement& element)
+ : InputType(element), InputTypeView(element) {}
+
+TextFieldInputType::~TextFieldInputType() = default;
+
+void TextFieldInputType::Trace(blink::Visitor* visitor) {
+ InputTypeView::Trace(visitor);
+ InputType::Trace(visitor);
+}
+
+InputTypeView* TextFieldInputType::CreateView() {
+ return this;
+}
+
+InputType::ValueMode TextFieldInputType::GetValueMode() const {
+ return ValueMode::kValue;
+}
+
+SpinButtonElement* TextFieldInputType::GetSpinButtonElement() const {
+ return ToSpinButtonElementOrDie(
+ GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::SpinButton()));
+}
+
+bool TextFieldInputType::ShouldShowFocusRingOnMouseFocus() const {
+ return true;
+}
+
+bool TextFieldInputType::IsTextField() const {
+ return true;
+}
+
+bool TextFieldInputType::ValueMissing(const String& value) const {
+ return GetElement().IsRequired() && value.IsEmpty();
+}
+
+bool TextFieldInputType::CanSetSuggestedValue() {
+ return true;
+}
+
+void TextFieldInputType::SetValue(const String& sanitized_value,
+ bool value_changed,
+ TextFieldEventBehavior event_behavior,
+ TextControlSetValueSelection selection) {
+ // We don't use InputType::setValue. TextFieldInputType dispatches events
+ // different way from InputType::setValue.
+ if (event_behavior == kDispatchNoEvent)
+ GetElement().SetNonAttributeValue(sanitized_value);
+ else
+ GetElement().SetNonAttributeValueByUserEdit(sanitized_value);
+
+ // The following early-return can't be moved to the beginning of this
+ // function. We need to update non-attribute value even if the value is not
+ // changed. For example, <input type=number> has a badInput string, that is
+ // to say, IDL value=="", and new value is "", which should clear the badInput
+ // string and update validiity.
+ if (!value_changed)
+ return;
+ GetElement().UpdateView();
+
+ if (selection == TextControlSetValueSelection::kSetSelectionToEnd) {
+ unsigned max = VisibleValue().length();
+ GetElement().SetSelectionRange(max, max);
+ }
+
+ switch (event_behavior) {
+ case kDispatchChangeEvent:
+ // If the user is still editing this field, dispatch an input event rather
+ // than a change event. The change event will be dispatched when editing
+ // finishes.
+ if (GetElement().IsFocused())
+ GetElement().DispatchInputEvent();
+ else
+ GetElement().DispatchFormControlChangeEvent();
+ break;
+
+ case kDispatchInputAndChangeEvent: {
+ GetElement().DispatchInputEvent();
+ GetElement().DispatchFormControlChangeEvent();
+ break;
+ }
+
+ case kDispatchNoEvent:
+ break;
+ }
+}
+
+void TextFieldInputType::HandleKeydownEvent(KeyboardEvent* event) {
+ if (!GetElement().IsFocused())
+ return;
+ if (ChromeClient* chrome_client = GetChromeClient()) {
+ chrome_client->HandleKeyboardEventOnTextField(GetElement(), *event);
+ return;
+ }
+ event->SetDefaultHandled();
+}
+
+void TextFieldInputType::HandleKeydownEventForSpinButton(KeyboardEvent* event) {
+ if (GetElement().IsDisabledOrReadOnly())
+ return;
+ const String& key = event->key();
+ if (key == "ArrowUp")
+ SpinButtonStepUp();
+ else if (key == "ArrowDown" && !event->altKey())
+ SpinButtonStepDown();
+ else
+ return;
+ GetElement().DispatchFormControlChangeEvent();
+ event->SetDefaultHandled();
+}
+
+void TextFieldInputType::ForwardEvent(Event* event) {
+ if (SpinButtonElement* spin_button = GetSpinButtonElement()) {
+ spin_button->ForwardEvent(event);
+ if (event->DefaultHandled())
+ return;
+ }
+
+ if (GetElement().GetLayoutObject() &&
+ (event->IsMouseEvent() || event->IsDragEvent() ||
+ event->HasInterface(EventNames::WheelEvent) ||
+ event->type() == EventTypeNames::blur ||
+ event->type() == EventTypeNames::focus)) {
+ LayoutTextControlSingleLine* layout_text_control =
+ ToLayoutTextControlSingleLine(GetElement().GetLayoutObject());
+ if (event->type() == EventTypeNames::blur) {
+ if (LayoutBox* inner_editor_layout_object =
+ GetElement().InnerEditorElement()->GetLayoutBox()) {
+ // FIXME: This class has no need to know about PaintLayer!
+ 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);
+ }
+ }
+ }
+
+ layout_text_control->CapsLockStateMayHaveChanged();
+ } else if (event->type() == EventTypeNames::focus) {
+ layout_text_control->CapsLockStateMayHaveChanged();
+ }
+
+ GetElement().ForwardEvent(event);
+ }
+}
+
+void TextFieldInputType::HandleBlurEvent() {
+ InputTypeView::HandleBlurEvent();
+ GetElement().EndEditing();
+ if (SpinButtonElement* spin_button = GetSpinButtonElement())
+ spin_button->ReleaseCapture();
+}
+
+bool TextFieldInputType::ShouldSubmitImplicitly(Event* event) {
+ return (event->type() == EventTypeNames::textInput &&
+ event->HasInterface(EventNames::TextEvent) &&
+ ToTextEvent(event)->data() == "\n") ||
+ InputTypeView::ShouldSubmitImplicitly(event);
+}
+
+LayoutObject* TextFieldInputType::CreateLayoutObject(
+ const ComputedStyle&) const {
+ return new LayoutTextControlSingleLine(&GetElement());
+}
+
+bool TextFieldInputType::ShouldHaveSpinButton() const {
+ return LayoutTheme::GetTheme().ShouldHaveSpinButton(&GetElement());
+}
+
+void TextFieldInputType::CreateShadowSubtree() {
+ DCHECK(IsShadowHost(GetElement()));
+ ShadowRoot* shadow_root = GetElement().UserAgentShadowRoot();
+ DCHECK(!shadow_root->HasChildren());
+
+ Document& document = GetElement().GetDocument();
+ bool should_have_spin_button = ShouldHaveSpinButton();
+ bool should_have_data_list_indicator = GetElement().HasValidDataListOptions();
+ bool creates_container = should_have_spin_button ||
+ should_have_data_list_indicator || NeedsContainer();
+
+ HTMLElement* inner_editor = GetElement().CreateInnerEditorElement();
+ if (!creates_container) {
+ shadow_root->AppendChild(inner_editor);
+ return;
+ }
+
+ TextControlInnerContainer* container =
+ TextControlInnerContainer::Create(document);
+ container->SetShadowPseudoId(
+ AtomicString("-webkit-textfield-decoration-container"));
+ shadow_root->AppendChild(container);
+
+ EditingViewPortElement* editing_view_port =
+ EditingViewPortElement::Create(document);
+ editing_view_port->AppendChild(inner_editor);
+ container->AppendChild(editing_view_port);
+
+ if (should_have_data_list_indicator)
+ container->AppendChild(DataListIndicatorElement::Create(document));
+ // FIXME: Because of a special handling for a spin button in
+ // LayoutTextControlSingleLine, we need to put it to the last position. It's
+ // inconsistent with multiple-fields date/time types.
+ if (should_have_spin_button)
+ container->AppendChild(SpinButtonElement::Create(document, *this));
+
+ // See listAttributeTargetChanged too.
+}
+
+Element* TextFieldInputType::ContainerElement() const {
+ return GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::TextFieldContainer());
+}
+
+void TextFieldInputType::DestroyShadowSubtree() {
+ InputTypeView::DestroyShadowSubtree();
+ if (SpinButtonElement* spin_button = GetSpinButtonElement())
+ spin_button->RemoveSpinButtonOwner();
+}
+
+void TextFieldInputType::ListAttributeTargetChanged() {
+ if (ChromeClient* chrome_client = GetChromeClient())
+ chrome_client->TextFieldDataListChanged(GetElement());
+ Element* picker = GetElement().UserAgentShadowRoot()->getElementById(
+ ShadowElementNames::PickerIndicator());
+ bool did_have_picker_indicator = picker;
+ bool will_have_picker_indicator = GetElement().HasValidDataListOptions();
+ if (did_have_picker_indicator == will_have_picker_indicator)
+ return;
+ EventDispatchForbiddenScope::AllowUserAgentEvents allow_events;
+ if (will_have_picker_indicator) {
+ Document& document = GetElement().GetDocument();
+ if (Element* container = ContainerElement()) {
+ container->InsertBefore(DataListIndicatorElement::Create(document),
+ GetSpinButtonElement());
+ } else {
+ // FIXME: The following code is similar to createShadowSubtree(),
+ // but they are different. We should simplify the code by making
+ // containerElement mandatory.
+ Element* rp_container = TextControlInnerContainer::Create(document);
+ rp_container->SetShadowPseudoId(
+ AtomicString("-webkit-textfield-decoration-container"));
+ Element* inner_editor = GetElement().InnerEditorElement();
+ inner_editor->parentNode()->ReplaceChild(rp_container, inner_editor);
+ Element* editing_view_port = EditingViewPortElement::Create(document);
+ editing_view_port->AppendChild(inner_editor);
+ rp_container->AppendChild(editing_view_port);
+ rp_container->AppendChild(DataListIndicatorElement::Create(document));
+ if (GetElement().GetDocument().FocusedElement() == GetElement())
+ GetElement().UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore);
+ }
+ } else {
+ picker->remove(ASSERT_NO_EXCEPTION);
+ }
+}
+
+void TextFieldInputType::AttributeChanged() {
+ // FIXME: Updating on any attribute update should be unnecessary. We should
+ // figure out what attributes affect.
+ UpdateView();
+}
+
+void TextFieldInputType::DisabledAttributeChanged() {
+ if (SpinButtonElement* spin_button = GetSpinButtonElement())
+ spin_button->ReleaseCapture();
+}
+
+void TextFieldInputType::ReadonlyAttributeChanged() {
+ if (SpinButtonElement* spin_button = GetSpinButtonElement())
+ spin_button->ReleaseCapture();
+}
+
+bool TextFieldInputType::SupportsReadOnly() const {
+ return true;
+}
+
+static bool IsASCIILineBreak(UChar c) {
+ return c == '\r' || c == '\n';
+}
+
+static String LimitLength(const String& string, unsigned max_length) {
+ unsigned new_length = std::min(max_length, string.length());
+ if (new_length == string.length())
+ return string;
+ if (new_length > 0 && U16_IS_LEAD(string[new_length - 1]))
+ --new_length;
+ return string.Left(new_length);
+}
+
+String TextFieldInputType::SanitizeValue(const String& proposed_value) const {
+ return LimitLength(proposed_value.RemoveCharacters(IsASCIILineBreak),
+ std::numeric_limits<int>::max());
+}
+
+void TextFieldInputType::HandleBeforeTextInsertedEvent(
+ BeforeTextInsertedEvent* event) {
+ // Make sure that the text to be inserted will not violate the maxLength.
+
+ // We use HTMLInputElement::innerEditorValue() instead of
+ // HTMLInputElement::value() because they can be mismatched by
+ // sanitizeValue() in HTMLInputElement::subtreeHasChanged() in some cases.
+ unsigned old_length = GetElement().InnerEditorValue().length();
+
+ // selectionLength represents the selection length of this text field to be
+ // removed by this insertion.
+ // If the text field has no focus, we don't need to take account of the
+ // selection length. The selection is the source of text drag-and-drop in
+ // that case, and nothing in the text field will be removed.
+ unsigned selection_length = 0;
+ if (GetElement().IsFocused()) {
+ // TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets
+ // needs to be audited. See http://crbug.com/590369 for more details.
+ GetElement().GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+
+ selection_length = GetElement()
+ .GetDocument()
+ .GetFrame()
+ ->Selection()
+ .SelectedText()
+ .length();
+ }
+ DCHECK_GE(old_length, selection_length);
+
+ // Selected characters will be removed by the next text event.
+ unsigned base_length = old_length - selection_length;
+ unsigned max_length;
+ if (MaxLength() < 0)
+ max_length = std::numeric_limits<int>::max();
+ else
+ max_length = static_cast<unsigned>(MaxLength());
+ unsigned appendable_length =
+ max_length > base_length ? max_length - base_length : 0;
+
+ // Truncate the inserted text to avoid violating the maxLength and other
+ // constraints.
+ String event_text = event->GetText();
+ unsigned text_length = event_text.length();
+ while (text_length > 0 && IsASCIILineBreak(event_text[text_length - 1]))
+ text_length--;
+ event_text.Truncate(text_length);
+ event_text.Replace("\r\n", " ");
+ event_text.Replace('\r', ' ');
+ event_text.Replace('\n', ' ');
+
+ event->SetText(LimitLength(event_text, appendable_length));
+}
+
+bool TextFieldInputType::ShouldRespectListAttribute() {
+ return true;
+}
+
+void TextFieldInputType::UpdatePlaceholderText() {
+ if (!SupportsPlaceholder())
+ return;
+ HTMLElement* placeholder = GetElement().PlaceholderElement();
+ String placeholder_text = GetElement().GetPlaceholderValue();
+ if (placeholder_text.IsEmpty()) {
+ if (placeholder)
+ placeholder->remove(ASSERT_NO_EXCEPTION);
+ return;
+ }
+ if (!placeholder) {
+ HTMLElement* new_element =
+ HTMLDivElement::Create(GetElement().GetDocument());
+ placeholder = new_element;
+ placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder"));
+ placeholder->SetInlineStyleProperty(
+ CSSPropertyDisplay,
+ GetElement().IsPlaceholderVisible() ? CSSValueBlock : CSSValueNone,
+ true);
+ placeholder->setAttribute(idAttr, ShadowElementNames::Placeholder());
+ Element* container = ContainerElement();
+ Node* previous = container ? container : GetElement().InnerEditorElement();
+ previous->parentNode()->InsertBefore(placeholder, previous);
+ SECURITY_DCHECK(placeholder->parentNode() == previous->parentNode());
+ }
+ placeholder->setTextContent(placeholder_text);
+}
+
+void TextFieldInputType::AppendToFormData(FormData& form_data) const {
+ InputType::AppendToFormData(form_data);
+ const AtomicString& dirname_attr_value =
+ GetElement().FastGetAttribute(dirnameAttr);
+ if (!dirname_attr_value.IsNull())
+ form_data.append(dirname_attr_value, GetElement().DirectionForFormData());
+}
+
+String TextFieldInputType::ConvertFromVisibleValue(
+ const String& visible_value) const {
+ return visible_value;
+}
+
+void TextFieldInputType::SubtreeHasChanged() {
+ GetElement().SetValueFromRenderer(SanitizeUserInputValue(
+ ConvertFromVisibleValue(GetElement().InnerEditorValue())));
+ GetElement().UpdatePlaceholderVisibility();
+ GetElement().PseudoStateChanged(CSSSelector::kPseudoValid);
+ GetElement().PseudoStateChanged(CSSSelector::kPseudoInvalid);
+ GetElement().PseudoStateChanged(CSSSelector::kPseudoInRange);
+ GetElement().PseudoStateChanged(CSSSelector::kPseudoOutOfRange);
+
+ DidSetValueByUserEdit();
+}
+
+void TextFieldInputType::DidSetValueByUserEdit() {
+ if (!GetElement().IsFocused())
+ return;
+ if (ChromeClient* chrome_client = GetChromeClient())
+ chrome_client->DidChangeValueInTextField(GetElement());
+}
+
+void TextFieldInputType::SpinButtonStepDown() {
+ StepUpFromLayoutObject(-1);
+}
+
+void TextFieldInputType::SpinButtonStepUp() {
+ StepUpFromLayoutObject(1);
+}
+
+void TextFieldInputType::UpdateView() {
+ if (GetElement().SuggestedValue().IsEmpty() &&
+ GetElement().NeedsToUpdateViewValue()) {
+ // Update the view only if needsToUpdateViewValue is true. It protects
+ // an unacceptable view value from being overwritten with the DOM value.
+ //
+ // e.g. <input type=number> has a view value "abc", and input.max is
+ // updated. In this case, updateView() is called but we should not
+ // update the view value.
+ GetElement().SetInnerEditorValue(VisibleValue());
+ GetElement().UpdatePlaceholderVisibility();
+ }
+}
+
+void TextFieldInputType::FocusAndSelectSpinButtonOwner() {
+ GetElement().focus();
+ GetElement().SetSelectionRange(0, std::numeric_limits<int>::max());
+}
+
+bool TextFieldInputType::ShouldSpinButtonRespondToMouseEvents() {
+ return !GetElement().IsDisabledOrReadOnly();
+}
+
+bool TextFieldInputType::ShouldSpinButtonRespondToWheelEvents() {
+ return ShouldSpinButtonRespondToMouseEvents() && GetElement().IsFocused();
+}
+
+void TextFieldInputType::SpinButtonDidReleaseMouseCapture(
+ SpinButtonElement::EventDispatch event_dispatch) {
+ if (event_dispatch == SpinButtonElement::kEventDispatchAllowed)
+ GetElement().DispatchFormControlChangeEvent();
+}
+
+} // 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
new file mode 100644
index 00000000000..fe8ca8497ea
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_FIELD_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_FIELD_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/input_type.h"
+#include "third_party/blink/renderer/core/html/forms/input_type_view.h"
+#include "third_party/blink/renderer/core/html/forms/spin_button_element.h"
+
+namespace blink {
+
+// The class represents types of which UI contain text fields.
+// It supports not only the types for BaseTextInputType but also type=number.
+class TextFieldInputType : public InputType,
+ public InputTypeView,
+ protected SpinButtonElement::SpinButtonOwner {
+ USING_GARBAGE_COLLECTED_MIXIN(TextFieldInputType);
+
+ public:
+ void Trace(blink::Visitor*) override;
+ using InputType::GetElement;
+
+ protected:
+ TextFieldInputType(HTMLInputElement&);
+ ~TextFieldInputType() override;
+ bool CanSetSuggestedValue() override;
+ void HandleKeydownEvent(KeyboardEvent*) override;
+
+ void CreateShadowSubtree() override;
+ void DestroyShadowSubtree() override;
+ void AttributeChanged() override;
+ void DisabledAttributeChanged() override;
+ void ReadonlyAttributeChanged() override;
+ bool SupportsReadOnly() const override;
+ void HandleBlurEvent() final;
+ String SanitizeValue(const String&) const override;
+ void SetValue(const String&,
+ bool value_changed,
+ TextFieldEventBehavior,
+ TextControlSetValueSelection) override;
+ void UpdateView() override;
+ LayoutObject* CreateLayoutObject(const ComputedStyle&) const override;
+
+ virtual bool NeedsContainer() const { return false; }
+ virtual String ConvertFromVisibleValue(const String&) const;
+ virtual void DidSetValueByUserEdit();
+
+ void HandleKeydownEventForSpinButton(KeyboardEvent*);
+ bool ShouldHaveSpinButton() const;
+ Element* ContainerElement() const;
+
+ private:
+ InputTypeView* CreateView() override;
+ ValueMode GetValueMode() const override;
+ bool ShouldShowFocusRingOnMouseFocus() const final;
+ bool IsTextField() const final;
+ bool ValueMissing(const String&) const override;
+ void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) override;
+ void ForwardEvent(Event*) final;
+ bool ShouldSubmitImplicitly(Event*) final;
+ bool ShouldRespectListAttribute() override;
+ void ListAttributeTargetChanged() override;
+ void UpdatePlaceholderText() final;
+ void AppendToFormData(FormData&) const override;
+ void SubtreeHasChanged() final;
+
+ // SpinButtonElement::SpinButtonOwner functions.
+ void FocusAndSelectSpinButtonOwner() final;
+ bool ShouldSpinButtonRespondToMouseEvents() final;
+ bool ShouldSpinButtonRespondToWheelEvents() final;
+ void SpinButtonStepDown() final;
+ void SpinButtonStepUp() final;
+ void SpinButtonDidReleaseMouseCapture(SpinButtonElement::EventDispatch) final;
+
+ SpinButtonElement* GetSpinButtonElement() const;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_FIELD_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..29847acfacd
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/text_input_type.h"
+
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+InputType* TextInputType::Create(HTMLInputElement& element) {
+ return new TextInputType(element);
+}
+
+void TextInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeText);
+ if (GetElement().FastHasAttribute(maxlengthAttr))
+ CountUsageIfVisible(WebFeature::kInputTypeTextMaxLength);
+ const AtomicString& type = GetElement().FastGetAttribute(typeAttr);
+ if (DeprecatedEqualIgnoringCase(type, InputTypeNames::datetime))
+ CountUsageIfVisible(WebFeature::kInputTypeDateTimeFallback);
+ else if (DeprecatedEqualIgnoringCase(type, InputTypeNames::week))
+ CountUsageIfVisible(WebFeature::kInputTypeWeekFallback);
+}
+
+const AtomicString& TextInputType::FormControlType() const {
+ return InputTypeNames::text;
+}
+
+bool TextInputType::SupportsInputModeAttribute() const {
+ return true;
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.h
new file mode 100644
index 00000000000..012d79470f9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h"
+
+namespace blink {
+
+class TextInputType final : public BaseTextInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ TextInputType(HTMLInputElement& element) : BaseTextInputType(element) {}
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ bool SupportsInputModeAttribute() const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_INPUT_TYPE_H_
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
new file mode 100644
index 00000000000..a3a7c972960
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/time_input_type.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"
+#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/input_type_names.h"
+#include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/date_math.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/time.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+static const int kTimeDefaultStep = 60;
+static const int kTimeDefaultStepBase = 0;
+static const int kTimeStepScaleFactor = 1000;
+
+TimeInputType::TimeInputType(HTMLInputElement& element)
+ : BaseTemporalInputType(element) {}
+
+InputType* TimeInputType::Create(HTMLInputElement& element) {
+ return new TimeInputType(element);
+}
+
+void TimeInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeTime);
+}
+
+const AtomicString& TimeInputType::FormControlType() const {
+ return InputTypeNames::time;
+}
+
+Decimal TimeInputType::DefaultValueForStepUp() const {
+ DateComponents date;
+ date.SetMillisecondsSinceMidnight(ConvertToLocalTime(CurrentTimeMS()));
+ double milliseconds = date.MillisecondsSinceEpoch();
+ DCHECK(std::isfinite(milliseconds));
+ return Decimal::FromDouble(milliseconds);
+}
+
+StepRange TimeInputType::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ DEFINE_STATIC_LOCAL(
+ const StepRange::StepDescription, step_description,
+ (kTimeDefaultStep, kTimeDefaultStepBase, kTimeStepScaleFactor,
+ StepRange::kScaledStepValueShouldBeInteger));
+
+ return InputType::CreateStepRange(
+ any_step_handling, kTimeDefaultStepBase,
+ Decimal::FromDouble(DateComponents::MinimumTime()),
+ Decimal::FromDouble(DateComponents::MaximumTime()), step_description);
+}
+
+bool TimeInputType::ParseToDateComponentsInternal(const String& string,
+ DateComponents* out) const {
+ DCHECK(out);
+ unsigned end;
+ return out->ParseTime(string, 0, end) && end == string.length();
+}
+
+bool TimeInputType::SetMillisecondToDateComponents(double value,
+ DateComponents* date) const {
+ DCHECK(date);
+ return date->SetMillisecondsSinceMidnight(value);
+}
+
+void TimeInputType::WarnIfValueIsInvalid(const String& value) const {
+ if (value != GetElement().SanitizeValue(value)) {
+ AddWarningToConsole(
+ "The specified value %s does not conform to the required format. The "
+ "format is \"HH:mm\", \"HH:mm:ss\" or \"HH:mm:ss.SSS\" where HH is "
+ "00-23, mm is 00-59, ss is 00-59, and SSS is 000-999.",
+ value);
+ }
+}
+
+String TimeInputType::LocalizeValue(const String& proposed_value) const {
+ DateComponents date;
+ if (!ParseToDateComponents(proposed_value, &date))
+ return proposed_value;
+
+ Locale::FormatType format_type = ShouldHaveSecondField(date)
+ ? Locale::kFormatTypeMedium
+ : Locale::kFormatTypeShort;
+
+ String localized = GetElement().GetLocale().FormatDateTime(date, format_type);
+ return localized.IsEmpty() ? proposed_value : localized;
+}
+
+String TimeInputType::FormatDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) const {
+ if (!date_time_fields_state.HasHour() ||
+ !date_time_fields_state.HasMinute() || !date_time_fields_state.HasAMPM())
+ return g_empty_string;
+ if (date_time_fields_state.HasMillisecond() &&
+ date_time_fields_state.Millisecond()) {
+ return String::Format(
+ "%02u:%02u:%02u.%03u", date_time_fields_state.Hour23(),
+ date_time_fields_state.Minute(),
+ date_time_fields_state.HasSecond() ? date_time_fields_state.Second()
+ : 0,
+ date_time_fields_state.Millisecond());
+ }
+ if (date_time_fields_state.HasSecond() && date_time_fields_state.Second()) {
+ return String::Format("%02u:%02u:%02u", date_time_fields_state.Hour23(),
+ date_time_fields_state.Minute(),
+ date_time_fields_state.Second());
+ }
+ return String::Format("%02u:%02u", date_time_fields_state.Hour23(),
+ date_time_fields_state.Minute());
+}
+
+void TimeInputType::SetupLayoutParameters(
+ DateTimeEditElement::LayoutParameters& layout_parameters,
+ const DateComponents& date) const {
+ if (ShouldHaveSecondField(date)) {
+ layout_parameters.date_time_format = layout_parameters.locale.TimeFormat();
+ layout_parameters.fallback_date_time_format = "HH:mm:ss";
+ } else {
+ layout_parameters.date_time_format =
+ layout_parameters.locale.ShortTimeFormat();
+ layout_parameters.fallback_date_time_format = "HH:mm";
+ }
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr),
+ &layout_parameters.minimum))
+ layout_parameters.minimum = DateComponents();
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr),
+ &layout_parameters.maximum))
+ layout_parameters.maximum = DateComponents();
+}
+
+bool TimeInputType::IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const {
+ return has_hour && has_minute && has_ampm;
+}
+
+} // 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
new file mode 100644
index 00000000000..91abeeead30
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TIME_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TIME_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+
+namespace blink {
+
+class TimeInputType final : public BaseTemporalInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ explicit TimeInputType(HTMLInputElement&);
+
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ Decimal DefaultValueForStepUp() const override;
+ StepRange CreateStepRange(AnyStepHandling) const override;
+ bool ParseToDateComponentsInternal(const String&,
+ DateComponents*) const override;
+ bool SetMillisecondToDateComponents(double, DateComponents*) const override;
+ void WarnIfValueIsInvalid(const String&) const override;
+ String LocalizeValue(const String&) const override;
+
+ // BaseTemporalInputType functions
+ String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override;
+ void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&,
+ const DateComponents&) const override;
+ bool IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TIME_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/type_ahead.cc b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.cc
new file mode 100644
index 00000000000..2081ed3d154
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.cc
@@ -0,0 +1,128 @@
+/*
+ * 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/type_ahead.h"
+
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
+
+namespace blink {
+
+TypeAhead::TypeAhead(TypeAheadDataSource* data_source)
+ : data_source_(data_source), repeating_char_(0) {}
+
+constexpr TimeDelta kTypeAheadTimeout = TimeDelta::FromSecondsD(1);
+
+static String StripLeadingWhiteSpace(const String& string) {
+ unsigned length = string.length();
+
+ unsigned i;
+ for (i = 0; i < length; ++i) {
+ if (string[i] != kNoBreakSpaceCharacter && !IsSpaceOrNewline(string[i]))
+ break;
+ }
+
+ return string.Substring(i, length - i);
+}
+
+int TypeAhead::HandleEvent(KeyboardEvent* event, MatchModeFlags match_mode) {
+ if (event->PlatformTimeStamp() < last_type_time_)
+ return -1;
+
+ int option_count = data_source_->OptionCount();
+ TimeDelta delta = event->PlatformTimeStamp() - last_type_time_;
+ last_type_time_ = event->PlatformTimeStamp();
+
+ UChar c = event->charCode();
+
+ if (delta > kTypeAheadTimeout)
+ buffer_.Clear();
+
+ buffer_.Append(c);
+
+ if (option_count < 1)
+ return -1;
+
+ int search_start_offset = 1;
+ String prefix;
+ if (match_mode & kCycleFirstChar && c == repeating_char_) {
+ // The user is likely trying to cycle through all the items starting
+ // with this character, so just search on the character.
+ prefix = String(&c, 1);
+ repeating_char_ = c;
+ } else if (match_mode & kMatchPrefix) {
+ prefix = buffer_.ToString();
+ if (buffer_.length() > 1) {
+ repeating_char_ = 0;
+ search_start_offset = 0;
+ } else {
+ repeating_char_ = c;
+ }
+ }
+
+ if (!prefix.IsEmpty()) {
+ int selected = data_source_->IndexOfSelectedOption();
+ int index = (selected < 0 ? 0 : selected) + search_start_offset;
+ index %= option_count;
+
+ // Compute a case-folded copy of the prefix string before beginning the
+ // search for a matching element. This code uses foldCase to work around the
+ // fact that String::startWith does not fold non-ASCII characters. This code
+ // can be changed to use startWith once that is fixed.
+ String prefix_with_case_folded(prefix.FoldCase());
+ for (int i = 0; i < option_count; ++i, index = (index + 1) % option_count) {
+ // Fold the option string and check if its prefix is equal to the folded
+ // prefix.
+ String text = data_source_->OptionAtIndex(index);
+ if (StripLeadingWhiteSpace(text).FoldCase().StartsWith(
+ prefix_with_case_folded))
+ return index;
+ }
+ }
+
+ if (match_mode & kMatchIndex) {
+ bool ok = false;
+ int index = buffer_.ToString().ToInt(&ok);
+ if (index > 0 && index <= option_count)
+ return index - 1;
+ }
+ return -1;
+}
+
+bool TypeAhead::HasActiveSession(KeyboardEvent* event) {
+ TimeDelta delta = event->PlatformTimeStamp() - last_type_time_;
+ return delta <= kTypeAheadTimeout;
+}
+
+void TypeAhead::ResetSession() {
+ last_type_time_ = TimeTicks();
+ buffer_.Clear();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/type_ahead.h b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.h
new file mode 100644
index 00000000000..b8b59424884
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TYPE_AHEAD_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TYPE_AHEAD_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/time.h"
+
+namespace blink {
+
+class KeyboardEvent;
+
+class CORE_EXPORT TypeAheadDataSource {
+ public:
+ virtual ~TypeAheadDataSource() = default;
+
+ virtual int IndexOfSelectedOption() const = 0;
+ virtual int OptionCount() const = 0;
+ virtual String OptionAtIndex(int index) const = 0;
+};
+
+class TypeAhead {
+ DISALLOW_NEW();
+
+ public:
+ TypeAhead(TypeAheadDataSource*);
+
+ enum ModeFlag {
+ kMatchPrefix = 1 << 0,
+ kCycleFirstChar = 1 << 1,
+ kMatchIndex = 1 << 2,
+ };
+ using MatchModeFlags = unsigned;
+
+ // Returns the index for the matching option.
+ int HandleEvent(KeyboardEvent*, MatchModeFlags);
+ bool HasActiveSession(KeyboardEvent*);
+ void ResetSession();
+
+ private:
+ TypeAheadDataSource* data_source_;
+ // platform timestamp of last keyboard event in seconds
+ TimeTicks last_type_time_;
+ UChar repeating_char_;
+ StringBuilder buffer_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TYPE_AHEAD_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/url_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.cc
new file mode 100644
index 00000000000..6cc7a579274
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/url_input_type.h"
+
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
+#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+
+namespace blink {
+
+InputType* URLInputType::Create(HTMLInputElement& element) {
+ return new URLInputType(element);
+}
+
+void URLInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeURL);
+}
+
+const AtomicString& URLInputType::FormControlType() const {
+ return InputTypeNames::url;
+}
+
+bool URLInputType::TypeMismatchFor(const String& value) const {
+ return !value.IsEmpty() && !KURL(NullURL(), value).IsValid();
+}
+
+bool URLInputType::TypeMismatch() const {
+ return TypeMismatchFor(GetElement().value());
+}
+
+String URLInputType::TypeMismatchText() const {
+ return GetLocale().QueryString(
+ WebLocalizedString::kValidationTypeMismatchForURL);
+}
+
+String URLInputType::SanitizeValue(const String& proposed_value) const {
+ return BaseTextInputType::SanitizeValue(
+ StripLeadingAndTrailingHTMLSpaces(proposed_value));
+}
+
+String URLInputType::SanitizeUserInputValue(
+ const String& proposed_value) const {
+ // Do not call URLInputType::sanitizeValue.
+ return BaseTextInputType::SanitizeValue(proposed_value);
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/url_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.h
new file mode 100644
index 00000000000..6084b52eddc
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_URL_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_URL_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h"
+
+namespace blink {
+
+class URLInputType final : public BaseTextInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ URLInputType(HTMLInputElement& element) : BaseTextInputType(element) {}
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ bool TypeMismatchFor(const String&) const override;
+ bool TypeMismatch() const override;
+ String TypeMismatchText() const override;
+ String SanitizeValue(const String&) const override;
+ String SanitizeUserInputValue(const String&) const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_URL_INPUT_TYPE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/validity_state.cc b/chromium/third_party/blink/renderer/core/html/forms/validity_state.cc
new file mode 100644
index 00000000000..d5b64098f78
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/validity_state.cc
@@ -0,0 +1,76 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com>
+ * Copyright (C) 2010 Apple Inc. All rights reserved.
+ *
+ * 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/validity_state.h"
+
+namespace blink {
+
+String ValidityState::ValidationMessage() const {
+ return control_->validationMessage();
+}
+
+bool ValidityState::valueMissing() const {
+ return control_->ValueMissing();
+}
+
+bool ValidityState::typeMismatch() const {
+ return control_->TypeMismatch();
+}
+
+bool ValidityState::patternMismatch() const {
+ return control_->PatternMismatch();
+}
+
+bool ValidityState::tooLong() const {
+ return control_->TooLong();
+}
+
+bool ValidityState::tooShort() const {
+ return control_->TooShort();
+}
+
+bool ValidityState::rangeUnderflow() const {
+ return control_->RangeUnderflow();
+}
+
+bool ValidityState::rangeOverflow() const {
+ return control_->RangeOverflow();
+}
+
+bool ValidityState::stepMismatch() const {
+ return control_->StepMismatch();
+}
+
+bool ValidityState::badInput() const {
+ return control_->HasBadInput();
+}
+
+bool ValidityState::customError() const {
+ return control_->CustomError();
+}
+
+bool ValidityState::valid() const {
+ return control_->Valid();
+}
+
+} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/html/forms/validity_state.h b/chromium/third_party/blink/renderer/core/html/forms/validity_state.h
new file mode 100644
index 00000000000..483bbcd863d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/validity_state.h
@@ -0,0 +1,71 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com>
+ * Copyright (C) 2010 Apple Inc. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_VALIDITY_STATE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_VALIDITY_STATE_H_
+
+#include "base/macros.h"
+#include "third_party/blink/renderer/core/html/forms/listed_element.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+
+namespace blink {
+
+class ValidityState final : public ScriptWrappable {
+ DEFINE_WRAPPERTYPEINFO();
+
+ public:
+ static ValidityState* Create(ListedElement* control) {
+ return new ValidityState(control);
+ }
+ void Trace(blink::Visitor* visitor) override {
+ visitor->Trace(control_);
+ ScriptWrappable::Trace(visitor);
+ }
+
+ String ValidationMessage() const;
+
+ void SetCustomErrorMessage(const String&);
+
+ bool valueMissing() const;
+ bool typeMismatch() const;
+ bool patternMismatch() const;
+ bool tooLong() const;
+ bool tooShort() const;
+ bool rangeUnderflow() const;
+ bool rangeOverflow() const;
+ bool stepMismatch() const;
+ bool badInput() const;
+ bool customError() const;
+ bool valid() const;
+
+ private:
+ explicit ValidityState(ListedElement* control) : control_(control) {}
+
+ Member<ListedElement> control_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValidityState);
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_VALIDITY_STATE_H_
diff --git a/chromium/third_party/blink/renderer/core/html/forms/validity_state.idl b/chromium/third_party/blink/renderer/core/html/forms/validity_state.idl
new file mode 100644
index 00000000000..117bb925acb
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/validity_state.idl
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the WebKit project.
+ *
+ * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.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.
+ *
+ */
+
+// https://html.spec.whatwg.org/#validitystate
+
+interface ValidityState {
+ readonly attribute boolean valueMissing;
+ readonly attribute boolean typeMismatch;
+ readonly attribute boolean patternMismatch;
+ readonly attribute boolean tooLong;
+ readonly attribute boolean tooShort;
+ readonly attribute boolean rangeUnderflow;
+ readonly attribute boolean rangeOverflow;
+ readonly attribute boolean stepMismatch;
+ readonly attribute boolean badInput;
+ readonly attribute boolean customError;
+ readonly attribute boolean valid;
+};
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
new file mode 100644
index 00000000000..1dcd8d01db2
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/forms/week_input_type.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"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/platform/date_components.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+using namespace HTMLNames;
+
+static const int kWeekDefaultStepBase =
+ -259200000; // The first day of 1970-W01.
+static const int kWeekDefaultStep = 1;
+static const int kWeekStepScaleFactor = 604800000;
+
+InputType* WeekInputType::Create(HTMLInputElement& element) {
+ return new WeekInputType(element);
+}
+
+void WeekInputType::CountUsage() {
+ CountUsageIfVisible(WebFeature::kInputTypeWeek);
+}
+
+const AtomicString& WeekInputType::FormControlType() const {
+ return InputTypeNames::week;
+}
+
+StepRange WeekInputType::CreateStepRange(
+ AnyStepHandling any_step_handling) const {
+ DEFINE_STATIC_LOCAL(
+ const StepRange::StepDescription, step_description,
+ (kWeekDefaultStep, kWeekDefaultStepBase, kWeekStepScaleFactor,
+ StepRange::kParsedStepValueShouldBeInteger));
+
+ return InputType::CreateStepRange(
+ any_step_handling, kWeekDefaultStepBase,
+ Decimal::FromDouble(DateComponents::MinimumWeek()),
+ Decimal::FromDouble(DateComponents::MaximumWeek()), step_description);
+}
+
+bool WeekInputType::ParseToDateComponentsInternal(const String& string,
+ DateComponents* out) const {
+ DCHECK(out);
+ unsigned end;
+ return out->ParseWeek(string, 0, end) && end == string.length();
+}
+
+bool WeekInputType::SetMillisecondToDateComponents(double value,
+ DateComponents* date) const {
+ DCHECK(date);
+ return date->SetMillisecondsSinceEpochForWeek(value);
+}
+
+void WeekInputType::WarnIfValueIsInvalid(const String& value) const {
+ if (value != GetElement().SanitizeValue(value))
+ AddWarningToConsole(
+ "The specified value %s does not conform to the required format. The "
+ "format is \"yyyy-Www\" where yyyy is year in four or more digits, and "
+ "ww is 01-53.",
+ value);
+}
+
+String WeekInputType::FormatDateTimeFieldsState(
+ const DateTimeFieldsState& date_time_fields_state) const {
+ if (!date_time_fields_state.HasYear() ||
+ !date_time_fields_state.HasWeekOfYear())
+ return g_empty_string;
+ return String::Format("%04u-W%02u", date_time_fields_state.Year(),
+ date_time_fields_state.WeekOfYear());
+}
+
+void WeekInputType::SetupLayoutParameters(
+ DateTimeEditElement::LayoutParameters& layout_parameters,
+ const DateComponents&) const {
+ layout_parameters.date_time_format = GetLocale().WeekFormatInLDML();
+ layout_parameters.fallback_date_time_format = "yyyy-'W'ww";
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr),
+ &layout_parameters.minimum))
+ layout_parameters.minimum = DateComponents();
+ if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr),
+ &layout_parameters.maximum))
+ layout_parameters.maximum = DateComponents();
+ layout_parameters.placeholder_for_year = "----";
+}
+
+bool WeekInputType::IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const {
+ return has_year && has_week;
+}
+
+} // 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
new file mode 100644
index 00000000000..6486a627dc3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_WEEK_INPUT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_WEEK_INPUT_TYPE_H_
+
+#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h"
+
+namespace blink {
+
+class WeekInputType final : public BaseTemporalInputType {
+ public:
+ static InputType* Create(HTMLInputElement&);
+
+ private:
+ explicit WeekInputType(HTMLInputElement& element)
+ : BaseTemporalInputType(element) {}
+
+ void CountUsage() override;
+ const AtomicString& FormControlType() const override;
+ StepRange CreateStepRange(AnyStepHandling) const override;
+ bool ParseToDateComponentsInternal(const String&,
+ DateComponents*) const override;
+ bool SetMillisecondToDateComponents(double, DateComponents*) const override;
+ void WarnIfValueIsInvalid(const String&) const override;
+
+ // BaseTemporalInputType functions
+ String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override;
+ void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&,
+ const DateComponents&) const override;
+ bool IsValidFormat(bool has_year,
+ bool has_month,
+ bool has_week,
+ bool has_day,
+ bool has_ampm,
+ bool has_hour,
+ bool has_minute,
+ bool has_second) const override;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_WEEK_INPUT_TYPE_H_