Changeset 71393 in webkit


Ignore:
Timestamp:
Nov 4, 2010 10:08:02 PM (13 years ago)
Author:
tonikitoo@webkit.org
Message:

2010-11-03 Antonio Gomes <tonikitoo@webkit.org>, Andre Pedralho <andre.pedralho@gmail.com>

Reviewed by Kenneth Rohde Christiansen and Antti Koivisto.

[Qt] Making effective use of nodesFromRect.
https://bugs.webkit.org/show_bug.cgi?id=44089

Patch adds a helper class (named TouchAdjuster) to improve tap actions
on mobile touch devices. TouchAdjuster makes use of the newly added rect
based hit test extension through the Document::nodesFromRect method.
Given a rectangle as input, nodesFromRect returns a z-index ordered list
of nodes whose boundaries intersect the rectangle.

Basically the TouchAdjuster class intercepts the QGraphicsSceneMouseEvent
passed to both QWebPagePrivate::mouse{Press,Release}Event methods before
they are passed down to WebCore. The goal is to infer the target click position.
For that, a rectangle is built up using the event position as a center point and
expanding it based on the values and directions set in the Platform Plugin
QWebTouchModifier::hitTestPadding.

TouchAdjuster iterates over the list of nodes returned by nodesFromRect and
picks the clickable one that has the largest intersection area with the hit
test rectangle. The target position is then the center point of this intersection
area.

In case of no clickable node intersects the hit test area, the click position
is not altered.

TouchAdjuster is *only* working for QGraphicsWebView based views.

  • Api/qwebpage.cpp: (QWebPagePrivate::mousePressEvent): (QWebPagePrivate::mouseReleaseEvent): (QWebPagePrivate::adjustPointForClicking): (QWebPagePrivate::TouchAdjuster::TouchAdjuster): (QWebPagePrivate::TouchAdjuster::findCandidatePointForTouch): (isClickableElement): (hasMouseListener): (isValidFrameOwner): (nodeToElement):
  • Api/qwebpage_p.h:
