• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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