Home | History | Annotate | Download | only in jni
      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