Location:
trunk/WebKit/qt
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebKit/qt/Api/qwebpage.cpp

    r70999 r71393  
    3232#include "qwebinspector_p.h"
    3333#include "qwebsettings.h"
     34#include "qwebkitplatformplugin.h"
    3435#include "qwebkitversion.h"
    3536
     37#include "CSSComputedStyleDeclaration.h"
     38#include "CSSParser.h"
    3639#include "ApplicationCacheStorage.h"
    3740#include "BackForwardListImpl.h"
     
    3942#include "Chrome.h"
    4043#include "ChromeClientQt.h"
     44#include "ClientRect.h"
    4145#include "ContextMenu.h"
    4246#include "ContextMenuClientQt.h"
     
    6165#include "GeolocationPermissionClientQt.h"
    6266#include "HTMLFormElement.h"
     67#include "HTMLFrameOwnerElement.h"
    6368#include "HTMLInputElement.h"
    6469#include "HTMLNames.h"
     
    7580#include "NavigationAction.h"
    7681#include "NetworkingContext.h"
     82#include "NodeList.h"
    7783#include "NotificationPresenterClientQt.h"
     84#include "NotImplemented.h"
    7885#include "Page.h"
    7986#include "PageClientQt.h"
     
    8794#include "PluginPackage.h"
    8895#include "ProgressTracker.h"
     96#include "QtPlatformPlugin.h"
    8997#include "RefPtr.h"
    9098#include "RenderTextControl.h"
     
    658666
    659667    bool accepted = false;
     668    adjustPointForClicking(ev);
    660669    PlatformMouseEvent mev(ev, 1);
    661670    // ignore the event if we can't map Qt's mouse buttons to WebCore::MouseButton
     
    737746
    738747    bool accepted = false;
     748    adjustPointForClicking(ev);
    739749    PlatformMouseEvent mev(ev, 0);
    740750    // ignore the event if we can't map Qt's mouse buttons to WebCore::MouseButton
     
    12621272
    12631273    return frame->eventHandler()->scrollRecursively(direction, granularity);
     1274}
     1275
     1276void QWebPagePrivate::adjustPointForClicking(QMouseEvent*)
     1277{
     1278    notImplemented();
     1279}
     1280
     1281void QWebPagePrivate::adjustPointForClicking(QGraphicsSceneMouseEvent* ev)
     1282{
     1283    QtPlatformPlugin platformPlugin;
     1284    QWebTouchModifier* touchModifier = platformPlugin.createTouchModifier();
     1285    if (!touchModifier)
     1286        return;
     1287
     1288    unsigned topPadding = touchModifier->hitTestPaddingForTouch(QWebTouchModifier::Up);
     1289    unsigned rightPadding = touchModifier->hitTestPaddingForTouch(QWebTouchModifier::Right);
     1290    unsigned bottomPadding = touchModifier->hitTestPaddingForTouch(QWebTouchModifier::Down);
     1291    unsigned leftPadding = touchModifier->hitTestPaddingForTouch(QWebTouchModifier::Left);
     1292
     1293    delete touchModifier;
     1294    touchModifier = 0;
     1295
     1296    if (!topPadding && !rightPadding && !bottomPadding && !leftPadding)
     1297        return;
     1298
     1299    Document* startingDocument = page->mainFrame()->document();
     1300    if (!startingDocument)
     1301        return;
     1302
     1303    IntPoint originalPoint(QPointF(ev->pos()).toPoint());
     1304    TouchAdjuster touchAdjuster(topPadding, rightPadding, bottomPadding, leftPadding);
     1305    IntPoint adjustedPoint = touchAdjuster.findCandidatePointForTouch(originalPoint, startingDocument);
     1306    if (adjustedPoint == IntPoint::zero())
     1307        return;
     1308
     1309    ev->setPos(QPointF(adjustedPoint));
    12641310}
    12651311
     
    14281474#endif
    14291475    return 0;
     1476}
     1477
     1478static bool hasMouseListener(Element* element)
     1479{
     1480    ASSERT(element);
     1481    return element->hasEventListeners(eventNames().clickEvent)
     1482        || element->hasEventListeners(eventNames().mousedownEvent)
     1483        || element->hasEventListeners(eventNames().mouseupEvent);
     1484}
     1485
     1486static bool isClickableElement(Element* element, RefPtr<NodeList> list)
     1487{
     1488    ASSERT(element);
     1489    bool isClickable = hasMouseListener(element);
     1490    if (!isClickable && list) {
     1491        Element* parent = element->parentElement();
     1492        unsigned count = list->length();
     1493        for (unsigned i = 0; i < count && parent; i++) {
     1494            if (list->item(i) != parent)
     1495                continue;
     1496
     1497            isClickable = hasMouseListener(parent);
     1498            if (isClickable)
     1499                break;
     1500
     1501            parent = parent->parentElement();
     1502        }
     1503    }
     1504
     1505    ExceptionCode ec = 0;
     1506    return isClickable
     1507        || element->webkitMatchesSelector("a,*:link,*:visited,*[role=button],button,input,select,label", ec)
     1508        || computedStyle(element)->getPropertyValue(cssPropertyID("cursor")) == "pointer";
     1509}
     1510
     1511static bool isValidFrameOwner(Element* element)
     1512{
     1513    ASSERT(element);
     1514    return element->isFrameOwnerElement() && static_cast<HTMLFrameOwnerElement*>(element)->contentFrame();
     1515}
     1516
     1517static Element* nodeToElement(Node* node)
     1518{
     1519    if (node && node->isElementNode())
     1520        return static_cast<Element*>(node);
     1521    return 0;
     1522}
     1523
     1524QWebPagePrivate::TouchAdjuster::TouchAdjuster(unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
     1525    : m_topPadding(topPadding)
     1526    , m_rightPadding(rightPadding)
     1527    , m_bottomPadding(bottomPadding)
     1528    , m_leftPadding(leftPadding)
     1529{
     1530}
     1531
     1532IntPoint QWebPagePrivate::TouchAdjuster::findCandidatePointForTouch(const IntPoint& touchPoint, Document* document) const
     1533{
     1534    if (!document)
     1535        return IntPoint();
     1536
     1537    int x = touchPoint.x();
     1538    int y = touchPoint.y();
     1539
     1540    RefPtr<NodeList> intersectedNodes = document->nodesFromRect(x, y, m_topPadding, m_rightPadding, m_bottomPadding, m_leftPadding, false);
     1541    if (!intersectedNodes)
     1542        return IntPoint();
     1543
     1544    Element* closestClickableElement = 0;
     1545    IntRect largestIntersectionRect;
     1546    FrameView* view = document->frame()->view();
     1547
     1548    // Touch rect in contents coordinates.
     1549    IntRect touchRect(HitTestResult::rectForPoint(view->windowToContents(IntPoint(x, y)), m_topPadding, m_rightPadding, m_bottomPadding, m_leftPadding));
     1550
     1551    // Iterate over the list of nodes hit looking for the one whose bounding area
     1552    // has largest intersection with the touch area (point + padding).
     1553    for (unsigned i = 0; i < intersectedNodes->length(); i++) {
     1554        Node* currentNode = intersectedNodes->item(i);
     1555
     1556        Element* currentElement = nodeToElement(currentNode);
     1557        if (!currentElement || (!isClickableElement(currentElement, 0) && !isValidFrameOwner(currentElement)))
     1558            continue;
     1559
     1560        IntRect currentElementBoundingRect = currentElement->getRect();
     1561        currentElementBoundingRect.intersect(touchRect);
     1562
     1563        if (currentElementBoundingRect.isEmpty())
     1564            continue;
     1565
     1566        int currentIntersectionRectArea = currentElementBoundingRect.width() * currentElementBoundingRect.height();
     1567        int largestIntersectionRectArea = largestIntersectionRect.width() * largestIntersectionRect.height();
     1568        if (currentIntersectionRectArea > largestIntersectionRectArea) {
     1569            closestClickableElement = currentElement;
     1570            largestIntersectionRect = currentElementBoundingRect;
     1571        }
     1572    }
     1573
     1574    if (largestIntersectionRect.isEmpty())
     1575        return IntPoint();
     1576
     1577    // Handle the case when user taps a inner frame. It is done in three steps:
     1578    // 1) Transform the original touch point to the inner document coordinates;
     1579    // 1) Call nodesFromRect for the inner document in case;
     1580    // 3) Re-add the inner frame offset (location) before passing the new clicking
     1581    //    position to WebCore.
     1582    if (closestClickableElement->isFrameOwnerElement()) {
     1583        // Adjust client coordinates' origin to be top left of inner frame viewport.
     1584        PassRefPtr<ClientRect> rect = closestClickableElement->getBoundingClientRect();
     1585        IntPoint newTouchPoint = touchPoint;
     1586        IntSize offset =  IntSize(rect->left(), rect->top());
     1587        newTouchPoint -= offset;
     1588
     1589        HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(closestClickableElement);
     1590        Document* childDocument = owner->contentFrame()->document();
     1591        return findCandidatePointForTouch(newTouchPoint, childDocument);
     1592    }
     1593    return view->contentsToWindow(largestIntersectionRect).center();
    14301594}
    14311595
  • trunk/WebKit/qt/Api/qwebpage_p.h

    r69331 r71393  
    3232#include "qwebframe.h"
    3333
     34#include "IntPoint.h"
    3435#include "KURL.h"
    3536#include "PlatformString.h"
     
    4445    class ContextMenuItem;
    4546    class ContextMenu;
     47    class Document;
    4648    class EditorClientQt;
    4749    class Element;
    4850    class InspectorController;
     51    class IntRect;
    4952    class Node;
     53    class NodeList;
    5054    class Page;
    5155    class Frame;
     
    127131    bool touchEvent(QTouchEvent*);
    128132
     133    class TouchAdjuster {
     134    public:
     135        TouchAdjuster(unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding);
     136
     137        WebCore::IntPoint findCandidatePointForTouch(const WebCore::IntPoint&, WebCore::Document*) const;
     138
     139    private:
     140        unsigned m_topPadding;
     141        unsigned m_rightPadding;
     142        unsigned m_bottomPadding;
     143        unsigned m_leftPadding;
     144    };
     145
     146    void adjustPointForClicking(QMouseEvent*);
     147    void adjustPointForClicking(QGraphicsSceneMouseEvent*);
     148
    129149    void setInspector(QWebInspector*);
    130150    QWebInspector* getOrCreateInspector();
  • trunk/WebKit/qt/ChangeLog

    r71388 r71393  
     12010-11-03  Antonio Gomes  <tonikitoo@webkit.org>, Andre Pedralho  <andre.pedralho@gmail.com>
     2
     3        Reviewed by Kenneth Rohde Christiansen and Antti Koivisto.
     4
     5        [Qt] Making effective use of nodesFromRect.
     6        https://bugs.webkit.org/show_bug.cgi?id=44089
     7
     8        Patch adds a helper class (named TouchAdjuster) to improve tap actions
     9        on mobile touch devices. TouchAdjuster makes use of the newly added rect
     10        based hit test extension through the Document::nodesFromRect method.
     11        Given a rectangle as input, nodesFromRect returns a z-index ordered list
     12        of nodes whose boundaries intersect the rectangle.
     13
     14        Basically the TouchAdjuster class intercepts the QGraphicsSceneMouseEvent
     15        passed to both QWebPagePrivate::mouse{Press,Release}Event methods before
     16        they are passed down to WebCore. The goal is to infer the target click position.
     17        For that, a rectangle is built up using the event position as a center point and
     18        expanding it based on the values and directions set in the Platform Plugin
     19        QWebTouchModifier::hitTestPadding.
     20
     21        TouchAdjuster iterates over the list of nodes returned by nodesFromRect and
     22        picks the clickable one that has the largest intersection area with the hit
     23        test rectangle. The target position is then the center point of this intersection
     24        area.
     25
     26        In case of no clickable node intersects the hit test area, the click position
     27        is not altered.
     28
     29        TouchAdjuster is *only* working for QGraphicsWebView based views.
     30
     31        * Api/qwebpage.cpp:
     32        (QWebPagePrivate::mousePressEvent):
     33        (QWebPagePrivate::mouseReleaseEvent):
     34        (QWebPagePrivate::adjustPointForClicking):
     35        (QWebPagePrivate::TouchAdjuster::TouchAdjuster):
     36        (QWebPagePrivate::TouchAdjuster::findCandidatePointForTouch):
     37        (isClickableElement):
     38        (hasMouseListener):
     39        (isValidFrameOwner):
     40        (nodeToElement):
     41        * Api/qwebpage_p.h:
     42
    1432010-10-20  Chang Shu  <chang.shu@nokia.com>, Antonio Gomes <tonikitoo@webkit.org>
    244
Note: See TracChangeset for help on using the changeset viewer.