1/*
2 * Copyright 2012, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#define LOG_TAG "AndroidHitTestResult"
27
28#include "config.h"
29#include "AndroidHitTestResult.h"
30
31#include "content/address_detector.h"
32#include "content/PhoneEmailDetector.h"
33#include "android/WebHitTestInfo.h"
34#include "Document.h"
35#include "Element.h"
36#include "Frame.h"
37#include "HitTestResult.h"
38#include "KURL.h"
39#include "LayerAndroid.h"
40#include "PlatformString.h"
41#include "Range.h"
42#include "RenderLayer.h"
43#include "RenderLayerBacking.h"
44#include "RenderObject.h"
45#include "WebCoreJni.h"
46#include "WebViewCore.h"
47
48#include <cutils/log.h>
49#include <JNIHelp.h>
50#include <JNIUtility.h>
51
52namespace android {
53
54using namespace WebCore;
55
56static bool gJniInitialized = false;
57static struct {
58    jmethodID m_Init;
59    jfieldID m_LinkUrl;
60    jfieldID m_AnchorText;
61    jfieldID m_ImageUrl;
62    jfieldID m_AltDisplayString;
63    jfieldID m_Title;
64    jfieldID m_Editable;
65    jfieldID m_TouchRects;
66    jfieldID m_TapHighlightColor;
67    jfieldID m_EnclosingParentRects;
68    jfieldID m_HasFocus;
69    jfieldID m_IntentUrl;
70} gHitTestGlue;
71
72struct field {
73    jclass m_class;
74    const char *m_fieldName;
75    const char *m_fieldType;
76    jfieldID *m_jfield;
77};
78
79static void InitJni(JNIEnv* env)
80{
81    if (gJniInitialized)
82        return;
83
84    jclass rectClass = env->FindClass("android/graphics/Rect");
85    ALOG_ASSERT(rectClass, "Could not find android/graphics/Rect");
86    jclass hitTestClass = env->FindClass("android/webkit/WebViewCore$WebKitHitTest");
87    ALOG_ASSERT(hitTestClass, "Could not find android/webkit/WebViewCore$WebKitHitTest");
88
89    gHitTestGlue.m_Init = env->GetMethodID(hitTestClass, "<init>",  "()V");
90    ALOG_ASSERT(gHitTestGlue.m_Init, "Could not find init method on android/webkit/WebViewCore$WebKitHitTest");
91
92    field fields[] = {
93        { hitTestClass, "mTouchRects", "[Landroid/graphics/Rect;", &gHitTestGlue.m_TouchRects },
94        { hitTestClass, "mEditable", "Z", &gHitTestGlue.m_Editable },
95        { hitTestClass, "mLinkUrl", "Ljava/lang/String;", &gHitTestGlue.m_LinkUrl },
96        { hitTestClass, "mIntentUrl", "Ljava/lang/String;", &gHitTestGlue.m_IntentUrl },
97        { hitTestClass, "mAnchorText", "Ljava/lang/String;", &gHitTestGlue.m_AnchorText },
98        { hitTestClass, "mImageUrl", "Ljava/lang/String;", &gHitTestGlue.m_ImageUrl },
99        { hitTestClass, "mAltDisplayString", "Ljava/lang/String;", &gHitTestGlue.m_AltDisplayString },
100        { hitTestClass, "mTitle", "Ljava/lang/String;", &gHitTestGlue.m_Title },
101        { hitTestClass, "mTapHighlightColor", "I", &gHitTestGlue.m_TapHighlightColor },
102        { hitTestClass, "mEnclosingParentRects", "[Landroid/graphics/Rect;", &gHitTestGlue.m_EnclosingParentRects },
103        { hitTestClass, "mHasFocus", "Z", &gHitTestGlue.m_HasFocus },
104        {0, 0, 0, 0},
105    };
106
107    for (int i = 0; fields[i].m_jfield; i++) {
108        field *f = &fields[i];
109        jfieldID field = env->GetFieldID(f->m_class, f->m_fieldName, f->m_fieldType);
110        ALOG_ASSERT(field, "Can't find %s", f->m_fieldName);
111        *(f->m_jfield) = field;
112    }
113
114    gJniInitialized = true;
115}
116
117AndroidHitTestResult::AndroidHitTestResult(WebViewCore* webViewCore, WebCore::HitTestResult& hitTestResult)
118    : m_webViewCore(webViewCore)
119    , m_hitTestResult(hitTestResult)
120{
121    buildHighlightRects();
122}
123
124void AndroidHitTestResult::setURLElement(Element* element)
125{
126    m_hitTestResult.setURLElement(element);
127    buildHighlightRects();
128}
129
130void AndroidHitTestResult::buildHighlightRects()
131{
132    m_highlightRects.clear();
133    Node* node = m_hitTestResult.URLElement();
134    if (!node || !node->renderer())
135        node = m_hitTestResult.innerNode();
136    if (!node || !node->renderer())
137        return;
138    if (!WebViewCore::nodeIsClickableOrFocusable(node))
139        return;
140    Frame* frame = node->document()->frame();
141    IntPoint frameOffset = m_webViewCore->convertGlobalContentToFrameContent(IntPoint(), frame);
142    RenderObject* renderer = node->renderer();
143    Vector<FloatQuad> quads;
144    if (renderer->isInline())
145        renderer->absoluteFocusRingQuads(quads);
146    if (!quads.size())
147        renderer->absoluteQuads(quads); // No fancy rings, grab a bounding box
148    for (size_t i = 0; i < quads.size(); i++) {
149        IntRect boundingBox = quads[i].enclosingBoundingBox();
150        boundingBox.move(-frameOffset.x(), -frameOffset.y());
151        m_highlightRects.append(boundingBox);
152    }
153}
154
155void AndroidHitTestResult::searchContentDetectors()
156{
157    AddressDetector address;
158    PhoneEmailDetector phoneEmail;
159    Node* node = m_hitTestResult.innerNode();
160    if (!node || !node->isTextNode())
161        return;
162    if (!m_hitTestResult.absoluteLinkURL().isEmpty())
163        return;
164    WebKit::WebHitTestInfo webHitTest(m_hitTestResult);
165    m_searchResult = address.FindTappedContent(webHitTest);
166    if (!m_searchResult.valid) {
167        m_searchResult = phoneEmail.FindTappedContent(webHitTest);
168    }
169    if (m_searchResult.valid) {
170        m_highlightRects.clear();
171        RefPtr<Range> range = (PassRefPtr<Range>) m_searchResult.range;
172        range->textRects(m_highlightRects, true);
173    }
174}
175
176void setStringField(JNIEnv* env, jobject obj, jfieldID field, const String& str)
177{
178    jstring jstr = wtfStringToJstring(env, str, false);
179    env->SetObjectField(obj, field, jstr);
180    env->DeleteLocalRef(jstr);
181}
182
183void setStringField(JNIEnv* env, jobject obj, jfieldID field, const GURL& url)
184{
185    jstring jstr = stdStringToJstring(env, url.spec(), false);
186    env->SetObjectField(obj, field, jstr);
187    env->DeleteLocalRef(jstr);
188}
189
190void setRectArray(JNIEnv* env, jobject obj, jfieldID field, Vector<IntRect> &rects)
191{
192    jobjectArray array = intRectVectorToRectArray(env, rects);
193    env->SetObjectField(obj, field, array);
194    env->DeleteLocalRef(array);
195}
196
197// Some helper macros specific to setting hitTest fields
198#define _SET(jtype, jfield, value) env->Set ## jtype ## Field(hitTest, gHitTestGlue.m_ ## jfield, value)
199#define SET_BOOL(jfield, value) _SET(Boolean, jfield, value)
200#define SET_STRING(jfield, value) setStringField(env, hitTest, gHitTestGlue.m_ ## jfield, value)
201#define SET_INT(jfield, value) _SET(Int, jfield, value)
202
203jobject AndroidHitTestResult::createJavaObject(JNIEnv* env)
204{
205    InitJni(env);
206    jclass hitTestClass = env->FindClass("android/webkit/WebViewCore$WebKitHitTest");
207    ALOG_ASSERT(hitTestClass, "Could not find android/webkit/WebViewCore$WebKitHitTest");
208
209    jobject hitTest = env->NewObject(hitTestClass, gHitTestGlue.m_Init);
210    setRectArray(env, hitTest, gHitTestGlue.m_TouchRects, m_highlightRects);
211
212    Vector<IntRect> rects = enclosingParentRects(m_hitTestResult.innerNode());
213    setRectArray(env, hitTest, gHitTestGlue.m_EnclosingParentRects, rects);
214
215    SET_BOOL(Editable, m_hitTestResult.isContentEditable());
216    SET_STRING(LinkUrl, m_hitTestResult.absoluteLinkURL().string());
217    if (m_searchResult.valid)
218        SET_STRING(IntentUrl, m_searchResult.intent_url);
219    SET_STRING(ImageUrl, m_hitTestResult.absoluteImageURL().string());
220    SET_STRING(AltDisplayString, m_hitTestResult.altDisplayString());
221    TextDirection titleTextDirection;
222    SET_STRING(Title, m_hitTestResult.title(titleTextDirection));
223    if (m_hitTestResult.URLElement()) {
224        Element* urlElement = m_hitTestResult.URLElement();
225        SET_STRING(AnchorText, urlElement->innerText());
226        if (urlElement->renderer()) {
227            SET_INT(TapHighlightColor,
228                    urlElement->renderer()->style()->tapHighlightColor().rgb());
229        }
230    }
231    Node* focusedNode = m_webViewCore->focusedFrame()->document()->focusedNode();
232    SET_BOOL(HasFocus,
233             focusedNode == m_hitTestResult.URLElement()
234             || focusedNode == m_hitTestResult.innerNode()
235             || focusedNode == m_hitTestResult.innerNonSharedNode());
236
237    env->DeleteLocalRef(hitTestClass);
238
239    return hitTest;
240}
241
242Vector<IntRect> AndroidHitTestResult::enclosingParentRects(Node* node)
243{
244    int count = 0;
245    int lastX = 0;
246    Vector<IntRect> rects;
247
248    while (node && count < 5) {
249        RenderObject* render = node->renderer();
250        if (!render || render->isBody())
251            break;
252
253        IntPoint frameOffset = m_webViewCore->convertGlobalContentToFrameContent(IntPoint(),
254                node->document()->frame());
255        IntRect rect = render->absoluteBoundingBoxRect();
256        rect.move(-frameOffset.x(), -frameOffset.y());
257        if (count == 0 || rect.x() != lastX) {
258            rects.append(rect);
259            lastX = rect.x();
260            count++;
261        }
262
263        node = node->parentNode();
264    }
265
266    return rects;
267}
268
269} /* namespace android */
270