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