| // 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/fileapi/file_list.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #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/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/layout/layout_object.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>İ</option>" // LATIN CAPITAL LETTER I WITH DOT ABOVE |
| "<option>i</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(html_names::kRequiredAttr, 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(html_names::kRequiredAttr, true); |
| form->AppendChild(input_with_form); |
| EXPECT_EQ("<<ValidationValueMissing>>", input_with_form->DefaultToolTip()); |
| |
| form->SetBooleanAttribute(html_names::kNovalidateAttr, true); |
| EXPECT_EQ(String(), input_with_form->DefaultToolTip()); |
| } |
| |
| // crbug.com/589838 |
| TEST_F(HTMLInputElementTest, ImageTypeCrash) { |
| auto* input = HTMLInputElement::Create(GetDocument(), CreateElementFlags()); |
| input->setAttribute(html_names::kTypeAttr, "image"); |
| input->EnsureFallbackContent(); |
| // Make sure ensurePrimaryContent() recreates UA shadow tree, and updating |
| // |value| doesn't crash. |
| input->EnsurePrimaryContent(); |
| input->setAttribute(html_names::kValueAttr, "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(html_names::kStyleAttr, "position:fixed"); |
| KeyboardEventInit* init = KeyboardEventInit::Create(); |
| 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' />"); |
| UpdateAllLifecyclePhasesForTest(); |
| 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(html_names::kTypeAttr, "date"); |
| input->setAttribute(html_names::kMinAttr, "2010-02-10"); |
| input->setAttribute(html_names::kStepAttr, "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(html_names::kTypeAttr, "text"); |
| EXPECT_NE(nullptr, input->UserAgentShadowRoot()); |
| } |
| |
| TEST_F(HTMLInputElementTest, RepaintAfterClearingFile) { |
| GetDocument().body()->SetInnerHTMLFromString("<input type='file' />"); |
| HTMLInputElement* input = |
| ToHTMLInputElement(GetDocument().body()->firstChild()); |
| |
| FileChooserFileInfoList files; |
| files.push_back(CreateFileChooserFileInfoNative("/native/path/native-file", |
| "display-name")); |
| FileList* list = FileInputType::CreateFileList(files, base::FilePath()); |
| ASSERT_TRUE(list); |
| EXPECT_EQ(1u, list->length()); |
| |
| input->setFiles(list); |
| UpdateAllLifecyclePhasesForTest(); |
| |
| ASSERT_TRUE(input->GetLayoutObject()); |
| EXPECT_FALSE(input->GetLayoutObject()->ShouldCheckForPaintInvalidation()); |
| |
| input->setValue(""); |
| GetDocument().UpdateStyleAndLayoutTree(); |
| |
| ASSERT_TRUE(input->GetLayoutObject()); |
| EXPECT_TRUE(input->GetLayoutObject()->ShouldCheckForPaintInvalidation()); |
| } |
| |
| } // namespace blink |