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 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 117 AndroidHitTestResult::AndroidHitTestResult(WebViewCore* webViewCore, WebCore::HitTestResult& hitTestResult) 118 : m_webViewCore(webViewCore) 119 , m_hitTestResult(hitTestResult) 120 { 121 buildHighlightRects(); 122 } 123 124 void AndroidHitTestResult::setURLElement(Element* element) 125 { 126 m_hitTestResult.setURLElement(element); 127 buildHighlightRects(); 128 } 129 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 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 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 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 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 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 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