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