blob: 220dd750c83c3a0c3c9fdae89abfc4112756e364 [file] [log] [blame]
/*
* 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