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
52 namespace android {
53
54 using namespace WebCore;
55
56 static bool gJniInitialized = false;
57 static 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
72 struct field {
73 jclass m_class;
74 const char *m_fieldName;
75 const char *m_fieldType;
76 jfieldID *m_jfield;
77 };
78
InitJni(JNIEnv * env)79 static 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
AndroidHitTestResult(WebViewCore * webViewCore,WebCore::HitTestResult & hitTestResult)117 AndroidHitTestResult::AndroidHitTestResult(WebViewCore* webViewCore, WebCore::HitTestResult& hitTestResult)
118 : m_webViewCore(webViewCore)
119 , m_hitTestResult(hitTestResult)
120 {
121 buildHighlightRects();
122 }
123
setURLElement(Element * element)124 void AndroidHitTestResult::setURLElement(Element* element)
125 {
126 m_hitTestResult.setURLElement(element);
127 buildHighlightRects();
128 }
129
buildHighlightRects()130 void 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
searchContentDetectors()155 void 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
setStringField(JNIEnv * env,jobject obj,jfieldID field,const String & str)176 void 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
setStringField(JNIEnv * env,jobject obj,jfieldID field,const GURL & url)183 void 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
setRectArray(JNIEnv * env,jobject obj,jfieldID field,Vector<IntRect> & rects)190 void 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
createJavaObject(JNIEnv * env)203 jobject 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
enclosingParentRects(Node * node)242 Vector<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