diff options
Diffstat (limited to 'Source/WebCore/rendering/TextControlInnerElements.cpp')
-rw-r--r-- | Source/WebCore/rendering/TextControlInnerElements.cpp | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/TextControlInnerElements.cpp b/Source/WebCore/rendering/TextControlInnerElements.cpp new file mode 100644 index 0000000..d6fc7aa --- /dev/null +++ b/Source/WebCore/rendering/TextControlInnerElements.cpp @@ -0,0 +1,505 @@ +/* + * 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 "config.h" +#include "TextControlInnerElements.h" + +#include "BeforeTextInsertedEvent.h" +#include "Document.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HTMLTextAreaElement.h" +#include "MouseEvent.h" +#include "Page.h" +#include "RenderLayer.h" +#include "RenderTextControlSingleLine.h" +#include "ScrollbarTheme.h" +#include "SpeechInput.h" +#include "SpeechInputEvent.h" + +namespace WebCore { + +using namespace HTMLNames; + +class RenderTextControlInnerBlock : public RenderBlock { +public: + RenderTextControlInnerBlock(Node* node, bool isMultiLine) : RenderBlock(node), m_multiLine(isMultiLine) { } + +private: + virtual bool hasLineIfEmpty() const { return true; } + virtual VisiblePosition positionForPoint(const IntPoint&); + + bool m_multiLine; +}; + +VisiblePosition RenderTextControlInnerBlock::positionForPoint(const IntPoint& point) +{ + IntPoint contentsPoint(point); + + // Multiline text controls have the scroll on shadowAncestorNode, so we need to take that + // into account here. + if (m_multiLine) { + RenderTextControl* renderer = toRenderTextControl(node()->shadowAncestorNode()->renderer()); + if (renderer->hasOverflowClip()) + contentsPoint += renderer->layer()->scrolledContentOffset(); + } + + return RenderBlock::positionForPoint(contentsPoint); +} + +// ---------------------------- + +TextControlInnerElement::TextControlInnerElement(Document* document, HTMLElement* shadowParent) + : HTMLDivElement(divTag, document) + , m_shadowParent(shadowParent) +{ + setShadowHost(shadowParent); +} + +PassRefPtr<TextControlInnerElement> TextControlInnerElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new TextControlInnerElement(shadowParent->document(), shadowParent)); +} + +void TextControlInnerElement::attachInnerElement(Node* parent, PassRefPtr<RenderStyle> style, RenderArena* arena) +{ + // When adding these elements, create the renderer & style first before adding to the DOM. + // Otherwise, the render tree will create some anonymous blocks that will mess up our layout. + + // Create the renderer with the specified style + RenderObject* renderer = createRenderer(arena, style.get()); + if (renderer) { + setRenderer(renderer); + renderer->setStyle(style); + } + + // Set these explicitly since this normally happens during an attach() + setAttached(); + setInDocument(); + + // For elements not yet in shadow DOM, add the node to the DOM normally. + if (!isShadowRoot()) { + // FIXME: This code seems very wrong. Why are we magically adding |this| to the DOM here? + // We shouldn't be calling parser API methods outside of the parser! + parent->deprecatedParserAddChild(this); + } + + // Add the renderer to the render tree + if (renderer) + parent->renderer()->addChild(renderer); +} + +void TextControlInnerElement::detach() +{ + HTMLDivElement::detach(); + // FIXME: Remove once shadow DOM uses Element::setShadowRoot(). + setShadowHost(0); +} + +// ---------------------------- + +inline TextControlInnerTextElement::TextControlInnerTextElement(Document* document, HTMLElement* shadowParent) + : TextControlInnerElement(document, shadowParent) +{ +} + +PassRefPtr<TextControlInnerTextElement> TextControlInnerTextElement::create(Document* document, HTMLElement* shadowParent) +{ + return adoptRef(new TextControlInnerTextElement(document, shadowParent)); +} + +void TextControlInnerTextElement::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() == eventNames().webkitEditableContentChangedEvent) { + Node* shadowAncestor = shadowAncestorNode(); + // A TextControlInnerTextElement can be its own shadow ancestor 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 (shadowAncestor && shadowAncestor != this) + shadowAncestor->defaultEventHandler(event); + } + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +RenderObject* TextControlInnerTextElement::createRenderer(RenderArena* arena, RenderStyle*) +{ + bool multiLine = false; + Node* shadowAncestor = shadowAncestorNode(); + if (shadowAncestor && shadowAncestor->renderer()) { + ASSERT(shadowAncestor->renderer()->isTextField() || shadowAncestor->renderer()->isTextArea()); + multiLine = shadowAncestor->renderer()->isTextArea(); + } + return new (arena) RenderTextControlInnerBlock(this, multiLine); +} + +// ---------------------------- + +inline SearchFieldResultsButtonElement::SearchFieldResultsButtonElement(Document* document) + : TextControlInnerElement(document) +{ +} + +PassRefPtr<SearchFieldResultsButtonElement> SearchFieldResultsButtonElement::create(Document* document) +{ + return adoptRef(new SearchFieldResultsButtonElement(document)); +} + +void SearchFieldResultsButtonElement::defaultEventHandler(Event* event) +{ + // On mousedown, bring up a menu, if needed + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + input->focus(); + input->select(); + RenderTextControlSingleLine* renderer = toRenderTextControlSingleLine(input->renderer()); + if (renderer->popupIsVisible()) + renderer->hidePopup(); + else if (input->maxResults() > 0) + renderer->showPopup(); + event->setDefaultHandled(); + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +// ---------------------------- + +inline SearchFieldCancelButtonElement::SearchFieldCancelButtonElement(Document* document) + : TextControlInnerElement(document) + , m_capturing(false) +{ +} + +PassRefPtr<SearchFieldCancelButtonElement> SearchFieldCancelButtonElement::create(Document* document) +{ + return adoptRef(new SearchFieldCancelButtonElement(document)); +} + +void SearchFieldCancelButtonElement::detach() +{ + if (m_capturing) { + if (Frame* frame = document()->frame()) + frame->eventHandler()->setCapturingMouseEventsNode(0); + } + TextControlInnerElement::detach(); +} + + +void SearchFieldCancelButtonElement::defaultEventHandler(Event* event) +{ + // If the element is visible, on mouseup, clear the value, and set selection + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(this); + m_capturing = true; + } + } + input->focus(); + input->select(); + event->setDefaultHandled(); + } + if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (m_capturing && renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(0); + m_capturing = false; + } + if (hovered()) { + RefPtr<HTMLInputElement> protector(input); + String oldValue = input->value(); + input->setValue(""); + if (!oldValue.isEmpty()) { + toRenderTextControl(input->renderer())->setChangedSinceLastChangeEvent(true); + input->dispatchEvent(Event::create(eventNames().inputEvent, true, false)); + } + input->onSearch(); + event->setDefaultHandled(); + } + } + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +// ---------------------------- + +inline SpinButtonElement::SpinButtonElement(HTMLElement* shadowParent) + : TextControlInnerElement(shadowParent->document(), shadowParent) + , m_capturing(false) + , m_upDownState(Indeterminate) + , m_pressStartingState(Indeterminate) + , m_repeatingTimer(this, &SpinButtonElement::repeatingTimerFired) +{ +} + +PassRefPtr<SpinButtonElement> SpinButtonElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new SpinButtonElement(shadowParent)); +} + +void SpinButtonElement::defaultEventHandler(Event* event) +{ + if (!event->isMouseEvent()) { + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); + return; + } + + RenderBox* box = renderBox(); + if (!box) { + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); + return; + } + + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (input->disabled() || input->isReadOnlyFormControl()) { + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); + return; + } + + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + IntPoint local = roundedIntPoint(box->absoluteToLocal(mouseEvent->absoluteLocation(), false, true)); + if (mouseEvent->type() == eventNames().mousedownEvent && mouseEvent->button() == LeftButton) { + if (box->borderBoxRect().contains(local)) { + RefPtr<Node> protector(input); + input->focus(); + input->select(); + input->stepUpFromRenderer(m_upDownState == Up ? 1 : -1); + event->setDefaultHandled(); + startRepeatingTimer(); + } + } else if (mouseEvent->type() == eventNames().mouseupEvent && mouseEvent->button() == LeftButton) + stopRepeatingTimer(); + else if (event->type() == eventNames().mousemoveEvent) { + if (box->borderBoxRect().contains(local)) { + if (!m_capturing) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(this); + m_capturing = true; + } + } + UpDownState oldUpDownState = m_upDownState; + m_upDownState = local.y() < box->height() / 2 ? Up : Down; + if (m_upDownState != oldUpDownState) + renderer()->repaint(); + } else { + if (m_capturing) { + stopRepeatingTimer(); + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(0); + m_capturing = false; + } + } + } + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +void SpinButtonElement::startRepeatingTimer() +{ + m_pressStartingState = m_upDownState; + ScrollbarTheme* theme = ScrollbarTheme::nativeTheme(); + m_repeatingTimer.start(theme->initialAutoscrollTimerDelay(), theme->autoscrollTimerDelay()); +} + +void SpinButtonElement::stopRepeatingTimer() +{ + m_repeatingTimer.stop(); +} + +void SpinButtonElement::repeatingTimerFired(Timer<SpinButtonElement>*) +{ + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (input->disabled() || input->isReadOnlyFormControl()) + 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 !OS(MAC_OS_X) + if (m_upDownState != m_pressStartingState) + return; +#endif + input->stepUpFromRenderer(m_upDownState == Up ? 1 : -1); +} + +void SpinButtonElement::setHovered(bool flag) +{ + if (!hovered() && flag) + m_upDownState = Indeterminate; + TextControlInnerElement::setHovered(flag); +} + + +// ---------------------------- + +#if ENABLE(INPUT_SPEECH) + +inline InputFieldSpeechButtonElement::InputFieldSpeechButtonElement(HTMLElement* shadowParent) + : TextControlInnerElement(shadowParent->document(), shadowParent) + , m_capturing(false) + , m_state(Idle) + , m_listenerId(document()->page()->speechInput()->registerListener(this)) +{ +} + +InputFieldSpeechButtonElement::~InputFieldSpeechButtonElement() +{ + SpeechInput* speech = speechInput(); + if (speech) { // Could be null when page is unloading. + if (m_state != Idle) + speech->cancelRecognition(m_listenerId); + speech->unregisterListener(m_listenerId); + } +} + +PassRefPtr<InputFieldSpeechButtonElement> InputFieldSpeechButtonElement::create(HTMLElement* shadowParent) +{ + return adoptRef(new InputFieldSpeechButtonElement(shadowParent)); +} + +void InputFieldSpeechButtonElement::defaultEventHandler(Event* event) +{ + // For privacy reasons, only allow clicks directly coming from the user. + if (!event->fromUserGesture()) { + HTMLDivElement::defaultEventHandler(event); + return; + } + + // On mouse down, select the text and set focus. + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(this); + m_capturing = true; + } + } + // The call to focus() below dispatches a focus event, and an event handler in the page might + // remove the input element from DOM. To make sure it remains valid until we finish our work + // here, we take a temporary reference. + RefPtr<HTMLInputElement> holdRef(input); + input->focus(); + input->select(); + event->setDefaultHandled(); + } + // On mouse up, release capture cleanly. + if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { + if (m_capturing && renderer() && renderer()->visibleToHitTesting()) { + if (Frame* frame = document()->frame()) { + frame->eventHandler()->setCapturingMouseEventsNode(0); + m_capturing = false; + } + } + } + + if (event->type() == eventNames().clickEvent) { + switch (m_state) { + case Idle: + if (speechInput()->startRecognition(m_listenerId, input->renderer()->absoluteBoundingBoxRect(), input->computeInheritedLanguage(), input->getAttribute(webkitgrammarAttr))) + setState(Recording); + break; + case Recording: + speechInput()->stopRecording(m_listenerId); + break; + case Recognizing: + // Nothing to do here, we will continue to wait for results. + break; + } + event->setDefaultHandled(); + } + + if (!event->defaultHandled()) + HTMLDivElement::defaultEventHandler(event); +} + +void InputFieldSpeechButtonElement::setState(SpeechInputState state) +{ + if (m_state != state) { + m_state = state; + shadowAncestorNode()->renderer()->repaint(); + } +} + +SpeechInput* InputFieldSpeechButtonElement::speechInput() +{ + return document()->page() ? document()->page()->speechInput() : 0; +} + +void InputFieldSpeechButtonElement::didCompleteRecording(int) +{ + setState(Recognizing); +} + +void InputFieldSpeechButtonElement::didCompleteRecognition(int) +{ + setState(Idle); +} + +void InputFieldSpeechButtonElement::setRecognitionResult(int, const SpeechInputResultArray& results) +{ + m_results = results; + + HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode()); + // The call to setValue() below dispatches an event, and an event handler in the page might + // remove the input element from DOM. To make sure it remains valid until we finish our work + // here, we take a temporary reference. + RefPtr<HTMLInputElement> holdRef(input); + input->setValue(results.isEmpty() ? "" : results[0]->utterance()); + input->dispatchEvent(SpeechInputEvent::create(eventNames().webkitspeechchangeEvent, results)); + renderer()->repaint(); +} + +void InputFieldSpeechButtonElement::detach() +{ + if (m_capturing) { + if (Frame* frame = document()->frame()) + frame->eventHandler()->setCapturingMouseEventsNode(0); + } + + if (m_state != Idle) + speechInput()->cancelRecognition(m_listenerId); + + TextControlInnerElement::detach(); +} + +#endif // ENABLE(INPUT_SPEECH) + +} |