| /* |
| * 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/dom_token_list.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/events/mouse_event.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/layout/layout_object_factory.h" |
| |
| namespace blink { |
| |
| EditingViewPortElement::EditingViewPortElement(Document& document) |
| : HTMLDivElement(document) { |
| SetHasCustomStyleCallbacks(); |
| setAttribute(html_names::kIdAttr, shadow_element_names::kIdEditingViewPort); |
| } |
| |
| scoped_refptr<ComputedStyle> EditingViewPortElement::CustomStyleForLayoutObject( |
| const StyleRecalcContext&) { |
| // FXIME: Move these styles to html.css. |
| |
| scoped_refptr<ComputedStyle> style = ComputedStyle::Create(); |
| style->InheritFrom(OwnerShadowHost()->ComputedStyleRef()); |
| |
| style->SetFlexGrow(1); |
| style->SetMinWidth(Length::Fixed(0)); |
| 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); |
| |
| return style; |
| } |
| |
| bool EditingViewPortElement::TypeShouldForceLegacyLayout() const { |
| return !RuntimeEnabledFeatures::LayoutNGTextControlEnabled(); |
| } |
| |
| // --------------------------- |
| |
| TextControlInnerEditorElement::TextControlInnerEditorElement(Document& document) |
| : HTMLDivElement(document) { |
| SetHasCustomStyleCallbacks(); |
| } |
| |
| 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() == event_type_names::kWebkitEditableContentChanged) { |
| 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.type() == event_type_names::kScroll) { |
| // The scroller for a text control is inside of a shadow tree but the |
| // scroll event won't bubble past the shadow root and authors cannot add |
| // an event listener to it. Fire the scroll event at the shadow host so |
| // that the page can hear about the scroll. |
| Element* shadow_ancestor = OwnerShadowHost(); |
| if (shadow_ancestor) |
| shadow_ancestor->DispatchEvent(event); |
| } |
| |
| if (!event.DefaultHandled()) |
| HTMLDivElement::DefaultEventHandler(event); |
| } |
| |
| void TextControlInnerEditorElement::SetVisibility(bool is_visible) { |
| if (is_visible_ != is_visible) { |
| is_visible_ = is_visible; |
| SetNeedsStyleRecalc(kLocalStyleChange, |
| StyleChangeReasonForTracing::Create( |
| style_change_reason::kControlValue)); |
| } |
| } |
| |
| void TextControlInnerEditorElement::FocusChanged() { |
| // When the focus changes for the host element, we may need to recalc style |
| // for text-overflow. See TextControlElement::ValueForTextOverflow(). |
| SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::Create( |
| style_change_reason::kControl)); |
| } |
| |
| bool TextControlInnerEditorElement::TypeShouldForceLegacyLayout() const { |
| return !RuntimeEnabledFeatures::LayoutNGTextControlEnabled(); |
| } |
| |
| LayoutObject* TextControlInnerEditorElement::CreateLayoutObject( |
| const ComputedStyle& style, |
| LegacyLayout legacy) { |
| return LayoutObjectFactory::CreateTextControlInnerEditor(*this, style, |
| legacy); |
| } |
| |
| scoped_refptr<ComputedStyle> |
| TextControlInnerEditorElement::CustomStyleForLayoutObject( |
| const StyleRecalcContext&) { |
| scoped_refptr<ComputedStyle> inner_editor_style = CreateInnerEditorStyle(); |
| // 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; |
| } |
| |
| scoped_refptr<ComputedStyle> |
| TextControlInnerEditorElement::CreateInnerEditorStyle() const { |
| Element* host = OwnerShadowHost(); |
| DCHECK(host); |
| const ComputedStyle& start_style = host->ComputedStyleRef(); |
| scoped_refptr<ComputedStyle> text_block_style = ComputedStyle::Create(); |
| text_block_style->InheritFrom(start_style); |
| // The inner block, if present, always has its direction set to LTR, |
| // so we need to inherit the direction and unicode-bidi style from the |
| // element. |
| text_block_style->SetDirection(start_style.Direction()); |
| text_block_style->SetUnicodeBidi(start_style.GetUnicodeBidi()); |
| text_block_style->SetUserSelect(EUserSelect::kText); |
| text_block_style->SetUserModify( |
| To<HTMLFormControlElement>(host)->IsDisabledOrReadOnly() |
| ? EUserModify::kReadOnly |
| : EUserModify::kReadWritePlaintextOnly); |
| text_block_style->SetDisplay(EDisplay::kBlock); |
| text_block_style->SetHasLineIfEmpty(true); |
| text_block_style->SetShouldIgnoreOverflowPropertyForInlineBlockBaseline(); |
| |
| if (!IsA<HTMLTextAreaElement>(host)) { |
| text_block_style->SetWhiteSpace(EWhiteSpace::kPre); |
| text_block_style->SetOverflowWrap(EOverflowWrap::kNormal); |
| text_block_style->SetTextOverflow( |
| ToTextControl(host)->ValueForTextOverflow()); |
| int computed_line_height = start_style.ComputedLineHeight(); |
| // Do not allow line-height to be smaller than our default. |
| if (text_block_style->FontSize() >= computed_line_height) { |
| text_block_style->SetLineHeight( |
| ComputedStyleInitialValues::InitialLineHeight()); |
| } |
| |
| // We'd like to remove line-height if it's unnecessary because |
| // overflow:scroll clips editing text by line-height. |
| const Length& logical_height = start_style.LogicalHeight(); |
| // Here, we remove line-height if the INPUT fixed height is taller than the |
| // line-height. It's not the precise condition because logicalHeight |
| // includes border and padding if box-sizing:border-box, and there are cases |
| // in which we don't want to remove line-height with percent or calculated |
| // length. |
| // TODO(tkent): This should be done during layout. |
| if (logical_height.IsPercentOrCalc() || |
| (logical_height.IsFixed() && |
| logical_height.GetFloatValue() > computed_line_height)) { |
| text_block_style->SetLineHeight( |
| ComputedStyleInitialValues::InitialLineHeight()); |
| } |
| |
| if (To<HTMLInputElement>(host)->ShouldRevealPassword()) |
| text_block_style->SetTextSecurity(ETextSecurity::kNone); |
| |
| text_block_style->SetOverflowX(EOverflow::kScroll); |
| // overflow-y:visible doesn't work because overflow-x:scroll makes a layer. |
| text_block_style->SetOverflowY(EOverflow::kScroll); |
| scoped_refptr<ComputedStyle> no_scrollbar_style = ComputedStyle::Create(); |
| no_scrollbar_style->SetStyleType(kPseudoIdScrollbar); |
| no_scrollbar_style->SetDisplay(EDisplay::kNone); |
| text_block_style->AddCachedPseudoElementStyle(no_scrollbar_style); |
| text_block_style->SetHasPseudoElementStyle(kPseudoIdScrollbar); |
| |
| text_block_style->SetDisplay(EDisplay::kFlowRoot); |
| if (parentNode()->IsShadowRoot()) |
| text_block_style->SetAlignSelfBlockCenter(true); |
| } |
| |
| return text_block_style; |
| } |
| |
| // ---------------------------- |
| |
| SearchFieldCancelButtonElement::SearchFieldCancelButtonElement( |
| Document& document) |
| : HTMLDivElement(document) { |
| SetShadowPseudoId(AtomicString("-webkit-search-cancel-button")); |
| setAttribute(html_names::kIdAttr, shadow_element_names::kIdSearchClearButton); |
| } |
| |
| void SearchFieldCancelButtonElement::DefaultEventHandler(Event& event) { |
| // If the element is visible, on mouseup, clear the value, and set selection |
| auto* mouse_event = DynamicTo<MouseEvent>(event); |
| auto* input = To<HTMLInputElement>(OwnerShadowHost()); |
| if (!input || input->IsDisabledOrReadOnly()) { |
| if (!event.DefaultHandled()) |
| HTMLDivElement::DefaultEventHandler(event); |
| return; |
| } |
| |
| if (event.type() == event_type_names::kClick && mouse_event && |
| mouse_event->button() == |
| static_cast<int16_t>(WebPointerProperties::Button::kLeft)) { |
| input->SetValueForUser(""); |
| input->SetAutofillState(WebAutofillState::kNotFilled); |
| input->OnSearch(); |
| event.SetDefaultHandled(); |
| } |
| |
| if (!event.DefaultHandled()) |
| HTMLDivElement::DefaultEventHandler(event); |
| } |
| |
| bool SearchFieldCancelButtonElement::WillRespondToMouseClickEvents() { |
| auto* input = To<HTMLInputElement>(OwnerShadowHost()); |
| if (input && !input->IsDisabledOrReadOnly()) |
| return true; |
| |
| return HTMLDivElement::WillRespondToMouseClickEvents(); |
| } |
| |
| bool SearchFieldCancelButtonElement::TypeShouldForceLegacyLayout() const { |
| return !RuntimeEnabledFeatures::LayoutNGTextControlEnabled(); |
| } |
| |
| // ---------------------------- |
| |
| PasswordRevealButtonElement::PasswordRevealButtonElement(Document& document) |
| : HTMLDivElement(document) { |
| SetShadowPseudoId(AtomicString("-internal-reveal")); |
| setAttribute(html_names::kIdAttr, |
| shadow_element_names::kIdPasswordRevealButton); |
| } |
| |
| void PasswordRevealButtonElement::DefaultEventHandler(Event& event) { |
| auto* input = To<HTMLInputElement>(OwnerShadowHost()); |
| if (!input || input->IsDisabledOrReadOnly()) { |
| if (!event.DefaultHandled()) |
| HTMLDivElement::DefaultEventHandler(event); |
| return; |
| } |
| |
| // Toggle the should-reveal-password state when clicked. |
| if (event.type() == event_type_names::kClick && IsA<MouseEvent>(event)) { |
| bool shouldRevealPassword = !input->ShouldRevealPassword(); |
| |
| input->SetShouldRevealPassword(shouldRevealPassword); |
| input->UpdateView(); |
| |
| event.SetDefaultHandled(); |
| } |
| |
| if (!event.DefaultHandled()) |
| HTMLDivElement::DefaultEventHandler(event); |
| } |
| |
| bool PasswordRevealButtonElement::WillRespondToMouseClickEvents() { |
| auto* input = To<HTMLInputElement>(OwnerShadowHost()); |
| if (input && !input->IsDisabledOrReadOnly()) |
| return true; |
| |
| return HTMLDivElement::WillRespondToMouseClickEvents(); |
| } |
| } // namespace blink |