blob: 6a29823209f8874f600d37488204dd25f609c15f [file] [log] [blame]
/*
* Copyright 2006, The Android Open Source Project
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define LOG_TAG "webcoreglue"
#include "config.h"
#include "WebViewCore.h"
#include "AccessibilityObject.h"
#include "AndroidHitTestResult.h"
#include "ApplicationCacheStorage.h"
#include "Attribute.h"
#include "content/address_detector.h"
#include "Chrome.h"
#include "ChromeClientAndroid.h"
#include "ChromiumIncludes.h"
#include "ClientRect.h"
#include "ClientRectList.h"
#include "Color.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "DatabaseTracker.h"
#include "Document.h"
#include "DocumentMarkerController.h"
#include "DOMWindow.h"
#include "DOMSelection.h"
#include "Element.h"
#include "Editor.h"
#include "EditorClientAndroid.h"
#include "EventHandler.h"
#include "EventNames.h"
#include "ExceptionCode.h"
#include "FocusController.h"
#include "Font.h"
#include "FontCache.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClientAndroid.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "Geolocation.h"
#include "GraphicsContext.h"
#include "GraphicsJNI.h"
#include "HTMLAnchorElement.h"
#include "HTMLAreaElement.h"
#include "HTMLElement.h"
#include "HTMLFormControlElement.h"
#include "HTMLImageElement.h"
#include "HTMLInputElement.h"
#include "HTMLLabelElement.h"
#include "HTMLMapElement.h"
#include "HTMLNames.h"
#include "HTMLOptGroupElement.h"
#include "HTMLOptionElement.h"
#include "HTMLSelectElement.h"
#include "HTMLTextAreaElement.h"
#include "HistoryItem.h"
#include "HitTestRequest.h"
#include "HitTestResult.h"
#include "InlineTextBox.h"
#include "KeyboardEvent.h"
#include "MemoryUsage.h"
#include "NamedNodeMap.h"
#include "Navigator.h"
#include "Node.h"
#include "NodeList.h"
#include "Page.h"
#include "PageGroup.h"
#include "PictureLayerContent.h"
#include "PicturePileLayerContent.h"
#include "PlatformKeyboardEvent.h"
#include "PlatformString.h"
#include "PluginWidgetAndroid.h"
#include "PluginView.h"
#include "Position.h"
#include "ProgressTracker.h"
#include "Range.h"
#include "RenderBox.h"
#include "RenderImage.h"
#include "RenderInline.h"
#include "RenderLayer.h"
#include "RenderPart.h"
#include "RenderText.h"
#include "RenderTextControl.h"
#include "RenderThemeAndroid.h"
#include "RenderView.h"
#include "ResourceRequest.h"
#include "RuntimeEnabledFeatures.h"
#include "SchemeRegistry.h"
#include "ScopedLocalRef.h"
#include "ScriptController.h"
#include "SelectionController.h"
#include "SelectText.h"
#include "Settings.h"
#include "SkANP.h"
#include "SkTemplates.h"
#include "SkTDArray.h"
#include "SkTypes.h"
#include "SkCanvas.h"
#include "SkGraphics.h"
#include "SkPicture.h"
#include "SkUtils.h"
#include "Text.h"
#include "TextIterator.h"
#include "TilesManager.h"
#include "TypingCommand.h"
#include "WebCache.h"
#include "WebCoreFrameBridge.h"
#include "WebCoreJni.h"
#include "WebFrameView.h"
#include "WindowsKeyboardCodes.h"
#include "autofill/WebAutofill.h"
#include "htmlediting.h"
#include "markup.h"
#include "unicode/uloc.h"
#include "visible_units.h"
#include <JNIHelp.h>
#include <JNIUtility.h>
#include <androidfw/KeycodeLabels.h>
#include <cutils/properties.h>
#include <v8.h>
#include <wtf/CurrentTime.h>
#include <wtf/text/AtomicString.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringImpl.h>
#if DEBUG_NAV_UI
#include "SkTime.h"
#endif
#if ENABLE(TOUCH_EVENTS) // Android
#include "PlatformTouchEvent.h"
#endif
#ifdef ANDROID_DOM_LOGGING
#include "AndroidLog.h"
#include "RenderTreeAsText.h"
#include <wtf/text/CString.h>
FILE* gDomTreeFile = 0;
FILE* gRenderTreeFile = 0;
#endif
#include "BaseLayerAndroid.h"
#if USE(ACCELERATED_COMPOSITING)
#include "GraphicsLayerAndroid.h"
#include "RenderLayerCompositor.h"
#endif
#define FOREGROUND_TIMER_INTERVAL 0.004 // 4ms
#define BACKGROUND_TIMER_INTERVAL 1.0 // 1s
// How many ms to wait for the scroll to "settle" before we will consider doing
// prerenders
#define PRERENDER_AFTER_SCROLL_DELAY 750
#define TOUCH_FLAG_HIT_HANDLER 0x1
#define TOUCH_FLAG_PREVENT_DEFAULT 0x2
////////////////////////////////////////////////////////////////////////////////////////////////
namespace android {
// Copied from CacheBuilder, not sure if this is needed/correct
IntRect getAreaRect(const HTMLAreaElement* area)
{
Node* node = area->document();
while ((node = node->traverseNextNode()) != NULL) {
RenderObject* renderer = node->renderer();
if (renderer && renderer->isRenderImage()) {
RenderImage* image = static_cast<RenderImage*>(renderer);
HTMLMapElement* map = image->imageMap();
if (map) {
Node* n;
for (n = map->firstChild(); n;
n = n->traverseNextNode(map)) {
if (n == area) {
if (area->isDefault())
return image->absoluteBoundingBoxRect();
return area->computeRect(image);
}
}
}
}
}
return IntRect();
}
// Copied from CacheBuilder, not sure if this is needed/correct
// TODO: See if this is even needed (I suspect not), and if not remove it
bool validNode(Frame* startFrame, void* matchFrame,
void* matchNode)
{
if (matchFrame == startFrame) {
if (matchNode == NULL)
return true;
Node* node = startFrame->document();
while (node != NULL) {
if (node == matchNode) {
const IntRect& rect = node->hasTagName(HTMLNames::areaTag) ?
getAreaRect(static_cast<HTMLAreaElement*>(node)) : node->getRect();
// Consider nodes with empty rects that are not at the origin
// to be valid, since news.google.com has valid nodes like this
if (rect.x() == 0 && rect.y() == 0 && rect.isEmpty())
return false;
return true;
}
node = node->traverseNextNode();
}
return false;
}
Frame* child = startFrame->tree()->firstChild();
while (child) {
bool result = validNode(child, matchFrame, matchNode);
if (result)
return result;
child = child->tree()->nextSibling();
}
return false;
}
static SkTDArray<WebViewCore*> gInstanceList;
void WebViewCore::addInstance(WebViewCore* inst) {
*gInstanceList.append() = inst;
}
void WebViewCore::removeInstance(WebViewCore* inst) {
int index = gInstanceList.find(inst);
ALOG_ASSERT(index >= 0, "RemoveInstance inst not found");
if (index >= 0) {
gInstanceList.removeShuffle(index);
}
}
bool WebViewCore::isInstance(WebViewCore* inst) {
return gInstanceList.find(inst) >= 0;
}
jobject WebViewCore::getApplicationContext() {
// check to see if there is a valid webviewcore object
if (gInstanceList.isEmpty())
return 0;
// get the context from the webview
jobject context = gInstanceList[0]->getContext();
if (!context)
return 0;
// get the application context using JNI
JNIEnv* env = JSC::Bindings::getJNIEnv();
jclass contextClass = env->GetObjectClass(context);
jmethodID appContextMethod = env->GetMethodID(contextClass, "getApplicationContext", "()Landroid/content/Context;");
env->DeleteLocalRef(contextClass);
jobject result = env->CallObjectMethod(context, appContextMethod);
checkException(env);
return result;
}
struct WebViewCoreStaticMethods {
jmethodID m_isSupportedMediaMimeType;
} gWebViewCoreStaticMethods;
// Check whether a media mimeType is supported in Android media framework.
bool WebViewCore::isSupportedMediaMimeType(const WTF::String& mimeType) {
JNIEnv* env = JSC::Bindings::getJNIEnv();
jstring jMimeType = wtfStringToJstring(env, mimeType);
jclass webViewCore = env->FindClass("android/webkit/WebViewCore");
bool val = env->CallStaticBooleanMethod(webViewCore,
gWebViewCoreStaticMethods.m_isSupportedMediaMimeType, jMimeType);
checkException(env);
env->DeleteLocalRef(webViewCore);
env->DeleteLocalRef(jMimeType);
return val;
}
// ----------------------------------------------------------------------------
// Field ids for WebViewCore
struct WebViewCoreFields {
jfieldID m_nativeClass;
jfieldID m_viewportWidth;
jfieldID m_viewportHeight;
jfieldID m_viewportInitialScale;
jfieldID m_viewportMinimumScale;
jfieldID m_viewportMaximumScale;
jfieldID m_viewportUserScalable;
jfieldID m_viewportDensityDpi;
jfieldID m_drawIsPaused;
jfieldID m_lowMemoryUsageMb;
jfieldID m_highMemoryUsageMb;
jfieldID m_highUsageDeltaMb;
} gWebViewCoreFields;
// ----------------------------------------------------------------------------
struct WebViewCore::JavaGlue {
jweak m_obj;
jmethodID m_scrollTo;
jmethodID m_contentDraw;
jmethodID m_requestListBox;
jmethodID m_openFileChooser;
jmethodID m_requestSingleListBox;
jmethodID m_jsAlert;
jmethodID m_jsConfirm;
jmethodID m_jsPrompt;
jmethodID m_jsUnload;
jmethodID m_jsInterrupt;
jmethodID m_getWebView;
jmethodID m_didFirstLayout;
jmethodID m_updateViewport;
jmethodID m_sendNotifyProgressFinished;
jmethodID m_sendViewInvalidate;
jmethodID m_updateTextfield;
jmethodID m_updateTextSelection;
jmethodID m_updateTextSizeAndScroll;
jmethodID m_clearTextEntry;
jmethodID m_restoreScale;
jmethodID m_needTouchEvents;
jmethodID m_requestKeyboard;
jmethodID m_exceededDatabaseQuota;
jmethodID m_reachedMaxAppCacheSize;
jmethodID m_populateVisitedLinks;
jmethodID m_geolocationPermissionsShowPrompt;
jmethodID m_geolocationPermissionsHidePrompt;
jmethodID m_getDeviceMotionService;
jmethodID m_getDeviceOrientationService;
jmethodID m_addMessageToConsole;
jmethodID m_focusNodeChanged;
jmethodID m_getPluginClass;
jmethodID m_showFullScreenPlugin;
jmethodID m_hideFullScreenPlugin;
jmethodID m_createSurface;
jmethodID m_addSurface;
jmethodID m_updateSurface;
jmethodID m_destroySurface;
jmethodID m_getContext;
jmethodID m_keepScreenOn;
jmethodID m_showRect;
jmethodID m_centerFitRect;
jmethodID m_setScrollbarModes;
jmethodID m_exitFullscreenVideo;
jmethodID m_setWebTextViewAutoFillable;
jmethodID m_selectAt;
jmethodID m_initEditField;
jmethodID m_chromeCanTakeFocus;
jmethodID m_chromeTakeFocus;
AutoJObject object(JNIEnv* env) {
// We hold a weak reference to the Java WebViewCore to avoid memeory
// leaks due to circular references when WebView.destroy() is not
// called manually. The WebView and hence the WebViewCore could become
// weakly reachable at any time, after which the GC could null our weak
// reference, so we have to check the return value of this method at
// every use. Note that our weak reference will be nulled before the
// WebViewCore is finalized.
return getRealObject(env, m_obj);
}
};
struct WebViewCore::TextFieldInitDataGlue {
jmethodID m_constructor;
jfieldID m_fieldPointer;
jfieldID m_text;
jfieldID m_type;
jfieldID m_isSpellCheckEnabled;
jfieldID m_isTextFieldNext;
jfieldID m_isTextFieldPrev;
jfieldID m_isAutoCompleteEnabled;
jfieldID m_name;
jfieldID m_label;
jfieldID m_maxLength;
jfieldID m_contentBounds;
jfieldID m_nodeLayerId;
jfieldID m_clientRect;
};
/*
* WebViewCore Implementation
*/
static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[])
{
jmethodID m = env->GetMethodID(clazz, name, signature);
ALOG_ASSERT(m, "Could not find method %s", name);
return m;
}
WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* mainframe)
: m_touchGeneration(0)
, m_lastGeneration(0)
, m_javaGlue(new JavaGlue)
, m_textFieldInitDataGlue(new TextFieldInitDataGlue)
, m_mainFrame(mainframe)
, m_popupReply(0)
, m_blockTextfieldUpdates(false)
, m_skipContentDraw(false)
, m_textGeneration(0)
, m_maxXScroll(320/4)
, m_maxYScroll(240/4)
, m_scrollOffsetX(0)
, m_scrollOffsetY(0)
, m_scrollSetTime(0)
, m_mousePos(WebCore::IntPoint(0,0))
, m_screenWidth(320)
, m_screenHeight(240)
, m_textWrapWidth(320)
, m_scale(1.0f)
, m_groupForVisitedLinks(0)
, m_cacheMode(0)
, m_fullscreenVideoMode(false)
, m_matchCount(0)
, m_activeMatchIndex(0)
, m_activeMatch(0)
, m_pluginInvalTimer(this, &WebViewCore::pluginInvalTimerFired)
, m_screenOnCounter(0)
, m_currentNodeDomNavigationAxis(0)
, m_deviceMotionAndOrientationManager(this)
, m_geolocationManager(this)
#if ENABLE(TOUCH_EVENTS)
, m_forwardingTouchEvents(false)
#endif
, m_webRequestContext(0)
, m_prerenderEnabled(false)
{
ALOG_ASSERT(m_mainFrame, "Uh oh, somehow a frameview was made without an initial frame!");
jclass clazz = env->GetObjectClass(javaWebViewCore);
m_javaGlue->m_obj = env->NewWeakGlobalRef(javaWebViewCore);
m_javaGlue->m_scrollTo = GetJMethod(env, clazz, "contentScrollTo", "(IIZZ)V");
m_javaGlue->m_contentDraw = GetJMethod(env, clazz, "contentDraw", "()V");
m_javaGlue->m_requestListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[I[I)V");
m_javaGlue->m_openFileChooser = GetJMethod(env, clazz, "openFileChooser", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
m_javaGlue->m_requestSingleListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[II)V");
m_javaGlue->m_jsAlert = GetJMethod(env, clazz, "jsAlert", "(Ljava/lang/String;Ljava/lang/String;)V");
m_javaGlue->m_jsConfirm = GetJMethod(env, clazz, "jsConfirm", "(Ljava/lang/String;Ljava/lang/String;)Z");
m_javaGlue->m_jsPrompt = GetJMethod(env, clazz, "jsPrompt", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
m_javaGlue->m_jsUnload = GetJMethod(env, clazz, "jsUnload", "(Ljava/lang/String;Ljava/lang/String;)Z");
m_javaGlue->m_jsInterrupt = GetJMethod(env, clazz, "jsInterrupt", "()Z");
m_javaGlue->m_getWebView = GetJMethod(env, clazz, "getWebView", "()Landroid/webkit/WebView;");
m_javaGlue->m_didFirstLayout = GetJMethod(env, clazz, "didFirstLayout", "(Z)V");
m_javaGlue->m_updateViewport = GetJMethod(env, clazz, "updateViewport", "()V");
m_javaGlue->m_sendNotifyProgressFinished = GetJMethod(env, clazz, "sendNotifyProgressFinished", "()V");
m_javaGlue->m_sendViewInvalidate = GetJMethod(env, clazz, "sendViewInvalidate", "(IIII)V");
m_javaGlue->m_updateTextfield = GetJMethod(env, clazz, "updateTextfield", "(ILjava/lang/String;I)V");
m_javaGlue->m_updateTextSelection = GetJMethod(env, clazz, "updateTextSelection", "(IIIII)V");
m_javaGlue->m_updateTextSizeAndScroll = GetJMethod(env, clazz, "updateTextSizeAndScroll", "(IIIII)V");
m_javaGlue->m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()V");
m_javaGlue->m_restoreScale = GetJMethod(env, clazz, "restoreScale", "(FF)V");
m_javaGlue->m_needTouchEvents = GetJMethod(env, clazz, "needTouchEvents", "(Z)V");
m_javaGlue->m_requestKeyboard = GetJMethod(env, clazz, "requestKeyboard", "(Z)V");
m_javaGlue->m_exceededDatabaseQuota = GetJMethod(env, clazz, "exceededDatabaseQuota", "(Ljava/lang/String;Ljava/lang/String;JJ)V");
m_javaGlue->m_reachedMaxAppCacheSize = GetJMethod(env, clazz, "reachedMaxAppCacheSize", "(JJ)V");
m_javaGlue->m_populateVisitedLinks = GetJMethod(env, clazz, "populateVisitedLinks", "()V");
m_javaGlue->m_geolocationPermissionsShowPrompt = GetJMethod(env, clazz, "geolocationPermissionsShowPrompt", "(Ljava/lang/String;)V");
m_javaGlue->m_geolocationPermissionsHidePrompt = GetJMethod(env, clazz, "geolocationPermissionsHidePrompt", "()V");
m_javaGlue->m_getDeviceMotionService = GetJMethod(env, clazz, "getDeviceMotionService", "()Landroid/webkit/DeviceMotionService;");
m_javaGlue->m_getDeviceOrientationService = GetJMethod(env, clazz, "getDeviceOrientationService", "()Landroid/webkit/DeviceOrientationService;");
m_javaGlue->m_addMessageToConsole = GetJMethod(env, clazz, "addMessageToConsole", "(Ljava/lang/String;ILjava/lang/String;I)V");
m_javaGlue->m_focusNodeChanged = GetJMethod(env, clazz, "focusNodeChanged", "(ILandroid/webkit/WebViewCore$WebKitHitTest;)V");
m_javaGlue->m_getPluginClass = GetJMethod(env, clazz, "getPluginClass", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;");
m_javaGlue->m_showFullScreenPlugin = GetJMethod(env, clazz, "showFullScreenPlugin", "(Landroid/webkit/ViewManager$ChildView;II)V");
m_javaGlue->m_hideFullScreenPlugin = GetJMethod(env, clazz, "hideFullScreenPlugin", "()V");
m_javaGlue->m_createSurface = GetJMethod(env, clazz, "createSurface", "(Landroid/view/View;)Landroid/webkit/ViewManager$ChildView;");
m_javaGlue->m_addSurface = GetJMethod(env, clazz, "addSurface", "(Landroid/view/View;IIII)Landroid/webkit/ViewManager$ChildView;");
m_javaGlue->m_updateSurface = GetJMethod(env, clazz, "updateSurface", "(Landroid/webkit/ViewManager$ChildView;IIII)V");
m_javaGlue->m_destroySurface = GetJMethod(env, clazz, "destroySurface", "(Landroid/webkit/ViewManager$ChildView;)V");
m_javaGlue->m_getContext = GetJMethod(env, clazz, "getContext", "()Landroid/content/Context;");
m_javaGlue->m_keepScreenOn = GetJMethod(env, clazz, "keepScreenOn", "(Z)V");
m_javaGlue->m_showRect = GetJMethod(env, clazz, "showRect", "(IIIIIIFFFF)V");
m_javaGlue->m_centerFitRect = GetJMethod(env, clazz, "centerFitRect", "(IIII)V");
m_javaGlue->m_setScrollbarModes = GetJMethod(env, clazz, "setScrollbarModes", "(II)V");
#if ENABLE(VIDEO)
m_javaGlue->m_exitFullscreenVideo = GetJMethod(env, clazz, "exitFullscreenVideo", "()V");
#endif
m_javaGlue->m_setWebTextViewAutoFillable = GetJMethod(env, clazz, "setWebTextViewAutoFillable", "(ILjava/lang/String;)V");
m_javaGlue->m_selectAt = GetJMethod(env, clazz, "selectAt", "(II)V");
m_javaGlue->m_initEditField = GetJMethod(env, clazz, "initEditField", "(IIILandroid/webkit/WebViewCore$TextFieldInitData;)V");
m_javaGlue->m_chromeCanTakeFocus = GetJMethod(env, clazz, "chromeCanTakeFocus", "(I)Z");
m_javaGlue->m_chromeTakeFocus = GetJMethod(env, clazz, "chromeTakeFocus", "(I)V");
env->DeleteLocalRef(clazz);
env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this);
jclass tfidClazz = env->FindClass("android/webkit/WebViewCore$TextFieldInitData");
m_textFieldInitDataGlue->m_fieldPointer = env->GetFieldID(tfidClazz, "mFieldPointer", "I");
m_textFieldInitDataGlue->m_text = env->GetFieldID(tfidClazz, "mText", "Ljava/lang/String;");
m_textFieldInitDataGlue->m_type = env->GetFieldID(tfidClazz, "mType", "I");
m_textFieldInitDataGlue->m_isSpellCheckEnabled = env->GetFieldID(tfidClazz, "mIsSpellCheckEnabled", "Z");
m_textFieldInitDataGlue->m_isTextFieldNext = env->GetFieldID(tfidClazz, "mIsTextFieldNext", "Z");
m_textFieldInitDataGlue->m_isTextFieldPrev = env->GetFieldID(tfidClazz, "mIsTextFieldPrev", "Z");
m_textFieldInitDataGlue->m_isAutoCompleteEnabled = env->GetFieldID(tfidClazz, "mIsAutoCompleteEnabled", "Z");
m_textFieldInitDataGlue->m_name = env->GetFieldID(tfidClazz, "mName", "Ljava/lang/String;");
m_textFieldInitDataGlue->m_label = env->GetFieldID(tfidClazz, "mLabel", "Ljava/lang/String;");
m_textFieldInitDataGlue->m_maxLength = env->GetFieldID(tfidClazz, "mMaxLength", "I");
m_textFieldInitDataGlue->m_contentBounds = env->GetFieldID(tfidClazz, "mContentBounds", "Landroid/graphics/Rect;");
m_textFieldInitDataGlue->m_nodeLayerId = env->GetFieldID(tfidClazz, "mNodeLayerId", "I");
m_textFieldInitDataGlue->m_clientRect = env->GetFieldID(tfidClazz, "mClientRect", "Landroid/graphics/Rect;");
m_textFieldInitDataGlue->m_constructor = GetJMethod(env, tfidClazz, "<init>", "()V");
env->DeleteLocalRef(tfidClazz);
PageGroup::setShouldTrackVisitedLinks(true);
clearContent();
MemoryUsage::setLowMemoryUsageMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_lowMemoryUsageMb));
MemoryUsage::setHighMemoryUsageMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_highMemoryUsageMb));
MemoryUsage::setHighUsageDeltaMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_highUsageDeltaMb));
WebViewCore::addInstance(this);
AndroidNetworkLibraryImpl::InitWithApplicationContext(env, 0);
// increase the font cache size beyond the standard system setting
SkGraphics::SetFontCacheLimit(1572864); // 1572864 bytes == 1.5 MB
// Static initialisation of certain important V8 static data gets performed at system startup when
// libwebcore gets loaded. We now need to associate the WebCore thread with V8 to complete
// initialisation.
v8::V8::Initialize();
// Configure any RuntimeEnabled features that we need to change from their default now.
// See WebCore/bindings/generic/RuntimeEnabledFeatures.h
// HTML5 History API
RuntimeEnabledFeatures::setPushStateEnabled(true);
if (m_mainFrame)
m_mainFrame->settings()->setMinDOMTimerInterval(FOREGROUND_TIMER_INTERVAL);
}
WebViewCore::~WebViewCore()
{
WebViewCore::removeInstance(this);
// Release the focused view
::Release(m_popupReply);
if (m_javaGlue->m_obj) {
JNIEnv* env = JSC::Bindings::getJNIEnv();
env->DeleteWeakGlobalRef(m_javaGlue->m_obj);
m_javaGlue->m_obj = 0;
}
delete m_javaGlue;
delete m_textFieldInitDataGlue;
}
WebViewCore* WebViewCore::getWebViewCore(const WebCore::FrameView* view)
{
if (!view)
return 0;
if (view->platformWidget())
return static_cast<WebFrameView*>(view->platformWidget())->webViewCore();
Frame* frame = view->frame();
while (Frame* parent = frame->tree()->parent())
frame = parent;
WebFrameView* webFrameView = 0;
if (frame && frame->view())
webFrameView = static_cast<WebFrameView*>(frame->view()->platformWidget());
if (!webFrameView)
return 0;
return webFrameView->webViewCore();
}
WebViewCore* WebViewCore::getWebViewCore(const WebCore::ScrollView* view)
{
if (!view)
return 0;
if (view->platformWidget())
return static_cast<WebFrameView*>(view->platformWidget())->webViewCore();
const FrameView* frameView = 0;
if (view->isFrameView())
frameView = static_cast<const FrameView*>(view);
else {
frameView = static_cast<const FrameView*>(view->root());
if (!frameView)
return 0;
}
return getWebViewCore(frameView);
}
static bool layoutIfNeededRecursive(WebCore::Frame* f)
{
if (!f)
return true;
WebCore::FrameView* v = f->view();
if (!v)
return true;
v->updateLayoutAndStyleIfNeededRecursive();
return !v->needsLayout();
}
WebCore::Node* WebViewCore::currentFocus()
{
return focusedFrame()->document()->focusedNode();
}
void WebViewCore::layout()
{
TRACE_METHOD();
// if there is no document yet, just return
if (!m_mainFrame->document()) {
ALOGV("!m_mainFrame->document()");
return;
}
// Call layout to ensure that the contentWidth and contentHeight are correct
// it's fine for layout to gather invalidates, but defeat sending a message
// back to java to call webkitDraw, since we're already in the middle of
// doing that
bool success = layoutIfNeededRecursive(m_mainFrame);
// We may be mid-layout and thus cannot draw.
if (!success)
return;
// if the webkit page dimensions changed, discard the pictureset and redraw.
WebCore::FrameView* view = m_mainFrame->view();
int width = view->contentsWidth();
int height = view->contentsHeight();
// Use the contents width and height as a starting point.
SkIRect contentRect;
contentRect.set(0, 0, width, height);
SkIRect total(contentRect);
// Traverse all the frames and add their sizes if they are in the visible
// rectangle.
for (WebCore::Frame* frame = m_mainFrame->tree()->traverseNext(); frame;
frame = frame->tree()->traverseNext()) {
// If the frame doesn't have an owner then it is the top frame and the
// view size is the frame size.
WebCore::RenderPart* owner = frame->ownerRenderer();
if (owner && owner->style()->visibility() == VISIBLE) {
int x = owner->x();
int y = owner->y();
// Traverse the tree up to the parent to find the absolute position
// of this frame.
WebCore::Frame* parent = frame->tree()->parent();
while (parent) {
WebCore::RenderPart* parentOwner = parent->ownerRenderer();
if (parentOwner) {
x += parentOwner->x();
y += parentOwner->y();
}
parent = parent->tree()->parent();
}
// Use the owner dimensions so that padding and border are
// included.
int right = x + owner->width();
int bottom = y + owner->height();
SkIRect frameRect = {x, y, right, bottom};
// Ignore a width or height that is smaller than 1. Some iframes
// have small dimensions in order to be hidden. The iframe
// expansion code does not expand in that case so we should ignore
// them here.
if (frameRect.width() > 1 && frameRect.height() > 1
&& SkIRect::Intersects(total, frameRect))
total.join(x, y, right, bottom);
}
}
// If the new total is larger than the content, resize the view to include
// all the content.
if (!contentRect.contains(total)) {
// TODO: Does this ever happen? Is this needed now that we don't flatten
// frames?
// Resize the view to change the overflow clip.
view->resize(total.fRight, total.fBottom);
// We have to force a layout in order for the clip to change.
m_mainFrame->contentRenderer()->setNeedsLayoutAndPrefWidthsRecalc();
view->forceLayout();
// Relayout similar to above
layoutIfNeededRecursive(m_mainFrame);
}
}
void WebViewCore::recordPicturePile()
{
// if the webkit page dimensions changed, discard the pictureset and redraw.
WebCore::FrameView* view = m_mainFrame->view();
int width = view ? view->contentsWidth() : 0;
int height = view ? view->contentsHeight() : 0;
m_content.setSize(IntSize(width, height));
// Rebuild the pictureset (webkit repaint)
m_content.updatePicturesIfNeeded(this);
}
void WebViewCore::clearContent()
{
m_content.reset();
updateLocale();
}
void WebViewCore::paintContents(WebCore::GraphicsContext* gc, WebCore::IntRect& dirty)
{
WebCore::FrameView* view = m_mainFrame->view();
if (!view) {
gc->setFillColor(WebCore::Color::white, WebCore::ColorSpaceDeviceRGB);
gc->fillColor();
return;
}
IntPoint origin = view->minimumScrollPosition();
IntRect drawArea = dirty;
gc->translate(-origin.x(), -origin.y());
drawArea.move(origin.x(), origin.y());
view->platformWidget()->draw(gc, drawArea);
}
void WebViewCore::setPrerenderingEnabled(bool enable)
{
MutexLocker locker(m_prerenderLock);
m_prerenderEnabled = enable;
}
bool WebViewCore::prerenderingEnabled()
{
MutexLocker locker(m_prerenderLock);
return m_prerenderEnabled;
}
SkCanvas* WebViewCore::createPrerenderCanvas(PrerenderedInval* prerendered)
{
// Has WebView disabled prerenders (not attached, etc...)?
if (!prerenderingEnabled())
return 0;
// Does this WebView have focus?
if (!m_mainFrame->page()->focusController()->isActive())
return 0;
// Are we scrolling?
if (currentTimeMS() - m_scrollSetTime < PRERENDER_AFTER_SCROLL_DELAY)
return 0;
// Do we have anything to render?
if (prerendered->area.isEmpty())
return 0;
FloatRect scaleTemp(m_scrollOffsetX, m_scrollOffsetY, m_screenWidth, m_screenHeight);
scaleTemp.scale(m_scale);
IntRect visibleTileClip = enclosingIntRect(scaleTemp);
FloatRect scaledArea = prerendered->area;
scaledArea.scale(m_scale);
IntRect enclosingScaledArea = enclosingIntRect(scaledArea);
if (enclosingScaledArea.isEmpty())
return 0;
// "round out" the screen to tile boundaries so that we can clip yet still
// cover any visible tiles with the prerender
int tw = TilesManager::tileWidth();
int th = TilesManager::tileHeight();
float left = tw * (int) (visibleTileClip.x() / tw);
float top = th * (int) (visibleTileClip.y() / th);
float right = tw * (int) ceilf(visibleTileClip.maxX() / (float) tw);
float bottom = th * (int) ceilf(visibleTileClip.maxY() / (float) th);
visibleTileClip = IntRect(left, top, right - left, bottom - top);
enclosingScaledArea.intersect(visibleTileClip);
if (enclosingScaledArea.isEmpty())
return 0;
prerendered->screenArea = enclosingScaledArea;
FloatRect enclosingDocArea(enclosingScaledArea);
enclosingDocArea.scale(1 / m_scale);
prerendered->area = enclosingIntRect(enclosingDocArea);
if (prerendered->area.isEmpty())
return 0;
prerendered->bitmap.setConfig(SkBitmap::kARGB_8888_Config,
enclosingScaledArea.width(),
enclosingScaledArea.height());
prerendered->bitmap.allocPixels();
SkCanvas* bitmapCanvas = new SkCanvas(prerendered->bitmap);
bitmapCanvas->scale(m_scale, m_scale);
bitmapCanvas->translate(-enclosingDocArea.x(), -enclosingDocArea.y());
return bitmapCanvas;
}
void WebViewCore::notifyAnimationStarted()
{
// We notify webkit that the animations have begun
// TODO: handle case where not all have begun
ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client());
GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync());
if (root)
root->notifyClientAnimationStarted();
}
BaseLayerAndroid* WebViewCore::createBaseLayer(GraphicsLayerAndroid* root)
{
// We set the background color
Color background = Color::white;
bool bodyHasFixedBackgroundImage = false;
bool bodyHasCSSBackground = false;
if (m_mainFrame && m_mainFrame->document()
&& m_mainFrame->document()->body()) {
Document* document = m_mainFrame->document();
RefPtr<RenderStyle> style = document->styleForElementIgnoringPendingStylesheets(document->body());
if (style->hasBackground()) {
background = style->visitedDependentColor(CSSPropertyBackgroundColor);
bodyHasCSSBackground = true;
}
WebCore::FrameView* view = m_mainFrame->view();
if (view) {
Color viewBackground = view->baseBackgroundColor();
background = bodyHasCSSBackground ? viewBackground.blend(background) : viewBackground;
}
if (style->hasFixedBackgroundImage()) {
Image* backgroundImage = FixedBackgroundImageLayerAndroid::GetCachedImage(style);
if (backgroundImage && backgroundImage->width() > 1 && backgroundImage->height() > 1)
bodyHasFixedBackgroundImage = true;
}
}
PicturePileLayerContent* content = new PicturePileLayerContent(m_content);
m_content.clearPrerenders();
BaseLayerAndroid* realBase = 0;
LayerAndroid* base = 0;
//If we have a fixed background image on the body element, the fixed image
// will be contained in the PictureSet (the content object), and the foreground
//of the body element will be moved to a layer.
//In that case, let's change the hierarchy to obtain:
//
//BaseLayerAndroid
// \- FixedBackgroundBaseLayerAndroid (fixed positioning)
// \- ForegroundBaseLayerAndroid
// \- root layer (webkit composited tree)
if (bodyHasFixedBackgroundImage) {
base = new ForegroundBaseLayerAndroid(0);
base->setSize(content->width(), content->height());
Document* document = m_mainFrame->document();
RefPtr<RenderStyle> style = document->styleForElementIgnoringPendingStylesheets(document->body());
FixedBackgroundImageLayerAndroid* baseBackground =
new FixedBackgroundImageLayerAndroid(style, content->width(), content->height());
realBase = new BaseLayerAndroid(0);
realBase->setSize(content->width(), content->height());
realBase->addChild(baseBackground);
realBase->addChild(base);
baseBackground->unref();
base->unref();
} else {
realBase = new BaseLayerAndroid(content);
base = realBase;
}
realBase->setBackgroundColor(background);
SkSafeUnref(content);
// We update the layers
if (root) {
LayerAndroid* copyLayer = new LayerAndroid(*root->contentLayer());
base->addChild(copyLayer);
copyLayer->unref();
root->contentLayer()->clearDirtyRegion();
}
return realBase;
}
BaseLayerAndroid* WebViewCore::recordContent(SkIPoint* point)
{
m_skipContentDraw = true;
layout();
ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client());
GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync());
m_skipContentDraw = false;
recordPicturePile();
BaseLayerAndroid* baseLayer = createBaseLayer(root);
baseLayer->markAsDirty(m_content.dirtyRegion());
m_content.dirtyRegion().setEmpty();
#if USE(ACCELERATED_COMPOSITING)
#else
baseLayer->markAsDirty(m_rebuildInval);
#endif
point->fX = m_content.size().width();
point->fY = m_content.size().height();
return baseLayer;
}
void WebViewCore::scrollTo(int x, int y, bool animate)
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_scrollTo,
x, y, animate, false);
checkException(env);
}
void WebViewCore::sendNotifyProgressFinished()
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_sendNotifyProgressFinished);
checkException(env);
}
void WebViewCore::viewInvalidate(const WebCore::IntRect& rect)
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
env->CallVoidMethod(javaObject.get(),
m_javaGlue->m_sendViewInvalidate,
rect.x(), rect.y(), rect.maxX(), rect.maxY());
checkException(env);
}
void WebViewCore::contentDraw()
{
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_contentDraw);
checkException(env);
}
void WebViewCore::contentInvalidate(const WebCore::IntRect &r)
{
IntPoint origin = m_mainFrame->view()->minimumScrollPosition();
IntRect dirty = r;
dirty.move(-origin.x(), -origin.y());
m_content.invalidate(dirty);
if (!m_skipContentDraw)
contentDraw();
}
void WebViewCore::contentInvalidateAll()
{
WebCore::FrameView* view = m_mainFrame->view();
contentInvalidate(WebCore::IntRect(0, 0,
view->contentsWidth(), view->contentsHeight()));
}
void WebViewCore::offInvalidate(const WebCore::IntRect &r)
{
// FIXME: these invalidates are offscreen, and can be throttled or
// deferred until the area is visible. For now, treat them as
// regular invals so that drawing happens (inefficiently) for now.
contentInvalidate(r);
}
void WebViewCore::didFirstLayout()
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
const WebCore::KURL& url = m_mainFrame->document()->url();
if (url.isEmpty())
return;
ALOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data());
WebCore::FrameLoadType loadType = m_mainFrame->loader()->loadType();
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_didFirstLayout,
loadType == WebCore::FrameLoadTypeStandard
// When redirect with locked history, we would like to reset the
// scale factor. This is important for www.yahoo.com as it is
// redirected to www.yahoo.com/?rs=1 on load.
|| loadType == WebCore::FrameLoadTypeRedirectWithLockedBackForwardList
// When "request desktop page" is used, we want to treat it as
// a newly-loaded page.
|| loadType == WebCore::FrameLoadTypeSame);
checkException(env);
}
void WebViewCore::updateViewport()
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_updateViewport);
checkException(env);
}
void WebViewCore::restoreScale(float scale, float textWrapScale)
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_restoreScale, scale, textWrapScale);
checkException(env);
}
void WebViewCore::needTouchEvents(bool need)
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
#if ENABLE(TOUCH_EVENTS)
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
if (m_forwardingTouchEvents == need)
return;
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_needTouchEvents, need);
checkException(env);
m_forwardingTouchEvents = need;
#endif
}
void WebViewCore::requestKeyboard(bool showKeyboard)
{
ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!");
JNIEnv* env = JSC::Bindings::getJNIEnv();
AutoJObject javaObject = m_javaGlue->object(env);
if (!javaObject.get())
return;
env->CallVoidMethod(javaObject.get(), m_javaGlue->m_requestKeyboard, showKeyboard);
checkException(env);
}
void WebViewCore::notifyProgressFinished()
{
sendNotifyProgressFinished();
}
void WebViewCore::setScrollOffset(bool sendScrollEvent, int dx, int dy)
{
if (m_scrollOffsetX != dx || m_scrollOffsetY != dy) {
m_scrollOffsetX = dx;
m_scrollOffsetY = dy;
m_scrollSetTime = currentTimeMS();
// The visible rect is located within our coordinate space so it
// contains the actual scroll position. Setting the location makes hit
// testing work correctly.
m_mainFrame->view()->platformWidget()->setLocation(m_scrollOffsetX,
m_scrollOffsetY);
if (sendScrollEvent) {
m_mainFrame->eventHandler()->sendScrollEvent();
// Only update history position if it's user scrolled.
// Update history item to reflect the new scroll position.
// This also helps save the history information when the browser goes to
// background, so scroll position will be restored if browser gets
// killed while in background.
WebCore::HistoryController* history = m_mainFrame->loader()->history();
// Because the history item saving could be heavy for large sites and
// scrolling can generate lots of small scroll offset, the following code
// reduces the saving frequency.
static const int MIN_SCROLL_DIFF = 32;
if (history->currentItem()) {
WebCore::IntPoint currentPoint = history->currentItem()->scrollPoint();
if (std::abs(currentPoint.x() - dx) >= MIN_SCROLL_DIFF ||
std::abs(currentPoint.y() - dy) >= MIN_SCROLL_DIFF) {
history->saveScrollPositionAndViewStateToItem(history->currentItem());
}
}
}
// update the currently visible screen
sendPluginVisibleScreen();
}
}
void WebViewCore::setGlobalBounds(int x, int y, int h, int v)
{
m_mainFrame->view()->platformWidget()->setWindowBounds(x, y, h, v);
}
void WebViewCore::setSizeScreenWidthAndScale(int width, int height,
int textWrapWidth, float scale, int screenWidth, int screenHeight,
int anchorX, int anchorY, bool ignoreHeight)
{
// Ignore the initial empty document.
const WebCore::KURL& url = m_mainFrame->document()->url();
if (url.isEmpty())
return;
WebCoreViewBridge* window = m_mainFrame->view()->platformWidget();
int ow = window->width();
int oh = window->height();
int osw = m_screenWidth;
int osh = m_screenHeight;
int otw = m_textWrapWidth;
m_screenWidth = screenWidth;
m_screenHeight = screenHeight;
m_textWrapWidth = textWrapWidth;
if (scale >= 0) // negative means keep the current scale
m_scale = scale;
m_maxXScroll = screenWidth >> 2;
m_maxYScroll = m_maxXScroll * height / width;
// Don't reflow if the diff is small.
const bool reflow = otw && textWrapWidth &&
((float) abs(otw - textWrapWidth) / textWrapWidth) >= 0.01f;
// When the screen size change, fixed positioned element should be updated.
// This is supposed to be light weighted operation without a full layout.
if (osh != screenHeight || osw != screenWidth)
m_mainFrame->view()->updatePositionedObjects();
if (ow != width || (!ignoreHeight && oh != height) || reflow) {
WebCore::RenderObject *r = m_mainFrame->contentRenderer();
if (r) {
WebCore::IntPoint anchorPoint = WebCore::IntPoint(anchorX, anchorY);
RefPtr<WebCore::Node> node;
WebCore::IntRect bounds;
WebCore::IntPoint offset;
// If the text wrap changed, it is probably zoom change or
// orientation change. Try to keep the anchor at the same place.
if (otw && textWrapWidth && otw != textWrapWidth &&
(anchorX != 0 || anchorY != 0)) {
WebCore::HitTestResult hitTestResult =
m_mainFrame->eventHandler()->hitTestResultAtPoint(
anchorPoint, false);
node = hitTestResult.innerNode();
if (node && !node->isTextNode()) {
// If the hitTestResultAtPoint didn't find a suitable node
// for anchoring, try again with some slop.
static const int HIT_SLOP = 30;
anchorPoint.move(HIT_SLOP, HIT_SLOP);
hitTestResult =
m_mainFrame->eventHandler()->hitTestResultAtPoint(
anchorPoint, false);
node = hitTestResult.innerNode();
}
}
if (node) {
bounds = node->getRect();
// sites like nytimes.com insert a non-standard tag <nyt_text>
// in the html. If it is the HitTestResult, it may have zero
// width and height. In this case, use its parent node.
if (bounds.width() == 0) {
node = node->parentOrHostNode();
if (node) {
bounds = node->getRect();
}
}
}
// Set the size after finding the old anchor point as
// hitTestResultAtPoint causes a layout.
window->setSize(width, height);
window->setVisibleSize(screenWidth, screenHeight);
if (width != screenWidth) {
m_mainFrame->view()->setUseFixedLayout(true);
m_mainFrame->view()->setFixedLayoutSize(IntSize(width, height));
} else
m_mainFrame->view()->setUseFixedLayout(false);
r->setNeedsLayoutAndPrefWidthsRecalc();
if (m_mainFrame->view()->didFirstLayout())
m_mainFrame->view()->forceLayout();
// scroll to restore current screen center
if (node && node->inDocument()) {
const WebCore::IntRect& newBounds = node->getRect();
if ((osw && osh && bounds.width() && bounds.height())
&& (bounds != newBounds)) {
WebCore::FrameView* view = m_mainFrame->view();
// force left align if width is not changed while height changed.
// the anchorPoint is probably at some white space in the node
// which is affected by text wrap around the screen width.
const bool leftAlign = (otw != textWrapWidth)
&& (bounds.width() == newBounds.width())
&& (bounds.height() != newBounds.height());
const float xPercentInDoc =
leftAlign ? 0.0 : (float) (anchorX - bounds.x()) / bounds.width();
const float xPercentInView =
leftAlign ? 0.0 : (float) (anchorX - m_scrollOffsetX) / osw;
const float yPercentInDoc = (float) (anchorY - bounds.y()) / bounds.height();
const float yPercentInView = (float) (anchorY - m_scrollOffsetY) / osh;
showRect(newBounds.x(), newBounds.y(), newBounds.width(),
newBounds.height(), view->contentsWidth(),
view->contentsHeight(),
xPercentInDoc, xPercentInView,
yPercentInDoc, yPercentInView);
}
}
}
} else {
window->setSize(width, height);
window->setVisibleSize(screenWidth, screenHeight);
m_mainFrame->view()->resize(width, height);
if (width != screenWidth) {
m_mainFrame->view()->setUseFixedLayout(true);
m_mainFrame->view()->setFixedLayoutSize(IntSize(width, height));
} else
m_mainFrame->view()->setUseFixedLayout(false);
}
// update the currently visible screen as perceived by the plugin
sendPluginVisibleScreen();
}
void WebViewCore::dumpDomTree(bool useFile)
{
#ifdef ANDROID_DOM_LOGGING
if (useFile)
gDomTreeFile = fopen(DOM_TREE_LOG_FILE, "w");
m_mainFrame->document()->showTreeForThis();
if (gDomTreeFile) {
fclose(gDomTreeFile);
gDomTreeFile = 0;
}
#endif
}
void WebViewCore::dumpRenderTree(bool useFile)
{
#ifdef ANDROID_DOM_LOGGING
WTF::CString renderDump = WebCore::externalRepresentation(m_mainFrame).utf8();
const char* data = renderDump.data();
if (useFile) {
gRenderTreeFile = fopen(RENDER_TREE_LOG_FILE, "w");
DUMP_RENDER_LOGD("%s", data);
fclose(gRenderTreeFile);
gRenderTreeFile = 0;
} else {
// adb log can only output 1024 characters, so write out line by line.
// exclude '\n' as adb log adds it for each output.
int length = renderDump.length();
for (int i = 0, last = 0; i < length; i++) {
if (data[i] == '\n') {
if (i != last)
DUMP_RENDER_LOGD("%.*s", (i - last), &(data[last]));
last = i + 1;
}
}
}
#endif
}
HTMLElement* WebViewCore::retrieveElement(int x, int y,
const QualifiedName& tagName)
{
HitTestResult hitTestResult = m_mainFrame->eventHandler()
->hitTestResultAtPoint(IntPoint(x, y), false, false,
DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly,
IntSize(1, 1));
if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) {
ALOGE("Should not happen: no in document Node found");
return 0;
}
const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult();
if (list.isEmpty()) {
ALOGE("Should not happen: no rect-based-test nodes found");
return 0;
}
Node* node = hitTestResult.innerNode();
Node* element = node;
while (element && (!element->isElementNode()
|| !element->hasTagName(tagName))) {
element = element->parentNode();
}
return static_cast<WebCore::HTMLElement*>(element);
}
HTMLAnchorElement* WebViewCore::retrieveAnchorElement(int x, int y)
{
return static_cast<HTMLAnchorElement*>
(retrieveElement(x, y, HTMLNames::aTag));
}
HTMLImageElement* WebViewCore::retrieveImageElement(int x, int y)
{
return static_cast<HTMLImageElement*>
(retrieveElement(x, y, HTMLNames::imgTag));
}
WTF::String WebViewCore::retrieveHref(int x, int y)
{
// TODO: This is expensive, cache
HitTestResult result = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y),
false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(1, 1));
return result.absoluteLinkURL();
}
WTF::String WebViewCore::retrieveAnchorText(int x, int y)
{
WebCore::HTMLAnchorElement* anchor = retrieveAnchorElement(x, y);
return anchor ? anchor->text() : WTF::String();
}
WTF::String WebViewCore::retrieveImageSource(int x, int y)
{
// TODO: This is expensive, cache
HitTestResult result = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y),
false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(1, 1));
return result.absoluteImageURL();
}
WTF::String WebViewCore::requestLabel(WebCore::Frame* frame,
WebCore::Node* node)
{
if (node && validNode(m_mainFrame, frame, node)) {
RefPtr<WebCore::NodeList> list = node->document()->getElementsByTagName("label");
unsigned length = list->length();
for (unsigned i = 0; i < length; i++) {
WebCore::HTMLLabelElement* label = static_cast<WebCore::HTMLLabelElement*>(
list->item(i));
if (label->control() == node) {
Node* node = label;
String result;
while ((node = node->traverseNextNode(label))) {
if (node->isTextNode()) {
Text* textNode = static_cast<Text*>(node);
result += textNode->dataImpl();
}
}
return result;
}
}
}
return WTF::String();
}
static bool isContentEditable(const WebCore::Node* node)
{
if (!node)
return false;
return node->isContentEditable();
}
// Returns true if the node is a textfield, textarea, or contentEditable
static bool isTextInput(const WebCore::Node* node)
{
if (!node)
return false;
if (isContentEditable(node))
return true;
WebCore::RenderObject* renderer = node->renderer();
return renderer && (renderer->isTextField() || renderer->isTextArea());
}
void WebViewCore::revealSelection()
{
WebCore::Node* focus = currentFocus();
if (!focus)
return;
if (!isTextInput(focus))
return;
WebCore::Frame* focusedFrame = focus->document()->frame();
if (!focusedFrame->page()->focusController()->isActive())
return;
focusedFrame->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
}
struct TouchNodeData {
Node* mUrlNode;
Node* mInnerNode;
IntRect mBounds;
};
// get the bounding box of the Node
static IntRect getAbsoluteBoundingBox(Node* node) {
IntRect rect;
RenderObject* render = node->renderer();
if (!render)
return rect;
if (render->isRenderInline())
rect = toRenderInline(render)->linesVisualOverflowBoundingBox();
else if (render->isBox())
rect = toRenderBox(render)->visualOverflowRect();
else if (render->isText())
rect = toRenderText(render)->linesBoundingBox();
else
ALOGE("getAbsoluteBoundingBox failed for node %p, name %s", node, render->renderName());
FloatPoint absPos = render->localToAbsolute(FloatPoint(), false, true);
rect.move(absPos.x(), absPos.y());
return rect;
}
WebCore::Frame* WebViewCore::focusedFrame() const
{
return m_mainFrame->page()->focusController()->focusedOrMainFrame();
}
VisiblePosition WebViewCore::visiblePositionForContentPoint(int x, int y)
{
return visiblePositionForContentPoint(IntPoint(x, y));
}
VisiblePosition WebViewCore::visiblePositionForContentPoint(const IntPoint& point)
{
// Hit test of this kind required for this to work inside input fields
HitTestRequest request(HitTestRequest::Active
| HitTestRequest::MouseMove
| HitTestRequest::ReadOnly
| HitTestRequest::IgnoreClipping);
HitTestResult result(point);
focusedFrame()->document()->renderView()->layer()->hitTest(request, result);
// Matching the logic in MouseEventWithHitTestResults::targetNode()
Node* node = result.innerNode();
if (!node)
return VisiblePosition();
Element* element = node->parentElement();
if (!node->inDocument() && element && element->inDocument())
node = element;
return node->renderer()->positionForPoint(result.localPoint());
}
bool WebViewCore::selectWordAt(int x, int y)
{
HitTestResult hoverResult;
moveMouse(x, y, &hoverResult);
if (hoverResult.innerNode()) {
Node* node = hoverResult.innerNode();
Frame* frame = node->document()->frame();
Page* page = m_mainFrame->document()->page();
page->focusController()->setFocusedFrame(frame);
}
IntPoint point = convertGlobalContentToFrameContent(IntPoint(x, y));
// Hit test of this kind required for this to work inside input fields
HitTestRequest request(HitTestRequest::Active);
HitTestResult result(point);
focusedFrame()->document()->renderView()->layer()->hitTest(request, result);
// Matching the logic in MouseEventWithHitTestResults::targetNode()
Node* node = result.innerNode();
if (!node)
return false;
Element* element = node->parentElement();
if (!node->inDocument() && element && element->inDocument())
node = element;
SelectionController* sc = focusedFrame()->selection();
bool wordSelected = false;
if (!sc->contains(point) && (node->isContentEditable() || node->isTextNode()) && !result.isLiveLink()
&& node->dispatchEvent(Event::create(eventNames().selectstartEvent, true, true))) {
VisiblePosition pos(node->renderer()->positionForPoint(result.localPoint()));
wordSelected = selectWordAroundPosition(node->document()->frame(), pos);
}
return wordSelected;
}
bool WebViewCore::selectWordAroundPosition(Frame* frame, VisiblePosition pos)
{
VisibleSelection selection(pos);
selection.expandUsingGranularity(WordGranularity);
SelectionController* selectionController = frame->selection();
selection = VisibleSelection(selection.start(), selection.end());
bool wordSelected = false;
if (selectionController->shouldChangeSelection(selection)) {
bool allWhitespaces = true;
RefPtr<Range> firstRange = selection.firstRange();
String text = firstRange.get() ? firstRange->text() : "";
for (size_t i = 0; i < text.length(); ++i) {
if (!isSpaceOrNewline(text[i])) {
allWhitespaces = false;
break;
}
}
if (allWhitespaces) {
VisibleSelection emptySelection(pos);
selectionController->setSelection(emptySelection);
} else {
selectionController->setSelection(selection);
wordSelected = true;
}
}
return wordSelected;
}
int WebViewCore::platformLayerIdFromNode(Node* node, LayerAndroid** outLayer)
{
if (!node || !node->renderer())
return -1;
RenderLayer* renderLayer = node->renderer()->enclosingLayer();
while (renderLayer && !renderLayer->isComposited())
renderLayer = renderLayer->parent();
if (!renderLayer || !renderLayer->isComposited())
return -1;
GraphicsLayer* graphicsLayer = renderLayer->backing()->graphicsLayer();
if (!graphicsLayer)
return -1;
GraphicsLayerAndroid* agl = static_cast<GraphicsLayerAndroid*>(graphicsLayer);
LayerAndroid* layer = agl->foregroundLayer();
if (!layer)
layer = agl->contentLayer();
if (!layer)
return -1;
if (outLayer)
*outLayer = layer;
return layer->uniqueId();
}
void WebViewCore::layerToAbsoluteOffset(const LayerAndroid* layer, IntPoint& offset)
{
while (layer) {
const SkPoint& pos = layer->getPosition();
offset.move(pos.fX, pos.fY);
const IntPoint& scroll = layer->getScrollOffset();
offset.move(-scroll.x(), -scroll.y());
layer = static_cast<LayerAndroid*>(layer->getParent());
}
}
void WebViewCore::setSelectionCaretInfo(SelectText* selectTextContainer,
const WebCore::Position& pos, const IntPoint& frameOffset,
SelectText::HandleId handleId, SelectText::HandleType handleType,
int caretRectOffset, EAffinity affinity)
{
Node* node = pos.anchorNode();
LayerAndroid* layer = 0;
int layerId = platformLayerIdFromNode(node, &layer);
selectTextContainer->setCaretLayerId(handleId, layerId);
IntPoint offset = frameOffset;
layerToAbsoluteOffset(layer, offset);
RenderObject* r = node->renderer();
RenderText* renderText = toRenderText(r);
int caretOffset;
InlineBox* inlineBox;
pos.getInlineBoxAndOffset(affinity, inlineBox, caretOffset);
IntRect caretRect = renderText->localCaretRect(inlineBox, caretOffset);
FloatPoint absoluteOffset = renderText->localToAbsolute(caretRect.location());
caretRect.setX(absoluteOffset.x() - offset.x() + caretRectOffset);
caretRect.setY(absoluteOffset.y() - offset.y());
selectTextContainer->setCaretRect(handleId, caretRect);
selectTextContainer->setHandleType(handleId, handleType);
selectTextContainer->setTextRect(handleId,
positionToTextRect(pos, affinity, offset, caretRect));
}
bool WebViewCore::isLtr(const Position& position)
{
InlineBox* inlineBox = 0;
int caretOffset = 0;
position.getInlineBoxAndOffset(DOWNSTREAM, inlineBox, caretOffset);
bool isLtr;
if (inlineBox)
isLtr = inlineBox->isLeftToRightDirection();
else
isLtr = position.primaryDirection() == LTR;
return isLtr;
}
static Node* findInputParent(Node* node)
{
Node* testNode = node;
while (testNode) {
RenderObject* renderer = testNode->renderer();
if (renderer
&& (renderer->isTextArea() || renderer->isTextControl())) {
return testNode;
}
testNode = testNode->parentOrHostNode();
}
return node;
}
SelectText* WebViewCore::createSelectText(const VisibleSelection& selection)
{
bool isCaret = selection.isCaret();
Position base = selection.base();
Position extent = selection.extent();
if (selection.isNone() || (!selection.isContentEditable() && isCaret)
|| !base.anchorNode() || !base.anchorNode()->renderer()
|| !extent.anchorNode() || !extent.anchorNode()->renderer())
return 0;
RefPtr<Range> range = selection.firstRange();
Node* startContainer = range->startContainer();
Node* endContainer = range->endContainer();
if (!startContainer || !endContainer)
return 0;
if (!isCaret && startContainer == endContainer
&& range->startOffset() == range->endOffset())
return 0;
IntPoint frameOffset = convertGlobalContentToFrameContent(IntPoint());
SelectText* selectTextContainer = new SelectText();
if (isCaret) {
setSelectionCaretInfo(selectTextContainer, base, frameOffset,
SelectText::BaseHandle, SelectText::CenterHandle, 0,
selection.affinity());
setSelectionCaretInfo(selectTextContainer, base, frameOffset,
SelectText::ExtentHandle, SelectText::CenterHandle, 0,
selection.affinity());
} else {
bool isBaseLtr = isLtr(base);
bool isBaseStart = comparePositions(base, extent) <= 0;
int baseOffset = isBaseLtr ? 0 : -1;
SelectText::HandleType baseHandleType = (isBaseLtr == isBaseStart)
? SelectText::LeftHandle : SelectText::RightHandle;
EAffinity affinity = selection.affinity();
setSelectionCaretInfo(selectTextContainer, base, frameOffset,
SelectText::BaseHandle, baseHandleType, baseOffset, affinity);
bool isExtentLtr = isLtr(extent);
int extentOffset = isExtentLtr ? 0 : -1;
SelectText::HandleType extentHandleType = (isExtentLtr == isBaseStart)
? SelectText::RightHandle : SelectText::LeftHandle;
setSelectionCaretInfo(selectTextContainer, extent, frameOffset,
SelectText::ExtentHandle, extentHandleType, extentOffset, affinity);
IntRect clipRect;
if (selection.isContentEditable()) {
Node* editable = findInputParent(base.anchorNode());
RenderObject* render = editable->renderer();
if (render && render->isBox() && !render->isBody()) {
RenderBox* renderBox = toRenderBox(render);
clipRect = renderBox->clientBoxRect();
FloatPoint pos = renderBox->localToAbsolute(clipRect.location());
clipRect.setX(pos.x());
clipRect.setY(pos.y());
}
}
Node* stopNode = range->pastLastNode();
for (Node* node = range->firstNode(); node != stopNode; node = node->traverseNextNode()) {
RenderObject* r = node->renderer();
if (!r || !r->isText() || r->style()->visibility() != VISIBLE)
continue;
RenderText* renderText = toRenderText(r);
int startOffset = node == startContainer ? range->startOffset() : 0;
int endOffset = node == endContainer ? range->endOffset() : numeric_limits<int>::max();
LayerAndroid* layer = 0;
int layerId = platformLayerIdFromNode(node, &layer);
Vector<IntRect> rects;
renderText->absoluteRectsForRange(rects, startOffset, endOffset, true);
selectTextContainer->addHighlightRegion(layer, rects, frameOffset,
clipRect);
}
}
selectTextContainer->setText(range->text());
return selectTextContainer;
}
IntRect WebViewCore::positionToTextRect(const Position& position,
EAffinity affinity, const WebCore::IntPoint& offset, const IntRect& caretRect)
{
IntRect textRect = caretRect;
InlineBox* inlineBox;
int offsetIndex;
position.getInlineBoxAndOffset(affinity, inlineBox, offsetIndex);
if (inlineBox && inlineBox->isInlineTextBox()) {
InlineTextBox* box = static_cast<InlineTextBox*>(inlineBox);
RootInlineBox* root = box->root();
RenderText* renderText = box->textRenderer();
int left = root->logicalLeft();
int width = root->logicalWidth();
int top = root->selectionTop();
int height = root->selectionHeight();
if (!renderText->style()->isHorizontalWritingMode()) {
swap(left, top);
swap(width, height);
}
FloatPoint origin(left, top);
FloatPoint absoluteOrigin = renderText->localToAbsolute(origin);
textRect.setX(absoluteOrigin.x() - offset.x());
textRect.setWidth(width);
textRect.setY(absoluteOrigin.y() - offset.y());
textRect.setHeight(height);
}
return textRect;
}
IntPoint WebViewCore::convertGlobalContentToFrameContent(const IntPoint& point, WebCore::Frame* frame)
{
if (!frame) frame = focusedFrame();
IntPoint frameOffset(-m_scrollOffsetX, -m_scrollOffsetY);
frameOffset = frame->view()->windowToContents(frameOffset);
return IntPoint(point.x() + frameOffset.x(), point.y() + frameOffset.y());
}
VisiblePosition WebViewCore::trimSelectionPosition(const VisiblePosition &start,
const VisiblePosition& stop)
{
int direction = comparePositions(start, stop);
if (direction == 0)
return start;
bool forward = direction < 0;
bool move;
VisiblePosition pos = start;
bool movedTooFar = false;
do {
move = true;
Node* node = pos.deepEquivalent().anchorNode();
if (node && node->isTextNode() && node->renderer()) {
RenderText *textRenderer = toRenderText(node->renderer());
move = !textRenderer->textLength();
}
if (move) {
VisiblePosition nextPos = forward ? pos.next() : pos.previous();
movedTooFar = nextPos.isNull() || pos == nextPos
|| ((comparePositions(nextPos, stop) < 0) != forward);
pos = nextPos;
}
} while (move && !movedTooFar);
if (movedTooFar)
pos = stop;
return pos;
}
void WebViewCore::selectText(SelectText::HandleId handleId, int x, int y)
{
SelectionController* sc = focusedFrame()->selection();
VisibleSelection selection = sc->selection();
Position base = selection.base();
Position extent = selection.extent();
IntPoint dragPoint = convertGlobalContentToFrameContent(IntPoint(x, y));
VisiblePosition dragPosition(visiblePositionForContentPoint(dragPoint));
if (base.isNull() || extent.isNull() || dragPosition.isNull())
return;
bool draggingBase = (handleId == SelectText::BaseHandle);
if (draggingBase)
base = dragPosition.deepEquivalent();
else
extent = dragPosition.deepEquivalent();
bool baseIsStart = (comparePositions(base, extent) <= 0);
Position& start = baseIsStart ? base : extent;
Position& end = baseIsStart ? extent : base;
VisiblePosition startPosition(start, selection.affinity());
VisiblePosition endPosition(end, selection.affinity());
bool draggingStart = (baseIsStart == draggingBase);
if (draggingStart) {
if (selection.isRange()) {
startPosition = trimSelectionPosition(startPosition, endPosition);
if ((startPosition != endPosition) && isEndOfBlock(startPosition)) {
// Ensure startPosition is not at end of block
VisiblePosition nextStartPosition(startPosition.next());
if (nextStartPosition.isNotNull())
startPosition = nextStartPosition;
}
}
startPosition = endPosition.honorEditableBoundaryAtOrAfter(startPosition);
if (startPosition.isNull())
return;
start = startPosition.deepEquivalent();
if (selection.isCaret())
end = start;
} else {
if (selection.isRange()) {
endPosition = trimSelectionPosition(endPosition, startPosition);
if ((start != end) && isStartOfBlock(endPosition)) {
// Ensure endPosition is not at start of block
VisiblePosition prevEndPosition(endPosition.previous());
if (!prevEndPosition.isNull())
endPosition = prevEndPosition;
}
}
endPosition = startPosition.honorEditableBoundaryAtOrAfter(endPosition);
if (endPosition.isNull())
return;
end = endPosition.deepEquivalent();
if (selection.isCaret())
start = end;
}
selection = VisibleSelection(base, extent);
// Only allow changes between caret positions or to text selection.
bool selectChangeAllowed = (!selection.isCaret() || sc->isCaret());
if (selectChangeAllowed && sc->shouldChangeSelection(selection))
sc->setSelection(selection);
}
bool WebViewCore::nodeIsClickableOrFocusable(Node* node)
{
if (!node)
return false;
if (node->disabled())
return false;
if (!node->inDocument())
return false;
if (!node->renderer() || node->renderer()->style()->visibility() != VISIBLE)
return false;
return node->supportsFocus()
|| node->hasEventListeners(eventNames().clickEvent)
|| node->hasEventListeners(eventNames().mousedownEvent)
|| node->hasEventListeners(eventNames().mouseupEvent)
|| node->hasEventListeners(eventNames().mouseoverEvent);
}
// get the highlight rectangles for the touch point (x, y) with the slop
AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool doMoveMouse)
{
if (doMoveMouse)
moveMouse(x, y, 0, true);
HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y),
false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(slop, slop));
AndroidHitTestResult androidHitResult(this, hitTestResult);
if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) {
ALOGE("Should not happen: no in document Node found");
return androidHitResult;
}
const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult();
if (list.isEmpty()) {
ALOGE("Should not happen: no rect-based-test nodes found");
return androidHitResult;
}
Frame* frame = hitTestResult.innerNode()->document()->frame();
Vector<TouchNodeData> nodeDataList;
if (hitTestResult.innerNode() != hitTestResult.innerNonSharedNode()
&& hitTestResult.innerNode()->hasTagName(WebCore::HTMLNames::areaTag)) {
HTMLAreaElement* area = static_cast<HTMLAreaElement*>(hitTestResult.innerNode());
androidHitResult.hitTestResult().setURLElement(area);
androidHitResult.highlightRects().append(area->computeRect(
hitTestResult.innerNonSharedNode()->renderer()));
return androidHitResult;
}
ListHashSet<RefPtr<Node> >::const_iterator last = list.end();
for (ListHashSet<RefPtr<Node> >::const_iterator it = list.begin(); it != last; ++it) {
// TODO: it seems reasonable to not search across the frame. Isn't it?
// if the node is not in the same frame as the innerNode, skip it
if (it->get()->document()->frame() != frame)
continue;
// traverse up the tree to find the first node that needs highlight
bool found = false;
Node* eventNode = it->get();
Node* innerNode = eventNode;
while (eventNode) {
RenderObject* render = eventNode->renderer();
if (render && (render->isBody() || render->isRenderView()))
break;
if (nodeIsClickableOrFocusable(eventNode)) {
found = true;
break;
}
// the nodes in the rectBasedTestResult() are ordered based on z-index during hit testing.
// so do not search for the eventNode across explicit z-index border.
// TODO: this is a hard one to call. z-index is quite complicated as its value only
// matters when you compare two RenderLayer in the same hierarchy level. e.g. in
// the following example, "b" is on the top as its z level is the highest. even "c"
// has 100 as z-index, it is still below "d" as its parent has the same z-index as
// "d" and logically before "d". Of course "a" is the lowest in the z level.
//
// z-index:auto "a"
// z-index:2 "b"
// z-index:1
// z-index:100 "c"
// z-index:1 "d"
//
// If the fat point touches everyone, the order in the list should be "b", "d", "c"
// and "a". When we search for the event node for "b", we really don't want "a" as
// in the z-order it is behind everything else.
if (render && !render->style()->hasAutoZIndex())
break;
eventNode = eventNode->parentNode();
}
// didn't find any eventNode, skip it
if (!found)
continue;
// first quick check whether it is a duplicated node before computing bounding box
Vector<TouchNodeData>::const_iterator nlast = nodeDataList.end();
for (Vector<TouchNodeData>::const_iterator n = nodeDataList.begin(); n != nlast; ++n) {
// found the same node, skip it
if (eventNode == n->mUrlNode) {
found = false;
break;
}
}
if (!found)
continue;
// next check whether the node is fully covered by or fully covering another node.
found = false;
IntRect rect = getAbsoluteBoundingBox(eventNode);
if (rect.isEmpty()) {
// if the node's bounds is empty and it is not a ContainerNode, skip it.
if (!eventNode->isContainerNode())
continue;
// if the node's children are all positioned objects, its bounds can be empty.
// Walk through the children to find the bounding box.
Node* child = static_cast<const ContainerNode*>(eventNode)->firstChild();
while (child) {
IntRect childrect;
if (child->renderer())
childrect = getAbsoluteBoundingBox(child);
if (!childrect.isEmpty()) {
rect.unite(childrect);
child = child->traverseNextSibling(eventNode);
} else
child = child->traverseNextNode(eventNode);
}
}
for (int i = nodeDataList.size() - 1; i >= 0; i--) {
TouchNodeData n = nodeDataList.at(i);
// the new node is enclosing an existing node, skip it
if (rect.contains(n.mBounds)) {
found = true;
break;
}
// the new node is fully inside an existing node, remove the existing node
if (n.mBounds.contains(rect))
nodeDataList.remove(i);
}
if (!found) {
TouchNodeData newNode;
newNode.mUrlNode = eventNode;
newNode.mBounds = rect;
newNode.mInnerNode = innerNode;
nodeDataList.append(newNode);
}
}
if (!nodeDataList.size()) {
androidHitResult.searchContentDetectors();
return androidHitResult;
}
// finally select the node with the largest overlap with the fat point
TouchNodeData final;
final.mUrlNode = 0;
IntPoint docPos = frame->view()->windowToContents(m_mousePos);
IntRect testRect(docPos.x() - slop, docPos.y() - slop, 2 * slop + 1, 2 * slop + 1);
int area = 0;
Vector<TouchNodeData>::const_iterator nlast = nodeDataList.end();
for (Vector<TouchNodeData>::const_iterator n = nodeDataList.begin(); n != nlast; ++n) {
IntRect rect = n->mBounds;
rect.intersect(testRect);
int a = rect.width() * rect.height();
if (a > area || !final.mUrlNode) {
final = *n;
area = a;
}
}
// now get the node's highlight rectangles in the page coordinate system
if (final.mUrlNode) {
// Update innerNode and innerNonSharedNode
androidHitResult.hitTestResult().setInnerNode(final.mInnerNode);
androidHitResult.hitTestResult().setInnerNonSharedNode(final.mInnerNode);
if (final.mUrlNode->isElementNode()) {
// We found a URL element. Update the hitTestResult
androidHitResult.setURLElement(static_cast<Element*>(final.mUrlNode));
} else {
androidHitResult.setURLElement(0);
}
Vector<IntRect>& highlightRects = androidHitResult.highlightRects();
if (doMoveMouse && highlightRects.size() > 0) {
// adjust m_mousePos if it is not inside the returned highlight
// rectangles
IntRect foundIntersection;
IntRect inputRect = IntRect(x - slop, y - slop,
slop * 2 + 1, slop * 2 + 1);
for (size_t i = 0; i < highlightRects.size(); i++) {
IntRect& hr = highlightRects[i];
IntRect test = inputRect;
test.intersect(hr);
if (!test.isEmpty()) {
foundIntersection = test;
break;
}
}
if (!foundIntersection.isEmpty() && !foundIntersection.contains(x, y)) {
IntPoint pt = foundIntersection.center();
moveMouse(pt.x(), pt.y(), 0, true);
}
}
} else {
androidHitResult.searchContentDetectors();
}
return androidHitResult;
}
///////////////////////////////////////////////////////////////////////////////
void WebViewCore::addPlugin(PluginWidgetAndroid* w)
{
// SkDebugf("----------- addPlugin %p", w);
/* The plugin must be appended to the end of the array. This ensures that if
the plugin is added while iterating through the array (e.g. sendEvent(...))
that the iteration process is not corrupted.
*/
*m_plugins.append() = w;
}
void WebViewCore::removePlugin(PluginWidgetAndroid* w)
{
// SkDebugf("----------- removePlugin %p", w);
int index = m_plugins.find(w);
if (index < 0) {
SkDebugf("--------------- pluginwindow not found! %p\n", w);
} else {
m_plugins.removeShuffle(index);
}
}
bool WebViewCore::isPlugin(PluginWidgetAndroid* w) const
{
return m_plugins.find(w) >= 0;
}
void WebViewCore::invalPlugin(PluginWidgetAndroid* w)
{
const double PLUGIN_INVAL_DELAY = 1.0 / 60;
if (!m_pluginInvalTimer.isActive()) {
m_pluginInvalTimer.startOneShot(PLUGIN_INVAL_DELAY);
}
}
void WebViewCore::drawPlugins()
{
SkRegion inval; // accumualte what needs to be redrawn
PluginWidgetAndroid** iter = m_plugins.begin();
PluginWidgetAndroid** stop = m_plugins.end();
for (; iter < stop; ++iter) {
PluginWidgetAndroid* w = *iter;
SkIRect dirty;
if (w->isDirty(&dirty)) {
w->draw();
inval.op(dirty, SkRegion::kUnion_Op);
}
}
if (!inval.isEmpty()) {
// inval.getBounds() is our rectangle
const SkIRect& bounds = inval.getBounds();
WebCore::IntRect r(bounds.fLeft, bounds.fTop,
bounds.width(), bounds.height());
this->viewInvalidate(r);
}
}
void WebViewCore::notifyPluginsOnFrameLoad(const Frame* frame) {
// if frame is the parent then notify all plugins
if (!frame->tree()->parent()) {
// trigger an event notifying the plugins that the page has loaded
ANPEvent event;
SkANP::InitEvent(&event, kLifecycle_ANPEventType);
event.data.lifecycle.action = kOnLoad_ANPLifecycleAction;
sendPluginEvent(event);
// trigger the on/off screen notification if the page was reloaded
sendPluginVisibleScreen();
}
// else if frame's parent has completed
else if (!frame->tree()->parent()->loader()->isLoading()) {
// send to all plugins who have this frame in their heirarchy
PluginWidgetAndroid** iter = m_plugins.begin();
PluginWidgetAndroid** stop = m_plugins.end();
for (; iter < stop; ++iter) {
Frame* currentFrame = (*iter)->pluginView()->parentFrame();
while (currentFrame) {
if (frame == currentFrame) {
ANPEvent event;
SkANP::InitEvent(&event, kLifecycle_ANPEventType);
event.data.lifecycle.action = kOnLoad_ANPLifecycleAction;
(*iter)->sendEvent(event);
// trigger the on/off screen notification if the page was reloaded
ANPRectI visibleRect;
getVisibleScreen(visibleRect);
(*iter)->setVisibleScreen(visibleRect, m_scale);
break;
}
currentFrame = currentFrame->tree()->parent();
}
}
}
}
void WebViewCore::getVisibleScreen(ANPRectI& visibleRect)
{
visibleRect.left = m_scrollOffsetX;
visibleRect.top = m_scrollOffsetY;
visibleRect.right = m_scrollOffsetX + m_screenWidth;
visibleRect.bottom = m_scrollOffsetY + m_screenHeight;
}
void WebViewCore::sendPluginVisibleScreen()
{
/* We may want to cache the previous values and only send the notification
to the plugin in the event that one of the values has changed.
*/
ANPRectI visibleRect;
getVisibleScreen(visibleRect);
PluginWidgetAndroid** iter = m_plugins.begin();
PluginWidgetAndroid** stop = m_plugins.end();
for (; iter < stop; ++iter) {
(*iter)->setVisibleScreen(visibleRect, m_scale);
}
}
void WebViewCore::sendPluginSurfaceReady()
{
PluginWidgetAndroid** iter = m_plugins.begin();
PluginWidgetAndroid** stop = m_plugins.end();
for (; iter < stop; ++iter) {
(*iter)->checkSurfaceReady();
}
}
void WebViewCore::sendPluginEvent(const ANPEvent& evt)
{
/* The list of plugins may be manipulated as we iterate through the list.
This implementation allows for the addition of new plugins during an
iteration, but may fail if a plugin is removed. Currently, there are not
any use cases where a plugin is deleted while processing this loop, but
if it does occur we will have to use an alternate data structure and/or
iteration mechanism.
*/
for (int x = 0; x < m_plugins.count(); x++) {
m_plugins[x]->sendEvent(evt);
}
}
PluginWidgetAndroid* WebViewCore::getPluginWidget(NPP npp)
{
PluginWidgetAndroid** iter = m_plugins.begin();
PluginWidgetAndroid** stop = m_plugins.end();
for (; iter < stop; ++iter) {
if ((*iter)->pluginView()->instance() == npp) {
return (*iter);
}
}
return 0;
}
static PluginView* nodeIsPlugin(Node* node) {
RenderObject* renderer = node->renderer();
if (renderer && renderer->isWidget()) {
Widget* widget = static_cast<RenderWidget*>(renderer)->widget();
if (widget && widget->isPluginView())
return static_cast<PluginView*>(widget);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// Update mouse position
void WebViewCore::moveMouse(int x, int y, HitTestResult* hoveredNode, bool isClickCandidate)
{
// mouse event expects the position in the window coordinate
m_mousePos = WebCore::IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY);
if (isClickCandidate)
m_mouseClickPos = m_mousePos;
// validNode will still return true if the node is null, as long as we have
// a valid frame. Do not want to make a call on frame unless it is valid.
WebCore::PlatformMouseEvent mouseEvent(m_mousePos, m_mousePos,
WebCore::NoButton, WebCore::MouseEventMoved, 1, false, false, false,
false, WTF::currentTime());
m_mainFrame->eventHandler()->handleMouseMoveEvent(mouseEvent, hoveredNode);
}
Position WebViewCore::getPositionForOffset(Node* node, int offset)
{
Position start = firstPositionInNode(node);
Position end = lastPositionInNode(node);
Document* document = node->document();
PassRefPtr<Range> range = Range::create(document, start, end);
WebCore::CharacterIterator iterator(range.get());
iterator.advance(offset);
return iterator.range()->startPosition();
}
void WebViewCore::setSelection(Node* node, int start, int end)
{
RenderTextControl* control = toRenderTextControl(node);
if (control)
setSelectionRange(node, start, end);
else {
Position startPosition = getPositionForOffset(node, start);
Position endPosition = getPositionForOffset(node, end);
VisibleSelection selection(startPosition, endPosition);
SelectionController* selector = node->document()->frame()->selection();
selector->setSelection(selection);
}
}
void WebViewCore::setSelection(int start, int end)
{
WebCore::Node* focus = currentFocus();
if (!focus)
return;
if (start > end)
swap(start, end);
// Tell our EditorClient that this change was generated from the UI, so it
// does not need to echo it to the UI.
EditorClientAndroid* client = static_cast<EditorClientAndroid*>(
m_mainFrame->editor()->client());
client->setUiGeneratedSelectionChange(true);
setSelection(focus, start, end);
RenderTextControl* control = toRenderTextControl(focus);
if (start != end && control) {
// Fire a select event. No event is sent when the selection reduces to
// an insertion point
control->selectionChanged(true);
}
client->setUiGeneratedSelectionChange(false);
bool isPasswordField = false;
if (focus->isElementNode()) {
WebCore::Element* element = static_cast<WebCore::Element*>(focus);
if (WebCore::InputElement* inputElement = element->toInputElement())
isPasswordField = static_cast<WebCore::HTMLInputElement*>(inputElement)->isPasswordField();
}
// For password fields, this is done in the UI side via
// bringPointIntoView, since the UI does the drawing.
if ((control && control->isTextArea()) || !isPasswordField)
revealSelection();
}
String WebViewCore::modifySelection(const int direction, const int axis)
{
DOMSelection* selection = m_mainFrame->domWindow()->getSelection();
ASSERT(selection);
// We've seen crashes where selection is null, but we don't know why
// See http://b/5244036
if (!selection)
return String();
if (selection->rangeCount() > 1)
selection->removeAllRanges();
switch (axis) {
case AXIS_CHARACTER:
case AXIS_WORD:
case AXIS_SENTENCE:
return modifySelectionTextNavigationAxis(selection, direction, axis);
case AXIS_HEADING:
case AXIS_SIBLING:
case AXIS_PARENT_FIRST_CHILD:
case AXIS_DOCUMENT:
return modifySelectionDomNavigationAxis(selection, direction, axis);
default:
ALOGE("Invalid navigation axis: %d", axis);
return String();
}
}
void WebViewCore::scrollNodeIntoView(Frame* frame, Node* node)
{
if (!frame || !node)
return;
Element* elementNode = 0;
// If not an Element, find a visible predecessor
// Element to scroll into view.
if (!node->isElementNode()) {
HTMLElement* body = frame->document()->body();
do {
if (node == body)
return;
node = node->parentNode();
} while (node && !node->isElementNode() && !isVisible(node));
}
// Couldn't find a visible predecessor.
if (!node)
return;
elementNode = static_cast<Element*>(node);
elementNode->scrollIntoViewIfNeeded(true);
}
String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int axis)
{
Node* body = m_mainFrame->document()->body();
ExceptionCode ec = 0;
String markup;
// initialize the selection if necessary
if (selection->rangeCount() == 0) {
if (m_currentNodeDomNavigationAxis
&& validNode(m_mainFrame,
m_mainFrame, m_currentNodeDomNavigationAxis)) {
RefPtr<Range> rangeRef =
selection->frame()->document()->createRange();
rangeRef->selectNode(m_currentNodeDomNavigationAxis, ec);
m_currentNodeDomNavigationAxis = 0;
if (ec)
return String();
selection->addRange(rangeRef.get());
} else if (currentFocus()) {
selection->setPosition(currentFocus(), 0, ec);
} else {
selection->setPosition(body, 0, ec);
}
if (ec)
return String();
}
// collapse the selection
if (direction == DIRECTION_FORWARD)
selection->collapseToEnd(ec);
else
selection->collapseToStart(ec);
if (ec)
return String();
// Make sure the anchor node is a text node since we are generating
// the markup of the selection which includes the anchor, the focus,
// and any crossed nodes. Forcing the condition that the selection
// starts and ends on text nodes guarantees symmetric selection markup.
// Also this way the text content, rather its container, is highlighted.
Node* anchorNode = selection->anchorNode();
if (anchorNode->isElementNode()) {
// Collapsed selection while moving forward points to the
// next unvisited node and while moving backward to the
// last visited node.
if (direction == DIRECTION_FORWARD)
advanceAnchorNode(selection, direction, markup, false, ec);
else
advanceAnchorNode(selection, direction, markup, true, ec);
if (ec)
return String();
if (!markup.isEmpty())
return markup;
}
// If the selection is at the end of a non white space text move
// it to the next visible text node with non white space content.
// This is a workaround for the selection getting stuck.
anchorNode = selection->anchorNode();
if (anchorNode->isTextNode()) {
if (direction == DIRECTION_FORWARD) {
String suffix = anchorNode->textContent().substring(
selection->anchorOffset(), caretMaxOffset(anchorNode));
// If at the end of non white space text we advance the
// anchor node to either an input element or non empty text.
if (suffix.stripWhiteSpace().isEmpty()) {
advanceAnchorNode(selection, direction, markup, true, ec);
}
} else {
String prefix = anchorNode->textContent().substring(0,
selection->anchorOffset());
// If at the end of non white space text we advance the
// anchor node to either an input element or non empty text.
if (prefix.stripWhiteSpace().isEmpty()) {
advanceAnchorNode(selection, direction, markup, true, ec);
}
}
if (ec)
return String();
if (!markup.isEmpty())
return markup;
}
// extend the selection
String directionStr;
if (direction == DIRECTION_FORWARD)
directionStr = "forward";
else
directionStr = "backward";
String axisStr;
if (axis == AXIS_CHARACTER)
axisStr = "character";
else if (axis == AXIS_WORD)
axisStr = "word";
else
axisStr = "sentence";
selection->modify("extend", directionStr, axisStr);
// Make sure the focus node is a text node in order to have the
// selection generate symmetric markup because the latter
// includes all nodes crossed by the selection. Also this way
// the text content, rather its container, is highlighted.
Node* focusNode = selection->focusNode();
if (focusNode->isElementNode()) {
focusNode = getImplicitBoundaryNode(selection->focusNode(),
selection->focusOffset(), direction);
if (!focusNode)
return String();
if (direction == DIRECTION_FORWARD) {
focusNode = focusNode->traversePreviousSiblingPostOrder(body);
if (focusNode && !isContentTextNode(focusNode)) {
Node* textNode = traverseNextContentTextNode(focusNode,
anchorNode, DIRECTION_BACKWARD);
if (textNode)
anchorNode = textNode;
}
if (focusNode && isContentTextNode(focusNode)) {
selection->extend(focusNode, caretMaxOffset(focusNode), ec);
if (ec)
return String();
}
} else {
focusNode = focusNode->traverseNextSibling();
if (focusNode && !isContentTextNode(focusNode)) {
Node* textNode = traverseNextContentTextNode(focusNode,
anchorNode, DIRECTION_FORWARD);
if (textNode)
anchorNode = textNode;
}
if (anchorNode && isContentTextNode(anchorNode)) {
selection->extend(focusNode, 0, ec);
if (ec)
return String();
}
}
}
// Enforce that the selection does not cross anchor boundaries. This is
// a workaround for the asymmetric behavior of WebKit while crossing
// anchors.
anchorNode = getImplicitBoundaryNode(selection->anchorNode(),
selection->anchorOffset(), direction);
focusNode = getImplicitBoundaryNode(selection->focusNode(),
selection->focusOffset(), direction);
if (anchorNode && focusNode && anchorNode != focusNode) {
Node* inputControl = getIntermediaryInputElement(anchorNode, focusNode,
direction);
if (inputControl) {
if (direction == DIRECTION_FORWARD) {
if (isDescendantOf(inputControl, anchorNode)) {
focusNode = inputControl;
} else {
focusNode = inputControl->traversePreviousSiblingPostOrder(
body);
if (!focusNode)
focusNode = inputControl;
}
// We prefer a text node contained in the input element.
if (!isContentTextNode(focusNode)) {
Node* textNode = traverseNextContentTextNode(focusNode,
anchorNode, DIRECTION_BACKWARD);
if (textNode)
focusNode = textNode;
}
// If we found text in the input select it.
// Otherwise, select the input element itself.
if (isContentTextNode(focusNode)) {
selection->extend(focusNode, caretMaxOffset(focusNode), ec);
} else if (anchorNode != focusNode) {
// Note that the focusNode always has parent and that
// the offset can be one more that the index of the last
// element - this is how WebKit selects such elements.
selection->extend(focusNode->parentNode(),
focusNode->nodeIndex() + 1, ec);
}
if (ec)
return String();
} else {
if (isDescendantOf(inputControl, anchorNode)) {
focusNode = inputControl;
} else {
focusNode = inputControl->traverseNextSibling();
if (!focusNode)
focusNode = inputControl;
}
// We prefer a text node contained in the input element.
if (!isContentTextNode