TouchEventHandler.cpp [plain text]
#include "config.h"
#include "TouchEventHandler.h"
#include "DOMSupport.h"
#include "Document.h"
#include "DocumentMarkerController.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameView.h"
#include "HTMLAnchorElement.h"
#include "HTMLAreaElement.h"
#include "HTMLImageElement.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLPlugInElement.h"
#include "InputHandler.h"
#include "IntRect.h"
#include "IntSize.h"
#include "Node.h"
#include "Page.h"
#include "PlatformMouseEvent.h"
#include "PlatformTouchEvent.h"
#include "RenderLayer.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "RenderedDocumentMarker.h"
#include "SelectionHandler.h"
#include "WebPage_p.h"
#include <wtf/MathExtras.h>
using namespace WebCore;
using namespace WTF;
namespace BlackBerry {
namespace WebKit {
static bool hasMouseMoveListener(Element* element)
{
ASSERT(element);
return element->hasEventListeners(eventNames().mousemoveEvent) || element->document()->hasEventListeners(eventNames().mousemoveEvent);
}
static bool hasTouchListener(Element* element)
{
ASSERT(element);
return element->hasEventListeners(eventNames().touchstartEvent)
|| element->hasEventListeners(eventNames().touchmoveEvent)
|| element->hasEventListeners(eventNames().touchcancelEvent)
|| element->hasEventListeners(eventNames().touchendEvent);
}
static bool isRangeControlElement(Element* element)
{
ASSERT(element);
if (!element->hasTagName(HTMLNames::inputTag))
return false;
HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(element);
return inputElement->isRangeControl();
}
static bool shouldConvertTouchToMouse(Element* element)
{
if (!element)
return false;
if ((element->hasTagName(HTMLNames::objectTag) || element->hasTagName(HTMLNames::embedTag)) && static_cast<HTMLPlugInElement*>(element))
return true;
do {
element = toElement(element->shadowAncestorNode()); if (isRangeControlElement(element))
return true;
} while (element->isInShadowTree());
return hasMouseMoveListener(element) && !hasTouchListener(element);
}
TouchEventHandler::TouchEventHandler(WebPagePrivate* webpage)
: m_webPage(webpage)
, m_didCancelTouch(false)
, m_convertTouchToMouse(false)
, m_existingTouchMode(ProcessedTouchEvents)
{
}
TouchEventHandler::~TouchEventHandler()
{
}
bool TouchEventHandler::shouldSuppressMouseDownOnTouchDown() const
{
return m_lastFatFingersResult.isTextInput() || m_webPage->m_inputHandler->isInputMode() || m_webPage->m_selectionHandler->isSelectionActive();
}
void TouchEventHandler::touchEventCancel()
{
m_webPage->m_inputHandler->processPendingKeyboardVisibilityChange();
if (!shouldSuppressMouseDownOnTouchDown()) {
m_webPage->m_page->focusController()->focusedOrMainFrame()->eventHandler()->setMousePressed(false);
}
m_convertTouchToMouse = false;
m_didCancelTouch = true;
Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
if (elementUnderFatFinger && elementUnderFatFinger->renderer()) {
HitTestRequest request(HitTestRequest::FingerUp);
HitTestResult result(IntPoint::zero());
result.setInnerNode(elementUnderFatFinger);
Document* document = elementUnderFatFinger->document();
ASSERT(document);
document->renderView()->layer()->updateHoverActiveState(request, result);
document->updateStyleIfNeeded();
if (elementUnderFatFinger->renderer())
elementUnderFatFinger->renderer()->repaint();
ASSERT(!elementUnderFatFinger->hovered());
}
m_lastFatFingersResult.reset();
}
void TouchEventHandler::touchEventCancelAndClearFocusedNode()
{
touchEventCancel();
m_webPage->clearFocusNode();
}
void TouchEventHandler::touchHoldEvent()
{
if (shouldSuppressMouseDownOnTouchDown())
handleFatFingerPressed();
if (m_lastFatFingersResult.node() && m_lastFatFingersResult.node()->isLink())
m_webPage->clearFocusNode();
}
bool TouchEventHandler::handleTouchPoint(Platform::TouchPoint& point)
{
m_webPage->m_inputHandler->enableInputMode();
switch (point.m_state) {
case Platform::TouchPoint::TouchPressed:
{
m_lastFatFingersResult.reset(); m_didCancelTouch = false;
m_lastScreenPoint = point.m_screenPos;
IntPoint contentPos(m_webPage->mapFromViewportToContents(point.m_pos));
m_lastFatFingersResult = FatFingers(m_webPage, contentPos, FatFingers::ClickableElement).findBestPoint();
Element* elementUnderFatFinger = 0;
if (m_lastFatFingersResult.positionWasAdjusted() && m_lastFatFingersResult.node()) {
ASSERT(m_lastFatFingersResult.node()->isElementNode());
elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
}
Element* possibleTargetNodeForMouseMoveEvents = static_cast<Element*>(m_lastFatFingersResult.positionWasAdjusted() ? elementUnderFatFinger : m_lastFatFingersResult.node());
m_convertTouchToMouse = shouldConvertTouchToMouse(possibleTargetNodeForMouseMoveEvents);
if (elementUnderFatFinger)
drawTapHighlight();
if (m_convertTouchToMouse
&& (m_webPage->m_inputHandler->isInputMode() && !m_lastFatFingersResult.isTextInput())) {
m_webPage->m_inputHandler->setDelayKeyboardVisibilityChange(true);
handleFatFingerPressed();
} else if (!shouldSuppressMouseDownOnTouchDown())
handleFatFingerPressed();
return true;
}
case Platform::TouchPoint::TouchReleased:
{
unsigned spellLength = spellCheck(point);
m_webPage->m_inputHandler->processPendingKeyboardVisibilityChange();
if (shouldSuppressMouseDownOnTouchDown())
handleFatFingerPressed();
if (m_webPage->m_inputHandler->isInputMode())
m_webPage->m_inputHandler->notifyClientOfKeyboardVisibilityChange(true);
IntPoint adjustedPoint;
if (m_convertTouchToMouse) {
adjustedPoint = point.m_pos;
m_convertTouchToMouse = false;
} else adjustedPoint = m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition());
PlatformMouseEvent mouseEvent(adjustedPoint, m_lastScreenPoint, PlatformEvent::MouseReleased, 1, LeftButton, TouchScreen);
m_webPage->handleMouseEvent(mouseEvent);
m_lastFatFingersResult.reset(); if (spellLength) {
unsigned end = m_webPage->m_inputHandler->caretPosition();
unsigned start = end - spellLength;
m_webPage->m_client->requestSpellingSuggestionsForString(start, end);
}
return true;
}
case Platform::TouchPoint::TouchMoved:
if (m_convertTouchToMouse) {
PlatformMouseEvent mouseEvent(point.m_pos, m_lastScreenPoint, PlatformEvent::MouseMoved, 1, LeftButton, TouchScreen);
m_lastScreenPoint = point.m_screenPos;
if (!m_webPage->handleMouseEvent(mouseEvent)) {
m_convertTouchToMouse = false;
return false;
}
return true;
}
break;
default:
break;
}
return false;
}
unsigned TouchEventHandler::spellCheck(Platform::TouchPoint& touchPoint)
{
Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
if (!m_lastFatFingersResult.isTextInput() || !elementUnderFatFinger)
return 0;
LayoutPoint contentPos(m_webPage->mapFromViewportToContents(touchPoint.m_pos));
contentPos = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), m_webPage->focusedOrMainFrame(), contentPos);
Document* document = elementUnderFatFinger->document();
ASSERT(document);
RenderedDocumentMarker* marker = document->markers()->renderedMarkerContainingPoint(contentPos, DocumentMarker::Spelling);
if (!marker)
return 0;
IntRect rect = marker->renderedRect();
LayoutPoint newContentPos = LayoutPoint(rect.x() + rect.width(), rect.y() + rect.height() / 2);
Frame* frame = m_webPage->focusedOrMainFrame();
if (frame != m_webPage->mainFrame())
newContentPos = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(newContentPos));
m_lastFatFingersResult.m_adjustedPosition = newContentPos;
m_lastFatFingersResult.m_positionWasAdjusted = true;
return marker->endOffset() - marker->startOffset();
}
void TouchEventHandler::handleFatFingerPressed()
{
if (!m_didCancelTouch) {
PlatformMouseEvent mouseMoveEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, PlatformEvent::MouseMoved, 0, LeftButton, TouchScreen);
m_webPage->handleMouseEvent(mouseMoveEvent);
PlatformMouseEvent mousePressedEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, PlatformEvent::MousePressed, 1, LeftButton, TouchScreen);
m_webPage->handleMouseEvent(mousePressedEvent);
}
}
static Element* elementForTapHighlight(Element* elementUnderFatFinger)
{
if (elementUnderFatFinger->renderer()) {
Color tapHighlightColor = elementUnderFatFinger->renderStyle()->tapHighlightColor();
if (tapHighlightColor != RenderTheme::defaultTheme()->platformTapHighlightColor())
return elementUnderFatFinger;
}
bool isArea = elementUnderFatFinger->hasTagName(HTMLNames::areaTag);
Node* linkNode = elementUnderFatFinger->enclosingLinkEventParentOrSelf();
if (!linkNode || !linkNode->isHTMLElement() || (!linkNode->renderer() && !isArea))
return 0;
ASSERT(linkNode->isLink());
Element* highlightCandidateElement = static_cast<Element*>(linkNode);
if (!isArea)
return highlightCandidateElement;
HTMLAreaElement* area = static_cast<HTMLAreaElement*>(highlightCandidateElement);
HTMLImageElement* image = area->imageElement();
if (image && image->renderer())
return image;
return 0;
}
void TouchEventHandler::drawTapHighlight()
{
Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
if (!elementUnderFatFinger)
return;
Element* element = elementForTapHighlight(elementUnderFatFinger);
if (!element)
return;
RenderObject* renderer = element->renderer();
ASSERT(renderer);
Frame* elementFrame = element->document()->frame();
ASSERT(elementFrame);
FrameView* elementFrameView = elementFrame->view();
if (!elementFrameView)
return;
RenderLayer* layer = m_webPage->enclosingFixedPositionedAncestorOrSelfIfFixedPositioned(renderer->enclosingLayer());
bool shouldHideTapHighlightRightAfterScrolling = !layer->renderer()->isRenderView();
shouldHideTapHighlightRightAfterScrolling |= !!m_webPage->m_inRegionScrollStartingNode.get();
IntPoint framePos(m_webPage->frameOffset(elementFrame));
IntRect absoluteRect = renderer->absoluteClippedOverflowRect();
absoluteRect.move(framePos.x(), framePos.y());
IntRect clippingRect;
if (elementFrame == m_webPage->mainFrame())
clippingRect = IntRect(IntPoint(0, 0), elementFrameView->contentsSize());
else
clippingRect = m_webPage->mainFrame()->view()->windowToContents(m_webPage->getRecursiveVisibleWindowRect(elementFrameView, true ));
clippingRect = intersection(absoluteRect, clippingRect);
Vector<FloatQuad> focusRingQuads;
renderer->absoluteFocusRingQuads(focusRingQuads);
Platform::IntRectRegion region;
for (size_t i = 0; i < focusRingQuads.size(); ++i) {
IntRect rect = focusRingQuads[i].enclosingBoundingBox();
rect.move(framePos.x(), framePos.y());
IntRect clippedRect = intersection(clippingRect, rect);
clippedRect.inflate(2);
region = unionRegions(region, Platform::IntRect(clippedRect));
}
Color highlightColor = element->renderStyle()->tapHighlightColor();
m_webPage->m_client->drawTapHighlight(region,
highlightColor.red(),
highlightColor.green(),
highlightColor.blue(),
highlightColor.alpha(),
shouldHideTapHighlightRightAfterScrolling);
}
}
}