[BlackBerry] Tie up the scrolling machinery to the graphics tree when applicable...
[WebKit-https.git] / Source / WebKit / blackberry / WebKitSupport / TouchEventHandler.cpp
1 /*
2  * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "TouchEventHandler.h"
21
22 #include "DOMSupport.h"
23 #include "Document.h"
24 #include "DocumentMarkerController.h"
25 #include "FocusController.h"
26 #include "Frame.h"
27 #include "FrameView.h"
28 #include "HTMLAnchorElement.h"
29 #include "HTMLAreaElement.h"
30 #include "HTMLImageElement.h"
31 #include "HTMLInputElement.h"
32 #include "HTMLNames.h"
33 #include "HTMLPlugInElement.h"
34 #include "InRegionScroller_p.h"
35 #include "InputHandler.h"
36 #include "IntRect.h"
37 #include "IntSize.h"
38 #include "Node.h"
39 #include "Page.h"
40 #include "PlatformMouseEvent.h"
41 #include "PlatformTouchEvent.h"
42 #include "RenderLayer.h"
43 #include "RenderTheme.h"
44 #include "RenderView.h"
45 #include "RenderedDocumentMarker.h"
46 #include "SelectionHandler.h"
47 #include "WebPage_p.h"
48 #include "WebTapHighlight.h"
49
50 #include <wtf/MathExtras.h>
51
52 using namespace WebCore;
53 using namespace WTF;
54
55 namespace BlackBerry {
56 namespace WebKit {
57
58 static bool hasMouseMoveListener(Element* element)
59 {
60     ASSERT(element);
61     return element->hasEventListeners(eventNames().mousemoveEvent) || element->document()->hasEventListeners(eventNames().mousemoveEvent);
62 }
63
64 static bool hasTouchListener(Element* element)
65 {
66     ASSERT(element);
67     return element->hasEventListeners(eventNames().touchstartEvent)
68         || element->hasEventListeners(eventNames().touchmoveEvent)
69         || element->hasEventListeners(eventNames().touchcancelEvent)
70         || element->hasEventListeners(eventNames().touchendEvent);
71 }
72
73 static bool isRangeControlElement(Element* element)
74 {
75     ASSERT(element);
76     if (!element->hasTagName(HTMLNames::inputTag))
77         return false;
78
79     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(element);
80     return inputElement->isRangeControl();
81 }
82
83 static bool shouldConvertTouchToMouse(Element* element)
84 {
85     if (!element)
86         return false;
87
88     if ((element->hasTagName(HTMLNames::objectTag) || element->hasTagName(HTMLNames::embedTag)) && static_cast<HTMLPlugInElement*>(element))
89         return true;
90
91     // Input Range element is a special case that requires natural mouse events
92     // in order to allow dragging of the slider handle.
93     // Input Range element might appear in the webpage, or it might appear in the shadow tree,
94     // like the timeline and volume media controls all use Input Range.
95     // won't operate on the shadow node of other element type, because the webpages
96     // aren't able to attach listeners to shadow content.
97     do {
98         if (isRangeControlElement(element))
99             return true;
100         element = toElement(element->shadowAncestorNode()); // If an element is not in shadow tree, shadowAncestorNode returns itself.
101     } while (element->isInShadowTree());
102
103     // Check if the element has a mouse listener and no touch listener. If so,
104     // the field will require touch events be converted to mouse events to function properly.
105     return hasMouseMoveListener(element) && !hasTouchListener(element);
106
107 }
108
109 TouchEventHandler::TouchEventHandler(WebPagePrivate* webpage)
110     : m_webPage(webpage)
111     , m_didCancelTouch(false)
112     , m_convertTouchToMouse(false)
113     , m_existingTouchMode(ProcessedTouchEvents)
114 {
115 }
116
117 TouchEventHandler::~TouchEventHandler()
118 {
119 }
120
121 bool TouchEventHandler::shouldSuppressMouseDownOnTouchDown() const
122 {
123     return m_lastFatFingersResult.isTextInput() || m_webPage->m_inputHandler->isInputMode() || m_webPage->m_selectionHandler->isSelectionActive();
124 }
125
126 void TouchEventHandler::touchEventCancel()
127 {
128     m_webPage->m_inputHandler->processPendingKeyboardVisibilityChange();
129
130     // Input elements delay mouse down and do not need to be released on touch cancel.
131     if (!shouldSuppressMouseDownOnTouchDown())
132         m_webPage->m_page->focusController()->focusedOrMainFrame()->eventHandler()->setMousePressed(false);
133
134     m_convertTouchToMouse = m_webPage->m_touchEventMode == PureTouchEventsWithMouseConversion;
135     m_didCancelTouch = true;
136
137     // If we cancel a single touch event, we need to also clean up any hover
138     // state we get into by synthetically moving the mouse to the m_fingerPoint.
139     Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
140     if (elementUnderFatFinger && elementUnderFatFinger->renderer()) {
141
142         HitTestRequest request(HitTestRequest::FingerUp);
143         // The HitTestResult point is not actually needed.
144         HitTestResult result(IntPoint::zero());
145         result.setInnerNode(elementUnderFatFinger);
146
147         Document* document = elementUnderFatFinger->document();
148         ASSERT(document);
149         document->renderView()->layer()->updateHoverActiveState(request, result);
150         document->updateStyleIfNeeded();
151         // Updating the document style may destroy the renderer.
152         if (elementUnderFatFinger->renderer())
153             elementUnderFatFinger->renderer()->repaint();
154         ASSERT(!elementUnderFatFinger->hovered());
155     }
156
157     m_lastFatFingersResult.reset();
158 }
159
160 void TouchEventHandler::touchHoldEvent()
161 {
162     // This is a hack for our hack that converts the touch pressed event that we've delayed because the user has focused a input field
163     // to the page as a mouse pressed event.
164     if (shouldSuppressMouseDownOnTouchDown())
165         handleFatFingerPressed();
166
167     // Clear the focus ring indication if tap-and-hold'ing on a link.
168     if (m_lastFatFingersResult.node() && m_lastFatFingersResult.node()->isLink())
169         m_webPage->clearFocusNode();
170 }
171
172 bool TouchEventHandler::handleTouchPoint(Platform::TouchPoint& point, bool useFatFingers)
173 {
174     // Enable input mode on any touch event.
175     m_webPage->m_inputHandler->setInputModeEnabled();
176     bool pureWithMouseConversion = m_webPage->m_touchEventMode == PureTouchEventsWithMouseConversion;
177
178     switch (point.m_state) {
179     case Platform::TouchPoint::TouchPressed:
180         {
181             // FIXME: bypass FatFingers if useFatFingers is false
182             m_lastFatFingersResult.reset(); // Theoretically this shouldn't be required. Keep it just in case states get mangled.
183             m_didCancelTouch = false;
184             m_lastScreenPoint = point.m_screenPos;
185
186             IntPoint contentPos(m_webPage->mapFromViewportToContents(point.m_pos));
187
188             m_lastFatFingersResult = FatFingers(m_webPage, contentPos, FatFingers::ClickableElement).findBestPoint();
189
190             Element* elementUnderFatFinger = 0;
191             if (m_lastFatFingersResult.positionWasAdjusted() && m_lastFatFingersResult.node()) {
192                 ASSERT(m_lastFatFingersResult.node()->isElementNode());
193                 elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
194             }
195
196             // Set or reset the touch mode.
197             Element* possibleTargetNodeForMouseMoveEvents = static_cast<Element*>(m_lastFatFingersResult.positionWasAdjusted() ? elementUnderFatFinger : m_lastFatFingersResult.node());
198             m_convertTouchToMouse = pureWithMouseConversion ? true : shouldConvertTouchToMouse(possibleTargetNodeForMouseMoveEvents);
199
200             if (elementUnderFatFinger)
201                 drawTapHighlight();
202
203             // Lets be conservative here: since we have problems on major website having
204             // mousemove listener for no good reason (e.g. google.com, desktop edition),
205             // let only delay client notifications when there is not input text node involved.
206             if (m_convertTouchToMouse
207                 && (m_webPage->m_inputHandler->isInputMode() && !m_lastFatFingersResult.isTextInput())) {
208                 m_webPage->m_inputHandler->setDelayKeyboardVisibilityChange(true);
209                 handleFatFingerPressed();
210             } else if (!shouldSuppressMouseDownOnTouchDown())
211                 handleFatFingerPressed();
212
213             return true;
214         }
215     case Platform::TouchPoint::TouchReleased:
216         {
217             unsigned spellLength = spellCheck(point);
218             // Apply any suppressed changes. This does not eliminate the need
219             // for the show after the handling of fat finger pressed as it may
220             // have triggered a state change.
221             m_webPage->m_inputHandler->processPendingKeyboardVisibilityChange();
222
223             if (shouldSuppressMouseDownOnTouchDown())
224                 handleFatFingerPressed();
225
226             // The rebase has eliminated a necessary event when the mouse does not
227             // trigger an actual selection change preventing re-showing of the
228             // keyboard. If input mode is active, call showVirtualKeyboard which
229             // will update the state and display keyboard if needed.
230             if (m_webPage->m_inputHandler->isInputMode())
231                 m_webPage->m_inputHandler->notifyClientOfKeyboardVisibilityChange(true);
232
233             IntPoint adjustedPoint;
234             if (m_convertTouchToMouse || !useFatFingers) {
235                 adjustedPoint = point.m_pos;
236                 m_convertTouchToMouse = pureWithMouseConversion;
237             } else // Fat finger point in viewport coordinates.
238                 adjustedPoint = m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition());
239
240             PlatformMouseEvent mouseEvent(adjustedPoint, m_lastScreenPoint, PlatformEvent::MouseReleased, 1, LeftButton, TouchScreen);
241             m_webPage->handleMouseEvent(mouseEvent);
242             m_lastFatFingersResult.reset(); // Reset the fat finger result as its no longer valid when a user's finger is not on the screen.
243             if (spellLength) {
244                 unsigned end = m_webPage->m_inputHandler->caretPosition();
245                 unsigned start = end - spellLength;
246                 m_webPage->m_client->requestSpellingSuggestionsForString(start, end);
247             }
248             return true;
249         }
250     case Platform::TouchPoint::TouchMoved:
251         if (m_convertTouchToMouse) {
252             PlatformMouseEvent mouseEvent(point.m_pos, m_lastScreenPoint, PlatformEvent::MouseMoved, 1, LeftButton, TouchScreen);
253             m_lastScreenPoint = point.m_screenPos;
254             if (!m_webPage->handleMouseEvent(mouseEvent)) {
255                 m_convertTouchToMouse = pureWithMouseConversion;
256                 return false;
257             }
258             return true;
259         }
260         break;
261     default:
262         break;
263     }
264     return false;
265 }
266
267 unsigned TouchEventHandler::spellCheck(Platform::TouchPoint& touchPoint)
268 {
269     Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
270     if (!m_lastFatFingersResult.isTextInput() || !elementUnderFatFinger)
271         return 0;
272
273     LayoutPoint contentPos(m_webPage->mapFromViewportToContents(touchPoint.m_pos));
274     contentPos = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), m_webPage->focusedOrMainFrame(), contentPos);
275
276     Document* document = elementUnderFatFinger->document();
277     ASSERT(document);
278     RenderedDocumentMarker* marker = document->markers()->renderedMarkerContainingPoint(contentPos, DocumentMarker::Spelling);
279     if (!marker)
280         return 0;
281
282     IntRect rect = marker->renderedRect();
283     LayoutPoint newContentPos = LayoutPoint(rect.x() + rect.width(), rect.y() + rect.height() / 2);
284     Frame* frame = m_webPage->focusedOrMainFrame();
285     if (frame != m_webPage->mainFrame())
286         newContentPos = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(newContentPos));
287     m_lastFatFingersResult.m_adjustedPosition = newContentPos;
288     m_lastFatFingersResult.m_positionWasAdjusted = true;
289     return marker->endOffset() - marker->startOffset();
290 }
291
292 void TouchEventHandler::handleFatFingerPressed()
293 {
294     if (!m_didCancelTouch) {
295         // First update the mouse position with a MouseMoved event.
296         PlatformMouseEvent mouseMoveEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, PlatformEvent::MouseMoved, 0, LeftButton, TouchScreen);
297         m_webPage->handleMouseEvent(mouseMoveEvent);
298
299         // Then send the MousePressed event.
300         PlatformMouseEvent mousePressedEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, PlatformEvent::MousePressed, 1, LeftButton, TouchScreen);
301         m_webPage->handleMouseEvent(mousePressedEvent);
302     }
303 }
304
305 // This method filters what element will get tap-highlight'ed or not. To start with,
306 // we are going to highlight links (anchors with a valid href element), and elements
307 // whose tap highlight color value is different than the default value.
308 static Element* elementForTapHighlight(Element* elementUnderFatFinger)
309 {
310     // Do not bail out right way here if there element does not have a renderer.
311     // It is the casefor <map> (descendent of <area>) elements. The associated <image>
312     // element actually has the renderer.
313     if (elementUnderFatFinger->renderer()) {
314         Color tapHighlightColor = elementUnderFatFinger->renderStyle()->tapHighlightColor();
315         if (tapHighlightColor != RenderTheme::defaultTheme()->platformTapHighlightColor())
316             return elementUnderFatFinger;
317     }
318
319     bool isArea = elementUnderFatFinger->hasTagName(HTMLNames::areaTag);
320     Node* linkNode = elementUnderFatFinger->enclosingLinkEventParentOrSelf();
321     if (!linkNode || !linkNode->isHTMLElement() || (!linkNode->renderer() && !isArea))
322         return 0;
323
324     ASSERT(linkNode->isLink());
325
326     // FatFingers class selector ensure only anchor with valid href attr value get here.
327     // It includes empty hrefs.
328     Element* highlightCandidateElement = static_cast<Element*>(linkNode);
329
330     if (!isArea)
331         return highlightCandidateElement;
332
333     HTMLAreaElement* area = static_cast<HTMLAreaElement*>(highlightCandidateElement);
334     HTMLImageElement* image = area->imageElement();
335     if (image && image->renderer())
336         return image;
337
338     return 0;
339 }
340
341 void TouchEventHandler::drawTapHighlight()
342 {
343     Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
344     if (!elementUnderFatFinger)
345         return;
346
347     Element* element = elementForTapHighlight(elementUnderFatFinger);
348     if (!element)
349         return;
350
351     // Get the element bounding rect in transformed coordinates so we can extract
352     // the focus ring relative position each rect.
353     RenderObject* renderer = element->renderer();
354     ASSERT(renderer);
355
356     Frame* elementFrame = element->document()->frame();
357     ASSERT(elementFrame);
358
359     FrameView* elementFrameView = elementFrame->view();
360     if (!elementFrameView)
361         return;
362
363     // Tell the client if the element is either in a scrollable container or in a fixed positioned container.
364     // On the client side, this info is being used to hide the tap highlight window on scroll.
365     RenderLayer* layer = m_webPage->enclosingFixedPositionedAncestorOrSelfIfFixedPositioned(renderer->enclosingLayer());
366     bool shouldHideTapHighlightRightAfterScrolling = !layer->renderer()->isRenderView();
367     shouldHideTapHighlightRightAfterScrolling |= !!m_webPage->m_inRegionScroller->d->node();
368
369     IntPoint framePos(m_webPage->frameOffset(elementFrame));
370
371     // FIXME: We can get more precise on the <map> case by calculating the rect with HTMLAreaElement::computeRect().
372     IntRect absoluteRect = renderer->absoluteClippedOverflowRect();
373     absoluteRect.move(framePos.x(), framePos.y());
374
375     IntRect clippingRect;
376     if (elementFrame == m_webPage->mainFrame())
377         clippingRect = IntRect(IntPoint(0, 0), elementFrameView->contentsSize());
378     else
379         clippingRect = m_webPage->mainFrame()->view()->windowToContents(m_webPage->getRecursiveVisibleWindowRect(elementFrameView, true /*noClipToMainFrame*/));
380     clippingRect = intersection(absoluteRect, clippingRect);
381
382     Vector<FloatQuad> focusRingQuads;
383     renderer->absoluteFocusRingQuads(focusRingQuads);
384
385     Platform::IntRectRegion region;
386     for (size_t i = 0; i < focusRingQuads.size(); ++i) {
387         IntRect rect = focusRingQuads[i].enclosingBoundingBox();
388         rect.move(framePos.x(), framePos.y());
389         IntRect clippedRect = intersection(clippingRect, rect);
390         clippedRect.inflate(2);
391         region = unionRegions(region, Platform::IntRect(clippedRect));
392     }
393
394     Color highlightColor = element->renderStyle()->tapHighlightColor();
395
396     m_webPage->m_tapHighlight->draw(region,
397                                     highlightColor.red(), highlightColor.green(), highlightColor.blue(), highlightColor.alpha(),
398                                     shouldHideTapHighlightRightAfterScrolling);
399 }
400
401 }
402 }