diff options
Diffstat (limited to 'Source')
204 files changed, 48905 insertions, 0 deletions
diff --git a/Source/WebKit/Android.mk b/Source/WebKit/Android.mk new file mode 100644 index 0000000..5998227 --- /dev/null +++ b/Source/WebKit/Android.mk @@ -0,0 +1,132 @@ +## +## +## Copyright 2008, The Android Open Source Project +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## + +LOCAL_SRC_FILES := \ + android/WebCoreSupport/CachedFramePlatformDataAndroid.cpp \ + android/WebCoreSupport/ChromeClientAndroid.cpp \ + android/WebCoreSupport/ContextMenuClientAndroid.cpp \ + android/WebCoreSupport/DeviceMotionClientAndroid.cpp \ + android/WebCoreSupport/DeviceOrientationClientAndroid.cpp \ + android/WebCoreSupport/DragClientAndroid.cpp \ + android/WebCoreSupport/EditorClientAndroid.cpp \ + android/WebCoreSupport/FrameLoaderClientAndroid.cpp \ + android/WebCoreSupport/FrameNetworkingContextAndroid.cpp \ + android/WebCoreSupport/GeolocationPermissions.cpp \ + android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp \ + android/WebCoreSupport/MemoryUsage.cpp \ + android/WebCoreSupport/PlatformBridge.cpp \ + android/WebCoreSupport/ResourceLoaderAndroid.cpp \ + android/WebCoreSupport/UrlInterceptResponse.cpp \ + android/WebCoreSupport/V8Counters.cpp + +ifeq ($(HTTP_STACK),chrome) +LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ + android/WebCoreSupport/ChromiumInit.cpp \ + android/WebCoreSupport/CacheResult.cpp \ + android/WebCoreSupport/WebCache.cpp \ + android/WebCoreSupport/WebCookieJar.cpp \ + android/WebCoreSupport/WebUrlLoader.cpp \ + android/WebCoreSupport/WebUrlLoaderClient.cpp \ + android/WebCoreSupport/WebRequest.cpp \ + android/WebCoreSupport/WebRequestContext.cpp \ + android/WebCoreSupport/WebResourceRequest.cpp \ + android/WebCoreSupport/WebResponse.cpp \ + android/WebCoreSupport/WebViewClientError.cpp +endif # HTTP_STACK == chrome + +LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ + android/RenderSkinAndroid.cpp \ + android/RenderSkinButton.cpp \ + android/RenderSkinCombo.cpp \ + android/RenderSkinMediaButton.cpp \ + android/RenderSkinNinePatch.cpp \ + android/RenderSkinRadio.cpp \ + android/TimeCounter.cpp \ + \ + android/benchmark/Intercept.cpp \ + android/benchmark/MyJavaVM.cpp \ + \ + android/icu/unicode/ucnv.cpp \ + \ + android/jni/CacheManager.cpp \ + android/jni/CookieManager.cpp \ + android/jni/DeviceMotionAndOrientationManager.cpp \ + android/jni/DeviceMotionClientImpl.cpp \ + android/jni/DeviceOrientationClientImpl.cpp \ + android/jni/GeolocationPermissionsBridge.cpp \ + android/jni/JavaBridge.cpp \ + android/jni/JavaSharedClient.cpp \ + android/jni/JniUtil.cpp \ + android/jni/MIMETypeRegistry.cpp \ + android/jni/MockGeolocation.cpp \ + android/jni/PictureSet.cpp \ + android/jni/WebCoreFrameBridge.cpp \ + android/jni/WebCoreJni.cpp \ + android/jni/WebCoreResourceLoader.cpp \ + android/jni/WebFrameView.cpp \ + android/jni/WebHistory.cpp \ + android/jni/WebIconDatabase.cpp \ + android/jni/WebStorage.cpp \ + android/jni/WebSettings.cpp \ + android/jni/WebViewCore.cpp \ + \ + android/nav/CacheBuilder.cpp \ + android/nav/CachedColor.cpp \ + android/nav/CachedFrame.cpp \ + android/nav/CachedHistory.cpp \ + android/nav/CachedInput.cpp \ + android/nav/CachedLayer.cpp \ + android/nav/CachedNode.cpp \ + android/nav/CachedRoot.cpp \ + android/nav/FindCanvas.cpp \ + android/nav/SelectText.cpp \ + android/nav/WebView.cpp \ + \ + android/plugins/ANPBitmapInterface.cpp \ + android/plugins/ANPCanvasInterface.cpp \ + android/plugins/ANPEventInterface.cpp \ + android/plugins/ANPLogInterface.cpp \ + android/plugins/ANPMatrixInterface.cpp \ + android/plugins/ANPOpenGLInterface.cpp \ + android/plugins/ANPPaintInterface.cpp \ + android/plugins/ANPPathInterface.cpp \ + android/plugins/ANPSoundInterface.cpp \ + android/plugins/ANPSurfaceInterface.cpp \ + android/plugins/ANPSystemInterface.cpp \ + android/plugins/ANPTypefaceInterface.cpp \ + android/plugins/ANPVideoInterface.cpp \ + android/plugins/ANPWindowInterface.cpp \ + android/plugins/PluginDebugAndroid.cpp \ + android/plugins/PluginTimer.cpp \ + android/plugins/PluginViewBridgeAndroid.cpp \ + android/plugins/PluginWidgetAndroid.cpp \ + android/plugins/SkANP.cpp \ + \ + android/wds/Command.cpp \ + android/wds/Connection.cpp \ + android/wds/DebugServer.cpp + +# Needed for autofill. +ifeq ($(ENABLE_AUTOFILL),true) +LOCAL_CFLAGS += -DENABLE_WEB_AUTOFILL + +LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ + android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp \ + android/WebCoreSupport/autofill/FormFieldAndroid.cpp \ + android/WebCoreSupport/autofill/FormManagerAndroid.cpp \ + android/WebCoreSupport/autofill/WebAutoFill.cpp +endif # ENABLE_AUTOFILL == true diff --git a/Source/WebKit/android/AndroidLog.h b/Source/WebKit/android/AndroidLog.h new file mode 100644 index 0000000..a69dce6 --- /dev/null +++ b/Source/WebKit/android/AndroidLog.h @@ -0,0 +1,48 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef ANDROIDLOG_H_ +#define ANDROIDLOG_H_ + +#ifdef ANDROID_DOM_LOGGING +#include <stdio.h> +extern FILE* gDomTreeFile; +#define DOM_TREE_LOG_FILE "/sdcard/domTree.txt" +#define DUMP_DOM_LOGD(...) { if (gDomTreeFile) \ + fprintf(gDomTreeFile, __VA_ARGS__); else LOGD(__VA_ARGS__); } + +extern FILE* gRenderTreeFile; +#define RENDER_TREE_LOG_FILE "/sdcard/renderTree.txt" +#define DUMP_RENDER_LOGD(...) { if (gRenderTreeFile) \ + fprintf(gRenderTreeFile, __VA_ARGS__); else LOGD(__VA_ARGS__); } +#else +#define DUMP_DOM_LOGD(...) ((void)0) +#define DUMP_RENDER_LOGD(...) ((void)0) +#endif /* ANDROID_DOM_LOGGING */ + +#define DISPLAY_TREE_LOG_FILE "/sdcard/displayTree.txt" +#define LAYERS_TREE_LOG_FILE "/sdcard/layersTree.plist" + +#endif /* ANDROIDLOG_H_ */ diff --git a/Source/WebKit/android/JavaVM/jni.h b/Source/WebKit/android/JavaVM/jni.h new file mode 100644 index 0000000..da02603 --- /dev/null +++ b/Source/WebKit/android/JavaVM/jni.h @@ -0,0 +1,32 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef _JNI_COVER_H_ +#define _JNI_COVER_H_ + +#include "nativehelper/jni.h" +#define AttachCurrentThread(a, b) AttachCurrentThread((JNIEnv**) a, b) + +#endif diff --git a/Source/WebKit/android/RenderSkinAndroid.cpp b/Source/WebKit/android/RenderSkinAndroid.cpp new file mode 100644 index 0000000..9383a9c --- /dev/null +++ b/Source/WebKit/android/RenderSkinAndroid.cpp @@ -0,0 +1,73 @@ +/* + * 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 "WebCore" + +#include "config.h" +#include "RenderSkinAndroid.h" +#include "RenderSkinButton.h" +#include "RenderSkinCombo.h" +#include "RenderSkinMediaButton.h" +#include "RenderSkinRadio.h" +#include "SkImageDecoder.h" + +#include "utils/AssetManager.h" +#include "utils/Asset.h" + +namespace WebCore { + +RenderSkinAndroid::~RenderSkinAndroid() +{ + delete m_button; +} +RenderSkinAndroid::RenderSkinAndroid(android::AssetManager* am, String drawableDirectory) +{ + m_button = new RenderSkinButton(am, drawableDirectory); + RenderSkinCombo::Init(am, drawableDirectory); + RenderSkinMediaButton::Init(am, drawableDirectory); + RenderSkinRadio::Init(am, drawableDirectory); +} + +bool RenderSkinAndroid::DecodeBitmap(android::AssetManager* am, const char* fileName, SkBitmap* bitmap) +{ + android::Asset* asset = am->open(fileName, android::Asset::ACCESS_BUFFER); + if (!asset) { + asset = am->openNonAsset(fileName, android::Asset::ACCESS_BUFFER); + if (!asset) { + LOGD("RenderSkinAndroid: File \"%s\" not found.\n", fileName); + return false; + } + } + + bool success = SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(), bitmap); + if (!success) { + LOGD("RenderSkinAndroid: Failed to decode %s\n", fileName); + } + + delete asset; + return success; +} + +} // namespace WebCore diff --git a/Source/WebKit/android/RenderSkinAndroid.h b/Source/WebKit/android/RenderSkinAndroid.h new file mode 100644 index 0000000..73773ea --- /dev/null +++ b/Source/WebKit/android/RenderSkinAndroid.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef RenderSkinAndroid_h +#define RenderSkinAndroid_h + +#include "PlatformString.h" + +namespace android { + class AssetManager; +} + +class SkBitmap; + +namespace WebCore { +class Node; +class RenderSkinButton; + +class RenderSkinAndroid +{ +public: + enum State { + kDisabled, + kNormal, + kFocused, + kPressed, + + kNumStates + }; + + /** + * Initialize the Android skinning system. The AssetManager may be used to find resources used + * in rendering. + */ + RenderSkinAndroid(android::AssetManager*, String drawableDirectory); + ~RenderSkinAndroid(); + + /* DecodeBitmap determines which file to use, with the given fileName of the form + * "images/bitmap.png", and uses the asset manager to select the exact one. It + * returns true if it successfully decoded the bitmap, false otherwise. + */ + static bool DecodeBitmap(android::AssetManager* am, const char* fileName, SkBitmap* bitmap); + + const RenderSkinButton* renderSkinButton() const { return m_button; } + +private: + RenderSkinButton* m_button; +}; + +} // WebCore + +#endif diff --git a/Source/WebKit/android/RenderSkinButton.cpp b/Source/WebKit/android/RenderSkinButton.cpp new file mode 100644 index 0000000..6a0ae54 --- /dev/null +++ b/Source/WebKit/android/RenderSkinButton.cpp @@ -0,0 +1,90 @@ +/* + * 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 "WebCore" + +#include "config.h" +#include "android_graphics.h" +#include "Document.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinButton.h" +#include "RenderSkinNinePatch.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include "SkRect.h" +#include <utils/Asset.h> +#include <utils/AssetManager.h> +#include <utils/Debug.h> +#include <utils/Log.h> +#include <utils/ResourceTypes.h> +#include <wtf/text/CString.h> + +static const char* gFiles[] = { + "btn_default_disabled_holo.9.png", + "btn_default_normal_holo.9.png", + "btn_default_focused_holo.9.png", + "btn_default_pressed_holo.9.png" + }; + +namespace WebCore { + +RenderSkinButton::RenderSkinButton(android::AssetManager* am, String drawableDirectory) +{ + m_decoded = true; + for (size_t i = 0; i < 4; i++) { + String path = String(drawableDirectory.impl()); + path.append(String(gFiles[i])); + if (!RenderSkinNinePatch::decodeAsset(am, path.utf8().data(), &m_buttons[i])) { + m_decoded = false; + LOGE("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw"); + return; + } + } + + // Ensure our enums properly line up with our arrays. + android::CompileTimeAssert<(RenderSkinAndroid::kDisabled == 0)> a1; + android::CompileTimeAssert<(RenderSkinAndroid::kNormal == 1)> a2; + android::CompileTimeAssert<(RenderSkinAndroid::kFocused == 2)> a3; + android::CompileTimeAssert<(RenderSkinAndroid::kPressed == 3)> a4; +} + +void RenderSkinButton::draw(SkCanvas* canvas, const IntRect& r, + RenderSkinAndroid::State newState) const +{ + // If we failed to decode, do nothing. This way the browser still works, + // and webkit will still draw the label and layout space for us. + if (!m_decoded) { + return; + } + + // Ensure that the state is within the valid range of our array. + SkASSERT(static_cast<unsigned>(newState) < + static_cast<unsigned>(RenderSkinAndroid::kNumStates)); + + RenderSkinNinePatch::DrawNinePatch(canvas, SkRect(r), m_buttons[newState]); +} + +} //WebCore diff --git a/Source/WebKit/android/RenderSkinButton.h b/Source/WebKit/android/RenderSkinButton.h new file mode 100644 index 0000000..e9db74c --- /dev/null +++ b/Source/WebKit/android/RenderSkinButton.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef RenderSkinButton_h +#define RenderSkinButton_h + +#include "RenderSkinAndroid.h" +#include "RenderSkinNinePatch.h" + +class SkCanvas; + +namespace WebCore { +class IntRect; + +class RenderSkinButton { +public: + /** + * Initialize the class before use. Uses the AssetManager to initialize any + * bitmaps the class may use. + */ + RenderSkinButton(android::AssetManager*, String drawableDirectory); + /** + * Draw the skin to the canvas, using the rectangle for its bounds and the + * State to determine which skin to use, i.e. focused or not focused. + */ + void draw(SkCanvas* , const IntRect& , RenderSkinAndroid::State) const; +private: + bool m_decoded; + NinePatch m_buttons[4]; +}; + +} // WebCore +#endif diff --git a/Source/WebKit/android/RenderSkinCombo.cpp b/Source/WebKit/android/RenderSkinCombo.cpp new file mode 100644 index 0000000..b30dc29 --- /dev/null +++ b/Source/WebKit/android/RenderSkinCombo.cpp @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#include "config.h" +#include "RenderSkinCombo.h" + +#include "Document.h" +#include "Element.h" +#include "Node.h" +#include "NodeRenderStyle.h" +#include "RenderStyle.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include <wtf/text/CString.h> + +namespace WebCore { + +// Indicates if the entire asset is being drawn, or if the border is being +// excluded and just the arrow drawn. +enum BorderStyle { + FullAsset, + NoBorder +}; + +// There are 2.5 different concepts of a 'border' here, which results +// in rather a lot of magic constants. In each case, there are 2 +// numbers, one for medium res and one for high-res. All sizes are in pixels. + +// Firstly, we have the extra padding that webkit needs to know about, +// which defines how much bigger this element is made by the +// asset. This is actually a bit broader than the actual border on the +// asset, to make things look less cramped. The border is the same +// width on all sides, except on the right when it's significantly +// wider to allow for the arrow. +const int RenderSkinCombo::arrowMargin[2] = {22, 34}; +const int RenderSkinCombo::padMargin[2] = {2, 5}; + +// Then we have the borders used for the 9-patch stretch. The +// rectangle at the centre of these borders is entirely below and to +// the left of the arrow in the asset. Hence the border widths are the +// same for the bottom and left, but are different for the top. The +// right hand border width happens to be the same as arrowMargin +// defined above. +static const int stretchMargin[2] = {3, 5}; // border width for the bottom and left of the 9-patch +static const int stretchTop[2] = {15, 23}; // border width for the top of the 9-patch + +// Finally, if the border is defined by the CSS, we only draw the +// arrow and not the border. We do this by drawing the relevant subset +// of the bitmap, which must now be precisely determined by what's in +// the asset with no extra padding to make things look properly +// spaced. The border to remove at the top, right and bottom of the +// image is the same as stretchMargin above, but we need to know the width +// of the arrow. +static const int arrowWidth[2] = {22, 31}; + +RenderSkinCombo::Resolution RenderSkinCombo::resolution = MedRes; + +const SkIRect RenderSkinCombo::margin[2][2] = {{{ stretchMargin[MedRes], stretchTop[MedRes], + RenderSkinCombo::arrowMargin[MedRes] + stretchMargin[MedRes], stretchMargin[MedRes] }, + {0, stretchTop[MedRes], 0, stretchMargin[MedRes]}}, + {{ stretchMargin[HighRes], stretchTop[HighRes], + RenderSkinCombo::arrowMargin[HighRes] + stretchMargin[HighRes], stretchMargin[HighRes] }, + {0, stretchTop[HighRes], 0, stretchMargin[HighRes]}}}; +static SkBitmap bitmaps[2][2]; // Collection of assets for a combo box +static bool isDecoded; // True if all assets were decoded + +void RenderSkinCombo::Init(android::AssetManager* am, String drawableDirectory) +{ + if (isDecoded) + return; + + if (drawableDirectory[drawableDirectory.length() - 5] == 'h') + resolution = HighRes; + + isDecoded = RenderSkinAndroid::DecodeBitmap(am, (drawableDirectory + "combobox_nohighlight.png").utf8().data(), &bitmaps[kNormal][FullAsset]); + isDecoded &= RenderSkinAndroid::DecodeBitmap(am, (drawableDirectory + "combobox_disabled.png").utf8().data(), &bitmaps[kDisabled][FullAsset]); + + int width = bitmaps[kNormal][FullAsset].width(); + int height = bitmaps[kNormal][FullAsset].height(); + SkIRect subset; + subset.set(width - arrowWidth[resolution], 0, width, height); + bitmaps[kNormal][FullAsset].extractSubset(&bitmaps[kNormal][NoBorder], subset); + bitmaps[kDisabled][FullAsset].extractSubset(&bitmaps[kDisabled][NoBorder], subset); +} + + +bool RenderSkinCombo::Draw(SkCanvas* canvas, Node* element, int x, int y, int width, int height) +{ + if (!isDecoded) + return true; + + State state = (element->isElementNode() && static_cast<Element*>(element)->isEnabledFormControl()) ? kNormal : kDisabled; + height = std::max(height, (stretchMargin[resolution]<<1) + 1); + + SkRect bounds; + BorderStyle drawBorder = FullAsset; + + bounds.set(SkIntToScalar(x+1), SkIntToScalar(y+1), SkIntToScalar(x + width-1), SkIntToScalar(y + height-1)); + RenderStyle* style = element->renderStyle(); + SkPaint paint; + paint.setColor(style->visitedDependentColor(CSSPropertyBackgroundColor).rgb()); + canvas->drawRect(bounds, paint); + + bounds.set(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + width), SkIntToScalar(y + height)); + + // If this is an appearance where RenderTheme::paint returns true + // without doing anything, this means that + // RenderBox::PaintBoxDecorationWithSize will end up painting the + // border, so we shouldn't paint a border here. + if (style->appearance() == MenulistButtonPart || + style->appearance() == ListboxPart || + style->appearance() == TextFieldPart || + style->appearance() == TextAreaPart) { + bounds.fLeft += SkIntToScalar(width - RenderSkinCombo::extraWidth()); + bounds.fRight -= SkIntToScalar(style->borderRightWidth()); + bounds.fTop += SkIntToScalar(style->borderTopWidth()); + bounds.fBottom -= SkIntToScalar(style->borderBottomWidth()); + drawBorder = NoBorder; + } + SkNinePatch::DrawNine(canvas, bounds, bitmaps[state][drawBorder], margin[resolution][drawBorder]); + return false; +} + +} //WebCore diff --git a/Source/WebKit/android/RenderSkinCombo.h b/Source/WebKit/android/RenderSkinCombo.h new file mode 100644 index 0000000..38cd048 --- /dev/null +++ b/Source/WebKit/android/RenderSkinCombo.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#ifndef RenderSkinCombo_h +#define RenderSkinCombo_h + +#include "RenderSkinAndroid.h" +#include "SkRect.h" + +class SkCanvas; + +namespace WebCore { + +// This is very similar to RenderSkinButton - maybe they should be the same class? +class RenderSkinCombo : public RenderSkinAndroid +{ +public: + /** + * Initialize the class before use. Uses the AssetManager to initialize any bitmaps the class may use. + */ + static void Init(android::AssetManager*, String drawableDirectory); + + /** + * Draw the provided Node on the SkCanvas, using the dimensions provided by + * x,y,w,h. Return true if we did not draw, and WebKit needs to draw it, + * false otherwise. + */ + static bool Draw(SkCanvas* , Node* , int x, int y, int w, int h); + + // The image is wider than the RenderObject, so this accounts for that. + static int extraWidth() { return arrowMargin[resolution]; } + static int padding() { return padMargin[resolution]; } + + enum Resolution { + MedRes, + HighRes + }; +private: + static Resolution resolution; + const static int arrowMargin[2]; + const static int padMargin[2]; + const static SkIRect margin[2][2]; +}; + +} // WebCore + +#endif diff --git a/Source/WebKit/android/RenderSkinMediaButton.cpp b/Source/WebKit/android/RenderSkinMediaButton.cpp new file mode 100644 index 0000000..090d55e --- /dev/null +++ b/Source/WebKit/android/RenderSkinMediaButton.cpp @@ -0,0 +1,215 @@ +/* + * Copyright 2010, 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 "WebCore" + +#include "config.h" +#include "android_graphics.h" +#include "Document.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderObject.h" +#include "RenderSkinMediaButton.h" +#include "RenderSlider.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include "SkRect.h" +#include <utils/Debug.h> +#include <utils/Log.h> +#include <wtf/text/CString.h> + +struct PatchData { + const char* name; + int8_t outset, margin; +}; + +static const PatchData gFiles[] = + { + { "scrubber_primary_holo.9.png", 0, 0 }, // SLIDER_TRACK, left of the SLIDER_THUMB + { "ic_media_pause.png", 0, 0}, // PAUSE + { "ic_media_play.png", 0, 0 }, // PLAY + { "ic_media_pause.png", 0, 0 }, // MUTE + { "ic_media_rew.png", 0, 0 }, // REWIND + { "ic_media_ff.png", 0, 0 }, // FORWARD + { "ic_media_fullscreen.png", 0, 0 }, // FULLSCREEN + { "spinner_76_outer_holo.png", 0, 0 }, // SPINNER_OUTER + { "spinner_76_inner_holo.png", 0, 0 }, // SPINNER_INNER + { "ic_media_video_poster.png", 0, 0 }, // VIDEO + { "btn_media_player_disabled.9.png", 0, 0 }, // BACKGROUND_SLIDER + { "scrubber_track_holo_dark.9.png", 0, 0 }, // SLIDER_TRACK + { "scrubber_control_holo.png", 0, 0 } // SLIDER_THUMB + }; + +static SkBitmap gButton[sizeof(gFiles)/sizeof(gFiles[0])]; +static bool gDecoded; +static bool gHighRes; + +namespace WebCore { + +void RenderSkinMediaButton::Init(android::AssetManager* am, String drawableDirectory) +{ + static bool gInited; + if (gInited) + return; + + gInited = true; + gDecoded = true; + gHighRes = drawableDirectory[drawableDirectory.length() - 5] == 'h'; + for (size_t i = 0; i < sizeof(gFiles)/sizeof(gFiles[0]); i++) { + String path = drawableDirectory + gFiles[i].name; + if (!RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &gButton[i])) { + gDecoded = false; + LOGD("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw"); + break; + } + } +} + +void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonType, + bool translucent, RenderObject* o) +{ + // If we failed to decode, do nothing. This way the browser still works, + // and webkit will still draw the label and layout space for us. + if (!gDecoded) { + return; + } + + bool drawsNinePatch = false; + bool drawsImage = true; + bool drawsBackgroundColor = true; + + int ninePatchIndex = 0; + int imageIndex = 0; + + SkRect bounds(r); + SkScalar imageMargin = 8; + SkPaint paint; + + int alpha = 255; + if (translucent) + alpha = 190; + + SkColor backgroundColor = SkColorSetARGB(alpha, 34, 34, 34); + SkColor trackBackgroundColor = SkColorSetARGB(255, 100, 100, 100); + paint.setColor(backgroundColor); + paint.setFlags(SkPaint::kFilterBitmap_Flag); + + switch (buttonType) { + case PAUSE: + case PLAY: + case MUTE: + case REWIND: + case FORWARD: + case FULLSCREEN: + { + imageIndex = buttonType + 1; + paint.setColor(backgroundColor); + break; + } + case SPINNER_OUTER: + case SPINNER_INNER: + case VIDEO: + { + drawsBackgroundColor = false; + imageIndex = buttonType + 1; + break; + } + case BACKGROUND_SLIDER: + { + drawsBackgroundColor = false; + drawsImage = false; + break; + } + case SLIDER_TRACK: + { + drawsNinePatch = true; + drawsImage = false; + ninePatchIndex = buttonType + 1; + break; + } + case SLIDER_THUMB: + { + drawsBackgroundColor = false; + imageMargin = 0; + imageIndex = buttonType + 1; + break; + } + default: + return; + } + + if (drawsBackgroundColor) { + canvas->drawRect(r, paint); + } + + if (drawsNinePatch) { + const PatchData& pd = gFiles[ninePatchIndex]; + int marginValue = pd.margin + pd.outset; + + SkIRect margin; + margin.set(marginValue, marginValue, marginValue, marginValue); + if (buttonType == SLIDER_TRACK) { + // Cut the height in half (with some extra slop determined by trial + // and error to get the placement just right. + SkScalar quarterHeight = SkScalarHalf(SkScalarHalf(bounds.height())); + bounds.fTop += quarterHeight + SkScalarHalf(3); + bounds.fBottom += -quarterHeight + SK_ScalarHalf; + if (o && o->isSlider()) { + RenderSlider* slider = toRenderSlider(o); + IntRect thumb = slider->thumbRect(); + // Inset the track by half the width of the thumb, so the track + // does not appear to go beyond the space where the thumb can + // be. + SkScalar thumbHalfWidth = SkIntToScalar(thumb.width()/2); + bounds.fLeft += thumbHalfWidth; + bounds.fRight -= thumbHalfWidth; + if (thumb.x() > 0) { + // The video is past the starting point. Show the area to + // left of the thumb as having been played. + SkScalar alreadyPlayed = SkIntToScalar(thumb.center().x() + r.x()); + SkRect playedRect(bounds); + playedRect.fRight = alreadyPlayed; + SkNinePatch::DrawNine(canvas, playedRect, gButton[0], margin); + bounds.fLeft = alreadyPlayed; + } + + } + } + SkNinePatch::DrawNine(canvas, bounds, gButton[ninePatchIndex], margin); + } + + if (drawsImage) { + SkScalar SIZE = gButton[imageIndex].width(); + SkScalar width = r.width(); + SkScalar scale = SkScalarDiv(width - 2*imageMargin, SIZE); + int saveScaleCount = canvas->save(); + canvas->translate(bounds.fLeft + imageMargin, bounds.fTop + imageMargin); + canvas->scale(scale, scale); + canvas->drawBitmap(gButton[imageIndex], 0, 0, &paint); + canvas->restoreToCount(saveScaleCount); + } +} + +} // WebCore diff --git a/Source/WebKit/android/RenderSkinMediaButton.h b/Source/WebKit/android/RenderSkinMediaButton.h new file mode 100644 index 0000000..6aa9c4e --- /dev/null +++ b/Source/WebKit/android/RenderSkinMediaButton.h @@ -0,0 +1,63 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef RenderSkinMediaButton_h +#define RenderSkinMediaButton_h + +#include "RenderSkinAndroid.h" + +class SkCanvas; + +namespace WebCore { +class IntRect; +class RenderObject; + +class RenderSkinMediaButton { +public: + /** + * Initialize the class before use. Uses the AssetManager to initialize any + * bitmaps the class may use. + */ + static void Init(android::AssetManager*, String drawableDirectory); + /** + * Draw the skin to the canvas, using the rectangle for its bounds and the + * State to determine which skin to use, i.e. focused or not focused. + */ + static void Draw(SkCanvas* , const IntRect& , int buttonType, bool translucent = false, + RenderObject* o = 0); + /** + * Button types + */ + enum { PAUSE, PLAY, MUTE, REWIND, FORWARD, FULLSCREEN, SPINNER_OUTER, SPINNER_INNER , VIDEO, BACKGROUND_SLIDER, SLIDER_TRACK, SLIDER_THUMB }; + /** + * Slider dimensions + */ + static int sliderThumbWidth() { return 32; } + static int sliderThumbHeight() { return 32; } + +}; + +} // WebCore +#endif // RenderSkinMediaButton_h diff --git a/Source/WebKit/android/RenderSkinNinePatch.cpp b/Source/WebKit/android/RenderSkinNinePatch.cpp new file mode 100644 index 0000000..0c915c0 --- /dev/null +++ b/Source/WebKit/android/RenderSkinNinePatch.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" + +#include "RenderSkinNinePatch.h" +#include "NinePatchPeeker.h" +#include "SkCanvas.h" +#include "SkImageDecoder.h" +#include "SkRect.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include <utils/Asset.h> +#include <utils/AssetManager.h> +#include <utils/Log.h> +#include <utils/ResourceTypes.h> + +class SkPaint; +class SkRegion; + +using namespace android; + +extern void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds, + const SkBitmap& bitmap, const Res_png_9patch& chunk, + const SkPaint* paint, SkRegion** outRegion); + +bool RenderSkinNinePatch::decodeAsset(AssetManager* am, const char* filename, NinePatch* ninepatch) { + Asset* asset = am->open(filename, android::Asset::ACCESS_BUFFER); + if (!asset) { + asset = am->openNonAsset(filename, android::Asset::ACCESS_BUFFER); + if (!asset) { + return false; + } + } + + SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; + SkBitmap::Config prefConfig = SkBitmap::kNo_Config; + SkStream* stream = new SkMemoryStream(asset->getBuffer(false), asset->getLength()); + SkImageDecoder* decoder = SkImageDecoder::Factory(stream); + if (!decoder) { + asset->close(); + LOGE("RenderSkinNinePatch::Failed to create an image decoder"); + return false; + } + + decoder->setSampleSize(1); + decoder->setDitherImage(true); + decoder->setPreferQualityOverSpeed(false); + + NinePatchPeeker peeker(decoder); + + SkAutoTDelete<SkImageDecoder> add(decoder); + + decoder->setPeeker(&peeker); + if (!decoder->decode(stream, &ninepatch->m_bitmap, prefConfig, mode, true)) { + asset->close(); + LOGE("RenderSkinNinePatch::Failed to decode nine patch asset"); + return false; + } + + asset->close(); + if (!peeker.fPatchIsValid) { + LOGE("RenderSkinNinePatch::Patch data not valid"); + return false; + } + void** data = &ninepatch->m_serializedPatchData; + *data = malloc(peeker.fPatch->serializedSize()); + peeker.fPatch->serialize(*data); + return true; +} + +void RenderSkinNinePatch::DrawNinePatch(SkCanvas* canvas, const SkRect& bounds, + const NinePatch& patch) { + Res_png_9patch* data = Res_png_9patch::deserialize(patch.m_serializedPatchData); + NinePatch_Draw(canvas, bounds, patch.m_bitmap, *data, 0, 0); +} diff --git a/Source/WebKit/android/RenderSkinNinePatch.h b/Source/WebKit/android/RenderSkinNinePatch.h new file mode 100644 index 0000000..e4db260 --- /dev/null +++ b/Source/WebKit/android/RenderSkinNinePatch.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RenderSkinNinePatch_h +#define RenderSkinNinePatch_h + +#include "SkBitmap.h" +#include "utils/Asset.h" + +namespace android { + class AssetManager; +} + +class SkCanvas; +class SkRect; + +struct NinePatch { + SkBitmap m_bitmap; + void* m_serializedPatchData; + NinePatch() { + m_serializedPatchData = 0; + } + ~NinePatch() { + if (m_serializedPatchData) + free(m_serializedPatchData); + } +}; + +class RenderSkinNinePatch { +public: + static bool decodeAsset(android::AssetManager*, const char* fileName, NinePatch*); + static void DrawNinePatch(SkCanvas*, const SkRect&, const NinePatch&); +}; + +#endif // RenderSkinNinePatch_h diff --git a/Source/WebKit/android/RenderSkinRadio.cpp b/Source/WebKit/android/RenderSkinRadio.cpp new file mode 100644 index 0000000..5dfee4a --- /dev/null +++ b/Source/WebKit/android/RenderSkinRadio.cpp @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#include "config.h" +#include "RenderSkinRadio.h" + +#include "android_graphics.h" +#include "Document.h" +#include "Element.h" +#include "InputElement.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinAndroid.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkRect.h" +#include <wtf/text/CString.h> + +static const char* checks[] = { "btn_check_off_holo.png", + "btn_check_on_holo.png", + "btn_radio_off_holo.png", + "btn_radio_on_holo.png"}; +// Matches the width of the bitmap +static SkScalar SIZE; + +namespace WebCore { + +static SkBitmap s_bitmap[4]; +static bool s_decoded; + +void RenderSkinRadio::Init(android::AssetManager* am, String drawableDirectory) +{ + if (s_decoded) + return; + String path = drawableDirectory + checks[0]; + s_decoded = RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &s_bitmap[0]); + path = drawableDirectory + checks[1]; + s_decoded = RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &s_bitmap[1]) && s_decoded; + path = drawableDirectory + checks[2]; + s_decoded = RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &s_bitmap[2]) && s_decoded; + path = drawableDirectory + checks[3]; + s_decoded = RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &s_bitmap[3]) && s_decoded; + SIZE = SkIntToScalar(s_bitmap[0].width()); +} + +void RenderSkinRadio::Draw(SkCanvas* canvas, Node* element, const IntRect& ir, + bool isCheckBox) +{ + if (!s_decoded || !element) { + return; + } + SkRect r(ir); + // Set up a paint to with filtering to look better. + SkPaint paint; + paint.setFlags(SkPaint::kFilterBitmap_Flag); + int saveScaleCount = 0; + + if (!element->isElementNode() || + !static_cast<Element*>(element)->isEnabledFormControl()) { + paint.setAlpha(0x80); + } + SkScalar width = r.width(); + SkScalar scale = SkScalarDiv(width, SIZE); + saveScaleCount = canvas->save(); + canvas->translate(r.fLeft, r.fTop); + canvas->scale(scale, scale); + + bool checked = false; + if (InputElement* inputElement = toInputElement(static_cast<Element*>(element))) { + checked = inputElement->isChecked(); + } + + canvas->drawBitmap(s_bitmap[checked + 2*(!isCheckBox)], + 0, 0, &paint); + canvas->restoreToCount(saveScaleCount); +} + +} //WebCore diff --git a/Source/WebKit/android/RenderSkinRadio.h b/Source/WebKit/android/RenderSkinRadio.h new file mode 100644 index 0000000..f77e1be --- /dev/null +++ b/Source/WebKit/android/RenderSkinRadio.h @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#ifndef RenderSkinRadio_h +#define RenderSkinRadio_h + +#include "PlatformString.h" + +class SkCanvas; + +namespace android { + class AssetManager; +} + +namespace WebCore { + +class Node; +class IntRect; + +/* RenderSkin for a radio button or a checkbox + */ +class RenderSkinRadio +{ +public: + /** + * Initialize the class before use. Uses the AssetManager to initialize any bitmaps the class may use. + */ + static void Init(android::AssetManager*, String drawableDirectory); + + /** + * Draw the element to the canvas at the specified size and location. + * param isCheckBox If true, draws a checkbox. Else, draw a radio button. + */ + static void Draw(SkCanvas* canvas, Node* element, const IntRect&, + bool isCheckBox); +}; + +} // WebCore +#endif diff --git a/Source/WebKit/android/TimeCounter.cpp b/Source/WebKit/android/TimeCounter.cpp new file mode 100644 index 0000000..2393f8a --- /dev/null +++ b/Source/WebKit/android/TimeCounter.cpp @@ -0,0 +1,198 @@ +/* + * Copyright 2009, 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 "WebCore" + +#include "config.h" +#include "TimeCounter.h" + +#include "MemoryCache.h" +#include "KURL.h" +#include "Node.h" +#include "SystemTime.h" +#include "StyleBase.h" +#include <sys/time.h> +#include <time.h> +#include <utils/Log.h> +#include <wtf/CurrentTime.h> +#include <wtf/text/CString.h> + +#if USE(JSC) +#include "JSDOMWindow.h" +#include <runtime/JSGlobalObject.h> +#include <runtime/JSLock.h> +#endif + +using namespace WebCore; +using namespace WTF; +using namespace JSC; + +namespace android { + +uint32_t getThreadMsec() +{ +#if defined(HAVE_POSIX_CLOCKS) + struct timespec tm; + + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); + return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000; +#else + struct timeval now; + struct timezone zone; + + gettimeofday(&now, &zone); + return now.tv_sec * 1000LL + now.tv_usec / 1000; +#endif +} + +#ifdef ANDROID_INSTRUMENT + +static double sStartTotalTime; +static uint32_t sStartThreadTime; +static double sLastTotalTime; +static uint32_t sLastThreadTime; + +uint32_t TimeCounter::sStartWebCoreThreadTime; +uint32_t TimeCounter::sEndWebCoreThreadTime; +bool TimeCounter::sRecordWebCoreTime; +uint32_t TimeCounter::sTotalTimeUsed[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sLastTimeUsed[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sCounter[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sLastCounter[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sStartTime[TimeCounter::TotalTimeCounterCount]; + +int QemuTracerAuto::reentry_count = 0; + +static const char* timeCounterNames[] = { + "css parsing", + "javascript", + "javascript init", + "javascript parsing", + "javascript execution", + "calculate style", + "Java callback (frame bridge)", + "parsing (may include calcStyle, Java callback or inline script execution)", + "layout", + "native 1 (frame bridge)", + "native 2 (resource load)", + "native 3 (shared timer)", + "build nav (webview core)", + "record content (webview core)", + "native 4 (webview core)", + "draw content (webview ui)", +}; + +void TimeCounter::record(enum Type type, const char* functionName) +{ + recordNoCounter(type, functionName); + sCounter[type]++; +} + +void TimeCounter::recordNoCounter(enum Type type, const char* functionName) +{ + uint32_t time = sEndWebCoreThreadTime = getThreadMsec(); + uint32_t elapsed = time - sStartTime[type]; + sTotalTimeUsed[type] += elapsed; + if (elapsed > 1000) + LOGW("***** %s() used %d ms\n", functionName, elapsed); +} + +void TimeCounter::report(const KURL& url, int live, int dead, size_t arenaSize) +{ + String urlString = url; + int totalTime = static_cast<int>((currentTime() - sStartTotalTime) * 1000); + int threadTime = getThreadMsec() - sStartThreadTime; + LOGD("*-* Total load time: %d ms, thread time: %d ms for %s\n", + totalTime, threadTime, urlString.utf8().data()); + for (Type type = (Type) 0; type < TotalTimeCounterCount; type + = (Type) (type + 1)) { + char scratch[256]; + int index = sprintf(scratch, "*-* Total %s time: %d ms", + timeCounterNames[type], sTotalTimeUsed[type]); + if (sCounter[type] > 0) + sprintf(&scratch[index], " called %d times", sCounter[type]); + LOGD("%s", scratch); + } + LOGD("Current cache has %d bytes live and %d bytes dead", live, dead); + LOGD("Current render arena takes %d bytes", arenaSize); +#if USE(JSC) + JSLock lock(false); + Heap::Statistics jsHeapStatistics = JSDOMWindow::commonJSGlobalData()->heap.statistics(); + LOGD("Current JavaScript heap size is %d and has %d bytes free", + jsHeapStatistics.size, jsHeapStatistics.free); +#endif + LOGD("Current CSS styles use %d bytes", StyleBase::reportStyleSize()); + LOGD("Current DOM nodes use %d bytes", WebCore::Node::reportDOMNodesSize()); +} + +void TimeCounter::reportNow() +{ + double current = currentTime(); + uint32_t currentThread = getThreadMsec(); + int elapsedTime = static_cast<int>((current - sLastTotalTime) * 1000); + int elapsedThreadTime = currentThread - sLastThreadTime; + LOGD("*-* Elapsed time: %d ms, ui thread time: %d ms, webcore thread time:" + " %d ms\n", elapsedTime, elapsedThreadTime, sEndWebCoreThreadTime - + sStartWebCoreThreadTime); + for (Type type = (Type) 0; type < TotalTimeCounterCount; type + = (Type) (type + 1)) { + if (sTotalTimeUsed[type] == sLastTimeUsed[type]) + continue; + char scratch[256]; + int index = sprintf(scratch, "*-* Diff %s time: %d ms", + timeCounterNames[type], sTotalTimeUsed[type] - sLastTimeUsed[type]); + if (sCounter[type] > sLastCounter[type]) + sprintf(&scratch[index], " called %d times", sCounter[type] + - sLastCounter[type]); + LOGD("%s", scratch); + } + memcpy(sLastTimeUsed, sTotalTimeUsed, sizeof(sTotalTimeUsed)); + memcpy(sLastCounter, sCounter, sizeof(sCounter)); + sLastTotalTime = current; + sLastThreadTime = currentThread; + sRecordWebCoreTime = true; +} + +void TimeCounter::reset() { + bzero(sTotalTimeUsed, sizeof(sTotalTimeUsed)); + bzero(sCounter, sizeof(sCounter)); + LOGD("*-* Start browser instrument\n"); + sStartTotalTime = currentTime(); + sStartThreadTime = getThreadMsec(); +} + +void TimeCounter::start(enum Type type) +{ + uint32_t time = getThreadMsec(); + if (sRecordWebCoreTime) { + sStartWebCoreThreadTime = time; + sRecordWebCoreTime = false; + } + sStartTime[type] = time; +} + +#endif // ANDROID_INSTRUMENT + +} diff --git a/Source/WebKit/android/TimeCounter.h b/Source/WebKit/android/TimeCounter.h new file mode 100644 index 0000000..64908d1 --- /dev/null +++ b/Source/WebKit/android/TimeCounter.h @@ -0,0 +1,120 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef TIME_COUNTER_H +#define TIME_COUNTER_H + +#include "hardware_legacy/qemu_tracing.h" + +namespace WebCore { + +class KURL; + +} + +namespace android { + +uint32_t getThreadMsec(); + +#ifdef ANDROID_INSTRUMENT + +class TimeCounter { +public: + enum Type { + // function base counters + CSSParseTimeCounter, + JavaScriptTimeCounter, + JavaScriptInitTimeCounter, + JavaScriptParseTimeCounter, + JavaScriptExecuteTimeCounter, + CalculateStyleTimeCounter, + JavaCallbackTimeCounter, + ParsingTimeCounter, + LayoutTimeCounter, + // file base counters + NativeCallbackTimeCounter, // WebCoreFrameBridge.cpp + ResourceTimeCounter, // WebCoreResourceLoader.cpp + SharedTimerTimeCounter, // JavaBridge.cpp + WebViewCoreBuildNavTimeCounter, + WebViewCoreRecordTimeCounter, + WebViewCoreTimeCounter, // WebViewCore.cpp + WebViewUIDrawTimeCounter, + TotalTimeCounterCount + }; + + static void record(enum Type type, const char* functionName); + static void recordNoCounter(enum Type type, const char* functionName); + static void report(const WebCore::KURL& , int live, int dead, size_t arenaSize); + static void reportNow(); + static void reset(); + static void start(enum Type type); +private: + static uint32_t sStartWebCoreThreadTime; + static uint32_t sEndWebCoreThreadTime; + static bool sRecordWebCoreTime; + static uint32_t sTotalTimeUsed[TotalTimeCounterCount]; + static uint32_t sLastTimeUsed[TotalTimeCounterCount]; + static uint32_t sCounter[TotalTimeCounterCount]; + static uint32_t sLastCounter[TotalTimeCounterCount]; + static uint32_t sStartTime[TotalTimeCounterCount]; + friend class TimeCounterAuto; +}; + +class TimeCounterAuto { +public: + TimeCounterAuto(TimeCounter::Type type) : + m_type(type), m_startTime(getThreadMsec()) {} + ~TimeCounterAuto() { + uint32_t time = getThreadMsec(); + TimeCounter::sEndWebCoreThreadTime = time; + TimeCounter::sTotalTimeUsed[m_type] += time - m_startTime; + TimeCounter::sCounter[m_type]++; + } +private: + TimeCounter::Type m_type; + uint32_t m_startTime; +}; + +class QemuTracerAuto { +public: + QemuTracerAuto() { + if (!reentry_count) + qemu_start_tracing(); + reentry_count++; + } + + ~QemuTracerAuto() { + reentry_count--; + if (!reentry_count) + qemu_stop_tracing(); + } +private: + static int reentry_count; +}; +#endif // ANDROID_INSTRUMENT + +} + +#endif diff --git a/Source/WebKit/android/TimerClient.h b/Source/WebKit/android/TimerClient.h new file mode 100644 index 0000000..0c3c84c --- /dev/null +++ b/Source/WebKit/android/TimerClient.h @@ -0,0 +1,42 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef TIMER_CLIENT_H +#define TIMER_CLIENT_H + +namespace android { + + class TimerClient + { + public: + virtual ~TimerClient() {} + virtual void setSharedTimerCallback(void(*f)()) = 0; + virtual void setSharedTimer(long long timemillis) = 0; + virtual void stopSharedTimer() = 0; + virtual void signalServiceFuncPtrQueue() = 0; + }; + +} +#endif diff --git a/Source/WebKit/android/WebCoreSupport/CacheResult.cpp b/Source/WebKit/android/WebCoreSupport/CacheResult.cpp new file mode 100644 index 0000000..5309c66 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/CacheResult.cpp @@ -0,0 +1,251 @@ +/* + * Copyright 2011, 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. + */ + +#include "config.h" +#include "CacheResult.h" + +#include "WebResponse.h" +#include "WebUrlLoaderClient.h" +#include <platform/FileSystem.h> +#include <wtf/text/CString.h> + +using namespace base; +using namespace disk_cache; +using namespace net; +using namespace std; + +namespace android { + +// All public methods are called on a UI thread but we do work on the +// Chromium thread. However, because we block the WebCore thread while this +// work completes, we can never receive new public method calls while the +// Chromium thread work is in progress. + +// Copied from HttpCache +enum { + kResponseInfoIndex = 0, + kResponseContentIndex +}; + +CacheResult::CacheResult(disk_cache::Entry* entry, String url) + : m_entry(entry) + , m_onResponseHeadersDoneCallback(this, &CacheResult::onResponseHeadersDone) + , m_onReadNextChunkDoneCallback(this, &CacheResult::onReadNextChunkDone) + , m_url(url) +{ + ASSERT(m_entry); +} + +CacheResult::~CacheResult() +{ + m_entry->Close(); + // TODO: Should we also call DoneReadingFromEntry() on the cache for our + // entry? +} + +int64 CacheResult::contentSize() const +{ + // The android stack does not take the content length from the HTTP response + // headers but calculates it when writing the content to disk. It can never + // overflow a long because we limit the cache size. + return m_entry->GetDataSize(kResponseContentIndex); +} + +bool CacheResult::firstResponseHeader(const char* name, String* result, bool allowEmptyString) const +{ + string value; + if (responseHeaders() && responseHeaders()->EnumerateHeader(NULL, name, &value) && (!value.empty() || allowEmptyString)) { + *result = String(value.c_str()); + return true; + } + return false; +} + +String CacheResult::mimeType() const +{ + string mimeType; + if (responseHeaders()) + responseHeaders()->GetMimeType(&mimeType); + if (!mimeType.length() && m_url.length()) + mimeType = WebResponse::resolveMimeType(std::string(m_url.utf8().data(), m_url.length()), ""); + return String(mimeType.c_str()); +} + +int64 CacheResult::expires() const +{ + // We have to do this manually, rather than using HttpResponseHeaders::GetExpiresValue(), + // to handle the "-1" and "0" special cases. + string expiresString; + if (responseHeaders() && responseHeaders()->EnumerateHeader(NULL, "expires", &expiresString)) { + wstring expiresStringWide(expiresString.begin(), expiresString.end()); // inflate ascii + // We require the time expressed as ms since the epoch. + Time time; + if (Time::FromString(expiresStringWide.c_str(), &time)) { + // Will not overflow for a very long time! + return static_cast<int64>(1000.0 * time.ToDoubleT()); + } + + if (expiresString == "-1" || expiresString == "0") + return 0; + } + + // TODO + // The Android stack applies a heuristic to set an expiry date if the + // expires header is not set or can't be parsed. I'm not sure whether the Chromium cache + // does this, and if so, it may not be possible for us to get hold of it + // anyway to set it on the result. + return -1; +} + +int CacheResult::responseCode() const +{ + return responseHeaders() ? responseHeaders()->response_code() : 0; +} + +bool CacheResult::writeToFile(const String& filePath) const +{ + // Getting the headers is potentially async, so post to the Chromium thread + // and block here. + MutexLocker lock(m_mutex); + + base::Thread* thread = WebUrlLoaderClient::ioThread(); + if (!thread) + return false; + + CacheResult* me = const_cast<CacheResult*>(this); + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(me, &CacheResult::writeToFileImpl)); + + m_filePath = filePath.threadsafeCopy(); + m_isAsyncOperationInProgress = true; + while (m_isAsyncOperationInProgress) + m_condition.wait(m_mutex); + + return m_wasWriteToFileSuccessful; +} + +void CacheResult::writeToFileImpl() +{ + m_bufferSize = m_entry->GetDataSize(kResponseContentIndex); + m_readOffset = 0; + m_wasWriteToFileSuccessful = false; + readNextChunk(); +} + +void CacheResult::readNextChunk() +{ + m_buffer = new IOBuffer(m_bufferSize); + int rv = m_entry->ReadData(kResponseInfoIndex, m_readOffset, m_buffer, m_bufferSize, &m_onReadNextChunkDoneCallback); + if (rv == ERR_IO_PENDING) + return; + + onReadNextChunkDone(rv); +}; + +void CacheResult::onReadNextChunkDone(int size) +{ + if (size > 0) { + // Still more reading to be done. + if (writeChunkToFile()) { + // TODO: I assume that we need to clear and resize the buffer for the next read? + m_readOffset += size; + m_bufferSize -= size; + readNextChunk(); + } else + onWriteToFileDone(); + return; + } + + if (!size) { + // Reached end of file. + if (writeChunkToFile()) + m_wasWriteToFileSuccessful = true; + } + onWriteToFileDone(); +} + +bool CacheResult::writeChunkToFile() +{ + PlatformFileHandle file; + file = openFile(m_filePath, OpenForWrite); + if (!isHandleValid(file)) + return false; + return WebCore::writeToFile(file, m_buffer->data(), m_bufferSize) == m_bufferSize; +} + +void CacheResult::onWriteToFileDone() +{ + MutexLocker lock(m_mutex); + m_isAsyncOperationInProgress = false; + m_condition.signal(); +} + +HttpResponseHeaders* CacheResult::responseHeaders() const +{ + MutexLocker lock(m_mutex); + if (m_responseHeaders) + return m_responseHeaders; + + // Getting the headers is potentially async, so post to the Chromium thread + // and block here. + base::Thread* thread = WebUrlLoaderClient::ioThread(); + if (!thread) + return 0; + + CacheResult* me = const_cast<CacheResult*>(this); + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(me, &CacheResult::responseHeadersImpl)); + + m_isAsyncOperationInProgress = true; + while (m_isAsyncOperationInProgress) + m_condition.wait(m_mutex); + + return m_responseHeaders; +} + +void CacheResult::responseHeadersImpl() +{ + m_bufferSize = m_entry->GetDataSize(kResponseInfoIndex); + m_buffer = new IOBuffer(m_bufferSize); + + int rv = m_entry->ReadData(kResponseInfoIndex, 0, m_buffer, m_bufferSize, &m_onResponseHeadersDoneCallback); + if (rv == ERR_IO_PENDING) + return; + + onResponseHeadersDone(rv); +}; + +void CacheResult::onResponseHeadersDone(int size) +{ + MutexLocker lock(m_mutex); + // It's OK to throw away the HttpResponseInfo object as we hold our own ref + // to the headers. + HttpResponseInfo response; + bool truncated = false; // TODO: Waht is this param for? + if (size == m_bufferSize && HttpCache::ParseResponseInfo(m_buffer->data(), m_bufferSize, &response, &truncated)) + m_responseHeaders = response.headers; + m_isAsyncOperationInProgress = false; + m_condition.signal(); +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/CacheResult.h b/Source/WebKit/android/WebCoreSupport/CacheResult.h new file mode 100644 index 0000000..c39570c --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/CacheResult.h @@ -0,0 +1,86 @@ +/* + * Copyright 2011, 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. + */ + +#ifndef CacheResult_h +#define CacheResult_h + +#include "ChromiumIncludes.h" + +#include <wtf/RefCounted.h> +#include <wtf/ThreadingPrimitives.h> +#include <wtf/text/WTFString.h> + +namespace android { + +// A wrapper around a disk_cache::Entry. Provides fields appropriate for constructing a Java CacheResult object. +class CacheResult : public base::RefCountedThreadSafe<CacheResult> { +public: + // Takes ownership of the Entry passed to the constructor. + CacheResult(disk_cache::Entry*, String url); + ~CacheResult(); + + int64 contentSize() const; + bool firstResponseHeader(const char* name, WTF::String* result, bool allowEmptyString) const; + // The Android stack uses the empty string if no Content-Type headers are + // found, so we use the same default here. + WTF::String mimeType() const; + // Returns the value of the expires header as milliseconds since the epoch. + int64 expires() const; + int responseCode() const; + bool writeToFile(const WTF::String& filePath) const; +private: + net::HttpResponseHeaders* responseHeaders() const; + void responseHeadersImpl(); + void onResponseHeadersDone(int size); + + void writeToFileImpl(); + void readNextChunk(); + void onReadNextChunkDone(int size); + bool writeChunkToFile(); + void onWriteToFileDone(); + + disk_cache::Entry* m_entry; + + scoped_refptr<net::HttpResponseHeaders> m_responseHeaders; + + int m_readOffset; + bool m_wasWriteToFileSuccessful; + mutable String m_filePath; + + int m_bufferSize; + scoped_refptr<net::IOBuffer> m_buffer; + mutable bool m_isAsyncOperationInProgress; + mutable WTF::Mutex m_mutex; + mutable WTF::ThreadCondition m_condition; + + net::CompletionCallbackImpl<CacheResult> m_onResponseHeadersDoneCallback; + net::CompletionCallbackImpl<CacheResult> m_onReadNextChunkDoneCallback; + + String m_url; +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/CachedFramePlatformDataAndroid.cpp b/Source/WebKit/android/WebCoreSupport/CachedFramePlatformDataAndroid.cpp new file mode 100644 index 0000000..30f374f --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/CachedFramePlatformDataAndroid.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2009, 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. + */ + +#include "config.h" + +#include "CachedFramePlatformDataAndroid.h" +#include "Settings.h" + +namespace android { + +CachedFramePlatformDataAndroid::CachedFramePlatformDataAndroid(WebCore::Settings* settings) +{ +#ifdef ANDROID_META_SUPPORT + m_viewport_width = settings->viewportWidth(); + m_viewport_height = settings->viewportHeight(); + m_viewport_initial_scale = settings->viewportInitialScale(); + m_viewport_minimum_scale = settings->viewportMinimumScale(); + m_viewport_maximum_scale = settings->viewportMaximumScale(); + m_viewport_target_densitydpi = settings->viewportTargetDensityDpi(); + m_viewport_user_scalable = settings->viewportUserScalable(); + m_format_detection_address = settings->formatDetectionAddress(); + m_format_detection_email = settings->formatDetectionEmail(); + m_format_detection_telephone = settings->formatDetectionTelephone(); +#endif + +} + +#ifdef ANDROID_META_SUPPORT +void CachedFramePlatformDataAndroid::restoreMetadata(WebCore::Settings* settings) +{ + settings->setViewportWidth(m_viewport_width); + settings->setViewportHeight(m_viewport_height); + settings->setViewportInitialScale(m_viewport_initial_scale); + settings->setViewportMinimumScale(m_viewport_minimum_scale); + settings->setViewportMaximumScale(m_viewport_maximum_scale); + settings->setViewportTargetDensityDpi(m_viewport_target_densitydpi); + settings->setViewportUserScalable(m_viewport_user_scalable); + settings->setFormatDetectionAddress(m_format_detection_address); + settings->setFormatDetectionEmail(m_format_detection_email); + settings->setFormatDetectionTelephone(m_format_detection_telephone); +} +#endif + +} diff --git a/Source/WebKit/android/WebCoreSupport/CachedFramePlatformDataAndroid.h b/Source/WebKit/android/WebCoreSupport/CachedFramePlatformDataAndroid.h new file mode 100644 index 0000000..20c7be4 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/CachedFramePlatformDataAndroid.h @@ -0,0 +1,63 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef CachedFramePlatformDatatAndroid_h +#define CachedFramePlatformDatatAndroid_h + +#include "CachedFramePlatformData.h" + +namespace WebCore { + class Settings; +} + +namespace android { + +class CachedFramePlatformDataAndroid : public WebCore::CachedFramePlatformData { +public: + CachedFramePlatformDataAndroid(WebCore::Settings* settings); + +#ifdef ANDROID_META_SUPPORT + void restoreMetadata(WebCore::Settings* settings); +#endif + +private: +#ifdef ANDROID_META_SUPPORT + // meta data of the frame + int m_viewport_width; + int m_viewport_height; + int m_viewport_initial_scale; + int m_viewport_minimum_scale; + int m_viewport_maximum_scale; + int m_viewport_target_densitydpi; + bool m_viewport_user_scalable : 1; + bool m_format_detection_address : 1; + bool m_format_detection_email : 1; + bool m_format_detection_telephone : 1; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp new file mode 100644 index 0000000..6f872b8 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp @@ -0,0 +1,605 @@ +/* + * Copyright 2007, 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 "WebCore" + +#include "config.h" + +#include "ApplicationCacheStorage.h" +#include "ChromeClientAndroid.h" +#include "DatabaseTracker.h" +#include "Document.h" +#include "PlatformString.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "Geolocation.h" +#include "HTMLMediaElement.h" +#include "HTMLNames.h" +#include "Icon.h" +#include "LayerAndroid.h" +#include "Page.h" +#include "PopupMenuAndroid.h" +#include "ScriptController.h" +#include "SearchPopupMenuAndroid.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreViewBridge.h" +#include "WebViewCore.h" +#include "WindowFeatures.h" +#include "Settings.h" +#include "UserGestureIndicator.h" +#include <wtf/text/CString.h> + +namespace android { + +#if ENABLE(DATABASE) +static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota); +#endif + +#if USE(ACCELERATED_COMPOSITING) + +WebCore::GraphicsLayer* ChromeClientAndroid::layersSync() +{ + if (m_rootGraphicsLayer && m_needsLayerSync && m_webFrame) { + if (FrameView* frameView = m_webFrame->page()->mainFrame()->view()) + frameView->syncCompositingStateRecursive(); + } + m_needsLayerSync = false; + return m_rootGraphicsLayer; +} + +void ChromeClientAndroid::scheduleCompositingLayerSync() +{ + if (m_needsLayerSync) + return; + m_needsLayerSync = true; + WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view()); + if (webViewCore) + webViewCore->layersDraw(); +} + +void ChromeClientAndroid::setNeedsOneShotDrawingSynchronization() +{ + // This should not be needed +} + +void ChromeClientAndroid::attachRootGraphicsLayer(WebCore::Frame*, WebCore::GraphicsLayer* layer) +{ + // frame is not used in Android as we should only get root graphics layer for the main frame + m_rootGraphicsLayer = layer; + if (!layer) + return; + scheduleCompositingLayerSync(); +} + +#endif + +void ChromeClientAndroid::setWebFrame(android::WebFrame* webframe) +{ + Release(m_webFrame); + m_webFrame = webframe; + Retain(m_webFrame); +} + +void ChromeClientAndroid::chromeDestroyed() +{ + Release(m_webFrame); + delete this; +} + +void ChromeClientAndroid::setWindowRect(const FloatRect&) { notImplemented(); } + +FloatRect ChromeClientAndroid::windowRect() { + ASSERT(m_webFrame); + if (!m_webFrame) + return FloatRect(); + FrameView* frameView = m_webFrame->page()->mainFrame()->view(); + if (!frameView) + return FloatRect(); + const WebCoreViewBridge* bridge = frameView->platformWidget(); + const IntRect& rect = bridge->getWindowBounds(); + FloatRect fRect(rect.x(), rect.y(), rect.width(), rect.height()); + return fRect; +} + +FloatRect ChromeClientAndroid::pageRect() { notImplemented(); return FloatRect(); } + +float ChromeClientAndroid::scaleFactor() +{ + ASSERT(m_webFrame); + return m_webFrame->density(); +} + +void ChromeClientAndroid::focus() +{ + ASSERT(m_webFrame); + bool isUserGesture = UserGestureIndicator::processingUserGesture(); + + // Ask the application to focus this WebView if the action is intiated by the user + if (isUserGesture) + m_webFrame->requestFocus(); +} +void ChromeClientAndroid::unfocus() { notImplemented(); } + +bool ChromeClientAndroid::canTakeFocus(FocusDirection) { notImplemented(); return false; } +void ChromeClientAndroid::takeFocus(FocusDirection) { notImplemented(); } + +void ChromeClientAndroid::focusedNodeChanged(Node* node) +{ + android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->focusNodeChanged(node); +} + +void ChromeClientAndroid::focusedFrameChanged(Frame*) { notImplemented(); } + +Page* ChromeClientAndroid::createWindow(Frame* frame, const FrameLoadRequest&, + const WindowFeatures& features, const NavigationAction&) +{ + ASSERT(frame); +#ifdef ANDROID_MULTIPLE_WINDOWS + if (frame->settings() && !(frame->settings()->supportMultipleWindows())) + // If the client doesn't support multiple windows, just return the current page + return frame->page(); +#endif + + const WebCoreViewBridge* bridge = frame->view()->platformWidget(); + bool dialog = features.dialog || !features.resizable + || (features.heightSet && features.height < bridge->height() + && features.widthSet && features.width < bridge->width()) + || (!features.menuBarVisible && !features.statusBarVisible + && !features.toolBarVisible && !features.locationBarVisible + && !features.scrollbarsVisible); + // fullscreen definitely means no dialog + if (features.fullscreen) + dialog = false; + WebCore::Frame* newFrame = m_webFrame->createWindow(dialog, + ScriptController::processingUserGesture()); + if (newFrame) { + WebCore::Page* page = newFrame->page(); + page->setGroupName(frame->page()->groupName()); + return page; + } + return NULL; +} + +void ChromeClientAndroid::show() { notImplemented(); } + +bool ChromeClientAndroid::canRunModal() { notImplemented(); return false; } +void ChromeClientAndroid::runModal() { notImplemented(); } + +void ChromeClientAndroid::setToolbarsVisible(bool) { notImplemented(); } +bool ChromeClientAndroid::toolbarsVisible() { notImplemented(); return false; } + +void ChromeClientAndroid::setStatusbarVisible(bool) { notImplemented(); } +bool ChromeClientAndroid::statusbarVisible() { notImplemented(); return false; } + +void ChromeClientAndroid::setScrollbarsVisible(bool) { notImplemented(); } +bool ChromeClientAndroid::scrollbarsVisible() { notImplemented(); return false; } + +void ChromeClientAndroid::setMenubarVisible(bool) { notImplemented(); } +bool ChromeClientAndroid::menubarVisible() { notImplemented(); return false; } + +void ChromeClientAndroid::setResizable(bool) { notImplemented(); } + +#if ENABLE(CONTEXT_MENUS) +void ChromeClientAndroid::showContextMenu() { notImplemented(); } +#endif + +// This function is called by the JavaScript bindings to print usually an error to +// a message console. Pass the message to the java side so that the client can +// handle it as it sees fit. +void ChromeClientAndroid::addMessageToConsole(MessageSource, MessageType, MessageLevel msgLevel, const String& message, unsigned int lineNumber, const String& sourceID) { + android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->addMessageToConsole(message, lineNumber, sourceID, msgLevel); +} + +void ChromeClientAndroid::formDidBlur(const WebCore::Node* node) +{ + android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->formDidBlur(node); +} + +bool ChromeClientAndroid::canRunBeforeUnloadConfirmPanel() { return true; } +bool ChromeClientAndroid::runBeforeUnloadConfirmPanel(const String& message, Frame* frame) { + String url = frame->document()->documentURI(); + return android::WebViewCore::getWebViewCore(frame->view())->jsUnload(url, message); +} + +void ChromeClientAndroid::closeWindowSoon() +{ + ASSERT(m_webFrame); + Page* page = m_webFrame->page(); + Frame* mainFrame = page->mainFrame(); + // This will prevent javascript cross-scripting during unload + page->setGroupName(String()); + // Stop loading but do not send the unload event + mainFrame->loader()->stopLoading(UnloadEventPolicyNone); + // Cancel all pending loaders + mainFrame->loader()->stopAllLoaders(); + // Remove all event listeners so that no javascript can execute as a result + // of mouse/keyboard events. + mainFrame->document()->removeAllEventListeners(); + // Close the window. + m_webFrame->closeWindow(android::WebViewCore::getWebViewCore(mainFrame->view())); +} + +void ChromeClientAndroid::runJavaScriptAlert(Frame* frame, const String& message) +{ + String url = frame->document()->documentURI(); + + android::WebViewCore::getWebViewCore(frame->view())->jsAlert(url, message); +} + +bool ChromeClientAndroid::runJavaScriptConfirm(Frame* frame, const String& message) +{ + String url = frame->document()->documentURI(); + + return android::WebViewCore::getWebViewCore(frame->view())->jsConfirm(url, message); +} + +/* This function is called for the javascript method Window.prompt(). A dialog should be shown on + * the screen with an input put box. First param is the text, the second is the default value for + * the input box, third is return param. If the function returns true, the value set in the third parameter + * is provided to javascript, else null is returned to the script. + */ +bool ChromeClientAndroid::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result) +{ + String url = frame->document()->documentURI(); + return android::WebViewCore::getWebViewCore(frame->view())->jsPrompt(url, message, defaultValue, result); +} +void ChromeClientAndroid::setStatusbarText(const String&) { notImplemented(); } + +// This is called by the JavaScript interpreter when a script has been running for a long +// time. A dialog should be shown to the user asking them if they would like to cancel the +// Javascript. If true is returned, the script is cancelled. +// To make a device more responsive, we default to return true to disallow long running script. +// This implies that some of scripts will not be completed. +bool ChromeClientAndroid::shouldInterruptJavaScript() { + FrameView* frameView = m_webFrame->page()->mainFrame()->view(); + return android::WebViewCore::getWebViewCore(frameView)->jsInterrupt(); +} + +bool ChromeClientAndroid::tabsToLinks() const { return false; } + +IntRect ChromeClientAndroid::windowResizerRect() const { return IntRect(0, 0, 0, 0); } + +void ChromeClientAndroid::invalidateWindow(const IntRect&, bool) +{ + notImplemented(); +} + +void ChromeClientAndroid::invalidateContentsAndWindow(const IntRect& updateRect, bool /*immediate*/) +{ + notImplemented(); +} + +void ChromeClientAndroid::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate) +{ + notImplemented(); +} + +// new to change 38068 (Nov 6, 2008) +void ChromeClientAndroid::scroll(const IntSize& scrollDelta, + const IntRect& rectToScroll, const IntRect& clipRect) { + notImplemented(); +} + +// new to change 38068 (Nov 6, 2008) +IntPoint ChromeClientAndroid::screenToWindow(const IntPoint&) const { + notImplemented(); + return IntPoint(); +} + +// new to change 38068 (Nov 6, 2008) +IntRect ChromeClientAndroid::windowToScreen(const IntRect&) const { + notImplemented(); + return IntRect(); +} + +PlatformPageClient ChromeClientAndroid::platformPageClient() const { + Page* page = m_webFrame->page(); + Frame* mainFrame = page->mainFrame(); + FrameView* view = mainFrame->view(); + PlatformWidget viewBridge = view->platformWidget(); + return viewBridge; +} + +void ChromeClientAndroid::contentsSizeChanged(Frame*, const IntSize&) const +{ + notImplemented(); +} + +void ChromeClientAndroid::scrollRectIntoView(const IntRect&, const ScrollView*) const +{ + notImplemented(); +} + +void ChromeClientAndroid::formStateDidChange(const Node*) +{ + notImplemented(); +} + +void ChromeClientAndroid::scrollbarsModeDidChange() const +{ + notImplemented(); +} + +void ChromeClientAndroid::mouseDidMoveOverElement(const HitTestResult&, unsigned int) {} +void ChromeClientAndroid::setToolTip(const String&, TextDirection) {} +void ChromeClientAndroid::print(Frame*) {} + +/* + * This function is called on the main (webcore) thread by SQLTransaction::deliverQuotaIncreaseCallback. + * The way that the callback mechanism is designed inside SQLTransaction means that there must be a new quota + * (which may be equal to the old quota if the user did not allow more quota) when this function returns. As + * we call into the browser thread to ask what to do with the quota, we block here and get woken up when the + * browser calls the native WebViewCore::SetDatabaseQuota method with the new quota value. + */ +#if ENABLE(DATABASE) +void ChromeClientAndroid::exceededDatabaseQuota(Frame* frame, const String& name) +{ + SecurityOrigin* origin = frame->document()->securityOrigin(); + DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); + + // We want to wait on a new quota from the UI thread. Reset the m_newQuota variable to represent we haven't received a new quota. + m_newQuota = -1; + + // This origin is being tracked and has exceeded it's quota. Call into + // the Java side of things to inform the user. + unsigned long long currentQuota = 0; + if (tracker.hasEntryForOrigin(origin)) + currentQuota = tracker.quotaForOrigin(origin); + + unsigned long long estimatedSize = 0; + + // Only update estimatedSize if we are trying to create a a new database, i.e. the usage for the database is 0. + if (tracker.usageForDatabase(name, origin) == 0) + estimatedSize = tracker.detailsForNameAndOrigin(name, origin).expectedUsage(); + + android::WebViewCore::getWebViewCore(frame->view())->exceededDatabaseQuota(frame->document()->documentURI(), name, currentQuota, estimatedSize); + + // We've sent notification to the browser so now wait for it to come back. + m_quotaThreadLock.lock(); + while (m_newQuota == -1) { + m_quotaThreadCondition.wait(m_quotaThreadLock); + } + m_quotaThreadLock.unlock(); + + // If new quota is unavailable, we may be able to resolve the situation by shrinking the quota of an origin that asked for a lot but is only using a little. + // If we find such a site, shrink it's quota and ask Java to try again. + + if ((unsigned long long) m_newQuota == currentQuota && !m_triedToReclaimDBQuota) { + m_triedToReclaimDBQuota = true; // we should only try this once per quota overflow. + unsigned long long reclaimedQuotaBytes = tryToReclaimDatabaseQuota(origin); + + // If we were able to free up enough space, try asking Java again. + // Otherwise, give up and deny the new database. :( + if (reclaimedQuotaBytes >= estimatedSize) { + exceededDatabaseQuota(frame, name); + return; + } + } + + // Update the DatabaseTracker with the new quota value (if the user declined + // new quota, this may equal the old quota) + tracker.setQuota(origin, m_newQuota); + m_triedToReclaimDBQuota = false; +} + +static unsigned long long tryToReclaimDatabaseQuota(SecurityOrigin* originNeedingQuota) { + DatabaseTracker& tracker = WebCore::DatabaseTracker::tracker(); + Vector<RefPtr<SecurityOrigin> > origins; + tracker.origins(origins); + unsigned long long reclaimedQuotaBytes = 0; + for (unsigned i = 0; i < origins.size(); i++) { + SecurityOrigin* originToReclaimFrom = origins[i].get(); + + // Don't try to reclaim from the origin that has exceeded its quota. + if (originToReclaimFrom->equal(originNeedingQuota)) + continue; + + unsigned long long originUsage = tracker.usageForOrigin(originToReclaimFrom); + unsigned long long originQuota = tracker.quotaForOrigin(originToReclaimFrom); + // If the origin has a quota that is more than it's current usage +1MB, shrink it. + static const int ONE_MB = 1 * 1024 * 1024; + if (originUsage + ONE_MB < originQuota) { + unsigned long long newQuota = originUsage + ONE_MB; + tracker.setQuota(originToReclaimFrom, newQuota); + reclaimedQuotaBytes += originQuota - newQuota; + } + } + return reclaimedQuotaBytes; +} +#endif + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) +void ChromeClientAndroid::reachedMaxAppCacheSize(int64_t spaceNeeded) +{ + // Set m_newQuota before calling into the Java side. If we do this after, + // we could overwrite the result passed from the Java side and deadlock in the + // wait call below. + m_newQuota = -1; + Page* page = m_webFrame->page(); + Frame* mainFrame = page->mainFrame(); + FrameView* view = mainFrame->view(); + android::WebViewCore::getWebViewCore(view)->reachedMaxAppCacheSize(spaceNeeded); + + // We've sent notification to the browser so now wait for it to come back. + m_quotaThreadLock.lock(); + while (m_newQuota == -1) { + m_quotaThreadCondition.wait(m_quotaThreadLock); + } + m_quotaThreadLock.unlock(); + if (m_newQuota > 0) { + WebCore::cacheStorage().setMaximumSize(m_newQuota); + // Now the app cache will retry the saving the previously failed cache. + } +} +#endif + +void ChromeClientAndroid::populateVisitedLinks() +{ + Page* page = m_webFrame->page(); + Frame* mainFrame = page->mainFrame(); + FrameView* view = mainFrame->view(); + android::WebViewCore::getWebViewCore(view)->populateVisitedLinks(&page->group()); +} + +void ChromeClientAndroid::requestGeolocationPermissionForFrame(Frame* frame, Geolocation* geolocation) +{ + ASSERT(geolocation); + if (!m_geolocationPermissions) { + m_geolocationPermissions = new GeolocationPermissions(android::WebViewCore::getWebViewCore(frame->view()), + m_webFrame->page()->mainFrame()); + } + m_geolocationPermissions->queryPermissionState(frame); +} + +void ChromeClientAndroid::cancelGeolocationPermissionRequestForFrame(Frame* frame, WebCore::Geolocation*) +{ + if (m_geolocationPermissions) + m_geolocationPermissions->cancelPermissionStateQuery(frame); +} + +void ChromeClientAndroid::provideGeolocationPermissions(const String &origin, bool allow, bool remember) +{ + ASSERT(m_geolocationPermissions); + m_geolocationPermissions->providePermissionState(origin, allow, remember); +} + +void ChromeClientAndroid::storeGeolocationPermissions() +{ + GeolocationPermissions::maybeStorePermanentPermissions(); +} + +void ChromeClientAndroid::onMainFrameLoadStarted() +{ + if (m_geolocationPermissions.get()) + m_geolocationPermissions->resetTemporaryPermissionStates(); +} + +void ChromeClientAndroid::runOpenPanel(Frame* frame, + PassRefPtr<FileChooser> chooser) +{ + android::WebViewCore* core = android::WebViewCore::getWebViewCore( + frame->view()); + core->openFileChooser(chooser); +} + +void ChromeClientAndroid::chooseIconForFiles(const Vector<WTF::String>&, FileChooser*) +{ + notImplemented(); +} + +void ChromeClientAndroid::setCursor(const Cursor&) +{ + notImplemented(); +} + +void ChromeClientAndroid::wakeUpMainThreadWithNewQuota(long newQuota) { + MutexLocker locker(m_quotaThreadLock); + m_newQuota = newQuota; + m_quotaThreadCondition.signal(); +} + +#if ENABLE(TOUCH_EVENTS) +void ChromeClientAndroid::needTouchEvents(bool needTouchEvents) +{ + FrameView* frameView = m_webFrame->page()->mainFrame()->view(); + android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); + if (core) + core->needTouchEvents(needTouchEvents); +} +#endif + +bool ChromeClientAndroid::selectItemWritingDirectionIsNatural() +{ + return false; +} + +PassRefPtr<PopupMenu> ChromeClientAndroid::createPopupMenu(PopupMenuClient* client) const +{ + return adoptRef(new PopupMenuAndroid(static_cast<ListPopupMenuClient*>(client))); +} + +PassRefPtr<SearchPopupMenu> ChromeClientAndroid::createSearchPopupMenu(PopupMenuClient*) const +{ + return adoptRef(new SearchPopupMenuAndroid); +} + +void ChromeClientAndroid::reachedApplicationCacheOriginQuota(SecurityOrigin*) +{ + notImplemented(); +} + +#if ENABLE(ANDROID_INSTALLABLE_WEB_APPS) +void ChromeClientAndroid::webAppCanBeInstalled() +{ + FrameView* frameView = m_webFrame->page()->mainFrame()->view(); + android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); + if (core) + core->notifyWebAppCanBeInstalled(); +} +#endif + +#if ENABLE(VIDEO) +bool ChromeClientAndroid::supportsFullscreenForNode(const Node* node) +{ + return node->hasTagName(HTMLNames::videoTag); +} + +void ChromeClientAndroid::enterFullscreenForNode(Node* node) +{ + if (!node->hasTagName(HTMLNames::videoTag)) + return; + + HTMLMediaElement* videoElement = static_cast<HTMLMediaElement*>(node); + String url = videoElement->currentSrc(); + LayerAndroid* layer = videoElement->platformLayer(); + if (!layer) + return; + + FrameView* frameView = m_webFrame->page()->mainFrame()->view(); + android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); + m_webFrame->page()->mainFrame()->document()->webkitWillEnterFullScreenForElement(videoElement); + if (core) + core->enterFullscreenForVideoLayer(layer->uniqueId(), url); +} + +void ChromeClientAndroid::exitFullscreenForNode(Node* node) +{ +} +#endif + +#if ENABLE(FULLSCREEN_API) +void ChromeClientAndroid::exitFullScreenForElement(Element* element) +{ + if (!element) + return; + + HTMLMediaElement* videoElement = static_cast<HTMLMediaElement*>(element); + videoElement->exitFullscreen(); +} +#endif + +} diff --git a/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.h b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.h new file mode 100644 index 0000000..d49cec8 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.h @@ -0,0 +1,216 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef ChromeClientAndroid_h +#define ChromeClientAndroid_h + +#include "ChromeClient.h" + +#include "GeolocationPermissions.h" +#include "PopupMenu.h" +#include "SearchPopupMenu.h" +#include "Timer.h" +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { +class PopupMenuClient; +class Geolocation; +} + +using namespace WebCore; + +namespace android { + class WebFrame; + + class ChromeClientAndroid : public ChromeClient { + public: + ChromeClientAndroid() : m_webFrame(0), m_geolocationPermissions(0) +#if USE(ACCELERATED_COMPOSITING) + , m_rootGraphicsLayer(0) + , m_needsLayerSync(false) +#endif + , m_triedToReclaimDBQuota(false) + { } + virtual void chromeDestroyed(); + + virtual void setWindowRect(const FloatRect&); + virtual FloatRect windowRect(); + + virtual FloatRect pageRect(); + + virtual float scaleFactor(); + + virtual void focus(); + virtual void unfocus(); + virtual void formDidBlur(const WebCore::Node*); + virtual bool canTakeFocus(FocusDirection); + virtual void takeFocus(FocusDirection); + + virtual void focusedNodeChanged(Node*); + virtual void focusedFrameChanged(Frame*); + + // The Frame pointer provides the ChromeClient with context about which + // Frame wants to create the new Page. Also, the newly created window + // should not be shown to the user until the ChromeClient of the newly + // created Page has its show method called. + virtual Page* createWindow(Frame*, const FrameLoadRequest&, const WindowFeatures&, const NavigationAction&); + virtual void show(); + + virtual bool canRunModal(); + virtual void runModal(); + + virtual void setToolbarsVisible(bool); + virtual bool toolbarsVisible(); + + virtual void setStatusbarVisible(bool); + virtual bool statusbarVisible(); + + virtual void setScrollbarsVisible(bool); + virtual bool scrollbarsVisible(); + + virtual void setMenubarVisible(bool); + virtual bool menubarVisible(); + + virtual void setResizable(bool); + + virtual void addMessageToConsole(MessageSource, MessageType, MessageLevel, const String& message, unsigned int lineNumber, const String& sourceID); + + virtual bool canRunBeforeUnloadConfirmPanel(); + virtual bool runBeforeUnloadConfirmPanel(const String& message, Frame* frame); + + virtual void closeWindowSoon(); + + virtual void runJavaScriptAlert(Frame*, const String&); + virtual bool runJavaScriptConfirm(Frame*, const String&); + virtual bool runJavaScriptPrompt(Frame*, const String& message, const String& defaultValue, String& result); + virtual void setStatusbarText(const String&); + virtual bool shouldInterruptJavaScript(); + virtual bool tabsToLinks() const; + + virtual IntRect windowResizerRect() const; + + // Methods used by HostWindow. + virtual void invalidateWindow(const WebCore::IntRect&, bool); + virtual void invalidateContentsAndWindow(const WebCore::IntRect&, bool); + virtual void invalidateContentsForSlowScroll(const WebCore::IntRect&, bool); + virtual void scroll(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect); + virtual IntPoint screenToWindow(const IntPoint&) const; + virtual IntRect windowToScreen(const IntRect&) const; + virtual PlatformPageClient platformPageClient() const; + virtual void contentsSizeChanged(Frame*, const IntSize&) const; + virtual void scrollRectIntoView(const IntRect&, const ScrollView*) const; + // End methods used by HostWindow. + + virtual void scrollbarsModeDidChange() const; + virtual void mouseDidMoveOverElement(const HitTestResult&, unsigned int); + + virtual void setToolTip(const String&, TextDirection); + + virtual void print(Frame*); +#if ENABLE(DATABASE) + virtual void exceededDatabaseQuota(Frame*, const String&); +#endif +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + virtual void reachedMaxAppCacheSize(int64_t spaceNeeded); + virtual void reachedApplicationCacheOriginQuota(SecurityOrigin*); +#endif + + virtual void populateVisitedLinks(); + +#if ENABLE(TOUCH_EVENTS) + virtual void needTouchEvents(bool); +#endif + + // Methods used to request and provide Geolocation permissions. + virtual void requestGeolocationPermissionForFrame(Frame*, Geolocation*); + virtual void cancelGeolocationPermissionRequestForFrame(WebCore::Frame*, WebCore::Geolocation*); + // Android-specific + void provideGeolocationPermissions(const String &origin, bool allow, bool remember); + void storeGeolocationPermissions(); + void onMainFrameLoadStarted(); + + virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>); + virtual void setCursor(const Cursor&); + virtual void chooseIconForFiles(const WTF::Vector<WTF::String>&, FileChooser*); + + // Notification that the given form element has changed. This function + // will be called frequently, so handling should be very fast. + virtual void formStateDidChange(const Node*); + + virtual PassOwnPtr<HTMLParserQuirks> createHTMLParserQuirks() { return 0; } + + // Android-specific + void setWebFrame(android::WebFrame* webframe); + android::WebFrame* webFrame() { return m_webFrame; } + void wakeUpMainThreadWithNewQuota(long newQuota); + +#if USE(ACCELERATED_COMPOSITING) + virtual void attachRootGraphicsLayer(WebCore::Frame*, WebCore::GraphicsLayer* g); + virtual void setNeedsOneShotDrawingSynchronization(); + virtual void scheduleCompositingLayerSync(); + virtual bool allowsAcceleratedCompositing() const { return true; } + WebCore::GraphicsLayer* layersSync(); +#endif + + virtual bool selectItemWritingDirectionIsNatural(); + virtual PassRefPtr<WebCore::PopupMenu> createPopupMenu(WebCore::PopupMenuClient*) const; + virtual PassRefPtr<WebCore::SearchPopupMenu> createSearchPopupMenu(WebCore::PopupMenuClient*) const; + +#if ENABLE(CONTEXT_MENUS) + virtual void showContextMenu(); +#endif + +#if ENABLE(ANDROID_INSTALLABLE_WEB_APPS) + virtual void webAppCanBeInstalled(); +#endif + +#if ENABLE(FULLSCREEN_API) + virtual void exitFullScreenForElement(Element*); +#endif + +#if ENABLE(VIDEO) + virtual bool supportsFullscreenForNode(const WebCore::Node*); + virtual void enterFullscreenForNode(WebCore::Node*); + virtual void exitFullscreenForNode(WebCore::Node*); +#endif + + private: + android::WebFrame* m_webFrame; + // The Geolocation permissions manager. + OwnPtr<GeolocationPermissions> m_geolocationPermissions; +#if USE(ACCELERATED_COMPOSITING) + WebCore::GraphicsLayer* m_rootGraphicsLayer; + bool m_needsLayerSync; +#endif + WTF::ThreadCondition m_quotaThreadCondition; + WTF::Mutex m_quotaThreadLock; + long m_newQuota; + bool m_triedToReclaimDBQuota; + }; + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/ChromiumIncludes.h b/Source/WebKit/android/WebCoreSupport/ChromiumIncludes.h new file mode 100644 index 0000000..8166eb7 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ChromiumIncludes.h @@ -0,0 +1,104 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef ChromiumIncludes_h +#define ChromiumIncludes_h + +#include "config.h" + +// Include all external/chromium files in this file so the problems with the LOG +// and LOG_ASSERT defines can be handled in one place. + +// Undefine LOG and LOG_ASSERT before including chrome code, and if they were +// defined attempt to set the macros to the Android logging macros (which are +// the only ones that actually log). + +#ifdef LOG +#define LOG_WAS_DEFINED LOG +#undef LOG +#endif + +#ifdef LOG_ASSERT +#define LOG_ASSERT_WAS_DEFINED LOG_ASSERT +#undef LOG_ASSERT +#endif + +#include <android/net/android_network_library_impl.h> +#include <base/callback.h> +#include <base/condition_variable.h> +#include <base/lock.h> +#include <base/message_loop_proxy.h> +#include <base/ref_counted.h> +#include <base/string_util.h> +#include <base/sys_string_conversions.h> +#include <base/thread.h> +#include <base/time.h> +#include <base/tuple.h> +#include <chrome/browser/net/sqlite_persistent_cookie_store.h> +#include <net/base/auth.h> +#include <net/base/cookie_monster.h> +#include <net/base/cookie_policy.h> +#include <net/base/data_url.h> +#include <net/base/host_resolver.h> +#include <net/base/io_buffer.h> +#include <net/base/load_flags.h> +#include <net/base/net_errors.h> +#include <net/base/mime_util.h> +#include <net/base/ssl_config_service.h> +#include <net/disk_cache/disk_cache.h> +#include <net/http/http_auth_handler_factory.h> +#include <net/http/http_cache.h> +#include <net/http/http_network_layer.h> +#include <net/http/http_response_headers.h> +#include <net/proxy/proxy_config_service_android.h> +#include <net/proxy/proxy_service.h> +#include <net/url_request/url_request.h> +#include <net/url_request/url_request_context.h> + +#if ENABLE(WEB_AUTOFILL) +#include <autofill/autofill_manager.h> +#include <autofill/autofill_profile.h> +#include <autofill/personal_data_manager.h> +#include <base/logging.h> +#include <base/scoped_vector.h> +#include <base/string16.h> +#include <base/utf_string_conversions.h> +#include <chrome/browser/autofill/autofill_host.h> +#include <chrome/browser/profile.h> +#include <chrome/browser/tab_contents/tab_contents.h> +#include <webkit/glue/form_data.h> +#endif + +#undef LOG +#if defined(LOG_WAS_DEFINED) && defined(LOG_PRI) +#define LOG(priority, tag, ...) LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +#undef LOG_ASSERT +#if defined(LOG_ASSERT_WAS_DEFINED) && defined(LOG_FATAL_IF) +#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__) +#endif + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/ChromiumInit.cpp b/Source/WebKit/android/WebCoreSupport/ChromiumInit.cpp new file mode 100644 index 0000000..1872fb9 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ChromiumInit.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "ChromiumInit.h" + +#include "ChromiumIncludes.h" + +#include <cutils/log.h> +#include <string> + +namespace android { + +bool logMessageHandler(int severity, const char* file, int line, size_t message_start, const std::string& str) { + int androidSeverity = ANDROID_LOG_VERBOSE; + switch(severity) { + case logging::LOG_FATAL: + androidSeverity = ANDROID_LOG_FATAL; + break; + case logging::LOG_ERROR_REPORT: + case logging::LOG_ERROR: + androidSeverity = ANDROID_LOG_ERROR; + break; + case logging::LOG_WARNING: + androidSeverity = ANDROID_LOG_WARN; + break; + default: + androidSeverity = ANDROID_LOG_VERBOSE; + break; + } + android_printLog(androidSeverity, "chromium", "%s:%d: %s", file, line, str.c_str()); + return false; +} + +namespace { + scoped_ptr<net::NetworkChangeNotifier> networkChangeNotifier; +} + +void initChromium() +{ + static Lock lock; + AutoLock aLock(lock); + static bool initCalled = false; + if (!initCalled) { + logging::SetLogMessageHandler(logMessageHandler); + networkChangeNotifier.reset(net::NetworkChangeNotifier::Create()); + net::HttpNetworkLayer::EnableSpdy("npn"); + initCalled = true; + } +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/ChromiumInit.h b/Source/WebKit/android/WebCoreSupport/ChromiumInit.h new file mode 100644 index 0000000..235c3dc --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ChromiumInit.h @@ -0,0 +1,38 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef ChromiumLogging_h +#define ChromiumLogging_h + +namespace android { + +// Sends chromium logs to logcat +// +// This only calls into chromium once, but can be called multiple times. +// It should be called before any other calls into external/chromium. +void initChromium(); +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.cpp new file mode 100644 index 0000000..3dc4b00 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2007, 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. + */ + +#include "config.h" +#include "ContextMenuClientAndroid.h" + +#include "NotImplemented.h" +#include <wtf/Assertions.h> + +namespace WebCore { + +void ContextMenuClientAndroid::contextMenuDestroyed() { delete this; } + +PlatformMenuDescription ContextMenuClientAndroid::getCustomMenuFromDefaultItems(ContextMenu*) { notImplemented(); return 0; } +void ContextMenuClientAndroid::contextMenuItemSelected(ContextMenuItem*, const ContextMenu*) { notImplemented(); } + +void ContextMenuClientAndroid::downloadURL(const KURL& url) { notImplemented(); } +void ContextMenuClientAndroid::copyImageToClipboard(const HitTestResult&) { notImplemented(); } +void ContextMenuClientAndroid::searchWithGoogle(const Frame*) { notImplemented(); } +void ContextMenuClientAndroid::lookUpInDictionary(Frame*) { notImplemented(); } +void ContextMenuClientAndroid::speak(const String&) { notImplemented(); } +void ContextMenuClientAndroid::stopSpeaking() { notImplemented(); } +bool ContextMenuClientAndroid::isSpeaking() +{ + notImplemented(); + return false; +} + +} diff --git a/Source/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.h b/Source/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.h new file mode 100644 index 0000000..4563829 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.h @@ -0,0 +1,51 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef ContextMenuClientAndroid_h +#define ContextMenuClientAndroid_h + +#include "ContextMenuClient.h" + +namespace WebCore { + +class ContextMenuClientAndroid : public ContextMenuClient { +public: + virtual void contextMenuDestroyed(); + + virtual PlatformMenuDescription getCustomMenuFromDefaultItems(ContextMenu*); + virtual void contextMenuItemSelected(ContextMenuItem*, const ContextMenu*); + + virtual void downloadURL(const KURL& url); + virtual void copyImageToClipboard(const HitTestResult&); + virtual void searchWithGoogle(const Frame*); + virtual void lookUpInDictionary(Frame*); + virtual void speak(const String&); + virtual void stopSpeaking(); + virtual bool isSpeaking(); +}; + +} // namespace WebCore + +#endif // ContextMenuClientAndroid_h diff --git a/Source/WebKit/android/WebCoreSupport/CookieClient.h b/Source/WebKit/android/WebCoreSupport/CookieClient.h new file mode 100644 index 0000000..56d9382 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/CookieClient.h @@ -0,0 +1,46 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef COOKIE_CLIENT_H +#define COOKIE_CLIENT_H + +#include <KURL.h> +#include <PlatformString.h> + +using namespace WebCore; + +namespace android { + +class CookieClient { + +public: + virtual ~CookieClient() {} + virtual void setCookies(const KURL& url, const String& value) = 0; + virtual String cookies(const KURL& url) = 0; + virtual bool cookiesEnabled() = 0; +}; + +} +#endif diff --git a/Source/WebKit/android/WebCoreSupport/DeviceMotionClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/DeviceMotionClientAndroid.cpp new file mode 100644 index 0000000..f8de733 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/DeviceMotionClientAndroid.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "DeviceMotionClientAndroid.h" + +#include "WebViewCore.h" + +using namespace WebCore; + +namespace android { + +DeviceMotionClientAndroid::DeviceMotionClientAndroid() + : m_client(0) +{ +} + +void DeviceMotionClientAndroid::setWebViewCore(WebViewCore* webViewCore) +{ + m_webViewCore = webViewCore; + ASSERT(m_webViewCore); +} + +void DeviceMotionClientAndroid::setController(DeviceMotionController* controller) +{ + // This will be called by the Page constructor before the WebViewCore + // has been configured regarding the mock. We cache the controller for + // later use. + m_controller = controller; + ASSERT(m_controller); +} + +void DeviceMotionClientAndroid::startUpdating() +{ + client()->startUpdating(); +} + +void DeviceMotionClientAndroid::stopUpdating() +{ + client()->stopUpdating(); +} + +DeviceMotionData* DeviceMotionClientAndroid::currentDeviceMotion() const +{ + return client()->currentDeviceMotion(); +} + +void DeviceMotionClientAndroid::deviceMotionControllerDestroyed() +{ + delete this; +} + +DeviceMotionClient* DeviceMotionClientAndroid::client() const +{ + if (!m_client) { + m_client = m_webViewCore->deviceMotionAndOrientationManager()->motionClient(); + m_client->setController(m_controller); + } + return m_client; +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/DeviceMotionClientAndroid.h b/Source/WebKit/android/WebCoreSupport/DeviceMotionClientAndroid.h new file mode 100644 index 0000000..98d4709 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/DeviceMotionClientAndroid.h @@ -0,0 +1,65 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef DeviceMotionClientAndroid_h +#define DeviceMotionClientAndroid_h + +#include <DeviceMotionClient.h> + +namespace WebCore { +class DeviceMotionController; +} + +namespace android { + +class WebViewCore; + +// The Android implementation of DeviceMotionClient. Acts as a proxy to +// the real or mock impl, which is owned by the WebViewCore. +class DeviceMotionClientAndroid : public WebCore::DeviceMotionClient { +public: + DeviceMotionClientAndroid(); + + void setWebViewCore(WebViewCore*); + + // DeviceMotionClient methods + virtual void setController(WebCore::DeviceMotionController*); + virtual void startUpdating(); + virtual void stopUpdating(); + virtual WebCore::DeviceMotionData* currentDeviceMotion() const; + virtual void deviceMotionControllerDestroyed(); + +private: + WebCore::DeviceMotionClient* client() const; + + WebViewCore* m_webViewCore; + WebCore::DeviceMotionController* m_controller; + // Lazily obtained cache of the client owned by the WebViewCore. + mutable WebCore::DeviceMotionClient* m_client; +}; + +} // namespace android + +#endif // DeviceMotionClientAndroid_h diff --git a/Source/WebKit/android/WebCoreSupport/DeviceOrientationClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/DeviceOrientationClientAndroid.cpp new file mode 100644 index 0000000..9d7145c --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/DeviceOrientationClientAndroid.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "DeviceOrientationClientAndroid.h" + +#include "WebViewCore.h" + +using namespace WebCore; + +namespace android { + +DeviceOrientationClientAndroid::DeviceOrientationClientAndroid() + : m_client(0) +{ +} + +void DeviceOrientationClientAndroid::setWebViewCore(WebViewCore* webViewCore) +{ + m_webViewCore = webViewCore; + ASSERT(m_webViewCore); +} + +void DeviceOrientationClientAndroid::setController(DeviceOrientationController* controller) +{ + // This will be called by the Page constructor before the WebViewCore + // has been configured regarding the mock. We cache the controller for + // later use. + m_controller = controller; + ASSERT(m_controller); +} + +void DeviceOrientationClientAndroid::startUpdating() +{ + client()->startUpdating(); +} + +void DeviceOrientationClientAndroid::stopUpdating() +{ + client()->stopUpdating(); +} + +DeviceOrientation* DeviceOrientationClientAndroid::lastOrientation() const +{ + return client()->lastOrientation(); +} + +void DeviceOrientationClientAndroid::deviceOrientationControllerDestroyed() +{ + delete this; +} + +DeviceOrientationClient* DeviceOrientationClientAndroid::client() const +{ + if (!m_client) { + m_client = m_webViewCore->deviceMotionAndOrientationManager()->orientationClient(); + m_client->setController(m_controller); + } + return m_client; +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/DeviceOrientationClientAndroid.h b/Source/WebKit/android/WebCoreSupport/DeviceOrientationClientAndroid.h new file mode 100644 index 0000000..7842b95 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/DeviceOrientationClientAndroid.h @@ -0,0 +1,65 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef DeviceOrientationClientAndroid_h +#define DeviceOrientationClientAndroid_h + +#include <DeviceOrientationClient.h> + +namespace WebCore { +class DeviceOrientationController; +} + +namespace android { + +class WebViewCore; + +// The Android implementation of DeviceOrientationClient. Acts as a proxy to +// the real or mock impl, which is owned by the WebViewCore. +class DeviceOrientationClientAndroid : public WebCore::DeviceOrientationClient { +public: + DeviceOrientationClientAndroid(); + + void setWebViewCore(WebViewCore*); + + // DeviceOrientationClient methods + virtual void setController(WebCore::DeviceOrientationController*); + virtual void startUpdating(); + virtual void stopUpdating(); + virtual WebCore::DeviceOrientation* lastOrientation() const; + virtual void deviceOrientationControllerDestroyed(); + +private: + WebCore::DeviceOrientationClient* client() const; + + WebViewCore* m_webViewCore; + WebCore::DeviceOrientationController* m_controller; + // Lazily obtained cache of the client owned by the WebViewCore. + mutable WebCore::DeviceOrientationClient* m_client; +}; + +} // namespace android + +#endif // DeviceOrientationClientAndroid_h diff --git a/Source/WebKit/android/WebCoreSupport/DragClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/DragClientAndroid.cpp new file mode 100644 index 0000000..f64b80c --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/DragClientAndroid.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2007, 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 "WebCore" + +#include "config.h" +#include "DragClientAndroid.h" +#include "NotImplemented.h" + +namespace android { + +void DragClientAndroid::dragControllerDestroyed() { notImplemented(); delete this; } + +void DragClientAndroid::willPerformDragDestinationAction(DragDestinationAction, DragData*) { notImplemented(); } + +DragDestinationAction DragClientAndroid::actionMaskForDrag(DragData*) { notImplemented(); return DragDestinationActionNone; } + +DragSourceAction DragClientAndroid::dragSourceActionMaskForPoint(const IntPoint&) { notImplemented(); return DragSourceActionNone; } + +void* DragClientAndroid::createDragImageForLink(KURL&, String const&, Frame*) { return NULL; } +void DragClientAndroid::willPerformDragSourceAction(DragSourceAction, IntPoint const&, Clipboard*) {} +void DragClientAndroid::startDrag(void*, IntPoint const&, IntPoint const&, Clipboard*, Frame*, bool) {} + +} diff --git a/Source/WebKit/android/WebCoreSupport/DragClientAndroid.h b/Source/WebKit/android/WebCoreSupport/DragClientAndroid.h new file mode 100644 index 0000000..020e1f1 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/DragClientAndroid.h @@ -0,0 +1,51 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef DragClientAndroid_h +#define DragClientAndroid_h + +#include "DragClient.h" + +using namespace WebCore; + +namespace android { + + class DragClientAndroid : public DragClient { + public: + virtual void willPerformDragDestinationAction(DragDestinationAction, DragData*); + virtual void willPerformDragSourceAction(DragSourceAction, const IntPoint&, Clipboard*); + virtual DragDestinationAction actionMaskForDrag(DragData*); + //We work in window rather than view coordinates here + virtual DragSourceAction dragSourceActionMaskForPoint(const IntPoint&); + + virtual void startDrag(DragImageRef dragImage, const IntPoint& dragImageOrigin, const IntPoint& eventPos, Clipboard*, Frame*, bool linkDrag = false); + virtual DragImageRef createDragImageForLink(KURL&, const String& label, Frame*); + + virtual void dragControllerDestroyed(); + }; + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp new file mode 100644 index 0000000..250fdbf --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp @@ -0,0 +1,280 @@ +/* + * Copyright 2007, 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 "WebCore" + +#include "config.h" +#include "Editor.h" +#include "EditorClientAndroid.h" +#include "Event.h" +#include "EventNames.h" +#include "FocusController.h" +#include "Frame.h" +#include "HTMLNames.h" +#include "KeyboardEvent.h" +#include "NotImplemented.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformString.h" +#include "WebViewCore.h" +#include "WindowsKeyboardCodes.h" + +using namespace WebCore::HTMLNames; + +namespace android { + +void EditorClientAndroid::pageDestroyed() { + delete this; +} + +bool EditorClientAndroid::shouldDeleteRange(Range*) { return true; } +bool EditorClientAndroid::shouldShowDeleteInterface(HTMLElement*) { notImplemented(); return false; } +bool EditorClientAndroid::smartInsertDeleteEnabled() { notImplemented(); return false; } +bool EditorClientAndroid::isSelectTrailingWhitespaceEnabled(){ notImplemented(); return false; } +bool EditorClientAndroid::isContinuousSpellCheckingEnabled() { notImplemented(); return false; } +void EditorClientAndroid::toggleContinuousSpellChecking() { notImplemented(); } +bool EditorClientAndroid::isGrammarCheckingEnabled() { notImplemented(); return false; } +void EditorClientAndroid::toggleGrammarChecking() { notImplemented(); } +int EditorClientAndroid::spellCheckerDocumentTag() { notImplemented(); return -1; } + +bool EditorClientAndroid::isEditable() { /* notImplemented(); */ return false; } + +// Following Qt's implementation. For shouldBeginEditing and shouldEndEditing. +// Returning true for these fixes issue http://b/issue?id=735185 +bool EditorClientAndroid::shouldBeginEditing(Range*) +{ + return true; +} + +bool EditorClientAndroid::shouldEndEditing(Range*) +{ + return true; +} + +bool EditorClientAndroid::shouldInsertNode(Node*, Range*, EditorInsertAction) { notImplemented(); return true; } +bool EditorClientAndroid::shouldInsertText(const String&, Range*, EditorInsertAction) { return true; } +bool EditorClientAndroid::shouldApplyStyle(CSSStyleDeclaration*, Range*) { notImplemented(); return true; } + +void EditorClientAndroid::didBeginEditing() { notImplemented(); } + +// This function is called so that the platform can handle changes to content. It is called +// after the contents have been edited or unedited (ie undo) +void EditorClientAndroid::respondToChangedContents() { notImplemented(); } + +void EditorClientAndroid::didEndEditing() { notImplemented(); } +void EditorClientAndroid::didWriteSelectionToPasteboard() { notImplemented(); } +void EditorClientAndroid::didSetSelectionTypesForPasteboard() { notImplemented(); } + +// Copied from the Window's port of WebKit. +static const unsigned AltKey = 1 << 0; +static const unsigned ShiftKey = 1 << 1; + +struct KeyDownEntry { + unsigned virtualKey; + unsigned modifiers; + const char* name; +}; + +struct KeyPressEntry { + unsigned charCode; + unsigned modifiers; + const char* name; +}; + +static const KeyDownEntry keyDownEntries[] = { + { VK_LEFT, 0, "MoveLeft" }, + { VK_LEFT, ShiftKey, "MoveLeftAndModifySelection" }, + { VK_LEFT, AltKey, "MoveWordLeft" }, + { VK_LEFT, AltKey | ShiftKey, "MoveWordLeftAndModifySelection" }, + { VK_RIGHT, 0, "MoveRight" }, + { VK_RIGHT, ShiftKey, "MoveRightAndModifySelection" }, + { VK_RIGHT, AltKey, "MoveWordRight" }, + { VK_RIGHT, AltKey | ShiftKey, "MoveWordRightAndModifySelection" }, + { VK_UP, 0, "MoveUp" }, + { VK_UP, ShiftKey, "MoveUpAndModifySelection" }, + { VK_DOWN, 0, "MoveDown" }, + { VK_DOWN, ShiftKey, "MoveDownAndModifySelection" }, + + { VK_BACK, 0, "BackwardDelete" }, + { VK_BACK, ShiftKey, "ForwardDelete" }, + { VK_BACK, AltKey, "DeleteWordBackward" }, + { VK_BACK, AltKey | ShiftKey, "DeleteWordForward" }, + + { VK_ESCAPE, 0, "Cancel" }, + { VK_TAB, 0, "InsertTab" }, + { VK_TAB, ShiftKey, "InsertBacktab" }, + { VK_RETURN, 0, "InsertNewline" }, + { VK_RETURN, AltKey, "InsertNewline" }, + { VK_RETURN, AltKey | ShiftKey, "InsertNewline" } +}; + +static const KeyPressEntry keyPressEntries[] = { + { '\t', 0, "InsertTab" }, + { '\t', ShiftKey, "InsertBackTab" }, + { '\r', 0, "InsertNewline" }, + { '\r', AltKey, "InsertNewline" }, + { '\r', AltKey | ShiftKey, "InsertNewline" } +}; + +static const char* interpretKeyEvent(const KeyboardEvent* evt) +{ + const PlatformKeyboardEvent* keyEvent = evt->keyEvent(); + + static HashMap<int, const char*>* keyDownCommandsMap = 0; + static HashMap<int, const char*>* keyPressCommandsMap = 0; + + if (!keyDownCommandsMap) { + keyDownCommandsMap = new HashMap<int, const char*>; + keyPressCommandsMap = new HashMap<int, const char*>; + + for (unsigned i = 0; i < sizeof(keyDownEntries)/sizeof(KeyDownEntry); i++) + keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name); + + for (unsigned i = 0; i < sizeof(keyPressEntries)/sizeof(KeyPressEntry); i++) + keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name); + } + + unsigned modifiers = 0; + if (keyEvent->shiftKey()) + modifiers |= ShiftKey; + if (keyEvent->altKey()) + modifiers |= AltKey; + + if (evt->type() == eventNames().keydownEvent) { + int mapKey = modifiers << 16 | evt->keyCode(); + return mapKey ? keyDownCommandsMap->get(mapKey) : 0; + } + + int mapKey = modifiers << 16 | evt->charCode(); + return mapKey ? keyPressCommandsMap->get(mapKey) : 0; +} + +void EditorClientAndroid::handleKeyboardEvent(KeyboardEvent* event) { + ASSERT(m_page); + Frame* frame = m_page->focusController()->focusedOrMainFrame(); + if (!frame) + return; + + const PlatformKeyboardEvent* keyEvent = event->keyEvent(); + // TODO: If the event is not coming from Android Java, e.g. from JavaScript, + // PlatformKeyboardEvent is null. We should support this later. + if (!keyEvent) + return; + + Editor::Command command = frame->editor()->command(interpretKeyEvent(event)); + if (keyEvent->type() == PlatformKeyboardEvent::RawKeyDown) { + if (!command.isTextInsertion() && command.execute(event)) { + // This function mimics the Windows version. However, calling event->setDefaultHandled() + // prevents the javascript key events for the delete key from happening. + // Update: Safari doesn't send delete key events to javascript so + // we will mimic that behavior. + event->setDefaultHandled(); + } + return; + } + + if (command.execute(event)) { + event->setDefaultHandled(); + return; + } + + // Don't insert null or control characters as they can result in unexpected behaviour + if (event->charCode() < ' ') + return; + + if (frame->editor()->insertText(keyEvent->text(), event)) + event->setDefaultHandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// we just don't support Undo/Redo at the moment + +void EditorClientAndroid::registerCommandForUndo(PassRefPtr<EditCommand>) {} +void EditorClientAndroid::registerCommandForRedo(PassRefPtr<EditCommand>) {} +void EditorClientAndroid::clearUndoRedoOperations() {} +bool EditorClientAndroid::canUndo() const { return false; } +bool EditorClientAndroid::canRedo() const { return false; } +void EditorClientAndroid::undo() {} +void EditorClientAndroid::redo() {} + +// functions new to Jun-07 tip of tree merge: +void EditorClientAndroid::showSpellingUI(bool) {} +void EditorClientAndroid::getGuessesForWord(String const&, const String&, WTF::Vector<String>&) {} +bool EditorClientAndroid::spellingUIIsShowing() { return false; } +void EditorClientAndroid::checkGrammarOfString(unsigned short const*, int, WTF::Vector<GrammarDetail>&, int*, int*) {} +void EditorClientAndroid::checkSpellingOfString(unsigned short const*, int, int*, int*) {} +String EditorClientAndroid::getAutoCorrectSuggestionForMisspelledWord(const String&) { return String(); } +void EditorClientAndroid::textFieldDidEndEditing(Element*) {} +void EditorClientAndroid::textDidChangeInTextArea(Element*) {} +void EditorClientAndroid::textDidChangeInTextField(Element*) {} +void EditorClientAndroid::textFieldDidBeginEditing(Element*) {} +void EditorClientAndroid::ignoreWordInSpellDocument(String const&) {} + +// We need to pass the selection up to the WebTextView +void EditorClientAndroid::respondToChangedSelection() { + if (m_uiGeneratedSelectionChange) + return; + Frame* frame = m_page->focusController()->focusedOrMainFrame(); + if (!frame || !frame->view()) + return; + WebViewCore* webViewCore = WebViewCore::getWebViewCore(frame->view()); + webViewCore->updateTextSelection(); +} + +bool EditorClientAndroid::shouldChangeSelectedRange(Range*, Range*, EAffinity, + bool) { + return m_shouldChangeSelectedRange; +} + +bool EditorClientAndroid::doTextFieldCommandFromEvent(Element*, KeyboardEvent*) { return false; } +void EditorClientAndroid::textWillBeDeletedInTextField(Element*) {} +void EditorClientAndroid::updateSpellingUIWithGrammarString(String const&, GrammarDetail const&) {} +void EditorClientAndroid::updateSpellingUIWithMisspelledWord(String const&) {} +void EditorClientAndroid::learnWord(String const&) {} + +// functions new to the Nov-16-08 tip of tree merge: +bool EditorClientAndroid::shouldMoveRangeAfterDelete(Range*, Range*) { return true; } +void EditorClientAndroid::setInputMethodState(bool) {} + +// functions new to Feb-19 tip of tree merge: +void EditorClientAndroid::handleInputMethodKeydown(KeyboardEvent*) {} + +void EditorClientAndroid::willSetInputMethodState() +{ + notImplemented(); +} + +void EditorClientAndroid::requestCheckingOfString(SpellChecker*, int, const String&) {} + +#if ENABLE(WEB_AUTOFILL) +WebAutoFill* EditorClientAndroid::getAutoFill() +{ + if (!m_autoFill) + m_autoFill.set(new WebAutoFill()); + + return m_autoFill.get(); +} +#endif + +} diff --git a/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.h b/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.h new file mode 100644 index 0000000..94a6518 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.h @@ -0,0 +1,135 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef EditorClientAndroid_h +#define EditorClientAndroid_h + +#include "config.h" + + +#include "EditorClient.h" +#include "Page.h" +#include "autofill/WebAutoFill.h" + +#include <wtf/OwnPtr.h> + +using namespace WebCore; + +namespace android { + +class EditorClientAndroid : public EditorClient { +public: + EditorClientAndroid() { + m_shouldChangeSelectedRange = true; + m_uiGeneratedSelectionChange = false; + } + virtual void pageDestroyed(); + + virtual bool shouldDeleteRange(Range*); + virtual bool shouldShowDeleteInterface(HTMLElement*); + virtual bool smartInsertDeleteEnabled(); + virtual bool isSelectTrailingWhitespaceEnabled(); + virtual bool isContinuousSpellCheckingEnabled(); + virtual void toggleContinuousSpellChecking(); + virtual bool isGrammarCheckingEnabled(); + virtual void toggleGrammarChecking(); + virtual int spellCheckerDocumentTag(); + + virtual bool isEditable(); + + virtual bool shouldBeginEditing(Range*); + virtual bool shouldEndEditing(Range*); + virtual bool shouldInsertNode(Node*, Range*, EditorInsertAction); + virtual bool shouldInsertText(const String&, Range*, EditorInsertAction); + virtual bool shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity, bool stillSelecting); + + virtual bool shouldApplyStyle(CSSStyleDeclaration*, Range*); +// virtual bool shouldChangeTypingStyle(CSSStyleDeclaration* fromStyle, CSSStyleDeclaration* toStyle); +// virtual bool doCommandBySelector(SEL selector); + virtual bool shouldMoveRangeAfterDelete(Range*, Range*); + + virtual void didBeginEditing(); + virtual void respondToChangedContents(); + virtual void respondToChangedSelection(); + virtual void didEndEditing(); + virtual void didWriteSelectionToPasteboard(); + virtual void didSetSelectionTypesForPasteboard(); +// virtual void didChangeTypingStyle:(NSNotification *)notification; +// virtual void didChangeSelection:(NSNotification *)notification; +// virtual NSUndoManager* undoManager:(WebView *)webView; + + virtual void registerCommandForUndo(PassRefPtr<EditCommand>); + virtual void registerCommandForRedo(PassRefPtr<EditCommand>); + virtual void clearUndoRedoOperations(); + + virtual bool canUndo() const; + virtual bool canRedo() const; + + virtual void undo(); + virtual void redo(); + + virtual void handleKeyboardEvent(KeyboardEvent*); + virtual void handleInputMethodKeydown(KeyboardEvent*); + + virtual void textFieldDidBeginEditing(Element*); + virtual void textFieldDidEndEditing(Element*); + virtual void textDidChangeInTextField(Element*); + virtual bool doTextFieldCommandFromEvent(Element*, KeyboardEvent*); + virtual void textWillBeDeletedInTextField(Element*); + virtual void textDidChangeInTextArea(Element*); + + virtual void ignoreWordInSpellDocument(const String&); + virtual void learnWord(const String&); + virtual void checkSpellingOfString(const UChar*, int length, int* misspellingLocation, int* misspellingLength); + virtual String getAutoCorrectSuggestionForMisspelledWord(const String& misspelledWorld); + virtual void checkGrammarOfString(const UChar*, int length, WTF::Vector<GrammarDetail>&, int* badGrammarLocation, int* badGrammarLength); + virtual void updateSpellingUIWithGrammarString(const String&, const GrammarDetail& detail); + virtual void updateSpellingUIWithMisspelledWord(const String&); + virtual void showSpellingUI(bool show); + virtual bool spellingUIIsShowing(); + virtual void getGuessesForWord(const String&, const String& context, WTF::Vector<String>& guesses); + virtual void willSetInputMethodState(); + virtual void setInputMethodState(bool); + virtual void requestCheckingOfString(SpellChecker*, int, const String&); + + // Android specific: + void setPage(Page* page) { m_page = page; } + void setShouldChangeSelectedRange(bool shouldChangeSelectedRange) { m_shouldChangeSelectedRange = shouldChangeSelectedRange; } + void setUiGeneratedSelectionChange(bool uiGenerated) { m_uiGeneratedSelectionChange = uiGenerated; } +#if ENABLE(WEB_AUTOFILL) + WebAutoFill* getAutoFill(); +#endif +private: + Page* m_page; + bool m_shouldChangeSelectedRange; + bool m_uiGeneratedSelectionChange; +#if ENABLE(WEB_AUTOFILL) + OwnPtr<WebAutoFill> m_autoFill; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/FileSystemClient.h b/Source/WebKit/android/WebCoreSupport/FileSystemClient.h new file mode 100644 index 0000000..5bde18a --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/FileSystemClient.h @@ -0,0 +1,41 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef FILESYSTEM_CLIENT_H +#define FILESYSTEM_CLIENT_H + +#include "PlatformString.h" + +using namespace WebCore; + +namespace android { + +class FileSystemClient { +public: + virtual ~FileSystemClient() { } + virtual String resolveFilePathForContentUri(const String&) = 0; +}; +} +#endif diff --git a/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp new file mode 100644 index 0000000..946a4a7 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp @@ -0,0 +1,1351 @@ +/* + * Copyright 2007, 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 "WebCore" + +#include "config.h" +#include "FrameLoaderClientAndroid.h" + +#include "BackForwardList.h" +#include "CachedFrame.h" +#include "CachedFramePlatformDataAndroid.h" +#include "Chrome.h" +#include "ChromeClientAndroid.h" +#include "DOMImplementation.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "EditorClientAndroid.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameNetworkingContextAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLPlugInElement.h" +#include "HistoryItem.h" +#include "IconDatabase.h" +#include "MIMETypeRegistry.h" +#include "NotImplemented.h" +#include "PackageNotifier.h" +#include "Page.h" +#include "PlatformBridge.h" +#include "PlatformGraphicsContext.h" +#include "PlatformString.h" +#include "PluginDatabase.h" +#include "PluginView.h" +#include "PluginViewBase.h" +#include "ProgressTracker.h" +#include "RenderPart.h" +#include "RenderView.h" +#include "RenderWidget.h" +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceHandleInternal.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SkCanvas.h" +#include "SkRect.h" +#include "TextEncoding.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreResourceLoader.h" +#include "WebHistory.h" +#include "WebIconDatabase.h" +#include "WebFrameView.h" +#include "WebViewClientError.h" +#include "WebViewCore.h" +#include "autofill/WebAutoFill.h" +#include "android_graphics.h" + +#include <utils/AssetManager.h> +#include <wtf/text/CString.h> + +extern android::AssetManager* globalAssetManager(); + +namespace android { + +static const int EXTRA_LAYOUT_DELAY = 1000; + +FrameLoaderClientAndroid::FrameLoaderClientAndroid(WebFrame* webframe) + : m_frame(NULL) + , m_webFrame(webframe) + , m_manualLoader(NULL) + , m_hasSentResponseToPlugin(false) + , m_onDemandPluginsEnabled(false) { + Retain(m_webFrame); +} + +FrameLoaderClientAndroid* FrameLoaderClientAndroid::get(const WebCore::Frame* frame) +{ + return static_cast<FrameLoaderClientAndroid*> (frame->loader()->client()); +} + +void FrameLoaderClientAndroid::frameLoaderDestroyed() { + registerForIconNotification(false); + m_frame = 0; + Release(m_webFrame); + delete this; +} + +bool FrameLoaderClientAndroid::hasWebView() const { + // FIXME, + // there is one web view per page, or top frame. + // as android's view is created from Java side, it is always there. + return true; +} + +void FrameLoaderClientAndroid::makeRepresentation(DocumentLoader*) { + m_onDemandPluginsEnabled = false; + // don't use representation + verifiedOk(); +} + +void FrameLoaderClientAndroid::forceLayout() { + ASSERT(m_frame); + m_frame->view()->forceLayout(); + // FIXME, should we adjust view size here? + m_frame->view()->adjustViewSize(); +} + +void FrameLoaderClientAndroid::forceLayoutForNonHTML() { + notImplemented(); +} + +void FrameLoaderClientAndroid::setCopiesOnScroll() { + // this is a hint about whether we need to force redraws, or can + // just copy the scrolled content. Since we always force a redraw + // anyways, we can ignore this call. + verifiedOk(); +} + +void FrameLoaderClientAndroid::detachedFromParent2() { + // FIXME, ready to detach frame from view +} + +void FrameLoaderClientAndroid::detachedFromParent3() { + // FIXME, ready to release view + notImplemented(); +} + +// This function is responsible for associating the "id" with a given +// subresource load. The following functions that accept an "id" are +// called for each subresource, so they should not be dispatched to the m_frame. +void FrameLoaderClientAndroid::assignIdentifierToInitialRequest(unsigned long id, + DocumentLoader*, const ResourceRequest&) { + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchWillSendRequest(DocumentLoader*, unsigned long id, + ResourceRequest&, const ResourceResponse&) { + lowPriority_notImplemented(); +} + +bool FrameLoaderClientAndroid::shouldUseCredentialStorage(DocumentLoader*, unsigned long identifier) +{ + notImplemented(); + return false; +} + +void FrameLoaderClientAndroid::dispatchDidReceiveAuthenticationChallenge(DocumentLoader*, + unsigned long id, const AuthenticationChallenge&) { + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidCancelAuthenticationChallenge(DocumentLoader*, + unsigned long id, const AuthenticationChallenge&) { + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidReceiveResponse(DocumentLoader*, + unsigned long id, const ResourceResponse&) { + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidReceiveContentLength(DocumentLoader*, + unsigned long id, int lengthReceived) { + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidFinishLoading(DocumentLoader*, + unsigned long id) { + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidFailLoading(DocumentLoader* docLoader, + unsigned long id, const ResourceError&) { + lowPriority_notImplemented(); +} + +bool FrameLoaderClientAndroid::dispatchDidLoadResourceFromMemoryCache(DocumentLoader*, + const ResourceRequest&, const ResourceResponse&, int length) { + notImplemented(); + return false; +} + +void FrameLoaderClientAndroid::dispatchDidHandleOnloadEvents() { +} + +void FrameLoaderClientAndroid::dispatchDidReceiveServerRedirectForProvisionalLoad() { + ASSERT(m_frame); + // Tell the load it was a redirect. + m_webFrame->loadStarted(m_frame); +} + +void FrameLoaderClientAndroid::dispatchDidCancelClientRedirect() { + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchWillPerformClientRedirect(const KURL&, + double interval, double fireDate) { + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidChangeLocationWithinPage() { + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidPushStateWithinPage() +{ + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidReplaceStateWithinPage() +{ + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidPopStateWithinPage() +{ + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchWillClose() { + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidReceiveIcon() { + ASSERT(m_frame); + if (m_frame->tree() && m_frame->tree()->parent()) + return; + WTF::String url(m_frame->loader()->url().string()); + // Try to obtain the icon image. + WebCore::Image* icon = WebCore::iconDatabase()->iconForPageURL( + url, WebCore::IntSize(16, 16)); + // If the request fails, try the original request url. + if (!icon) { + DocumentLoader* docLoader = m_frame->loader()->activeDocumentLoader(); + KURL originalURL = docLoader->originalRequest().url(); + icon = WebCore::iconDatabase()->iconForPageURL( + originalURL, WebCore::IntSize(16, 16)); + } + // There is a bug in webkit where cancelling an icon load is treated as a + // failure. When this is fixed, we can ASSERT again that we have an icon. + if (icon) { + LOGV("Received icon (%p) for %s", icon, + url.utf8().data()); + m_webFrame->didReceiveIcon(icon); + } else { + LOGV("Icon data for %s unavailable, registering for notification...", + url.utf8().data()); + registerForIconNotification(); + } +} + +void FrameLoaderClientAndroid::dispatchDidReceiveTouchIconURL(const String& url, bool precomposed) { + ASSERT(m_frame); + // Do not report sub frame touch icons + if (m_frame->tree() && m_frame->tree()->parent()) + return; + m_webFrame->didReceiveTouchIconURL(url, precomposed); +} + +void FrameLoaderClientAndroid::dispatchDidStartProvisionalLoad() { + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidReceiveTitle(const String& title) { + ASSERT(m_frame); + // Used to check for FrameLoadTypeStandard but we only want to send the title for + // the top frame and not sub-frames. + if (!m_frame->tree() || !m_frame->tree()->parent()) { + m_webFrame->setTitle(title); + } +} + +void FrameLoaderClientAndroid::dispatchDidCommitLoad() { +#if ENABLE(WEB_AUTOFILL) + if (m_frame == m_frame->page()->mainFrame()) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(m_frame->page()->editorClient()); + WebAutoFill* autoFill = editorC->getAutoFill(); + autoFill->reset(); + } +#endif + verifiedOk(); +} + +static void loadDataIntoFrame(Frame* frame, KURL baseUrl, const String& url, + const String& data) { + if (baseUrl.isEmpty()) { + baseUrl = blankURL(); + } + ResourceRequest request(baseUrl); + CString cstr = data.utf8(); + RefPtr<WebCore::SharedBuffer> buf = WebCore::SharedBuffer::create(cstr.data(), cstr.length()); + SubstituteData subData(buf, String("text/html"), String("utf-8"), + KURL(KURL(), url)); + frame->loader()->load(request, subData, false); +} + +void FrameLoaderClientAndroid::dispatchDidFailProvisionalLoad(const ResourceError& error) { + ASSERT(m_frame); + // Ignore ErrorInterrupted since it is due to a policy interruption. This + // is caused by a decision to download the main resource rather than + // display it. + if (error.errorCode() == InternalErrorInterrupted + || error.errorCode() == InternalErrorCancelled) { + // If we decided to download the main resource or if the user cancelled + // it, make sure we report that the load is done. + didFinishLoad(); + return; + } + + AssetManager* am = globalAssetManager(); + + // Check to see if the error code was not generated internally + WebCore::PlatformBridge::rawResId id = WebCore::PlatformBridge::NoDomain; + if ((error.errorCode() == ErrorFile || + error.errorCode() == ErrorFileNotFound) && + (!error.localizedDescription().isEmpty())) { + id = WebCore::PlatformBridge::LoadError; + } + String filename = m_webFrame->getRawResourceFilename(id); + if (filename.isEmpty()) + return; + + // Grab the error page from the asset manager + Asset* a = am->openNonAsset( + filename.utf8().data(), Asset::ACCESS_BUFFER); + if (!a) + return; + + // Take the failing url and encode html entities so javascript urls are not + // executed. + CString failingUrl = error.failingURL().utf8(); + WTF::Vector<char> url; + int len = failingUrl.length(); + const char* data = failingUrl.data(); + for (int i = 0; i < len; i++) { + char c = data[i]; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9')) + url.append(c); + else { + char buf[16]; + int res = sprintf(buf, "&#%d;", c); + buf[res] = 0; + url.append(buf, res); + } + } + + // Replace all occurances of %s with the failing url. + String s = UTF8Encoding().decode((const char*)a->getBuffer(false), a->getLength()); + s = s.replace("%s", String(url.data(), url.size())); + + // Replace all occurances of %e with the error text + s = s.replace("%e", error.localizedDescription()); + + // Create the request and the substitute data and tell the FrameLoader to + // load with the replacement data. + // use KURL(const char*) as KURL(const String& url) can trigger ASSERT for + // invalidate URL string. + loadDataIntoFrame(m_frame, KURL(ParsedURLString, data), error.failingURL(), s); + + // Delete the asset. + delete a; + + // Report that the load is finished, since it failed. + didFinishLoad(); +} + +void FrameLoaderClientAndroid::dispatchDidFailLoad(const ResourceError&) { + // called when page is completed with error + didFinishLoad(); +} + +void FrameLoaderClientAndroid::dispatchDidFinishDocumentLoad() { + // called when finishedParsing + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidFinishLoad() { + didFinishLoad(); +} + +void FrameLoaderClientAndroid::dispatchDidFirstLayout() { + ASSERT(m_frame); + // set EXTRA_LAYOUT_DELAY if the loader is not completed yet + if (!m_frame->loader()->isComplete()) + m_frame->document()->setExtraLayoutDelay(EXTRA_LAYOUT_DELAY); + // we need to do this here instead of dispatchDidFirstVisuallyNonEmptyLayout + // so that about:blank will update the screen. + if (!m_frame->tree()->parent()) { + // Only need to notify Java side for the top frame + WebViewCore::getWebViewCore(m_frame->view())->didFirstLayout(); + } +} + +void FrameLoaderClientAndroid::dispatchDidFirstVisuallyNonEmptyLayout() +{ + notImplemented(); +} + +Frame* FrameLoaderClientAndroid::dispatchCreatePage(const NavigationAction&) { + ASSERT(m_frame); +#ifdef ANDROID_MULTIPLE_WINDOWS + if (m_frame->settings() && m_frame->settings()->supportMultipleWindows()) + // Always a user gesture since window.open maps to + // ChromeClientAndroid::createWindow + return m_webFrame->createWindow(false, true); + else +#endif + // If the client doesn't support multiple windows, just replace the + // current frame's contents. + return m_frame; +} + +void FrameLoaderClientAndroid::dispatchShow() { + ASSERT(m_frame); + m_frame->view()->invalidate(); +} + + +static bool TreatAsAttachment(const String& content_disposition) { + // Some broken sites just send + // Content-Disposition: ; filename="file" + // screen those out here. + if (content_disposition.startsWith(";")) + return false; + + if (content_disposition.startsWith("inline", false)) + return false; + + // Some broken sites just send + // Content-Disposition: filename="file" + // without a disposition token... screen those out. + if (content_disposition.startsWith("filename", false)) + return false; + + // Also in use is Content-Disposition: name="file" + if (content_disposition.startsWith("name", false)) + return false; + + // We have a content-disposition of "attachment" or unknown. + // RFC 2183, section 2.8 says that an unknown disposition + // value should be treated as "attachment" + return true; +} + +void FrameLoaderClientAndroid::dispatchDecidePolicyForMIMEType(FramePolicyFunction func, + const String& MIMEType, const ResourceRequest& request) { + ASSERT(m_frame); + ASSERT(func); + if (!func) + return; + + PolicyChecker* policy = m_frame->loader()->policyChecker(); + + if (request.isNull()) { + (policy->*func)(PolicyIgnore); + return; + } + // Default to Use (display internally). + PolicyAction action = PolicyUse; + // Check if we should Download instead. + const ResourceResponse& response = m_frame->loader()->activeDocumentLoader()->response(); + const String& content_disposition = response.httpHeaderField("Content-Disposition"); + if (!content_disposition.isEmpty() && + TreatAsAttachment(content_disposition)) { + // Server wants to override our normal policy. + // Check to see if we are a sub frame (main frame has no owner element) + if (m_frame->ownerElement() != 0) + action = PolicyIgnore; + else + action = PolicyDownload; + (policy->*func)(action); + return; + } + + // Ask if it can be handled internally. + if (!canShowMIMEType(MIMEType)) { + // Check to see if we are a sub frame (main frame has no owner element) + if (m_frame->ownerElement() != 0) + action = PolicyIgnore; + else + action = PolicyDownload; + (policy->*func)(action); + return; + } + // A status code of 204 indicates no content change. Ignore the result. + WebCore::DocumentLoader* docLoader = m_frame->loader()->activeDocumentLoader(); + if (docLoader->response().httpStatusCode() == 204) + action = PolicyIgnore; + (policy->*func)(action); +} + +void FrameLoaderClientAndroid::dispatchDecidePolicyForNewWindowAction(FramePolicyFunction func, + const NavigationAction& action, const ResourceRequest& request, + PassRefPtr<FormState> formState, const String& frameName) { + ASSERT(m_frame); + ASSERT(func); + if (!func) + return; + + if (request.isNull()) { + (m_frame->loader()->policyChecker()->*func)(PolicyIgnore); + return; + } + + if (action.type() == NavigationTypeFormSubmitted || action.type() == NavigationTypeFormResubmitted) + m_frame->loader()->resetMultipleFormSubmissionProtection(); + + // If we get to this point it means that a link has a target that was not + // found by the frame tree. Instead of creating a new frame, return the + // current frame in dispatchCreatePage. + if (canHandleRequest(request)) + (m_frame->loader()->policyChecker()->*func)(PolicyUse); + else + (m_frame->loader()->policyChecker()->*func)(PolicyIgnore); +} + +void FrameLoaderClientAndroid::cancelPolicyCheck() { + lowPriority_notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchUnableToImplementPolicy(const ResourceError&) { + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDecidePolicyForNavigationAction(FramePolicyFunction func, + const NavigationAction& action, const ResourceRequest& request, + PassRefPtr<FormState> formState) { + ASSERT(m_frame); + ASSERT(func); + if (!func) + return; + if (request.isNull()) { + (m_frame->loader()->policyChecker()->*func)(PolicyIgnore); + return; + } + + // Reset multiple form submission protection. If this is a resubmission, we check with the + // user and reset the protection if they choose to resubmit the form (see WebCoreFrameBridge.cpp) + if (action.type() == NavigationTypeFormSubmitted) + m_frame->loader()->resetMultipleFormSubmissionProtection(); + + if (action.type() == NavigationTypeFormResubmitted) { + m_webFrame->decidePolicyForFormResubmission(func); + return; + } else + (m_frame->loader()->policyChecker()->*func)(PolicyUse); +} + +void FrameLoaderClientAndroid::dispatchWillSubmitForm(FramePolicyFunction func, PassRefPtr<FormState>) { + ASSERT(m_frame); + ASSERT(func); + (m_frame->loader()->policyChecker()->*func)(PolicyUse); +} + +void FrameLoaderClientAndroid::dispatchWillSendSubmitEvent(HTMLFormElement* form) +{ + if (m_webFrame->shouldSaveFormData()) + m_webFrame->saveFormData(form); +} + +void FrameLoaderClientAndroid::dispatchDidLoadMainResource(DocumentLoader*) { + notImplemented(); +} + +void FrameLoaderClientAndroid::revertToProvisionalState(DocumentLoader*) { + notImplemented(); +} + +void FrameLoaderClientAndroid::setMainDocumentError(DocumentLoader* docLoader, const ResourceError& error) { + ASSERT(m_frame); + if (m_manualLoader) { + m_manualLoader->didFail(error); + m_manualLoader = NULL; + m_hasSentResponseToPlugin = false; + } else { + if (!error.isNull() && error.errorCode() >= InternalErrorLast && error.errorCode() != ERROR_OK) + m_webFrame->reportError(error.errorCode(), + error.localizedDescription(), error.failingURL()); + } +} + +// This function is called right before the progress is updated. +void FrameLoaderClientAndroid::willChangeEstimatedProgress() { + verifiedOk(); +} + +// This function is called after the progress has been updated. The bad part +// about this is that when a page is completed, this function is called after +// the progress has been reset to 0. +void FrameLoaderClientAndroid::didChangeEstimatedProgress() { + verifiedOk(); +} + +// This will give us the initial estimate when the page first starts to load. +void FrameLoaderClientAndroid::postProgressStartedNotification() { + ASSERT(m_frame); + if (m_frame->page()) + m_webFrame->setProgress(m_frame->page()->progress()->estimatedProgress()); +} + +// This will give us any updated progress including the final progress. +void FrameLoaderClientAndroid::postProgressEstimateChangedNotification() { + ASSERT(m_frame); + if (m_frame->page()) + m_webFrame->setProgress(m_frame->page()->progress()->estimatedProgress()); +} + +// This is just a notification that the progress has finished. Don't call +// setProgress(1) because postProgressEstimateChangedNotification will do so. +void FrameLoaderClientAndroid::postProgressFinishedNotification() { + WebViewCore* core = WebViewCore::getWebViewCore(m_frame->view()); + if (!m_frame->tree()->parent()) { + // only need to notify Java for the top frame + core->notifyProgressFinished(); + } + // notify plugins that the frame has loaded + core->notifyPluginsOnFrameLoad(m_frame); +} + +void FrameLoaderClientAndroid::setMainFrameDocumentReady(bool) { + // this is only interesting once we provide an external API for the DOM + notImplemented(); +} + +void FrameLoaderClientAndroid::startDownload(const ResourceRequest&) { + notImplemented(); +} + +void FrameLoaderClientAndroid::willChangeTitle(DocumentLoader*) { + verifiedOk(); +} + +void FrameLoaderClientAndroid::didChangeTitle(DocumentLoader* loader) { + verifiedOk(); +} + +void FrameLoaderClientAndroid::finishedLoading(DocumentLoader* docLoader) { + // Telling the frame we received some data and passing 0 as the data is our + // way to get work done that is normally done when the first bit of data is + // received, even for the case of a document with no data (like about:blank) + if (!m_manualLoader) { + committedLoad(docLoader, 0, 0); + return; + } + + m_manualLoader->didFinishLoading(); + m_manualLoader = NULL; + m_hasSentResponseToPlugin = false; +} + +void FrameLoaderClientAndroid::updateGlobalHistory() { + ASSERT(m_frame); + + DocumentLoader* docLoader = m_frame->loader()->documentLoader(); + ASSERT(docLoader); + + // Code copied from FrameLoader.cpp:createHistoryItem + // Only add this URL to the database if it is a valid page + if (docLoader->unreachableURL().isEmpty() + && docLoader->response().httpStatusCode() < 400) { + m_webFrame->updateVisitedHistory(docLoader->urlForHistory(), false); + if (!docLoader->serverRedirectSourceForHistory().isNull()) + m_webFrame->updateVisitedHistory(KURL(ParsedURLString, docLoader->serverRedirectDestinationForHistory()), false); + } +} + +void FrameLoaderClientAndroid::updateGlobalHistoryRedirectLinks() { + // Note, do we need to do anything where there is no HistoryItem? If we call + // updateGlobalHistory(), we will add bunch of "data:xxx" urls for gmail.com + // which is not what we want. Opt to do nothing now. +} + +bool FrameLoaderClientAndroid::shouldGoToHistoryItem(HistoryItem* item) const { + // hmmm, seems like we might do a more thoughtful check + ASSERT(m_frame); + return item != NULL; +} + +void FrameLoaderClientAndroid::didDisplayInsecureContent() +{ + notImplemented(); +} + +void FrameLoaderClientAndroid::didRunInsecureContent(SecurityOrigin*) +{ + notImplemented(); +} + +void FrameLoaderClientAndroid::committedLoad(DocumentLoader* loader, const char* data, int length) { + if (!m_manualLoader) + loader->commitData(data, length); + + // commit data may have created a manual plugin loader + if (m_manualLoader) { + if (!m_hasSentResponseToPlugin) { + m_manualLoader->didReceiveResponse(loader->response()); + // Failure could cause the main document to have an error causing + // the manual loader to be reset. + if (!m_manualLoader) + return; + m_hasSentResponseToPlugin = true; + } + m_manualLoader->didReceiveData(data, length); + } +} + +ResourceError FrameLoaderClientAndroid::cancelledError(const ResourceRequest& request) { + return ResourceError(String(), InternalErrorCancelled, request.url(), String()); +} + +ResourceError FrameLoaderClientAndroid::cannotShowURLError(const ResourceRequest& request) { + return ResourceError(String(), InternalErrorCannotShowUrl, request.url(), String()); +} + +ResourceError FrameLoaderClientAndroid::interruptForPolicyChangeError(const ResourceRequest& request) { + return ResourceError(String(), InternalErrorInterrupted, request.url(), String()); +} + +ResourceError FrameLoaderClientAndroid::cannotShowMIMETypeError(const ResourceResponse& request) { + return ResourceError(String(), InternalErrorCannotShowMimeType, request.url(), String()); +} + +ResourceError FrameLoaderClientAndroid::fileDoesNotExistError(const ResourceResponse& request) { + return ResourceError(String(), InternalErrorFileDoesNotExist, request.url(), String()); +} + +ResourceError FrameLoaderClientAndroid::pluginWillHandleLoadError(const ResourceResponse& request) { + return ResourceError(String(), InternalErrorPluginWillHandleLoadError, request.url(), String()); +} + +bool FrameLoaderClientAndroid::shouldFallBack(const ResourceError&) { + notImplemented(); + return false; +} + +bool FrameLoaderClientAndroid::canHandleRequest(const ResourceRequest& request) const { + ASSERT(m_frame); + // Don't allow hijacking of intrapage navigation + if (WebCore::equalIgnoringFragmentIdentifier(request.url(), m_frame->loader()->url())) + return true; + + // Don't allow hijacking of iframe urls that are http or https + if (request.url().protocol().startsWith("http", false) && + m_frame->tree() && m_frame->tree()->parent()) + return true; + + return m_webFrame->canHandleRequest(request); +} + +bool FrameLoaderClientAndroid::canShowMIMEType(const String& mimeType) const { + // FIXME: This looks like it has to do with whether or not a type can be + // shown "internally" (i.e. inside the browser) regardless of whether + // or not the browser is doing the rendering, e.g. a full page plugin. + if (MIMETypeRegistry::isSupportedImageResourceMIMEType(mimeType) || + MIMETypeRegistry::isSupportedNonImageMIMEType(mimeType) || + MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType) || + (m_frame && m_frame->settings() + && m_frame->settings()->arePluginsEnabled() + && PluginDatabase::installedPlugins()->isMIMETypeRegistered( + mimeType)) || + (DOMImplementation::isTextMIMEType(mimeType) && + !mimeType.startsWith("text/vnd")) || + DOMImplementation::isXMLMIMEType(mimeType)) + return true; + return false; +} + +bool FrameLoaderClientAndroid::canShowMIMETypeAsHTML(const String& mimeType) const { + return false; +} + +bool FrameLoaderClientAndroid::representationExistsForURLScheme(const String&) const { + // don't use representation + verifiedOk(); + return false; +} + +String FrameLoaderClientAndroid::generatedMIMETypeForURLScheme(const String& URLScheme) const { + // FIXME, copy from Apple's port + String mimetype("x-apple-web-kit/"); + mimetype.append(URLScheme.lower()); + return mimetype; +} + +void FrameLoaderClientAndroid::frameLoadCompleted() { + // copied from Apple port, without this back with sub-frame will trigger ASSERT + ASSERT(m_frame); +} + +void FrameLoaderClientAndroid::saveViewStateToItem(HistoryItem* item) { + ASSERT(m_frame); + ASSERT(item); + // store the current scale (only) for the top frame + if (!m_frame->tree()->parent()) { + // We should have added a bridge when the child item was added to its + // parent. + AndroidWebHistoryBridge* bridge = item->bridge(); + ASSERT(bridge); + WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_frame->view()); + bridge->setScale(webViewCore->scale()); + bridge->setTextWrapScale(webViewCore->textWrapScale()); + } + + WebCore::notifyHistoryItemChanged(item); +} + +void FrameLoaderClientAndroid::restoreViewState() { + WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_frame->view()); + HistoryItem* item = m_frame->loader()->history()->currentItem(); + AndroidWebHistoryBridge* bridge = item->bridge(); + // restore the scale (only) for the top frame + if (!m_frame->tree()->parent()) { + webViewCore->restoreScale(bridge->scale(), bridge->textWrapScale()); + } +} + +void FrameLoaderClientAndroid::dispatchDidAddBackForwardItem(HistoryItem* item) const { + ASSERT(m_frame); + m_webFrame->addHistoryItem(item); +} + +void FrameLoaderClientAndroid::dispatchDidRemoveBackForwardItem(HistoryItem* item) const { + ASSERT(m_frame); + m_webFrame->removeHistoryItem(0); +} + +void FrameLoaderClientAndroid::dispatchDidChangeBackForwardIndex() const { + ASSERT(m_frame); + BackForwardList* list = m_frame->page()->backForwardList(); + ASSERT(list); + m_webFrame->updateHistoryIndex(list->backListCount()); +} + +void FrameLoaderClientAndroid::provisionalLoadStarted() { + ASSERT(m_frame); + m_webFrame->loadStarted(m_frame); +} + +void FrameLoaderClientAndroid::didFinishLoad() { + ASSERT(m_frame); + m_frame->document()->setExtraLayoutDelay(0); + m_webFrame->didFinishLoad(m_frame); +} + +void FrameLoaderClientAndroid::prepareForDataSourceReplacement() { + verifiedOk(); +} + +PassRefPtr<DocumentLoader> FrameLoaderClientAndroid::createDocumentLoader( + const ResourceRequest& request, const SubstituteData& data) { + RefPtr<DocumentLoader> loader = DocumentLoader::create(request, data); + return loader.release(); +} + +void FrameLoaderClientAndroid::setTitle(const String& title, const KURL& url) { + // Not needed. dispatchDidReceiveTitle is called immediately after this. + // url is used to update the Apple port history items. + verifiedOk(); +} + +String FrameLoaderClientAndroid::userAgent(const KURL& u) { + return m_webFrame->userAgentForURL(&u); +} + +void FrameLoaderClientAndroid::savePlatformDataToCachedFrame(WebCore::CachedFrame* cachedFrame) { + CachedFramePlatformDataAndroid* platformData = new CachedFramePlatformDataAndroid(m_frame->settings()); + cachedFrame->setCachedFramePlatformData(platformData); +} + +void FrameLoaderClientAndroid::transitionToCommittedFromCachedFrame(WebCore::CachedFrame* cachedFrame) { + CachedFramePlatformDataAndroid* platformData = reinterpret_cast<CachedFramePlatformDataAndroid*>(cachedFrame->cachedFramePlatformData()); +#ifdef ANDROID_META_SUPPORT + platformData->restoreMetadata(m_frame->settings()); +#endif + m_webFrame->transitionToCommitted(m_frame); +} + +void FrameLoaderClientAndroid::transitionToCommittedForNewPage() { + ASSERT(m_frame); + +#ifdef ANDROID_META_SUPPORT + // reset metadata settings for the main frame as they are not preserved cross page + if (m_frame == m_frame->page()->mainFrame() && m_frame->settings()) + m_frame->settings()->resetMetadataSettings(); +#endif + + // Save the old WebViewCore before creating a new FrameView. There is one + // WebViewCore per page. Each frame, including the main frame and sub frame, + // has a 1:1 FrameView and WebFrameView. + WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_frame->view()); + Retain(webViewCore); + + // Save the old WebFrameView's bounds and apply them to the new WebFrameView + WebFrameView* oldWebFrameView = static_cast<WebFrameView*> (m_frame->view()->platformWidget()); + IntRect bounds = oldWebFrameView->getBounds(); + IntRect visBounds = oldWebFrameView->getVisibleBounds(); + IntRect windowBounds = oldWebFrameView->getWindowBounds(); + WebCore::FrameView* oldFrameView = oldWebFrameView->view(); + const float oldZoomFactor = oldFrameView->frame()->textZoomFactor(); + m_frame->createView(bounds.size(), oldFrameView->baseBackgroundColor(), oldFrameView->isTransparent(), + oldFrameView->fixedLayoutSize(), oldFrameView->useFixedLayout()); + if (oldZoomFactor != 1.0f && oldZoomFactor != m_frame->textZoomFactor()) { + m_frame->setTextZoomFactor(oldZoomFactor); + } + + // Create a new WebFrameView for the new FrameView + WebFrameView* newFrameView = new WebFrameView(m_frame->view(), webViewCore); + newFrameView->setLocation(bounds.x(), bounds.y()); + newFrameView->setSize(bounds.width(), bounds.height()); + newFrameView->setVisibleSize(visBounds.width(), visBounds.height()); + newFrameView->setWindowBounds(windowBounds.x(), windowBounds.y(), windowBounds.width(), windowBounds.height()); + // newFrameView attaches itself to FrameView which Retains the reference, so + // call Release for newFrameView + Release(newFrameView); + // WebFrameView Retains webViewCore, so call Release for webViewCore + Release(webViewCore); + + m_webFrame->transitionToCommitted(m_frame); +} + +void FrameLoaderClientAndroid::dispatchDidBecomeFrameset(bool) +{ +} + +bool FrameLoaderClientAndroid::canCachePage() const { + return true; +} + +void FrameLoaderClientAndroid::download(ResourceHandle* handle, const ResourceRequest&, + const ResourceRequest&, const ResourceResponse&) { + // Get the C++ side of the load listener and tell it to handle the download + handle->getInternal()->m_loader->downloadFile(); +} + +WTF::PassRefPtr<WebCore::Frame> FrameLoaderClientAndroid::createFrame(const KURL& url, const String& name, + HTMLFrameOwnerElement* ownerElement, const String& referrer, + bool allowsScrolling, int marginWidth, int marginHeight) +{ + Frame* parent = ownerElement->document()->frame(); + FrameLoaderClientAndroid* loaderC = new FrameLoaderClientAndroid(m_webFrame); + RefPtr<Frame> pFrame = Frame::create(parent->page(), ownerElement, loaderC); + Frame* newFrame = pFrame.get(); + loaderC->setFrame(newFrame); + // Append the subframe to the parent and set the name of the subframe. The name must be set after + // appending the child so that the name becomes unique. + parent->tree()->appendChild(newFrame); + newFrame->tree()->setName(name); + // Create a new FrameView and WebFrameView for the child frame to draw into. + RefPtr<FrameView> frameView = FrameView::create(newFrame); + WebFrameView* webFrameView = new WebFrameView(frameView.get(), + WebViewCore::getWebViewCore(parent->view())); + // frameView Retains webFrameView, so call Release for webFrameView + Release(webFrameView); + // Attach the frameView to the newFrame. + newFrame->setView(frameView); + newFrame->init(); + newFrame->selection()->setFocused(true); + LOGV("::WebCore:: createSubFrame returning %p", newFrame); + + // The creation of the frame may have run arbitrary JavaScript that removed it from the page already. + if (!pFrame->page()) + return 0; + + parent->loader()->loadURLIntoChildFrame(url, referrer, pFrame.get()); + + // onLoad may cuase the frame to be removed from the document. Allow the RefPtr to delete the child frame. + if (!pFrame->tree()->parent()) + return NULL; + + return pFrame.release(); +} + +// YouTube flash url path starts with /v/ +static const char slash_v_slash[] = { '/', 'v', '/' }; +static const char slash_e_slash[] = { '/', 'e', '/' }; + +static bool isValidYouTubeVideo(const String& path) +{ + if (!charactersAreAllASCII(path.characters(), path.length())) + return false; + unsigned int len = path.length(); + if (len <= sizeof(slash_v_slash)) // check for more than just /v/ + return false; + CString str = path.lower().utf8(); + const char* data = str.data(); + // Youtube flash url can start with /v/ or /e/ + if (memcmp(data, slash_v_slash, sizeof(slash_v_slash)) != 0) + if (memcmp(data, slash_e_slash, sizeof(slash_e_slash)) != 0) + return false; + // Start after /v/ + for (unsigned int i = sizeof(slash_v_slash); i < len; i++) { + char c = data[i]; + // Check for alpha-numeric characters only. + if (WTF::isASCIIAlphanumeric(c) || c == '_' || c == '-') + continue; + // The url can have more parameters such as &hl=en after the video id. + // Once we start seeing extra parameters we can return true. + return c == '&' && i > sizeof(slash_v_slash); + } + return true; +} + +static bool isYouTubeUrl(const KURL& url, const String& mimeType) +{ + String host = url.host(); + bool youtube = host.endsWith("youtube.com") + || host.endsWith("youtube-nocookie.com"); + return youtube && isValidYouTubeVideo(url.path()) + && equalIgnoringCase(mimeType, "application/x-shockwave-flash"); +} + +static bool isYouTubeInstalled() { + return WebCore::packageNotifier().isPackageInstalled("com.google.android.youtube"); +} + +// Use PluginViewBase rather than an Android specific sub class as we do not require any +// Android specific functionality; this just renders a placeholder which will later +// activate the real plugin. +class PluginToggleWidget : public PluginViewBase { +public: + PluginToggleWidget(Frame* parent, const IntSize& size, + HTMLPlugInElement* elem, const KURL& url, + const WTF::Vector<String>& paramNames, + const WTF::Vector<String>& paramValues, const String& mimeType, + bool loadManually) + : PluginViewBase(0) + , m_parent(parent) + , m_size(size) + , m_element(elem) + , m_url(url) + , m_paramNames(paramNames) + , m_paramValues(paramValues) + , m_mimeType(mimeType) + , m_loadManually(loadManually) + { + resize(size); + } + + virtual void paint(GraphicsContext* ctx, const IntRect& rect) + { + // Most of this code is copied from PluginView::paintMissingPluginIcon + // with slight modification. + + static RefPtr<Image> image; + if (!image) { + image = Image::loadPlatformResource("togglePlugin"); + } + + IntRect imageRect(x(), y(), image->width(), image->height()); + + int xOffset = (width() - imageRect.width()) >> 1; + int yOffset = (height() - imageRect.height()) >> 1; + + imageRect.move(xOffset, yOffset); + + if (!rect.intersects(imageRect)) + return; + + // FIXME: We need to clip similarly to paintMissingPluginIcon but it is + // way screwed up right now. It has something to do with how we tell + // webkit the scroll position and it causes the placeholder to get + // clipped very badly. http://b/issue?id=2533303 + + ctx->save(); + ctx->clip(frameRect()); + + ctx->setFillColor(Color::white, ColorSpaceDeviceRGB); + ctx->fillRect(frameRect()); + if (frameRect().contains(imageRect)) { + // Leave a 2 pixel padding. + const int pixelWidth = 2; + IntRect innerRect = frameRect(); + innerRect.inflate(-pixelWidth); + // Draw a 2 pixel light gray border. + ctx->setStrokeColor(Color::lightGray, ColorSpaceDeviceRGB); + ctx->strokeRect(innerRect, pixelWidth); + } + + // Draw the image in the center + ctx->drawImage(image.get(), ColorSpaceDeviceRGB, imageRect.location()); + ctx->restore(); + } + + virtual void handleEvent(Event* event) + { + if (event->type() != eventNames().clickEvent) + return; + + Frame* frame = m_parent->page()->mainFrame(); + while (frame) { + RenderView* view = frame->contentRenderer(); + const HashSet<RenderWidget*> widgets = view->widgets(); + HashSet<RenderWidget*>::const_iterator it = widgets.begin(); + HashSet<RenderWidget*>::const_iterator end = widgets.end(); + for (; it != end; ++it) { + Widget* widget = (*it)->widget(); + // PluginWidget is used only with PluginToggleWidget + if (widget && widget->isPluginViewBase()) { + PluginToggleWidget* ptw = + static_cast<PluginToggleWidget*>(widget); + ptw->swapPlugin(*it); + } + } + frame = frame->tree()->traverseNext(); + } + } + + void swapPlugin(RenderWidget* renderer) { + typedef FrameLoaderClientAndroid FLCA; + FLCA* client = static_cast<FLCA*>(m_parent->loader()->client()); + client->enableOnDemandPlugins(); + WTF::PassRefPtr<PluginView> prpWidget = + PluginView::create(m_parent.get(), + m_size, + m_element, + m_url, + m_paramNames, + m_paramValues, + m_mimeType, + m_loadManually); + RefPtr<Widget> myProtector(this); + prpWidget->focusPluginElement(); + renderer->setWidget(prpWidget); + } + +private: + void invalidateRect(const IntRect& rect) { } + + RefPtr<Frame> m_parent; + IntSize m_size; + HTMLPlugInElement* m_element; + KURL m_url; + WTF::Vector<String> m_paramNames; + WTF::Vector<String> m_paramValues; + String m_mimeType; + bool m_loadManually; +}; + +WTF::PassRefPtr<Widget> FrameLoaderClientAndroid::createPlugin( + const IntSize& size, + HTMLPlugInElement* element, + const KURL& url, + const WTF::Vector<String>& names, + const WTF::Vector<String>& values, + const String& mimeType, + bool loadManually) { + WTF::PassRefPtr<PluginView> prpWidget = 0; +#ifdef ANDROID_PLUGINS + // This is copied from PluginView.cpp. We need to determine if a plugin + // will be found before doing some of the work in PluginView. + String mimeTypeCopy = mimeType; + PluginPackage* plugin = + PluginDatabase::installedPlugins()->findPlugin(url, mimeTypeCopy); + if (!plugin && PluginDatabase::installedPlugins()->refresh()) { + mimeTypeCopy = mimeType; + plugin = PluginDatabase::installedPlugins()->findPlugin(url, + mimeTypeCopy); + } + Settings* settings = m_frame->settings(); + // Do the placeholder if plugins are on-demand and there is a plugin for the + // given mime type. + if (settings && settings->arePluginsOnDemand() && plugin && + !m_onDemandPluginsEnabled) { + return adoptRef(new PluginToggleWidget(m_frame, size, element, url, + names, values, mimeType, loadManually)); + } + prpWidget = PluginView::create(m_frame, + size, + element, + url, + names, + values, + mimeType, + loadManually); + // Return the plugin if it was loaded successfully. Otherwise, fallback to + // the youtube placeholder if possible. No need to check prpWidget as + // PluginView::create will create a PluginView for missing plugins. + // Note: this check really only checks if the plugin was found and not if + // the plugin was loaded. + if (prpWidget->status() == PluginStatusLoadedSuccessfully) + return prpWidget; +#endif + // Create an iframe for youtube urls. + if (isYouTubeUrl(url, mimeType) && isYouTubeInstalled()) { + WTF::RefPtr<Frame> frame = createFrame(blankURL(), String(), element, + String(), false, 0, 0); + if (frame) { + // grab everything after /v/ + String videoId = url.path().substring(sizeof(slash_v_slash)); + // Extract just the video id + unsigned videoIdEnd = 0; + for (; videoIdEnd < videoId.length(); videoIdEnd++) { + if (videoId[videoIdEnd] == '&') { + videoId = videoId.left(videoIdEnd); + break; + } + } + AssetManager* am = globalAssetManager(); + Asset* a = am->open("webkit/youtube.html", + Asset::ACCESS_BUFFER); + if (!a) + return NULL; + String s = String((const char*)a->getBuffer(false), a->getLength()); + s = s.replace("VIDEO_ID", videoId); + delete a; + loadDataIntoFrame(frame.get(), + KURL(ParsedURLString, "file:///android_asset/webkit/"), String(), s); + // Transfer ownership to a local refptr. + WTF::RefPtr<Widget> widget(frame->view()); + return widget.release(); + } + } + return prpWidget; +} + +void FrameLoaderClientAndroid::redirectDataToPlugin(Widget* pluginWidget) { + // Do not redirect data if the Widget is our plugin placeholder. + if (pluginWidget->isPluginView()) { + m_manualLoader = static_cast<PluginView*>(pluginWidget); + } +} + +WTF::PassRefPtr<Widget> FrameLoaderClientAndroid::createJavaAppletWidget(const IntSize&, HTMLAppletElement*, + const KURL& baseURL, const WTF::Vector<String>& paramNames, + const WTF::Vector<String>& paramValues) { + // don't support widget yet + notImplemented(); + return 0; +} + +void FrameLoaderClientAndroid::didTransferChildFrameToNewDocument(WebCore::Page*) +{ + ASSERT(m_frame); + // m_webFrame points to the WebFrame for the page that our frame previously + // belonged to. If the frame now belongs to a new page, we need to update + // m_webFrame to point to the WebFrame for the new page. + Page* newPage = m_frame->page(); + if (newPage != m_webFrame->page()) { + ChromeClientAndroid* chromeClient = static_cast<ChromeClientAndroid*>(newPage->chrome()->client()); + Release(m_webFrame); + m_webFrame = chromeClient->webFrame(); + Retain(m_webFrame); + } +} + +void FrameLoaderClientAndroid::transferLoadingResourceFromPage(unsigned long, DocumentLoader*, const ResourceRequest&, Page*) +{ + notImplemented(); +} + +// This function is used by the <OBJECT> element to determine the type of +// the contents and work out if it can render it. +ObjectContentType FrameLoaderClientAndroid::objectContentType(const KURL& url, + const String& mimeType) { + return FrameLoader::defaultObjectContentType(url, mimeType); +} + +// This function allows the application to set the correct CSS media +// style. Android could use it to set the media style 'handheld'. Safari +// may use it to set the media style to 'print' when the user wants to print +// a particular web page. +String FrameLoaderClientAndroid::overrideMediaType() const { + lowPriority_notImplemented(); + return String(); +} + +// This function is used to re-attach Javascript<->native code classes. +void FrameLoaderClientAndroid::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld* world) +{ + if (world != mainThreadNormalWorld()) + return; + + ASSERT(m_frame); + LOGV("::WebCore:: windowObjectCleared called on frame %p for %s\n", + m_frame, m_frame->loader()->url().string().ascii().data()); + m_webFrame->windowObjectCleared(m_frame); +} + +void FrameLoaderClientAndroid::documentElementAvailable() { +} + +// functions new to Jun-07 tip of tree merge: +ResourceError FrameLoaderClientAndroid::blockedError(ResourceRequest const& request) { + return ResourceError(String(), InternalErrorFileDoesNotExist, String(), String()); +} + +// functions new to Nov-07 tip of tree merge: +void FrameLoaderClientAndroid::didPerformFirstNavigation() const { + // This seems to be just a notification that the UI can listen to, to + // know if the user has performed first navigation action. + // It is called from + // void FrameLoader::addBackForwardItemClippedAtTarget(bool doClip) + // "Navigation" here means a transition from one page to another that + // ends up in the back/forward list. +} + +void FrameLoaderClientAndroid::registerForIconNotification(bool listen) { + if (listen) + WebIconDatabase::RegisterForIconNotification(this); + else + WebIconDatabase::UnregisterForIconNotification(this); +} + +// This is the WebIconDatabaseClient method for receiving a notification when we +// get the icon for the page. +void FrameLoaderClientAndroid::didAddIconForPageUrl(const String& pageUrl) { + // This call must happen before dispatchDidReceiveIcon since that method + // may register for icon notifications again since the icon data may have + // to be read from disk. + registerForIconNotification(false); + KURL u(ParsedURLString, pageUrl); + if (equalIgnoringFragmentIdentifier(u, m_frame->loader()->url())) { + dispatchDidReceiveIcon(); + } +} + +void FrameLoaderClientAndroid::dispatchDidChangeIcons() { + notImplemented(); +} + +PassRefPtr<FrameNetworkingContext> FrameLoaderClientAndroid::createNetworkingContext() +{ + return FrameNetworkingContextAndroid::create(getFrame()); +} + +} diff --git a/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h new file mode 100644 index 0000000..25561a8 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h @@ -0,0 +1,271 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef FrameLoaderClientAndroid_h +#define FrameLoaderClientAndroid_h + +#include "CacheBuilder.h" +#include "FrameLoaderClient.h" +#include "ResourceResponse.h" +#include "WebIconDatabase.h" +#include <wtf/Forward.h> + +namespace WebCore { +class PluginManualLoader; +} + +using namespace WebCore; + +namespace android { + class WebFrame; + + class FrameLoaderClientAndroid : public FrameLoaderClient, + WebIconDatabaseClient { + public: + FrameLoaderClientAndroid(WebFrame* webframe); + + Frame* getFrame() { return m_frame; } + static FrameLoaderClientAndroid* get(const Frame* frame); + + void setFrame(Frame* frame) { m_frame = frame; } + WebFrame* webFrame() const { return m_webFrame; } + + virtual void frameLoaderDestroyed(); + + virtual bool hasWebView() const; // mainly for assertions + + virtual void makeRepresentation(DocumentLoader*); + virtual void forceLayout(); + virtual void forceLayoutForNonHTML(); + + virtual void setCopiesOnScroll(); + + virtual void detachedFromParent2(); + virtual void detachedFromParent3(); + + virtual void assignIdentifierToInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&); + + virtual void dispatchWillSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse); + virtual bool shouldUseCredentialStorage(DocumentLoader*, unsigned long identifier); + virtual void dispatchDidReceiveAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&); + virtual void dispatchDidCancelAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&); + virtual void dispatchDidReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse&); + virtual void dispatchDidReceiveContentLength(DocumentLoader*, unsigned long identifier, int lengthReceived); + virtual void dispatchDidFinishLoading(DocumentLoader*, unsigned long identifier); + virtual void dispatchDidFailLoading(DocumentLoader*, unsigned long identifier, const ResourceError&); + virtual bool dispatchDidLoadResourceFromMemoryCache(DocumentLoader*, const ResourceRequest&, const ResourceResponse&, int length); + + virtual void dispatchDidHandleOnloadEvents(); + virtual void dispatchDidReceiveServerRedirectForProvisionalLoad(); + virtual void dispatchDidCancelClientRedirect(); + virtual void dispatchWillPerformClientRedirect(const KURL&, double interval, double fireDate); + virtual void dispatchDidChangeLocationWithinPage(); + virtual void dispatchDidPushStateWithinPage(); + virtual void dispatchDidReplaceStateWithinPage(); + virtual void dispatchDidPopStateWithinPage(); + virtual void dispatchWillClose(); + virtual void dispatchDidReceiveIcon(); + virtual void dispatchDidStartProvisionalLoad(); + virtual void dispatchDidReceiveTitle(const String& title); + virtual void dispatchDidCommitLoad(); + virtual void dispatchDidFailProvisionalLoad(const ResourceError&); + virtual void dispatchDidFailLoad(const ResourceError&); + virtual void dispatchDidFinishDocumentLoad(); + virtual void dispatchDidFinishLoad(); + virtual void dispatchDidFirstLayout(); + virtual void dispatchDidFirstVisuallyNonEmptyLayout(); + + virtual Frame* dispatchCreatePage(const NavigationAction&); + virtual void dispatchShow(); + + virtual void dispatchDecidePolicyForMIMEType(FramePolicyFunction, const String& MIMEType, const ResourceRequest&); + virtual void dispatchDecidePolicyForNewWindowAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName); + virtual void dispatchDecidePolicyForNavigationAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>); + virtual void cancelPolicyCheck(); + + virtual void dispatchUnableToImplementPolicy(const ResourceError&); + + virtual void dispatchWillSubmitForm(FramePolicyFunction, PassRefPtr<FormState>); + + virtual void dispatchDidLoadMainResource(DocumentLoader*); + virtual void revertToProvisionalState(DocumentLoader*); + virtual void setMainDocumentError(DocumentLoader*, const ResourceError&); + + virtual void willChangeEstimatedProgress(); + virtual void didChangeEstimatedProgress(); + virtual void postProgressStartedNotification(); + virtual void postProgressEstimateChangedNotification(); + virtual void postProgressFinishedNotification(); + + virtual void setMainFrameDocumentReady(bool); + + virtual void startDownload(const ResourceRequest&); + + virtual void willChangeTitle(DocumentLoader*); + virtual void didChangeTitle(DocumentLoader*); + + virtual void committedLoad(DocumentLoader*, const char*, int); + virtual void finishedLoading(DocumentLoader*); + + virtual void updateGlobalHistory(); + virtual void updateGlobalHistoryRedirectLinks(); + + virtual bool shouldGoToHistoryItem(HistoryItem*) const; + + virtual void didDisplayInsecureContent(); + virtual void didRunInsecureContent(SecurityOrigin*); + + virtual void dispatchDidAddBackForwardItem(HistoryItem*) const; + virtual void dispatchDidRemoveBackForwardItem(HistoryItem*) const; + virtual void dispatchDidChangeBackForwardIndex() const; + + virtual ResourceError cancelledError(const ResourceRequest&); + virtual ResourceError blockedError(const ResourceRequest&); + virtual ResourceError cannotShowURLError(const ResourceRequest&); + virtual ResourceError interruptForPolicyChangeError(const ResourceRequest&); + + virtual ResourceError cannotShowMIMETypeError(const ResourceResponse&); + virtual ResourceError fileDoesNotExistError(const ResourceResponse&); + virtual ResourceError pluginWillHandleLoadError(const ResourceResponse&); + + virtual bool shouldFallBack(const ResourceError&); + + virtual bool canHandleRequest(const ResourceRequest&) const; + virtual bool canShowMIMEType(const String& MIMEType) const; + virtual bool canShowMIMETypeAsHTML(const String& MIMEType) const; + virtual bool representationExistsForURLScheme(const String& URLScheme) const; + virtual String generatedMIMETypeForURLScheme(const String& URLScheme) const; + + virtual void frameLoadCompleted(); + virtual void saveViewStateToItem(HistoryItem*); + virtual void restoreViewState(); + virtual void provisionalLoadStarted(); + virtual void didFinishLoad(); + virtual void prepareForDataSourceReplacement(); + + virtual PassRefPtr<DocumentLoader> createDocumentLoader(const ResourceRequest&, const SubstituteData&); + virtual void setTitle(const String& title, const KURL&); + + // This provides the userAgent to WebCore. It is used by WebCore to + // populate navigator.userAgent and to set the HTTP header in + // ResourceRequest objects. We also set a userAgent on WebRequestContext + // for the Chromium HTTP stack, which overrides the value on the + // ResourceRequest. + virtual String userAgent(const KURL&); + + virtual void savePlatformDataToCachedFrame(WebCore::CachedFrame*); + virtual void transitionToCommittedFromCachedFrame(WebCore::CachedFrame*); + virtual void transitionToCommittedForNewPage(); + + virtual void dispatchDidBecomeFrameset(bool isFrameSet); + + virtual bool canCachePage() const; + virtual void download(ResourceHandle*, const ResourceRequest&, const ResourceRequest&, const ResourceResponse&); + + virtual WTF::PassRefPtr<Frame> createFrame(const KURL& url, const String& name, HTMLFrameOwnerElement* ownerElement, const String& referrer, bool allowsScrolling, int marginWidth, int marginHeight); + virtual void didTransferChildFrameToNewDocument(WebCore::Page*); + virtual void transferLoadingResourceFromPage(unsigned long identifier, DocumentLoader*, const ResourceRequest&, Page* oldPage); + virtual WTF::PassRefPtr<Widget> createPlugin(const IntSize&, HTMLPlugInElement*, const KURL&, const WTF::Vector<String>&, const WTF::Vector<String>&, const String&, bool loadManually); + virtual void redirectDataToPlugin(Widget* pluginWidget); + + virtual WTF::PassRefPtr<Widget> createJavaAppletWidget(const IntSize&, HTMLAppletElement*, const KURL& baseURL, const WTF::Vector<String>& paramNames, const WTF::Vector<String>& paramValues); + + virtual ObjectContentType objectContentType(const KURL& url, const String& mimeType); + virtual String overrideMediaType() const; + + virtual void dispatchDidClearWindowObjectInWorld(DOMWrapperWorld*); + virtual void documentElementAvailable(); + virtual void didPerformFirstNavigation() const; + +#if USE(V8) + // TODO(benm): Implement + virtual void didCreateScriptContextForFrame() { } + virtual void didDestroyScriptContextForFrame() { } + virtual void didCreateIsolatedScriptContext() { } + + virtual bool allowScriptExtension(const String& extensionName, int extensionGroup) { return false; } +#endif + + virtual void registerForIconNotification(bool listen = true); + + virtual void dispatchDidReceiveTouchIconURL(const String& url, bool precomposed); + + virtual PassRefPtr<FrameNetworkingContext> createNetworkingContext(); + + // WebIconDatabaseClient api + virtual void didAddIconForPageUrl(const String& pageUrl); + + // FIXME: this doesn't really go here, but it's better than Frame + CacheBuilder& getCacheBuilder() { return m_cacheBuilder; } + + void enableOnDemandPlugins() { m_onDemandPluginsEnabled = true; } + + void dispatchDidChangeIcons(); + void dispatchWillSendSubmitEvent(HTMLFormElement*); + + virtual void didSaveToPageCache() { } + virtual void didRestoreFromPageCache() { } + private: + CacheBuilder m_cacheBuilder; + Frame* m_frame; + WebFrame* m_webFrame; + PluginManualLoader* m_manualLoader; + bool m_hasSentResponseToPlugin; + bool m_onDemandPluginsEnabled; + + enum ResourceErrors { + InternalErrorCancelled = -99, + InternalErrorCannotShowUrl, + InternalErrorInterrupted, + InternalErrorCannotShowMimeType, + InternalErrorFileDoesNotExist, + InternalErrorPluginWillHandleLoadError, + InternalErrorLast + }; + + /* XXX: These must match android.net.http.EventHandler */ + enum EventHandlerErrors { + Error = -1, + ErrorLookup = -2, + ErrorUnsupportedAuthScheme = -3, + ErrorAuth = -4, + ErrorProxyAuth = -5, + ErrorConnect = -6, + ErrorIO = -7, + ErrorTimeout = -8, + ErrorRedirectLoop = -9, + ErrorUnsupportedScheme = -10, + ErrorFailedSslHandshake = -11, + ErrorBadUrl = -12, + ErrorFile = -13, + ErrorFileNotFound = -14, + ErrorTooManyRequests = -15 + }; + friend class CacheBuilder; + }; + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/FrameNetworkingContextAndroid.cpp b/Source/WebKit/android/WebCoreSupport/FrameNetworkingContextAndroid.cpp new file mode 100644 index 0000000..a5fe494 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/FrameNetworkingContextAndroid.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "FrameNetworkingContextAndroid.h" + +#include "DocumentLoader.h" +#include "MainResourceLoader.h" +#include "Settings.h" + +using namespace WebCore; + +namespace android { + +FrameNetworkingContextAndroid::FrameNetworkingContextAndroid(WebCore::Frame* frame) + : WebCore::FrameNetworkingContext(frame) +{ +} + +MainResourceLoader* FrameNetworkingContextAndroid::mainResourceLoader() const +{ + return frame()->loader()->activeDocumentLoader()->mainResourceLoader(); +} + +FrameLoaderClient* FrameNetworkingContextAndroid::frameLoaderClient() const +{ + return frame()->loader()->client(); +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/FrameNetworkingContextAndroid.h b/Source/WebKit/android/WebCoreSupport/FrameNetworkingContextAndroid.h new file mode 100644 index 0000000..d0ff979 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/FrameNetworkingContextAndroid.h @@ -0,0 +1,54 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef FrameNetworkingContextAndroid_h +#define FrameNetworkingContextAndroid_h + +#include "FrameNetworkingContext.h" + +namespace WebCore { +class MainResourceLoader; +class FrameLoaderClient; +} + +namespace android { + +class FrameNetworkingContextAndroid : public WebCore::FrameNetworkingContext { +public: + static PassRefPtr<FrameNetworkingContextAndroid> create(WebCore::Frame* frame) + { + return adoptRef(new FrameNetworkingContextAndroid(frame)); + } + +private: + FrameNetworkingContextAndroid(WebCore::Frame*); + + virtual WebCore::MainResourceLoader* mainResourceLoader() const; + virtual WebCore::FrameLoaderClient* frameLoaderClient() const; +}; + +} // namespace android + +#endif // FrameNetworkingContextAndroid_h diff --git a/Source/WebKit/android/WebCoreSupport/GeolocationPermissions.cpp b/Source/WebKit/android/WebCoreSupport/GeolocationPermissions.cpp new file mode 100755 index 0000000..36a9b61 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/GeolocationPermissions.cpp @@ -0,0 +1,419 @@ +/* + * Copyright 2009, 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. + */ + +#include "config.h" +#include "GeolocationPermissions.h" + +#include "DOMWindow.h" +#include "Frame.h" +#include "Geolocation.h" +#include "Navigator.h" +#include "SQLiteDatabase.h" +#include "SQLiteFileSystem.h" +#include "SQLiteStatement.h" +#include "SQLiteTransaction.h" +#include "WebViewCore.h" + +#include <text/CString.h> + +using namespace WebCore; + +namespace android { + +GeolocationPermissions::PermissionsMap GeolocationPermissions::s_permanentPermissions; +GeolocationPermissions::GeolocationPermissionsVector GeolocationPermissions::s_instances; +bool GeolocationPermissions::s_alwaysDeny = false; +bool GeolocationPermissions::s_permanentPermissionsLoaded = false; +bool GeolocationPermissions::s_permanentPermissionsModified = false; +String GeolocationPermissions::s_databasePath; + +static const char* databaseName = "GeolocationPermissions.db"; + +GeolocationPermissions::GeolocationPermissions(WebViewCore* webViewCore, Frame* mainFrame) + : m_webViewCore(webViewCore) + , m_mainFrame(mainFrame) + , m_timer(this, &GeolocationPermissions::timerFired) + +{ + ASSERT(m_webViewCore); + maybeLoadPermanentPermissions(); + s_instances.append(this); +} + +GeolocationPermissions::~GeolocationPermissions() +{ + size_t index = s_instances.find(this); + s_instances.remove(index); +} + +void GeolocationPermissions::queryPermissionState(Frame* frame) +{ + ASSERT(s_permanentPermissionsLoaded); + + // We use SecurityOrigin::toString to key the map. Note that testing + // the SecurityOrigin pointer for equality is insufficient. + String originString = frame->document()->securityOrigin()->toString(); + + // If we've been told to always deny requests, do so. + if (s_alwaysDeny) { + makeAsynchronousCallbackToGeolocation(originString, false); + return; + } + + // See if we have a record for this origin in the permanent permissions. + // These take precedence over temporary permissions so that changes made + // from the browser settings work as intended. + PermissionsMap::const_iterator iter = s_permanentPermissions.find(originString); + PermissionsMap::const_iterator end = s_permanentPermissions.end(); + if (iter != end) { + bool allow = iter->second; + makeAsynchronousCallbackToGeolocation(originString, allow); + return; + } + + // Check the temporary permisions. + iter = m_temporaryPermissions.find(originString); + end = m_temporaryPermissions.end(); + if (iter != end) { + bool allow = iter->second; + makeAsynchronousCallbackToGeolocation(originString, allow); + return; + } + + // If there's no pending request, prompt the user. + if (nextOriginInQueue().isEmpty()) { + // Although multiple tabs may request permissions for the same origin + // simultaneously, the routing in WebViewCore/CallbackProxy ensures that + // the result of the request will make it back to this object, so + // there's no need for a globally unique ID for the request. + m_webViewCore->geolocationPermissionsShowPrompt(originString); + } + + // Add this request to the queue so we can track which frames requested it. + if (m_queuedOrigins.find(originString) == WTF::notFound) { + m_queuedOrigins.append(originString); + FrameSet frameSet; + frameSet.add(frame); + m_queuedOriginsToFramesMap.add(originString, frameSet); + } else { + ASSERT(m_queuedOriginsToFramesMap.contains(originString)); + m_queuedOriginsToFramesMap.find(originString)->second.add(frame); + } +} + +void GeolocationPermissions::cancelPermissionStateQuery(WebCore::Frame* frame) +{ + // We cancel any queued request for the given frame. There can be at most + // one of these, since each frame maps to a single origin. We only cancel + // the request if this frame is the only one reqesting permission for this + // origin. + // + // We can use the origin string to avoid searching the map. + String originString = frame->document()->securityOrigin()->toString(); + size_t index = m_queuedOrigins.find(originString); + if (index == WTF::notFound) + return; + + ASSERT(m_queuedOriginsToFramesMap.contains(originString)); + OriginToFramesMap::iterator iter = m_queuedOriginsToFramesMap.find(originString); + ASSERT(iter->second.contains(frame)); + iter->second.remove(frame); + if (!iter->second.isEmpty()) + return; + + m_queuedOrigins.remove(index); + m_queuedOriginsToFramesMap.remove(iter); + + // If this is the origin currently being shown, cancel the prompt + // and show the next in the queue, if present. + if (index == 0) { + m_webViewCore->geolocationPermissionsHidePrompt(); + if (!nextOriginInQueue().isEmpty()) + m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue()); + } +} + +void GeolocationPermissions::makeAsynchronousCallbackToGeolocation(String origin, bool allow) +{ + m_callbackData.origin = origin; + m_callbackData.allow = allow; + m_timer.startOneShot(0); +} + +void GeolocationPermissions::providePermissionState(String origin, bool allow, bool remember) +{ + ASSERT(s_permanentPermissionsLoaded); + + // It's possible that this method is called with an origin that doesn't + // match m_originInProgress. This can occur if this object is reset + // while a permission result is in the process of being marshalled back to + // the WebCore thread from the browser. In this case, we simply ignore the + // call. + if (origin != nextOriginInQueue()) + return; + + maybeCallbackFrames(origin, allow); + recordPermissionState(origin, allow, remember); + + // If the permissions are set to be remembered, cancel any queued requests + // for this domain in other tabs. + if (remember) + cancelPendingRequestsInOtherTabs(origin); + + // Clear the origin from the queue. + ASSERT(!m_queuedOrigins.isEmpty()); + m_queuedOrigins.remove(0); + ASSERT(m_queuedOriginsToFramesMap.contains(origin)); + m_queuedOriginsToFramesMap.remove(origin); + + // If there are other requests queued, start the next one. + if (!nextOriginInQueue().isEmpty()) + m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue()); +} + +void GeolocationPermissions::recordPermissionState(String origin, bool allow, bool remember) +{ + if (remember) { + s_permanentPermissions.set(origin, allow); + s_permanentPermissionsModified = true; + } else { + // It's possible that another tab recorded a permanent permission for + // this origin while our request was in progress, but we record it + // anyway. + m_temporaryPermissions.set(origin, allow); + } +} + +void GeolocationPermissions::cancelPendingRequestsInOtherTabs(String origin) +{ + for (GeolocationPermissionsVector::const_iterator iter = s_instances.begin(); + iter != s_instances.end(); + ++iter) + (*iter)->cancelPendingRequests(origin); +} + +void GeolocationPermissions::cancelPendingRequests(String origin) +{ + size_t index = m_queuedOrigins.find(origin); + + // Don't cancel the request if it's currently being shown, in which case + // it's at index 0. + if (index == WTF::notFound || !index) + return; + + // Get the permission from the permanent list. + ASSERT(s_permanentPermissions.contains(origin)); + PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin); + bool allow = iter->second; + + maybeCallbackFrames(origin, allow); + + m_queuedOrigins.remove(index); + ASSERT(m_queuedOriginsToFramesMap.contains(origin)); + m_queuedOriginsToFramesMap.remove(origin); +} + +void GeolocationPermissions::timerFired(Timer<GeolocationPermissions>* timer) +{ + ASSERT_UNUSED(timer, timer == &m_timer); + maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow); +} + +void GeolocationPermissions::resetTemporaryPermissionStates() +{ + ASSERT(s_permanentPermissionsLoaded); + m_queuedOrigins.clear(); + m_queuedOriginsToFramesMap.clear(); + m_temporaryPermissions.clear(); + // If any permission results are being marshalled back to this thread, this + // will render them inefective. + m_timer.stop(); + + m_webViewCore->geolocationPermissionsHidePrompt(); +} + +const WTF::String& GeolocationPermissions::nextOriginInQueue() +{ + static const String emptyString = ""; + return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0]; +} + +void GeolocationPermissions::maybeCallbackFrames(String origin, bool allow) +{ + // We can't track which frame issued the request, as frames can be deleted + // or have their contents replaced. Even uniqueChildName is not unique when + // frames are dynamically deleted and created. Instead, we simply call back + // to the Geolocation object in all frames from the correct origin. + for (Frame* frame = m_mainFrame; frame; frame = frame->tree()->traverseNext()) { + if (origin == frame->document()->securityOrigin()->toString()) { + // If the page has changed, it may no longer have a Geolocation + // object. + Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); + if (geolocation) + geolocation->setIsAllowed(allow); + } + } +} + +GeolocationPermissions::OriginSet GeolocationPermissions::getOrigins() +{ + maybeLoadPermanentPermissions(); + OriginSet origins; + PermissionsMap::const_iterator end = s_permanentPermissions.end(); + for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) + origins.add(iter->first); + return origins; +} + +bool GeolocationPermissions::getAllowed(String origin) +{ + maybeLoadPermanentPermissions(); + bool allowed = false; + PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin); + PermissionsMap::const_iterator end = s_permanentPermissions.end(); + if (iter != end) + allowed = iter->second; + return allowed; +} + +void GeolocationPermissions::clear(String origin) +{ + maybeLoadPermanentPermissions(); + PermissionsMap::iterator iter = s_permanentPermissions.find(origin); + if (iter != s_permanentPermissions.end()) { + s_permanentPermissions.remove(iter); + s_permanentPermissionsModified = true; + } +} + +void GeolocationPermissions::allow(String origin) +{ + maybeLoadPermanentPermissions(); + // We replace any existing permanent permission. + s_permanentPermissions.set(origin, true); + s_permanentPermissionsModified = true; +} + +void GeolocationPermissions::clearAll() +{ + maybeLoadPermanentPermissions(); + s_permanentPermissions.clear(); + s_permanentPermissionsModified = true; +} + +void GeolocationPermissions::maybeLoadPermanentPermissions() +{ + if (s_permanentPermissionsLoaded) + return; + s_permanentPermissionsLoaded = true; + + SQLiteDatabase database; + if (!openDatabase(&database)) + return; + + // Create the table here, such that even if we've just created the DB, the + // commands below should succeed. + if (!database.executeCommand("CREATE TABLE IF NOT EXISTS Permissions (origin TEXT UNIQUE NOT NULL, allow INTEGER NOT NULL)")) { + database.close(); + return; + } + + SQLiteStatement statement(database, "SELECT * FROM Permissions"); + if (statement.prepare() != SQLResultOk) { + database.close(); + return; + } + + ASSERT(s_permanentPermissions.size() == 0); + while (statement.step() == SQLResultRow) + s_permanentPermissions.set(statement.getColumnText(0), statement.getColumnInt64(1)); + + database.close(); +} + +void GeolocationPermissions::maybeStorePermanentPermissions() +{ + // If the permanent permissions haven't been modified, there's no need to + // save them to the DB. (If we haven't even loaded them, writing them now + // would overwrite the stored permissions with the empty set.) + if (!s_permanentPermissionsModified) + return; + + SQLiteDatabase database; + if (!openDatabase(&database)) + return; + + SQLiteTransaction transaction(database); + + // The number of entries should be small enough that it's not worth trying + // to perform a diff. Simply clear the table and repopulate it. + if (!database.executeCommand("DELETE FROM Permissions")) { + database.close(); + return; + } + + PermissionsMap::const_iterator end = s_permanentPermissions.end(); + for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) { + SQLiteStatement statement(database, "INSERT INTO Permissions (origin, allow) VALUES (?, ?)"); + if (statement.prepare() != SQLResultOk) + continue; + statement.bindText(1, iter->first); + statement.bindInt64(2, iter->second); + statement.executeCommand(); + } + + transaction.commit(); + database.close(); + + s_permanentPermissionsModified = false; +} + +void GeolocationPermissions::setDatabasePath(String path) +{ + // Take the first non-empty value. + if (s_databasePath.length() > 0) + return; + s_databasePath = path; +} + +bool GeolocationPermissions::openDatabase(SQLiteDatabase* database) +{ + ASSERT(database); + String filename = SQLiteFileSystem::appendDatabaseFileNameToPath(s_databasePath, databaseName); + if (!database->open(filename)) + return false; + if (chmod(filename.utf8().data(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) { + database->close(); + return false; + } + return true; +} + +void GeolocationPermissions::setAlwaysDeny(bool deny) +{ + s_alwaysDeny = deny; +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/GeolocationPermissions.h b/Source/WebKit/android/WebCoreSupport/GeolocationPermissions.h new file mode 100644 index 0000000..fb31dfe --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/GeolocationPermissions.h @@ -0,0 +1,178 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef GeolocationPermissions_h +#define GeolocationPermissions_h + +#include "PlatformString.h" +#include "Timer.h" + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + class Frame; + class Geolocation; + class SQLiteDatabase; +} + +namespace android { + + class WebViewCore; + + // The GeolocationPermissions class manages Geolocation permissions for the + // browser. Permissions are managed on a per-origin basis, as required by + // the Geolocation spec - http://dev.w3.org/geo/api/spec-source.html. An + // origin specifies the scheme, host and port of particular frame. An + // origin is represented here as a string, using the output of + // WebCore::SecurityOrigin::toString. + // + // Each instance handles permissions for a given main frame. The class + // enforces the following policy. + // - Non-remembered permissions last for the dureation of the main frame. + // - Remembered permissions last indefinitely. + // - All permissions are shared between child frames of a main frame. + // - Only remembered permissions are shared between main frames. + // - Remembered permissions are made available for use in the browser + // settings menu. + class GeolocationPermissions : public RefCounted<GeolocationPermissions> { + public: + // Creates the GeolocationPermissions object to manage permissions for + // the specified main frame (i.e. tab). The WebViewCore is used to + // communicate with the browser to display UI. + GeolocationPermissions(WebViewCore* webViewCore, WebCore::Frame* mainFrame); + virtual ~GeolocationPermissions(); + + // Queries the permission state for the specified frame. If the + // permission state has not yet been set, prompts the user. Once the + // permission state has been determined, asynchronously calls back to + // the Geolocation objects in all frames in this WebView that are from + // the same origin as the requesting frame. + void queryPermissionState(WebCore::Frame* frame); + void cancelPermissionStateQuery(WebCore::Frame*); + + // Provides this object with a permission state set by the user. The + // permission is specified by 'allow' and applied to 'origin'. If + // 'remember' is set, the permission state is remembered permanently. + // The new permission state is recorded and will trigger callbacks to + // geolocation objects as described above. If any other permission + // requests are queued, the next is started. + void providePermissionState(WTF::String origin, bool allow, bool remember); + + // Clears the temporary permission state and any pending requests. Used + // when the main frame is refreshed or navigated to a new URL. + void resetTemporaryPermissionStates(); + + // Static methods for use from Java. These are used to interact with the + // browser settings menu and to update the permanent permissions when + // system settings are changed. + // Gets the list of all origins for which permanent permissions are + // recorded. + typedef HashSet<WTF::String> OriginSet; + static OriginSet getOrigins(); + // Gets whether the specified origin is allowed. + static bool getAllowed(WTF::String origin); + // Clears the permission state for the specified origin. + static void clear(WTF::String origin); + // Sets the permission state for the specified origin to allowed. + static void allow(WTF::String origin); + // Clears the permission state for all origins. + static void clearAll(); + // Sets whether the GeolocationPermissions object should always deny + // permission requests, irrespective of previously recorded permission + // states. + static void setAlwaysDeny(bool deny); + + static void setDatabasePath(WTF::String path); + static bool openDatabase(WebCore::SQLiteDatabase*); + + // Saves the permanent permissions to the DB if required. + static void maybeStorePermanentPermissions(); + + private: + // Records the permission state for the specified origin and whether + // this should be remembered. + void recordPermissionState(WTF::String origin, bool allow, bool remember); + + // Used to make an asynchronous callback to the Geolocation objects. + void makeAsynchronousCallbackToGeolocation(WTF::String origin, bool allow); + void timerFired(WebCore::Timer<GeolocationPermissions>* timer); + + // Calls back to the Geolocation objects in all frames from the + // specified origin. There may be no such objects, as the frames using + // Geolocation from the specified origin may no longer use Geolocation, + // or may have been navigated to a different origin.. + void maybeCallbackFrames(WTF::String origin, bool allow); + + // Cancels pending permission requests for the specified origin in + // other main frames (ie browser tabs). This is used when the user + // specifies permission to be remembered. + static void cancelPendingRequestsInOtherTabs(WTF::String origin); + void cancelPendingRequests(WTF::String origin); + + static void maybeLoadPermanentPermissions(); + + const WTF::String& nextOriginInQueue(); + + WebViewCore* m_webViewCore; + WebCore::Frame* m_mainFrame; + // A vector of the origins queued to make a permission request. + // The first in the vector is the origin currently making the request. + typedef Vector<WTF::String> OriginVector; + OriginVector m_queuedOrigins; + // A map from a queued origin to the set of frames that have requested + // permission for that origin. + typedef HashSet<WebCore::Frame*> FrameSet; + typedef HashMap<WTF::String, FrameSet> OriginToFramesMap; + OriginToFramesMap m_queuedOriginsToFramesMap; + + typedef WTF::HashMap<WTF::String, bool> PermissionsMap; + PermissionsMap m_temporaryPermissions; + static PermissionsMap s_permanentPermissions; + + typedef WTF::Vector<GeolocationPermissions*> GeolocationPermissionsVector; + static GeolocationPermissionsVector s_instances; + + WebCore::Timer<GeolocationPermissions> m_timer; + + struct CallbackData { + WTF::String origin; + bool allow; + }; + CallbackData m_callbackData; + + static bool s_alwaysDeny; + + static bool s_permanentPermissionsLoaded; + static bool s_permanentPermissionsModified; + static WTF::String s_databasePath; + }; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/InspectorClientAndroid.h b/Source/WebKit/android/WebCoreSupport/InspectorClientAndroid.h new file mode 100644 index 0000000..9d734e8 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/InspectorClientAndroid.h @@ -0,0 +1,54 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef InspectorClientAndroid_h +#define InspectorClientAndroid_h + +#include "InspectorClient.h" + +#include <wtf/Forward.h> + +namespace android { + +class InspectorClientAndroid : public InspectorClient { +public: + virtual ~InspectorClientAndroid() { } + + virtual void inspectorDestroyed() { delete this; } + + virtual void openInspectorFrontend(WebCore::InspectorController*) {} + + virtual void highlight(Node*) {} + virtual void hideHighlight() {} + + virtual void populateSetting(const String& key, String* value) {} + virtual void storeSetting(const String& key, const String& value) {} + + virtual bool sendMessageToFrontend(const WTF::String&) { return false; } +}; + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/KeyGeneratorClient.h b/Source/WebKit/android/WebCoreSupport/KeyGeneratorClient.h new file mode 100644 index 0000000..1bcd8e8 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/KeyGeneratorClient.h @@ -0,0 +1,46 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef KEY_GENERATOR_CLIENT_H +#define KEY_GENERATOR_CLIENT_H + +#include "KURL.h" +#include "PlatformString.h" + +#include <wtf/Vector.h> + +using namespace WebCore; + +namespace android { + +class KeyGeneratorClient { +public: + virtual ~KeyGeneratorClient() { } + virtual WTF::Vector<String> getSupportedKeyStrengthList() = 0; + virtual String getSignedPublicKeyAndChallengeString(unsigned index, + const String& challenge, const KURL& url) = 0; + }; +} +#endif diff --git a/Source/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp b/Source/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp new file mode 100644 index 0000000..e6a2710 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp @@ -0,0 +1,654 @@ +/* + * Copyright 2009, 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. + */ + +#include "config.h" +#include "MediaPlayerPrivateAndroid.h" + +#if ENABLE(VIDEO) + +#include "BaseLayerAndroid.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "SkiaUtils.h" +#include "VideoLayerAndroid.h" +#include "WebCoreJni.h" +#include "WebViewCore.h" +#include <GraphicsJNI.h> +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SkBitmap.h> +#include <gui/SurfaceTexture.h> + +using namespace android; +// Forward decl +namespace android { +sp<SurfaceTexture> SurfaceTexture_getSurfaceTexture(JNIEnv* env, jobject thiz); +}; + +namespace WebCore { + +static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy"; +static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio"; + +struct MediaPlayerPrivate::JavaGlue { + jobject m_javaProxy; + jmethodID m_play; + jmethodID m_teardown; + jmethodID m_seek; + jmethodID m_pause; + // Audio + jmethodID m_newInstance; + jmethodID m_setDataSource; + jmethodID m_getMaxTimeSeekable; + // Video + jmethodID m_getInstance; + jmethodID m_loadPoster; +}; + +MediaPlayerPrivate::~MediaPlayerPrivate() +{ + // m_videoLayer is reference counted, unref is enough here. + m_videoLayer->unref(); + if (m_glue->m_javaProxy) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (env) { + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown); + env->DeleteGlobalRef(m_glue->m_javaProxy); + } + } + delete m_glue; +} + +void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) +{ + registrar(create, getSupportedTypes, supportsType); +} + +MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) +{ + if (WebViewCore::isSupportedMediaMimeType(type)) + return MediaPlayer::MayBeSupported; + return MediaPlayer::IsNotSupported; +} + +void MediaPlayerPrivate::pause() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_glue->m_javaProxy || !m_url.length()) + return; + + m_paused = true; + m_player->playbackStateChanged(); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause); + checkException(env); +} + +void MediaPlayerPrivate::setVisible(bool visible) +{ + m_isVisible = visible; + if (m_isVisible) + createJavaPlayerIfNeeded(); +} + +void MediaPlayerPrivate::seek(float time) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length()) + return; + + if (m_glue->m_javaProxy) { + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f)); + m_currentTime = time; + } + checkException(env); +} + +void MediaPlayerPrivate::prepareToPlay() +{ + // We are about to start playing. Since our Java VideoView cannot + // buffer any data, we just simply transition to the HaveEnoughData + // state in here. This will allow the MediaPlayer to transition to + // the "play" state, at which point our VideoView will start downloading + // the content and start the playback. + m_networkState = MediaPlayer::Loaded; + m_player->networkStateChanged(); + m_readyState = MediaPlayer::HaveEnoughData; + m_player->readyStateChanged(); +} + +MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) + : m_player(player), + m_glue(0), + m_duration(1), // keep this minimal to avoid initial seek problem + m_currentTime(0), + m_paused(true), + m_readyState(MediaPlayer::HaveNothing), + m_networkState(MediaPlayer::Empty), + m_poster(0), + m_naturalSize(100, 100), + m_naturalSizeUnknown(true), + m_isVisible(false), + m_videoLayer(new VideoLayerAndroid()) +{ +} + +void MediaPlayerPrivate::onEnded() +{ + m_currentTime = duration(); + m_player->timeChanged(); + m_paused = true; + m_player->playbackStateChanged(); + m_networkState = MediaPlayer::Idle; +} + +void MediaPlayerPrivate::onPaused() +{ + m_paused = true; + m_player->playbackStateChanged(); + m_networkState = MediaPlayer::Idle; + m_player->playbackStateChanged(); +} + +void MediaPlayerPrivate::onTimeupdate(int position) +{ + m_currentTime = position / 1000.0f; + m_player->timeChanged(); +} + +void MediaPlayerPrivate::onStopFullscreen() +{ + if (m_player && m_player->mediaPlayerClient() + && m_player->mediaPlayerClient()->mediaPlayerOwningDocument()) { + m_player->mediaPlayerClient()->mediaPlayerOwningDocument()->webkitCancelFullScreen(); + } +} + +class MediaPlayerVideoPrivate : public MediaPlayerPrivate { +public: + void load(const String& url) + { + m_url = url; + // Cheat a bit here to make sure Window.onLoad event can be triggered + // at the right time instead of real video play time, since only full + // screen video play is supported in Java's VideoView. + // See also comments in prepareToPlay function. + m_networkState = MediaPlayer::Loading; + m_player->networkStateChanged(); + m_readyState = MediaPlayer::HaveCurrentData; + m_player->readyStateChanged(); + } + + void play() + { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length() || !m_glue->m_javaProxy) + return; + + // We only play video fullscreen on Android, so stop sites playing fullscreen video in the onload handler. + Frame* frame = m_player->frameView()->frame(); + if (frame && !frame->loader()->documentLoader()->wasOnloadHandled()) + return; + + m_paused = false; + m_player->playbackStateChanged(); + + if (m_currentTime == duration()) + m_currentTime = 0; + + jstring jUrl = wtfStringToJstring(env, m_url); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl, + static_cast<jint>(m_currentTime * 1000.0f), + m_videoLayer->uniqueId()); + env->DeleteLocalRef(jUrl); + + checkException(env); + } + bool canLoadPoster() const { return true; } + void setPoster(const String& url) + { + if (m_posterUrl == url) + return; + + m_posterUrl = url; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_glue->m_javaProxy || !m_posterUrl.length()) + return; + // Send the poster + jstring jUrl = wtfStringToJstring(env, m_posterUrl); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); + env->DeleteLocalRef(jUrl); + } + void paint(GraphicsContext* ctxt, const IntRect& r) + { + if (ctxt->paintingDisabled()) + return; + + if (!m_isVisible) + return; + + if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef())) + return; + + SkCanvas* canvas = ctxt->platformContext()->mCanvas; + // We paint with the following rules in mind: + // - only downscale the poster, never upscale + // - maintain the natural aspect ratio of the poster + // - the poster should be centered in the target rect + float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height()); + int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width(); + int posterHeight = posterWidth / originalRatio; + int posterX = ((r.width() - posterWidth) / 2) + r.x(); + int posterY = ((r.height() - posterHeight) / 2) + r.y(); + IntRect targetRect(posterX, posterY, posterWidth, posterHeight); + canvas->drawBitmapRect(*m_poster, 0, targetRect, 0); + } + + void onPosterFetched(SkBitmap* poster) + { + m_poster = poster; + if (m_naturalSizeUnknown) { + // We had to fake the size at startup, or else our paint + // method would not be called. If we haven't yet received + // the onPrepared event, update the intrinsic size to the size + // of the poster. That will be overriden when onPrepare comes. + // In case of an error, we should report the poster size, rather + // than our initial fake value. + m_naturalSize = IntSize(poster->width(), poster->height()); + m_player->sizeChanged(); + } + } + + void onPrepared(int duration, int width, int height) + { + m_duration = duration / 1000.0f; + m_naturalSize = IntSize(width, height); + m_naturalSizeUnknown = false; + m_player->durationChanged(); + m_player->sizeChanged(); + } + + virtual bool hasAudio() const { return false; } // do not display the audio UI + virtual bool hasVideo() const { return true; } + virtual bool supportsFullscreen() const { return true; } + + MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) + { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClass); + + if (!clazz) + return; + + m_glue = new JavaGlue; + m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;"); + m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V"); + m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;II)V"); + + m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); + m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); + m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); + m_glue->m_javaProxy = 0; + env->DeleteLocalRef(clazz); + // An exception is raised if any of the above fails. + checkException(env); + } + + void createJavaPlayerIfNeeded() + { + // Check if we have been already created. + if (m_glue->m_javaProxy) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClass); + + if (!clazz) + return; + + jobject obj = 0; + + FrameView* frameView = m_player->frameView(); + if (!frameView) + return; + WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView); + ASSERT(webViewCore); + + // Get the HTML5VideoViewProxy instance + obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this); + m_glue->m_javaProxy = env->NewGlobalRef(obj); + // Send the poster + jstring jUrl = 0; + if (m_posterUrl.length()) + jUrl = wtfStringToJstring(env, m_posterUrl); + // Sending a NULL jUrl allows the Java side to try to load the default poster. + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); + if (jUrl) + env->DeleteLocalRef(jUrl); + + // Clean up. + if (obj) + env->DeleteLocalRef(obj); + env->DeleteLocalRef(clazz); + checkException(env); + } + + float maxTimeSeekable() const + { + return m_duration; + } +}; + +class MediaPlayerAudioPrivate : public MediaPlayerPrivate { +public: + void load(const String& url) + { + m_url = url; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length()) + return; + + createJavaPlayerIfNeeded(); + + if (!m_glue->m_javaProxy) + return; + + jstring jUrl = wtfStringToJstring(env, m_url); + // start loading the data asynchronously + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl); + env->DeleteLocalRef(jUrl); + checkException(env); + } + + void play() + { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length()) + return; + + createJavaPlayerIfNeeded(); + + if (!m_glue->m_javaProxy) + return; + + m_paused = false; + m_player->playbackStateChanged(); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play); + checkException(env); + } + + virtual bool hasAudio() const { return true; } + virtual bool hasVideo() const { return false; } + virtual bool supportsFullscreen() const { return false; } + + float maxTimeSeekable() const + { + if (m_glue->m_javaProxy) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (env) { + float maxTime = env->CallFloatMethod(m_glue->m_javaProxy, + m_glue->m_getMaxTimeSeekable); + checkException(env); + return maxTime; + } + } + return 0; + } + + MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) + { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClassAudio); + + if (!clazz) + return; + + m_glue = new JavaGlue; + m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V"); + m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V"); + m_glue->m_play = env->GetMethodID(clazz, "play", "()V"); + m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F"); + m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); + m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); + m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); + m_glue->m_javaProxy = 0; + env->DeleteLocalRef(clazz); + // An exception is raised if any of the above fails. + checkException(env); + } + + void createJavaPlayerIfNeeded() + { + // Check if we have been already created. + if (m_glue->m_javaProxy) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClassAudio); + + if (!clazz) + return; + + jobject obj = 0; + + // Get the HTML5Audio instance + obj = env->NewObject(clazz, m_glue->m_newInstance, this); + m_glue->m_javaProxy = env->NewGlobalRef(obj); + + // Clean up. + if (obj) + env->DeleteLocalRef(obj); + env->DeleteLocalRef(clazz); + checkException(env); + } + + void onPrepared(int duration, int width, int height) + { + // Android media player gives us a duration of 0 for a live + // stream, so in that case set the real duration to infinity. + // We'll still be able to handle the case that we genuinely + // get an audio clip with a duration of 0s as we'll get the + // ended event when it stops playing. + if (duration > 0) { + m_duration = duration / 1000.0f; + } else { + m_duration = std::numeric_limits<float>::infinity(); + } + m_player->durationChanged(); + m_player->sizeChanged(); + m_player->prepareToPlay(); + } +}; + +MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) +{ + if (player->mediaElementType() == MediaPlayer::Video) + return new MediaPlayerVideoPrivate(player); + return new MediaPlayerAudioPrivate(player); +} + +} + +namespace android { + +static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) +{ + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + player->onPrepared(duration, width, height); + } +} + +static void OnEnded(JNIEnv* env, jobject obj, int pointer) +{ + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + player->onEnded(); + } +} + +static void OnPaused(JNIEnv* env, jobject obj, int pointer) +{ + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + player->onPaused(); + } +} + +static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) +{ + if (!pointer || !poster) + return; + + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster); + if (!posterNative) + return; + player->onPosterFetched(posterNative); +} + +static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) +{ + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + // TODO: player->onBuffering(percent); + } +} + +static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) +{ + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + player->onTimeupdate(position); + } +} + +// This is called on the UI thread only. +// The video layers are composited on the webkit thread and then copied over +// to the UI thread with the same ID. For rendering, we are only using the +// video layers on the UI thread. Therefore, on the UI thread, we have to use +// the videoLayerId from Java side to find the exact video layer in the tree +// to set the surface texture. +// Every time a play call into Java side, the videoLayerId will be sent and +// saved in Java side. Then every time setBaseLayer call, the saved +// videoLayerId will be passed to this function to find the Video Layer. +// Return value: true when the video layer is found. +static bool SendSurfaceTexture(JNIEnv* env, jobject obj, jobject surfTex, + int baseLayer, int videoLayerId, + int textureName, int playerState) { + if (!surfTex) + return false; + + sp<SurfaceTexture> texture = android::SurfaceTexture_getSurfaceTexture(env, surfTex); + if (!texture.get()) + return false; + + BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(baseLayer); + if (!layerImpl) + return false; + if (!layerImpl->countChildren()) + return false; + LayerAndroid* compositedRoot = static_cast<LayerAndroid*>(layerImpl->getChild(0)); + if (!compositedRoot) + return false; + + VideoLayerAndroid* videoLayer = + static_cast<VideoLayerAndroid*>(compositedRoot->findById(videoLayerId)); + if (!videoLayer) + return false; + + // Set the SurfaceTexture to the layer we found + videoLayer->setSurfaceTexture(texture, textureName, static_cast<PlayerState>(playerState)); + return true; +} + +static void OnStopFullscreen(JNIEnv* env, jobject obj, int pointer) +{ + if (pointer) { + WebCore::MediaPlayerPrivate* player = + reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + player->onStopFullscreen(); + } +} + +/* + * JNI registration + */ +static JNINativeMethod g_MediaPlayerMethods[] = { + { "nativeOnPrepared", "(IIII)V", + (void*) OnPrepared }, + { "nativeOnEnded", "(I)V", + (void*) OnEnded }, + { "nativeOnStopFullscreen", "(I)V", + (void*) OnStopFullscreen }, + { "nativeOnPaused", "(I)V", + (void*) OnPaused }, + { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V", + (void*) OnPosterFetched }, + { "nativeSendSurfaceTexture", "(Landroid/graphics/SurfaceTexture;IIII)Z", + (void*) SendSurfaceTexture }, + { "nativeOnTimeupdate", "(II)V", + (void*) OnTimeupdate }, +}; + +static JNINativeMethod g_MediaAudioPlayerMethods[] = { + { "nativeOnBuffering", "(II)V", + (void*) OnBuffering }, + { "nativeOnEnded", "(I)V", + (void*) OnEnded }, + { "nativeOnPrepared", "(IIII)V", + (void*) OnPrepared }, + { "nativeOnTimeupdate", "(II)V", + (void*) OnTimeupdate }, +}; + +int registerMediaPlayerVideo(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, g_ProxyJavaClass, + g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods)); +} + +int registerMediaPlayerAudio(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio, + g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods)); +} + +} +#endif // VIDEO diff --git a/Source/WebKit/android/WebCoreSupport/MemoryUsage.cpp b/Source/WebKit/android/WebCoreSupport/MemoryUsage.cpp new file mode 100644 index 0000000..32cdebf --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/MemoryUsage.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2010 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. + */ + +#include "config.h" +#include "MemoryUsage.h" + +#include <malloc.h> +#include <wtf/CurrentTime.h> + +#if USE(V8) +#include <v8.h> +#endif // USE(V8) + +using namespace WTF; + +class MemoryUsageCache { +public: + MemoryUsageCache() + : m_cachedMemoryUsage(0) + , m_cacheTime(0) + { + } + + int getCachedMemoryUsage(bool forceFresh); + +private: + unsigned m_cachedMemoryUsage; + double m_cacheTime; + static const int CACHE_VALIDITY_MS = 2000; +}; + +int MemoryUsageCache::getCachedMemoryUsage(bool forceFresh) +{ + if (!forceFresh && currentTimeMS() <= m_cacheTime + CACHE_VALIDITY_MS) + return m_cachedMemoryUsage; + + struct mallinfo minfo = mallinfo(); + m_cachedMemoryUsage = (minfo.hblkhd + minfo.arena) >> 20; + +#if USE(V8) + v8::HeapStatistics stat; + v8::V8::GetHeapStatistics(&stat); + unsigned v8Usage = stat.total_heap_size() >> 20; + m_cachedMemoryUsage += v8Usage; +#endif // USE(V8) + + m_cacheTime = currentTimeMS(); + return m_cachedMemoryUsage; +} + +int MemoryUsage::memoryUsageMb(bool forceFresh) +{ + static MemoryUsageCache cache; + return cache.getCachedMemoryUsage(forceFresh); +} + +int MemoryUsage::m_lowMemoryUsageMb = 0; +int MemoryUsage::m_highMemoryUsageMb = 0; +int MemoryUsage::m_highUsageDeltaMb = 0; diff --git a/Source/WebKit/android/WebCoreSupport/MemoryUsage.h b/Source/WebKit/android/WebCoreSupport/MemoryUsage.h new file mode 100644 index 0000000..2a3d7ed --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/MemoryUsage.h @@ -0,0 +1,45 @@ +/* + * Copyright 2010 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. + */ + +#ifndef MemoryUsage_h +#define MemoryUsage_h + +class MemoryUsage { +public: + static int memoryUsageMb(bool forceFresh); + static int lowMemoryUsageMb() { return m_lowMemoryUsageMb; } + static int highMemoryUsageMb() { return m_highMemoryUsageMb; } + static int highUsageDeltaMb() { return m_highUsageDeltaMb; } + static void setHighMemoryUsageMb(int highMemoryUsageMb) { m_highMemoryUsageMb = highMemoryUsageMb; } + static void setLowMemoryUsageMb(int lowMemoryUsageMb) { m_lowMemoryUsageMb = lowMemoryUsageMb; } + static void setHighUsageDeltaMb(int highUsageDeltaMb) { m_highUsageDeltaMb = highUsageDeltaMb; } + +private: + static int m_lowMemoryUsageMb; + static int m_highMemoryUsageMb; + static int m_highUsageDeltaMb; +}; + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/PlatformBridge.cpp b/Source/WebKit/android/WebCoreSupport/PlatformBridge.cpp new file mode 100644 index 0000000..8d8d809 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/PlatformBridge.cpp @@ -0,0 +1,261 @@ +/* + * Copyright 2009, 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. + */ + +#include "config.h" +#include <PlatformBridge.h> + +#include "CookieClient.h" +#include "Document.h" +#include "FileSystemClient.h" +#include "FrameView.h" +#include "JavaSharedClient.h" +#include "KeyGeneratorClient.h" +#include "MemoryUsage.h" +#include "PluginView.h" +#include "Settings.h" +#include "WebCookieJar.h" +#include "WebRequestContext.h" +#include "WebViewCore.h" +#include "npruntime.h" + +#include <surfaceflinger/SurfaceComposerClient.h> +#include <ui/DisplayInfo.h> +#include <ui/PixelFormat.h> +#include <wtf/android/AndroidThreading.h> +#include <wtf/MainThread.h> + +using namespace android; + +namespace WebCore { + +WTF::Vector<String> PlatformBridge::getSupportedKeyStrengthList() +{ + KeyGeneratorClient* client = JavaSharedClient::GetKeyGeneratorClient(); + if (!client) + return WTF::Vector<String>(); + + return client->getSupportedKeyStrengthList(); +} + +String PlatformBridge::getSignedPublicKeyAndChallengeString(unsigned index, const String& challenge, const KURL& url) +{ + KeyGeneratorClient* client = JavaSharedClient::GetKeyGeneratorClient(); + if (!client) + return String(); + + return client->getSignedPublicKeyAndChallengeString(index, challenge, url); +} + +void PlatformBridge::setCookies(const Document* document, const KURL& url, const String& value) +{ +#if USE(CHROME_NETWORK_STACK) + std::string cookieValue(value.utf8().data()); + GURL cookieGurl(url.string().utf8().data()); + bool isPrivateBrowsing = document->settings() && document->settings()->privateBrowsingEnabled(); + WebCookieJar::get(isPrivateBrowsing)->cookieStore()->SetCookie(cookieGurl, cookieValue); +#else + CookieClient* client = JavaSharedClient::GetCookieClient(); + if (!client) + return; + + client->setCookies(url, value); +#endif +} + +String PlatformBridge::cookies(const Document* document, const KURL& url) +{ +#if USE(CHROME_NETWORK_STACK) + GURL cookieGurl(url.string().utf8().data()); + bool isPrivateBrowsing = document->settings() && document->settings()->privateBrowsingEnabled(); + std::string cookies = WebCookieJar::get(isPrivateBrowsing)->cookieStore()->GetCookies(cookieGurl); + String cookieString(cookies.c_str()); + return cookieString; +#else + CookieClient* client = JavaSharedClient::GetCookieClient(); + if (!client) + return String(); + + return client->cookies(url); +#endif +} + +bool PlatformBridge::cookiesEnabled(const Document* document) +{ +#if USE(CHROME_NETWORK_STACK) + bool isPrivateBrowsing = document->settings() && document->settings()->privateBrowsingEnabled(); + return WebCookieJar::get(isPrivateBrowsing)->allowCookies(); +#else + CookieClient* client = JavaSharedClient::GetCookieClient(); + if (!client) + return false; + + return client->cookiesEnabled(); +#endif +} + +NPObject* PlatformBridge::pluginScriptableObject(Widget* widget) +{ +#if USE(V8) + if (!widget->isPluginView()) + return 0; + + PluginView* pluginView = static_cast<PluginView*>(widget); + return pluginView->getNPObject(); +#else + return 0; +#endif +} + +bool PlatformBridge::isWebViewPaused(const WebCore::FrameView* frameView) +{ + android::WebViewCore* webViewCore = android::WebViewCore::getWebViewCore(frameView); + return webViewCore->isPaused(); +} + +bool PlatformBridge::popupsAllowed(NPP) +{ + return false; +} + +String PlatformBridge::resolveFilePathForContentUri(const String& contentUri) +{ + FileSystemClient* client = JavaSharedClient::GetFileSystemClient(); + return client->resolveFilePathForContentUri(contentUri); +} + +int PlatformBridge::PlatformBridge::screenDepth() +{ + android::DisplayInfo info; + android::SurfaceComposerClient::getDisplayInfo(android::DisplayID(0), &info); + return info.pixelFormatInfo.bitsPerPixel; +} + +FloatRect PlatformBridge::screenRect() +{ + android::DisplayInfo info; + android::SurfaceComposerClient::getDisplayInfo(android::DisplayID(0), &info); + return FloatRect(0.0, 0.0, info.w, info.h); +} + +// The visible size on screen in document coordinate +int PlatformBridge::screenWidthInDocCoord(const WebCore::FrameView* frameView) +{ + android::WebViewCore* webViewCore = android::WebViewCore::getWebViewCore(frameView); + return webViewCore->screenWidth(); +} + +int PlatformBridge::screenHeightInDocCoord(const WebCore::FrameView* frameView) +{ + android::WebViewCore* webViewCore = android::WebViewCore::getWebViewCore(frameView); + return webViewCore->screenHeight(); +} + +String PlatformBridge::computeDefaultLanguage() +{ +#if USE(CHROME_NETWORK_STACK) + String acceptLanguages = WebRequestContext::acceptLanguage(); + size_t length = acceptLanguages.find(','); + if (length == std::string::npos) + length = acceptLanguages.length(); + return acceptLanguages.substring(0, length); +#else + return "en"; +#endif +} + +void PlatformBridge::updateViewport(FrameView* frameView) +{ + android::WebViewCore* webViewCore = android::WebViewCore::getWebViewCore(frameView); + webViewCore->updateViewport(); +} + +void PlatformBridge::updateTextfield(FrameView* frameView, Node* nodePtr, bool changeToPassword, const WTF::String& text) +{ + android::WebViewCore* webViewCore = android::WebViewCore::getWebViewCore(frameView); + webViewCore->updateTextfield(nodePtr, changeToPassword, text); +} + +void PlatformBridge::setScrollPosition(ScrollView* scrollView, int x, int y) { + // Check to make sure the view is the main FrameView. + android::WebViewCore *webViewCore = android::WebViewCore::getWebViewCore(scrollView); + if (webViewCore->mainFrame()->view() == scrollView) + webViewCore->scrollTo(x, y); +} + +int PlatformBridge::lowMemoryUsageMB() +{ + return MemoryUsage::lowMemoryUsageMb(); +} + +int PlatformBridge::highMemoryUsageMB() +{ + return MemoryUsage::highMemoryUsageMb(); +} + +int PlatformBridge::highUsageDeltaMB() +{ + return MemoryUsage::highUsageDeltaMb(); +} + +int PlatformBridge::memoryUsageMB() +{ + return MemoryUsage::memoryUsageMb(false); +} + +int PlatformBridge::actualMemoryUsageMB() +{ + return MemoryUsage::memoryUsageMb(true); +} + +} // namespace WebCore + + +// This is the implementation of AndroidThreading, which is declared in +// JavaScriptCore/wtf/android/AndroidThreading.h. It is provided here, rather +// than in its own source file, to avoid linker problems. +// +// By default, when building a shared library, the linker strips from static +// libraries any compilation units which do not contain any code referenced from +// that static library. Since +// AndroidThreading::scheduleDispatchFunctionsOnMainThread is not referenced +// from libwebcore.a, implementing it in its own compilation unit results in it +// being stripped. This stripping can be avoided by using the linker option +// --whole-archive for libwebcore.a, but this adds considerably to the size of +// libwebcore.so. + +namespace WTF { + +// Callback in the main thread. +static void timeoutFired(void*) +{ + dispatchFunctionsFromMainThread(); +} + +void AndroidThreading::scheduleDispatchFunctionsOnMainThread() +{ + JavaSharedClient::EnqueueFunctionPtr(timeoutFired, 0); +} + +} // namespace WTF diff --git a/Source/WebKit/android/WebCoreSupport/ResourceLoaderAndroid.cpp b/Source/WebKit/android/WebCoreSupport/ResourceLoaderAndroid.cpp new file mode 100644 index 0000000..7f54810 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/ResourceLoaderAndroid.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2009, 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. + */ + +#include <config.h> +#include <ResourceLoaderAndroid.h> + +#include "Frame.h" +#include "FrameLoaderClientAndroid.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreResourceLoader.h" +#include "WebUrlLoader.h" +#include "WebViewCore.h" + +using namespace android; + +namespace WebCore { + +PassRefPtr<ResourceLoaderAndroid> ResourceLoaderAndroid::start( + ResourceHandle* handle, const ResourceRequest& request, FrameLoaderClient* client, bool isMainResource, bool isSync) +{ + // Called on main thread + FrameLoaderClientAndroid* clientAndroid = static_cast<FrameLoaderClientAndroid*>(client); +#if USE(CHROME_NETWORK_STACK) + WebViewCore* webViewCore = WebViewCore::getWebViewCore(clientAndroid->getFrame()->view()); + bool isMainFrame = !(clientAndroid->getFrame()->tree() && clientAndroid->getFrame()->tree()->parent()); + return WebUrlLoader::start(client, handle, request, isMainResource, isMainFrame, isSync, webViewCore->webRequestContext()); +#else + return clientAndroid->webFrame()->startLoadingResource(handle, request, isMainResource, isSync); +#endif +} + +bool ResourceLoaderAndroid::willLoadFromCache(const WebCore::KURL& url, int64_t identifier) +{ +#if USE(CHROME_NETWORK_STACK) + // This method is used to determine if a POST request can be repeated from + // cache, but you cannot really know until you actually try to read from the + // cache. Even if we checked now, something else could come along and wipe + // out the cache entry by the time we fetch it. + // + // So, we always say yes here, to prevent the FrameLoader from initiating a + // reload. Then in FrameLoaderClientImpl::dispatchWillSendRequest, we + // fix-up the cache policy of the request to force a load from the cache. + return true; +#else + return WebCoreResourceLoader::willLoadFromCache(url, identifier); +#endif +} + +} diff --git a/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.cpp b/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.cpp new file mode 100644 index 0000000..3779ba8 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2010, 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 "UrlInterceptResponse" +#include "config.h" + +#include "JNIUtility.h" +#include "UrlInterceptResponse.h" +#include "WebCoreJni.h" + +#include <utils/Log.h> + +namespace android { + +class JavaInputStreamWrapper { +public: + JavaInputStreamWrapper(JNIEnv* env, jobject inputStream) + : m_inputStream(env->NewGlobalRef(inputStream)) + , m_buffer(0) { + LOG_ALWAYS_FATAL_IF(!inputStream); + jclass inputStreamClass = env->FindClass("java/io/InputStream"); + LOG_ALWAYS_FATAL_IF(!inputStreamClass); + m_read = env->GetMethodID(inputStreamClass, "read", "([B)I"); + LOG_ALWAYS_FATAL_IF(!m_read); + m_close = env->GetMethodID(inputStreamClass, "close", "()V"); + LOG_ALWAYS_FATAL_IF(!m_close); + } + + ~JavaInputStreamWrapper() { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_inputStream, m_close); + checkException(env); + env->DeleteGlobalRef(m_inputStream); + // In case we never call read(). + if (m_buffer) + env->DeleteGlobalRef(m_buffer); + } + + void read(std::vector<char>* out) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + // Initialize our read buffer to the capacity of out. + if (!m_buffer) { + m_buffer = env->NewByteArray(out->capacity()); + m_buffer = (jbyteArray) env->NewGlobalRef(m_buffer); + } + int size = (int) env->CallIntMethod(m_inputStream, m_read, m_buffer); + if (checkException(env) || size < 0) + return; + // Copy from m_buffer to out. + out->resize(size); + env->GetByteArrayRegion(m_buffer, 0, size, (jbyte*)&out->front()); + } + +private: + jobject m_inputStream; + jbyteArray m_buffer; + jmethodID m_read; + jmethodID m_close; +}; + +UrlInterceptResponse::UrlInterceptResponse(JNIEnv* env, jobject response) { + jclass javaResponse = env->FindClass("android/webkit/WebResourceResponse"); + LOG_ALWAYS_FATAL_IF(!javaResponse); + jfieldID mimeType = env->GetFieldID(javaResponse, "mMimeType", + "Ljava/lang/String;"); + LOG_ALWAYS_FATAL_IF(!mimeType); + jfieldID encoding = env->GetFieldID(javaResponse, "mEncoding", + "Ljava/lang/String;"); + LOG_ALWAYS_FATAL_IF(!encoding); + jfieldID inputStream = env->GetFieldID(javaResponse, "mInputStream", + "Ljava/io/InputStream;"); + LOG_ALWAYS_FATAL_IF(!inputStream); + + jobject stream = env->GetObjectField(response, inputStream); + if (stream) + m_inputStream.set(new JavaInputStreamWrapper(env, stream)); + + jstring mimeStr = (jstring) env->GetObjectField(response, mimeType); + jstring encodingStr = (jstring) env->GetObjectField(response, encoding); + + if (mimeStr) { + const char* s = env->GetStringUTFChars(mimeStr, NULL); + m_mimeType.assign(s, env->GetStringUTFLength(mimeStr)); + env->ReleaseStringUTFChars(mimeStr, s); + } + if (encodingStr) { + const char* s = env->GetStringUTFChars(encodingStr, NULL); + m_encoding.assign(s, env->GetStringUTFLength(encodingStr)); + env->ReleaseStringUTFChars(encodingStr, s); + } + + env->DeleteLocalRef(javaResponse); + env->DeleteLocalRef(mimeStr); + env->DeleteLocalRef(encodingStr); +} + +UrlInterceptResponse::~UrlInterceptResponse() { + // Cannot be inlined because of JavaInputStreamWrapper visibility. +} + +bool UrlInterceptResponse::readStream(std::vector<char>* out) const { + if (!m_inputStream) + return false; + m_inputStream->read(out); + return true; +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.h b/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.h new file mode 100644 index 0000000..64dad69 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.h @@ -0,0 +1,70 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef UrlInterceptResponse_h +#define UrlInterceptResponse_h + +#include "PlatformString.h" +#include "wtf/Noncopyable.h" +#include "wtf/OwnPtr.h" + +#include <jni.h> +#include <string> +#include <vector> + +namespace android { + +class JavaInputStreamWrapper; + +class UrlInterceptResponse : public Noncopyable { +public: + UrlInterceptResponse(JNIEnv* env, jobject response); + ~UrlInterceptResponse(); + + const std::string& mimeType() const { + return m_mimeType; + } + + const std::string& encoding() const { + return m_encoding; + } + + int status() const { + return m_inputStream ? 200 : 404; + } + + // Read from the input stream. Returns false if reading failed. + // A size of 0 indicates eof. + bool readStream(std::vector<char>* out) const; + +private: + std::string m_mimeType; + std::string m_encoding; + OwnPtr<JavaInputStreamWrapper> m_inputStream; +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/V8Counters.cpp b/Source/WebKit/android/WebCoreSupport/V8Counters.cpp new file mode 100644 index 0000000..d164f9a --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/V8Counters.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2010, 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. + */ + + +#ifdef ANDROID_INSTRUMENT + +#define LOG_TAG "WebCore" + +#include "config.h" +#include "V8Counters.h" + +#include "NotImplemented.h" +#include <utils/Log.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringHash.h> + +#if USE(V8) + +namespace WebCore { + +V8Counters::Counter::Counter(bool isHistogram) + : m_count(0), m_sampleTotal(0), m_isHistogram(isHistogram) { } + +void V8Counters::Counter::addSample(int sample) +{ + m_count++; + m_sampleTotal += sample; +} + +HashMap<String, V8Counters::Counter*> V8Counters::m_counters; + +// static +int* V8Counters::counterForName(const char* name) +{ + Counter* counter = m_counters.get(name); + if (!counter) { + counter = new Counter(false); + m_counters.add(name, counter); + } + return *counter; +} + +// static +void* V8Counters::createHistogram(const char* name, int min, int max, + size_t buckets) +{ + Counter* counter = new Counter(true); + m_counters.add(name, counter); + return counter; +} + +// static +void V8Counters::addHistogramSample(void* histogram, int sample) +{ + Counter* counter = reinterpret_cast<Counter*>(histogram); + counter->addSample(sample); +} + +// static +void V8Counters::initCounters() +{ + static bool isInitialized = false; + if (!isInitialized) { + v8::V8::SetCounterFunction(counterForName); + v8::V8::SetCreateHistogramFunction(createHistogram); + v8::V8::SetAddHistogramSampleFunction(addHistogramSample); + isInitialized = true; + } +} + +// static +void V8Counters::dumpCounters() +{ + LOGD("+----------------------------------------+-------------+\n"); + LOGD("| Name | Value |\n"); + LOGD("+----------------------------------------+-------------+\n"); + typedef HashMap<String, V8Counters::Counter*>::iterator CounterIterator; + for (CounterIterator iter = m_counters.begin(); iter != m_counters.end(); ++iter) { + Counter* counter = iter->second; + if (counter->isHistogram()) { + LOGD("| c:%-36s | %11i |\n", iter->first.latin1().data(), counter->count()); + LOGD("| t:%-36s | %11i |\n", iter->first.latin1().data(), counter->sampleTotal()); + } else { + LOGD("| %-38s | %11i |\n", iter->first.latin1().data(), counter->count()); + } + } + LOGD("+----------------------------------------+-------------+\n"); +} + +} + +#endif // ANDROID_INSTRUMENT + +#endif // USE(V8) diff --git a/Source/WebKit/android/WebCoreSupport/V8Counters.h b/Source/WebKit/android/WebCoreSupport/V8Counters.h new file mode 100644 index 0000000..499b856 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/V8Counters.h @@ -0,0 +1,77 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef V8Counters_h +#define V8Counters_h + +#if USE(V8) + +#ifdef ANDROID_INSTRUMENT + +#include <PlatformString.h> +#include <v8.h> +#include <wtf/HashMap.h> + +namespace WebCore { + +class V8Counters { +public: + // Counter callbacks, see v8.h + static int* counterForName(const char* name); + + static void* createHistogram(const char* name, + int min, + int max, + size_t buckets); + + static void addHistogramSample(void* histogram, int sample); + + static void initCounters(); + static void dumpCounters(); +private: + class Counter { + public: + Counter(bool isHistogram); + + int count() { return m_count; } + int sampleTotal() { return m_sampleTotal; } + bool isHistogram() { return m_isHistogram; } + void addSample(int32_t sample); + + operator int*() { return &m_count; } + private: + int m_count; + int m_sampleTotal; + bool m_isHistogram; + }; + + static HashMap<String, Counter*> m_counters; +}; + +} + +#endif // ANDROID_INSTRUMENT +#endif // USE(V8) +#endif // V8Counters_h diff --git a/Source/WebKit/android/WebCoreSupport/WebCache.cpp b/Source/WebKit/android/WebCoreSupport/WebCache.cpp new file mode 100644 index 0000000..be9a700 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebCache.cpp @@ -0,0 +1,237 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebCache.h" + +#include "JNIUtility.h" +#include "WebCoreJni.h" +#include "WebRequestContext.h" +#include "WebUrlLoaderClient.h" + +#include <wtf/text/CString.h> + +using namespace WTF; +using namespace disk_cache; +using namespace net; +using namespace std; + +namespace android { + +static WTF::Mutex instanceMutex; + +static const string& rootDirectory() +{ + // This method may be called on any thread, as the Java method is + // synchronized. + static WTF::Mutex mutex; + MutexLocker lock(mutex); + static string cacheDirectory; + if (cacheDirectory.empty()) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jclass bridgeClass = env->FindClass("android/webkit/JniUtil"); + jmethodID method = env->GetStaticMethodID(bridgeClass, "getCacheDirectory", "()Ljava/lang/String;"); + cacheDirectory = jstringToStdString(env, static_cast<jstring>(env->CallStaticObjectMethod(bridgeClass, method))); + env->DeleteLocalRef(bridgeClass); + } + return cacheDirectory; +} + +static string storageDirectory() +{ + // Private cache is currently in memory only + static const char* const kDirectory = "/webviewCacheChromium"; + string storageDirectory = rootDirectory(); + storageDirectory.append(kDirectory); + return storageDirectory; +} + +static scoped_refptr<WebCache>* instance(bool isPrivateBrowsing) +{ + static scoped_refptr<WebCache> regularInstance; + static scoped_refptr<WebCache> privateInstance; + return isPrivateBrowsing ? &privateInstance : ®ularInstance; +} + +WebCache* WebCache::get(bool isPrivateBrowsing) +{ + MutexLocker lock(instanceMutex); + scoped_refptr<WebCache>* instancePtr = instance(isPrivateBrowsing); + if (!instancePtr->get()) + *instancePtr = new WebCache(isPrivateBrowsing); + return instancePtr->get(); +} + +void WebCache::cleanup(bool isPrivateBrowsing) +{ + MutexLocker lock(instanceMutex); + scoped_refptr<WebCache>* instancePtr = instance(isPrivateBrowsing); + *instancePtr = 0; +} + +WebCache::WebCache(bool isPrivateBrowsing) + : m_doomAllEntriesCallback(this, &WebCache::doomAllEntries) + , m_onClearDoneCallback(this, &WebCache::onClearDone) + , m_isClearInProgress(false) + , m_openEntryCallback(this, &WebCache::openEntry) + , m_onGetEntryDoneCallback(this, &WebCache::onGetEntryDone) + , m_isGetEntryInProgress(false) + , m_cacheBackend(0) +{ + base::Thread* ioThread = WebUrlLoaderClient::ioThread(); + scoped_refptr<base::MessageLoopProxy> cacheMessageLoopProxy = ioThread->message_loop_proxy(); + + static const int kMaximumCacheSizeBytes = 20 * 1024 * 1024; + m_hostResolver = net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism, 0, 0); + + m_proxyConfigService = new ProxyConfigServiceAndroid(); + net::HttpCache::BackendFactory* backendFactory; + if (isPrivateBrowsing) + backendFactory = net::HttpCache::DefaultBackend::InMemory(kMaximumCacheSizeBytes / 2); + else { + FilePath directoryPath(storageDirectory().c_str()); + backendFactory = new net::HttpCache::DefaultBackend(net::DISK_CACHE, directoryPath, kMaximumCacheSizeBytes, cacheMessageLoopProxy); + } + + m_cache = new net::HttpCache(m_hostResolver.get(), + 0, // dnsrr_resolver + 0, // dns_cert_checker + net::ProxyService::CreateWithoutProxyResolver(m_proxyConfigService, 0 /* net_log */), + net::SSLConfigService::CreateSystemSSLConfigService(), + net::HttpAuthHandlerFactory::CreateDefault(m_hostResolver.get()), + 0, // network_delegate + 0, // net_log + backendFactory); +} + +void WebCache::clear() +{ + base::Thread* thread = WebUrlLoaderClient::ioThread(); + if (thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::clearImpl)); +} + +void WebCache::clearImpl() +{ + if (m_isClearInProgress) + return; + m_isClearInProgress = true; + + if (!m_cacheBackend) { + int code = m_cache->GetBackend(&m_cacheBackend, &m_doomAllEntriesCallback); + // Code ERR_IO_PENDING indicates that the operation is still in progress and + // the supplied callback will be invoked when it completes. + if (code == ERR_IO_PENDING) + return; + else if (code != OK) { + onClearDone(0 /*unused*/); + return; + } + } + doomAllEntries(0 /*unused*/); +} + +void WebCache::doomAllEntries(int) +{ + if (!m_cacheBackend) { + onClearDone(0 /*unused*/); + return; + } + + // Code ERR_IO_PENDING indicates that the operation is still in progress and + // the supplied callback will be invoked when it completes. + if (m_cacheBackend->DoomAllEntries(&m_onClearDoneCallback) == ERR_IO_PENDING) + return; + onClearDone(0 /*unused*/); +} + +void WebCache::onClearDone(int) +{ + m_isClearInProgress = false; +} + +scoped_refptr<CacheResult> WebCache::getCacheResult(String url) +{ + // This is called on the UI thread. + MutexLocker lock(m_getEntryMutex); + if (m_isGetEntryInProgress) + return 0; // TODO: OK? Or can we queue 'em up? + + // The Chromium methods are asynchronous, but we need this method to be + // synchronous. Do the work on the Chromium thread but block this thread + // here waiting for the work to complete. + base::Thread* thread = WebUrlLoaderClient::ioThread(); + if (!thread) + return 0; + + m_entry = 0; + m_isGetEntryInProgress = true; + m_entryUrl = url.threadsafeCopy(); + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::getEntryImpl)); + + while (m_isGetEntryInProgress) + m_getEntryCondition.wait(m_getEntryMutex); + + if (!m_entry) + return 0; + + return new CacheResult(m_entry, url); +} + +void WebCache::getEntryImpl() +{ + if (!m_cacheBackend) { + int code = m_cache->GetBackend(&m_cacheBackend, &m_openEntryCallback); + if (code == ERR_IO_PENDING) + return; + else if (code != OK) { + onGetEntryDone(0 /*unused*/); + return; + } + } + openEntry(0 /*unused*/); +} + +void WebCache::openEntry(int) +{ + if (!m_cacheBackend) { + onGetEntryDone(0 /*unused*/); + return; + } + + if (m_cacheBackend->OpenEntry(string(m_entryUrl.utf8().data()), &m_entry, &m_onGetEntryDoneCallback) == ERR_IO_PENDING) + return; + onGetEntryDone(0 /*unused*/); +} + +void WebCache::onGetEntryDone(int) +{ + // Unblock the UI thread in getEntry(); + MutexLocker lock(m_getEntryMutex); + m_isGetEntryInProgress = false; + m_getEntryCondition.signal(); +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/WebCache.h b/Source/WebKit/android/WebCoreSupport/WebCache.h new file mode 100644 index 0000000..7149fcc --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebCache.h @@ -0,0 +1,88 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebCache_h +#define WebCache_h + +#include "CacheResult.h" +#include "ChromiumIncludes.h" + +#include <OwnPtr.h> +#include <platform/text/PlatformString.h> +#include <wtf/ThreadingPrimitives.h> + +namespace android { + +// This class is not generally threadsafe. However, get() and cleanup() are +// threadsafe. +class WebCache : public base::RefCountedThreadSafe<WebCache> { +public: + static WebCache* get(bool isPrivateBrowsing); + static void cleanup(bool isPrivateBrowsing); + + void clear(); + scoped_refptr<CacheResult> getCacheResult(WTF::String url); + net::HostResolver* hostResolver() { return m_hostResolver.get(); } + net::HttpCache* cache() { return m_cache.get(); } + net::ProxyConfigServiceAndroid* proxy() { return m_proxyConfigService; } + +private: + WebCache(bool isPrivateBrowsing); + + // For clear() + void clearImpl(); + void doomAllEntries(int); + void onClearDone(int); + + // For getEntry() + void getEntryImpl(); + void openEntry(int); + void onGetEntryDone(int); + + OwnPtr<net::HostResolver> m_hostResolver; + OwnPtr<net::HttpCache> m_cache; + // This is owned by the ProxyService, which is owned by the HttpNetworkLayer, + // which is owned by the HttpCache, which is owned by this class. + net::ProxyConfigServiceAndroid* m_proxyConfigService; + + // For clear() + net::CompletionCallbackImpl<WebCache> m_doomAllEntriesCallback; + net::CompletionCallbackImpl<WebCache> m_onClearDoneCallback; + bool m_isClearInProgress; + // For getEntry() + net::CompletionCallbackImpl<WebCache> m_openEntryCallback; + net::CompletionCallbackImpl<WebCache> m_onGetEntryDoneCallback; + bool m_isGetEntryInProgress; + String m_entryUrl; + disk_cache::Entry* m_entry; + WTF::Mutex m_getEntryMutex; + WTF::ThreadCondition m_getEntryCondition; + + disk_cache::Backend* m_cacheBackend; +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp b/Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp new file mode 100644 index 0000000..99de67e --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebCookieJar.h" + +#include "JNIUtility.h" +#include "WebCoreJni.h" +#include "WebRequestContext.h" +#include "WebUrlLoaderClient.h" + +#include <cutils/log.h> +#include <dirent.h> + +#undef ASSERT +#define ASSERT(assertion, ...) do \ + if (!(assertion)) { \ + android_printLog(ANDROID_LOG_ERROR, __FILE__, __VA_ARGS__); \ + } \ +while (0) + +namespace android { + +static WTF::Mutex instanceMutex; +static bool isFirstInstanceCreated = false; +static bool fileSchemeCookiesEnabled = false; + +static const std::string& databaseDirectory() +{ + // This method may be called on any thread, as the Java method is + // synchronized. + static WTF::Mutex databaseDirectoryMutex; + MutexLocker lock(databaseDirectoryMutex); + static std::string databaseDirectory; + if (databaseDirectory.empty()) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jclass bridgeClass = env->FindClass("android/webkit/JniUtil"); + jmethodID method = env->GetStaticMethodID(bridgeClass, "getDatabaseDirectory", "()Ljava/lang/String;"); + databaseDirectory = jstringToStdString(env, static_cast<jstring>(env->CallStaticObjectMethod(bridgeClass, method))); + env->DeleteLocalRef(bridgeClass); + } + return databaseDirectory; +} + +static void removeFileOrDirectory(const char* filename) +{ + struct stat filetype; + if (stat(filename, &filetype) != 0) + return; + if (S_ISDIR(filetype.st_mode)) { + DIR* directory = opendir(filename); + if (directory) { + while (struct dirent* entry = readdir(directory)) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + std::string entryName(filename); + entryName.append("/"); + entryName.append(entry->d_name); + removeFileOrDirectory(entryName.c_str()); + } + closedir(directory); + rmdir(filename); + } + return; + } + unlink(filename); +} + +static std::string databaseDirectory(bool isPrivateBrowsing) +{ + static const char* const kDatabaseFilename = "/webviewCookiesChromium.db"; + static const char* const kDatabaseFilenamePrivateBrowsing = "/webviewCookiesChromiumPrivate.db"; + + std::string databaseFilePath = databaseDirectory(); + databaseFilePath.append(isPrivateBrowsing ? kDatabaseFilenamePrivateBrowsing : kDatabaseFilename); + return databaseFilePath; +} + +scoped_refptr<WebCookieJar>* instance(bool isPrivateBrowsing) +{ + static scoped_refptr<WebCookieJar> regularInstance; + static scoped_refptr<WebCookieJar> privateInstance; + return isPrivateBrowsing ? &privateInstance : ®ularInstance; +} + +WebCookieJar* WebCookieJar::get(bool isPrivateBrowsing) +{ + MutexLocker lock(instanceMutex); + if (!isFirstInstanceCreated && fileSchemeCookiesEnabled) + net::CookieMonster::EnableFileScheme(); + isFirstInstanceCreated = true; + scoped_refptr<WebCookieJar>* instancePtr = instance(isPrivateBrowsing); + if (!instancePtr->get()) + *instancePtr = new WebCookieJar(databaseDirectory(isPrivateBrowsing)); + return instancePtr->get(); +} + +void WebCookieJar::cleanup(bool isPrivateBrowsing) +{ + // This is called on the UI thread. + MutexLocker lock(instanceMutex); + scoped_refptr<WebCookieJar>* instancePtr = instance(isPrivateBrowsing); + *instancePtr = 0; + removeFileOrDirectory(databaseDirectory(isPrivateBrowsing).c_str()); +} + +WebCookieJar::WebCookieJar(const std::string& databaseFilePath) + : m_allowCookies(true) +{ + // Setup the permissions for the file + const char* cDatabasePath = databaseFilePath.c_str(); + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + if (access(cDatabasePath, F_OK) == 0) + chmod(cDatabasePath, mode); + else { + int fd = open(cDatabasePath, O_CREAT, mode); + if (fd >= 0) + close(fd); + } + + FilePath cookiePath(databaseFilePath.c_str()); + m_cookieDb = new SQLitePersistentCookieStore(cookiePath); + m_cookieStore = new net::CookieMonster(m_cookieDb.get(), 0); +} + +bool WebCookieJar::allowCookies() +{ + MutexLocker lock(m_allowCookiesMutex); + return m_allowCookies; +} + +void WebCookieJar::setAllowCookies(bool allow) +{ + MutexLocker lock(m_allowCookiesMutex); + m_allowCookies = allow; +} + +int WebCookieJar::getNumCookiesInDatabase() +{ + if (!m_cookieStore) + return 0; + return m_cookieStore->GetCookieMonster()->GetAllCookies().size(); +} + +// From CookiePolicy in chromium +int WebCookieJar::CanGetCookies(const GURL&, const GURL&, net::CompletionCallback*) +{ + MutexLocker lock(m_allowCookiesMutex); + return m_allowCookies ? net::OK : net::ERR_ACCESS_DENIED; +} + +// From CookiePolicy in chromium +int WebCookieJar::CanSetCookie(const GURL&, const GURL&, const std::string&, net::CompletionCallback*) +{ + MutexLocker lock(m_allowCookiesMutex); + return m_allowCookies ? net::OK : net::ERR_ACCESS_DENIED; +} + +class FlushSemaphore : public base::RefCounted<FlushSemaphore> +{ +public: + FlushSemaphore() + : m_condition(&m_lock) + , m_count(0) + {} + + void SendFlushRequest(net::CookieMonster* monster) { + // FlushStore() needs to run on a Chrome thread (because it will need + // to post the callback, and it may want to do so on its own thread.) + // We use the IO thread for this purpose. + // + // TODO(husky): Our threads are hidden away in various files. Clean this + // up and consider integrating with Chrome's browser_thread.h. Might be + // a better idea to use the DB thread here rather than the IO thread. + + base::Thread* ioThread = WebUrlLoaderClient::ioThread(); + if (ioThread) { + Task* callback = NewRunnableMethod(this, &FlushSemaphore::Callback); + ioThread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + monster, &net::CookieMonster::FlushStore, callback)); + } else { + Callback(); + } + } + + // Block until the given number of callbacks has been made. + void Wait(int numCallbacks) { + AutoLock al(m_lock); + int lastCount = m_count; + while (m_count < numCallbacks) { + // TODO(husky): Maybe use TimedWait() here? But it's not obvious what + // to do if the flush fails. Might be okay just to let the OS kill us. + m_condition.Wait(); + ASSERT(lastCount != m_count, "Wait finished without incrementing m_count %d %d", m_count, lastCount); + lastCount = m_count; + } + m_count -= numCallbacks; + } + +private: + friend class base::RefCounted<FlushSemaphore>; + + void Callback() { + AutoLock al(m_lock); + m_count++; + m_condition.Broadcast(); + } + + Lock m_lock; + ConditionVariable m_condition; + volatile int m_count; +}; + +void WebCookieJar::flush() +{ + // Flush both cookie stores (private and non-private), wait for 2 callbacks. + static scoped_refptr<FlushSemaphore> semaphore(new FlushSemaphore()); + semaphore->SendFlushRequest(get(false)->cookieStore()->GetCookieMonster()); + semaphore->SendFlushRequest(get(true)->cookieStore()->GetCookieMonster()); + semaphore->Wait(2); +} + +bool WebCookieJar::acceptFileSchemeCookies() +{ + MutexLocker lock(instanceMutex); + return fileSchemeCookiesEnabled; +} + +void WebCookieJar::setAcceptFileSchemeCookies(bool accept) +{ + // The Chromium HTTP stack only reflects changes to this flag when creating + // a new CookieMonster instance. While we could track whether any + // CookieMonster instances currently exist, this would be complicated and is + // not required, so we only allow this flag to be changed before the first + // instance is created. + MutexLocker lock(instanceMutex); + if (!isFirstInstanceCreated) + fileSchemeCookiesEnabled = accept; +} + +} diff --git a/Source/WebKit/android/WebCoreSupport/WebCookieJar.h b/Source/WebKit/android/WebCoreSupport/WebCookieJar.h new file mode 100644 index 0000000..1f4266c --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebCookieJar.h @@ -0,0 +1,78 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebCookieJar_h +#define WebCookieJar_h + +#include "ChromiumIncludes.h" + +#include <wtf/ThreadingPrimitives.h> + +namespace android { + +// This class is threadsafe. It is used from the IO, WebCore and Chromium IO +// threads. +class WebCookieJar : public net::CookiePolicy, public base::RefCountedThreadSafe<WebCookieJar> { +public: + static WebCookieJar* get(bool isPrivateBrowsing); + static void cleanup(bool isPrivateBrowsing); + + // Flush all cookies to disk. Synchronous. + static void flush(); + + // CookiePolicy implementation from external/chromium + virtual int CanGetCookies(const GURL& url, const GURL& first_party_for_cookies, net::CompletionCallback*); + virtual int CanSetCookie(const GURL& url, const GURL& first_party_for_cookies, const std::string& cookie_line, net::CompletionCallback*); + + bool allowCookies(); + void setAllowCookies(bool allow); + + // Getter and setter for whether we accept cookies for file scheme URLS. + // Defaults to false. Note that calls to the setter are ignored once the + // first instance of this class has been created. + static bool acceptFileSchemeCookies(); + static void setAcceptFileSchemeCookies(bool); + + // Instead of this it would probably be better to add the cookie methods + // here so the rest of WebKit doesn't have to know about Chromium classes + net::CookieStore* cookieStore() { return m_cookieStore.get(); } + net::CookiePolicy* cookiePolicy() { return this; } + + // Get the number of cookies that have actually been saved to flash. + // (This is used to implement CookieManager.hasCookies() in the Java framework.) + int getNumCookiesInDatabase(); + +private: + WebCookieJar(const std::string& databaseFilePath); + + scoped_refptr<SQLitePersistentCookieStore> m_cookieDb; + scoped_refptr<net::CookieStore> m_cookieStore; + bool m_allowCookies; + WTF::Mutex m_allowCookiesMutex; +}; + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebRequest.cpp b/Source/WebKit/android/WebCoreSupport/WebRequest.cpp new file mode 100644 index 0000000..a7321da --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebRequest.cpp @@ -0,0 +1,531 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebRequest.h" + +#include "JNIUtility.h" +#include "MainThread.h" +#include "UrlInterceptResponse.h" +#include "WebCoreFrameBridge.h" +#include "WebRequestContext.h" +#include "WebResourceRequest.h" +#include "WebUrlLoaderClient.h" +#include "jni.h" + +#include <cutils/log.h> +#include <string> +#include <utils/AssetManager.h> + +extern android::AssetManager* globalAssetManager(); + +// TODO: +// - Finish the file upload. Testcase is mobile buzz +// - Add network throttle needed by Android plugins + +// TODO: Turn off asserts crashing before release +// http://b/issue?id=2951985 +#undef ASSERT +#define ASSERT(assertion, ...) do \ + if (!(assertion)) { \ + android_printLog(ANDROID_LOG_ERROR, __FILE__, __VA_ARGS__); \ + } \ +while (0) + +namespace android { + +namespace { + const int kInitialReadBufSize = 32768; +} + +WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest) + : m_urlLoader(loader) + , m_androidUrl(false) + , m_url(webResourceRequest.url()) + , m_userAgent(webResourceRequest.userAgent()) + , m_loadState(Created) + , m_authRequestCount(0) + , m_cacheMode(0) + , m_runnableFactory(this) + , m_wantToPause(false) + , m_isPaused(false) + , m_isSync(false) +{ + GURL gurl(m_url); + + m_request = new URLRequest(gurl, this); + + m_request->SetExtraRequestHeaders(webResourceRequest.requestHeaders()); + m_request->set_referrer(webResourceRequest.referrer()); + m_request->set_method(webResourceRequest.method()); + m_request->set_load_flags(webResourceRequest.loadFlags()); +} + +// This is a special URL for Android. Query the Java InputStream +// for data and send to WebCore +WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest, UrlInterceptResponse* intercept) + : m_urlLoader(loader) + , m_interceptResponse(intercept) + , m_androidUrl(true) + , m_url(webResourceRequest.url()) + , m_userAgent(webResourceRequest.userAgent()) + , m_loadState(Created) + , m_authRequestCount(0) + , m_cacheMode(0) + , m_runnableFactory(this) + , m_wantToPause(false) + , m_isPaused(false) + , m_isSync(false) +{ +} + +WebRequest::~WebRequest() +{ + ASSERT(m_loadState == Finished, "dtor called on a WebRequest in a different state than finished (%d)", m_loadState); + + m_loadState = Deleted; +} + +const std::string& WebRequest::getUrl() const +{ + return m_url; +} + +const std::string& WebRequest::getUserAgent() const +{ + return m_userAgent; +} + +#ifdef LOG_REQUESTS +namespace { +int remaining = 0; +} +#endif + +void WebRequest::finish(bool success) +{ + m_runnableFactory.RevokeAll(); + ASSERT(m_loadState < Finished, "(%p) called finish on an already finished WebRequest (%d) (%s)", this, m_loadState, m_url.c_str()); + if (m_loadState >= Finished) + return; +#ifdef LOG_REQUESTS + time_t finish; + time(&finish); + finish = finish - m_startTime; + struct tm * timeinfo; + char buffer[80]; + timeinfo = localtime(&finish); + strftime(buffer, 80, "Time: %M:%S",timeinfo); + android_printLog(ANDROID_LOG_DEBUG, "KM", "(%p) finish (%d) (%s) (%d) (%s)", this, --remaining, buffer, success, m_url.c_str()); +#endif + + // Make sure WebUrlLoaderClient doesn't delete us in the middle of this method. + scoped_refptr<WebRequest> guard(this); + + m_loadState = Finished; + if (success) { + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didFinishLoading)); + } else { + if (m_interceptResponse == NULL) { + OwnPtr<WebResponse> webResponse(new WebResponse(m_request.get())); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didFail, webResponse.release())); + } else { + OwnPtr<WebResponse> webResponse(new WebResponse(m_url, m_interceptResponse->mimeType(), 0, + m_interceptResponse->encoding(), m_interceptResponse->status())); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didFail, webResponse.release())); + } + } + m_networkBuffer = 0; + m_request = 0; + m_urlLoader = 0; +} + +void WebRequest::appendFileToUpload(const std::string& filename) +{ + // AppendFileToUpload is only valid before calling start + ASSERT(m_loadState == Created, "appendFileToUpload called on a WebRequest not in CREATED state: (%s)", m_url.c_str()); + FilePath filePath(filename); + m_request->AppendFileToUpload(filePath); +} + +void WebRequest::appendBytesToUpload(WTF::Vector<char>* data) +{ + // AppendBytesToUpload is only valid before calling start + ASSERT(m_loadState == Created, "appendBytesToUpload called on a WebRequest not in CREATED state: (%s)", m_url.c_str()); + m_request->AppendBytesToUpload(data->data(), data->size()); + delete data; +} + +void WebRequest::setRequestContext(WebRequestContext* context) +{ + m_cacheMode = context->getCacheMode(); + if (m_request) + m_request->set_context(context); +} + +void WebRequest::updateLoadFlags(int& loadFlags) +{ + if (m_cacheMode == 1) { // LOAD_CACHE_ELSE_NETWORK + loadFlags |= net::LOAD_PREFERRING_CACHE; + loadFlags &= ~net::LOAD_VALIDATE_CACHE; + } + if (m_cacheMode == 2) // LOAD_NO_CACHE + loadFlags |= net::LOAD_BYPASS_CACHE; + if (m_cacheMode == 3) // LOAD_CACHE_ONLY + loadFlags |= net::LOAD_ONLY_FROM_CACHE; + + if (m_isSync) + loadFlags |= net::LOAD_IGNORE_LIMITS; +} + +void WebRequest::start() +{ + ASSERT(m_loadState == Created, "Start called on a WebRequest not in CREATED state: (%s)", m_url.c_str()); +#ifdef LOG_REQUESTS + android_printLog(ANDROID_LOG_DEBUG, "KM", "(%p) start (%d) (%s)", this, ++remaining, m_url.c_str()); + time(&m_startTime); +#endif + + m_loadState = Started; + + if (m_interceptResponse != NULL) + return handleInterceptedURL(); + + // Handle data urls before we send it off to the http stack + if (m_request->url().SchemeIs("data")) + return handleDataURL(m_request->url()); + + if (m_request->url().SchemeIs("browser")) + return handleBrowserURL(m_request->url()); + + // Update load flags with settings from WebSettings + int loadFlags = m_request->load_flags(); + updateLoadFlags(loadFlags); + m_request->set_load_flags(loadFlags); + + m_request->Start(); +} + +void WebRequest::cancel() +{ + ASSERT(m_loadState >= Started, "Cancel called on a not started WebRequest: (%s)", m_url.c_str()); + ASSERT(m_loadState != Cancelled, "Cancel called on an already cancelled WebRequest: (%s)", m_url.c_str()); + + // There is a possible race condition between the IO thread finishing the request and + // the WebCore thread cancelling it. If the request has already finished, do + // nothing to avoid sending duplicate finish messages to WebCore. + if (m_loadState > Cancelled) { + return; + } + ASSERT(m_request, "Request set to 0 before it is finished"); + + m_loadState = Cancelled; + + m_request->Cancel(); + finish(true); +} + +void WebRequest::pauseLoad(bool pause) +{ + ASSERT(m_loadState >= GotData, "PauseLoad in state other than RESPONSE and GOTDATA"); + if (pause) { + if (!m_isPaused) + m_wantToPause = true; + } else { + m_wantToPause = false; + if (m_isPaused) { + m_isPaused = false; + MessageLoop::current()->PostTask(FROM_HERE, m_runnableFactory.NewRunnableMethod(&WebRequest::startReading)); + } + } +} + +void WebRequest::handleInterceptedURL() +{ + m_loadState = Response; + + const std::string& mime = m_interceptResponse->mimeType(); + // Get the MIME type from the URL. "text/html" is a last resort, hopefully overridden. + std::string mimeType("text/html"); + if (mime == "") { + // Gmail appends the MIME to the end of the URL, with a ? separator. + size_t mimeTypeIndex = m_url.find_last_of('?'); + if (mimeTypeIndex != std::string::npos) { + mimeType.assign(m_url.begin() + mimeTypeIndex + 1, m_url.end()); + } else { + // Get the MIME type from the file extension, if any. + FilePath path(m_url); + net::GetMimeTypeFromFile(path, &mimeType); + } + } else { + // Set from the intercept response. + mimeType = mime; + } + + + OwnPtr<WebResponse> webResponse(new WebResponse(m_url, mimeType, 0, m_interceptResponse->encoding(), m_interceptResponse->status())); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release())); + + do { + // data is deleted in WebUrlLoaderClient::didReceiveAndroidFileData + // data is sent to the webcore thread + OwnPtr<std::vector<char> > data(new std::vector<char>); + data->reserve(kInitialReadBufSize); + + // Read returns false on error and size of 0 on eof. + if (!m_interceptResponse->readStream(data.get()) || data->size() == 0) + break; + + m_loadState = GotData; + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveAndroidFileData, data.release())); + } while (true); + + finish(m_interceptResponse->status() == 200); +} + +void WebRequest::handleDataURL(GURL url) +{ + OwnPtr<std::string> data(new std::string); + std::string mimeType; + std::string charset; + + if (net::DataURL::Parse(url, &mimeType, &charset, data.get())) { + // PopulateURLResponse from chrome implementation + // weburlloader_impl.cc + m_loadState = Response; + OwnPtr<WebResponse> webResponse(new WebResponse(url.spec(), mimeType, data->size(), charset, 200)); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release())); + + if (!data->empty()) { + m_loadState = GotData; + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveDataUrl, data.release())); + } + } else { + // handle the failed case + } + + finish(true); +} + +void WebRequest::handleBrowserURL(GURL url) +{ + std::string data("data:text/html;charset=utf-8,"); + if (url.spec() == "browser:incognito") { + AssetManager* assetManager = globalAssetManager(); + Asset* asset = assetManager->open("webkit/incognito_mode_start_page.html", Asset::ACCESS_BUFFER); + if (asset) { + data.append((const char*)asset->getBuffer(false), asset->getLength()); + delete asset; + } + } + GURL dataURL(data.c_str()); + handleDataURL(dataURL); +} + +// Called upon a server-initiated redirect. The delegate may call the +// request's Cancel method to prevent the redirect from being followed. +// Since there may be multiple chained redirects, there may also be more +// than one redirect call. +// +// When this function is called, the request will still contain the +// original URL, the destination of the redirect is provided in 'new_url'. +// If the delegate does not cancel the request and |*defer_redirect| is +// false, then the redirect will be followed, and the request's URL will be +// changed to the new URL. Otherwise if the delegate does not cancel the +// request and |*defer_redirect| is true, then the redirect will be +// followed once FollowDeferredRedirect is called on the URLRequest. +// +// The caller must set |*defer_redirect| to false, so that delegates do not +// need to set it if they are happy with the default behavior of not +// deferring redirect. +void WebRequest::OnReceivedRedirect(URLRequest* newRequest, const GURL& newUrl, bool* deferRedirect) +{ + ASSERT(m_loadState < Response, "Redirect after receiving response"); + ASSERT(newRequest && newRequest->status().is_success(), "Invalid redirect"); + + OwnPtr<WebResponse> webResponse(new WebResponse(newRequest)); + webResponse->setUrl(newUrl.spec()); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::willSendRequest, webResponse.release())); + + // Defer the redirect until followDeferredRedirect() is called. + *deferRedirect = true; +} + +// Called when we receive an authentication failure. The delegate should +// call request->SetAuth() with the user's credentials once it obtains them, +// or request->CancelAuth() to cancel the login and display the error page. +// When it does so, the request will be reissued, restarting the sequence +// of On* callbacks. +void WebRequest::OnAuthRequired(URLRequest* request, net::AuthChallengeInfo* authInfo) +{ + ASSERT(m_loadState == Started, "OnAuthRequired called on a WebRequest not in STARTED state (state=%d)", m_loadState); + + scoped_refptr<net::AuthChallengeInfo> authInfoPtr(authInfo); + bool firstTime = (m_authRequestCount == 0); + ++m_authRequestCount; + + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::authRequired, authInfoPtr, firstTime)); +} + +// Called when we received an SSL certificate error. The delegate will provide +// the user the options to proceed, cancel, or view certificates. +void WebRequest::OnSSLCertificateError(URLRequest* request, int cert_error, net::X509Certificate* cert) +{ + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::reportSslCertError, cert_error, cert)); +} + +// After calling Start(), the delegate will receive an OnResponseStarted +// callback when the request has completed. If an error occurred, the +// request->status() will be set. On success, all redirects have been +// followed and the final response is beginning to arrive. At this point, +// meta data about the response is available, including for example HTTP +// response headers if this is a request for a HTTP resource. +void WebRequest::OnResponseStarted(URLRequest* request) +{ + ASSERT(m_loadState == Started, "Got response after receiving response"); + + m_loadState = Response; + if (request && request->status().is_success()) { + OwnPtr<WebResponse> webResponse(new WebResponse(request)); + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release())); + + // Start reading the response + startReading(); + } else { + finish(false); + } +} + +void WebRequest::setAuth(const string16& username, const string16& password) +{ + ASSERT(m_loadState == Started, "setAuth called on a WebRequest not in STARTED state (state=%d)", m_loadState); + + m_request->SetAuth(username, password); +} + +void WebRequest::cancelAuth() +{ + ASSERT(m_loadState == Started, "cancelAuth called on a WebRequest not in STARTED state (state=%d)", m_loadState); + + m_request->CancelAuth(); +} + +void WebRequest::followDeferredRedirect() +{ + ASSERT(m_loadState < Response, "Redirect after receiving response"); + + m_request->FollowDeferredRedirect(); +} + +void WebRequest::proceedSslCertError() +{ + m_request->ContinueDespiteLastError(); +} + +void WebRequest::cancelSslCertError(int cert_error) +{ + m_request->SimulateError(cert_error); +} + +void WebRequest::startReading() +{ + ASSERT(m_networkBuffer == 0, "startReading called with a nonzero buffer"); + ASSERT(m_isPaused == 0, "startReading called in paused state"); + ASSERT(m_loadState == Response || m_loadState == GotData, "StartReading in state other than RESPONSE and GOTDATA"); + if (m_loadState > GotData) // We have been cancelled between reads + return; + + if (m_wantToPause) { + m_isPaused = true; + return; + } + + int bytesRead = 0; + + if (!read(&bytesRead)) { + if (m_request && m_request->status().is_io_pending()) + return; // Wait for OnReadCompleted() + return finish(false); + } + + // bytesRead == 0 indicates finished + if (!bytesRead) + return finish(true); + + m_loadState = GotData; + // Read ok, forward buffer to webcore + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(m_urlLoader.get(), &WebUrlLoaderClient::didReceiveData, m_networkBuffer, bytesRead)); + m_networkBuffer = 0; + MessageLoop::current()->PostTask(FROM_HERE, m_runnableFactory.NewRunnableMethod(&WebRequest::startReading)); +} + +bool WebRequest::read(int* bytesRead) +{ + ASSERT(m_loadState == Response || m_loadState == GotData, "read in state other than RESPONSE and GOTDATA"); + ASSERT(m_networkBuffer == 0, "Read called with a nonzero buffer"); + + // TODO: when asserts work, check that the buffer is 0 here + m_networkBuffer = new net::IOBuffer(kInitialReadBufSize); + return m_request->Read(m_networkBuffer, kInitialReadBufSize, bytesRead); +} + +// This is called when there is data available + +// Called when the a Read of the response body is completed after an +// IO_PENDING status from a Read() call. +// The data read is filled into the buffer which the caller passed +// to Read() previously. +// +// If an error occurred, request->status() will contain the error, +// and bytes read will be -1. +void WebRequest::OnReadCompleted(URLRequest* request, int bytesRead) +{ + ASSERT(m_loadState == Response || m_loadState == GotData, "OnReadCompleted in state other than RESPONSE and GOTDATA"); + + if (request->status().is_success()) { + m_loadState = GotData; + m_urlLoader->maybeCallOnMainThread(NewRunnableMethod( + m_urlLoader.get(), &WebUrlLoaderClient::didReceiveData, m_networkBuffer, bytesRead)); + m_networkBuffer = 0; + + // Get the rest of the data + startReading(); + } else { + finish(false); + } +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/WebRequest.h b/Source/WebKit/android/WebCoreSupport/WebRequest.h new file mode 100644 index 0000000..252267b --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebRequest.h @@ -0,0 +1,125 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebRequest_h +#define WebRequest_h + +#include "ChromiumIncludes.h" +#include <wtf/Vector.h> + +class MessageLoop; + +namespace android { + +enum LoadState { + Created, + Started, + Response, + GotData, + Cancelled, + Finished, + Deleted +}; + +class UrlInterceptResponse; +class WebFrame; +class WebRequestContext; +class WebResourceRequest; +class WebUrlLoaderClient; + +// All methods in this class must be called on the io thread +class WebRequest : public URLRequest::Delegate, public base::RefCountedThreadSafe<WebRequest> { +public: + WebRequest(WebUrlLoaderClient*, const WebResourceRequest&); + + // If this is an android specific url or the application wants to load + // custom data, we load the data through an input stream. + // Used for: + // - file:///android_asset + // - file:///android_res + // - content:// + WebRequest(WebUrlLoaderClient*, const WebResourceRequest&, UrlInterceptResponse* intercept); + + // Optional, but if used has to be called before start + void appendBytesToUpload(Vector<char>* data); + void appendFileToUpload(const std::string& filename); + + void setRequestContext(WebRequestContext* context); + void start(); + void cancel(); + void pauseLoad(bool pause); + + // From URLRequest::Delegate + virtual void OnReceivedRedirect(URLRequest*, const GURL&, bool* deferRedirect); + virtual void OnResponseStarted(URLRequest*); + virtual void OnReadCompleted(URLRequest*, int bytesRead); + virtual void OnAuthRequired(URLRequest*, net::AuthChallengeInfo*); + virtual void OnSSLCertificateError(URLRequest* request, int cert_error, net::X509Certificate* cert); + + // Methods called during a request by the UI code (via WebUrlLoaderClient). + void setAuth(const string16& username, const string16& password); + void cancelAuth(); + void followDeferredRedirect(); + void proceedSslCertError(); + void cancelSslCertError(int cert_error); + + const std::string& getUrl() const; + const std::string& getUserAgent() const; + + void setSync(bool sync) { m_isSync = sync; } +private: + void startReading(); + bool read(int* bytesRead); + + friend class base::RefCountedThreadSafe<WebRequest>; + virtual ~WebRequest(); + void handleDataURL(GURL); + void handleBrowserURL(GURL); + void handleInterceptedURL(); + void finish(bool success); + void updateLoadFlags(int& loadFlags); + + scoped_refptr<WebUrlLoaderClient> m_urlLoader; + OwnPtr<URLRequest> m_request; + scoped_refptr<net::IOBuffer> m_networkBuffer; + scoped_ptr<UrlInterceptResponse> m_interceptResponse; + bool m_androidUrl; + std::string m_url; + std::string m_userAgent; + LoadState m_loadState; + int m_authRequestCount; + int m_cacheMode; + ScopedRunnableMethodFactory<WebRequest> m_runnableFactory; + bool m_wantToPause; + bool m_isPaused; + bool m_isSync; +#ifdef LOG_REQUESTS + time_t m_startTime; +#endif +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebRequestContext.cpp b/Source/WebKit/android/WebCoreSupport/WebRequestContext.cpp new file mode 100644 index 0000000..78c3501 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebRequestContext.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebRequestContext.h" + +#include "ChromiumIncludes.h" +#include "ChromiumInit.h" +#include "WebCache.h" +#include "WebCookieJar.h" + +#include <wtf/text/CString.h> + +static std::string acceptLanguageStdString(""); +static WTF::String acceptLanguageWtfString(""); +static WTF::Mutex acceptLanguageMutex; + +static int numPrivateBrowsingInstances; + +extern void ANPSystemInterface_CleanupIncognito(); + +using namespace WTF; + +namespace android { + +WebRequestContext::WebRequestContext(bool isPrivateBrowsing) + : m_isPrivateBrowsing(isPrivateBrowsing) +{ + // Initialize chromium logging, needs to be done before any chromium code is called. + initChromium(); + + if (m_isPrivateBrowsing) { + // Delete the old files if this is the first private browsing instance + // They are probably leftovers from a power cycle + // We do not need to clear the cache as it is in memory only for private browsing + if (!numPrivateBrowsingInstances) + WebCookieJar::cleanup(true); + numPrivateBrowsingInstances++; + } + + WebCache* cache = WebCache::get(m_isPrivateBrowsing); + host_resolver_ = cache->hostResolver(); + http_transaction_factory_ = cache->cache(); + + WebCookieJar* cookieJar = WebCookieJar::get(m_isPrivateBrowsing); + cookie_store_ = cookieJar->cookieStore(); + cookie_policy_ = cookieJar; + + // Also hardcoded in FrameLoader.java + accept_charset_ = "utf-8, iso-8859-1, utf-16, *;q=0.7"; +} + +WebRequestContext::~WebRequestContext() +{ + if (m_isPrivateBrowsing) { + numPrivateBrowsingInstances--; + + // This is the last private browsing context, delete the cookies and cache + if (!numPrivateBrowsingInstances) { + WebCookieJar::cleanup(true); + WebCache::cleanup(true); + ANPSystemInterface_CleanupIncognito(); + } + } +} + +void WebRequestContext::setUserAgent(const String& string) +{ + MutexLocker lock(m_userAgentMutex); + m_userAgent = string.utf8().data(); +} + +void WebRequestContext::setCacheMode(int mode) +{ + m_cacheMode = mode; +} + +int WebRequestContext::getCacheMode() +{ + return m_cacheMode; +} + +const std::string& WebRequestContext::GetUserAgent(const GURL& url) const +{ + MutexLocker lock(m_userAgentMutex); + return m_userAgent; +} + +void WebRequestContext::setAcceptLanguage(const String& string) +{ + MutexLocker lock(acceptLanguageMutex); + acceptLanguageStdString = string.utf8().data(); + acceptLanguageWtfString = string; +} + +const std::string& WebRequestContext::GetAcceptLanguage() const +{ + MutexLocker lock(acceptLanguageMutex); + return acceptLanguageStdString; +} + +const String& WebRequestContext::acceptLanguage() +{ + MutexLocker lock(acceptLanguageMutex); + return acceptLanguageWtfString; +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/WebRequestContext.h b/Source/WebKit/android/WebCoreSupport/WebRequestContext.h new file mode 100644 index 0000000..51f0514 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebRequestContext.h @@ -0,0 +1,64 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebRequestContext_h +#define WebRequestContext_h + +#include "ChromiumIncludes.h" +#include "PlatformString.h" + +#include <wtf/ThreadingPrimitives.h> + +namespace android { + +// This class is generally not threadsafe. +class WebRequestContext : public URLRequestContext { +public: + // URLRequestContext overrides. + virtual const std::string& GetUserAgent(const GURL&) const; + virtual const std::string& GetAcceptLanguage() const; + + WebRequestContext(bool isPrivateBrowsing); + + // These methods are threadsafe. + void setUserAgent(const WTF::String&); + void setCacheMode(int); + int getCacheMode(); + static void setAcceptLanguage(const WTF::String&); + static const WTF::String& acceptLanguage(); + +private: + WebRequestContext(); + ~WebRequestContext(); + + std::string m_userAgent; + int m_cacheMode; + mutable WTF::Mutex m_userAgentMutex; + bool m_isPrivateBrowsing; +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebResourceRequest.cpp b/Source/WebKit/android/WebCoreSupport/WebResourceRequest.cpp new file mode 100644 index 0000000..9b70fce --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebResourceRequest.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebResourceRequest.h" + +#include "ResourceRequest.h" + +#include <wtf/text/CString.h> + +using namespace WebCore; + +namespace android { + +WebResourceRequest::WebResourceRequest(const WebCore::ResourceRequest& resourceRequest) +{ + // Set the load flags based on the WebCore request. + m_loadFlags = net::LOAD_NORMAL; + switch (resourceRequest.cachePolicy()) { + case ReloadIgnoringCacheData: + m_loadFlags |= net::LOAD_VALIDATE_CACHE; + break; + case ReturnCacheDataElseLoad: + m_loadFlags |= net::LOAD_PREFERRING_CACHE; + break; + case ReturnCacheDataDontLoad: + m_loadFlags |= net::LOAD_ONLY_FROM_CACHE; + break; + case UseProtocolCachePolicy: + break; + } + + // TODO: We should consider setting these flags and net::LOAD_DO_NOT_SEND_AUTH_DATA + // when FrameLoaderClient::shouldUseCredentialStorage() is false. However, + // the required WebKit logic is not yet in place. See Chromium's + // FrameLoaderClientImpl::shouldUseCredentialStorage(). + if (!resourceRequest.allowCookies()) { + m_loadFlags |= net::LOAD_DO_NOT_SAVE_COOKIES; + m_loadFlags |= net::LOAD_DO_NOT_SEND_COOKIES; + } + + + // Set the request headers + const HTTPHeaderMap& map = resourceRequest.httpHeaderFields(); + for (HTTPHeaderMap::const_iterator it = map.begin(); it != map.end(); ++it) { + const std::string& nameUtf8 = it->first.string().utf8().data(); + const std::string& valueUtf8 = it->second.utf8().data(); + + // Skip over referrer headers found in the header map because we already + // pulled it out as a separate parameter. We likewise prune the UA since + // that will be added back by the network layer. + if (LowerCaseEqualsASCII(nameUtf8, "referer") || LowerCaseEqualsASCII(nameUtf8, "user-agent")) + continue; + + // Skip over "Cache-Control: max-age=0" header if the corresponding + // load flag is already specified. FrameLoader sets both the flag and + // the extra header -- the extra header is redundant since our network + // implementation will add the necessary headers based on load flags. + // See http://code.google.com/p/chromium/issues/detail?id=3434. + if ((m_loadFlags & net::LOAD_VALIDATE_CACHE) && + LowerCaseEqualsASCII(nameUtf8, "cache-control") && LowerCaseEqualsASCII(valueUtf8, "max-age=0")) + continue; + + m_requestHeaders.SetHeader(nameUtf8, valueUtf8); + } + + m_method = resourceRequest.httpMethod().utf8().data(); + m_referrer = resourceRequest.httpReferrer().utf8().data(); + m_userAgent = resourceRequest.httpUserAgent().utf8().data(); + + m_url = resourceRequest.url().string().utf8().data(); +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/WebResourceRequest.h b/Source/WebKit/android/WebCoreSupport/WebResourceRequest.h new file mode 100644 index 0000000..38f37b5 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebResourceRequest.h @@ -0,0 +1,86 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebResourceRequest_h +#define WebResourceRequest_h + +#include "ChromiumIncludes.h" + +#include <string> + +namespace WebCore { +class ResourceRequest; +} + +namespace android { + +class WebResourceRequest { + +public: + WebResourceRequest(const WebCore::ResourceRequest&); + + const std::string& method() const + { + return m_method; + } + + const std::string& referrer() const + { + return m_referrer; + } + + const std::string& userAgent() const + { + return m_userAgent; + } + + const net::HttpRequestHeaders& requestHeaders() const + { + return m_requestHeaders; + } + + const std::string& url() const + { + return m_url; + } + + int loadFlags() const + { + return m_loadFlags; + } + +private: + std::string m_method; + std::string m_referrer; + std::string m_userAgent; + net::HttpRequestHeaders m_requestHeaders; + std::string m_url; + int m_loadFlags; +}; + +} // namespace android + + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebResponse.cpp b/Source/WebKit/android/WebCoreSupport/WebResponse.cpp new file mode 100644 index 0000000..4d297d7 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebResponse.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebResponse.h" + +#include "MIMETypeRegistry.h" +#include "PlatformString.h" +#include "ResourceResponse.h" +#include "ResourceError.h" + +#include <wtf/text/CString.h> + +using namespace std; + +namespace android { + +WebResponse::WebResponse(URLRequest* request) + : m_httpStatusCode(0) +{ + // The misleadingly-named os_error() is actually a net::Error enum constant. + m_error = net::Error(request->status().os_error()); + + m_url = request->url().spec(); + m_host = request->url().HostNoBrackets(); + request->GetMimeType(&m_mime); + + request->GetCharset(&m_encoding); + m_expectedSize = request->GetExpectedContentSize(); + + m_sslInfo = request->ssl_info(); + + net::HttpResponseHeaders* responseHeaders = request->response_headers(); + if (!responseHeaders) + return; + + m_httpStatusCode = responseHeaders->response_code(); + m_httpStatusText = responseHeaders->GetStatusText(); + + string value; + string name; + void* iter = 0; + while (responseHeaders->EnumerateHeaderLines(&iter, &name, &value)) + m_headerFields[name] = value; +} + +WebResponse::WebResponse(const string &url, const string &mimeType, long long expectedSize, const string &encoding, int httpStatusCode) + : m_error(net::OK) + , m_encoding(encoding) + , m_httpStatusCode(httpStatusCode) + , m_expectedSize(expectedSize) + , m_mime(mimeType) + , m_url(url) +{ +} + +WebCore::ResourceResponse WebResponse::createResourceResponse() +{ + WebCore::ResourceResponse resourceResponse(createKurl(), getMimeType().c_str(), m_expectedSize, m_encoding.c_str(), ""); + resourceResponse.setHTTPStatusCode(m_httpStatusCode); + resourceResponse.setHTTPStatusText(m_httpStatusText.c_str()); + + map<string, string>::const_iterator it; + for (it = m_headerFields.begin(); it != m_headerFields.end(); ++it) + resourceResponse.setHTTPHeaderField(it->first.c_str(), it->second.c_str()); + + return resourceResponse; +} + +WebCore::ResourceError WebResponse::createResourceError() +{ + WebCore::ResourceError error(m_host.c_str(), ToWebViewClientError(m_error), m_url.c_str(), WTF::String()); + return error; +} + + +WebCore::KURL WebResponse::createKurl() +{ + WebCore::KURL kurl(WebCore::ParsedURLString, m_url.c_str()); + return kurl; +} + +const string& WebResponse::getUrl() const +{ + return m_url; +} + +void WebResponse::setUrl(const string& url) +{ + m_url = url; +} + +// Calls WebCore APIs so should only be called from the WebCore thread. +// TODO: can we return a WTF::String directly? Need to check all callsites. +const string& WebResponse::getMimeType() +{ + if (!m_url.length()) + return m_mime; + + if (!m_mime.length() || !m_mime.compare("text/plain") || !m_mime.compare("application/octet-stream")) + m_mime = resolveMimeType(m_url, m_mime); + + return m_mime; +} + +const string WebResponse::resolveMimeType(const string& url, const string& old_mime) +{ + // Use "text/html" as a default (matching the behaviour of the Apache + // HTTP stack -- see guessMimeType() in LoadListener.java). + string mimeType = old_mime.length() ? old_mime : "text/html"; + // Try to guess a better MIME type from the URL. We call + // getMIMETypeForExtension rather than getMIMETypeForPath because the + // latter defaults to "application/octet-stream" on failure. + WebCore::KURL kurl(WebCore::ParsedURLString, url.c_str()); + WTF::String path = kurl.path(); + size_t extensionPos = path.reverseFind('.'); + if (extensionPos != WTF::notFound) { + // We found a file extension. + path.remove(0, extensionPos + 1); + // TODO: Should use content-disposition instead of url if it is there + WTF::String mime = WebCore::MIMETypeRegistry::getMIMETypeForExtension(path); + if (!mime.isEmpty()) { + // Great, we found a MIME type. + mimeType = std::string(mime.utf8().data(), mime.length()); + } + } + return mimeType; +} + +bool WebResponse::getHeader(const string& header, string* result) const +{ + map<string, string>::const_iterator iter = m_headerFields.find(header); + if (iter == m_headerFields.end()) + return false; + if (result) + *result = iter->second; + return true; +} + +long long WebResponse::getExpectedSize() const +{ + return m_expectedSize; +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/WebResponse.h b/Source/WebKit/android/WebCoreSupport/WebResponse.h new file mode 100644 index 0000000..88c8917 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebResponse.h @@ -0,0 +1,91 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebResponse_h +#define WebResponse_h + +#include "ChromiumIncludes.h" +#include "KURL.h" +#include "WebViewClientError.h" + +#include <map> +#include <string> + +namespace WebCore { +class ResourceResponse; +class ResourceError; +} + +namespace android { + +class WebResponse { + +public: + WebResponse() {} + WebResponse(URLRequest*); + WebResponse(const std::string &url, const std::string &mimeType, long long expectedSize, const std::string &encoding, int httpStatusCode); + + const std::string& getUrl() const; + void setUrl(const std::string&); + + const std::string& getMimeType(); // Use only on WebCore thread. + bool getHeader(const std::string& header, std::string* result) const; + long long getExpectedSize() const; + + const net::SSLInfo& getSslInfo() const { return m_sslInfo; } + + // The create() methods create WebCore objects. They must only be called on the WebKit thread. + WebCore::KURL createKurl(); + WebCore::ResourceResponse createResourceResponse(); + WebCore::ResourceError createResourceError(); + + static const std::string resolveMimeType(const std::string& url, const std::string& old_mime); + +private: + net::Error m_error; + std::string m_encoding; + int m_httpStatusCode; + std::string m_host; + std::string m_httpStatusText; + long long m_expectedSize; + std::string m_mime; + std::string m_url; + net::SSLInfo m_sslInfo; + + struct CaseInsensitiveLessThan { + bool operator()(const std::string& lhs, const std::string& rhs) const { + return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; + } + }; + + // Header fields are case insensitive, so we use a case-insensitive map. + // See RFC 822, 3.4.7, "CASE INDEPENDENCE". + std::map<std::string, std::string, CaseInsensitiveLessThan> m_headerFields; + +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebUrlLoader.cpp b/Source/WebKit/android/WebCoreSupport/WebUrlLoader.cpp new file mode 100644 index 0000000..0c90bc5 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebUrlLoader.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" + +#include "WebUrlLoader.h" + +#include "FrameLoaderClientAndroid.h" +#include "WebCoreFrameBridge.h" +#include "WebUrlLoaderClient.h" + +namespace android { + +// on main thread +WebUrlLoader::WebUrlLoader(WebFrame* webFrame, WebCore::ResourceHandle* resourceHandle, const WebCore::ResourceRequest& resourceRequest) +{ + m_loaderClient = new WebUrlLoaderClient(webFrame, resourceHandle, resourceRequest); +} + +// on main thread +WebUrlLoader::~WebUrlLoader() +{ +} + +PassRefPtr<WebUrlLoader> WebUrlLoader::start(FrameLoaderClient* client, WebCore::ResourceHandle* resourceHandle, + const WebCore::ResourceRequest& resourceRequest, bool isMainResource, bool isMainFrame, bool isSync, WebRequestContext* context) +{ + FrameLoaderClientAndroid* androidClient = static_cast<FrameLoaderClientAndroid*>(client); + WebFrame* webFrame = androidClient->webFrame(); + + if (webFrame->blockNetworkLoads() && + (resourceRequest.url().protocolIs("http") || + resourceRequest.url().protocolIs("https"))) + return NULL; + + webFrame->maybeSavePassword(androidClient->getFrame(), resourceRequest); + + RefPtr<WebUrlLoader> loader = WebUrlLoader::create(webFrame, resourceHandle, resourceRequest); + loader->m_loaderClient->start(isMainResource, isMainFrame, isSync, context); + + return loader.release(); +} + +PassRefPtr<WebUrlLoader> WebUrlLoader::create(WebFrame* webFrame, WebCore::ResourceHandle* resourceHandle, const WebCore::ResourceRequest& resourceRequest) +{ + return adoptRef(new WebUrlLoader(webFrame, resourceHandle, resourceRequest)); +} + +// on main thread +void WebUrlLoader::cancel() +{ + m_loaderClient->cancel(); +} + +void WebUrlLoader::downloadFile() +{ + m_loaderClient->downloadFile(); +} + +void WebUrlLoader::pauseLoad(bool pause) +{ + m_loaderClient->pauseLoad(pause); +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/WebUrlLoader.h b/Source/WebKit/android/WebCoreSupport/WebUrlLoader.h new file mode 100644 index 0000000..dd88e73 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebUrlLoader.h @@ -0,0 +1,57 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebUrlLoader_h +#define WebUrlLoader_h + +#include "ChromiumIncludes.h" +#include "ResourceLoaderAndroid.h" + +using namespace WebCore; + +namespace android { +class WebUrlLoaderClient; +class WebFrame; +class WebRequestContext; + +class WebUrlLoader : public ResourceLoaderAndroid { +public: + virtual ~WebUrlLoader(); + static PassRefPtr<WebUrlLoader> start(FrameLoaderClient* client, WebCore::ResourceHandle*, const WebCore::ResourceRequest&, bool isMainResource, bool isMainFrame, bool sync, WebRequestContext*); + + virtual void cancel(); + virtual void downloadFile(); + virtual void pauseLoad(bool pause); + +private: + WebUrlLoader(WebFrame*, WebCore::ResourceHandle*, const WebCore::ResourceRequest&); + static PassRefPtr<WebUrlLoader> create(WebFrame*, WebCore::ResourceHandle*, const WebCore::ResourceRequest&); + + scoped_refptr<WebUrlLoaderClient> m_loaderClient; +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp b/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp new file mode 100644 index 0000000..cf218e7 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp @@ -0,0 +1,482 @@ +/* + * Copyright 2010, 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 "WebUrlLoaderClient" + +#include "config.h" +#include "WebUrlLoaderClient.h" + +#include "ChromiumIncludes.h" +#include "OwnPtr.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceResponse.h" +#include "WebCoreFrameBridge.h" +#include "WebRequest.h" +#include "WebResourceRequest.h" + +#include <wtf/text/CString.h> + +namespace android { + +base::Thread* WebUrlLoaderClient::ioThread() +{ + static base::Thread* networkThread = 0; + static Lock networkThreadLock; + + // Multiple threads appear to access the ioThread so we must ensure the + // critical section ordering. + AutoLock lock(networkThreadLock); + + if (!networkThread) + networkThread = new base::Thread("network"); + + if (!networkThread) + return 0; + + if (networkThread->IsRunning()) + return networkThread; + + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + if (!networkThread->StartWithOptions(options)) { + delete networkThread; + networkThread = 0; + } + + return networkThread; +} + +Lock* WebUrlLoaderClient::syncLock() { + static Lock s_syncLock; + return &s_syncLock; +} + +ConditionVariable* WebUrlLoaderClient::syncCondition() { + static ConditionVariable s_syncCondition(syncLock()); + return &s_syncCondition; +} + +WebUrlLoaderClient::~WebUrlLoaderClient() +{ +} + +bool WebUrlLoaderClient::isActive() const +{ + if (m_cancelling) + return false; + if (!m_resourceHandle) + return false; + if (!m_resourceHandle->client()) + return false; + if (m_finished) + return false; + + return true; +} + +WebUrlLoaderClient::WebUrlLoaderClient(WebFrame* webFrame, WebCore::ResourceHandle* resourceHandle, const WebCore::ResourceRequest& resourceRequest) + : m_webFrame(webFrame) + , m_resourceHandle(resourceHandle) + , m_isMainResource(false) + , m_isMainFrame(false) + , m_isCertMimeType(false) + , m_cancelling(false) + , m_sync(false) + , m_finished(false) +{ + WebResourceRequest webResourceRequest(resourceRequest); + UrlInterceptResponse* intercept = webFrame->shouldInterceptRequest(resourceRequest.url().string()); + if (intercept) { + m_request = new WebRequest(this, webResourceRequest, intercept); + return; + } + + m_request = new WebRequest(this, webResourceRequest); + + // Set uploads before start is called on the request + if (resourceRequest.httpBody() && !(webResourceRequest.method() == "GET" || webResourceRequest.method() == "HEAD")) { + Vector<FormDataElement>::iterator iter; + Vector<FormDataElement> elements = resourceRequest.httpBody()->elements(); + for (iter = elements.begin(); iter != elements.end(); iter++) { + FormDataElement element = *iter; + + switch (element.m_type) { + case FormDataElement::data: + if (!element.m_data.isEmpty()) { + // WebKit sometimes gives up empty data to append. These aren't + // necessary so we just optimize those out here. + base::Thread* thread = ioThread(); + if (thread) { + Vector<char>* data = new Vector<char>(element.m_data); + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::appendBytesToUpload, data)); + } + } + break; + case FormDataElement::encodedFile: + { + // Chromium check if it is a directory by checking + // element.m_fileLength, that doesn't work in Android + std::string filename = element.m_filename.utf8().data(); + if (filename.size()) { + // Change from a url string to a filename + if (filename.find("file://") == 0) // Found at pos 0 + filename.erase(0, 7); + base::Thread* thread = ioThread(); + if (thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::appendFileToUpload, filename)); + } + } + break; +#if ENABLE(BLOB) + case FormDataElement::encodedBlob: + LOG_ASSERT(false, "Unexpected use of FormDataElement::encodedBlob"); + break; +#endif // ENABLE(BLOB) + default: + LOG_ASSERT(false, "Unexpected default case in WebUrlLoaderClient.cpp"); + break; + } + } + } +} + +bool WebUrlLoaderClient::start(bool isMainResource, bool isMainFrame, bool sync, WebRequestContext* context) +{ + base::Thread* thread = ioThread(); + if (!thread) { + return false; + } + + m_isMainResource = isMainResource; + m_isMainFrame = isMainFrame; + m_sync = sync; + if (m_sync) { + AutoLock autoLock(*syncLock()); + m_request->setSync(sync); + m_request->setRequestContext(context); + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::start)); + + // Run callbacks until the queue is exhausted and m_finished is true. + // Sometimes, a sync load can wait forever and lock up the WebCore thread, + // here we use TimedWait() with multiple tries to avoid locking. + const int kMaxNumTimeout = 3; + const int kCallbackWaitingTime = 10; + int num_timeout = 0; + while(!m_finished) { + while (!m_queue.empty()) { + OwnPtr<Task> task(m_queue.front()); + m_queue.pop_front(); + task->Run(); + } + if (m_finished) break; + + syncCondition()->TimedWait(base::TimeDelta::FromSeconds(kCallbackWaitingTime)); + if (m_queue.empty()) { + LOGE("Synchronous request timed out after %d seconds for the %dth try, URL: %s", + kCallbackWaitingTime, num_timeout, m_request->getUrl().c_str()); + num_timeout++; + if (num_timeout >= kMaxNumTimeout) { + cancel(); + m_resourceHandle = 0; + return false; + } + } + } + + // This may be the last reference to us, so we may be deleted now. + // Don't access any more member variables after releasing this reference. + m_resourceHandle = 0; + } else { + // Asynchronous start. + // Important to set this before the thread starts so it has a reference and can't be deleted + // before the task starts running on the IO thread. + m_request->setRequestContext(context); + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::start)); + } + return true; +} + +namespace { +// Check if the mime type is for certificate installation. +// The items must be consistent with the sCertificateTypeMap +// in frameworks/base/core/java/android/webkit/CertTool.java. +bool isMimeTypeForCert(const std::string& mimeType) +{ + static std::hash_set<std::string> sCertificateTypeSet; + if (sCertificateTypeSet.empty()) { + sCertificateTypeSet.insert("application/x-x509-ca-cert"); + sCertificateTypeSet.insert("application/x-x509-user-cert"); + sCertificateTypeSet.insert("application/x-pkcs12"); + } + return sCertificateTypeSet.find(mimeType) != sCertificateTypeSet.end(); +} +} + +void WebUrlLoaderClient::downloadFile() +{ + if (m_response) { + std::string contentDisposition; + m_response->getHeader("content-disposition", &contentDisposition); + m_webFrame->downloadStart(m_response->getUrl(), m_request->getUserAgent(), contentDisposition, m_response->getMimeType(), m_response->getExpectedSize()); + + m_isCertMimeType = isMimeTypeForCert(m_response->getMimeType()); + // Currently, only certificate mime type needs to receive the data. + // Other mime type, e.g. wav, will send the url to other application + // which will load the data by url. + if (!m_isCertMimeType) + cancel(); + } else { + LOGE("Unexpected call to downloadFile() before didReceiveResponse(). URL: %s", m_request->getUrl().c_str()); + // TODO: Turn off asserts crashing before release + // http://b/issue?id=2951985 + CRASH(); + } +} + +void WebUrlLoaderClient::cancel() +{ + if (!isActive()) + return; + + m_cancelling = true; + + base::Thread* thread = ioThread(); + if (thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::cancel)); +} + +void WebUrlLoaderClient::pauseLoad(bool pause) +{ + if (!isActive()) + return; + + base::Thread* thread = ioThread(); + if (thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::pauseLoad, pause)); +} + +void WebUrlLoaderClient::setAuth(const std::string& username, const std::string& password) +{ + if (!isActive()) + return; + + base::Thread* thread = ioThread(); + if (!thread) { + return; + } + string16 username16 = ASCIIToUTF16(username); + string16 password16 = ASCIIToUTF16(password); + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::setAuth, username16, password16)); +} + +void WebUrlLoaderClient::cancelAuth() +{ + if (!isActive()) + return; + + base::Thread* thread = ioThread(); + if (!thread) { + return; + } + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::cancelAuth)); +} + +void WebUrlLoaderClient::proceedSslCertError() +{ + base::Thread* thread = ioThread(); + if (isActive() && thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::proceedSslCertError)); + this->Release(); +} + +void WebUrlLoaderClient::cancelSslCertError(int cert_error) +{ + base::Thread* thread = ioThread(); + if (isActive() && thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::cancelSslCertError, cert_error)); + this->Release(); +} + + +void WebUrlLoaderClient::finish() +{ + m_finished = true; + if (!m_sync) { + // This is the last reference to us, so we will be deleted now. + // We only release the reference here if start() was called asynchronously! + m_resourceHandle = 0; + } + m_request = 0; +} + +namespace { +// Trampoline to wrap a Chromium Task* in a WebKit-style static function + void*. +static void RunTask(void* v) { + OwnPtr<Task> task(static_cast<Task*>(v)); + task->Run(); +} +} + +// This is called from the IO thread, and dispatches the callback to the main thread. +void WebUrlLoaderClient::maybeCallOnMainThread(Task* task) +{ + if (m_sync) { + AutoLock autoLock(*syncLock()); + if (m_queue.empty()) { + syncCondition()->Broadcast(); + } + m_queue.push_back(task); + } else { + // Let WebKit handle it. + callOnMainThread(RunTask, task); + } +} + +// Response methods +void WebUrlLoaderClient::didReceiveResponse(PassOwnPtr<WebResponse> webResponse) +{ + if (!isActive()) + return; + + m_response = webResponse; + m_resourceHandle->client()->didReceiveResponse(m_resourceHandle.get(), m_response->createResourceResponse()); + + // Set the main page's certificate to WebView. + if (m_isMainResource && m_isMainFrame) { + const net::SSLInfo& ssl_info = m_response->getSslInfo(); + if (ssl_info.is_valid()) { + std::vector<std::string> chain_bytes; + ssl_info.cert->GetChainDEREncodedBytes(&chain_bytes); + m_webFrame->setCertificate(chain_bytes[0]); + } + + // Look for X-Auto-Login on the main resource to log in the user. + std::string login; + if (m_response->getHeader("x-auto-login", &login)) + m_webFrame->autoLogin(login); + } +} + +void WebUrlLoaderClient::didReceiveData(scoped_refptr<net::IOBuffer> buf, int size) +{ + if (m_isMainResource && m_isCertMimeType) { + m_webFrame->didReceiveData(buf->data(), size); + } + + if (!isActive() || !size) + return; + + // didReceiveData will take a copy of the data + if (m_resourceHandle && m_resourceHandle->client()) + m_resourceHandle->client()->didReceiveData(m_resourceHandle.get(), buf->data(), size, size); +} + +// For data url's +void WebUrlLoaderClient::didReceiveDataUrl(PassOwnPtr<std::string> str) +{ + if (!isActive() || !str->size()) + return; + + // didReceiveData will take a copy of the data + m_resourceHandle->client()->didReceiveData(m_resourceHandle.get(), str->data(), str->size(), str->size()); +} + +// For special android files +void WebUrlLoaderClient::didReceiveAndroidFileData(PassOwnPtr<std::vector<char> > vector) +{ + if (!isActive() || !vector->size()) + return; + + // didReceiveData will take a copy of the data + m_resourceHandle->client()->didReceiveData(m_resourceHandle.get(), vector->begin(), vector->size(), vector->size()); +} + +void WebUrlLoaderClient::didFail(PassOwnPtr<WebResponse> webResponse) +{ + if (isActive()) + m_resourceHandle->client()->didFail(m_resourceHandle.get(), webResponse->createResourceError()); + + // Always finish a request, if not it will leak + finish(); +} + +void WebUrlLoaderClient::willSendRequest(PassOwnPtr<WebResponse> webResponse) +{ + if (!isActive()) + return; + + KURL url = webResponse->createKurl(); + OwnPtr<WebCore::ResourceRequest> resourceRequest(new WebCore::ResourceRequest(url)); + m_resourceHandle->client()->willSendRequest(m_resourceHandle.get(), *resourceRequest, webResponse->createResourceResponse()); + + // WebKit may have killed the request. + if (!isActive()) + return; + + // Like Chrome, we only follow the redirect if WebKit left the URL unmodified. + if (url == resourceRequest->url()) { + ioThread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(m_request.get(), &WebRequest::followDeferredRedirect)); + } else { + cancel(); + } +} + +void WebUrlLoaderClient::didFinishLoading() +{ + if (isActive()) + m_resourceHandle->client()->didFinishLoading(m_resourceHandle.get(), 0); + + if (m_isMainResource && m_isCertMimeType) { + m_webFrame->didFinishLoading(); + } + + // Always finish a request, if not it will leak + finish(); +} + +void WebUrlLoaderClient::authRequired(scoped_refptr<net::AuthChallengeInfo> authChallengeInfo, bool firstTime) +{ + if (!isActive()) + return; + + std::string host = base::SysWideToUTF8(authChallengeInfo->host_and_port); + std::string realm = base::SysWideToUTF8(authChallengeInfo->realm); + + m_webFrame->didReceiveAuthenticationChallenge(this, host, realm, firstTime); +} + +void WebUrlLoaderClient::reportSslCertError(int cert_error, net::X509Certificate* cert) +{ + if (!isActive()) + return; + + std::vector<std::string> chain_bytes; + cert->GetChainDEREncodedBytes(&chain_bytes); + this->AddRef(); + m_webFrame->reportSslCertError(this, cert_error, chain_bytes[0]); +} + +} // namespace android diff --git a/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.h b/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.h new file mode 100644 index 0000000..dc101db --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.h @@ -0,0 +1,130 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebUrlLoaderClient_h +#define WebUrlLoaderClient_h + +#include "ChromiumIncludes.h" +#include "RefCounted.h" +#include "WebResponse.h" +#include "WebUrlLoader.h" + +#include <string> +#include <deque> +#include <string> +#include <vector> + +class Lock; +class ConditionVariable; + +namespace base { +class Thread; +} + +namespace net { +class IOBuffer; +class AuthChallengeInfo; +} + +namespace android { + +class WebFrame; +class WebRequest; +class WebRequestContext; + +// This class handles communication between the IO thread where loading happens +// and the webkit main thread. +// TODO: +// - Implement didFail +// - Implement sync requests +// - Implement downloadFile +// - Implement pauseLoad +class WebUrlLoaderClient : public base::RefCountedThreadSafe<WebUrlLoaderClient> { +public: + WebUrlLoaderClient(WebFrame*, WebCore::ResourceHandle*, const WebCore::ResourceRequest&); + + // Called from WebCore, will be forwarded to the IO thread + bool start(bool isMainResource, bool isMainFrame, bool sync, WebRequestContext*); + void cancel(); + void downloadFile(); + void pauseLoad(bool pause); + void setAuth(const std::string& username, const std::string& password); + void cancelAuth(); + void proceedSslCertError(); + void cancelSslCertError(int cert_error); + + typedef void CallbackFunction(void*); + + // This is called from the IO thread, and dispatches the callback to the main thread. + // (For asynchronous calls, we just delegate to WebKit's callOnMainThread.) + void maybeCallOnMainThread(Task* task); + + // Called by WebRequest (using maybeCallOnMainThread), should be forwarded to WebCore. + void didReceiveResponse(PassOwnPtr<WebResponse>); + void didReceiveData(scoped_refptr<net::IOBuffer>, int size); + void didReceiveDataUrl(PassOwnPtr<std::string>); + void didReceiveAndroidFileData(PassOwnPtr<std::vector<char> >); + void didFinishLoading(); + void didFail(PassOwnPtr<WebResponse>); + void willSendRequest(PassOwnPtr<WebResponse>); + void authRequired(scoped_refptr<net::AuthChallengeInfo>, bool firstTime); + void reportSslCertError(int cert_error, net::X509Certificate* cert); + + // Handle to the chrome IO thread + static base::Thread* ioThread(); + +private: + friend class base::RefCountedThreadSafe<WebUrlLoaderClient>; + virtual ~WebUrlLoaderClient(); + + void finish(); + + WebFrame* m_webFrame; + RefPtr<WebCore::ResourceHandle> m_resourceHandle; + bool m_isMainResource; + bool m_isMainFrame; + bool m_isCertMimeType; + bool m_cancelling; + bool m_sync; + volatile bool m_finished; + + scoped_refptr<WebRequest> m_request; + OwnPtr<WebResponse> m_response; // NULL until didReceiveResponse is called. + + // Check if a request is active + bool isActive() const; + + // Mutex and condition variable used for synchronous requests. + // Note that these are static. This works because there's only one main thread. + static Lock* syncLock(); + static ConditionVariable* syncCondition(); + + // Queue of callbacks to be executed by the main thread. Must only be accessed inside mutex. + std::deque<Task*> m_queue; +}; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/WebViewClientError.cpp b/Source/WebKit/android/WebCoreSupport/WebViewClientError.cpp new file mode 100644 index 0000000..59542da --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebViewClientError.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebViewClientError.h" + +using namespace net; + +namespace android { + +WebViewClientError ToWebViewClientError(net::Error error) { + // Note: many net::Error constants don't have an obvious mapping. + // These will be handled by the default case, ERROR_UNKNOWN. + switch(error) { + case ERR_UNSUPPORTED_AUTH_SCHEME: + return ERROR_UNSUPPORTED_AUTH_SCHEME; + + case ERR_INVALID_AUTH_CREDENTIALS: + case ERR_MISSING_AUTH_CREDENTIALS: + case ERR_MISCONFIGURED_AUTH_ENVIRONMENT: + return ERROR_AUTHENTICATION; + + case ERR_TOO_MANY_REDIRECTS: + return ERROR_REDIRECT_LOOP; + + case ERR_UPLOAD_FILE_CHANGED: + return ERROR_FILE_NOT_FOUND; + + case ERR_INVALID_URL: + return ERROR_BAD_URL; + + case ERR_DISALLOWED_URL_SCHEME: + case ERR_UNKNOWN_URL_SCHEME: + return ERROR_UNSUPPORTED_SCHEME; + + case ERR_IO_PENDING: + case ERR_NETWORK_IO_SUSPENDED: + return ERROR_IO; + + case ERR_CONNECTION_TIMED_OUT: + case ERR_TIMED_OUT: + return ERROR_TIMEOUT; + + case ERR_FILE_TOO_BIG: + return ERROR_FILE; + + case ERR_HOST_RESOLVER_QUEUE_TOO_LARGE: + case ERR_INSUFFICIENT_RESOURCES: + case ERR_OUT_OF_MEMORY: + return ERROR_TOO_MANY_REQUESTS; + + case ERR_CONNECTION_CLOSED: + case ERR_CONNECTION_RESET: + case ERR_CONNECTION_REFUSED: + case ERR_CONNECTION_ABORTED: + case ERR_CONNECTION_FAILED: + case ERR_SOCKET_NOT_CONNECTED: + return ERROR_CONNECT; + + case ERR_ADDRESS_INVALID: + case ERR_ADDRESS_UNREACHABLE: + case ERR_NAME_NOT_RESOLVED: + case ERR_NAME_RESOLUTION_FAILED: + return ERROR_HOST_LOOKUP; + + case ERR_SSL_PROTOCOL_ERROR: + case ERR_SSL_CLIENT_AUTH_CERT_NEEDED: + case ERR_TUNNEL_CONNECTION_FAILED: + case ERR_NO_SSL_VERSIONS_ENABLED: + case ERR_SSL_VERSION_OR_CIPHER_MISMATCH: + case ERR_SSL_RENEGOTIATION_REQUESTED: + case ERR_CERT_ERROR_IN_SSL_RENEGOTIATION: + case ERR_BAD_SSL_CLIENT_AUTH_CERT: + case ERR_SSL_NO_RENEGOTIATION: + case ERR_SSL_DECOMPRESSION_FAILURE_ALERT: + case ERR_SSL_BAD_RECORD_MAC_ALERT: + case ERR_SSL_UNSAFE_NEGOTIATION: + case ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY: + case ERR_SSL_SNAP_START_NPN_MISPREDICTION: + case ERR_SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED: + case ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY: + return ERROR_FAILED_SSL_HANDSHAKE; + + case ERR_PROXY_AUTH_UNSUPPORTED: + case ERR_PROXY_AUTH_REQUESTED: + case ERR_PROXY_CONNECTION_FAILED: + case ERR_UNEXPECTED_PROXY_AUTH: + return ERROR_PROXY_AUTHENTICATION; + + /* The certificate errors are handled by their own dialog + * and don't need to be reported to the framework again. + */ + case ERR_CERT_COMMON_NAME_INVALID: + case ERR_CERT_DATE_INVALID: + case ERR_CERT_AUTHORITY_INVALID: + case ERR_CERT_CONTAINS_ERRORS: + case ERR_CERT_NO_REVOCATION_MECHANISM: + case ERR_CERT_UNABLE_TO_CHECK_REVOCATION: + case ERR_CERT_REVOKED: + case ERR_CERT_INVALID: + case ERR_CERT_WEAK_SIGNATURE_ALGORITHM: + case ERR_CERT_NOT_IN_DNS: + return ERROR_OK; + + default: + return ERROR_UNKNOWN; + } +} + +} diff --git a/Source/WebKit/android/WebCoreSupport/WebViewClientError.h b/Source/WebKit/android/WebCoreSupport/WebViewClientError.h new file mode 100644 index 0000000..d274dc7 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/WebViewClientError.h @@ -0,0 +1,74 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebViewClientError_h +#define WebViewClientError_h + +#include "ChromiumIncludes.h" + +namespace android { + +// This enum must be kept in sync with WebViewClient.java +enum WebViewClientError { + /** Success */ + ERROR_OK = 0, + /** Generic error */ + ERROR_UNKNOWN = -1, + /** Server or proxy hostname lookup failed */ + ERROR_HOST_LOOKUP = -2, + /** Unsupported authentication scheme (not basic or digest) */ + ERROR_UNSUPPORTED_AUTH_SCHEME = -3, + /** User authentication failed on server */ + ERROR_AUTHENTICATION = -4, + /** User authentication failed on proxy */ + ERROR_PROXY_AUTHENTICATION = -5, + /** Failed to connect to the server */ + ERROR_CONNECT = -6, + /** Failed to read or write to the server */ + ERROR_IO = -7, + /** Connection timed out */ + ERROR_TIMEOUT = -8, + /** Too many redirects */ + ERROR_REDIRECT_LOOP = -9, + /** Unsupported URI scheme */ + ERROR_UNSUPPORTED_SCHEME = -10, + /** Failed to perform SSL handshake */ + ERROR_FAILED_SSL_HANDSHAKE = -11, + /** Malformed URL */ + ERROR_BAD_URL = -12, + /** Generic file error */ + ERROR_FILE = -13, + /** File not found */ + ERROR_FILE_NOT_FOUND = -14, + /** Too many requests during this load */ + ERROR_TOO_MANY_REQUESTS = -15, +}; + +// Get the closest WebViewClient match to the given Chrome error code. +WebViewClientError ToWebViewClientError(net::Error); + +} // namespace android + +#endif // WebViewClientError_h diff --git a/Source/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp b/Source/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp new file mode 100644 index 0000000..5bc4c92 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "AutoFillHostAndroid.h" + +#include "autofill/WebAutoFill.h" + +namespace android { + +AutoFillHostAndroid::AutoFillHostAndroid(WebAutoFill* autoFill) + : mAutoFill(autoFill) +{ +} + +void AutoFillHostAndroid::AutoFillSuggestionsReturned(const std::vector<string16>& names, const std::vector<string16>& labels, const std::vector<string16>& icons, const std::vector<int>& uniqueIds) +{ + // TODO: what do we do with icons? + if (mAutoFill) + mAutoFill->querySuccessful(names[0], labels[0], uniqueIds[0]); +} + +void AutoFillHostAndroid::AutoFillFormDataFilled(int queryId, const webkit_glue::FormData& form) +{ + if (mAutoFill) + mAutoFill->fillFormInPage(queryId, form); +} + +} diff --git a/Source/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.h b/Source/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.h new file mode 100644 index 0000000..9677b46 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.h @@ -0,0 +1,54 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef AutoFillHostAndroid_h +#define AutoFillHostAndroid_h + +#include "ChromiumIncludes.h" + +#include <vector> + +namespace webkit_glue { +class FormData; +} + +namespace android { +class WebAutoFill; + +// This class receives the callbacks from the AutoFillManager in the Chromium code. +class AutoFillHostAndroid : public AutoFillHost { +public: + AutoFillHostAndroid(WebAutoFill* autoFill); + virtual ~AutoFillHostAndroid() { } + + virtual void AutoFillSuggestionsReturned(const std::vector<string16>& names, const std::vector<string16>& labels, const std::vector<string16>& icons, const std::vector<int>& uniqueIds); + virtual void AutoFillFormDataFilled(int queryId, const webkit_glue::FormData&); + +private: + WebAutoFill* mAutoFill; +}; +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.cpp b/Source/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.cpp new file mode 100644 index 0000000..6af0875 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, 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. + */ + +#include "config.h" +#include "FormFieldAndroid.h" + +#include "ChromiumIncludes.h" +#include "Element.h" +#include "HTMLFormControlElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "StringUtils.h" +#include <wtf/Vector.h> + +using WebCore::Element; +using WebCore::HTMLFormControlElement; +using WebCore::HTMLInputElement; +using WebCore::HTMLOptionElement; +using WebCore::HTMLSelectElement; + +using namespace WebCore::HTMLNames; + +// TODO: This file is taken from chromium/webkit/glue/form_field.cc and +// customised to use WebCore types rather than WebKit API types. It would +// be nice and would ease future merge pain if the two could be combined. + +namespace webkit_glue { + +FormField::FormField() + : max_length_(0), + is_autofilled_(false) { +} + +// TODO: This constructor should probably be deprecated and the +// functionality moved to FormManager. +FormField::FormField(const HTMLFormControlElement& element) + : max_length_(0), + is_autofilled_(false) { + name_ = nameForAutoFill(element); + + // TODO: Extract the field label. For now we just use the field + // name. + label_ = name_; + + form_control_type_ = formControlType(element); + if (form_control_type_ == kText) { + const HTMLInputElement& input_element = static_cast<const HTMLInputElement&>(element); + value_ = WTFStringToString16(input_element.value()); + max_length_ = input_element.size(); + is_autofilled_ = input_element.isAutofilled(); + } else if (form_control_type_ == kSelectOne) { + const HTMLSelectElement& const_select_element = static_cast<const HTMLSelectElement&>(element); + HTMLSelectElement& select_element = const_cast<HTMLSelectElement&>(const_select_element); + value_ = WTFStringToString16(select_element.value()); + + // For select-one elements copy option strings. + WTF::Vector<Element*> list_items = select_element.listItems(); + option_strings_.reserve(list_items.size()); + for (size_t i = 0; i < list_items.size(); ++i) { + if (list_items[i]->hasTagName(optionTag)) + option_strings_.push_back(WTFStringToString16(static_cast<HTMLOptionElement*>(list_items[i])->value())); + } + } + + TrimWhitespace(value_, TRIM_LEADING, &value_); +} + +FormField::FormField(const string16& label, const string16& name, const string16& value, const string16& form_control_type, int max_length, bool is_autofilled) + : label_(label), + name_(name), + value_(value), + form_control_type_(form_control_type), + max_length_(max_length), + is_autofilled_(is_autofilled) { +} + +FormField::~FormField() { +} + +bool FormField::operator==(const FormField& field) const { + // A FormField stores a value, but the value is not part of the identity of + // the field, so we don't want to compare the values. + return (label_ == field.label_ && + name_ == field.name_ && + form_control_type_ == field.form_control_type_ && + max_length_ == field.max_length_); +} + +bool FormField::operator!=(const FormField& field) const { + return !operator==(field); +} + +bool FormField::StrictlyEqualsHack(const FormField& field) const { + return (label_ == field.label_ && + name_ == field.name_ && + value_ == field.value_ && + form_control_type_ == field.form_control_type_ && + max_length_ == field.max_length_); +} + +std::ostream& operator<<(std::ostream& os, const FormField& field) { + return os + << UTF16ToUTF8(field.label()) + << " " + << UTF16ToUTF8(field.name()) + << " " + << UTF16ToUTF8(field.value()) + << " " + << UTF16ToUTF8(field.form_control_type()) + << " " + << field.max_length(); +} + +} // namespace webkit_glue diff --git a/Source/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.h b/Source/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.h new file mode 100644 index 0000000..c5e3ecc --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, 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. + */ + +#ifndef FormFieldAndroid_h +#define FormFieldAndroid_h + +#include <base/string16.h> +#include <vector> + +// TODO: This file is taken from chromium/webkit/glue/form_field.h and +// customised to use WebCore types rather than WebKit API types. It would +// be nice and would ease future merge pain if the two could be combined. + +namespace WebCore { +class HTMLFormControlElement; +} + +namespace webkit_glue { + +// Stores information about a field in a form. +class FormField { +public: + FormField(); + explicit FormField(const WebCore::HTMLFormControlElement& element); + FormField(const string16& label, const string16& name, const string16& value, const string16& form_control_type, int max_length, bool is_autofilled); + virtual ~FormField(); + + const string16& label() const { return label_; } + const string16& name() const { return name_; } + const string16& value() const { return value_; } + const string16& form_control_type() const { return form_control_type_; } + int max_length() const { return max_length_; } + bool is_autofilled() const { return is_autofilled_; } + + // Returns option string for elements for which they make sense (select-one, + // for example) for the rest of elements return an empty array. + const std::vector<string16>& option_strings() const { return option_strings_; } + + void set_label(const string16& label) { label_ = label; } + void set_name(const string16& name) { name_ = name; } + void set_value(const string16& value) { value_ = value; } + void set_form_control_type(const string16& form_control_type) { form_control_type_ = form_control_type; } + void set_max_length(int max_length) { max_length_ = max_length; } + void set_autofilled(bool is_autofilled) { is_autofilled_ = is_autofilled; } + void set_option_strings(const std::vector<string16>& strings) { option_strings_ = strings; } + + // Equality tests for identity which does not include |value_| or |size_|. + // Use |StrictlyEqualsHack| method to test all members. + // TODO: These operators need to be revised when we implement field + // ids. + bool operator==(const FormField& field) const; + bool operator!=(const FormField& field) const; + + // Test equality of all data members. + // TODO: This will be removed when we implement field ids. + bool StrictlyEqualsHack(const FormField& field) const; + +private: + string16 label_; + string16 name_; + string16 value_; + string16 form_control_type_; + int max_length_; + bool is_autofilled_; + std::vector<string16> option_strings_; +}; + +// So we can compare FormFields with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const FormField& field); + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FORM_FIELD_H_ diff --git a/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp b/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp new file mode 100644 index 0000000..9652794 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp @@ -0,0 +1,871 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, 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. + */ + +#include "config.h" +#include "FormManagerAndroid.h" + +#include "DocumentLoader.h" +#include "Element.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "HTMLCollection.h" +#include "HTMLFormControlElement.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLLabelElement.h" +#include "HTMLNames.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "Node.h" +#include "NodeList.h" +#include "HTMLCollection.h" +#include "FormAssociatedElement.h" +#include "FormFieldAndroid.h" +#include "QualifiedName.h" +#include "StringUtils.h" + +// TODO: This file is taken from chromium/chrome/renderer/form_manager.cc and +// customised to use WebCore types rather than WebKit API types. It would be +// nice and would ease future merge pain if the two could be combined. + +using webkit_glue::FormData; +using webkit_glue::FormField; +using WebCore::Element; +using WebCore::FormAssociatedElement; +using WebCore::HTMLCollection; +using WebCore::HTMLElement; +using WebCore::HTMLFormControlElement; +using WebCore::HTMLFormElement; +using WebCore::HTMLInputElement; +using WebCore::HTMLLabelElement; +using WebCore::HTMLOptionElement; +using WebCore::HTMLSelectElement; +using WebCore::Node; +using WebCore::NodeList; + +using namespace WebCore::HTMLNames; + +namespace { + +// The number of fields required by AutoFill. Ideally we could send the forms +// to AutoFill no matter how many fields are in the forms; however, finding the +// label for each field is a costly operation and we can't spare the cycles if +// it's not necessary. +// Note the on ANDROID we reduce this from Chromium's 3 as it allows us to +// autofill simple name/email forms for example. This improves the mobile +// device experience where form filling can be time consuming and frustrating. +const size_t kRequiredAutoFillFields = 2; + +// The maximum length allowed for form data. +const size_t kMaxDataLength = 1024; + +// This is a helper function for the FindChildText() function. +// Returns the aggregated values of the descendants or siblings of |node| that +// are non-empty text nodes. This is a faster alternative to |innerText()| for +// performance critical operations. It does a full depth-first search so +// can be used when the structure is not directly known. The text is +// accumulated after the whitespace has been stropped. Search depth is limited +// with the |depth| parameter. +string16 FindChildTextInner(Node* node, int depth) { + string16 element_text; + if (!node || depth <= 0) + return element_text; + + string16 node_text = WTFStringToString16(node->nodeValue()); + TrimWhitespace(node_text, TRIM_ALL, &node_text); + if (!node_text.empty()) + element_text = node_text; + + string16 child_text = FindChildTextInner(node->firstChild(), depth-1); + if (!child_text.empty()) + element_text = element_text + child_text; + + string16 sibling_text = FindChildTextInner(node->nextSibling(), depth-1); + if (!sibling_text.empty()) + element_text = element_text + sibling_text; + + return element_text; +} + +// Returns the node value of the first decendant of |element| that is a +// non-empty text node. "Non-empty" in this case means non-empty after the +// whitespace has been stripped. Search is limited to withing 10 siblings and/or +// descendants. +string16 FindChildText(Element* element) { + Node* child = element->firstChild(); + + const int kChildSearchDepth = 10; + return FindChildTextInner(child, kChildSearchDepth); +} + +string16 InferLabelFromPrevious(const HTMLFormControlElement& element) { + string16 inferred_label; + Node* previous = element.previousSibling(); + if (!previous) + return string16(); + + if (previous->isTextNode()) { + inferred_label = WTFStringToString16(previous->nodeValue()); + TrimWhitespace(inferred_label, TRIM_ALL, &inferred_label); + } + + // If we didn't find text, check for previous paragraph. + // Eg. <p>Some Text</p><input ...> + // Note the lack of whitespace between <p> and <input> elements. + if (inferred_label.empty() && previous->isElementNode()) { + Element* element = static_cast<Element*>(previous); + if (element->hasTagName(pTag)) + inferred_label = FindChildText(element); + } + + // If we didn't find paragraph, check for previous paragraph to this. + // Eg. <p>Some Text</p> <input ...> + // Note the whitespace between <p> and <input> elements. + if (inferred_label.empty()) { + Node* sibling = previous->previousSibling(); + if (sibling && sibling->isElementNode()) { + Element* element = static_cast<Element*>(sibling); + if (element->hasTagName(pTag)) + inferred_label = FindChildText(element); + } + } + + // Look for text node prior to <img> tag. + // Eg. Some Text<img/><input ...> + if (inferred_label.empty()) { + while (inferred_label.empty() && previous) { + if (previous->isTextNode()) { + inferred_label = WTFStringToString16(previous->nodeValue()); + TrimWhitespace(inferred_label, TRIM_ALL, &inferred_label); + } else if (previous->isElementNode()) { + Element* element = static_cast<Element*>(previous); + if (!element->hasTagName(imgTag)) + break; + } else + break; + previous = previous->previousSibling(); + } + } + + // Look for label node prior to <input> tag. + // Eg. <label>Some Text</label><input ...> + if (inferred_label.empty()) { + while (inferred_label.empty() && previous) { + if (previous->isTextNode()) { + inferred_label = WTFStringToString16(previous->nodeValue()); + TrimWhitespace(inferred_label, TRIM_ALL, &inferred_label); + } else if (previous->isElementNode()) { + Element* element = static_cast<Element*>(previous); + if (element->hasTagName(labelTag)) { + inferred_label = FindChildText(element); + } else { + break; + } + } else { + break; + } + + previous = previous->previousSibling(); + } + } + + return inferred_label; +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// surrounding table structure. +// Eg. <tr><td>Some Text</td><td><input ...></td></tr> +// Eg. <tr><td><b>Some Text</b></td><td><b><input ...></b></td></tr> +string16 InferLabelFromTable(const HTMLFormControlElement& element) { + string16 inferred_label; + Node* parent = element.parentNode(); + while (parent && parent->isElementNode() && !static_cast<Element*>(parent)->hasTagName(tdTag)) + parent = parent->parentNode(); + + // Check all previous siblings, skipping non-element nodes, until we find a + // non-empty text block. + Node* previous = parent; + while(previous) { + if (previous->isElementNode()) { + Element* e = static_cast<Element*>(previous); + if (e->hasTagName(tdTag)) { + inferred_label = FindChildText(e); + if (!inferred_label.empty()) + break; + } + } + previous = previous->previousSibling(); + } + return inferred_label; +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// a surrounding div table. +// Eg. <div>Some Text<span><input ...></span></div> +string16 InferLabelFromDivTable(const HTMLFormControlElement& element) { + Node* parent = element.parentNode(); + while (parent && parent->isElementNode() && !static_cast<Element*>(parent)->hasTagName(divTag)) + parent = parent->parentNode(); + + if (!parent || !parent->isElementNode()) + return string16(); + + Element* e = static_cast<Element*>(parent); + if (!e || !e->hasTagName(divTag)) + return string16(); + + return FindChildText(e); +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// a surrounding definition list. +// Eg. <dl><dt>Some Text</dt><dd><input ...></dd></dl> +// Eg. <dl><dt><b>Some Text</b></dt><dd><b><input ...></b></dd></dl> +string16 InferLabelFromDefinitionList(const HTMLFormControlElement& element) { + string16 inferred_label; + Node* parent = element.parentNode(); + while (parent && parent->isElementNode() && !static_cast<Element*>(parent)->hasTagName(ddTag)) + parent = parent->parentNode(); + + if (parent && parent->isElementNode()) { + Element* element = static_cast<Element*>(parent); + if (element->hasTagName(ddTag)) { + Node* previous = parent->previousSibling(); + + // Skip by any intervening text nodes. + while (previous && previous->isTextNode()) + previous = previous->previousSibling(); + + if (previous && previous->isElementNode()) { + element = static_cast<Element*>(previous); + if (element->hasTagName(dtTag)) + inferred_label = FindChildText(element); + } + } + } + return inferred_label; +} + +void GetOptionStringsFromElement(HTMLFormControlElement* element, std::vector<string16>* option_strings) { + DCHECK(element); + DCHECK(option_strings); + option_strings->clear(); + if (formControlType(*element) == kSelectOne) { + HTMLSelectElement* select_element = static_cast<HTMLSelectElement*>(element); + + // For select-one elements copy option strings. + WTF::Vector<Element*> list_items = select_element->listItems(); + option_strings->reserve(list_items.size()); + for (size_t i = 0; i < list_items.size(); ++i) { + if (list_items[i]->hasTagName(optionTag)) + option_strings->push_back(WTFStringToString16(static_cast<HTMLOptionElement*>(list_items[i])->value())); + } + } +} + +} // namespace + +namespace android { + +struct FormManager::FormElement { + RefPtr<HTMLFormElement> form_element; + std::vector<RefPtr<HTMLFormControlElement> > control_elements; + std::vector<string16> control_values; +}; + +FormManager::FormManager() { +} + +FormManager::~FormManager() { + Reset(); +} + +// static +void FormManager::HTMLFormControlElementToFormField(HTMLFormControlElement* element, ExtractMask extract_mask, FormField* field) { + DCHECK(field); + + // The label is not officially part of a HTMLFormControlElement; however, the + // labels for all form control elements are scraped from the DOM and set in + // WebFormElementToFormData. + field->set_name(nameForAutoFill(*element)); + field->set_form_control_type(formControlType(*element)); + + if (extract_mask & EXTRACT_OPTIONS) { + std::vector<string16> option_strings; + GetOptionStringsFromElement(element, &option_strings); + field->set_option_strings(option_strings); + } + + if (formControlType(*element) == kText) { + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(element); + field->set_max_length(input_element->maxLength()); + field->set_autofilled(input_element->isAutofilled()); + } + + if (!(extract_mask & EXTRACT_VALUE)) + return; + + // TODO: In WebKit, move value() and setValue() to + // WebFormControlElement. + string16 value; + if (formControlType(*element) == kText || + formControlType(*element) == kHidden) { + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(element); + value = WTFStringToString16(input_element->value()); + } else if (formControlType(*element) == kSelectOne) { + HTMLSelectElement* select_element = static_cast<HTMLSelectElement*>(element); + value = WTFStringToString16(select_element->value()); + + // Convert the |select_element| value to text if requested. + if (extract_mask & EXTRACT_OPTION_TEXT) { + Vector<Element*> list_items = select_element->listItems(); + for (size_t i = 0; i < list_items.size(); ++i) { + if (list_items[i]->hasTagName(optionTag) && + WTFStringToString16(static_cast<HTMLOptionElement*>(list_items[i])->value()) == value) { + value = WTFStringToString16(static_cast<HTMLOptionElement*>(list_items[i])->text()); + break; + } + } + } + } + + // TODO: This is a temporary stop-gap measure designed to prevent + // a malicious site from DOS'ing the browser with extremely large profile + // data. The correct solution is to parse this data asynchronously. + // See http://crbug.com/49332. + if (value.size() > kMaxDataLength) + value = value.substr(0, kMaxDataLength); + + field->set_value(value); +} + +// static +string16 FormManager::LabelForElement(const HTMLFormControlElement& element) { + // Don't scrape labels for hidden elements. + if (formControlType(element) == kHidden) + return string16(); + + RefPtr<NodeList> labels = element.document()->getElementsByTagName("label"); + for (unsigned i = 0; i < labels->length(); ++i) { + Node* e = labels->item(i); + if (e->hasTagName(labelTag)) { + HTMLLabelElement* label = static_cast<HTMLLabelElement*>(e); + if (label->control() == &element) + return FindChildText(label); + } + } + + // Infer the label from context if not found in label element. + return FormManager::InferLabelForElement(element); +} + +// static +bool FormManager::HTMLFormElementToFormData(HTMLFormElement* element, RequirementsMask requirements, ExtractMask extract_mask, FormData* form) { + DCHECK(form); + + Frame* frame = element->document()->frame(); + if (!frame) + return false; + + if (requirements & REQUIRE_AUTOCOMPLETE && !element->autoComplete()) + return false; + + form->name = WTFStringToString16(element->name()); + form->method = WTFStringToString16(element->method()); + form->origin = GURL(WTFStringToString16(frame->loader()->documentLoader()->url().string())); + form->action = GURL(WTFStringToString16(frame->document()->completeURL(element->action()))); + form->user_submitted = element->wasUserSubmitted(); + + // If the completed URL is not valid, just use the action we get from + // WebKit. + if (!form->action.is_valid()) + form->action = GURL(WTFStringToString16(element->action())); + + // A map from a FormField's name to the FormField itself. + std::map<string16, FormField*> name_map; + + // The extracted FormFields. We use pointers so we can store them in + // |name_map|. + ScopedVector<FormField> form_fields; + + WTF::Vector<WebCore::FormAssociatedElement*> control_elements = element->associatedElements(); + + // A vector of bools that indicate whether each field in the form meets the + // requirements and thus will be in the resulting |form|. + std::vector<bool> fields_extracted(control_elements.size(), false); + + for (size_t i = 0; i < control_elements.size(); ++i) { + if (!control_elements[i]->isFormControlElement()) + continue; + + HTMLFormControlElement* control_element = static_cast<HTMLFormControlElement*>(control_elements[i]); + if (!(control_element->hasTagName(inputTag) || control_element->hasTagName(selectTag))) + continue; + + if (requirements & REQUIRE_AUTOCOMPLETE && + formControlType(*control_element) == kText) { + const WebCore::HTMLInputElement* input_element = static_cast<const WebCore::HTMLInputElement*>(control_element); + if (!input_element->autoComplete()) + continue; + } + + if (requirements & REQUIRE_ENABLED && !control_element->isEnabledFormControl()) + continue; + + // Create a new FormField, fill it out and map it to the field's name. + FormField* field = new FormField; + HTMLFormControlElementToFormField(control_element, extract_mask, field); + form_fields.push_back(field); + // TODO: A label element is mapped to a form control element's id. + // field->name() will contain the id only if the name does not exist. Add + // an id() method to HTMLFormControlElement and use that here. + name_map[field->name()] = field; + fields_extracted[i] = true; + } + + // Don't extract field labels if we have no fields. + if (form_fields.empty()) + return false; + + // Loop through the label elements inside the form element. For each label + // element, get the corresponding form control element, use the form control + // element's name as a key into the <name, FormField> map to find the + // previously created FormField and set the FormField's label to the + // label.firstChild().nodeValue() of the label element. + RefPtr<WebCore::NodeList> labels = element->getElementsByTagName("label"); + for (unsigned i = 0; i < labels->length(); ++i) { + HTMLLabelElement* label = static_cast<WebCore::HTMLLabelElement*>(labels->item(i)); + HTMLFormControlElement* field_element = label->control(); + if (!field_element || field_element->type() == "hidden") + continue; + + std::map<string16, FormField*>::iterator iter = + name_map.find(nameForAutoFill(*field_element)); + if (iter != name_map.end()) + iter->second->set_label(FindChildText(label)); + } + + // Loop through the form control elements, extracting the label text from the + // DOM. We use the |fields_extracted| vector to make sure we assign the + // extracted label to the correct field, as it's possible |form_fields| will + // not contain all of the elements in |control_elements|. + for (size_t i = 0, field_idx = 0; i < control_elements.size() && field_idx < form_fields.size(); ++i) { + // This field didn't meet the requirements, so don't try to find a label for + // it. + if (!fields_extracted[i]) + continue; + + if (!control_elements[i]->isFormControlElement()) + continue; + + const HTMLFormControlElement* control_element = static_cast<HTMLFormControlElement*>(control_elements[i]); + if (form_fields[field_idx]->label().empty()) + form_fields[field_idx]->set_label(FormManager::InferLabelForElement(*control_element)); + + ++field_idx; + + } + // Copy the created FormFields into the resulting FormData object. + for (ScopedVector<FormField>::const_iterator iter = form_fields.begin(); iter != form_fields.end(); ++iter) + form->fields.push_back(**iter); + + return true; +} + +void FormManager::ExtractForms(Frame* frame) { + + ResetFrame(frame); + + WTF::PassRefPtr<HTMLCollection> web_forms = frame->document()->forms(); + + for (size_t i = 0; i < web_forms->length(); ++i) { + FormElement* form_element = new FormElement; + HTMLFormElement* html_form_element = static_cast<HTMLFormElement*>(web_forms->item(i)); + form_element->form_element = html_form_element; + + WTF::Vector<FormAssociatedElement*> control_elements = html_form_element->associatedElements(); + for (size_t j = 0; j < control_elements.size(); ++j) { + if (!control_elements[j]->isFormControlElement()) + continue; + + HTMLFormControlElement* element = static_cast<HTMLFormControlElement*>(control_elements[j]); + form_element->control_elements.push_back(element); + + // Save original values of "select-one" inputs so we can restore them + // when |ClearFormWithNode()| is invoked. + if (formControlType(*element) == kSelectOne) { + HTMLSelectElement* select_element = static_cast<HTMLSelectElement*>(element); + string16 value = WTFStringToString16(select_element->value()); + form_element->control_values.push_back(value); + } else + form_element->control_values.push_back(string16()); + } + + form_elements_.push_back(form_element); + } +} + +void FormManager::GetFormsInFrame(const Frame* frame, RequirementsMask requirements, std::vector<FormData>* forms) { + DCHECK(frame); + DCHECK(forms); + + for (FormElementList::const_iterator form_iter = form_elements_.begin(); form_iter != form_elements_.end(); ++form_iter) { + FormElement* form_element = *form_iter; + + if (form_element->form_element->document()->frame() != frame) + continue; + + // We need at least |kRequiredAutoFillFields| fields before appending this + // form to |forms|. + if (form_element->control_elements.size() < kRequiredAutoFillFields) + continue; + + if (requirements & REQUIRE_AUTOCOMPLETE && !form_element->form_element->autoComplete()) + continue; + + FormData form; + HTMLFormElementToFormData(form_element->form_element.get(), requirements, EXTRACT_VALUE, &form); + if (form.fields.size() >= kRequiredAutoFillFields) + forms->push_back(form); + } +} + +bool FormManager::FindFormWithFormControlElement(HTMLFormControlElement* element, RequirementsMask requirements, FormData* form) { + DCHECK(form); + + const Frame* frame = element->document()->frame(); + if (!frame) + return false; + + for (FormElementList::const_iterator iter = form_elements_.begin(); iter != form_elements_.end(); ++iter) { + const FormElement* form_element = *iter; + + if (form_element->form_element->document()->frame() != frame) + continue; + + for (std::vector<RefPtr<HTMLFormControlElement> >::const_iterator iter = form_element->control_elements.begin(); iter != form_element->control_elements.end(); ++iter) { + HTMLFormControlElement* candidate = iter->get(); + if (nameForAutoFill(*candidate) == nameForAutoFill(*element)) { + ExtractMask extract_mask = static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS); + return HTMLFormElementToFormData(form_element->form_element.get(), requirements, extract_mask, form); + } + } + } + return false; +} + +bool FormManager::FillForm(const FormData& form, Node* node) { + FormElement* form_element = NULL; + if (!FindCachedFormElement(form, &form_element)) + return false; + + RequirementsMask requirements = static_cast<RequirementsMask>(REQUIRE_AUTOCOMPLETE | REQUIRE_ENABLED | REQUIRE_EMPTY); + ForEachMatchingFormField(form_element, node, requirements, form, NewCallback(this, &FormManager::FillFormField)); + + return true; +} + +bool FormManager::PreviewForm(const FormData& form, Node* node) { + FormElement* form_element = NULL; + if (!FindCachedFormElement(form, &form_element)) + return false; + + RequirementsMask requirements = static_cast<RequirementsMask>(REQUIRE_AUTOCOMPLETE | REQUIRE_ENABLED | REQUIRE_EMPTY); + ForEachMatchingFormField(form_element, node, requirements, form, NewCallback(this, &FormManager::PreviewFormField)); + + return true; +} + +bool FormManager::ClearFormWithNode(Node* node) { + FormElement* form_element = NULL; + if (!FindCachedFormElementWithNode(node, &form_element)) + return false; + + for (size_t i = 0; i < form_element->control_elements.size(); ++i) { + HTMLFormControlElement* element = form_element->control_elements[i].get(); + if (formControlType(*element) == kText) { + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(element); + + // We don't modify the value of disabled fields. + if (!input_element->isEnabledFormControl()) + continue; + + input_element->setValue(""); + input_element->setAutofilled(false); + // Clearing the value in the focused node (above) can cause selection + // to be lost. We force selection range to restore the text cursor. + if (node == input_element) { + int length = input_element->value().length(); + input_element->setSelectionRange(length, length); + } + } else if (formControlType(*element) == kSelectOne) { + HTMLSelectElement* select_element = static_cast<HTMLSelectElement*>(element); + select_element->setValue(form_element->control_values[i].c_str()); + } + } + + return true; +} + +bool FormManager::ClearPreviewedFormWithNode(Node* node, bool was_autofilled) { + FormElement* form_element = NULL; + if (!FindCachedFormElementWithNode(node, &form_element)) + return false; + + for (size_t i = 0; i < form_element->control_elements.size(); ++i) { + HTMLFormControlElement* element = form_element->control_elements[i].get(); + + // Only input elements can be previewed. + if (formControlType(*element) != kText) + continue; + + // If the input element has not been auto-filled, FormManager has not + // previewed this field, so we have nothing to reset. + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(element); + if (!input_element->isAutofilled()) + continue; + + // There might be unrelated elements in this form which have already been + // auto-filled. For example, the user might have already filled the address + // part of a form and now be dealing with the credit card section. We only + // want to reset the auto-filled status for fields that were previewed. + if (input_element->suggestedValue().isEmpty()) + continue; + + // Clear the suggested value. For the initiating node, also restore the + // original value. + input_element->setSuggestedValue(""); + bool is_initiating_node = (node == input_element); + if (is_initiating_node) { + // Call |setValue()| to force the renderer to update the field's displayed + // value. + input_element->setValue(input_element->value()); + input_element->setAutofilled(was_autofilled); + } else { + input_element->setAutofilled(false); + } + + // Clearing the suggested value in the focused node (above) can cause + // selection to be lost. We force selection range to restore the text + // cursor. + if (is_initiating_node) { + int length = input_element->value().length(); + input_element->setSelectionRange(length, length); + } + } + + return true; +} + +void FormManager::Reset() { + STLDeleteElements(&form_elements_); +} + +void FormManager::ResetFrame(const Frame* frame) { + FormElementList::iterator iter = form_elements_.begin(); + while (iter != form_elements_.end()) { + if ((*iter)->form_element->document()->frame() == frame) { + delete *iter; + iter = form_elements_.erase(iter); + } else + ++iter; + } +} + +bool FormManager::FormWithNodeIsAutoFilled(Node* node) { + FormElement* form_element = NULL; + if (!FindCachedFormElementWithNode(node, &form_element)) + return false; + + for (size_t i = 0; i < form_element->control_elements.size(); ++i) { + HTMLFormControlElement* element = form_element->control_elements[i].get(); + if (formControlType(*element) != kText) + continue; + + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(element); + if (input_element->isAutofilled()) + return true; + } + + return false; +} + +// static +string16 FormManager::InferLabelForElement(const HTMLFormControlElement& element) { + // Don't scrape labels for hidden elements. + if (formControlType(element) == kHidden) + return string16(); + + string16 inferred_label = InferLabelFromPrevious(element); + + // If we didn't find a label, check for table cell case. + if (inferred_label.empty()) + inferred_label = InferLabelFromTable(element); + + // If we didn't find a label, check for div table case. + if (inferred_label.empty()) + inferred_label = InferLabelFromDivTable(element); + + // If we didn't find a label, check for definition list case. + if (inferred_label.empty()) + inferred_label = InferLabelFromDefinitionList(element); + + return inferred_label; +} + +bool FormManager::FindCachedFormElementWithNode(Node* node, + FormElement** form_element) { + for (FormElementList::const_iterator form_iter = form_elements_.begin(); form_iter != form_elements_.end(); ++form_iter) { + for (std::vector<RefPtr<HTMLFormControlElement> >::const_iterator iter = (*form_iter)->control_elements.begin(); iter != (*form_iter)->control_elements.end(); ++iter) { + if (iter->get() == node) { + *form_element = *form_iter; + return true; + } + } + } + + return false; +} + +bool FormManager::FindCachedFormElement(const FormData& form, FormElement** form_element) { + for (FormElementList::iterator form_iter = form_elements_.begin(); form_iter != form_elements_.end(); ++form_iter) { + // TODO: matching on form name here which is not guaranteed to + // be unique for the page, nor is it guaranteed to be non-empty. Need to + // find a way to uniquely identify the form cross-process. For now we'll + // check form name and form action for identity. + // http://crbug.com/37990 test file sample8.html. + // Also note that WebString() == WebString(string16()) does not seem to + // evaluate to |true| for some reason TBD, so forcing to string16. + string16 element_name(WTFStringToString16((*form_iter)->form_element->name())); + GURL action(WTFStringToString16((*form_iter)->form_element->document()->completeURL((*form_iter)->form_element->action()).string())); + if (element_name == form.name && action == form.action) { + *form_element = *form_iter; + return true; + } + } + + return false; +} + + +void FormManager::ForEachMatchingFormField(FormElement* form, Node* node, RequirementsMask requirements, const FormData& data, Callback* callback) { + // It's possible that the site has injected fields into the form after the + // page has loaded, so we can't assert that the size of the cached control + // elements is equal to the size of the fields in |form|. Fortunately, the + // one case in the wild where this happens, paypal.com signup form, the fields + // are appended to the end of the form and are not visible. + for (size_t i = 0, j = 0; i < form->control_elements.size() && j < data.fields.size(); ++i) { + HTMLFormControlElement* element = form->control_elements[i].get(); + string16 element_name = nameForAutoFill(*element); + + if (element_name.empty()) + continue; + + // Search forward in the |form| for a corresponding field. + size_t k = j; + while (k < data.fields.size() && element_name != data.fields[k].name()) + k++; + + if (k >= data.fields.size()) + continue; + + DCHECK_EQ(data.fields[k].name(), element_name); + + bool is_initiating_node = false; + + // More than likely |requirements| will contain REQUIRE_AUTOCOMPLETE and/or + // REQUIRE_EMPTY, which both require text form control elements, so special- + // case this type of element. + if (formControlType(*element) == kText) { + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(element); + + // TODO: WebKit currently doesn't handle the autocomplete + // attribute for select control elements, but it probably should. + if (requirements & REQUIRE_AUTOCOMPLETE && !input_element->autoComplete()) + continue; + + is_initiating_node = (input_element == node); + // Don't require the node that initiated the auto-fill process to be + // empty. The user is typing in this field and we should complete the + // value when the user selects a value to fill out. + if (requirements & REQUIRE_EMPTY && !is_initiating_node && !input_element->value().isEmpty()) + continue; + } + + if (requirements & REQUIRE_ENABLED && !element->isEnabledFormControl()) + continue; + + callback->Run(element, &data.fields[k], is_initiating_node); + + // We found a matching form field so move on to the next. + ++j; + } + + delete callback; +} + +void FormManager::FillFormField(HTMLFormControlElement* field, const FormField* data, bool is_initiating_node) { + // Nothing to fill. + if (data->value().empty()) + return; + + if (formControlType(*field) == kText) { + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(field); + + // If the maxlength attribute contains a negative value, maxLength() + // returns the default maxlength value. + input_element->setValue(data->value().substr(0, input_element->maxLength()).c_str()); + input_element->setAutofilled(true); + if (is_initiating_node) { + int length = input_element->value().length(); + input_element->setSelectionRange(length, length); + } + } else if (formControlType(*field) == kSelectOne) { + HTMLSelectElement* select_element = static_cast<HTMLSelectElement*>(field); + select_element->setValue(data->value().c_str()); + } +} + +void FormManager::PreviewFormField(HTMLFormControlElement* field, const FormField* data, bool is_initiating_node) { + // Nothing to preview. + if (data->value().empty()) + return; + + // Only preview input fields. + if (formControlType(*field) != kText) + return; + + HTMLInputElement* input_element = static_cast<HTMLInputElement*>(field); + + // If the maxlength attribute contains a negative value, maxLength() + // returns the default maxlength value. + input_element->setSuggestedValue(data->value().substr(0, input_element->maxLength()).c_str()); + input_element->setAutofilled(true); + if (is_initiating_node) + input_element->setSelectionRange(0, input_element->suggestedValue().length()); +} + +} diff --git a/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.h b/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.h new file mode 100644 index 0000000..e844981 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, 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. + */ + +#ifndef FormManagerAndroid_h +#define FormManagerAndroid_h + +#include "ChromiumIncludes.h" + +#include <map> +#include <vector> + +// TODO: This file is taken from chromium/chrome/renderer/form_manager.h and +// customised to use WebCore types rather than WebKit API types. It would be +// nice and would ease future merge pain if the two could be combined. + +namespace webkit_glue { +struct FormData; +class FormField; +} // namespace webkit_glue + +namespace WebCore { +class Frame; +class HTMLFormControlElement; +class HTMLFormElement; +class Node; +} + +using WebCore::Frame; +using WebCore::HTMLFormControlElement; +using WebCore::HTMLFormElement; +using WebCore::Node; + +namespace android { + +// Manages the forms in a Document. +class FormManager { +public: + // A bit field mask for form requirements. + enum RequirementsMask { + REQUIRE_NONE = 0, // No requirements. + REQUIRE_AUTOCOMPLETE = 1 << 0, // Require that autocomplete != off. + REQUIRE_ENABLED = 1 << 1, // Require that disabled attribute is off. + REQUIRE_EMPTY = 1 << 2, // Require that the fields are empty. + }; + + // A bit field mask to extract data from HTMLFormControlElement. + enum ExtractMask { + EXTRACT_NONE = 0, + EXTRACT_VALUE = 1 << 0, // Extract value from HTMLFormControlElement. + EXTRACT_OPTION_TEXT = 1 << 1, // Extract option text from HTMLFormSelectElement. Only valid when |EXTRACT_VALUE| is set. This is used for form submission where humand readable value is captured. + EXTRACT_OPTIONS = 1 << 2, // Extract options from HTMLFormControlElement. + }; + + FormManager(); + virtual ~FormManager(); + + // Fills out a FormField object from a given HTMLFormControlElement. + // |extract_mask|: See the enum ExtractMask above for details. + static void HTMLFormControlElementToFormField(HTMLFormControlElement* element, ExtractMask extract_mask, webkit_glue::FormField* field); + + // Returns the corresponding label for |element|. WARNING: This method can + // potentially be very slow. Do not use during any code paths where the page + // is loading. + static string16 LabelForElement(const HTMLFormControlElement& element); + + // Fills out a FormData object from a given WebFormElement. If |get_values| + // is true, the fields in |form| will have the values filled out. Returns + // true if |form| is filled out; it's possible that |element| won't meet the + // requirements in |requirements|. This also returns false if there are no + // fields in |form|. + // TODO: Remove the user of this in RenderView and move this to + // private. + static bool HTMLFormElementToFormData(HTMLFormElement* element, RequirementsMask requirements, ExtractMask extract_mask, webkit_glue::FormData* form); + + // Scans the DOM in |frame| extracting and storing forms. + void ExtractForms(Frame* frame); + + // Returns a vector of forms in |frame| that match |requirements|. + void GetFormsInFrame(const Frame* frame, RequirementsMask requirements, std::vector<webkit_glue::FormData>* forms); + + // Finds the form that contains |element| and returns it in |form|. Returns + // false if the form is not found. + bool FindFormWithFormControlElement(HTMLFormControlElement* element, RequirementsMask requirements, webkit_glue::FormData* form); + + // Fills the form represented by |form|. |form| should have the name set to + // the name of the form to fill out, and the number of elements and values + // must match the number of stored elements in the form. |node| is the form + // control element that initiated the auto-fill process. + // TODO: Is matching on name alone good enough? It's possible to + // store multiple forms with the same names from different frames. + bool FillForm(const webkit_glue::FormData& form, Node* node); + + // Previews the form represented by |form|. |node| is the form control element + // that initiated the preview process. Same conditions as FillForm. + bool PreviewForm(const webkit_glue::FormData& form, Node* node); + + // Clears the values of all input elements in the form that contains |node|. + // Returns false if the form is not found. + bool ClearFormWithNode(Node* node); + + // Clears the placeholder values and the auto-filled background for any fields + // in the form containing |node| that have been previewed. Resets the + // autofilled state of |node| to |was_autofilled|. Returns false if the form + // is not found. + bool ClearPreviewedFormWithNode(Node* node, bool was_autofilled); + + // Resets the stored set of forms. + void Reset(); + + // Resets the forms for the specified |frame|. + void ResetFrame(const Frame* frame); + + // Returns true if |form| has any auto-filled fields. + bool FormWithNodeIsAutoFilled(Node* node); + +private: + // Stores the HTMLFormElement and the form control elements for a form. + // Original form values are stored so when we clear a form we can reset + // "select-one" values to their original state. + struct FormElement; + + // Type for cache of FormElement objects. + typedef std::vector<FormElement*> FormElementList; + + // The callback type used by ForEachMatchingFormField(). + typedef Callback3<HTMLFormControlElement*, const webkit_glue::FormField*, bool>::Type Callback; + + // Infers corresponding label for |element| from surrounding context in the + // DOM. Contents of preceeding <p> tag or preceeding text element found in + // the form. + static string16 InferLabelForElement(const HTMLFormControlElement& element); + + // Finds the cached FormElement that contains |node|. + bool FindCachedFormElementWithNode(Node* node, FormElement** form_element); + + // Uses the data in |form| to find the cached FormElement. + bool FindCachedFormElement(const webkit_glue::FormData& form, FormElement** form_element); + + // For each field in |data| that matches the corresponding field in |form| + // and meets the |requirements|, |callback| is called with the actual + // WebFormControlElement and the FormField data from |form|. The field that + // matches |node| is not required to be empty if |requirements| includes + // REQUIRE_EMPTY. This method owns |callback|. + void ForEachMatchingFormField(FormElement* form, Node* node, RequirementsMask requirements, const webkit_glue::FormData& data, Callback* callback); + + // A ForEachMatchingFormField() callback that sets |field|'s value using the + // value in |data|. This method also sets the autofill attribute, causing the + // background to be yellow. + void FillFormField(HTMLFormControlElement* field, const webkit_glue::FormField* data, bool is_initiating_node); + + // A ForEachMatchingFormField() callback that sets |field|'s placeholder value + // using the value in |data|, causing the test to be greyed-out. This method + // also sets the autofill attribute, causing the background to be yellow. + void PreviewFormField(HTMLFormControlElement* field, const webkit_glue::FormField* data, bool is_initiaiting_node); + + // The cached FormElement objects. + FormElementList form_elements_; + + DISALLOW_COPY_AND_ASSIGN(FormManager); +}; + +} // namespace android + +#endif // FormManagerAndroid_h diff --git a/Source/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp b/Source/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp new file mode 100644 index 0000000..598b9c4 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2010, 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. + */ + + +#include "config.h" +#include "MainThreadProxy.h" + +#include <wtf/MainThread.h> + +void MainThreadProxy::CallOnMainThread(CallOnMainThreadFunction f, void* c) +{ + callOnMainThread(f, c); +} diff --git a/Source/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.h b/Source/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.h new file mode 100644 index 0000000..d9310bb --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.h @@ -0,0 +1,37 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef MAIN_THREAD_PROXY_H +#define MAIN_THREAD_PROXY_H + +typedef void CallOnMainThreadFunction(void*); + +class MainThreadProxy +{ +public: + static void CallOnMainThread(CallOnMainThreadFunction, void*); +}; + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/autofill/StringUtils.h b/Source/WebKit/android/WebCoreSupport/autofill/StringUtils.h new file mode 100644 index 0000000..aa408a5 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/StringUtils.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010 The Android Open Source Project. All rights reserved. + * + * 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. + */ + + +#ifndef AutoFillStringUtils_h_ +#define AutoFillStringUtils_h_ + +#include "ChromiumIncludes.h" +#include "HTMLFormControlElement.h" +#include <wtf/text/WTFString.h> + +using WebCore::HTMLFormControlElement; + +const string16 kText = ASCIIToUTF16("text"); +const string16 kHidden = ASCIIToUTF16("hidden"); +const string16 kSelectOne = ASCIIToUTF16("select-one"); + +inline string16 WTFStringToString16(const WTF::String& wtfString) +{ + WTF::String str = wtfString; + + if (str.charactersWithNullTermination()) + return string16(str.charactersWithNullTermination()); + else + return string16(); +} + +inline string16 nameForAutoFill(const HTMLFormControlElement& element) +{ + // Taken from WebKit/chromium/src/WebFormControlElement.cpp, ported + // to use WebCore types for accessing element properties. + String name = element.name(); + String trimmedName = name.stripWhiteSpace(); + if (!trimmedName.isEmpty()) + return WTFStringToString16(trimmedName); + name = element.getIdAttribute(); + trimmedName = name.stripWhiteSpace(); + if (!trimmedName.isEmpty()) + return WTFStringToString16(trimmedName); + return string16(); +} + +inline string16 formControlType(const HTMLFormControlElement& element) +{ + // Taken from WebKit/chromium/src/WebFormControlElement.cpp, ported + // to use WebCore types for accessing element properties. + return WTFStringToString16(element.type()); +} + +#endif + diff --git a/Source/WebKit/android/WebCoreSupport/autofill/WebAutoFill.cpp b/Source/WebKit/android/WebCoreSupport/autofill/WebAutoFill.cpp new file mode 100644 index 0000000..a80636c --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/WebAutoFill.cpp @@ -0,0 +1,296 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "WebAutoFill.h" + +#if ENABLE(WEB_AUTOFILL) + +#include "AutoFillHostAndroid.h" +#include "Frame.h" +#include "FormData.h" +#include "FormManagerAndroid.h" +#include "FrameLoader.h" +#include "HTMLFormControlElement.h" +#include "MainThreadProxy.h" +#include "Node.h" +#include "Page.h" +#include "Settings.h" +#include "WebFrame.h" +#include "WebRequestContext.h" +#include "WebUrlLoaderClient.h" +#include "WebViewCore.h" + +#define NO_PROFILE_SET 0 +#define FORM_NOT_AUTOFILLABLE -1 + +namespace android +{ +WebAutoFill::WebAutoFill() + : mQueryId(1) + , mWebViewCore(0) + , mLastSearchDomVersion(0) + , mParsingForms(false) +{ + mTabContents = new TabContents(); + setEmptyProfile(); +} + +void WebAutoFill::init() +{ + if (mAutoFillManager) + return; + + mFormManager = new FormManager(); + // We use the WebView's WebRequestContext, which may be a private browsing context. + ASSERT(mWebViewCore); + mAutoFillManager = new AutoFillManager(mTabContents.get()); + mAutoFillHost = new AutoFillHostAndroid(this); + mTabContents->SetProfileRequestContext(new AndroidURLRequestContextGetter(mWebViewCore->webRequestContext(), WebUrlLoaderClient::ioThread())); + mTabContents->SetAutoFillHost(mAutoFillHost.get()); +} + +WebAutoFill::~WebAutoFill() +{ + cleanUpQueryMap(); + mUniqueIdMap.clear(); +} + +void WebAutoFill::cleanUpQueryMap() +{ + for (AutoFillQueryFormDataMap::iterator it = mQueryMap.begin(); it != mQueryMap.end(); it++) + delete it->second; + mQueryMap.clear(); +} + +void WebAutoFill::searchDocument(WebCore::Frame* frame) +{ + if (!enabled()) + return; + + MutexLocker lock(mFormsSeenMutex); + + init(); + + cleanUpQueryMap(); + mUniqueIdMap.clear(); + mForms.clear(); + mQueryId = 1; + + ASSERT(mFormManager); + ASSERT(mAutoFillManager); + + mAutoFillManager->Reset(); + mFormManager->Reset(); + + mFormManager->ExtractForms(frame); + mFormManager->GetFormsInFrame(frame, FormManager::REQUIRE_AUTOCOMPLETE, &mForms); + + // Needs to be done on a Chrome thread as it will make a URL request to the AutoFill server. + // TODO: Use our own Autofill thread instead of the IO thread. + // TODO: For now, block here. Would like to make this properly async. + base::Thread* thread = WebUrlLoaderClient::ioThread(); + mParsingForms = true; + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebAutoFill::formsSeenImpl)); + while (mParsingForms) + mFormsSeenCondition.wait(mFormsSeenMutex); +} + +// Called on the Chromium IO thread. +void WebAutoFill::formsSeenImpl() +{ + MutexLocker lock(mFormsSeenMutex); + mAutoFillManager->FormsSeen(mForms); + mParsingForms = false; + mFormsSeenCondition.signal(); +} + +void WebAutoFill::formFieldFocused(WebCore::HTMLFormControlElement* formFieldElement) +{ + ASSERT(formFieldElement); + + Document* doc = formFieldElement->document(); + Frame* frame = doc->frame(); + + // FIXME: AutoFill only works in main frame for now. Should consider + // child frames. + if (frame != frame->page()->mainFrame()) + return; + + unsigned domVersion = doc->domTreeVersion(); + ASSERT(domVersion > 0); + + if (mLastSearchDomVersion != domVersion) { + // Need to extract forms as DOM version has changed since the last time + // we searched. + searchDocument(formFieldElement->document()->frame()); + mLastSearchDomVersion = domVersion; + } + + if (!enabled()) { + // In case that we've just been disabled and the last time we got autofill + // suggestions and told Java about them, clear that bit Java side now + // we're disabled. + mWebViewCore->setWebTextViewAutoFillable(FORM_NOT_AUTOFILLABLE, string16()); + return; + } + + // Get the FormField from the Node. + webkit_glue::FormField* formField = new webkit_glue::FormField; + FormManager::HTMLFormControlElementToFormField(formFieldElement, FormManager::EXTRACT_NONE, formField); + formField->set_label(FormManager::LabelForElement(*formFieldElement)); + + webkit_glue::FormData* form = new webkit_glue::FormData; + mFormManager->FindFormWithFormControlElement(formFieldElement, FormManager::REQUIRE_AUTOCOMPLETE, form); + mQueryMap[mQueryId] = new FormDataAndField(form, formField); + + bool suggestions = mAutoFillManager->GetAutoFillSuggestions(*form, *formField); + + mQueryId++; + if (!suggestions) { + ASSERT(mWebViewCore); + // Tell Java no autofill suggestions for this form. + mWebViewCore->setWebTextViewAutoFillable(FORM_NOT_AUTOFILLABLE, string16()); + return; + } +} + +void WebAutoFill::querySuccessful(const string16& value, const string16& label, int uniqueId) +{ + if (!enabled()) + return; + + // Store the unique ID for the query and inform java that autofill suggestions for this form are available. + // Pass java the queryId so that it can pass it back if the user decides to use autofill. + mUniqueIdMap[mQueryId] = uniqueId; + + ASSERT(mWebViewCore); + mWebViewCore->setWebTextViewAutoFillable(mQueryId, mAutoFillProfile->Label()); +} + +void WebAutoFill::fillFormFields(int queryId) +{ + if (!enabled()) + return; + + webkit_glue::FormData* form = mQueryMap[queryId]->form(); + webkit_glue::FormField* field = mQueryMap[queryId]->field(); + ASSERT(form); + ASSERT(field); + + AutoFillQueryToUniqueIdMap::iterator iter = mUniqueIdMap.find(queryId); + if (iter == mUniqueIdMap.end()) { + // The user has most likely tried to AutoFill the form again without + // refocussing the form field. The UI should protect against this + // but stop here to be certain. + return; + } + mAutoFillManager->FillAutoFillFormData(queryId, *form, *field, iter->second); + mUniqueIdMap.erase(iter); +} + +void WebAutoFill::fillFormInPage(int queryId, const webkit_glue::FormData& form) +{ + if (!enabled()) + return; + + // FIXME: Pass a pointer to the Node that triggered the AutoFill flow here instead of 0. + // The consquence of passing 0 is that we should always fail the test in FormManader::ForEachMathcingFormField():169 + // that says "only overwrite an elements current value if the user triggered autofill through that element" + // for elements that have a value already. But by a quirk of Android text views we are OK. We should still + // fix this though. + mFormManager->FillForm(form, 0); +} + +bool WebAutoFill::enabled() const +{ + Page* page = mWebViewCore->mainFrame()->page(); + return page ? page->settings()->autoFillEnabled() : false; +} + +void WebAutoFill::setProfile(const string16& fullName, const string16& emailAddress, const string16& companyName, + const string16& addressLine1, const string16& addressLine2, const string16& city, + const string16& state, const string16& zipCode, const string16& country, const string16& phoneNumber) +{ + if (!mAutoFillProfile) + mAutoFillProfile.set(new AutoFillProfile()); + + // Update the profile. + // Constants for AutoFill field types are found in external/chromium/chrome/browser/autofill/field_types.h. + mAutoFillProfile->SetInfo(AutoFillType(NAME_FULL), fullName); + mAutoFillProfile->SetInfo(AutoFillType(EMAIL_ADDRESS), emailAddress); + mAutoFillProfile->SetInfo(AutoFillType(COMPANY_NAME), companyName); + mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_LINE1), addressLine1); + mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_LINE2), addressLine2); + mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_CITY), city); + mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_STATE), state); + mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_ZIP), zipCode); + mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_COUNTRY), country); + mAutoFillProfile->SetInfo(AutoFillType(PHONE_HOME_WHOLE_NUMBER), phoneNumber); + + std::vector<AutoFillProfile> profiles; + profiles.push_back(*mAutoFillProfile); + updateProfileLabel(); + mTabContents->profile()->GetPersonalDataManager()->SetProfiles(&profiles); +} + +bool WebAutoFill::updateProfileLabel() +{ + std::vector<AutoFillProfile*> profiles; + profiles.push_back(mAutoFillProfile.get()); + return AutoFillProfile::AdjustInferredLabels(&profiles); +} + +void WebAutoFill::clearProfiles() +{ + if (!mAutoFillProfile) + return; + // For now Chromium only ever knows about one profile, so we can just + // remove it. If we support multiple profiles in the future + // we need to remove them all here. + std::string profileGuid = mAutoFillProfile->guid(); + mTabContents->profile()->GetPersonalDataManager()->RemoveProfile(profileGuid); + setEmptyProfile(); +} + +void WebAutoFill::setEmptyProfile() +{ + // Set an empty profile. This will ensure that when autofill is enabled, + // we will still search the document for autofillable forms and inform + // java of their presence so we can invite the user to set up + // their own profile. + + // Chromium code will strip the values sent into the profile so we need them to be + // at least one non-whitespace character long. We need to set all fields of the + // profile to a non-empty string so that any field type can trigger the autofill + // suggestion. AutoFill will not detect form fields if the profile value for that + // field is an empty string. + static const string16 empty = string16(ASCIIToUTF16("a")); + setProfile(empty, empty, empty, empty, empty, empty, empty, empty, empty, empty); +} + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/autofill/WebAutoFill.h b/Source/WebKit/android/WebCoreSupport/autofill/WebAutoFill.h new file mode 100644 index 0000000..97e478e --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/autofill/WebAutoFill.h @@ -0,0 +1,127 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef WebAutoFill_h +#define WebAutoFill_h + +#if ENABLE(WEB_AUTOFILL) + +#include "ChromiumIncludes.h" + +#include <map> +#include <vector> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/ThreadingPrimitives.h> + +class AutoFillManager; +class AutoFillProfile; +class AutoFillHost; + +namespace WebCore { +class Frame; +class HTMLFormControlElement; +} + +namespace android +{ +class FormManager; +class WebViewCore; + +class FormDataAndField { +public: + FormDataAndField(webkit_glue::FormData* form, webkit_glue::FormField* field) + : mForm(form) + , mField(field) + { + } + + webkit_glue::FormData* form() { return mForm.get(); } + webkit_glue::FormField* field() { return mField.get(); } + +private: + OwnPtr<webkit_glue::FormData> mForm; + OwnPtr<webkit_glue::FormField> mField; +}; + +class WebAutoFill : public Noncopyable +{ +public: + WebAutoFill(); + virtual ~WebAutoFill(); + void formFieldFocused(WebCore::HTMLFormControlElement*); + void fillFormFields(int queryId); + void querySuccessful(const string16& value, const string16& label, int uniqueId); + void fillFormInPage(int queryId, const webkit_glue::FormData& form); + void setWebViewCore(WebViewCore* webViewCore) { mWebViewCore = webViewCore; } + bool enabled() const; + + void setProfile(const string16& fullName, const string16& emailAddress, const string16& companyName, + const string16& addressLine1, const string16& addressLine2, const string16& city, + const string16& state, const string16& zipCode, const string16& country, const string16& phoneNumber); + void clearProfiles(); + + bool updateProfileLabel(); + + void reset() { mLastSearchDomVersion = 0; } + +private: + void init(); + void searchDocument(WebCore::Frame*); + void setEmptyProfile(); + void formsSeenImpl(); + void cleanUpQueryMap(); + + OwnPtr<FormManager> mFormManager; + OwnPtr<AutoFillManager> mAutoFillManager; + OwnPtr<AutoFillHost> mAutoFillHost; + OwnPtr<TabContents> mTabContents; + OwnPtr<AutoFillProfile> mAutoFillProfile; + + typedef std::vector<webkit_glue::FormData, std::allocator<webkit_glue::FormData> > FormList; + FormList mForms; + + typedef std::map<int, FormDataAndField*> AutoFillQueryFormDataMap; + AutoFillQueryFormDataMap mQueryMap; + + typedef std::map<int, int> AutoFillQueryToUniqueIdMap; + AutoFillQueryToUniqueIdMap mUniqueIdMap; + int mQueryId; + + WebViewCore* mWebViewCore; + + unsigned mLastSearchDomVersion; + + WTF::Mutex mFormsSeenMutex; // Guards mFormsSeenCondition and mParsingForms. + WTF::ThreadCondition mFormsSeenCondition; + bool volatile mParsingForms; +}; + +} + +DISABLE_RUNNABLE_METHOD_REFCOUNT(android::WebAutoFill); + +#endif // ENABLE(WEB_AUTOFILL) +#endif // WebAutoFill_h diff --git a/Source/WebKit/android/benchmark/Android.mk b/Source/WebKit/android/benchmark/Android.mk new file mode 100644 index 0000000..5b189e1 --- /dev/null +++ b/Source/WebKit/android/benchmark/Android.mk @@ -0,0 +1,41 @@ +## +## +## Copyright 2009, 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 APPLE COMPUTER, INC. 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. +## + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main.cpp + +# Pull the webkit definitions from the base webkit makefile. +LOCAL_SHARED_LIBRARIES := libwebcore $(WEBKIT_SHARED_LIBRARIES) +LOCAL_LDLIBS := $(WEBKIT_LDLIBS) + +LOCAL_MODULE := webcore_test + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/Source/WebKit/android/benchmark/Intercept.cpp b/Source/WebKit/android/benchmark/Intercept.cpp new file mode 100644 index 0000000..deffac2 --- /dev/null +++ b/Source/WebKit/android/benchmark/Intercept.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2009, 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 APPLE COMPUTER, INC. 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 "webcore_test" +#include "config.h" + +#include "Base64.h" +#include "HTTPParsers.h" +#include "Intercept.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "TextEncoding.h" + +#include <utils/Log.h> +#include <wtf/HashMap.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringHash.h> + +PassRefPtr<WebCore::ResourceLoaderAndroid> MyResourceLoader::create( + ResourceHandle* handle, String url) +{ + return adoptRef<WebCore::ResourceLoaderAndroid>( + new MyResourceLoader(handle, url)); +} + +void MyResourceLoader::handleRequest() +{ + if (protocolIs(m_url, "data")) + loadData(m_url.substring(5)); // 5 for data: + else if (protocolIs(m_url, "file")) + loadFile(m_url.substring(7)); // 7 for file:// +} + +void MyResourceLoader::loadData(const String& data) +{ + LOGD("Loading data (%s) ...", data.latin1().data()); + ResourceHandleClient* client = m_handle->client(); + int index = data.find(','); + if (index == -1) { + client->cannotShowURL(m_handle); + return; + } + + String mediaType = data.substring(0, index); + String base64 = data.substring(index + 1); + + bool decode = mediaType.endsWith(";base64", false); + if (decode) + mediaType = mediaType.left(mediaType.length() - 7); // 7 for base64; + + if (mediaType.isEmpty()) + mediaType = "text/plain;charset=US-ASCII"; + + String mimeType = extractMIMETypeFromMediaType(mediaType); + String charset = extractCharsetFromMediaType(mediaType); + + ResourceResponse response; + response.setMimeType(mimeType); + + if (decode) { + base64 = decodeURLEscapeSequences(base64); + response.setTextEncodingName(charset); + client->didReceiveResponse(m_handle, response); + + // FIXME: This is annoying. WebCore's Base64 decoder chokes on spaces. + // That is correct with strict decoding but html authors (particularly + // the acid3 authors) put spaces in the data which should be ignored. + // Remove them here before sending to the decoder. + Vector<char> in; + CString str = base64.latin1(); + const char* chars = str.data(); + unsigned i = 0; + while (i < str.length()) { + char c = chars[i]; + // Don't send spaces or control characters. + if (c != ' ' && c != '\n' && c != '\t' && c != '\b' + && c != '\f' && c != '\r') + in.append(chars[i]); + i++; + } + Vector<char> out; + if (base64Decode(in, out) && out.size() > 0) + client->didReceiveData(m_handle, out.data(), out.size(), 0); + } else { + base64 = decodeURLEscapeSequences(base64, TextEncoding(charset)); + response.setTextEncodingName("UTF-16"); + client->didReceiveResponse(m_handle, response); + if (base64.length() > 0) + client->didReceiveData(m_handle, (const char*)base64.characters(), + base64.length() * sizeof(UChar), 0); + } + client->didFinishLoading(m_handle, 0); +} +static String mimeTypeForExtension(const String& file) +{ + static HashMap<String, String, CaseFoldingHash> extensionToMime; + if (extensionToMime.isEmpty()) { + extensionToMime.set("txt", "text/plain"); + extensionToMime.set("html", "text/html"); + extensionToMime.set("htm", "text/html"); + extensionToMime.set("png", "image/png"); + extensionToMime.set("jpeg", "image/jpeg"); + extensionToMime.set("jpg", "image/jpeg"); + extensionToMime.set("gif", "image/gif"); + extensionToMime.set("ico", "image/x-icon"); + extensionToMime.set("js", "text/javascript"); + } + int dot = file.reverseFind('.'); + String mime("text/plain"); + if (dot != -1) { + String ext = file.substring(dot + 1); + if (extensionToMime.contains(ext)) + mime = extensionToMime.get(ext); + } + return mime; +} + +void MyResourceLoader::loadFile(const String& file) +{ + LOGD("Loading file (%s) ...", file.latin1().data()); + FILE* f = fopen(file.latin1().data(), "r"); + ResourceHandleClient* client = m_handle->client(); + if (!f) { + client->didFail(m_handle, + ResourceError("", -14, file, "Could not open file")); + } else { + ResourceResponse response; + response.setTextEncodingName("utf-8"); + response.setMimeType(mimeTypeForExtension(file)); + client->didReceiveResponse(m_handle, response); + char buf[512]; + while (true) { + int res = fread(buf, 1, sizeof(buf), f); + if (res <= 0) + break; + client->didReceiveData(m_handle, buf, res, 0); + } + fclose(f); + client->didFinishLoading(m_handle, 0); + } +} + +PassRefPtr<WebCore::ResourceLoaderAndroid> MyWebFrame::startLoadingResource( + ResourceHandle* handle, const ResourceRequest& req, bool ignore, + bool ignore2) +{ + RefPtr<WebCore::ResourceLoaderAndroid> loader = + MyResourceLoader::create(handle, req.url().string()); + m_requests.append(loader); + if (!m_timer.isActive()) + m_timer.startOneShot(0); + return loader.release(); +} + +void MyWebFrame::timerFired(Timer<MyWebFrame>*) +{ + LOGD("Handling requests..."); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> > reqs; + reqs.swap(m_requests); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> >::iterator i = reqs.begin(); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> >::iterator end = reqs.end(); + for (; i != end; i++) + static_cast<MyResourceLoader*>((*i).get())->handleRequest(); + + LOGD("...done"); +} diff --git a/Source/WebKit/android/benchmark/Intercept.h b/Source/WebKit/android/benchmark/Intercept.h new file mode 100644 index 0000000..edd5123 --- /dev/null +++ b/Source/WebKit/android/benchmark/Intercept.h @@ -0,0 +1,82 @@ +/* + * Copyright 2009, 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef INTERCEPT_H +#define INTERCEPT_H + +#include "MyJavaVM.h" +#include "PlatformString.h" +#include "Timer.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreResourceLoader.h" +#include <JNIUtility.h> +#include <wtf/Vector.h> + +namespace WebCore { + class Page; + class ResourceHandle; + class ResourceRequest; +} + +using namespace android; +using namespace WebCore; +using namespace WTF; + +class MyResourceLoader : public WebCoreResourceLoader { +public: + static PassRefPtr<WebCore::ResourceLoaderAndroid> create( + ResourceHandle* handle, String url); + void handleRequest(); + +private: + MyResourceLoader(ResourceHandle* handle, String url) + : WebCoreResourceLoader(JSC::Bindings::getJNIEnv(), MY_JOBJECT) + , m_handle(handle) + , m_url(url) {} + + void loadData(const String&); + void loadFile(const String&); + ResourceHandle* m_handle; + String m_url; +}; + +class MyWebFrame : public WebFrame { +public: + MyWebFrame(Page* page) + : WebFrame(JSC::Bindings::getJNIEnv(), MY_JOBJECT, MY_JOBJECT, page) + , m_timer(this, &MyWebFrame::timerFired) {} + + virtual PassRefPtr<WebCore::ResourceLoaderAndroid> startLoadingResource( + ResourceHandle* handle, const ResourceRequest& req, bool, bool); + + virtual bool canHandleRequest(const ResourceRequest&) { return true; } + +private: + void timerFired(Timer<MyWebFrame>*); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> > m_requests; + Timer<MyWebFrame> m_timer; +}; + +#endif diff --git a/Source/WebKit/android/benchmark/MyJavaVM.cpp b/Source/WebKit/android/benchmark/MyJavaVM.cpp new file mode 100644 index 0000000..574c745 --- /dev/null +++ b/Source/WebKit/android/benchmark/MyJavaVM.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2009, 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 APPLE COMPUTER, INC. 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. + */ + +#include "config.h" +#include "MyJavaVM.h" + +#include <JNIUtility.h> +#include <jni.h> + +static JNIEnv* s_env; +static JavaVM* s_jvm; + +// JavaVM functions +jint vm_attachCurrentThread(JavaVM*, JNIEnv** env, void*) { + *env = s_env; + return JNI_OK; +} + +// JNIEnv functions +jobject env_callObjectMethodV(JNIEnv*, jobject, jmethodID, va_list) { + return MY_JOBJECT; +} +void env_callVoidMethodV(JNIEnv*, jobject, jmethodID, va_list) {} +void env_deleteRef(JNIEnv*, jobject) {} +jboolean env_exceptionCheck(JNIEnv*) { + return false; +} +jclass env_findClass(JNIEnv*, const char*) { + return (jclass) 1; +} +jbyte* env_getByteArrayElements(JNIEnv*, jbyteArray, jboolean*) { + return NULL; +} +jmethodID env_getMethodID(JNIEnv*, jclass, const char*, const char*) { + return (jmethodID) 1; +} +jclass env_getObjectClass(JNIEnv*, jobject) { + return (jclass) 1; +} +static const char* s_fakeString = "Fake Java String"; +const jchar* env_getStringChars(JNIEnv*, jstring, jboolean* isCopy) { + if (isCopy) + *isCopy = false; + return (const jchar*)s_fakeString; +} +jsize env_getStringLength(JNIEnv*, jstring) { + return sizeof(s_fakeString) - 1; +} +jbyteArray env_newByteArray(JNIEnv*, jsize) { + return (jbyteArray) 1; +} +jobject env_newRef(JNIEnv*, jobject obj) { + return obj; +} +jobject env_newObjectV(JNIEnv*, jclass, jmethodID, va_list) { + return MY_JOBJECT; +} +jstring env_newString(JNIEnv*, const jchar*, jsize) { + return (jstring) 1; +} +void env_releaseByteArrayElements(JNIEnv*, jbyteArray, jbyte*, jint) {} +void env_releaseStringChars(JNIEnv*, jstring, const jchar*) {} +void env_setByteArrayRegion(JNIEnv*, jbyteArray, jsize, jsize, const jbyte*) {} +void env_setIntField(JNIEnv*, jobject, jfieldID, jint) {} + +void InitializeJavaVM() { + // First, create the fake vm + s_jvm = new JavaVM; + JNIInvokeInterface* i = new JNIInvokeInterface; + memset(i, 0, sizeof(JNIInvokeInterface)); + s_jvm->functions = i; + + // Now, assign the functions of the vm to our fake ones. + i->AttachCurrentThread = vm_attachCurrentThread; + + // Create the fake env next + s_env = new JNIEnv; + JNINativeInterface* n = new JNINativeInterface; + memset(n, 0, sizeof(JNINativeInterface)); + s_env->functions = n; + + // Point the functions we care about to out fake ones. + n->CallObjectMethodV = env_callObjectMethodV; + n->CallVoidMethodV = env_callVoidMethodV; + n->DeleteLocalRef = env_deleteRef; + n->DeleteGlobalRef = env_deleteRef; + n->DeleteWeakGlobalRef = env_deleteRef; + n->ExceptionCheck = env_exceptionCheck; + n->FindClass = env_findClass; + n->GetByteArrayElements = env_getByteArrayElements; + n->GetMethodID = env_getMethodID; + n->GetObjectClass = env_getObjectClass; + n->GetStringChars = env_getStringChars; + n->GetStringLength = env_getStringLength; + n->NewByteArray = env_newByteArray; + n->NewLocalRef = env_newRef; + n->NewGlobalRef = env_newRef; + n->NewWeakGlobalRef = env_newRef; + n->NewObjectV = env_newObjectV; + n->NewString = env_newString; + n->ReleaseByteArrayElements = env_releaseByteArrayElements; + n->ReleaseStringChars = env_releaseStringChars; + n->SetByteArrayRegion = env_setByteArrayRegion; + n->SetIntField = env_setIntField; + + // Tell WebCore about the vm + JSC::Bindings::setJavaVM(s_jvm); +} diff --git a/Source/WebKit/android/benchmark/MyJavaVM.h b/Source/WebKit/android/benchmark/MyJavaVM.h new file mode 100644 index 0000000..36d478d --- /dev/null +++ b/Source/WebKit/android/benchmark/MyJavaVM.h @@ -0,0 +1,34 @@ +/* + * Copyright 2009, 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef MY_JAVA_VM_H +#define MY_JAVA_VM_H + +// Make it 1 just to appease any assertions or checks for valid objects +#define MY_JOBJECT ((jobject) 1) + +void InitializeJavaVM(); + +#endif diff --git a/Source/WebKit/android/benchmark/main.cpp b/Source/WebKit/android/benchmark/main.cpp new file mode 100644 index 0000000..fcb797d --- /dev/null +++ b/Source/WebKit/android/benchmark/main.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2009, 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 APPLE COMPUTER, INC. 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 "webcore_test" + +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <utils/Log.h> + +namespace android { +extern void benchmark(const char*, int, int ,int); +} + +int main(int argc, char** argv) { + int width = 800; + int height = 600; + int reloadCount = 0; + while (true) { + int c = getopt(argc, argv, "d:r:"); + if (c == -1) + break; + else if (c == 'd') { + char* x = strchr(optarg, 'x'); + if (x) { + width = atoi(optarg); + height = atoi(x + 1); + LOGD("Rendering page at %dx%d", width, height); + } + } else if (c == 'r') { + reloadCount = atoi(optarg); + if (reloadCount < 0) + reloadCount = 0; + LOGD("Reloading %d times", reloadCount); + } + } + if (optind >= argc) { + LOGE("Please supply a file to read\n"); + return 1; + } + + android::benchmark(argv[optind], reloadCount, width, height); +} diff --git a/Source/WebKit/android/icu/unicode/ucnv.cpp b/Source/WebKit/android/icu/unicode/ucnv.cpp new file mode 100644 index 0000000..1b40573 --- /dev/null +++ b/Source/WebKit/android/icu/unicode/ucnv.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2009, 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. + */ + +// BEGIN android-added +// Add config.h to avoid compiler error in uobject.h +// ucnv.h includes uobject.h indirectly and uobjetcs.h defines new/delete. +// new/delete are also defined in WebCorePrefix.h which auto included in Android make. +// +// config.h has to be on top of the include list. +#include "config.h" +// END android-added + +#include "EmojiFont.h" +#include <icu4c/common/unicode/ucnv.h> + +namespace android { + +U_STABLE UConverter* U_EXPORT2 +ucnv_open_emoji(const char *converterName, UErrorCode *err) { + if (EmojiFont::IsAvailable()) { + if (strcmp(converterName, "Shift_JIS") == 0) { + converterName = EmojiFont::GetShiftJisConverterName(); + } + } + return ucnv_open(converterName, err); +} + +} // end namespace android diff --git a/Source/WebKit/android/icu/unicode/ucnv.h b/Source/WebKit/android/icu/unicode/ucnv.h new file mode 100644 index 0000000..5ddaedb --- /dev/null +++ b/Source/WebKit/android/icu/unicode/ucnv.h @@ -0,0 +1,47 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef ANDROID_UCNV_H +#define ANDROID_UCNV_H + +// Include the real ucnv.h file from icu. Use a more exact reference so we do +// not conflict with this file. +#include <icu4c/common/unicode/ucnv.h> + +namespace android { + +U_STABLE UConverter* U_EXPORT2 +ucnv_open_emoji(const char *converterName, UErrorCode *err); + +} + +// Redefine ucnv_open to android::ucnv_open_emoji. This relies heavily on the +// fact that this file will be included before any of the real icu headers. +// This is done in Android.mk by including WebKit/android/icu before the +// regular icu directory. +#undef ucnv_open +#define ucnv_open android::ucnv_open_emoji + +#endif diff --git a/Source/WebKit/android/jni/CacheManager.cpp b/Source/WebKit/android/jni/CacheManager.cpp new file mode 100644 index 0000000..144b62a --- /dev/null +++ b/Source/WebKit/android/jni/CacheManager.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2011, 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. + */ + +#include "config.h" + +#if USE(CHROME_NETWORK_STACK) + +#include "ChromiumIncludes.h" +#include "WebCache.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <platform/FileSystem.h> +#include <platform/text/Base64.h> +#include <wtf/text/CString.h> +#include <wtf/text/WTFString.h> + +using namespace WebCore; +using namespace base; +using namespace disk_cache; +using namespace net; +using namespace std; + +namespace android { + +// JNI for android.webkit.CacheManager +static const char* javaCacheManagerClass = "android/webkit/CacheManager"; + +static void setStringField(JNIEnv* env, const jobject& object, const jfieldID& field, const String& str) +{ + jstring jstr = wtfStringToJstring(env, str); + env->SetObjectField(object, field, jstr); + env->DeleteLocalRef(jstr); +} + +static void setFieldFromHeaderIfPresent(CacheResult* result, const char* header, JNIEnv* env, const jobject& object, const jfieldID& field, bool allowEmptyString) +{ + String value; + if (result->firstResponseHeader(header, &value, allowEmptyString)) + setStringField(env, object, field, value); +} + +static String getCacheFileBaseDir(JNIEnv* env) +{ + static String baseDir; + if (baseDir.isEmpty()) { + jclass cacheManagerClass = env->FindClass("android/webkit/CacheManager"); + jmethodID getCacheFileBaseDirMethod = env->GetStaticMethodID(cacheManagerClass, "getCacheFileBaseDir", "()Ljava/io/File;"); + jclass fileClass = env->FindClass("java/io/File"); + jmethodID getPathMethod = env->GetMethodID(fileClass, "getPath", "()Ljava/lang/String;"); + jobject fileObject = env->CallStaticObjectMethod(cacheManagerClass, getCacheFileBaseDirMethod); + baseDir = jstringToWtfString(env, static_cast<jstring>(env->CallObjectMethod(fileObject, getPathMethod))); + } + return baseDir; +} + +static jobject getCacheResult(JNIEnv* env, jobject, jstring url) +{ + // This is called on the UI thread. + scoped_refptr<CacheResult> result = WebCache::get(false /*privateBrowsing*/)->getCacheResult(jstringToWtfString(env, url)); + if (!result) + return 0; + + // We create and populate a file with the cache entry. This allows us to + // replicate the behaviour of the Android HTTP stack in the Java + // CacheManager, which opens the cache file and provides an input stream to + // the file as part of the Java CacheResult object! + String urlWtfString = jstringToWtfString(env, url); + Vector<char> encodedUrl; + base64Encode(urlWtfString.utf8().data(), urlWtfString.length(), encodedUrl, false /*insertLFs*/); + String filePath = pathByAppendingComponent(getCacheFileBaseDir(env), encodedUrl.data()); + if (!result->writeToFile(filePath)) + return 0; + + jclass cacheResultClass = env->FindClass("android/webkit/CacheManager$CacheResult"); + jmethodID constructor = env->GetMethodID(cacheResultClass, "<init>", "()V"); + // We only bother with the fields that are made accessible through the public API. + jfieldID contentdispositionField = env->GetFieldID(cacheResultClass, "contentdisposition", "Ljava/lang/String;"); + jfieldID contentLengthField = env->GetFieldID(cacheResultClass, "contentLength", "J"); + jfieldID etagField = env->GetFieldID(cacheResultClass, "etag", "Ljava/lang/String;"); + jfieldID encodingField = env->GetFieldID(cacheResultClass, "encoding", "Ljava/lang/String;"); + jfieldID expiresField = env->GetFieldID(cacheResultClass, "expires", "J"); + jfieldID expiresStringField = env->GetFieldID(cacheResultClass, "expiresString", "Ljava/lang/String;"); + jfieldID httpStatusCodeField = env->GetFieldID(cacheResultClass, "httpStatusCode", "I"); + jfieldID lastModifiedField = env->GetFieldID(cacheResultClass, "lastModified", "Ljava/lang/String;"); + jfieldID localPathField = env->GetFieldID(cacheResultClass, "localPath", "Ljava/lang/String;"); + jfieldID locationField = env->GetFieldID(cacheResultClass, "location", "Ljava/lang/String;"); + jfieldID mimeTypeField = env->GetFieldID(cacheResultClass, "mimeType", "Ljava/lang/String;"); + + jobject javaResult = env->NewObject(cacheResultClass, constructor); + setFieldFromHeaderIfPresent(result.get(), "content-disposition", env, javaResult, contentdispositionField, true); + env->SetLongField(javaResult, contentLengthField, result->contentSize()); + setFieldFromHeaderIfPresent(result.get(), "etag", env, javaResult, etagField, false); + setStringField(env, javaResult, encodingField, "TODO"); // TODO: Where does the Android stack set this? + env->SetLongField(javaResult, expiresField, result->expires()); + env->SetIntField(javaResult, httpStatusCodeField, result->responseCode()); + setFieldFromHeaderIfPresent(result.get(), "last-modified", env, javaResult, lastModifiedField, false); + setStringField(env, javaResult, localPathField, encodedUrl.data()); + setFieldFromHeaderIfPresent(result.get(), "location", env, javaResult, locationField, false); + setStringField(env, javaResult, mimeTypeField, result->mimeType()); + + return javaResult; +} + +static JNINativeMethod gCacheManagerMethods[] = { + { "nativeGetCacheResult", "(Ljava/lang/String;)Landroid/webkit/CacheManager$CacheResult;", (void*) getCacheResult }, +}; + +int registerCacheManager(JNIEnv* env) +{ +#ifndef NDEBUG + jclass cacheManager = env->FindClass(javaCacheManagerClass); + LOG_ASSERT(cacheManager, "Unable to find class"); + env->DeleteLocalRef(cacheManager); +#endif + return jniRegisterNativeMethods(env, javaCacheManagerClass, gCacheManagerMethods, NELEM(gCacheManagerMethods)); +} + +} // namespace android + +#endif // USE(CHROME_NETWORK_STACK) diff --git a/Source/WebKit/android/jni/CookieManager.cpp b/Source/WebKit/android/jni/CookieManager.cpp new file mode 100644 index 0000000..0bdf303 --- /dev/null +++ b/Source/WebKit/android/jni/CookieManager.cpp @@ -0,0 +1,201 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" + +#include "ChromiumIncludes.h" +#include "WebCookieJar.h" +#include "WebCoreJni.h" +#include <JNIHelp.h> + +using namespace base; +using namespace net; + +namespace android { + +// JNI for android.webkit.CookieManager +static const char* javaCookieManagerClass = "android/webkit/CookieManager"; + +static bool acceptCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + // This is a static method which gets the cookie policy for all WebViews. We + // always apply the same configuration to the contexts for both regular and + // private browsing, so expect the same result here. + bool regularAcceptCookies = WebCookieJar::get(false)->allowCookies(); + ASSERT(regularAcceptCookies == WebCookieJar::get(true)->allowCookies()); + return regularAcceptCookies; +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return false; +#endif +} + +static jstring getCookie(JNIEnv* env, jobject, jstring url, jboolean privateBrowsing) +{ +#if USE(CHROME_NETWORK_STACK) + GURL gurl(jstringToStdString(env, url)); + CookieOptions options; + options.set_include_httponly(); + std::string cookies = WebCookieJar::get(privateBrowsing)->cookieStore()->GetCookieMonster()->GetCookiesWithOptions(gurl, options); + return stdStringToJstring(env, cookies); +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return jstring(); +#endif +} + +static bool hasCookies(JNIEnv*, jobject, jboolean privateBrowsing) +{ +#if USE(CHROME_NETWORK_STACK) + return WebCookieJar::get(privateBrowsing)->getNumCookiesInDatabase() > 0; +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return false; +#endif +} + +static void removeAllCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + WebCookieJar::get(false)->cookieStore()->GetCookieMonster()->DeleteAll(true); + // This will lazily create a new private browsing context. However, if the + // context doesn't already exist, there's no need to create it, as cookies + // for such contexts are cleared up when we're done with them. + // TODO: Consider adding an optimisation to not create the context if it + // doesn't already exist. + WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->DeleteAll(true); + + // The Java code removes cookies directly from the backing database, so we do the same, + // but with a NULL callback so it's asynchronous. + WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->FlushStore(NULL); +#endif +} + +static void removeExpiredCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + // This simply forces a GC. The getters delete expired cookies so won't return expired cookies anyway. + WebCookieJar::get(false)->cookieStore()->GetCookieMonster()->GetAllCookies(); + WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->GetAllCookies(); +#endif +} + +static void removeSessionCookies(WebCookieJar* cookieJar) +{ +#if USE(CHROME_NETWORK_STACK) + CookieMonster* cookieMonster = cookieJar->cookieStore()->GetCookieMonster(); + CookieMonster::CookieList cookies = cookieMonster->GetAllCookies(); + for (CookieMonster::CookieList::const_iterator iter = cookies.begin(); iter != cookies.end(); ++iter) { + if (iter->IsSessionCookie()) + cookieMonster->DeleteCanonicalCookie(*iter); + } +#endif +} + +static void removeSessionCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + removeSessionCookies(WebCookieJar::get(false)); + removeSessionCookies(WebCookieJar::get(true)); +#endif +} + +static void setAcceptCookie(JNIEnv*, jobject, jboolean accept) +{ +#if USE(CHROME_NETWORK_STACK) + // This is a static method which configures the cookie policy for all + // WebViews, so we configure the contexts for both regular and private + // browsing. + WebCookieJar::get(false)->setAllowCookies(accept); + WebCookieJar::get(true)->setAllowCookies(accept); +#endif +} + +static void setCookie(JNIEnv* env, jobject, jstring url, jstring value, jboolean privateBrowsing) +{ +#if USE(CHROME_NETWORK_STACK) + GURL gurl(jstringToStdString(env, url)); + std::string line(jstringToStdString(env, value)); + CookieOptions options; + options.set_include_httponly(); + WebCookieJar::get(privateBrowsing)->cookieStore()->GetCookieMonster()->SetCookieWithOptions(gurl, line, options); +#endif +} + +static void flushCookieStore(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + WebCookieJar::flush(); +#endif +} + +static bool acceptFileSchemeCookies(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + return WebCookieJar::acceptFileSchemeCookies(); +#else + // File scheme cookies are always accepted with the Android HTTP stack. + return true; +#endif +} + +static void setAcceptFileSchemeCookies(JNIEnv*, jobject, jboolean accept) +{ +#if USE(CHROME_NETWORK_STACK) + WebCookieJar::setAcceptFileSchemeCookies(accept); +#else + // File scheme cookies are always accepted with the Android HTTP stack. +#endif +} + +static JNINativeMethod gCookieManagerMethods[] = { + { "nativeAcceptCookie", "()Z", (void*) acceptCookie }, + { "nativeGetCookie", "(Ljava/lang/String;Z)Ljava/lang/String;", (void*) getCookie }, + { "nativeHasCookies", "(Z)Z", (void*) hasCookies }, + { "nativeRemoveAllCookie", "()V", (void*) removeAllCookie }, + { "nativeRemoveExpiredCookie", "()V", (void*) removeExpiredCookie }, + { "nativeRemoveSessionCookie", "()V", (void*) removeSessionCookie }, + { "nativeSetAcceptCookie", "(Z)V", (void*) setAcceptCookie }, + { "nativeSetCookie", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*) setCookie }, + { "nativeFlushCookieStore", "()V", (void*) flushCookieStore }, + { "nativeAcceptFileSchemeCookies", "()Z", (void*) acceptFileSchemeCookies }, + { "nativeSetAcceptFileSchemeCookies", "(Z)V", (void*) setAcceptFileSchemeCookies }, +}; + +int registerCookieManager(JNIEnv* env) +{ +#ifndef NDEBUG + jclass cookieManager = env->FindClass(javaCookieManagerClass); + LOG_ASSERT(cookieManager, "Unable to find class"); + env->DeleteLocalRef(cookieManager); +#endif + return jniRegisterNativeMethods(env, javaCookieManagerClass, gCookieManagerMethods, NELEM(gCookieManagerMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp new file mode 100644 index 0000000..8beb372 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "DeviceMotionAndOrientationManager.h" + +#include "DeviceMotionClientImpl.h" +#include "DeviceOrientationClientImpl.h" +#include "DeviceOrientationController.h" +#include "WebViewCore.h" +#include "Frame.h" +#include "Page.h" + +#include <DeviceOrientationClientMock.h> +#include <JNIHelp.h> + +using namespace WebCore; + +namespace android { + +DeviceMotionAndOrientationManager::DeviceMotionAndOrientationManager(WebViewCore* webViewCore) + : m_useMock(false) + , m_webViewCore(webViewCore) +{ +} + +void DeviceMotionAndOrientationManager::useMock() +{ + m_useMock = true; +} + +void DeviceMotionAndOrientationManager::setMockMotion(PassRefPtr<DeviceMotionData> motion) +{ + // TODO: There is not yet a DeviceMotion mock. +} + +void DeviceMotionAndOrientationManager::onMotionChange(PassRefPtr<DeviceMotionData> motion) +{ + ASSERT(!m_useMock); + static_cast<DeviceMotionClientImpl*>(m_motionClient.get())->onMotionChange(motion); +} + +void DeviceMotionAndOrientationManager::setMockOrientation(PassRefPtr<DeviceOrientation> orientation) +{ + if (m_useMock) + static_cast<DeviceOrientationClientMock*>(orientationClient())->setOrientation(orientation); +} + +void DeviceMotionAndOrientationManager::onOrientationChange(PassRefPtr<DeviceOrientation> orientation) +{ + ASSERT(!m_useMock); + static_cast<DeviceOrientationClientImpl*>(m_orientationClient.get())->onOrientationChange(orientation); +} + +void DeviceMotionAndOrientationManager::maybeSuspendClients() +{ + if (!m_useMock) { + if (m_motionClient) + static_cast<DeviceMotionClientImpl*>(m_motionClient.get())->suspend(); + if (m_orientationClient) + static_cast<DeviceOrientationClientImpl*>(m_orientationClient.get())->suspend(); + } +} + +void DeviceMotionAndOrientationManager::maybeResumeClients() +{ + if (!m_useMock) { + if (m_motionClient) + static_cast<DeviceMotionClientImpl*>(m_motionClient.get())->resume(); + if (m_orientationClient) + static_cast<DeviceOrientationClientImpl*>(m_orientationClient.get())->resume(); + } +} + +DeviceMotionClient* DeviceMotionAndOrientationManager::motionClient() +{ + // TODO: There is not yet a DeviceMotion mock. + if (!m_motionClient) + m_motionClient.set(m_useMock ? 0 + : static_cast<DeviceMotionClient*>(new DeviceMotionClientImpl(m_webViewCore))); + ASSERT(m_motionClient); + return m_motionClient.get(); +} + +DeviceOrientationClient* DeviceMotionAndOrientationManager::orientationClient() +{ + if (!m_orientationClient) + m_orientationClient.set(m_useMock ? new DeviceOrientationClientMock + : static_cast<DeviceOrientationClient*>(new DeviceOrientationClientImpl(m_webViewCore))); + ASSERT(m_orientationClient); + return m_orientationClient.get(); +} + +// JNI for android.webkit.DeviceMotionAndOrientationManager +static const char* javaDeviceMotionAndOrientationManagerClass = "android/webkit/DeviceMotionAndOrientationManager"; + +static WebViewCore* getWebViewCore(JNIEnv* env, jobject webViewCoreObject) +{ + jclass webViewCoreClass = env->FindClass("android/webkit/WebViewCore"); + jfieldID nativeClassField = env->GetFieldID(webViewCoreClass, "mNativeClass", "I"); + env->DeleteLocalRef(webViewCoreClass); + return reinterpret_cast<WebViewCore*>(env->GetIntField(webViewCoreObject, nativeClassField)); +} + +static void useMock(JNIEnv* env, jobject, jobject webViewCoreObject) +{ + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->useMock(); +} + +static void onMotionChange(JNIEnv* env, jobject, jobject webViewCoreObject, bool canProvideX, double x, bool canProvideY, double y, bool canProvideZ, double z, double interval) +{ + // We only provide accelerationIncludingGravity. + RefPtr<DeviceMotionData::Acceleration> accelerationIncludingGravity = DeviceMotionData::Acceleration::create(canProvideX, x, canProvideY, y, canProvideZ, z); + bool canProvideInterval = canProvideX || canProvideY || canProvideZ; + RefPtr<DeviceMotionData> motion = DeviceMotionData::create(0, accelerationIncludingGravity.release(), 0, canProvideInterval, interval); + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->onMotionChange(motion.release()); +} + +static void setMockOrientation(JNIEnv* env, jobject, jobject webViewCoreObject, bool canProvideAlpha, double alpha, bool canProvideBeta, double beta, bool canProvideGamma, double gamma) +{ + RefPtr<DeviceOrientation> orientation = DeviceOrientation::create(canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma); + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->setMockOrientation(orientation.release()); +} + +static void onOrientationChange(JNIEnv* env, jobject, jobject webViewCoreObject, bool canProvideAlpha, double alpha, bool canProvideBeta, double beta, bool canProvideGamma, double gamma) +{ + RefPtr<DeviceOrientation> orientation = DeviceOrientation::create(canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma); + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->onOrientationChange(orientation.release()); +} + +static JNINativeMethod gDeviceMotionAndOrientationManagerMethods[] = { + { "nativeUseMock", "(Landroid/webkit/WebViewCore;)V", (void*) useMock }, + { "nativeOnMotionChange", "(Landroid/webkit/WebViewCore;ZDZDZDD)V", (void*) onMotionChange }, + { "nativeSetMockOrientation", "(Landroid/webkit/WebViewCore;ZDZDZD)V", (void*) setMockOrientation }, + { "nativeOnOrientationChange", "(Landroid/webkit/WebViewCore;ZDZDZD)V", (void*) onOrientationChange } +}; + +int registerDeviceMotionAndOrientationManager(JNIEnv* env) +{ +#ifndef NDEBUG + jclass deviceMotionAndOrientationManager = env->FindClass(javaDeviceMotionAndOrientationManagerClass); + LOG_ASSERT(deviceMotionAndOrientationManager, "Unable to find class"); + env->DeleteLocalRef(deviceMotionAndOrientationManager); +#endif + + return jniRegisterNativeMethods(env, javaDeviceMotionAndOrientationManagerClass, gDeviceMotionAndOrientationManagerMethods, NELEM(gDeviceMotionAndOrientationManagerMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h new file mode 100644 index 0000000..44463c1 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h @@ -0,0 +1,68 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef DeviceMotionAndOrientationManager_h +#define DeviceMotionAndOrientationManager_h + +#include <DeviceMotionData.h> +#include <DeviceMotionClient.h> +#include <DeviceOrientation.h> +#include <DeviceOrientationClient.h> +#include <OwnPtr.h> +#include <PassRefPtr.h> +#include <RefPtr.h> + +namespace android { + +class WebViewCore; + +// This class takes care of the fact that the clients used for DeviceMotion and +// DeviceOrientation may be either the real implementations or mocks. It also +// handles setting the data on both the real and mock clients. This class is +// owned by WebViewCore and exists to keep cruft out of that class. +class DeviceMotionAndOrientationManager { +public: + DeviceMotionAndOrientationManager(WebViewCore*); + + void useMock(); + void setMockMotion(PassRefPtr<WebCore::DeviceMotionData>); + void onMotionChange(PassRefPtr<WebCore::DeviceMotionData>); + void setMockOrientation(PassRefPtr<WebCore::DeviceOrientation>); + void onOrientationChange(PassRefPtr<WebCore::DeviceOrientation>); + void maybeSuspendClients(); + void maybeResumeClients(); + WebCore::DeviceMotionClient* motionClient(); + WebCore::DeviceOrientationClient* orientationClient(); + +private: + bool m_useMock; + WebViewCore* m_webViewCore; + OwnPtr<WebCore::DeviceMotionClient> m_motionClient; + OwnPtr<WebCore::DeviceOrientationClient> m_orientationClient; +}; + +} // namespace android + +#endif // DeviceMotionAndOrientationManager_h diff --git a/Source/WebKit/android/jni/DeviceMotionClientImpl.cpp b/Source/WebKit/android/jni/DeviceMotionClientImpl.cpp new file mode 100644 index 0000000..82f3c35 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionClientImpl.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "DeviceMotionClientImpl.h" + +#include "WebViewCore.h" +#include <DeviceMotionController.h> +#include <Frame.h> +#include <JNIHelp.h> + +namespace android { + +using JSC::Bindings::getJNIEnv; + +enum javaServiceClassMethods { + ServiceMethodStart = 0, + ServiceMethodStop, + ServiceMethodSuspend, + ServiceMethodResume, + ServiceMethodCount +}; +static jmethodID javaServiceClassMethodIDs[ServiceMethodCount]; + +DeviceMotionClientImpl::DeviceMotionClientImpl(WebViewCore* webViewCore) + : m_webViewCore(webViewCore) + , m_javaServiceObject(0) +{ + ASSERT(m_webViewCore); +} + +DeviceMotionClientImpl::~DeviceMotionClientImpl() +{ + releaseJavaInstance(); +} + +jobject DeviceMotionClientImpl::getJavaInstance() +{ + // Lazily get the Java object. We can't do this until the WebViewCore is all + // set up. + if (m_javaServiceObject) + return m_javaServiceObject; + + JNIEnv* env = getJNIEnv(); + + ASSERT(m_webViewCore); + jobject object = m_webViewCore->getDeviceMotionService(); + + // Get the Java DeviceMotionService class. + jclass javaServiceClass = env->GetObjectClass(object); + ASSERT(javaServiceClass); + + // Set up the methods we wish to call on the Java DeviceMotionService + // class. + javaServiceClassMethodIDs[ServiceMethodStart] = + env->GetMethodID(javaServiceClass, "start", "()V"); + javaServiceClassMethodIDs[ServiceMethodStop] = + env->GetMethodID(javaServiceClass, "stop", "()V"); + javaServiceClassMethodIDs[ServiceMethodSuspend] = + env->GetMethodID(javaServiceClass, "suspend", "()V"); + javaServiceClassMethodIDs[ServiceMethodResume] = + env->GetMethodID(javaServiceClass, "resume", "()V"); + env->DeleteLocalRef(javaServiceClass); + + m_javaServiceObject = getJNIEnv()->NewGlobalRef(object); + getJNIEnv()->DeleteLocalRef(object); + + ASSERT(m_javaServiceObject); + return m_javaServiceObject; +} + +void DeviceMotionClientImpl::releaseJavaInstance() +{ + ASSERT(m_javaServiceObject); + getJNIEnv()->DeleteGlobalRef(m_javaServiceObject); +} + +void DeviceMotionClientImpl::startUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodStart]); +} + +void DeviceMotionClientImpl::stopUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodStop]); +} + +void DeviceMotionClientImpl::onMotionChange(PassRefPtr<DeviceMotionData> motion) +{ + m_lastMotion = motion; + m_controller->didChangeDeviceMotion(m_lastMotion.get()); +} + +void DeviceMotionClientImpl::suspend() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodSuspend]); +} + +void DeviceMotionClientImpl::resume() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodResume]); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceMotionClientImpl.h b/Source/WebKit/android/jni/DeviceMotionClientImpl.h new file mode 100644 index 0000000..c979098 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionClientImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef DeviceMotionClientImpl_h +#define DeviceMotionClientImpl_h + +#include <DeviceMotionClient.h> +#include <DeviceMotionData.h> +#include <JNIUtility.h> +#include <PassRefPtr.h> +#include <RefPtr.h> + +using namespace WebCore; + +namespace android { + +class DeviceMotionAndOrientationManager; +class WebViewCore; + +class DeviceMotionClientImpl : public DeviceMotionClient { +public: + DeviceMotionClientImpl(WebViewCore*); + virtual ~DeviceMotionClientImpl(); + + void onMotionChange(PassRefPtr<DeviceMotionData>); + void suspend(); + void resume(); + + // DeviceMotionClient methods + virtual void startUpdating(); + virtual void stopUpdating(); + virtual DeviceMotionData* currentDeviceMotion() const { return m_lastMotion.get(); } + virtual void setController(DeviceMotionController* controller) { m_controller = controller; } + virtual void deviceMotionControllerDestroyed() { } + +private: + jobject getJavaInstance(); + void releaseJavaInstance(); + + WebViewCore* m_webViewCore; + jobject m_javaServiceObject; + DeviceMotionController* m_controller; + RefPtr<DeviceMotionData> m_lastMotion; +}; + +} // namespace android + +#endif // DeviceMotionClientImpl_h diff --git a/Source/WebKit/android/jni/DeviceOrientationClientImpl.cpp b/Source/WebKit/android/jni/DeviceOrientationClientImpl.cpp new file mode 100644 index 0000000..bf3b3c3 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceOrientationClientImpl.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" +#include "DeviceOrientationClientImpl.h" + +#include "WebViewCore.h" +#include <DeviceOrientationController.h> +#include <Frame.h> +#include <JNIHelp.h> + +namespace android { + +using JSC::Bindings::getJNIEnv; + +enum javaDeviceOrientationServiceClassMethods { + DeviceOrientationServiceMethodStart = 0, + DeviceOrientationServiceMethodStop, + DeviceOrientationServiceMethodSuspend, + DeviceOrientationServiceMethodResume, + DeviceOrientationServiceMethodCount +}; +static jmethodID javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodCount]; + +DeviceOrientationClientImpl::DeviceOrientationClientImpl(WebViewCore* webViewCore) + : m_webViewCore(webViewCore) + , m_javaDeviceOrientationServiceObject(0) +{ + ASSERT(m_webViewCore); +} + +DeviceOrientationClientImpl::~DeviceOrientationClientImpl() +{ + releaseJavaInstance(); +} + +jobject DeviceOrientationClientImpl::getJavaInstance() +{ + // Lazily get the Java object. We can't do this until the WebViewCore is all + // set up. + if (m_javaDeviceOrientationServiceObject) + return m_javaDeviceOrientationServiceObject; + + JNIEnv* env = getJNIEnv(); + + ASSERT(m_webViewCore); + jobject object = m_webViewCore->getDeviceOrientationService(); + + // Get the Java DeviceOrientationService class. + jclass javaDeviceOrientationServiceClass = env->GetObjectClass(object); + ASSERT(javaDeviceOrientationServiceClass); + + // Set up the methods we wish to call on the Java DeviceOrientationService + // class. + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStart] = + env->GetMethodID(javaDeviceOrientationServiceClass, "start", "()V"); + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStop] = + env->GetMethodID(javaDeviceOrientationServiceClass, "stop", "()V"); + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodSuspend] = + env->GetMethodID(javaDeviceOrientationServiceClass, "suspend", "()V"); + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodResume] = + env->GetMethodID(javaDeviceOrientationServiceClass, "resume", "()V"); + env->DeleteLocalRef(javaDeviceOrientationServiceClass); + + m_javaDeviceOrientationServiceObject = getJNIEnv()->NewGlobalRef(object); + getJNIEnv()->DeleteLocalRef(object); + + ASSERT(m_javaDeviceOrientationServiceObject); + return m_javaDeviceOrientationServiceObject; +} + +void DeviceOrientationClientImpl::releaseJavaInstance() +{ + ASSERT(m_javaDeviceOrientationServiceObject); + getJNIEnv()->DeleteGlobalRef(m_javaDeviceOrientationServiceObject); +} + +void DeviceOrientationClientImpl::startUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStart]); +} + +void DeviceOrientationClientImpl::stopUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStop]); +} + +void DeviceOrientationClientImpl::onOrientationChange(PassRefPtr<DeviceOrientation> orientation) +{ + m_lastOrientation = orientation; + m_controller->didChangeDeviceOrientation(m_lastOrientation.get()); +} + +void DeviceOrientationClientImpl::suspend() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodSuspend]); +} + +void DeviceOrientationClientImpl::resume() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodResume]); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceOrientationClientImpl.h b/Source/WebKit/android/jni/DeviceOrientationClientImpl.h new file mode 100644 index 0000000..0e3f6b3 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceOrientationClientImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef DeviceOrientationClientImpl_h +#define DeviceOrientationClientImpl_h + +#include <DeviceOrientation.h> +#include <DeviceOrientationClient.h> +#include <JNIUtility.h> +#include <PassRefPtr.h> +#include <RefPtr.h> + +using namespace WebCore; + +namespace android { + +class DeviceMotionAndOrientationManager; +class WebViewCore; + +class DeviceOrientationClientImpl : public DeviceOrientationClient { +public: + DeviceOrientationClientImpl(WebViewCore*); + virtual ~DeviceOrientationClientImpl(); + + void onOrientationChange(PassRefPtr<DeviceOrientation>); + void suspend(); + void resume(); + + // DeviceOrientationClient methods + virtual void startUpdating(); + virtual void stopUpdating(); + virtual DeviceOrientation* lastOrientation() const { return m_lastOrientation.get(); } + virtual void setController(DeviceOrientationController* controller) { m_controller = controller; } + virtual void deviceOrientationControllerDestroyed() { } + +private: + jobject getJavaInstance(); + void releaseJavaInstance(); + + WebViewCore* m_webViewCore; + jobject m_javaDeviceOrientationServiceObject; + DeviceOrientationController* m_controller; + RefPtr<DeviceOrientation> m_lastOrientation; +}; + +} // namespace android + +#endif // DeviceOrientationClientImpl_h diff --git a/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp b/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp new file mode 100755 index 0000000..a366601 --- /dev/null +++ b/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2009, 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. + */ + +#include "config.h" + +#include <JNIHelp.h> // For jniRegisterNativeMethods +#include "GeolocationPermissions.h" +#include "WebCoreJni.h" // For jstringToWtfString + + +/** + * This file provides a set of functions to bridge between the Java and C++ + * GeolocationPermissions classes. The java GeolocationPermissions object calls + * the functions provided here, which in turn call static methods on the C++ + * GeolocationPermissions class. + */ + +namespace android { + +static jobject getOrigins(JNIEnv* env, jobject obj) +{ + GeolocationPermissions::OriginSet origins = GeolocationPermissions::getOrigins(); + + jclass setClass = env->FindClass("java/util/HashSet"); + jmethodID constructor = env->GetMethodID(setClass, "<init>", "()V"); + jmethodID addMethod = env->GetMethodID(setClass, "add", "(Ljava/lang/Object;)Z"); + jobject set = env->NewObject(setClass, constructor); + env->DeleteLocalRef(setClass); + + GeolocationPermissions::OriginSet::const_iterator end = origins.end(); + for (GeolocationPermissions::OriginSet::const_iterator iter = origins.begin(); iter != end; ++iter) { + jstring originString = wtfStringToJstring(env, *iter); + env->CallBooleanMethod(set, addMethod, originString); + env->DeleteLocalRef(originString); + } + return set; +} + +static bool getAllowed(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originString = jstringToWtfString(env, origin); + return GeolocationPermissions::getAllowed(originString); +} + +static void clear(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originString = jstringToWtfString(env, origin); + GeolocationPermissions::clear(originString); +} + +static void allow(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originString = jstringToWtfString(env, origin); + GeolocationPermissions::allow(originString); +} + +static void clearAll(JNIEnv* env, jobject obj) +{ + GeolocationPermissions::clearAll(); +} + +/* + * JNI registration + */ +static JNINativeMethod gGeolocationPermissionsMethods[] = { + { "nativeGetOrigins", "()Ljava/util/Set;", + (void*) getOrigins }, + { "nativeGetAllowed", "(Ljava/lang/String;)Z", + (void*) getAllowed }, + { "nativeClear", "(Ljava/lang/String;)V", + (void*) clear }, + { "nativeAllow", "(Ljava/lang/String;)V", + (void*) allow }, + { "nativeClearAll", "()V", + (void*) clearAll } +}; + +int registerGeolocationPermissions(JNIEnv* env) +{ + const char* kGeolocationPermissionsClass = "android/webkit/GeolocationPermissions"; +#ifndef NDEBUG + jclass geolocationPermissions = env->FindClass(kGeolocationPermissionsClass); + LOG_ASSERT(geolocationPermissions, "Unable to find class"); + env->DeleteLocalRef(geolocationPermissions); +#endif + + return jniRegisterNativeMethods(env, kGeolocationPermissionsClass, + gGeolocationPermissionsMethods, NELEM(gGeolocationPermissionsMethods)); +} + +} diff --git a/Source/WebKit/android/jni/JavaBridge.cpp b/Source/WebKit/android/jni/JavaBridge.cpp new file mode 100644 index 0000000..2fa12fc --- /dev/null +++ b/Source/WebKit/android/jni/JavaBridge.cpp @@ -0,0 +1,514 @@ +/* + * 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 "MemoryCache.h" +#include "Connection.h" +#include "CookieClient.h" +#include "FileSystemClient.h" +#include "JavaSharedClient.h" +#include "KeyGeneratorClient.h" +#include "KURL.h" +#include "NetworkStateNotifier.h" +#include "PackageNotifier.h" +#include "Page.h" +#include "PluginClient.h" +#include "PluginDatabase.h" +#include "Timer.h" +#include "TimerClient.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif +#include "WebCache.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SkUtils.h> +#include <jni.h> +#include <utils/misc.h> +#include <wtf/Platform.h> +#include <wtf/StdLibExtras.h> +#include <wtf/text/AtomicString.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static jfieldID gJavaBridge_ObjectID; + +// ---------------------------------------------------------------------------- + +class JavaBridge : public TimerClient, public CookieClient, public PluginClient, public KeyGeneratorClient, public FileSystemClient +{ +public: + JavaBridge(JNIEnv* env, jobject obj); + virtual ~JavaBridge(); + + /* + * WebCore -> Java API + */ + virtual void setSharedTimer(long long timemillis); + virtual void stopSharedTimer(); + + virtual void setCookies(WebCore::KURL const& url, WTF::String const& value); + virtual WTF::String cookies(WebCore::KURL const& url); + virtual bool cookiesEnabled(); + + virtual WTF::Vector<WTF::String> getPluginDirectories(); + virtual WTF::String getPluginSharedDataDirectory(); + + virtual WTF::Vector<String> getSupportedKeyStrengthList(); + virtual WTF::String getSignedPublicKeyAndChallengeString(unsigned index, + const WTF::String& challenge, const WebCore::KURL& url); + virtual WTF::String resolveFilePathForContentUri(const WTF::String& uri); + + //////////////////////////////////////////// + + virtual void setSharedTimerCallback(void (*f)()); + + //////////////////////////////////////////// + + virtual void signalServiceFuncPtrQueue(); + + // jni functions + static void Constructor(JNIEnv* env, jobject obj); + static void Finalize(JNIEnv* env, jobject obj); + static void SharedTimerFired(JNIEnv* env, jobject); + static void SetCacheSize(JNIEnv* env, jobject obj, jint bytes); + static void SetNetworkOnLine(JNIEnv* env, jobject obj, jboolean online); + static void SetNetworkType(JNIEnv* env, jobject obj, jstring type, jstring subtype); + static void SetDeferringTimers(JNIEnv* env, jobject obj, jboolean defer); + static void ServiceFuncPtrQueue(JNIEnv*); + static void UpdatePluginDirectories(JNIEnv* env, jobject obj, jobjectArray array, jboolean reload); + static void AddPackageNames(JNIEnv* env, jobject obj, jobject packageNames); + static void AddPackageName(JNIEnv* env, jobject obj, jstring packageName); + static void RemovePackageName(JNIEnv* env, jobject obj, jstring packageName); + static void UpdateProxy(JNIEnv* env, jobject obj, jstring newProxy); + + +private: + jweak mJavaObject; + jmethodID mSetSharedTimer; + jmethodID mStopSharedTimer; + jmethodID mSetCookies; + jmethodID mCookies; + jmethodID mCookiesEnabled; + jmethodID mGetPluginDirectories; + jmethodID mGetPluginSharedDataDirectory; + jmethodID mSignalFuncPtrQueue; + jmethodID mGetKeyStrengthList; + jmethodID mGetSignedPublicKey; + jmethodID mResolveFilePathForContentUri; + AutoJObject javaObject(JNIEnv* env) { return getRealObject(env, mJavaObject); } +}; + +static void (*sSharedTimerFiredCallback)(); + +JavaBridge::JavaBridge(JNIEnv* env, jobject obj) +{ + mJavaObject = env->NewWeakGlobalRef(obj); + jclass clazz = env->GetObjectClass(obj); + + mSetSharedTimer = env->GetMethodID(clazz, "setSharedTimer", "(J)V"); + mStopSharedTimer = env->GetMethodID(clazz, "stopSharedTimer", "()V"); + mSetCookies = env->GetMethodID(clazz, "setCookies", "(Ljava/lang/String;Ljava/lang/String;)V"); + mCookies = env->GetMethodID(clazz, "cookies", "(Ljava/lang/String;)Ljava/lang/String;"); + mCookiesEnabled = env->GetMethodID(clazz, "cookiesEnabled", "()Z"); + mGetPluginDirectories = env->GetMethodID(clazz, "getPluginDirectories", "()[Ljava/lang/String;"); + mGetPluginSharedDataDirectory = env->GetMethodID(clazz, "getPluginSharedDataDirectory", "()Ljava/lang/String;"); + mSignalFuncPtrQueue = env->GetMethodID(clazz, "signalServiceFuncPtrQueue", "()V"); + mGetKeyStrengthList = env->GetMethodID(clazz, "getKeyStrengthList", "()[Ljava/lang/String;"); + mGetSignedPublicKey = env->GetMethodID(clazz, "getSignedPublicKey", "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + mResolveFilePathForContentUri = env->GetMethodID(clazz, "resolveFilePathForContentUri", "(Ljava/lang/String;)Ljava/lang/String;"); + env->DeleteLocalRef(clazz); + + LOG_ASSERT(mSetSharedTimer, "Could not find method setSharedTimer"); + LOG_ASSERT(mStopSharedTimer, "Could not find method stopSharedTimer"); + LOG_ASSERT(mSetCookies, "Could not find method setCookies"); + LOG_ASSERT(mCookies, "Could not find method cookies"); + LOG_ASSERT(mCookiesEnabled, "Could not find method cookiesEnabled"); + LOG_ASSERT(mGetPluginDirectories, "Could not find method getPluginDirectories"); + LOG_ASSERT(mGetPluginSharedDataDirectory, "Could not find method getPluginSharedDataDirectory"); + LOG_ASSERT(mGetKeyStrengthList, "Could not find method getKeyStrengthList"); + LOG_ASSERT(mGetSignedPublicKey, "Could not find method getSignedPublicKey"); + + JavaSharedClient::SetTimerClient(this); + JavaSharedClient::SetCookieClient(this); + JavaSharedClient::SetPluginClient(this); + JavaSharedClient::SetKeyGeneratorClient(this); + JavaSharedClient::SetFileSystemClient(this); +} + +JavaBridge::~JavaBridge() +{ + if (mJavaObject) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteWeakGlobalRef(mJavaObject); + mJavaObject = 0; + } + + JavaSharedClient::SetTimerClient(NULL); + JavaSharedClient::SetCookieClient(NULL); + JavaSharedClient::SetPluginClient(NULL); + JavaSharedClient::SetKeyGeneratorClient(NULL); + JavaSharedClient::SetFileSystemClient(NULL); +} + +void +JavaBridge::setSharedTimer(long long timemillis) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mSetSharedTimer, timemillis); +} + +void +JavaBridge::stopSharedTimer() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mStopSharedTimer); +} + +void +JavaBridge::setCookies(WebCore::KURL const& url, WTF::String const& value) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + const WTF::String& urlStr = url.string(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jstring jValueStr = wtfStringToJstring(env, value); + + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mSetCookies, jUrlStr, jValueStr); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jValueStr); +} + +WTF::String +JavaBridge::cookies(WebCore::KURL const& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + const WTF::String& urlStr = url.string(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + + AutoJObject obj = javaObject(env); + jstring string = (jstring)(env->CallObjectMethod(obj.get(), mCookies, jUrlStr)); + + WTF::String ret = jstringToWtfString(env, string); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(string); + return ret; +} + +bool +JavaBridge::cookiesEnabled() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jboolean ret = env->CallBooleanMethod(obj.get(), mCookiesEnabled); + return (ret != 0); +} + +WTF::Vector<WTF::String> +JavaBridge::getPluginDirectories() +{ + WTF::Vector<WTF::String> directories; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jobjectArray array = (jobjectArray) + env->CallObjectMethod(obj.get(), mGetPluginDirectories); + int count = env->GetArrayLength(array); + for (int i = 0; i < count; i++) { + jstring dir = (jstring) env->GetObjectArrayElement(array, i); + directories.append(jstringToWtfString(env, dir)); + env->DeleteLocalRef(dir); + } + env->DeleteLocalRef(array); + checkException(env); + return directories; +} + +WTF::String +JavaBridge::getPluginSharedDataDirectory() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jstring ret = (jstring)env->CallObjectMethod(obj.get(), mGetPluginSharedDataDirectory); + WTF::String path = jstringToWtfString(env, ret); + checkException(env); + return path; +} + +void +JavaBridge::setSharedTimerCallback(void (*f)()) +{ + LOG_ASSERT(!sSharedTimerFiredCallback || sSharedTimerFiredCallback==f, + "Shared timer callback may already be set or null!"); + + sSharedTimerFiredCallback = f; +} + +void JavaBridge::signalServiceFuncPtrQueue() +{ + // In order to signal the main thread we must go through JNI. This + // is the only usage on most threads, so we need to ensure a JNI + // environment is setup. + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mSignalFuncPtrQueue); +} + +WTF::Vector<WTF::String>JavaBridge::getSupportedKeyStrengthList() { + WTF::Vector<WTF::String> list; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jobjectArray array = (jobjectArray) env->CallObjectMethod(obj.get(), + mGetKeyStrengthList); + int count = env->GetArrayLength(array); + for (int i = 0; i < count; ++i) { + jstring keyStrength = (jstring) env->GetObjectArrayElement(array, i); + list.append(jstringToWtfString(env, keyStrength)); + env->DeleteLocalRef(keyStrength); + } + env->DeleteLocalRef(array); + checkException(env); + return list; +} + +WTF::String JavaBridge::getSignedPublicKeyAndChallengeString(unsigned index, + const WTF::String& challenge, const WebCore::KURL& url) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jChallenge = wtfStringToJstring(env, challenge); + const WTF::String& urlStr = url.string(); + jstring jUrl = wtfStringToJstring(env, urlStr); + AutoJObject obj = javaObject(env); + jstring key = (jstring) env->CallObjectMethod(obj.get(), + mGetSignedPublicKey, index, jChallenge, jUrl); + WTF::String ret = jstringToWtfString(env, key); + env->DeleteLocalRef(jChallenge); + env->DeleteLocalRef(jUrl); + env->DeleteLocalRef(key); + return ret; +} + +WTF::String JavaBridge::resolveFilePathForContentUri(const WTF::String& uri) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUri = wtfStringToJstring(env, uri); + AutoJObject obj = javaObject(env); + jstring path = static_cast<jstring>(env->CallObjectMethod(obj.get(), mResolveFilePathForContentUri, jUri)); + WTF::String ret = jstringToWtfString(env, path); + env->DeleteLocalRef(jUri); + env->DeleteLocalRef(path); + return ret; +} + +// ---------------------------------------------------------------------------- + +void JavaBridge::Constructor(JNIEnv* env, jobject obj) +{ + JavaBridge* javaBridge = new JavaBridge(env, obj); + env->SetIntField(obj, gJavaBridge_ObjectID, (jint)javaBridge); +} + +void JavaBridge::Finalize(JNIEnv* env, jobject obj) +{ + JavaBridge* javaBridge = (JavaBridge*) + (env->GetIntField(obj, gJavaBridge_ObjectID)); + LOG_ASSERT(javaBridge, "Finalize should not be called twice for the same java bridge!"); + LOGV("webcore_javabridge::nativeFinalize(%p)\n", javaBridge); + delete javaBridge; + env->SetIntField(obj, gJavaBridge_ObjectID, 0); +} + +// we don't use the java bridge object, as we're just looking at a global +void JavaBridge::SharedTimerFired(JNIEnv* env, jobject) +{ + if (sSharedTimerFiredCallback) + { +#ifdef ANDROID_INSTRUMENT + TimeCounter::start(TimeCounter::SharedTimerTimeCounter); +#endif + SkAutoMemoryUsageProbe mup("JavaBridge::sharedTimerFired"); + sSharedTimerFiredCallback(); +#ifdef ANDROID_INSTRUMENT + TimeCounter::record(TimeCounter::SharedTimerTimeCounter, __FUNCTION__); +#endif + } +} + +void JavaBridge::SetCacheSize(JNIEnv* env, jobject obj, jint bytes) +{ + WebCore::cache()->setCapacities(0, bytes/2, bytes); +} + +void JavaBridge::SetNetworkOnLine(JNIEnv* env, jobject obj, jboolean online) +{ + WebCore::networkStateNotifier().networkStateChange(online); +} + +void JavaBridge::SetNetworkType(JNIEnv* env, jobject obj, jstring javatype, jstring javasubtype) +{ + DEFINE_STATIC_LOCAL(AtomicString, wifi, ("wifi")); + DEFINE_STATIC_LOCAL(AtomicString, mobile, ("mobile")); + DEFINE_STATIC_LOCAL(AtomicString, mobileSupl, ("mobile_supl")); + DEFINE_STATIC_LOCAL(AtomicString, gprs, ("gprs")); + DEFINE_STATIC_LOCAL(AtomicString, edge, ("edge")); + DEFINE_STATIC_LOCAL(AtomicString, umts, ("umts")); + + String type = jstringToWtfString(env, javatype); + String subtype = jstringToWtfString(env, javasubtype); + Connection::ConnectionType connectionType = Connection::UNKNOWN; + if (type == wifi) + connectionType = Connection::WIFI; + else if (type == mobile || type == mobileSupl) { + if (subtype == edge || subtype == gprs) + connectionType = Connection::CELL_2G; + else if (subtype == umts) + connectionType = Connection::CELL_3G; + } + WebCore::networkStateNotifier().networkTypeChange(connectionType); +} + +void JavaBridge::ServiceFuncPtrQueue(JNIEnv*) +{ + JavaSharedClient::ServiceFunctionPtrQueue(); +} + +void JavaBridge::UpdatePluginDirectories(JNIEnv* env, jobject obj, + jobjectArray array, jboolean reload) { + WTF::Vector<WTF::String> directories; + int count = env->GetArrayLength(array); + for (int i = 0; i < count; i++) { + jstring dir = (jstring) env->GetObjectArrayElement(array, i); + directories.append(jstringToWtfString(env, dir)); + env->DeleteLocalRef(dir); + } + checkException(env); + WebCore::PluginDatabase *pluginDatabase = + WebCore::PluginDatabase::installedPlugins(); + pluginDatabase->setPluginDirectories(directories); + // refreshPlugins() should refresh both PluginDatabase and Page's PluginData + WebCore::Page::refreshPlugins(reload); +} + +void JavaBridge::AddPackageNames(JNIEnv* env, jobject obj, jobject packageNames) +{ + if (!packageNames) + return; + + // dalvikvm will raise exception if any of these fail + jclass setClass = env->FindClass("java/util/Set"); + jmethodID iterator = env->GetMethodID(setClass, "iterator", + "()Ljava/util/Iterator;"); + jobject iter = env->CallObjectMethod(packageNames, iterator); + + jclass iteratorClass = env->FindClass("java/util/Iterator"); + jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext", "()Z"); + jmethodID next = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;"); + + HashSet<WTF::String> namesSet; + while (env->CallBooleanMethod(iter, hasNext)) { + jstring name = static_cast<jstring>(env->CallObjectMethod(iter, next)); + namesSet.add(jstringToWtfString(env, name)); + env->DeleteLocalRef(name); + } + + packageNotifier().addPackageNames(namesSet); + + env->DeleteLocalRef(iteratorClass); + env->DeleteLocalRef(iter); + env->DeleteLocalRef(setClass); +} + +void JavaBridge::AddPackageName(JNIEnv* env, jobject obj, jstring packageName) +{ + packageNotifier().addPackageName(jstringToWtfString(env, packageName)); +} + +void JavaBridge::RemovePackageName(JNIEnv* env, jobject obj, jstring packageName) +{ + packageNotifier().removePackageName(jstringToWtfString(env, packageName)); +} + +void JavaBridge::UpdateProxy(JNIEnv* env, jobject obj, jstring newProxy) +{ +#if USE(CHROME_NETWORK_STACK) + std::string proxy = jstringToStdString(env, newProxy); + WebCache::get(false)->proxy()->UpdateProxySettings(proxy); + WebCache::get(true)->proxy()->UpdateProxySettings(proxy); +#endif +} + + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gWebCoreJavaBridgeMethods[] = { + /* name, signature, funcPtr */ + { "nativeConstructor", "()V", + (void*) JavaBridge::Constructor }, + { "nativeFinalize", "()V", + (void*) JavaBridge::Finalize }, + { "sharedTimerFired", "()V", + (void*) JavaBridge::SharedTimerFired }, + { "setCacheSize", "(I)V", + (void*) JavaBridge::SetCacheSize }, + { "setNetworkOnLine", "(Z)V", + (void*) JavaBridge::SetNetworkOnLine }, + { "setNetworkType", "(Ljava/lang/String;Ljava/lang/String;)V", + (void*) JavaBridge::SetNetworkType }, + { "nativeServiceFuncPtrQueue", "()V", + (void*) JavaBridge::ServiceFuncPtrQueue }, + { "nativeUpdatePluginDirectories", "([Ljava/lang/String;Z)V", + (void*) JavaBridge::UpdatePluginDirectories }, + { "addPackageNames", "(Ljava/util/Set;)V", + (void*) JavaBridge::AddPackageNames }, + { "addPackageName", "(Ljava/lang/String;)V", + (void*) JavaBridge::AddPackageName }, + { "removePackageName", "(Ljava/lang/String;)V", + (void*) JavaBridge::RemovePackageName }, + { "updateProxy", "(Ljava/lang/String;)V", + (void*) JavaBridge::UpdateProxy } +}; + +int registerJavaBridge(JNIEnv* env) +{ + jclass javaBridge = env->FindClass("android/webkit/JWebCoreJavaBridge"); + LOG_FATAL_IF(javaBridge == NULL, "Unable to find class android/webkit/JWebCoreJavaBridge"); + gJavaBridge_ObjectID = env->GetFieldID(javaBridge, "mNativeBridge", "I"); + LOG_FATAL_IF(gJavaBridge_ObjectID == NULL, "Unable to find android/webkit/JWebCoreJavaBridge.mNativeBridge"); + env->DeleteLocalRef(javaBridge); + + return jniRegisterNativeMethods(env, "android/webkit/JWebCoreJavaBridge", + gWebCoreJavaBridgeMethods, NELEM(gWebCoreJavaBridgeMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/JavaSharedClient.cpp b/Source/WebKit/android/jni/JavaSharedClient.cpp new file mode 100644 index 0000000..e884c99 --- /dev/null +++ b/Source/WebKit/android/jni/JavaSharedClient.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2007, 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. + */ + +#include "config.h" +#include "FileSystemClient.h" +#include "JavaSharedClient.h" +#include "TimerClient.h" +#include "SkDeque.h" +#include "SkThread.h" + +namespace android { + TimerClient* JavaSharedClient::GetTimerClient() + { + return gTimerClient; + } + + CookieClient* JavaSharedClient::GetCookieClient() + { + return gCookieClient; + } + + PluginClient* JavaSharedClient::GetPluginClient() + { + return gPluginClient; + } + + KeyGeneratorClient* JavaSharedClient::GetKeyGeneratorClient() + { + return gKeyGeneratorClient; + } + + FileSystemClient* JavaSharedClient::GetFileSystemClient() + { + return gFileSystemClient; + } + + void JavaSharedClient::SetTimerClient(TimerClient* client) + { + gTimerClient = client; + } + + void JavaSharedClient::SetCookieClient(CookieClient* client) + { + gCookieClient = client; + } + + void JavaSharedClient::SetPluginClient(PluginClient* client) + { + gPluginClient = client; + } + + void JavaSharedClient::SetKeyGeneratorClient(KeyGeneratorClient* client) + { + gKeyGeneratorClient = client; + } + + void JavaSharedClient::SetFileSystemClient(FileSystemClient* client) + { + gFileSystemClient = client; + } + + TimerClient* JavaSharedClient::gTimerClient = NULL; + CookieClient* JavaSharedClient::gCookieClient = NULL; + PluginClient* JavaSharedClient::gPluginClient = NULL; + KeyGeneratorClient* JavaSharedClient::gKeyGeneratorClient = NULL; + FileSystemClient* JavaSharedClient::gFileSystemClient = NULL; + + /////////////////////////////////////////////////////////////////////////// + + struct FuncPtrRec { + void (*fProc)(void* payload); + void* fPayload; + }; + + static SkMutex gFuncPtrQMutex; + static SkDeque gFuncPtrQ(sizeof(FuncPtrRec)); + + void JavaSharedClient::EnqueueFunctionPtr(void (*proc)(void* payload), + void* payload) + { + gFuncPtrQMutex.acquire(); + + FuncPtrRec* rec = (FuncPtrRec*)gFuncPtrQ.push_back(); + rec->fProc = proc; + rec->fPayload = payload; + + gFuncPtrQMutex.release(); + + gTimerClient->signalServiceFuncPtrQueue(); + } + + void JavaSharedClient::ServiceFunctionPtrQueue() + { + for (;;) { + void (*proc)(void*) = 0; + void* payload = 0; + const FuncPtrRec* rec; + + // we have to copy the proc/payload (if present). we do this so we + // don't call the proc inside the mutex (possible deadlock!) + gFuncPtrQMutex.acquire(); + rec = (const FuncPtrRec*)gFuncPtrQ.front(); + if (rec) { + proc = rec->fProc; + payload = rec->fPayload; + gFuncPtrQ.pop_front(); + } + gFuncPtrQMutex.release(); + + if (!rec) + break; + proc(payload); + } + } +} diff --git a/Source/WebKit/android/jni/JavaSharedClient.h b/Source/WebKit/android/jni/JavaSharedClient.h new file mode 100644 index 0000000..9a09280 --- /dev/null +++ b/Source/WebKit/android/jni/JavaSharedClient.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef JAVA_SHARED_CLIENT_H +#define JAVA_SHARED_CLIENT_H + +namespace android { + + class TimerClient; + class CookieClient; + class PluginClient; + class KeyGeneratorClient; + class FileSystemClient; + + class JavaSharedClient + { + public: + static TimerClient* GetTimerClient(); + static CookieClient* GetCookieClient(); + static PluginClient* GetPluginClient(); + static KeyGeneratorClient* GetKeyGeneratorClient(); + static FileSystemClient* GetFileSystemClient(); + + static void SetTimerClient(TimerClient* client); + static void SetCookieClient(CookieClient* client); + static void SetPluginClient(PluginClient* client); + static void SetKeyGeneratorClient(KeyGeneratorClient* client); + static void SetFileSystemClient(FileSystemClient* client); + + // can be called from any thread, to be executed in webkit thread + static void EnqueueFunctionPtr(void (*proc)(void*), void* payload); + // only call this from webkit thread + static void ServiceFunctionPtrQueue(); + + private: + static TimerClient* gTimerClient; + static CookieClient* gCookieClient; + static PluginClient* gPluginClient; + static KeyGeneratorClient* gKeyGeneratorClient; + static FileSystemClient* gFileSystemClient; + }; +} +#endif diff --git a/Source/WebKit/android/jni/JniUtil.cpp b/Source/WebKit/android/jni/JniUtil.cpp new file mode 100644 index 0000000..ee1e3f9 --- /dev/null +++ b/Source/WebKit/android/jni/JniUtil.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2010, 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. + */ + +#include "config.h" + +#include "ChromiumIncludes.h" +#include <JNIHelp.h> + +namespace android { + +static const char* javaJniUtilClass = "android/webkit/JniUtil"; + +static bool useChromiumHttpStack(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + return true; +#else + return false; +#endif +} + +static JNINativeMethod gJniUtilMethods[] = { + { "nativeUseChromiumHttpStack", "()Z", (void*) useChromiumHttpStack }, +}; + +int registerJniUtil(JNIEnv* env) +{ +#ifndef NDEBUG + jclass jniUtil = env->FindClass(javaJniUtilClass); + LOG_ASSERT(jniUtil, "Unable to find class"); + env->DeleteLocalRef(jniUtil); +#endif + return jniRegisterNativeMethods(env, javaJniUtilClass, gJniUtilMethods, NELEM(gJniUtilMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/MIMETypeRegistry.cpp b/Source/WebKit/android/jni/MIMETypeRegistry.cpp new file mode 100644 index 0000000..40f8cef --- /dev/null +++ b/Source/WebKit/android/jni/MIMETypeRegistry.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2010, 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 "WebCore" + +#include "config.h" +#include "MIMETypeRegistry.h" + +#include "PlatformString.h" +#include "WebCoreJni.h" + +#include <JNIUtility.h> +#include <jni.h> +#include <utils/Log.h> + +using namespace android; + +namespace WebCore { + +String MIMETypeRegistry::getMIMETypeForExtension(const String& ext) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jclass mimeClass = env->FindClass("android/webkit/MimeTypeMap"); + LOG_ASSERT(mimeClass, "Could not find class MimeTypeMap"); + jmethodID mimeTypeFromExtension = env->GetStaticMethodID(mimeClass, + "mimeTypeFromExtension", + "(Ljava/lang/String;)Ljava/lang/String;"); + LOG_ASSERT(mimeTypeFromExtension, + "Could not find method mimeTypeFromExtension"); + jstring extString = wtfStringToJstring(env, ext); + jobject mimeType = env->CallStaticObjectMethod(mimeClass, + mimeTypeFromExtension, extString); + String result = android::jstringToWtfString(env, (jstring) mimeType); + env->DeleteLocalRef(mimeClass); + env->DeleteLocalRef(extString); + env->DeleteLocalRef(mimeType); + return result; +} + +bool MIMETypeRegistry::isApplicationPluginMIMEType(const String&) +{ + return false; +} + +} diff --git a/Source/WebKit/android/jni/MockGeolocation.cpp b/Source/WebKit/android/jni/MockGeolocation.cpp new file mode 100755 index 0000000..1370715 --- /dev/null +++ b/Source/WebKit/android/jni/MockGeolocation.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2009, 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. + */ + +// The functions in this file are used to configure the mock GeolocationService +// for the LayoutTests. + +#include "config.h" + +#include "Coordinates.h" +#include "GeolocationServiceMock.h" +#include "Geoposition.h" +#include "JavaSharedClient.h" +#include "PositionError.h" +#include "WebCoreJni.h" +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <wtf/CurrentTime.h> + +using namespace WebCore; + +namespace android { + +static const char* javaMockGeolocationClass = "android/webkit/MockGeolocation"; + +static void setPosition(JNIEnv* env, jobject, double latitude, double longitude, double accuracy) +{ + RefPtr<Coordinates> coordinates = Coordinates::create(latitude, + longitude, + false, 0.0, // altitude, + accuracy, + false, 0.0, // altitudeAccuracy, + false, 0.0, // heading + false, 0.0); // speed + RefPtr<Geoposition> position = Geoposition::create(coordinates.release(), WTF::currentTimeMS()); + GeolocationServiceMock::setPosition(position.release()); +} + +static void setError(JNIEnv* env, jobject, int code, jstring message) +{ + PositionError::ErrorCode codeEnum = static_cast<PositionError::ErrorCode>(code); + String messageString = jstringToWtfString(env, message); + RefPtr<PositionError> error = PositionError::create(codeEnum, messageString); + GeolocationServiceMock::setError(error.release()); +} + +static JNINativeMethod gMockGeolocationMethods[] = { + { "nativeSetPosition", "(DDD)V", (void*) setPosition }, + { "nativeSetError", "(ILjava/lang/String;)V", (void*) setError } +}; + +int registerMockGeolocation(JNIEnv* env) +{ +#ifndef NDEBUG + jclass mockGeolocation = env->FindClass(javaMockGeolocationClass); + LOG_ASSERT(mockGeolocation, "Unable to find class"); + env->DeleteLocalRef(mockGeolocation); +#endif + + return jniRegisterNativeMethods(env, javaMockGeolocationClass, gMockGeolocationMethods, NELEM(gMockGeolocationMethods)); +} + +} diff --git a/Source/WebKit/android/jni/PictureSet.cpp b/Source/WebKit/android/jni/PictureSet.cpp new file mode 100644 index 0000000..6dafd26 --- /dev/null +++ b/Source/WebKit/android/jni/PictureSet.cpp @@ -0,0 +1,676 @@ +/* + * Copyright 2008, 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_NDEBUG 0 +#define LOG_TAG "pictureset" + +//#include <config.h> +#include "CachedPrefix.h" +#include "android_graphics.h" +#include "PictureSet.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkRegion.h" +#include "SkStream.h" +#include "TimeCounter.h" + +#define MAX_DRAW_TIME 100 +#define MIN_SPLITTABLE 400 + +#if PICTURE_SET_DEBUG +class MeasureStream : public SkWStream { +public: + MeasureStream() : mTotal(0) {} + virtual bool write(const void* , size_t size) { + mTotal += size; + return true; + } + size_t mTotal; +}; +#endif + +namespace android { + +PictureSet::PictureSet() +{ + mWidth = mHeight = 0; +} + +PictureSet::~PictureSet() +{ + clear(); +} + +void PictureSet::add(const Pictures* temp) +{ + Pictures pictureAndBounds = *temp; + SkSafeRef(pictureAndBounds.mPicture); + pictureAndBounds.mWroteElapsed = false; + mPictures.append(pictureAndBounds); +} + +void PictureSet::add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split, bool empty) +{ + DBG_SET_LOGD("%p area={%d,%d,r=%d,b=%d} pict=%p elapsed=%d split=%d", this, + area.getBounds().fLeft, area.getBounds().fTop, + area.getBounds().fRight, area.getBounds().fBottom, picture, + elapsed, split); + SkSafeRef(picture); + /* if nothing is drawn beneath part of the new picture, mark it as a base */ + SkRegion diff = SkRegion(area); + Pictures* last = mPictures.end(); + for (Pictures* working = mPictures.begin(); working != last; working++) + diff.op(working->mArea, SkRegion::kDifference_Op); + Pictures pictureAndBounds = {area, picture, area.getBounds(), + elapsed, split, false, diff.isEmpty() == false, empty}; + mPictures.append(pictureAndBounds); +} + +/* +Pictures are discarded when they are fully drawn over. +When a picture is partially drawn over, it is discarded if it is not a base, and +its rectangular bounds is reduced if it is a base. +*/ +bool PictureSet::build() +{ + bool rebuild = false; + DBG_SET_LOGD("%p", this); + // walk pictures back to front, removing or trimming obscured ones + SkRegion drawn; + SkRegion inval; + Pictures* first = mPictures.begin(); + Pictures* last = mPictures.end(); + Pictures* working; + bool checkForNewBases = false; + for (working = last; working != first; ) { + --working; + SkRegion& area = working->mArea; + SkRegion visibleArea(area); + visibleArea.op(drawn, SkRegion::kDifference_Op); +#if PICTURE_SET_DEBUG + const SkIRect& a = area.getBounds(); + const SkIRect& d = drawn.getBounds(); + const SkIRect& i = inval.getBounds(); + const SkIRect& v = visibleArea.getBounds(); + DBG_SET_LOGD("%p [%d] area={%d,%d,r=%d,b=%d} drawn={%d,%d,r=%d,b=%d}" + " inval={%d,%d,r=%d,b=%d} vis={%d,%d,r=%d,b=%d}", + this, working - first, + a.fLeft, a.fTop, a.fRight, a.fBottom, + d.fLeft, d.fTop, d.fRight, d.fBottom, + i.fLeft, i.fTop, i.fRight, i.fBottom, + v.fLeft, v.fTop, v.fRight, v.fBottom); +#endif + bool tossPicture = false; + if (working->mBase == false) { + if (area != visibleArea) { + if (visibleArea.isEmpty() == false) { + DBG_SET_LOGD("[%d] partially overdrawn", working - first); + inval.op(visibleArea, SkRegion::kUnion_Op); + } else + DBG_SET_LOGD("[%d] fully hidden", working - first); + area.setEmpty(); + tossPicture = true; + } + } else { + const SkIRect& visibleBounds = visibleArea.getBounds(); + const SkIRect& areaBounds = area.getBounds(); + if (visibleBounds != areaBounds) { + DBG_SET_LOGD("[%d] base to be reduced", working - first); + area.setRect(visibleBounds); + checkForNewBases = tossPicture = true; + } + if (area.intersects(inval)) { + DBG_SET_LOGD("[%d] base to be redrawn", working - first); + tossPicture = true; + } + } + if (tossPicture) { + SkSafeUnref(working->mPicture); + working->mPicture = NULL; // mark to redraw + } + if (working->mPicture == NULL) // may have been set to null elsewhere + rebuild = true; + drawn.op(area, SkRegion::kUnion_Op); + } + // collapse out empty regions + Pictures* writer = first; + for (working = first; working != last; working++) { + if (working->mArea.isEmpty()) + continue; + *writer++ = *working; + } +#if PICTURE_SET_DEBUG + if ((unsigned) (writer - first) != mPictures.size()) + DBG_SET_LOGD("shrink=%d (was %d)", writer - first, mPictures.size()); +#endif + mPictures.shrink(writer - first); + /* When a base is discarded because it was entirely drawn over, all + remaining pictures are checked to see if one has become a base. */ + if (checkForNewBases) { + drawn.setEmpty(); + Pictures* last = mPictures.end(); + for (working = mPictures.begin(); working != last; working++) { + SkRegion& area = working->mArea; + if (drawn.contains(working->mArea) == false) { + working->mBase = true; + DBG_SET_LOGD("[%d] new base", working - mPictures.begin()); + } + drawn.op(working->mArea, SkRegion::kUnion_Op); + } + } + validate(__FUNCTION__); + return rebuild; +} + +void PictureSet::checkDimensions(int width, int height, SkRegion* inval) +{ + if (mWidth == width && mHeight == height) + return; + DBG_SET_LOGD("%p old:(w=%d,h=%d) new:(w=%d,h=%d)", this, + mWidth, mHeight, width, height); + if (mWidth == width && height > mHeight) { // only grew vertically + SkIRect rect; + rect.set(0, mHeight, width, height - mHeight); + inval->op(rect, SkRegion::kUnion_Op); + } else { + clear(); // if both width/height changed, clear the old cache + inval->setRect(0, 0, width, height); + } + mWidth = width; + mHeight = height; +} + +void PictureSet::clear() +{ + DBG_SET_LOG(""); + Pictures* last = mPictures.end(); + for (Pictures* working = mPictures.begin(); working != last; working++) { + working->mArea.setEmpty(); + SkSafeUnref(working->mPicture); + } + mPictures.clear(); + mWidth = mHeight = 0; +} + +bool PictureSet::draw(SkCanvas* canvas) +{ + validate(__FUNCTION__); + Pictures* first = mPictures.begin(); + Pictures* last = mPictures.end(); + Pictures* working; + SkRect bounds; + if (canvas->getClipBounds(&bounds) == false) + return false; + SkIRect irect; + bounds.roundOut(&irect); + for (working = last; working != first; ) { + --working; + if (working->mArea.contains(irect)) { +#if PICTURE_SET_DEBUG + const SkIRect& b = working->mArea.getBounds(); + DBG_SET_LOGD("contains working->mArea={%d,%d,%d,%d}" + " irect={%d,%d,%d,%d}", b.fLeft, b.fTop, b.fRight, b.fBottom, + irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); +#endif + first = working; + break; + } + } + DBG_SET_LOGD("%p first=%d last=%d", this, first - mPictures.begin(), + last - mPictures.begin()); + uint32_t maxElapsed = 0; + for (working = first; working != last; working++) { + const SkRegion& area = working->mArea; + if (area.quickReject(irect)) { +#if PICTURE_SET_DEBUG + const SkIRect& b = area.getBounds(); + DBG_SET_LOGD("[%d] %p quickReject working->mArea={%d,%d,%d,%d}" + " irect={%d,%d,%d,%d}", working - first, working, + b.fLeft, b.fTop, b.fRight, b.fBottom, + irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); +#endif + working->mElapsed = 0; + continue; + } + int saved = canvas->save(); + SkRect pathBounds; + if (area.isComplex()) { + SkPath pathClip; + area.getBoundaryPath(&pathClip); + canvas->clipPath(pathClip); + pathBounds = pathClip.getBounds(); + } else { + pathBounds.set(area.getBounds()); + canvas->clipRect(pathBounds); + } + canvas->translate(pathBounds.fLeft, pathBounds.fTop); + canvas->save(); + uint32_t startTime = getThreadMsec(); + canvas->drawPicture(*working->mPicture); + size_t elapsed = working->mElapsed = getThreadMsec() - startTime; + working->mWroteElapsed = true; + if (maxElapsed < elapsed && (pathBounds.width() >= MIN_SPLITTABLE || + pathBounds.height() >= MIN_SPLITTABLE)) + maxElapsed = elapsed; + canvas->restoreToCount(saved); +#define DRAW_TEST_IMAGE 01 +#if DRAW_TEST_IMAGE && PICTURE_SET_DEBUG + SkColor color = 0x3f000000 | (0xffffff & (unsigned) working); + canvas->drawColor(color); + SkPaint paint; + color ^= 0x00ffffff; + paint.setColor(color); + char location[256]; + for (int x = area.getBounds().fLeft & ~0x3f; + x < area.getBounds().fRight; x += 0x40) { + for (int y = area.getBounds().fTop & ~0x3f; + y < area.getBounds().fBottom; y += 0x40) { + int len = snprintf(location, sizeof(location) - 1, "(%d,%d)", x, y); + canvas->drawText(location, len, x, y, paint); + } + } +#endif + DBG_SET_LOGD("[%d] %p working->mArea={%d,%d,%d,%d} elapsed=%d base=%s", + working - first, working, + area.getBounds().fLeft, area.getBounds().fTop, + area.getBounds().fRight, area.getBounds().fBottom, + working->mElapsed, working->mBase ? "true" : "false"); + } + // dump(__FUNCTION__); + return maxElapsed >= MAX_DRAW_TIME; +} + +void PictureSet::dump(const char* label) const +{ +#if PICTURE_SET_DUMP + DBG_SET_LOGD("%p %s (%d) (w=%d,h=%d)", this, label, mPictures.size(), + mWidth, mHeight); + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + const SkIRect& bounds = working->mArea.getBounds(); + const SkIRect& unsplit = working->mUnsplit; + MeasureStream measure; + if (working->mPicture != NULL) + working->mPicture->serialize(&measure); + LOGD(" [%d]" + " mArea.bounds={%d,%d,r=%d,b=%d}" + " mPicture=%p" + " mUnsplit={%d,%d,r=%d,b=%d}" + " mElapsed=%d" + " mSplit=%s" + " mWroteElapsed=%s" + " mBase=%s" + " pict-size=%d", + working - mPictures.begin(), + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, + working->mPicture, + unsplit.fLeft, unsplit.fTop, unsplit.fRight, unsplit.fBottom, + working->mElapsed, working->mSplit ? "true" : "false", + working->mWroteElapsed ? "true" : "false", + working->mBase ? "true" : "false", + measure.mTotal); + } +#endif +} + +class IsEmptyBounder : public SkBounder { + virtual bool onIRect(const SkIRect& rect) { + return false; + } +}; + +class IsEmptyCanvas : public SkCanvas { +public: + IsEmptyCanvas(SkBounder* bounder, SkPicture* picture) : + mPicture(picture), mEmpty(true) { + setBounder(bounder); + } + + void notEmpty() { + mEmpty = false; + mPicture->abortPlayback(); + } + + virtual bool clipPath(const SkPath&, SkRegion::Op) { + // this can be expensive to actually do, and doesn't affect the + // question of emptiness, so we make it a no-op + return true; + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& , const SkPaint& ) { + if (bitmap.width() <= 1 || bitmap.height() <= 1) + return; + DBG_SET_LOGD("abort {%d,%d}", bitmap.width(), bitmap.height()); + notEmpty(); + } + + virtual void drawPaint(const SkPaint& paint) { + } + + virtual void drawPath(const SkPath& , const SkPaint& paint) { + DBG_SET_LOG("abort"); + notEmpty(); + } + + virtual void drawPoints(PointMode , size_t , const SkPoint [], + const SkPaint& paint) { + } + + virtual void drawRect(const SkRect& , const SkPaint& paint) { + // wait for visual content + if (paint.getColor() != SK_ColorWHITE) + notEmpty(); + } + + virtual void drawSprite(const SkBitmap& , int , int , + const SkPaint* paint = NULL) { + DBG_SET_LOG("abort"); + notEmpty(); + } + + virtual void drawText(const void* , size_t byteLength, SkScalar , + SkScalar , const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPosText(const void* , size_t byteLength, + const SkPoint [], const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPosTextH(const void* , size_t byteLength, + const SkScalar [], SkScalar , + const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawTextOnPath(const void* , size_t byteLength, + const SkPath& , const SkMatrix* , + const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPicture(SkPicture& picture) { + SkCanvas::drawPicture(picture); + } + + SkPicture* mPicture; + bool mEmpty; +}; + +bool PictureSet::emptyPicture(SkPicture* picture) const +{ + IsEmptyBounder isEmptyBounder; + IsEmptyCanvas checker(&isEmptyBounder, picture); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, mWidth, mHeight); + checker.setBitmapDevice(bitmap); + checker.drawPicture(*picture); + return checker.mEmpty; +} + +bool PictureSet::isEmpty() const +{ + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + if (!working->mEmpty) + return false; + } + return true; +} + +bool PictureSet::reuseSubdivided(const SkRegion& inval) +{ + validate(__FUNCTION__); + if (inval.isComplex()) + return false; + Pictures* working, * last = mPictures.end(); + const SkIRect& invalBounds = inval.getBounds(); + bool steal = false; + for (working = mPictures.begin(); working != last; working++) { + if (working->mSplit && invalBounds == working->mUnsplit) { + steal = true; + continue; + } + if (steal == false) + continue; + SkRegion temp = SkRegion(inval); + temp.op(working->mArea, SkRegion::kIntersect_Op); + if (temp.isEmpty() || temp == working->mArea) + continue; + return false; + } + if (steal == false) + return false; + for (working = mPictures.begin(); working != last; working++) { + if ((working->mSplit == false || invalBounds != working->mUnsplit) && + inval.contains(working->mArea) == false) + continue; + SkSafeUnref(working->mPicture); + working->mPicture = NULL; + } + return true; +} + +void PictureSet::set(const PictureSet& src) +{ + DBG_SET_LOGD("start %p src=%p", this, &src); + clear(); + mWidth = src.mWidth; + mHeight = src.mHeight; + const Pictures* last = src.mPictures.end(); + for (const Pictures* working = src.mPictures.begin(); working != last; working++) + add(working); + // dump(__FUNCTION__); + validate(__FUNCTION__); + DBG_SET_LOG("end"); +} + +void PictureSet::setDrawTimes(const PictureSet& src) +{ + validate(__FUNCTION__); + if (mWidth != src.mWidth || mHeight != src.mHeight) + return; + Pictures* last = mPictures.end(); + Pictures* working = mPictures.begin(); + if (working == last) + return; + const Pictures* srcLast = src.mPictures.end(); + const Pictures* srcWorking = src.mPictures.begin(); + for (; srcWorking != srcLast; srcWorking++) { + if (srcWorking->mWroteElapsed == false) + continue; + while ((srcWorking->mArea != working->mArea || + srcWorking->mPicture != working->mPicture)) { + if (++working == last) + return; + } + DBG_SET_LOGD("%p [%d] [%d] {%d,%d,r=%d,b=%d} working->mElapsed=%d <- %d", + this, working - mPictures.begin(), srcWorking - src.mPictures.begin(), + working->mArea.getBounds().fLeft, working->mArea.getBounds().fTop, + working->mArea.getBounds().fRight, working->mArea.getBounds().fBottom, + working->mElapsed, srcWorking->mElapsed); + working->mElapsed = srcWorking->mElapsed; + } +} + +void PictureSet::setPicture(size_t i, SkPicture* p) +{ + SkSafeUnref(mPictures[i].mPicture); + mPictures[i].mPicture = p; + mPictures[i].mEmpty = emptyPicture(p); +} + +void PictureSet::split(PictureSet* out) const +{ + dump(__FUNCTION__); + DBG_SET_LOGD("%p", this); + SkIRect totalBounds; + out->mWidth = mWidth; + out->mHeight = mHeight; + totalBounds.set(0, 0, mWidth, mHeight); + SkRegion* total = new SkRegion(totalBounds); + const Pictures* last = mPictures.end(); + const Pictures* working; + uint32_t balance = 0; + int multiUnsplitFastPictures = 0; // > 1 has more than 1 + for (working = mPictures.begin(); working != last; working++) { + if (working->mElapsed >= MAX_DRAW_TIME || working->mSplit) + continue; + if (++multiUnsplitFastPictures > 1) + break; + } + for (working = mPictures.begin(); working != last; working++) { + uint32_t elapsed = working->mElapsed; + if (elapsed < MAX_DRAW_TIME) { + bool split = working->mSplit; + DBG_SET_LOGD("elapsed=%d working=%p total->getBounds()=" + "{%d,%d,r=%d,b=%d} split=%s", elapsed, working, + total->getBounds().fLeft, total->getBounds().fTop, + total->getBounds().fRight, total->getBounds().fBottom, + split ? "true" : "false"); + if (multiUnsplitFastPictures <= 1 || split) { + total->op(working->mArea, SkRegion::kDifference_Op); + out->add(working->mArea, working->mPicture, elapsed, split, + working->mEmpty); + } else if (balance < elapsed) + balance = elapsed; + continue; + } + total->op(working->mArea, SkRegion::kDifference_Op); + const SkIRect& bounds = working->mArea.getBounds(); + int width = bounds.width(); + int height = bounds.height(); + int across = 1; + int down = 1; + while (height >= MIN_SPLITTABLE || width >= MIN_SPLITTABLE) { + if (height >= width) { + height >>= 1; + down <<= 1; + } else { + width >>= 1; + across <<= 1 ; + } + if ((elapsed >>= 1) < MAX_DRAW_TIME) + break; + } + width = bounds.width(); + height = bounds.height(); + int top = bounds.fTop; + for (int indexY = 0; indexY < down; ) { + int bottom = bounds.fTop + height * ++indexY / down; + int left = bounds.fLeft; + for (int indexX = 0; indexX < across; ) { + int right = bounds.fLeft + width * ++indexX / across; + SkIRect cBounds; + cBounds.set(left, top, right, bottom); + out->add(SkRegion(cBounds), (across | down) != 1 ? NULL : + working->mPicture, elapsed, true, + (across | down) != 1 ? false : working->mEmpty); + left = right; + } + top = bottom; + } + } + DBG_SET_LOGD("%p w=%d h=%d total->isEmpty()=%s multiUnsplitFastPictures=%d", + this, mWidth, mHeight, total->isEmpty() ? "true" : "false", + multiUnsplitFastPictures); + if (!total->isEmpty() && multiUnsplitFastPictures > 1) + out->add(*total, NULL, balance, false, false); + delete total; + validate(__FUNCTION__); + out->dump("split-out"); +} + +bool PictureSet::validate(const char* funct) const +{ + bool valid = true; +#if PICTURE_SET_VALIDATE + SkRegion all; + const Pictures* first = mPictures.begin(); + for (const Pictures* working = mPictures.end(); working != first; ) { + --working; + const SkPicture* pict = working->mPicture; + const SkRegion& area = working->mArea; + const SkIRect& bounds = area.getBounds(); + bool localValid = false; + if (working->mUnsplit.isEmpty()) + LOGD("%s working->mUnsplit.isEmpty()", funct); + else if (working->mUnsplit.contains(bounds) == false) + LOGD("%s working->mUnsplit.contains(bounds) == false", funct); + else if (working->mElapsed >= 1000) + LOGD("%s working->mElapsed >= 1000", funct); + else if ((working->mSplit & 0xfe) != 0) + LOGD("%s (working->mSplit & 0xfe) != 0", funct); + else if ((working->mWroteElapsed & 0xfe) != 0) + LOGD("%s (working->mWroteElapsed & 0xfe) != 0", funct); + else if (pict != NULL) { + int pictWidth = pict->width(); + int pictHeight = pict->height(); + if (pictWidth < bounds.width()) + LOGD("%s pictWidth=%d < bounds.width()=%d", funct, pictWidth, bounds.width()); + else if (pictHeight < bounds.height()) + LOGD("%s pictHeight=%d < bounds.height()=%d", funct, pictHeight, bounds.height()); + else if (working->mArea.isEmpty()) + LOGD("%s working->mArea.isEmpty()", funct); + else + localValid = true; + } else + localValid = true; + working->mArea.validate(); + if (localValid == false) { + if (all.contains(area) == true) + LOGD("%s all.contains(area) == true", funct); + else + localValid = true; + } + valid &= localValid; + all.op(area, SkRegion::kUnion_Op); + } + const SkIRect& allBounds = all.getBounds(); + if (valid) { + valid = false; + if (allBounds.width() != mWidth) + LOGD("%s allBounds.width()=%d != mWidth=%d", funct, allBounds.width(), mWidth); + else if (allBounds.height() != mHeight) + LOGD("%s allBounds.height()=%d != mHeight=%d", funct, allBounds.height(), mHeight); + else + valid = true; + } + while (valid == false) + ; +#endif + return valid; +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/PictureSet.h b/Source/WebKit/android/jni/PictureSet.h new file mode 100644 index 0000000..b177958 --- /dev/null +++ b/Source/WebKit/android/jni/PictureSet.h @@ -0,0 +1,104 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef PICTURESET_H +#define PICTURESET_H + +#define PICTURE_SET_DUMP 0 +#define PICTURE_SET_DEBUG 0 +#define PICTURE_SET_VALIDATE 0 + +#if PICTURE_SET_DEBUG +#define DBG_SET_LOG(message) LOGD("%s %s", __FUNCTION__, message) +#define DBG_SET_LOGD(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#define DEBUG_SET_UI_LOGD(...) LOGD(__VA_ARGS__) +#else +#define DBG_SET_LOG(message) ((void)0) +#define DBG_SET_LOGD(format, ...) ((void)0) +#define DEBUG_SET_UI_LOGD(...) ((void)0) +#endif + +#include "jni.h" +#include "SkRegion.h" +#include <wtf/Vector.h> + +class SkCanvas; +class SkPicture; +class SkIRect; + +namespace android { + + class PictureSet { + public: + PictureSet(); + PictureSet(const PictureSet& src) { set(src); } + virtual ~PictureSet(); + void add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split) + { + add(area, picture, elapsed, split, emptyPicture(picture)); + } + void add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split, bool empty); + const SkIRect& bounds(size_t i) const { + return mPictures[i].mArea.getBounds(); } + bool build(); + // Update mWidth/mHeight, and adds any additional inval region + void checkDimensions(int width, int height, SkRegion* inval); + void clear(); + bool draw(SkCanvas* ); + static PictureSet* GetNativePictureSet(JNIEnv* env, jobject jpic); + int height() const { return mHeight; } + bool isEmpty() const; // returns true if empty or only trivial content + bool reuseSubdivided(const SkRegion& ); + void set(const PictureSet& ); + void setDrawTimes(const PictureSet& ); + void setPicture(size_t i, SkPicture* p); + size_t size() const { return mPictures.size(); } + void split(PictureSet* result) const; + bool upToDate(size_t i) const { return mPictures[i].mPicture != NULL; } + int width() const { return mWidth; } + void dump(const char* label) const; + bool validate(const char* label) const; + private: + bool emptyPicture(SkPicture* ) const; // true if no text, images, paths + struct Pictures { + SkRegion mArea; + SkPicture* mPicture; + SkIRect mUnsplit; + uint32_t mElapsed; + bool mSplit : 8; + bool mWroteElapsed : 8; + bool mBase : 8; // true if nothing is drawn underneath this + bool mEmpty : 8; // true if the picture only draws white + }; + void add(const Pictures* temp); + WTF::Vector<Pictures> mPictures; + int mHeight; + int mWidth; + }; +} + +#endif diff --git a/Source/WebKit/android/jni/WebCoreFrameBridge.cpp b/Source/WebKit/android/jni/WebCoreFrameBridge.cpp new file mode 100644 index 0000000..15b6d20 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreFrameBridge.cpp @@ -0,0 +1,2125 @@ +/* + * 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 "WebCoreFrameBridge.h" + +#include "Arena.h" +#include "BackForwardList.h" +#include "MemoryCache.h" +#include "Chrome.h" +#include "ChromeClientAndroid.h" +#include "ChromiumInit.h" +#include "ContextMenuClientAndroid.h" +#include "DeviceMotionClientAndroid.h" +#include "DeviceOrientationClientAndroid.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "DragClientAndroid.h" +#include "EditorClientAndroid.h" +#include "Element.h" +#include "FocusController.h" +#include "Font.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameLoadRequest.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HistoryItem.h" +#include "HTMLCollection.h" +#include "HTMLElement.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "IconDatabase.h" +#include "Image.h" +#include "InspectorClientAndroid.h" +#include "KURL.h" +#include "Page.h" +#include "PageCache.h" +#include "PlatformString.h" +#include "RenderPart.h" +#include "RenderSkinAndroid.h" +#include "RenderTreeAsText.h" +#include "RenderView.h" +#include "ResourceHandle.h" +#include "ResourceHandleInternal.h" +#include "ScriptController.h" +#include "ScriptValue.h" +#include "SecurityOrigin.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SubstituteData.h" +#include "UrlInterceptResponse.h" +#include "UserGestureIndicator.h" +#include "WebCache.h" +#include "WebCoreJni.h" +#include "WebCoreResourceLoader.h" +#include "WebHistory.h" +#include "WebIconDatabase.h" +#include "WebFrameView.h" +#include "WebUrlLoaderClient.h" +#include "WebViewCore.h" +#include "android_graphics.h" +#include "jni.h" +#include "wds/DebugServer.h" + +#include <JNIUtility.h> +#include <JNIHelp.h> +#include <SkGraphics.h> +#include <android_runtime/android_util_AssetManager.h> +#include <utils/misc.h> +#include <utils/AssetManager.h> +#include <wtf/CurrentTime.h> +#include <wtf/Platform.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> + +#if USE(JSC) +#include "GCController.h" +#include "JSDOMWindow.h" +#include "JavaInstanceJSC.h" +#include <runtime_object.h> +#include <runtime_root.h> +#include <runtime/JSLock.h> +#elif USE(V8) +#include "JavaNPObjectV8.h" +#include "JavaInstanceV8.h" +#include "V8Counters.h" +#endif // USE(JSC) + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#if ENABLE(ARCHIVE) +#include "WebArchiveAndroid.h" +#endif + +#if ENABLE(WEB_AUTOFILL) +#include "autofill/WebAutoFill.h" +#endif + +using namespace JSC::Bindings; + +static String* gUploadFileLabel; +static String* gResetLabel; +static String* gSubmitLabel; +static String* gNoFileChosenLabel; + +String* WebCore::PlatformBridge::globalLocalizedName( + WebCore::PlatformBridge::rawResId resId) +{ + switch (resId) { + case WebCore::PlatformBridge::FileUploadLabel: + return gUploadFileLabel; + case WebCore::PlatformBridge::ResetLabel: + return gResetLabel; + case WebCore::PlatformBridge::SubmitLabel: + return gSubmitLabel; + case WebCore::PlatformBridge::FileUploadNoFileChosenLabel: + return gNoFileChosenLabel; + + default: + return 0; + } +} +/** + * Instantiate the localized name desired. + */ +void initGlobalLocalizedName(WebCore::PlatformBridge::rawResId resId, + android::WebFrame* webFrame) +{ + String** pointer; + switch (resId) { + case WebCore::PlatformBridge::FileUploadLabel: + pointer = &gUploadFileLabel; + break; + case WebCore::PlatformBridge::ResetLabel: + pointer = &gResetLabel; + break; + case WebCore::PlatformBridge::SubmitLabel: + pointer = &gSubmitLabel; + break; + case WebCore::PlatformBridge::FileUploadNoFileChosenLabel: + pointer = &gNoFileChosenLabel; + break; + default: + return; + } + if (!(*pointer) && webFrame) { + (*pointer) = new String(webFrame->getRawResourceFilename(resId).impl()); + } +} + +namespace android { + +// ---------------------------------------------------------------------------- + +#define WEBCORE_MEMORY_CAP 15 * 1024 * 1024 + +// ---------------------------------------------------------------------------- + +struct WebFrame::JavaBrowserFrame +{ + jweak mObj; + jweak mHistoryList; // WebBackForwardList object + jmethodID mStartLoadingResource; + jmethodID mMaybeSavePassword; + jmethodID mShouldInterceptRequest; + jmethodID mLoadStarted; + jmethodID mTransitionToCommitted; + jmethodID mLoadFinished; + jmethodID mReportError; + jmethodID mSetTitle; + jmethodID mWindowObjectCleared; + jmethodID mSetProgress; + jmethodID mDidReceiveIcon; + jmethodID mDidReceiveTouchIconUrl; + jmethodID mUpdateVisitedHistory; + jmethodID mHandleUrl; + jmethodID mCreateWindow; + jmethodID mCloseWindow; + jmethodID mDecidePolicyForFormResubmission; + jmethodID mRequestFocus; + jmethodID mGetRawResFilename; + jmethodID mDensity; + jmethodID mGetFileSize; + jmethodID mGetFile; + jmethodID mDidReceiveAuthenticationChallenge; + jmethodID mReportSslCertError; + jmethodID mDownloadStart; + jmethodID mDidReceiveData; + jmethodID mDidFinishLoading; + jmethodID mSetCertificate; + jmethodID mShouldSaveFormData; + jmethodID mSaveFormData; + jmethodID mAutoLogin; + AutoJObject frame(JNIEnv* env) { + return getRealObject(env, mObj); + } + AutoJObject history(JNIEnv* env) { + return getRealObject(env, mHistoryList); + } +}; + +static jfieldID gFrameField; +#define GET_NATIVE_FRAME(env, obj) ((WebCore::Frame*)env->GetIntField(obj, gFrameField)) +#define SET_NATIVE_FRAME(env, obj, frame) (env->SetIntField(obj, gFrameField, frame)) + +// ---------------------------------------------------------------------------- + +WebFrame::WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* page) + : mPage(page) +{ + jclass clazz = env->GetObjectClass(obj); + mJavaFrame = new JavaBrowserFrame; + mJavaFrame->mObj = env->NewWeakGlobalRef(obj); + mJavaFrame->mHistoryList = env->NewWeakGlobalRef(historyList); + mJavaFrame->mStartLoadingResource = env->GetMethodID(clazz, "startLoadingResource", + "(ILjava/lang/String;Ljava/lang/String;Ljava/util/HashMap;[BJIZZZLjava/lang/String;Ljava/lang/String;)Landroid/webkit/LoadListener;"); + mJavaFrame->mMaybeSavePassword = env->GetMethodID(clazz, "maybeSavePassword", + "([BLjava/lang/String;Ljava/lang/String;)V"); + mJavaFrame->mShouldInterceptRequest = + env->GetMethodID(clazz, "shouldInterceptRequest", + "(Ljava/lang/String;)Landroid/webkit/WebResourceResponse;"); + mJavaFrame->mLoadStarted = env->GetMethodID(clazz, "loadStarted", + "(Ljava/lang/String;Landroid/graphics/Bitmap;IZ)V"); + mJavaFrame->mTransitionToCommitted = env->GetMethodID(clazz, "transitionToCommitted", + "(IZ)V"); + mJavaFrame->mLoadFinished = env->GetMethodID(clazz, "loadFinished", + "(Ljava/lang/String;IZ)V"); + mJavaFrame->mReportError = env->GetMethodID(clazz, "reportError", + "(ILjava/lang/String;Ljava/lang/String;)V"); + mJavaFrame->mSetTitle = env->GetMethodID(clazz, "setTitle", + "(Ljava/lang/String;)V"); + mJavaFrame->mWindowObjectCleared = env->GetMethodID(clazz, "windowObjectCleared", + "(I)V"); + mJavaFrame->mSetProgress = env->GetMethodID(clazz, "setProgress", + "(I)V"); + mJavaFrame->mDidReceiveIcon = env->GetMethodID(clazz, "didReceiveIcon", + "(Landroid/graphics/Bitmap;)V"); + mJavaFrame->mDidReceiveTouchIconUrl = env->GetMethodID(clazz, "didReceiveTouchIconUrl", + "(Ljava/lang/String;Z)V"); + mJavaFrame->mUpdateVisitedHistory = env->GetMethodID(clazz, "updateVisitedHistory", + "(Ljava/lang/String;Z)V"); + mJavaFrame->mHandleUrl = env->GetMethodID(clazz, "handleUrl", + "(Ljava/lang/String;)Z"); + mJavaFrame->mCreateWindow = env->GetMethodID(clazz, "createWindow", + "(ZZ)Landroid/webkit/BrowserFrame;"); + mJavaFrame->mCloseWindow = env->GetMethodID(clazz, "closeWindow", + "(Landroid/webkit/WebViewCore;)V"); + mJavaFrame->mDecidePolicyForFormResubmission = env->GetMethodID(clazz, + "decidePolicyForFormResubmission", "(I)V"); + mJavaFrame->mRequestFocus = env->GetMethodID(clazz, "requestFocus", + "()V"); + mJavaFrame->mGetRawResFilename = env->GetMethodID(clazz, "getRawResFilename", + "(I)Ljava/lang/String;"); + mJavaFrame->mDensity = env->GetMethodID(clazz, "density","()F"); + mJavaFrame->mGetFileSize = env->GetMethodID(clazz, "getFileSize", "(Ljava/lang/String;)I"); + mJavaFrame->mGetFile = env->GetMethodID(clazz, "getFile", "(Ljava/lang/String;[BII)I"); + mJavaFrame->mDidReceiveAuthenticationChallenge = env->GetMethodID(clazz, "didReceiveAuthenticationChallenge", + "(ILjava/lang/String;Ljava/lang/String;Z)V"); + mJavaFrame->mReportSslCertError = env->GetMethodID(clazz, "reportSslCertError", "(II[B)V"); + mJavaFrame->mDownloadStart = env->GetMethodID(clazz, "downloadStart", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V"); + mJavaFrame->mDidReceiveData = env->GetMethodID(clazz, "didReceiveData", "([BI)V"); + mJavaFrame->mDidFinishLoading = env->GetMethodID(clazz, "didFinishLoading", "()V"); + mJavaFrame->mSetCertificate = env->GetMethodID(clazz, "setCertificate", "([B)V"); + mJavaFrame->mShouldSaveFormData = env->GetMethodID(clazz, "shouldSaveFormData", "()Z"); + mJavaFrame->mSaveFormData = env->GetMethodID(clazz, "saveFormData", "(Ljava/util/HashMap;)V"); + mJavaFrame->mAutoLogin = env->GetMethodID(clazz, "autoLogin", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + env->DeleteLocalRef(clazz); + + LOG_ASSERT(mJavaFrame->mStartLoadingResource, "Could not find method startLoadingResource"); + LOG_ASSERT(mJavaFrame->mMaybeSavePassword, "Could not find method maybeSavePassword"); + LOG_ASSERT(mJavaFrame->mShouldInterceptRequest, "Could not find method shouldInterceptRequest"); + LOG_ASSERT(mJavaFrame->mLoadStarted, "Could not find method loadStarted"); + LOG_ASSERT(mJavaFrame->mTransitionToCommitted, "Could not find method transitionToCommitted"); + LOG_ASSERT(mJavaFrame->mLoadFinished, "Could not find method loadFinished"); + LOG_ASSERT(mJavaFrame->mReportError, "Could not find method reportError"); + LOG_ASSERT(mJavaFrame->mSetTitle, "Could not find method setTitle"); + LOG_ASSERT(mJavaFrame->mWindowObjectCleared, "Could not find method windowObjectCleared"); + LOG_ASSERT(mJavaFrame->mSetProgress, "Could not find method setProgress"); + LOG_ASSERT(mJavaFrame->mDidReceiveIcon, "Could not find method didReceiveIcon"); + LOG_ASSERT(mJavaFrame->mDidReceiveTouchIconUrl, "Could not find method didReceiveTouchIconUrl"); + LOG_ASSERT(mJavaFrame->mUpdateVisitedHistory, "Could not find method updateVisitedHistory"); + LOG_ASSERT(mJavaFrame->mHandleUrl, "Could not find method handleUrl"); + LOG_ASSERT(mJavaFrame->mCreateWindow, "Could not find method createWindow"); + LOG_ASSERT(mJavaFrame->mCloseWindow, "Could not find method closeWindow"); + LOG_ASSERT(mJavaFrame->mDecidePolicyForFormResubmission, "Could not find method decidePolicyForFormResubmission"); + LOG_ASSERT(mJavaFrame->mRequestFocus, "Could not find method requestFocus"); + LOG_ASSERT(mJavaFrame->mGetRawResFilename, "Could not find method getRawResFilename"); + LOG_ASSERT(mJavaFrame->mDensity, "Could not find method density"); + LOG_ASSERT(mJavaFrame->mGetFileSize, "Could not find method getFileSize"); + LOG_ASSERT(mJavaFrame->mGetFile, "Could not find method getFile"); + LOG_ASSERT(mJavaFrame->mDidReceiveAuthenticationChallenge, "Could not find method didReceiveAuthenticationChallenge"); + LOG_ASSERT(mJavaFrame->mReportSslCertError, "Could not find method reportSslCertError"); + LOG_ASSERT(mJavaFrame->mDownloadStart, "Could not find method downloadStart"); + LOG_ASSERT(mJavaFrame->mDidReceiveData, "Could not find method didReceiveData"); + LOG_ASSERT(mJavaFrame->mDidFinishLoading, "Could not find method didFinishLoading"); + LOG_ASSERT(mJavaFrame->mSetCertificate, "Could not find method setCertificate"); + LOG_ASSERT(mJavaFrame->mShouldSaveFormData, "Could not find method shouldSaveFormData"); + LOG_ASSERT(mJavaFrame->mSaveFormData, "Could not find method saveFormData"); + LOG_ASSERT(mJavaFrame->mAutoLogin, "Could not find method autoLogin"); + + mUserAgent = WTF::String(); + mUserInitiatedAction = false; + mBlockNetworkLoads = false; + m_renderSkins = 0; +} + +WebFrame::~WebFrame() +{ + if (mJavaFrame->mObj) { + JNIEnv* env = getJNIEnv(); + env->DeleteWeakGlobalRef(mJavaFrame->mObj); + env->DeleteWeakGlobalRef(mJavaFrame->mHistoryList); + mJavaFrame->mObj = 0; + } + delete mJavaFrame; + delete m_renderSkins; +} + +WebFrame* WebFrame::getWebFrame(const WebCore::Frame* frame) +{ + FrameLoaderClientAndroid* client = + static_cast<FrameLoaderClientAndroid*> (frame->loader()->client()); + return client->webFrame(); +} + +static jobject createJavaMapFromHTTPHeaders(JNIEnv* env, const WebCore::HTTPHeaderMap& map) +{ + jclass mapClass = env->FindClass("java/util/HashMap"); + LOG_ASSERT(mapClass, "Could not find HashMap class!"); + jmethodID init = env->GetMethodID(mapClass, "<init>", "(I)V"); + LOG_ASSERT(init, "Could not find constructor for HashMap"); + jobject hashMap = env->NewObject(mapClass, init, map.size()); + LOG_ASSERT(hashMap, "Could not create a new HashMap"); + jmethodID put = env->GetMethodID(mapClass, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + LOG_ASSERT(put, "Could not find put method on HashMap"); + + WebCore::HTTPHeaderMap::const_iterator end = map.end(); + for (WebCore::HTTPHeaderMap::const_iterator i = map.begin(); i != end; ++i) { + if (i->first.length() == 0 || i->second.length() == 0) + continue; + jstring key = wtfStringToJstring(env, i->first); + jstring val = wtfStringToJstring(env, i->second); + if (key && val) { + env->CallObjectMethod(hashMap, put, key, val); + } + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + + env->DeleteLocalRef(mapClass); + + return hashMap; +} + +// This class stores the URI and the size of each file for upload. The URI is +// stored so we do not have to create it again. The size is stored so we can +// compare the actual size of the file with the stated size. If the actual size +// is larger, we will not copy it, since we will not have enough space in our +// buffer. +class FileInfo { +public: + FileInfo(JNIEnv* env, const WTF::String& name) { + m_uri = wtfStringToJstring(env, name); + checkException(env); + m_size = 0; + m_env = env; + } + ~FileInfo() { + m_env->DeleteLocalRef(m_uri); + } + int getSize() { return m_size; } + jstring getUri() { return m_uri; } + void setSize(int size) { m_size = size; } +private: + // This is only a pointer to the JNIEnv* returned by + // JSC::Bindings::getJNIEnv(). Used to delete the jstring when finished. + JNIEnv* m_env; + jstring m_uri; + int m_size; +}; + +PassRefPtr<WebCore::ResourceLoaderAndroid> +WebFrame::startLoadingResource(WebCore::ResourceHandle* loader, + const WebCore::ResourceRequest& request, + bool mainResource, + bool synchronous) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: startLoadingResource(%p, %s)", + loader, request.url().string().latin1().data()); + + WTF::String method = request.httpMethod(); + WebCore::HTTPHeaderMap headers = request.httpHeaderFields(); + + JNIEnv* env = getJNIEnv(); + WTF::String urlStr = request.url().string(); + int colon = urlStr.find(':'); + bool allLower = true; + for (int index = 0; index < colon; index++) { + UChar ch = urlStr[index]; + if (!WTF::isASCIIAlpha(ch)) + break; + allLower &= WTF::isASCIILower(ch); + if (index == colon - 1 && !allLower) { + urlStr = urlStr.substring(0, colon).lower() + + urlStr.substring(colon); + } + } + LOGV("%s lower=%s", __FUNCTION__, urlStr.latin1().data()); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jstring jMethodStr = NULL; + if (!method.isEmpty()) + jMethodStr = wtfStringToJstring(env, method); + WebCore::FormData* formdata = request.httpBody(); + jbyteArray jPostDataStr = getPostData(request); + jobject jHeaderMap = createJavaMapFromHTTPHeaders(env, headers); + + // Convert the WebCore Cache Policy to a WebView Cache Policy. + int cacheMode = 0; // WebSettings.LOAD_NORMAL + switch (request.cachePolicy()) { + case WebCore::ReloadIgnoringCacheData: + cacheMode = 2; // WebSettings.LOAD_NO_CACHE + break; + case WebCore::ReturnCacheDataDontLoad: + cacheMode = 3; // WebSettings.LOAD_CACHE_ONLY + break; + case WebCore::ReturnCacheDataElseLoad: + cacheMode = 1; // WebSettings.LOAD_CACHE_ELSE_NETWORK + break; + case WebCore::UseProtocolCachePolicy: + default: + break; + } + + LOGV("::WebCore:: startLoadingResource %s with cacheMode %d", urlStr.ascii().data(), cacheMode); + + ResourceHandleInternal* loaderInternal = loader->getInternal(); + jstring jUsernameString = loaderInternal->m_user.isEmpty() ? + NULL : wtfStringToJstring(env, loaderInternal->m_user); + jstring jPasswordString = loaderInternal->m_pass.isEmpty() ? + NULL : wtfStringToJstring(env, loaderInternal->m_pass); + + bool isUserGesture = UserGestureIndicator::processingUserGesture(); + jobject jLoadListener = + env->CallObjectMethod(mJavaFrame->frame(env).get(), mJavaFrame->mStartLoadingResource, + (int)loader, jUrlStr, jMethodStr, jHeaderMap, + jPostDataStr, formdata ? formdata->identifier(): 0, + cacheMode, mainResource, isUserGesture, + synchronous, jUsernameString, jPasswordString); + + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jMethodStr); + env->DeleteLocalRef(jPostDataStr); + env->DeleteLocalRef(jHeaderMap); + env->DeleteLocalRef(jUsernameString); + env->DeleteLocalRef(jPasswordString); + if (checkException(env)) + return NULL; + + PassRefPtr<WebCore::ResourceLoaderAndroid> h; + if (jLoadListener) + h = WebCoreResourceLoader::create(env, jLoadListener); + env->DeleteLocalRef(jLoadListener); + return h; +} + +UrlInterceptResponse* +WebFrame::shouldInterceptRequest(const WTF::String& url) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: shouldInterceptRequest(%s)", url.latin1().data()); + + JNIEnv* env = getJNIEnv(); + jstring urlStr = wtfStringToJstring(env, url); + jobject response = env->CallObjectMethod(mJavaFrame->frame(env).get(), mJavaFrame->mShouldInterceptRequest, urlStr); + env->DeleteLocalRef(urlStr); + if (response == 0) + return 0; + return new UrlInterceptResponse(env, response); +} + +void +WebFrame::reportError(int errorCode, const WTF::String& description, + const WTF::String& failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: reportError(%d, %s)", errorCode, description.ascii().data()); + JNIEnv* env = getJNIEnv(); + + jstring descStr = wtfStringToJstring(env, description); + jstring failUrl = wtfStringToJstring(env, failingUrl); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mReportError, + errorCode, descStr, failUrl); + env->DeleteLocalRef(descStr); + env->DeleteLocalRef(failUrl); +} + +void +WebFrame::loadStarted(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + // activeDocumentLoader() can return null. + DocumentLoader* documentLoader = frame->loader()->activeDocumentLoader(); + if (documentLoader == NULL) + return; + + const WebCore::KURL& url = documentLoader->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: loadStarted %s", url.string().ascii().data()); + + bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); + WebCore::FrameLoadType loadType = frame->loader()->loadType(); + + if (loadType == WebCore::FrameLoadTypeReplace || + (loadType == WebCore::FrameLoadTypeRedirectWithLockedBackForwardList && + !isMainFrame)) + return; + + JNIEnv* env = getJNIEnv(); + const WTF::String& urlString = url.string(); + // If this is the main frame and we already have a favicon in the database, + // send it along with the page started notification. + jobject favicon = NULL; + if (isMainFrame) { + WebCore::Image* icon = WebCore::iconDatabase()->iconForPageURL(urlString, WebCore::IntSize(16, 16)); + if (icon) + favicon = webcoreImageToJavaBitmap(env, icon); + LOGV("favicons", "Starting load with icon %p for %s", icon, url.string().utf8().data()); + } + jstring urlStr = wtfStringToJstring(env, urlString); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mLoadStarted, urlStr, favicon, + (int)loadType, isMainFrame); + checkException(env); + env->DeleteLocalRef(urlStr); + if (favicon) + env->DeleteLocalRef(favicon); + + // Inform the client that the main frame has started a new load. + if (isMainFrame && mPage) { + Chrome* chrome = mPage->chrome(); + if (chrome) { + ChromeClientAndroid* client = static_cast<ChromeClientAndroid*>(chrome->client()); + if (client) + client->onMainFrameLoadStarted(); + } + } +} + +void +WebFrame::transitionToCommitted(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + WebCore::FrameLoadType loadType = frame->loader()->loadType(); + bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mTransitionToCommitted, + (int)loadType, isMainFrame); + checkException(env); +} + +void +WebFrame::didFinishLoad(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + // activeDocumentLoader() can return null. + WebCore::FrameLoader* loader = frame->loader(); + DocumentLoader* documentLoader = loader->activeDocumentLoader(); + if (documentLoader == NULL) + return; + + const WebCore::KURL& url = documentLoader->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: didFinishLoad %s", url.string().ascii().data()); + + bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); + WebCore::FrameLoadType loadType = loader->loadType(); + const WTF::String& urlString = url.string(); + jstring urlStr = wtfStringToJstring(env, urlString); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mLoadFinished, urlStr, + (int)loadType, isMainFrame); + checkException(env); + env->DeleteLocalRef(urlStr); +} + +void +WebFrame::addHistoryItem(WebCore::HistoryItem* item) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: addHistoryItem"); + JNIEnv* env = getJNIEnv(); + WebHistory::AddItem(mJavaFrame->history(env), item); +} + +void +WebFrame::removeHistoryItem(int index) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: removeHistoryItem at %d", index); + JNIEnv* env = getJNIEnv(); + WebHistory::RemoveItem(mJavaFrame->history(env), index); +} + +void +WebFrame::updateHistoryIndex(int newIndex) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: updateHistoryIndex to %d", newIndex); + JNIEnv* env = getJNIEnv(); + WebHistory::UpdateHistoryIndex(mJavaFrame->history(env), newIndex); +} + +void +WebFrame::setTitle(const WTF::String& title) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif +#ifndef NDEBUG + LOGV("setTitle(%s)", title.ascii().data()); +#endif + JNIEnv* env = getJNIEnv(); + jstring jTitleStr = wtfStringToJstring(env, title); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetTitle, jTitleStr); + checkException(env); + env->DeleteLocalRef(jTitleStr); +} + +void +WebFrame::windowObjectCleared(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: windowObjectCleared"); + JNIEnv* env = getJNIEnv(); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mWindowObjectCleared, (int)frame); + checkException(env); +} + +void +WebFrame::setProgress(float newProgress) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + int progress = (int) (100 * newProgress); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetProgress, progress); + checkException(env); +} + +const WTF::String +WebFrame::userAgentForURL(const WebCore::KURL* url) +{ + return mUserAgent; +} + +void +WebFrame::didReceiveIcon(WebCore::Image* icon) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOG_ASSERT(icon, "DidReceiveIcon called without an image!"); + JNIEnv* env = getJNIEnv(); + jobject bitmap = webcoreImageToJavaBitmap(env, icon); + if (!bitmap) + return; + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDidReceiveIcon, bitmap); + env->DeleteLocalRef(bitmap); + checkException(env); +} + +void +WebFrame::didReceiveTouchIconURL(const WTF::String& url, bool precomposed) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDidReceiveTouchIconUrl, jUrlStr, precomposed); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +void +WebFrame::updateVisitedHistory(const WebCore::KURL& url, bool reload) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + const WTF::String& urlStr = url.string(); + JNIEnv* env = getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mUpdateVisitedHistory, jUrlStr, reload); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +bool +WebFrame::canHandleRequest(const WebCore::ResourceRequest& request) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + // always handle "POST" in place + if (equalIgnoringCase(request.httpMethod(), "POST")) + return true; + const WebCore::KURL& requestUrl = request.url(); + bool isUserGesture = UserGestureIndicator::processingUserGesture(); + if (!mUserInitiatedAction && !isUserGesture && + (requestUrl.protocolIs("http") || requestUrl.protocolIs("https") || + requestUrl.protocolIs("file") || requestUrl.protocolIs("about") || + WebCore::protocolIsJavaScript(requestUrl.string()))) + return true; + const WTF::String& url = requestUrl.string(); + // Empty urls should not be sent to java + if (url.isEmpty()) + return true; + JNIEnv* env = getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + + // check to see whether browser app wants to hijack url loading. + // if browser app handles the url, we will return false to bail out WebCore loading + jboolean ret = env->CallBooleanMethod(mJavaFrame->frame(env).get(), mJavaFrame->mHandleUrl, jUrlStr); + checkException(env); + env->DeleteLocalRef(jUrlStr); + return (ret == 0); +} + +bool +WebFrame::shouldSaveFormData() +{ + JNIEnv* env = getJNIEnv(); + jboolean ret = env->CallBooleanMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mShouldSaveFormData); + checkException(env); + return ret; +} + +WebCore::Frame* +WebFrame::createWindow(bool dialog, bool userGesture) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + jobject obj = env->CallObjectMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mCreateWindow, dialog, userGesture); + if (obj) { + WebCore::Frame* frame = GET_NATIVE_FRAME(env, obj); + return frame; + } + return NULL; +} + +void +WebFrame::requestFocus() const +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mRequestFocus); + checkException(env); +} + +void +WebFrame::closeWindow(WebViewCore* webViewCore) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + assert(webViewCore); + JNIEnv* env = getJNIEnv(); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mCloseWindow, + webViewCore->getJavaObject().get()); +} + +struct PolicyFunctionWrapper { + WebCore::FramePolicyFunction func; +}; + +void +WebFrame::decidePolicyForFormResubmission(WebCore::FramePolicyFunction func) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + PolicyFunctionWrapper* p = new PolicyFunctionWrapper; + p->func = func; + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDecidePolicyForFormResubmission, p); +} + +WTF::String +WebFrame::getRawResourceFilename(WebCore::PlatformBridge::rawResId id) const +{ + JNIEnv* env = getJNIEnv(); + jstring ret = (jstring) env->CallObjectMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mGetRawResFilename, (int)id); + + return jstringToWtfString(env, ret); +} + +float +WebFrame::density() const +{ + JNIEnv* env = getJNIEnv(); + jfloat dpi = env->CallFloatMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDensity); + checkException(env); + return dpi; +} + +#if USE(CHROME_NETWORK_STACK) +void +WebFrame::didReceiveAuthenticationChallenge(WebUrlLoaderClient* client, const std::string& host, const std::string& realm, bool useCachedCredentials) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + int jHandle = reinterpret_cast<int>(client); + jstring jHost = stdStringToJstring(env, host, true); + jstring jRealm = stdStringToJstring(env, realm, true); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDidReceiveAuthenticationChallenge, jHandle, jHost, jRealm, useCachedCredentials); + env->DeleteLocalRef(jHost); + env->DeleteLocalRef(jRealm); + checkException(env); +} +#endif + +void +WebFrame::reportSslCertError(WebUrlLoaderClient* client, int cert_error, const std::string& cert) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + int jHandle = reinterpret_cast<int>(client); + + int len = cert.length(); + jbyteArray jCert = env->NewByteArray(len); + jbyte* bytes = env->GetByteArrayElements(jCert, NULL); + cert.copy(reinterpret_cast<char*>(bytes), len); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mReportSslCertError, jHandle, cert_error, jCert); + env->DeleteLocalRef(jCert); + checkException(env); +} + +#if USE(CHROME_NETWORK_STACK) +void +WebFrame::downloadStart(const std::string& url, const std::string& userAgent, const std::string& contentDisposition, const std::string& mimetype, long long contentLength) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + jstring jUrl = stdStringToJstring(env, url, true); + jstring jUserAgent = stdStringToJstring(env, userAgent, true); + jstring jContentDisposition = stdStringToJstring(env, contentDisposition, true); + jstring jMimetype = stdStringToJstring(env, mimetype, true); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDownloadStart, jUrl, jUserAgent, jContentDisposition, jMimetype, contentLength); + + env->DeleteLocalRef(jUrl); + env->DeleteLocalRef(jUserAgent); + env->DeleteLocalRef(jContentDisposition); + env->DeleteLocalRef(jMimetype); + checkException(env); +} + +void +WebFrame::didReceiveData(const char* data, int size) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + jbyteArray jData = env->NewByteArray(size); + jbyte* bytes = env->GetByteArrayElements(jData, NULL); + memcpy(reinterpret_cast<char*>(bytes), data, size); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDidReceiveData, jData, size); + env->DeleteLocalRef(jData); + checkException(env); +} + +void +WebFrame::didFinishLoading() { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDidFinishLoading); + checkException(env); +} + +#endif + +#if USE(CHROME_NETWORK_STACK) +void WebFrame::setCertificate(const std::string& cert) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + int len = cert.length(); + jbyteArray jCert = env->NewByteArray(len); + jbyte* bytes = env->GetByteArrayElements(jCert, NULL); + cert.copy(reinterpret_cast<char*>(bytes), len); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetCertificate, jCert); + + env->DeleteLocalRef(jCert); + checkException(env); +} +#endif + +void WebFrame::autoLogin(const std::string& loginHeader) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimerCoutner::JavaCallbackTimeCounter); +#endif + WTF::String header(loginHeader.c_str(), loginHeader.length()); + WTF::Vector<WTF::String> split; + header.split('&', split); + if (!split.isEmpty()) { + WTF::String realm; + WTF::String account; + WTF::String args; + int len = split.size(); + while (len--) { + WTF::String& str = split[len]; + size_t equals = str.find('='); + if (equals == WTF::notFound) + continue; + + WTF::String* result = 0; + if (str.startsWith("realm", false)) + result = &realm; + else if (str.startsWith("account", false)) + result = &account; + else if (str.startsWith("args", false)) + result = &args; + + if (result) + // Decode url escape sequences before sending to the app. + *result = WebCore::decodeURLEscapeSequences(str.substring(equals + 1)); + } + + // realm and args are required parameters. + if (realm.isEmpty() || args.isEmpty()) + return; + + JNIEnv* env = getJNIEnv(); + jstring jRealm = wtfStringToJstring(env, realm, true); + jstring jAccount = wtfStringToJstring(env, account); + jstring jArgs = wtfStringToJstring(env, args, true); + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mAutoLogin, jRealm, jAccount, jArgs); + } +} + +void WebFrame::maybeSavePassword(WebCore::Frame* frame, const WebCore::ResourceRequest& request) +{ + if (request.httpMethod() != "POST") + return; + + WTF::String username; + WTF::String password; + if (!getUsernamePasswordFromDom(frame, username, password)) + return; + + JNIEnv* env = getJNIEnv(); + jstring jUsername = wtfStringToJstring(env, username); + jstring jPassword = wtfStringToJstring(env, password); + jbyteArray jPostData = getPostData(request); + if (jPostData) { + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mMaybeSavePassword, jPostData, jUsername, jPassword); + } + + env->DeleteLocalRef(jPostData); + env->DeleteLocalRef(jUsername); + env->DeleteLocalRef(jPassword); + checkException(env); +} + +bool WebFrame::getUsernamePasswordFromDom(WebCore::Frame* frame, WTF::String& username, WTF::String& password) +{ + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = frame->document()->forms(); + WebCore::Node* node = form->firstItem(); + while (node && !found && !node->namespaceURI().isNull() && + !node->namespaceURI().isEmpty()) { + const WTF::Vector<WebCore::FormAssociatedElement*>& elements = + ((WebCore::HTMLFormElement*)node)->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->isPasswordField()) + password = input->value(); + else if (input->isTextField() || input->isEmailField()) + username = input->value(); + if (!username.isNull() && !password.isNull()) + found = true; + } + } + node = form->nextItem(); + } + return found; +} + +jbyteArray WebFrame::getPostData(const WebCore::ResourceRequest& request) +{ + jbyteArray jPostDataStr = NULL; + WebCore::FormData* formdata = request.httpBody(); + if (formdata) { + JNIEnv* env = getJNIEnv(); + AutoJObject obj = mJavaFrame->frame(env); + + // We can use the formdata->flatten() but it will result in two + // memcpys, first through loading up the vector with the form data + // then another to copy it out of the vector and into the java byte + // array. Instead, we copy the form data ourselves below saving a + // memcpy. + const WTF::Vector<WebCore::FormDataElement>& elements = + formdata->elements(); + + // Sizing pass + int size = 0; + size_t n = elements.size(); + FileInfo** fileinfos = new FileInfo*[n]; + for (size_t i = 0; i < n; ++i) { + fileinfos[i] = 0; + const WebCore::FormDataElement& e = elements[i]; + if (e.m_type == WebCore::FormDataElement::data) { + size += e.m_data.size(); + } else if (e.m_type == WebCore::FormDataElement::encodedFile) { + fileinfos[i] = new FileInfo(env, e.m_filename); + int delta = env->CallIntMethod(obj.get(), + mJavaFrame->mGetFileSize, fileinfos[i]->getUri()); + checkException(env); + fileinfos[i]->setSize(delta); + size += delta; + } + } + + // Only create the byte array if there is POST data to pass up. + // The Java code is expecting null if there is no data. + if (size > 0) { + // Copy the actual form data. + jPostDataStr = env->NewByteArray(size); + if (jPostDataStr) { + // Write the form data to the java array. + jbyte* bytes = env->GetByteArrayElements(jPostDataStr, NULL); + int offset = 0; + for (size_t i = 0; i < n; ++i) { + const WebCore::FormDataElement& e = elements[i]; + if (e.m_type == WebCore::FormDataElement::data) { + int delta = e.m_data.size(); + memcpy(bytes + offset, e.m_data.data(), delta); + offset += delta; + } else if (e.m_type + == WebCore::FormDataElement::encodedFile) { + int delta = env->CallIntMethod(obj.get(), + mJavaFrame->mGetFile, fileinfos[i]->getUri(), + jPostDataStr, offset, fileinfos[i]->getSize()); + checkException(env); + offset += delta; + } + } + env->ReleaseByteArrayElements(jPostDataStr, bytes, 0); + } + } + delete[] fileinfos; + } + return jPostDataStr; +} + +// ---------------------------------------------------------------------------- + +static void CallPolicyFunction(JNIEnv* env, jobject obj, jint func, jint decision) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeCallPolicyFunction must take a valid frame pointer!"); + PolicyFunctionWrapper* pFunc = (PolicyFunctionWrapper*)func; + LOG_ASSERT(pFunc, "nativeCallPolicyFunction must take a valid function pointer!"); + + // If we are resending the form then we should reset the multiple submission protection. + if (decision == WebCore::PolicyUse) + pFrame->loader()->resetMultipleFormSubmissionProtection(); + + (pFrame->loader()->policyChecker()->*(pFunc->func))((WebCore::PolicyAction)decision); +} + +static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAssetManager, jobject historyList) +{ + ScriptController::initializeThreading(); + +#if USE(CHROME_NETWORK_STACK) + // needs to be called before any other chromium code + initChromium(); +#endif + +#ifdef ANDROID_INSTRUMENT +#if USE(V8) + V8Counters::initCounters(); +#endif + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + // Create a new page + ChromeClientAndroid* chromeC = new ChromeClientAndroid; + EditorClientAndroid* editorC = new EditorClientAndroid; + DeviceMotionClientAndroid* deviceMotionC = new DeviceMotionClientAndroid; + DeviceOrientationClientAndroid* deviceOrientationC = new DeviceOrientationClientAndroid; + + WebCore::Page::PageClients pageClients; + pageClients.chromeClient = chromeC; + pageClients.contextMenuClient = new ContextMenuClientAndroid; + pageClients.editorClient = editorC; + pageClients.dragClient = new DragClientAndroid; + pageClients.inspectorClient = new InspectorClientAndroid; + pageClients.deviceMotionClient = deviceMotionC; + pageClients.deviceOrientationClient = deviceOrientationC; + WebCore::Page* page = new WebCore::Page(pageClients); + + editorC->setPage(page); + page->setGroupName("android.webkit"); + + // Create a WebFrame to access the Java BrowserFrame associated with this page + WebFrame* webFrame = new WebFrame(env, obj, historyList, page); + // Attach webFrame to pageClients.chromeClient and release our ownership + chromeC->setWebFrame(webFrame); + Release(webFrame); + + FrameLoaderClientAndroid* loaderC = new FrameLoaderClientAndroid(webFrame); + // Create a Frame and the page holds its reference + WebCore::Frame* frame = WebCore::Frame::create(page, NULL, loaderC).get(); + loaderC->setFrame(frame); +#if ENABLE(WDS) + WDS::server()->addFrame(frame); +#endif + + // Create a WebViewCore to access the Java WebViewCore associated with this page + WebViewCore* webViewCore = new WebViewCore(env, javaview, frame); + +#if ENABLE(WEB_AUTOFILL) + editorC->getAutoFill()->setWebViewCore(webViewCore); +#endif + + // Create a FrameView + RefPtr<WebCore::FrameView> frameView = WebCore::FrameView::create(frame); + // Create a WebFrameView + WebFrameView* webFrameView = new WebFrameView(frameView.get(), webViewCore); + // As webFrameView Retains webViewCore, release our ownership + Release(webViewCore); + // As frameView Retains webFrameView, release our ownership + Release(webFrameView); + // Attach the frameView to the frame and release our ownership + frame->setView(frameView); + // Set the frame to active to turn on keyboard focus. + frame->init(); + frame->selection()->setFocused(true); + frame->page()->focusController()->setFocused(true); + deviceMotionC->setWebViewCore(webViewCore); + deviceOrientationC->setWebViewCore(webViewCore); + + // Allow local access to file:/// and substitute data + WebCore::SecurityOrigin::setLocalLoadPolicy( + WebCore::SecurityOrigin::AllowLocalLoadsForLocalAndSubstituteData); + + LOGV("::WebCore:: createFrame %p", frame); + + // Set the mNativeFrame field in Frame + SET_NATIVE_FRAME(env, obj, (int)frame); + + String directory = webFrame->getRawResourceFilename( + WebCore::PlatformBridge::DrawableDir); + if (directory.isEmpty()) + LOGE("Can't find the drawable directory"); + else { + // Setup the asset manager. + AssetManager* am = assetManagerForJavaObject(env, jAssetManager); + // Initialize our skinning classes + webFrame->setRenderSkins(new WebCore::RenderSkinAndroid(am, directory)); + } + for (int i = WebCore::PlatformBridge::FileUploadLabel; + i <= WebCore::PlatformBridge::FileUploadNoFileChosenLabel; i++) + initGlobalLocalizedName( + static_cast<WebCore::PlatformBridge::rawResId>(i), webFrame); +} + +static void DestroyFrame(JNIEnv* env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeDestroyFrame must take a valid frame pointer!"); + + LOGV("::WebCore:: deleting frame %p", pFrame); + + WebCore::FrameView* view = pFrame->view(); + view->ref(); + // detachFromParent will cause the page to be closed. + WebCore::FrameLoader* fl = pFrame->loader(); + // retain a pointer because detachFromParent will set the page to null. + WebCore::Page* page = pFrame->page(); + if (fl) + fl->detachFromParent(); + delete page; + view->deref(); + + SET_NATIVE_FRAME(env, obj, 0); +#if ENABLE(WDS) + WDS::server()->removeFrame(pFrame); +#endif +} + +static void LoadUrl(JNIEnv *env, jobject obj, jstring url, jobject headers) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!"); + + WTF::String webcoreUrl = jstringToWtfString(env, url); + WebCore::KURL kurl(WebCore::KURL(), webcoreUrl); + WebCore::ResourceRequest request(kurl); + if (headers) { + // dalvikvm will raise exception if any of these fail + jclass mapClass = env->FindClass("java/util/Map"); + jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", + "()Ljava/util/Set;"); + jobject set = env->CallObjectMethod(headers, entrySet); + + jclass setClass = env->FindClass("java/util/Set"); + jmethodID iterator = env->GetMethodID(setClass, "iterator", + "()Ljava/util/Iterator;"); + jobject iter = env->CallObjectMethod(set, iterator); + + jclass iteratorClass = env->FindClass("java/util/Iterator"); + jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext", "()Z"); + jmethodID next = env->GetMethodID(iteratorClass, "next", + "()Ljava/lang/Object;"); + jclass entryClass = env->FindClass("java/util/Map$Entry"); + jmethodID getKey = env->GetMethodID(entryClass, "getKey", + "()Ljava/lang/Object;"); + jmethodID getValue = env->GetMethodID(entryClass, "getValue", + "()Ljava/lang/Object;"); + + while (env->CallBooleanMethod(iter, hasNext)) { + jobject entry = env->CallObjectMethod(iter, next); + jstring key = (jstring) env->CallObjectMethod(entry, getKey); + jstring value = (jstring) env->CallObjectMethod(entry, getValue); + request.setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, value)); + env->DeleteLocalRef(entry); + env->DeleteLocalRef(key); + env->DeleteLocalRef(value); + } + + env->DeleteLocalRef(entryClass); + env->DeleteLocalRef(iteratorClass); + env->DeleteLocalRef(iter); + env->DeleteLocalRef(setClass); + env->DeleteLocalRef(set); + env->DeleteLocalRef(mapClass); + } + LOGV("LoadUrl %s", kurl.string().latin1().data()); + pFrame->loader()->load(request, false); +} + +static void PostUrl(JNIEnv *env, jobject obj, jstring url, jbyteArray postData) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativePostUrl must take a valid frame pointer!"); + + WebCore::KURL kurl(WebCore::KURL(), jstringToWtfString(env, url)); + WebCore::ResourceRequest request(kurl); + request.setHTTPMethod("POST"); + request.setHTTPContentType("application/x-www-form-urlencoded"); + + if (postData) { + jsize size = env->GetArrayLength(postData); + jbyte* bytes = env->GetByteArrayElements(postData, NULL); + RefPtr<FormData> formData = FormData::create((const void*)bytes, size); + // the identifier uses the same logic as generateFormDataIdentifier() in + // HTMLFormElement.cpp + formData->setIdentifier(static_cast<int64_t>(WTF::currentTime() * 1000000.0)); + request.setHTTPBody(formData); + env->ReleaseByteArrayElements(postData, bytes, 0); + } + + LOGV("PostUrl %s", kurl.string().latin1().data()); + WebCore::FrameLoadRequest frameRequest(pFrame->document()->securityOrigin(), request); + pFrame->loader()->loadFrameRequest(frameRequest, false, false, 0, 0, WebCore::SendReferrer); +} + +static void LoadData(JNIEnv *env, jobject obj, jstring baseUrl, jstring data, + jstring mimeType, jstring encoding, jstring failUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeLoadData must take a valid frame pointer!"); + + // Setup the resource request + WebCore::ResourceRequest request(jstringToWtfString(env, baseUrl)); + + // Setup the substituteData + const char* dataStr = env->GetStringUTFChars(data, NULL); + WTF::PassRefPtr<WebCore::SharedBuffer> sharedBuffer = + WebCore::SharedBuffer::create(); + LOG_ASSERT(dataStr, "nativeLoadData has a null data string."); + sharedBuffer->append(dataStr, strlen(dataStr)); + env->ReleaseStringUTFChars(data, dataStr); + + WebCore::SubstituteData substituteData(sharedBuffer, + jstringToWtfString(env, mimeType), jstringToWtfString(env, encoding), + WebCore::KURL(ParsedURLString, jstringToWtfString(env, failUrl))); + + // Perform the load + pFrame->loader()->load(request, substituteData, false); +} + +static void StopLoading(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeStopLoading must take a valid frame pointer!"); + LOGV("::WebCore:: stopLoading %p", pFrame); + + // Stop loading the page and do not send an unload event + pFrame->loader()->stopForUserCancel(); +} + +#if ENABLE(ARCHIVE) +static String saveArchiveAutoname(String basename, String name, String extension) { + if (name.isNull() || name.isEmpty()) { + name = String("index"); + } + + String testname = basename; + testname.append(name); + testname.append(extension); + + errno = 0; + struct stat permissions; + if (stat(testname.utf8().data(), &permissions) < 0) { + if (errno == ENOENT) + return testname; + return String(); + } + + const int maxAttempts = 100; + for (int i = 1; i < maxAttempts; i++) { + String testname = basename; + testname.append(name); + testname.append("-"); + testname.append(String::number(i)); + testname.append(extension); + + errno = 0; + if (stat(testname.utf8().data(), &permissions) < 0) { + if (errno == ENOENT) + return testname; + return String(); + } + } + + return String(); +} +#endif + +static jstring SaveWebArchive(JNIEnv *env, jobject obj, jstring basename, jboolean autoname) +{ +#if ENABLE(ARCHIVE) + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeSaveWebArchive must take a valid frame pointer!"); + String mimeType = pFrame->loader()->documentLoader()->mainResource()->mimeType(); + if ((mimeType != "text/html") && (mimeType != "application/xhtml+xml")) + return NULL; + + const char* basenameNative = getCharactersFromJStringInEnv(env, basename); + String basenameString = String::fromUTF8(basenameNative); + String filename; + + if (autoname) { + String name = pFrame->loader()->documentLoader()->originalURL().lastPathComponent(); + String extension = String(".webarchivexml"); + filename = saveArchiveAutoname(basenameString, name, extension); + } else { + filename = basenameString; + } + + if (filename.isNull() || filename.isEmpty()) { + LOGD("saveWebArchive: Failed to select a filename to save."); + releaseCharactersForJStringInEnv(env, basename, basenameNative); + return NULL; + } + + const int noCompression = 0; + xmlTextWriterPtr writer = xmlNewTextWriterFilename(filename.utf8().data(), noCompression); + if (writer == NULL) { + LOGD("saveWebArchive: Failed to initialize xml writer."); + releaseCharactersForJStringInEnv(env, basename, basenameNative); + return NULL; + } + + RefPtr<WebArchiveAndroid> archive = WebCore::WebArchiveAndroid::create(pFrame); + + bool result = archive->saveWebArchive(writer); + + releaseCharactersForJStringInEnv(env, basename, basenameNative); + xmlFreeTextWriter(writer); + + if (result) + return wtfStringToJstring(env, filename); + + return NULL; +#endif +} + +static jstring ExternalRepresentation(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "android_webcore_nativeExternalRepresentation must take a valid frame pointer!"); + + // Request external representation of the render tree + WTF::String renderDump = WebCore::externalRepresentation(pFrame); + return wtfStringToJstring(env, renderDump); +} + +static StringBuilder FrameAsText(WebCore::Frame *pFrame, jboolean dumpChildFrames) { + StringBuilder renderDump; + if (!pFrame) + return renderDump; + WebCore::Element *documentElement = pFrame->document()->documentElement(); + if (!documentElement) + return renderDump; + if (pFrame->tree()->parent()) { + renderDump.append("\n--------\nFrame: '"); + renderDump.append(pFrame->tree()->name()); + renderDump.append("'\n--------\n"); + } + renderDump.append(((WebCore::HTMLElement*)documentElement)->innerText()); + renderDump.append("\n"); + if (dumpChildFrames) { + for (unsigned i = 0; i < pFrame->tree()->childCount(); ++i) { + renderDump.append(FrameAsText(pFrame->tree()->child(i), dumpChildFrames).toString()); + } + } + return renderDump; +} + +static jstring DocumentAsText(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); + + WTF::String renderDump = FrameAsText(pFrame, false /* dumpChildFrames */).toString(); + return wtfStringToJstring(env, renderDump); +} + +static jstring ChildFramesAsText(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); + + StringBuilder renderDumpBuilder; + for (unsigned i = 0; i < pFrame->tree()->childCount(); ++i) { + renderDumpBuilder.append(FrameAsText(pFrame->tree()->child(i), true /* dumpChildFrames */).toString()); + } + WTF::String renderDump = renderDumpBuilder.toString(); + return wtfStringToJstring(env, renderDump); +} + +static void Reload(JNIEnv *env, jobject obj, jboolean allowStale) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeReload must take a valid frame pointer!"); + + WebCore::FrameLoader* loader = pFrame->loader(); + if (allowStale) { + // load the current page with FrameLoadTypeIndexedBackForward so that it + // will use cache when it is possible + WebCore::Page* page = pFrame->page(); + WebCore::HistoryItem* item = page->backForwardList()->currentItem(); + if (item) + page->goToItem(item, FrameLoadTypeIndexedBackForward); + } else + loader->reload(true); +} + +static void GoBackOrForward(JNIEnv *env, jobject obj, jint pos) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeGoBackOrForward must take a valid frame pointer!"); + + if (pos == 1) + pFrame->page()->goForward(); + else if (pos == -1) + pFrame->page()->goBack(); + else + pFrame->page()->goBackOrForward(pos); +} + +static jobject StringByEvaluatingJavaScriptFromString(JNIEnv *env, jobject obj, jstring script) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "stringByEvaluatingJavaScriptFromString must take a valid frame pointer!"); + + WebCore::ScriptValue value = + pFrame->script()->executeScript(jstringToWtfString(env, script), true); + WTF::String result = WTF::String(); + ScriptState* scriptState = mainWorldScriptState(pFrame); + if (!value.getString(scriptState, result)) + return NULL; + return wtfStringToJstring(env, result); +} + +// Wrap the JavaInstance used when binding custom javascript interfaces. Use a +// weak reference so that the gc can collect the WebView. Override virtualBegin +// and virtualEnd and swap the weak reference for the real object. +class WeakJavaInstance : public JavaInstance { +public: +#if USE(JSC) + static PassRefPtr<WeakJavaInstance> create(jobject obj, PassRefPtr<RootObject> root) + { + return adoptRef(new WeakJavaInstance(obj, root)); + } +#elif USE(V8) + static PassRefPtr<WeakJavaInstance> create(jobject obj) + { + return adoptRef(new WeakJavaInstance(obj)); + } +#endif + +private: +#if USE(JSC) + WeakJavaInstance(jobject instance, PassRefPtr<RootObject> rootObject) + : JavaInstance(instance, rootObject) +#elif USE(V8) + WeakJavaInstance(jobject instance) + : JavaInstance(instance) +#endif + , m_beginEndDepth(0) + { + JNIEnv* env = getJNIEnv(); + // JavaInstance creates a global ref to instance in its constructor. + env->DeleteGlobalRef(m_instance->instance()); + // Set the object to a weak reference. + m_instance->setInstance(env->NewWeakGlobalRef(instance)); + } + ~WeakJavaInstance() + { + JNIEnv* env = getJNIEnv(); + // Store the weak reference so we can delete it later. + jweak weak = m_instance->instance(); + // The JavaInstance destructor attempts to delete the global ref stored + // in m_instance. Since we replaced it in our constructor with a weak + // reference, restore the global ref here so the vm will not complain. + m_instance->setInstance(env->NewGlobalRef( + getRealObject(env, m_instance->instance()).get())); + // Delete the weak reference. + env->DeleteWeakGlobalRef(weak); + } + + virtual void virtualBegin() + { + if (m_beginEndDepth++ > 0) + return; + m_weakRef = m_instance->instance(); + JNIEnv* env = getJNIEnv(); + // This is odd. getRealObject returns an AutoJObject which is used to + // cleanly create and delete a local reference. But, here we need to + // maintain the local reference across calls to virtualBegin() and + // virtualEnd(). So, release the local reference from the AutoJObject + // and delete the local reference in virtualEnd(). + m_realObject = getRealObject(env, m_weakRef).release(); + // Point to the real object + m_instance->setInstance(m_realObject); + // Call the base class method + INHERITED::virtualBegin(); + } + + virtual void virtualEnd() + { + if (--m_beginEndDepth > 0) + return; + // Call the base class method first to pop the local frame. + INHERITED::virtualEnd(); + // Get rid of the local reference to the real object. + getJNIEnv()->DeleteLocalRef(m_realObject); + // Point back to the WeakReference. + m_instance->setInstance(m_weakRef); + } + +private: + typedef JavaInstance INHERITED; + jobject m_realObject; + jweak m_weakRef; + // The current depth of nested calls to virtualBegin and virtualEnd. + int m_beginEndDepth; +}; + +static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer, + jobject javascriptObj, jstring interfaceName) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = 0; + if (nativeFramePointer == 0) + pFrame = GET_NATIVE_FRAME(env, obj); + else + pFrame = (WebCore::Frame*)nativeFramePointer; + LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!"); + + JavaVM* vm; + env->GetJavaVM(&vm); + LOGV("::WebCore:: addJSInterface: %p", pFrame); + +#if USE(JSC) + // Copied from qwebframe.cpp + JSC::JSLock lock(JSC::SilenceAssertionsOnly); + WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame, mainThreadNormalWorld()); + if (window) { + RootObject *root = pFrame->script()->bindingRootObject(); + setJavaVM(vm); + // Add the binding to JS environment + JSC::ExecState* exec = window->globalExec(); + JSC::JSObject* addedObject = WeakJavaInstance::create(javascriptObj, + root)->createRuntimeObject(exec); + const jchar* s = env->GetStringChars(interfaceName, NULL); + if (s) { + // Add the binding name to the window's table of child objects. + JSC::PutPropertySlot slot; + window->put(exec, JSC::Identifier(exec, (const UChar *)s, + env->GetStringLength(interfaceName)), addedObject, slot); + env->ReleaseStringChars(interfaceName, s); + checkException(env); + } + } +#elif USE(V8) + if (pFrame) { + RefPtr<JavaInstance> addedObject = WeakJavaInstance::create(javascriptObj); + const char* name = getCharactersFromJStringInEnv(env, interfaceName); + // Pass ownership of the added object to bindToWindowObject. + NPObject* npObject = JavaInstanceToNPObject(addedObject.get()); + pFrame->script()->bindToWindowObject(pFrame, name, npObject); + // bindToWindowObject calls NPN_RetainObject on the + // returned one (see createV8ObjectForNPObject in V8NPObject.cpp). + // bindToWindowObject also increases obj's ref count and decreases + // the ref count when the object is not reachable from JavaScript + // side. Code here must release the reference count increased by + // bindToWindowObject. + + // Note that while this function is declared in WebCore/bridge/npruntime.h, for V8 builds + // we use WebCore/bindings/v8/npruntime.cpp (rather than + // WebCore/bridge/npruntime.cpp), so the function is implemented there. + // TODO: Combine the two versions of these NPAPI files. + NPN_ReleaseObject(npObject); + releaseCharactersForJString(interfaceName, name); + } +#endif + +} + +static void SetCacheDisabled(JNIEnv *env, jobject obj, jboolean disabled) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::cache()->setDisabled(disabled); +} + +static jboolean CacheDisabled(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + return WebCore::cache()->disabled(); +} + +static void ClearWebCoreCache() +{ + if (!WebCore::cache()->disabled()) { + // Disabling the cache will remove all resources from the cache. They may + // still live on if they are referenced by some Web page though. + WebCore::cache()->setDisabled(true); + WebCore::cache()->setDisabled(false); + } + + // clear page cache + int pageCapacity = WebCore::pageCache()->capacity(); + // Setting size to 0, makes all pages be released. + WebCore::pageCache()->setCapacity(0); + WebCore::pageCache()->releaseAutoreleasedPagesNow(); + WebCore::pageCache()->setCapacity(pageCapacity); +} + +static void ClearWebViewCache() +{ +#if USE(CHROME_NETWORK_STACK) + WebCache::get(false /*privateBrowsing*/)->clear(); +#else + // The Android network stack provides a WebView cache in CacheManager.java. + // Clearing this is handled entirely Java-side. +#endif +} + +static void ClearCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#if USE(JSC) + JSC::JSLock lock(false); + JSC::Heap::Statistics jsHeapStatistics = WebCore::JSDOMWindow::commonJSGlobalData()->heap.statistics(); + LOGD("About to gc and JavaScript heap size is %d and has %d bytes free", + jsHeapStatistics.size, jsHeapStatistics.free); +#endif // USE(JSC) + LOGD("About to clear cache and current cache has %d bytes live and %d bytes dead", + cache()->getLiveSize(), cache()->getDeadSize()); +#endif // ANDROID_INSTRUMENT + ClearWebCoreCache(); + ClearWebViewCache(); +#if USE(JSC) + // force JavaScript to GC when clear cache + WebCore::gcController().garbageCollectSoon(); +#elif USE(V8) + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + pFrame->script()->lowMemoryNotification(); +#endif // USE(JSC) +} + +static jboolean DocumentHasImages(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "DocumentHasImages must take a valid frame pointer!"); + + return pFrame->document()->images()->length() > 0; +} + +static jboolean HasPasswordField(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "HasPasswordField must take a valid frame pointer!"); + + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WebCore::Node* node = form->firstItem(); + // Null/Empty namespace means that node is not created in HTMLFormElement + // class, but just normal Element class. + while (node && !found && !node->namespaceURI().isNull() && + !node->namespaceURI().isEmpty()) { + const WTF::Vector<WebCore::FormAssociatedElement*>& elements = + ((WebCore::HTMLFormElement*)node)->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + if (static_cast<WebCore::HTMLInputElement*>(e)->isPasswordField()) + found = true; + } + } + node = form->nextItem(); + } + return found; +} + +static jobjectArray GetUsernamePassword(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "GetUsernamePassword must take a valid frame pointer!"); + jobjectArray strArray = NULL; + WTF::String username; + WTF::String password; + if (WebFrame::getWebFrame(pFrame)->getUsernamePasswordFromDom(pFrame, username, password)) { + jclass stringClass = env->FindClass("java/lang/String"); + strArray = env->NewObjectArray(2, stringClass, NULL); + env->DeleteLocalRef(stringClass); + env->SetObjectArrayElement(strArray, 0, wtfStringToJstring(env, username)); + env->SetObjectArrayElement(strArray, 1, wtfStringToJstring(env, password)); + } + return strArray; +} + +static void SetUsernamePassword(JNIEnv *env, jobject obj, + jstring username, jstring password) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "SetUsernamePassword must take a valid frame pointer!"); + + WebCore::HTMLInputElement* usernameEle = NULL; + WebCore::HTMLInputElement* passwordEle = NULL; + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WebCore::Node* node = form->firstItem(); + while (node && !found && !node->namespaceURI().isNull() && + !node->namespaceURI().isEmpty()) { + const WTF::Vector<WebCore::FormAssociatedElement*>& elements = + ((WebCore::HTMLFormElement*)node)->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->isPasswordField()) + passwordEle = input; + else if (input->isTextField() || input->isEmailField()) + usernameEle = input; + if (usernameEle != NULL && passwordEle != NULL) + found = true; + } + } + node = form->nextItem(); + } + if (found) { + usernameEle->setValue(jstringToWtfString(env, username)); + passwordEle->setValue(jstringToWtfString(env, password)); + } +} + +void +WebFrame::saveFormData(HTMLFormElement* form) +{ + if (form->autoComplete()) { + JNIEnv* env = getJNIEnv(); + jclass mapClass = env->FindClass("java/util/HashMap"); + LOG_ASSERT(mapClass, "Could not find HashMap class!"); + jmethodID init = env->GetMethodID(mapClass, "<init>", "(I)V"); + LOG_ASSERT(init, "Could not find constructor for HashMap"); + jobject hashMap = env->NewObject(mapClass, init, 1); + LOG_ASSERT(hashMap, "Could not create a new HashMap"); + jmethodID put = env->GetMethodID(mapClass, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + LOG_ASSERT(put, "Could not find put method on HashMap"); + WTF::Vector<WebCore::FormAssociatedElement*> elements = form->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i < size; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasTagName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = static_cast<WebCore::HTMLInputElement*>(e); + if (input->isTextField() && !input->isPasswordField() + && input->autoComplete()) { + WTF::String value = input->value(); + int len = value.length(); + if (len) { + const WTF::AtomicString& name = input->name(); + jstring key = wtfStringToJstring(env, name); + jstring val = wtfStringToJstring(env, value); + LOG_ASSERT(key && val, "name or value not set"); + env->CallObjectMethod(hashMap, put, key, val); + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + } + } + } + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSaveFormData, hashMap); + env->DeleteLocalRef(hashMap); + env->DeleteLocalRef(mapClass); + } +} + +static void OrientationChanged(JNIEnv *env, jobject obj, int orientation) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOGV("Sending orientation: %d", orientation); + pFrame->sendOrientationChangeEvent(orientation); +} + +#if USE(CHROME_NETWORK_STACK) + +static void AuthenticationProceed(JNIEnv *env, jobject obj, int handle, jstring jUsername, jstring jPassword) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + std::string username = jstringToStdString(env, jUsername); + std::string password = jstringToStdString(env, jPassword); + client->setAuth(username, password); +} + +static void AuthenticationCancel(JNIEnv *env, jobject obj, int handle) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + client->cancelAuth(); +} + +static void SslCertErrorProceed(JNIEnv *env, jobject obj, int handle) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + client->proceedSslCertError(); +} + +static void SslCertErrorCancel(JNIEnv *env, jobject obj, int handle, int cert_error) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + client->cancelSslCertError(cert_error); +} + +#else + +static void AuthenticationProceed(JNIEnv *env, jobject obj, int handle, jstring jUsername, jstring jPassword) +{ + LOGW("Chromium authentication API called, but libchromium is not available"); +} + +static void AuthenticationCancel(JNIEnv *env, jobject obj, int handle) +{ + LOGW("Chromium authentication API called, but libchromium is not available"); +} + +static void SslCertErrorProceed(JNIEnv *env, jobject obj, int handle) +{ + LOGW("Chromium SSL API called, but libchromium is not available"); +} + +static void SslCertErrorCancel(JNIEnv *env, jobject obj, int handle, int cert_error) +{ + LOGW("Chromium SSL API called, but libchromium is not available"); +} + +#endif // USE(CHROME_NETWORK_STACK) + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gBrowserFrameNativeMethods[] = { + /* name, signature, funcPtr */ + { "nativeCallPolicyFunction", "(II)V", + (void*) CallPolicyFunction }, + { "nativeCreateFrame", "(Landroid/webkit/WebViewCore;Landroid/content/res/AssetManager;Landroid/webkit/WebBackForwardList;)V", + (void*) CreateFrame }, + { "nativeDestroyFrame", "()V", + (void*) DestroyFrame }, + { "nativeStopLoading", "()V", + (void*) StopLoading }, + { "nativeLoadUrl", "(Ljava/lang/String;Ljava/util/Map;)V", + (void*) LoadUrl }, + { "nativePostUrl", "(Ljava/lang/String;[B)V", + (void*) PostUrl }, + { "nativeLoadData", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (void*) LoadData }, + { "nativeSaveWebArchive", "(Ljava/lang/String;Z)Ljava/lang/String;", + (void*) SaveWebArchive }, + { "externalRepresentation", "()Ljava/lang/String;", + (void*) ExternalRepresentation }, + { "documentAsText", "()Ljava/lang/String;", + (void*) DocumentAsText }, + { "childFramesAsText", "()Ljava/lang/String;", + (void*) ChildFramesAsText }, + { "reload", "(Z)V", + (void*) Reload }, + { "nativeGoBackOrForward", "(I)V", + (void*) GoBackOrForward }, + { "nativeAddJavascriptInterface", "(ILjava/lang/Object;Ljava/lang/String;)V", + (void*) AddJavascriptInterface }, + { "stringByEvaluatingJavaScriptFromString", + "(Ljava/lang/String;)Ljava/lang/String;", + (void*) StringByEvaluatingJavaScriptFromString }, + { "setCacheDisabled", "(Z)V", + (void*) SetCacheDisabled }, + { "cacheDisabled", "()Z", + (void*) CacheDisabled }, + { "clearCache", "()V", + (void*) ClearCache }, + { "documentHasImages", "()Z", + (void*) DocumentHasImages }, + { "hasPasswordField", "()Z", + (void*) HasPasswordField }, + { "getUsernamePassword", "()[Ljava/lang/String;", + (void*) GetUsernamePassword }, + { "setUsernamePassword", "(Ljava/lang/String;Ljava/lang/String;)V", + (void*) SetUsernamePassword }, + { "nativeOrientationChanged", "(I)V", + (void*) OrientationChanged }, + { "nativeAuthenticationProceed", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) AuthenticationProceed }, + { "nativeAuthenticationCancel", "(I)V", + (void*) AuthenticationCancel }, + { "nativeSslCertErrorProceed", "(I)V", + (void*) SslCertErrorProceed }, + { "nativeSslCertErrorCancel", "(II)V", + (void*) SslCertErrorCancel }, +}; + +int registerWebFrame(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/BrowserFrame"); + LOG_ASSERT(clazz, "Cannot find BrowserFrame"); + gFrameField = env->GetFieldID(clazz, "mNativeFrame", "I"); + LOG_ASSERT(gFrameField, "Cannot find mNativeFrame on BrowserFrame"); + env->DeleteLocalRef(clazz); + + return jniRegisterNativeMethods(env, "android/webkit/BrowserFrame", + gBrowserFrameNativeMethods, NELEM(gBrowserFrameNativeMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebCoreFrameBridge.h b/Source/WebKit/android/jni/WebCoreFrameBridge.h new file mode 100644 index 0000000..6522a5f --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreFrameBridge.h @@ -0,0 +1,173 @@ +/* + * 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. + */ + +// TODO: change name to WebFrame.h + +#ifndef WEBFRAME_H +#define WEBFRAME_H + +#include "FrameLoaderClient.h" +#include "PlatformBridge.h" +#include "PlatformString.h" +#include "WebCoreRefObject.h" +#include <jni.h> +#include <string> +#include <wtf/RefCounted.h> + +namespace WebCore { + class HTMLFormElement; + class Frame; + class HistoryItem; + class Image; + class Page; + class RenderPart; + class RenderSkinAndroid; + class ResourceHandle; + class ResourceLoaderAndroid; + class ResourceRequest; +} + +namespace android { + +class WebViewCore; +class WebUrlLoaderClient; +class UrlInterceptResponse; + +// one instance of WebFrame per Page for calling into Java's BrowserFrame +class WebFrame : public WebCoreRefObject { + public: + WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* page); + ~WebFrame(); + + // helper function + static WebFrame* getWebFrame(const WebCore::Frame* frame); + + virtual PassRefPtr<WebCore::ResourceLoaderAndroid> startLoadingResource(WebCore::ResourceHandle*, + const WebCore::ResourceRequest& request, bool mainResource, + bool synchronous); + + UrlInterceptResponse* shouldInterceptRequest(const WTF::String& url); + + void reportError(int errorCode, const WTF::String& description, + const WTF::String& failingUrl); + + void loadStarted(WebCore::Frame* frame); + + void transitionToCommitted(WebCore::Frame* frame); + + void didFinishLoad(WebCore::Frame* frame); + + void addHistoryItem(WebCore::HistoryItem* item); + + void removeHistoryItem(int index); + + void updateHistoryIndex(int newIndex); + + void setTitle(const WTF::String& title); + + void windowObjectCleared(WebCore::Frame* frame); + + void setProgress(float newProgress); + + const WTF::String userAgentForURL(const WebCore::KURL* url); + + void didReceiveIcon(WebCore::Image* icon); + + void didReceiveTouchIconURL(const WTF::String& url, bool precomposed); + + void updateVisitedHistory(const WebCore::KURL& url, bool reload); + + virtual bool canHandleRequest(const WebCore::ResourceRequest& request); + + WebCore::Frame* createWindow(bool dialog, bool userGesture); + + void requestFocus() const; + + void closeWindow(WebViewCore* webViewCore); + + void decidePolicyForFormResubmission(WebCore::FramePolicyFunction func); + + void setUserAgent(WTF::String userAgent) { mUserAgent = userAgent; } + + WTF::String getRawResourceFilename(WebCore::PlatformBridge::rawResId) const; + + float density() const; + + void didReceiveAuthenticationChallenge(WebUrlLoaderClient*, const std::string& host, const std::string& realm, bool useCachedCredentials); + + void reportSslCertError(WebUrlLoaderClient* client, int cert_error, const std::string& cert); + + void downloadStart(const std::string& url, const std::string& userAgent, const std::string& contentDisposition, const std::string& mimetype, long long contentLength); + + void didReceiveData(const char* data, int size); + + void didFinishLoading(); + + void maybeSavePassword(WebCore::Frame* frame, const WebCore::ResourceRequest& request); + + void setCertificate(const std::string& cert); + + // Parse the x-auto-login header and propagate the parameters to the + // application. + void autoLogin(const std::string& loginHeader); + + /** + * When the user initiates a click, we set mUserInitiatedAction to true. + * If a load happens due to this click, then we ask the application if it wants + * to override the load. Otherwise, we attempt to load the resource internally. + */ + void setUserInitiatedAction(bool userInitiatedAction) { mUserInitiatedAction = userInitiatedAction; } + + WebCore::Page* page() const { return mPage; } + + // Currently used only by the chrome net stack. A similar field is used by + // FrameLoader.java to block java network loads. + void setBlockNetworkLoads(bool block) { mBlockNetworkLoads = block; } + bool blockNetworkLoads() const { return mBlockNetworkLoads; } + + /** + * Helper methods. These are typically chunks of code that are called in + * slightly different ways by the Apache and Chrome HTTP stacks. + */ + bool getUsernamePasswordFromDom(WebCore::Frame* frame, WTF::String& username, WTF::String& password); + jbyteArray getPostData(const WebCore::ResourceRequest& request); + + bool shouldSaveFormData(); + void saveFormData(WebCore::HTMLFormElement*); + const WebCore::RenderSkinAndroid* renderSkins() const { return m_renderSkins; } + void setRenderSkins(const WebCore::RenderSkinAndroid* skins) { m_renderSkins = skins; } +private: + struct JavaBrowserFrame; + JavaBrowserFrame* mJavaFrame; + WebCore::Page* mPage; + WTF::String mUserAgent; + bool mBlockNetworkLoads; + bool mUserInitiatedAction; + const WebCore::RenderSkinAndroid* m_renderSkins; +}; + +} // namespace android + +#endif // WEBFRAME_H diff --git a/Source/WebKit/android/jni/WebCoreJni.cpp b/Source/WebKit/android/jni/WebCoreJni.cpp new file mode 100644 index 0000000..2a07999 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreJni.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2007, 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 "WebCoreJni.h" + +#include "NotImplemented.h" +#include <JNIUtility.h> +#include <jni.h> +#include <utils/Log.h> + +namespace android { + +AutoJObject getRealObject(JNIEnv* env, jobject obj) +{ + jobject real = env->NewLocalRef(obj); + LOG_ASSERT(real, "The real object has been deleted!"); + return AutoJObject(env, real); +} + +/** + * Helper method for checking java exceptions + * @return true if an exception occurred. + */ +bool checkException(JNIEnv* env) +{ + if (env->ExceptionCheck() != 0) + { + LOGE("*** Uncaught exception returned from Java call!\n"); + env->ExceptionDescribe(); + return true; + } + return false; +} + +// This method is safe to call from the ui thread and the WebCore thread. +WTF::String jstringToWtfString(JNIEnv* env, jstring str) +{ + if (!str || !env) + return WTF::String(); + const jchar* s = env->GetStringChars(str, NULL); + if (!s) + return WTF::String(); + WTF::String ret(s, env->GetStringLength(str)); + env->ReleaseStringChars(str, s); + checkException(env); + return ret; +} + +jstring wtfStringToJstring(JNIEnv* env, const WTF::String& str, bool validOnZeroLength) +{ + int length = str.length(); + return length || validOnZeroLength ? env->NewString(str.characters(), length) : 0; +} + + +#if USE(CHROME_NETWORK_STACK) +string16 jstringToString16(JNIEnv* env, jstring jstr) +{ + if (!jstr || !env) + return string16(); + + const char* s = env->GetStringUTFChars(jstr, 0); + if (!s) + return string16(); + string16 str = UTF8ToUTF16(s); + env->ReleaseStringUTFChars(jstr, s); + checkException(env); + return str; +} + +std::string jstringToStdString(JNIEnv* env, jstring jstr) +{ + if (!jstr || !env) + return std::string(); + + const char* s = env->GetStringUTFChars(jstr, 0); + if (!s) + return std::string(); + std::string str(s); + env->ReleaseStringUTFChars(jstr, s); + checkException(env); + return str; +} + +jstring stdStringToJstring(JNIEnv* env, const std::string& str, bool validOnZeroLength) +{ + return !str.empty() || validOnZeroLength ? env->NewStringUTF(str.c_str()) : 0; +} + +#endif + +} diff --git a/Source/WebKit/android/jni/WebCoreJni.h b/Source/WebKit/android/jni/WebCoreJni.h new file mode 100644 index 0000000..ec25c8f --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreJni.h @@ -0,0 +1,96 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef ANDROID_WEBKIT_WEBCOREJNI_H +#define ANDROID_WEBKIT_WEBCOREJNI_H + +#include "ChromiumIncludes.h" +#include "PlatformString.h" +#include <jni.h> + +namespace android { + +// A helper class that automatically deletes the local reference to the jobject +// returned from getRealObject. +class AutoJObject { +public: + AutoJObject(const AutoJObject& other) + : m_env(other.m_env) + , m_obj(other.m_obj ? other.m_env->NewLocalRef(other.m_obj) : NULL) {} + ~AutoJObject() { + if (m_obj) + m_env->DeleteLocalRef(m_obj); + } + jobject get() const { + return m_obj; + } + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. + jobject release() { + jobject obj = m_obj; + m_obj = 0; + return obj; + } + JNIEnv* env() const { + return m_env; + } +private: + AutoJObject(); // Not permitted. + AutoJObject(JNIEnv* env, jobject obj) + : m_env(env) + , m_obj(obj) {} + JNIEnv* m_env; + jobject m_obj; + friend AutoJObject getRealObject(JNIEnv*, jobject); +}; + +// Get the real object stored in the weak reference returned as an +// AutoJObject. +AutoJObject getRealObject(JNIEnv*, jobject); + +// Helper method for check java exceptions. Returns true if an exception +// occurred and logs the exception. +bool checkException(JNIEnv* env); + +// Create a WTF::String object from a jstring object. +WTF::String jstringToWtfString(JNIEnv*, jstring); +// Returns a local reference to a new jstring. If validOnZeroLength is true then +// passing in an empty WTF String will result in an empty jstring. Otherwise +// an empty WTF String returns 0. +jstring wtfStringToJstring(JNIEnv*, const WTF::String&, bool validOnZeroLength = false); + +#if USE(CHROME_NETWORK_STACK) +string16 jstringToString16(JNIEnv*, jstring); + +std::string jstringToStdString(JNIEnv*, jstring); +// Returns a local reference to a new jstring. If validOnZeroLength is true then +// passing in an empty std::string will result in an empty jstring. Otherwise +// an empty std::string returns 0. +jstring stdStringToJstring(JNIEnv*, const std::string&, bool validOnZeroLength = false); +#endif + +} + +#endif diff --git a/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp b/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp new file mode 100644 index 0000000..1f264a2 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp @@ -0,0 +1,319 @@ +/* + * Copyright 2009, 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 "BackForwardList.h" +#include "ChromeClientAndroid.h" +#include "ContextMenuClientAndroid.h" +#include "CookieClient.h" +#include "DeviceMotionClientAndroid.h" +#include "DeviceOrientationClientAndroid.h" +#include "DragClientAndroid.h" +#include "EditorClientAndroid.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HistoryItem.h" +#include "InspectorClientAndroid.h" +#include "IntRect.h" +#include "JavaSharedClient.h" +#include "Page.h" +#include "PlatformGraphicsContext.h" +#include "ResourceRequest.h" +#include "ScriptController.h" +#include "SecurityOrigin.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkImageEncoder.h" +#include "SubstituteData.h" +#include "TimerClient.h" +#include "TextEncoding.h" +#include "WebCoreViewBridge.h" +#include "WebFrameView.h" +#include "WebViewCore.h" +#include "benchmark/Intercept.h" +#include "benchmark/MyJavaVM.h" + +#include <JNIUtility.h> +#include <jni.h> +#include <utils/Log.h> + +#define EXPORT __attribute__((visibility("default"))) + +namespace android { + +extern int registerWebFrame(JNIEnv*); +extern int registerJavaBridge(JNIEnv*); +extern int registerJniUtil(JNIEnv*); +extern int registerResourceLoader(JNIEnv*); +extern int registerWebViewCore(JNIEnv*); +extern int registerWebHistory(JNIEnv*); +extern int registerWebIconDatabase(JNIEnv*); +extern int registerWebSettings(JNIEnv*); +extern int registerWebView(JNIEnv*); +#if ENABLE(DATABASE) +extern int registerWebStorage(JNIEnv*); +#endif +extern int registerGeolocationPermissions(JNIEnv*); +extern int registerMockGeolocation(JNIEnv*); +#if ENABLE(VIDEO) +extern int registerMediaPlayerAudio(JNIEnv*); +extern int registerMediaPlayerVideo(JNIEnv*); +#endif +extern int registerDeviceMotionAndOrientationManager(JNIEnv*); +extern int registerCookieManager(JNIEnv*); +#if USE(CHROME_NETWORK_STACK) +extern int registerCacheManager(JNIEnv*); +#endif + +} + +struct RegistrationMethod { + const char* name; + int (*func)(JNIEnv*); +}; + +static RegistrationMethod gWebCoreRegMethods[] = { + { "JavaBridge", android::registerJavaBridge }, + { "JniUtil", android::registerJniUtil }, + { "WebFrame", android::registerWebFrame }, + { "WebCoreResourceLoader", android::registerResourceLoader }, + { "WebViewCore", android::registerWebViewCore }, + { "WebHistory", android::registerWebHistory }, + { "WebIconDatabase", android::registerWebIconDatabase }, + { "WebSettings", android::registerWebSettings }, +#if ENABLE(DATABASE) + { "WebStorage", android::registerWebStorage }, +#endif + { "WebView", android::registerWebView }, + { "GeolocationPermissions", android::registerGeolocationPermissions }, + { "MockGeolocation", android::registerMockGeolocation }, +#if ENABLE(VIDEO) + { "HTML5Audio", android::registerMediaPlayerAudio }, + { "HTML5VideoViewProxy", android::registerMediaPlayerVideo }, +#endif + { "DeviceMotionAndOrientationManager", android::registerDeviceMotionAndOrientationManager }, + { "CookieManager", android::registerCookieManager }, +#if USE(CHROME_NETWORK_STACK) + { "CacheManager", android::registerCacheManager }, +#endif +}; + +EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + // Save the JavaVM pointer for use globally. + JSC::Bindings::setJavaVM(vm); + + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("GetEnv failed!"); + return result; + } + LOG_ASSERT(env, "Could not retrieve the env!"); + + const RegistrationMethod* method = gWebCoreRegMethods; + const RegistrationMethod* end = method + sizeof(gWebCoreRegMethods)/sizeof(RegistrationMethod); + while (method != end) { + if (method->func(env) < 0) { + LOGE("%s registration failed!", method->name); + return result; + } + method++; + } + + // Initialize rand() function. The rand() function is used in + // FileSystemAndroid to create a random temporary filename. + srand(time(NULL)); + + return JNI_VERSION_1_4; +} + +class MyJavaSharedClient : public TimerClient, public CookieClient { +public: + MyJavaSharedClient() : m_hasTimer(false) {} + virtual void setSharedTimer(long long timemillis) { m_hasTimer = true; } + virtual void stopSharedTimer() { m_hasTimer = false; } + virtual void setSharedTimerCallback(void (*f)()) { m_func = f; } + virtual void signalServiceFuncPtrQueue() {} + + // Cookie methods that do nothing. + virtual void setCookies(const KURL&, const String&) {} + virtual String cookies(const KURL&) { return ""; } + virtual bool cookiesEnabled() { return false; } + + bool m_hasTimer; + void (*m_func)(); +}; + +static void historyItemChanged(HistoryItem* i) { + if (i->bridge()) + i->bridge()->updateHistoryItem(i); +} + +namespace android { + +EXPORT void benchmark(const char* url, int reloadCount, int width, int height) { + ScriptController::initializeThreading(); + + // Setting this allows data: urls to load from a local file. + SecurityOrigin::setLocalLoadPolicy(SecurityOrigin::AllowLocalLoadsForAll); + + // Create the fake JNIEnv and JavaVM + InitializeJavaVM(); + + // The real function is private to libwebcore but we know what it does. + notifyHistoryItemChanged = historyItemChanged; + + // Implement the shared timer callback + MyJavaSharedClient client; + JavaSharedClient::SetTimerClient(&client); + JavaSharedClient::SetCookieClient(&client); + + // Create the page with all the various clients + ChromeClientAndroid* chrome = new ChromeClientAndroid; + EditorClientAndroid* editor = new EditorClientAndroid; + DeviceMotionClientAndroid* deviceMotion = new DeviceMotionClientAndroid; + DeviceOrientationClientAndroid* deviceOrientation = new DeviceOrientationClientAndroid; + WebCore::Page::PageClients pageClients; + pageClients.chromeClient = chrome; + pageClients.contextMenuClient = new ContextMenuClientAndroid; + pageClients.editorClient = editor; + pageClients.dragClient = new DragClientAndroid; + pageClients.inspectorClient = new InspectorClientAndroid; + pageClients.deviceMotionClient = deviceMotion; + pageClients.deviceOrientationClient = deviceOrientation; + WebCore::Page* page = new WebCore::Page(pageClients); + editor->setPage(page); + + // Create MyWebFrame that intercepts network requests + MyWebFrame* webFrame = new MyWebFrame(page); + webFrame->setUserAgent("Performance testing"); // needs to be non-empty + chrome->setWebFrame(webFrame); + // ChromeClientAndroid maintains the reference. + Release(webFrame); + + // Create the Frame and the FrameLoaderClient + FrameLoaderClientAndroid* loader = new FrameLoaderClientAndroid(webFrame); + RefPtr<Frame> frame = Frame::create(page, NULL, loader); + loader->setFrame(frame.get()); + + // Build our View system, resize it to the given dimensions and release our + // references. Note: We keep a referenec to frameView so we can layout and + // draw later without risk of it being deleted. + WebViewCore* webViewCore = new WebViewCore(JSC::Bindings::getJNIEnv(), + MY_JOBJECT, frame.get()); + RefPtr<FrameView> frameView = FrameView::create(frame.get()); + WebFrameView* webFrameView = new WebFrameView(frameView.get(), webViewCore); + frame->setView(frameView); + frameView->resize(width, height); + Release(webViewCore); + Release(webFrameView); + + // Initialize the frame and turn of low-bandwidth display (it fails an + // assertion in the Cache code) + frame->init(); + frame->selection()->setFocused(true); + frame->page()->focusController()->setFocused(true); + + deviceMotion->setWebViewCore(webViewCore); + deviceOrientation->setWebViewCore(webViewCore); + + // Set all the default settings the Browser normally uses. + Settings* s = frame->settings(); +#ifdef ANDROID_LAYOUT + s->setLayoutAlgorithm(Settings::kLayoutNormal); // Normal layout for now +#endif + s->setStandardFontFamily("sans-serif"); + s->setFixedFontFamily("monospace"); + s->setSansSerifFontFamily("sans-serif"); + s->setSerifFontFamily("serif"); + s->setCursiveFontFamily("cursive"); + s->setFantasyFontFamily("fantasy"); + s->setMinimumFontSize(8); + s->setMinimumLogicalFontSize(8); + s->setDefaultFontSize(16); + s->setDefaultFixedFontSize(13); + s->setLoadsImagesAutomatically(true); + s->setJavaScriptEnabled(true); + s->setDefaultTextEncodingName("latin1"); + s->setPluginsEnabled(false); + s->setShrinksStandaloneImagesToFit(false); +#ifdef ANDROID_LAYOUT + s->setUseWideViewport(false); +#endif + + // Finally, load the actual data + ResourceRequest req(url); + frame->loader()->load(req, false); + + do { + // Layout the page and service the timer + frame->view()->layout(); + while (client.m_hasTimer) { + client.m_func(); + JavaSharedClient::ServiceFunctionPtrQueue(); + } + JavaSharedClient::ServiceFunctionPtrQueue(); + + // Layout more if needed. + while (frame->view()->needsLayout()) + frame->view()->layout(); + JavaSharedClient::ServiceFunctionPtrQueue(); + + if (reloadCount) + frame->loader()->reload(true); + } while (reloadCount--); + + // Draw into an offscreen bitmap + SkBitmap bmp; + bmp.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bmp.allocPixels(); + SkCanvas canvas(bmp); + PlatformGraphicsContext ctx(&canvas, NULL); + GraphicsContext gc(&ctx); + frame->view()->paintContents(&gc, IntRect(0, 0, width, height)); + + // Write the bitmap to the sdcard + SkImageEncoder* enc = SkImageEncoder::Create(SkImageEncoder::kPNG_Type); + enc->encodeFile("/sdcard/webcore_test.png", bmp, 100); + delete enc; + + // Tear down the world. + frame->loader()->detachFromParent(); + delete page; +} + +} // namespace android diff --git a/Source/WebKit/android/jni/WebCoreRefObject.h b/Source/WebKit/android/jni/WebCoreRefObject.h new file mode 100644 index 0000000..4228db6 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreRefObject.h @@ -0,0 +1,46 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef WEBCORE_FOUNDATION_h +#define WEBCORE_FOUNDATION_h + +#include "SkRefCnt.h" + +typedef SkRefCnt WebCoreRefObject; + +static inline WebCoreRefObject* Retain(WebCoreRefObject* obj) +{ + if (obj) + obj->ref(); + return obj; +} + +static inline void Release(WebCoreRefObject* obj) +{ + if (obj) + obj->unref(); +} + +#endif // WEBCORE_FOUNDATION_h diff --git a/Source/WebKit/android/jni/WebCoreResourceLoader.cpp b/Source/WebKit/android/jni/WebCoreResourceLoader.cpp new file mode 100644 index 0000000..f9acc97 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreResourceLoader.cpp @@ -0,0 +1,352 @@ +/* + * 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 "WebCoreResourceLoader.h" + +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceHandleInternal.h" +#include "ResourceResponse.h" +#include "SkUtils.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SkTypes.h> +#include <stdlib.h> +#include <utils/misc.h> +#include <wtf/Platform.h> +#include <wtf/text/CString.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct resourceloader_t { + jfieldID mObject; + jmethodID mCancelMethodID; + jmethodID mDownloadFileMethodID; + jmethodID mWillLoadFromCacheMethodID; + jmethodID mPauseLoadMethodID; +} gResourceLoader; + +// ---------------------------------------------------------------------------- + +#define GET_NATIVE_HANDLE(env, obj) ((WebCore::ResourceHandle*)env->GetIntField(obj, gResourceLoader.mObject)) +#define SET_NATIVE_HANDLE(env, obj, handle) (env->SetIntField(obj, gResourceLoader.mObject, handle)) + +//----------------------------------------------------------------------------- +// ResourceLoadHandler + +PassRefPtr<WebCore::ResourceLoaderAndroid> WebCoreResourceLoader::create(JNIEnv *env, jobject jLoadListener) +{ + return adoptRef<WebCore::ResourceLoaderAndroid>(new WebCoreResourceLoader(env, jLoadListener)); +} + +WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener) + : mPausedLoad(false) +{ + mJLoader = env->NewGlobalRef(jLoadListener); +} + +WebCoreResourceLoader::~WebCoreResourceLoader() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + SET_NATIVE_HANDLE(env, mJLoader, 0); + env->DeleteGlobalRef(mJLoader); + mJLoader = 0; +} + +void WebCoreResourceLoader::cancel() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mCancelMethodID); + checkException(env); +} + +void WebCoreResourceLoader::downloadFile() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mDownloadFileMethodID); + checkException(env); +} + +void WebCoreResourceLoader::pauseLoad(bool pause) +{ + if (mPausedLoad == pause) + return; + + mPausedLoad = pause; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mPauseLoadMethodID, pause); + checkException(env); +} + +/* +* This static method is called to check to see if a POST response is in +* the cache. This may be slow, but is only used during a navigation to +* a POST response. +*/ +bool WebCoreResourceLoader::willLoadFromCache(const WebCore::KURL& url, int64_t identifier) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WTF::String urlStr = url.string(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + bool val = env->CallStaticBooleanMethod(resourceLoader, gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr, identifier); + checkException(env); + env->DeleteLocalRef(resourceLoader); + env->DeleteLocalRef(jUrlStr); + + return val; +} + +// ---------------------------------------------------------------------------- +void WebCoreResourceLoader::SetResponseHeader(JNIEnv* env, jobject obj, jint nativeResponse, jstring key, jstring val) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + LOG_ASSERT(response, "nativeSetResponseHeader must take a valid response pointer!"); + + LOG_ASSERT(key, "How did a null value become a key?"); + if (val) + response->setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, val)); +} + +jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode, + jstring statusText, jstring mimeType, jlong expectedLength, + jstring encoding) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOG_ASSERT(url, "Must have a url in the response!"); + WebCore::KURL kurl(WebCore::ParsedURLString, jstringToWtfString(env, url)); + WTF::String encodingStr; + WTF::String mimeTypeStr; + if (mimeType) { + mimeTypeStr = jstringToWtfString(env, mimeType); + LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data()); + } + if (encoding) { + encodingStr = jstringToWtfString(env, encoding); + LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data()); + } + WebCore::ResourceResponse* response = new WebCore::ResourceResponse( + kurl, mimeTypeStr, (long long)expectedLength, + encodingStr, WTF::String()); + response->setHTTPStatusCode(statusCode); + if (statusText) { + WTF::String status = jstringToWtfString(env, statusText); + response->setHTTPStatusText(status); + LOGV("Response setStatusText: %s", status.latin1().data()); + } + return (int)response; +} + +void WebCoreResourceLoader::ReceivedResponse(JNIEnv* env, jobject obj, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeReceivedResponse must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + LOG_ASSERT(response, "nativeReceivedResponse must take a valid resource pointer!"); + handle->client()->didReceiveResponse(handle, *response); + // As the client makes a copy of the response, delete it here. + delete response; +} + +void WebCoreResourceLoader::AddData(JNIEnv* env, jobject obj, jbyteArray dataArray, jint length) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader data(%d)", length); + + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeAddData must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + SkAutoMemoryUsageProbe mup("android_webcore_resourceloader_nativeAddData"); + + bool result = false; + jbyte * data = env->GetByteArrayElements(dataArray, NULL); + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + handle->client()->didReceiveData(handle, (const char *)data, length, length); + env->ReleaseByteArrayElements(dataArray, data, JNI_ABORT); +} + +void WebCoreResourceLoader::Finished(JNIEnv* env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader finished"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeFinished must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + handle->client()->didFinishLoading(handle, 0); +} + +jstring WebCoreResourceLoader::RedirectedToUrl(JNIEnv* env, jobject obj, + jstring baseUrl, jstring redirectTo, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader redirectedToUrl"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeRedirectedToUrl must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return NULL; + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + WebCore::ResourceRequest r = handle->firstRequest(); + WebCore::KURL url(WebCore::KURL(WebCore::ParsedURLString, jstringToWtfString(env, baseUrl)), + jstringToWtfString(env, redirectTo)); + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + // If the url fails to resolve the relative path, return null. + if (url.protocol().isEmpty()) { + delete response; + return NULL; + } else { + // Ensure the protocol is lowercase. + url.setProtocol(url.protocol().lower()); + } + // Set the url after updating the protocol. + r.setURL(url); + if (r.httpMethod() == "POST") { + r.setHTTPMethod("GET"); + r.clearHTTPReferrer(); + r.setHTTPBody(0); + r.setHTTPContentType(""); + } + handle->client()->willSendRequest(handle, r, *response); + delete response; + return wtfStringToJstring(env, url.string()); +} + +void WebCoreResourceLoader::Error(JNIEnv* env, jobject obj, jint id, jstring description, + jstring failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader error"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeError must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + handle->client()->didFail(handle, WebCore::ResourceError("", id, + jstringToWtfString(env, failingUrl), jstringToWtfString(env, description))); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gResourceloaderMethods[] = { + /* name, signature, funcPtr */ + { "nativeSetResponseHeader", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) WebCoreResourceLoader::SetResponseHeader }, + { "nativeCreateResponse", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JLjava/lang/String;)I", + (void*) WebCoreResourceLoader::CreateResponse }, + { "nativeReceivedResponse", "(I)V", + (void*) WebCoreResourceLoader::ReceivedResponse }, + { "nativeAddData", "([BI)V", + (void*) WebCoreResourceLoader::AddData }, + { "nativeFinished", "()V", + (void*) WebCoreResourceLoader::Finished }, + { "nativeRedirectedToUrl", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;", + (void*) WebCoreResourceLoader::RedirectedToUrl }, + { "nativeError", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) WebCoreResourceLoader::Error } +}; + +int registerResourceLoader(JNIEnv* env) +{ + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + LOG_FATAL_IF(resourceLoader == NULL, + "Unable to find class android/webkit/LoadListener"); + + gResourceLoader.mObject = + env->GetFieldID(resourceLoader, "mNativeLoader", "I"); + LOG_FATAL_IF(gResourceLoader.mObject == NULL, + "Unable to find android/webkit/LoadListener.mNativeLoader"); + + gResourceLoader.mCancelMethodID = + env->GetMethodID(resourceLoader, "cancel", "()V"); + LOG_FATAL_IF(gResourceLoader.mCancelMethodID == NULL, + "Could not find method cancel on LoadListener"); + + gResourceLoader.mDownloadFileMethodID = + env->GetMethodID(resourceLoader, "downloadFile", "()V"); + LOG_FATAL_IF(gResourceLoader.mDownloadFileMethodID == NULL, + "Could not find method downloadFile on LoadListener"); + + gResourceLoader.mPauseLoadMethodID = + env->GetMethodID(resourceLoader, "pauseLoad", "(Z)V"); + LOG_FATAL_IF(gResourceLoader.mPauseLoadMethodID == NULL, + "Could not find method pauseLoad on LoadListener"); + + gResourceLoader.mWillLoadFromCacheMethodID = + env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;J)Z"); + LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL, + "Could not find static method willLoadFromCache on LoadListener"); + + env->DeleteLocalRef(resourceLoader); + + return jniRegisterNativeMethods(env, "android/webkit/LoadListener", + gResourceloaderMethods, NELEM(gResourceloaderMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebCoreResourceLoader.h b/Source/WebKit/android/jni/WebCoreResourceLoader.h new file mode 100644 index 0000000..c60b3f5 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreResourceLoader.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#ifndef ANDROID_WEBKIT_RESOURCELOADLISTENER_H +#define ANDROID_WEBKIT_RESOURCELOADLISTENER_H + +#include <KURL.h> +#include <ResourceLoaderAndroid.h> +#include <jni.h> + +namespace android { + +class WebCoreResourceLoader : public WebCore::ResourceLoaderAndroid +{ +public: + static PassRefPtr<WebCore::ResourceLoaderAndroid> create(JNIEnv *env, jobject jLoadListener); + virtual ~WebCoreResourceLoader(); + + /** + * Call to java to cancel the current load. + */ + virtual void cancel(); + + /** + * Call to java to download the current load rather than feed it + * back to WebCore + */ + virtual void downloadFile(); + + virtual void pauseLoad(bool); + + /** + * Call to java to find out if this URL is in the cache + */ + static bool willLoadFromCache(const WebCore::KURL& url, int64_t identifier); + + // Native jni functions + static void SetResponseHeader(JNIEnv*, jobject, jint, jstring, jstring); + static jint CreateResponse(JNIEnv*, jobject, jstring, jint, jstring, + jstring, jlong, jstring); + static void ReceivedResponse(JNIEnv*, jobject, jint); + static void AddData(JNIEnv*, jobject, jbyteArray, jint); + static void Finished(JNIEnv*, jobject); + static jstring RedirectedToUrl(JNIEnv*, jobject, jstring, jstring, jint); + static void Error(JNIEnv*, jobject, jint, jstring, jstring); + +protected: + WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener); +private: + jobject mJLoader; + bool mPausedLoad; +}; + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/jni/WebCoreViewBridge.h b/Source/WebKit/android/jni/WebCoreViewBridge.h new file mode 100644 index 0000000..59e1c9a --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreViewBridge.h @@ -0,0 +1,106 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef WEBCORE_VIEW_BRIDGE_H +#define WEBCORE_VIEW_BRIDGE_H + +// TODO: move this outside of jni directory + +#include "IntRect.h" +#include "WebCoreRefObject.h" + +namespace WebCore +{ + class GraphicsContext; +} + +class WebCoreViewBridge : public WebCoreRefObject { +public: + WebCoreViewBridge() { } + virtual ~WebCoreViewBridge() { } + + virtual void draw(WebCore::GraphicsContext* ctx, + const WebCore::IntRect& rect) = 0; + + const WebCore::IntRect& getBounds() const + { + return m_bounds; + } + + const WebCore::IntRect& getVisibleBounds() const + { + return m_visibleBounds; + } + + const WebCore::IntRect& getWindowBounds() const + { + return m_windowBounds; + } + + void setSize(int w, int h) + { + m_bounds.setWidth(w); + m_bounds.setHeight(h); + } + + void setVisibleSize(int w, int h) + { + m_visibleBounds.setWidth(w); + m_visibleBounds.setHeight(h); + } + + void setLocation(int x, int y) + { + m_bounds.setX(x); + m_bounds.setY(y); + m_visibleBounds.setX(x); + m_visibleBounds.setY(y); + } + + void setWindowBounds(int x, int y, int h, int v) + { + m_windowBounds = WebCore::IntRect(x, y, h, v); + } + + int width() const { return m_bounds.width(); } + int height() const { return m_bounds.height(); } + int locX() const { return m_bounds.x(); } + int locY() const { return m_bounds.y(); } + + int visibleWidth() const { return m_visibleBounds.width(); } + int visibleHeight() const { return m_visibleBounds.height(); } + int visibleX() const { return m_visibleBounds.x(); } + int visibleY() const { return m_visibleBounds.y(); } + + virtual bool forFrameView() const { return false; } + virtual bool forPluginView() const { return false; } + +private: + WebCore::IntRect m_bounds; + WebCore::IntRect m_windowBounds; + WebCore::IntRect m_visibleBounds; +}; + +#endif // WEBCORE_VIEW_BRIDGE_H diff --git a/Source/WebKit/android/jni/WebFrameView.cpp b/Source/WebKit/android/jni/WebFrameView.cpp new file mode 100644 index 0000000..8e5eac4 --- /dev/null +++ b/Source/WebKit/android/jni/WebFrameView.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2008, 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 "WebFrameView.h" + +#include "android_graphics.h" +#include "GraphicsContext.h" +#include "Frame.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HostWindow.h" +#include "PlatformGraphicsContext.h" +#include "WebViewCore.h" + +#include <SkCanvas.h> + +namespace android { + +WebFrameView::WebFrameView(WebCore::FrameView* frameView, WebViewCore* webViewCore) + : WebCoreViewBridge() + , mFrameView(frameView) + , mWebViewCore(webViewCore) { + // attach itself to mFrameView + mFrameView->setPlatformWidget(this); + Retain(mWebViewCore); +} + +WebFrameView::~WebFrameView() { + Release(mWebViewCore); +} + +void WebFrameView::draw(WebCore::GraphicsContext* ctx, const WebCore::IntRect& rect) { + WebCore::Frame* frame = mFrameView->frame(); + + if (NULL == frame->contentRenderer()) { + // We only do this if there is nothing else to draw. + // If there is a renderer, it will fill the bg itself, so we don't want to + // double-draw (slow) + SkCanvas* canvas = ctx->platformContext()->mCanvas; + canvas->drawColor(SK_ColorWHITE); + } else if (frame->tree()->parent()) { + // Note: this code was moved from FrameLoaderClientAndroid + // + // For subframe, create a new translated rect from the given rectangle. + WebCore::IntRect transRect(rect); + // In Frame::markAllMatchesForText(), it does a fake paint. So we need + // to handle the case where platformContext() is null. However, we still + // want to call paint, since WebKit must have called the paint for a reason. + SkCanvas* canvas = ctx->platformContext() ? ctx->platformContext()->mCanvas : NULL; + if (canvas) { + const WebCore::IntRect& bounds = getBounds(); + + // Grab the intersection of transRect and the frame's bounds. + transRect.intersect(bounds); + if (transRect.isEmpty()) + return; + + // Move the transRect into the frame's local coordinates. + transRect.move(-bounds.x(), -bounds.y()); + + // Translate the canvas, add a clip. + canvas->save(); + canvas->translate(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y())); + canvas->clipRect(transRect); + } + mFrameView->paintContents(ctx, transRect); + if (canvas) + canvas->restore(); + } else { + mFrameView->paintContents(ctx, rect); + } +} + +void WebFrameView::setView(WebCore::FrameView* frameView) { + mFrameView = frameView; + mFrameView->setPlatformWidget(this); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/WebFrameView.h b/Source/WebKit/android/jni/WebFrameView.h new file mode 100644 index 0000000..823f2b4 --- /dev/null +++ b/Source/WebKit/android/jni/WebFrameView.h @@ -0,0 +1,65 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef WEB_FRAMEVIEW_H +#define WEB_FRAMEVIEW_H + +#include "WebCoreViewBridge.h" + +namespace WebCore { + class FrameView; +} + +namespace android { + class WebViewCore; + + class WebFrameView: public WebCoreViewBridge { + public: + WebFrameView(WebCore::FrameView* frameView, WebViewCore* webViewCore); + virtual ~WebFrameView(); + + virtual void draw(WebCore::GraphicsContext* ctx, + const WebCore::IntRect& rect); + + WebViewCore* webViewCore() const { + return mWebViewCore; + } + + void setView(WebCore::FrameView* frameView); + + WebCore::FrameView* view() const { + return mFrameView; + } + + virtual bool forFrameView() const { return true; } + + private: + WebCore::FrameView* mFrameView; + WebViewCore* mWebViewCore; + }; + +} // namespace android + +#endif // WEB_FRAMEVIEW_H diff --git a/Source/WebKit/android/jni/WebHistory.cpp b/Source/WebKit/android/jni/WebHistory.cpp new file mode 100644 index 0000000..97ce23b --- /dev/null +++ b/Source/WebKit/android/jni/WebHistory.cpp @@ -0,0 +1,857 @@ +/* + * Copyright 2007, 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 "webhistory" + +#include "config.h" +#include "WebHistory.h" + +#include "BackForwardList.h" +#include "BackForwardListImpl.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "HistoryItem.h" +#include "IconDatabase.h" +#include "Page.h" +#include "TextEncoding.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" +#include "WebIconDatabase.h" + +#include <JNIHelp.h> +#include "JNIUtility.h" +#include <SkUtils.h> +#include <utils/misc.h> +#include <wtf/OwnPtr.h> +#include <wtf/Platform.h> +#include <wtf/text/CString.h> + +namespace android { + +// Forward declarations +static void write_item(WTF::Vector<char>& v, WebCore::HistoryItem* item); +static void write_children_recursive(WTF::Vector<char>& v, WebCore::HistoryItem* parent); +static bool read_item_recursive(WebCore::HistoryItem* child, const char** pData, int length); + +// Field ids for WebHistoryItems +struct WebHistoryItemFields { + jmethodID mInit; + jmethodID mUpdate; + jfieldID mTitle; + jfieldID mUrl; +} gWebHistoryItem; + +struct WebBackForwardListFields { + jmethodID mAddHistoryItem; + jmethodID mRemoveHistoryItem; + jmethodID mSetCurrentIndex; +} gWebBackForwardList; + +//-------------------------------------------------------------------------- +// WebBackForwardList native methods. +//-------------------------------------------------------------------------- + +static void WebHistoryClose(JNIEnv* env, jobject obj, jint frame) +{ + LOG_ASSERT(frame, "Close needs a valid Frame pointer!"); + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + + WebCore::BackForwardListImpl* list = static_cast<WebCore::BackForwardListImpl*>(pFrame->page()->backForwardList()); + RefPtr<WebCore::HistoryItem> current = list->currentItem(); + // Remove each item instead of using close(). close() is intended to be used + // right before the list is deleted. + WebCore::HistoryItemVector& entries = list->entries(); + int size = entries.size(); + for (int i = size - 1; i >= 0; --i) + list->removeItem(entries[i].get()); + // Add the current item back to the list. + if (current) { + current->setBridge(0); + // addItem will update the children to match the newly created bridge + list->addItem(current); + + /* + * The Grand Prix site uses anchor navigations to change the display. + * WebKit tries to be smart and not load child frames that have the + * same history urls during an anchor navigation. This means that the + * current history item stored in the child frame's loader does not + * match the item found in the history tree. If we remove all the + * entries in the back/foward list, we have to restore the entire tree + * or else a HistoryItem might have a deleted parent. + * + * In order to restore the history tree correctly, we have to look up + * all the frames first and then look up the history item. We do this + * because the history item in the tree may be null at this point. + * Unfortunately, a HistoryItem can only search its immediately + * children so we do a breadth-first rebuild of the tree. + */ + + // Keep a small list of child frames to traverse. + WTF::Vector<WebCore::Frame*> frameQueue; + // Fix the top-level item. + pFrame->loader()->history()->setCurrentItem(current.get()); + WebCore::Frame* child = pFrame->tree()->firstChild(); + // Remember the parent history item so we can search for a child item. + RefPtr<WebCore::HistoryItem> parent = current; + while (child) { + // Use the old history item since the current one may have a + // deleted parent. + WebCore::HistoryItem* item = parent->childItemWithTarget(child->tree()->name()); + child->loader()->history()->setCurrentItem(item); + // Append the first child to the queue if it exists. If there is no + // item, then we do not need to traverse the children since there + // will be no parent history item. + WebCore::Frame* firstChild; + if (item && (firstChild = child->tree()->firstChild())) + frameQueue.append(firstChild); + child = child->tree()->nextSibling(); + // If we don't have a sibling for this frame and the queue isn't + // empty, use the next entry in the queue. + if (!child && !frameQueue.isEmpty()) { + child = frameQueue.at(0); + frameQueue.remove(0); + // Figure out the parent history item used when searching for + // the history item to use. + parent = child->tree()->parent()->loader()->history()->currentItem(); + } + } + } +} + +static void WebHistoryRestoreIndex(JNIEnv* env, jobject obj, jint frame, jint index) +{ + LOG_ASSERT(frame, "RestoreState needs a valid Frame pointer!"); + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + WebCore::Page* page = pFrame->page(); + WebCore::HistoryItem* currentItem = + static_cast<WebCore::BackForwardListImpl*>(page->backForwardList())->entries()[index].get(); + + // load the current page with FrameLoadTypeIndexedBackForward so that it + // will use cache when it is possible + page->goToItem(currentItem, FrameLoadTypeIndexedBackForward); +} + +static void WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray data) +{ + LOG_ASSERT(frame, "Inflate needs a valid frame pointer!"); + LOG_ASSERT(data, "Inflate needs a valid data pointer!"); + + // Get the actual bytes and the length from the java array. + const jbyte* bytes = env->GetByteArrayElements(data, NULL); + jsize size = env->GetArrayLength(data); + + // Inflate the history tree into one HistoryItem or null if the inflation + // failed. + RefPtr<WebCore::HistoryItem> newItem = WebCore::HistoryItem::create(); + WebHistoryItem* bridge = new WebHistoryItem(env, obj, newItem.get()); + newItem->setBridge(bridge); + + // Inflate the item recursively. If it fails, that is ok. We'll have an + // incomplete HistoryItem but that is better than crashing due to a null + // item. + // We have a 2nd local variable since read_item_recursive may change the + // ptr's value. We can't pass &bytes since we have to send bytes to + // ReleaseByteArrayElements unchanged. + const char* ptr = reinterpret_cast<const char*>(bytes); + read_item_recursive(newItem.get(), &ptr, (int)size); + env->ReleaseByteArrayElements(data, const_cast<jbyte*>(bytes), JNI_ABORT); + bridge->setActive(); + + // Add the new item to the back/forward list. + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + pFrame->page()->backForwardList()->addItem(newItem); + + // Update the item. + bridge->updateHistoryItem(newItem.get()); +} + +// 6 empty strings + no document state + children count + 2 scales = 10 unsigned values +// 1 char for isTargetItem. +#define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 10 + sizeof(char))) + +jbyteArray WebHistory::Flatten(JNIEnv* env, WTF::Vector<char>& v, WebCore::HistoryItem* item) +{ + if (!item) + return NULL; + + // Reserve a vector of chars with an initial size of HISTORY_MIN_SIZE. + v.reserveCapacity(HISTORY_MIN_SIZE); + + // Write the top-level history item and then write all the children + // recursively. + LOG_ASSERT(item->bridge(), "Why don't we have a bridge object here?"); + write_item(v, item); + write_children_recursive(v, item); + + // Try to create a new java byte array. + jbyteArray b = env->NewByteArray(v.size()); + if (!b) + return NULL; + + // Write our flattened data to the java array. + env->SetByteArrayRegion(b, 0, v.size(), (const jbyte*)v.data()); + return b; +} + +WebHistoryItem::WebHistoryItem(JNIEnv* env, jobject obj, + WebCore::HistoryItem* item) : WebCore::AndroidWebHistoryBridge(item) { + m_object = env->NewWeakGlobalRef(obj); + m_parent = 0; +} + +WebHistoryItem::~WebHistoryItem() { + if (m_object) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + env->DeleteWeakGlobalRef(m_object); + } +} + +void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { + // Do not want to update during inflation. + if (!m_active) + return; + WebHistoryItem* webItem = this; + // Now we need to update the top-most WebHistoryItem based on the top-most + // HistoryItem. + if (m_parent) { + webItem = m_parent.get(); + if (webItem->hasOneRef()) { + // if the parent only has one ref, it is from this WebHistoryItem. + // This means that the matching WebCore::HistoryItem has been freed. + // This can happen during clear(). + LOGW("Can't updateHistoryItem as the top HistoryItem is gone"); + return; + } + while (webItem->parent()) + webItem = webItem->parent(); + item = webItem->historyItem(); + if (!item) { + // If a HistoryItem only exists for page cache, it is possible that + // the parent HistoryItem destroyed before the child HistoryItem. If + // it happens, skip updating. + LOGW("Can't updateHistoryItem as the top HistoryItem is gone"); + return; + } + } + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + // Don't do anything if the item has been gc'd already + AutoJObject realItem = getRealObject(env, webItem->m_object); + if (!realItem.get()) + return; + + const WTF::String& urlString = item->urlString(); + jstring urlStr = NULL; + if (!urlString.isNull()) + urlStr = wtfStringToJstring(env, urlString); + const WTF::String& originalUrlString = item->originalURLString(); + jstring originalUrlStr = NULL; + if (!originalUrlString.isNull()) + originalUrlStr = wtfStringToJstring(env, originalUrlString); + const WTF::String& titleString = item->title(); + jstring titleStr = NULL; + if (!titleString.isNull()) + titleStr = wtfStringToJstring(env, titleString); + + // Try to get the favicon from the history item. For some pages like Grand + // Prix, there are history items with anchors. If the icon fails for the + // item, try to get the icon using the url without the ref. + jobject favicon = NULL; + WTF::String url = item->urlString(); + if (item->url().hasFragmentIdentifier()) { + int refIndex = url.reverseFind('#'); + url = url.substring(0, refIndex); + } + WebCore::Image* icon = WebCore::iconDatabase()->iconForPageURL(url, + WebCore::IntSize(16, 16)); + + if (icon) + favicon = webcoreImageToJavaBitmap(env, icon); + + WTF::Vector<char> data; + jbyteArray array = WebHistory::Flatten(env, data, item); + env->CallVoidMethod(realItem.get(), gWebHistoryItem.mUpdate, urlStr, + originalUrlStr, titleStr, favicon, array); + env->DeleteLocalRef(urlStr); + env->DeleteLocalRef(originalUrlStr); + env->DeleteLocalRef(titleStr); + if (favicon) + env->DeleteLocalRef(favicon); + env->DeleteLocalRef(array); +} + +static void historyItemChanged(WebCore::HistoryItem* item) { + LOG_ASSERT(item, "historyItemChanged called with a null item"); + + if (item->bridge()) + item->bridge()->updateHistoryItem(item); +} + +void WebHistory::AddItem(const AutoJObject& list, WebCore::HistoryItem* item) +{ + LOG_ASSERT(item, "newItem must take a valid HistoryItem!"); + // Item already added. Should only happen when we are inflating the list. + if (item->bridge() || !list.get()) + return; + + JNIEnv* env = list.env(); + // Allocate a blank WebHistoryItem + jclass clazz = env->FindClass("android/webkit/WebHistoryItem"); + jobject newItem = env->NewObject(clazz, gWebHistoryItem.mInit); + env->DeleteLocalRef(clazz); + + // Create the bridge, make it active, and attach it to the item. + WebHistoryItem* bridge = new WebHistoryItem(env, newItem, item); + bridge->setActive(); + item->setBridge(bridge); + + // Update the history item which will flatten the data and call update on + // the java item. + bridge->updateHistoryItem(item); + + // Add it to the list. + env->CallVoidMethod(list.get(), gWebBackForwardList.mAddHistoryItem, newItem); + + // Delete our local reference. + env->DeleteLocalRef(newItem); +} + +void WebHistory::RemoveItem(const AutoJObject& list, int index) +{ + if (list.get()) + list.env()->CallVoidMethod(list.get(), gWebBackForwardList.mRemoveHistoryItem, index); +} + +void WebHistory::UpdateHistoryIndex(const AutoJObject& list, int newIndex) +{ + if (list.get()) + list.env()->CallVoidMethod(list.get(), gWebBackForwardList.mSetCurrentIndex, newIndex); +} + +static void write_string(WTF::Vector<char>& v, const WTF::String& str) +{ + unsigned strLen = str.length(); + // Only do work if the string has data. + if (strLen) { + // Determine how much to grow the vector. Use the worst case for utf8 to + // avoid reading the string twice. Add sizeof(unsigned) to hold the + // string length in utf8. + unsigned vectorLen = v.size() + sizeof(unsigned); + unsigned length = (strLen << 2) + vectorLen; + // Grow the vector. This will change the value of v.size() but we + // remember the original size above. + v.grow(length); + // Grab the position to write to. + char* data = v.begin() + vectorLen; + // Write the actual string + int l = SkUTF16_ToUTF8(str.characters(), strLen, data); + LOGV("Writing string %d %.*s", l, l, data); + // Go back and write the utf8 length. Subtract sizeof(unsigned) from + // data to get the position to write the length. + memcpy(data - sizeof(unsigned), (char*)&l, sizeof(unsigned)); + // Shrink the internal state of the vector so we match what was + // actually written. + v.shrink(vectorLen + l); + } else + v.append((char*)&strLen, sizeof(unsigned)); +} + +static void write_item(WTF::Vector<char>& v, WebCore::HistoryItem* item) +{ + // Original url + write_string(v, item->originalURLString()); + + // Url + write_string(v, item->urlString()); + + // Title + write_string(v, item->title()); + + // Form content type + write_string(v, item->formContentType()); + + // Form data + const WebCore::FormData* formData = item->formData(); + if (formData) { + write_string(v, formData->flattenToString()); + // save the identifier as it is not included in the flatten data + int64_t id = formData->identifier(); + v.append((char*)&id, sizeof(int64_t)); + } else + write_string(v, WTF::String()); // Empty constructor does not allocate a buffer. + + // Target + write_string(v, item->target()); + + AndroidWebHistoryBridge* bridge = item->bridge(); + LOG_ASSERT(bridge, "We should have a bridge here!"); + // Screen scale + const float scale = bridge->scale(); + LOGV("Writing scale %f", scale); + v.append((char*)&scale, sizeof(float)); + const float textWrapScale = bridge->textWrapScale(); + LOGV("Writing text wrap scale %f", textWrapScale); + v.append((char*)&textWrapScale, sizeof(float)); + + // Scroll position. + const int scrollX = item->scrollPoint().x(); + v.append((char*)&scrollX, sizeof(int)); + const int scrollY = item->scrollPoint().y(); + v.append((char*)&scrollY, sizeof(int)); + + // Document state + const WTF::Vector<WTF::String>& docState = item->documentState(); + WTF::Vector<WTF::String>::const_iterator end = docState.end(); + unsigned stateSize = docState.size(); + LOGV("Writing docState %d", stateSize); + v.append((char*)&stateSize, sizeof(unsigned)); + for (WTF::Vector<WTF::String>::const_iterator i = docState.begin(); i != end; ++i) { + write_string(v, *i); + } + + // Is target item + LOGV("Writing isTargetItem %d", item->isTargetItem()); + v.append((char)item->isTargetItem()); + + // Children count + unsigned childCount = item->children().size(); + LOGV("Writing childCount %d", childCount); + v.append((char*)&childCount, sizeof(unsigned)); +} + +static void write_children_recursive(WTF::Vector<char>& v, WebCore::HistoryItem* parent) +{ + const WebCore::HistoryItemVector& children = parent->children(); + WebCore::HistoryItemVector::const_iterator end = children.end(); + for (WebCore::HistoryItemVector::const_iterator i = children.begin(); i != end; ++i) { + WebCore::HistoryItem* item = (*i).get(); + LOG_ASSERT(parent->bridge(), + "The parent item should have a bridge object!"); + if (!item->bridge()) { + WebHistoryItem* bridge = new WebHistoryItem(static_cast<WebHistoryItem*>(parent->bridge())); + item->setBridge(bridge); + bridge->setActive(); + } else { + // The only time this item's parent may not be the same as the + // parent's bridge is during history close. In that case, the + // parent must not have a parent bridge. + WebHistoryItem* bridge = static_cast<WebHistoryItem*>(item->bridge()); + WebHistoryItem* parentBridge = static_cast<WebHistoryItem*>(parent->bridge()); + LOG_ASSERT(parentBridge->parent() == 0 || + bridge->parent() == parentBridge, + "Somehow this item has an incorrect parent"); + bridge->setParent(parentBridge); + } + write_item(v, item); + write_children_recursive(v, item); + } +} + +static bool read_item_recursive(WebCore::HistoryItem* newItem, + const char** pData, int length) +{ + if (!pData || length < HISTORY_MIN_SIZE) + return false; + + const WebCore::TextEncoding& e = WebCore::UTF8Encoding(); + const char* data = *pData; + const char* end = data + length; + int sizeofUnsigned = (int)sizeof(unsigned); + + // Read the original url + // Read the expected length of the string. + int l; + memcpy(&l, data, sizeofUnsigned); + // Increment data pointer by the size of an unsigned int. + data += sizeofUnsigned; + if (l) { + LOGV("Original url %d %.*s", l, l, data); + // If we have a length, check if that length exceeds the data length + // and return null if there is not enough data. + if (data + l < end) + newItem->setOriginalURLString(e.decode(data, l)); + else + return false; + // Increment the data pointer by the length of the string. + data += l; + } + // Check if we have enough data left to continue. + if (end - data < sizeofUnsigned) + return false; + + // Read the url + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Url %d %.*s", l, l, data); + if (data + l < end) + newItem->setURLString(e.decode(data, l)); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + // Read the title + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Title %d %.*s", l, l, data); + if (data + l < end) + newItem->setTitle(e.decode(data, l)); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + // Generate a new ResourceRequest object for populating form information. + WTF::String formContentType; + WTF::PassRefPtr<WebCore::FormData> formData = NULL; + + // Read the form content type + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Content type %d %.*s", l, l, data); + if (data + l < end) + formContentType = e.decode(data, l); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + // Read the form data + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Form data %d %.*s", l, l, data); + if (data + l < end) + formData = WebCore::FormData::create(data, l); + else + return false; + data += l; + // Read the identifier + { + int64_t id; + int size = (int)sizeof(int64_t); + memcpy(&id, data, size); + data += size; + if (id) + formData->setIdentifier(id); + } + } + if (end - data < sizeofUnsigned) + return false; + + // Set up the form info + if (formData != NULL) { + WebCore::ResourceRequest r; + r.setHTTPMethod("POST"); + r.setHTTPContentType(formContentType); + r.setHTTPBody(formData); + newItem->setFormInfoFromRequest(r); + } + + // Read the target + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Target %d %.*s", l, l, data); + if (data + l < end) + newItem->setTarget(e.decode(data, l)); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + AndroidWebHistoryBridge* bridge = newItem->bridge(); + LOG_ASSERT(bridge, "There should be a bridge object during inflate"); + float fValue; + // Read the screen scale + memcpy(&fValue, data, sizeof(float)); + LOGV("Screen scale %f", fValue); + bridge->setScale(fValue); + data += sizeof(float); + memcpy(&fValue, data, sizeofUnsigned); + LOGV("Text wrap scale %f", fValue); + bridge->setTextWrapScale(fValue); + data += sizeof(float); + + if (end - data < sizeofUnsigned) + return false; + + // Read scroll position. + int scrollX = 0; + memcpy(&scrollX, data, sizeofUnsigned); + data += sizeofUnsigned; + int scrollY = 0; + memcpy(&scrollY, data, sizeofUnsigned); + data += sizeofUnsigned; + newItem->setScrollPoint(IntPoint(scrollX, scrollY)); + + if (end - data < sizeofUnsigned) + return false; + + // Read the document state + memcpy(&l, data, sizeofUnsigned); + LOGV("Document state %d", l); + data += sizeofUnsigned; + if (l) { + // Check if we have enough data to at least parse the sizes of each + // document state string. + if (data + l * sizeofUnsigned >= end) + return false; + // Create a new vector and reserve enough space for the document state. + WTF::Vector<WTF::String> docState; + docState.reserveCapacity(l); + while (l--) { + // Check each time if we have enough to parse the length of the next + // string. + if (end - data < sizeofUnsigned) + return false; + int strLen; + memcpy(&strLen, data, sizeofUnsigned); + data += sizeofUnsigned; + if (data + strLen < end) + docState.append(e.decode(data, strLen)); + else + return false; + LOGV("\t\t%d %.*s", strLen, strLen, data); + data += strLen; + } + newItem->setDocumentState(docState); + } + // Check if we have enough to read the next byte + if (data >= end) + return false; + + // Read is target item + // Cast the value to unsigned char in order to make a negative value larger + // than 1. A value that is not 0 or 1 is a failure. + unsigned char c = (unsigned char)data[0]; + if (c > 1) + return false; + LOGV("Target item %d", c); + newItem->setIsTargetItem((bool)c); + data++; + if (end - data < sizeofUnsigned) + return false; + + // Read the child count + memcpy(&l, data, sizeofUnsigned); + LOGV("Child count %d", l); + data += sizeofUnsigned; + *pData = data; + if (l) { + // Check if we have the minimum amount need to parse l children. + if (data + l * HISTORY_MIN_SIZE >= end) + return false; + while (l--) { + // No need to check the length each time because read_item_recursive + // will return null if there isn't enough data left to parse. + WTF::PassRefPtr<WebCore::HistoryItem> child = WebCore::HistoryItem::create(); + // Set a bridge that will not call into java. + child->setBridge(new WebHistoryItem(static_cast<WebHistoryItem*>(bridge))); + // Read the child item. + if (!read_item_recursive(child.get(), pData, end - data)) { + child.clear(); + return false; + } + child->bridge()->setActive(); + newItem->addChildItem(child); + } + } + return true; +} + +// On arm, this test will cause memory corruption since converting char* will +// byte align the result and this test does not use memset (it probably +// should). +// On the simulator, using HistoryItem will invoke the IconDatabase which will +// initialize the main thread. Since this is invoked by the Zygote process, the +// main thread will be incorrect and an assert will fire later. +// In conclusion, define UNIT_TEST only if you know what you are doing. +#ifdef UNIT_TEST +static void unit_test() +{ + LOGD("Entering history unit test!"); + const char* test1 = new char[0]; + WTF::RefPtr<WebCore::HistoryItem> item = WebCore::HistoryItem::create(); + WebCore::HistoryItem* testItem = item.get(); + testItem->setBridge(new WebHistoryItem(0)); + LOG_ASSERT(!read_item_recursive(testItem, &test1, 0), "0 length array should fail!"); + delete[] test1; + const char* test2 = new char[2]; + LOG_ASSERT(!read_item_recursive(testItem, &test2, 2), "Small array should fail!"); + delete[] test2; + LOG_ASSERT(!read_item_recursive(testItem, NULL, HISTORY_MIN_SIZE), "Null data should fail!"); + // Original Url + char* test3 = new char[HISTORY_MIN_SIZE]; + const char* ptr = (const char*)test3; + memset(test3, 0, HISTORY_MIN_SIZE); + *(int*)test3 = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length originalUrl should fail!"); + // Url + int offset = 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length url should fail!"); + // Title + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length title should fail!"); + // Form content type + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length contentType should fail!"); + // Form data + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length form data should fail!"); + // Target + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length target should fail!"); + offset += 4; // Scale + // Document state + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length document state should fail!"); + // Is target item + offset += 1; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(char*)(test3 + offset) = '!'; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "IsTargetItem should fail with ! as the value!"); + // Child count + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 kids should fail!"); + offset = 36; + // Test document state + delete[] test3; + test3 = new char[HISTORY_MIN_SIZE + sizeof(unsigned)]; + memset(test3, 0, HISTORY_MIN_SIZE + sizeof(unsigned)); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 1; + *(int*)(test3 + offset + 4) = 20; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE + sizeof(unsigned)), "1 20 length document state string should fail!"); + delete[] test3; + test3 = new char[HISTORY_MIN_SIZE + 2 * sizeof(unsigned)]; + memset(test3, 0, HISTORY_MIN_SIZE + 2 * sizeof(unsigned)); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 2; + *(int*)(test3 + offset + 4) = 0; + *(int*)(test3 + offset + 8) = 20; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE + 2 * sizeof(unsigned) ), "2 20 length document state string should fail!"); + delete[] test3; +} +#endif + +//--------------------------------------------------------- +// JNI registration +//--------------------------------------------------------- +static JNINativeMethod gWebBackForwardListMethods[] = { + { "nativeClose", "(I)V", + (void*) WebHistoryClose }, + { "restoreIndex", "(II)V", + (void*) WebHistoryRestoreIndex } +}; + +static JNINativeMethod gWebHistoryItemMethods[] = { + { "inflate", "(I[B)V", + (void*) WebHistoryInflate } +}; + +int registerWebHistory(JNIEnv* env) +{ + // Get notified of all changes to history items. + WebCore::notifyHistoryItemChanged = historyItemChanged; +#ifdef UNIT_TEST + unit_test(); +#endif + // Find WebHistoryItem, its constructor, and the update method. + jclass clazz = env->FindClass("android/webkit/WebHistoryItem"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebHistoryItem"); + gWebHistoryItem.mInit = env->GetMethodID(clazz, "<init>", "()V"); + LOG_ASSERT(gWebHistoryItem.mInit, "Could not find WebHistoryItem constructor"); + gWebHistoryItem.mUpdate = env->GetMethodID(clazz, "update", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/graphics/Bitmap;[B)V"); + LOG_ASSERT(gWebHistoryItem.mUpdate, "Could not find method update in WebHistoryItem"); + + // Find the field ids for mTitle and mUrl. + gWebHistoryItem.mTitle = env->GetFieldID(clazz, "mTitle", "Ljava/lang/String;"); + LOG_ASSERT(gWebHistoryItem.mTitle, "Could not find field mTitle in WebHistoryItem"); + gWebHistoryItem.mUrl = env->GetFieldID(clazz, "mUrl", "Ljava/lang/String;"); + LOG_ASSERT(gWebHistoryItem.mUrl, "Could not find field mUrl in WebHistoryItem"); + env->DeleteLocalRef(clazz); + + // Find the WebBackForwardList object and method. + clazz = env->FindClass("android/webkit/WebBackForwardList"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebBackForwardList"); + gWebBackForwardList.mAddHistoryItem = env->GetMethodID(clazz, "addHistoryItem", + "(Landroid/webkit/WebHistoryItem;)V"); + LOG_ASSERT(gWebBackForwardList.mAddHistoryItem, "Could not find method addHistoryItem"); + gWebBackForwardList.mRemoveHistoryItem = env->GetMethodID(clazz, "removeHistoryItem", + "(I)V"); + LOG_ASSERT(gWebBackForwardList.mRemoveHistoryItem, "Could not find method removeHistoryItem"); + gWebBackForwardList.mSetCurrentIndex = env->GetMethodID(clazz, "setCurrentIndex", "(I)V"); + LOG_ASSERT(gWebBackForwardList.mSetCurrentIndex, "Could not find method setCurrentIndex"); + env->DeleteLocalRef(clazz); + + int result = jniRegisterNativeMethods(env, "android/webkit/WebBackForwardList", + gWebBackForwardListMethods, NELEM(gWebBackForwardListMethods)); + return (result < 0) ? result : jniRegisterNativeMethods(env, "android/webkit/WebHistoryItem", + gWebHistoryItemMethods, NELEM(gWebHistoryItemMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebHistory.h b/Source/WebKit/android/jni/WebHistory.h new file mode 100644 index 0000000..2d86aa4 --- /dev/null +++ b/Source/WebKit/android/jni/WebHistory.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef ANDROID_WEBKIT_WEBHISTORY_H +#define ANDROID_WEBKIT_WEBHISTORY_H + +#include "AndroidWebHistoryBridge.h" + +#include <jni.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace android { + +class AutoJObject; + +class WebHistory { +public: + static jbyteArray Flatten(JNIEnv*, WTF::Vector<char>&, WebCore::HistoryItem*); + static void AddItem(const AutoJObject&, WebCore::HistoryItem*); + static void RemoveItem(const AutoJObject&, int); + static void UpdateHistoryIndex(const AutoJObject&, int); +}; + +// there are two scale factors saved with each history item. m_scale reflects the +// viewport scale factor, default to 100 means 100%. m_textWrapScale records +// the scale factor for wrapping the text paragraph. +class WebHistoryItem : public WebCore::AndroidWebHistoryBridge { +public: + WebHistoryItem(WebHistoryItem* parent) + : WebCore::AndroidWebHistoryBridge(0) + , m_parent(parent) + , m_object(NULL) { } + WebHistoryItem(JNIEnv*, jobject, WebCore::HistoryItem*); + ~WebHistoryItem(); + void updateHistoryItem(WebCore::HistoryItem* item); + void setParent(WebHistoryItem* parent) { m_parent = parent; } + WebHistoryItem* parent() const { return m_parent.get(); } +private: + RefPtr<WebHistoryItem> m_parent; + jweak m_object; +}; + +}; + +#endif diff --git a/Source/WebKit/android/jni/WebIconDatabase.cpp b/Source/WebKit/android/jni/WebIconDatabase.cpp new file mode 100644 index 0000000..2a660d1 --- /dev/null +++ b/Source/WebKit/android/jni/WebIconDatabase.cpp @@ -0,0 +1,237 @@ +/* + * 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 "favicons" + +#include "config.h" +#include "WebIconDatabase.h" + +#include "FileSystem.h" +#include "GraphicsJNI.h" +#include "IconDatabase.h" +#include "Image.h" +#include "IntRect.h" +#include "JavaSharedClient.h" +#include "KURL.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SharedBuffer.h> +#include <SkBitmap.h> +#include <SkImageDecoder.h> +#include <SkTemplates.h> +#include <pthread.h> +#include <utils/misc.h> +#include <wtf/Platform.h> +#include <wtf/text/CString.h> + +namespace android { + +jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon) +{ + if (!icon) + return NULL; + SkBitmap bm; + WebCore::SharedBuffer* buffer = icon->data(); + if (!buffer || !SkImageDecoder::DecodeMemory(buffer->data(), buffer->size(), + &bm, SkBitmap::kNo_Config, + SkImageDecoder::kDecodePixels_Mode)) + return NULL; + + return GraphicsJNI::createBitmap(env, new SkBitmap(bm), false, NULL); +} + +static WebIconDatabase* gIconDatabaseClient = new WebIconDatabase(); + +// XXX: Called by the IconDatabase thread +void WebIconDatabase::dispatchDidAddIconForPageURL(const WTF::String& pageURL) +{ + mNotificationsMutex.lock(); + mNotifications.append(pageURL); + if (!mDeliveryRequested) { + mDeliveryRequested = true; + JavaSharedClient::EnqueueFunctionPtr(DeliverNotifications, this); + } + mNotificationsMutex.unlock(); +} + +// Called in the WebCore thread +void WebIconDatabase::RegisterForIconNotification(WebIconDatabaseClient* client) +{ + WebIconDatabase* db = gIconDatabaseClient; + for (unsigned i = 0; i < db->mClients.size(); ++i) { + // Do not add the same client twice. + if (db->mClients[i] == client) + return; + } + gIconDatabaseClient->mClients.append(client); +} + +// Called in the WebCore thread +void WebIconDatabase::UnregisterForIconNotification(WebIconDatabaseClient* client) +{ + WebIconDatabase* db = gIconDatabaseClient; + for (unsigned i = 0; i < db->mClients.size(); ++i) { + if (db->mClients[i] == client) { + db->mClients.remove(i); + break; + } + } +} + +// Called in the WebCore thread +void WebIconDatabase::DeliverNotifications(void* v) +{ + ASSERT(v); + ((WebIconDatabase*)v)->deliverNotifications(); +} + +// Called in the WebCore thread +void WebIconDatabase::deliverNotifications() +{ + ASSERT(mDeliveryRequested); + + // Swap the notifications queue + Vector<WTF::String> queue; + mNotificationsMutex.lock(); + queue.swap(mNotifications); + mDeliveryRequested = false; + mNotificationsMutex.unlock(); + + // Swap the clients queue + Vector<WebIconDatabaseClient*> clients; + clients.swap(mClients); + + for (unsigned i = 0; i < queue.size(); ++i) { + for (unsigned j = 0; j < clients.size(); ++j) { + clients[j]->didAddIconForPageUrl(queue[i]); + } + } +} + +static void Open(JNIEnv* env, jobject obj, jstring path) +{ + WebCore::IconDatabase* iconDb = WebCore::iconDatabase(); + if (iconDb->isOpen()) + return; + iconDb->setEnabled(true); + iconDb->setClient(gIconDatabaseClient); + LOG_ASSERT(path, "No path given to nativeOpen"); + WTF::String pathStr = jstringToWtfString(env, path); + WTF::CString fullPath = WebCore::pathByAppendingComponent(pathStr, + WebCore::IconDatabase::defaultDatabaseFilename()).utf8(); + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + bool didSetPermissions = false; + if (access(fullPath.data(), F_OK) == 0) { + if (chmod(fullPath.data(), mode) == 0) + didSetPermissions = true; + } else { + int fd = open(fullPath.data(), O_CREAT, mode); + if (fd >= 0) { + close(fd); + didSetPermissions = true; + } + } + if (didSetPermissions) { + LOGV("Opening WebIconDatabase file '%s'", pathStr.latin1().data()); + bool res = iconDb->open(pathStr); + if (!res) + LOGE("Open failed!"); + } else + LOGE("Failed to set permissions on '%s'", fullPath.data()); +} + +static void Close(JNIEnv* env, jobject obj) +{ + WebCore::iconDatabase()->close(); +} + +static void RemoveAllIcons(JNIEnv* env, jobject obj) +{ + LOGV("Removing all icons"); + WebCore::iconDatabase()->removeAllIcons(); +} + +static jobject IconForPageUrl(JNIEnv* env, jobject obj, jstring url) +{ + LOG_ASSERT(url, "No url given to iconForPageUrl"); + WTF::String urlStr = jstringToWtfString(env, url); + + WebCore::Image* icon = WebCore::iconDatabase()->iconForPageURL(urlStr, + WebCore::IntSize(16, 16)); + LOGV("Retrieving icon for '%s' %p", urlStr.latin1().data(), icon); + return webcoreImageToJavaBitmap(env, icon); +} + +static void RetainIconForPageUrl(JNIEnv* env, jobject obj, jstring url) +{ + LOG_ASSERT(url, "No url given to retainIconForPageUrl"); + WTF::String urlStr = jstringToWtfString(env, url); + + LOGV("Retaining icon for '%s'", urlStr.latin1().data()); + WebCore::iconDatabase()->retainIconForPageURL(urlStr); +} + +static void ReleaseIconForPageUrl(JNIEnv* env, jobject obj, jstring url) +{ + LOG_ASSERT(url, "No url given to releaseIconForPageUrl"); + WTF::String urlStr = jstringToWtfString(env, url); + + LOGV("Releasing icon for '%s'", urlStr.latin1().data()); + WebCore::iconDatabase()->releaseIconForPageURL(urlStr); +} + +/* + * JNI registration + */ +static JNINativeMethod gWebIconDatabaseMethods[] = { + { "nativeOpen", "(Ljava/lang/String;)V", + (void*) Open }, + { "nativeClose", "()V", + (void*) Close }, + { "nativeRemoveAllIcons", "()V", + (void*) RemoveAllIcons }, + { "nativeIconForPageUrl", "(Ljava/lang/String;)Landroid/graphics/Bitmap;", + (void*) IconForPageUrl }, + { "nativeRetainIconForPageUrl", "(Ljava/lang/String;)V", + (void*) RetainIconForPageUrl }, + { "nativeReleaseIconForPageUrl", "(Ljava/lang/String;)V", + (void*) ReleaseIconForPageUrl } +}; + +int registerWebIconDatabase(JNIEnv* env) +{ +#ifndef NDEBUG + jclass webIconDatabase = env->FindClass("android/webkit/WebIconDatabase"); + LOG_ASSERT(webIconDatabase, "Unable to find class android.webkit.WebIconDatabase"); + env->DeleteLocalRef(webIconDatabase); +#endif + + return jniRegisterNativeMethods(env, "android/webkit/WebIconDatabase", + gWebIconDatabaseMethods, NELEM(gWebIconDatabaseMethods)); +} + +} diff --git a/Source/WebKit/android/jni/WebIconDatabase.h b/Source/WebKit/android/jni/WebIconDatabase.h new file mode 100644 index 0000000..b2169aa --- /dev/null +++ b/Source/WebKit/android/jni/WebIconDatabase.h @@ -0,0 +1,75 @@ +/* + * 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. + */ + +#ifndef ANDROID_WEBKIT_WEBICONDATABASE_H +#define ANDROID_WEBKIT_WEBICONDATABASE_H + +#include "IconDatabaseClient.h" +#include "PlatformString.h" +#include "utils/threads.h" +#include <jni.h> +#include <wtf/Vector.h> + +namespace WebCore { + class Image; +} + +namespace android { + + class WebIconDatabaseClient { + public: + virtual ~WebIconDatabaseClient() {} + virtual void didAddIconForPageUrl(const WTF::String& pageUrl) = 0; + }; + + class WebIconDatabase : public WebCore::IconDatabaseClient { + public: + WebIconDatabase() : mDeliveryRequested(false) {} + // IconDatabaseClient method + virtual void dispatchDidAddIconForPageURL(const WTF::String& pageURL); + + static void RegisterForIconNotification(WebIconDatabaseClient* client); + static void UnregisterForIconNotification(WebIconDatabaseClient* client); + static void DeliverNotifications(void*); + + private: + // Deliver all the icon notifications + void deliverNotifications(); + + // List of clients. + Vector<WebIconDatabaseClient*> mClients; + + // Queue of page urls that have received an icon. + Vector<WTF::String> mNotifications; + android::Mutex mNotificationsMutex; + // Flag to indicate that we have requested a delivery of notifications. + bool mDeliveryRequested; + }; + + jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon); + +}; + +#endif diff --git a/Source/WebKit/android/jni/WebSettings.cpp b/Source/WebKit/android/jni/WebSettings.cpp new file mode 100644 index 0000000..468e7b0 --- /dev/null +++ b/Source/WebKit/android/jni/WebSettings.cpp @@ -0,0 +1,599 @@ +/* + * Copyright 2007, 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 "websettings" + +#include <config.h> +#include <wtf/Platform.h> + +#include "ApplicationCacheStorage.h" +#include "BitmapAllocatorAndroid.h" +#include "CachedResourceLoader.h" +#include "ChromiumIncludes.h" +#include "DatabaseTracker.h" +#include "Database.h" +#include "Document.h" +#include "EditorClientAndroid.h" +#include "FileSystem.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "GeolocationPermissions.h" +#include "GeolocationPositionCache.h" +#include "Page.h" +#include "PageCache.h" +#include "RenderTable.h" +#include "SQLiteFileSystem.h" +#include "Settings.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" +#if USE(V8) +#include "WorkerContextExecutionProxy.h" +#endif +#include "WebRequestContext.h" +#include "WebViewCore.h" + +#include <JNIHelp.h> +#include <utils/misc.h> +#include <wtf/text/CString.h> + +namespace android { + +static const int permissionFlags660 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + +struct FieldIds { + FieldIds(JNIEnv* env, jclass clazz) { + mLayoutAlgorithm = env->GetFieldID(clazz, "mLayoutAlgorithm", + "Landroid/webkit/WebSettings$LayoutAlgorithm;"); + mTextSize = env->GetFieldID(clazz, "mTextSize", + "Landroid/webkit/WebSettings$TextSize;"); + mStandardFontFamily = env->GetFieldID(clazz, "mStandardFontFamily", + "Ljava/lang/String;"); + mFixedFontFamily = env->GetFieldID(clazz, "mFixedFontFamily", + "Ljava/lang/String;"); + mSansSerifFontFamily = env->GetFieldID(clazz, "mSansSerifFontFamily", + "Ljava/lang/String;"); + mSerifFontFamily = env->GetFieldID(clazz, "mSerifFontFamily", + "Ljava/lang/String;"); + mCursiveFontFamily = env->GetFieldID(clazz, "mCursiveFontFamily", + "Ljava/lang/String;"); + mFantasyFontFamily = env->GetFieldID(clazz, "mFantasyFontFamily", + "Ljava/lang/String;"); + mDefaultTextEncoding = env->GetFieldID(clazz, "mDefaultTextEncoding", + "Ljava/lang/String;"); + mUserAgent = env->GetFieldID(clazz, "mUserAgent", + "Ljava/lang/String;"); + mAcceptLanguage = env->GetFieldID(clazz, "mAcceptLanguage", "Ljava/lang/String;"); + mMinimumFontSize = env->GetFieldID(clazz, "mMinimumFontSize", "I"); + mMinimumLogicalFontSize = env->GetFieldID(clazz, "mMinimumLogicalFontSize", "I"); + mDefaultFontSize = env->GetFieldID(clazz, "mDefaultFontSize", "I"); + mDefaultFixedFontSize = env->GetFieldID(clazz, "mDefaultFixedFontSize", "I"); + mLoadsImagesAutomatically = env->GetFieldID(clazz, "mLoadsImagesAutomatically", "Z"); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + mBlockNetworkImage = env->GetFieldID(clazz, "mBlockNetworkImage", "Z"); +#endif + mBlockNetworkLoads = env->GetFieldID(clazz, "mBlockNetworkLoads", "Z"); + mJavaScriptEnabled = env->GetFieldID(clazz, "mJavaScriptEnabled", "Z"); + mPluginState = env->GetFieldID(clazz, "mPluginState", + "Landroid/webkit/WebSettings$PluginState;"); +#if ENABLE(DATABASE) + mDatabaseEnabled = env->GetFieldID(clazz, "mDatabaseEnabled", "Z"); +#endif +#if ENABLE(DOM_STORAGE) + mDomStorageEnabled = env->GetFieldID(clazz, "mDomStorageEnabled", "Z"); +#endif +#if ENABLE(DATABASE) || ENABLE(DOM_STORAGE) + // The databases saved to disk for both the SQL and DOM Storage APIs are stored + // in the same base directory. + mDatabasePath = env->GetFieldID(clazz, "mDatabasePath", "Ljava/lang/String;"); + mDatabasePathHasBeenSet = env->GetFieldID(clazz, "mDatabasePathHasBeenSet", "Z"); +#endif +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + mAppCacheEnabled = env->GetFieldID(clazz, "mAppCacheEnabled", "Z"); + mAppCachePath = env->GetFieldID(clazz, "mAppCachePath", "Ljava/lang/String;"); + mAppCacheMaxSize = env->GetFieldID(clazz, "mAppCacheMaxSize", "J"); +#endif +#if ENABLE(WORKERS) + mWorkersEnabled = env->GetFieldID(clazz, "mWorkersEnabled", "Z"); +#endif + mGeolocationEnabled = env->GetFieldID(clazz, "mGeolocationEnabled", "Z"); + mGeolocationDatabasePath = env->GetFieldID(clazz, "mGeolocationDatabasePath", "Ljava/lang/String;"); + mXSSAuditorEnabled = env->GetFieldID(clazz, "mXSSAuditorEnabled", "Z"); + mJavaScriptCanOpenWindowsAutomatically = env->GetFieldID(clazz, + "mJavaScriptCanOpenWindowsAutomatically", "Z"); + mUseWideViewport = env->GetFieldID(clazz, "mUseWideViewport", "Z"); + mSupportMultipleWindows = env->GetFieldID(clazz, "mSupportMultipleWindows", "Z"); + mShrinksStandaloneImagesToFit = env->GetFieldID(clazz, "mShrinksStandaloneImagesToFit", "Z"); + mMaximumDecodedImageSize = env->GetFieldID(clazz, "mMaximumDecodedImageSize", "J"); + mPrivateBrowsingEnabled = env->GetFieldID(clazz, "mPrivateBrowsingEnabled", "Z"); + mSyntheticLinksEnabled = env->GetFieldID(clazz, "mSyntheticLinksEnabled", "Z"); + mUseDoubleTree = env->GetFieldID(clazz, "mUseDoubleTree", "Z"); + mPageCacheCapacity = env->GetFieldID(clazz, "mPageCacheCapacity", "I"); +#if ENABLE(WEB_AUTOFILL) + mAutoFillEnabled = env->GetFieldID(clazz, "mAutoFillEnabled", "Z"); + mAutoFillProfile = env->GetFieldID(clazz, "mAutoFillProfile", "Landroid/webkit/WebSettings$AutoFillProfile;"); + jclass autoFillProfileClass = env->FindClass("android/webkit/WebSettings$AutoFillProfile"); + mAutoFillProfileFullName = env->GetFieldID(autoFillProfileClass, "mFullName", "Ljava/lang/String;"); + mAutoFillProfileEmailAddress = env->GetFieldID(autoFillProfileClass, "mEmailAddress", "Ljava/lang/String;"); + mAutoFillProfileCompanyName = env->GetFieldID(autoFillProfileClass, "mCompanyName", "Ljava/lang/String;"); + mAutoFillProfileAddressLine1 = env->GetFieldID(autoFillProfileClass, "mAddressLine1", "Ljava/lang/String;"); + mAutoFillProfileAddressLine2 = env->GetFieldID(autoFillProfileClass, "mAddressLine2", "Ljava/lang/String;"); + mAutoFillProfileCity = env->GetFieldID(autoFillProfileClass, "mCity", "Ljava/lang/String;"); + mAutoFillProfileState = env->GetFieldID(autoFillProfileClass, "mState", "Ljava/lang/String;"); + mAutoFillProfileZipCode = env->GetFieldID(autoFillProfileClass, "mZipCode", "Ljava/lang/String;"); + mAutoFillProfileCountry = env->GetFieldID(autoFillProfileClass, "mCountry", "Ljava/lang/String;"); + mAutoFillProfilePhoneNumber = env->GetFieldID(autoFillProfileClass, "mPhoneNumber", "Ljava/lang/String;"); + env->DeleteLocalRef(autoFillProfileClass); +#endif +#if USE(CHROME_NETWORK_STACK) + mOverrideCacheMode = env->GetFieldID(clazz, "mOverrideCacheMode", "I"); +#endif + + LOG_ASSERT(mLayoutAlgorithm, "Could not find field mLayoutAlgorithm"); + LOG_ASSERT(mTextSize, "Could not find field mTextSize"); + LOG_ASSERT(mStandardFontFamily, "Could not find field mStandardFontFamily"); + LOG_ASSERT(mFixedFontFamily, "Could not find field mFixedFontFamily"); + LOG_ASSERT(mSansSerifFontFamily, "Could not find field mSansSerifFontFamily"); + LOG_ASSERT(mSerifFontFamily, "Could not find field mSerifFontFamily"); + LOG_ASSERT(mCursiveFontFamily, "Could not find field mCursiveFontFamily"); + LOG_ASSERT(mFantasyFontFamily, "Could not find field mFantasyFontFamily"); + LOG_ASSERT(mDefaultTextEncoding, "Could not find field mDefaultTextEncoding"); + LOG_ASSERT(mUserAgent, "Could not find field mUserAgent"); + LOG_ASSERT(mAcceptLanguage, "Could not find field mAcceptLanguage"); + LOG_ASSERT(mMinimumFontSize, "Could not find field mMinimumFontSize"); + LOG_ASSERT(mMinimumLogicalFontSize, "Could not find field mMinimumLogicalFontSize"); + LOG_ASSERT(mDefaultFontSize, "Could not find field mDefaultFontSize"); + LOG_ASSERT(mDefaultFixedFontSize, "Could not find field mDefaultFixedFontSize"); + LOG_ASSERT(mLoadsImagesAutomatically, "Could not find field mLoadsImagesAutomatically"); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + LOG_ASSERT(mBlockNetworkImage, "Could not find field mBlockNetworkImage"); +#endif + LOG_ASSERT(mBlockNetworkLoads, "Could not find field mBlockNetworkLoads"); + LOG_ASSERT(mJavaScriptEnabled, "Could not find field mJavaScriptEnabled"); + LOG_ASSERT(mPluginState, "Could not find field mPluginState"); +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + LOG_ASSERT(mAppCacheEnabled, "Could not find field mAppCacheEnabled"); + LOG_ASSERT(mAppCachePath, "Could not find field mAppCachePath"); + LOG_ASSERT(mAppCacheMaxSize, "Could not find field mAppCacheMaxSize"); +#endif +#if ENABLE(WORKERS) + LOG_ASSERT(mWorkersEnabled, "Could not find field mWorkersEnabled"); +#endif + LOG_ASSERT(mJavaScriptCanOpenWindowsAutomatically, + "Could not find field mJavaScriptCanOpenWindowsAutomatically"); + LOG_ASSERT(mUseWideViewport, "Could not find field mUseWideViewport"); + LOG_ASSERT(mSupportMultipleWindows, "Could not find field mSupportMultipleWindows"); + LOG_ASSERT(mShrinksStandaloneImagesToFit, "Could not find field mShrinksStandaloneImagesToFit"); + LOG_ASSERT(mMaximumDecodedImageSize, "Could not find field mMaximumDecodedImageSize"); + LOG_ASSERT(mUseDoubleTree, "Could not find field mUseDoubleTree"); + LOG_ASSERT(mPageCacheCapacity, "Could not find field mPageCacheCapacity"); + + jclass enumClass = env->FindClass("java/lang/Enum"); + LOG_ASSERT(enumClass, "Could not find Enum class!"); + mOrdinal = env->GetMethodID(enumClass, "ordinal", "()I"); + LOG_ASSERT(mOrdinal, "Could not find method ordinal"); + env->DeleteLocalRef(enumClass); + + jclass textSizeClass = env->FindClass("android/webkit/WebSettings$TextSize"); + LOG_ASSERT(textSizeClass, "Could not find TextSize enum"); + mTextSizeValue = env->GetFieldID(textSizeClass, "value", "I"); + env->DeleteLocalRef(textSizeClass); + } + + // Field ids + jfieldID mLayoutAlgorithm; + jfieldID mTextSize; + jfieldID mStandardFontFamily; + jfieldID mFixedFontFamily; + jfieldID mSansSerifFontFamily; + jfieldID mSerifFontFamily; + jfieldID mCursiveFontFamily; + jfieldID mFantasyFontFamily; + jfieldID mDefaultTextEncoding; + jfieldID mUserAgent; + jfieldID mAcceptLanguage; + jfieldID mMinimumFontSize; + jfieldID mMinimumLogicalFontSize; + jfieldID mDefaultFontSize; + jfieldID mDefaultFixedFontSize; + jfieldID mLoadsImagesAutomatically; +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + jfieldID mBlockNetworkImage; +#endif + jfieldID mBlockNetworkLoads; + jfieldID mJavaScriptEnabled; + jfieldID mPluginState; +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + jfieldID mAppCacheEnabled; + jfieldID mAppCachePath; + jfieldID mAppCacheMaxSize; +#endif +#if ENABLE(WORKERS) + jfieldID mWorkersEnabled; +#endif + jfieldID mJavaScriptCanOpenWindowsAutomatically; + jfieldID mUseWideViewport; + jfieldID mSupportMultipleWindows; + jfieldID mShrinksStandaloneImagesToFit; + jfieldID mMaximumDecodedImageSize; + jfieldID mPrivateBrowsingEnabled; + jfieldID mSyntheticLinksEnabled; + jfieldID mUseDoubleTree; + jfieldID mPageCacheCapacity; + // Ordinal() method and value field for enums + jmethodID mOrdinal; + jfieldID mTextSizeValue; + +#if ENABLE(DATABASE) + jfieldID mDatabaseEnabled; +#endif +#if ENABLE(DOM_STORAGE) + jfieldID mDomStorageEnabled; +#endif + jfieldID mGeolocationEnabled; + jfieldID mGeolocationDatabasePath; + jfieldID mXSSAuditorEnabled; +#if ENABLE(DATABASE) || ENABLE(DOM_STORAGE) + jfieldID mDatabasePath; + jfieldID mDatabasePathHasBeenSet; +#endif +#if ENABLE(WEB_AUTOFILL) + jfieldID mAutoFillEnabled; + jfieldID mAutoFillProfile; + jfieldID mAutoFillProfileFullName; + jfieldID mAutoFillProfileEmailAddress; + jfieldID mAutoFillProfileCompanyName; + jfieldID mAutoFillProfileAddressLine1; + jfieldID mAutoFillProfileAddressLine2; + jfieldID mAutoFillProfileCity; + jfieldID mAutoFillProfileState; + jfieldID mAutoFillProfileZipCode; + jfieldID mAutoFillProfileCountry; + jfieldID mAutoFillProfilePhoneNumber; +#endif +#if USE(CHROME_NETWORK_STACK) + jfieldID mOverrideCacheMode; +#endif +}; + +static struct FieldIds* gFieldIds; + +// Note: This is moved from the old FrameAndroid.cpp +static void recursiveCleanupForFullLayout(WebCore::RenderObject* obj) +{ + obj->setNeedsLayout(true, false); +#ifdef ANDROID_LAYOUT + if (obj->isTable()) + (static_cast<WebCore::RenderTable *>(obj))->clearSingleColumn(); +#endif + for (WebCore::RenderObject* n = obj->firstChild(); n; n = n->nextSibling()) + recursiveCleanupForFullLayout(n); +} + +#if ENABLE(WEB_AUTOFILL) +inline string16 getStringFieldAsString16(JNIEnv* env, jobject autoFillProfile, jfieldID fieldId) +{ + jstring str = static_cast<jstring>(env->GetObjectField(autoFillProfile, fieldId)); + return str ? jstringToString16(env, str) : string16(); +} + +void syncAutoFillProfile(JNIEnv* env, jobject autoFillProfile, WebAutoFill* webAutoFill) +{ + string16 fullName = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileFullName); + string16 emailAddress = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileEmailAddress); + string16 companyName = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileCompanyName); + string16 addressLine1 = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileAddressLine1); + string16 addressLine2 = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileAddressLine2); + string16 city = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileCity); + string16 state = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileState); + string16 zipCode = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileZipCode); + string16 country = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileCountry); + string16 phoneNumber = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfilePhoneNumber); + + webAutoFill->setProfile(fullName, emailAddress, companyName, addressLine1, addressLine2, city, state, zipCode, country, phoneNumber); +} +#endif + +class WebSettings { +public: + static void Sync(JNIEnv* env, jobject obj, jint frame) + { + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + LOG_ASSERT(pFrame, "%s must take a valid frame pointer!", __FUNCTION__); + WebCore::Settings* s = pFrame->settings(); + if (!s) + return; + WebCore::CachedResourceLoader* cachedResourceLoader = pFrame->document()->cachedResourceLoader(); + +#ifdef ANDROID_LAYOUT + jobject layout = env->GetObjectField(obj, gFieldIds->mLayoutAlgorithm); + WebCore::Settings::LayoutAlgorithm l = (WebCore::Settings::LayoutAlgorithm) + env->CallIntMethod(layout, gFieldIds->mOrdinal); + if (s->layoutAlgorithm() != l) { + s->setLayoutAlgorithm(l); + if (pFrame->document()) { + pFrame->document()->styleSelectorChanged(WebCore::RecalcStyleImmediately); + if (pFrame->document()->renderer()) { + recursiveCleanupForFullLayout(pFrame->document()->renderer()); + LOG_ASSERT(pFrame->view(), "No view for this frame when trying to relayout"); + pFrame->view()->layout(); + // FIXME: This call used to scroll the page to put the focus into view. + // It worked on the WebViewCore, but now scrolling is done outside of the + // WebViewCore, on the UI side, so there needs to be a new way to do this. + //pFrame->makeFocusVisible(); + } + } + } +#endif + jobject textSize = env->GetObjectField(obj, gFieldIds->mTextSize); + float zoomFactor = env->GetIntField(textSize, gFieldIds->mTextSizeValue) / 100.0f; + if (pFrame->textZoomFactor() != zoomFactor) + pFrame->setTextZoomFactor(zoomFactor); + + jstring str = (jstring)env->GetObjectField(obj, gFieldIds->mStandardFontFamily); + s->setStandardFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFixedFontFamily); + s->setFixedFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSansSerifFontFamily); + s->setSansSerifFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSerifFontFamily); + s->setSerifFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mCursiveFontFamily); + s->setCursiveFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFantasyFontFamily); + s->setFantasyFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mDefaultTextEncoding); + s->setDefaultTextEncodingName(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mUserAgent); + WebFrame::getWebFrame(pFrame)->setUserAgent(jstringToWtfString(env, str)); +#if USE(CHROME_NETWORK_STACK) + WebViewCore::getWebViewCore(pFrame->view())->setWebRequestContextUserAgent(); + + jint cacheMode = env->GetIntField(obj, gFieldIds->mOverrideCacheMode); + WebViewCore::getWebViewCore(pFrame->view())->setWebRequestContextCacheMode(cacheMode); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mAcceptLanguage); + WebRequestContext::setAcceptLanguage(jstringToWtfString(env, str)); +#endif + + jint size = env->GetIntField(obj, gFieldIds->mMinimumFontSize); + s->setMinimumFontSize(size); + + size = env->GetIntField(obj, gFieldIds->mMinimumLogicalFontSize); + s->setMinimumLogicalFontSize(size); + + size = env->GetIntField(obj, gFieldIds->mDefaultFontSize); + s->setDefaultFontSize(size); + + size = env->GetIntField(obj, gFieldIds->mDefaultFixedFontSize); + s->setDefaultFixedFontSize(size); + + jboolean flag = env->GetBooleanField(obj, gFieldIds->mLoadsImagesAutomatically); + s->setLoadsImagesAutomatically(flag); + if (flag) + cachedResourceLoader->setAutoLoadImages(true); + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + flag = env->GetBooleanField(obj, gFieldIds->mBlockNetworkImage); + s->setBlockNetworkImage(flag); + if(!flag) + cachedResourceLoader->setBlockNetworkImage(false); +#endif + flag = env->GetBooleanField(obj, gFieldIds->mBlockNetworkLoads); + WebFrame* webFrame = WebFrame::getWebFrame(pFrame); + webFrame->setBlockNetworkLoads(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mJavaScriptEnabled); + s->setJavaScriptEnabled(flag); + + // ON = 0 + // ON_DEMAND = 1 + // OFF = 2 + jobject pluginState = env->GetObjectField(obj, gFieldIds->mPluginState); + int state = env->CallIntMethod(pluginState, gFieldIds->mOrdinal); + s->setPluginsEnabled(state < 2); +#ifdef ANDROID_PLUGINS + s->setPluginsOnDemand(state == 1); +#endif + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + flag = env->GetBooleanField(obj, gFieldIds->mAppCacheEnabled); + s->setOfflineWebApplicationCacheEnabled(flag); + str = (jstring)env->GetObjectField(obj, gFieldIds->mAppCachePath); + if (str) { + String path = jstringToWtfString(env, str); + if (path.length() && cacheStorage().cacheDirectory().isNull()) { + cacheStorage().setCacheDirectory(path); + // This database is created on the first load. If the file + // doesn't exist, we create it and set its permissions. The + // filename must match that in ApplicationCacheStorage.cpp. + String filename = pathByAppendingComponent(path, "ApplicationCache.db"); + int fd = open(filename.utf8().data(), O_CREAT | O_EXCL, permissionFlags660); + if (fd >= 0) + close(fd); + } + } + jlong maxsize = env->GetLongField(obj, gFieldIds->mAppCacheMaxSize); + cacheStorage().setMaximumSize(maxsize); +#endif + + flag = env->GetBooleanField(obj, gFieldIds->mJavaScriptCanOpenWindowsAutomatically); + s->setJavaScriptCanOpenWindowsAutomatically(flag); + +#ifdef ANDROID_LAYOUT + flag = env->GetBooleanField(obj, gFieldIds->mUseWideViewport); + s->setUseWideViewport(flag); +#endif + +#ifdef ANDROID_MULTIPLE_WINDOWS + flag = env->GetBooleanField(obj, gFieldIds->mSupportMultipleWindows); + s->setSupportMultipleWindows(flag); +#endif + flag = env->GetBooleanField(obj, gFieldIds->mShrinksStandaloneImagesToFit); + s->setShrinksStandaloneImagesToFit(flag); + jlong maxImage = env->GetLongField(obj, gFieldIds->mMaximumDecodedImageSize); + // Since in ImageSourceAndroid.cpp, the image will always not exceed + // MAX_SIZE_BEFORE_SUBSAMPLE, there's no need to pass the max value to + // WebCore, which checks (image_width * image_height * 4) as an + // estimation against the max value, which is done in CachedImage.cpp. + // And there're cases where the decoded image size will not + // exceed the max, but the WebCore estimation will. So the following + // code is commented out to fix those cases. + // if (maxImage == 0) + // maxImage = computeMaxBitmapSizeForCache(); + s->setMaximumDecodedImageSize(maxImage); + + flag = env->GetBooleanField(obj, gFieldIds->mPrivateBrowsingEnabled); + s->setPrivateBrowsingEnabled(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mSyntheticLinksEnabled); + s->setDefaultFormatDetection(flag); + s->setFormatDetectionAddress(flag); + s->setFormatDetectionEmail(flag); + s->setFormatDetectionTelephone(flag); +#if ENABLE(DATABASE) + flag = env->GetBooleanField(obj, gFieldIds->mDatabaseEnabled); + WebCore::Database::setIsAvailable(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mDatabasePathHasBeenSet); + if (flag) { + // If the user has set the database path, sync it to the DatabaseTracker. + str = (jstring)env->GetObjectField(obj, gFieldIds->mDatabasePath); + if (str) { + String path = jstringToWtfString(env, str); + DatabaseTracker::tracker().setDatabaseDirectoryPath(path); + // This database is created when the first HTML5 Database object is + // instantiated. If the file doesn't exist, we create it and set its + // permissions. The filename must match that in + // DatabaseTracker.cpp. + String filename = SQLiteFileSystem::appendDatabaseFileNameToPath(path, "Databases.db"); + int fd = open(filename.utf8().data(), O_CREAT | O_EXCL, permissionFlags660); + if (fd >= 0) + close(fd); + } + } +#endif +#if ENABLE(DOM_STORAGE) + flag = env->GetBooleanField(obj, gFieldIds->mDomStorageEnabled); + s->setLocalStorageEnabled(flag); + str = (jstring)env->GetObjectField(obj, gFieldIds->mDatabasePath); + if (str) { + WTF::String localStorageDatabasePath = jstringToWtfString(env,str); + if (localStorageDatabasePath.length()) { + localStorageDatabasePath = WebCore::pathByAppendingComponent( + localStorageDatabasePath, "localstorage"); + // We need 770 for folders + mkdir(localStorageDatabasePath.utf8().data(), + permissionFlags660 | S_IXUSR | S_IXGRP); + s->setLocalStorageDatabasePath(localStorageDatabasePath); + } + } +#endif + + flag = env->GetBooleanField(obj, gFieldIds->mGeolocationEnabled); + GeolocationPermissions::setAlwaysDeny(!flag); + str = (jstring)env->GetObjectField(obj, gFieldIds->mGeolocationDatabasePath); + if (str) { + String path = jstringToWtfString(env, str); + GeolocationPermissions::setDatabasePath(path); + GeolocationPositionCache::instance()->setDatabasePath(path); + // This database is created when the first Geolocation object is + // instantiated. If the file doesn't exist, we create it and set its + // permissions. The filename must match that in + // GeolocationPositionCache.cpp. + String filename = SQLiteFileSystem::appendDatabaseFileNameToPath(path, "CachedGeoposition.db"); + int fd = open(filename.utf8().data(), O_CREAT | O_EXCL, permissionFlags660); + if (fd >= 0) + close(fd); + } + + flag = env->GetBooleanField(obj, gFieldIds->mXSSAuditorEnabled); + s->setXSSAuditorEnabled(flag); + + size = env->GetIntField(obj, gFieldIds->mPageCacheCapacity); + if (size > 0) { + s->setUsesPageCache(true); + WebCore::pageCache()->setCapacity(size); + } else + s->setUsesPageCache(false); + +#if ENABLE(WEB_AUTOFILL) + flag = env->GetBooleanField(obj, gFieldIds->mAutoFillEnabled); + // TODO: This updates the Settings WebCore side with the user's + // preference for autofill and will stop WebCore making requests + // into the chromium autofill code. That code in Chromium also has + // a notion of being enabled/disabled that gets read from the users + // preferences. At the moment, it's hardcoded to true on Android + // (see chrome/browser/autofill/autofill_manager.cc:405). This + // setting should probably be synced into Chromium also. + + s->setAutoFillEnabled(flag); + + if (flag) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(pFrame->page()->editorClient()); + WebAutoFill* webAutoFill = editorC->getAutoFill(); + // Set the active AutoFillProfile data. + jobject autoFillProfile = env->GetObjectField(obj, gFieldIds->mAutoFillProfile); + if (autoFillProfile) + syncAutoFillProfile(env, autoFillProfile, webAutoFill); + else { + // The autofill profile is null. We need to tell Chromium about this because + // this may be because the user just deleted their profile but left the + // autofill feature setting enabled. + webAutoFill->clearProfiles(); + } + } +#endif + } +}; + + +//------------------------------------------------------------- +// JNI registration +//------------------------------------------------------------- + +static JNINativeMethod gWebSettingsMethods[] = { + { "nativeSync", "(I)V", + (void*) WebSettings::Sync } +}; + +int registerWebSettings(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/WebSettings"); + LOG_ASSERT(clazz, "Unable to find class WebSettings!"); + gFieldIds = new FieldIds(env, clazz); + env->DeleteLocalRef(clazz); + return jniRegisterNativeMethods(env, "android/webkit/WebSettings", + gWebSettingsMethods, NELEM(gWebSettingsMethods)); +} + +} diff --git a/Source/WebKit/android/jni/WebStorage.cpp b/Source/WebKit/android/jni/WebStorage.cpp new file mode 100644 index 0000000..9ce207d --- /dev/null +++ b/Source/WebKit/android/jni/WebStorage.cpp @@ -0,0 +1,188 @@ +/* + * Copyright 2009, 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. + */ + +#include "config.h" + +#if ENABLE(DATABASE) + +#include "ApplicationCacheStorage.h" +#include "DatabaseTracker.h" +#include "JNIUtility.h" +#include "JavaSharedClient.h" +#include "KURL.h" +#include "PageGroup.h" +#include "SecurityOrigin.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> + +namespace android { + +static jobject GetOrigins(JNIEnv* env, jobject obj) +{ + Vector<RefPtr<WebCore::SecurityOrigin> > coreOrigins; + WebCore::DatabaseTracker::tracker().origins(coreOrigins); + Vector<WebCore::KURL> manifestUrls; + if (WebCore::cacheStorage().manifestURLs(&manifestUrls)) { + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) { + RefPtr<WebCore::SecurityOrigin> manifestOrigin = WebCore::SecurityOrigin::create(manifestUrls[i]); + if (manifestOrigin.get() == 0) + continue; + coreOrigins.append(manifestOrigin); + } + } + + jclass setClass = env->FindClass("java/util/HashSet"); + jmethodID cid = env->GetMethodID(setClass, "<init>", "()V"); + jmethodID mid = env->GetMethodID(setClass, "add", "(Ljava/lang/Object;)Z"); + jobject set = env->NewObject(setClass, cid); + env->DeleteLocalRef(setClass); + + for (unsigned i = 0; i < coreOrigins.size(); ++i) { + WebCore::SecurityOrigin* origin = coreOrigins[i].get(); + WTF::String url = origin->toString(); + jstring jUrl = wtfStringToJstring(env, url); + env->CallBooleanMethod(set, mid, jUrl); + env->DeleteLocalRef(jUrl); + } + + return set; +} + +static unsigned long long GetQuotaForOrigin(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + unsigned long long quota = WebCore::DatabaseTracker::tracker().quotaForOrigin(securityOrigin.get()); + return quota; +} + +static unsigned long long GetUsageForOrigin(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + unsigned long long usage = WebCore::DatabaseTracker::tracker().usageForOrigin(securityOrigin.get()); + Vector<WebCore::KURL> manifestUrls; + if (!WebCore::cacheStorage().manifestURLs(&manifestUrls)) + return usage; + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) { + RefPtr<WebCore::SecurityOrigin> manifestOrigin = WebCore::SecurityOrigin::create(manifestUrls[i]); + if (manifestOrigin.get() == 0) + continue; + if (manifestOrigin->isSameSchemeHostPort(securityOrigin.get())) { + int64_t cacheSize = 0; + WebCore::cacheStorage().cacheGroupSize(manifestUrls[i].string(), &cacheSize); + usage += cacheSize; + } + } + return usage; +} + +static void SetQuotaForOrigin(JNIEnv* env, jobject obj, jstring origin, unsigned long long quota) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + WebCore::DatabaseTracker::tracker().setQuota(securityOrigin.get(), quota); +} + +static void DeleteOrigin(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + WebCore::DatabaseTracker::tracker().deleteOrigin(securityOrigin.get()); + + Vector<WebCore::KURL> manifestUrls; + if (!WebCore::cacheStorage().manifestURLs(&manifestUrls)) + return; + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) { + RefPtr<WebCore::SecurityOrigin> manifestOrigin = WebCore::SecurityOrigin::create(manifestUrls[i]); + if (manifestOrigin.get() == 0) + continue; + if (manifestOrigin->isSameSchemeHostPort(securityOrigin.get())) + WebCore::cacheStorage().deleteCacheGroup(manifestUrls[i]); + } +} + +static void DeleteAllData(JNIEnv* env, jobject obj) +{ + WebCore::DatabaseTracker::tracker().deleteAllDatabases(); + + Vector<WebCore::KURL> manifestUrls; + if (!WebCore::cacheStorage().manifestURLs(&manifestUrls)) + return; + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) + WebCore::cacheStorage().deleteCacheGroup(manifestUrls[i]); + + // FIXME: this is a workaround for eliminating any DOM Storage data (both + // session and local storage) as there is no functionality inside WebKit at the + // moment to do it. That functionality is a WIP in https://bugs.webkit.org/show_bug.cgi?id=51878 + // and when that patch lands and we merge it, we should move towards that approach instead. + WebCore::PageGroup::clearDomStorage(); +} + +static void SetAppCacheMaximumSize(JNIEnv* env, jobject obj, unsigned long long size) +{ + WebCore::cacheStorage().setMaximumSize(size); +} + +/* + * JNI registration + */ +static JNINativeMethod gWebStorageMethods[] = { + { "nativeGetOrigins", "()Ljava/util/Set;", + (void*) GetOrigins }, + { "nativeGetUsageForOrigin", "(Ljava/lang/String;)J", + (void*) GetUsageForOrigin }, + { "nativeGetQuotaForOrigin", "(Ljava/lang/String;)J", + (void*) GetQuotaForOrigin }, + { "nativeSetQuotaForOrigin", "(Ljava/lang/String;J)V", + (void*) SetQuotaForOrigin }, + { "nativeDeleteOrigin", "(Ljava/lang/String;)V", + (void*) DeleteOrigin }, + { "nativeDeleteAllData", "()V", + (void*) DeleteAllData }, + { "nativeSetAppCacheMaximumSize", "(J)V", + (void*) SetAppCacheMaximumSize } +}; + +int registerWebStorage(JNIEnv* env) +{ +#ifndef NDEBUG + jclass webStorage = env->FindClass("android/webkit/WebStorage"); + LOG_ASSERT(webStorage, "Unable to find class android.webkit.WebStorage"); + env->DeleteLocalRef(webStorage); +#endif + + return jniRegisterNativeMethods(env, "android/webkit/WebStorage", + gWebStorageMethods, NELEM(gWebStorageMethods)); +} + +} + +#endif //ENABLE(DATABASE) diff --git a/Source/WebKit/android/jni/WebViewCore.cpp b/Source/WebKit/android/jni/WebViewCore.cpp new file mode 100644 index 0000000..f2680b5 --- /dev/null +++ b/Source/WebKit/android/jni/WebViewCore.cpp @@ -0,0 +1,4598 @@ +/* + * 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 "Attribute.h" +#include "BaseLayerAndroid.h" +#include "CachedNode.h" +#include "CachedRoot.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 "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 "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 "MemoryUsage.h" +#include "NamedNodeMap.h" +#include "Navigator.h" +#include "Node.h" +#include "NodeList.h" +#include "Page.h" +#include "PageGroup.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 "RenderInline.h" +#include "RenderLayer.h" +#include "RenderPart.h" +#include "RenderText.h" +#include "RenderTextControl.h" +#include "RenderThemeAndroid.h" +#include "RenderView.h" +#include "ResourceRequest.h" +#include "SchemeRegistry.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SkANP.h" +#include "SkTemplates.h" +#include "SkTDArray.h" +#include "SkTypes.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkUtils.h" +#include "Text.h" +#include "TypingCommand.h" +#include "WebCoreFrameBridge.h" +#include "WebFrameView.h" +#include "WindowsKeyboardCodes.h" +#include "android_graphics.h" +#include "autofill/WebAutoFill.h" +#include "htmlediting.h" +#include "markup.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <ui/KeycodeLabels.h> +#include <wtf/CurrentTime.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/StringImpl.h> + +#if USE(V8) +#include "ScriptController.h" +#include "V8Counters.h" +#include <wtf/text/CString.h> +#endif + +#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 + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#if USE(ACCELERATED_COMPOSITING) +#include "GraphicsLayerAndroid.h" +#include "RenderLayerCompositor.h" +#endif + +/* We pass this flag when recording the actual content, so that we don't spend + time actually regionizing complex path clips, when all we really want to do + is record them. + */ +#define PICT_RECORD_FLAGS SkPicture::kUsePathBoundsForClip_RecordingFlag + +//////////////////////////////////////////////////////////////////////////////////////////////// + +namespace android { + +static SkTDArray<WebViewCore*> gInstanceList; + +void WebViewCore::addInstance(WebViewCore* inst) { + *gInstanceList.append() = inst; +} + +void WebViewCore::removeInstance(WebViewCore* inst) { + int index = gInstanceList.find(inst); + LOG_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; +} + +// ---------------------------------------------------------------------------- + +#define GET_NATIVE_VIEW(env, obj) ((WebViewCore*)env->GetIntField(obj, gWebViewCoreFields.m_nativeClass)) + +// 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_webView; + 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_layersDraw; + 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_didFirstLayout; + jmethodID m_updateViewport; + jmethodID m_sendNotifyProgressFinished; + jmethodID m_sendViewInvalidate; + jmethodID m_updateTextfield; + jmethodID m_updateTextSelection; + jmethodID m_clearTextEntry; + jmethodID m_restoreScale; + jmethodID m_needTouchEvents; + jmethodID m_requestKeyboard; + jmethodID m_requestKeyboardWithSelection; + 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_formDidBlur; + 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_sendFindAgain; + jmethodID m_showRect; + jmethodID m_centerFitRect; + jmethodID m_setScrollbarModes; + jmethodID m_setInstallableWebApp; + jmethodID m_enterFullscreenForVideoLayer; + jmethodID m_setWebTextViewAutoFillable; + jmethodID m_selectAt; + AutoJObject object(JNIEnv* env) { + return getRealObject(env, m_obj); + } +}; + +/* + * WebViewCore Implementation + */ + +static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) +{ + jmethodID m = env->GetMethodID(clazz, name, signature); + LOG_ASSERT(m, "Could not find method %s", name); + return m; +} + +Mutex WebViewCore::gFrameCacheMutex; +Mutex WebViewCore::gButtonMutex; +Mutex WebViewCore::gCursorBoundsMutex; + +WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* mainframe) + : m_pluginInvalTimer(this, &WebViewCore::pluginInvalTimerFired) + , m_deviceMotionAndOrientationManager(this) +{ + m_mainFrame = mainframe; + + m_popupReply = 0; + m_moveGeneration = 0; + m_lastGeneration = 0; + m_touchGeneration = 0; + m_blockTextfieldUpdates = false; + // just initial values. These should be set by client + m_maxXScroll = 320/4; + m_maxYScroll = 240/4; + m_textGeneration = 0; + m_screenWidth = 320; + m_textWrapWidth = 320; + m_scale = 1; +#if ENABLE(TOUCH_EVENTS) + m_forwardingTouchEvents = false; +#endif + m_isPaused = false; + m_screenOnCounter = 0; + m_shouldPaintCaret = true; + + LOG_ASSERT(m_mainFrame, "Uh oh, somehow a frameview was made without an initial frame!"); + + jclass clazz = env->GetObjectClass(javaWebViewCore); + m_javaGlue = new JavaGlue; + 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_layersDraw = GetJMethod(env, clazz, "layersDraw", "()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;"); + 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_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", "(IZLjava/lang/String;I)V"); + m_javaGlue->m_updateTextSelection = GetJMethod(env, clazz, "updateTextSelection", "(IIII)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_requestKeyboardWithSelection = GetJMethod(env, clazz, "requestKeyboardWithSelection", "(IIII)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", "(J)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_formDidBlur = GetJMethod(env, clazz, "formDidBlur", "(I)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;I)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_sendFindAgain = GetJMethod(env, clazz, "sendFindAgain", "()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"); + m_javaGlue->m_setInstallableWebApp = GetJMethod(env, clazz, "setInstallableWebApp", "()V"); +#if ENABLE(VIDEO) + m_javaGlue->m_enterFullscreenForVideoLayer = GetJMethod(env, clazz, "enterFullscreenForVideoLayer", "(ILjava/lang/String;)V"); +#endif + m_javaGlue->m_setWebTextViewAutoFillable = GetJMethod(env, clazz, "setWebTextViewAutoFillable", "(ILjava/lang/String;)V"); + m_javaGlue->m_selectAt = GetJMethod(env, clazz, "selectAt", "(II)V"); + env->DeleteLocalRef(clazz); + + env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this); + + m_scrollOffsetX = m_scrollOffsetY = 0; + + PageGroup::setShouldTrackVisitedLinks(true); + + reset(true); + + 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); + +#if USE(CHROME_NETWORK_STACK) + AndroidNetworkLibraryImpl::InitWithApplicationContext(env, 0); +#endif +} + +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_frameCacheKit; + delete m_navPictureKit; +} + +WebViewCore* WebViewCore::getWebViewCore(const WebCore::FrameView* view) +{ + return getWebViewCore(static_cast<const WebCore::ScrollView*>(view)); +} + +WebViewCore* WebViewCore::getWebViewCore(const WebCore::ScrollView* view) +{ + if (!view) + return 0; + + WebFrameView* webFrameView = static_cast<WebFrameView*>(view->platformWidget()); + if (!webFrameView) + return 0; + return webFrameView->webViewCore(); +} + +void WebViewCore::reset(bool fromConstructor) +{ + DBG_SET_LOG(""); + if (fromConstructor) { + m_frameCacheKit = 0; + m_navPictureKit = 0; + } else { + gFrameCacheMutex.lock(); + delete m_frameCacheKit; + delete m_navPictureKit; + m_frameCacheKit = 0; + m_navPictureKit = 0; + gFrameCacheMutex.unlock(); + } + + m_lastFocused = 0; + m_blurringNodePointer = 0; + m_lastFocusedBounds = WebCore::IntRect(0,0,0,0); + m_focusBoundsChanged = false; + m_lastFocusedSelStart = 0; + m_lastFocusedSelEnd = 0; + clearContent(); + m_updatedFrameCache = true; + m_frameCacheOutOfDate = true; + m_skipContentDraw = false; + m_findIsUp = false; + m_domtree_version = 0; + m_check_domtree_version = true; + m_progressDone = false; + m_hasCursorBounds = false; + + m_scrollOffsetX = 0; + m_scrollOffsetY = 0; + m_screenWidth = 0; + m_screenHeight = 0; + m_groupForVisitedLinks = 0; + m_currentNodeDomNavigationAxis = 0; +} + +static bool layoutIfNeededRecursive(WebCore::Frame* f) +{ + if (!f) + return true; + + WebCore::FrameView* v = f->view(); + if (!v) + return true; + + if (v->needsLayout()) + v->layout(f->tree()->parent()); + + WebCore::Frame* child = f->tree()->firstChild(); + bool success = true; + while (child) { + success &= layoutIfNeededRecursive(child); + child = child->tree()->nextSibling(); + } + + return success && !v->needsLayout(); +} + +CacheBuilder& WebViewCore::cacheBuilder() +{ + return FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder(); +} + +WebCore::Node* WebViewCore::currentFocus() +{ + return cacheBuilder().currentFocus(); +} + +void WebViewCore::recordPicture(SkPicture* picture) +{ + // if there is no document yet, just return + if (!m_mainFrame->document()) { + DBG_NAV_LOG("no document"); + return; + } + // Call layout to ensure that the contentWidth and contentHeight are correct + if (!layoutIfNeededRecursive(m_mainFrame)) { + DBG_NAV_LOG("layout failed"); + return; + } + // draw into the picture's recording canvas + WebCore::FrameView* view = m_mainFrame->view(); + DBG_NAV_LOGD("view=(w=%d,h=%d)", view->contentsWidth(), + view->contentsHeight()); + SkAutoPictureRecord arp(picture, view->contentsWidth(), + view->contentsHeight(), PICT_RECORD_FLAGS); + SkAutoMemoryUsageProbe mup(__FUNCTION__); + + // Copy m_buttons so we can pass it to our graphics context. + gButtonMutex.lock(); + WTF::Vector<Container> buttons(m_buttons); + gButtonMutex.unlock(); + + WebCore::PlatformGraphicsContext pgc(arp.getRecordingCanvas(), &buttons); + WebCore::GraphicsContext gc(&pgc); + view->platformWidget()->draw(&gc, WebCore::IntRect(0, 0, + view->contentsWidth(), view->contentsHeight())); + + gButtonMutex.lock(); + updateButtonList(&buttons); + gButtonMutex.unlock(); +} + +void WebViewCore::recordPictureSet(PictureSet* content) +{ + // if there is no document yet, just return + if (!m_mainFrame->document()) { + DBG_SET_LOG("!m_mainFrame->document()"); + return; + } + // If there is a pending style recalculation, just return. + if (m_mainFrame->document()->isPendingStyleRecalc()) { + LOGW("recordPictureSet: pending style recalc, ignoring."); + return; + } + if (m_addInval.isEmpty()) { + DBG_SET_LOG("m_addInval.isEmpty()"); + 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 + m_skipContentDraw = true; + bool success = layoutIfNeededRecursive(m_mainFrame); + m_skipContentDraw = false; + + // We may be mid-layout and thus cannot draw. + if (!success) + return; + + { // collect WebViewCoreRecordTimeCounter after layoutIfNeededRecursive +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreRecordTimeCounter); +#endif + + // 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)) { + // 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 + m_skipContentDraw = true; + bool success = layoutIfNeededRecursive(m_mainFrame); + m_skipContentDraw = false; + if (!success) + return; + + // Set the computed content width + width = view->contentsWidth(); + height = view->contentsHeight(); + } + + if (cacheBuilder().pictureSetDisabled()) + content->clear(); + + content->checkDimensions(width, height, &m_addInval); + + // The inval region may replace existing pictures. The existing pictures + // may have already been split into pieces. If reuseSubdivided() returns + // true, the split pieces are the last entries in the picture already. They + // are marked as invalid, and are rebuilt by rebuildPictureSet(). + + // If the new region doesn't match a set of split pieces, add it to the end. + if (!content->reuseSubdivided(m_addInval)) { + const SkIRect& inval = m_addInval.getBounds(); + SkPicture* picture = rebuildPicture(inval); + DBG_SET_LOGD("{%d,%d,w=%d,h=%d}", inval.fLeft, + inval.fTop, inval.width(), inval.height()); + content->add(m_addInval, picture, 0, false); + SkSafeUnref(picture); + } + // Remove any pictures already in the set that are obscured by the new one, + // and check to see if any already split pieces need to be redrawn. + if (content->build()) + rebuildPictureSet(content); + } // WebViewCoreRecordTimeCounter + WebCore::Node* oldFocusNode = currentFocus(); + m_frameCacheOutOfDate = true; + WebCore::IntRect oldBounds; + int oldSelStart = 0; + int oldSelEnd = 0; + if (oldFocusNode) { + oldBounds = oldFocusNode->getRect(); + RenderObject* renderer = oldFocusNode->renderer(); + if (renderer && (renderer->isTextArea() || renderer->isTextField())) { + WebCore::RenderTextControl* rtc = + static_cast<WebCore::RenderTextControl*>(renderer); + oldSelStart = rtc->selectionStart(); + oldSelEnd = rtc->selectionEnd(); + } + } else + oldBounds = WebCore::IntRect(0,0,0,0); + unsigned latestVersion = 0; + if (m_check_domtree_version) { + // as domTreeVersion only increment, we can just check the sum to see + // whether we need to update the frame cache + for (Frame* frame = m_mainFrame; frame; frame = frame->tree()->traverseNext()) { + const Document* doc = frame->document(); + latestVersion += doc->domTreeVersion() + doc->styleVersion(); + } + } + DBG_NAV_LOGD("m_lastFocused=%p oldFocusNode=%p" + " m_lastFocusedBounds={%d,%d,%d,%d} oldBounds={%d,%d,%d,%d}" + " m_lastFocusedSelection={%d,%d} oldSelection={%d,%d}" + " m_check_domtree_version=%s latestVersion=%d m_domtree_version=%d", + m_lastFocused, oldFocusNode, + m_lastFocusedBounds.x(), m_lastFocusedBounds.y(), + m_lastFocusedBounds.width(), m_lastFocusedBounds.height(), + oldBounds.x(), oldBounds.y(), oldBounds.width(), oldBounds.height(), + m_lastFocusedSelStart, m_lastFocusedSelEnd, oldSelStart, oldSelEnd, + m_check_domtree_version ? "true" : "false", + latestVersion, m_domtree_version); + if (m_lastFocused == oldFocusNode && m_lastFocusedBounds == oldBounds + && m_lastFocusedSelStart == oldSelStart + && m_lastFocusedSelEnd == oldSelEnd + && !m_findIsUp + && (!m_check_domtree_version || latestVersion == m_domtree_version)) + { + return; + } + m_focusBoundsChanged |= m_lastFocused == oldFocusNode + && m_lastFocusedBounds != oldBounds; + m_lastFocused = oldFocusNode; + m_lastFocusedBounds = oldBounds; + m_lastFocusedSelStart = oldSelStart; + m_lastFocusedSelEnd = oldSelEnd; + m_domtree_version = latestVersion; + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + if (m_findIsUp) { + LOG_ASSERT(m_javaGlue->m_obj, + "A Java widget was not associated with this view bridge!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_sendFindAgain); + checkException(env); + } +} + +void WebViewCore::updateButtonList(WTF::Vector<Container>* buttons) +{ + // All the entries in buttons are either updates of previous entries in + // m_buttons or they need to be added to it. + Container* end = buttons->end(); + for (Container* updatedContainer = buttons->begin(); + updatedContainer != end; updatedContainer++) { + bool updated = false; + // Search for a previous entry that references the same node as our new + // data + Container* lastPossibleMatch = m_buttons.end(); + for (Container* possibleMatch = m_buttons.begin(); + possibleMatch != lastPossibleMatch; possibleMatch++) { + if (updatedContainer->matches(possibleMatch->node())) { + // Update our record, and skip to the next one. + possibleMatch->setRect(updatedContainer->rect()); + updated = true; + break; + } + } + if (!updated) { + // This is a brand new button, so append it to m_buttons + m_buttons.append(*updatedContainer); + } + } + size_t i = 0; + // count will decrease each time one is removed, so check count each time. + while (i < m_buttons.size()) { + if (m_buttons[i].canBeRemoved()) { + m_buttons[i] = m_buttons.last(); + m_buttons.removeLast(); + } else { + i++; + } + } +} + +// note: updateCursorBounds is called directly by the WebView thread +// This needs to be called each time we call CachedRoot::setCursor() with +// non-null CachedNode/CachedFrame, since otherwise the WebViewCore's data +// about the cursor is incorrect. When we call setCursor(0,0), we need +// to set hasCursorBounds to false. +void WebViewCore::updateCursorBounds(const CachedRoot* root, + const CachedFrame* cachedFrame, const CachedNode* cachedNode) +{ + LOG_ASSERT(root, "updateCursorBounds: root cannot be null"); + LOG_ASSERT(cachedNode, "updateCursorBounds: cachedNode cannot be null"); + LOG_ASSERT(cachedFrame, "updateCursorBounds: cachedFrame cannot be null"); + gCursorBoundsMutex.lock(); + m_hasCursorBounds = !cachedNode->isHidden(); + // If m_hasCursorBounds is false, we never look at the other + // values, so do not bother setting them. + if (m_hasCursorBounds) { + WebCore::IntRect bounds = cachedNode->bounds(cachedFrame); + if (m_cursorBounds != bounds) + DBG_NAV_LOGD("new cursor bounds=(%d,%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + m_cursorBounds = bounds; + m_cursorHitBounds = cachedNode->hitBounds(cachedFrame); + m_cursorFrame = cachedFrame->framePointer(); + root->getSimulatedMousePosition(&m_cursorLocation); + m_cursorNode = cachedNode->nodePointer(); + } + gCursorBoundsMutex.unlock(); +} + +void WebViewCore::clearContent() +{ + DBG_SET_LOG(""); + m_content.clear(); + m_addInval.setEmpty(); + m_rebuildInval.setEmpty(); +} + +bool WebViewCore::focusBoundsChanged() +{ + bool result = m_focusBoundsChanged; + m_focusBoundsChanged = false; + return result; +} + +SkPicture* WebViewCore::rebuildPicture(const SkIRect& inval) +{ + WebCore::FrameView* view = m_mainFrame->view(); + int width = view->contentsWidth(); + int height = view->contentsHeight(); + SkPicture* picture = new SkPicture(); + SkAutoPictureRecord arp(picture, width, height, PICT_RECORD_FLAGS); + SkAutoMemoryUsageProbe mup(__FUNCTION__); + SkCanvas* recordingCanvas = arp.getRecordingCanvas(); + + gButtonMutex.lock(); + WTF::Vector<Container> buttons(m_buttons); + gButtonMutex.unlock(); + + WebCore::PlatformGraphicsContext pgc(recordingCanvas, &buttons); + WebCore::GraphicsContext gc(&pgc); + recordingCanvas->translate(-inval.fLeft, -inval.fTop); + recordingCanvas->save(); + view->platformWidget()->draw(&gc, WebCore::IntRect(inval.fLeft, + inval.fTop, inval.width(), inval.height())); + m_rebuildInval.op(inval, SkRegion::kUnion_Op); + DBG_SET_LOGD("m_rebuildInval={%d,%d,r=%d,b=%d}", + m_rebuildInval.getBounds().fLeft, m_rebuildInval.getBounds().fTop, + m_rebuildInval.getBounds().fRight, m_rebuildInval.getBounds().fBottom); + + gButtonMutex.lock(); + updateButtonList(&buttons); + gButtonMutex.unlock(); + + return picture; +} + +void WebViewCore::rebuildPictureSet(PictureSet* pictureSet) +{ + WebCore::FrameView* view = m_mainFrame->view(); + size_t size = pictureSet->size(); + for (size_t index = 0; index < size; index++) { + if (pictureSet->upToDate(index)) + continue; + const SkIRect& inval = pictureSet->bounds(index); + DBG_SET_LOGD("pictSet=%p [%d] {%d,%d,w=%d,h=%d}", pictureSet, index, + inval.fLeft, inval.fTop, inval.width(), inval.height()); + pictureSet->setPicture(index, rebuildPicture(inval)); + } + pictureSet->validate(__FUNCTION__); +} + +BaseLayerAndroid* WebViewCore::createBaseLayer() +{ + BaseLayerAndroid* base = new BaseLayerAndroid(); + base->setContent(m_content); + + bool layoutSucceeded = layoutIfNeededRecursive(m_mainFrame); + // Layout only fails if called during a layout. + LOG_ASSERT(layoutSucceeded, "Can never be called recursively"); + +#if USE(ACCELERATED_COMPOSITING) + // We set the background color + 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()) { + Color color = style->visitedDependentColor(CSSPropertyBackgroundColor); + if (color.isValid() && color.alpha() > 0) + base->setBackgroundColor(color); + } + } + + // We update the layers + ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client()); + GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync()); + if (root) { + root->notifyClientAnimationStarted(); + LayerAndroid* copyLayer = new LayerAndroid(*root->contentLayer()); + base->addChild(copyLayer); + copyLayer->unref(); + } +#endif + + return base; +} + +BaseLayerAndroid* WebViewCore::recordContent(SkRegion* region, SkIPoint* point) +{ + DBG_SET_LOG("start"); + float progress = (float) m_mainFrame->page()->progress()->estimatedProgress(); + m_progressDone = progress <= 0.0f || progress >= 1.0f; + recordPictureSet(&m_content); + if (!m_progressDone && m_content.isEmpty()) { + DBG_SET_LOGD("empty (progress=%g)", progress); + return 0; + } + region->set(m_addInval); + m_addInval.setEmpty(); + region->op(m_rebuildInval, SkRegion::kUnion_Op); + m_rebuildInval.setEmpty(); + point->fX = m_content.width(); + point->fY = m_content.height(); + DBG_SET_LOGD("region={%d,%d,r=%d,b=%d}", region->getBounds().fLeft, + region->getBounds().fTop, region->getBounds().fRight, + region->getBounds().fBottom); + DBG_SET_LOG("end"); + + return createBaseLayer(); +} + +void WebViewCore::splitContent(PictureSet* content) +{ + bool layoutSucceeded = layoutIfNeededRecursive(m_mainFrame); + LOG_ASSERT(layoutSucceeded, "Can never be called recursively"); + content->split(&m_content); + rebuildPictureSet(&m_content); + content->set(m_content); +} + +void WebViewCore::scrollTo(int x, int y, bool animate) +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + +// LOGD("WebViewCore::scrollTo(%d %d)\n", x, y); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_scrollTo, + x, y, animate, false); + checkException(env); +} + +void WebViewCore::sendNotifyProgressFinished() +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendNotifyProgressFinished); + checkException(env); +} + +void WebViewCore::viewInvalidate(const WebCore::IntRect& rect) +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_sendViewInvalidate, + rect.x(), rect.y(), rect.right(), rect.bottom()); + checkException(env); +} + +void WebViewCore::contentDraw() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_contentDraw); + checkException(env); +} + +void WebViewCore::layersDraw() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_layersDraw); + checkException(env); +} + +void WebViewCore::contentInvalidate(const WebCore::IntRect &r) +{ + DBG_SET_LOGD("rect={%d,%d,w=%d,h=%d}", r.x(), r.y(), r.width(), r.height()); + SkIRect rect(r); + if (!rect.intersect(0, 0, INT_MAX, INT_MAX)) + return; + m_addInval.op(rect, SkRegion::kUnion_Op); + DBG_SET_LOGD("m_addInval={%d,%d,r=%d,b=%d}", + m_addInval.getBounds().fLeft, m_addInval.getBounds().fTop, + m_addInval.getBounds().fRight, m_addInval.getBounds().fBottom); + 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); +} + +static int pin_pos(int x, int width, int targetWidth) +{ + if (x + width > targetWidth) + x = targetWidth - width; + if (x < 0) + x = 0; + return x; +} + +void WebViewCore::didFirstLayout() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + WebCore::FrameLoader* loader = m_mainFrame->loader(); + const WebCore::KURL& url = loader->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data()); + + WebCore::FrameLoadType loadType = loader->loadType(); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).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); + checkException(env); + + DBG_NAV_LOG("call updateFrameCache"); + m_check_domtree_version = false; + updateFrameCache(); + m_history.setDidFirstLayout(true); +} + +void WebViewCore::updateViewport() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateViewport); + checkException(env); +} + +void WebViewCore::restoreScale(float scale, float textWrapScale) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_restoreScale, scale, textWrapScale); + checkException(env); +} + +void WebViewCore::needTouchEvents(bool need) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + +#if ENABLE(TOUCH_EVENTS) + if (m_forwardingTouchEvents == need) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_needTouchEvents, need); + checkException(env); + + m_forwardingTouchEvents = need; +#endif +} + +void WebViewCore::requestKeyboardWithSelection(const WebCore::Node* node, + int selStart, int selEnd) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestKeyboardWithSelection, + reinterpret_cast<int>(node), selStart, selEnd, m_textGeneration); + checkException(env); +} + +void WebViewCore::requestKeyboard(bool showKeyboard) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestKeyboard, showKeyboard); + checkException(env); +} + +void WebViewCore::notifyProgressFinished() +{ + m_check_domtree_version = true; + sendNotifyProgressFinished(); +} + +void WebViewCore::doMaxScroll(CacheBuilder::Direction dir) +{ + int dx = 0, dy = 0; + + switch (dir) { + case CacheBuilder::LEFT: + dx = -m_maxXScroll; + break; + case CacheBuilder::UP: + dy = -m_maxYScroll; + break; + case CacheBuilder::RIGHT: + dx = m_maxXScroll; + break; + case CacheBuilder::DOWN: + dy = m_maxYScroll; + break; + case CacheBuilder::UNINITIALIZED: + default: + LOG_ASSERT(0, "unexpected focus selector"); + } + WebCore::FrameView* view = m_mainFrame->view(); + this->scrollTo(view->scrollX() + dx, view->scrollY() + dy, true); +} + +void WebViewCore::setScrollOffset(int moveGeneration, bool sendScrollEvent, int dx, int dy) +{ + DBG_NAV_LOGD("{%d,%d} m_scrollOffset=(%d,%d), sendScrollEvent=%d", dx, dy, + m_scrollOffsetX, m_scrollOffsetY, sendScrollEvent); + if (m_scrollOffsetX != dx || m_scrollOffsetY != dy) { + m_scrollOffsetX = dx; + m_scrollOffsetY = dy; + // 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(); + } + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + IntPoint location = m_cursorLocation; + gCursorBoundsMutex.unlock(); + if (!hasCursorBounds) + return; + moveMouseIfLatest(moveGeneration, frame, location.x(), location.y()); +} + +void WebViewCore::setGlobalBounds(int x, int y, int h, int v) +{ + DBG_NAV_LOGD("{%d,%d}", x, y); + 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) +{ + 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; + float oldScale = m_scale; + DBG_NAV_LOGD("old:(w=%d,h=%d,sw=%d,scale=%g) new:(w=%d,h=%d,sw=%d,scale=%g)", + ow, oh, osw, m_scale, width, height, screenWidth, scale); + 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(); + DBG_NAV_LOGD("renderer=%p view=(w=%d,h=%d)", r, + screenWidth, screenHeight); + if (r) { + WebCore::IntPoint anchorPoint = WebCore::IntPoint(anchorX, anchorY); + DBG_NAV_LOGD("anchorX=%d anchorY=%d", 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) { + bounds = node->getRect(); + DBG_NAV_LOGD("ob:(x=%d,y=%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + // 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(); + DBG_NAV_LOGD("found a zero width node and use its parent, whose ob:(x=%d,y=%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + } + } + } + + // 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(); + m_mainFrame->view()->forceLayout(); + + // scroll to restore current screen center + if (node) { + const WebCore::IntRect& newBounds = node->getRect(); + DBG_NAV_LOGD("nb:(x=%d,y=%d,w=%d," + "h=%d)", newBounds.x(), newBounds.y(), + newBounds.width(), newBounds.height()); + 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 +} + +void WebViewCore::dumpNavTree() +{ +#if DUMP_NAV_CACHE + cacheBuilder().mDebug.print(); +#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()) { + LOGE("Should not happen: no in document Node found"); + return 0; + } + const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); + if (list.isEmpty()) { + LOGE("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(); + } + DBG_NAV_LOGD("node=%p element=%p x=%d y=%d nodeName=%s tagName=%s", node, + element, x, y, node->nodeName().utf8().data(), + element ? ((Element*) element)->tagName().utf8().data() : "<none>"); + 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) +{ + WebCore::HTMLAnchorElement* anchor = retrieveAnchorElement(x, y); + return anchor ? anchor->href() : WTF::String(); +} + +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) +{ + HTMLImageElement* image = retrieveImageElement(x, y); + return image ? image->src().string() : WTF::String(); +} + +WTF::String WebViewCore::requestLabel(WebCore::Frame* frame, + WebCore::Node* node) +{ + if (node && CacheBuilder::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->document()->frame()->selection()->isContentEditable(); +} + +// Returns true if the node is a textfield, textarea, or contentEditable +static bool isTextInput(const WebCore::Node* node) +{ + if (isContentEditable(node)) + return true; + if (!node) + return false; + 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); +} + +void WebViewCore::updateCacheOnNodeChange() +{ + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + Node* node = (Node*) m_cursorNode; + IntRect bounds = m_cursorHitBounds; + gCursorBoundsMutex.unlock(); + if (!hasCursorBounds || !node) + return; + if (CacheBuilder::validNode(m_mainFrame, frame, node)) { + RenderObject* renderer = node->renderer(); + if (renderer && renderer->style()->visibility() != HIDDEN) { + IntRect absBox = renderer->absoluteBoundingBoxRect(); + int globalX, globalY; + CacheBuilder::GetGlobalOffset(frame, &globalX, &globalY); + absBox.move(globalX, globalY); + if (absBox == bounds) + return; + DBG_NAV_LOGD("absBox=(%d,%d,%d,%d) bounds=(%d,%d,%d,%d)", + absBox.x(), absBox.y(), absBox.width(), absBox.height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + } + } + DBG_NAV_LOGD("updateFrameCache node=%p", node); + updateFrameCache(); +} + +void WebViewCore::updateFrameCache() +{ + if (!m_frameCacheOutOfDate) { + DBG_NAV_LOG("!m_frameCacheOutOfDate"); + return; + } + + // If there is a pending style recalculation, do not update the frame cache. + // Until the recalculation is complete, there may be internal objects that + // are in an inconsistent state (such as font pointers). + // In any event, there's not much point to updating the cache while a style + // recalculation is pending, since it will simply have to be updated again + // once the recalculation is complete. + // TODO: Do we need to reschedule an update for after the style is recalculated? + if (m_mainFrame && m_mainFrame->document() && m_mainFrame->document()->isPendingStyleRecalc()) { + LOGW("updateFrameCache: pending style recalc, ignoring."); + return; + } +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreBuildNavTimeCounter); +#endif + m_frameCacheOutOfDate = false; +#if DEBUG_NAV_UI + m_now = SkTime::GetMSecs(); +#endif + m_temp = new CachedRoot(); + m_temp->init(m_mainFrame, &m_history); +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* graphicsLayer = graphicsRootLayer(); + if (graphicsLayer) + m_temp->setRootLayer(graphicsLayer->contentLayer()); +#endif + CacheBuilder& builder = cacheBuilder(); + WebCore::Settings* settings = m_mainFrame->page()->settings(); + builder.allowAllTextDetection(); +#ifdef ANDROID_META_SUPPORT + if (settings) { + if (!settings->formatDetectionAddress()) + builder.disallowAddressDetection(); + if (!settings->formatDetectionEmail()) + builder.disallowEmailDetection(); + if (!settings->formatDetectionTelephone()) + builder.disallowPhoneDetection(); + } +#endif + builder.buildCache(m_temp); + m_tempPict = new SkPicture(); + recordPicture(m_tempPict); + m_temp->setPicture(m_tempPict); + m_temp->setTextGeneration(m_textGeneration); + WebCoreViewBridge* window = m_mainFrame->view()->platformWidget(); + m_temp->setVisibleRect(WebCore::IntRect(m_scrollOffsetX, + m_scrollOffsetY, window->width(), window->height())); + gFrameCacheMutex.lock(); + delete m_frameCacheKit; + delete m_navPictureKit; + m_frameCacheKit = m_temp; + m_navPictureKit = m_tempPict; + m_updatedFrameCache = true; +#if DEBUG_NAV_UI + const CachedNode* cachedFocusNode = m_frameCacheKit->currentFocus(); + DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p)", + cachedFocusNode ? cachedFocusNode->index() : 0, + cachedFocusNode ? cachedFocusNode->nodePointer() : 0); +#endif + gFrameCacheMutex.unlock(); +} + +void WebViewCore::updateFrameCacheIfLoading() +{ + if (!m_check_domtree_version) + updateFrameCache(); +} + +struct TouchNodeData { + Node* mNode; + IntRect mBounds; +}; + +// get the bounding box of the Node +static IntRect getAbsoluteBoundingBox(Node* node) { + IntRect rect; + RenderObject* render = node->renderer(); + if (render->isRenderInline()) + rect = toRenderInline(render)->linesVisualOverflowBoundingBox(); + else if (render->isBox()) + rect = toRenderBox(render)->visualOverflowRect(); + else if (render->isText()) + rect = toRenderText(render)->linesBoundingBox(); + else + LOGE("getAbsoluteBoundingBox failed for node %p, name %s", node, render->renderName()); + FloatPoint absPos = render->localToAbsolute(); + rect.move(absPos.x(), absPos.y()); + return rect; +} + +// get the highlight rectangles for the touch point (x, y) with the slop +Vector<IntRect> WebViewCore::getTouchHighlightRects(int x, int y, int slop) +{ + Vector<IntRect> rects; + m_mousePos = IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY); + HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), + false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(slop, slop)); + if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) { + LOGE("Should not happen: no in document Node found"); + return rects; + } + const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); + if (list.isEmpty()) { + LOGE("Should not happen: no rect-based-test nodes found"); + return rects; + } + Frame* frame = hitTestResult.innerNode()->document()->frame(); + Vector<TouchNodeData> nodeDataList; + 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(); + while (eventNode) { + RenderObject* render = eventNode->renderer(); + if (render->isBody() || render->isRenderView()) + break; + if (eventNode->supportsFocus() + || eventNode->hasEventListeners(eventNames().clickEvent) + || eventNode->hasEventListeners(eventNames().mousedownEvent) + || eventNode->hasEventListeners(eventNames().mouseupEvent)) { + 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->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->mNode) { + 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.mNode = eventNode; + newNode.mBounds = rect; + nodeDataList.append(newNode); + } + } + if (!nodeDataList.size()) + return rects; + // finally select the node with the largest overlap with the fat point + TouchNodeData final; + final.mNode = 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 = *n; + area = a; + } + } + // now get the node's highlight rectangles in the page coordinate system + if (final.mNode) { + IntPoint frameAdjust; + if (frame != m_mainFrame) { + frameAdjust = frame->view()->contentsToWindow(IntPoint()); + frameAdjust.move(m_scrollOffsetX, m_scrollOffsetY); + } + if (final.mNode->isLink()) { + // most of the links are inline instead of box style. So the bounding box is not + // a good representation for the highlights. Get the list of rectangles instead. + RenderObject* render = final.mNode->renderer(); + IntPoint offset = roundedIntPoint(render->localToAbsolute()); + render->absoluteRects(rects, offset.x() + frameAdjust.x(), offset.y() + frameAdjust.y()); + bool inside = false; + int distance = INT_MAX; + int newx = x, newy = y; + int i = rects.size(); + while (i--) { + if (rects[i].isEmpty()) { + rects.remove(i); + continue; + } + // check whether the point (x, y) is inside one of the rectangles. + if (inside) + continue; + if (rects[i].contains(x, y)) { + inside = true; + continue; + } + if (x >= rects[i].x() && x < rects[i].right()) { + if (y < rects[i].y()) { + if (rects[i].y() - y < distance) { + newx = x; + newy = rects[i].y(); + distance = rects[i].y() - y; + } + } else if (y >= rects[i].bottom()) { + if (y - rects[i].bottom() + 1 < distance) { + newx = x; + newy = rects[i].bottom() - 1; + distance = y - rects[i].bottom() + 1; + } + } + } else if (y >= rects[i].y() && y < rects[i].bottom()) { + if (x < rects[i].x()) { + if (rects[i].x() - x < distance) { + newx = rects[i].x(); + newy = y; + distance = rects[i].x() - x; + } + } else if (x >= rects[i].right()) { + if (x - rects[i].right() + 1 < distance) { + newx = rects[i].right() - 1; + newy = y; + distance = x - rects[i].right() + 1; + } + } + } + } + if (!rects.isEmpty()) { + if (!inside) { + // if neither x nor y has overlap, just pick the top/left of the first rectangle + if (newx == x && newy == y) { + newx = rects[0].x(); + newy = rects[0].y(); + } + m_mousePos.setX(newx - m_scrollOffsetX); + m_mousePos.setY(newy - m_scrollOffsetY); + DBG_NAV_LOGD("Move x/y from (%d, %d) to (%d, %d) scrollOffset is (%d, %d)", + x, y, m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, + m_scrollOffsetX, m_scrollOffsetY); + } + return rects; + } + } + IntRect rect = final.mBounds; + rect.move(frameAdjust.x(), frameAdjust.y()); + rects.append(rect); + // adjust m_mousePos if it is not inside the returned highlight rectangle + testRect.move(frameAdjust.x(), frameAdjust.y()); + testRect.intersect(rect); + if (!testRect.contains(x, y)) { + m_mousePos = testRect.center(); + m_mousePos.move(-m_scrollOffsetX, -m_scrollOffsetY); + DBG_NAV_LOGD("Move x/y from (%d, %d) to (%d, %d) scrollOffset is (%d, %d)", + x, y, m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, + m_scrollOffsetX, m_scrollOffsetY); + } + } + return rects; +} + +/////////////////////////////////////////////////////////////////////////////// + +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::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; +} + +Node* WebViewCore::cursorNodeIsPlugin() { + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + Node* node = (Node*) m_cursorNode; + gCursorBoundsMutex.unlock(); + if (hasCursorBounds && CacheBuilder::validNode(m_mainFrame, frame, node) + && nodeIsPlugin(node)) { + return node; + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +void WebViewCore::moveMouseIfLatest(int moveGeneration, + WebCore::Frame* frame, int x, int y) +{ + DBG_NAV_LOGD("m_moveGeneration=%d moveGeneration=%d" + " frame=%p x=%d y=%d", + m_moveGeneration, moveGeneration, frame, x, y); + if (m_moveGeneration > moveGeneration) { + DBG_NAV_LOGD("m_moveGeneration=%d > moveGeneration=%d", + m_moveGeneration, moveGeneration); + return; // short-circuit if a newer move has already been generated + } + m_lastGeneration = moveGeneration; + moveMouse(frame, x, y); +} + +void WebViewCore::moveFocus(WebCore::Frame* frame, WebCore::Node* node) +{ + DBG_NAV_LOGD("frame=%p node=%p", frame, node); + if (!node || !CacheBuilder::validNode(m_mainFrame, frame, node) + || !node->isElementNode()) + return; + // Code borrowed from FocusController::advanceFocus + WebCore::FocusController* focusController + = m_mainFrame->page()->focusController(); + WebCore::Document* oldDoc + = focusController->focusedOrMainFrame()->document(); + if (oldDoc->focusedNode() == node) + return; + if (node->document() != oldDoc) + oldDoc->setFocusedNode(0); + focusController->setFocusedFrame(frame); + static_cast<WebCore::Element*>(node)->focus(false); +} + +// Update mouse position +void WebViewCore::moveMouse(WebCore::Frame* frame, int x, int y) +{ + DBG_NAV_LOGD("frame=%p x=%d y=%d scrollOffset=(%d,%d)", frame, + x, y, m_scrollOffsetX, m_scrollOffsetY); + if (!frame || !CacheBuilder::validNode(m_mainFrame, frame, 0)) + frame = m_mainFrame; + // mouse event expects the position in the window coordinate + m_mousePos = WebCore::IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY); + // 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()); + frame->eventHandler()->handleMouseMoveEvent(mouseEvent); + updateCacheOnNodeChange(); +} + +void WebViewCore::setSelection(int start, int end) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) + return; + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) + return; + if (start > end) { + int temp = start; + start = end; + end = temp; + } + // 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); + setSelectionRange(focus, start, end); + client->setUiGeneratedSelectionChange(false); + WebCore::Frame* focusedFrame = focus->document()->frame(); + bool isPasswordField = false; + if (focus->isElementNode()) { + WebCore::Element* element = static_cast<WebCore::Element*>(focus); + if (WebCore::InputElement* inputElement = WebCore::toInputElement(element)) + 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 (renderer->isTextArea() || !isPasswordField) + revealSelection(); +} + +String WebViewCore::modifySelection(const int direction, const int axis) +{ + DOMSelection* selection = m_mainFrame->domWindow()->getSelection(); + 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: + LOGE("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 || node == body) + return; + node = node->parentNode(); + } while (!node->isElementNode() && !isVisible(node)); + } + + 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 + && CacheBuilder::validNode(m_mainFrame, + m_mainFrame, m_currentNodeDomNavigationAxis)) { + PassRefPtr<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 if (m_cursorNode + && CacheBuilder::validNode(m_mainFrame, + m_mainFrame, m_cursorNode)) { + PassRefPtr<Range> rangeRef = + selection->frame()->document()->createRange(); + rangeRef->selectNode(reinterpret_cast<Node*>(m_cursorNode), ec); + if (ec) + return String(); + selection->addRange(rangeRef.get()); + } 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(focusNode)) { + Node* textNode = traverseNextContentTextNode(focusNode, + anchorNode, DIRECTION_FORWARD); + 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, caretMinOffset(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(); + } + } + } + + // make sure the selection is visible + if (direction == DIRECTION_FORWARD) + scrollNodeIntoView(m_mainFrame, selection->focusNode()); + else + scrollNodeIntoView(m_mainFrame, selection->anchorNode()); + + // format markup for the visible content + PassRefPtr<Range> range = selection->getRangeAt(0, ec); + if (ec) + return String(); + IntRect bounds = range->boundingBox(); + selectAt(bounds.center().x(), bounds.center().y()); + markup = formatMarkup(selection); + LOGV("Selection markup: %s", markup.utf8().data()); + + return markup; +} + +Node* WebViewCore::getImplicitBoundaryNode(Node* node, unsigned offset, int direction) +{ + if (node->offsetInCharacters()) + return node; + if (!node->hasChildNodes()) + return node; + if (offset < node->childNodeCount()) + return node->childNode(offset); + else + if (direction == DIRECTION_FORWARD) + return node->traverseNextSibling(); + else + return node->traversePreviousNodePostOrder( + node->document()->body()); +} + +Node* WebViewCore::getNextAnchorNode(Node* anchorNode, bool ignoreFirstNode, int direction) +{ + Node* body = 0; + Node* currentNode = 0; + if (direction == DIRECTION_FORWARD) { + if (ignoreFirstNode) + currentNode = anchorNode->traverseNextNode(body); + else + currentNode = anchorNode; + } else { + body = anchorNode->document()->body(); + if (ignoreFirstNode) + currentNode = anchorNode->traversePreviousSiblingPostOrder(body); + else + currentNode = anchorNode; + } + while (currentNode) { + if (isContentTextNode(currentNode) + || isContentInputElement(currentNode)) + return currentNode; + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(); + else + currentNode = currentNode->traversePreviousNodePostOrder(body); + } + return 0; +} + +void WebViewCore::advanceAnchorNode(DOMSelection* selection, int direction, + String& markup, bool ignoreFirstNode, ExceptionCode& ec) +{ + Node* anchorNode = getImplicitBoundaryNode(selection->anchorNode(), + selection->anchorOffset(), direction); + if (!anchorNode) { + ec = NOT_FOUND_ERR; + return; + } + // If the anchor offset is invalid i.e. the anchor node has no + // child with that index getImplicitAnchorNode returns the next + // logical node in the current direction. In such a case our + // position in the DOM tree was has already been advanced, + // therefore we there is no need to do that again. + if (selection->anchorNode()->isElementNode()) { + unsigned anchorOffset = selection->anchorOffset(); + unsigned childNodeCount = selection->anchorNode()->childNodeCount(); + if (anchorOffset >= childNodeCount) + ignoreFirstNode = false; + } + // Find the next anchor node given our position in the DOM and + // whether we want the current node to be considered as well. + Node* nextAnchorNode = getNextAnchorNode(anchorNode, ignoreFirstNode, + direction); + if (!nextAnchorNode) { + ec = NOT_FOUND_ERR; + return; + } + if (nextAnchorNode->isElementNode()) { + // If this is an input element tell the WebView thread + // to set the cursor to that control. + if (isContentInputElement(nextAnchorNode)) { + IntRect bounds = nextAnchorNode->getRect(); + selectAt(bounds.center().x(), bounds.center().y()); + } + Node* textNode = 0; + // Treat the text content of links as any other text but + // for the rest input elements select the control itself. + if (nextAnchorNode->hasTagName(WebCore::HTMLNames::aTag)) + textNode = traverseNextContentTextNode(nextAnchorNode, + nextAnchorNode, direction); + // We prefer to select the text content of the link if such, + // otherwise just select the element itself. + if (textNode) { + nextAnchorNode = textNode; + } else { + if (direction == DIRECTION_FORWARD) { + selection->setBaseAndExtent(nextAnchorNode, + caretMinOffset(nextAnchorNode), nextAnchorNode, + caretMaxOffset(nextAnchorNode), ec); + } else { + selection->setBaseAndExtent(nextAnchorNode, + caretMaxOffset(nextAnchorNode), nextAnchorNode, + caretMinOffset(nextAnchorNode), ec); + } + if (!ec) + markup = formatMarkup(selection); + // make sure the selection is visible + scrollNodeIntoView(selection->frame(), nextAnchorNode); + return; + } + } + if (direction == DIRECTION_FORWARD) + selection->setPosition(nextAnchorNode, + caretMinOffset(nextAnchorNode), ec); + else + selection->setPosition(nextAnchorNode, + caretMaxOffset(nextAnchorNode), ec); +} + +bool WebViewCore::isContentInputElement(Node* node) +{ + return (isVisible(node) + && (node->hasTagName(WebCore::HTMLNames::selectTag) + || node->hasTagName(WebCore::HTMLNames::aTag) + || node->hasTagName(WebCore::HTMLNames::inputTag) + || node->hasTagName(WebCore::HTMLNames::buttonTag))); +} + +bool WebViewCore::isContentTextNode(Node* node) +{ + if (!node || !node->isTextNode()) + return false; + Text* textNode = static_cast<Text*>(node); + return (isVisible(textNode) && textNode->length() > 0 + && !textNode->containsOnlyWhitespace()); +} + +Text* WebViewCore::traverseNextContentTextNode(Node* fromNode, Node* toNode, int direction) +{ + Node* currentNode = fromNode; + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(toNode); + else + currentNode = currentNode->traversePreviousNodePostOrder(toNode); + } while (currentNode && !isContentTextNode(currentNode)); + return static_cast<Text*>(currentNode); +} + +Node* WebViewCore::getIntermediaryInputElement(Node* fromNode, Node* toNode, int direction) +{ + if (fromNode == toNode) + return 0; + if (direction == DIRECTION_FORWARD) { + Node* currentNode = fromNode; + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traverseNextNodePostOrder(); + } + currentNode = fromNode; + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traverseNextNode(); + } + } else { + Node* currentNode = fromNode->traversePreviousNode(); + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traversePreviousNode(); + } + currentNode = fromNode->traversePreviousNodePostOrder(); + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traversePreviousNodePostOrder(); + } + } + return 0; +} + +bool WebViewCore::isDescendantOf(Node* parent, Node* node) +{ + Node* currentNode = node; + while (currentNode) { + if (currentNode == parent) { + return true; + } + currentNode = currentNode->parentNode(); + } + return false; +} + +String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int axis) +{ + HTMLElement* body = m_mainFrame->document()->body(); + if (!m_currentNodeDomNavigationAxis && selection->focusNode()) { + m_currentNodeDomNavigationAxis = selection->focusNode(); + selection->empty(); + if (m_currentNodeDomNavigationAxis->isTextNode()) + m_currentNodeDomNavigationAxis = + m_currentNodeDomNavigationAxis->parentNode(); + } + if (!m_currentNodeDomNavigationAxis) + m_currentNodeDomNavigationAxis = currentFocus(); + if (!m_currentNodeDomNavigationAxis + || !CacheBuilder::validNode(m_mainFrame, m_mainFrame, + m_currentNodeDomNavigationAxis)) + m_currentNodeDomNavigationAxis = body; + Node* currentNode = m_currentNodeDomNavigationAxis; + if (axis == AXIS_HEADING) { + if (currentNode == body && direction == DIRECTION_BACKWARD) + currentNode = currentNode->lastDescendant(); + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(body); + else + currentNode = currentNode->traversePreviousNode(body); + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode) || !isHeading(currentNode))); + } else if (axis == AXIS_PARENT_FIRST_CHILD) { + if (direction == DIRECTION_FORWARD) { + currentNode = currentNode->firstChild(); + while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))) + currentNode = currentNode->nextSibling(); + } else { + do { + if (currentNode == body) + return String(); + currentNode = currentNode->parentNode(); + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))); + } + } else if (axis == AXIS_SIBLING) { + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->nextSibling(); + else { + if (currentNode == body) + return String(); + currentNode = currentNode->previousSibling(); + } + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))); + } else if (axis == AXIS_DOCUMENT) { + currentNode = body; + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->lastDescendant(); + } else { + LOGE("Invalid axis: %d", axis); + return String(); + } + if (currentNode) { + m_currentNodeDomNavigationAxis = currentNode; + scrollNodeIntoView(m_mainFrame, currentNode); + String selectionString = createMarkup(currentNode); + LOGV("Selection markup: %s", selectionString.utf8().data()); + return selectionString; + } + return String(); +} + +bool WebViewCore::isHeading(Node* node) +{ + if (node->hasTagName(WebCore::HTMLNames::h1Tag) + || node->hasTagName(WebCore::HTMLNames::h2Tag) + || node->hasTagName(WebCore::HTMLNames::h3Tag) + || node->hasTagName(WebCore::HTMLNames::h4Tag) + || node->hasTagName(WebCore::HTMLNames::h5Tag) + || node->hasTagName(WebCore::HTMLNames::h6Tag)) { + return true; + } + + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + String roleAttribute = + element->getAttribute(WebCore::HTMLNames::roleAttr).string(); + if (equalIgnoringCase(roleAttribute, "heading")) + return true; + } + + return false; +} + +bool WebViewCore::isVisible(Node* node) +{ + // start off an element + Element* element = 0; + if (node->isElementNode()) + element = static_cast<Element*>(node); + else + element = node->parentElement(); + // check renderer + if (!element->renderer()) { + return false; + } + // check size + if (element->offsetHeight() == 0 || element->offsetWidth() == 0) { + return false; + } + // check style + Node* body = m_mainFrame->document()->body(); + Node* currentNode = element; + while (currentNode && currentNode != body) { + RenderStyle* style = currentNode->computedStyle(); + if (style && + (style->display() == NONE || style->visibility() == HIDDEN)) { + return false; + } + currentNode = currentNode->parentNode(); + } + return true; +} + +String WebViewCore::formatMarkup(DOMSelection* selection) +{ + ExceptionCode ec = 0; + String markup = String(); + PassRefPtr<Range> wholeRange = selection->getRangeAt(0, ec); + if (ec) + return String(); + if (!wholeRange->startContainer() || !wholeRange->startContainer()) + return String(); + // Since formatted markup contains invisible nodes it + // is created from the concatenation of the visible fragments. + Node* firstNode = wholeRange->firstNode(); + Node* pastLastNode = wholeRange->pastLastNode(); + Node* currentNode = firstNode; + PassRefPtr<Range> currentRange; + + while (currentNode != pastLastNode) { + Node* nextNode = currentNode->traverseNextNode(); + if (!isVisible(currentNode)) { + if (currentRange) { + markup = markup + currentRange->toHTML().utf8().data(); + currentRange = 0; + } + } else { + if (!currentRange) { + currentRange = selection->frame()->document()->createRange(); + if (ec) + break; + if (currentNode == firstNode) { + currentRange->setStart(wholeRange->startContainer(), + wholeRange->startOffset(), ec); + if (ec) + break; + } else { + currentRange->setStart(currentNode->parentNode(), + currentNode->nodeIndex(), ec); + if (ec) + break; + } + } + if (nextNode == pastLastNode) { + currentRange->setEnd(wholeRange->endContainer(), + wholeRange->endOffset(), ec); + if (ec) + break; + markup = markup + currentRange->toHTML().utf8().data(); + } else { + if (currentNode->offsetInCharacters()) + currentRange->setEnd(currentNode, + currentNode->maxCharacterOffset(), ec); + else + currentRange->setEnd(currentNode->parentNode(), + currentNode->nodeIndex() + 1, ec); + if (ec) + break; + } + } + currentNode = nextNode; + } + return markup.stripWhiteSpace(); +} + +void WebViewCore::selectAt(int x, int y) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_selectAt, + x, y); + checkException(env); +} + +void WebViewCore::deleteSelection(int start, int end, int textGeneration) +{ + setSelection(start, end); + if (start == end) + return; + WebCore::Node* focus = currentFocus(); + if (!focus) + return; + // Prevent our editor client from passing a message to change the + // selection. + EditorClientAndroid* client = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setUiGeneratedSelectionChange(true); + PlatformKeyboardEvent down(AKEYCODE_DEL, 0, 0, true, false, false, false); + PlatformKeyboardEvent up(AKEYCODE_DEL, 0, 0, false, false, false, false); + key(down); + key(up); + client->setUiGeneratedSelectionChange(false); + m_textGeneration = textGeneration; + m_shouldPaintCaret = true; +} + +void WebViewCore::replaceTextfieldText(int oldStart, + int oldEnd, const WTF::String& replace, int start, int end, + int textGeneration) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) + return; + setSelection(oldStart, oldEnd); + // Prevent our editor client from passing a message to change the + // selection. + EditorClientAndroid* client = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setUiGeneratedSelectionChange(true); + WebCore::TypingCommand::insertText(focus->document(), replace, + false); + client->setUiGeneratedSelectionChange(false); + // setSelection calls revealSelection, so there is no need to do it here. + setSelection(start, end); + m_textGeneration = textGeneration; + m_shouldPaintCaret = true; +} + +void WebViewCore::passToJs(int generation, const WTF::String& current, + const PlatformKeyboardEvent& event) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) { + DBG_NAV_LOG("!focus"); + clearTextEntry(); + return; + } + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { + DBG_NAV_LOGD("renderer==%p || not text", renderer); + clearTextEntry(); + return; + } + // Block text field updates during a key press. + m_blockTextfieldUpdates = true; + // Also prevent our editor client from passing a message to change the + // selection. + EditorClientAndroid* client = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setUiGeneratedSelectionChange(true); + key(event); + client->setUiGeneratedSelectionChange(false); + m_blockTextfieldUpdates = false; + m_textGeneration = generation; + WebCore::RenderTextControl* renderText = + static_cast<WebCore::RenderTextControl*>(renderer); + WTF::String test = renderText->text(); + if (test != current) { + // If the text changed during the key event, update the UI text field. + updateTextfield(focus, false, test); + } else { + DBG_NAV_LOG("test == current"); + } + // Now that the selection has settled down, send it. + updateTextSelection(); + m_shouldPaintCaret = true; +} + +void WebViewCore::scrollFocusedTextInput(float xPercent, int y) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) { + DBG_NAV_LOG("!focus"); + clearTextEntry(); + return; + } + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { + DBG_NAV_LOGD("renderer==%p || not text", renderer); + clearTextEntry(); + return; + } + WebCore::RenderTextControl* renderText = + static_cast<WebCore::RenderTextControl*>(renderer); + int x = (int) (xPercent * (renderText->scrollWidth() - + renderText->clientWidth())); + DBG_NAV_LOGD("x=%d y=%d xPercent=%g scrollW=%d clientW=%d", x, y, + xPercent, renderText->scrollWidth(), renderText->clientWidth()); + renderText->setScrollLeft(x); + renderText->setScrollTop(y); +} + +void WebViewCore::setFocusControllerActive(bool active) +{ + m_mainFrame->page()->focusController()->setActive(active); +} + +void WebViewCore::saveDocumentState(WebCore::Frame* frame) +{ + if (!CacheBuilder::validNode(m_mainFrame, frame, 0)) + frame = m_mainFrame; + WebCore::HistoryItem *item = frame->loader()->history()->currentItem(); + + // item can be null when there is no offical URL for the current page. This happens + // when the content is loaded using with WebCoreFrameBridge::LoadData() and there + // is no failing URL (common case is when content is loaded using data: scheme) + if (item) { + item->setDocumentState(frame->document()->formElementsState()); + } +} + +// Create an array of java Strings. +static jobjectArray makeLabelArray(JNIEnv* env, const uint16_t** labels, size_t count) +{ + jclass stringClass = env->FindClass("java/lang/String"); + LOG_ASSERT(stringClass, "Could not find java/lang/String"); + jobjectArray array = env->NewObjectArray(count, stringClass, 0); + LOG_ASSERT(array, "Could not create new string array"); + + for (size_t i = 0; i < count; i++) { + jobject newString = env->NewString(&labels[i][1], labels[i][0]); + env->SetObjectArrayElement(array, i, newString); + env->DeleteLocalRef(newString); + checkException(env); + } + env->DeleteLocalRef(stringClass); + return array; +} + +void WebViewCore::openFileChooser(PassRefPtr<WebCore::FileChooser> chooser) { + if (!chooser) + return; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + + WTF::String acceptType = chooser->acceptTypes(); + jstring jAcceptType = wtfStringToJstring(env, acceptType, true); + jstring jName = (jstring) env->CallObjectMethod( + m_javaGlue->object(env).get(), m_javaGlue->m_openFileChooser, jAcceptType); + checkException(env); + env->DeleteLocalRef(jAcceptType); + + const UChar* string = static_cast<const UChar*>(env->GetStringChars(jName, NULL)); + + if (!string) + return; + + WTF::String webcoreString = jstringToWtfString(env, jName); + env->ReleaseStringChars(jName, string); + + if (webcoreString.length()) + chooser->chooseFile(webcoreString); +} + +void WebViewCore::listBoxRequest(WebCoreReply* reply, const uint16_t** labels, size_t count, const int enabled[], size_t enabledCount, + bool multiple, const int selected[], size_t selectedCountOrSelection) +{ + // If m_popupReply is not null, then we already have a list showing. + if (m_popupReply != 0) + return; + + LOG_ASSERT(m_javaGlue->m_obj, "No java widget associated with this view!"); + + // Create an array of java Strings for the drop down. + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobjectArray labelArray = makeLabelArray(env, labels, count); + + // Create an array determining whether each item is enabled. + jintArray enabledArray = env->NewIntArray(enabledCount); + checkException(env); + jint* ptrArray = env->GetIntArrayElements(enabledArray, 0); + checkException(env); + for (size_t i = 0; i < enabledCount; i++) { + ptrArray[i] = enabled[i]; + } + env->ReleaseIntArrayElements(enabledArray, ptrArray, 0); + checkException(env); + + if (multiple) { + // Pass up an array representing which items are selected. + jintArray selectedArray = env->NewIntArray(selectedCountOrSelection); + checkException(env); + jint* selArray = env->GetIntArrayElements(selectedArray, 0); + checkException(env); + for (size_t i = 0; i < selectedCountOrSelection; i++) { + selArray[i] = selected[i]; + } + env->ReleaseIntArrayElements(selectedArray, selArray, 0); + + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestListBox, labelArray, enabledArray, + selectedArray); + env->DeleteLocalRef(selectedArray); + } else { + // Pass up the single selection. + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestSingleListBox, labelArray, enabledArray, + selectedCountOrSelection); + } + + env->DeleteLocalRef(labelArray); + env->DeleteLocalRef(enabledArray); + checkException(env); + + Retain(reply); + m_popupReply = reply; +} + +bool WebViewCore::key(const PlatformKeyboardEvent& event) +{ + WebCore::EventHandler* eventHandler; + WebCore::Node* focusNode = currentFocus(); + DBG_NAV_LOGD("keyCode=%s unichar=%d focusNode=%p", + event.keyIdentifier().utf8().data(), event.unichar(), focusNode); + if (focusNode) { + WebCore::Frame* frame = focusNode->document()->frame(); + WebFrame* webFrame = WebFrame::getWebFrame(frame); + eventHandler = frame->eventHandler(); + VisibleSelection old = frame->selection()->selection(); + bool handled = eventHandler->keyEvent(event); + if (isContentEditable(focusNode)) { + // keyEvent will return true even if the contentEditable did not + // change its selection. In the case that it does not, we want to + // return false so that the key will be sent back to our navigation + // system. + handled |= frame->selection()->selection() != old; + } + return handled; + } else { + eventHandler = m_mainFrame->eventHandler(); + } + return eventHandler->keyEvent(event); +} + +// For when the user clicks the trackball, presses dpad center, or types into an +// unfocused textfield. In the latter case, 'fake' will be true +void WebViewCore::click(WebCore::Frame* frame, WebCore::Node* node, bool fake) { + if (!node) { + WebCore::IntPoint pt = m_mousePos; + pt.move(m_scrollOffsetX, m_scrollOffsetY); + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()-> + hitTestResultAtPoint(pt, false); + node = hitTestResult.innerNode(); + frame = node->document()->frame(); + DBG_NAV_LOGD("m_mousePos=(%d,%d) m_scrollOffset=(%d,%d) pt=(%d,%d)" + " node=%p", m_mousePos.x(), m_mousePos.y(), + m_scrollOffsetX, m_scrollOffsetY, pt.x(), pt.y(), node); + } + if (node) { + EditorClientAndroid* client + = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setShouldChangeSelectedRange(false); + handleMouseClick(frame, node, fake); + client->setShouldChangeSelectedRange(true); + } +} + +#if USE(ACCELERATED_COMPOSITING) +GraphicsLayerAndroid* WebViewCore::graphicsRootLayer() const +{ + RenderView* contentRenderer = m_mainFrame->contentRenderer(); + if (!contentRenderer) + return 0; + return static_cast<GraphicsLayerAndroid*>( + contentRenderer->compositor()->rootPlatformLayer()); +} +#endif + +bool WebViewCore::handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState) +{ + bool preventDefault = false; + +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* rootLayer = graphicsRootLayer(); + if (rootLayer) + rootLayer->pauseDisplay(true); +#endif + +#if ENABLE(TOUCH_EVENTS) // Android + #define MOTION_EVENT_ACTION_POINTER_DOWN 5 + #define MOTION_EVENT_ACTION_POINTER_UP 6 + + WebCore::TouchEventType type = WebCore::TouchStart; + WebCore::PlatformTouchPoint::State defaultTouchState; + Vector<WebCore::PlatformTouchPoint::State> touchStates(points.size()); + + switch (action) { + case 0: // MotionEvent.ACTION_DOWN + type = WebCore::TouchStart; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; + case 1: // MotionEvent.ACTION_UP + type = WebCore::TouchEnd; + defaultTouchState = WebCore::PlatformTouchPoint::TouchReleased; + break; + case 2: // MotionEvent.ACTION_MOVE + type = WebCore::TouchMove; + defaultTouchState = WebCore::PlatformTouchPoint::TouchMoved; + break; + case 3: // MotionEvent.ACTION_CANCEL + type = WebCore::TouchCancel; + defaultTouchState = WebCore::PlatformTouchPoint::TouchCancelled; + break; + case 5: // MotionEvent.ACTION_POINTER_DOWN + type = WebCore::TouchStart; + defaultTouchState = WebCore::PlatformTouchPoint::TouchStationary; + break; + case 6: // MotionEvent.ACTION_POINTER_UP + type = WebCore::TouchEnd; + defaultTouchState = WebCore::PlatformTouchPoint::TouchStationary; + break; + case 0x100: // WebViewCore.ACTION_LONGPRESS + type = WebCore::TouchLongPress; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; + case 0x200: // WebViewCore.ACTION_DOUBLETAP + type = WebCore::TouchDoubleTap; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; + default: + // We do not support other kinds of touch event inside WebCore + // at the moment. + LOGW("Java passed a touch event type that we do not support in WebCore: %d", action); + return 0; + } + + for (int c = 0; c < static_cast<int>(points.size()); c++) { + points[c].setX(points[c].x() - m_scrollOffsetX); + points[c].setY(points[c].y() - m_scrollOffsetY); + + // Setting the touch state for each point. + // Note: actionIndex will be 0 for all actions that are not ACTION_POINTER_DOWN/UP. + if (action == MOTION_EVENT_ACTION_POINTER_DOWN && c == actionIndex) { + touchStates[c] = WebCore::PlatformTouchPoint::TouchPressed; + } else if (action == MOTION_EVENT_ACTION_POINTER_UP && c == actionIndex) { + touchStates[c] = WebCore::PlatformTouchPoint::TouchReleased; + } else { + touchStates[c] = defaultTouchState; + }; + } + + WebCore::PlatformTouchEvent te(ids, points, type, touchStates, metaState); + preventDefault = m_mainFrame->eventHandler()->handleTouchEvent(te); +#endif + +#if USE(ACCELERATED_COMPOSITING) + if (rootLayer) + rootLayer->pauseDisplay(false); +#endif + return preventDefault; +} + +void WebViewCore::touchUp(int touchGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y) +{ + if (touchGeneration == 0) { + // m_mousePos should be set in getTouchHighlightRects() + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(m_mousePos, false); + node = hitTestResult.innerNode(); + if (node) + frame = node->document()->frame(); + else + frame = 0; + DBG_NAV_LOGD("touch up on (%d, %d), scrollOffset is (%d, %d), node:%p, frame:%p", m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, m_scrollOffsetX, m_scrollOffsetY, node, frame); + } else { + if (m_touchGeneration > touchGeneration) { + DBG_NAV_LOGD("m_touchGeneration=%d > touchGeneration=%d" + " x=%d y=%d", m_touchGeneration, touchGeneration, x, y); + return; // short circuit if a newer touch has been generated + } + // This moves m_mousePos to the correct place, and handleMouseClick uses + // m_mousePos to determine where the click happens. + moveMouse(frame, x, y); + m_lastGeneration = touchGeneration; + } + if (frame && CacheBuilder::validNode(m_mainFrame, frame, 0)) { + frame->loader()->resetMultipleFormSubmissionProtection(); + } + DBG_NAV_LOGD("touchGeneration=%d handleMouseClick frame=%p node=%p" + " x=%d y=%d", touchGeneration, frame, node, x, y); + handleMouseClick(frame, node, false); +} + +// Check for the "x-webkit-soft-keyboard" attribute. If it is there and +// set to hidden, do not show the soft keyboard. Node passed as a parameter +// must not be null. +static bool shouldSuppressKeyboard(const WebCore::Node* node) { + LOG_ASSERT(node, "node passed to shouldSuppressKeyboard cannot be null"); + const NamedNodeMap* attributes = node->attributes(); + if (!attributes) return false; + size_t length = attributes->length(); + for (size_t i = 0; i < length; i++) { + const Attribute* a = attributes->attributeItem(i); + if (a->localName() == "x-webkit-soft-keyboard" && a->value() == "hidden") + return true; + } + return false; +} + +// Common code for both clicking with the trackball and touchUp +// Also used when typing into a non-focused textfield to give the textfield focus, +// in which case, 'fake' is set to true +bool WebViewCore::handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr, bool fake) +{ + bool valid = !framePtr || CacheBuilder::validNode(m_mainFrame, framePtr, nodePtr); + WebFrame* webFrame = WebFrame::getWebFrame(m_mainFrame); + if (valid && nodePtr) { + // Need to special case area tags because an image map could have an area element in the middle + // so when attempting to get the default, the point chosen would be follow the wrong link. + if (nodePtr->hasTagName(WebCore::HTMLNames::areaTag)) { + webFrame->setUserInitiatedAction(true); + nodePtr->dispatchSimulatedClick(0, true, true); + webFrame->setUserInitiatedAction(false); + DBG_NAV_LOG("area"); + return true; + } + } + if (!valid || !framePtr) + framePtr = m_mainFrame; + webFrame->setUserInitiatedAction(true); + WebCore::PlatformMouseEvent mouseDown(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventPressed, 1, false, false, false, false, + WTF::currentTime()); + // ignore the return from as it will return true if the hit point can trigger selection change + framePtr->eventHandler()->handleMousePressEvent(mouseDown); + WebCore::PlatformMouseEvent mouseUp(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventReleased, 1, false, false, false, false, + WTF::currentTime()); + bool handled = framePtr->eventHandler()->handleMouseReleaseEvent(mouseUp); + webFrame->setUserInitiatedAction(false); + + // If the user clicked on a textfield, make the focusController active + // so we show the blinking cursor. + WebCore::Node* focusNode = currentFocus(); + DBG_NAV_LOGD("m_mousePos={%d,%d} focusNode=%p handled=%s", m_mousePos.x(), + m_mousePos.y(), focusNode, handled ? "true" : "false"); + if (focusNode) { + WebCore::RenderObject* renderer = focusNode->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + bool ime = !shouldSuppressKeyboard(focusNode) + && !(static_cast<WebCore::HTMLInputElement*>(focusNode))->readOnly(); + if (ime) { +#if ENABLE(WEB_AUTOFILL) + if (renderer->isTextField()) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(framePtr->page()->editorClient()); + WebAutoFill* autoFill = editorC->getAutoFill(); + autoFill->formFieldFocused(static_cast<HTMLFormControlElement*>(focusNode)); + } +#endif + if (!fake) { + RenderTextControl* rtc + = static_cast<RenderTextControl*> (renderer); + requestKeyboardWithSelection(focusNode, rtc->selectionStart(), + rtc->selectionEnd()); + } + } else if (!fake) { + requestKeyboard(false); + } + } else if (!fake){ + // If the selection is contentEditable, show the keyboard so the + // user can type. Otherwise hide the keyboard because no text + // input is needed. + if (isContentEditable(focusNode)) { + requestKeyboard(true); + } else if (!nodeIsPlugin(focusNode)) { + clearTextEntry(); + } + } + } else if (!fake) { + // There is no focusNode, so the keyboard is not needed. + clearTextEntry(); + } + return handled; +} + +void WebViewCore::popupReply(int index) +{ + if (m_popupReply) { + m_popupReply->replyInt(index); + Release(m_popupReply); + m_popupReply = 0; + } +} + +void WebViewCore::popupReply(const int* array, int count) +{ + if (m_popupReply) { + m_popupReply->replyIntArray(array, count); + Release(m_popupReply); + m_popupReply = 0; + } +} + +void WebViewCore::formDidBlur(const WebCore::Node* node) +{ + // If the blur is on a text input, keep track of the node so we can + // hide the soft keyboard when the new focus is set, if it is not a + // text input. + if (isTextInput(node)) + m_blurringNodePointer = reinterpret_cast<int>(node); +} + +void WebViewCore::focusNodeChanged(const WebCore::Node* newFocus) +{ + if (isTextInput(newFocus)) + m_shouldPaintCaret = true; + else if (m_blurringNodePointer) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_formDidBlur, m_blurringNodePointer); + checkException(env); + m_blurringNodePointer = 0; + } +} + +void WebViewCore::addMessageToConsole(const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceID, int msgLevel) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jMessageStr = wtfStringToJstring(env, message); + jstring jSourceIDStr = wtfStringToJstring(env, sourceID); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_addMessageToConsole, jMessageStr, lineNumber, + jSourceIDStr, msgLevel); + env->DeleteLocalRef(jMessageStr); + env->DeleteLocalRef(jSourceIDStr); + checkException(env); +} + +void WebViewCore::jsAlert(const WTF::String& url, const WTF::String& text) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = wtfStringToJstring(env, text); + jstring jUrlStr = wtfStringToJstring(env, url); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsAlert, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +void WebViewCore::exceededDatabaseQuota(const WTF::String& url, const WTF::String& databaseIdentifier, const unsigned long long currentQuota, unsigned long long estimatedSize) +{ +#if ENABLE(DATABASE) + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jDatabaseIdentifierStr = wtfStringToJstring(env, databaseIdentifier); + jstring jUrlStr = wtfStringToJstring(env, url); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_exceededDatabaseQuota, jUrlStr, + jDatabaseIdentifierStr, currentQuota, estimatedSize); + env->DeleteLocalRef(jDatabaseIdentifierStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); +#endif +} + +void WebViewCore::reachedMaxAppCacheSize(const unsigned long long spaceNeeded) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_reachedMaxAppCacheSize, spaceNeeded); + checkException(env); +#endif +} + +void WebViewCore::populateVisitedLinks(WebCore::PageGroup* group) +{ + m_groupForVisitedLinks = group; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_populateVisitedLinks); + checkException(env); +} + +void WebViewCore::geolocationPermissionsShowPrompt(const WTF::String& origin) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring originString = wtfStringToJstring(env, origin); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_geolocationPermissionsShowPrompt, + originString); + env->DeleteLocalRef(originString); + checkException(env); +} + +void WebViewCore::geolocationPermissionsHidePrompt() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_geolocationPermissionsHidePrompt); + checkException(env); +} + +jobject WebViewCore::getDeviceMotionService() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject object = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_getDeviceMotionService); + checkException(env); + return object; +} + +jobject WebViewCore::getDeviceOrientationService() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject object = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_getDeviceOrientationService); + checkException(env); + return object; +} + +bool WebViewCore::jsConfirm(const WTF::String& url, const WTF::String& text) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = wtfStringToJstring(env, text); + jstring jUrlStr = wtfStringToJstring(env, url); + jboolean result = env->CallBooleanMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsConfirm, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); + return result; +} + +bool WebViewCore::jsPrompt(const WTF::String& url, const WTF::String& text, const WTF::String& defaultValue, WTF::String& result) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + jstring jInputStr = wtfStringToJstring(env, text); + jstring jDefaultStr = wtfStringToJstring(env, defaultValue); + jstring returnVal = static_cast<jstring>(env->CallObjectMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsPrompt, jUrlStr, jInputStr, jDefaultStr)); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jDefaultStr); + checkException(env); + + // If returnVal is null, it means that the user cancelled the dialog. + if (!returnVal) + return false; + + result = jstringToWtfString(env, returnVal); + env->DeleteLocalRef(returnVal); + return true; +} + +bool WebViewCore::jsUnload(const WTF::String& url, const WTF::String& message) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = wtfStringToJstring(env, message); + jstring jUrlStr = wtfStringToJstring(env, url); + jboolean result = env->CallBooleanMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsUnload, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); + return result; +} + +bool WebViewCore::jsInterrupt() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jboolean result = env->CallBooleanMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsInterrupt); + checkException(env); + return result; +} + +AutoJObject +WebViewCore::getJavaObject() +{ + return m_javaGlue->object(JSC::Bindings::getJNIEnv()); +} + +jobject +WebViewCore::getWebViewJavaObject() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + return env->GetObjectField(m_javaGlue->object(env).get(), gWebViewCoreFields.m_webView); +} + +void WebViewCore::updateTextSelection() { + WebCore::Node* focusNode = currentFocus(); + if (!focusNode) + return; + RenderObject* renderer = focusNode->renderer(); + if (!renderer || (!renderer->isTextArea() && !renderer->isTextField())) + return; + RenderTextControl* rtc = static_cast<RenderTextControl*>(renderer); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_updateTextSelection, reinterpret_cast<int>(focusNode), + rtc->selectionStart(), rtc->selectionEnd(), m_textGeneration); + checkException(env); +} + +void WebViewCore::updateTextfield(WebCore::Node* ptr, bool changeToPassword, + const WTF::String& text) +{ + if (m_blockTextfieldUpdates) + return; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (changeToPassword) { + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateTextfield, + (int) ptr, true, 0, m_textGeneration); + checkException(env); + return; + } + jstring string = wtfStringToJstring(env, text); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateTextfield, + (int) ptr, false, string, m_textGeneration); + env->DeleteLocalRef(string); + checkException(env); +} + +void WebViewCore::clearTextEntry() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_clearTextEntry); +} + +void WebViewCore::setBackgroundColor(SkColor c) +{ + WebCore::FrameView* view = m_mainFrame->view(); + if (!view) + return; + + // need (int) cast to find the right constructor + WebCore::Color bcolor((int)SkColorGetR(c), (int)SkColorGetG(c), + (int)SkColorGetB(c), (int)SkColorGetA(c)); + view->setBaseBackgroundColor(bcolor); + + // Background color of 0 indicates we want a transparent background + if (c == 0) + view->setTransparent(true); +} + +jclass WebViewCore::getPluginClass(const WTF::String& libName, const char* className) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + + jstring libString = wtfStringToJstring(env, libName); + jstring classString = env->NewStringUTF(className); + jobject pluginClass = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_getPluginClass, + libString, classString); + checkException(env); + + // cleanup unneeded local JNI references + env->DeleteLocalRef(libString); + env->DeleteLocalRef(classString); + + if (pluginClass != NULL) { + return static_cast<jclass>(pluginClass); + } else { + return NULL; + } +} + +void WebViewCore::showFullScreenPlugin(jobject childView, NPP npp) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = m_javaGlue->object(env); + + env->CallVoidMethod(obj.get(), + m_javaGlue->m_showFullScreenPlugin, childView, (int)npp); + checkException(env); +} + +void WebViewCore::hideFullScreenPlugin() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_hideFullScreenPlugin); + checkException(env); +} + +jobject WebViewCore::createSurface(jobject view) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject result = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_createSurface, view); + checkException(env); + return result; +} + +jobject WebViewCore::addSurface(jobject view, int x, int y, int width, int height) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject result = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_addSurface, + view, x, y, width, height); + checkException(env); + return result; +} + +void WebViewCore::updateSurface(jobject childView, int x, int y, int width, int height) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_updateSurface, childView, + x, y, width, height); + checkException(env); +} + +void WebViewCore::destroySurface(jobject childView) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_destroySurface, childView); + checkException(env); +} + +jobject WebViewCore::getContext() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = m_javaGlue->object(env); + + jobject result = env->CallObjectMethod(obj.get(), m_javaGlue->m_getContext); + checkException(env); + return result; +} + +void WebViewCore::keepScreenOn(bool screenOn) { + if ((screenOn && m_screenOnCounter == 0) || (!screenOn && m_screenOnCounter == 1)) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_keepScreenOn, screenOn); + checkException(env); + } + + // update the counter + if (screenOn) + m_screenOnCounter++; + else if (m_screenOnCounter > 0) + m_screenOnCounter--; +} + +bool WebViewCore::validNodeAndBounds(Frame* frame, Node* node, + const IntRect& originalAbsoluteBounds) +{ + bool valid = CacheBuilder::validNode(m_mainFrame, frame, node); + if (!valid) + return false; + RenderObject* renderer = node->renderer(); + if (!renderer) + return false; + IntRect absBounds = node->hasTagName(HTMLNames::areaTag) + ? CacheBuilder::getAreaRect(static_cast<HTMLAreaElement*>(node)) + : renderer->absoluteBoundingBoxRect(); + return absBounds == originalAbsoluteBounds; +} + +void WebViewCore::showRect(int left, int top, int width, int height, + int contentWidth, int contentHeight, float xPercentInDoc, + float xPercentInView, float yPercentInDoc, float yPercentInView) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_showRect, + left, top, width, height, contentWidth, contentHeight, + xPercentInDoc, xPercentInView, yPercentInDoc, yPercentInView); + checkException(env); +} + +void WebViewCore::centerFitRect(int x, int y, int width, int height) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_centerFitRect, x, y, width, height); + checkException(env); +} + + +void WebViewCore::setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_setScrollbarModes, + horizontalMode, verticalMode); + checkException(env); +} + +void WebViewCore::notifyWebAppCanBeInstalled() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_setInstallableWebApp); + checkException(env); +} + +#if ENABLE(VIDEO) +void WebViewCore::enterFullscreenForVideoLayer(int layerId, const WTF::String& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_enterFullscreenForVideoLayer, layerId, jUrlStr); + checkException(env); +} +#endif + +void WebViewCore::setWebTextViewAutoFillable(int queryId, const string16& previewSummary) +{ +#if ENABLE(WEB_AUTOFILL) + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring preview = env->NewString(previewSummary.data(), previewSummary.length()); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_setWebTextViewAutoFillable, queryId, preview); + env->DeleteLocalRef(preview); +#endif +} + +bool WebViewCore::drawIsPaused() const +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + return env->GetBooleanField(m_javaGlue->object(env).get(), + gWebViewCoreFields.m_drawIsPaused); +} + +#if USE(CHROME_NETWORK_STACK) +void WebViewCore::setWebRequestContextUserAgent() +{ + // We cannot create a WebRequestContext, because we might not know it this is a private tab or not yet + if (m_webRequestContext) + m_webRequestContext->setUserAgent(WebFrame::getWebFrame(m_mainFrame)->userAgentForURL(0)); // URL not used +} + +void WebViewCore::setWebRequestContextCacheMode(int cacheMode) +{ + m_cacheMode = cacheMode; + // We cannot create a WebRequestContext, because we might not know it this is a private tab or not yet + if (!m_webRequestContext) + return; + + m_webRequestContext->setCacheMode(cacheMode); +} + +WebRequestContext* WebViewCore::webRequestContext() +{ + if (!m_webRequestContext) { + Settings* settings = mainFrame()->settings(); + m_webRequestContext = new WebRequestContext(settings && settings->privateBrowsingEnabled()); + setWebRequestContextUserAgent(); + setWebRequestContextCacheMode(m_cacheMode); + } + return m_webRequestContext.get(); +} +#endif + +void WebViewCore::scrollRenderLayer(int layer, const SkRect& rect) +{ +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* root = graphicsRootLayer(); + if (!root) + return; + + LayerAndroid* layerAndroid = root->platformLayer(); + if (!layerAndroid) + return; + + LayerAndroid* target = layerAndroid->findById(layer); + if (!target) + return; + + RenderLayer* owner = target->owningLayer(); + if (!owner) + return; + + if (owner->stackingContext()) + owner->scrollToOffset(rect.fLeft, rect.fTop, true, false); +#endif +} + +//---------------------------------------------------------------------- +// Native JNI methods +//---------------------------------------------------------------------- +static void RevealSelection(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->revealSelection(); +} + +static jstring RequestLabel(JNIEnv *env, jobject obj, int framePointer, + int nodePointer) +{ + return wtfStringToJstring(env, GET_NATIVE_VIEW(env, obj)->requestLabel( + (WebCore::Frame*) framePointer, (WebCore::Node*) nodePointer)); +} + +static void ClearContent(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->clearContent(); +} + +static void UpdateFrameCacheIfLoading(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->updateFrameCacheIfLoading(); +} + +static void SetSize(JNIEnv *env, jobject obj, jint width, jint height, + jint textWrapWidth, jfloat scale, jint screenWidth, jint screenHeight, + jint anchorX, jint anchorY, jboolean ignoreHeight) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOGV("webviewcore::nativeSetSize(%u %u)\n viewImpl: %p", (unsigned)width, (unsigned)height, viewImpl); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSetSize"); + viewImpl->setSizeScreenWidthAndScale(width, height, textWrapWidth, scale, + screenWidth, screenHeight, anchorX, anchorY, ignoreHeight); +} + +static void SetScrollOffset(JNIEnv *env, jobject obj, jint gen, jboolean sendScrollEvent, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); + + viewImpl->setScrollOffset(gen, sendScrollEvent, x, y); +} + +static void SetGlobalBounds(JNIEnv *env, jobject obj, jint x, jint y, jint h, + jint v) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); + + viewImpl->setGlobalBounds(x, y, h, v); +} + +static jboolean Key(JNIEnv *env, jobject obj, jint keyCode, jint unichar, + jint repeatCount, jboolean isShift, jboolean isAlt, jboolean isSym, + jboolean isDown) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + return GET_NATIVE_VIEW(env, obj)->key(PlatformKeyboardEvent(keyCode, + unichar, repeatCount, isDown, isShift, isAlt, isSym)); +} + +static void Click(JNIEnv *env, jobject obj, int framePtr, int nodePtr, jboolean fake) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in Click"); + + viewImpl->click(reinterpret_cast<WebCore::Frame*>(framePtr), + reinterpret_cast<WebCore::Node*>(nodePtr), fake); +} + +static void ContentInvalidateAll(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->contentInvalidateAll(); +} + +static void DeleteSelection(JNIEnv *env, jobject obj, jint start, jint end, + jint textGeneration) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->deleteSelection(start, end, textGeneration); +} + +static void SetSelection(JNIEnv *env, jobject obj, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->setSelection(start, end); +} + +static jstring ModifySelection(JNIEnv *env, jobject obj, jint direction, jint granularity) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + String selectionString = viewImpl->modifySelection(direction, granularity); + return wtfStringToJstring(env, selectionString); +} + +static void ReplaceTextfieldText(JNIEnv *env, jobject obj, + jint oldStart, jint oldEnd, jstring replace, jint start, jint end, + jint textGeneration) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + WTF::String webcoreString = jstringToWtfString(env, replace); + viewImpl->replaceTextfieldText(oldStart, + oldEnd, webcoreString, start, end, textGeneration); +} + +static void PassToJs(JNIEnv *env, jobject obj, + jint generation, jstring currentText, jint keyCode, + jint keyValue, jboolean down, jboolean cap, jboolean fn, jboolean sym) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WTF::String current = jstringToWtfString(env, currentText); + GET_NATIVE_VIEW(env, obj)->passToJs(generation, current, + PlatformKeyboardEvent(keyCode, keyValue, 0, down, cap, fn, sym)); +} + +static void ScrollFocusedTextInput(JNIEnv *env, jobject obj, jfloat xPercent, + jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->scrollFocusedTextInput(xPercent, y); +} + +static void SetFocusControllerActive(JNIEnv *env, jobject obj, jboolean active) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeSetFocusControllerActive()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSetFocusControllerActive"); + viewImpl->setFocusControllerActive(active); +} + +static void SaveDocumentState(JNIEnv *env, jobject obj, jint frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeSaveDocumentState()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSaveDocumentState"); + viewImpl->saveDocumentState((WebCore::Frame*) frame); +} + +void WebViewCore::addVisitedLink(const UChar* string, int length) +{ + if (m_groupForVisitedLinks) + m_groupForVisitedLinks->addVisitedLink(string, length); +} + +static jint UpdateLayers(JNIEnv *env, jobject obj, jobject region) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + BaseLayerAndroid* result = viewImpl->createBaseLayer(); + SkRegion* nativeRegion = GraphicsJNI::getNativeRegion(env, region); + if (result) { + SkIRect bounds; + LayerAndroid* root = static_cast<LayerAndroid*>(result->getChild(0)); + if (root) { + root->bounds().roundOut(&bounds); + nativeRegion->setRect(bounds); + } + } + return reinterpret_cast<jint>(result); +} + +static jint RecordContent(JNIEnv *env, jobject obj, jobject region, jobject pt) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + SkRegion* nativeRegion = GraphicsJNI::getNativeRegion(env, region); + SkIPoint nativePt; + BaseLayerAndroid* result = viewImpl->recordContent(nativeRegion, &nativePt); + GraphicsJNI::ipoint_to_jpoint(nativePt, env, pt); + return reinterpret_cast<jint>(result); +} + +static void SplitContent(JNIEnv *env, jobject obj, jint content) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->splitContent(reinterpret_cast<PictureSet*>(content)); +} + +static void SendListBoxChoice(JNIEnv* env, jobject obj, jint choice) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSendListBoxChoice"); + viewImpl->popupReply(choice); +} + +// Set aside a predetermined amount of space in which to place the listbox +// choices, to avoid unnecessary allocations. +// The size here is arbitrary. We want the size to be at least as great as the +// number of items in the average multiple-select listbox. +#define PREPARED_LISTBOX_STORAGE 10 + +static void SendListBoxChoices(JNIEnv* env, jobject obj, jbooleanArray jArray, + jint size) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSendListBoxChoices"); + jboolean* ptrArray = env->GetBooleanArrayElements(jArray, 0); + SkAutoSTMalloc<PREPARED_LISTBOX_STORAGE, int> storage(size); + int* array = storage.get(); + int count = 0; + for (int i = 0; i < size; i++) { + if (ptrArray[i]) { + array[count++] = i; + } + } + env->ReleaseBooleanArrayElements(jArray, ptrArray, JNI_ABORT); + viewImpl->popupReply(array, count); +} + +static jstring FindAddress(JNIEnv *env, jobject obj, jstring addr, + jboolean caseInsensitive) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + if (!addr) + return 0; + int length = env->GetStringLength(addr); + if (!length) + return 0; + const jchar* addrChars = env->GetStringChars(addr, 0); + int start, end; + bool success = CacheBuilder::FindAddress(addrChars, length, + &start, &end, caseInsensitive) == CacheBuilder::FOUND_COMPLETE; + jstring ret = 0; + if (success) + ret = env->NewString(addrChars + start, end - start); + env->ReleaseStringChars(addr, addrChars); + return ret; +} + +static jboolean HandleTouchEvent(JNIEnv *env, jobject obj, jint action, jintArray idArray, + jintArray xArray, jintArray yArray, + jint count, jint actionIndex, jint metaState) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + jint* ptrIdArray = env->GetIntArrayElements(idArray, 0); + jint* ptrXArray = env->GetIntArrayElements(xArray, 0); + jint* ptrYArray = env->GetIntArrayElements(yArray, 0); + Vector<int> ids(count); + Vector<IntPoint> points(count); + for (int c = 0; c < count; c++) { + ids[c] = ptrIdArray[c]; + points[c].setX(ptrXArray[c]); + points[c].setY(ptrYArray[c]); + } + env->ReleaseIntArrayElements(idArray, ptrIdArray, JNI_ABORT); + env->ReleaseIntArrayElements(xArray, ptrXArray, JNI_ABORT); + env->ReleaseIntArrayElements(yArray, ptrYArray, JNI_ABORT); + + return viewImpl->handleTouchEvent(action, ids, points, actionIndex, metaState); +} + +static void TouchUp(JNIEnv *env, jobject obj, jint touchGeneration, + jint frame, jint node, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->touchUp(touchGeneration, + (WebCore::Frame*) frame, (WebCore::Node*) node, x, y); +} + +static jstring RetrieveHref(JNIEnv *env, jobject obj, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + WTF::String result = viewImpl->retrieveHref(x, y); + if (!result.isEmpty()) + return wtfStringToJstring(env, result); + return 0; +} + +static jstring RetrieveAnchorText(JNIEnv *env, jobject obj, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + WTF::String result = viewImpl->retrieveAnchorText(x, y); + if (!result.isEmpty()) + return wtfStringToJstring(env, result); + return 0; +} + +static jstring RetrieveImageSource(JNIEnv *env, jobject obj, jint x, jint y) +{ + WTF::String result = GET_NATIVE_VIEW(env, obj)->retrieveImageSource(x, y); + return !result.isEmpty() ? wtfStringToJstring(env, result) : 0; +} + +static void StopPaintingCaret(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setShouldPaintCaret(false); +} + +static void MoveFocus(JNIEnv *env, jobject obj, jint framePtr, jint nodePtr) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveFocus((WebCore::Frame*) framePtr, (WebCore::Node*) nodePtr); +} + +static void MoveMouse(JNIEnv *env, jobject obj, jint frame, + jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveMouse((WebCore::Frame*) frame, x, y); +} + +static void MoveMouseIfLatest(JNIEnv *env, jobject obj, jint moveGeneration, + jint frame, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveMouseIfLatest(moveGeneration, + (WebCore::Frame*) frame, x, y); +} + +static void UpdateFrameCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->updateFrameCache(); +} + +static jint GetContentMinPrefWidth(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + WebCore::Frame* frame = viewImpl->mainFrame(); + if (frame) { + WebCore::Document* document = frame->document(); + if (document) { + WebCore::RenderObject* renderer = document->renderer(); + if (renderer && renderer->isRenderView()) { + return renderer->minPreferredLogicalWidth(); + } + } + } + return 0; +} + +static void SetViewportSettingsFromNative(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + WebCore::Settings* s = viewImpl->mainFrame()->page()->settings(); + if (!s) + return; + +#ifdef ANDROID_META_SUPPORT + env->SetIntField(obj, gWebViewCoreFields.m_viewportWidth, s->viewportWidth()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportHeight, s->viewportHeight()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportInitialScale, s->viewportInitialScale()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportMinimumScale, s->viewportMinimumScale()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportMaximumScale, s->viewportMaximumScale()); + env->SetBooleanField(obj, gWebViewCoreFields.m_viewportUserScalable, s->viewportUserScalable()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportDensityDpi, s->viewportTargetDensityDpi()); +#endif +} + +static void SetBackgroundColor(JNIEnv *env, jobject obj, jint color) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->setBackgroundColor((SkColor) color); +} + +static void DumpDomTree(JNIEnv *env, jobject obj, jboolean useFile) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->dumpDomTree(useFile); +} + +static void DumpRenderTree(JNIEnv *env, jobject obj, jboolean useFile) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->dumpRenderTree(useFile); +} + +static void DumpNavTree(JNIEnv *env, jobject obj) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->dumpNavTree(); +} + +static void DumpV8Counters(JNIEnv*, jobject) +{ +#if USE(V8) +#ifdef ANDROID_INSTRUMENT + V8Counters::dumpCounters(); +#endif +#endif +} + +static void SetJsFlags(JNIEnv *env, jobject obj, jstring flags) +{ +#if USE(V8) + WTF::String flagsString = jstringToWtfString(env, flags); + WTF::CString utf8String = flagsString.utf8(); + WebCore::ScriptController::setFlags(utf8String.data(), utf8String.length()); +#endif +} + + +// Called from the Java side to set a new quota for the origin or new appcache +// max size in response to a notification that the original quota was exceeded or +// that the appcache has reached its maximum size. +static void SetNewStorageLimit(JNIEnv* env, jobject obj, jlong quota) { +#if ENABLE(DATABASE) || ENABLE(OFFLINE_WEB_APPLICATIONS) + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + Frame* frame = viewImpl->mainFrame(); + + // The main thread is blocked awaiting this response, so now we can wake it + // up. + ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(frame->page()->chrome()->client()); + chromeC->wakeUpMainThreadWithNewQuota(quota); +#endif +} + +// Called from Java to provide a Geolocation permission state for the specified origin. +static void GeolocationPermissionsProvide(JNIEnv* env, jobject obj, jstring origin, jboolean allow, jboolean remember) { + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + Frame* frame = viewImpl->mainFrame(); + + ChromeClientAndroid* chromeClient = static_cast<ChromeClientAndroid*>(frame->page()->chrome()->client()); + chromeClient->provideGeolocationPermissions(jstringToWtfString(env, origin), allow, remember); +} + +static void RegisterURLSchemeAsLocal(JNIEnv* env, jobject obj, jstring scheme) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebCore::SchemeRegistry::registerURLSchemeAsLocal(jstringToWtfString(env, scheme)); +} + +static bool FocusBoundsChanged(JNIEnv* env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->focusBoundsChanged(); +} + +static void Pause(JNIEnv* env, jobject obj) +{ + // This is called for the foreground tab when the browser is put to the + // background (and also for any tab when it is put to the background of the + // browser). The browser can only be killed by the system when it is in the + // background, so saving the Geolocation permission state now ensures that + // is maintained when the browser is killed. + ChromeClient* chromeClient = GET_NATIVE_VIEW(env, obj)->mainFrame()->page()->chrome()->client(); + ChromeClientAndroid* chromeClientAndroid = static_cast<ChromeClientAndroid*>(chromeClient); + chromeClientAndroid->storeGeolocationPermissions(); + + Frame* mainFrame = GET_NATIVE_VIEW(env, obj)->mainFrame(); + for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext()) { + Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); + if (geolocation) + geolocation->suspend(); + } + + GET_NATIVE_VIEW(env, obj)->deviceMotionAndOrientationManager()->maybeSuspendClients(); + + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kPause_ANPLifecycleAction; + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); + + GET_NATIVE_VIEW(env, obj)->setIsPaused(true); +} + +static void Resume(JNIEnv* env, jobject obj) +{ + Frame* mainFrame = GET_NATIVE_VIEW(env, obj)->mainFrame(); + for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext()) { + Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); + if (geolocation) + geolocation->resume(); + } + + GET_NATIVE_VIEW(env, obj)->deviceMotionAndOrientationManager()->maybeResumeClients(); + + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kResume_ANPLifecycleAction; + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); + + GET_NATIVE_VIEW(env, obj)->setIsPaused(false); +} + +static void FreeMemory(JNIEnv* env, jobject obj) +{ + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kFreeMemory_ANPLifecycleAction; + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); +} + +static void ProvideVisitedHistory(JNIEnv *env, jobject obj, jobject hist) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + jobjectArray array = static_cast<jobjectArray>(hist); + + jsize len = env->GetArrayLength(array); + for (jsize i = 0; i < len; i++) { + jstring item = static_cast<jstring>(env->GetObjectArrayElement(array, i)); + const UChar* str = static_cast<const UChar*>(env->GetStringChars(item, 0)); + jsize len = env->GetStringLength(item); + viewImpl->addVisitedLink(str, len); + env->ReleaseStringChars(item, str); + env->DeleteLocalRef(item); + } +} + +// Notification from the UI thread that the plugin's full-screen surface has been discarded +static void FullScreenPluginHidden(JNIEnv* env, jobject obj, jint npp) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + PluginWidgetAndroid* plugin = viewImpl->getPluginWidget((NPP)npp); + if (plugin) + plugin->exitFullScreen(false); +} + +static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj) +{ + int L, T, R, B; + GraphicsJNI::get_jrect(env, obj, &L, &T, &R, &B); + return WebCore::IntRect(L, T, R - L, B - T); +} + +static bool ValidNodeAndBounds(JNIEnv *env, jobject obj, int frame, int node, + jobject rect) +{ + IntRect nativeRect = jrect_to_webrect(env, rect); + return GET_NATIVE_VIEW(env, obj)->validNodeAndBounds( + reinterpret_cast<Frame*>(frame), + reinterpret_cast<Node*>(node), nativeRect); +} + +static jobject GetTouchHighlightRects(JNIEnv* env, jobject obj, jint x, jint y, jint slop) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + if (!viewImpl) + return 0; + Vector<IntRect> rects = viewImpl->getTouchHighlightRects(x, y, slop); + if (rects.isEmpty()) + return 0; + + jclass arrayClass = env->FindClass("java/util/ArrayList"); + LOG_ASSERT(arrayClass, "Could not find java/util/ArrayList"); + jmethodID init = env->GetMethodID(arrayClass, "<init>", "(I)V"); + LOG_ASSERT(init, "Could not find constructor for ArrayList"); + jobject array = env->NewObject(arrayClass, init, rects.size()); + LOG_ASSERT(array, "Could not create a new ArrayList"); + jmethodID add = env->GetMethodID(arrayClass, "add", "(Ljava/lang/Object;)Z"); + LOG_ASSERT(add, "Could not find add method on ArrayList"); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find android/graphics/Rect"); + jmethodID rectinit = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + LOG_ASSERT(rectinit, "Could not find init method on Rect"); + + for (size_t i = 0; i < rects.size(); i++) { + jobject rect = env->NewObject(rectClass, rectinit, rects[i].x(), + rects[i].y(), rects[i].right(), rects[i].bottom()); + if (rect) { + env->CallBooleanMethod(array, add, rect); + env->DeleteLocalRef(rect); + } + } + + env->DeleteLocalRef(rectClass); + env->DeleteLocalRef(arrayClass); + return array; +} + +static void AutoFillForm(JNIEnv* env, jobject obj, jint queryId) +{ +#if ENABLE(WEB_AUTOFILL) + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + if (!viewImpl) + return; + + WebCore::Frame* frame = viewImpl->mainFrame(); + if (frame) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(frame->page()->editorClient()); + WebAutoFill* autoFill = editorC->getAutoFill(); + autoFill->fillFormFields(queryId); + } +#endif +} + +static void ScrollRenderLayer(JNIEnv* env, jobject obj, jint layer, jobject jRect) +{ + SkRect rect; + GraphicsJNI::jrect_to_rect(env, jRect, &rect); + GET_NATIVE_VIEW(env, obj)->scrollRenderLayer(layer, rect); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gJavaWebViewCoreMethods[] = { + { "nativeClearContent", "()V", + (void*) ClearContent }, + { "nativeFocusBoundsChanged", "()Z", + (void*) FocusBoundsChanged } , + { "nativeKey", "(IIIZZZZ)Z", + (void*) Key }, + { "nativeClick", "(IIZ)V", + (void*) Click }, + { "nativeContentInvalidateAll", "()V", + (void*) ContentInvalidateAll }, + { "nativeSendListBoxChoices", "([ZI)V", + (void*) SendListBoxChoices }, + { "nativeSendListBoxChoice", "(I)V", + (void*) SendListBoxChoice }, + { "nativeSetSize", "(IIIFIIIIZ)V", + (void*) SetSize }, + { "nativeSetScrollOffset", "(IZII)V", + (void*) SetScrollOffset }, + { "nativeSetGlobalBounds", "(IIII)V", + (void*) SetGlobalBounds }, + { "nativeSetSelection", "(II)V", + (void*) SetSelection } , + { "nativeModifySelection", "(II)Ljava/lang/String;", + (void*) ModifySelection }, + { "nativeDeleteSelection", "(III)V", + (void*) DeleteSelection } , + { "nativeReplaceTextfieldText", "(IILjava/lang/String;III)V", + (void*) ReplaceTextfieldText } , + { "nativeMoveFocus", "(II)V", + (void*) MoveFocus }, + { "nativeMoveMouse", "(III)V", + (void*) MoveMouse }, + { "nativeMoveMouseIfLatest", "(IIII)V", + (void*) MoveMouseIfLatest }, + { "passToJs", "(ILjava/lang/String;IIZZZZ)V", + (void*) PassToJs }, + { "nativeScrollFocusedTextInput", "(FI)V", + (void*) ScrollFocusedTextInput }, + { "nativeSetFocusControllerActive", "(Z)V", + (void*) SetFocusControllerActive }, + { "nativeSaveDocumentState", "(I)V", + (void*) SaveDocumentState }, + { "nativeFindAddress", "(Ljava/lang/String;Z)Ljava/lang/String;", + (void*) FindAddress }, + { "nativeHandleTouchEvent", "(I[I[I[IIII)Z", + (void*) HandleTouchEvent }, + { "nativeTouchUp", "(IIIII)V", + (void*) TouchUp }, + { "nativeRetrieveHref", "(II)Ljava/lang/String;", + (void*) RetrieveHref }, + { "nativeRetrieveAnchorText", "(II)Ljava/lang/String;", + (void*) RetrieveAnchorText }, + { "nativeRetrieveImageSource", "(II)Ljava/lang/String;", + (void*) RetrieveImageSource }, + { "nativeStopPaintingCaret", "()V", + (void*) StopPaintingCaret }, + { "nativeUpdateFrameCache", "()V", + (void*) UpdateFrameCache }, + { "nativeGetContentMinPrefWidth", "()I", + (void*) GetContentMinPrefWidth }, + { "nativeUpdateLayers", "(Landroid/graphics/Region;)I", + (void*) UpdateLayers }, + { "nativeRecordContent", "(Landroid/graphics/Region;Landroid/graphics/Point;)I", + (void*) RecordContent }, + { "setViewportSettingsFromNative", "()V", + (void*) SetViewportSettingsFromNative }, + { "nativeSplitContent", "(I)V", + (void*) SplitContent }, + { "nativeSetBackgroundColor", "(I)V", + (void*) SetBackgroundColor }, + { "nativeRegisterURLSchemeAsLocal", "(Ljava/lang/String;)V", + (void*) RegisterURLSchemeAsLocal }, + { "nativeDumpDomTree", "(Z)V", + (void*) DumpDomTree }, + { "nativeDumpRenderTree", "(Z)V", + (void*) DumpRenderTree }, + { "nativeDumpNavTree", "()V", + (void*) DumpNavTree }, + { "nativeDumpV8Counters", "()V", + (void*) DumpV8Counters }, + { "nativeSetNewStorageLimit", "(J)V", + (void*) SetNewStorageLimit }, + { "nativeGeolocationPermissionsProvide", "(Ljava/lang/String;ZZ)V", + (void*) GeolocationPermissionsProvide }, + { "nativePause", "()V", (void*) Pause }, + { "nativeResume", "()V", (void*) Resume }, + { "nativeFreeMemory", "()V", (void*) FreeMemory }, + { "nativeSetJsFlags", "(Ljava/lang/String;)V", (void*) SetJsFlags }, + { "nativeRequestLabel", "(II)Ljava/lang/String;", + (void*) RequestLabel }, + { "nativeRevealSelection", "()V", (void*) RevealSelection }, + { "nativeUpdateFrameCacheIfLoading", "()V", + (void*) UpdateFrameCacheIfLoading }, + { "nativeProvideVisitedHistory", "([Ljava/lang/String;)V", + (void*) ProvideVisitedHistory }, + { "nativeFullScreenPluginHidden", "(I)V", + (void*) FullScreenPluginHidden }, + { "nativeValidNodeAndBounds", "(IILandroid/graphics/Rect;)Z", + (void*) ValidNodeAndBounds }, + { "nativeGetTouchHighlightRects", "(III)Ljava/util/ArrayList;", + (void*) GetTouchHighlightRects }, + { "nativeAutoFillForm", "(I)V", + (void*) AutoFillForm }, + { "nativeScrollLayer", "(ILandroid/graphics/Rect;)V", + (void*) ScrollRenderLayer }, +}; + +int registerWebViewCore(JNIEnv* env) +{ + jclass widget = env->FindClass("android/webkit/WebViewCore"); + LOG_ASSERT(widget, + "Unable to find class android/webkit/WebViewCore"); + gWebViewCoreFields.m_nativeClass = env->GetFieldID(widget, "mNativeClass", + "I"); + LOG_ASSERT(gWebViewCoreFields.m_nativeClass, + "Unable to find android/webkit/WebViewCore.mNativeClass"); + gWebViewCoreFields.m_viewportWidth = env->GetFieldID(widget, + "mViewportWidth", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportWidth, + "Unable to find android/webkit/WebViewCore.mViewportWidth"); + gWebViewCoreFields.m_viewportHeight = env->GetFieldID(widget, + "mViewportHeight", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportHeight, + "Unable to find android/webkit/WebViewCore.mViewportHeight"); + gWebViewCoreFields.m_viewportInitialScale = env->GetFieldID(widget, + "mViewportInitialScale", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportInitialScale, + "Unable to find android/webkit/WebViewCore.mViewportInitialScale"); + gWebViewCoreFields.m_viewportMinimumScale = env->GetFieldID(widget, + "mViewportMinimumScale", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportMinimumScale, + "Unable to find android/webkit/WebViewCore.mViewportMinimumScale"); + gWebViewCoreFields.m_viewportMaximumScale = env->GetFieldID(widget, + "mViewportMaximumScale", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportMaximumScale, + "Unable to find android/webkit/WebViewCore.mViewportMaximumScale"); + gWebViewCoreFields.m_viewportUserScalable = env->GetFieldID(widget, + "mViewportUserScalable", "Z"); + LOG_ASSERT(gWebViewCoreFields.m_viewportUserScalable, + "Unable to find android/webkit/WebViewCore.mViewportUserScalable"); + gWebViewCoreFields.m_viewportDensityDpi = env->GetFieldID(widget, + "mViewportDensityDpi", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportDensityDpi, + "Unable to find android/webkit/WebViewCore.mViewportDensityDpi"); + gWebViewCoreFields.m_webView = env->GetFieldID(widget, + "mWebView", "Landroid/webkit/WebView;"); + LOG_ASSERT(gWebViewCoreFields.m_webView, + "Unable to find android/webkit/WebViewCore.mWebView"); + gWebViewCoreFields.m_drawIsPaused = env->GetFieldID(widget, + "mDrawIsPaused", "Z"); + LOG_ASSERT(gWebViewCoreFields.m_drawIsPaused, + "Unable to find android/webkit/WebViewCore.mDrawIsPaused"); + gWebViewCoreFields.m_lowMemoryUsageMb = env->GetFieldID(widget, "mLowMemoryUsageThresholdMb", "I"); + gWebViewCoreFields.m_highMemoryUsageMb = env->GetFieldID(widget, "mHighMemoryUsageThresholdMb", "I"); + gWebViewCoreFields.m_highUsageDeltaMb = env->GetFieldID(widget, "mHighUsageDeltaMb", "I"); + + gWebViewCoreStaticMethods.m_isSupportedMediaMimeType = + env->GetStaticMethodID(widget, "isSupportedMediaMimeType", "(Ljava/lang/String;)Z"); + LOG_FATAL_IF(!gWebViewCoreStaticMethods.m_isSupportedMediaMimeType, + "Could not find static method isSupportedMediaMimeType from WebViewCore"); + + env->DeleteLocalRef(widget); + + return jniRegisterNativeMethods(env, "android/webkit/WebViewCore", + gJavaWebViewCoreMethods, NELEM(gJavaWebViewCoreMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebViewCore.h b/Source/WebKit/android/jni/WebViewCore.h new file mode 100644 index 0000000..8d8f303 --- /dev/null +++ b/Source/WebKit/android/jni/WebViewCore.h @@ -0,0 +1,714 @@ +/* + * 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. + */ + +#ifndef WEBVIEWCORE_H +#define WEBVIEWCORE_H + +#include "CacheBuilder.h" +#include "CachedHistory.h" +#include "DeviceMotionAndOrientationManager.h" +#include "DOMSelection.h" +#include "FileChooser.h" +#include "PictureSet.h" +#include "PlatformGraphicsContext.h" +#include "SkColor.h" +#include "SkTDArray.h" +#include "SkRegion.h" +#include "Timer.h" +#include "WebCoreRefObject.h" +#include "WebCoreJni.h" +#include "WebRequestContext.h" +#include "android_npapi.h" + +#include <jni.h> +#include <ui/KeycodeLabels.h> +#include <ui/PixelFormat.h> + +namespace WebCore { + class Color; + class FrameView; + class HTMLAnchorElement; + class HTMLElement; + class HTMLImageElement; + class HTMLSelectElement; + class RenderPart; + class RenderText; + class Node; + class PlatformKeyboardEvent; + class QualifiedName; + class RenderTextControl; + class ScrollView; + class TimerBase; + class PageGroup; +} + +#if USE(ACCELERATED_COMPOSITING) +namespace WebCore { + class GraphicsLayerAndroid; +} +#endif + +namespace WebCore { + class BaseLayerAndroid; +} + +struct PluginWidgetAndroid; +class SkPicture; +class SkIRect; + +namespace android { + + enum Direction { + DIRECTION_BACKWARD = 0, + DIRECTION_FORWARD = 1 + }; + + enum NavigationAxis { + AXIS_CHARACTER = 0, + AXIS_WORD = 1, + AXIS_SENTENCE = 2, + AXIS_HEADING = 3, + AXIS_SIBLING = 4, + AXIS_PARENT_FIRST_CHILD = 5, + AXIS_DOCUMENT = 6 + }; + + class CachedFrame; + class CachedNode; + class CachedRoot; + class ListBoxReply; + + class WebCoreReply : public WebCoreRefObject { + public: + virtual ~WebCoreReply() {} + + virtual void replyInt(int value) { + SkDEBUGF(("WebCoreReply::replyInt(%d) not handled\n", value)); + } + + virtual void replyIntArray(const int* array, int count) { + SkDEBUGF(("WebCoreReply::replyIntArray() not handled\n")); + } + // add more replyFoo signatures as needed + }; + + // one instance of WebViewCore per page for calling into Java's WebViewCore + class WebViewCore : public WebCoreRefObject { + public: + /** + * Initialize the native WebViewCore with a JNI environment, a Java + * WebViewCore object and the main frame. + */ + WebViewCore(JNIEnv* env, jobject javaView, WebCore::Frame* mainframe); + ~WebViewCore(); + + // helper function + static WebViewCore* getWebViewCore(const WebCore::FrameView* view); + static WebViewCore* getWebViewCore(const WebCore::ScrollView* view); + + // Followings are called from native WebCore to Java + + /** + * Notification that a form was blurred. Pass a message to hide the + * keyboard if it was showing for that Node. + * @param Node The Node that blurred. + */ + void formDidBlur(const WebCore::Node*); + void focusNodeChanged(const WebCore::Node*); + + /** + * Scroll to an absolute position. + * @param x The x coordinate. + * @param y The y coordinate. + * @param animate If it is true, animate to the new scroll position + * + * This method calls Java to trigger a gradual scroll event. + */ + void scrollTo(int x, int y, bool animate = false); + + /** + * Record the invalid rectangle + */ + void contentInvalidate(const WebCore::IntRect &rect); + void contentInvalidateAll(); + + /** + * Satisfy any outstanding invalidates, so that the current state + * of the DOM is drawn. + */ + void contentDraw(); + + /** + * copy the layers to the UI side + */ + void layersDraw(); + +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* graphicsRootLayer() const; +#endif + + /** Invalidate the view/screen, NOT the content/DOM, but expressed in + * content/DOM coordinates (i.e. they need to eventually be scaled, + * by webview into view.java coordinates + */ + void viewInvalidate(const WebCore::IntRect& rect); + + /** + * Invalidate part of the content that may be offscreen at the moment + */ + void offInvalidate(const WebCore::IntRect &rect); + + /** + * Called by webcore when the progress indicator is done + * used to rebuild and display any changes in focus + */ + void notifyProgressFinished(); + + /** + * Notify the view that WebCore did its first layout. + */ + void didFirstLayout(); + + /** + * Notify the view to update the viewport. + */ + void updateViewport(); + + /** + * Notify the view to restore the screen width, which in turn restores + * the scale. Also restore the scale for the text wrap. + */ + void restoreScale(float scale, float textWrapScale); + + /** + * Tell the java side to update the focused textfield + * @param pointer Pointer to the node for the input field. + * @param changeToPassword If true, we are changing the textfield to + * a password field, and ignore the String + * @param text If changeToPassword is false, this is the new text that + * should go into the textfield. + */ + void updateTextfield(WebCore::Node* pointer, + bool changeToPassword, const WTF::String& text); + + /** + * Tell the java side to update the current selection in the focused + * textfield to the WebTextView. This function finds the currently + * focused textinput, and passes its selection to java. + * If there is no focus, or it is not a text input, this does nothing. + */ + void updateTextSelection(); + + void clearTextEntry(); + // JavaScript support + void jsAlert(const WTF::String& url, const WTF::String& text); + bool jsConfirm(const WTF::String& url, const WTF::String& text); + bool jsPrompt(const WTF::String& url, const WTF::String& message, + const WTF::String& defaultValue, WTF::String& result); + bool jsUnload(const WTF::String& url, const WTF::String& message); + bool jsInterrupt(); + + /** + * Tell the Java side that the origin has exceeded its database quota. + * @param url The URL of the page that caused the quota overflow + * @param databaseIdentifier the id of the database that caused the + * quota overflow. + * @param currentQuota The current quota for the origin + * @param estimatedSize The estimated size of the database + */ + void exceededDatabaseQuota(const WTF::String& url, + const WTF::String& databaseIdentifier, + const unsigned long long currentQuota, + const unsigned long long estimatedSize); + + /** + * Tell the Java side that the appcache has exceeded its max size. + * @param spaceNeeded is the amount of disk space that would be needed + * in order for the last appcache operation to succeed. + */ + void reachedMaxAppCacheSize(const unsigned long long spaceNeeded); + + /** + * Set up the PageGroup's idea of which links have been visited, + * with the browser history. + * @param group the object to deliver the links to. + */ + void populateVisitedLinks(WebCore::PageGroup*); + + /** + * Instruct the browser to show a Geolocation permission prompt for the + * specified origin. + * @param origin The origin of the frame requesting Geolocation + * permissions. + */ + void geolocationPermissionsShowPrompt(const WTF::String& origin); + /** + * Instruct the browser to hide the Geolocation permission prompt. + */ + void geolocationPermissionsHidePrompt(); + + jobject getDeviceMotionService(); + jobject getDeviceOrientationService(); + + void addMessageToConsole(const String& message, unsigned int lineNumber, const String& sourceID, int msgLevel); + + /** + * Tell the Java side of the scrollbar mode + */ + void setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode); + + // + // Followings support calls from Java to native WebCore + // + + WTF::String retrieveHref(int x, int y); + WTF::String retrieveAnchorText(int x, int y); + WTF::String retrieveImageSource(int x, int y); + WTF::String requestLabel(WebCore::Frame* , WebCore::Node* ); + + // If the focus is a textfield (<input>), textarea, or contentEditable, + // scroll the selection on screen (if necessary). + void revealSelection(); + // Create a single picture to represent the drawn DOM (used by navcache) + void recordPicture(SkPicture* picture); + + void moveFocus(WebCore::Frame* frame, WebCore::Node* node); + void moveMouse(WebCore::Frame* frame, int x, int y); + void moveMouseIfLatest(int moveGeneration, + WebCore::Frame* frame, int x, int y); + + // set the scroll amount that webview.java is currently showing + void setScrollOffset(int moveGeneration, bool sendScrollEvent, int dx, int dy); + + void setGlobalBounds(int x, int y, int h, int v); + + void setSizeScreenWidthAndScale(int width, int height, int screenWidth, + float scale, int realScreenWidth, int screenHeight, int anchorX, + int anchorY, bool ignoreHeight); + + /** + * Handle key events from Java. + * @return Whether keyCode was handled by this class. + */ + bool key(const WebCore::PlatformKeyboardEvent& event); + + /** + * Handle (trackball) click event / dpad center press from Java. + * Also used when typing into an unfocused textfield, in which case 'fake' + * will be true. + */ + void click(WebCore::Frame* frame, WebCore::Node* node, bool fake); + + /** + * Handle touch event + */ + bool handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState); + + /** + * Handle motionUp event from the UI thread (called touchUp in the + * WebCore thread). + * @param touchGeneration Generation number for touches so we can ignore + * touches when a newer one has been generated. + * @param frame Pointer to Frame containing the node that was touched. + * @param node Pointer to Node that was touched. + * @param x x-position of the touch. + * @param y y-position of the touch. + */ + void touchUp(int touchGeneration, WebCore::Frame* frame, + WebCore::Node* node, int x, int y); + + /** + * Sets the index of the label from a popup + */ + void popupReply(int index); + void popupReply(const int* array, int count); + + /** + * Delete text from start to end in the focused textfield. + * If start == end, set the selection, but perform no deletion. + * If there is no focus, silently fail. + * If start and end are out of order, swap them. + */ + void deleteSelection(int start, int end, int textGeneration); + + /** + * Set the selection of the currently focused textfield to (start, end). + * If start and end are out of order, swap them. + */ + void setSelection(int start, int end); + + /** + * Modifies the current selection. + * + * Note: Accessibility support. + * + * direction - The direction in which to alter the selection. + * granularity - The granularity of the selection modification. + * + * returns - The selected HTML as a string. This is not a well formed + * HTML, rather the selection annotated with the tags of all + * intermediary elements it crosses. + */ + String modifySelection(const int direction, const int granularity); + + /** + * Moves the selection to the given node in a given frame i.e. selects that node. + * + * Note: Accessibility support. + * + * frame - The frame in which to select is the node to be selected. + * node - The node to be selected. + * + * returns - The selected HTML as a string. This is not a well formed + * HTML, rather the selection annotated with the tags of all + * intermediary elements it crosses. + */ + String moveSelection(WebCore::Frame* frame, WebCore::Node* node); + + /** + * In the currently focused textfield, replace the characters from oldStart to oldEnd + * (if oldStart == oldEnd, this will be an insert at that position) with replace, + * and set the selection to (start, end). + */ + void replaceTextfieldText(int oldStart, + int oldEnd, const WTF::String& replace, int start, int end, + int textGeneration); + void passToJs(int generation, + const WTF::String& , const WebCore::PlatformKeyboardEvent& ); + /** + * Scroll the focused textfield to (x, y) in document space + */ + void scrollFocusedTextInput(float x, int y); + /** + * Set the FocusController's active and focused states, so that + * the caret will draw (true) or not. + */ + void setFocusControllerActive(bool active); + + void saveDocumentState(WebCore::Frame* frame); + + void addVisitedLink(const UChar*, int); + + // TODO: I don't like this hack but I need to access the java object in + // order to send it as a parameter to java + AutoJObject getJavaObject(); + + // Return the parent WebView Java object associated with this + // WebViewCore. + jobject getWebViewJavaObject(); + + void setBackgroundColor(SkColor c); + void updateFrameCache(); + void updateCacheOnNodeChange(); + void dumpDomTree(bool); + void dumpRenderTree(bool); + void dumpNavTree(); + + /* We maintain a list of active plugins. The list is edited by the + pluginview itself. The list is used to service invals to the plugin + pageflipping bitmap. + */ + void addPlugin(PluginWidgetAndroid*); + void removePlugin(PluginWidgetAndroid*); + // returns true if the pluginwidgit is in our active list + bool isPlugin(PluginWidgetAndroid*) const; + void invalPlugin(PluginWidgetAndroid*); + void drawPlugins(); + + // send the current screen size/zoom to all of the plugins in our list + void sendPluginVisibleScreen(); + + // send onLoad event to plugins who are descendents of the given frame + void notifyPluginsOnFrameLoad(const Frame*); + + // gets a rect representing the current on-screen portion of the document + void getVisibleScreen(ANPRectI&); + + // send this event to all of the plugins in our list + void sendPluginEvent(const ANPEvent&); + + // lookup the plugin widget struct given an NPP + PluginWidgetAndroid* getPluginWidget(NPP npp); + + // return the cursorNode if it is a plugin + Node* cursorNodeIsPlugin(); + + // Notify the Java side whether it needs to pass down the touch events + void needTouchEvents(bool); + + void requestKeyboardWithSelection(const WebCore::Node*, int selStart, int selEnd); + // Notify the Java side that webkit is requesting a keyboard + void requestKeyboard(bool showKeyboard); + + // Generates a class loader that contains classes from the plugin's apk + jclass getPluginClass(const WTF::String& libName, const char* className); + + // Creates a full screen surface for a plugin + void showFullScreenPlugin(jobject webkitPlugin, NPP npp); + + // Instructs the UI thread to discard the plugin's full-screen surface + void hideFullScreenPlugin(); + + // Creates a childView for the plugin but does not attach to the view hierarchy + jobject createSurface(jobject view); + + // Adds the plugin's view (aka surface) to the view hierarchy + jobject addSurface(jobject view, int x, int y, int width, int height); + + // Updates a Surface coordinates and dimensions for a plugin + void updateSurface(jobject childView, int x, int y, int width, int height); + + // Destroys a SurfaceView for a plugin + void destroySurface(jobject childView); + + // Returns the context (android.content.Context) of the WebView + jobject getContext(); + + // Manages requests to keep the screen on while the WebView is visible + void keepScreenOn(bool screenOn); + + bool validNodeAndBounds(Frame* , Node* , const IntRect& ); + + // Make the rect (left, top, width, height) visible. If it can be fully + // fit, center it on the screen. Otherwise make sure the point specified + // by (left + xPercentInDoc * width, top + yPercentInDoc * height) + // pinned at the screen position (xPercentInView, yPercentInView). + void showRect(int left, int top, int width, int height, int contentWidth, + int contentHeight, float xPercentInDoc, float xPercentInView, + float yPercentInDoc, float yPercentInView); + + // Scale the rect (x, y, width, height) to make it just fit and centered + // in the current view. + void centerFitRect(int x, int y, int width, int height); + + // return a list of rects matching the touch point (x, y) with the slop + Vector<IntRect> getTouchHighlightRects(int x, int y, int slop); + + // Open a file chooser for selecting a file to upload + void openFileChooser(PassRefPtr<WebCore::FileChooser> ); + + // reset the picture set to empty + void clearContent(); + + bool focusBoundsChanged(); + + // record the inval area, and the picture size + BaseLayerAndroid* recordContent(SkRegion* , SkIPoint* ); + + // This creates a new BaseLayerAndroid by copying the current m_content + // and doing a copy of the layers. The layers' content may be updated + // as we are calling layersSync(). + BaseLayerAndroid* createBaseLayer(); + + int textWrapWidth() const { return m_textWrapWidth; } + float scale() const { return m_scale; } + float textWrapScale() const { return m_screenWidth * m_scale / m_textWrapWidth; } + WebCore::Frame* mainFrame() const { return m_mainFrame; } + void updateCursorBounds(const CachedRoot* root, + const CachedFrame* cachedFrame, const CachedNode* cachedNode); + void updateFrameCacheIfLoading(); + + // utility to split slow parts of the picture set + void splitContent(PictureSet*); + + void notifyWebAppCanBeInstalled(); + +#if ENABLE(VIDEO) + void enterFullscreenForVideoLayer(int layerId, const WTF::String& url); +#endif + + void setWebTextViewAutoFillable(int queryId, const string16& previewSummary); + + DeviceMotionAndOrientationManager* deviceMotionAndOrientationManager() { return &m_deviceMotionAndOrientationManager; } + + void listBoxRequest(WebCoreReply* reply, const uint16_t** labels, + size_t count, const int enabled[], size_t enabledCount, + bool multiple, const int selected[], size_t selectedCountOrSelection); + bool shouldPaintCaret() { return m_shouldPaintCaret; } + void setShouldPaintCaret(bool should) { m_shouldPaintCaret = should; } + bool isPaused() const { return m_isPaused; } + void setIsPaused(bool isPaused) { m_isPaused = isPaused; } + bool drawIsPaused() const; + // The actual content (without title bar) size in doc coordinate + int screenWidth() const { return m_screenWidth; } + int screenHeight() const { return m_screenHeight; } +#if USE(CHROME_NETWORK_STACK) + void setWebRequestContextUserAgent(); + void setWebRequestContextCacheMode(int mode); + WebRequestContext* webRequestContext(); +#endif + // Attempts to scroll the layer to the x,y coordinates of rect. The + // layer is the id of the LayerAndroid. + void scrollRenderLayer(int layer, const SkRect& rect); + // call only from webkit thread (like add/remove), return true if inst + // is still alive + static bool isInstance(WebViewCore*); + // if there exists at least one WebViewCore instance then we return the + // application context, otherwise NULL is returned. + static jobject getApplicationContext(); + // Check whether a media mimeType is supported in Android media framework. + static bool isSupportedMediaMimeType(const WTF::String& mimeType); + + // these members are shared with webview.cpp + static Mutex gFrameCacheMutex; + CachedRoot* m_frameCacheKit; // nav data being built by webcore + SkPicture* m_navPictureKit; + int m_moveGeneration; // copy of state in WebViewNative triggered by move + int m_touchGeneration; // copy of state in WebViewNative triggered by touch + int m_lastGeneration; // last action using up to date cache + bool m_updatedFrameCache; + bool m_findIsUp; + bool m_hasCursorBounds; + WebCore::IntRect m_cursorBounds; + WebCore::IntRect m_cursorHitBounds; + void* m_cursorFrame; + IntPoint m_cursorLocation; + void* m_cursorNode; + static Mutex gCursorBoundsMutex; + // These two fields go together: we use the mutex to protect access to + // m_buttons, so that we, and webview.cpp can look/modify the m_buttons + // field safely from our respective threads + static Mutex gButtonMutex; + WTF::Vector<Container> m_buttons; + // end of shared members + + // internal functions + private: + CacheBuilder& cacheBuilder(); + WebCore::Node* currentFocus(); + // Compare the new set of buttons to the old one. All of the new + // buttons either replace our old ones or should be added to our list. + // Then check the old buttons to see if any are no longer needed. + void updateButtonList(WTF::Vector<Container>* buttons); + void reset(bool fromConstructor); + // Create a set of pictures to represent the drawn DOM, driven by + // the invalidated region and the time required to draw (used to draw) + void recordPictureSet(PictureSet* master); + + friend class ListBoxReply; + struct JavaGlue; + struct JavaGlue* m_javaGlue; + WebCore::Frame* m_mainFrame; + WebCoreReply* m_popupReply; + WebCore::Node* m_lastFocused; + WebCore::IntRect m_lastFocusedBounds; + int m_blurringNodePointer; + int m_lastFocusedSelStart; + int m_lastFocusedSelEnd; + PictureSet m_content; // the set of pictures to draw + SkRegion m_addInval; // the accumulated inval region (not yet drawn) + SkRegion m_rebuildInval; // the accumulated region for rebuilt pictures + // Used in passToJS to avoid updating the UI text field until after the + // key event has been processed. + bool m_blockTextfieldUpdates; + bool m_focusBoundsChanged; + bool m_skipContentDraw; + // Passed in with key events to know when they were generated. Store it + // with the cache so that we can ignore stale text changes. + int m_textGeneration; + CachedRoot* m_temp; + SkPicture* m_tempPict; + int m_maxXScroll; + int m_maxYScroll; + int m_scrollOffsetX; // webview.java's current scroll in X + int m_scrollOffsetY; // webview.java's current scroll in Y + WebCore::IntPoint m_mousePos; + bool m_frameCacheOutOfDate; + bool m_progressDone; + int m_lastPassed; + int m_lastVelocity; + CachedHistory m_history; + int m_screenWidth; // width of the visible rect in document coordinates + int m_screenHeight;// height of the visible rect in document coordinates + int m_textWrapWidth; + float m_scale; + unsigned m_domtree_version; + bool m_check_domtree_version; + PageGroup* m_groupForVisitedLinks; + bool m_isPaused; + int m_cacheMode; + bool m_shouldPaintCaret; + + SkTDArray<PluginWidgetAndroid*> m_plugins; + WebCore::Timer<WebViewCore> m_pluginInvalTimer; + void pluginInvalTimerFired(WebCore::Timer<WebViewCore>*) { + this->drawPlugins(); + } + int m_screenOnCounter; + + void doMaxScroll(CacheBuilder::Direction dir); + SkPicture* rebuildPicture(const SkIRect& inval); + void rebuildPictureSet(PictureSet* ); + void sendNotifyProgressFinished(); + /* + * Handle a mouse click, either from a touch or trackball press. + * @param frame Pointer to the Frame containing the node that was clicked on. + * @param node Pointer to the Node that was clicked on. + * @param fake This is a fake mouse click, used to put a textfield into focus. Do not + * open the IME. + */ + bool handleMouseClick(WebCore::Frame*, WebCore::Node*, bool fake); + WebCore::HTMLAnchorElement* retrieveAnchorElement(int x, int y); + WebCore::HTMLElement* retrieveElement(int x, int y, + const WebCore::QualifiedName& ); + WebCore::HTMLImageElement* retrieveImageElement(int x, int y); + // below are members responsible for accessibility support + String modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int granularity); + String modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int granularity); + Text* traverseNextContentTextNode(Node* fromNode, Node* toNode ,int direction); + bool isVisible(Node* node); + bool isHeading(Node* node); + String formatMarkup(DOMSelection* selection); + void selectAt(int x, int y); + Node* m_currentNodeDomNavigationAxis; + void scrollNodeIntoView(Frame* frame, Node* node); + bool isContentTextNode(Node* node); + Node* getIntermediaryInputElement(Node* fromNode, Node* toNode, int direction); + bool isContentInputElement(Node* node); + bool isDescendantOf(Node* parent, Node* node); + void advanceAnchorNode(DOMSelection* selection, int direction, String& markup, bool ignoreFirstNode, ExceptionCode& ec); + Node* getNextAnchorNode(Node* anchorNode, bool skipFirstHack, int direction); + Node* getImplicitBoundaryNode(Node* node, unsigned offset, int direction); + +#if ENABLE(TOUCH_EVENTS) + bool m_forwardingTouchEvents; +#endif +#if DEBUG_NAV_UI + uint32_t m_now; +#endif + DeviceMotionAndOrientationManager m_deviceMotionAndOrientationManager; +#if USE(CHROME_NETWORK_STACK) + scoped_refptr<WebRequestContext> m_webRequestContext; +#endif + + // called from constructor, to add this to a global list + static void addInstance(WebViewCore*); + // called from destructor, to remove this from a global list + static void removeInstance(WebViewCore*); + }; + +} // namespace android + +#endif // WEBVIEWCORE_H diff --git a/Source/WebKit/android/nav/CacheBuilder.cpp b/Source/WebKit/android/nav/CacheBuilder.cpp new file mode 100644 index 0000000..dc10f21 --- /dev/null +++ b/Source/WebKit/android/nav/CacheBuilder.cpp @@ -0,0 +1,3201 @@ +/* + * 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. + */ + +#include "CachedPrefix.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "ColumnInfo.h" +#include "Document.h" +#include "EventListener.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +//#include "GraphicsContext.h" +#include "HTMLAreaElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMapElement.h" +#include "HTMLNames.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "HTMLTextAreaElement.h" +#include "InlineTextBox.h" +#include "KURL.h" +#include "LayerAndroid.h" +#include "PluginView.h" +#include "RegisteredEventListener.h" +#include "RenderImage.h" +#include "RenderInline.h" +#include "RenderLayerBacking.h" +#include "RenderListBox.h" +#include "RenderSkinCombo.h" +#include "RenderTextControl.h" +#include "RenderView.h" +#include "RenderWidget.h" +#include "SkCanvas.h" +#include "SkPoint.h" +#include "Text.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreViewBridge.h" +#include "Widget.h" +#include <wtf/unicode/Unicode.h> + +#ifdef DUMP_NAV_CACHE_USING_PRINTF + FILE* gNavCacheLogFile = NULL; + android::Mutex gWriteLogMutex; +#endif + +#include "CacheBuilder.h" + +#define MINIMUM_FOCUSABLE_WIDTH 3 +#define MINIMUM_FOCUSABLE_HEIGHT 3 +#define MAXIMUM_FOCUS_RING_COUNT 32 + +namespace android { + +CacheBuilder* CacheBuilder::Builder(Frame* frame) { + return &((FrameLoaderClientAndroid*) frame->loader()->client())->getCacheBuilder(); +} + +Frame* CacheBuilder::FrameAnd(CacheBuilder* cacheBuilder) { + FrameLoaderClientAndroid* loader = (FrameLoaderClientAndroid*) + ((char*) cacheBuilder - OFFSETOF(FrameLoaderClientAndroid, m_cacheBuilder)); + return loader->getFrame(); +} + +Frame* CacheBuilder::FrameAnd(const CacheBuilder* cacheBuilder) { + FrameLoaderClientAndroid* loader = (FrameLoaderClientAndroid*) + ((char*) cacheBuilder - OFFSETOF(FrameLoaderClientAndroid, m_cacheBuilder)); + return loader->getFrame(); +} + +CacheBuilder::LayerTracker::~LayerTracker() { + // Check for a stacking context to prevent a crash in layers without a + // parent. + if (mRenderLayer && mRenderLayer->stackingContext()) + // Restore the scroll position of the layer. Does not affect layers + // without overflow scroll as the layer will not be scrolled. + mRenderLayer->scrollToOffset(mScroll.x(), mScroll.y(), false, false); +} + +#if DUMP_NAV_CACHE + +static bool hasEventListener(Node* node, const AtomicString& eventType) { + if (!node->isElementNode()) + return false; + Element* element = static_cast<Element*>(node); + EventListener* listener = element->getAttributeEventListener(eventType); + return 0 != listener; +} + +#define DEBUG_BUFFER_SIZE 256 +#define DEBUG_WRAP_SIZE 150 +#define DEBUG_WRAP_MAX 170 + +Frame* CacheBuilder::Debug::frameAnd() const { + CacheBuilder* nav = (CacheBuilder*) ((char*) this - OFFSETOF(CacheBuilder, mDebug)); + return CacheBuilder::FrameAnd(nav); +} + +void CacheBuilder::Debug::attr(const AtomicString& name, const AtomicString& value) { + if (name.isNull() || name.isEmpty() || value.isNull() || value.isEmpty()) + return; + uChar(name.characters(), name.length(), false); + print("="); + wideString(value.characters(), value.length(), false); + print(" "); +} + +void CacheBuilder::Debug::comma(const char* str) { + print(str); + print(", "); +} + +void CacheBuilder::Debug::flush() { + int len; + do { + int limit = mIndex; + if (limit < DEBUG_WRAP_SIZE) + return; + if (limit < DEBUG_WRAP_MAX) + len = limit; + else { + limit = DEBUG_WRAP_MAX; + len = DEBUG_WRAP_SIZE; + while (len < limit) { + char test = mBuffer[len]; + if (test < '/' || (test > '9' && test < 'A') || (test > 'Z' && test < 'a') || test > 'z') + break; + len++; + } + while (len > 0 && mBuffer[len - 1] == '\\') + len--; + while (mBuffer[len] == '"') + len++; + } + const char* prefix = mPrefix; + if (prefix[0] == '\"') { + // see if we're inside a quote + int quoteCount = 0; + for (int index = 0; index < len; index++) { + if (mBuffer[index] == '\\') { + index++; + continue; + } + if (mBuffer[index] == '\n') { + quoteCount = 0; + continue; + } + if (mBuffer[index] == '"') + quoteCount++; + } + if ((quoteCount & 1) == 0) + prefix = "\n"; + } + DUMP_NAV_LOGD("%.*s", len, mBuffer); + int copy = mIndex - len; + strcpy(mBuffer, prefix); + memcpy(&mBuffer[strlen(prefix)], &mBuffer[len], copy); + mIndex = strlen(prefix) + copy; + } while (true); +} + +void CacheBuilder::Debug::frameName(char*& namePtr, const char* max) const { + if (namePtr >= max) + return; + Frame* frame = frameAnd(); + Frame* parent = frame->tree()->parent(); + if (parent) + Builder(parent)->mDebug.frameName(namePtr, max); + const AtomicString& name = frame->tree()->name(); + if (name.length() == 0) + return; + unsigned index = 0; + if (name.startsWith(AtomicString("opener"))) + index = 6; + for (; index < name.length(); index++) { + char ch = name[index]; + if (ch <= ' ') + ch = '_'; + if (WTF::isASCIIAlphanumeric(ch) || ch == '_') + *namePtr++ = ch; + } +} + +void CacheBuilder::Debug::frames() { + Frame* frame = frameAnd(); + Document* doc = frame->document(); + if (doc == NULL) + return; + bool top = frame->tree()->parent() == NULL; + if (top) { +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gWriteLogMutex.lock(); + ASSERT(gNavCacheLogFile == NULL); + gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); +#endif + groups(); + } + Frame* child = frame->tree()->firstChild(); + bool hasChild = child != NULL; + if (top && hasChild) + DUMP_NAV_LOGD("\nnamespace TEST_SPACE {\n\n"); + while (child) { + Builder(child)->mDebug.frames(); + child = child->tree()->nextSibling(); + } + if (hasChild) { + child = frame->tree()->firstChild(); + while (child) { + char childName[256]; + char* childNamePtr = childName; + Builder(child)->mDebug.frameName(childNamePtr, childNamePtr + sizeof(childName) - 1); + *childNamePtr = '\0'; + if (child == frame->tree()->firstChild()) + DUMP_NAV_LOGD("DebugTestFrameGroup TEST%s_GROUP[] = {\n", childName); + Frame* next = child->tree()->nextSibling(); + Document* doc = child->document(); + if (doc != NULL) { + RenderObject* renderer = doc->renderer(); + if (renderer != NULL) { + RenderLayer* layer = renderer->enclosingLayer(); + if (layer != NULL) { + DUMP_NAV_LOGD("{ "); + DUMP_NAV_LOGD("TEST%s_RECTS, ", childName); + DUMP_NAV_LOGD("TEST%s_RECT_COUNT, ", childName); + DUMP_NAV_LOGD("TEST%s_RECTPARTS, ", childName); + DUMP_NAV_LOGD("TEST%s_BOUNDS,\n", childName); + DUMP_NAV_LOGD("TEST%s_WIDTH, ", childName); + DUMP_NAV_LOGD("TEST%s_HEIGHT,\n", childName); + DUMP_NAV_LOGD("0, 0, 0, 0,\n"); + DUMP_NAV_LOGD("TEST%s_FOCUS, ", childName); + Frame* grandChild = child->tree()->firstChild(); + if (grandChild) { + char grandChildName[256]; + char* grandChildNamePtr = grandChildName; + Builder(grandChild)->mDebug.frameName(grandChildNamePtr, + grandChildNamePtr + sizeof(grandChildName) - 1); + *grandChildNamePtr = '\0'; + DUMP_NAV_LOGD("TEST%s_GROUP, ", grandChildName); + DUMP_NAV_LOGD("sizeof(TEST%s_GROUP) / sizeof(DebugTestFrameGroup), ", grandChildName); + } else + DUMP_NAV_LOGD("NULL, 0, "); + DUMP_NAV_LOGD("\"%s\"\n", childName); + DUMP_NAV_LOGD("}%c\n", next ? ',' : ' '); + } + } + } + child = next; + } + DUMP_NAV_LOGD("};\n"); + } + if (top) { + if (hasChild) + DUMP_NAV_LOGD("\n} // end of namespace\n\n"); + char name[256]; + char* frameNamePtr = name; + frameName(frameNamePtr, frameNamePtr + sizeof(name) - 1); + *frameNamePtr = '\0'; + DUMP_NAV_LOGD("DebugTestFrameGroup TEST%s_GROUP = {\n", name); + DUMP_NAV_LOGD("TEST%s_RECTS, ", name); + DUMP_NAV_LOGD("TEST%s_RECT_COUNT, ", name); + DUMP_NAV_LOGD("TEST%s_RECTPARTS, ", name); + DUMP_NAV_LOGD("TEST%s_BOUNDS,\n", name); + DUMP_NAV_LOGD("TEST%s_WIDTH, ", name); + DUMP_NAV_LOGD("TEST%s_HEIGHT,\n", name); + DUMP_NAV_LOGD("TEST%s_MAX_H, ", name); + DUMP_NAV_LOGD("TEST%s_MIN_H, ", name); + DUMP_NAV_LOGD("TEST%s_MAX_V, ", name); + DUMP_NAV_LOGD("TEST%s_MIN_V,\n", name); + DUMP_NAV_LOGD("TEST%s_FOCUS, ", name); + if (hasChild) { + child = frame->tree()->firstChild(); + char childName[256]; + char* childNamePtr = childName; + Builder(child)->mDebug.frameName(childNamePtr, childNamePtr + sizeof(childName) - 1); + *childNamePtr = '\0'; + DUMP_NAV_LOGD("TEST_SPACE::TEST%s_GROUP, ", childName); + DUMP_NAV_LOGD("sizeof(TEST_SPACE::TEST%s_GROUP) / sizeof(DebugTestFrameGroup), \n" ,childName); + } else + DUMP_NAV_LOGD("NULL, 0, "); + DUMP_NAV_LOGD("\"%s\"\n", name); + DUMP_NAV_LOGD("};\n"); +#ifdef DUMP_NAV_CACHE_USING_PRINTF + if (gNavCacheLogFile) + fclose(gNavCacheLogFile); + gNavCacheLogFile = NULL; + gWriteLogMutex.unlock(); +#endif + } +} + +void CacheBuilder::Debug::init(char* buffer, size_t size) { + mBuffer = buffer; + mBufferSize = size; + mIndex = 0; + mPrefix = ""; +} + +void CacheBuilder::Debug::groups() { + Frame* frame = frameAnd(); + Frame* child = frame->tree()->firstChild(); + bool hasChild = child != NULL; + if (frame->tree()->parent() == NULL && hasChild) + DUMP_NAV_LOGD("namespace TEST_SPACE {\n\n"); + while (child) { + Builder(child)->mDebug.groups(); + child = child->tree()->nextSibling(); + } + if (frame->tree()->parent() == NULL && hasChild) + DUMP_NAV_LOGD("\n} // end of namespace\n\n"); + Document* doc = frame->document(); + char name[256]; + char* frameNamePtr = name; + frameName(frameNamePtr, frameNamePtr + sizeof(name) - 1); + *frameNamePtr = '\0'; + if (doc == NULL) { + DUMP_NAV_LOGD("// %s has no document\n", name); + return; + } + RenderObject* renderer = doc->renderer(); + if (renderer == NULL) { + DUMP_NAV_LOGD("// %s has no renderer\n", name); + return; + } + RenderLayer* layer = renderer->enclosingLayer(); + if (layer == NULL) { + DUMP_NAV_LOGD("// %s has no enclosingLayer\n", name); + return; + } + Node* node = doc; + Node* focus = doc->focusedNode(); + bool atLeastOne = false; + do { + if ((atLeastOne |= isFocusable(node)) != false) + break; + } while ((node = node->traverseNextNode()) != NULL); + int focusIndex = -1; + if (atLeastOne == false) { + DUMP_NAV_LOGD("static DebugTestNode TEST%s_RECTS[] = {\n" + "{{0, 0, 0, 0}, \"\", 0, -1, \"\", {0, 0, 0, 0}, false, 0}\n" + "};\n\n", name); + DUMP_NAV_LOGD("static int TEST%s_RECT_COUNT = 1;" + " // no focusable nodes\n", name); + DUMP_NAV_LOGD("#define TEST%s_RECTPARTS NULL\n", name); + } else { + node = doc; + int count = 1; + DUMP_NAV_LOGD("static DebugTestNode TEST%s_RECTS[] = {\n", name); + do { + String properties; + if (hasEventListener(node, eventNames().clickEvent)) + properties.append("ONCLICK | "); + if (hasEventListener(node, eventNames().mousedownEvent)) + properties.append("MOUSEDOWN | "); + if (hasEventListener(node, eventNames().mouseupEvent)) + properties.append("MOUSEUP | "); + if (hasEventListener(node, eventNames().mouseoverEvent)) + properties.append("MOUSEOVER | "); + if (hasEventListener(node, eventNames().mouseoutEvent)) + properties.append("MOUSEOUT | "); + if (hasEventListener(node, eventNames().keydownEvent)) + properties.append("KEYDOWN | "); + if (hasEventListener(node, eventNames().keyupEvent)) + properties.append("KEYUP | "); + if (CacheBuilder::HasFrame(node)) + properties.append("FRAME | "); + if (focus == node) { + properties.append("FOCUS | "); + focusIndex = count; + } + if (node->isKeyboardFocusable(NULL)) + properties.append("KEYBOARD_FOCUSABLE | "); + if (node->isMouseFocusable()) + properties.append("MOUSE_FOCUSABLE | "); + if (node->isFocusable()) + properties.append("SIMPLE_FOCUSABLE | "); + if (properties.isEmpty()) + properties.append("0"); + else + properties.truncate(properties.length() - 3); + IntRect rect = node->getRect(); + if (node->hasTagName(HTMLNames::areaTag)) + rect = getAreaRect(static_cast<HTMLAreaElement*>(node)); + char buffer[DEBUG_BUFFER_SIZE]; + memset(buffer, 0, sizeof(buffer)); + mBuffer = buffer; + mBufferSize = sizeof(buffer); + mPrefix = "\"\n\""; + mIndex = snprintf(buffer, sizeof(buffer), "{{%d, %d, %d, %d}, ", rect.x(), rect.y(), + rect.width(), rect.height()); + localName(node); + uChar(properties.characters(), properties.length(), false); + print(", "); + int parentIndex = ParentIndex(node, count, node->parentNode()); + char scratch[256]; + snprintf(scratch, sizeof(scratch), "%d", parentIndex); + comma(scratch); + Element* element = static_cast<Element*>(node); + if (node->isElementNode() && element->hasID()) + wideString(element->getIdAttribute()); + else if (node->isTextNode()) { + #if 01 // set to one to abbreviate text that can be omitted from the address detection code + if (rect.isEmpty() && node->textContent().length() > 100) { + wideString(node->textContent().characters(), 100, false); + snprintf(scratch, sizeof(scratch), "/* + %d bytes */", + node->textContent().length() - 100); + print(scratch); + } else + #endif + wideString(node->textContent().characters(), node->textContent().length(), true); + } else if (node->hasTagName(HTMLNames::aTag) || + node->hasTagName(HTMLNames::areaTag)) + { + HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(node); + wideString(anchor->href()); + } else if (node->hasTagName(HTMLNames::imgTag)) { + HTMLImageElement* image = static_cast<HTMLImageElement*>(node); + wideString(image->src()); + } else + print("\"\""); + RenderObject* renderer = node->renderer(); + int tabindex = node->isElementNode() ? node->tabIndex() : 0; + RenderLayer* layer = 0; + if (renderer) { + const IntRect& absB = renderer->absoluteBoundingBoxRect(); + bool hasLayer = renderer->hasLayer(); + layer = hasLayer ? toRenderBoxModelObject(renderer)->layer() : 0; + snprintf(scratch, sizeof(scratch), ", {%d, %d, %d, %d}, %s" + ", %d, %s, %s},", + absB.x(), absB.y(), absB.width(), absB.height(), + renderer->hasOverflowClip() ? "true" : "false", tabindex, + hasLayer ? "true" : "false", + hasLayer && layer->isComposited() ? "true" : "false"); + // TODO: add renderer->style()->visibility() + print(scratch); + } else + print(", {0, 0, 0, 0}, false, 0},"); + + flush(); + snprintf(scratch, sizeof(scratch), "// %d: ", count); + mPrefix = "\n// "; + print(scratch); + //print(renderer ? renderer->information().ascii() : "NO_RENDER_INFO"); + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + NamedNodeMap* attrs = element->attributes(); + unsigned length = attrs->length(); + if (length > 0) { + newLine(); + print("// attr: "); + for (unsigned i = 0; i < length; i++) { + Attribute* a = attrs->attributeItem(i); + attr(a->localName(), a->value()); + } + } + } + if (renderer) { + RenderStyle* style = renderer->style(); + snprintf(scratch, sizeof(scratch), "// renderStyle:" + " visibility=%s hasBackGround=%d" + " tapHighlightColor().alpha()=0x%02x" + " isTransparent()=%s", + style->visibility() == HIDDEN ? "HIDDEN" : "VISIBLE", + renderer->hasBackground(), style->tapHighlightColor().alpha(), + renderer->isTransparent() ? "true" : "false"); + newLine(); + print(scratch); + RenderBlock* renderBlock = static_cast<RenderBlock*>(renderer); + if (renderer->isRenderBlock() && renderBlock->hasColumns()) { + const RenderBox* box = static_cast<RenderBox*>(renderer); + const IntRect& oRect = box->visibleOverflowRect(); + snprintf(scratch, sizeof(scratch), "// renderBlock:" + " columnCount=%d columnGap=%d direction=%d" + " hasOverflowClip=%d overflow=(%d,%d,w=%d,h=%d)", + renderBlock->columnInfo()->columnCount(), renderBlock->columnGap(), + renderBlock->style()->direction(), renderer->hasOverflowClip(), + oRect.x(), oRect.y(), oRect.width(), oRect.height()); + newLine(); + print(scratch); + } + } + #if USE(ACCELERATED_COMPOSITING) + if (renderer && renderer->hasLayer()) { + RenderLayer* layer = toRenderBoxModelObject(renderer)->layer(); + RenderLayerBacking* back = layer->backing(); + GraphicsLayer* grLayer = back ? back->graphicsLayer() : 0; + LayerAndroid* aLayer = grLayer ? grLayer->platformLayer() : 0; + const SkPicture* pict = aLayer ? aLayer->picture() : 0; + const IntRect& r = renderer->absoluteBoundingBoxRect(); + snprintf(scratch, sizeof(scratch), "// layer:%p back:%p" + " gLayer:%p aLayer:%p pict:%p r:(%d,%d,w=%d,h=%d)", + layer, back, grLayer, aLayer, pict, r.x(), r.y(), + r.width(), r.height()); + newLine(); + print(scratch); + } + #endif + count++; + newLine(); + } while ((node = node->traverseNextNode()) != NULL); + DUMP_NAV_LOGD("}; // focusables = %d\n", count - 1); + DUMP_NAV_LOGD("\n"); + DUMP_NAV_LOGD("static int TEST%s_RECT_COUNT = %d;\n\n", name, count - 1); + // look for rects with multiple parts + node = doc; + count = 1; + bool hasRectParts = false; + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); + do { + IntRect rect; + bool _isFocusable = isFocusable(node) || (node->isTextNode() + && node->getRect().isEmpty() == false + ); + int nodeIndex = count++; + if (_isFocusable == false) + continue; + RenderObject* renderer = node->renderer(); + if (renderer == NULL) + continue; + WTF::Vector<IntRect> rects; + IntRect clipBounds = IntRect(0, 0, INT_MAX, INT_MAX); + IntRect focusBounds = IntRect(0, 0, INT_MAX, INT_MAX); + IntRect* rectPtr = &focusBounds; + int imageCount = 0; + if (node->isTextNode()) { + Text* textNode = (Text*) node; + if (CacheBuilder::ConstructTextRects(textNode, 0, textNode, + INT_MAX, globalOffsetX, globalOffsetY, rectPtr, + clipBounds, &rects) == false) + continue; + } else { + IntRect nodeBounds = node->getRect(); + if (CacheBuilder::ConstructPartRects(node, nodeBounds, rectPtr, + globalOffsetX, globalOffsetY, &rects, &imageCount) == false) + continue; + } + unsigned arraySize = rects.size(); + if (arraySize > 1 || (arraySize == 1 && (rectPtr->width() != rect.width())) || + rectPtr->height() != rect.height()) { + if (hasRectParts == false) { + DUMP_NAV_LOGD("static DebugTestRectPart TEST%s_RECTPARTS[] = {\n", name); + hasRectParts = true; + } + if (node->isTextNode() == false) { + unsigned rectIndex = 0; + for (; rectIndex < arraySize; rectIndex++) { + rectPtr = &rects.at(rectIndex); + DUMP_NAV_LOGD("{ %d, %d, %d, %d, %d }, // %d\n", nodeIndex, + rectPtr->x(), rectPtr->y(), rectPtr->width(), + rectPtr->height(), rectIndex + 1); + } + } else { + RenderText* renderText = (RenderText*) node->renderer(); + InlineTextBox* textBox = renderText->firstTextBox(); + unsigned rectIndex = 0; + while (textBox) { + FloatPoint pt = renderText->localToAbsolute(); + IntRect rect = textBox->selectionRect((int) pt.x(), (int) pt.y(), 0, INT_MAX); + mIndex = 0; + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, "{ %d, %d, %d, %d, %d", + nodeIndex, rect.x(), rect.y(), rect.width(), rect.height()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d", + textBox->len(), 0 /*textBox->selectionHeight()*/, + 0 /*textBox->selectionTop()*/); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d", + 0 /*textBox->spaceAdd()*/, textBox->start(), 0 /*textBox->textPos()*/); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d, %d", + textBox->x(), textBox->y(), textBox->logicalWidth(), textBox->logicalHeight()); + int baseline = textBox->renderer()->style(textBox->isFirstLineStyle())->font().ascent(); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d }, // %d ", + baseline, imageCount, ++rectIndex); + wideString(node->textContent().characters() + textBox->start(), textBox->len(), true); + DUMP_NAV_LOGD("%.*s\n", mIndex, mBuffer); + textBox = textBox->nextTextBox(); + } + } + } + } while ((node = node->traverseNextNode()) != NULL); + if (hasRectParts) + DUMP_NAV_LOGD("{0}\n};\n\n"); + else + DUMP_NAV_LOGD("static DebugTestRectPart* TEST%s_RECTPARTS = NULL;\n", name); + } + int contentsWidth = layer->width(); + int contentsHeight = layer->height(); + DUMP_NAV_LOGD("static int TEST%s_FOCUS = %d;\n", name, focusIndex); + DUMP_NAV_LOGD("static int TEST%s_WIDTH = %d;\n", name, contentsWidth); + DUMP_NAV_LOGD("static int TEST%s_HEIGHT = %d;\n\n", name, contentsHeight); +} + +bool CacheBuilder::Debug::isFocusable(Node* node) { + if (node->hasTagName(HTMLNames::areaTag)) + return true; + if (node->renderer() == false) + return false; + if (node->isKeyboardFocusable(NULL)) + return true; + if (node->isMouseFocusable()) + return true; + if (node->isFocusable()) + return true; + if (CacheBuilder::AnyIsClick(node)) + return false; + if (CacheBuilder::HasTriggerEvent(node)) + return true; + return false; +} + +void CacheBuilder::Debug::localName(Node* node) { + const AtomicString& local = node->localName(); + if (node->isTextNode()) + print("\"#text\""); + else + wideString(local.characters(), local.length(), false); + print(", "); +} + +void CacheBuilder::Debug::newLine(int indent) { + if (mPrefix[0] != '\n') + print(&mPrefix[0], 1); + flush(); + int lastnewline = mIndex - 1; + while (lastnewline >= 0 && mBuffer[lastnewline] != '\n') + lastnewline--; + lastnewline++; + char* buffer = mBuffer; + if (lastnewline > 0) { + DUMP_NAV_LOGD("%.*s", lastnewline, buffer); + mIndex -= lastnewline; + buffer += lastnewline; + } + size_t prefixLen = strlen(mPrefix); + int minPrefix = prefixLen - 1; + while (minPrefix >= 0 && mPrefix[minPrefix] != '\n') + minPrefix--; + minPrefix = prefixLen - minPrefix - 1; + if (mIndex > minPrefix) + DUMP_NAV_LOGD("%.*s\n", mIndex, buffer); + mIndex = 0; + setIndent(indent); +} + +int CacheBuilder::Debug::ParentIndex(Node* node, int count, Node* parent) +{ + if (parent == NULL) + return -1; + ASSERT(node != parent); + int result = count; + Node* previous = node; + do { + result--; + previous = previous->traversePreviousNode(); + } while (previous && previous != parent); + if (previous != NULL) + return result; + result = count; + do { + result++; + } while ((node = node->traverseNextNode()) != NULL && node != parent); + if (node != NULL) + return result; + ASSERT(0); + return -1; +} + +void CacheBuilder::Debug::print(const char* name) { + print(name, strlen(name)); +} + +void CacheBuilder::Debug::print(const char* name, unsigned len) { + do { + if (mIndex + len >= DEBUG_BUFFER_SIZE) + flush(); + int copyLen = mIndex + len < DEBUG_BUFFER_SIZE ? + len : DEBUG_BUFFER_SIZE - mIndex; + memcpy(&mBuffer[mIndex], name, copyLen); + mIndex += copyLen; + name += copyLen; + len -= copyLen; + } while (len > 0); + mBuffer[mIndex] = '\0'; +} + +void CacheBuilder::Debug::setIndent(int indent) +{ + char scratch[64]; + snprintf(scratch, sizeof(scratch), "%.*s", indent, + " "); + print(scratch); +} + +void CacheBuilder::Debug::uChar(const UChar* name, unsigned len, bool hex) { + const UChar* end = name + len; + bool wroteHex = false; + while (name < end) { + unsigned ch = *name++; + if (ch == '\t' || ch == '\n' || ch == '\r' || ch == 0xa0) + ch = ' '; + if (ch < ' ' || ch == 0x7f) { + if (hex) { + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, "\\x%02x", ch); + wroteHex = true; + } else + mBuffer[mIndex++] = '?'; + } else if (ch >= 0x80) { + if (hex) { + if (ch < 0x800) + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\\x%02x\\x%02x", ch >> 6 | 0xc0, (ch & 0x3f) | 0x80); + else + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\\x%02x\\x%02x\\x%02x", ch >> 12 | 0xe0, + (ch >> 6 & 0x3f) | 0x80, (ch & 0x3f) | 0x80); + wroteHex = true; + } else + mBuffer[mIndex++] = '?'; + } else { + if (wroteHex && WTF::isASCIIHexDigit((UChar) ch)) + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\" \""); + else if (ch == '"' || ch == '\\') + mBuffer[mIndex++] = '\\'; + mBuffer[mIndex++] = ch; + wroteHex = false; + } + if (mIndex + 1 >= DEBUG_BUFFER_SIZE) + flush(); + } + flush(); +} + +void CacheBuilder::Debug::validateFrame() { + Frame* frame = frameAnd(); + Page* page = frame->page(); + ASSERT(page); + ASSERT((int) page > 0x10000); + Frame* child = frame->tree()->firstChild(); + while (child) { + Builder(child)->mDebug.validateFrame(); + child = child->tree()->nextSibling(); + } +} + +void CacheBuilder::Debug::wideString(const UChar* chars, int length, bool hex) { + if (length == 0) + print("\"\""); + else { + print("\""); + uChar(chars, length, hex); + print("\""); + } +} + +void CacheBuilder::Debug::wideString(const String& str) { + wideString(str.characters(), str.length(), false); +} + +#endif // DUMP_NAV_CACHE + +CacheBuilder::CacheBuilder() +{ + mAllowableTypes = ALL_CACHEDNODE_BITS; +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gNavCacheLogFile = NULL; +#endif +} + +void CacheBuilder::adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds, RenderBlock* renderer) +{ + if (!renderer->hasColumns()) + return; + int x = 0; + int y = 0; + int tx = track.mBounds.x(); + int ty = track.mBounds.y(); + int columnGap = track.mColumnGap; + size_t limit = track.mColumnInfo->columnCount(); + for (size_t index = 0; index < limit; index++) { + IntRect column = renderer->columnRectAt(track.mColumnInfo, index); + column.move(tx, ty); + IntRect test = *bounds; + test.move(x, y); + if (column.contains(test)) { + if ((x | y) == 0) + return; + *bounds = test; + node->move(x, y); + return; + } + int xOffset = column.width() + columnGap; + x += track.mDirection == LTR ? xOffset : -xOffset; + y -= column.height(); + } +} + +// Checks if a node has one of event listener types. +bool CacheBuilder::NodeHasEventListeners(Node* node, AtomicString* eventTypes, int length) { + for (int i = 0; i < length; ++i) { + if (!node->getEventListeners(eventTypes[i]).isEmpty()) + return true; + } + return false; +} + +bool CacheBuilder::AnyChildIsClick(Node* node) +{ + AtomicString eventTypes[5] = { + eventNames().clickEvent, + eventNames().mousedownEvent, + eventNames().mouseupEvent, + eventNames().keydownEvent, + eventNames().keyupEvent + }; + + Node* child = node->firstChild(); + while (child != NULL) { + if (child->isFocusable() || + NodeHasEventListeners(child, eventTypes, 5)) + return true; + if (AnyChildIsClick(child)) + return true; + child = child->nextSibling(); + } + return false; +} + +bool CacheBuilder::AnyIsClick(Node* node) +{ + if (node->hasTagName(HTMLNames::bodyTag)) + return AnyChildIsClick(node); + + AtomicString eventTypeSetOne[4] = { + eventNames().mouseoverEvent, + eventNames().mouseoutEvent, + eventNames().keydownEvent, + eventNames().keyupEvent + }; + + if (!NodeHasEventListeners(node, eventTypeSetOne, 4)) + return false; + + AtomicString eventTypeSetTwo[3] = { + eventNames().clickEvent, + eventNames().mousedownEvent, + eventNames().mouseupEvent + }; + + if (NodeHasEventListeners(node, eventTypeSetTwo, 3)) + return false; + + return AnyChildIsClick(node); +} + +void CacheBuilder::buildCache(CachedRoot* root) +{ + Frame* frame = FrameAnd(this); + mPictureSetDisabled = false; + BuildFrame(frame, frame, root, (CachedFrame*) root); + root->finishInit(); // set up frame parent pointers, child pointers + setData((CachedFrame*) root); +} + +static Node* ParentWithChildren(Node* node) +{ + Node* parent = node; + while ((parent = parent->parentNode())) { + if (parent->childNodeCount() > 1) + return parent; + } + return 0; +} + +// FIXME +// Probably this should check for null instead of the caller. If the +// Tracker object is the last thing in the dom, checking for null in the +// caller in some cases fails to set up Tracker state which may be useful +// to the nodes parsed immediately after the tracked noe. +static Node* OneAfter(Node* node) +{ + Node* parent = node; + Node* sibling = NULL; + while ((parent = parent->parentNode()) != NULL) { + sibling = parent->nextSibling(); + if (sibling != NULL) + break; + } + return sibling; +} + +// return true if this renderer is really a pluinview, and it wants +// key-events (i.e. focus) +static bool checkForPluginViewThatWantsFocus(RenderObject* renderer) { + if (renderer->isWidget()) { + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (widget && (widget->isPluginView() || widget->isPluginViewBase())) { + // check if this plugin really wants key events (TODO) + return true; + } + } + return false; +} + +#if USE(ACCELERATED_COMPOSITING) +static void AddLayer(CachedFrame* frame, size_t index, const IntPoint& location, int id) +{ + DBG_NAV_LOGD("frame=%p index=%d loc=(%d,%d) id=%d", frame, index, + location.x(), location.y(), id); + CachedLayer cachedLayer; + cachedLayer.setCachedNodeIndex(index); + cachedLayer.setOffset(location); + cachedLayer.setUniqueId(id); + frame->add(cachedLayer); +} +#endif + +static int FindColorIndex(WTF::Vector<CachedColor>& colorTracker, + const CachedColor& cachedColor) +{ + CachedColor* work = colorTracker.begin() - 1; + CachedColor* end = colorTracker.end(); + while (++work < end) { + if (*work == cachedColor) + return work - colorTracker.begin(); + } + int result = colorTracker.size(); + colorTracker.grow(result + 1); + CachedColor& newColor = colorTracker.last(); + newColor = cachedColor; + return result; +} + +static void InitColor(CachedColor* color) +{ + color->setFillColor(RenderStyle::initialRingFillColor()); + color->setInnerWidth(RenderStyle::initialRingInnerWidth()); + color->setOuterWidth(RenderStyle::initialRingOuterWidth()); + color->setOutset(RenderStyle::initialRingOutset()); + color->setPressedInnerColor(RenderStyle::initialRingPressedInnerColor()); + color->setPressedOuterColor(RenderStyle::initialRingPressedOuterColor()); + color->setRadius(RenderStyle::initialRingRadius()); + color->setSelectedInnerColor(RenderStyle::initialRingSelectedInnerColor()); + color->setSelectedOuterColor(RenderStyle::initialRingSelectedOuterColor()); +} + +// when new focus is found, push it's parent on a stack + // as long as more focii are found with the same (grand) parent, note it + // (which only requires retrieving the last parent on the stack) +// when the parent's last child is found, pop the stack +// different from Tracker in that Tracker only pushes focii with children + +// making this work with focus - child focus - grandchild focus is tricky +// if I keep the generation number, I may be able to more quickly determine that +// a node is a grandchild of the focus's parent +// this additionally requires being able to find the grandchild's parent + +// keep nodes that are focusable +void CacheBuilder::BuildFrame(Frame* root, Frame* frame, + CachedRoot* cachedRoot, CachedFrame* cachedFrame) +{ + WTF::Vector<FocusTracker> tracker(1); // sentinel + { + FocusTracker* baseTracker = tracker.data(); + bzero(baseTracker, sizeof(FocusTracker)); + baseTracker->mCachedNodeIndex = -1; + } + WTF::Vector<LayerTracker> layerTracker(1); // sentinel + bzero(layerTracker.data(), sizeof(LayerTracker)); + WTF::Vector<ClipColumnTracker> clipTracker(1); // sentinel + bzero(clipTracker.data(), sizeof(ClipColumnTracker)); + WTF::Vector<TabIndexTracker> tabIndexTracker(1); // sentinel + bzero(tabIndexTracker.data(), sizeof(TabIndexTracker)); + WTF::Vector<CachedColor> colorTracker(1); + InitColor(colorTracker.data()); +#if DUMP_NAV_CACHE + char* frameNamePtr = cachedFrame->mDebug.mFrameName; + Builder(frame)->mDebug.frameName(frameNamePtr, frameNamePtr + + sizeof(cachedFrame->mDebug.mFrameName) - 1); + *frameNamePtr = '\0'; + int nodeIndex = 1; +#endif + NodeWalk walk; + Document* doc = frame->document(); + Node* parent = doc; + CachedNode cachedParentNode; + cachedParentNode.init(parent); +#if DUMP_NAV_CACHE + cachedParentNode.mDebug.mNodeIndex = nodeIndex; +#endif + cachedFrame->add(colorTracker[0]); + cachedFrame->add(cachedParentNode); + Node* node = parent; + int cacheIndex = 1; + int colorIndex = 0; // assume no special css ring colors + const void* lastStyleDataPtr = 0; + int textInputIndex = 0; + Node* focused = doc->focusedNode(); + if (focused) + cachedRoot->setFocusBounds(focused->getRect()); + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); +#if USE(ACCELERATED_COMPOSITING) + // The frame itself might be composited so we need to track the layer. Do + // not track the base frame's layer as the main content is draw as part of + // BaseLayerAndroid's picture. + if (frame != root && frame->contentRenderer() + && frame->contentRenderer()->usesCompositing() && node->lastChild()) + TrackLayer(layerTracker, frame->contentRenderer(), node->lastChild(), + globalOffsetX, globalOffsetY); +#endif + while (walk.mMore || (node = node->traverseNextNode()) != NULL) { +#if DUMP_NAV_CACHE + nodeIndex++; +#endif + FocusTracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + while (node == last->mLastChild) { + if (CleanUpContainedNodes(cachedRoot, cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + lastChildIndex = last->mCachedNodeIndex; + last = &tracker.last(); + } + do { + const ClipColumnTracker* lastClip = &clipTracker.last(); + if (node != lastClip->mLastChild) + break; + clipTracker.removeLast(); + } while (true); + do { + const LayerTracker* lastLayer = &layerTracker.last(); + if (node != lastLayer->mLastChild) + break; + layerTracker.removeLast(); + } while (true); + do { + const TabIndexTracker* lastTabIndex = &tabIndexTracker.last(); + if (node != lastTabIndex->mLastChild) + break; + tabIndexTracker.removeLast(); + } while (true); + Frame* child = HasFrame(node); + CachedNode cachedNode; + if (child != NULL) { + if (child->document() == NULL) + continue; + RenderObject* nodeRenderer = node->renderer(); + if (nodeRenderer != NULL && nodeRenderer->style()->visibility() == HIDDEN) + continue; + CachedFrame cachedChild; + cachedChild.init(cachedRoot, cacheIndex, child); + int childFrameIndex = cachedFrame->childCount(); + cachedFrame->addFrame(cachedChild); + cachedNode.init(node); + cachedNode.setIndex(cacheIndex++); + cachedNode.setDataIndex(childFrameIndex); + cachedNode.setType(FRAME_CACHEDNODETYPE); +#if DUMP_NAV_CACHE + cachedNode.mDebug.mNodeIndex = nodeIndex; + cachedNode.mDebug.mParentGroupIndex = Debug::ParentIndex( + node, nodeIndex, NULL); +#endif + cachedFrame->add(cachedNode); + CachedFrame* childPtr = cachedFrame->lastChild(); + BuildFrame(root, child, cachedRoot, childPtr); + continue; + } + int tabIndex = node->tabIndex(); + Node* lastChild = node->lastChild(); + if (tabIndex <= 0) + tabIndex = tabIndexTracker.last().mTabIndex; + else if (tabIndex > 0 && lastChild) { + DBG_NAV_LOGD("tabIndex=%d node=%p", tabIndex, node); + tabIndexTracker.grow(tabIndexTracker.size() + 1); + TabIndexTracker& indexTracker = tabIndexTracker.last(); + indexTracker.mTabIndex = tabIndex; + indexTracker.mLastChild = OneAfter(lastChild); + } + RenderObject* nodeRenderer = node->renderer(); + bool isTransparent = false; + bool hasCursorRing = true; + if (nodeRenderer != NULL) { + RenderStyle* style = nodeRenderer->style(); + if (style->visibility() == HIDDEN) + continue; + isTransparent = nodeRenderer->hasBackground() == false; +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + hasCursorRing = style->tapHighlightColor().alpha() > 0; +#endif +#if USE(ACCELERATED_COMPOSITING) + // If this renderer has its own layer and the layer is composited, + // start tracking it. + if (lastChild && nodeRenderer->hasLayer() && toRenderBoxModelObject(nodeRenderer)->layer()->backing()) + TrackLayer(layerTracker, nodeRenderer, lastChild, globalOffsetX, globalOffsetY); +#endif + } + bool more = walk.mMore; + walk.reset(); + // GetGlobalBounds(node, &bounds, false); + bool computeCursorRings = false; + bool hasClip = false; + bool hasMouseOver = false; + bool isUnclipped = false; + bool isFocus = node == focused; + bool takesFocus = false; + int columnGap = 0; + int imageCount = 0; + TextDirection direction = LTR; + String exported; + CachedNodeType type = NORMAL_CACHEDNODETYPE; + CachedColor cachedColor; + CachedInput cachedInput; + IntRect bounds; + IntRect absBounds; + IntRect originalAbsBounds; + ColumnInfo* columnInfo = NULL; + if (node->hasTagName(HTMLNames::areaTag)) { + type = AREA_CACHEDNODETYPE; + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + bounds = getAreaRect(area); + originalAbsBounds = bounds; + bounds.move(globalOffsetX, globalOffsetY); + absBounds = bounds; + isUnclipped = true; // FIXME: areamaps require more effort to detect + // assume areamaps are always visible for now + takesFocus = true; + goto keepNode; + } + if (nodeRenderer == NULL) + continue; + + // some common setup + absBounds = nodeRenderer->absoluteBoundingBoxRect(); + originalAbsBounds = absBounds; + absBounds.move(globalOffsetX, globalOffsetY); + hasClip = nodeRenderer->hasOverflowClip(); + + if (node->hasTagName(HTMLNames::canvasTag)) + mPictureSetDisabled = true; + if (checkForPluginViewThatWantsFocus(nodeRenderer)) { + bounds = absBounds; + isUnclipped = true; + takesFocus = true; + type = PLUGIN_CACHEDNODETYPE; + goto keepNode; + } + // Only use the root contentEditable element + if (node->isContentEditable() && !node->parentOrHostNode()->isContentEditable()) { + bounds = absBounds; + takesFocus = true; + type = CONTENT_EDITABLE_CACHEDNODETYPE; + goto keepNode; + } + if (nodeRenderer->isRenderBlock()) { + RenderBlock* renderBlock = (RenderBlock*) nodeRenderer; + if (renderBlock->hasColumns()) { + columnInfo = renderBlock->columnInfo(); + columnGap = renderBlock->columnGap(); + direction = renderBlock->style()->direction(); + } + } + if ((hasClip != false || columnInfo != NULL) && lastChild) { + clipTracker.grow(clipTracker.size() + 1); + ClipColumnTracker& clip = clipTracker.last(); + clip.mBounds = absBounds; + clip.mLastChild = OneAfter(lastChild); + clip.mNode = node; + clip.mColumnInfo = columnInfo; + clip.mColumnGap = columnGap; + clip.mHasClip = hasClip; + clip.mDirection = direction; + if (columnInfo != NULL) { + const IntRect& oRect = ((RenderBox*)nodeRenderer)->visualOverflowRect(); + clip.mBounds.move(oRect.x(), oRect.y()); + } + } + if (node->isTextNode() && mAllowableTypes != NORMAL_CACHEDNODE_BITS) { + if (last->mSomeParentTakesFocus) // don't look at text inside focusable node + continue; + CachedNodeType checkType; + if (isFocusableText(&walk, more, node, &checkType, + &exported) == false) + continue; + #if DUMP_NAV_CACHE + { + char buffer[DEBUG_BUFFER_SIZE]; + mDebug.init(buffer, sizeof(buffer)); + mDebug.print("text link found: "); + mDebug.wideString(exported); + DUMP_NAV_LOGD("%s\n", buffer); + } + #endif + type = checkType; + // !!! test ! is the following line correctly needed for frames to work? + cachedNode.init(node); + const ClipColumnTracker& clipTrack = clipTracker.last(); + const IntRect& clip = clipTrack.mHasClip ? clipTrack.mBounds : + IntRect(0, 0, INT_MAX, INT_MAX); + if (ConstructTextRects((WebCore::Text*) node, walk.mStart, + (WebCore::Text*) walk.mFinalNode, walk.mEnd, globalOffsetX, + globalOffsetY, &bounds, clip, &cachedNode.mCursorRing) == false) + continue; + absBounds = bounds; + cachedNode.setBounds(bounds); + if (bounds.width() < MINIMUM_FOCUSABLE_WIDTH) + continue; + if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) + continue; + computeCursorRings = true; + isUnclipped = true; // FIXME: to hide or partially occlude synthesized links, each + // focus ring will also need the offset and length of characters + // used to produce it + goto keepTextNode; + } + if (node->hasTagName(WebCore::HTMLNames::inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (input->isTextField()) { + if (input->readOnly()) + continue; + type = TEXT_INPUT_CACHEDNODETYPE; + cachedInput.init(); + cachedInput.setAutoComplete(input->autoComplete()); + cachedInput.setFormPointer(input->form()); + cachedInput.setIsTextField(true); + exported = input->value().threadsafeCopy(); + cachedInput.setMaxLength(input->maxLength()); + cachedInput.setTypeFromElement(input); + // If this does not need to be threadsafe, we can use crossThreadString(). + // See http://trac.webkit.org/changeset/49160. + cachedInput.setName(input->name().string().threadsafeCopy()); + // can't detect if this is drawn on top (example: deviant.com login parts) + isUnclipped = isTransparent; + } else if (input->isInputTypeHidden()) + continue; + else if (input->isRadioButton() || input->isCheckbox()) + isTransparent = false; + } else if (node->hasTagName(HTMLNames::textareaTag)) { + HTMLTextAreaElement* area = static_cast<HTMLTextAreaElement*>(node); + if (area->readOnly()) + continue; + cachedInput.init(); + type = TEXT_INPUT_CACHEDNODETYPE; + cachedInput.setFormPointer(area->form()); + cachedInput.setIsTextArea(true); + exported = area->value().threadsafeCopy(); + } else if (node->hasTagName(HTMLNames::aTag)) { + const HTMLAnchorElement* anchorNode = + (const HTMLAnchorElement*) node; + if (!anchorNode->isFocusable() && !HasTriggerEvent(node)) + continue; + if (node->disabled()) + continue; + hasMouseOver = NodeHasEventListeners(node, &eventNames().mouseoverEvent, 1); + type = ANCHOR_CACHEDNODETYPE; + KURL href = anchorNode->href(); + if (!href.isEmpty() && !WebCore::protocolIsJavaScript(href.string())) + // Set the exported string for all non-javascript anchors. + exported = href.string().threadsafeCopy(); + } else if (node->hasTagName(HTMLNames::selectTag)) { + type = SELECT_CACHEDNODETYPE; + } + if (type == TEXT_INPUT_CACHEDNODETYPE) { + RenderTextControl* renderText = + static_cast<RenderTextControl*>(nodeRenderer); + if (isFocus) + cachedRoot->setSelection(renderText->selectionStart(), renderText->selectionEnd()); + // FIXME: Are we sure there will always be a style and font, and it's correct? + RenderStyle* style = nodeRenderer->style(); + if (style) { + isUnclipped |= !style->hasAppearance(); + int lineHeight = -1; + Length lineHeightLength = style->lineHeight(); + // If the lineHeight is negative, WebTextView will calculate it + // based on the text size, using the Paint. + // See RenderStyle.computedLineHeight. + if (lineHeightLength.isPositive()) + lineHeight = style->computedLineHeight(); + cachedInput.setLineHeight(lineHeight); + cachedInput.setTextSize(style->font().size()); + cachedInput.setIsRtlText(style->direction() == RTL + || style->textAlign() == WebCore::RIGHT + || style->textAlign() == WebCore::WEBKIT_RIGHT); + } + cachedInput.setPaddingLeft(renderText->paddingLeft() + renderText->borderLeft()); + cachedInput.setPaddingTop(renderText->paddingTop() + renderText->borderTop()); + cachedInput.setPaddingRight(renderText->paddingRight() + renderText->borderRight()); + cachedInput.setPaddingBottom(renderText->paddingBottom() + renderText->borderBottom()); + } + takesFocus = true; + bounds = absBounds; + if (type != ANCHOR_CACHEDNODETYPE) { + bool isFocusable = node->isKeyboardFocusable(NULL) || + node->isMouseFocusable() || node->isFocusable(); + if (isFocusable == false) { + if (node->disabled()) + continue; + bool overOrOut = HasOverOrOut(node); + bool hasTrigger = HasTriggerEvent(node); + if (overOrOut == false && hasTrigger == false) + continue; + takesFocus = hasTrigger; + } + } + computeCursorRings = true; + keepNode: + cachedNode.init(node); + if (computeCursorRings == false) { + cachedNode.setBounds(bounds); + cachedNode.mCursorRing.append(bounds); + } else if (ConstructPartRects(node, bounds, &cachedNode.mBounds, + globalOffsetX, globalOffsetY, &cachedNode.mCursorRing, + &imageCount) == false) + continue; + keepTextNode: + if (nodeRenderer) { // area tags' node->renderer() == 0 + RenderStyle* style = nodeRenderer->style(); + const void* styleDataPtr = style->ringData(); + // to save time, see if we're pointing to the same style data as before + if (lastStyleDataPtr != styleDataPtr) { + lastStyleDataPtr = styleDataPtr; + cachedColor.setFillColor(style->ringFillColor()); + cachedColor.setInnerWidth(style->ringInnerWidth()); + cachedColor.setOuterWidth(style->ringOuterWidth()); + cachedColor.setOutset(style->ringOutset()); + cachedColor.setPressedInnerColor(style->ringPressedInnerColor()); + cachedColor.setPressedOuterColor(style->ringPressedOuterColor()); + cachedColor.setRadius(style->ringRadius()); + cachedColor.setSelectedInnerColor(style->ringSelectedInnerColor()); + cachedColor.setSelectedOuterColor(style->ringSelectedOuterColor()); + int oldSize = colorTracker.size(); + colorIndex = FindColorIndex(colorTracker, cachedColor); + if (colorIndex == oldSize) + cachedFrame->add(cachedColor); + } + } else + colorIndex = 0; + IntRect clip = hasClip ? bounds : absBounds; + size_t clipIndex = clipTracker.size(); + if (clipTracker.last().mNode == node) + clipIndex -= 1; + while (--clipIndex > 0) { + const ClipColumnTracker& clipTrack = clipTracker.at(clipIndex); + if (clipTrack.mHasClip == false) { + adjustForColumns(clipTrack, &cachedNode, &absBounds, static_cast<RenderBlock*>(nodeRenderer)); + continue; + } + const IntRect& parentClip = clipTrack.mBounds; + if (hasClip == false && type == ANCHOR_CACHEDNODETYPE) + clip = parentClip; + else + clip.intersect(parentClip); + hasClip = true; + } + bool isInLayer = false; +#if USE(ACCELERATED_COMPOSITING) + // If this renderer has a composited parent layer (including itself), + // add the node to the cached layer. + // FIXME: does not work for area rects + RenderLayer* enclosingLayer = nodeRenderer->enclosingLayer(); + if (enclosingLayer && enclosingLayer->enclosingCompositingLayer()) { + LayerAndroid* layer = layerTracker.last().mLayer; + if (layer) { + const IntRect& layerClip = layerTracker.last().mBounds; + if (!layerClip.isEmpty() && !cachedNode.clip(layerClip)) { + DBG_NAV_LOGD("skipped on layer clip %d", cacheIndex); + continue; // skip this node if outside of the clip + } + isInLayer = true; + isUnclipped = true; // assume that layers do not have occluded nodes + hasClip = false; + AddLayer(cachedFrame, cachedFrame->size(), layerClip.location(), + layer->uniqueId()); + } + } +#endif + if (hasClip) { + if (clip.isEmpty()) + continue; // skip this node if clip prevents all drawing + else if (cachedNode.clip(clip) == false) + continue; // skip this node if outside of the clip + } + cachedNode.setNavableRects(); + cachedNode.setColorIndex(colorIndex); + cachedNode.setExport(exported); + cachedNode.setHasCursorRing(hasCursorRing); + cachedNode.setHasMouseOver(hasMouseOver); + cachedNode.setHitBounds(absBounds); + cachedNode.setIndex(cacheIndex); + cachedNode.setIsFocus(isFocus); + cachedNode.setIsInLayer(isInLayer); + cachedNode.setIsTransparent(isTransparent); + cachedNode.setIsUnclipped(isUnclipped); + cachedNode.setOriginalAbsoluteBounds(originalAbsBounds); + cachedNode.setParentIndex(last->mCachedNodeIndex); + cachedNode.setParentGroup(ParentWithChildren(node)); + cachedNode.setSingleImage(imageCount == 1); + cachedNode.setTabIndex(tabIndex); + cachedNode.setType(type); + if (type == TEXT_INPUT_CACHEDNODETYPE) { + cachedFrame->add(cachedInput); + cachedNode.setDataIndex(textInputIndex); + textInputIndex++; + } else + cachedNode.setDataIndex(-1); +#if DUMP_NAV_CACHE + cachedNode.mDebug.mNodeIndex = nodeIndex; + cachedNode.mDebug.mParentGroupIndex = Debug::ParentIndex( + node, nodeIndex, (Node*) cachedNode.parentGroup()); +#endif + cachedFrame->add(cachedNode); + { + int lastIndex = cachedFrame->size() - 1; + if (node == focused) { + CachedNode* cachedNodePtr = cachedFrame->getIndex(lastIndex); + cachedRoot->setCachedFocus(cachedFrame, cachedNodePtr); + } + if (lastChild != NULL) { + tracker.grow(tracker.size() + 1); + FocusTracker& working = tracker.last(); + working.mCachedNodeIndex = lastIndex; + working.mLastChild = OneAfter(lastChild); + last = &tracker.at(tracker.size() - 2); + working.mSomeParentTakesFocus = last->mSomeParentTakesFocus | takesFocus; + } + } + cacheIndex++; + } + while (tracker.size() > 1) { + FocusTracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + if (CleanUpContainedNodes(cachedRoot, cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + } +} + +bool CacheBuilder::CleanUpContainedNodes(CachedRoot* cachedRoot, + CachedFrame* cachedFrame, const FocusTracker* last, int lastChildIndex) +{ + // if outer is body, disable outer + // or if there's more than one inner, disable outer + // or if inner is keyboard focusable, disable outer + // else disable inner by removing it + int childCount = lastChildIndex - last->mCachedNodeIndex; + if (childCount == 0) + return false; + CachedNode* lastCached = cachedFrame->getIndex(last->mCachedNodeIndex); + Node* lastNode = (Node*) lastCached->nodePointer(); + if ((childCount > 1 && lastNode->hasTagName(HTMLNames::selectTag) == false) || + lastNode->hasTagName(HTMLNames::bodyTag) || + lastNode->hasTagName(HTMLNames::formTag)) { + lastCached->setBounds(IntRect(0, 0, 0, 0)); + lastCached->mCursorRing.clear(); + lastCached->setNavableRects(); + return false; + } + CachedNode* onlyChildCached = cachedFrame->lastNode(); + Node* onlyChild = (Node*) onlyChildCached->nodePointer(); + bool outerIsMouseMoveOnly = + lastNode->isKeyboardFocusable(NULL) == false && + lastNode->isMouseFocusable() == false && + lastNode->isFocusable() == false && + HasOverOrOut(lastNode) == true && + HasTriggerEvent(lastNode) == false; + if (onlyChildCached->parent() == lastCached) + onlyChildCached->setParentIndex(lastCached->parentIndex()); + bool hasFocus = lastCached->isFocus() || onlyChildCached->isFocus(); + if (outerIsMouseMoveOnly || onlyChild->isKeyboardFocusable(NULL) + || onlyChildCached->isPlugin()) { + int index = lastCached->index(); + *lastCached = *onlyChildCached; + lastCached->setIndex(index); + CachedFrame* frame = cachedFrame->hasFrame(lastCached); + if (frame) + frame->setIndexInParent(index); + } + cachedFrame->removeLast(); + if (hasFocus) + cachedRoot->setCachedFocus(cachedFrame, cachedFrame->lastNode()); + return true; +} + +Node* CacheBuilder::currentFocus() const +{ + Frame* frame = FrameAnd(this); + Document* doc = frame->document(); + if (doc != NULL) { + Node* focus = doc->focusedNode(); + if (focus != NULL) + return focus; + } + Frame* child = frame->tree()->firstChild(); + while (child) { + CacheBuilder* cacheBuilder = Builder(child); + Node* focus = cacheBuilder->currentFocus(); + if (focus) + return focus; + child = child->tree()->nextSibling(); + } + return NULL; +} + +static bool strCharCmp(const char* matches, const UChar* test, int wordLength, + int wordCount) +{ + for (int index = 0; index < wordCount; index++) { + for (int inner = 0; inner < wordLength; inner++) { + if (matches[inner] != test[inner]) { + matches += wordLength; + goto next; + } + } + return true; +next: + ; + } + return false; +} + +static const int stateTwoLetter[] = { + 0x02060c00, // A followed by: [KLRSZ] + 0x00000000, // B + 0x00084001, // C followed by: [AOT] + 0x00000014, // D followed by: [CE] + 0x00000000, // E + 0x00001800, // F followed by: [LM] + 0x00100001, // G followed by: [AU] + 0x00000100, // H followed by: [I] + 0x00002809, // I followed by: [ADLN] + 0x00000000, // J + 0x01040000, // K followed by: [SY] + 0x00000001, // L followed by: [A] + 0x000ce199, // M followed by: [ADEHINOPST] + 0x0120129c, // N followed by: [CDEHJMVY] + 0x00020480, // O followed by: [HKR] + 0x00420001, // P followed by: [ARW] + 0x00000000, // Q + 0x00000100, // R followed by: [I] + 0x0000000c, // S followed by: [CD] + 0x00802000, // T followed by: [NX] + 0x00080000, // U followed by: [T] + 0x00080101, // V followed by: [AIT] + 0x01200101 // W followed by: [AIVY] +}; + +static const char firstIndex[] = { + 0, 5, 5, 8, 10, 10, 12, 14, + 15, 19, 19, 21, 22, 32, 40, 43, + 46, 46, 47, 49, 51, 52, 55, 59 +}; + +// from http://infolab.stanford.edu/~manku/bitcount/bitcount.html +#define TWO(c) (0x1u << (c)) +#define MASK(c) (((unsigned int)(-1)) / (TWO(TWO(c)) + 1u)) +#define COUNT(x,c) ((x) & MASK(c)) + (((x) >> (TWO(c))) & MASK(c)) + +int bitcount (unsigned int n) +{ + n = COUNT(n, 0); + n = COUNT(n, 1); + n = COUNT(n, 2); + n = COUNT(n, 3); + return COUNT(n, 4); +} + +#undef TWO +#undef MASK +#undef COUNT + +static bool isUnicodeSpace(UChar ch) +{ + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == 0xa0; +} + +static bool validZip(int stateIndex, const UChar* zipPtr) +{ + static const struct { + char mLow; + char mHigh; + char mException1; + char mException2; + } zipRange[] = { + { 99, 99, -1, -1 }, // AK Alaska + { 35, 36, -1, -1 }, // AL Alabama + { 71, 72, -1, -1 }, // AR Arkansas + { 96, 96, -1, -1 }, // AS American Samoa + { 85, 86, -1, -1 }, // AZ Arizona + { 90, 96, -1, -1 }, // CA California + { 80, 81, -1, -1 }, // CO Colorado + { 6, 6, -1, -1 }, // CT Connecticut + { 20, 20, -1, -1 }, // DC District of Columbia + { 19, 19, -1, -1 }, // DE Delaware + { 32, 34, -1, -1 }, // FL Florida + { 96, 96, -1, -1 }, // FM Federated States of Micronesia + { 30, 31, -1, -1 }, // GA Georgia + { 96, 96, -1, -1 }, // GU Guam + { 96, 96, -1, -1 }, // HI Hawaii + { 50, 52, -1, -1 }, // IA Iowa + { 83, 83, -1, -1 }, // ID Idaho + { 60, 62, -1, -1 }, // IL Illinois + { 46, 47, -1, -1 }, // IN Indiana + { 66, 67, 73, -1 }, // KS Kansas + { 40, 42, -1, -1 }, // KY Kentucky + { 70, 71, -1, -1 }, // LA Louisiana + { 1, 2, -1, -1 }, // MA Massachusetts + { 20, 21, -1, -1 }, // MD Maryland + { 3, 4, -1, -1 }, // ME Maine + { 96, 96, -1, -1 }, // MH Marshall Islands + { 48, 49, -1, -1 }, // MI Michigan + { 55, 56, -1, -1 }, // MN Minnesota + { 63, 65, -1, -1 }, // MO Missouri + { 96, 96, -1, -1 }, // MP Northern Mariana Islands + { 38, 39, -1, -1 }, // MS Mississippi + { 55, 56, -1, -1 }, // MT Montana + { 27, 28, -1, -1 }, // NC North Carolina + { 58, 58, -1, -1 }, // ND North Dakota + { 68, 69, -1, -1 }, // NE Nebraska + { 3, 4, -1, -1 }, // NH New Hampshire + { 7, 8, -1, -1 }, // NJ New Jersey + { 87, 88, 86, -1 }, // NM New Mexico + { 88, 89, 96, -1 }, // NV Nevada + { 10, 14, 0, 6 }, // NY New York + { 43, 45, -1, -1 }, // OH Ohio + { 73, 74, -1, -1 }, // OK Oklahoma + { 97, 97, -1, -1 }, // OR Oregon + { 15, 19, -1, -1 }, // PA Pennsylvania + { 6, 6, 0, 9 }, // PR Puerto Rico + { 96, 96, -1, -1 }, // PW Palau + { 2, 2, -1, -1 }, // RI Rhode Island + { 29, 29, -1, -1 }, // SC South Carolina + { 57, 57, -1, -1 }, // SD South Dakota + { 37, 38, -1, -1 }, // TN Tennessee + { 75, 79, 87, 88 }, // TX Texas + { 84, 84, -1, -1 }, // UT Utah + { 22, 24, 20, -1 }, // VA Virginia + { 6, 9, -1, -1 }, // VI Virgin Islands + { 5, 5, -1, -1 }, // VT Vermont + { 98, 99, -1, -1 }, // WA Washington + { 53, 54, -1, -1 }, // WI Wisconsin + { 24, 26, -1, -1 }, // WV West Virginia + { 82, 83, -1, -1 } // WY Wyoming + }; + + int zip = zipPtr[0] - '0'; + zip *= 10; + zip += zipPtr[1] - '0'; + int low = zipRange[stateIndex].mLow; + int high = zipRange[stateIndex].mHigh; + if (zip >= low && zip <= high) + return true; + if (zip == zipRange[stateIndex].mException1) + return true; + if (zip == zipRange[stateIndex].mException2) + return true; + return false; +} + +#define MAX_PLACE_NAME_LENGTH 25 // the longest allowable one word place name + +CacheBuilder::FoundState CacheBuilder::FindAddress(const UChar* chars, + unsigned length, int* start, int* end, bool caseInsensitive) +{ + FindState addressState; + FindReset(&addressState); + addressState.mWords[0] = addressState.mStarts[0] = chars; + addressState.mCaseInsensitive = caseInsensitive; + FoundState state = FindPartialAddress(chars, chars, length, &addressState); + if (state == FOUND_PARTIAL && addressState.mProgress == ZIP_CODE && + addressState.mNumberCount == 0) { + addressState.mProgress = FIND_STREET; + state = FindPartialAddress(NULL, NULL, 0, &addressState); + } + *start = addressState.mStartResult; + *end = addressState.mEndResult; + return state; +} + +CacheBuilder::FoundState CacheBuilder::FindPartialAddress(const UChar* baseChars, + const UChar* chars, unsigned length, FindState* s) +{ + // lower case letters are optional; trailing asterisk is optional 's' + static char const* const longStreetNames[] = { + "\x04" "LleY" "\x04" "NneX" "\x05" "RCade" "\x05" "VEnue" "\x06" "LAMEDA", // A + "\x04" "aYoU" "\x04" "eaCH" "\x03" "eND" "\x05" "LuFf*" "\x05" "oTtoM" + "\x08" "ouLeVarD" "\x05" "Ranch" "\x05" "RidGe" "\x05" "RooK*" + "\x04" "urG*" "\x05" "YPass" "\x07" "roadWAY", // B + "\x05" "AMINO" + "\x03" "amP" "\x05" "anYoN" "\x03" "aPE" "\x07" "auSeWaY" "\x06" "enTeR*" + "\x06" "IRcle*" "\x05" "LiFf*" "\x03" "LuB" "\x05" "oMmoN" "\x06" "ORner*" + "\x05" "ouRSE" "\x05" "ourT*" "\x04" "oVe*" "\x04" "ReeK" "\x07" "REScent" + "\x04" "ReST" "\x07" "ROSSING" "\x08" "ROSSROAD" "\x04" "URVe" + "\x05" "AMINO" "\x06" "IRCULO" "\x07" "REScent", // C + "\x03" "aLe" "\x02" "aM" "\x05" "iVide" "\x05" "Rive*", // D + "\x06" "STate*" "\x09" "XPresswaY" "\x09" "XTension*", // E + "\x04" "ALL*" "\x04" "eRrY" "\x05" "ieLD*" "\x04" "LaT*" "\x04" "oRD*" + "\x05" "oReST" "\x05" "oRGe*" "\x04" "oRK*" "\x03" "orT" "\x06" "reeWaY", // F + "\x06" "arDeN*" "\x06" "aTeWaY" "\x04" "LeN*" "\x05" "ReeN*" "\x05" "RoVe*", // G + "\x06" "arBoR*" "\x04" "aVeN" "\x06" "eighTS" "\x06" "ighWaY" "\x04" "iLl*" + "\x05" "OLloW", // H + "\x04" "NLeT" "\x06" "Sland*" "\x03" "SLE", // I + "\x08" "unCTion*", // J + "\x03" "eY*" "\x05" "NoLl*", // K + "\x04" "aKe*" "\x03" "AND" "\x06" "aNDinG" "\x03" "aNe" "\x05" "iGhT*" + "\x03" "oaF" "\x04" "oCK*" "\x04" "oDGe" "\x03" "OOP", // L + "\x03" "ALL" "\x05" "aNoR*" "\x06" "eaDoW*" "\x03" "EWS" "\x04" "iLl*" + "\x06" "iSsioN" "\x07" "oTorWaY" "\x04" "ounT" "\x08" "ounTaiN*", // M + "\x03" "eCK", // N + "\x06" "RCHard" "\x03" "VAL" "\x07" "verPASs", // O + "\x04" "ARK*" "\x07" "arKWaY*" "\x03" "ASS" "\x06" "aSsaGE" "\x03" "ATH" + "\x03" "IKE" "\x04" "iNE*" "\x04" "Lace" "\x05" "LaiN*" "\x04" "LaZa" + "\x05" "oinT*" "\x04" "oRT*" "\x06" "Rairie" "\x06" "RIVADA", // P + NULL, + "\x05" "ADiaL" "\x03" "AMP" "\x04" "aNCH" "\x05" "aPiD*" + "\x03" "eST" + "\x05" "iDGe*" "\x04" "IVer" "\x04" "oaD*" "\x04" "ouTE" "\x02" "OW" + "\x02" "UE" "\x02" "UN", // R + "\x05" "HoaL*" "\x05" "HoRe*" "\x05" "KyWaY" "\x06" "PrinG*" "\x04" "PUR*" + "\x06" "Quare*" "\x06" "TAtion" "\x08" "TRAvenue" "\x05" "TReaM" + "\x06" "Treet*" "\x05" "uMmiT" "\x07" "PeeDWaY", // S + "\x06" "ERrace" "\x09" "hRoughWaY" "\x04" "RaCE" "\x04" "RAcK" "\x09" "RaFficwaY" + "\x04" "RaiL" "\x05" "UNneL" "\x07" "urnPiKE", // T + "\x08" "nderPASs" "\x05" "Nion*", // U + "\x06" "aLleY*" "\x06" "IAduct" "\x04" "ieW*" "\x07" "iLlaGe*" "\x04" "iLle" + "\x04" "ISta", // V + "\x04" "ALK*" "\x03" "ALL" "\x03" "AY*" "\x04" "eLl*", // W + "\x03" "ING" "\x02" "RD", // X + }; + + static char const* const longStateNames[] = { + "\x8e" "la" "\x85" "bama" "\x02" "\x84" "ska" "\x01" "\x8f" "merican Samoa" "\x04" + "\x91" "r" "\x86" "izona" "\x05" "\x87" "kansas" "\x03", + NULL, + "\x8b" "alifornia" "\x06" "\x95" "o" "\x87" "lorado" "\x07" "\x8a" "nnecticut" "\x08", + "\x89" "elaware" "\x0a" "\x95" "istrict of Columbia" "\x09", + NULL, + "\x9f" "ederated States of Micronesia" "\x0c" "\x88" "lorida" "\x0b", + "\x85" "uam" "\x0e" "\x88" "eorgia" "\x0d", + "\x87" "awaii" "\x0f", + "\x86" "daho" "\x11" "\x89" "llinois" "\x12" "\x88" "ndiana" "\x13" "\x85" + "owa" "\x10", + NULL, + "\x87" "ansas" "\x14" "\x89" "entucky" "\x15", + "\x8a" "ouisiana" "\x16", + "\x86" "aine" "\x19" "\x99" "ar" "\x8e" "shall Islands" "\x1a" "\x86" "yland" "\x18" + "\x8e" "assachusetts" "\x17" "\x93" "i" "\x87" "chigan" "\x1b" + "\x88" "nnesota" "\x1c" "\x93" "iss" "\x88" "issippi" "\x1f" "\x85" + "ouri" "\x1d" "\x88" "ontana" "\x20", + "\x90" "e" "\x87" "braska" "\x23" "\x85" "vada" "\x27" "\xa5" "ew " "\x8a" + "Hampshire" "\x24" "\x87" "Jersey" "\x25" "\x87" "Mexico" "\x26" + "\x85" "York" "\x28" "\x98" "orth " "\x89" "Carolina" "\x21" "\x87" + "Dakota" "\x22" "\x99" "orthern Mariana Islands" "\x1e", + "\x85" "hio" "\x29" "\x89" "klahoma" "\x2a" "\x87" "regon" "\x2b", + "\x86" "alau" "\x2e" "\x8d" "ennsylvania" "\x2c" "\x8c" "uerto Rico" "\x2d", + NULL, + "\x8d" "hode Island" "\x2f", + "\x98" "outh " "\x89" "Carolina" "\x30" "\x87" "Dakota" "\x31", + "\x90" "e" "\x88" "nnessee" "\x32" "\x84" "xas" "\x33", + "\x85" "tah" "\x34", + "\x88" "ermont" "\x37" "\x94" "irgin" "\x89" " Islands" "\x36" "\x83" "ia" "\x35", + "\x8b" "ashington" "\x38" "\x8e" "est Virginia" "\x3a" "\x8a" "isconsin" "\x39" + "\x88" "yoming" "\x3b" + }; + +#if 0 // DEBUG_NAV_UI + static char const* const progressNames[] = { + "NO_ADDRESS", + "SKIP_TO_SPACE", + "HOUSE_NUMBER", + "NUMBER_TRAILING_SPACE", + "ADDRESS_LINE", + "STATE_NAME", + "SECOND_HALF", + "ZIP_CODE", + "PLUS_4", + "FIND_STREET" + }; +#endif + // strategy: US only support at first + // look for a 1 - 5 digit number for a street number (no support for 'One Microsoft Way') + // ignore if preceded by '#', Suite, Ste, Rm + // look for two or more words (up to 5? North Frank Lloyd Wright Blvd) + // note: "The Circle at North Hills St." has six words, and a lower 'at' -- allow at, by, of, in, the, and, ... ? + // if a word starts with a lowercase letter, no match + // allow: , . - # / (for 1/2) ' " + // don't look for street name type yet + // look for one or two delimiters to represent possible 2nd addr line and city name + // look for either full state name, or state two letters, and/or zip code (5 or 9 digits) + // now look for street suffix, either in full or abbreviated form, with optional 's' if there's an asterisk + + s->mCurrentStart = chars; + s->mEnd = chars + length; + int candIndex = 0; + bool retryState; + bool mustBeAllUpper = false; + bool secondHalf = false; + chars -= 1; + UChar ch = s->mCurrent; + while (++chars <= s->mEnd) { + UChar prior = ch; + ch = chars < s->mEnd ? *chars : ' '; + switch (s->mProgress) { + case NO_ADDRESS: + if (WTF::isASCIIDigit(ch) == false) { + if (ch != 'O') // letter 'O', not zero + continue; + if (s->mEnd - chars < 3) + continue; + prior = *++chars; + ch = *++chars; + if ((prior != 'n' || ch != 'e') && (prior != 'N' || ch != 'E')) + continue; + if (isUnicodeSpace(*++chars) == false) + continue; + s->mProgress = ADDRESS_LINE; + s->mStartResult = chars - 3 - s->mCurrentStart; + continue; + } + if (isUnicodeSpace(prior) == false) { + s->mProgress = SKIP_TO_SPACE; + continue; + } + s->mNumberCount = 1; + s->mProgress = HOUSE_NUMBER; + s->mStartResult = chars - s->mCurrentStart; + continue; + case SKIP_TO_SPACE: + if (isUnicodeSpace(ch) == false) + continue; + break; + case HOUSE_NUMBER: + if (WTF::isASCIIDigit(ch)) { + if (++s->mNumberCount >= 6) + s->mProgress = SKIP_TO_SPACE; + continue; + } + if (WTF::isASCIIUpper(ch)) { // allow one letter after house number, e.g. 12A SKOLFIELD PL, HARPSWELL, ME 04079 + if (WTF::isASCIIDigit(prior) == false) + s->mProgress = SKIP_TO_SPACE; + continue; + } + if (ch == '-') { + if (s->mNumberCount > 0) { // permit 21-23 ELM ST + ++s->mNumberCount; + continue; + } + } + s->mNumberCount = 0; + s->mProgress = NUMBER_TRAILING_SPACE; + case NUMBER_TRAILING_SPACE: + if (isUnicodeSpace(ch)) + continue; + if (0 && WTF::isASCIIDigit(ch)) { + s->mNumberCount = 1; + s->mProgress = HOUSE_NUMBER; + s->mStartResult = chars - s->mCurrentStart; + continue; + } + if (WTF::isASCIIDigit(ch) == false && WTF::isASCIIUpper(ch) == false) + break; + s->mProgress = ADDRESS_LINE; + case ADDRESS_LINE: + if (WTF::isASCIIAlpha(ch) || ch == '\'' || ch == '-' || ch == '&' || ch == '(' || ch == ')') { + if (++s->mLetterCount > 1) { + s->mNumberWords &= ~(1 << s->mWordCount); + continue; + } + if (s->mNumberCount >= 5) + break; +// FIXME: the test below was added to give up on a non-address, but it +// incorrectly discards addresses where the house number is in one node +// and the street name is in the next; I don't recall what the failing case +// is that suggested this fix. +// if (s->mWordCount == 0 && s->mContinuationNode) +// return FOUND_NONE; + s->newWord(baseChars, chars); + if (WTF::isASCIILower(ch) && s->mNumberCount == 0) + s->mFirstLower = chars; + s->mNumberCount = 0; + if (WTF::isASCIILower(ch) || (WTF::isASCIIAlpha(ch) == false && ch != '-')) + s->mNumberWords &= ~(1 << s->mWordCount); + s->mUnparsed = true; + continue; + } else if (s->mLetterCount >= MAX_PLACE_NAME_LENGTH) { + break; + } else if (s->mFirstLower != NULL) { + if (s->mCaseInsensitive) + goto resetWord; + size_t length = chars - s->mFirstLower; + if (length > 3) + break; + if (length == 3 && strCharCmp("and" "the", s->mFirstLower, 3, 2) == false) + break; + if (length == 2 && strCharCmp("at" "by" "el" "in" "of", s->mFirstLower, 2, 5) == false) + break; + goto resetWord; + } + if (ch == ',' || ch == '*') { // delimits lines + // asterisk as delimiter: http://www.sa.sc.edu/wellness/members.html + if (++s->mLineCount > 5) + break; + goto lookForState; + } + if (isUnicodeSpace(ch) || prior == '-') { + lookForState: + if (s->mUnparsed == false) + continue; + const UChar* candidate = s->mWords[s->mWordCount]; + UChar firstLetter = candidate[0]; + if (WTF::isASCIIUpper(firstLetter) == false && WTF::isASCIIDigit(firstLetter) == false) + goto resetWord; + s->mWordCount++; + if ((s->mWordCount == 2 && s->mNumberWords == 3 && WTF::isASCIIDigit(s->mWords[1][1])) || // choose second of 8888 333 Main + (s->mWordCount >= sizeof(s->mWords) / sizeof(s->mWords[0]) - 1)) { // subtract 1 since state names may have two parts + // search for simple number already stored since first potential house # didn't pan out + if (s->mNumberWords == 0) + break; + int shift = 0; + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + s->mNumberWords >>= ++shift; + if (s->mBases[0] != s->mBases[shift]) // if we're past the original node, bail + break; + s->shiftWords(shift); + s->mStartResult = s->mWords[0] - s->mStarts[0]; + s->mWordCount -= shift; + // FIXME: need to adjust lineCount to account for discarded delimiters + } + if (s->mWordCount < 4) + goto resetWord; + firstLetter -= 'A'; + if (firstLetter > 'W' - 'A') + goto resetWord; + UChar secondLetter = candidate[1]; + if (prior == '-') + s->mLetterCount--; // trim trailing dashes, to accept CA-94043 + if (s->mLetterCount == 2) { + secondLetter -= 'A'; + if (secondLetter > 'Z' - 'A') + goto resetWord; + if ((stateTwoLetter[firstLetter] & 1 << secondLetter) != 0) { + // special case to ignore 'et al' + if (strCharCmp("ET", s->mWords[s->mWordCount - 2], 2, 1) == false) { + s->mStateWord = s->mWordCount - 1; + s->mZipHint = firstIndex[firstLetter] + + bitcount(stateTwoLetter[firstLetter] & ((1 << secondLetter) - 1)); + goto foundStateName; + } + } + goto resetWord; + } + s->mStates = longStateNames[firstLetter]; + if (s->mStates == NULL) + goto resetWord; + mustBeAllUpper = false; + s->mProgress = STATE_NAME; + unsigned char section = s->mStates[0]; + ASSERT(section > 0x80); + s->mSectionLength = section & 0x7f; + candIndex = 1; + secondHalf = false; + s->mStateWord = s->mWordCount - 1; + goto stateName; + } + if (WTF::isASCIIDigit(ch)) { + if (s->mLetterCount == 0) { + if (++s->mNumberCount > 1) + continue; + if (s->mWordCount == 0 && s->mContinuationNode) + return FOUND_NONE; + s->newWord(baseChars, chars); + s->mNumberWords |= 1 << s->mWordCount; + s->mUnparsed = true; + } + continue; + } + if (ch == '.') { // optionally can follow letters + if (s->mLetterCount == 0) + break; + if (s->mNumberCount > 0) + break; + continue; + } + if (ch == '/') // between numbers (1/2) between words (12 Main / Ste 4d) + goto resetWord; + if (ch == '#') // can precede numbers, allow it to appear randomly + goto resetWord; + if (ch == '"') // sometimes parts of addresses are quoted (FIXME: cite an example here) + continue; + break; + case SECOND_HALF: + if (WTF::isASCIIAlpha(ch)) { + if (s->mLetterCount == 0) { + s->newWord(baseChars, chars); + s->mWordCount++; + } + s->mLetterCount++; + continue; + } + if (WTF::isASCIIDigit(ch) == false) { + if (s->mLetterCount > 0) { + s->mProgress = STATE_NAME; + candIndex = 0; + secondHalf = true; + goto stateName; + } + continue; + } + s->mProgress = ADDRESS_LINE; + goto resetState; + case STATE_NAME: + stateName: + // pick up length of first section + do { + int stateIndex = 1; + int skip = 0; + int prefix = 0; + bool subStr = false; + do { + unsigned char match = s->mStates[stateIndex]; + if (match >= 0x80) { + if (stateIndex == s->mSectionLength) + break; + subStr = true; + // if (skip > 0) + // goto foundStateName; + prefix = candIndex; + skip = match & 0x7f; + match = s->mStates[++stateIndex]; + } + UChar candChar = s->mWords[s->mWordCount - 1][candIndex]; + if (mustBeAllUpper && WTF::isASCIILower(candChar)) + goto skipToNext; + if (match != candChar) { + if (match != WTF::toASCIILower(candChar)) { + skipToNext: + if (subStr == false) + break; + if (stateIndex == s->mSectionLength) { + if (secondHalf) { + s->mProgress = ADDRESS_LINE; + goto resetState; + } + break; + } + stateIndex += skip; + skip = 0; + candIndex = prefix; + continue; // try next substring + } + mustBeAllUpper = true; + } + int nextindex = stateIndex + 1; + if (++candIndex >= s->mLetterCount && s->mStates[nextindex] == ' ') { + s->mProgress = SECOND_HALF; + s->mStates += nextindex; + s->mSectionLength -= nextindex; + goto resetWord; + } + if (nextindex + 1 == s->mSectionLength || skip == 2) { + s->mZipHint = s->mStates[nextindex] - 1; + goto foundStateName; + } + stateIndex += 1; + skip -= 1; + } while (true); + s->mStates += s->mSectionLength; + ASSERT(s->mStates[0] == 0 || (unsigned) s->mStates[0] > 0x80); + s->mSectionLength = s->mStates[0] & 0x7f; + candIndex = 1; + subStr = false; + } while (s->mSectionLength != 0); + s->mProgress = ADDRESS_LINE; + goto resetState; + foundStateName: + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + s->mProgress = ZIP_CODE; + // a couple of delimiters is an indication that the state name is good + // or, a non-space / non-alpha-digit is also good + s->mZipDelimiter = s->mLineCount > 2 + || isUnicodeSpace(ch) == false + || chars == s->mEnd; + if (WTF::isASCIIDigit(ch)) + s->mZipStart = chars; + goto resetState; + case ZIP_CODE: + if (WTF::isASCIIDigit(ch)) { + int count = ++s->mNumberCount; + if (count == 1) { + if (WTF::isASCIIDigit(prior)) + ++s->mNumberCount; + else + s->mZipStart = chars; + } + if (count <= 9) + continue; + } else if (isUnicodeSpace(ch)) { + if (s->mNumberCount == 0) { + s->mZipDelimiter = true; // two spaces delimit state name + continue; + } + } else if (ch == '-') { + if (s->mNumberCount == 5 && validZip(s->mZipHint, s->mZipStart)) { + s->mNumberCount = 0; + s->mProgress = PLUS_4; + continue; + } + if (s->mNumberCount == 0) + s->mZipDelimiter = true; + } else if (WTF::isASCIIAlpha(ch) == false) + s->mZipDelimiter = true; + else { + if (s->mLetterCount == 0) { + s->newWord(baseChars, chars); + s->mUnparsed = true; + } + ++s->mLetterCount; + } + if (s->mNumberCount == 5 || s->mNumberCount == 9) { + if (validZip(s->mZipHint, s->mZipStart) == false) + goto noZipMatch; + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + } else if (s->mZipDelimiter == false) { + noZipMatch: + --chars; + s->mProgress = ADDRESS_LINE; + continue; + } + s->mProgress = FIND_STREET; + goto findStreet; + case PLUS_4: + if (WTF::isASCIIDigit(ch)) { + if (++s->mNumberCount <= 4) + continue; + } + if (isUnicodeSpace(ch)) { + if (s->mNumberCount == 0) + continue; + } + if (s->mNumberCount == 4) { + if (WTF::isASCIIAlpha(ch) == false) { + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + } + } else if (s->mNumberCount != 0) + break; + s->mProgress = FIND_STREET; + case FIND_STREET: + findStreet: + retryState = false; + for (int wordsIndex = s->mStateWord - 1; wordsIndex >= 0; --wordsIndex) { + const UChar* test = s->mWords[wordsIndex]; + UChar letter = test[0]; + letter -= 'A'; + if (letter > 'X' - 'A') + continue; + const char* names = longStreetNames[letter]; + if (names == NULL) + continue; + int offset; + while ((offset = *names++) != 0) { + int testIndex = 1; + bool abbr = false; + for (int idx = 0; idx < offset; idx++) { + char nameLetter = names[idx]; + char testUpper = WTF::toASCIIUpper(test[testIndex]); + if (nameLetter == '*') { + if (testUpper == 'S') + testIndex++; + break; + } + bool fullOnly = WTF::isASCIILower(nameLetter); + nameLetter = WTF::toASCIIUpper(nameLetter); + if (testUpper == nameLetter) { + if (abbr && fullOnly) + goto nextTest; + testIndex++; + continue; + } + if (fullOnly == false) + goto nextTest; + abbr = true; + } + letter = &test[testIndex] < s->mEnds[wordsIndex] ? + test[testIndex] : ' '; + if (WTF::isASCIIAlpha(letter) == false && WTF::isASCIIDigit(letter) == false) { + if (s->mNumberWords != 0) { + int shift = 0; + int wordReduction = -1; + do { + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + if (shift > wordsIndex) + break; + wordReduction = shift; + } while (s->mNumberWords >> ++shift != 0); + if (wordReduction >= 0) { + if (s->mContinuationNode) { + if (retryState) + break; + return FOUND_NONE; + } + s->mStartResult = s->mWords[wordReduction] - s->mStarts[wordReduction]; + } + } + if (wordsIndex != s->mStateWord - 1) + return FOUND_COMPLETE; + retryState = true; + } + nextTest: + names += offset; + } + } + if (retryState) { + s->mProgress = ADDRESS_LINE; + s->mStates = NULL; + continue; + } + if (s->mNumberWords != 0) { + unsigned shift = 0; + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + s->mNumberWords >>= ++shift; + if (s->mBases[0] != s->mBases[shift]) + return FOUND_NONE; + s->shiftWords(shift); + s->mStartResult = s->mWords[0] - s->mStarts[0]; + s->mWordCount -= shift; + s->mProgress = ADDRESS_LINE; + --chars; + continue; + } + break; + } + if (s->mContinuationNode) + return FOUND_NONE; + s->mProgress = NO_ADDRESS; + s->mWordCount = s->mLineCount = 0; + s->mNumberWords = 0; + resetState: + s->mStates = NULL; + resetWord: + s->mNumberCount = s->mLetterCount = 0; + s->mFirstLower = NULL; + s->mUnparsed = false; + } + s->mCurrent = ch; + return s->mProgress == NO_ADDRESS ? FOUND_NONE : FOUND_PARTIAL; +} + +// Recogize common email patterns only. Currently has lots of state, walks text forwards and backwards -- will be +// a real challenge to adapt to walk text across multiple nodes, I imagine +// FIXME: it's too hard for the caller to call these incrementally -- it's probably best for this to +// either walk the node tree directly or make a callout to get the next or previous node, if there is one +// walking directly will avoid adding logic in caller to track the multiple partial or full nodes that compose this +// text pattern. +CacheBuilder::FoundState CacheBuilder::FindPartialEMail(const UChar* chars, unsigned length, + FindState* s) +{ + // the following tables were generated by tests/browser/focusNavigation/BrowserDebug.cpp + // hand-edit at your own risk + static const int domainTwoLetter[] = { + 0x02df797c, // a followed by: [cdefgilmnoqrstuwxz] + 0x036e73fb, // b followed by: [abdefghijmnorstvwyz] + 0x03b67ded, // c followed by: [acdfghiklmnorsuvxyz] + 0x02005610, // d followed by: [ejkmoz] + 0x001e00d4, // e followed by: [ceghrstu] + 0x00025700, // f followed by: [ijkmor] + 0x015fb9fb, // g followed by: [abdefghilmnpqrstuwy] + 0x001a3400, // h followed by: [kmnrtu] + 0x000f7818, // i followed by: [delmnoqrst] + 0x0000d010, // j followed by: [emop] + 0x0342b1d0, // k followed by: [eghimnprwyz] + 0x013e0507, // l followed by: [abcikrstuvy] + 0x03fffccd, // m followed by: [acdghklmnopqrstuvwxyz] + 0x0212c975, // n followed by: [acefgilopruz] + 0x00001000, // o followed by: [m] + 0x014e3cf1, // p followed by: [aefghklmnrstwy] + 0x00000001, // q followed by: [a] + 0x00504010, // r followed by: [eouw] + 0x032a7fdf, // s followed by: [abcdeghijklmnortvyz] + 0x026afeec, // t followed by: [cdfghjklmnoprtvwz] + 0x03041441, // u followed by: [agkmsyz] + 0x00102155, // v followed by: [aceginu] + 0x00040020, // w followed by: [fs] + 0x00000000, // x + 0x00180010, // y followed by: [etu] + 0x00401001, // z followed by: [amw] + }; + + static char const* const longDomainNames[] = { + "\x03" "ero" "\x03" "rpa", // aero, arpa + "\x02" "iz", // biz + "\x02" "at" "\x02" "om" "\x03" "oop", // cat, com, coop + NULL, // d + "\x02" "du", // edu + NULL, // f + "\x02" "ov", // gov + NULL, // h + "\x03" "nfo" "\x02" "nt", // info, int + "\x03" "obs", // jobs + NULL, // k + NULL, // l + "\x02" "il" "\x03" "obi" "\x05" "useum", // mil, mobi, museum + "\x03" "ame" "\x02" "et", // name, net + "\x02" "rg", // , org + "\x02" "ro", // pro + NULL, // q + NULL, // r + NULL, // s + "\x05" "ravel", // travel + NULL, // u + NULL, // v + NULL, // w + NULL, // x + NULL, // y + NULL, // z + }; + + const UChar* start = chars; + const UChar* end = chars + length; + while (chars < end) { + UChar ch = *chars++; + if (ch != '@') + continue; + const UChar* atLocation = chars - 1; + // search for domain + ch = *chars++ | 0x20; // convert uppercase to lower + if (ch < 'a' || ch > 'z') + continue; + while (chars < end) { + ch = *chars++; + if (IsDomainChar(ch) == false) + goto nextAt; + if (ch != '.') + continue; + UChar firstLetter = *chars++ | 0x20; // first letter of the domain + if (chars >= end) + return FOUND_NONE; // only one letter; must be at least two + firstLetter -= 'a'; + if (firstLetter > 'z' - 'a') + continue; // non-letter followed '.' + int secondLetterMask = domainTwoLetter[firstLetter]; + ch = *chars | 0x20; // second letter of the domain + ch -= 'a'; + if (ch >= 'z' - 'a') + continue; + bool secondMatch = (secondLetterMask & 1 << ch) != 0; + const char* wordMatch = longDomainNames[firstLetter]; + int wordIndex = 0; + while (wordMatch != NULL) { + int len = *wordMatch++; + char match; + do { + match = wordMatch[wordIndex]; + if (match < 0x20) + goto foundDomainStart; + if (chars[wordIndex] != match) + break; + wordIndex++; + } while (true); + wordMatch += len; + if (*wordMatch == '\0') + break; + wordIndex = 0; + } + if (secondMatch) { + wordIndex = 1; + foundDomainStart: + chars += wordIndex; + if (chars < end) { + ch = *chars; + if (ch != '.') { + if (IsDomainChar(ch)) + goto nextDot; + } else if (chars + 1 < end && IsDomainChar(chars[1])) + goto nextDot; + } + // found domain. Search backwards from '@' for beginning of email address + s->mEndResult = chars - start; + chars = atLocation; + if (chars <= start) + goto nextAt; + ch = *--chars; + if (ch == '.') + goto nextAt; // mailbox can't end in period + do { + if (IsMailboxChar(ch) == false) { + chars++; + break; + } + if (chars == start) + break; + ch = *--chars; + } while (true); + UChar firstChar = *chars; + if (firstChar == '.' || firstChar == '@') // mailbox can't start with period or be empty + goto nextAt; + s->mStartResult = chars - start; + return FOUND_COMPLETE; + } + nextDot: + ; + } +nextAt: + chars = atLocation + 1; + } + return FOUND_NONE; +} + +#define PHONE_PATTERN "(200) /-.\\ 100 -. 0000" // poor man's regex: parens optional, any one of punct, digit smallest allowed + +CacheBuilder::FoundState CacheBuilder::FindPartialNumber(const UChar* chars, unsigned length, + FindState* s) +{ + char* pattern = s->mPattern; + UChar* store = s->mStorePtr; + const UChar* start = chars; + const UChar* end = chars + length; + const UChar* lastDigit = NULL; + do { + bool initialized = s->mInitialized; + while (chars < end) { + if (initialized == false) { + s->mBackTwo = s->mBackOne; + s->mBackOne = s->mCurrent; + } + UChar ch = s->mCurrent = *chars; + do { + char patternChar = *pattern; + switch (patternChar) { + case '2': + if (initialized == false) { + s->mStartResult = chars - start; + initialized = true; + } + case '0': + case '1': + if (ch < patternChar || ch > '9') + goto resetPattern; + *store++ = ch; + pattern++; + lastDigit = chars; + goto nextChar; + case '\0': + if (WTF::isASCIIDigit(ch) == false) { + *store = '\0'; + goto checkMatch; + } + goto resetPattern; + case ' ': + if (ch == patternChar) + goto nextChar; + break; + case '(': + if (ch == patternChar) { + s->mStartResult = chars - start; + initialized = true; + s->mOpenParen = true; + } + goto commonPunctuation; + case ')': + if ((ch == patternChar) ^ s->mOpenParen) + goto resetPattern; + default: + commonPunctuation: + if (ch == patternChar) { + pattern++; + goto nextChar; + } + } + } while (++pattern); // never false + nextChar: + chars++; + } + break; +resetPattern: + if (s->mContinuationNode) + return FOUND_NONE; + FindResetNumber(s); + pattern = s->mPattern; + store = s->mStorePtr; + } while (++chars < end); +checkMatch: + if (WTF::isASCIIDigit(s->mBackOne != '1' ? s->mBackOne : s->mBackTwo)) + return FOUND_NONE; + *store = '\0'; + s->mStorePtr = store; + s->mPattern = pattern; + s->mEndResult = lastDigit - start + 1; + char pState = pattern[0]; + return pState == '\0' ? FOUND_COMPLETE : pState == '(' || (WTF::isASCIIDigit(pState) && WTF::isASCIIDigit(pattern[-1])) ? + FOUND_NONE : FOUND_PARTIAL; +} + +CacheBuilder::FoundState CacheBuilder::FindPhoneNumber(const UChar* chars, unsigned length, + int* start, int* end) +{ + FindState state; + FindReset(&state); + FoundState result = FindPartialNumber(chars, length, &state); + *start = state.mStartResult; + *end = state.mEndResult; + return result; +} + +void CacheBuilder::FindReset(FindState* state) +{ + memset(state, 0, sizeof(FindState)); + state->mCurrent = ' '; + FindResetNumber(state); +} + +void CacheBuilder::FindResetNumber(FindState* state) +{ + state->mOpenParen = false; + state->mPattern = (char*) PHONE_PATTERN; + state->mStorePtr = state->mStore; +} + +IntRect CacheBuilder::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->getRect(image); + } + } + } + } + } + return IntRect(); +} + +void CacheBuilder::GetGlobalOffset(Node* node, int* x, int * y) +{ + GetGlobalOffset(node->document()->frame(), x, y); +} + +void CacheBuilder::GetGlobalOffset(Frame* frame, int* x, int* y) +{ +// TIMER_PROBE(__FUNCTION__); + ASSERT(x); + ASSERT(y); + *x = 0; + *y = 0; + if (!frame->view()) + return; + Frame* parent; + while ((parent = frame->tree()->parent()) != NULL) { + const WebCore::IntRect& rect = frame->view()->platformWidget()->getBounds(); + *x += rect.x(); + *y += rect.y(); + frame = parent; + } + // TIMER_PROBE_END(); +} + +Frame* CacheBuilder::HasFrame(Node* node) +{ + RenderObject* renderer = node->renderer(); + if (renderer == NULL) + return NULL; + if (renderer->isWidget() == false) + return NULL; + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (widget == NULL) + return NULL; + if (widget->isFrameView() == false) + return NULL; + return static_cast<FrameView*>(widget)->frame(); +} + +bool CacheBuilder::HasOverOrOut(Node* node) +{ + // eventNames are thread-local data, I avoid using 'static' variable here. + AtomicString eventTypes[2] = { + eventNames().mouseoverEvent, + eventNames().mouseoutEvent + }; + + return NodeHasEventListeners(node, eventTypes, 2); +} + +bool CacheBuilder::HasTriggerEvent(Node* node) +{ + AtomicString eventTypes[5] = { + eventNames().clickEvent, + eventNames().mousedownEvent, + eventNames().mouseupEvent, + eventNames().keydownEvent, + eventNames().keyupEvent + }; + + return NodeHasEventListeners(node, eventTypes, 5); +} + +// #define EMAIL_PATTERN "x@y.d" // where 'x' is letters, numbers, and '-', '.', '_' ; 'y' is 'x' without the underscore, and 'd' is a valid domain +// - 0x2D . 0x2E 0-9 0x30-39 A-Z 0x41-5A _ 0x5F a-z 0x61-7A + +bool CacheBuilder::IsDomainChar(UChar ch) +{ + static const unsigned body[] = {0x03ff6000, 0x07fffffe, 0x07fffffe}; // 0-9 . - A-Z a-z + ch -= 0x20; + if (ch > 'z' - 0x20) + return false; + return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; +} + +bool CacheBuilder::isFocusableText(NodeWalk* walk, bool more, Node* node, + CachedNodeType* type, String* exported) const +{ + Text* textNode = static_cast<Text*>(node); + StringImpl* string = textNode->dataImpl(); + const UChar* baseChars = string->characters(); +// const UChar* originalBase = baseChars; + int length = string->length(); + int index = 0; + while (index < length && isUnicodeSpace(baseChars[index])) + index++; + if (index >= length) + return false; + if (more == false) { + walk->mStart = 0; + walk->mEnd = 0; + walk->mFinalNode = node; + walk->mLastInline = NULL; + } + // starting with this node, search forward for email, phone number, and address + // if any of the three is found, track it so that the remaining can be looked for later + FoundState state = FOUND_NONE; + RenderText* renderer = (RenderText*) node->renderer(); + bool foundBetter = false; + InlineTextBox* baseInline = walk->mLastInline != NULL ? walk->mLastInline : + renderer->firstTextBox(); + if (baseInline == NULL) + return false; + int start = walk->mEnd; + InlineTextBox* saveInline; + int baseStart, firstStart = start; + saveInline = baseInline; + baseStart = start; + for (CachedNodeType checkType = ADDRESS_CACHEDNODETYPE; + checkType <= PHONE_CACHEDNODETYPE; + checkType = static_cast<CachedNodeType>(checkType + 1)) + { + if ((1 << (checkType - 1) & mAllowableTypes) == 0) + continue; + InlineTextBox* inlineTextBox = baseInline; + FindState findState; + FindReset(&findState); + start = baseStart; + if (checkType == ADDRESS_CACHEDNODETYPE) { + findState.mBases[0] = baseChars; + findState.mWords[0] = baseChars + start; + findState.mStarts[0] = baseChars + start; + } + Node* lastPartialNode = NULL; + int lastPartialEnd = -1; + bool lastPartialMore = false; + bool firstPartial = true; + InlineTextBox* lastPartialInline = NULL; + do { + do { + const UChar* chars = baseChars + start; + length = inlineTextBox == NULL ? 0 : + inlineTextBox->end() - start + 1; + bool wasInitialized = findState.mInitialized; + switch (checkType) { + case ADDRESS_CACHEDNODETYPE: + state = FindPartialAddress(baseChars, chars, length, &findState); + break; + case EMAIL_CACHEDNODETYPE: + state = FindPartialEMail(chars, length, &findState); + break; + case PHONE_CACHEDNODETYPE: + state = FindPartialNumber(chars, length, &findState); + break; + default: + ASSERT(0); + } + findState.mInitialized = state != FOUND_NONE; + if (wasInitialized != findState.mInitialized) + firstStart = start; + if (state == FOUND_PARTIAL) { + lastPartialNode = node; + lastPartialEnd = findState.mEndResult + start; + lastPartialMore = firstPartial && + lastPartialEnd < (int) string->length(); + firstPartial = false; + lastPartialInline = inlineTextBox; + findState.mContinuationNode = true; + } else if (state == FOUND_COMPLETE) { + if (foundBetter == false || walk->mStart > findState.mStartResult) { + walk->mStart = findState.mStartResult + firstStart; + if (findState.mEndResult > 0) { + walk->mFinalNode = node; + walk->mEnd = findState.mEndResult + start; + walk->mMore = node == textNode && + walk->mEnd < (int) string->length(); + walk->mLastInline = inlineTextBox; + } else { + walk->mFinalNode = lastPartialNode; + walk->mEnd = lastPartialEnd; + walk->mMore = lastPartialMore; + walk->mLastInline = lastPartialInline; + } + *type = checkType; + if (checkType == PHONE_CACHEDNODETYPE) { + const UChar* store = findState.mStore; + *exported = String(store); + } else { + Node* temp = textNode; + length = 1; + start = walk->mStart; + exported->truncate(0); + do { + Text* tempText = static_cast<Text*>(temp); + StringImpl* string = tempText->dataImpl(); + int end = tempText == walk->mFinalNode ? + walk->mEnd : string->length(); + exported->append(String(string->substring( + start, end - start))); + ASSERT(end > start); + length += end - start + 1; + if (temp == walk->mFinalNode) + break; + start = 0; + do { + temp = temp->traverseNextNode(); + ASSERT(temp); + } while (temp->isTextNode() == false); + // add a space in between text nodes to avoid + // words collapsing together + exported->append(" "); + } while (true); + } + foundBetter = true; + } + goto tryNextCheckType; + } else if (findState.mContinuationNode) + break; + if (inlineTextBox == NULL) + break; + inlineTextBox = inlineTextBox->nextTextBox(); + if (inlineTextBox == NULL) + break; + start = inlineTextBox->start(); + if (state == FOUND_PARTIAL && node == textNode) + findState.mContinuationNode = false; + } while (true); + if (state == FOUND_NONE) + break; + // search for next text node, if any + Text* nextNode; + do { + do { + do { + if (node) + node = node->traverseNextNode(); + if (node == NULL || node->hasTagName(HTMLNames::aTag) + || node->hasTagName(HTMLNames::inputTag) + || node->hasTagName(HTMLNames::textareaTag)) { + if (state == FOUND_PARTIAL && + checkType == ADDRESS_CACHEDNODETYPE && + findState.mProgress == ZIP_CODE && + findState.mNumberCount == 0) { + baseChars = NULL; + inlineTextBox = NULL; + start = 0; + findState.mProgress = FIND_STREET; + goto finalNode; + } + goto tryNextCheckType; + } + } while (node->isTextNode() == false); + nextNode = static_cast<Text*>(node); + renderer = (RenderText*) nextNode->renderer(); + } while (renderer == NULL); + baseInline = renderer->firstTextBox(); + } while (baseInline == NULL); + string = nextNode->dataImpl(); + baseChars = string->characters(); + inlineTextBox = baseInline; + start = inlineTextBox->start(); + finalNode: + findState.mEndResult = 0; + } while (true); +tryNextCheckType: + node = textNode; + baseInline = saveInline; + string = textNode->dataImpl(); + baseChars = string->characters(); + } + if (foundBetter) { + CachedNodeType temp = *type; + switch (temp) { + case ADDRESS_CACHEDNODETYPE: { + static const char geoString[] = "geo:0,0?q="; + exported->insert(String(geoString), 0); + int index = sizeof(geoString) - 1; + String escapedComma("%2C"); + while ((index = exported->find(',', index)) >= 0) + exported->replace(index, 1, escapedComma); + } break; + case EMAIL_CACHEDNODETYPE: { + String encoded = WebCore::encodeWithURLEscapeSequences(*exported); + exported->swap(encoded); + exported->insert(WTF::String("mailto:"), 0); + } break; + case PHONE_CACHEDNODETYPE: + exported->insert(WTF::String("tel:"), 0); + break; + default: + break; + } + return true; + } +noTextMatch: + walk->reset(); + return false; +} + +bool CacheBuilder::IsMailboxChar(UChar ch) +{ + // According to http://en.wikipedia.org/wiki/Email_address + // ! # $ % & ' * + - . / 0-9 = ? + // A-Z ^ _ + // ` a-z { | } ~ + static const unsigned body[] = {0xa3ffecfa, 0xc7fffffe, 0x7fffffff}; + ch -= 0x20; + if (ch > '~' - 0x20) + return false; + return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; +} + +bool CacheBuilder::setData(CachedFrame* cachedFrame) +{ + Frame* frame = FrameAnd(this); + Document* doc = frame->document(); + if (doc == NULL) + return false; + RenderObject* renderer = doc->renderer(); + if (renderer == NULL) + return false; + RenderLayer* layer = renderer->enclosingLayer(); + if (layer == NULL) + return false; + if (layer->width() == 0 || layer->height() == 0) + return false; + if (!frame->view()) + return false; + int x, y; + GetGlobalOffset(frame, &x, &y); + WebCore::IntRect viewBounds = frame->view()->platformWidget()->getBounds(); + if ((x | y) != 0) + viewBounds.setLocation(WebCore::IntPoint(x, y)); + cachedFrame->setLocalViewBounds(viewBounds); + cachedFrame->setContentsSize(layer->width(), layer->height()); + if (cachedFrame->childCount() == 0) + return true; + CachedFrame* lastCachedFrame = cachedFrame->lastChild(); + cachedFrame = cachedFrame->firstChild(); + do { + CacheBuilder* cacheBuilder = Builder((Frame* )cachedFrame->framePointer()); + cacheBuilder->setData(cachedFrame); + } while (cachedFrame++ != lastCachedFrame); + return true; +} + +#if USE(ACCELERATED_COMPOSITING) +void CacheBuilder::TrackLayer(WTF::Vector<LayerTracker>& layerTracker, + RenderObject* nodeRenderer, Node* lastChild, int offsetX, int offsetY) +{ + RenderLayer* layer = nodeRenderer->enclosingLayer(); + RenderLayerBacking* back = layer->backing(); + if (!back) + return; + GraphicsLayer* grLayer = back->graphicsLayer(); + if (back->hasContentsLayer()) + grLayer = back->foregroundLayer(); + if (!grLayer) + return; + LayerAndroid* aLayer = grLayer->platformLayer(); + if (!aLayer) + return; + IntPoint scroll(layer->scrollXOffset(), layer->scrollYOffset()); +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + // If this is an overflow element, track the content layer. + if (layer->hasOverflowScroll() && aLayer->getChild(0)) + aLayer = aLayer->getChild(0)->getChild(0); + if (!aLayer) + return; + // Prevent a crash when scrolling a layer that does not have a parent. + if (layer->stackingContext()) + layer->scrollToOffset(0, 0, false, false); +#endif + layerTracker.grow(layerTracker.size() + 1); + LayerTracker& indexTracker = layerTracker.last(); + indexTracker.mLayer = aLayer; + indexTracker.mRenderLayer = layer; + indexTracker.mBounds = enclosingIntRect(aLayer->bounds()); + // Use the absolute location of the layer as the bounds location. This + // provides the original offset of nodes in the layer so that we can + // translate nodes between their original location and the layer's new + // location. + indexTracker.mBounds.setLocation(layer->absoluteBoundingBox().location()); + indexTracker.mBounds.move(offsetX, offsetY); + indexTracker.mScroll = scroll; + indexTracker.mLastChild = OneAfter(lastChild); + DBG_NAV_LOGD("layer=%p [%d] bounds=(%d,%d,w=%d,h=%d)", aLayer, + aLayer->uniqueId(), indexTracker.mBounds.x(), indexTracker.mBounds.y(), + indexTracker.mBounds.width(), indexTracker.mBounds.height()); +} +#endif + +bool CacheBuilder::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(); + } + DBG_NAV_LOGD("frame=%p valid node=%p invalid\n", matchFrame, matchNode); + return false; + } + Frame* child = startFrame->tree()->firstChild(); + while (child) { + bool result = validNode(child, matchFrame, matchNode); + if (result) + return result; + child = child->tree()->nextSibling(); + } +#if DEBUG_NAV_UI + if (startFrame->tree()->parent() == NULL) + DBG_NAV_LOGD("frame=%p node=%p false\n", matchFrame, matchNode); +#endif + return false; +} + +static int Area(const IntRect& rect) +{ + return rect.width() * rect.height(); +} + +bool CacheBuilder::AddPartRect(IntRect& bounds, int x, int y, + WTF::Vector<IntRect>* result, IntRect* focusBounds) +{ + if (bounds.isEmpty()) + return true; + bounds.move(x, y); + if (bounds.right() <= 0 || bounds.bottom() <= 0) + return true; + IntRect* work = result->begin() - 1; + IntRect* end = result->end(); + while (++work < end) { + if (work->contains(bounds)) + return true; + if (bounds.contains(*work)) { + *work = bounds; + focusBounds->unite(bounds); + return true; + } + if ((bounds.x() != work->x() || bounds.width() != work->width()) && + (bounds.y() != work->y() || bounds.height() != work->height())) + continue; + IntRect test = *work; + test.unite(bounds); + if (Area(test) > Area(*work) + Area(bounds)) + continue; + *work = test; + focusBounds->unite(bounds); + return true; + } + if (result->size() >= MAXIMUM_FOCUS_RING_COUNT) + return false; + result->append(bounds); + if (focusBounds->isEmpty()) + *focusBounds = bounds; + else + focusBounds->unite(bounds); + return true; +} + +bool CacheBuilder::ConstructPartRects(Node* node, const IntRect& bounds, + IntRect* focusBounds, int x, int y, WTF::Vector<IntRect>* result, + int* imageCountPtr) +{ + WTF::Vector<ClipColumnTracker> clipTracker(1); + ClipColumnTracker* baseTracker = clipTracker.data(); // sentinel + bzero(baseTracker, sizeof(ClipColumnTracker)); + if (node->hasChildNodes() && node->hasTagName(HTMLNames::buttonTag) == false + && node->hasTagName(HTMLNames::selectTag) == false) { + // collect all text rects from first to last child + Node* test = node->firstChild(); + Node* last = NULL; + Node* prior = node; + while ((prior = prior->lastChild()) != NULL) + last = prior; + ASSERT(last != NULL); + bool nodeIsAnchor = node->hasTagName(HTMLNames::aTag); + do { + do { + const ClipColumnTracker* lastClip = &clipTracker.last(); + if (test != lastClip->mLastChild) + break; + clipTracker.removeLast(); + } while (true); + RenderObject* renderer = test->renderer(); + if (renderer == NULL) + continue; + EVisibility vis = renderer->style()->visibility(); + if (vis == HIDDEN) + continue; + bool hasClip = renderer->hasOverflowClip(); + size_t clipIndex = clipTracker.size(); + IntRect clipBounds = IntRect(0, 0, INT_MAX, INT_MAX); + if (hasClip || --clipIndex > 0) { + clipBounds = hasClip ? renderer->absoluteBoundingBoxRect() : + clipTracker.at(clipIndex).mBounds; // x, y fixup done by ConstructTextRect + } + if (test->isTextNode()) { + RenderText* renderText = (RenderText*) renderer; + InlineTextBox *textBox = renderText->firstTextBox(); + if (textBox == NULL) + continue; + if (ConstructTextRect((Text*) test, textBox, 0, INT_MAX, + x, y, focusBounds, clipBounds, result) == false) { + return false; + } + continue; + } + if (test->hasTagName(HTMLNames::imgTag)) { + IntRect bounds = test->getRect(); + bounds.intersect(clipBounds); + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + *imageCountPtr += 1; + continue; + } + if (hasClip == false) { + if (nodeIsAnchor && test->hasTagName(HTMLNames::divTag)) { + IntRect bounds = renderer->absoluteBoundingBoxRect(); // x, y fixup done by AddPartRect + int left = bounds.x() + ((RenderBox*)renderer)->paddingLeft() + + ((RenderBox*)renderer)->borderLeft(); + int top = bounds.y() + ((RenderBox*)renderer)->paddingTop() + + ((RenderBox*)renderer)->borderTop(); + int right = bounds.right() - ((RenderBox*)renderer)->paddingRight() + - ((RenderBox*)renderer)->borderRight(); + int bottom = bounds.bottom() - ((RenderBox*)renderer)->paddingBottom() + - ((RenderBox*)renderer)->borderBottom(); + if (left >= right || top >= bottom) + continue; + bounds = IntRect(left, top, right - left, bottom - top); + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + } + continue; + } + Node* lastChild = test->lastChild(); + if (lastChild == NULL) + continue; + clipTracker.grow(clipTracker.size() + 1); + ClipColumnTracker& clip = clipTracker.last(); + clip.mBounds = renderer->absoluteBoundingBoxRect(); // x, y fixup done by ConstructTextRect + clip.mLastChild = OneAfter(lastChild); + clip.mNode = test; + } while (test != last && (test = test->traverseNextNode()) != NULL); + } + if (result->size() == 0 || focusBounds->width() < MINIMUM_FOCUSABLE_WIDTH + || focusBounds->height() < MINIMUM_FOCUSABLE_HEIGHT) { + if (bounds.width() < MINIMUM_FOCUSABLE_WIDTH) + return false; + if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) + return false; + result->append(bounds); + *focusBounds = bounds; + } + return true; +} + +static inline bool isNotSpace(UChar c) +{ + return c <= 0xA0 ? isUnicodeSpace(c) == false : + WTF::Unicode::direction(c) != WTF::Unicode::WhiteSpaceNeutral; +} + +bool CacheBuilder::ConstructTextRect(Text* textNode, + InlineTextBox* textBox, int start, int relEnd, int x, int y, + IntRect* focusBounds, const IntRect& clipBounds, WTF::Vector<IntRect>* result) +{ + RenderText* renderText = (RenderText*) textNode->renderer(); + EVisibility vis = renderText->style()->visibility(); + StringImpl* string = textNode->dataImpl(); + const UChar* chars = string->characters(); + FloatPoint pt = renderText->localToAbsolute(); + do { + int textBoxStart = textBox->start(); + int textBoxEnd = textBoxStart + textBox->len(); + if (textBoxEnd <= start) + continue; + if (textBoxEnd > relEnd) + textBoxEnd = relEnd; + IntRect bounds = textBox->selectionRect((int) pt.x(), (int) pt.y(), + start, textBoxEnd); + bounds.intersect(clipBounds); + if (bounds.isEmpty()) + continue; + bool drawable = false; + for (int index = start; index < textBoxEnd; index++) + if ((drawable |= isNotSpace(chars[index])) != false) + break; + if (drawable && vis != HIDDEN) { + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + } + if (textBoxEnd == relEnd) + break; + } while ((textBox = textBox->nextTextBox()) != NULL); + return true; +} + +bool CacheBuilder::ConstructTextRects(Text* node, int start, + Text* last, int end, int x, int y, IntRect* focusBounds, + const IntRect& clipBounds, WTF::Vector<IntRect>* result) +{ + result->clear(); + *focusBounds = IntRect(0, 0, 0, 0); + do { + RenderText* renderText = (RenderText*) node->renderer(); + int relEnd = node == last ? end : renderText->textLength(); + InlineTextBox *textBox = renderText->firstTextBox(); + if (textBox != NULL) { + do { + if ((int) textBox->end() >= start) + break; + } while ((textBox = textBox->nextTextBox()) != NULL); + if (textBox && ConstructTextRect(node, textBox, start, relEnd, + x, y, focusBounds, clipBounds, result) == false) + return false; + } + start = 0; + do { + if (node == last) + return true; + node = (Text*) node->traverseNextNode(); + ASSERT(node != NULL); + } while (node->isTextNode() == false || node->renderer() == NULL); + } while (true); +} + +} diff --git a/Source/WebKit/android/nav/CacheBuilder.h b/Source/WebKit/android/nav/CacheBuilder.h new file mode 100644 index 0000000..d48a045 --- /dev/null +++ b/Source/WebKit/android/nav/CacheBuilder.h @@ -0,0 +1,297 @@ +/* + * 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. + */ + +#ifndef CacheBuilder_H +#define CacheBuilder_H + +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" +#include "TextDirection.h" +#include <wtf/Forward.h> +#include <wtf/Vector.h> + +#define NAVIGATION_MAX_PHONE_LENGTH 14 + +using namespace WebCore; + +namespace WebCore { + +class ColumnInfo; +class Document; +class Frame; +class HTMLAreaElement; +class InlineTextBox; +class LayerAndroid; +class Node; +class PlatformGraphicsContext; +class RenderBlock; +class RenderFlow; +class RenderLayer; +class RenderObject; +class Text; + +} + +namespace android { + +class CachedFrame; +class CachedNode; +class CachedRoot; + +class CacheBuilder { +public: + enum Direction { + UNINITIALIZED = -1, + LEFT, + RIGHT, + UP, + DOWN, + DIRECTION_COUNT, + UP_DOWN = UP & DOWN, // mask and result + RIGHT_DOWN = RIGHT & DOWN, // mask and result + }; + enum FoundState { + FOUND_NONE, + FOUND_PARTIAL, + FOUND_COMPLETE + }; + CacheBuilder(); + void allowAllTextDetection() { mAllowableTypes = ALL_CACHEDNODE_BITS; } + void buildCache(CachedRoot* root); + static bool ConstructPartRects(Node* node, const IntRect& bounds, + IntRect* focusBounds, int x, int y, WTF::Vector<IntRect>* result, + int* imageCountPtr); + Node* currentFocus() const; + void disallowAddressDetection() { mAllowableTypes = (CachedNodeBits) ( + mAllowableTypes & ~ADDRESS_CACHEDNODE_BIT); } + void disallowEmailDetection() { mAllowableTypes = (CachedNodeBits) ( + mAllowableTypes & ~EMAIL_CACHEDNODE_BIT); } + void disallowPhoneDetection() { mAllowableTypes = (CachedNodeBits) ( + mAllowableTypes & ~PHONE_CACHEDNODE_BIT); } + static FoundState FindAddress(const UChar* , unsigned length, int* start, + int* end, bool caseInsensitive); + static IntRect getAreaRect(const HTMLAreaElement* area); + static void GetGlobalOffset(Frame* , int* x, int * y); + static void GetGlobalOffset(Node* , int* x, int * y); + bool pictureSetDisabled() { return mPictureSetDisabled; } + static bool validNode(Frame* startFrame, void* framePtr, void* nodePtr); +private: + enum AddressProgress { + NO_ADDRESS, + SKIP_TO_SPACE, + HOUSE_NUMBER, + NUMBER_TRAILING_SPACE, + ADDRESS_LINE, + STATE_NAME, + SECOND_HALF, + ZIP_CODE, + PLUS_4, + FIND_STREET + }; + struct NodeWalk { + NodeWalk() { reset(); } + int mStart; + int mEnd; + Node* mFinalNode; + InlineTextBox* mLastInline; + bool mMore; + void reset() { mMore = false; } + }; + struct BoundsPart { + IntRect mRect; + int mStart; + int mEnd; + }; + struct Bounds { + typedef bool (*FindText)(BoundsPart* result, InlineTextBox* , const String& match); + IntRect mNodeBounds; + BoundsPart mPart; + WTF::Vector<BoundsPart> mParts; + char mStore[NAVIGATION_MAX_PHONE_LENGTH + 1]; + int mPartIndex; + Node* mNode; + Node* mFinalNode; + void reset() { mNode = NULL; } + }; + struct FindState { + int mStartResult; + int mEndResult; + const UChar* mCurrentStart; + const UChar* mEnd; + AddressProgress mProgress; + int mNumberCount; + int mLetterCount; + unsigned mWordCount; + int mLineCount; + const UChar* mFirstLower; + const UChar* mZipStart; + const UChar* mBases[16]; // FIXME: random guess, maybe too small, maybe too big + const UChar* mWords[16]; + const UChar* mEnds[16]; + const UChar* mStarts[16]; // text is not necessarily contiguous + const char* mStates; + int mEndWord; + int mStateWord; + int mZipHint; + int mSectionLength; + unsigned mNumberWords; // must contain as many bits as mWords contains elements + char* mPattern; + UChar mStore[NAVIGATION_MAX_PHONE_LENGTH + 1]; + UChar* mStorePtr; + UChar mBackOne; + UChar mBackTwo; + UChar mCurrent; + bool mUnparsed; + bool mZipDelimiter; + bool mOpenParen; + bool mInitialized; + bool mContinuationNode; + bool mCaseInsensitive; + void shiftWords(int shift) { + memmove(mBases, &mBases[shift], (sizeof(mBases) / + sizeof(mBases[0]) - shift) * sizeof(mBases[0])); + memmove(mWords, &mWords[shift], (sizeof(mWords) / + sizeof(mWords[0]) - shift) * sizeof(mWords[0])); + memmove(mEnds, &mEnds[shift], (sizeof(mEnds) / + sizeof(mEnds[0]) - shift) * sizeof(mEnds[0])); + memmove(mStarts, &mStarts[shift], (sizeof(mStarts) / + sizeof(mStarts[0]) - shift) * sizeof(mStarts[0])); + } + void newWord(const UChar* baseChars, const UChar* chars) { + mBases[mWordCount] = baseChars; + mWords[mWordCount] = chars; + mEnds[mWordCount] = mEnd; + mStarts[mWordCount] = mCurrentStart; + } + }; + struct Tracker { + Node* mLastChild; + }; + struct ClipColumnTracker : Tracker { + Node* mNode; + IntRect mBounds; + ColumnInfo* mColumnInfo; + int mColumnGap; + TextDirection mDirection; + bool mHasClip; + }; + struct LayerTracker : Tracker { + LayerAndroid* mLayer; + RenderLayer* mRenderLayer; + IntRect mBounds; + IntPoint mScroll; + ~LayerTracker(); + }; + struct TabIndexTracker : Tracker { + int mTabIndex; + }; + struct FocusTracker : TabIndexTracker { + int mCachedNodeIndex; + bool mSomeParentTakesFocus; + }; + void adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds, RenderBlock*); + static bool AddPartRect(IntRect& bounds, int x, int y, + WTF::Vector<IntRect>* result, IntRect* focusBounds); + static bool AnyIsClick(Node* node); + static bool AnyChildIsClick(Node* node); + static bool NodeHasEventListeners(Node* node, AtomicString* eventTypes, int length); + void BuildFrame(Frame* root, Frame* frame, + CachedRoot* cachedRoot, CachedFrame* cachedFrame); + bool CleanUpContainedNodes(CachedRoot* cachedRoot, CachedFrame* cachedFrame, + const FocusTracker* last, int lastChildIndex); + static bool ConstructTextRect(Text* textNode, + InlineTextBox* textBox, int start, int relEnd, int x, int y, + IntRect* focusBounds, const IntRect& clip, WTF::Vector<IntRect>* result); + static bool ConstructTextRects(Text* node, int start, + Text* last, int end, int x, int y, IntRect* focusBounds, + const IntRect& clip, WTF::Vector<IntRect>* result); + static FoundState FindPartialAddress(const UChar* , const UChar* , unsigned length, FindState* ); + static FoundState FindPartialEMail(const UChar* , unsigned length, FindState* ); + static FoundState FindPartialNumber(const UChar* , unsigned length, FindState* ); + static FoundState FindPhoneNumber(const UChar* chars, unsigned length, int* start, int* end); + static void FindReset(FindState* ); + static void FindResetNumber(FindState* ); + static Frame* FrameAnd(CacheBuilder* focusNav); + static Frame* FrameAnd(const CacheBuilder* focusNav); + static CacheBuilder* Builder(Frame* ); + static Frame* HasFrame(Node* ); + static bool HasOverOrOut(Node* ); + static bool HasTriggerEvent(Node* ); + static bool IsDomainChar(UChar ch); + bool isFocusableText(NodeWalk* , bool oldMore, Node* , CachedNodeType* type, + String* exported) const; //returns true if it is focusable + static bool IsMailboxChar(UChar ch); + static bool IsRealNode(Frame* , Node* ); + int overlap(int left, int right); // returns distance scale factor as 16.16 scalar + bool setData(CachedFrame* ); +#if USE(ACCELERATED_COMPOSITING) + void TrackLayer(WTF::Vector<LayerTracker>& layerTracker, + RenderObject* nodeRenderer, Node* lastChild, int offsetX, int offsetY); +#endif + Node* tryFocus(Direction direction); + Node* trySegment(Direction direction, int mainStart, int mainEnd); + CachedNodeBits mAllowableTypes; + bool mPictureSetDisabled; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + void frameName(char*& namePtr, const char* max) const; + void init(char* buffer, size_t size); + static int ParentIndex(Node* node, int count, Node* parent); + void print() { frames(); } + void print(const char* name); + void wideString(const String& str); +private: + void attr(const AtomicString& name, const AtomicString& value); + void comma(const char* str); + void flush(); + Frame* frameAnd() const; + void frames(); + void groups(); + bool isFocusable(Node* node); + void localName(Node* node); + void newLine(int indent = 0); + void print(const char* name, unsigned len); + void setIndent(int ); + void uChar(const UChar* name, unsigned len, bool hex); + void validateFrame(); + void validateStringData(); + void wideString(const UChar* chars, int length, bool hex); + char* mBuffer; + size_t mBufferSize; + int mIndex; + const char* mPrefix; + int mMinPrefix; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedColor.cpp b/Source/WebKit/android/nav/CachedColor.cpp new file mode 100644 index 0000000..c610022 --- /dev/null +++ b/Source/WebKit/android/nav/CachedColor.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2010, 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. + */ + +#include "CachedPrefix.h" +#include "CachedColor.h" + +namespace android { + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_COLOR(field) \ + DUMP_NAV_LOGD("// SkColor " #field "=0x%08x;\n", b->field) + +CachedColor* CachedColor::Debug::base() const { + CachedColor* nav = (CachedColor*) ((char*) this - OFFSETOF(CachedColor, mDebug)); + return nav; +} + +void CachedColor::Debug::print() const +{ + CachedColor* b = base(); + DEBUG_PRINT_COLOR(mFillColor); + DUMP_NAV_LOGD("// int mInnerWidth=%d;\n", b->mInnerWidth); + DUMP_NAV_LOGD("// int mOuterWidth=%d;\n", b->mOuterWidth); + DUMP_NAV_LOGD("// int mOutset=%d;\n", b->mOutset); + DEBUG_PRINT_COLOR(mPressedInnerColor); + DEBUG_PRINT_COLOR(mPressedOuterColor); + DUMP_NAV_LOGD("// int mRadius=%d;\n", b->mRadius); + DEBUG_PRINT_COLOR(mSelectedInnerColor); + DEBUG_PRINT_COLOR(mSelectedOuterColor); +} + +#endif + +} + diff --git a/Source/WebKit/android/nav/CachedColor.h b/Source/WebKit/android/nav/CachedColor.h new file mode 100644 index 0000000..4b39810 --- /dev/null +++ b/Source/WebKit/android/nav/CachedColor.h @@ -0,0 +1,87 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef CachedColor_H +#define CachedColor_H + +#include "CachedDebug.h" +#include "Color.h" +#include "Length.h" +#include "SkColor.h" + +using namespace WebCore; + +namespace android { + +class CachedColor { +public: + CachedColor() { + // Initiaized to 0 in its array, so nothing to do in the + // constructor + } + bool operator==(const CachedColor& o) const { + return memcmp(&o, this, sizeof(this)) == 0; } + SkColor fillColor() const { return mFillColor; } + void init(); + int innerWidth() const { return mInnerWidth; } + int outerWidth() const { return mOuterWidth; } + int outset() const { return mOutset; } + SkColor pressedInnerColor() const { return mPressedInnerColor; } + SkColor pressedOuterColor() const { return mPressedOuterColor; } + int radius() const { return mRadius; } + SkColor selectedInnerColor() const { return mSelectedInnerColor; } + SkColor selectedOuterColor() const { return mSelectedOuterColor; } + void setFillColor(const Color& c) { mFillColor = c.rgb(); } + void setInnerWidth(Length l) { mInnerWidth = l.value(); } + void setOuterWidth(Length l) { mOuterWidth = l.value(); } + void setOutset(Length l) { mOutset = l.value(); } + void setPressedInnerColor(const Color& c) { mPressedInnerColor = c.rgb(); } + void setPressedOuterColor(const Color& c) { mPressedOuterColor = c.rgb(); } + void setRadius(Length l) { mRadius = l.value(); } + void setSelectedInnerColor(const Color& c) { mSelectedInnerColor = c.rgb(); } + void setSelectedOuterColor(const Color& c) { mSelectedOuterColor = c.rgb(); } +private: + SkColor mFillColor; + int mInnerWidth; + int mOuterWidth; + int mOutset; + SkColor mPressedInnerColor; + SkColor mPressedOuterColor; + int mRadius; + SkColor mSelectedInnerColor; + SkColor mSelectedOuterColor; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedColor* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedDebug.h b/Source/WebKit/android/nav/CachedDebug.h new file mode 100644 index 0000000..3d9e012 --- /dev/null +++ b/Source/WebKit/android/nav/CachedDebug.h @@ -0,0 +1,72 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef CachedDebug_H +#define CachedDebug_H + +#define DUMP_NAV_CACHE 0 +#define DEBUG_NAV_UI 0 +#define DEBUG_NAV_UI_VERBOSE 0 + +#if DEBUG_NAV_UI +#define DBG_NAV_LOG(message) LOGD("%s %s", __FUNCTION__, message) +#define DBG_NAV_LOGD(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#define DEBUG_NAV_UI_LOGD(...) LOGD(__VA_ARGS__) +#else +#define DBG_NAV_LOG(message) ((void)0) +#define DBG_NAV_LOGD(format, ...) ((void)0) +#define DEBUG_NAV_UI_LOGD(...) ((void)0) +#endif + +#if DEBUG_NAV_UI_VERBOSE +#define DBG_NAV_LOGV(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#else +#define DBG_NAV_LOGV(format, ...) ((void)0) +#endif + +#if DUMP_NAV_CACHE != 0 && !defined DUMP_NAV_CACHE_USING_PRINTF && defined NDEBUG +#define DUMP_NAV_CACHE_USING_PRINTF +#endif + +#if DUMP_NAV_CACHE +#ifdef DUMP_NAV_CACHE_USING_PRINTF +#include <stdio.h> +extern FILE* gNavCacheLogFile; +#define NAV_CACHE_LOG_FILE "/data/data/com.android.browser/navlog" +#define DUMP_NAV_LOGD(...) do { if (gNavCacheLogFile) \ + fprintf(gNavCacheLogFile, __VA_ARGS__); else LOGD(__VA_ARGS__); } while (false) +#define DUMP_NAV_LOGX(format, ...) do { if (gNavCacheLogFile) \ + fprintf(gNavCacheLogFile, format, __VA_ARGS__); \ + else LOGD("%s " format, __FUNCTION__, __VA_ARGS__); } while (false) +#else +#define DUMP_NAV_LOGD(...) LOGD(__VA_ARGS__) +#define DUMP_NAV_LOGX(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#endif +#else +#define DUMP_NAV_LOGD(...) ((void)0) +#define DUMP_NAV_LOGX(...) ((void)0) +#endif + +#endif diff --git a/Source/WebKit/android/nav/CachedFrame.cpp b/Source/WebKit/android/nav/CachedFrame.cpp new file mode 100644 index 0000000..b26e24b --- /dev/null +++ b/Source/WebKit/android/nav/CachedFrame.cpp @@ -0,0 +1,1494 @@ +/* + * Copyright 2007, 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. + */ + +#include "CachedPrefix.h" +#include "CachedHistory.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "LayerAndroid.h" + +#include "CachedFrame.h" + +#define OFFSETOF(type, field) ((char*)&(((type*)1)->field) - (char*)1) // avoids gnu warning + +#define MIN_OVERLAP 3 // if rects overlap by 2 pixels or fewer, treat them as non-intersecting + +namespace android { + +WebCore::IntRect CachedFrame::adjustBounds(const CachedNode* node, + const WebCore::IntRect& rect) const +{ + DBG_NAV_LOGV("node=%p [%d] rect=(%d,%d,w=%d,h=%d) view=(%d,%d,w=%d,h=%d)" + " local=(%d,%d,w=%d,h=%d) root=(%d,%d,w=%d,h=%d)", + node, node->index(), rect.x(), rect.y(), rect.width(), rect.height(), + mViewBounds.x(), mViewBounds.y(), + mViewBounds.width(), mViewBounds.height(), + mLocalViewBounds.x(), mLocalViewBounds.y(), + mLocalViewBounds.width(), mLocalViewBounds.height(), + mRoot->mViewBounds.x(), mRoot->mViewBounds.y(), + mRoot->mViewBounds.width(), mRoot->mViewBounds.height()); +#if USE(ACCELERATED_COMPOSITING) + if (!mRoot) + return rect; + + const CachedLayer* cachedLayer = layer(node); + if (!cachedLayer) + return rect; + + const WebCore::LayerAndroid* rootLayer = mRoot->rootLayer(); + const LayerAndroid* aLayer = cachedLayer->layer(rootLayer); + if (aLayer) + return cachedLayer->adjustBounds(rootLayer, rect); +#endif + return rect; +} + +bool CachedFrame::CheckBetween(Direction direction, const WebCore::IntRect& bestRect, + const WebCore::IntRect& prior, WebCore::IntRect* result) +{ + int left, top, width, height; + if (direction & UP_DOWN) { + top = direction == UP ? bestRect.bottom() : prior.bottom(); + int bottom = direction == UP ? prior.y() : bestRect.y(); + height = bottom - top; + if (height < 0) + return false; + left = prior.x(); + int testLeft = bestRect.x(); + if (left > testLeft) + left = testLeft; + int right = prior.right(); + int testRight = bestRect.right(); + if (right < testRight) + right = testRight; + width = right - left; + } else { + left = direction == LEFT ? bestRect.right() : prior.right(); + int right = direction == LEFT ? prior.x() : bestRect.x(); + width = right - left; + if (width < 0) + return false; + top = prior.y(); + int testTop = bestRect.y(); + if (top > testTop) + top = testTop; + int bottom = prior.bottom(); + int testBottom = bestRect.bottom(); + if (bottom < testBottom) + bottom = testBottom; + height = bottom - top; + } + *result = WebCore::IntRect(left, top, width, height); + return true; +} + +bool CachedFrame::checkBetween(BestData* best, Direction direction) +{ + const WebCore::IntRect& bestRect = best->bounds(); + BestData test; + test.mDistance = INT_MAX; + test.mNode = NULL; + int index = direction; + int limit = index + DIRECTION_COUNT; + do { + WebCore::IntRect edges; + Direction check = (Direction) (index & DIRECTION_MASK); + if (CheckBetween(check, bestRect, + history()->priorBounds(), &edges) == false) + continue; + WebCore::IntRect clip = mRoot->scrolledBounds(); + clip.intersect(edges); + if (clip.isEmpty()) + continue; + findClosest(&test, direction, check, &clip); + if (test.mNode == NULL) + continue; + if (direction == check) + break; + } while (++index < limit); + if (test.mNode == NULL) + return false; + *best = test; + return true; +} + +bool CachedFrame::checkRings(const CachedNode* node, + const WebCore::IntRect& testBounds) const +{ + return mRoot->checkRings(picture(node), node, testBounds); +} + +bool CachedFrame::checkVisited(const CachedNode* node, Direction direction) const +{ + return history()->checkVisited(node, direction); +} + +void CachedFrame::clearCursor() +{ + DBG_NAV_LOGD("mCursorIndex=%d", mCursorIndex); + if (mCursorIndex < CURSOR_SET) + return; + CachedNode& cursor = mCachedNodes[mCursorIndex]; + cursor.clearCursor(this); + mCursorIndex = CURSOR_CLEARED; // initialized and explicitly cleared +} + +// returns 0 if test is preferable to best, 1 if not preferable, or -1 if unknown +int CachedFrame::compare(BestData& testData, const BestData& bestData) const +{ + if (testData.mNode->tabIndex() != bestData.mNode->tabIndex()) { + if (testData.mNode->tabIndex() < bestData.mNode->tabIndex() + || (mRoot->mCursor && mRoot->mCursor->tabIndex() < bestData.mNode->tabIndex())) { + testData.mNode->setCondition(CachedNode::HIGHER_TAB_INDEX); + return REJECT_TEST; + } + return TEST_IS_BEST; + } + // if the test minor axis line intersects the line segment between cursor + // center and best center, choose it + // give more weight to exact major axis alignment (rows, columns) + if (testData.mInNav != bestData.mInNav) { + if (bestData.mInNav) { + testData.mNode->setCondition(CachedNode::IN_CURSOR); + return REJECT_TEST; + } + return TEST_IS_BEST; + } + if (testData.mInNav) { + if (bestData.mMajorDelta < testData.mMajorDelta) { + testData.mNode->setCondition(CachedNode::CLOSER_IN_CURSOR); + return REJECT_TEST; + } + if (testData.mMajorDelta < bestData.mMajorDelta) + return TEST_IS_BEST; + } + if (testData.mMajorDelta < 0 && bestData.mMajorDelta >= 0) { + testData.mNode->setCondition(CachedNode::FURTHER); + return REJECT_TEST; + } + if ((testData.mMajorDelta ^ bestData.mMajorDelta) < 0) // one above, one below (or one left, one right) + return TEST_IS_BEST; + bool bestInWorking = bestData.inOrSubsumesWorking(); + bool testInWorking = testData.inOrSubsumesWorking(); + if (bestInWorking && testData.mWorkingOutside && testData.mNavOutside) { + testData.mNode->setCondition(CachedNode::IN_WORKING); + return REJECT_TEST; + } + if (testInWorking && bestData.mWorkingOutside && bestData.mNavOutside) + return TEST_IS_BEST; + bool bestInNav = directionChange() && bestData.inOrSubsumesNav(); + bool testInNav = directionChange() && testData.inOrSubsumesNav(); + if (bestInWorking == false && testInWorking == false) { + if (bestInNav && testData.mNavOutside) { + testData.mNode->setCondition(CachedNode::IN_UMBRA); + return REJECT_TEST; + } + if (testInNav && bestData.mNavOutside) + return TEST_IS_BEST; + } +#if 01 // hopefully butt test will remove need for this + if (testData.mCursorChild != bestData.mCursorChild) { + if (bestData.mCursorChild) { + testData.mNode->setCondition(CachedNode::IN_CURSOR_CHILDREN); + return REJECT_TEST; + } + return TEST_IS_BEST; + } +#endif + bool bestTestIn = (bestInWorking || bestInNav) && (testInWorking || testInNav); + bool testOverlap = bestTestIn || (testData.mWorkingOverlap != 0 && bestData.mWorkingOverlap == 0); + bool bestOverlap = bestTestIn || (testData.mWorkingOverlap == 0 && bestData.mWorkingOverlap != 0); +#if 01 // this isn't working? + if (testOverlap == bestOverlap) { + if (bestData.mMajorButt < 10 && testData.mMajorButt >= 40) { + testData.mNode->setCondition(CachedNode::BUTTED_UP); + return REJECT_TEST; + } + if (testData.mMajorButt < 10 && bestData.mMajorButt >= 40) + return TEST_IS_BEST; + } +#endif + if (bestOverlap && bestData.mMajorDelta < testData.mMajorDelta) { // choose closest major axis center + testData.mNode->setCondition(CachedNode::CLOSER); + return REJECT_TEST; + } + if (testOverlap && testData.mMajorDelta < bestData.mMajorDelta) + return TEST_IS_BEST; + if (bestOverlap && bestData.mMajorDelta2 < testData.mMajorDelta2) { + testData.mNode->setCondition(CachedNode::CLOSER_TOP); + return REJECT_TEST; + } + if (testOverlap && testData.mMajorDelta2 < bestData.mMajorDelta2) + return TEST_IS_BEST; +#if 01 + if (bestOverlap && ((bestData.mSideDistance <= 0 && testData.mSideDistance > 0) || + abs(bestData.mSideDistance) < abs(testData.mSideDistance))) { + testData.mNode->setCondition(CachedNode::LEFTMOST); + return REJECT_TEST; + } + if (testOverlap && ((testData.mSideDistance <= 0 && bestData.mSideDistance > 0) || + abs(testData.mSideDistance) < abs(bestData.mSideDistance))) + return TEST_IS_BEST; +// fix me : the following ASSERT fires -- not sure if this case should be handled or not +// ASSERT(bestOverlap == false && testOverlap == false); +#endif + SkFixed testMultiplier = testData.mWorkingOverlap > testData.mNavOverlap ? + testData.mWorkingOverlap : testData.mNavOverlap; + SkFixed bestMultiplier = bestData.mWorkingOverlap > bestData.mNavOverlap ? + bestData.mWorkingOverlap : bestData.mNavOverlap; + int testDistance = testData.mDistance; + int bestDistance = bestData.mDistance; +// start here; + // this fails if they're off by 1 + // try once again to implement sliding scale so that off by 1 is nearly like zero, + // and off by a lot causes sideDistance to have little or no effect + // try elliptical distance -- lengthen side contribution + // these ASSERTs should not fire, but do fire on mail.google.com + // can't debug yet, won't reproduce + ASSERT(testDistance >= 0); + ASSERT(bestDistance >= 0); + testDistance += testDistance; // multiply by 2 + testDistance *= testDistance; + bestDistance += bestDistance; // multiply by 2 + bestDistance *= bestDistance; + int side = testData.mSideDistance; + int negative = side < 0 && bestData.mSideDistance > 0; + side *= side; + if (negative) + side = -side; + testDistance += side; + side = bestData.mSideDistance; + negative = side < 0 && testData.mSideDistance > 0; + side *= side; + if (negative) + side = -side; + bestDistance += side; + if (testMultiplier > (SK_Fixed1 >> 1) || bestMultiplier > (SK_Fixed1 >> 1)) { // considerable working overlap? + testDistance = SkFixedMul(testDistance, bestMultiplier); + bestDistance = SkFixedMul(bestDistance, testMultiplier); + } + if (bestDistance < testDistance) { + testData.mNode->setCondition(CachedNode::CLOSER_OVERLAP); + return REJECT_TEST; + } + if (testDistance < bestDistance) + return TEST_IS_BEST; +#if 0 + int distance = testData.mDistance + testData.mSideDistance; + int best = bestData.mDistance + bestData.mSideDistance; + if (distance > best) { + testData.mNode->setCondition(CachedNode::CLOSER_RAW_DISTANCE); + return REJECT_TEST; + } + else if (distance < best) + return TEST_IS_BEST; + best = bestData.mSideDistance; + if (testData.mSideDistance > best) { + testData.mNode->setCondition(CachedNode::SIDE_DISTANCE); + return REJECT_TEST; + } + if (testData.mSideDistance < best) + return TEST_IS_BEST; +#endif + if (testData.mPreferred < bestData.mPreferred) { + testData.mNode->setCondition(CachedNode::PREFERRED); + return REJECT_TEST; + } + if (testData.mPreferred > bestData.mPreferred) + return TEST_IS_BEST; + return UNDECIDED; +} + +const CachedNode* CachedFrame::currentCursor(const CachedFrame** framePtr) const +{ + if (framePtr) + *framePtr = this; + if (mCursorIndex < CURSOR_SET) + return NULL; + const CachedNode* result = &mCachedNodes[mCursorIndex]; + const CachedFrame* frame = hasFrame(result); + if (frame != NULL) + return frame->currentCursor(framePtr); + (const_cast<CachedNode*>(result))->fixUpCursorRects(this); + return result; +} + +const CachedNode* CachedFrame::currentFocus(const CachedFrame** framePtr) const +{ + if (framePtr) + *framePtr = this; + if (mFocusIndex < 0) + return NULL; + const CachedNode* result = &mCachedNodes[mFocusIndex]; + const CachedFrame* frame = hasFrame(result); + if (frame != NULL) + return frame->currentFocus(framePtr); + return result; +} + +bool CachedFrame::directionChange() const +{ + return history()->directionChange(); +} + +#ifdef BROWSER_DEBUG +CachedNode* CachedFrame::find(WebCore::Node* node) // !!! probably debugging only +{ + for (CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) + if (node == test->webCoreNode()) + return test; + for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); + frame++) { + CachedNode* result = frame->find(node); + if (result != NULL) + return result; + } + return NULL; +} +#endif + +const CachedNode* CachedFrame::findBestAt(const WebCore::IntRect& rect, + int* best, bool* inside, const CachedNode** directHit, + const CachedFrame** directHitFramePtr, + const CachedFrame** framePtr, int* x, int* y, + bool checkForHiddenStart) const +{ + const CachedNode* result = NULL; + int rectWidth = rect.width(); + WebCore::IntPoint center = WebCore::IntPoint(rect.x() + (rectWidth >> 1), + rect.y() + (rect.height() >> 1)); + mRoot->setupScrolledBounds(); + for (const CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) { + if (test->disabled()) + continue; + size_t parts = test->navableRects(); + BestData testData; + testData.mNode = test; + testData.mFrame = this; + WebCore::IntRect bounds = test->bounds(this); + testData.setMouseBounds(bounds); + testData.setNodeBounds(bounds); + bool checkForHidden = checkForHiddenStart; + for (size_t part = 0; part < parts; part++) { + WebCore::IntRect testRect = test->ring(this, part); + if (testRect.intersects(rect)) { +#if DEBUG_NAV_UI + if (test->isInLayer()) { + DBG_NAV_LOGD("[%d] intersects=%s testRect=(%d,%d,w=%d,h=%d)" + " rect=(%d,%d,w=%d,h=%d)", test->index(), + testRect.intersects(rect) ? "true" : "false", + testRect.x(), testRect.y(), + testRect.width(), testRect.height(), + rect.x(), rect.y(), rect.width(), rect.height()); + } +#endif + if (checkForHidden && mRoot->maskIfHidden(&testData) == true) { + DBG_NAV_LOGD("hidden [%d]", test->index()); + break; + } + checkForHidden = false; + testRect.intersect(testData.mouseBounds()); + if (testRect.contains(center)) { + // We have a direct hit. + if (*directHit == NULL) { + DBG_NAV_LOGD("direct hit 1 [%d]", test->index()); + *directHit = test; + *directHitFramePtr = this; + IntRect r(center, IntSize(0, 0)); + *x = r.x(); + *y = r.y(); + } else { + DBG_NAV_LOGD("direct hit 2 [%d]", test->index()); + // We have hit another one before + const CachedNode* d = *directHit; + if (d->bounds(this).contains(testRect)) { + // This rectangle is inside the other one, so it is + // the best one. + *directHit = test; + *directHitFramePtr = this; + } + } + } + if (NULL != *directHit) { + // If we have a direct hit already, there is no need to + // calculate the distances, or check the other parts + break; + } + DBG_NAV_LOGD("indirect hit [%d]", test->index()); + WebCore::IntRect both = rect; + int smaller = testRect.width() < testRect.height() ? + testRect.width() : testRect.height(); + smaller -= rectWidth; + int inset = smaller < rectWidth ? smaller : rectWidth; + inset >>= 1; // inflate doubles the width decrease + if (inset > 1) + both.inflate(1 - inset); + both.intersect(testRect); + if (both.isEmpty()) + continue; + bool testInside = testRect.contains(center); + if (*inside && !testInside) + continue; + WebCore::IntPoint testCenter = WebCore::IntPoint(testRect.x() + + (testRect.width() >> 1), testRect.y() + (testRect.height() >> 1)); + int dx = testCenter.x() - center.x(); + int dy = testCenter.y() - center.y(); + int distance = dx * dx + dy * dy; + if ((!*inside && testInside) || *best >= distance) { + *best = distance; + *inside = testInside; + result = test; + *framePtr = this; + *x = both.x() + (both.width() >> 1); + *y = both.y() + (both.height() >> 1); + } + } + } + } + for (const CachedFrame* frame = mCachedFrames.begin(); + frame != mCachedFrames.end(); frame++) { + const CachedNode* frameResult = frame->findBestAt(rect, best, inside, + directHit, directHitFramePtr, framePtr, x, y, checkForHiddenStart); + if (NULL != frameResult) + result = frameResult; + } + if (NULL != *directHit) { + result = *directHit; + *framePtr = *directHitFramePtr; + } + return result; +} + +const CachedFrame* CachedFrame::findBestFrameAt(int x, int y) const +{ + if (mLocalViewBounds.contains(x, y) == false) + return NULL; + const CachedFrame* result = this; + for (const CachedFrame* frame = mCachedFrames.begin(); + frame != mCachedFrames.end(); frame++) { + const CachedFrame* frameResult = frame->findBestFrameAt(x, y); + if (NULL != frameResult) + result = frameResult; + } + return result; +} + +const CachedNode* CachedFrame::findBestHitAt(const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* x, int* y) const +{ + mRoot->setupScrolledBounds(); + for (const CachedFrame* frame = mCachedFrames.end() - 1; + frame != mCachedFrames.begin() - 1; frame--) { + const CachedNode* frameResult = frame->findBestHitAt(rect, + framePtr, x, y); + if (NULL != frameResult) + return frameResult; + } + for (const CachedNode* test = mCachedNodes.end() - 1; + test != mCachedNodes.begin() - 1; test--) { + if (test->disabled()) + continue; + WebCore::IntRect testRect = test->hitBounds(this); + if (testRect.intersects(rect) == false) + continue; + BestData testData; + testData.mNode = test; + testData.mFrame = this; + testData.setMouseBounds(testRect); + testData.setNodeBounds(testRect); + if (mRoot->maskIfHidden(&testData) == true) + continue; + DBG_NAV_LOGD("candidate %d rect=(%d,%d,r=%d,b=%d)" + " testRect=(%d,%d,r=%d,b=%d)", + test->index(), rect.x(), rect.y(), rect.right(), rect.bottom(), + testRect.x(), testRect.y(), testRect.right(), testRect.bottom()); + for (int i = 0; i < test->navableRects(); i++) { + WebCore::IntRect cursorRect = test->ring(this, i); + DBG_NAV_LOGD("candidate %d cursorRect=(%d,%d,r=%d,b=%d)", + i, cursorRect.x(), cursorRect.y(), cursorRect.right(), + cursorRect.bottom()); + if (cursorRect.intersects(rect)) { + WebCore::IntRect intersection(cursorRect); + intersection.intersect(rect); + *x = intersection.x() + (intersection.width() >> 1); + *y = intersection.y() + (intersection.height() >> 1); + *framePtr = this; + return test; + } + } + testRect.intersect(rect); + *x = testRect.x() + (testRect.width() >> 1); + *y = testRect.y() + (testRect.height() >> 1); + *framePtr = this; + return test; + } + return NULL; +} + +void CachedFrame::findClosest(BestData* bestData, Direction originalDirection, + Direction direction, WebCore::IntRect* clip) const +{ + const CachedNode* test = mCachedNodes.begin(); + while ((test = test->traverseNextNode()) != NULL) { + const CachedFrame* child = hasFrame(test); + if (child != NULL) { + const CachedNode* childDoc = child->validDocument(); + if (childDoc == NULL) + continue; + child->findClosest(bestData, originalDirection, direction, clip); + } + if (test->noSecondChance()) + continue; + if (test->isNavable(this, *clip) == false) + continue; + if (checkVisited(test, originalDirection) == false) + continue; + size_t partMax = test->navableRects(); + for (size_t part = 0; part < partMax; part++) { + WebCore::IntRect testBounds = test->ring(this, part); + if (clip->intersects(testBounds) == false) + continue; + if (clip->contains(testBounds) == false) { + if (direction & UP_DOWN) { +// if (testBounds.x() > clip->x() || testBounds.right() < clip->right()) +// continue; + testBounds.setX(clip->x()); + testBounds.setWidth(clip->width()); + } else { +// if (testBounds.y() > clip->y() || testBounds.bottom() < clip->bottom()) +// continue; + testBounds.setY(clip->y()); + testBounds.setHeight(clip->height()); + } + if (clip->contains(testBounds) == false) + continue; + } + int distance; + // seems like distance for UP for instance needs to be 'test top closest to + // clip bottom' -- keep the old code but try this instead + switch (direction) { +#if 0 + case LEFT: distance = testBounds.x() - clip->x(); break; + case RIGHT: distance = clip->right() - testBounds.right(); break; + case UP: distance = testBounds.y() - clip->y(); break; + case DOWN: distance = clip->bottom() - testBounds.bottom(); break; +#else + case LEFT: distance = clip->right() - testBounds.x(); break; + case RIGHT: distance = testBounds.right() - clip->x(); break; + case UP: distance = clip->bottom() - testBounds.y(); break; + case DOWN: distance = testBounds.bottom() - clip->y(); break; +#endif + default: + distance = 0; ASSERT(0); + } + if (distance < bestData->mDistance) { + bestData->mNode = test; + bestData->mFrame = this; + bestData->mDistance = distance; + WebCore::IntRect rect = test->ring(this, part); + bestData->setMouseBounds(rect); + bestData->setNodeBounds(rect); + CachedHistory* cachedHistory = history(); + switch (direction) { + case LEFT: + bestData->setLeftDirection(cachedHistory); + break; + case RIGHT: + bestData->setRightDirection(cachedHistory); + break; + case UP: + bestData->setUpDirection(cachedHistory); + break; + case DOWN: + bestData->setDownDirection(cachedHistory); + break; + default: + ASSERT(0); + } + } + } + } +} + +void CachedFrame::finishInit() +{ + CachedNode* lastCached = lastNode(); + lastCached->setLast(); + CachedFrame* child = mCachedFrames.begin(); + while (child != mCachedFrames.end()) { + child->mParent = this; + child->finishInit(); + child++; + } + CachedFrame* frameParent; + if (mFocusIndex >= 0 && (frameParent = parent())) + frameParent->setFocusIndex(indexInParent()); +} + +const CachedNode* CachedFrame::frameDown(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameDown, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, DOWN) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setDownDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameDown(document(), test, &innerData); + if (checkVisited(innerData.mNode, DOWN)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, DOWN)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + // does the best contain something (or, is it contained by an area which is not the cursor?) + // if so, is the conainer/containee should have been chosen, but wasn't -- so there's a better choice + // in the doc list prior to this choice + // + return bestData->mNode; +} + +const CachedNode* CachedFrame::frameLeft(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameLeft, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, LEFT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setLeftDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameLeft(document(), test, &innerData); + if (checkVisited(innerData.mNode, LEFT)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, LEFT)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); // FIXME ??? left and up should use traversePreviousNode to choose reverse document order + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + return bestData->mNode; +} + +int CachedFrame::frameNodeCommon(BestData& testData, const CachedNode* test, + BestData* bestData, BestData* originalData) const +{ + testData.mFrame = this; + testData.mNode = test; + test->clearCondition(); + if (test->disabled()) { + testData.mNode->setCondition(CachedNode::DISABLED); + return REJECT_TEST; + } + WebCore::IntRect bounds = test->bounds(this); + if (bounds.isEmpty()) { + testData.mNode->setCondition(CachedNode::NAVABLE); + return REJECT_TEST; + } + if (mRoot->scrolledBounds().intersects(bounds) == false) { + testData.mNode->setCondition(CachedNode::NAVABLE); + return REJECT_TEST; + } + if (mRoot->rootLayer() && !test->isInLayer() + && !mRoot->baseUncovered().intersects(bounds)) { + testData.mNode->setCondition(CachedNode::UNDER_LAYER); + return REJECT_TEST; + } +// if (isNavable(test, &testData.mNodeBounds, walk) == false) { +// testData.mNode->setCondition(CachedNode::NAVABLE); +// return REJECT_TEST; +// } +// + if (test == mRoot->mCursor) { + testData.mNode->setCondition(CachedNode::NOT_CURSOR_NODE); + return REJECT_TEST; + } +// if (test->bounds().contains(mRoot->mCursorBounds)) { +// testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); +// return REJECT_TEST; +// } + void* par = mRoot->mCursor ? mRoot->mCursor->parentGroup() : NULL; + testData.mCursorChild = par ? test->parentGroup() == par : false; + if (bestData->mNode == NULL) + return TEST_IS_BEST; + if (mRoot->mCursor && testData.mNode->parentIndex() != bestData->mNode->parentIndex()) { + int cursorParentIndex = mRoot->mCursor->parentIndex(); + if (cursorParentIndex >= 0) { + if (bestData->mNode->parentIndex() == cursorParentIndex) + return REJECT_TEST; + if (testData.mNode->parentIndex() == cursorParentIndex) + return TEST_IS_BEST; + } + } + if (testData.mNode->parent() == bestData->mNode) { + testData.mNode->setCondition(CachedNode::CHILD); + return REJECT_TEST; + } + if (testData.mNode == bestData->mNode->parent()) + return TEST_IS_BEST; + int testInBest = testData.isContainer(bestData); /* -1 pick best over test, 0 no containership, 1 pick test over best */ + if (testInBest == 1) { + if (test->isArea() || bestData->mNode->isArea()) + return UNDECIDED; + bestData->mNode = NULL; // force part tests to be ignored, yet still set up remaining test data for later comparisons + return TEST_IS_BEST; + } + if (testInBest == -1) { + testData.mNode->setCondition(CachedNode::OUTSIDE_OF_BEST); + return REJECT_TEST; + } + if (originalData->mNode != NULL) { // test is best case + testInBest = testData.isContainer(originalData); + if (testInBest == -1) { /* test is inside best */ + testData.mNode->setCondition(CachedNode::OUTSIDE_OF_ORIGINAL); + return REJECT_TEST; + } + } + return UNDECIDED; +} + +int CachedFrame::framePartCommon(BestData& testData, + const CachedNode* test, BestData* bestData) const +{ + if (mRoot->mCursor + && testData.bounds().contains(mRoot->mCursorBounds) + && !test->wantsKeyEvents()) { + testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + testData.setDistances(); + if (bestData->mNode != NULL) { + int compared = compare(testData, *bestData); + if (compared == 0 && test->isArea() == false && bestData->mNode->isArea() == false) + goto pickTest; + if (compared >= 0) + return compared; + } +pickTest: + return -1; // pick test +} + +const CachedNode* CachedFrame::frameRight(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameRight, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, RIGHT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setRightDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameRight(document(), test, &innerData); + if (checkVisited(innerData.mNode, RIGHT)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, RIGHT)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + return bestData->mNode; +} + +const CachedNode* CachedFrame::frameUp(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameUp, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, UP) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setUpDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameUp(document(), test, &innerData); + if (checkVisited(innerData.mNode, UP)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, UP)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); // FIXME ??? left and up should use traversePreviousNode to choose reverse document order + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + return bestData->mNode; +} + +CachedFrame* CachedFrame::hasFrame(const CachedNode* node) +{ + return node->isFrame() ? &mCachedFrames[node->childFrameIndex()] : NULL; +} + +void CachedFrame::hideCursor() +{ + DBG_NAV_LOGD("mCursorIndex=%d", mCursorIndex); + if (mCursorIndex < CURSOR_SET) + return; + CachedNode& cursor = mCachedNodes[mCursorIndex]; + cursor.hideCursor(this); +} + +CachedHistory* CachedFrame::history() const +{ + return mRoot->rootHistory(); +} + +void CachedFrame::init(const CachedRoot* root, int childFrameIndex, + WebCore::Frame* frame) +{ + mContents = WebCore::IntRect(0, 0, 0, 0); // fixed up for real in setData() + mLocalViewBounds = WebCore::IntRect(0, 0, 0, 0); + mViewBounds = WebCore::IntRect(0, 0, 0, 0); + mRoot = root; + mCursorIndex = CURSOR_UNINITIALIZED; // not explicitly cleared + mFocusIndex = -1; + mFrame = frame; + mParent = NULL; // set up parents after stretchy arrays are set up + mIndexInParent = childFrameIndex; +} + +#if USE(ACCELERATED_COMPOSITING) +const CachedLayer* CachedFrame::layer(const CachedNode* node) const +{ + if (!node->isInLayer()) + return 0; + CachedLayer test; + test.setCachedNodeIndex(node->index()); + return std::lower_bound(mCachedLayers.begin(), mCachedLayers.end(), test); +} +#endif + +WebCore::IntRect CachedFrame::localBounds(const CachedNode* node, + const WebCore::IntRect& rect) const +{ + DBG_NAV_LOGD("node=%p [%d] rect=(%d,%d,w=%d,h=%d)", + node, node->index(), rect.x(), rect.y(), rect.width(), rect.height()); +#if USE(ACCELERATED_COMPOSITING) + return layer(node)->localBounds(mRoot->rootLayer(), rect); +#else + return rect; +#endif +} + +int CachedFrame::minWorkingHorizontal() const +{ + return history()->minWorkingHorizontal(); +} + +int CachedFrame::minWorkingVertical() const +{ + return history()->minWorkingVertical(); +} + +int CachedFrame::maxWorkingHorizontal() const +{ + return history()->maxWorkingHorizontal(); +} + +int CachedFrame::maxWorkingVertical() const +{ + return history()->maxWorkingVertical(); +} + +const CachedNode* CachedFrame::nextTextField(const CachedNode* start, + const CachedFrame** framePtr, bool* startFound) const +{ + const CachedNode* test = mCachedNodes.begin(); + while ((test = test->traverseNextNode())) { + const CachedFrame* frame = hasFrame(test); + if (frame) { + if (!frame->validDocument()) + continue; + const CachedNode* node + = frame->nextTextField(start, framePtr, startFound); + if (node) + return node; + } else if (test->isTextInput()) { + if (test == start) + *startFound = true; + else if (*startFound) { + if (framePtr) + *framePtr = this; + return test; + } + } + } + return 0; +} + +bool CachedFrame::moveInFrame(MoveInDirection moveInDirection, + const CachedNode* test, BestData* bestData) const +{ + const CachedFrame* frame = hasFrame(test); + if (frame == NULL) + return false; // if it's not a frame, let the caller have another swing at it + const CachedNode* childDoc = frame->validDocument(); + if (childDoc == NULL) + return true; + (frame->*moveInDirection)(childDoc, NULL, bestData); + return true; +} + +const WebCore::IntRect& CachedFrame::_navBounds() const +{ + return history()->navBounds(); +} + +SkPicture* CachedFrame::picture(const CachedNode* node) const +{ +#if USE(ACCELERATED_COMPOSITING) + if (node->isInLayer()) + return layer(node)->picture(mRoot->rootLayer()); +#endif + return mRoot->mPicture; +} + +SkPicture* CachedFrame::picture(const CachedNode* node, int* xPtr, int* yPtr) const +{ +#if USE(ACCELERATED_COMPOSITING) + if (node->isInLayer()) { + const CachedLayer* cachedLayer = layer(node); + const LayerAndroid* rootLayer = mRoot->rootLayer(); + cachedLayer->toLocal(rootLayer, xPtr, yPtr); + return cachedLayer->picture(rootLayer); + } +#endif + return mRoot->mPicture; +} + +void CachedFrame::resetClippedOut() +{ + for (CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) + { + if (test->clippedOut()) { + test->setDisabled(false); + test->setClippedOut(false); + } + } + for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); + frame++) { + frame->resetClippedOut(); + } +} + +void CachedFrame::resetLayers() +{ +#if USE(ACCELERATED_COMPOSITING) + for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); + frame++) { + frame->resetLayers(); + } +#endif +} + +bool CachedFrame::sameFrame(const CachedFrame* test) const +{ + ASSERT(test); + if (mIndexInParent != test->mIndexInParent) + return false; + if (mIndexInParent == -1) // index within parent's array of children, or -1 if root + return true; + return mParent->sameFrame(test->mParent); +} + +void CachedFrame::setData() +{ + if (this != mRoot) { + mViewBounds = mLocalViewBounds; + mViewBounds.intersect(mRoot->mViewBounds); + } + int x, y; + if (parent() == NULL) + x = y = 0; + else { + x = mLocalViewBounds.x(); + y = mLocalViewBounds.y(); + } + mContents.setX(x); + mContents.setY(y); + CachedFrame* child = mCachedFrames.begin(); + while (child != mCachedFrames.end()) { + child->setData(); + child++; + } +} + +bool CachedFrame::setCursor(WebCore::Frame* frame, WebCore::Node* node, + int x, int y) +{ + if (NULL == node) { + const_cast<CachedRoot*>(mRoot)->setCursor(NULL, NULL); + return true; + } + if (mFrame != frame) { + for (CachedFrame* testF = mCachedFrames.begin(); testF != mCachedFrames.end(); + testF++) { + if (testF->setCursor(frame, node, x, y)) + return true; + } + DBG_NAV_LOGD("no frame frame=%p node=%p", frame, node); + return false; + } + bool first = true; + CachedNode const * const end = mCachedNodes.end(); + do { + for (CachedNode* test = mCachedNodes.begin(); test != end; test++) { + if (test->nodePointer() != node && first) + continue; + size_t partMax = test->navableRects(); + for (size_t part = 0; part < partMax; part++) { + WebCore::IntRect testBounds = test->ring(this, part); + if (testBounds.contains(x, y) == false) + continue; + if (test->isCursor()) { + DBG_NAV_LOGD("already set? test=%d frame=%p node=%p x=%d y=%d", + test->index(), frame, node, x, y); + return false; + } + const_cast<CachedRoot*>(mRoot)->setCursor(this, test); + return true; + } + } + DBG_NAV_LOGD("moved? frame=%p node=%p x=%d y=%d", frame, node, x, y); + } while ((first ^= true) == false); +failed: + DBG_NAV_LOGD("no match frame=%p node=%p", frame, node); + return false; +} + +const CachedNode* CachedFrame::validDocument() const +{ + const CachedNode* doc = document(); + return doc != NULL && mViewBounds.isEmpty() == false ? doc : NULL; +} + +bool CachedFrame::BestData::canBeReachedByAnotherDirection() +{ + if (mMajorButt > -MIN_OVERLAP) + return false; + mMajorButt = -mMajorButt; + return mNavOutside; +} + +int CachedFrame::BestData::isContainer(CachedFrame::BestData* other) +{ + int _x = x(); + int otherRight = other->right(); + if (_x >= otherRight) + return 0; // does not intersect + int _y = y(); + int otherBottom = other->bottom(); + if (_y >= otherBottom) + return 0; // does not intersect + int _right = right(); + int otherX = other->x(); + if (otherX >= _right) + return 0; // does not intersect + int _bottom = bottom(); + int otherY = other->y(); + if (otherY >= _bottom) + return 0; // does not intersect + int intoX = otherX - _x; + int intoY = otherY - _y; + int intoRight = otherRight - _right; + int intoBottom = otherBottom - _bottom; + bool contains = intoX >= 0 && intoY >= 0 && intoRight <= 0 && intoBottom <= 0; + if (contains && mNode->partRectsContains(other->mNode)) { +// if (mIsArea == false && hasMouseOver()) +// other->mMouseOver = mNode; + return mNode->isArea() ? 1 : -1; + } + bool containedBy = intoX <= 0 && intoY <= 0 && intoRight >= 0 && intoBottom >= 0; + if (containedBy && other->mNode->partRectsContains(mNode)) { +// if (other->mIsArea == false && other->hasMouseOver()) +// mMouseOver = other->mNode; + return other->mNode->isArea() ? -1 : 1; + } + return 0; +} + +// distance scale factor factor as a 16.16 scalar +SkFixed CachedFrame::BestData::Overlap(int span, int left, int right) +{ + unsigned result; + if (left > 0 && left < span && right > span) + result = (unsigned) left; + else if (right > 0 && right < span && left > span) + result = (unsigned) right; + else if (left > 0 && right > 0) + return SK_Fixed1; + else + return 0; + result = (result << 16) / (unsigned) span; // linear proportion, always less than fixed 1 + return (SkFixed) result; +// !!! experiment with weight -- enable if overlaps are preferred too much +// or reverse weighting if overlaps are preferred to little +// return (SkFixed) (result * result >> 16); // but fall off with square +} + +void CachedFrame::BestData::setDistances() +{ + mDistance = abs(mMajorDelta); + int sideDistance = mWorkingDelta; + if (mWorkingOverlap < SK_Fixed1) { + if (mPreferred > 0) + sideDistance = mWorkingDelta2; + } else if (sideDistance >= 0 && mWorkingDelta2 >=- 0) + sideDistance = 0; + else { + ASSERT(sideDistance <= 0 && mWorkingDelta2 <= 0); + if (sideDistance < mWorkingDelta2) + sideDistance = mWorkingDelta2; + } + // if overlap, smaller abs mWorkingDelta is better, smaller abs majorDelta is better + // if not overlap, positive mWorkingDelta is better + mSideDistance = sideDistance; +} + +bool CachedFrame::BestData::setDownDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = mNodeBounds.y() - navBounds.bottom(); + int testX = mNodeBounds.x(); + int testRight = mNodeBounds.right(); + setNavOverlap(navBounds.width(), navBounds.right() - testX, + testRight - navBounds.x()); + if (canBeReachedByAnotherDirection()) { + mNode->setCondition(CachedNode::BEST_DIRECTION); + return REJECT_TEST; + } + int inNavTop = mNodeBounds.y() - navBounds.y(); + mMajorDelta2 = inNavTop; + mMajorDelta = mMajorDelta2 + ((mNodeBounds.height() - + navBounds.height()) >> 1); + if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { + mNode->setCondition(CachedNode::CENTER_FURTHER); // never move up or sideways + return REJECT_TEST; + } + int inNavBottom = navBounds.bottom() - mNodeBounds.bottom(); + setNavInclusion(testRight - navBounds.right(), navBounds.x() - testX); + bool subsumes = navBounds.height() > 0 && inOrSubsumesNav(); + if (inNavTop <= 0 && inNavBottom <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxV = history->maxWorkingVertical(); + int minV = history->minWorkingVertical(); + setWorkingOverlap(testRight - testX, maxV - testX, testRight - minV); + setWorkingInclusion(testRight - maxV, minV - testX); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavBottom >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavTop >= 0 && + inNavBottom > 0 && subsumes; + return false; +} + +bool CachedFrame::BestData::setLeftDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = navBounds.x() - mNodeBounds.right(); + int testY = mNodeBounds.y(); + int testBottom = mNodeBounds.bottom(); + setNavOverlap(navBounds.height(), navBounds.bottom() - testY, + testBottom - navBounds.y()); + if (canBeReachedByAnotherDirection()) { + mNode->setCondition(CachedNode::BEST_DIRECTION); + return REJECT_TEST; + } + int inNavRight = navBounds.right() - mNodeBounds.right(); + mMajorDelta2 = inNavRight; + mMajorDelta = mMajorDelta2 - ((navBounds.width() - + mNodeBounds.width()) >> 1); + if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { + mNode->setCondition(CachedNode::CENTER_FURTHER); // never move right or sideways + return REJECT_TEST; + } + int inNavLeft = mNodeBounds.x() - navBounds.x(); + setNavInclusion(navBounds.y() - testY, testBottom - navBounds.bottom()); + bool subsumes = navBounds.width() > 0 && inOrSubsumesNav(); + if (inNavLeft <= 0 && inNavRight <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxH = history->maxWorkingHorizontal(); + int minH = history->minWorkingHorizontal(); + setWorkingOverlap(testBottom - testY, maxH - testY, testBottom - minH); + setWorkingInclusion(minH - testY, testBottom - maxH); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavLeft >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavLeft >= 0 && + inNavRight > 0 && subsumes; /* both L/R in or out */ + return false; +} + +bool CachedFrame::BestData::setRightDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = mNodeBounds.x() - navBounds.right(); + int testY = mNodeBounds.y(); + int testBottom = mNodeBounds.bottom(); + setNavOverlap(navBounds.height(), navBounds.bottom() - testY, + testBottom - navBounds.y()); + if (canBeReachedByAnotherDirection()) { + mNode->setCondition(CachedNode::BEST_DIRECTION); + return REJECT_TEST; + } + int inNavLeft = mNodeBounds.x() - navBounds.x(); + mMajorDelta2 = inNavLeft; + mMajorDelta = mMajorDelta2 + ((mNodeBounds.width() - + navBounds.width()) >> 1); + if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { + mNode->setCondition(CachedNode::CENTER_FURTHER); // never move left or sideways + return REJECT_TEST; + } + int inNavRight = navBounds.right() - mNodeBounds.right(); + setNavInclusion(testBottom - navBounds.bottom(), navBounds.y() - testY); + bool subsumes = navBounds.width() > 0 && inOrSubsumesNav(); + if (inNavLeft <= 0 && inNavRight <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxH = history->maxWorkingHorizontal(); + int minH = history->minWorkingHorizontal(); + setWorkingOverlap(testBottom - testY, testBottom - minH, maxH - testY); + setWorkingInclusion(testBottom - maxH, minH - testY); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavRight >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavLeft >= 0 && + inNavRight > 0 && subsumes; /* both L/R in or out */ + return false; +} + +bool CachedFrame::BestData::setUpDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = navBounds.y() - mNodeBounds.bottom(); + int testX = mNodeBounds.x(); + int testRight = mNodeBounds.right(); + setNavOverlap(navBounds.width(), navBounds.right() - testX, + testRight - navBounds.x()); + if (canBeReachedByAnotherDirection()) { + mNode->setCondition(CachedNode::BEST_DIRECTION); + return REJECT_TEST; + } + int inNavBottom = navBounds.bottom() - mNodeBounds.bottom(); + mMajorDelta2 = inNavBottom; + mMajorDelta = mMajorDelta2 - ((navBounds.height() - + mNodeBounds.height()) >> 1); + if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { + mNode->setCondition(CachedNode::CENTER_FURTHER); // never move down or sideways + return REJECT_TEST; + } + int inNavTop = mNodeBounds.y() - navBounds.y(); + setNavInclusion(navBounds.x() - testX, testRight - navBounds.right()); + bool subsumes = navBounds.height() > 0 && inOrSubsumesNav(); + if (inNavTop <= 0 && inNavBottom <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxV = history->maxWorkingVertical(); + int minV = history->minWorkingVertical(); + setWorkingOverlap(testRight - testX, testRight - minV, maxV - testX); + setWorkingInclusion(minV - testX, testRight - maxV); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavTop >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavTop >= 0 && + inNavBottom > 0 && subsumes; /* both L/R in or out */ + return false; +} + +void CachedFrame::BestData::setNavInclusion(int left, int right) +{ + // if left and right <= 0, test node is completely in umbra of cursor + // prefer leftmost center + // if left and right > 0, test node subsumes cursor + mNavDelta = left; + mNavDelta2 = right; +} + +void CachedFrame::BestData::setNavOverlap(int span, int left, int right) +{ + // if left or right < 0, test node is not in umbra of cursor + mNavOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; + mNavOverlap = Overlap(span, left, right); // prefer smallest negative left +} + +void CachedFrame::BestData::setWorkingInclusion(int left, int right) +{ + mWorkingDelta = left; + mWorkingDelta2 = right; +} + +// distance scale factor factor as a 16.16 scalar +void CachedFrame::BestData::setWorkingOverlap(int span, int left, int right) +{ + // if left or right < 0, test node is not in umbra of cursor + mWorkingOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; + mWorkingOverlap = Overlap(span, left, right); + mPreferred = left <= 0 ? 0 : left; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_RECT(prefix, debugName, field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("%s DebugTestRect TEST%s_" #debugName "={%d, %d, %d, %d}; //" #field "\n", \ + prefix, mFrameName, r.x(), r.y(), r.width(), r.height()); } + +CachedFrame* CachedFrame::Debug::base() const { + CachedFrame* nav = (CachedFrame*) ((char*) this - OFFSETOF(CachedFrame, mDebug)); + return nav; +} + +void CachedFrame::Debug::print() const +{ + CachedFrame* b = base(); + DEBUG_PRINT_RECT("//", CONTENTS, mContents); + DEBUG_PRINT_RECT("", BOUNDS, mLocalViewBounds); + DEBUG_PRINT_RECT("//", VIEW, mViewBounds); + + DUMP_NAV_LOGD("// CachedNode mCachedNodes={ // count=%d\n", b->mCachedNodes.size()); + for (CachedNode* node = b->mCachedNodes.begin(); + node != b->mCachedNodes.end(); node++) { + node->mDebug.print(); + const CachedInput* input = b->textInput(node); + if (input) + input->mDebug.print(); + DUMP_NAV_LOGD("\n"); + } + DUMP_NAV_LOGD("// }; // end of nodes\n"); +#if USE(ACCELERATED_COMPOSITING) + DUMP_NAV_LOGD("// CachedLayer mCachedLayers={ // count=%d\n", b->mCachedLayers.size()); + for (CachedLayer* layer = b->mCachedLayers.begin(); + layer != b->mCachedLayers.end(); layer++) { + layer->mDebug.print(); + } + DUMP_NAV_LOGD("// }; // end of layers\n"); +#endif // USE(ACCELERATED_COMPOSITING) + DUMP_NAV_LOGD("// CachedColor mCachedColors={ // count=%d\n", b->mCachedColors.size()); + for (CachedColor* color = b->mCachedColors.begin(); + color != b->mCachedColors.end(); color++) { + color->mDebug.print(); + } + DUMP_NAV_LOGD("// }; // end of colors\n"); + DUMP_NAV_LOGD("// CachedFrame mCachedFrames={ // count=%d\n", b->mCachedFrames.size()); + for (CachedFrame* child = b->mCachedFrames.begin(); + child != b->mCachedFrames.end(); child++) + { + child->mDebug.print(); + } + DUMP_NAV_LOGD("// }; // end of child frames\n"); + DUMP_NAV_LOGD("// void* mFrame=(void*) %p;\n", b->mFrame); + DUMP_NAV_LOGD("// CachedFrame* mParent=%p;\n", b->mParent); + DUMP_NAV_LOGD("// int mIndexInParent=%d;\n", b->mIndexInParent); + DUMP_NAV_LOGD("// const CachedRoot* mRoot=%p;\n", b->mRoot); + DUMP_NAV_LOGD("// int mCursorIndex=%d;\n", b->mCursorIndex); + DUMP_NAV_LOGD("// int mFocusIndex=%d;\n", b->mFocusIndex); +} + +bool CachedFrame::Debug::validate(const CachedNode* node) const +{ + const CachedFrame* b = base(); + if (b->mCachedNodes.size() == 0) + return false; + if (node >= b->mCachedNodes.begin() && node < b->mCachedNodes.end()) + return true; + for (const CachedFrame* child = b->mCachedFrames.begin(); + child != b->mCachedFrames.end(); child++) + if (child->mDebug.validate(node)) + return true; + return false; +} + +#undef DEBUG_PRINT_RECT + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedFrame.h b/Source/WebKit/android/nav/CachedFrame.h new file mode 100644 index 0000000..039a0ee --- /dev/null +++ b/Source/WebKit/android/nav/CachedFrame.h @@ -0,0 +1,285 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef CachedFrame_H +#define CachedFrame_H + +#include "CachedColor.h" +#include "CachedInput.h" +#include "CachedLayer.h" +#include "CachedNode.h" +#include "IntRect.h" +#include "SkFixed.h" +#include "wtf/Vector.h" + +class SkPicture; + +namespace WebCore { + class Frame; + class Node; +} + +namespace android { + +class CachedHistory; +class CachedRoot; + + // first node referenced by cache is always document +class CachedFrame { +public: + enum Direction { + UNINITIALIZED = -1, + LEFT, + RIGHT, + UP, + DOWN, + DIRECTION_COUNT, + DIRECTION_MASK = DIRECTION_COUNT - 1, + UP_DOWN = UP & DOWN, // mask and result + RIGHT_DOWN = RIGHT & DOWN, // mask and result + }; + enum Compare { + UNDECIDED = -1, + TEST_IS_BEST, + REJECT_TEST + }; + enum CursorInit { + CURSOR_UNINITIALIZED = -2, + CURSOR_CLEARED = -1, + CURSOR_SET = 0 + }; + CachedFrame() {} + void add(CachedColor& color) { mCachedColors.append(color); } + void add(CachedInput& input) { mCachedTextInputs.append(input); } +#if USE(ACCELERATED_COMPOSITING) + void add(CachedLayer& layer) { mCachedLayers.append(layer); } +#endif + void add(CachedNode& node) { mCachedNodes.append(node); } + void addFrame(CachedFrame& child) { mCachedFrames.append(child); } + WebCore::IntRect adjustBounds(const CachedNode* , + const WebCore::IntRect& ) const; + bool checkRings(const CachedNode* node, + const WebCore::IntRect& testBounds) const; + bool checkVisited(const CachedNode* , CachedFrame::Direction ) const; + size_t childCount() { return mCachedFrames.size(); } + void clearCursor(); + const CachedColor& color(const CachedNode* node) const { + return mCachedColors[node->colorIndex()]; + } + const CachedNode* currentCursor() const { return currentCursor(NULL); } + const CachedNode* currentCursor(const CachedFrame** ) const; + const CachedNode* currentFocus() const { return currentFocus(NULL); } + const CachedNode* currentFocus(const CachedFrame** ) const; + bool directionChange() const; + const CachedNode* document() const { return mCachedNodes.begin(); } + bool empty() const { return mCachedNodes.size() < 2; } // must have 1 past doc + const CachedNode* findBestAt(const WebCore::IntRect& , int* best, + bool* inside, const CachedNode** , const CachedFrame** directFrame, + const CachedFrame** resultFrame, int* x, + int* y, bool checkForHidden) const; + const CachedFrame* findBestFrameAt(int x, int y) const; + const CachedNode* findBestHitAt(const WebCore::IntRect& , + const CachedFrame** , int* x, int* y) const; + void finishInit(); + CachedFrame* firstChild() { return mCachedFrames.begin(); } + const CachedFrame* firstChild() const { return mCachedFrames.begin(); } + void* framePointer() const { return mFrame; } + CachedNode* getIndex(int index) { return index >= 0 ? + &mCachedNodes[index] : NULL; } + const CachedFrame* hasFrame(const CachedNode* node) const { + return const_cast<CachedFrame*>(this)->hasFrame(node); + } + CachedFrame* hasFrame(const CachedNode* node); + void hideCursor(); + int indexInParent() const { return mIndexInParent; } + void init(const CachedRoot* root, int index, WebCore::Frame* frame); + const CachedFrame* lastChild() const { return &mCachedFrames.last(); } +#if USE(ACCELERATED_COMPOSITING) + const CachedLayer* lastLayer() const { return &mCachedLayers.last(); } +#endif + CachedNode* lastNode() { return &mCachedNodes.last(); } + CachedFrame* lastChild() { return &mCachedFrames.last(); } +#if USE(ACCELERATED_COMPOSITING) + const CachedLayer* layer(const CachedNode* ) const; + size_t layerCount() const { return mCachedLayers.size(); } +#endif + WebCore::IntRect localBounds(const CachedNode* , + const WebCore::IntRect& ) const; + const CachedFrame* parent() const { return mParent; } + CachedFrame* parent() { return mParent; } + SkPicture* picture(const CachedNode* ) const; + SkPicture* picture(const CachedNode* , int* xPtr, int* yPtr) const; + void resetLayers(); + bool sameFrame(const CachedFrame* ) const; + void removeLast() { mCachedNodes.removeLast(); } + void resetClippedOut(); + void setContentsSize(int width, int height) { mContents.setWidth(width); + mContents.setHeight(height); } + bool setCursor(WebCore::Frame* , WebCore::Node* , int x, int y); + void setCursorIndex(int index) { mCursorIndex = index; } + void setData(); + bool setFocus(WebCore::Frame* , WebCore::Node* , int x, int y); + void setFocusIndex(int index) { mFocusIndex = index; } + void setIndexInParent(int index) { mIndexInParent = index; } + void setLocalViewBounds(const WebCore::IntRect& bounds) { mLocalViewBounds = bounds; } + int size() { return mCachedNodes.size(); } + const CachedInput* textInput(const CachedNode* node) const { + return node->isTextInput() ? &mCachedTextInputs[node->textInputIndex()] + : 0; + } + const CachedNode* validDocument() const; +protected: + const CachedNode* nextTextField(const CachedNode* start, + const CachedFrame** framePtr, bool* found) const; + struct BestData { + int mDistance; + int mSideDistance; + int mMajorDelta; // difference of center of object + // used only when leading and trailing edges contain another set of edges + int mMajorDelta2; // difference of leading edge (only used when center is same) + int mMajorButt; // checks for next cell butting up against or close to previous one + int mWorkingDelta; + int mWorkingDelta2; + int mNavDelta; + int mNavDelta2; + const CachedFrame* mFrame; + const CachedNode* mNode; + SkFixed mWorkingOverlap; // this and below are fuzzy answers instead of bools + SkFixed mNavOverlap; + SkFixed mPreferred; + bool mCursorChild; + bool mInNav; + bool mNavOutside; + bool mWorkingOutside; + int bottom() const { return bounds().bottom(); } + const WebCore::IntRect& bounds() const { return mNodeBounds; } + bool canBeReachedByAnotherDirection(); + int height() const { return bounds().height(); } + bool inOrSubsumesNav() const { return (mNavDelta ^ mNavDelta2) >= 0; } + bool inOrSubsumesWorking() const { return (mWorkingDelta ^ mWorkingDelta2) >= 0; } + int isContainer(BestData* ); + const WebCore::IntRect& mouseBounds() const { return mMouseBounds; } + static SkFixed Overlap(int span, int left, int right); + void reset() { mNode = NULL; } + int right() const { return bounds().right(); } + void setMouseBounds(const WebCore::IntRect& b) { mMouseBounds = b; } + void setNodeBounds(const WebCore::IntRect& b) { mNodeBounds = b; } + void setDistances(); + bool setDownDirection(const CachedHistory* ); + bool setLeftDirection(const CachedHistory* ); + bool setRightDirection(const CachedHistory* ); + bool setUpDirection(const CachedHistory* ); + void setNavInclusion(int left, int right); + void setNavOverlap(int span, int left, int right); + void setWorkingInclusion(int left, int right); + void setWorkingOverlap(int span, int left, int right); + int width() const { return bounds().width(); } + int x() const { return bounds().x(); } + int y() const { return bounds().y(); } +private: // since computing these is complicated, protect them so that the + // are only written by appropriate helpers + WebCore::IntRect mMouseBounds; + WebCore::IntRect mNodeBounds; + }; + typedef const CachedNode* (CachedFrame::*MoveInDirection)( + const CachedNode* test, const CachedNode* limit, BestData* ) const; + void adjustToTextColumn(int* delta) const; + static bool CheckBetween(Direction , const WebCore::IntRect& bestRect, + const WebCore::IntRect& prior, WebCore::IntRect* result); + bool checkBetween(BestData* , Direction ); + int compare(BestData& testData, const BestData& bestData) const; + void findClosest(BestData* , Direction original, Direction test, + WebCore::IntRect* clip) const; + int frameNodeCommon(BestData& testData, const CachedNode* test, + BestData* bestData, BestData* originalData) const; + int framePartCommon(BestData& testData, const CachedNode* test, + BestData* ) const; + const CachedNode* frameDown(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + const CachedNode* frameLeft(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + const CachedNode* frameRight(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + const CachedNode* frameUp(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + int minWorkingHorizontal() const; + int minWorkingVertical() const; + int maxWorkingHorizontal() const; + int maxWorkingVertical() const; + bool moveInFrame(MoveInDirection , const CachedNode* test, BestData* ) const; + const WebCore::IntRect& _navBounds() const; + WebCore::IntRect mContents; + WebCore::IntRect mLocalViewBounds; + WebCore::IntRect mViewBounds; + WTF::Vector<CachedColor> mCachedColors; + WTF::Vector<CachedNode> mCachedNodes; + WTF::Vector<CachedFrame> mCachedFrames; + WTF::Vector<CachedInput> mCachedTextInputs; +#if USE(ACCELERATED_COMPOSITING) + WTF::Vector<CachedLayer> mCachedLayers; +#endif + void* mFrame; // WebCore::Frame*, used only to compare pointers + CachedFrame* mParent; + int mCursorIndex; + int mFocusIndex; + int mIndexInParent; // index within parent's array of children, or -1 if root + const CachedRoot* mRoot; +private: + CachedHistory* history() const; +#ifdef BROWSER_DEBUG +public: + CachedNode* find(WebCore::Node* ); // !!! probably debugging only + int mDebugIndex; + int mDebugLoopbackOffset; +#endif +#if !defined NDEBUG || DUMP_NAV_CACHE +public: + class Debug { +public: + Debug() { +#if DUMP_NAV_CACHE + mFrameName[0] = '\0'; +#endif +#if !defined NDEBUG + mInUse = true; +#endif + } +#if !defined NDEBUG + ~Debug() { mInUse = false; } + bool mInUse; +#endif +#if DUMP_NAV_CACHE + CachedFrame* base() const; + void print() const; + bool validate(const CachedNode* ) const; + char mFrameName[256]; +#endif + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedHistory.cpp b/Source/WebKit/android/nav/CachedHistory.cpp new file mode 100644 index 0000000..9066412 --- /dev/null +++ b/Source/WebKit/android/nav/CachedHistory.cpp @@ -0,0 +1,187 @@ +/* + * Copyright 2007, 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. + */ + +#include "CachedPrefix.h" +#include "CachedFrame.h" +#include "CachedNode.h" +#if DUMP_NAV_CACHE +#include "CachedRoot.h" +#endif + +#include "CachedHistory.h" + +namespace android { + +CachedHistory::CachedHistory() { + memset(this, 0, sizeof(CachedHistory)); // this assume the class has no virtuals + mLastMove = CachedFrame::UNINITIALIZED; + mPriorMove = CachedFrame::UNINITIALIZED; +} + + +void CachedHistory::addToVisited(const CachedNode* node, CachedFrame::Direction direction) +{ + memmove(&mVisited[1], &mVisited[0], sizeof(mVisited) - sizeof(mVisited[0])); + mVisited[0].mNode = node; + mVisited[0].mDirection = direction; +} + +bool CachedHistory::checkVisited(const CachedNode* node, CachedFrame::Direction direction) const +{ + // if the direction is unchanged and we've already visited this node, don't visit it again + int index = 0; + while (index < NAVIGATION_VISIT_DEPTH - 1) { + if (direction != mVisited[index].mDirection) + break; + index++; // compare with last direction, previous to last node (where the arrow took us from) + if (node == mVisited[index].mNode) + return false; + } + return true; +} + +void CachedHistory::pinMaxMin(const WebCore::IntRect& viewBounds) +{ + if (mMinWorkingHorizontal < viewBounds.y() || + mMinWorkingHorizontal >= viewBounds.bottom()) + mMinWorkingHorizontal = viewBounds.y(); + if (mMaxWorkingHorizontal > viewBounds.bottom() || + mMaxWorkingHorizontal <= viewBounds.y()) + mMaxWorkingHorizontal = viewBounds.bottom(); + if (mMinWorkingVertical < viewBounds.x() || + mMinWorkingVertical >= viewBounds.right()) + mMinWorkingVertical = viewBounds.x(); + if (mMaxWorkingVertical > viewBounds.right() || + mMaxWorkingVertical <= viewBounds.x()) + mMaxWorkingVertical = viewBounds.right(); +} + +void CachedHistory::reset() +{ + memset(mVisited, 0, sizeof(mVisited)); +// mLastScroll = 0; + mPriorBounds = WebCore::IntRect(0, 0, 0, 0); + mDirectionChange = false; + mDidFirstLayout = false; + mPriorMove = mLastMove = CachedFrame::UNINITIALIZED; + mMinWorkingHorizontal = mMinWorkingVertical = INT_MIN; + mMaxWorkingHorizontal = mMaxWorkingVertical = INT_MAX; +} + +void CachedHistory::setWorking(CachedFrame::Direction newMove, + const CachedFrame* cursorFrame, const CachedNode* cursor, + const WebCore::IntRect& viewBounds) +{ + CachedFrame::Direction lastAxis = (CachedFrame::Direction) (mLastMove & ~CachedFrame::RIGHT_DOWN); // up, left or uninitialized + CachedFrame::Direction newAxis = (CachedFrame::Direction) (newMove & ~CachedFrame::RIGHT_DOWN); + bool change = newAxis != lastAxis; + mDirectionChange = change && mLastMove != CachedFrame::UNINITIALIZED; + if (cursor != NULL || mLastMove != CachedFrame::UNINITIALIZED) { + mPriorMove = mLastMove; + mLastMove = newMove; + } + const WebCore::IntRect* navBounds = &mNavBounds; + if (cursor != NULL) { + WebCore::IntRect cursorBounds = cursor->bounds(cursorFrame); + if (cursorBounds.isEmpty() == false) + mNavBounds = cursorBounds; + } + if (change) { // uninitialized or change in direction + if (lastAxis != CachedFrame::LEFT && navBounds->height() > 0) { + mMinWorkingHorizontal = navBounds->y(); + mMaxWorkingHorizontal = navBounds->bottom(); + } + if (lastAxis != CachedFrame::UP && navBounds->width() > 0) { + mMinWorkingVertical = navBounds->x(); + mMaxWorkingVertical = navBounds->right(); + } + } + pinMaxMin(viewBounds); +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedHistory* CachedHistory::Debug::base() const { + CachedHistory* nav = (CachedHistory*) ((char*) this - OFFSETOF(CachedHistory, mDebug)); + return nav; +} + +const char* CachedHistory::Debug::direction(CachedFrame::Direction d) const +{ + switch (d) { + case CachedFrame::LEFT: return "LEFT"; break; + case CachedFrame::RIGHT: return "RIGHT"; break; + case CachedFrame::UP: return "UP"; break; + case CachedFrame::DOWN: return "DOWN"; break; + default: return "UNINITIALIZED"; + } +} + +void CachedHistory::Debug::print(CachedRoot* root) const +{ + CachedHistory* b = base(); + DUMP_NAV_LOGD("// Visited mVisited[]={\n"); + for (size_t i = 0; i < NAVIGATION_VISIT_DEPTH; i++) { + const Visited& visit = b->mVisited[i]; + const CachedNode* node = visit.mNode; + int index = root != NULL && root->CachedFrame::mDebug.validate(node) ? + node->index() : -1; + DUMP_NAV_LOGD(" // { 0x%p (%d), %s },\n", node, index, direction(visit.mDirection)); + } + DUMP_NAV_LOGD("// };\n"); +// DUMP_NAV_LOGD("// int mLastScroll=%d;\n", b->mLastScroll); + DEBUG_PRINT_RECT(mMouseBounds); + DEBUG_PRINT_RECT(mNavBounds); + DEBUG_PRINT_RECT(mPriorBounds); + DEBUG_PRINT_BOOL(mDirectionChange); + DEBUG_PRINT_BOOL(mDidFirstLayout); + DUMP_NAV_LOGD("// CachedFrame::Direction mLastMove=%s, mPriorMove=%s;\n", + direction(b->mLastMove), direction(b->mPriorMove)); + int max = b->mMaxWorkingHorizontal; + DUMP_NAV_LOGD("static int TEST_MAX_H = %d;\n", max); + int min = b->mMinWorkingHorizontal; + if (min == INT_MIN) + min++; + DUMP_NAV_LOGD("static int TEST_MIN_H = %d;\n", min); + max = b->mMaxWorkingVertical; + DUMP_NAV_LOGD("static int TEST_MAX_V = %d;\n", max); + min = b->mMinWorkingVertical; + if (min == INT_MIN) + min++; + DUMP_NAV_LOGD("static int TEST_MIN_V = %d;\n", min); + DUMP_NAV_LOGD("\n"); +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedHistory.h b/Source/WebKit/android/nav/CachedHistory.h new file mode 100644 index 0000000..e8c1ad9 --- /dev/null +++ b/Source/WebKit/android/nav/CachedHistory.h @@ -0,0 +1,89 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef CachedHistory_H +#define CachedHistory_H + +#include "CachedFrame.h" + +#define NAVIGATION_VISIT_DEPTH 8 // the number of nodes last visited -- used to detect ping-ponging (number should be tuned) + +namespace android { + +class CachedRoot; + +// CachedHistory is maintained even if DOM is rebuilt by running script. +// It uses blind pointers for comparison in the previously visited nodes. +class CachedHistory { +public: + CachedHistory(); + void addToVisited(const CachedNode* , CachedFrame::Direction ); + bool checkVisited(const CachedNode* , CachedFrame::Direction ) const; + bool didFirstLayout() const { return mDidFirstLayout; } + bool directionChange() const { return mDirectionChange; } + int minWorkingHorizontal() const { return mMinWorkingHorizontal; } + int minWorkingVertical() const { return mMinWorkingVertical; } + int maxWorkingHorizontal() const { return mMaxWorkingHorizontal; } + int maxWorkingVertical() const { return mMaxWorkingVertical; } + const WebCore::IntRect& navBounds() const { return mNavBounds; } + const WebCore::IntRect& priorBounds() const { return mPriorBounds; } + void setDidFirstLayout(bool did) { mDidFirstLayout = did; } + void setMouseBounds(const WebCore::IntRect& loc) { mMouseBounds = loc; } + void setNavBounds(const WebCore::IntRect& loc) { mNavBounds = loc; } + void setWorking(CachedFrame::Direction , const CachedFrame* , + const CachedNode* , const WebCore::IntRect& viewBounds); + void reset(); +private: + void pinMaxMin(const WebCore::IntRect& viewBounds); + struct Visited { + const CachedNode* mNode; + CachedFrame::Direction mDirection; + } mVisited[NAVIGATION_VISIT_DEPTH]; + WebCore::IntRect mMouseBounds; // constricted bounds, if cursor ring is partially visible + WebCore::IntRect mNavBounds; // cursor ring bounds plus optional keystroke movement + WebCore::IntRect mPriorBounds; // prior chosen cursor ring (for reversing narrowing) + bool mDirectionChange; + bool mDidFirstLayout; // set true when page is newly laid out + CachedFrame::Direction mLastMove; + CachedFrame::Direction mPriorMove; + int mMinWorkingHorizontal; + int mMaxWorkingHorizontal; + int mMinWorkingVertical; + int mMaxWorkingVertical; + friend class CachedRoot; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedHistory* base() const; + const char* direction(CachedFrame::Direction d) const; + void print(CachedRoot* ) const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedInput.cpp b/Source/WebKit/android/nav/CachedInput.cpp new file mode 100644 index 0000000..a6a57ef --- /dev/null +++ b/Source/WebKit/android/nav/CachedInput.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2009, 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. + */ + +#include "CachedPrefix.h" +#include "CachedInput.h" + +namespace android { + +void CachedInput::init() { + bzero(this, sizeof(CachedInput)); + mName = WTF::String(); +} + +void CachedInput::setTypeFromElement(WebCore::HTMLInputElement* element) +{ + ASSERT(element); + + if (element->isPasswordField()) + mType = PASSWORD; + else if (element->isSearchField()) + mType = SEARCH; + else if (element->isEmailField()) + mType = EMAIL; + else if (element->isNumberField()) + mType = NUMBER; + else if (element->isTelephoneField()) + mType = TELEPHONE; + else if (element->isURLField()) + mType = URL; + else + mType = NORMAL_TEXT_FIELD; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +CachedInput* CachedInput::Debug::base() const { + CachedInput* nav = (CachedInput*) ((char*) this - OFFSETOF(CachedInput, mDebug)); + return nav; +} + +static void printWebCoreString(const char* label, + const WTF::String& string) { + char scratch[256]; + size_t index = snprintf(scratch, sizeof(scratch), label); + const UChar* ch = string.characters(); + while (ch && *ch && index < sizeof(scratch)) { + UChar c = *ch++; + if (c < ' ' || c >= 0x7f) c = ' '; + scratch[index++] = c; + } + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); +} + +void CachedInput::Debug::print() const +{ + CachedInput* b = base(); + DEBUG_PRINT_BOOL(mAutoComplete); + DUMP_NAV_LOGD("// void* mForm=%p;\n", b->mForm); + printWebCoreString("// char* mName=\"", b->mName); + DUMP_NAV_LOGD("// int mMaxLength=%d;\n", b->mMaxLength); + DUMP_NAV_LOGD("// int mPaddingLeft=%d;\n", b->mPaddingLeft); + DUMP_NAV_LOGD("// int mPaddingTop=%d;\n", b->mPaddingTop); + DUMP_NAV_LOGD("// int mPaddingRight=%d;\n", b->mPaddingRight); + DUMP_NAV_LOGD("// int mPaddingBottom=%d;\n", b->mPaddingBottom); + DUMP_NAV_LOGD("// float mTextSize=%f;\n", b->mTextSize); + DUMP_NAV_LOGD("// int mLineHeight=%d;\n", b->mLineHeight); + DUMP_NAV_LOGD("// Type mType=%d;\n", b->mType); + DEBUG_PRINT_BOOL(mIsRtlText); + DEBUG_PRINT_BOOL(mIsTextField); + DEBUG_PRINT_BOOL(mIsTextArea); +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedInput.h b/Source/WebKit/android/nav/CachedInput.h new file mode 100644 index 0000000..f7f9eea --- /dev/null +++ b/Source/WebKit/android/nav/CachedInput.h @@ -0,0 +1,112 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef CachedInput_H +#define CachedInput_H + +#include "CachedDebug.h" +#include "HTMLInputElement.h" +#include "PlatformString.h" + +namespace android { + +class CachedInput { +public: + CachedInput() { + // Initiaized to 0 in its array, so nothing to do in the + // constructor + } + + enum Type { + NONE = -1, + NORMAL_TEXT_FIELD = 0, + TEXT_AREA = 1, + PASSWORD = 2, + SEARCH = 3, + EMAIL = 4, + NUMBER = 5, + TELEPHONE = 6, + URL = 7 + }; + + bool autoComplete() const { return mAutoComplete; } + void* formPointer() const { return mForm; } + void init(); + void setTypeFromElement(WebCore::HTMLInputElement*); + Type getType() const { return mType; } + bool isRtlText() const { return mIsRtlText; } + bool isTextField() const { return mIsTextField; } + bool isTextArea() const { return mIsTextArea; } + int lineHeight() const { return mLineHeight; } + int maxLength() const { return mMaxLength; }; + const WTF::String& name() const { return mName; } + int paddingBottom() const { return mPaddingBottom; } + int paddingLeft() const { return mPaddingLeft; } + int paddingRight() const { return mPaddingRight; } + int paddingTop() const { return mPaddingTop; } + void setAutoComplete(bool autoComplete) { mAutoComplete = autoComplete; } + void setFormPointer(void* form) { mForm = form; } + void setIsRtlText(bool isRtlText) { mIsRtlText = isRtlText; } + void setIsTextField(bool isTextField) { mIsTextField = isTextField; } + void setIsTextArea(bool isTextArea) { mIsTextArea = isTextArea; } + void setLineHeight(int height) { mLineHeight = height; } + void setMaxLength(int maxLength) { mMaxLength = maxLength; } + void setName(const WTF::String& name) { mName = name; } + void setPaddingBottom(int bottom) { mPaddingBottom = bottom; } + void setPaddingLeft(int left) { mPaddingLeft = left; } + void setPaddingRight(int right) { mPaddingRight = right; } + void setPaddingTop(int top) { mPaddingTop = top; } + void setTextSize(float textSize) { mTextSize = textSize; } + float textSize() const { return mTextSize; } + +private: + + void* mForm; + int mLineHeight; + int mMaxLength; + WTF::String mName; + int mPaddingBottom; + int mPaddingLeft; + int mPaddingRight; + int mPaddingTop; + float mTextSize; + Type mType; + bool mAutoComplete : 1; + bool mIsRtlText : 1; + bool mIsTextField : 1; + bool mIsTextArea : 1; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedInput* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedLayer.cpp b/Source/WebKit/android/nav/CachedLayer.cpp new file mode 100644 index 0000000..299f2d1 --- /dev/null +++ b/Source/WebKit/android/nav/CachedLayer.cpp @@ -0,0 +1,221 @@ +/* + * Copyright 2010, 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. + */ + +#include "CachedPrefix.h" + +#include "CachedLayer.h" +#include "FloatRect.h" +#include "LayerAndroid.h" + +namespace android { + +#if USE(ACCELERATED_COMPOSITING) + +IntRect CachedLayer::adjustBounds(const LayerAndroid* root, + const IntRect& bounds) const +{ + const LayerAndroid* aLayer = layer(root); + if (!aLayer) { + DBG_NAV_LOGD("no layer in root=%p uniqueId=%d", root, mUniqueId); +#if DUMP_NAV_CACHE + if (root) + mDebug.printRootLayerAndroid(root); +#endif + return bounds; + } + FloatRect temp = bounds; + // First, remove the original offset from the bounds. + temp.move(-mOffset.x(), -mOffset.y()); + + // Next, add in the new position of the layer (could be different due to a + // fixed position layer). + FloatPoint position = getGlobalPosition(aLayer); + temp.move(position.x(), position.y()); + + // Add in any layer translation. + // FIXME: Should use bounds() and apply the entire transformation matrix. + const FloatPoint& translation = aLayer->translation(); + temp.move(translation.x(), translation.y()); + + SkRect clip; + aLayer->bounds(&clip); + + // Do not try to traverse the parent chain if this is the root as the parent + // will not be a LayerAndroid. + if (aLayer != root) { + LayerAndroid* parent = static_cast<LayerAndroid*>(aLayer->getParent()); + while (parent) { + SkRect pClip; + parent->bounds(&pClip); + + // Move our position into our parent's coordinate space. + clip.offset(pClip.fLeft, pClip.fTop); + // Clip our visible rectangle to the parent. + clip.intersect(pClip); + + // Stop at the root. + if (parent == root) + break; + parent = static_cast<LayerAndroid*>(parent->getParent()); + } + } + + // Intersect the result with the visible clip. + temp.intersect(clip); + + IntRect result = enclosingIntRect(temp); + + DBG_NAV_LOGV("root=%p aLayer=%p [%d]" + " bounds=(%d,%d,w=%d,h=%d) trans=(%g,%g) pos=(%f,%f)" + " offset=(%d,%d)" + " result=(%d,%d,w=%d,h=%d)", + root, aLayer, aLayer->uniqueId(), + bounds.x(), bounds.y(), bounds.width(), bounds.height(), + translation.x(), translation.y(), position.x(), position.y(), + mOffset.x(), mOffset.y(), + result.x(), result.y(), result.width(), result.height()); + return result; +} + +FloatPoint CachedLayer::getGlobalPosition(const LayerAndroid* aLayer) const +{ + SkPoint result = aLayer->getPosition(); + const SkLayer* parent = aLayer->getParent(); + while (parent) { + result += parent->getPosition(); + DBG_NAV_LOGV("result=(%g,%g) parent=%p [%d]", result.fX, result.fY, + parent, ((LayerAndroid*) parent)->uniqueId()); + parent = parent->getParent(); + } + return result; +} + +const LayerAndroid* CachedLayer::layer(const LayerAndroid* root) const +{ + if (!root) + return 0; + return root->findById(mUniqueId); +} + +// return bounds relative to the layer as recorded when walking the dom +IntRect CachedLayer::localBounds(const LayerAndroid* root, + const IntRect& bounds) const +{ + IntRect temp = bounds; + // Remove the original offset from the bounds. + temp.move(-mOffset.x(), -mOffset.y()); + +#if DEBUG_NAV_UI + const LayerAndroid* aLayer = layer(root); + DBG_NAV_LOGD("aLayer=%p [%d] bounds=(%d,%d,w=%d,h=%d) offset=(%d,%d)" + " result=(%d,%d,w=%d,h=%d)", + aLayer, aLayer ? aLayer->uniqueId() : 0, + bounds.x(), bounds.y(), bounds.width(), bounds.height(), + mOffset.x(), mOffset.y(), + temp.x(), temp.y(), temp.width(), temp.height()); +#endif + + return temp; +} + +SkPicture* CachedLayer::picture(const LayerAndroid* root) const +{ + const LayerAndroid* aLayer = layer(root); + if (!aLayer) + return 0; + DBG_NAV_LOGD("root=%p aLayer=%p [%d] picture=%p", + root, aLayer, aLayer->uniqueId(), aLayer->picture()); + return aLayer->picture(); +} + +void CachedLayer::toLocal(const LayerAndroid* root, int* xPtr, int* yPtr) const +{ + const LayerAndroid* aLayer = layer(root); + if (!aLayer) + return; + DBG_NAV_LOGD("root=%p aLayer=%p [%d]", root, aLayer, aLayer->uniqueId()); + SkRect localBounds; + aLayer->bounds(&localBounds); + *xPtr -= localBounds.fLeft; + *yPtr -= localBounds.fTop; +} + +#if DUMP_NAV_CACHE + +CachedLayer* CachedLayer::Debug::base() const { + return (CachedLayer*) ((char*) this - OFFSETOF(CachedLayer, mDebug)); +} + +void CachedLayer::Debug::print() const +{ + CachedLayer* b = base(); + DUMP_NAV_LOGD(" // int mCachedNodeIndex=%d;\n", b->mCachedNodeIndex); + DUMP_NAV_LOGD(" // int mOffset=(%d, %d);\n", + b->mOffset.x(), b->mOffset.y()); + DUMP_NAV_LOGD(" // int mUniqueId=%p;\n", b->mUniqueId); + DUMP_NAV_LOGD("%s\n", ""); +} + +#endif + +#if DUMP_NAV_CACHE + +int CachedLayer::Debug::spaces; + +void CachedLayer::Debug::printLayerAndroid(const LayerAndroid* layer) +{ + ++spaces; + SkRect bounds; + layer->bounds(&bounds); + DBG_NAV_LOGD("%.*s layer=%p [%d] (%g,%g,%g,%g)" + " position=(%g,%g) translation=(%g,%g) anchor=(%g,%g)" + " matrix=(%g,%g) childMatrix=(%g,%g) picture=%p clipped=%s" + " scrollable=%s\n", + spaces, " ", layer, layer->uniqueId(), + bounds.fLeft, bounds.fTop, bounds.width(), bounds.height(), + layer->getPosition().fX, layer->getPosition().fY, + layer->translation().x(), layer->translation().y(), + layer->getAnchorPoint().fX, layer->getAnchorPoint().fY, + layer->getMatrix().getTranslateX(), layer->getMatrix().getTranslateY(), + layer->getChildrenMatrix().getTranslateX(), + layer->getChildrenMatrix().getTranslateY(), + layer->picture(), layer->m_haveClip ? "true" : "false", + layer->contentIsScrollable() ? "true" : "false"); + for (int i = 0; i < layer->countChildren(); i++) + printLayerAndroid(layer->getChild(i)); + --spaces; +} + +void CachedLayer::Debug::printRootLayerAndroid(const LayerAndroid* layer) +{ + spaces = 0; + printLayerAndroid(layer); +} +#endif + +#endif // USE(ACCELERATED_COMPOSITING) + +} + diff --git a/Source/WebKit/android/nav/CachedLayer.h b/Source/WebKit/android/nav/CachedLayer.h new file mode 100644 index 0000000..3d963e0 --- /dev/null +++ b/Source/WebKit/android/nav/CachedLayer.h @@ -0,0 +1,86 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef CachedLayer_H +#define CachedLayer_H + +#include "CachedDebug.h" +#include "IntRect.h" + +class SkPicture; + +namespace WebCore { + class FloatPoint; + class LayerAndroid; +} + +using namespace WebCore; + +namespace android { + +class CachedLayer { +public: +#if USE(ACCELERATED_COMPOSITING) + bool operator<(const CachedLayer& l) const { + return mCachedNodeIndex < l.mCachedNodeIndex; + } + // FIXME: adjustBounds should be renamed globalBounds or toGlobal + IntRect adjustBounds(const LayerAndroid* root, const IntRect& bounds) const; + int cachedNodeIndex() const { return mCachedNodeIndex; } + FloatPoint getGlobalPosition(const LayerAndroid* ) const; + const LayerAndroid* layer(const LayerAndroid* root) const; + IntRect localBounds(const LayerAndroid* root, const IntRect& bounds) const; + SkPicture* picture(const LayerAndroid* root) const; + void toLocal(const LayerAndroid* root, int* xPtr, int* yPtr) const; + void setCachedNodeIndex(int index) { mCachedNodeIndex = index; } + // Set the global position of the layer. This is recorded by the nav cache + // and corresponds to RenderLayer::absoluteBoundingBox() which is in + // document coordinates. This can be different from the global position of + // the layer if the layer is fixed positioned or scrollable. + void setOffset(const IntPoint& offset) { mOffset = offset; } + void setUniqueId(int uniqueId) { mUniqueId = uniqueId; } + int uniqueId() const { return mUniqueId; } +private: + int mCachedNodeIndex; + IntPoint mOffset; + int mUniqueId; + +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedLayer* base() const; + void print() const; + static void printLayerAndroid(const LayerAndroid* ); + static void printRootLayerAndroid(const LayerAndroid* ); + static int spaces; + } mDebug; +#endif +#endif // USE(ACCELERATED_COMPOSITING) +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedNode.cpp b/Source/WebKit/android/nav/CachedNode.cpp new file mode 100644 index 0000000..e3ba34d --- /dev/null +++ b/Source/WebKit/android/nav/CachedNode.cpp @@ -0,0 +1,432 @@ +/* + * Copyright 2007, 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. + */ + +#include "CachedPrefix.h" +#include "android_graphics.h" +#include "CachedFrame.h" +#include "CachedHistory.h" +#include "Node.h" +#include "PlatformString.h" + +#include "CachedNode.h" + +namespace android { + +WebCore::IntRect CachedNode::bounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->adjustBounds(this, mBounds) : mBounds; +} + +void CachedNode::clearCursor(CachedFrame* parent) +{ + if (isFrame()) { + CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this)); + child->clearCursor(); + } + mIsCursor = false; +} + +bool CachedNode::Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner, + WTF::Vector<WebCore::IntRect>* rings) +{ + if (outer.contains(*inner)) + return true; +// DBG_NAV_LOGD("outer:{%d,%d,%d,%d} does not contain inner:{%d,%d,%d,%d}", +// outer.x(), outer.y(), outer.width(), outer.height(), +// inner->x(), inner->y(), inner->width(), inner->height()); + bool intersects = outer.intersects(*inner); + size_t size = intersects ? rings->size() : 0; + *inner = WebCore::IntRect(0, 0, 0, 0); + if (intersects) { + WebCore::IntRect * const start = rings->begin(); + WebCore::IntRect* ring = start + size - 1; + do { + ring->intersect(outer); + if (ring->isEmpty()) { + if ((size_t) (ring - start) != --size) + *ring = start[size]; + } else + inner->unite(*ring); + } while (ring-- != start); + } + rings->shrink(size); +// DBG_NAV_LOGD("size:%d", size); + return size != 0; +} + +bool CachedNode::clip(const WebCore::IntRect& bounds) +{ + return Clip(bounds, &mBounds, &mCursorRing); +} + + +void CachedNode::cursorRings(const CachedFrame* frame, + WTF::Vector<WebCore::IntRect>* rings) const +{ + rings->clear(); + for (unsigned index = 0; index < mCursorRing.size(); index++) + rings->append(ring(frame, index)); +} + +WebCore::IntRect CachedNode::cursorRingBounds(const CachedFrame* frame) const +{ + int partMax = mNavableRects; + ASSERT(partMax > 0); + WebCore::IntRect bounds = mCursorRing[0]; + for (int partIndex = 1; partIndex < partMax; partIndex++) + bounds.unite(mCursorRing[partIndex]); + bounds.inflate(CURSOR_RING_HIT_TEST_RADIUS); + return mIsInLayer ? frame->adjustBounds(this, bounds) : bounds; +} + +#define OVERLAP 3 + +void CachedNode::fixUpCursorRects(const CachedFrame* frame) +{ + if (mFixedUpCursorRects) + return; + mFixedUpCursorRects = true; + // if the hit-test rect doesn't intersect any other rect, use it + if (mHitBounds != mBounds && mHitBounds.contains(mBounds) && + frame->checkRings(this, mHitBounds)) { + DBG_NAV_LOGD("use mHitBounds (%d,%d,%d,%d)", mHitBounds.x(), + mHitBounds.y(), mHitBounds.width(), mHitBounds.height()); + mUseHitBounds = true; + return; + } + if (mNavableRects <= 1) + return; + // if there is more than 1 rect, and the bounds doesn't intersect + // any other cursor ring bounds, use it + IntRect sloppyBounds = mBounds; + sloppyBounds.inflate(2); // give it a couple of extra pixels + if (frame->checkRings(this, sloppyBounds)) { + DBG_NAV_LOGD("use mBounds (%d,%d,%d,%d)", mBounds.x(), + mBounds.y(), mBounds.width(), mBounds.height()); + mUseBounds = true; + return; + } +#if DEBUG_NAV_UI + { + WebCore::IntRect* boundsPtr = mCursorRing.begin() - 1; + const WebCore::IntRect* const boundsEnd = mCursorRing.begin() + mCursorRing.size(); + while (++boundsPtr < boundsEnd) + LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, boundsPtr - mCursorRing.begin(), + boundsPtr->x(), boundsPtr->y(), boundsPtr->width(), boundsPtr->height()); + } +#endif + // q: need to know when rects are for drawing and hit-testing, but not mouse down calcs? + bool again; + do { + again = false; + size_t size = mCursorRing.size(); + WebCore::IntRect* unitBoundsPtr = mCursorRing.begin() - 1; + const WebCore::IntRect* const unitBoundsEnd = mCursorRing.begin() + size; + while (++unitBoundsPtr < unitBoundsEnd) { + // any other unitBounds to the left or right of this one? + int unitTop = unitBoundsPtr->y(); + int unitBottom = unitBoundsPtr->bottom(); + int unitLeft = unitBoundsPtr->x(); + int unitRight = unitBoundsPtr->right(); + WebCore::IntRect* testBoundsPtr = mCursorRing.begin() - 1; + while (++testBoundsPtr < unitBoundsEnd) { + if (unitBoundsPtr == testBoundsPtr) + continue; + int testTop = testBoundsPtr->y(); + int testBottom = testBoundsPtr->bottom(); + int testLeft = testBoundsPtr->x(); + int testRight = testBoundsPtr->right(); + int candidateTop = unitTop > testTop ? unitTop : testTop; + int candidateBottom = unitBottom < testBottom ? unitBottom : testBottom; + int candidateLeft = unitRight < testLeft ? unitRight : testRight; + int candidateRight = unitRight > testLeft ? unitLeft : testLeft; + bool leftRight = true; + if (candidateTop + OVERLAP >= candidateBottom || + candidateLeft + OVERLAP >= candidateRight) { + candidateTop = unitBottom < testTop ? unitBottom : testBottom; + candidateBottom = unitBottom > testTop ? unitTop : testTop; + candidateLeft = unitLeft > testLeft ? unitLeft : testLeft; + candidateRight = unitRight < testRight ? unitRight : testRight; + if (candidateTop + OVERLAP >= candidateBottom || + candidateLeft + OVERLAP >= candidateRight) + continue; + leftRight = false; + } + // construct candidate to add + WebCore::IntRect candidate = WebCore::IntRect(candidateLeft, candidateTop, + candidateRight - candidateLeft, candidateBottom - candidateTop); + // does a different unit bounds intersect the candidate? if so, don't add + WebCore::IntRect* checkBoundsPtr = mCursorRing.begin() - 1; + while (++checkBoundsPtr < unitBoundsEnd) { + if (checkBoundsPtr->intersects(candidate) == false) + continue; + if (leftRight) { + if (candidateTop >= checkBoundsPtr->y() && + candidateBottom > checkBoundsPtr->bottom()) + candidateTop = checkBoundsPtr->bottom(); + else if (candidateTop < checkBoundsPtr->y() && + candidateBottom <= checkBoundsPtr->bottom()) + candidateBottom = checkBoundsPtr->y(); + else + goto nextCheck; + } else { + if (candidateLeft >= checkBoundsPtr->x() && + candidateRight > checkBoundsPtr->right()) + candidateLeft = checkBoundsPtr->right(); + else if (candidateLeft < checkBoundsPtr->x() && + candidateRight <= checkBoundsPtr->right()) + candidateRight = checkBoundsPtr->x(); + else + goto nextCheck; + } + } + candidate = WebCore::IntRect(candidateLeft, candidateTop, + candidateRight - candidateLeft, candidateBottom - candidateTop); + ASSERT(candidate.isEmpty() == false); +#if DEBUG_NAV_UI + LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, mCursorRing.size(), + candidate.x(), candidate.y(), candidate.width(), candidate.height()); +#endif + mCursorRing.append(candidate); + again = true; + goto tryAgain; + nextCheck: + continue; + } + } +tryAgain: + ; + } while (again); +} + + +void CachedNode::hideCursor(CachedFrame* parent) +{ + if (isFrame()) { + CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this)); + child->hideCursor(); + } + mIsHidden = true; +} + +WebCore::IntRect CachedNode::hitBounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->adjustBounds(this, mHitBounds) : mHitBounds; +} + +void CachedNode::init(WebCore::Node* node) +{ + bzero(this, sizeof(CachedNode)); + mExport = WTF::String(); + mNode = node; + mParentIndex = mDataIndex = -1; + mType = android::NORMAL_CACHEDNODETYPE; +} + +bool CachedNode::isTextField(const CachedFrame* frame) const +{ + const CachedInput* input = frame->textInput(this); + return input ? input->isTextField() : false; +} + +void CachedNode::localCursorRings(const CachedFrame* frame, + WTF::Vector<WebCore::IntRect>* rings) const +{ + rings->clear(); + for (unsigned index = 0; index < mCursorRing.size(); index++) + rings->append(localRing(frame, index)); +} + +WebCore::IntRect CachedNode::localBounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->localBounds(this, mBounds) : mBounds; +} + +WebCore::IntRect CachedNode::localHitBounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->localBounds(this, mHitBounds) : mHitBounds; +} + +WebCore::IntRect CachedNode::localRing(const CachedFrame* frame, + size_t part) const +{ + const WebCore::IntRect& rect = mCursorRing.at(part); + return mIsInLayer ? frame->localBounds(this, rect) : rect; +} + +void CachedNode::move(int x, int y) +{ + mBounds.move(x, y); + // mHitTestBounds will be moved by caller + WebCore::IntRect* first = mCursorRing.begin(); + WebCore::IntRect* last = first + mCursorRing.size(); + --first; + while (++first != last) + first->move(x, y); +} + +bool CachedNode::partRectsContains(const CachedNode* other) const +{ + int outerIndex = 0; + int outerMax = mNavableRects; + int innerMax = other->mNavableRects; + do { + const WebCore::IntRect& outerBounds = mCursorRing[outerIndex]; + int innerIndex = 0; + do { + const WebCore::IntRect& innerBounds = other->mCursorRing[innerIndex]; + if (innerBounds.contains(outerBounds)) + return true; + } while (++innerIndex < innerMax); + } while (++outerIndex < outerMax); + return false; +} + +WebCore::IntRect CachedNode::ring(const CachedFrame* frame, size_t part) const +{ + const WebCore::IntRect& rect = mCursorRing.at(part); + return mIsInLayer ? frame->adjustBounds(this, rect) : rect; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedNode* CachedNode::Debug::base() const { + CachedNode* nav = (CachedNode*) ((char*) this - OFFSETOF(CachedNode, mDebug)); + return nav; +} + +const char* CachedNode::Debug::condition(Condition t) const +{ + switch (t) { + case NOT_REJECTED: return "NOT_REJECTED"; break; + case BUTTED_UP: return "BUTTED_UP"; break; + case CENTER_FURTHER: return "CENTER_FURTHER"; break; + case CLOSER: return "CLOSER"; break; + case CLOSER_IN_CURSOR: return "CLOSER_IN_CURSOR"; break; + case CLOSER_OVERLAP: return "CLOSER_OVERLAP"; break; + case CLOSER_TOP: return "CLOSER_TOP"; break; + case NAVABLE: return "NAVABLE"; break; + case FURTHER: return "FURTHER"; break; + case IN_UMBRA: return "IN_UMBRA"; break; + case IN_WORKING: return "IN_WORKING"; break; + case LEFTMOST: return "LEFTMOST"; break; + case OVERLAP_OR_EDGE_FURTHER: return "OVERLAP_OR_EDGE_FURTHER"; break; + case PREFERRED: return "PREFERRED"; break; + case ANCHOR_IN_ANCHOR: return "ANCHOR_IN_ANCHOR"; break; + case BEST_DIRECTION: return "BEST_DIRECTION"; break; + case CHILD: return "CHILD"; break; + case DISABLED: return "DISABLED"; break; + case HIGHER_TAB_INDEX: return "HIGHER_TAB_INDEX"; break; + case IN_CURSOR: return "IN_CURSOR"; break; + case IN_CURSOR_CHILDREN: return "IN_CURSOR_CHILDREN"; break; + case NOT_ENCLOSING_CURSOR: return "NOT_ENCLOSING_CURSOR"; break; + case NOT_CURSOR_NODE: return "NOT_CURSOR_NODE"; break; + case OUTSIDE_OF_BEST: return "OUTSIDE_OF_BEST"; break; + case OUTSIDE_OF_ORIGINAL: return "OUTSIDE_OF_ORIGINAL"; break; + default: return "???"; + } +} + +const char* CachedNode::Debug::type(android::CachedNodeType t) const +{ + switch (t) { + case NORMAL_CACHEDNODETYPE: return "NORMAL"; break; + case ADDRESS_CACHEDNODETYPE: return "ADDRESS"; break; + case EMAIL_CACHEDNODETYPE: return "EMAIL"; break; + case PHONE_CACHEDNODETYPE: return "PHONE"; break; + case ANCHOR_CACHEDNODETYPE: return "ANCHOR"; break; + case AREA_CACHEDNODETYPE: return "AREA"; break; + case FRAME_CACHEDNODETYPE: return "FRAME"; break; + case PLUGIN_CACHEDNODETYPE: return "PLUGIN"; break; + case TEXT_INPUT_CACHEDNODETYPE: return "INPUT"; break; + case SELECT_CACHEDNODETYPE: return "SELECT"; break; + case CONTENT_EDITABLE_CACHEDNODETYPE: return "CONTENT_EDITABLE"; break; + default: return "???"; + } +} + +void CachedNode::Debug::print() const +{ + CachedNode* b = base(); + char scratch[256]; + size_t index = snprintf(scratch, sizeof(scratch), "// char* mExport=\""); + const UChar* ch = b->mExport.characters(); + while (ch && *ch && index < sizeof(scratch)) { + UChar c = *ch++; + if (c < ' ' || c >= 0x7f) c = ' '; + scratch[index++] = c; + } + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); + DEBUG_PRINT_RECT(mBounds); + DEBUG_PRINT_RECT(mHitBounds); + DEBUG_PRINT_RECT(mOriginalAbsoluteBounds); + const WTF::Vector<WebCore::IntRect>* rects = &b->mCursorRing; + size_t size = rects->size(); + DUMP_NAV_LOGD("// IntRect cursorRings={ // size=%d\n", size); + for (size_t i = 0; i < size; i++) { + const WebCore::IntRect& rect = (*rects)[i]; + DUMP_NAV_LOGD(" // {%d, %d, %d, %d}, // %d\n", rect.x(), rect.y(), + rect.width(), rect.height(), i); + } + DUMP_NAV_LOGD("// };\n"); + DUMP_NAV_LOGD("// void* mNode=%p; // (%d) \n", b->mNode, mNodeIndex); + DUMP_NAV_LOGD("// void* mParentGroup=%p; // (%d) \n", b->mParentGroup, mParentGroupIndex); + DUMP_NAV_LOGD("// int mDataIndex=%d;\n", b->mDataIndex); + DUMP_NAV_LOGD("// int mIndex=%d;\n", b->mIndex); + DUMP_NAV_LOGD("// int mNavableRects=%d;\n", b->mNavableRects); + DUMP_NAV_LOGD("// int mParentIndex=%d;\n", b->mParentIndex); + DUMP_NAV_LOGD("// int mTabIndex=%d;\n", b->mTabIndex); + DUMP_NAV_LOGD("// int mColorIndex=%d;\n", b->mColorIndex); + DUMP_NAV_LOGD("// Condition mCondition=%s;\n", condition(b->mCondition)); + DUMP_NAV_LOGD("// Type mType=%s;\n", type(b->mType)); + DEBUG_PRINT_BOOL(mClippedOut); + DEBUG_PRINT_BOOL(mDisabled); + DEBUG_PRINT_BOOL(mFixedUpCursorRects); + DEBUG_PRINT_BOOL(mHasCursorRing); + DEBUG_PRINT_BOOL(mHasMouseOver); + DEBUG_PRINT_BOOL(mIsCursor); + DEBUG_PRINT_BOOL(mIsFocus); + DEBUG_PRINT_BOOL(mIsHidden); + DEBUG_PRINT_BOOL(mIsInLayer); + DEBUG_PRINT_BOOL(mIsParentAnchor); + DEBUG_PRINT_BOOL(mIsTransparent); + DEBUG_PRINT_BOOL(mIsUnclipped); + DEBUG_PRINT_BOOL(mLast); + DEBUG_PRINT_BOOL(mUseBounds); + DEBUG_PRINT_BOOL(mUseHitBounds); + DEBUG_PRINT_BOOL(mSingleImage); +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedNode.h b/Source/WebKit/android/nav/CachedNode.h new file mode 100644 index 0000000..f9bcbed --- /dev/null +++ b/Source/WebKit/android/nav/CachedNode.h @@ -0,0 +1,247 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef CachedNode_H +#define CachedNode_H + +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" + +#include <wtf/Vector.h> +#include <wtf/text/AtomicString.h> + +class SkPicture; + +namespace WebCore { + class Node; +} + +namespace android { + +class CachedFrame; +class CachedRoot; + +class CachedNode { +public: +// Nodes are rejected because either they are spacially not the best (first set) +// or because they have the wrong DOM attribute (in focus, a focused child, etc) +// findClosest() gives only spacially rejected nodes a second chance + enum Condition { // if bigger than 32, increase bitfield size below + // rejections that get a second chance + NOT_REJECTED = 0, + SECOND_CHANCE_START = NOT_REJECTED, // must be first in list + BUTTED_UP, + CENTER_FURTHER, + CLOSER, + CLOSER_IN_CURSOR, + CLOSER_OVERLAP, + CLOSER_TOP, + NAVABLE, + FURTHER, + IN_UMBRA, + IN_WORKING, + LEFTMOST, + NOT_ENCLOSING_CURSOR, + OVERLAP_OR_EDGE_FURTHER, + PREFERRED, // better overlap measure + SECOND_CHANCE_END = PREFERRED, // must be last in list + // rejections that don't get a second chance + ANCHOR_IN_ANCHOR, + BEST_DIRECTION, // can be reached by another direction + CHILD, + DISABLED, + HIGHER_TAB_INDEX, + IN_CURSOR, + IN_CURSOR_CHILDREN, + NOT_CURSOR_NODE, + OUTSIDE_OF_BEST, // containership + OUTSIDE_OF_ORIGINAL, // containership + UNDER_LAYER, + CONDITION_SIZE // FIXME: test that CONDITION_SIZE fits in mCondition + }; + CachedNode() { + // The node is initiaized to 0 in its array, so nothing to do in the + // constructor + } + + WebCore::IntRect bounds(const CachedFrame* ) const; + int childFrameIndex() const { return isFrame() ? mDataIndex : -1; } + void clearCondition() const { mCondition = NOT_REJECTED; } + void clearCursor(CachedFrame* ); + static bool Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner, + WTF::Vector<WebCore::IntRect>* rings); + bool clip(const WebCore::IntRect& ); + bool clippedOut() { return mClippedOut; } + int colorIndex() const { return mColorIndex; } + WebCore::IntRect cursorRingBounds(const CachedFrame* ) const; + void cursorRings(const CachedFrame* , WTF::Vector<WebCore::IntRect>* ) const; + bool disabled() const { return mDisabled; } + const CachedNode* document() const { return &this[-mIndex]; } + void fixUpCursorRects(const CachedFrame* frame); + const WTF::String& getExport() const { return mExport; } + bool hasCursorRing() const { return mHasCursorRing; } + bool hasMouseOver() const { return mHasMouseOver; } + void hideCursor(CachedFrame* ); + WebCore::IntRect hitBounds(const CachedFrame* ) const; + int index() const { return mIndex; } + void init(WebCore::Node* node); + bool isAnchor() const { return mType == ANCHOR_CACHEDNODETYPE; } + bool isContentEditable() const { return mType == CONTENT_EDITABLE_CACHEDNODETYPE; } + bool isCursor() const { return mIsCursor; } + bool isArea() const { return mType == AREA_CACHEDNODETYPE; } + bool isFocus() const { return mIsFocus; } + bool isFrame() const { return mType == FRAME_CACHEDNODETYPE; } + bool isHidden() const { return mIsHidden; } + bool isInLayer() const { return mIsInLayer; } + bool isNavable(const CachedFrame* frame, const WebCore::IntRect& clip) const { + return clip.intersects(bounds(frame)); + } + bool isPlugin() const { return mType == PLUGIN_CACHEDNODETYPE; } + bool isSelect() const { return mType == SELECT_CACHEDNODETYPE; } + bool isSyntheticLink() const { + return mType >= ADDRESS_CACHEDNODETYPE && mType <= PHONE_CACHEDNODETYPE; + } + bool isTextField(const CachedFrame*) const; + bool isTextInput() const { return mType == TEXT_INPUT_CACHEDNODETYPE; } + bool isTransparent() const { return mIsTransparent; } + bool isUnclipped() const { return mIsUnclipped; } + // localXXX functions are used only for drawing cursor rings + WebCore::IntRect localBounds(const CachedFrame* ) const; + void localCursorRings(const CachedFrame* , + WTF::Vector<WebCore::IntRect>* ) const; + WebCore::IntRect localHitBounds(const CachedFrame* ) const; + WebCore::IntRect localRing(const CachedFrame* , size_t part) const; + void move(int x, int y); + int navableRects() const { return mNavableRects; } + void* nodePointer() const { return mNode; } + bool noSecondChance() const { return mCondition > SECOND_CHANCE_END; } + const WebCore::IntRect& originalAbsoluteBounds() const { + return mOriginalAbsoluteBounds; } + const CachedNode* parent() const { return document() + mParentIndex; } + void* parentGroup() const { return mParentGroup; } + int parentIndex() const { return mParentIndex; } + bool partRectsContains(const CachedNode* other) const; + const WebCore::IntRect& rawBounds() const { return mBounds; } + void reset(); + WebCore::IntRect ring(const CachedFrame* , size_t part) const; + const WTF::Vector<WebCore::IntRect>& rings() const { return mCursorRing; } + void setBounds(const WebCore::IntRect& bounds) { mBounds = bounds; } + void setClippedOut(bool clipped) { mClippedOut = clipped; } + void setColorIndex(int index) { mColorIndex = index; } + void setCondition(Condition condition) const { mCondition = condition; } + void setDataIndex(int index) { mDataIndex = index; } + void setDisabled(bool disabled) { mDisabled = disabled; } + void setExport(const WTF::String& exported) { mExport = exported; } + void setHasCursorRing(bool hasRing) { mHasCursorRing = hasRing; } + void setHasMouseOver(bool hasMouseOver) { mHasMouseOver = hasMouseOver; } + void setHitBounds(const WebCore::IntRect& bounds) { mHitBounds = bounds; } + void setOriginalAbsoluteBounds(const WebCore::IntRect& bounds) { + mOriginalAbsoluteBounds = bounds; } + void setIndex(int index) { mIndex = index; } + void setIsCursor(bool isCursor) { mIsCursor = isCursor; } + void setIsFocus(bool isFocus) { mIsFocus = isFocus; } + void setIsInLayer(bool isInLayer) { mIsInLayer = isInLayer; } + void setIsParentAnchor(bool isAnchor) { mIsParentAnchor = isAnchor; } + void setIsTransparent(bool isTransparent) { mIsTransparent = isTransparent; } + void setIsUnclipped(bool unclipped) { mIsUnclipped = unclipped; } + void setLast() { mLast = true; } + void setNavableRects() { mNavableRects = mCursorRing.size(); } + void setParentGroup(void* group) { mParentGroup = group; } + void setParentIndex(int parent) { mParentIndex = parent; } + void setSingleImage(bool single) { mSingleImage = single; } + void setTabIndex(int index) { mTabIndex = index; } + void setType(CachedNodeType type) { mType = type; } + void show() { mIsHidden = false; } + bool singleImage() const { return mSingleImage; } + int tabIndex() const { return mTabIndex; } + int textInputIndex() const { return isTextInput() ? mDataIndex : -1; } + const CachedNode* traverseNextNode() const { return mLast ? NULL : &this[1]; } + bool useBounds() const { return mUseBounds; } + bool useHitBounds() const { return mUseHitBounds; } + bool wantsKeyEvents() const { return isTextInput() || isPlugin() + || isContentEditable() || isFrame(); } +private: + friend class CacheBuilder; + WTF::String mExport; + WebCore::IntRect mBounds; + WebCore::IntRect mHitBounds; + WebCore::IntRect mOriginalAbsoluteBounds; + WTF::Vector<WebCore::IntRect> mCursorRing; + void* mNode; // WebCore::Node*, only used to match pointers + void* mParentGroup; // WebCore::Node*, only used to match pointers + int mDataIndex; // child frame if a frame; input data index; or -1 + int mIndex; // index of itself, to find first in array (document) + int mNavableRects; // FIXME: could be bitfield once I limit max number of rects + int mParentIndex; + int mTabIndex; + int mColorIndex; // index to ring color and other stylable properties + mutable Condition mCondition : 5; // why the node was not chosen on the first pass + CachedNodeType mType : 4; + bool mClippedOut : 1; + bool mDisabled : 1; + bool mFixedUpCursorRects : 1; + bool mHasCursorRing : 1; + bool mHasMouseOver : 1; + bool mIsCursor : 1; + bool mIsFocus : 1; + bool mIsHidden : 1; + bool mIsInLayer : 1; + bool mIsParentAnchor : 1; + bool mIsTransparent : 1; + bool mIsUnclipped : 1; + bool mLast : 1; // true if this is the last node in a group + bool mSingleImage : 1; + bool mUseBounds : 1; + bool mUseHitBounds : 1; +#ifdef BROWSER_DEBUG +public: + WebCore::Node* webCoreNode() const { return (WebCore::Node*) mNode; } + bool mDisplayMeasure; + mutable bool mInCompare; + int mSideDistance; + int mSecondSide; +#endif +#if DEBUG_NAV_UI || DUMP_NAV_CACHE +public: + class Debug { +public: + CachedNode* base() const; + const char* condition(Condition t) const; + void print() const; + const char* type(CachedNodeType t) const; +#if DUMP_NAV_CACHE + int mNodeIndex; + int mParentGroupIndex; +#endif + } mDebug; + friend class CachedNode::Debug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedNodeType.h b/Source/WebKit/android/nav/CachedNodeType.h new file mode 100644 index 0000000..8bc9328 --- /dev/null +++ b/Source/WebKit/android/nav/CachedNodeType.h @@ -0,0 +1,56 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef CachedNodeType_H +#define CachedNodeType_H + +namespace android { + +enum CachedNodeType { + NORMAL_CACHEDNODETYPE, + ADDRESS_CACHEDNODETYPE, + EMAIL_CACHEDNODETYPE, + PHONE_CACHEDNODETYPE, + ANCHOR_CACHEDNODETYPE, + AREA_CACHEDNODETYPE, + FRAME_CACHEDNODETYPE, + PLUGIN_CACHEDNODETYPE, + TEXT_INPUT_CACHEDNODETYPE, + SELECT_CACHEDNODETYPE, + CONTENT_EDITABLE_CACHEDNODETYPE +}; + +enum CachedNodeBits { + NORMAL_CACHEDNODE_BITS = 0, + ADDRESS_CACHEDNODE_BIT = 1 << (ADDRESS_CACHEDNODETYPE - 1), + EMAIL_CACHEDNODE_BIT = 1 << (EMAIL_CACHEDNODETYPE - 1), + PHONE_CACHEDNODE_BIT = 1 << (PHONE_CACHEDNODETYPE - 1), + ALL_CACHEDNODE_BITS = ADDRESS_CACHEDNODE_BIT | EMAIL_CACHEDNODE_BIT + | PHONE_CACHEDNODE_BIT +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedPrefix.h b/Source/WebKit/android/nav/CachedPrefix.h new file mode 100644 index 0000000..73a5c2c --- /dev/null +++ b/Source/WebKit/android/nav/CachedPrefix.h @@ -0,0 +1,53 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef CachedPrefix_H +#define CachedPrefix_H + +#ifndef LOG_TAG +#define LOG_TAG "navcache" +#endif + +#include "config.h" +#include "CachedDebug.h" + +#ifndef _LIBS_CUTILS_LOG_H + #ifdef LOG + #undef LOG + #endif + + #include <utils/Log.h> +#endif + +#define OFFSETOF(type, field) ((char*)&(((type*)1)->field) - (char*)1) // avoids gnu warning + +#ifndef BZERO_DEFINED +#define BZERO_DEFINED +// http://www.opengroup.org/onlinepubs/000095399/functions/bzero.html +// For maximum portability, it is recommended to replace the function call to bzero() as follows: +#define bzero(b,len) (memset((b), '\0', (len)), (void) 0) +#endif + +#endif diff --git a/Source/WebKit/android/nav/CachedRoot.cpp b/Source/WebKit/android/nav/CachedRoot.cpp new file mode 100644 index 0000000..64bf19a --- /dev/null +++ b/Source/WebKit/android/nav/CachedRoot.cpp @@ -0,0 +1,1813 @@ +/* + * Copyright 2007, 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. + */ + +#include "CachedPrefix.h" +#include "android_graphics.h" +#include "CachedHistory.h" +#include "CachedInput.h" +#include "CachedLayer.h" +#include "CachedNode.h" +#include "FindCanvas.h" +#include "FloatRect.h" +#include "LayerAndroid.h" +#include "ParseCanvas.h" +#include "SkBitmap.h" +#include "SkBounder.h" +#include "SkPixelRef.h" +#include "SkRegion.h" + +#include "CachedRoot.h" + +#if DEBUG_NAV_UI +#include "wtf/text/CString.h" +#endif + +#define DONT_CENTER_IF_ALREADY_VISIBLE + +using std::min; +using std::max; + +#ifdef DUMP_NAV_CACHE_USING_PRINTF + extern android::Mutex gWriteLogMutex; +#endif + +namespace android { + +class CommonCheck : public SkBounder { +public: + enum Type { + kNo_Type, + kDrawBitmap_Type, + kDrawGlyph_Type, + kDrawPaint_Type, + kDrawPath_Type, + kDrawPicture_Type, + kDrawPoints_Type, + kDrawPosText_Type, + kDrawPosTextH_Type, + kDrawRect_Type, + kDrawSprite_Type, + kDrawText_Type, + kDrawTextOnPath_Type, + kPopLayer_Type, + kPushLayer_Type, + kPushSave_Type + }; + + static bool isTextType(Type t) { + return t == kDrawPosTextH_Type || t == kDrawText_Type; + } + + CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) { + setEmpty(); + } + + bool doRect(Type type) { + mType = type; + return doIRect(mUnion); + } + + bool isEmpty() { return mUnion.isEmpty(); } + + bool joinGlyphs(const SkIRect& rect) { + bool isGlyph = mType == kDrawGlyph_Type; + if (isGlyph) + mUnion.join(rect); + return isGlyph; + } + + void setAllOpaque(bool opaque) { mAllOpaque = opaque; } + void setEmpty() { mUnion.setEmpty(); } + void setIsOpaque(bool opaque) { mIsOpaque = opaque; } + void setType(Type type) { mType = type; } + + Type mType; + SkIRect mUnion; + bool mAllOpaque; + bool mIsOpaque; +}; + +#if DEBUG_NAV_UI + static const char* TypeNames[] = { + "kNo_Type", + "kDrawBitmap_Type", + "kDrawGlyph_Type", + "kDrawPaint_Type", + "kDrawPath_Type", + "kDrawPicture_Type", + "kDrawPoints_Type", + "kDrawPosText_Type", + "kDrawPosTextH_Type", + "kDrawRect_Type", + "kDrawSprite_Type", + "kDrawText_Type", + "kDrawTextOnPath_Type", + "kPopLayer_Type", + "kPushLayer_Type", + "kPushSave_Type" + }; +#endif + +#define kMargin 16 +#define kSlop 2 + +class BoundsCanvas : public ParseCanvas { +public: + + BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) { + mTransparentLayer = 0; + setBounder(bounder); + } + + virtual void drawPaint(const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPaint_Type); + INHERITED::drawPaint(paint); + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPoints_Type); + INHERITED::drawPoints(mode, count, pts, paint); + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawRect_Type); + INHERITED::drawRect(rect, paint); + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPath_Type); + INHERITED::drawPath(path, paint); + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& matrix, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawBitmap_Type); + mBounder.setIsOpaque(bitmap.isOpaque()); + INHERITED::commonDrawBitmap(bitmap, rect, matrix, paint); + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint) { + mBounder.setType(CommonCheck::kDrawSprite_Type); + mBounder.setIsOpaque(bitmap.isOpaque() && + (!paint || paint->getAlpha() == 255)); + INHERITED::drawSprite(bitmap, left, top, paint); + } + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawText(text, byteLength, x, y, paint); + mBounder.doRect(CommonCheck::kDrawText_Type); + } + + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawPosText(text, byteLength, pos, paint); + if (!mBounder.isEmpty()) + mBounder.doRect(CommonCheck::kDrawPosText_Type); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); + if (mBounder.mUnion.isEmpty()) { + DBG_NAV_LOGD("empty constY=%g", SkScalarToFloat(constY)); + return; + } + SkPaint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent}, + {xpos[0], constY + metrics.fDescent} }; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapPoints(upDown, 2); + if (upDown[0].fX == upDown[1].fX) { + mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY); + mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY); + } + mBounder.doRect(CommonCheck::kDrawPosTextH_Type); + } + + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint); + mBounder.doRect(CommonCheck::kDrawTextOnPath_Type); + } + + virtual void drawPicture(SkPicture& picture) { + mBounder.setType(CommonCheck::kDrawPicture_Type); + INHERITED::drawPicture(picture); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) { + int depth = INHERITED::saveLayer(bounds, paint, flags); + if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) { + mTransparentLayer = depth; + mBounder.setAllOpaque(false); + } + return depth; + } + + virtual void restore() { + mBounder.setType(CommonCheck::kDrawSprite_Type); // for layer draws + int depth = getSaveCount(); + if (depth == mTransparentLayer) { + mTransparentLayer = 0; + mBounder.setAllOpaque(true); + } + INHERITED::restore(); + } + + int mTransparentLayer; + CommonCheck& mBounder; +private: + typedef ParseCanvas INHERITED; +}; + +/* +LeftCheck examines the text in a picture, within a viewable rectangle, +and returns via left() the position of the left edge of the paragraph. +It first looks at the left edge of the test point, then looks above and below +it for more lines of text to determine the div's left edge. +*/ +class LeftCheck : public CommonCheck { +public: + LeftCheck(int x, int y) : mX(x), mY(y), mHitLeft(INT_MAX), + mMostLeft(INT_MAX) { + mHit.set(x - (HIT_SLOP << 1), y - HIT_SLOP, x, y + HIT_SLOP); + mPartial.setEmpty(); + mBounds.setEmpty(); + mPartialType = kNo_Type; + } + + int left() { + if (isTextType(mType)) + doRect(); // process the final line of text + return mMostLeft != INT_MAX ? mMostLeft : mX >> 1; + } + + // FIXME: this is identical to CenterCheck::onIRect() + // refactor so that LeftCheck and CenterCheck inherit common functions + virtual bool onIRect(const SkIRect& rect) { + bool opaqueBitmap = mType == kDrawBitmap_Type && mIsOpaque; + if (opaqueBitmap && rect.contains(mX, mY)) { + mMostLeft = rect.fLeft; + return false; + } + if (joinGlyphs(rect)) // assembles glyphs into a text string + return false; + if (!isTextType(mType) && !opaqueBitmap) + return false; + /* Text on one line may be broken into several parts. Reassemble + the text into a rectangle before considering it. */ + if (rect.fTop < mPartial.fBottom + && rect.fBottom > mPartial.fTop + && mPartial.fRight + JOIN_SLOP_X >= rect.fLeft + && (mPartialType != kDrawBitmap_Type + || mPartial.height() <= rect.height() + JOIN_SLOP_Y)) { + DBG_NAV_LOGD("LeftCheck join mPartial=(%d, %d, %d, %d)" + " rect=(%d, %d, %d, %d)", + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + mPartial.join(rect); + return false; + } + if (mPartial.isEmpty() == false) { + doRect(); // process the previous line of text +#if DEBUG_NAV_UI + if (mHitLeft == INT_MAX) + DBG_NAV_LOGD("LeftCheck disabled rect=(%d, %d, %d, %d)", + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); +#endif + } + mPartial = rect; + mPartialType = mType; + return false; + } + + void doRect() + { + /* Record the outer bounds of the lines of text that intersect the + touch coordinates, given some slop */ + if (SkIRect::Intersects(mPartial, mHit)) { + if (mHitLeft > mPartial.fLeft) + mHitLeft = mPartial.fLeft; + DBG_NAV_LOGD("LeftCheck mHitLeft=%d", mHitLeft); + } else if (mHitLeft == INT_MAX) + return; // wait for intersect success + /* If text is too far away vertically, don't consider it */ + if (!mBounds.isEmpty() && (mPartial.fTop > mBounds.fBottom + HIT_SLOP + || mPartial.fBottom < mBounds.fTop - HIT_SLOP)) { + DBG_NAV_LOGD("LeftCheck stop mPartial=(%d, %d, %d, %d)" + " mBounds=(%d, %d, %d, %d)", + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, + mBounds.fLeft, mBounds.fTop, mBounds.fRight, mBounds.fBottom); + mHitLeft = INT_MAX; // and disable future comparisons + return; + } + /* If the considered text is completely to the left or right of the + touch coordinates, skip it, turn off further detection */ + if (mPartial.fLeft > mX || mPartial.fRight < mX) { + DBG_NAV_LOGD("LeftCheck stop mX=%d mPartial=(%d, %d, %d, %d)", mX, + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom); + mHitLeft = INT_MAX; + return; + } + /* record the smallest margins on the left and right */ + if (mMostLeft > mPartial.fLeft) { + DBG_NAV_LOGD("LeftCheck new mMostLeft=%d (old=%d)", mPartial.fLeft, + mMostLeft); + mMostLeft = mPartial.fLeft; + } + if (mBounds.isEmpty()) + mBounds = mPartial; + else if (mPartial.fBottom > mBounds.fBottom) { + DBG_NAV_LOGD("LeftCheck new bottom=%d (old=%d)", mPartial.fBottom, + mBounds.fBottom); + mBounds.fBottom = mPartial.fBottom; + } + } + + static const int JOIN_SLOP_X = 30; // horizontal space between text parts + static const int JOIN_SLOP_Y = 5; // vertical space between text lines + static const int HIT_SLOP = 30; // diameter allowing for tap size + /* const */ SkIRect mHit; // sloppy hit rectangle + SkIRect mBounds; // reference bounds + SkIRect mPartial; // accumulated text bounds, per line + const int mX; // touch location + const int mY; + int mHitLeft; // touched text extremes + int mMostLeft; // paragraph extremes + Type mPartialType; +}; + +/* +CenterCheck examines the text in a picture, within a viewable rectangle, +and returns via center() the optimal amount to scroll in x to display the +paragraph of text. + +The caller of CenterCheck has configured (but not allocated) a bitmap +the height and three times the width of the view. The picture is drawn centered +in the bitmap, so text that would be revealed, if the view was scrolled up to +a view-width to the left or right, is considered. +*/ +class CenterCheck : public CommonCheck { +public: + CenterCheck(int x, int y, int width) : mX(x), mY(y), + mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX), + mViewLeft(width), mViewRight(width << 1) { + mHit.set(x - CENTER_SLOP, y - CENTER_SLOP, + x + CENTER_SLOP, y + CENTER_SLOP); + mPartial.setEmpty(); + } + + int center() { + doRect(); // process the final line of text + /* If the touch coordinates aren't near any text, return 0 */ + if (mHitLeft == mHitRight) { + DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft); + return 0; + } + int leftOver = mHitLeft - mViewLeft; + int rightOver = mHitRight - mViewRight; + int center; + /* If the touched text is too large to entirely fit on the screen, + center it. */ + if (leftOver < 0 && rightOver > 0) { + center = (leftOver + rightOver) >> 1; + DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d", + leftOver, rightOver, center); + return center; + } + center = (mMostLeft + mMostRight) >> 1; // the paragraph center + if (leftOver > 0 && rightOver >= 0) { // off to the right + if (center > mMostLeft) // move to center loses left-most text? + center = mMostLeft; + } else if (rightOver < 0 && leftOver <= 0) { // off to the left + if (center < mMostRight) // move to center loses right-most text? + center = mMostRight; + } else { +#ifdef DONT_CENTER_IF_ALREADY_VISIBLE + center = 0; // paragraph is already fully visible +#endif + } + DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d", + leftOver, rightOver, center); + return center; + } + +protected: + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) // assembles glyphs into a text string + return false; + if (!isTextType(mType)) + return false; + /* Text on one line may be broken into several parts. Reassemble + the text into a rectangle before considering it. */ + if (rect.fTop < mPartial.fBottom && rect.fBottom > + mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) { + DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)", + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + mPartial.join(rect); + return false; + } + if (mPartial.isEmpty() == false) + doRect(); // process the previous line of text + mPartial = rect; + return false; + } + + void doRect() + { + /* Record the outer bounds of the lines of text that was 'hit' by the + touch coordinates, given some slop */ + if (SkIRect::Intersects(mPartial, mHit)) { + if (mHitLeft > mPartial.fLeft) + mHitLeft = mPartial.fLeft; + if (mHitRight < mPartial.fRight) + mHitRight = mPartial.fRight; + DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight); + } + /* If the considered text is completely to the left or right of the + touch coordinates, skip it */ + if (mPartial.fLeft > mX || mPartial.fRight < mX) + return; + int leftOver = mPartial.fLeft - mViewLeft; + int rightOver = mPartial.fRight - mViewRight; + /* If leftOver <= 0, the text starts off the screen. + If rightOver >= 0, the text ends off the screen. + */ + if (leftOver <= 0 && rightOver >= 0) // discard wider than screen + return; +#ifdef DONT_CENTER_IF_ALREADY_VISIBLE + if (leftOver > 0 && rightOver < 0) // discard already visible + return; +#endif + /* record the smallest margins on the left and right */ + if (mMostLeft > leftOver) + mMostLeft = leftOver; + if (mMostRight < rightOver) + mMostRight = rightOver; + DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d", + leftOver, rightOver, mMostLeft, mMostRight); + } + + static const int CENTER_SLOP = 10; // space between text parts and lines + /* const */ SkIRect mHit; // sloppy hit rectangle + SkIRect mPartial; // accumulated text bounds, per line + const int mX; // touch location + const int mY; + int mHitLeft; // touched text extremes + int mHitRight; + int mMostLeft; // paragraph extremes + int mMostRight; + const int mViewLeft; // middle third of 3x-wide view + const int mViewRight; +}; + +class ImageCanvas : public ParseCanvas { +public: + ImageCanvas(SkBounder* bounder) : mURI(NULL) { + setBounder(bounder); + } + + const char* getURI() { return mURI; } + +protected: +// Currently webkit's bitmap draws always seem to be cull'd before this entry +// point is called, so we assume that any bitmap that gets here is inside our +// tiny clip (may not be true in the future) + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& , const SkPaint& ) { + SkPixelRef* pixelRef = bitmap.pixelRef(); + if (pixelRef != NULL) { + mURI = pixelRef->getURI(); + } + } + +private: + const char* mURI; +}; + +class ImageCheck : public SkBounder { +public: + virtual bool onIRect(const SkIRect& rect) { + return false; + } +}; + +class JiggleCheck : public CommonCheck { +public: + JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) { + mMaxJiggle = 0; + mMinX = mMinJiggle = abs(delta); + mMaxWidth = width + mMinX; + } + + int jiggle() { + if (mMinJiggle > mMaxJiggle) + return mDelta; + int avg = (mMinJiggle + mMaxJiggle + 1) >> 1; + return mDelta < 0 ? -avg : avg; + } + + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) + return false; + if (mType != kDrawBitmap_Type && !isTextType(mType)) + return false; + int min, max; + if (mDelta < 0) { + min = mMinX - rect.fLeft; + max = mMaxWidth - rect.fRight; + } else { + min = rect.fRight - mMaxX; + max = rect.fLeft; + } + if (min <= 0) + return false; + if (max >= mMinX) + return false; + if (mMinJiggle > min) + mMinJiggle = min; + if (mMaxJiggle < max) + mMaxJiggle = max; + return false; + } + + int mDelta; + int mMaxJiggle; + int mMaxX; + int mMinJiggle; + int mMinX; + int mMaxWidth; +}; + +class RingCheck : public CommonCheck { +public: + RingCheck(const WTF::Vector<WebCore::IntRect>& rings, + const WebCore::IntRect& bitBounds, const WebCore::IntRect& testBounds, + bool singleImage) + : mTestBounds(testBounds) + , mBitBounds(bitBounds) + , mPushPop(false) + , mSingleImage(singleImage) + { + const WebCore::IntRect* r; + for (r = rings.begin(); r != rings.end(); r++) { + SkIRect fatter = {r->x(), r->y(), r->right(), r->bottom()}; + fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS); + DBG_NAV_LOGD("RingCheck fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop, + fatter.fRight, fatter.fBottom); + mTextSlop.op(fatter, SkRegion::kUnion_Op); + mTextTest.op(*r, SkRegion::kUnion_Op); + } + int dx = -bitBounds.x(); + int dy = -bitBounds.y(); + DBG_NAV_LOGD("RingCheck translate=(%d,%d)", dx, dy); + mTextSlop.translate(dx, dy); + mTextTest.translate(dx, dy); + mTestBounds.translate(dx, dy); + mEmpty.setEmpty(); + } + + bool hiddenRings(SkRegion* clipped) + { + findBestLayer(); + if (!mBestLayer) { + DBG_NAV_LOG("RingCheck empty"); + clipped->setEmpty(); + return true; + } + const SkRegion* layersEnd = mLayers.end(); + const Type* layerTypes = &mLayerTypes[mBestLayer - mLayers.begin()]; + bool collectGlyphs = true; + bool collectOvers = false; + SkRegion over; + for (const SkRegion* layers = mBestLayer; layers != layersEnd; layers++) { + Type layerType = *layerTypes++; + DBG_NAV_LOGD("RingCheck #%d %s (%d,%d,r=%d,b=%d)", + layers - mLayers.begin(), TypeNames[layerType], + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom); + if (collectGlyphs && (layerType == kDrawGlyph_Type + || ((layerType == kDrawRect_Type && mTextTest.contains(*layers)) + || (layerType == kDrawBitmap_Type && mTextSlop.contains(*layers))))) { + DBG_NAV_LOGD("RingCheck #%d collectOvers", layers - mLayers.begin()); + collectOvers = true; + clipped->op(*layers, SkRegion::kUnion_Op); + continue; + } + collectGlyphs &= layerType != kPushLayer_Type; + if (collectOvers && (layerType == kDrawRect_Type + || layerType == kDrawBitmap_Type + || (!collectGlyphs && layerType == kDrawSprite_Type))) { + DBG_NAV_LOGD("RingCheck #%d over.op", layers - mLayers.begin()); + over.op(*layers, SkRegion::kUnion_Op); + } + } + bool result = !collectOvers || clipped->intersects(over); + const SkIRect t = clipped->getBounds(); + const SkIRect o = over.getBounds(); + clipped->op(over, SkRegion::kDifference_Op); + clipped->translate(mBitBounds.x(), mBitBounds.y()); + const SkIRect c = clipped->getBounds(); + DBG_NAV_LOGD("RingCheck intersects=%s text=(%d,%d,r=%d,b=%d)" + " over=(%d,%d,r=%d,b=%d) clipped=(%d,%d,r=%d,b=%d)", + result ? "true" : "false", + t.fLeft, t.fTop, t.fRight, t.fBottom, + o.fLeft, o.fTop, o.fRight, o.fBottom, + c.fLeft, c.fTop, c.fRight, c.fBottom); + return result; + } + + void push(Type type, const SkIRect& bounds) + { +#if DEBUG_NAV_UI + // this caches the push string and subquently ignores if pushSave + // is immediately followed by popLayer. Push/pop pairs happen + // frequently and just add noise to the log. + static String lastLog; + String currentLog = String("RingCheck append #") + + String::number(mLayers.size()) + + " type=" + TypeNames[type] + " bounds=(" + + String::number(bounds.fLeft) + + "," + String::number(bounds.fTop) + "," + + String::number(bounds.fRight) + "," + + String::number(bounds.fBottom) + ")"; + if (lastLog.length() == 0 || type != kPopLayer_Type) { + if (lastLog.length() != 0) + DBG_NAV_LOGD("%s", lastLog.latin1().data()); + if (type == kPushSave_Type) + lastLog = currentLog; + else + DBG_NAV_LOGD("%s", currentLog.latin1().data()); + } else + lastLog = ""; +#endif + popEmpty(); + mPushPop |= type >= kPopLayer_Type; + if (type == kPopLayer_Type) { + Type last = mLayerTypes.last(); + // remove empty brackets + if (last == kPushLayer_Type || last == kPushSave_Type) { + mLayers.removeLast(); + mLayerTypes.removeLast(); + return; + } + // remove push/pop from push/bitmap/pop + size_t pushIndex = mLayerTypes.size() - 2; + if (last == kDrawBitmap_Type + && mLayerTypes.at(pushIndex) == kPushLayer_Type) { + mLayers.at(pushIndex) = mLayers.last(); + mLayerTypes.at(pushIndex) = kDrawBitmap_Type; + mLayers.removeLast(); + mLayerTypes.removeLast(); + return; + } + // remove non-layer brackets + int stack = 0; + Type* types = mLayerTypes.end(); + while (types != mLayerTypes.begin()) { + Type type = *--types; + if (type == kPopLayer_Type) { + stack++; + continue; + } + if (type != kPushLayer_Type && type != kPushSave_Type) + continue; + if (--stack >= 0) + continue; + if (type == kPushLayer_Type) + break; + int remove = types - mLayerTypes.begin(); + DBG_NAV_LOGD("RingCheck remove=%d mLayers.size=%d" + " mLayerTypes.size=%d", remove, mLayers.size(), + mLayerTypes.size()); + mLayers.remove(remove); + mLayerTypes.remove(remove); + mAppendLikeTypes = false; + return; + } + } + mLayers.append(bounds); + mLayerTypes.append(type); + } + + void startText(const SkPaint& paint) + { + mPaint = &paint; + if (!mLayerTypes.isEmpty() && mLayerTypes.last() == kDrawGlyph_Type + && !mLayers.last().isEmpty()) { + push(kDrawGlyph_Type, mEmpty); + } + } + + bool textOutsideRings() + { + findBestLayer(); + if (!mBestLayer) { + DBG_NAV_LOG("RingCheck empty"); + return false; + } + const SkRegion* layers = mBestLayer; + const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; + // back up to include text drawn before the best layer + SkRegion active = SkRegion(mBitBounds); + active.translate(-mBitBounds.x(), -mBitBounds.y()); + while (layers != mLayers.begin()) { + --layers; + Type layerType = *--layerTypes; + DBG_NAV_LOGD("RingCheck #%d %s" + " mTestBounds=(%d,%d,r=%d,b=%d) layers=(%d,%d,r=%d,b=%d)" + " active=(%d,%d,r=%d,b=%d)", + layers - mLayers.begin(), TypeNames[layerType], + mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, + mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom, + active.getBounds().fLeft, active.getBounds().fTop, + active.getBounds().fRight, active.getBounds().fBottom); + if (layerType == kDrawRect_Type || layerType == kDrawBitmap_Type) { + SkRegion temp = *layers; + temp.op(mTestBounds, SkRegion::kIntersect_Op); + active.op(temp, SkRegion::kDifference_Op); + if (active.isEmpty()) { + DBG_NAV_LOGD("RingCheck #%d empty", layers - mLayers.begin()); + break; + } + } else if (layerType == kDrawGlyph_Type) { + SkRegion temp = *layers; + temp.op(active, SkRegion::kIntersect_Op); + if (!mTestBounds.intersects(temp)) + continue; + if (!mTestBounds.contains(temp)) + return false; + } else + break; + } + layers = mBestLayer; + layerTypes = &mLayerTypes[layers - mLayers.begin()]; + bool foundGlyph = false; + bool collectGlyphs = true; + do { + Type layerType = *layerTypes++; + DBG_NAV_LOGD("RingCheck #%d %s mTestBounds=(%d,%d,r=%d,b=%d)" + " layers=(%d,%d,r=%d,b=%d) collects=%s intersects=%s contains=%s", + layers - mLayers.begin(), TypeNames[layerType], + mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, + mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom, + collectGlyphs ? "true" : "false", + mTestBounds.intersects(*layers) ? "true" : "false", + mTextSlop.contains(*layers) ? "true" : "false"); + if (collectGlyphs && layerType == kDrawGlyph_Type) { + if (!mTestBounds.intersects(*layers)) + continue; + if (!mTextSlop.contains(*layers)) + return false; + foundGlyph = true; + } + collectGlyphs &= layerType != kPushLayer_Type; + } while (++layers != mLayers.end()); + DBG_NAV_LOGD("RingCheck foundGlyph=%s", foundGlyph ? "true" : "false"); + return foundGlyph; + } + +protected: + virtual bool onIRect(const SkIRect& rect) + { + joinGlyphs(rect); + if (mType != kDrawGlyph_Type && mType != kDrawRect_Type + && mType != kDrawSprite_Type && mType != kDrawBitmap_Type) + return false; + if (mLayerTypes.isEmpty() || mLayerTypes.last() != mType + || !mAppendLikeTypes || mPushPop || mSingleImage + // if the last and current were not glyphs, + // and the two bounds have a gap between, don't join them -- push + // an empty between them + || (mType != kDrawGlyph_Type && !joinable(rect))) { + push(mType, mEmpty); + } + DBG_NAV_LOGD("RingCheck join %s (%d,%d,r=%d,b=%d) '%c'", + TypeNames[mType], rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + mCh); + mLayers.last().op(rect, SkRegion::kUnion_Op); + mAppendLikeTypes = true; + mPushPop = false; + return false; + } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + mCh = ' '; + if (mPaint) { + SkUnichar unichar; + SkPaint utfPaint = *mPaint; + utfPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + utfPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar); + mCh = unichar < 0x7f ? unichar : '?'; + } + return onIRect(rect); + } + +private: + int calcOverlap(SkRegion& testRegion) + { + if (testRegion.isEmpty()) + return INT_MAX; + testRegion.op(mTextTest, SkRegion::kXOR_Op); + SkRegion::Iterator iter(testRegion); + int area = 0; + while (!iter.done()) { + const SkIRect& cr = iter.rect(); + area += cr.width() * cr.height(); + iter.next(); + } + DBG_NAV_LOGD("RingCheck area=%d", area); + return area; + } + + void findBestLayer() + { + popEmpty(); + mBestLayer = 0; + const SkRegion* layers = mLayers.begin(); + const SkRegion* layersEnd = mLayers.end(); + if (layers == layersEnd) { + DBG_NAV_LOG("RingCheck empty"); + return; + } + // find text most like focus rings by xoring found with original + int bestArea = INT_MAX; + const SkRegion* testLayer = 0; + SkRegion testRegion; + const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; + for (; layers != mLayers.end(); layers++) { + Type layerType = *layerTypes++; +#if DEBUG_NAV_UI + const SkIRect& gb = layers->getBounds(); + const SkIRect& tb = mTextSlop.getBounds(); + DBG_NAV_LOGD("RingCheck #%d %s mTextSlop=(%d,%d,%d,%d)" + " contains=%s bounds=(%d,%d,%d,%d)", + layers - mLayers.begin(), TypeNames[layerType], + tb.fLeft, tb.fTop, tb.fRight, tb.fBottom, + mTextSlop.contains(*layers) ? "true" : "false", + gb.fLeft, gb.fTop, gb.fRight, gb.fBottom); +#endif + if (((layerType == kDrawGlyph_Type || layerType == kDrawBitmap_Type) + && mTextSlop.contains(*layers)) + || (layerType == kDrawRect_Type + && mTextTest.contains(*layers))) { + if (!testLayer) + testLayer = layers; + testRegion.op(*layers, SkRegion::kUnion_Op); + continue; + } + if (testLayer) { + int area = calcOverlap(testRegion); + if (bestArea > area) { + bestArea = area; + mBestLayer = testLayer; + } + DBG_NAV_LOGD("RingCheck #%d push test=%d best=%d", + layers - mLayers.begin(), testLayer - mLayers.begin(), + mBestLayer ? mBestLayer - mLayers.begin() : -1); + testRegion.setEmpty(); + testLayer = 0; + } + } + if (testLayer && bestArea > calcOverlap(testRegion)) { + DBG_NAV_LOGD("RingCheck last best=%d", testLayer - mLayers.begin()); + mBestLayer = testLayer; + } + } + + bool joinable(const SkIRect& rect) + { + SkRegion region = mLayers.last(); + if (!region.isRect()) + return false; + const SkIRect& bounds1 = region.getBounds(); + int area1 = bounds1.width() * bounds1.height(); + area1 += rect.width() * rect.height(); + region.op(rect, SkRegion::kUnion_Op); + const SkIRect& bounds2 = region.getBounds(); + int area2 = bounds2.width() * bounds2.height(); + return area2 <= area1; + } + + void popEmpty() + { + if (mLayerTypes.size() == 0) + return; + Type last = mLayerTypes.last(); + if (last >= kPopLayer_Type) + return; + const SkRegion& area = mLayers.last(); + if (!area.isEmpty()) + return; + DBG_NAV_LOGD("RingCheck #%d %s", mLayers.size() - 1, TypeNames[last]); + mLayers.removeLast(); + mLayerTypes.removeLast(); + } + + SkRegion mTestBounds; + IntRect mBitBounds; + SkIRect mEmpty; + const SkRegion* mBestLayer; + SkRegion mTextSlop; // outset rects for inclusion test + SkRegion mTextTest; // exact rects for xor area test + Type mLastType; + Vector<SkRegion> mLayers; + Vector<Type> mLayerTypes; + const SkPaint* mPaint; + char mCh; + bool mAppendLikeTypes; + bool mPushPop; + bool mSingleImage; +}; + +class RingCanvas : public BoundsCanvas { +public: + RingCanvas(RingCheck* bounder) + : INHERITED(bounder) + { + } + +protected: + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawText(text, byteLength, x, y, paint); + } + + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawPosText(text, byteLength, pos, paint); + } + + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); + } + + virtual int save(SaveFlags flags) + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPushSave_Type, getTotalClip().getBounds()); + return INHERITED::save(flags); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPushLayer_Type, getTotalClip().getBounds()); + return INHERITED::save(flags); + } + + virtual void restore() + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPopLayer_Type, getTotalClip().getBounds()); + INHERITED::restore(); + } + +private: + typedef BoundsCanvas INHERITED; +}; + +bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction, + WebCore::IntPoint* scrollPtr, bool findClosest) +{ + WebCore::IntRect newOutset; + const CachedNode* newNode = best->mNode; + // see if there's a middle node + // if the middle node is in the visited list, + // or if none was computed and the newNode is in the visited list, + // treat result as NULL + if (newNode != NULL && findClosest) { + if (best->bounds().intersects(mHistory->mPriorBounds) == false && + checkBetween(best, direction)) + newNode = best->mNode; + if (findClosest && maskIfHidden(best)) { + innerMove(document(), best, direction, scrollPtr, false); + return true; + } + newOutset = newNode->cursorRingBounds(best->mFrame); + } + int delta; + bool newNodeInView = scrollDelta(newOutset, direction, &delta); + if (delta && scrollPtr && (newNode == NULL || newNodeInView == false || + (best->mNavOutside && best->mWorkingOutside))) + *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta, + direction & UP_DOWN ? delta : 0); + return false; +} + +void CachedRoot::calcBitBounds(const IntRect& nodeBounds, IntRect* bitBounds) const +{ + IntRect contentBounds = IntRect(0, 0, mPicture->width(), mPicture->height()); + IntRect overBounds = nodeBounds; + overBounds.inflate(kMargin); + IntRect viewableBounds = mScrolledBounds; + viewableBounds.unite(mViewBounds); + *bitBounds = contentBounds; + bitBounds->intersect(overBounds); + if (!bitBounds->intersects(viewableBounds)) + *bitBounds = IntRect(0, 0, 0, 0); + DBG_NAV_LOGD("contentBounds=(%d,%d,r=%d,b=%d) overBounds=(%d,%d,r=%d,b=%d)" + " mScrolledBounds=(%d,%d,r=%d,b=%d) mViewBounds=(%d,%d,r=%d,b=%d)" + " bitBounds=(%d,%d,r=%d,b=%d)", + contentBounds.x(), contentBounds.y(), contentBounds.right(), + contentBounds.bottom(), + overBounds.x(), overBounds.y(), overBounds.right(), overBounds.bottom(), + mScrolledBounds.x(), mScrolledBounds.y(), mScrolledBounds.right(), + mScrolledBounds.bottom(), + mViewBounds.x(), mViewBounds.y(), mViewBounds.right(), + mViewBounds.bottom(), + bitBounds->x(), bitBounds->y(), bitBounds->right(), + bitBounds->bottom()); +} + + +int CachedRoot::checkForCenter(int x, int y) const +{ + int width = mViewBounds.width(); + SkPicture* picture = pictureAt(&x, &y); + CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(), + width); + BoundsCanvas checker(¢erCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3, + mViewBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(width - mViewBounds.x()), + SkIntToScalar(-mViewBounds.y())); + checker.drawPicture(*picture); + return centerCheck.center(); +} + +void CachedRoot::checkForJiggle(int* xDeltaPtr) const +{ + int xDelta = *xDeltaPtr; + JiggleCheck jiggleCheck(xDelta, mViewBounds.width()); + BoundsCanvas checker(&jiggleCheck); + SkBitmap bitmap; + int absDelta = abs(xDelta); + bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() + + absDelta, mViewBounds.height()); + checker.setBitmapDevice(bitmap); + int x = -mViewBounds.x() - (xDelta < 0 ? xDelta : 0); + int y = -mViewBounds.y(); + SkPicture* picture = pictureAt(&x, &y); + checker.translate(SkIntToScalar(x), SkIntToScalar(y)); + checker.drawPicture(*picture); + *xDeltaPtr = jiggleCheck.jiggle(); +} + +bool CachedRoot::checkRings(SkPicture* picture, const CachedNode* node, + const WebCore::IntRect& testBounds) const +{ + if (!picture) + return false; + const WTF::Vector<WebCore::IntRect>& rings = node->rings(); + const WebCore::IntRect& nodeBounds = node->rawBounds(); + IntRect bitBounds; + calcBitBounds(nodeBounds, &bitBounds); + RingCheck ringCheck(rings, bitBounds, testBounds, node->singleImage()); + RingCanvas checker(&ringCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), + bitBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(-bitBounds.x()), + SkIntToScalar(-bitBounds.y())); + checker.drawPicture(*picture); + bool result = ringCheck.textOutsideRings(); + DBG_NAV_LOGD("bitBounds=(%d,%d,r=%d,b=%d) nodeBounds=(%d,%d,r=%d,b=%d)" + " testBounds=(%d,%d,r=%d,b=%d) success=%s", + bitBounds.x(), bitBounds.y(), bitBounds.right(), bitBounds.bottom(), + nodeBounds.x(), nodeBounds.y(), nodeBounds.right(), nodeBounds.bottom(), + testBounds.x(), testBounds.y(), testBounds.right(), testBounds.bottom(), + result ? "true" : "false"); + return result; +} + +void CachedRoot::draw(FindCanvas& canvas) const +{ + canvas.setLayerId(-1); // overlays change the ID as their pictures draw + canvas.drawPicture(*mPicture); +#if USE(ACCELERATED_COMPOSITING) + if (!mRootLayer) + return; + canvas.drawLayers(mRootLayer); +#endif +} + +const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* x, int* y, bool checkForHidden) const +{ +#if DEBUG_NAV_UI + DBG_NAV_LOGD("rect=(%d,%d,w=%d,h=%d) xy=(%d,%d)", rect.x(), rect.y(), + rect.width(), rect.height(), *x, *y); + if (mRootLayer) CachedLayer::Debug::printRootLayerAndroid(mRootLayer); +#endif + int best = INT_MAX; + bool inside = false; + (const_cast<CachedRoot*>(this))->resetClippedOut(); + const CachedFrame* directHitFramePtr; + const CachedNode* directHit = NULL; + const CachedNode* node = findBestAt(rect, &best, &inside, &directHit, + &directHitFramePtr, framePtr, x, y, checkForHidden); + DBG_NAV_LOGD("node=%d (%p) xy=(%d,%d)", node == NULL ? 0 : node->index(), + node == NULL ? NULL : node->nodePointer(), *x, *y); + if (node == NULL) { + node = findBestHitAt(rect, framePtr, x, y); + DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), + node == NULL ? NULL : node->nodePointer()); + } + if (node == NULL) { + *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1), + rect.y() + (rect.height() >> 1)); + } + return node; +} + +WebCore::IntPoint CachedRoot::cursorLocation() const +{ + const WebCore::IntRect& bounds = mHistory->mNavBounds; + return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1), + bounds.y() + (bounds.height() >> 1)); +} + +WebCore::IntPoint CachedRoot::focusLocation() const +{ + return WebCore::IntPoint(mFocusBounds.x() + (mFocusBounds.width() >> 1), + mFocusBounds.y() + (mFocusBounds.height() >> 1)); +} + +// These reset the values because we only want to get the selection the first time. +// After that, the selection is no longer accurate. +int CachedRoot::getAndResetSelectionEnd() +{ + int end = mSelectionEnd; + mSelectionEnd = -1; + return end; +} + +int CachedRoot::getAndResetSelectionStart() +{ + int start = mSelectionStart; + mSelectionStart = -1; + return start; +} + +int CachedRoot::getBlockLeftEdge(int x, int y, float scale) const +{ + DBG_NAV_LOGD("x=%d y=%d scale=%g mViewBounds=(%d,%d,%d,%d)", x, y, scale, + mViewBounds.x(), mViewBounds.y(), mViewBounds.width(), + mViewBounds.height()); + // if (x, y) is in a textArea or textField, return that + const int slop = 1; + WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop, + slop * 2, slop * 2); + const CachedFrame* frame; + int fx, fy; + const CachedNode* node = findAt(rect, &frame, &fx, &fy, true); + if (node && node->wantsKeyEvents()) { + DBG_NAV_LOGD("x=%d (%s)", node->bounds(frame).x(), + node->isTextInput() ? "text" : "plugin"); + return node->bounds(frame).x(); + } + SkPicture* picture = node ? frame->picture(node, &x, &y) : pictureAt(&x, &y); + if (!picture) + return x; + int halfW = (int) (mViewBounds.width() * scale * 0.5f); + int fullW = halfW << 1; + int halfH = (int) (mViewBounds.height() * scale * 0.5f); + int fullH = halfH << 1; + LeftCheck leftCheck(fullW, halfH); + BoundsCanvas checker(&leftCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, fullW, fullH); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(fullW - x), SkIntToScalar(halfH - y)); + checker.drawPicture(*picture); + int result = x + leftCheck.left() - fullW; + DBG_NAV_LOGD("halfW=%d halfH=%d mMostLeft=%d x=%d", + halfW, halfH, leftCheck.mMostLeft, result); + return result; +} + +void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) const +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds; + int x = mouseBounds.x(); + int y = mouseBounds.y(); + int width = mouseBounds.width(); + int height = mouseBounds.height(); + point->setX(x + (width >> 1)); // default to box center + point->setY(y + (height >> 1)); +#if DEBUG_NAV_UI + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + DBG_NAV_LOGD("mHistory->mNavBounds={%d,%d,%d,%d} " + "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}", + navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(), + mouseBounds.x(), mouseBounds.y(), mouseBounds.width(), + mouseBounds.height(), point->x(), point->y()); +#endif +} + +void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history) +{ + CachedFrame::init(this, -1, frame); + reset(); + mHistory = history; + mPicture = NULL; +} + +bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingVertical() >= mViewBounds.x()); + ASSERT(maxWorkingVertical() <= mViewBounds.right()); + setupScrolledBounds(); + // (line up) + mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); + int testTop = mScrolledBounds.y(); + int viewBottom = mViewBounds.bottom(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.bottom() > viewBottom && viewBottom < mContents.height()) + return false; + if (navBounds.isEmpty() == false) { + int navTop = navBounds.y(); + int scrollBottom; + if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) { + mScrolledBounds.setHeight(scrollBottom - navTop); + mScrolledBounds.setY(navTop); + } + } + setCursorCache(0, mMaxYScroll); + frameDown(test, NULL, bestData); + return true; +} + +bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingHorizontal() >= mViewBounds.y()); + ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); + setupScrolledBounds(); + mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll); + mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); + int testRight = mScrolledBounds.right(); + int viewLeft = mViewBounds.x(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.x() < viewLeft && viewLeft > mContents.x()) + return false; + if (navBounds.isEmpty() == false) { + int navRight = navBounds.right(); + int scrollLeft; + if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x())) + mScrolledBounds.setWidth(navRight - scrollLeft); + } + setCursorCache(-mMaxXScroll, 0); + frameLeft(test, NULL, bestData); + return true; +} + + +void CachedRoot::innerMove(const CachedNode* node, BestData* bestData, + Direction direction, WebCore::IntPoint* scroll, bool firstCall) +{ + bestData->reset(); + bool outOfCursor = mCursorIndex == CURSOR_CLEARED; + DBG_NAV_LOGD("mHistory->didFirstLayout()=%s && mCursorIndex=%d", + mHistory->didFirstLayout() ? "true" : "false", mCursorIndex); + if (mHistory->didFirstLayout() && mCursorIndex < CURSOR_SET) { + mHistory->reset(); + outOfCursor = true; + } + const CachedFrame* cursorFrame; + const CachedNode* cursor = currentCursor(&cursorFrame); + mHistory->setWorking(direction, cursorFrame, cursor, mViewBounds); + bool findClosest = false; + if (mScrollOnly == false) { + switch (direction) { + case LEFT: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(), + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerLeft(node, bestData); + break; + case RIGHT: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1, + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerRight(node, bestData); + break; + case UP: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), + mViewBounds.bottom(), mViewBounds.width(), 1); + findClosest = innerUp(node, bestData); + break; + case DOWN: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), + mViewBounds.y() - 1, mViewBounds.width(), 1); + findClosest = innerDown(node, bestData); + break; + case UNINITIALIZED: + default: + ASSERT(0); + } + } + if (firstCall) + mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL + bestData->setMouseBounds(bestData->bounds()); + if (adjustForScroll(bestData, direction, scroll, findClosest)) + return; + if (bestData->mNode != NULL) { + mHistory->addToVisited(bestData->mNode, direction); + mHistory->mNavBounds = bestData->bounds(); + mHistory->mMouseBounds = bestData->mouseBounds(); + } else if (scroll->x() != 0 || scroll->y() != 0) { + WebCore::IntRect newBounds = mHistory->mNavBounds; + int offsetX = scroll->x(); + int offsetY = scroll->y(); + newBounds.move(offsetX, offsetY); + if (mViewBounds.x() > newBounds.x()) + offsetX = mViewBounds.x() - mHistory->mNavBounds.x(); + else if (mViewBounds.right() < newBounds.right()) + offsetX = mViewBounds.right() - mHistory->mNavBounds.right(); + if (mViewBounds.y() > newBounds.y()) + offsetY = mViewBounds.y() - mHistory->mNavBounds.y(); + else if (mViewBounds.bottom() < newBounds.bottom()) + offsetY = mViewBounds.bottom() - mHistory->mNavBounds.bottom(); + mHistory->mNavBounds.move(offsetX, offsetY); + } + mHistory->setDidFirstLayout(false); +} + +bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingHorizontal() >= mViewBounds.y()); + ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); + setupScrolledBounds(); + // (align) + mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); + int testLeft = mScrolledBounds.x(); + int viewRight = mViewBounds.right(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.right() > viewRight && viewRight < mContents.width()) + return false; + if (navBounds.isEmpty() == false) { + int navLeft = navBounds.x(); + int scrollRight; + if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) { + mScrolledBounds.setWidth(scrollRight - navLeft); + mScrolledBounds.setX(navLeft); + } + } + setCursorCache(mMaxXScroll, 0); + frameRight(test, NULL, bestData); + return true; +} + +bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingVertical() >= mViewBounds.x()); + ASSERT(maxWorkingVertical() <= mViewBounds.right()); + setupScrolledBounds(); + mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll); + mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); + int testBottom = mScrolledBounds.bottom(); + int viewTop = mViewBounds.y(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.y() < viewTop && viewTop > mContents.y()) + return false; + if (navBounds.isEmpty() == false) { + int navBottom = navBounds.bottom(); + int scrollTop; + if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y())) + mScrolledBounds.setHeight(navBottom - scrollTop); + } + setCursorCache(0, -mMaxYScroll); + frameUp(test, NULL, bestData); + return true; +} + +WTF::String CachedRoot::imageURI(int x, int y) const +{ + DBG_NAV_LOGD("x/y=(%d,%d)", x, y); + ImageCheck imageCheck; + ImageCanvas checker(&imageCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); + checker.setBitmapDevice(bitmap); + SkPicture* picture = pictureAt(&x, &y); + checker.translate(SkIntToScalar(-x), SkIntToScalar(-y)); + checker.drawPicture(*picture); + DBG_NAV_LOGD("uri=%s", checker.getURI()); + return WTF::String(checker.getURI()); +} + +bool CachedRoot::maskIfHidden(BestData* best) const +{ + const CachedNode* bestNode = best->mNode; + if (bestNode->isUnclipped()) + return false; + const CachedFrame* frame = best->mFrame; + SkPicture* picture = frame->picture(bestNode); + if (picture == NULL) { + DBG_NAV_LOG("missing picture"); + return false; + } + Vector<IntRect> rings; + bestNode->cursorRings(frame, &rings); + const WebCore::IntRect& bounds = bestNode->bounds(frame); + IntRect bitBounds; + calcBitBounds(bounds, &bitBounds); + RingCheck ringCheck(rings, bitBounds, bounds, bestNode->singleImage()); + RingCanvas checker(&ringCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), + bitBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(-bitBounds.x()), + SkIntToScalar(-bitBounds.y())); + checker.drawPicture(*picture); + SkRegion clipRgn; + bool clipped = ringCheck.hiddenRings(&clipRgn); + CachedNode* node = const_cast<CachedNode*>(best->mNode); + DBG_NAV_LOGD("clipped=%s clipRgn.isEmpty=%s", clipped ? "true" : "false", + clipRgn.isEmpty() ? "true" : "false"); + if (clipped && clipRgn.isEmpty()) { + node->setDisabled(true); + IntRect clippedBounds = bounds; + clippedBounds.intersect(bitBounds); + node->setClippedOut(clippedBounds != bounds); + return true; + } + // was it partially occluded by later drawing? + // if partially occluded, modify the bounds so that the mouse click has a better x,y + if (clipped) { + DBG_NAV_LOGD("clipped clipRgn={%d,%d,r=%d,b=%d}", + clipRgn.getBounds().fLeft, clipRgn.getBounds().fTop, + clipRgn.getBounds().fRight, clipRgn.getBounds().fBottom); + best->setMouseBounds(clipRgn.getBounds()); + if (!node->clip(best->mouseBounds())) { + node->setDisabled(true); + node->setClippedOut(true); + return true; + } + } else + node->fixUpCursorRects(frame); + return false; +} + +const CachedNode* CachedRoot::moveCursor(Direction direction, const CachedFrame** framePtr, + WebCore::IntPoint* scroll) +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + CachedRoot* frame = this; + const CachedNode* node = frame->document(); + if (node == NULL) + return NULL; + if (mViewBounds.isEmpty()) + return NULL; + resetClippedOut(); + setData(); + BestData bestData; + innerMove(node, &bestData, direction, scroll, true); + // if node is partially or fully concealed by layer, scroll it into view + if (mRootLayer && bestData.mNode && !bestData.mNode->isInLayer()) { +#if USE(ACCELERATED_COMPOSITING) +#if DUMP_NAV_CACHE + CachedLayer::Debug::printRootLayerAndroid(mRootLayer); +#endif + SkIRect original = bestData.mNode->cursorRingBounds(bestData.mFrame); + DBG_NAV_LOGD("original=(%d,%d,w=%d,h=%d) scroll=(%d,%d)", + original.fLeft, original.fTop, original.width(), original.height(), + scroll->x(), scroll->y()); + original.offset(-scroll->x(), -scroll->y()); + SkRegion rings(original); + SkTDArray<SkRect> region; + mRootLayer->clipArea(®ion); + SkRegion layers; + for (int index = 0; index < region.count(); index++) { + SkIRect enclosing; + region[index].round(&enclosing); + rings.op(enclosing, SkRegion::kDifference_Op); + layers.op(enclosing, SkRegion::kUnion_Op); + } + SkIRect layerBounds(layers.getBounds()); + SkIRect ringBounds(rings.getBounds()); + int scrollX = scroll->x(); + int scrollY = scroll->y(); + if (rings.getBounds() != original) { + int topOverlap = layerBounds.fBottom - original.fTop; + int bottomOverlap = original.fBottom - layerBounds.fTop; + int leftOverlap = layerBounds.fRight - original.fLeft; + int rightOverlap = original.fRight - layerBounds.fLeft; + if (direction & UP_DOWN) { + if (layerBounds.fLeft < original.fLeft && leftOverlap < 0) + scroll->setX(leftOverlap); + if (original.fRight < layerBounds.fRight && rightOverlap > 0 + && -leftOverlap > rightOverlap) + scroll->setX(rightOverlap); + bool topSet = scrollY > topOverlap && (direction == UP + || !scrollY); + if (topSet) + scroll->setY(topOverlap); + if (scrollY < bottomOverlap && (direction == DOWN || (!scrollY + && (!topSet || -topOverlap > bottomOverlap)))) + scroll->setY(bottomOverlap); + } else { + if (layerBounds.fTop < original.fTop && topOverlap < 0) + scroll->setY(topOverlap); + if (original.fBottom < layerBounds.fBottom && bottomOverlap > 0 + && -topOverlap > bottomOverlap) + scroll->setY(bottomOverlap); + bool leftSet = scrollX > leftOverlap && (direction == LEFT + || !scrollX); + if (leftSet) + scroll->setX(leftOverlap); + if (scrollX < rightOverlap && (direction == RIGHT || (!scrollX + && (!leftSet || -leftOverlap > rightOverlap)))) + scroll->setX(rightOverlap); + } + DBG_NAV_LOGD("rings=(%d,%d,w=%d,h=%d) layers=(%d,%d,w=%d,h=%d)" + " scroll=(%d,%d)", + ringBounds.fLeft, ringBounds.fTop, ringBounds.width(), ringBounds.height(), + layerBounds.fLeft, layerBounds.fTop, layerBounds.width(), layerBounds.height(), + scroll->x(), scroll->y()); + } +#endif + } + *framePtr = bestData.mFrame; + return const_cast<CachedNode*>(bestData.mNode); +} + +const CachedNode* CachedRoot::nextTextField(const CachedNode* start, + const CachedFrame** framePtr) const +{ + bool startFound = false; + return CachedFrame::nextTextField(start, framePtr, &startFound); +} + +SkPicture* CachedRoot::pictureAt(int* xPtr, int* yPtr, int* id) const +{ +#if USE(ACCELERATED_COMPOSITING) + if (mRootLayer) { + const LayerAndroid* layer = mRootLayer->find(xPtr, yPtr, mPicture); + if (layer) { + SkPicture* picture = layer->picture(); + DBG_NAV_LOGD("layer %d picture=%p (%d,%d)", layer->uniqueId(), + picture, picture ? picture->width() : 0, + picture ? picture->height() : 0); + if (picture) { + if (id) + *id = layer->uniqueId(); + return picture; + } + } + } +#endif + DBG_NAV_LOGD("root mPicture=%p (%d,%d)", mPicture, mPicture ? + mPicture->width() : 0, mPicture ? mPicture->height() : 0); + if (id) + *id = -1; + return mPicture; +} + +void CachedRoot::reset() +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0); + mMaxXScroll = mMaxYScroll = 0; + mRootLayer = 0; + mSelectionStart = mSelectionEnd = -1; + mScrollOnly = false; +} + +bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta) +{ + switch (direction) { + case LEFT: + *delta = -mMaxXScroll; + return newOutset.x() >= mViewBounds.x(); + case RIGHT: + *delta = mMaxXScroll; + return newOutset.right() <= mViewBounds.right(); + case UP: + *delta = -mMaxYScroll; + return newOutset.y() >= mViewBounds.y(); + case DOWN: + *delta = mMaxYScroll; + return newOutset.bottom() <= mViewBounds.bottom(); + default: + *delta = 0; + ASSERT(0); + } + return false; +} + +void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node) +{ + mFocusBounds = WebCore::IntRect(0, 0, 0, 0); + if (node == NULL) + return; + node->setIsFocus(true); + mFocusBounds = node->bounds(frame); + frame->setFocusIndex(node - frame->document()); + CachedFrame* parent; + while ((parent = frame->parent()) != NULL) { + parent->setFocusIndex(frame->indexInParent()); + frame = parent; + } +#if DEBUG_NAV_UI + const CachedFrame* focusFrame; + const CachedNode* focus = currentFocus(&focusFrame); + WebCore::IntRect bounds = WebCore::IntRect(0, 0, 0, 0); + if (focus) + bounds = focus->bounds(focusFrame); + DBG_NAV_LOGD("new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif +} + +void CachedRoot::setCursor(CachedFrame* frame, CachedNode* node) +{ +#if DEBUG_NAV_UI + const CachedFrame* cursorFrame; + const CachedNode* cursor = currentCursor(&cursorFrame); + WebCore::IntRect bounds; + if (cursor) + bounds = cursor->bounds(cursorFrame); + DBG_NAV_LOGD("old cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", + cursor ? cursor->index() : 0, + cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif + clearCursor(); + if (node == NULL) + return; + node->setIsCursor(true); + node->show(); + frame->setCursorIndex(node - frame->document()); + CachedFrame* parent; + while ((parent = frame->parent()) != NULL) { + parent->setCursorIndex(frame->indexInParent()); + frame = parent; + } +#if DEBUG_NAV_UI + cursor = currentCursor(&cursorFrame); + bounds = WebCore::IntRect(0, 0, 0, 0); + if (cursor) + bounds = cursor->bounds(cursorFrame); + DBG_NAV_LOGD("new cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", + cursor ? cursor->index() : 0, + cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif +} + +void CachedRoot::setCursorCache(int scrollX, int scrollY) const +{ + mCursor = currentCursor(); + if (mCursor) + mCursorBounds = mCursor->bounds(this); + if (!mRootLayer) + return; + SkRegion baseScrolled(mScrolledBounds); + mBaseUncovered = SkRegion(mScrolledBounds); +#if USE(ACCELERATED_COMPOSITING) +#if DUMP_NAV_CACHE + CachedLayer::Debug::printRootLayerAndroid(mRootLayer); +#endif + SkTDArray<SkRect> region; + mRootLayer->clipArea(®ion); + WebCore::IntSize offset( + copysign(min(max(0, mContents.width() - mScrolledBounds.width()), + abs(scrollX)), scrollX), + copysign(min(max(0, mContents.height() - mScrolledBounds.height()), + abs(scrollY)), scrollY)); + bool hasOffset = offset.width() || offset.height(); + // restrict scrollBounds to that which is not under layer + for (int index = 0; index < region.count(); index++) { + SkIRect less; + region[index].round(&less); + DBG_NAV_LOGD("less=(%d,%d,w=%d,h=%d)", less.fLeft, less.fTop, + less.width(), less.height()); + mBaseUncovered.op(less, SkRegion::kDifference_Op); + if (!hasOffset) + continue; + less.offset(offset.width(), offset.height()); + baseScrolled.op(less, SkRegion::kDifference_Op); + } + if (hasOffset) + mBaseUncovered.op(baseScrolled, SkRegion::kUnion_Op); +#endif +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedRoot* CachedRoot::Debug::base() const { + CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug)); + return nav; +} + +void CachedRoot::Debug::print() const +{ +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gWriteLogMutex.lock(); + ASSERT(gNavCacheLogFile == NULL); + gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); +#endif + CachedRoot* b = base(); + b->CachedFrame::mDebug.print(); + b->mHistory->mDebug.print(b); + DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n", + b->mMaxXScroll, b->mMaxYScroll); + if (b->mRootLayer) + CachedLayer::Debug::printRootLayerAndroid(b->mRootLayer); +#ifdef DUMP_NAV_CACHE_USING_PRINTF + if (gNavCacheLogFile) + fclose(gNavCacheLogFile); + gNavCacheLogFile = NULL; + gWriteLogMutex.unlock(); +#endif +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedRoot.h b/Source/WebKit/android/nav/CachedRoot.h new file mode 100644 index 0000000..1f8b851 --- /dev/null +++ b/Source/WebKit/android/nav/CachedRoot.h @@ -0,0 +1,142 @@ +/* + * Copyright 2007, 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. + */ + +#ifndef CachedRoot_H +#define CachedRoot_H + +#include "CachedFrame.h" +#include "IntRect.h" +#include "SkPicture.h" +#include "SkRegion.h" +#include "wtf/Vector.h" + +class SkRect; + +namespace WebCore { + class LayerAndroid; +} + +namespace android { + +class CachedHistory; +class CachedNode; +class FindCanvas; + +class CachedRoot : public CachedFrame { +public: + bool adjustForScroll(BestData* , Direction , WebCore::IntPoint* scrollPtr, + bool findClosest); + const SkRegion& baseUncovered() const { return mBaseUncovered; } + void calcBitBounds(const IntRect& , IntRect* ) const; + int checkForCenter(int x, int y) const; + void checkForJiggle(int* ) const; + bool checkRings(SkPicture* , const CachedNode* , + const WebCore::IntRect& testBounds) const; + WebCore::IntPoint cursorLocation() const; + int documentHeight() { return mContents.height(); } + int documentWidth() { return mContents.width(); } + void draw(FindCanvas& ) const; + const CachedNode* findAt(const WebCore::IntRect& , const CachedFrame** , + int* x, int* y, bool checkForHidden) const; + const WebCore::IntRect& focusBounds() const { return mFocusBounds; } + WebCore::IntPoint focusLocation() const; + int getAndResetSelectionEnd(); + int getAndResetSelectionStart(); + int getBlockLeftEdge(int x, int y, float scale) const; + void getSimulatedMousePosition(WebCore::IntPoint* ) const; + void init(WebCore::Frame* , CachedHistory* ); + bool innerDown(const CachedNode* , BestData* ) const; + bool innerLeft(const CachedNode* , BestData* ) const; + void innerMove(const CachedNode* ,BestData* bestData, Direction , + WebCore::IntPoint* scroll, bool firstCall); + bool innerRight(const CachedNode* , BestData* ) const; + bool innerUp(const CachedNode* , BestData* ) const; + WTF::String imageURI(int x, int y) const; + bool maskIfHidden(BestData* ) const; + const CachedNode* moveCursor(Direction , const CachedFrame** , WebCore::IntPoint* scroll); + /** + * Find the next textfield/textarea + * @param start The textfield/textarea to search from. + * @param framePtr If non-zero, returns CachedFrame* containing result. + * @return CachedNode* Next textfield/textarea or null (0) if none. + */ + const CachedNode* nextTextField(const CachedNode* start, + const CachedFrame** framePtr) const; + SkPicture* pictureAt(int* xPtr, int* yPtr, int* id) const; + SkPicture* pictureAt(int* xPtr, int* yPtr) const { + return pictureAt(xPtr, yPtr, 0); } + void reset(); + CachedHistory* rootHistory() const { return mHistory; } + const WebCore::LayerAndroid* rootLayer() const { return mRootLayer; } + bool scrollDelta(WebCore::IntRect& cursorRingBounds, Direction , int* delta); + const WebCore::IntRect& scrolledBounds() const { return mScrolledBounds; } + void setCursor(CachedFrame* , CachedNode* ); + void setCursorCache(int scrollX, int scrollY) const; // compute cached state used to find next cursor + void setCachedFocus(CachedFrame* , CachedNode* ); + void setFocusBounds(const WebCore::IntRect& r) { mFocusBounds = r; } + void setTextGeneration(int textGeneration) { mTextGeneration = textGeneration; } + void setMaxScroll(int x, int y) { mMaxXScroll = x; mMaxYScroll = y; } + void setPicture(SkPicture* picture) { mPicture = picture; } + void setRootLayer(WebCore::LayerAndroid* layer) { + mRootLayer = layer; + resetLayers(); + } + void setScrollOnly(bool state) { mScrollOnly = state; } + void setSelection(int start, int end) { mSelectionStart = start; mSelectionEnd = end; } + void setupScrolledBounds() const { mScrolledBounds = mViewBounds; } + void setVisibleRect(const WebCore::IntRect& r) { mViewBounds = r; } + int textGeneration() const { return mTextGeneration; } + int width() const { return mPicture ? mPicture->width() : 0; } +private: + friend class CachedFrame; + CachedHistory* mHistory; + SkPicture* mPicture; + WebCore::LayerAndroid* mRootLayer; + WebCore::IntRect mFocusBounds; // dom text input focus node bounds + mutable WebCore::IntRect mScrolledBounds; // view bounds + amount visible as result of scroll + int mTextGeneration; + int mMaxXScroll; + int mMaxYScroll; + // These two are ONLY used when the tree is rebuilt and the focus is a textfield/area + int mSelectionStart; + int mSelectionEnd; + // these four set up as cache for use by frameDown/Up/Left/Right etc + mutable WebCore::IntRect mCursorBounds; + mutable const CachedNode* mCursor; + mutable SkRegion mBaseUncovered; + bool mScrollOnly; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedRoot* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/DrawExtra.h b/Source/WebKit/android/nav/DrawExtra.h new file mode 100644 index 0000000..6716a65 --- /dev/null +++ b/Source/WebKit/android/nav/DrawExtra.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef DrawExtra_h +#define DrawExtra_h + +class SkCanvas; + +namespace WebCore { + class IntRect; + class LayerAndroid; +} + +using namespace WebCore; + +namespace android { + +class DrawExtra { +public: + virtual ~DrawExtra() {} + virtual void draw(SkCanvas* , LayerAndroid* , IntRect* ) = 0; +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/FindCanvas.cpp b/Source/WebKit/android/nav/FindCanvas.cpp new file mode 100644 index 0000000..2d310b3 --- /dev/null +++ b/Source/WebKit/android/nav/FindCanvas.cpp @@ -0,0 +1,699 @@ +/* + * Copyright 2008, 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 "webviewglue" + +#include "config.h" +#include "FindCanvas.h" +#include "LayerAndroid.h" +#include "IntRect.h" +#include "SelectText.h" +#include "SkBlurMaskFilter.h" +#include "SkCornerPathEffect.h" +#include "SkRect.h" +#include "SkUtils.h" + +#include <utils/Log.h> + +#define INTEGER_OUTSET 2 + +namespace android { + +// MatchInfo methods +//////////////////////////////////////////////////////////////////////////////// + +MatchInfo::MatchInfo() { + m_picture = 0; +} + +MatchInfo::~MatchInfo() { + SkSafeUnref(m_picture); +} + +MatchInfo::MatchInfo(const MatchInfo& src) { + m_layerId = src.m_layerId; + m_location = src.m_location; + m_picture = src.m_picture; + SkSafeRef(m_picture); +} + +void MatchInfo::set(const SkRegion& region, SkPicture* pic, int layerId) { + SkSafeUnref(m_picture); + m_layerId = layerId; + m_location = region; + m_picture = pic; + SkASSERT(pic); + pic->ref(); +} + +// GlyphSet methods +//////////////////////////////////////////////////////////////////////////////// + +GlyphSet::GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper, + size_t byteLength) { + SkPaint clonePaint(paint); + clonePaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + mTypeface = paint.getTypeface(); + mCount = clonePaint.textToGlyphs(lower, byteLength, NULL); + if (mCount > MAX_STORAGE_COUNT) { + mLowerGlyphs = new uint16_t[2*mCount]; + } else { + mLowerGlyphs = &mStorage[0]; + } + // Use one array, and have mUpperGlyphs point to a portion of it, + // so that we can reduce the number of new/deletes + mUpperGlyphs = mLowerGlyphs + mCount; + int count2 = clonePaint.textToGlyphs(lower, byteLength, mLowerGlyphs); + SkASSERT(mCount == count2); + count2 = clonePaint.textToGlyphs(upper, byteLength, mUpperGlyphs); + SkASSERT(mCount == count2); +} + +GlyphSet::~GlyphSet() { + // Do not need to delete mTypeface, which is not owned by us. + if (mCount > MAX_STORAGE_COUNT) { + delete[] mLowerGlyphs; + } // Otherwise, we just used local storage space, so no need to delete + // Also do not need to delete mUpperGlyphs, which simply points to a + // part of mLowerGlyphs +} + +GlyphSet& GlyphSet::operator=(GlyphSet& src) { + mTypeface = src.mTypeface; + mCount = src.mCount; + if (mCount > MAX_STORAGE_COUNT) { + mLowerGlyphs = new uint16_t[2*mCount]; + } else { + mLowerGlyphs = &mStorage[0]; + } + memcpy(mLowerGlyphs, src.mLowerGlyphs, 2*mCount*sizeof(uint16_t)); + mUpperGlyphs = mLowerGlyphs + mCount; + return *this; +} + +bool GlyphSet::characterMatches(uint16_t c, int index) { + SkASSERT(index < mCount && index >= 0); + return c == mLowerGlyphs[index] || c == mUpperGlyphs[index]; +} + +// FindCanvas methods +//////////////////////////////////////////////////////////////////////////////// + +FindCanvas::FindCanvas(int width, int height, const UChar* lower, + const UChar* upper, size_t byteLength) + : mLowerText(lower) + , mUpperText(upper) + , mLength(byteLength) + , mNumFound(0) { + // the text has been provided in read order. Reverse as needed so the + // result contains left-to-right characters. + const uint16_t* start = mLowerText; + size_t count = byteLength >> 1; + const uint16_t* end = mLowerText + count; + while (start < end) { + SkUnichar ch = SkUTF16_NextUnichar(&start); + WTF::Unicode::Direction charDirection = WTF::Unicode::direction(ch); + if (WTF::Unicode::RightToLeftArabic == charDirection + || WTF::Unicode::RightToLeft == charDirection) { + mLowerReversed.clear(); + mLowerReversed.append(mLowerText, count); + WebCore::ReverseBidi(mLowerReversed.begin(), count); + mLowerText = mLowerReversed.begin(); + mUpperReversed.clear(); + mUpperReversed.append(mUpperText, count); + WebCore::ReverseBidi(mUpperReversed.begin(), count); + mUpperText = mUpperReversed.begin(); + break; + } + } + + setBounder(&mBounder); + mOutset = -SkIntToScalar(INTEGER_OUTSET); + mMatches = new WTF::Vector<MatchInfo>(); + mWorkingIndex = 0; + mWorkingCanvas = 0; + mWorkingPicture = 0; +} + +FindCanvas::~FindCanvas() { + setBounder(NULL); + /* Just in case getAndClear was not called. */ + delete mMatches; + SkSafeUnref(mWorkingPicture); +} + +// Each version of addMatch returns a rectangle for a match. +// Not all of the parameters are used by each version. +SkRect FindCanvas::addMatchNormal(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y) { + const uint16_t* lineStart = glyphs - index; + /* Use the original paint, since "text" is in glyphs */ + SkScalar before = paint.measureText(lineStart, index * sizeof(uint16_t), 0); + SkRect rect; + rect.fLeft = pos[0] + before; + int countInBytes = count * sizeof(uint16_t); + rect.fRight = paint.measureText(glyphs, countInBytes, 0) + rect.fLeft; + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + SkScalar baseline = y; + rect.fTop = baseline + fontMetrics.fAscent; + rect.fBottom = baseline + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&rect); + // Add the text to our picture. + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawText(glyphs, countInBytes, pos[0] + before, y, paint); + canvas->restoreToCount(saveCount); + return rect; +} + +SkRect FindCanvas::addMatchPos(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar xPos[], SkScalar /* y */) { + SkRect r; + r.setEmpty(); + const SkPoint* temp = reinterpret_cast<const SkPoint*> (xPos); + const SkPoint* points = &temp[index]; + int countInBytes = count * sizeof(uint16_t); + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + // Need to check each character individually, since the heights may be + // different. + for (int j = 0; j < count; j++) { + SkRect bounds; + bounds.fLeft = points[j].fX; + bounds.fRight = bounds.fLeft + + paint.measureText(&glyphs[j], sizeof(uint16_t), 0); + SkScalar baseline = points[j].fY; + bounds.fTop = baseline + fontMetrics.fAscent; + bounds.fBottom = baseline + fontMetrics.fDescent; + /* Accumulate and then add the resulting rect to mMatches */ + r.join(bounds); + } + SkMatrix matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosText(glyphs, countInBytes, points, paint); + canvas->restoreToCount(saveCount); + return r; +} + +SkRect FindCanvas::addMatchPosH(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar position[], SkScalar constY) { + SkRect r; + // We only care about the positions starting at the index of our match + const SkScalar* xPos = &position[index]; + // This assumes that the position array is monotonic increasing + // The left bounds will be the position of the left most character + r.fLeft = xPos[0]; + // The right bounds will be the position of the last character plus its + // width + int lastIndex = count - 1; + r.fRight = paint.measureText(&glyphs[lastIndex], sizeof(uint16_t), 0) + + xPos[lastIndex]; + // Grab font metrics to determine the top and bottom of the bounds + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + r.fTop = constY + fontMetrics.fAscent; + r.fBottom = constY + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosTextH(glyphs, count * sizeof(uint16_t), xPos, constY, paint); + canvas->restoreToCount(saveCount); + return r; +} + +void FindCanvas::drawLayers(LayerAndroid* layer) { +#if USE(ACCELERATED_COMPOSITING) + SkPicture* picture = layer->picture(); + if (picture) { + setLayerId(layer->uniqueId()); + drawPicture(*picture); + } + for (int i = 0; i < layer->countChildren(); i++) + drawLayers(layer->getChild(i)); +#endif +} + +void FindCanvas::drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + findHelper(text, byteLength, paint, &x, y, &FindCanvas::addMatchNormal); +} + +void FindCanvas::drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + // Pass in the first y coordinate for y so that we can check to see whether + // it is lower than the last draw call (to check if we are continuing to + // another line). + findHelper(text, byteLength, paint, (const SkScalar*) pos, pos[0].fY, + &FindCanvas::addMatchPos); +} + +void FindCanvas::drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + findHelper(text, byteLength, paint, xpos, constY, + &FindCanvas::addMatchPosH); +} + +/* The current behavior is to skip substring matches. This means that in the + * string + * batbatbat + * a search for + * batbat + * will return 1 match. If the desired behavior is to return 2 matches, define + * INCLUDE_SUBSTRING_MATCHES to be 1. + */ +#define INCLUDE_SUBSTRING_MATCHES 0 + +// Need a quick way to know a maximum distance between drawText calls to know if +// they are part of the same logical phrase when searching. By crude +// inspection, half the point size seems a good guess at the width of a space +// character. +static inline SkScalar approximateSpaceWidth(const SkPaint& paint) { + return SkScalarHalf(paint.getTextSize()); +} + +void FindCanvas::findHelper(const void* text, size_t byteLength, + const SkPaint& paint, const SkScalar positions[], + SkScalar y, + SkRect (FindCanvas::*addMatch)(int index, + const SkPaint& paint, int count, + const uint16_t* glyphs, + const SkScalar positions[], SkScalar y)) { + SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); + SkASSERT(mMatches); + GlyphSet* glyphSet = getGlyphs(paint); + const int count = glyphSet->getCount(); + int numCharacters = byteLength >> 1; + const uint16_t* chars = (const uint16_t*) text; + // This block will check to see if we are continuing from another line. If + // so, the user needs to have added a space, which we do not draw. + if (mWorkingIndex) { + SkPoint newY; + getTotalMatrix().mapXY(0, y, &newY); + SkIRect workingBounds = mWorkingRegion.getBounds(); + int newYInt = SkScalarRound(newY.fY); + if (workingBounds.fTop > newYInt) { + // The new text is above the working region, so we know it's not + // a continuation. + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } else if (workingBounds.fBottom < newYInt) { + // Now we know that this line is lower than our partial match. + SkPaint clonePaint(paint); + clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + uint16_t space; + clonePaint.textToGlyphs(" ", 1, &space); + if (glyphSet->characterMatches(space, mWorkingIndex)) { + mWorkingIndex++; + if (mWorkingIndex == count) { + // We already know that it is not clipped out because we + // checked for that before saving the working region. + insertMatchInfo(mWorkingRegion); + + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + // We have found a match, so continue on this line from + // scratch. + } + } else { + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } + } + // If neither one is true, then we are likely continuing on the same + // line, but are in a new draw call because the paint has changed. In + // this case, we can continue without adding a space. + } + // j is the position in the search text + // Start off with mWorkingIndex in case we are continuing from a prior call + int j = mWorkingIndex; + // index is the position in the drawn text + int index = 0; + for ( ; index != numCharacters; index++) { + if (glyphSet->characterMatches(chars[index], j)) { + // The jth character in the search text matches the indexth position + // in the drawn text, so increase j. + j++; + if (j != count) { + continue; + } + // The last count characters match, so we found the entire + // search string. + int remaining = count - mWorkingIndex; + int matchIndex = index - remaining + 1; + // Set up a pointer to the matching text in 'chars'. + const uint16_t* glyphs = chars + matchIndex; + SkRect rect = (this->*addMatch)(matchIndex, paint, + remaining, glyphs, positions, y); + // We need an SkIRect for SkRegion operations. + SkIRect iRect; + rect.roundOut(&iRect); + // Want to outset the drawn rectangle by the same amount as + // mOutset + iRect.inset(-INTEGER_OUTSET, -INTEGER_OUTSET); + SkRegion regionToAdd(iRect); + if (!mWorkingRegion.isEmpty()) { + // If this is on the same line as our working region, make + // sure that they are close enough together that they are + // supposed to be part of the same text string. + // The width of two spaces has arbitrarily been chosen. + const SkIRect& workingBounds = mWorkingRegion.getBounds(); + if (workingBounds.fTop <= iRect.fBottom && + workingBounds.fBottom >= iRect.fTop && + SkIntToScalar(iRect.fLeft - workingBounds.fRight) > + approximateSpaceWidth(paint)) { + index = -1; // Will increase to 0 on next run + // In this case, we need to start from the beginning of + // the text being searched and our search term. + j = 0; + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + continue; + } + // Add the mWorkingRegion, which contains rectangles from + // the previous line(s). + regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op); + } + insertMatchInfo(regionToAdd); +#if INCLUDE_SUBSTRING_MATCHES + // Reset index to the location of the match and reset j to the + // beginning, so that on the next iteration of the loop, index + // will advance by 1 and we will compare the next character in + // chars to the first character in the GlyphSet. + index = matchIndex; +#endif + // Whether the clip contained it or not, we need to start over + // with our recording canvas + resetWorkingCanvas(); + } else { + // Index needs to be set to index - j + 1. + // This is a ridiculous case, but imagine the situation where the + // user is looking for the string "jjog" in the drawText call for + // "jjjog". The first two letters match. However, when the index + // is 2, and we discover that 'o' and 'j' do not match, we should go + // back to 1, where we do, in fact, have a match + // FIXME: This does not work if (as in our example) "jj" is in one + // draw call and "jog" is in the next. Doing so would require a + // stack, keeping track of multiple possible working indeces and + // regions. This is likely an uncommon case. + index = index - j; // index will be increased by one on the next + // iteration + } + // We reach here in one of two cases: + // 1) We just completed a match, so any working rectangle/index is no + // longer needed, and we will start over from the beginning + // 2) The glyphs do not match, so we start over at the beginning of + // the search string. + j = 0; + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } + // At this point, we have searched all of the text in the current drawText + // call. + // Keep track of a partial match that may start on this line. + if (j > 0) { // if j is greater than 0, we have a partial match + int relativeCount = j - mWorkingIndex; // Number of characters in this + // part of the match. + int partialIndex = index - relativeCount; // Index that starts our + // partial match. + const uint16_t* partialGlyphs = chars + partialIndex; + SkRect partial = (this->*addMatch)(partialIndex, paint, relativeCount, + partialGlyphs, positions, y); + partial.inset(mOutset, mOutset); + SkIRect dest; + partial.roundOut(&dest); + mWorkingRegion.op(dest, SkRegion::kUnion_Op); + mWorkingIndex = j; + return; + } + // This string doesn't go into the next drawText, so reset our working + // variables + mWorkingRegion.setEmpty(); + mWorkingIndex = 0; +} + +SkCanvas* FindCanvas::getWorkingCanvas() { + if (!mWorkingPicture) { + mWorkingPicture = new SkPicture; + mWorkingCanvas = mWorkingPicture->beginRecording(0,0); + } + return mWorkingCanvas; +} + +GlyphSet* FindCanvas::getGlyphs(const SkPaint& paint) { + SkTypeface* typeface = paint.getTypeface(); + GlyphSet* end = mGlyphSets.end(); + for (GlyphSet* ptr = mGlyphSets.begin();ptr != end; ptr++) { + if (ptr->getTypeface() == typeface) { + return ptr; + } + } + + GlyphSet set(paint, mLowerText, mUpperText, mLength); + *mGlyphSets.append() = set; + return &(mGlyphSets.top()); +} + +void FindCanvas::insertMatchInfo(const SkRegion& region) { + mNumFound++; + mWorkingPicture->endRecording(); + MatchInfo matchInfo; + mMatches->append(matchInfo); + LOGD("%s region=%p pict=%p layer=%d", __FUNCTION__, + ®ion, mWorkingPicture, mLayerId); + mMatches->last().set(region, mWorkingPicture, mLayerId); +} + +void FindCanvas::resetWorkingCanvas() { + mWorkingPicture->unref(); + mWorkingPicture = 0; + // Do not need to reset mWorkingCanvas itself because we only access it via + // getWorkingCanvas. +} + +// This function sets up the paints that are used to draw the matches. +void FindOnPage::setUpFindPaint() { + // Set up the foreground paint + m_findPaint.setAntiAlias(true); + const SkScalar roundiness = SkIntToScalar(2); + SkCornerPathEffect* cornerEffect = new SkCornerPathEffect(roundiness); + m_findPaint.setPathEffect(cornerEffect); + m_findPaint.setARGB(255, 132, 190, 0); + + // Set up the background blur paint. + m_findBlurPaint.setAntiAlias(true); + m_findBlurPaint.setARGB(204, 0, 0, 0); + m_findBlurPaint.setPathEffect(cornerEffect); + cornerEffect->unref(); + SkMaskFilter* blurFilter = SkBlurMaskFilter::Create(SK_Scalar1, + SkBlurMaskFilter::kNormal_BlurStyle); + m_findBlurPaint.setMaskFilter(blurFilter)->unref(); + m_isFindPaintSetUp = true; +} + +IntRect FindOnPage::currentMatchBounds() const { + IntRect noBounds = IntRect(0, 0, 0, 0); + if (!m_matches || !m_matches->size()) + return noBounds; + MatchInfo& info = (*m_matches)[m_findIndex]; + // FIXME: this should test if the match in the layer is visible + if (info.isInLayer()) + return noBounds; + return info.getLocation().getBounds(); +} + +bool FindOnPage::currentMatchIsInLayer() const { + if (!m_matches || !m_matches->size()) + return false; + MatchInfo& info = (*m_matches)[m_findIndex]; + return info.isInLayer(); +} + +// This function is only used by findNext and setMatches. In it, we store +// upper left corner of the match specified by m_findIndex in +// m_currentMatchLocation. +void FindOnPage::storeCurrentMatchLocation() { + SkASSERT(m_findIndex < m_matches->size()); + const SkIRect& bounds = (*m_matches)[m_findIndex].getLocation().getBounds(); + m_currentMatchLocation.set(bounds.fLeft, bounds.fTop); + m_hasCurrentLocation = true; +} + +// Put a cap on the number of matches to draw. If the current page has more +// matches than this, only draw the focused match. +#define MAX_NUMBER_OF_MATCHES_TO_DRAW 101 + +void FindOnPage::draw(SkCanvas* canvas, LayerAndroid* layer, IntRect* inval) { + if (!m_lastBounds.isEmpty()) { + inval->unite(m_lastBounds); + m_lastBounds.setEmpty(); + } + if (!m_hasCurrentLocation || !m_matches || !m_matches->size()) + return; + int layerId = layer->uniqueId(); + if (m_findIndex >= m_matches->size()) + m_findIndex = 0; + const MatchInfo& matchInfo = (*m_matches)[m_findIndex]; + const SkRegion& currentMatchRegion = matchInfo.getLocation(); + + // Set up the paints used for drawing the matches + if (!m_isFindPaintSetUp) + setUpFindPaint(); + + // Draw the current match + if (matchInfo.layerId() == layerId) { + drawMatch(currentMatchRegion, canvas, true); + // Now draw the picture, so that it shows up on top of the rectangle + int saveCount = canvas->save(); + SkPath matchPath; + currentMatchRegion.getBoundaryPath(&matchPath); + canvas->clipPath(matchPath); + canvas->drawPicture(*matchInfo.getPicture()); + canvas->restoreToCount(saveCount); + const SkMatrix& matrix = canvas->getTotalMatrix(); + const SkRect& localBounds = matchPath.getBounds(); + SkRect globalBounds; + matrix.mapRect(&globalBounds, localBounds); + globalBounds.round(&m_lastBounds); + inval->unite(m_lastBounds); + } + // Draw the rest + unsigned numberOfMatches = m_matches->size(); + if (numberOfMatches > 1 + && numberOfMatches < MAX_NUMBER_OF_MATCHES_TO_DRAW) { + for(unsigned i = 0; i < numberOfMatches; i++) { + // The current match has already been drawn + if (i == m_findIndex) + continue; + if ((*m_matches)[i].layerId() != layerId) + continue; + const SkRegion& region = (*m_matches)[i].getLocation(); + // Do not draw matches which intersect the current one, or if it is + // offscreen + if (currentMatchRegion.intersects(region)) + continue; + SkRect bounds; + bounds.set(region.getBounds()); + if (canvas->quickReject(bounds, SkCanvas::kAA_EdgeType)) + continue; + drawMatch(region, canvas, false); + } + } +} + +// Draw the match specified by region to the canvas. +void FindOnPage::drawMatch(const SkRegion& region, SkCanvas* canvas, + bool focused) +{ + // For the match which has focus, use a filled paint. For the others, use + // a stroked paint. + if (focused) { + m_findPaint.setStyle(SkPaint::kFill_Style); + m_findBlurPaint.setStyle(SkPaint::kFill_Style); + } else { + m_findPaint.setStyle(SkPaint::kStroke_Style); + m_findPaint.setStrokeWidth(SK_Scalar1); + m_findBlurPaint.setStyle(SkPaint::kStroke_Style); + m_findBlurPaint.setStrokeWidth(SkIntToScalar(2)); + } + // Find the path for the current match + SkPath matchPath; + region.getBoundaryPath(&matchPath); + // Offset the path for a blurred shadow + SkPath blurPath; + matchPath.offset(SK_Scalar1, SkIntToScalar(2), &blurPath); + int saveCount = 0; + if (!focused) { + saveCount = canvas->save(); + canvas->clipPath(matchPath, SkRegion::kDifference_Op); + } + // Draw the blurred background + canvas->drawPath(blurPath, m_findBlurPaint); + if (!focused) + canvas->restoreToCount(saveCount); + // Draw the foreground + canvas->drawPath(matchPath, m_findPaint); +} + +void FindOnPage::findNext(bool forward) +{ + if (!m_matches || !m_matches->size() || !m_hasCurrentLocation) + return; + if (forward) { + m_findIndex++; + if (m_findIndex == m_matches->size()) + m_findIndex = 0; + } else { + if (m_findIndex == 0) { + m_findIndex = m_matches->size() - 1; + } else { + m_findIndex--; + } + } + storeCurrentMatchLocation(); +} + +// With this call, WebView takes ownership of matches, and is responsible for +// deleting it. +void FindOnPage::setMatches(WTF::Vector<MatchInfo>* matches) +{ + if (m_matches) + delete m_matches; + m_matches = matches; + if (m_matches->size()) { + if (m_hasCurrentLocation) { + for (unsigned i = 0; i < m_matches->size(); i++) { + const SkIRect& rect = (*m_matches)[i].getLocation().getBounds(); + if (rect.fLeft == m_currentMatchLocation.fX + && rect.fTop == m_currentMatchLocation.fY) { + m_findIndex = i; + return; + } + } + } + // If we did not have a stored location, or if we were unable to restore + // it, store the new one. + m_findIndex = 0; + storeCurrentMatchLocation(); + } else { + m_hasCurrentLocation = false; + } +} + +} diff --git a/Source/WebKit/android/nav/FindCanvas.h b/Source/WebKit/android/nav/FindCanvas.h new file mode 100644 index 0000000..76ee1e2 --- /dev/null +++ b/Source/WebKit/android/nav/FindCanvas.h @@ -0,0 +1,255 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef Find_Canvas_h +#define Find_Canvas_h + +#include "DrawExtra.h" +#include "IntRect.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkRegion.h" +#include "SkTDArray.h" + +#include <unicode/umachine.h> +#include <wtf/Vector.h> + +namespace android { + +// Stores both region information and an SkPicture of the match, so that the +// region can be drawn, followed by drawing the matching text on top of it. +// This class owns its SkPicture +class MatchInfo { +public: + MatchInfo(); + ~MatchInfo(); + MatchInfo(const MatchInfo& src); + const SkRegion& getLocation() const { return m_location; } + // Return a pointer to our picture, representing the matching text. Does + // not transfer ownership of the picture. + SkPicture* getPicture() const { return m_picture; } + // This will make a copy of the region, and increase the ref count on the + // SkPicture. If this MatchInfo already had one, unref it. + bool isInLayer() const { return m_layerId >= 0; } + int layerId() const { return m_layerId; } + void set(const SkRegion& region, SkPicture* pic, int layerId); +private: + MatchInfo& operator=(MatchInfo& src); + SkRegion m_location; + SkPicture* m_picture; + int m_layerId; +}; + +// A class containing a typeface for reference, the length in glyphs, and +// the upper and lower case representations of the search string. +class GlyphSet { +public: + GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper, + size_t byteLength); + ~GlyphSet(); + GlyphSet& operator=(GlyphSet& src); + + // Return true iff c matches one of our glyph arrays at index + bool characterMatches(uint16_t c, int index); + + int getCount() const { return mCount; } + + const SkTypeface* getTypeface() const { return mTypeface; } + +private: + // Disallow copy constructor + GlyphSet(GlyphSet& src) { } + + // mTypeface is used for comparison only + const SkTypeface* mTypeface; + // mLowerGlyphs points to all of our storage space: the lower set followed + // by the upper set. mUpperGlyphs is purely a convenience pointer to the + // start of the upper case glyphs. + uint16_t* mLowerGlyphs; + uint16_t* mUpperGlyphs; + // mCount is the number of glyphs of the search string. Must be the same + // for both the lower case set and the upper case set. + int mCount; + + // Arbitrarily chose the maximum storage to use in the GlyphSet. This is + // based on the length of the word being searched. If users are always + // searching for 3 letter words (for example), an ideal number would be 3. + // Each time the user searches for a word longer than (in this case, 3) that + // will result in calling new/delete. + enum Storage { + MAX_STORAGE_COUNT = 16 + }; + // In order to eliminate new/deletes, create storage that will be enough + // most of the time + uint16_t mStorage[2*MAX_STORAGE_COUNT]; +}; + +class FindBounder : public SkBounder { +public: + FindBounder() {} + ~FindBounder() {} +protected: + virtual bool onIRect(const SkIRect&) { return false; } +}; + +class FindCanvas : public SkCanvas { +public: + FindCanvas(int width, int height, const UChar* , const UChar*, + size_t byteLength); + + virtual ~FindCanvas(); + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint); + + /* FIXME: This path has not been tested. */ + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint); + + /* Also untested */ + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint); + + /* Not sure what to do here or for drawTextOnPathHV */ + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + } + + void drawLayers(LayerAndroid* ); + int found() const { return mNumFound; } + void setLayerId(int layerId) { mLayerId = layerId; } + + // This method detaches our array of matches and passes ownership to + // the caller, who is then responsible for deleting them. + WTF::Vector<MatchInfo>* detachMatches() { + WTF::Vector<MatchInfo>* array = mMatches; + mMatches = NULL; + return array; + } + +private: + // These calls are made by findHelper to store information about each match + // that is found. They return a rectangle which is used to highlight the + // match. They also add to our SkPicture (which can be accessed with + // getDrawnMatches) a draw of each match. This way it can be drawn after + // the rectangle. The rect that is returned is in device coordinates. + SkRect addMatchNormal(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y); + + SkRect addMatchPos(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar xPos[], SkScalar /* y */); + + SkRect addMatchPosH(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar position[], SkScalar constY); + + // Helper for each of our draw calls + void findHelper(const void* text, size_t byteLength, const SkPaint& paint, + const SkScalar xPos[], SkScalar y, + SkRect (FindCanvas::*addMatch)(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y)); + + // If we already have a working canvas, grab it. Otherwise, create a new + // one. + SkCanvas* getWorkingCanvas(); + + // Return the set of glyphs and its count for the text being searched for + // and the parameter paint. If one has already been created and cached + // for this paint, use it. If not, create a new one and cache it. + GlyphSet* getGlyphs(const SkPaint& paint); + + // Store all the accumulated info about a match in our vector. + void insertMatchInfo(const SkRegion& region); + + // Throw away our cumulative information about our working SkCanvas. After + // this call, next call to getWorkingCanvas will create a new one. + void resetWorkingCanvas(); + + // Since we may transfer ownership of this array (see detachRects()), we + // hold a pointer to the array instead of just the array itself. + WTF::Vector<MatchInfo>* mMatches; + const UChar* mLowerText; + const UChar* mUpperText; + Vector<UChar> mLowerReversed; + Vector<UChar> mUpperReversed; + size_t mLength; + FindBounder mBounder; + int mNumFound; + SkScalar mOutset; + SkTDArray<GlyphSet> mGlyphSets; + + SkPicture* mWorkingPicture; + SkCanvas* mWorkingCanvas; + SkRegion mWorkingRegion; + int mWorkingIndex; + int mLayerId; +}; + +class FindOnPage : public DrawExtra { +public: + FindOnPage() { + m_matches = 0; + m_hasCurrentLocation = false; + m_isFindPaintSetUp = false; + m_lastBounds.setEmpty(); + } + virtual ~FindOnPage() { delete m_matches; } + void clearCurrentLocation() { m_hasCurrentLocation = false; } + IntRect currentMatchBounds() const; + int currentMatchIndex() const { return m_findIndex; } + bool currentMatchIsInLayer() const; + virtual void draw(SkCanvas* , LayerAndroid* , IntRect* ); + void findNext(bool forward); + bool isCurrentLocationValid() { return m_hasCurrentLocation; } + void setMatches(WTF::Vector<MatchInfo>* matches); +private: + void drawMatch(const SkRegion& region, SkCanvas* canvas, bool focused); + void setUpFindPaint(); + void storeCurrentMatchLocation(); + WTF::Vector<MatchInfo>* m_matches; + // Stores the location of the current match. + SkIPoint m_currentMatchLocation; + // Tells whether the value in m_currentMatchLocation is valid. + bool m_hasCurrentLocation; + // Tells whether we have done the setup to draw the Find matches. + bool m_isFindPaintSetUp; + // Paint used to draw our Find matches. + SkPaint m_findPaint; + // Paint used for the background of our Find matches. + SkPaint m_findBlurPaint; + unsigned m_findIndex; + SkIRect m_lastBounds; +}; + +} + +#endif // Find_Canvas_h diff --git a/Source/WebKit/android/nav/ParseCanvas.h b/Source/WebKit/android/nav/ParseCanvas.h new file mode 100644 index 0000000..9b7a889 --- /dev/null +++ b/Source/WebKit/android/nav/ParseCanvas.h @@ -0,0 +1,52 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef PARSE_CANVAS_H +#define PARSE_CANVAS_H + +#include "SkCanvas.h" + +namespace android { + +class ParseCanvas : public SkCanvas { +protected: + virtual ~ParseCanvas() { + setBounder(0); + } + + // Pictures parsed for content never want to create offscreen bitmaps. + // Instead, just save and restore the canvas state -- this also keeps + // the picture contents at their original coordinates. + virtual int saveLayer(const SkRect* , const SkPaint* , SaveFlags flags) { + return INHERITED::save(flags); + } +private: + typedef SkCanvas INHERITED; +}; + +} + +#endif + diff --git a/Source/WebKit/android/nav/SelectText.cpp b/Source/WebKit/android/nav/SelectText.cpp new file mode 100644 index 0000000..f8ea799 --- /dev/null +++ b/Source/WebKit/android/nav/SelectText.cpp @@ -0,0 +1,1983 @@ +/* + * Copyright 2008, 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 "webviewglue" + +#include "CachedPrefix.h" +#include "BidiResolver.h" +#include "CachedRoot.h" +#include "LayerAndroid.h" +#include "ParseCanvas.h" +#include "SelectText.h" +#include "SkBitmap.h" +#include "SkBounder.h" +#include "SkGradientShader.h" +#include "SkMatrix.h" +#include "SkPicture.h" +#include "SkPixelXorXfermode.h" +#include "SkPoint.h" +#include "SkRect.h" +#include "SkRegion.h" +#include "SkUtils.h" +#include "TextRun.h" + +#ifdef DEBUG_NAV_UI +#include <wtf/text/CString.h> +#endif + +#define VERBOSE_LOGGING 0 +// #define EXTRA_NOISY_LOGGING 1 + +// TextRunIterator has been copied verbatim from GraphicsContext.cpp +namespace WebCore { + +class TextRunIterator { +public: + TextRunIterator() + : m_textRun(0) + , m_offset(0) + { + } + + TextRunIterator(const TextRun* textRun, unsigned offset) + : m_textRun(textRun) + , m_offset(offset) + { + } + + TextRunIterator(const TextRunIterator& other) + : m_textRun(other.m_textRun) + , m_offset(other.m_offset) + { + } + + unsigned offset() const { return m_offset; } + void increment() { m_offset++; } + bool atEnd() const { return !m_textRun || m_offset >= m_textRun->length(); } + UChar current() const { return (*m_textRun)[m_offset]; } + WTF::Unicode::Direction direction() const { return atEnd() ? WTF::Unicode::OtherNeutral : WTF::Unicode::direction(current()); } + + bool operator==(const TextRunIterator& other) + { + return m_offset == other.m_offset && m_textRun == other.m_textRun; + } + + bool operator!=(const TextRunIterator& other) { return !operator==(other); } + +private: + const TextRun* m_textRun; + int m_offset; +}; + +// ReverseBidi is a trimmed-down version of GraphicsContext::drawBidiText() +void ReverseBidi(UChar* chars, int len) { + using namespace WTF::Unicode; + WTF::Vector<UChar> result; + result.reserveCapacity(len); + TextRun run(chars, len); + BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; + bidiResolver.setStatus(BidiStatus(LeftToRight, LeftToRight, LeftToRight, + BidiContext::create(0, LeftToRight, false))); + bidiResolver.setPosition(TextRunIterator(&run, 0)); + bidiResolver.createBidiRunsForLine(TextRunIterator(&run, len)); + if (!bidiResolver.runCount()) + return; + BidiCharacterRun* bidiRun = bidiResolver.firstRun(); + while (bidiRun) { + int bidiStart = bidiRun->start(); + int bidiStop = bidiRun->stop(); + int size = result.size(); + int bidiCount = bidiStop - bidiStart; + result.append(chars + bidiStart, bidiCount); + if (bidiRun->level() % 2) { + UChar* start = &result[size]; + UChar* end = start + bidiCount; + // reverse the order of any RTL substrings + while (start < end) { + UChar temp = *start; + *start++ = *--end; + *end = temp; + } + start = &result[size]; + end = start + bidiCount - 1; + // if the RTL substring had a surrogate pair, restore its order + while (start < end) { + UChar trail = *start++; + if (!U16_IS_SURROGATE(trail)) + continue; + start[-1] = *start; // lead + *start++ = trail; + } + } + bidiRun = bidiRun->next(); + } + bidiResolver.deleteRuns(); + memcpy(chars, &result[0], len * sizeof(UChar)); +} + +} + +namespace android { + +#define HYPHEN_MINUS 0x2D // ASCII hyphen +#define SOLIDUS 0x2F // ASCII slash +#define REVERSE_SOLIDUS 0x5C // ASCII backslash +#define HYPHEN 0x2010 // unicode hyphen, first in range of dashes +#define HORZ_BAR 0x2015 // unicode horizontal bar, last in range of dashes +#define TOUCH_SLOP 10 // additional distance from character rect when hit + +class CommonCheck : public SkBounder { +public: + CommonCheck(const SkIRect& area) + : mArea(area) + , mLastUni(0) + { + mLastGlyph.fGlyphID = static_cast<uint16_t>(-1); + mLastCandidate.fGlyphID = static_cast<uint16_t>(-1); + mMatrix.reset(); + reset(); + } + + /* called only while the picture is parsed */ + int base() { + if (mBase == INT_MAX) { + SkPoint result; + mMatrix.mapXY(0, mY, &result); + mBase = SkScalarFloor(result.fY); + } + return mBase; + } + + /* called only while the picture is parsed */ + int bottom() { + if (mBottom == INT_MAX) { + SkPoint result; + SkPaint::FontMetrics metrics; + mPaint.getFontMetrics(&metrics); + mMatrix.mapXY(0, metrics.fDescent + mY, &result); + mBottom = SkScalarCeil(result.fY); + } + return mBottom; + } + +#if DEBUG_NAV_UI + // make current (possibily uncomputed) value visible for debugging + int bottomDebug() const + { + return mBottom; + } +#endif + + bool addNewLine(const SkBounder::GlyphRec& rec) + { + SkFixed lineSpacing = SkFixedAbs(mLastGlyph.fLSB.fY - rec.fLSB.fY); + SkFixed lineHeight = SkIntToFixed(bottom() - top()); + return lineSpacing >= lineHeight + (lineHeight >> 1); // 1.5 + } + + bool addSpace(const SkBounder::GlyphRec& rec) + { + bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY; + if (((mLastUni >= HYPHEN && mLastUni <= HORZ_BAR) + || mLastUni == HYPHEN_MINUS || mLastUni == SOLIDUS + || mLastUni == REVERSE_SOLIDUS) && newBaseLine) + { + return false; + } + return isSpace(rec); + } + + void finishGlyph() + { + mLastGlyph = mLastCandidate; + mLastUni = mLastUniCandidate; + mLastPaint = mLastPaintCandidate; + } + + const SkIRect& getArea() const { + return mArea; + } + + /* called only while the picture is parsed */ + SkUnichar getUniChar(const SkBounder::GlyphRec& rec) + { + SkUnichar unichar; + SkPaint::TextEncoding save = mPaint.getTextEncoding(); + mPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + mPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar); + mPaint.setTextEncoding(save); + return unichar; + } + + bool isSpace(const SkBounder::GlyphRec& rec) + { + if (mLastGlyph.fGlyphID == static_cast<uint16_t>(-1)) + return true; + DBG_NAV_LOGD("mLastGlyph=((%g, %g),(%g, %g), %d)" + " rec=((%g, %g),(%g, %g), %d) mLastUni=0x%04x '%c'", + SkFixedToScalar(mLastGlyph.fLSB.fX), + SkFixedToScalar(mLastGlyph.fLSB.fY), + SkFixedToScalar(mLastGlyph.fRSB.fX), + SkFixedToScalar(mLastGlyph.fRSB.fY), mLastGlyph.fGlyphID, + SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fLSB.fY), + SkFixedToScalar(rec.fRSB.fX), SkFixedToScalar(rec.fRSB.fY), + rec.fGlyphID, + mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?'); + bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY; + if (newBaseLine) + return true; + SkFixed gapOne = mLastGlyph.fLSB.fX - rec.fRSB.fX; + SkFixed gapTwo = rec.fLSB.fX - mLastGlyph.fRSB.fX; + if (gapOne < 0 && gapTwo < 0) + return false; // overlaps + const SkBounder::GlyphRec& first = mLastGlyph.fLSB.fX < rec.fLSB.fX + ? mLastGlyph : rec; + const SkBounder::GlyphRec& second = mLastGlyph.fLSB.fX < rec.fLSB.fX + ? rec : mLastGlyph; + uint16_t firstGlyph = first.fGlyphID; + SkScalar firstWidth = mLastPaint.measureText(&firstGlyph, sizeof(firstGlyph)); + SkFixed ceilWidth = SkIntToFixed(SkScalarCeil(firstWidth)); + SkFixed posNoSpace = first.fLSB.fX + ceilWidth; + SkFixed ceilSpace = SkIntToFixed(SkFixedCeil(minSpaceWidth(mLastPaint))); + SkFixed posWithSpace = posNoSpace + ceilSpace; + SkFixed diffNoSpace = SkFixedAbs(second.fLSB.fX - posNoSpace); + SkFixed diffWithSpace = SkFixedAbs(second.fLSB.fX - posWithSpace); + DBG_NAV_LOGD("second=%g width=%g (%g) noSpace=%g (%g) withSpace=%g (%g)" + " fontSize=%g", + SkFixedToScalar(second.fLSB.fX), + firstWidth, SkFixedToScalar(ceilWidth), + SkFixedToScalar(posNoSpace), SkFixedToScalar(diffNoSpace), + SkFixedToScalar(posWithSpace), SkFixedToScalar(diffWithSpace), + mLastPaint.getTextSize()); + return diffWithSpace <= diffNoSpace; + } + + SkFixed minSpaceWidth(SkPaint& paint) + { + if (mMinSpaceWidth == SK_FixedMax) { + SkPaint::TextEncoding save = paint.getTextEncoding(); + paint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + SkScalar width = paint.measureText(" ", 1); + mMinSpaceWidth = SkScalarToFixed(width * mMatrix.getScaleX()); + paint.setTextEncoding(save); + DBG_NAV_LOGV("width=%g matrix sx/sy=(%g, %g) tx/ty=(%g, %g)" + " mMinSpaceWidth=%g", width, + mMatrix.getScaleX(), mMatrix.getScaleY(), + mMatrix.getTranslateX(), mMatrix.getTranslateY(), + SkFixedToScalar(mMinSpaceWidth)); + } + return mMinSpaceWidth; + } + + void recordGlyph(const SkBounder::GlyphRec& rec) + { + mLastCandidate = rec; + mLastUniCandidate = getUniChar(rec); + mLastPaintCandidate = mPaint; + } + + void reset() + { + mMinSpaceWidth = SK_FixedMax; // mark as uninitialized + mBase = mBottom = mTop = INT_MAX; // mark as uninitialized + } + + void set(CommonCheck& check) + { + mLastGlyph = check.mLastGlyph; + mLastUni = check.mLastUni; + mMatrix = check.mMatrix; + mLastPaint = check.mLastPaint; + reset(); + } + + void setGlyph(CommonCheck& check) + { + mLastGlyph = check.mLastGlyph; + mLastUni = check.mLastUni; + mLastPaint = check.mLastPaint; + } + + void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y, + const void* text) + { + mMatrix = matrix; + mPaint = paint; + mText = static_cast<const uint16_t*>(text); + mY = y; + reset(); + } + + /* called only while the picture is parsed */ + int top() { + if (mTop == INT_MAX) { + SkPoint result; + SkPaint::FontMetrics metrics; + mPaint.getFontMetrics(&metrics); + mMatrix.mapXY(0, metrics.fAscent + mY, &result); + mTop = SkScalarFloor(result.fY); + } + return mTop; + } + +#if DEBUG_NAV_UI + // make current (possibily uncomputed) value visible for debugging + int topDebug() const + { + return mTop; + } +#endif + +protected: + SkIRect mArea; + SkBounder::GlyphRec mLastCandidate; + SkBounder::GlyphRec mLastGlyph; + SkPaint mLastPaint; // available after picture has been parsed + SkPaint mLastPaintCandidate; // associated with candidate glyph + SkUnichar mLastUni; + SkUnichar mLastUniCandidate; + SkMatrix mMatrix; + SkPaint mPaint; // only set up while the picture is parsed + const uint16_t* mText; + SkScalar mY; +private: + int mBase; + int mBottom; + SkFixed mMinSpaceWidth; + int mTop; + friend class EdgeCheck; +}; + +// generate the limit area for the new selection +class LineCheck : public CommonCheck { +public: + LineCheck(int x, int y, const SkIRect& area) + : INHERITED(area) + , mX(x) + , mY(y) + , mInBetween(false) + { + mLast.setEmpty(); + } + + void finish(const SkRegion& selectedRgn) + { + if (!mParagraphs.count() && mLast.isEmpty()) + return; + processLine(); + bool above = false; + bool below = false; + bool selected = false; + SkRegion localRgn(selectedRgn); + localRgn.translate(-mArea.fLeft, -mArea.fTop, &localRgn); + DBG_NAV_LOGD("localRgn=(%d,%d,%d,%d)", + localRgn.getBounds().fLeft, localRgn.getBounds().fTop, + localRgn.getBounds().fRight, localRgn.getBounds().fBottom); + for (int index = 0; index < mParagraphs.count(); index++) { + const SkIRect& rect = mParagraphs[index]; + bool localSelected = localRgn.intersects(rect); + DBG_NAV_LOGD("[%d] rect=(%d,%d,%d,%d)", index, rect.fLeft, rect.fTop, + rect.fRight, rect.fBottom); + if (localSelected) { + DBG_NAV_LOGD("[%d] localSelected=true", index); + *mSelected.append() = rect; + } + if (rect.fRight <= mX || rect.fLeft >= mX) + continue; + if (mY > rect.fBottom) { + below = true; + selected |= localSelected; + DBG_NAV_LOGD("[%d] below=true localSelected=%s", index, + localSelected ? "true" : "false"); + } + if (mY < rect.fTop) { + above = true; + selected |= localSelected; + DBG_NAV_LOGD("[%d] above=true localSelected=%s", index, + localSelected ? "true" : "false"); + } + } + DBG_NAV_LOGD("mX=%d mY=%d above=%s below=%s selected=%s", + mX, mY, above ? "true" : "false", below ? "true" : "false", + selected ? "true" : "false"); + mInBetween = above && below && selected; + } + + bool inBetween() const + { + return mInBetween; + } + + bool inColumn(const SkIRect& test) const + { + for (int index = 0; index < mSelected.count(); index++) { + const SkIRect& rect = mSelected[index]; + if (rect.fRight > test.fLeft && rect.fLeft < test.fRight) + return true; + } + return false; + } + + bool inColumn(int x, int y) const + { + for (int index = 0; index < mSelected.count(); index++) { + const SkIRect& rect = mSelected[index]; + if (rect.contains(x, y)) + return true; + } + return false; + } + + virtual bool onIRect(const SkIRect& rect) + { + SkIRect bounds; + bounds.set(rect.fLeft, top(), rect.fRight, bottom()); + // assume that characters must be consecutive to describe spaces + // (i.e., don't join rects drawn at different times) + if (bounds.fTop != mLast.fTop || bounds.fBottom != mLast.fBottom + || bounds.fLeft > mLast.fRight + minSpaceWidth(mPaint) + || bounds.fLeft < mLast.fLeft) { + processLine(); + mLast = bounds; + } else + mLast.join(bounds); + return false; + } + + void processLine() + { + // assume line spacing of 1.5 + int lineHeight = bottom() - top(); + mLast.inset(0, -lineHeight >> 1); + // collect arrays of rectangles making up glyphs below or above this one + for (int index = 0; index < mParagraphs.count(); index++) { + SkIRect& rect = mParagraphs[index]; + if (SkIRect::Intersects(rect, mLast)) { + rect.join(mLast); + return; + } + } + *mParagraphs.append() = mLast; + } + +protected: + int mX; + int mY; + SkIRect mLast; + SkTDArray<SkIRect> mParagraphs; + SkTDArray<SkIRect> mSelected; + bool mInBetween; +private: + typedef CommonCheck INHERITED; +}; + +class SelectText::FirstCheck : public CommonCheck { +public: + FirstCheck(int x, int y, const SkIRect& area) + : INHERITED(area) + , mLineCheck(0) + , mFocusX(x - area.fLeft) + , mFocusY(y - area.fTop) + , mBestInColumn(false) + , mRecordGlyph(false) + { + reset(); + } + + const SkIRect& adjustedBounds(int* base) + { + *base = mBestBase + mArea.fTop; + mBestBounds.offset(mArea.fLeft, mArea.fTop); + DBG_NAV_LOGD("FirstCheck mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d", + mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, + mBestBounds.fBottom, topDebug(), bottomDebug()); + return mBestBounds; + } + + int focusX() const { return mFocusX; } + int focusY() const { return mFocusY; } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + /* compute distance from rectangle center. + * centerX = (rect.L + rect.R) / 2 + * multiply centerX and comparison x by 2 to retain better precision + */ + SkIRect testBounds = {rect.fLeft, top(), rect.fRight, bottom()}; + // dx and dy are the distances from the tested edge + // The edge distance is paramount if the test point is far away + int dx = std::max(0, std::max(testBounds.fLeft - mFocusX, + mFocusX - testBounds.fRight)); + int dy = std::max(0, std::max(testBounds.fTop - mFocusY, + mFocusY - testBounds.fBottom)); + bool testInColumn = false; + bool inBetween = false; + bool inFocus = false; + if (mLineCheck) { + testInColumn = mLineCheck->inColumn(testBounds); + inBetween = mLineCheck->inBetween(); + inFocus = mLineCheck->inColumn(mFocusX, mFocusY); + } +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) { + SkUnichar ch = getUniChar(rec); + DBG_NAV_LOGD("FC dx/y=%d,%d mDx/y=%d,%d test=%d,%d,%d,%d" + " best=%d,%d,%d,%d bestIn=%s tween=%s testIn=%s focus=%s ch=%c", + dx, dy, mDx, mDy, + testBounds.fLeft, testBounds.fTop, testBounds.fRight, + testBounds.fBottom, mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, + mBestInColumn ? "true" : "false", inBetween ? "true" : "false", + testInColumn ? "true" : "false", inFocus ? "true" : "false", + ch < 0x7f ? ch : '?'); + } +#endif + if ((mBestInColumn || inBetween) && !testInColumn) { +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) DBG_NAV_LOG("FirstCheck reject column"); +#endif + return false; + } + bool ignoreColumn = mBestInColumn == testInColumn || !inFocus; + if (ignoreColumn && dy > 0 && (mDy < dy + || (mDy == dy && dx > 0 && mDx <= dx))) { +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) DBG_NAV_LOG("FirstCheck reject edge"); +#endif + return false; + } + // cx and cy are the distances from the tested center + // The center distance is used when the test point is over the text + int cx = std::abs(((testBounds.fLeft + testBounds.fRight) >> 1) + - mFocusX); + int cy = std::abs(((testBounds.fTop + testBounds.fBottom) >> 1) + - mFocusY); + if (ignoreColumn && dy == 0 && mDy == 0) { + if (mCy < cy) { +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("FirstCheck reject cy=%d mCy=%d", cy, mCy); +#endif + return false; + } + if (mCy == cy) { + if (dx == 0 && mDx == 0) { + if (mCx < cx) { +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("FirstCheck reject cx=%d mCx=%d", cx, mCx); +#endif + return false; + } + } else if (dx > 0 && mDx <= dx) { +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("FirstCheck reject dx=%d mDx=%d", dx, mDx); +#endif + return false; + } + } + } +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) { + DBG_NAV_LOGD("FirstCheck cx/y=(%d,%d)", cx, cy); + } +#endif + mBestBase = base(); + mBestBounds = testBounds; + mBestInColumn = testInColumn; +#ifndef EXTRA_NOISY_LOGGING + if (dy < 10 && dx < 10) +#endif + { +#if DEBUG_NAV_UI + SkUnichar ch = getUniChar(rec); +#endif + DBG_NAV_LOGD("FirstCheck dx/y=(%d,%d) mFocus=(%d,%d)" + " mBestBounds={%d,%d,r=%d,b=%d} inColumn=%s ch=%c", + dx, dy, mFocusX, mFocusY, + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, + mBestInColumn ? "true" : "false", ch < 0x7f ? ch : '?'); + } + mCx = cx; + mCy = cy; + mDx = dx; + mDy = dy; + if (mRecordGlyph) + recordGlyph(rec); + return false; + } + + void reset() + { + mBestBounds.setEmpty(); + mDx = mDy = mCx = mCy = INT_MAX; + } + + void setLines(const LineCheck* lineCheck) { mLineCheck = lineCheck; } + void setRecordGlyph() { mRecordGlyph = true; } + +protected: + const LineCheck* mLineCheck; + int mBestBase; + SkIRect mBestBounds; + int mCx; + int mCy; + int mDx; + int mDy; + int mFocusX; + int mFocusY; + bool mBestInColumn; + bool mRecordGlyph; +private: + typedef CommonCheck INHERITED; +}; + +class SelectText::EdgeCheck : public SelectText::FirstCheck { +public: + EdgeCheck(int x, int y, const SkIRect& area, CommonCheck& last, bool left) + : INHERITED(x, y, area) + , mLast(area) + , mLeft(left) + { + mLast.set(last); // CommonCheck::set() + setGlyph(last); + } + + bool adjacent() + { + return !mLast.isSpace(mLastGlyph); + } + + const SkIRect& bestBounds(int* base) + { + *base = mBestBase; + return mBestBounds; + } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + int dx = mLeft ? mFocusX - rect.fRight : rect.fLeft - mFocusX; + int dy = ((top() + bottom()) >> 1) - mFocusY; + dx = abs(dx); + dy = abs(dy); + if (mLeft ? mFocusX <= rect.fLeft : mFocusX >= rect.fRight) { + if (dx <= 10 && dy <= 10) { + DBG_NAV_LOGD("EdgeCheck fLeft=%d fRight=%d mFocusX=%d dx=%d dy=%d", + rect.fLeft, rect.fRight, mFocusX, dx, dy); + } + return false; + } + if (mDy > dy || (mDy == dy && mDx > dx)) { + if (rec.fLSB == mLastGlyph.fLSB && rec.fRSB == mLastGlyph.fRSB) { + DBG_NAV_LOGD("dup rec.fLSB.fX=%g rec.fRSB.fX=%g", + SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fRSB.fX)); + return false; + } + recordGlyph(rec); + mDx = dx; + mDy = dy; + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + if (dx <= 10 && dy <= 10) { + DBG_NAV_LOGD("EdgeCheck mBestBounds={%d,%d,r=%d,b=%d} dx/y=(%d, %d)", + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, dx, dy); + } + } + return false; + } + + void shiftStart(SkIRect bounds) + { + DBG_NAV_LOGD("EdgeCheck mFocusX=%d mLeft=%s bounds.fLeft=%d bounds.fRight=%d", + mFocusX, mLeft ? "true" : "false", bounds.fLeft, bounds.fRight); + reset(); + mFocusX = mLeft ? bounds.fLeft : bounds.fRight; + mLast.set(*this); // CommonCheck::set() + } + +protected: + CommonCheck mLast; + bool mLeft; +private: + typedef SelectText::FirstCheck INHERITED; +}; + +class FindFirst : public CommonCheck { +public: + FindFirst(const SkIRect& area) + : INHERITED(area) + { + mBestBounds.set(area.width(), area.height(), area.width(), area.height()); + } + + const SkIRect& bestBounds(int* base) + { + *base = mBestBase; + return mBestBounds; + } + + virtual bool onIRect(const SkIRect& rect) + { + if (mBestBounds.isEmpty()) { + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + } + return false; + } + +protected: + int mBestBase; + SkIRect mBestBounds; +private: + typedef CommonCheck INHERITED; +}; + +class FindLast : public FindFirst { +public: + FindLast(const SkIRect& area) + : INHERITED(area) + { + mBestBounds.setEmpty(); + } + + virtual bool onIRect(const SkIRect& rect) + { + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + return false; + } + +private: + typedef FindFirst INHERITED; +}; + +static bool baseLinesAgree(const SkIRect& rectA, int baseA, + const SkIRect& rectB, int baseB) +{ + return (rectA.fTop < baseB && rectA.fBottom >= baseB) + || (rectB.fTop < baseA && rectB.fBottom >= baseA); +} + +class BuilderCheck : public CommonCheck { +protected: + enum IntersectionType { + NO_INTERSECTION, // debugging printf expects this to equal zero + LAST_INTERSECTION, // debugging printf expects this to equal one + WAIT_FOR_INTERSECTION + }; + + BuilderCheck(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area) + : INHERITED(area) + , mCapture(false) + , mEnd(end) + , mEndBase(endBase) + , mStart(start) + , mStartBase(startBase) + { + mEnd.offset(-area.fLeft, -area.fTop); + mEndBase -= area.fTop; + mEndExtra.setEmpty(); + mLast.setEmpty(); + mLastBase = INT_MAX; + mSelectRect.setEmpty(); + mStart.offset(-area.fLeft, -area.fTop); + mStartBase -= area.fTop; + mStartExtra.setEmpty(); + DBG_NAV_LOGD(" mStart=(%d,%d,r=%d,b=%d) mStartBase=%d" + " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d", + mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase, + mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase); + } + + int checkFlipRect(const SkIRect& full, int fullBase) { + mCollectFull = false; + // is the text to collect between the selection top and bottom? + if (fullBase < mStart.fTop || fullBase > mEnd.fBottom) { + if (VERBOSE_LOGGING && !mLast.isEmpty()) DBG_NAV_LOGD("%s 1" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase); + return mLastIntersects; + } + // is the text to the left of the selection start? + if (baseLinesAgree(mStart, mStartBase, full, fullBase) + && full.fLeft < mStart.fLeft) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 2" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mStart=(%d,%d,r=%d,b=%d) mStartBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase); + mStartExtra.join(full); + return mLastIntersects; + } + // is the text to the right of the selection end? + if (baseLinesAgree(mEnd, mEndBase, full, fullBase) + && full.fRight > mEnd.fRight) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 3" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase); + mEndExtra.join(full); + return mLastIntersects; + } + int spaceGap = SkFixedRound(minSpaceWidth(mPaint) * 3); + // should text to the left of the start be added to the selection bounds? + if (!mStartExtra.isEmpty()) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)" + " mStartExtra=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom); + if (mStartExtra.fRight + spaceGap >= mStart.fLeft) + mSelectRect.join(mStartExtra); + mStartExtra.setEmpty(); + } + // should text to the right of the end be added to the selection bounds? + if (!mEndExtra.isEmpty()) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)" + " mEndExtra=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom); + if (mEndExtra.fLeft - spaceGap <= mEnd.fRight) + mSelectRect.join(mEndExtra); + mEndExtra.setEmpty(); + } + bool sameBaseLine = baseLinesAgree(mLast, mLastBase, full, fullBase); + bool adjacent = (full.fLeft - mLast.fRight) < spaceGap; + // is this the first, or are there more characters on the same line? + if (mLast.isEmpty() || (sameBaseLine && adjacent)) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("WAIT_FOR_INTERSECTION" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mSelectRect=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom); + mLast.join(full); + mLastIntersects = SkIRect::Intersects(mLast, mSelectRect); + return WAIT_FOR_INTERSECTION; + } + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 4" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mSelectRect=(%d,%d,r=%d,b=%d)" + " mStartExtra=(%d,%d,r=%d,b=%d)" + " mEndExtra=(%d,%d,r=%d,b=%d)", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom, + mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom); + // after the caller determines what to do with the last collection, + // start the collection over with full and fullBase. + mCollectFull = true; + return mLastIntersects; + } + + bool resetLast(const SkIRect& full, int fullBase) + { + if (mCollectFull) { + mLast = full; + mLastBase = fullBase; + mLastIntersects = SkIRect::Intersects(mLast, mSelectRect); + } else { + mLast.setEmpty(); + mLastBase = INT_MAX; + mLastIntersects = false; + } + return mCollectFull; + } + + void setFlippedState() + { + mSelectRect = mStart; + mSelectRect.join(mEnd); + DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom); + mLast.setEmpty(); + mLastBase = INT_MAX; + mLastIntersects = NO_INTERSECTION; + } + + bool mCapture; + bool mCollectFull; + SkIRect mEnd; + int mEndBase; + SkIRect mEndExtra; + bool mFlipped; + SkIRect mLast; + int mLastBase; + int mLastIntersects; + SkIRect mSelectRect; + SkIRect mStart; + SkIRect mStartExtra; + int mStartBase; +private: + typedef CommonCheck INHERITED; + +}; + +class MultilineBuilder : public BuilderCheck { +public: + MultilineBuilder(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area, SkRegion* region) + : INHERITED(start, startBase, end, endBase, area) + , mSelectRegion(region) + { + mFlipped = false; + } + + void addLastToRegion() { + if (VERBOSE_LOGGING) DBG_NAV_LOGD(" mLast=(%d,%d,r=%d,b=%d)", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom); + mSelectRegion->op(mLast, SkRegion::kUnion_Op); + } + + void finish() { + if (!mFlipped || !mLastIntersects) + return; + addLastToRegion(); + } + + // return true if capture end was not found after capture begin + bool flipped() { + DBG_NAV_LOGD("flipped=%s", mCapture ? "true" : "false"); + if (!mCapture) + return false; + mFlipped = true; + setFlippedState(); + mSelectRegion->setEmpty(); + return true; + } + + virtual bool onIRect(const SkIRect& rect) { + SkIRect full; + full.set(rect.fLeft, top(), rect.fRight, bottom()); + int fullBase = base(); + if (mFlipped) { + int intersectType = checkFlipRect(full, fullBase); + if (intersectType == LAST_INTERSECTION) + addLastToRegion(); + if (intersectType != WAIT_FOR_INTERSECTION) + resetLast(full, fullBase); + return false; + } + if (full == mStart) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mStart full=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom); + mCapture = true; + } + if (mCapture) { + bool sameLines = baseLinesAgree(mLast, mLastBase, full, fullBase); + if (sameLines) + mLast.join(full); + if (!sameLines || full == mEnd) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("finish mLast=(%d,%d,r=%d,b=%d)", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom); + addLastToRegion(); + mLast = full; + mLastBase = fullBase; + } + } + if (full == mEnd) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mEnd full=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom); + mCapture = false; + if (full == mStart) + addLastToRegion(); + } + return false; + } + +protected: + SkRegion* mSelectRegion; +private: + typedef BuilderCheck INHERITED; +}; + +static inline bool compareBounds(const SkIRect* first, const SkIRect* second) +{ + return first->fTop < second->fTop; +} + +class TextExtractor : public BuilderCheck { +public: + TextExtractor(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area, bool flipped) + : INHERITED(start, startBase, end, endBase, area) + , mSelectStartIndex(-1) + , mSkipFirstSpace(true) // don't start with a space + { + mFlipped = flipped; + if (flipped) + setFlippedState(); + } + + void addCharacter(const SkBounder::GlyphRec& rec) + { + if (mSelectStartIndex < 0) + mSelectStartIndex = mSelectText.count(); + if (!mSkipFirstSpace) { + if (addNewLine(rec)) { + DBG_NAV_LOG("write new line"); + *mSelectText.append() = '\n'; + *mSelectText.append() = '\n'; + } else if (addSpace(rec)) { + DBG_NAV_LOG("write space"); + *mSelectText.append() = ' '; + } + } else + mSkipFirstSpace = false; + recordGlyph(rec); + finishGlyph(); + if (VERBOSE_LOGGING) DBG_NAV_LOGD("glyphID=%d uni=%d '%c'", rec.fGlyphID, + mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?'); + if (mLastUni) { + uint16_t chars[2]; + size_t count = SkUTF16_FromUnichar(mLastUni, chars); + *mSelectText.append() = chars[0]; + if (count == 2) + *mSelectText.append() = chars[1]; + } + } + + void addLast() + { + *mSelectBounds.append() = mLast; + *mSelectStart.append() = mSelectStartIndex; + *mSelectEnd.append() = mSelectText.count(); + } + + /* Text characters are collected before it's been determined that the + characters are part of the selection. The bounds describe valid parts + of the selection, but the bounds are out of order. + + This sorts the characters by sorting the bounds, then copying the + characters that were captured. + */ + void finish() + { + if (mLastIntersects) + addLast(); + Vector<SkIRect*> sortedBounds; + SkTDArray<uint16_t> temp; + int index; + DBG_NAV_LOGD("mSelectBounds.count=%d text=%d", mSelectBounds.count(), + mSelectText.count()); + for (index = 0; index < mSelectBounds.count(); index++) + sortedBounds.append(&mSelectBounds[index]); + std::sort(sortedBounds.begin(), sortedBounds.end(), compareBounds); + int lastEnd = -1; + for (index = 0; index < mSelectBounds.count(); index++) { + int order = sortedBounds[index] - &mSelectBounds[0]; + int start = mSelectStart[order]; + int end = mSelectEnd[order]; + DBG_NAV_LOGD("order=%d start=%d end=%d top=%d", order, start, end, + mSelectBounds[order].fTop); + int count = temp.count(); + if (count > 0 && temp[count - 1] != '\n' && start != lastEnd) { + // always separate paragraphs when original text is out of order + DBG_NAV_LOG("write new line"); + *temp.append() = '\n'; + *temp.append() = '\n'; + } + temp.append(end - start, &mSelectText[start]); + lastEnd = end; + } + mSelectText.swap(temp); + } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + SkIRect full; + full.set(rect.fLeft, top(), rect.fRight, bottom()); + int fullBase = base(); + if (mFlipped) { + int intersectType = checkFlipRect(full, fullBase); + if (WAIT_FOR_INTERSECTION == intersectType) + addCharacter(rec); // may not be copied + else { + if (LAST_INTERSECTION == intersectType) + addLast(); + else + mSkipFirstSpace = true; + mSelectStartIndex = -1; + if (resetLast(full, fullBase)) + addCharacter(rec); // may not be copied + } + return false; + } + if (full == mStart) + mCapture = true; + if (mCapture) + addCharacter(rec); + else + mSkipFirstSpace = true; + if (full == mEnd) + mCapture = false; + return false; + } + + WTF::String text() { + if (mFlipped) + finish(); + // the text has been copied in visual order. Reverse as needed if + // result contains right-to-left characters. + const uint16_t* start = mSelectText.begin(); + const uint16_t* end = mSelectText.end(); + while (start < end) { + SkUnichar ch = SkUTF16_NextUnichar(&start); + WTF::Unicode::Direction charDirection = WTF::Unicode::direction(ch); + if (WTF::Unicode::RightToLeftArabic == charDirection + || WTF::Unicode::RightToLeft == charDirection) { + WebCore::ReverseBidi(mSelectText.begin(), mSelectText.count()); + break; + } + } + return WTF::String(mSelectText.begin(), mSelectText.count()); + } + +protected: + SkIRect mEmpty; + SkTDArray<SkIRect> mSelectBounds; + SkTDArray<int> mSelectEnd; + SkTDArray<int> mSelectStart; + int mSelectStartIndex; + SkTDArray<uint16_t> mSelectText; + bool mSkipFirstSpace; +private: + typedef BuilderCheck INHERITED; +}; + +class TextCanvas : public ParseCanvas { +public: + + TextCanvas(CommonCheck* bounder) + : mBounder(*bounder) { + setBounder(bounder); + SkBitmap bitmap; + const SkIRect& area = bounder->getArea(); + bitmap.setConfig(SkBitmap::kARGB_8888_Config, area.width(), + area.height()); + setBitmapDevice(bitmap); + translate(SkIntToScalar(-area.fLeft), SkIntToScalar(-area.fTop)); +#ifdef DEBUG_NAV_UI + const SkIRect& clip = getTotalClip().getBounds(); + const SkMatrix& matrix = getTotalMatrix(); + DBG_NAV_LOGD("bitmap=(%d,%d) clip=(%d,%d,%d,%d) matrix=(%g,%g)", + bitmap.width(), bitmap.height(), clip.fLeft, clip.fTop, + clip.fRight, clip.fBottom, matrix.getTranslateX(), matrix.getTranslateY()); +#endif + } + + virtual void drawPaint(const SkPaint& paint) { + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) { + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) { + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& matrix, const SkPaint& paint) { + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint = NULL) { + } + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + mBounder.setUp(paint, getTotalMatrix(), y, text); + INHERITED::drawText(text, byteLength, x, y, paint); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + mBounder.setUp(paint, getTotalMatrix(), constY, text); + INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); + } + + virtual void drawVertices(VertexMode vmode, int vertexCount, + const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) { + } + + CommonCheck& mBounder; +private: + typedef ParseCanvas INHERITED; +}; + +static bool buildSelection(const SkPicture& picture, const SkIRect& area, + const SkIRect& selStart, int startBase, + const SkIRect& selEnd, int endBase, SkRegion* region) +{ + DBG_NAV_LOGD("area=(%d, %d, %d, %d) selStart=(%d, %d, %d, %d)" + " selEnd=(%d, %d, %d, %d)", + area.fLeft, area.fTop, area.fRight, area.fBottom, + selStart.fLeft, selStart.fTop, selStart.fRight, selStart.fBottom, + selEnd.fLeft, selEnd.fTop, selEnd.fRight, selEnd.fBottom); + MultilineBuilder builder(selStart, startBase, selEnd, endBase, area, region); + TextCanvas checker(&builder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + bool flipped = builder.flipped(); + if (flipped) { + TextCanvas checker(&builder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + } + builder.finish(); + region->translate(area.fLeft, area.fTop); + return flipped; +} + +static SkIRect findFirst(const SkPicture& picture, int* base) +{ + SkIRect area; + area.set(0, 0, picture.width(), picture.height()); + FindFirst finder(area); + TextCanvas checker(&finder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return finder.bestBounds(base); +} + +static SkIRect findLast(const SkPicture& picture, int* base) +{ + SkIRect area; + area.set(0, 0, picture.width(), picture.height()); + FindLast finder(area); + TextCanvas checker(&finder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return finder.bestBounds(base); +} + +static WTF::String text(const SkPicture& picture, const SkIRect& area, + const SkIRect& start, int startBase, const SkIRect& end, + int endBase, bool flipped) +{ + TextExtractor extractor(start, startBase, end, endBase, area, flipped); + TextCanvas checker(&extractor); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return extractor.text(); +} + +#define CONTROL_NOTCH 16 +#define CONTROL_HEIGHT 35 +#define CONTROL_WIDTH 21 +#define STROKE_WIDTH 1.0f +#define STROKE_OUTSET 3.5f +#define STROKE_I_OUTSET 4 // (int) ceil(STROKE_OUTSET) +#define STROKE_COLOR 0x66000000 +#define OUTER_COLOR 0x33000000 +#define INNER_COLOR 0xe6aae300 + +#define SLOP 35 + +SelectText::SelectText() +{ + m_picture = 0; + reset(); + SkPaint paint; + SkRect oval; + + SkPath startOuterPath; + oval.set(-CONTROL_WIDTH - STROKE_OUTSET, CONTROL_NOTCH - STROKE_OUTSET, + -CONTROL_WIDTH + STROKE_OUTSET, CONTROL_NOTCH + STROKE_OUTSET); + startOuterPath.arcTo(oval, 180, 45, true); + oval.set(-STROKE_OUTSET, -STROKE_OUTSET, STROKE_OUTSET, STROKE_OUTSET); + startOuterPath.arcTo(oval, 180 + 45, 135, false); + oval.set(-STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + startOuterPath.arcTo(oval, 0, 90, false); + oval.set(-CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + -CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + startOuterPath.arcTo(oval, 90, 90, false); + startOuterPath.close(); + SkPath startInnerPath; + startInnerPath.moveTo(-CONTROL_WIDTH, CONTROL_NOTCH); + startInnerPath.lineTo(-CONTROL_WIDTH, CONTROL_HEIGHT); + startInnerPath.lineTo(0, CONTROL_HEIGHT); + startInnerPath.lineTo(0, 0); + startInnerPath.close(); + startOuterPath.addPath(startInnerPath, 0, 0); + + SkCanvas* canvas = m_startControl.beginRecording( + CONTROL_WIDTH + STROKE_OUTSET * 2, + CONTROL_HEIGHT + STROKE_OUTSET * 2); + paint.setAntiAlias(true); + paint.setColor(INNER_COLOR); + paint.setStyle(SkPaint::kFill_Style); + canvas->drawPath(startInnerPath, paint); + paint.setColor(OUTER_COLOR); + canvas->drawPath(startOuterPath, paint); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(STROKE_COLOR); + paint.setStrokeWidth(STROKE_WIDTH); + canvas->drawPath(startInnerPath, paint); + m_startControl.endRecording(); + + SkPath endOuterPath; + oval.set(-STROKE_OUTSET, -STROKE_OUTSET, STROKE_OUTSET, STROKE_OUTSET); + endOuterPath.arcTo(oval, 180, 135, true); + oval.set(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_NOTCH - STROKE_OUTSET, + CONTROL_WIDTH + STROKE_OUTSET, CONTROL_NOTCH + STROKE_OUTSET); + endOuterPath.arcTo(oval, 360 - 45, 45, false); + oval.set(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + endOuterPath.arcTo(oval, 0, 90, false); + oval.set(-STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + endOuterPath.arcTo(oval, 90, 90, false); + startOuterPath.close(); + SkPath endInnerPath; + endInnerPath.moveTo(0, 0); + endInnerPath.lineTo(0, CONTROL_HEIGHT); + endInnerPath.lineTo(CONTROL_WIDTH, CONTROL_HEIGHT); + endInnerPath.lineTo(CONTROL_WIDTH, CONTROL_NOTCH); + endInnerPath.close(); + endOuterPath.addPath(endInnerPath, 0, 0); + + canvas = m_endControl.beginRecording(CONTROL_WIDTH + STROKE_OUTSET * 2, + CONTROL_HEIGHT + STROKE_OUTSET * 2); + paint.setColor(INNER_COLOR); + paint.setStyle(SkPaint::kFill_Style); + canvas->drawPath(endInnerPath, paint); + paint.setColor(OUTER_COLOR); + canvas->drawPath(endOuterPath, paint); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(STROKE_COLOR); + paint.setStrokeWidth(STROKE_WIDTH); + canvas->drawPath(endInnerPath, paint); + m_endControl.endRecording(); +} + +SelectText::~SelectText() +{ + SkSafeUnref(m_picture); +} + +void SelectText::draw(SkCanvas* canvas, LayerAndroid* layer, IntRect* inval) +{ + if (m_layerId != layer->uniqueId()) + return; + // reset m_picture to match m_layerId + SkSafeUnref(m_picture); + m_picture = layer->picture(); + SkSafeRef(m_picture); + DBG_NAV_LOGD("m_extendSelection=%d m_drawPointer=%d layer [%d]", + m_extendSelection, m_drawPointer, layer->uniqueId()); + if (m_extendSelection) + drawSelectionRegion(canvas, inval); + if (m_drawPointer) + drawSelectionPointer(canvas, inval); +} + +static void addInval(IntRect* inval, const SkCanvas* canvas, + const SkRect& bounds) { + const SkMatrix& matrix = canvas->getTotalMatrix(); + SkRect transformed; + matrix.mapRect(&transformed, bounds); + SkIRect iTrans; + transformed.round(&iTrans); + inval->unite(iTrans); +} + +void SelectText::drawSelectionPointer(SkCanvas* canvas, IntRect* inval) +{ + SkPath path; + if (m_extendSelection) + getSelectionCaret(&path); + else + getSelectionArrow(&path); + SkPixelXorXfermode xorMode(SK_ColorWHITE); + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLACK); + if (m_extendSelection) + paint.setXfermode(&xorMode); + else + paint.setStrokeWidth(SK_Scalar1 * 2); + int sc = canvas->save(); + canvas->scale(m_inverseScale, m_inverseScale); + canvas->translate(m_selectX, m_selectY); + canvas->drawPath(path, paint); + if (!m_extendSelection) { + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorWHITE); + canvas->drawPath(path, paint); + } + SkRect bounds = path.getBounds(); + bounds.inset(-SK_Scalar1 * 2, -SK_Scalar1 * 2); // stroke width + addInval(inval, canvas, bounds); + canvas->restoreToCount(sc); +} + +static void addStart(SkRegion* diff, const SkIRect& rect) +{ + SkIRect bounds; + bounds.set(rect.fLeft - CONTROL_WIDTH - STROKE_I_OUTSET, + rect.fBottom - STROKE_I_OUTSET, rect.fLeft + STROKE_I_OUTSET, + rect.fBottom + CONTROL_HEIGHT + STROKE_I_OUTSET); + diff->op(bounds, SkRegion::kUnion_Op); +} + +static void addEnd(SkRegion* diff, const SkIRect& rect) +{ + SkIRect bounds; + bounds.set(rect.fRight - STROKE_I_OUTSET, rect.fBottom - STROKE_I_OUTSET, + rect.fRight + CONTROL_WIDTH + STROKE_I_OUTSET, + rect.fBottom + CONTROL_HEIGHT + STROKE_I_OUTSET); + diff->op(bounds, SkRegion::kUnion_Op); +} + +void SelectText::drawSelectionRegion(SkCanvas* canvas, IntRect* inval) +{ + if (!m_picture) + return; + SkIRect ivisBounds = m_visibleRect; + ivisBounds.join(m_selStart); + ivisBounds.join(m_selEnd); + DBG_NAV_LOGD("m_selStart=(%d,%d,r=%d,b=%d) m_selEnd=(%d,%d,r=%d,b=%d)" + " ivisBounds=(%d,%d,r=%d,b=%d)", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom, + ivisBounds.fLeft, ivisBounds.fTop, ivisBounds.fRight, ivisBounds.fBottom); + if (m_lastSelRegion != m_selRegion) + m_lastSelRegion.set(m_selRegion); + SkRegion diff(m_lastSelRegion); + m_selRegion.setEmpty(); + m_flipped = buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase, + m_selEnd, m_endBase, &m_selRegion); + SkPath path; + m_selRegion.getBoundaryPath(&path); + path.setFillType(SkPath::kEvenOdd_FillType); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SkColorSetARGB(0x80, 0x83, 0xCC, 0x39)); + canvas->drawPath(path, paint); + // experiment to draw touchable controls that resize the selection + canvas->save(); + canvas->translate(m_selStart.fLeft, m_selStart.fBottom); + canvas->drawPicture(m_startControl); + canvas->restore(); + canvas->save(); + canvas->translate(m_selEnd.fRight, m_selEnd.fBottom); + canvas->drawPicture(m_endControl); + canvas->restore(); + SkIRect a = diff.getBounds(); + SkIRect b = m_selRegion.getBounds(); + diff.op(m_selRegion, SkRegion::kXOR_Op); + SkIRect c = diff.getBounds(); + DBG_NAV_LOGD("old=(%d,%d,r=%d,b=%d) new=(%d,%d,r=%d,b=%d) diff=(%d,%d,r=%d,b=%d)", + a.fLeft, a.fTop, a.fRight, a.fBottom, b.fLeft, b.fTop, b.fRight, b.fBottom, + c.fLeft, c.fTop, c.fRight, c.fBottom); + DBG_NAV_LOGD("lastStart=(%d,%d,r=%d,b=%d) m_lastEnd=(%d,%d,r=%d,b=%d)", + m_lastStart.fLeft, m_lastStart.fTop, m_lastStart.fRight, m_lastStart.fBottom, + m_lastEnd.fLeft, m_lastEnd.fTop, m_lastEnd.fRight, m_lastEnd.fBottom); + if (!m_lastDrawnStart.isEmpty()) + addStart(&diff, m_lastDrawnStart); + if (m_lastStart != m_selStart) { + m_lastDrawnStart = m_lastStart; + m_lastStart = m_selStart; + } + addStart(&diff, m_selStart); + if (!m_lastDrawnEnd.isEmpty()) + addEnd(&diff, m_lastDrawnEnd); + if (m_lastEnd != m_selEnd) { + m_lastDrawnEnd = m_lastEnd; + m_lastEnd = m_selEnd; + } + addEnd(&diff, m_selEnd); + SkIRect iBounds = diff.getBounds(); + DBG_NAV_LOGD("diff=(%d,%d,r=%d,b=%d)", + iBounds.fLeft, iBounds.fTop, iBounds.fRight, iBounds.fBottom); + SkRect bounds; + bounds.set(iBounds); + addInval(inval, canvas, bounds); +} + +void SelectText::extendSelection(const IntRect& vis, int x, int y) +{ + if (!m_picture) + return; + setVisibleRect(vis); + SkIRect clipRect = m_visibleRect; + int base; + DBG_NAV_LOGD("extend x/y=%d,%d m_startOffset=%d,%d", x, y, + m_startOffset.fX, m_startOffset.fY); + x -= m_startOffset.fX; + y -= m_startOffset.fY; + if (m_startSelection) { + if (!clipRect.contains(x, y) + || !clipRect.contains(m_original.fX, m_original.fY)) { + clipRect.set(m_original.fX, m_original.fY, x, y); + clipRect.sort(); + clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height()); + } + FirstCheck center(m_original.fX, m_original.fY, clipRect); + m_selStart = m_selEnd = findClosest(center, *m_picture, &base); + if (m_selStart.isEmpty()) + return; + DBG_NAV_LOGD("selStart clip=(%d,%d,%d,%d) m_original=%d,%d" + " m_selStart=(%d,%d,%d,%d)", clipRect.fLeft, clipRect.fTop, + clipRect.fRight, clipRect.fBottom, m_original.fX, m_original.fY, + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom); + m_startBase = m_endBase = base; + m_startSelection = false; + m_extendSelection = true; + m_original.fX = m_original.fY = 0; + } + DBG_NAV_LOGD("extend x/y=%d,%d m_original=%d,%d", x, y, + m_original.fX, m_original.fY); + x -= m_original.fX; + y -= m_original.fY; + if (!clipRect.contains(x, y) || !clipRect.contains(m_selStart)) { + clipRect.set(m_selStart.fLeft, m_selStart.fTop, x, y); + clipRect.sort(); + clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height()); + } + DBG_NAV_LOGD("extend clip=(%d,%d,%d,%d) x/y=%d,%d wordSel=%s outsideWord=%s", + clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom, x, y, + m_wordSelection ? "true" : "false", m_outsideWord ? "true" : "false"); + FirstCheck extension(x, y, clipRect); + SkIRect found = findClosest(extension, *m_picture, &base); + if (m_wordSelection) { + SkIRect wordBounds = m_wordBounds; + if (!m_outsideWord) + wordBounds.inset(-TOUCH_SLOP, -TOUCH_SLOP); + DBG_NAV_LOGD("x=%d y=%d wordBounds=(%d,%d,r=%d,b=%d)" + " found=(%d,%d,r=%d,b=%d)", x, y, wordBounds.fLeft, wordBounds.fTop, + wordBounds.fRight, wordBounds.fBottom, found.fLeft, found.fTop, + found.fRight, found.fBottom); + if (wordBounds.contains(x, y)) { + DBG_NAV_LOG("wordBounds.contains=true"); + m_outsideWord = false; + return; + } + m_outsideWord = true; + if (found.fBottom <= wordBounds.fTop) + m_hitTopLeft = true; + else if (found.fTop >= wordBounds.fBottom) + m_hitTopLeft = false; + else + m_hitTopLeft = (found.fLeft + found.fRight) + < (wordBounds.fLeft + wordBounds.fRight); + } + DBG_NAV_LOGD("x=%d y=%d m_startSelection=%s %s=(%d, %d, %d, %d)" + " m_extendSelection=%s", + x, y, m_startSelection ? "true" : "false", + m_hitTopLeft ? "m_selStart" : "m_selEnd", + found.fLeft, found.fTop, found.fRight, found.fBottom, + m_extendSelection ? "true" : "false"); + if (m_hitTopLeft) { + m_startBase = base; + m_selStart = found; + } else { + m_endBase = base; + m_selEnd = found; + } + swapAsNeeded(); +} + +SkIRect SelectText::findClosest(FirstCheck& check, const SkPicture& picture, + int* base) +{ + LineCheck lineCheck(check.focusX(), check.focusY(), check.getArea()); + TextCanvas lineChecker(&lineCheck); + lineChecker.drawPicture(const_cast<SkPicture&>(picture)); + lineCheck.finish(m_selRegion); + check.setLines(&lineCheck); + TextCanvas checker(&check); + checker.drawPicture(const_cast<SkPicture&>(picture)); + check.finishGlyph(); + return check.adjustedBounds(base); +} + +SkIRect SelectText::findEdge(const SkPicture& picture, const SkIRect& area, + int x, int y, bool left, int* base) +{ + SkIRect result; + result.setEmpty(); + FirstCheck center(x, y, area); + center.setRecordGlyph(); + int closestBase; + SkIRect closest = findClosest(center, picture, &closestBase); + SkIRect sloppy = closest; + sloppy.inset(-TOUCH_SLOP, -TOUCH_SLOP); + if (!sloppy.contains(x, y)) { + DBG_NAV_LOGD("sloppy=(%d, %d, %d, %d) area=(%d, %d, %d, %d) x/y=%d,%d", + sloppy.fLeft, sloppy.fTop, sloppy.fRight, sloppy.fBottom, + area.fLeft, area.fTop, area.fRight, area.fBottom, x, y); + return result; + } + EdgeCheck edge(x, y, area, center, left); + do { // detect left or right until there's a gap + DBG_NAV_LOGD("edge=%p picture=%p area=%d,%d,%d,%d", + &edge, &picture, area.fLeft, area.fTop, area.fRight, area.fBottom); + TextCanvas checker(&edge); + checker.drawPicture(const_cast<SkPicture&>(picture)); + edge.finishGlyph(); + if (!edge.adjacent()) { + if (result.isEmpty()) { + *base = closestBase; + DBG_NAV_LOGD("closest=%d,%d,%d,%d", closest.fLeft, + closest.fTop, closest.fRight, closest.fBottom); + return closest; + } + DBG_NAV_LOG("adjacent break"); + break; + } + int nextBase; + const SkIRect& next = edge.bestBounds(&nextBase); + if (next.isEmpty()) { + DBG_NAV_LOG("empty"); + break; + } + if (result == next) { + DBG_NAV_LOG("result == next"); + break; + } + *base = nextBase; + result = next; + edge.shiftStart(result); + } while (true); + if (!result.isEmpty()) { + *base += area.fTop; + result.offset(area.fLeft, area.fTop); + } + return result; +} + +SkIRect SelectText::findLeft(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base) +{ + return findEdge(picture, area, x, y, true, base); +} + +SkIRect SelectText::findRight(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base) +{ + return findEdge(picture, area, x, y, false, base); +} + +const String SelectText::getSelection() +{ + if (!m_picture) + return String(); + SkIRect clipRect; + clipRect.set(0, 0, m_picture->width(), m_picture->height()); + String result = text(*m_picture, clipRect, m_selStart, m_startBase, + m_selEnd, m_endBase, m_flipped); + DBG_NAV_LOGD("clip=(%d,%d,%d,%d)" + " m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)", + clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom, + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); + DBG_NAV_LOGD("text=%s", result.latin1().data()); // uses CString + return result; +} + +void SelectText::getSelectionArrow(SkPath* path) +{ + const int arrow[] = { + 0, 14, 3, 11, 5, 15, 9, 15, 7, 11, 11, 11 + }; + for (unsigned index = 0; index < sizeof(arrow)/sizeof(arrow[0]); index += 2) + path->lineTo(arrow[index], arrow[index + 1]); + path->close(); +} + +void SelectText::getSelectionCaret(SkPath* path) +{ + SkScalar height = m_selStart.fBottom - m_selStart.fTop; + SkScalar dist = height / 4; + path->moveTo(0, -height / 2); + path->rLineTo(0, height); + path->rLineTo(-dist, dist); + path->rMoveTo(0, -0.5f); + path->rLineTo(dist * 2, 0); + path->rMoveTo(0, 0.5f); + path->rLineTo(-dist, -dist); +} + +bool SelectText::hitCorner(int cx, int cy, int x, int y) const +{ + SkIRect test; + test.set(cx, cy, cx, cy); + test.inset(-SLOP, -SLOP); + return test.contains(x, y); +} + +bool SelectText::hitSelection(int x, int y) const +{ + x -= m_startOffset.fX; + y -= m_startOffset.fY; + int left = m_selStart.fLeft - CONTROL_WIDTH / 2; + int top = m_selStart.fBottom + CONTROL_HEIGHT / 2; + if (hitCorner(left, top, x, y)) + return true; + int right = m_selEnd.fRight + CONTROL_WIDTH / 2; + int bottom = m_selEnd.fBottom + CONTROL_HEIGHT / 2; + if (hitCorner(right, bottom, x, y)) + return true; + return m_selRegion.contains(x, y); +} + +void SelectText::moveSelection(const IntRect& vis, int x, int y) +{ + if (!m_picture) + return; + x -= m_startOffset.fX; + y -= m_startOffset.fY; + setVisibleRect(vis); + SkIRect clipRect = m_visibleRect; + clipRect.join(m_selStart); + clipRect.join(m_selEnd); + FirstCheck center(x, y, clipRect); + int base; + SkIRect found = findClosest(center, *m_picture, &base); + if (m_hitTopLeft || !m_extendSelection) { + m_startBase = base; + m_selStart = found; + } + if (!m_hitTopLeft || !m_extendSelection) { + m_endBase = base; + m_selEnd = found; + } + swapAsNeeded(); + DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)" + " m_selEnd=(%d, %d, %d, %d)", x, y, m_extendSelection ? "true" : "false", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); +} + +void SelectText::reset() +{ + DBG_NAV_LOG("m_extendSelection=false"); + m_selStart.setEmpty(); + m_lastStart.setEmpty(); + m_lastDrawnStart.setEmpty(); + m_selEnd.setEmpty(); + m_lastEnd.setEmpty(); + m_lastDrawnEnd.setEmpty(); + m_extendSelection = false; + m_startSelection = false; + SkSafeUnref(m_picture); + m_picture = 0; + m_layerId = 0; +} + +IntPoint SelectText::selectableText(const CachedRoot* root) +{ + int x = 0; + int y = 0; + SkPicture* picture = root->pictureAt(&x, &y, &m_layerId); + if (!picture) { + DBG_NAV_LOG("picture==0"); + return IntPoint(0, 0); + } + int width = picture->width(); + int height = picture->height(); + IntRect vis(0, 0, width, height); + FirstCheck center(width >> 1, height >> 1, vis); + int base; + const SkIRect& closest = findClosest(center, *picture, &base); + return IntPoint((closest.fLeft + closest.fRight) >> 1, + (closest.fTop + closest.fBottom) >> 1); +} + +void SelectText::selectAll() +{ + if (!m_picture) + return; + m_selStart = findFirst(*m_picture, &m_startBase); + m_selEnd = findLast(*m_picture, &m_endBase); + m_extendSelection = true; +} + +int SelectText::selectionX() const +{ + return (m_hitTopLeft ? m_selStart.fLeft : m_selEnd.fRight) + m_startOffset.fX; +} + +int SelectText::selectionY() const +{ + const SkIRect& rect = m_hitTopLeft ? m_selStart : m_selEnd; + return ((rect.fTop + rect.fBottom) >> 1) + m_startOffset.fY; +} + +void SelectText::setVisibleRect(const IntRect& vis) +{ + DBG_NAV_LOGD("vis=(%d,%d,w=%d,h=%d) offset=(%d,%d)", + vis.x(), vis.y(), vis.width(), vis.height(), m_startOffset.fX, + m_startOffset.fY); + m_visibleRect = vis; + m_visibleRect.offset(-m_startOffset.fX, -m_startOffset.fY); +} + +bool SelectText::startSelection(const CachedRoot* root, const IntRect& vis, + int x, int y) +{ + m_wordSelection = false; + m_startOffset.set(x, y); + DBG_NAV_LOGD("x/y=(%d,%d)", x, y); + SkSafeUnref(m_picture); + m_picture = root->pictureAt(&x, &y, &m_layerId); + DBG_NAV_LOGD("m_picture=%p m_layerId=%d x/y=(%d,%d)", m_picture, m_layerId, + x, y); + if (!m_picture) { + DBG_NAV_LOG("picture==0"); + return false; + } + m_picture->ref(); + m_startOffset.fX -= x; + m_startOffset.fY -= y; + m_original.fX = x; + m_original.fY = y; + setVisibleRect(vis); + if (m_selStart.isEmpty()) { + DBG_NAV_LOGD("empty start picture=(%d,%d) x=%d y=%d", + m_picture->width(), m_picture->height(), x, y); + m_startSelection = true; + return true; + } + int left = m_selStart.fLeft - CONTROL_WIDTH / 2; + int top = m_selStart.fBottom + CONTROL_HEIGHT / 2; + m_hitTopLeft = hitCorner(left, top, x, y); + int right = m_selEnd.fRight + CONTROL_WIDTH / 2; + int bottom = m_selEnd.fBottom + CONTROL_HEIGHT / 2; + bool hitBottomRight = hitCorner(right, bottom, x, y); + DBG_NAV_LOGD("picture=(%d,%d) left=%d top=%d right=%d bottom=%d x=%d y=%d", + m_picture->width(), m_picture->height(),left, top, right, bottom, x, y); + if (m_hitTopLeft && (!hitBottomRight || y - top < bottom - y)) { + DBG_NAV_LOG("hit top left"); + m_original.fX -= m_selStart.fLeft; + m_original.fY -= (m_selStart.fTop + m_selStart.fBottom) >> 1; + } else if (hitBottomRight) { + DBG_NAV_LOG("hit bottom right"); + m_original.fX -= m_selEnd.fRight; + m_original.fY -= (m_selEnd.fTop + m_selEnd.fBottom) >> 1; + } + return m_hitTopLeft || hitBottomRight; +} + +/* selects the word at (x, y) +* a word is normally delimited by spaces +* a string of digits (even with inside spaces) is a word (for phone numbers) +* FIXME: digit find isn't implemented yet +* returns true if a word was selected +*/ +bool SelectText::wordSelection(const CachedRoot* root, const IntRect& vis, + int x, int y) +{ + IntRect tapArea = IntRect(x - TOUCH_SLOP, y - TOUCH_SLOP, TOUCH_SLOP * 2, + TOUCH_SLOP * 2); + if (!startSelection(root, tapArea, x, y)) + return false; + extendSelection(tapArea, x, y); + if (m_selStart.isEmpty()) + return false; + setDrawPointer(false); + setVisibleRect(vis); + SkIRect ivisBounds = m_visibleRect; + ivisBounds.join(m_selStart); + ivisBounds.join(m_selEnd); + DBG_NAV_LOGD("m_selStart=(%d,%d,r=%d,b=%d) m_selEnd=(%d,%d,r=%d,b=%d)" + " ivisBounds=(%d,%d,r=%d,b=%d)", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom, + ivisBounds.fLeft, ivisBounds.fTop, ivisBounds.fRight, ivisBounds.fBottom); + m_selRegion.setEmpty(); + buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase, + m_selEnd, m_endBase, &m_selRegion); + x = m_selStart.fLeft; + y = (m_selStart.fTop + m_selStart.fBottom) >> 1; + SkIRect clipRect = m_visibleRect; + clipRect.fLeft -= m_visibleRect.width() >> 1; + clipRect.fLeft = std::max(clipRect.fLeft, 0); + int base; + SkIRect left = findLeft(*m_picture, clipRect, x, y, &base); + if (!left.isEmpty()) { + m_startBase = base; + m_selStart = left; + } + x = m_selEnd.fRight; + y = (m_selEnd.fTop + m_selEnd.fBottom) >> 1; + clipRect = m_visibleRect; + clipRect.fRight += m_visibleRect.width() >> 1; + SkIRect right = findRight(*m_picture, clipRect, x, y, &base); + if (!right.isEmpty()) { + m_endBase = base; + m_selEnd = right; + } + DBG_NAV_LOGD("m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); + if (!left.isEmpty() || !right.isEmpty()) { + m_wordBounds = m_selStart; + m_wordBounds.join(m_selEnd); + m_extendSelection = m_wordSelection = true; + m_outsideWord = false; + return true; + } + return false; +} + +void SelectText::swapAsNeeded() +{ + if (m_selStart.fTop >= (m_selEnd.fTop + m_selEnd.fBottom) >> 1 + || (m_selEnd.fTop < (m_selStart.fTop + m_selStart.fBottom) >> 1 + && m_selStart.fRight > m_selEnd.fLeft)) + { + SkTSwap(m_startBase, m_endBase); + SkTSwap(m_selStart, m_selEnd); + m_hitTopLeft ^= true; + DBG_NAV_LOGD("m_hitTopLeft=%s", m_hitTopLeft ? "true" : "false"); + } +} + +} diff --git a/Source/WebKit/android/nav/SelectText.h b/Source/WebKit/android/nav/SelectText.h new file mode 100644 index 0000000..42239cf --- /dev/null +++ b/Source/WebKit/android/nav/SelectText.h @@ -0,0 +1,116 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef SELECT_TEXT_H +#define SELECT_TEXT_H + +#include "DrawExtra.h" +#include "IntPoint.h" +#include "IntRect.h" +#include "PlatformString.h" +#include "SkPath.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkRegion.h" + +namespace android { + +class CachedRoot; + +class SelectText : public DrawExtra { +public: + SelectText(); + virtual ~SelectText(); + virtual void draw(SkCanvas* , LayerAndroid* , IntRect* ); + void extendSelection(const IntRect& vis, int x, int y); + const String getSelection(); + bool hitSelection(int x, int y) const; + void moveSelection(const IntRect& vis, int x, int y); + void reset(); + IntPoint selectableText(const CachedRoot* ); + void selectAll(); + int selectionX() const; + int selectionY() const; + void setDrawPointer(bool drawPointer) { m_drawPointer = drawPointer; } + void setExtendSelection(bool extend) { m_extendSelection = extend; } + bool startSelection(const CachedRoot* , const IntRect& vis, int x, int y); + bool wordSelection(const CachedRoot* , const IntRect& vis, int x, int y); +public: + float m_inverseScale; // inverse scale, x, y used for drawing select path + int m_selectX; + int m_selectY; +private: + class FirstCheck; + class EdgeCheck; + void drawSelectionPointer(SkCanvas* , IntRect* ); + void drawSelectionRegion(SkCanvas* , IntRect* ); + SkIRect findClosest(FirstCheck& , const SkPicture& , int* base); + SkIRect findEdge(const SkPicture& , const SkIRect& area, + int x, int y, bool left, int* base); + SkIRect findLeft(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base); + SkIRect findRight(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base); + static void getSelectionArrow(SkPath* ); + void getSelectionCaret(SkPath* ); + bool hitCorner(int cx, int cy, int x, int y) const; + void setVisibleRect(const IntRect& ); + void swapAsNeeded(); + SkIPoint m_original; // computed start of extend selection + SkIPoint m_startOffset; // difference from global to layer + SkIRect m_selStart; + SkIRect m_selEnd; + SkIRect m_lastStart; + SkIRect m_lastEnd; + SkIRect m_lastDrawnStart; + SkIRect m_lastDrawnEnd; + SkIRect m_wordBounds; + int m_startBase; + int m_endBase; + int m_layerId; + SkIRect m_visibleRect; // constrains picture computations to visible area + SkRegion m_lastSelRegion; + SkRegion m_selRegion; // computed from sel start, end + SkPicture m_startControl; + SkPicture m_endControl; + const SkPicture* m_picture; + bool m_drawPointer; + bool m_extendSelection; // false when trackball is moving pointer + bool m_flipped; + bool m_hitTopLeft; + bool m_startSelection; + bool m_wordSelection; + bool m_outsideWord; +}; + +} + +namespace WebCore { + +void ReverseBidi(UChar* chars, int len); + +} + +#endif diff --git a/Source/WebKit/android/nav/WebView.cpp b/Source/WebKit/android/nav/WebView.cpp new file mode 100644 index 0000000..ff5d73d --- /dev/null +++ b/Source/WebKit/android/nav/WebView.cpp @@ -0,0 +1,2673 @@ +/* + * Copyright 2007, 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 "webviewglue" + +#include "config.h" + +#include "AndroidAnimation.h" +#include "AndroidLog.h" +#include "BaseLayerAndroid.h" +#include "CachedFrame.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "DrawExtra.h" +#include "FindCanvas.h" +#include "Frame.h" +#include "GraphicsJNI.h" +#include "HTMLInputElement.h" +#include "IntPoint.h" +#include "IntRect.h" +#include "LayerAndroid.h" +#include "Node.h" +#include "utils/Functor.h" +#include "private/hwui/DrawGlInfo.h" +#include "PlatformGraphicsContext.h" +#include "PlatformString.h" +#include "ScrollableLayerAndroid.h" +#include "SelectText.h" +#include "SkCanvas.h" +#include "SkDumpCanvas.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkTime.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif +#include "TilesManager.h" +#include "WebCoreJni.h" +#include "WebRequestContext.h" +#include "WebViewCore.h" +#include "android_graphics.h" + +#ifdef GET_NATIVE_VIEW +#undef GET_NATIVE_VIEW +#endif + +#define GET_NATIVE_VIEW(env, obj) ((WebView*)env->GetIntField(obj, gWebViewField)) + +#include <JNIUtility.h> +#include <JNIHelp.h> +#include <jni.h> +#include <android_runtime/android_util_AssetManager.h> +#include <ui/KeycodeLabels.h> +#include <utils/AssetManager.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/CString.h> + +namespace android { + +static jfieldID gWebViewField; + +//------------------------------------- + +static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) +{ + jmethodID m = env->GetMethodID(clazz, name, signature); + LOG_ASSERT(m, "Could not find method %s", name); + return m; +} + +//------------------------------------- +// This class provides JNI for making calls into native code from the UI side +// of the multi-threaded WebView. +class WebView +{ +public: +enum FrameCachePermission { + DontAllowNewer, + AllowNewer, + AllowNewest +}; + +enum DrawExtras { // keep this in sync with WebView.java + DrawExtrasNone = 0, + DrawExtrasFind = 1, + DrawExtrasSelection = 2, + DrawExtrasCursorRing = 3 +}; + +struct JavaGlue { + jweak m_obj; + jmethodID m_calcOurContentVisibleRectF; + jmethodID m_overrideLoading; + jmethodID m_scrollBy; + jmethodID m_sendMoveFocus; + jmethodID m_sendMoveMouse; + jmethodID m_sendMoveMouseIfLatest; + jmethodID m_sendMotionUp; + jmethodID m_domChangedFocus; + jmethodID m_getScaledMaxXScroll; + jmethodID m_getScaledMaxYScroll; + jmethodID m_getVisibleRect; + jmethodID m_rebuildWebTextView; + jmethodID m_viewInvalidate; + jmethodID m_viewInvalidateRect; + jmethodID m_postInvalidateDelayed; + jmethodID m_inFullScreenMode; + jfieldID m_rectLeft; + jfieldID m_rectTop; + jmethodID m_rectWidth; + jmethodID m_rectHeight; + jfieldID m_rectFLeft; + jfieldID m_rectFTop; + jmethodID m_rectFWidth; + jmethodID m_rectFHeight; + AutoJObject object(JNIEnv* env) { + return getRealObject(env, m_obj); + } +} m_javaGlue; + +WebView(JNIEnv* env, jobject javaWebView, int viewImpl, WTF::String drawableDir, AssetManager* am) : + m_ring((WebViewCore*) viewImpl) +{ + jclass clazz = env->FindClass("android/webkit/WebView"); + // m_javaGlue = new JavaGlue; + m_javaGlue.m_obj = env->NewWeakGlobalRef(javaWebView); + m_javaGlue.m_scrollBy = GetJMethod(env, clazz, "setContentScrollBy", "(IIZ)Z"); + m_javaGlue.m_calcOurContentVisibleRectF = GetJMethod(env, clazz, "calcOurContentVisibleRectF", "(Landroid/graphics/RectF;)V"); + m_javaGlue.m_overrideLoading = GetJMethod(env, clazz, "overrideLoading", "(Ljava/lang/String;)V"); + m_javaGlue.m_sendMoveFocus = GetJMethod(env, clazz, "sendMoveFocus", "(II)V"); + m_javaGlue.m_sendMoveMouse = GetJMethod(env, clazz, "sendMoveMouse", "(IIII)V"); + m_javaGlue.m_sendMoveMouseIfLatest = GetJMethod(env, clazz, "sendMoveMouseIfLatest", "(ZZ)V"); + m_javaGlue.m_sendMotionUp = GetJMethod(env, clazz, "sendMotionUp", "(IIIII)V"); + m_javaGlue.m_domChangedFocus = GetJMethod(env, clazz, "domChangedFocus", "()V"); + m_javaGlue.m_getScaledMaxXScroll = GetJMethod(env, clazz, "getScaledMaxXScroll", "()I"); + m_javaGlue.m_getScaledMaxYScroll = GetJMethod(env, clazz, "getScaledMaxYScroll", "()I"); + m_javaGlue.m_getVisibleRect = GetJMethod(env, clazz, "sendOurVisibleRect", "()Landroid/graphics/Rect;"); + m_javaGlue.m_rebuildWebTextView = GetJMethod(env, clazz, "rebuildWebTextView", "()V"); + m_javaGlue.m_viewInvalidate = GetJMethod(env, clazz, "viewInvalidate", "()V"); + m_javaGlue.m_viewInvalidateRect = GetJMethod(env, clazz, "viewInvalidate", "(IIII)V"); + m_javaGlue.m_postInvalidateDelayed = GetJMethod(env, clazz, + "viewInvalidateDelayed", "(JIIII)V"); + m_javaGlue.m_inFullScreenMode = GetJMethod(env, clazz, "inFullScreenMode", "()Z"); + env->DeleteLocalRef(clazz); + + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find Rect class"); + m_javaGlue.m_rectLeft = env->GetFieldID(rectClass, "left", "I"); + m_javaGlue.m_rectTop = env->GetFieldID(rectClass, "top", "I"); + m_javaGlue.m_rectWidth = GetJMethod(env, rectClass, "width", "()I"); + m_javaGlue.m_rectHeight = GetJMethod(env, rectClass, "height", "()I"); + env->DeleteLocalRef(rectClass); + + jclass rectClassF = env->FindClass("android/graphics/RectF"); + LOG_ASSERT(rectClassF, "Could not find RectF class"); + m_javaGlue.m_rectFLeft = env->GetFieldID(rectClassF, "left", "F"); + m_javaGlue.m_rectFTop = env->GetFieldID(rectClassF, "top", "F"); + m_javaGlue.m_rectFWidth = GetJMethod(env, rectClassF, "width", "()F"); + m_javaGlue.m_rectFHeight = GetJMethod(env, rectClassF, "height", "()F"); + env->DeleteLocalRef(rectClassF); + + env->SetIntField(javaWebView, gWebViewField, (jint)this); + m_viewImpl = (WebViewCore*) viewImpl; + m_frameCacheUI = 0; + m_navPictureUI = 0; + m_generation = 0; + m_heightCanMeasure = false; + m_lastDx = 0; + m_lastDxTime = 0; + m_ringAnimationEnd = 0; + m_baseLayer = 0; + m_glDrawFunctor = 0; + if (drawableDir.isEmpty()) + m_buttonSkin = 0; + else + m_buttonSkin = new RenderSkinButton(am, drawableDir); +#if USE(ACCELERATED_COMPOSITING) + m_glWebViewState = 0; +#endif +} + +~WebView() +{ + if (m_javaGlue.m_obj) + { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteWeakGlobalRef(m_javaGlue.m_obj); + m_javaGlue.m_obj = 0; + } +#if USE(ACCELERATED_COMPOSITING) + // We must remove the m_glWebViewState prior to deleting m_baseLayer. If we + // do not remove it here, we risk having BaseTiles trying to paint using a + // deallocated base layer. + stopGL(); +#endif + delete m_frameCacheUI; + delete m_navPictureUI; + SkSafeUnref(m_baseLayer); + delete m_glDrawFunctor; + delete m_buttonSkin; +} + +void stopGL() +{ +#if USE(ACCELERATED_COMPOSITING) + delete m_glWebViewState; + m_glWebViewState = 0; +#endif +} + +WebViewCore* getWebViewCore() const { + return m_viewImpl; +} + +// removes the cursor altogether (e.g., when going to a new page) +void clearCursor() +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + DBG_NAV_LOG(""); + m_viewImpl->m_hasCursorBounds = false; + root->clearCursor(); + viewInvalidate(); +} + +// leaves the cursor where it is, but suppresses drawing it +void hideCursor() +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + DBG_NAV_LOG(""); + hideCursor(root); +} + +void hideCursor(CachedRoot* root) +{ + DBG_NAV_LOG("inner"); + m_viewImpl->m_hasCursorBounds = false; + root->hideCursor(); + viewInvalidate(); +} + +#if DUMP_NAV_CACHE +void debugDump() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) + root->mDebug.print(); +} +#endif + +// Traverse our stored array of buttons that are in our picture, and update +// their subpictures according to their current state. +// Called from the UI thread. This is the one place in the UI thread where we +// access the buttons stored in the WebCore thread. +// hasFocus keeps track of whether the WebView has focus && windowFocus. +// If not, we do not want to draw the button in a selected or pressed state +void nativeRecordButtons(bool hasFocus, bool pressed, bool invalidate) +{ + bool cursorIsOnButton = false; + const CachedFrame* cachedFrame; + const CachedNode* cachedCursor = 0; + // Lock the mutex, since we now share with the WebCore thread. + m_viewImpl->gButtonMutex.lock(); + if (m_viewImpl->m_buttons.size() && m_buttonSkin) { + // FIXME: In a future change, we should keep track of whether the selection + // has changed to short circuit (note that we would still need to update + // if we received new buttons from the WebCore thread). + WebCore::Node* cursor = 0; + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + cachedCursor = root->currentCursor(&cachedFrame); + if (cachedCursor) + cursor = (WebCore::Node*) cachedCursor->nodePointer(); + } + + // Traverse the array, and update each button, depending on whether it + // is selected. + Container* end = m_viewImpl->m_buttons.end(); + for (Container* ptr = m_viewImpl->m_buttons.begin(); ptr != end; ptr++) { + RenderSkinAndroid::State state = RenderSkinAndroid::kNormal; + if (ptr->matches(cursor)) { + cursorIsOnButton = true; + // If the WebView is out of focus/window focus, set the state to + // normal, but still keep track of the fact that the selected is a + // button + if (hasFocus) { + if (pressed || m_ring.m_isPressed) + state = RenderSkinAndroid::kPressed; + else if (SkTime::GetMSecs() < m_ringAnimationEnd) + state = RenderSkinAndroid::kFocused; + } + } + ptr->updateFocusState(state, m_buttonSkin); + } + } + m_viewImpl->gButtonMutex.unlock(); + if (invalidate && cachedCursor && cursorIsOnButton) { + const WebCore::IntRect& b = cachedCursor->bounds(cachedFrame); + viewInvalidateRect(b.x(), b.y(), b.right(), b.bottom()); + } +} + +// The caller has already determined that the desired document rect corresponds +// to the main picture, and not a layer +void scrollRectOnScreen(const IntRect& rect) +{ + if (rect.isEmpty()) + return; + SkRect visible; + calcOurContentVisibleRect(&visible); + int dx = 0; + int left = rect.x(); + int right = rect.right(); + if (left < visible.fLeft) { + dx = left - visible.fLeft; + // Only scroll right if the entire width can fit on screen. + } else if (right > visible.fRight && right - left < visible.width()) { + dx = right - visible.fRight; + } + int dy = 0; + int top = rect.y(); + int bottom = rect.bottom(); + if (top < visible.fTop) { + dy = top - visible.fTop; + // Only scroll down if the entire height can fit on screen + } else if (bottom > visible.fBottom && bottom - top < visible.height()) { + dy = bottom - visible.fBottom; + } + if ((dx|dy) == 0 || !scrollBy(dx, dy)) + return; + viewInvalidate(); +} + +void calcOurContentVisibleRect(SkRect* r) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jclass rectClass = env->FindClass("android/graphics/RectF"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(FFFF)V"); + jobject jRect = env->NewObject(rectClass, init, 0, 0, 0, 0); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_calcOurContentVisibleRectF, jRect); + r->fLeft = env->GetFloatField(jRect, m_javaGlue.m_rectFLeft); + r->fTop = env->GetFloatField(jRect, m_javaGlue.m_rectFTop); + r->fRight = r->fLeft + env->CallFloatMethod(jRect, m_javaGlue.m_rectFWidth); + r->fBottom = r->fTop + env->CallFloatMethod(jRect, m_javaGlue.m_rectFHeight); + env->DeleteLocalRef(rectClass); + env->DeleteLocalRef(jRect); + checkException(env); +} + +void resetCursorRing() +{ + m_ringAnimationEnd = 0; + m_viewImpl->m_hasCursorBounds = false; +} + +bool drawCursorPreamble(CachedRoot* root) +{ + const CachedFrame* frame; + const CachedNode* node = root->currentCursor(&frame); + if (!node) { + DBG_NAV_LOGV("%s", "!node"); + resetCursorRing(); + return false; + } + m_ring.setIsButton(node); + if (node->isHidden()) { + DBG_NAV_LOG("node->isHidden()"); + m_viewImpl->m_hasCursorBounds = false; + return false; + } +#if USE(ACCELERATED_COMPOSITING) + if (node->isInLayer() && root->rootLayer()) { + LayerAndroid* layer = const_cast<LayerAndroid*>(root->rootLayer()); + SkRect visible; + calcOurContentVisibleRect(&visible); + layer->updateFixedLayersPositions(visible); + layer->updatePositions(); + } +#endif + setVisibleRect(root); + m_ring.m_root = root; + m_ring.m_frame = frame; + m_ring.m_node = node; + SkMSec time = SkTime::GetMSecs(); + m_ring.m_isPressed = time < m_ringAnimationEnd + && m_ringAnimationEnd != UINT_MAX; + return true; +} + +void drawCursorPostamble() +{ + if (m_ringAnimationEnd == UINT_MAX) + return; + SkMSec time = SkTime::GetMSecs(); + if (time < m_ringAnimationEnd) { + // views assume that inval bounds coordinates are non-negative + WebCore::IntRect invalBounds(0, 0, INT_MAX, INT_MAX); + invalBounds.intersect(m_ring.m_absBounds); + postInvalidateDelayed(m_ringAnimationEnd - time, invalBounds); + } else { + hideCursor(const_cast<CachedRoot*>(m_ring.m_root)); + } +} + +bool drawGL(WebCore::IntRect& viewRect, WebCore::IntRect* invalRect, WebCore::IntRect& webViewRect, + int titleBarHeight, WebCore::IntRect& clip, float scale, int extras) +{ +#if USE(ACCELERATED_COMPOSITING) + if (!m_baseLayer || inFullScreenMode()) + return false; + + if (!m_glWebViewState) { + m_glWebViewState = new GLWebViewState(&m_viewImpl->gButtonMutex); + if (m_baseLayer->content()) { + SkRegion region; + SkIRect rect; + rect.set(0, 0, m_baseLayer->content()->width(), m_baseLayer->content()->height()); + region.setRect(rect); + m_glWebViewState->setBaseLayer(m_baseLayer, region, false, false); + } + } + + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + if (extras == DrawExtrasCursorRing) + resetCursorRing(); + return false; + } + DrawExtra* extra = 0; + switch (extras) { + case DrawExtrasFind: + extra = &m_findOnPage; + break; + case DrawExtrasSelection: + extra = &m_selectText; + break; + case DrawExtrasCursorRing: + if (drawCursorPreamble(root) && m_ring.setup()) { + if (!m_ring.m_isButton) + extra = &m_ring; + drawCursorPostamble(); + } + break; + default: + ; + } + + unsigned int pic = m_glWebViewState->currentPictureCounter(); + + SkPicture picture; + IntRect rect(0, 0, 0, 0); + bool allowSame = false; + m_glWebViewState->resetRings(); + if (extra) { + if (extra == &m_ring) { + m_glWebViewState->setRings(m_ring.rings(), m_ring.m_isPressed); + } else { + LayerAndroid mainPicture(m_navPictureUI); + PictureSet* content = m_baseLayer->content(); + SkCanvas* canvas = picture.beginRecording(content->width(), + content->height()); + extra->draw(canvas, &mainPicture, &rect); + picture.endRecording(); + } + } else if (extras == DrawExtrasCursorRing && m_ring.m_isButton) { + const CachedFrame* cachedFrame; + const CachedNode* cachedCursor = root->currentCursor(&cachedFrame); + if (cachedCursor) { + rect = cachedCursor->bounds(cachedFrame); + allowSame = true; + } + } + m_glWebViewState->setExtra(m_baseLayer, picture, rect, allowSame); + + LayerAndroid* compositeLayer = compositeRoot(); + if (compositeLayer) + compositeLayer->setExtra(extra); + + SkRect visibleRect; + calcOurContentVisibleRect(&visibleRect); + bool ret = m_glWebViewState->drawGL(viewRect, visibleRect, invalRect, + webViewRect, titleBarHeight, clip, scale); + if (ret || m_glWebViewState->currentPictureCounter() != pic) + return true; +#endif + return false; +} + +PictureSet* draw(SkCanvas* canvas, SkColor bgColor, int extras, bool split) +{ + PictureSet* ret = 0; + if (!m_baseLayer) { + canvas->drawColor(bgColor); + return ret; + } + + // draw the content of the base layer first + PictureSet* content = m_baseLayer->content(); + int sc = canvas->save(SkCanvas::kClip_SaveFlag); + canvas->clipRect(SkRect::MakeLTRB(0, 0, content->width(), + content->height()), SkRegion::kDifference_Op); + canvas->drawColor(bgColor); + canvas->restoreToCount(sc); + if (content->draw(canvas)) + ret = split ? new PictureSet(*content) : 0; + + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + if (extras == DrawExtrasCursorRing) + resetCursorRing(); + return ret; + } + LayerAndroid mainPicture(m_navPictureUI); + DrawExtra* extra = 0; + switch (extras) { + case DrawExtrasFind: + extra = &m_findOnPage; + break; + case DrawExtrasSelection: + extra = &m_selectText; + break; + case DrawExtrasCursorRing: + if (drawCursorPreamble(root) && m_ring.setup()) { + if (!m_ring.m_isButton) + extra = &m_ring; + drawCursorPostamble(); + } + break; + default: + ; + } + if (extra) { + IntRect dummy; // inval area, unused for now + extra->draw(canvas, &mainPicture, &dummy); + } +#if USE(ACCELERATED_COMPOSITING) + LayerAndroid* compositeLayer = compositeRoot(); + if (!compositeLayer) + return ret; + compositeLayer->setExtra(extra); + SkRect visible; + calcOurContentVisibleRect(&visible); + // call this to be sure we've adjusted for any scrolling or animations + // before we actually draw + compositeLayer->updateFixedLayersPositions(visible); + compositeLayer->updatePositions(); + // We have to set the canvas' matrix on the base layer + // (to have fixed layers work as intended) + SkAutoCanvasRestore restore(canvas, true); + m_baseLayer->setMatrix(canvas->getTotalMatrix()); + canvas->resetMatrix(); + m_baseLayer->draw(canvas); +#endif + return ret; +} + + +bool cursorIsTextInput(FrameCachePermission allowNewer) +{ + CachedRoot* root = getFrameCache(allowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* cursor = root->currentCursor(); + if (!cursor) { + DBG_NAV_LOG("!cursor"); + return false; + } + DBG_NAV_LOGD("%s", cursor->isTextInput() ? "true" : "false"); + return cursor->isTextInput(); +} + +void cursorRingBounds(WebCore::IntRect* bounds) +{ + DBG_NAV_LOGD("%s", ""); + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedFrame* cachedFrame; + const CachedNode* cachedNode = root->currentCursor(&cachedFrame); + if (cachedNode) { + *bounds = cachedNode->cursorRingBounds(cachedFrame); + DBG_NAV_LOGD("bounds={%d,%d,%d,%d}", bounds->x(), bounds->y(), + bounds->width(), bounds->height()); + return; + } + } + *bounds = WebCore::IntRect(0, 0, 0, 0); +} + +void fixCursor() +{ + m_viewImpl->gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_viewImpl->m_hasCursorBounds; + IntRect bounds = m_viewImpl->m_cursorBounds; + m_viewImpl->gCursorBoundsMutex.unlock(); + if (!hasCursorBounds) + return; + int x, y; + const CachedFrame* frame; + const CachedNode* node = m_frameCacheUI->findAt(bounds, &frame, &x, &y, true); + if (!node) + return; + // require that node have approximately the same bounds (+/- 4) and the same + // center (+/- 2) + IntPoint oldCenter = IntPoint(bounds.x() + (bounds.width() >> 1), + bounds.y() + (bounds.height() >> 1)); + IntRect newBounds = node->bounds(frame); + IntPoint newCenter = IntPoint(newBounds.x() + (newBounds.width() >> 1), + newBounds.y() + (newBounds.height() >> 1)); + DBG_NAV_LOGD("oldCenter=(%d,%d) newCenter=(%d,%d)" + " bounds=(%d,%d,w=%d,h=%d) newBounds=(%d,%d,w=%d,h=%d)", + oldCenter.x(), oldCenter.y(), newCenter.x(), newCenter.y(), + bounds.x(), bounds.y(), bounds.width(), bounds.height(), + newBounds.x(), newBounds.y(), newBounds.width(), newBounds.height()); + if (abs(oldCenter.x() - newCenter.x()) > 2) + return; + if (abs(oldCenter.y() - newCenter.y()) > 2) + return; + if (abs(bounds.x() - newBounds.x()) > 4) + return; + if (abs(bounds.y() - newBounds.y()) > 4) + return; + if (abs(bounds.right() - newBounds.right()) > 4) + return; + if (abs(bounds.bottom() - newBounds.bottom()) > 4) + return; + DBG_NAV_LOGD("node=%p frame=%p x=%d y=%d bounds=(%d,%d,w=%d,h=%d)", + node, frame, x, y, bounds.x(), bounds.y(), bounds.width(), + bounds.height()); + m_frameCacheUI->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(node)); +} + +CachedRoot* getFrameCache(FrameCachePermission allowNewer) +{ + if (!m_viewImpl->m_updatedFrameCache) { + DBG_NAV_LOGV("%s", "!m_viewImpl->m_updatedFrameCache"); + return m_frameCacheUI; + } + if (allowNewer == DontAllowNewer && m_viewImpl->m_lastGeneration < m_generation) { + DBG_NAV_LOGD("allowNewer==DontAllowNewer m_viewImpl->m_lastGeneration=%d" + " < m_generation=%d", m_viewImpl->m_lastGeneration, m_generation); + return m_frameCacheUI; + } + DBG_NAV_LOGD("%s", "m_viewImpl->m_updatedFrameCache == true"); + const CachedFrame* oldCursorFrame; + const CachedNode* oldCursorNode = m_frameCacheUI ? + m_frameCacheUI->currentCursor(&oldCursorFrame) : 0; +#if USE(ACCELERATED_COMPOSITING) + int layerId = -1; + if (oldCursorNode && oldCursorNode->isInLayer()) { + const LayerAndroid* cursorLayer = oldCursorFrame->layer(oldCursorNode) + ->layer(m_frameCacheUI->rootLayer()); + if (cursorLayer) + layerId = cursorLayer->uniqueId(); + } +#endif + // get id from old layer and use to find new layer + bool oldFocusIsTextInput = false; + void* oldFocusNodePointer = 0; + if (m_frameCacheUI) { + const CachedNode* oldFocus = m_frameCacheUI->currentFocus(); + if (oldFocus) { + oldFocusIsTextInput = oldFocus->isTextInput(); + oldFocusNodePointer = oldFocus->nodePointer(); + } + } + m_viewImpl->gFrameCacheMutex.lock(); + delete m_frameCacheUI; + SkSafeUnref(m_navPictureUI); + m_viewImpl->m_updatedFrameCache = false; + m_frameCacheUI = m_viewImpl->m_frameCacheKit; + m_navPictureUI = m_viewImpl->m_navPictureKit; + m_viewImpl->m_frameCacheKit = 0; + m_viewImpl->m_navPictureKit = 0; + m_viewImpl->gFrameCacheMutex.unlock(); + if (m_frameCacheUI) + m_frameCacheUI->setRootLayer(compositeRoot()); +#if USE(ACCELERATED_COMPOSITING) + if (layerId >= 0) { + SkRect visible; + calcOurContentVisibleRect(&visible); + LayerAndroid* layer = const_cast<LayerAndroid*>( + m_frameCacheUI->rootLayer()); + if (layer) { + layer->updateFixedLayersPositions(visible); + layer->updatePositions(); + } + } +#endif + fixCursor(); + if (oldFocusIsTextInput) { + const CachedNode* newFocus = m_frameCacheUI->currentFocus(); + if (newFocus && oldFocusNodePointer != newFocus->nodePointer() + && newFocus->isTextInput() + && newFocus != m_frameCacheUI->currentCursor()) { + // The focus has changed. We may need to update things. + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_domChangedFocus); + checkException(env); + } + } + if (oldCursorNode && (!m_frameCacheUI || !m_frameCacheUI->currentCursor())) + viewInvalidate(); // redraw in case cursor ring is still visible + return m_frameCacheUI; +} + +int getScaledMaxXScroll() +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + int result = env->CallIntMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getScaledMaxXScroll); + checkException(env); + return result; +} + +int getScaledMaxYScroll() +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + int result = env->CallIntMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getScaledMaxYScroll); + checkException(env); + return result; +} + +IntRect getVisibleRect() +{ + IntRect rect; + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject jRect = env->CallObjectMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getVisibleRect); + checkException(env); + rect.setX(env->GetIntField(jRect, m_javaGlue.m_rectLeft)); + checkException(env); + rect.setY(env->GetIntField(jRect, m_javaGlue.m_rectTop)); + checkException(env); + rect.setWidth(env->CallIntMethod(jRect, m_javaGlue.m_rectWidth)); + checkException(env); + rect.setHeight(env->CallIntMethod(jRect, m_javaGlue.m_rectHeight)); + checkException(env); + env->DeleteLocalRef(jRect); + checkException(env); + return rect; +} + +static CachedFrame::Direction KeyToDirection(int32_t keyCode) +{ + switch (keyCode) { + case AKEYCODE_DPAD_RIGHT: + DBG_NAV_LOGD("keyCode=%s", "right"); + return CachedFrame::RIGHT; + case AKEYCODE_DPAD_LEFT: + DBG_NAV_LOGD("keyCode=%s", "left"); + return CachedFrame::LEFT; + case AKEYCODE_DPAD_DOWN: + DBG_NAV_LOGD("keyCode=%s", "down"); + return CachedFrame::DOWN; + case AKEYCODE_DPAD_UP: + DBG_NAV_LOGD("keyCode=%s", "up"); + return CachedFrame::UP; + default: + DBG_NAV_LOGD("bad key %d sent", keyCode); + return CachedFrame::UNINITIALIZED; + } +} + +WTF::String imageURI(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + return root ? root->imageURI(x, y) : WTF::String(); +} + +bool cursorWantsKeyEvents() +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedNode* focus = root->currentCursor(); + if (focus) + return focus->wantsKeyEvents(); + } + return false; +} + + +/* returns true if the key had no effect (neither scrolled nor changed cursor) */ +bool moveCursor(int keyCode, int count, bool ignoreScroll) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return true; + } + + m_viewImpl->m_moveGeneration++; + CachedFrame::Direction direction = KeyToDirection(keyCode); + const CachedFrame* cachedFrame, * oldFrame = 0; + const CachedNode* cursor = root->currentCursor(&oldFrame); + WebCore::IntPoint cursorLocation = root->cursorLocation(); + DBG_NAV_LOGD("old cursor %d (nativeNode=%p) cursorLocation={%d, %d}", + cursor ? cursor->index() : 0, + cursor ? cursor->nodePointer() : 0, cursorLocation.x(), cursorLocation.y()); + WebCore::IntRect visibleRect = setVisibleRect(root); + int xMax = getScaledMaxXScroll(); + int yMax = getScaledMaxYScroll(); + root->setMaxScroll(xMax, yMax); + const CachedNode* cachedNode = 0; + int dx = 0; + int dy = 0; + int counter = count; + while (--counter >= 0) { + WebCore::IntPoint scroll = WebCore::IntPoint(0, 0); + cachedNode = root->moveCursor(direction, &cachedFrame, &scroll); + dx += scroll.x(); + dy += scroll.y(); + } + DBG_NAV_LOGD("new cursor %d (nativeNode=%p) cursorLocation={%d, %d}" + "bounds={%d,%d,w=%d,h=%d}", cachedNode ? cachedNode->index() : 0, + cachedNode ? cachedNode->nodePointer() : 0, + root->cursorLocation().x(), root->cursorLocation().y(), + cachedNode ? cachedNode->bounds(cachedFrame).x() : 0, + cachedNode ? cachedNode->bounds(cachedFrame).y() : 0, + cachedNode ? cachedNode->bounds(cachedFrame).width() : 0, + cachedNode ? cachedNode->bounds(cachedFrame).height() : 0); + // If !m_heightCanMeasure (such as in the browser), we want to scroll no + // matter what + if (!ignoreScroll && (!m_heightCanMeasure || + !cachedNode || + (cursor && cursor->nodePointer() == cachedNode->nodePointer()))) + { + if (count == 1 && dx != 0 && dy == 0 && -m_lastDx == dx && + SkTime::GetMSecs() - m_lastDxTime < 1000) + root->checkForJiggle(&dx); + DBG_NAV_LOGD("scrollBy %d,%d", dx, dy); + if ((dx | dy)) + this->scrollBy(dx, dy); + m_lastDx = dx; + m_lastDxTime = SkTime::GetMSecs(); + } + bool result = false; + if (cachedNode) { + showCursorUntimed(); + m_viewImpl->updateCursorBounds(root, cachedFrame, cachedNode); + root->setCursor(const_cast<CachedFrame*>(cachedFrame), + const_cast<CachedNode*>(cachedNode)); + const CachedNode* focus = root->currentFocus(); + bool clearTextEntry = cachedNode != focus && focus + && cachedNode->nodePointer() != focus->nodePointer() && focus->isTextInput(); + // Stop painting the caret if the old focus was a text input and so is the new cursor. + bool stopPaintingCaret = clearTextEntry && cachedNode->wantsKeyEvents(); + sendMoveMouseIfLatest(clearTextEntry, stopPaintingCaret); + } else { + int docHeight = root->documentHeight(); + int docWidth = root->documentWidth(); + if (visibleRect.bottom() + dy > docHeight) + dy = docHeight - visibleRect.bottom(); + else if (visibleRect.y() + dy < 0) + dy = -visibleRect.y(); + if (visibleRect.right() + dx > docWidth) + dx = docWidth - visibleRect.right(); + else if (visibleRect.x() < 0) + dx = -visibleRect.x(); + result = direction == CachedFrame::LEFT ? dx >= 0 : + direction == CachedFrame::RIGHT ? dx <= 0 : + direction == CachedFrame::UP ? dy >= 0 : dy <= 0; + } + return result; +} + +void notifyProgressFinished() +{ + DBG_NAV_LOGD("cursorIsTextInput=%d", cursorIsTextInput(DontAllowNewer)); + rebuildWebTextView(); +#if DEBUG_NAV_UI + if (m_frameCacheUI) { + const CachedNode* focus = m_frameCacheUI->currentFocus(); + DBG_NAV_LOGD("focus %d (nativeNode=%p)", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : 0); + } +#endif +} + +const CachedNode* findAt(CachedRoot* root, const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* rxPtr, int* ryPtr) +{ + *rxPtr = 0; + *ryPtr = 0; + *framePtr = 0; + if (!root) + return 0; + setVisibleRect(root); + return root->findAt(rect, framePtr, rxPtr, ryPtr, true); +} + +IntRect setVisibleRect(CachedRoot* root) +{ + IntRect visibleRect = getVisibleRect(); + DBG_NAV_LOGD("getVisibleRect %d,%d,%d,%d", + visibleRect.x(), visibleRect.y(), visibleRect.width(), visibleRect.height()); + root->setVisibleRect(visibleRect); + return visibleRect; +} + +void selectBestAt(const WebCore::IntRect& rect) +{ + const CachedFrame* frame; + int rx, ry; + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + const CachedNode* node = findAt(root, rect, &frame, &rx, &ry); + if (!node) { + DBG_NAV_LOGD("no nodes found root=%p", root); + root->rootHistory()->setMouseBounds(rect); + m_viewImpl->m_hasCursorBounds = false; + root->setCursor(0, 0); + viewInvalidate(); + } else { + DBG_NAV_LOGD("CachedNode:%p (%d)", node, node->index()); + WebCore::IntRect bounds = node->bounds(frame); + root->rootHistory()->setMouseBounds(bounds); + m_viewImpl->updateCursorBounds(root, frame, node); + showCursorTimed(); + root->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(node)); + } + sendMoveMouseIfLatest(false, false); +} + +const CachedNode* m_cacheHitNode; +const CachedFrame* m_cacheHitFrame; + +bool pointInNavCache(int x, int y, int slop) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return false; + IntRect rect = IntRect(x - slop, y - slop, slop * 2, slop * 2); + int rx, ry; + return (m_cacheHitNode = findAt(root, rect, &m_cacheHitFrame, &rx, &ry)); +} + +bool motionUp(int x, int y, int slop) +{ + bool pageScrolled = false; + IntRect rect = IntRect(x - slop, y - slop, slop * 2, slop * 2); + int rx, ry; + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return 0; + const CachedFrame* frame = 0; + const CachedNode* result = findAt(root, rect, &frame, &rx, &ry); + CachedHistory* history = root->rootHistory(); + if (!result) { + DBG_NAV_LOGD("no nodes found root=%p", root); + history->setNavBounds(rect); + m_viewImpl->m_hasCursorBounds = false; + root->hideCursor(); + int dx = root->checkForCenter(x, y); + if (dx) { + scrollBy(dx, 0); + pageScrolled = true; + } + sendMotionUp(frame ? (WebCore::Frame*) frame->framePointer() : 0, + 0, x, y); + viewInvalidate(); + return pageScrolled; + } + DBG_NAV_LOGD("CachedNode:%p (%d) x=%d y=%d rx=%d ry=%d", result, + result->index(), x, y, rx, ry); + WebCore::IntRect navBounds = WebCore::IntRect(rx, ry, 1, 1); + history->setNavBounds(navBounds); + history->setMouseBounds(navBounds); + m_viewImpl->updateCursorBounds(root, frame, result); + root->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(result)); + if (result->isSyntheticLink()) + overrideUrlLoading(result->getExport()); + else { + sendMotionUp( + (WebCore::Frame*) frame->framePointer(), + (WebCore::Node*) result->nodePointer(), rx, ry); + } + if (result->isTextInput() || result->isSelect() + || result->isContentEditable()) { + showCursorUntimed(); + } else + showCursorTimed(); + return pageScrolled; +} + +#if USE(ACCELERATED_COMPOSITING) +static const ScrollableLayerAndroid* findScrollableLayer( + const LayerAndroid* parent, int x, int y, SkIRect* foundBounds) { + SkRect bounds; + parent->bounds(&bounds); + // Check the parent bounds first; this will clip to within a masking layer's + // bounds. + if (parent->masksToBounds() && !bounds.contains(x, y)) + return 0; + // Move the hit test local to parent. + x -= bounds.fLeft; + y -= bounds.fTop; + int count = parent->countChildren(); + while (count--) { + const LayerAndroid* child = parent->getChild(count); + const ScrollableLayerAndroid* result = findScrollableLayer(child, x, y, + foundBounds); + if (result) { + foundBounds->offset(bounds.fLeft, bounds.fTop); + if (parent->masksToBounds()) { + if (bounds.width() < foundBounds->width()) + foundBounds->fRight = foundBounds->fLeft + bounds.width(); + if (bounds.height() < foundBounds->height()) + foundBounds->fBottom = foundBounds->fTop + bounds.height(); + } + return result; + } + } + if (parent->contentIsScrollable()) { + foundBounds->set(0, 0, bounds.width(), bounds.height()); + return static_cast<const ScrollableLayerAndroid*>(parent); + } + return 0; +} +#endif + +int scrollableLayer(int x, int y, SkIRect* layerRect, SkIRect* bounds) +{ +#if USE(ACCELERATED_COMPOSITING) + const LayerAndroid* layerRoot = compositeRoot(); + if (!layerRoot) + return 0; + const ScrollableLayerAndroid* result = findScrollableLayer(layerRoot, x, y, + bounds); + if (result) { + result->getScrollRect(layerRect); + return result->uniqueId(); + } +#endif + return 0; +} + +int getBlockLeftEdge(int x, int y, float scale) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (root) + return root->getBlockLeftEdge(x, y, scale); + return -1; +} + +void overrideUrlLoading(const WTF::String& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jName = wtfStringToJstring(env, url); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_overrideLoading, jName); + env->DeleteLocalRef(jName); +} + +void setFindIsUp(bool up) +{ + DBG_NAV_LOGD("up=%d", up); + m_viewImpl->m_findIsUp = up; +} + +void setFindIsEmpty() +{ + DBG_NAV_LOG(""); + m_findOnPage.clearCurrentLocation(); +} + +void showCursorTimed() +{ + DBG_NAV_LOG(""); + m_ringAnimationEnd = SkTime::GetMSecs() + 500; + viewInvalidate(); +} + +void showCursorUntimed() +{ + DBG_NAV_LOG(""); + m_ring.m_isPressed = false; + m_ringAnimationEnd = UINT_MAX; + viewInvalidate(); +} + +void setHeightCanMeasure(bool measure) +{ + m_heightCanMeasure = measure; +} + +String getSelection() +{ + return m_selectText.getSelection(); +} + +void moveSelection(int x, int y) +{ + m_selectText.moveSelection(getVisibleRect(), x, y); +} + +IntPoint selectableText() +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return IntPoint(0, 0); + return m_selectText.selectableText(root); +} + +void selectAll() +{ + m_selectText.selectAll(); +} + +int selectionX() +{ + return m_selectText.selectionX(); +} + +int selectionY() +{ + return m_selectText.selectionY(); +} + +void resetSelection() +{ + m_selectText.reset(); +} + +bool startSelection(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return false; + return m_selectText.startSelection(root, getVisibleRect(), x, y); +} + +bool wordSelection(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return false; + return m_selectText.wordSelection(root, getVisibleRect(), x, y); +} + +bool extendSelection(int x, int y) +{ + m_selectText.extendSelection(getVisibleRect(), x, y); + return true; +} + +bool hitSelection(int x, int y) +{ + return m_selectText.hitSelection(x, y); +} + +void setExtendSelection() +{ + m_selectText.setExtendSelection(true); +} + +void setSelectionPointer(bool set, float scale, int x, int y) +{ + m_selectText.setDrawPointer(set); + if (!set) + return; + m_selectText.m_inverseScale = scale; + m_selectText.m_selectX = x; + m_selectText.m_selectY = y; +} + +void sendMoveFocus(WebCore::Frame* framePtr, WebCore::Node* nodePtr) +{ + DBG_NAV_LOGD("framePtr=%p nodePtr=%p", framePtr, nodePtr); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_sendMoveFocus, (jint) framePtr, (jint) nodePtr); + checkException(env); +} + +void sendMoveMouse(WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y) +{ + DBG_NAV_LOGD("framePtr=%p nodePtr=%p x=%d y=%d", framePtr, nodePtr, x, y); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendMoveMouse, + (jint) framePtr, (jint) nodePtr, x, y); + checkException(env); +} + +void sendMoveMouseIfLatest(bool clearTextEntry, bool stopPaintingCaret) +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_sendMoveMouseIfLatest, clearTextEntry, stopPaintingCaret); + checkException(env); +} + +void sendMotionUp( + WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y) +{ + m_viewImpl->m_touchGeneration = ++m_generation; + DBG_NAV_LOGD("m_generation=%d framePtr=%p nodePtr=%p x=%d y=%d", + m_generation, framePtr, nodePtr, x, y); + LOG_ASSERT(m_javaGlue.m_obj, "A WebView was not associated with this WebViewNative!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendMotionUp, + m_generation, (jint) framePtr, (jint) nodePtr, x, y); + checkException(env); +} + +void findNext(bool forward) +{ + m_findOnPage.findNext(forward); + if (!m_findOnPage.currentMatchIsInLayer()) + scrollRectOnScreen(m_findOnPage.currentMatchBounds()); + viewInvalidate(); +} + +// With this call, WebView takes ownership of matches, and is responsible for +// deleting it. +void setMatches(WTF::Vector<MatchInfo>* matches, jboolean sameAsLastSearch) +{ + // If this search is the same as the last one, check against the old + // location to determine whether to scroll. If the same word is found + // in the same place, then do not scroll. + IntRect oldLocation; + bool checkAgainstOldLocation; + if (sameAsLastSearch && m_findOnPage.isCurrentLocationValid()) { + oldLocation = m_findOnPage.currentMatchBounds(); + checkAgainstOldLocation = true; + } else + checkAgainstOldLocation = false; + + m_findOnPage.setMatches(matches); + + if (!checkAgainstOldLocation + || oldLocation != m_findOnPage.currentMatchBounds()) { + // FIXME: Need to scroll if the match is in a layer. + if (!m_findOnPage.currentMatchIsInLayer()) + scrollRectOnScreen(m_findOnPage.currentMatchBounds()); + } + viewInvalidate(); +} + +int currentMatchIndex() +{ + return m_findOnPage.currentMatchIndex(); +} + +bool scrollBy(int dx, int dy) +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + bool result = env->CallBooleanMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_scrollBy, dx, dy, true); + checkException(env); + return result; +} + +bool hasCursorNode() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* cursorNode = root->currentCursor(); + DBG_NAV_LOGD("cursorNode=%d (nodePointer=%p)", + cursorNode ? cursorNode->index() : -1, + cursorNode ? cursorNode->nodePointer() : 0); + return cursorNode; +} + +bool hasFocusNode() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* focusNode = root->currentFocus(); + DBG_NAV_LOGD("focusNode=%d (nodePointer=%p)", + focusNode ? focusNode->index() : -1, + focusNode ? focusNode->nodePointer() : 0); + return focusNode; +} + +void rebuildWebTextView() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_rebuildWebTextView); + checkException(env); +} + +void viewInvalidate() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_viewInvalidate); + checkException(env); +} + +void viewInvalidateRect(int l, int t, int r, int b) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_viewInvalidateRect, l, r, t, b); + checkException(env); +} + +void postInvalidateDelayed(int64_t delay, const WebCore::IntRect& bounds) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_postInvalidateDelayed, + delay, bounds.x(), bounds.y(), bounds.right(), bounds.bottom()); + checkException(env); +} + +bool inFullScreenMode() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jboolean result = env->CallBooleanMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_inFullScreenMode); + checkException(env); + return result; +} + +int moveGeneration() +{ + return m_viewImpl->m_moveGeneration; +} + +LayerAndroid* compositeRoot() const +{ + LOG_ASSERT(!m_baseLayer || m_baseLayer->countChildren() == 1, + "base layer can't have more than one child %s", __FUNCTION__); + if (m_baseLayer && m_baseLayer->countChildren() == 1) + return static_cast<LayerAndroid*>(m_baseLayer->getChild(0)); + else + return 0; +} + +#if ENABLE(ANDROID_OVERFLOW_SCROLL) +static void copyScrollPositionRecursive(const LayerAndroid* from, + LayerAndroid* root) +{ + if (!from || !root) + return; + for (int i = 0; i < from->countChildren(); i++) { + const LayerAndroid* l = from->getChild(i); + if (l->contentIsScrollable()) { + const SkPoint& pos = l->getPosition(); + LayerAndroid* match = root->findById(l->uniqueId()); + if (match && match->contentIsScrollable()) + match->setPosition(pos.fX, pos.fY); + } + copyScrollPositionRecursive(l, root); + } +} +#endif + +void setBaseLayer(BaseLayerAndroid* layer, SkRegion& inval, bool showVisualIndicator, + bool isPictureAfterFirstLayout) +{ +#if USE(ACCELERATED_COMPOSITING) + if (m_glWebViewState) + m_glWebViewState->setBaseLayer(layer, inval, showVisualIndicator, + isPictureAfterFirstLayout); +#endif + +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + if (layer) { + LayerAndroid* newCompositeRoot = static_cast<LayerAndroid*>(layer->getChild(0)); + copyScrollPositionRecursive(compositeRoot(), newCompositeRoot); + } +#endif + SkSafeUnref(m_baseLayer); + m_baseLayer = layer; + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + root->resetLayers(); + root->setRootLayer(compositeRoot()); +} + +void replaceBaseContent(PictureSet* set) +{ + if (!m_baseLayer) + return; + m_baseLayer->setContent(*set); + delete set; +} + +void copyBaseContentToPicture(SkPicture* picture) +{ + if (!m_baseLayer) + return; + PictureSet* content = m_baseLayer->content(); + m_baseLayer->drawCanvas(picture->beginRecording(content->width(), content->height(), + SkPicture::kUsePathBoundsForClip_RecordingFlag)); + picture->endRecording(); +} + +bool hasContent() { + if (!m_baseLayer) + return false; + return !m_baseLayer->content()->isEmpty(); +} + +void setFunctor(Functor* functor) { + delete m_glDrawFunctor; + m_glDrawFunctor = functor; +} + +Functor* getFunctor() { + return m_glDrawFunctor; +} + +private: // local state for WebView + // private to getFrameCache(); other functions operate in a different thread + CachedRoot* m_frameCacheUI; // navigation data ready for use + WebViewCore* m_viewImpl; + int m_generation; // associate unique ID with sent kit focus to match with ui + SkPicture* m_navPictureUI; + SkMSec m_ringAnimationEnd; + // Corresponds to the same-named boolean on the java side. + bool m_heightCanMeasure; + int m_lastDx; + SkMSec m_lastDxTime; + SelectText m_selectText; + FindOnPage m_findOnPage; + CursorRing m_ring; + BaseLayerAndroid* m_baseLayer; + Functor* m_glDrawFunctor; +#if USE(ACCELERATED_COMPOSITING) + GLWebViewState* m_glWebViewState; +#endif + const RenderSkinButton* m_buttonSkin; +}; // end of WebView class + + +/** + * This class holds a function pointer and parameters for calling drawGL into a specific + * viewport. The pointer to the Functor will be put on a framework display list to be called + * when the display list is replayed. + */ +class GLDrawFunctor : Functor { + public: + GLDrawFunctor(WebView* _wvInstance, + bool(WebView::*_funcPtr)(WebCore::IntRect&, WebCore::IntRect*, WebCore::IntRect&, int, WebCore::IntRect&, jfloat, jint), + WebCore::IntRect _viewRect, float _scale, int _extras) { + wvInstance = _wvInstance; + funcPtr = _funcPtr; + viewRect = _viewRect; + scale = _scale; + extras = _extras; + }; + status_t operator()(int messageId, void* data) { + if (viewRect.isEmpty()) { + // NOOP operation if viewport is empty + return 0; + } + + WebCore::IntRect inval; + int titlebarHeight = webViewRect.height() - viewRect.height(); + + uirenderer::DrawGlInfo* info = reinterpret_cast<uirenderer::DrawGlInfo*>(data); + WebCore::IntRect localViewRect = viewRect; + if (info->isLayer) + localViewRect.move(-1 * localViewRect.x(), -1 * localViewRect.y()); + + WebCore::IntRect clip(info->clipLeft, info->clipTop, + info->clipRight - info->clipLeft, + info->clipBottom - info->clipTop); + + bool retVal = (*wvInstance.*funcPtr)(localViewRect, &inval, webViewRect, titlebarHeight, clip, scale, extras); + if (retVal) { + IntRect finalInval; + if (inval.isEmpty()) { + finalInval = webViewRect; + retVal = true; + } else { + finalInval.setX(webViewRect.x() + inval.x()); + finalInval.setY(webViewRect.y() + titlebarHeight + inval.y()); + finalInval.setWidth(inval.width()); + finalInval.setHeight(inval.height()); + } + info->dirtyLeft = finalInval.x(); + info->dirtyTop = finalInval.y(); + info->dirtyRight = finalInval.right(); + info->dirtyBottom = finalInval.bottom(); + } + // return 1 if invalidation needed, 0 otherwise + return retVal ? 1 : 0; + } + void updateRect(WebCore::IntRect& _viewRect) { + viewRect = _viewRect; + } + void updateViewRect(WebCore::IntRect& _viewRect) { + webViewRect = _viewRect; + } + private: + WebView* wvInstance; + bool (WebView::*funcPtr)(WebCore::IntRect&, WebCore::IntRect*, WebCore::IntRect&, int, WebCore::IntRect&, float, int); + WebCore::IntRect viewRect; + WebCore::IntRect webViewRect; + jfloat scale; + jint extras; +}; + +/* + * Native JNI methods + */ +static int nativeCacheHitFramePointer(JNIEnv *env, jobject obj) +{ + return reinterpret_cast<int>(GET_NATIVE_VIEW(env, obj) + ->m_cacheHitFrame->framePointer()); +} + +static jobject nativeCacheHitNodeBounds(JNIEnv *env, jobject obj) +{ + WebCore::IntRect bounds = GET_NATIVE_VIEW(env, obj) + ->m_cacheHitNode->originalAbsoluteBounds(); + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, bounds.x(), + bounds.y(), bounds.right(), bounds.bottom()); + env->DeleteLocalRef(rectClass); + return rect; +} + +static int nativeCacheHitNodePointer(JNIEnv *env, jobject obj) +{ + return reinterpret_cast<int>(GET_NATIVE_VIEW(env, obj) + ->m_cacheHitNode->nodePointer()); +} + +static bool nativeCacheHitIsPlugin(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->m_cacheHitNode->isPlugin(); +} + +static void nativeClearCursor(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->clearCursor(); +} + +static void nativeCreate(JNIEnv *env, jobject obj, int viewImpl, jstring drawableDir, + jobject jAssetManager) +{ + AssetManager* am = assetManagerForJavaObject(env, jAssetManager); + WTF::String dir = jstringToWtfString(env, drawableDir); + WebView* webview = new WebView(env, obj, viewImpl, dir, am); + // NEED THIS OR SOMETHING LIKE IT! + //Release(obj); +} + +static jint nativeCursorFramePointer(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedFrame* frame = 0; + (void) root->currentCursor(&frame); + return reinterpret_cast<int>(frame ? frame->framePointer() : 0); +} + +static const CachedNode* getCursorNode(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentCursor() : 0; +} + +static const CachedNode* getCursorNode(JNIEnv *env, jobject obj, + const CachedFrame** frame) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentCursor(frame) : 0; +} + +static const CachedNode* getFocusCandidate(JNIEnv *env, jobject obj, + const CachedFrame** frame) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedNode* cursor = root->currentCursor(frame); + if (cursor && cursor->wantsKeyEvents()) + return cursor; + return root->currentFocus(frame); +} + +static bool focusCandidateHasNextTextfield(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return false; + const CachedNode* cursor = root->currentCursor(); + if (!cursor || !cursor->isTextInput()) + cursor = root->currentFocus(); + if (!cursor || !cursor->isTextInput()) return false; + return root->nextTextField(cursor, 0); +} + +static const CachedNode* getFocusNode(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentFocus() : 0; +} + +static const CachedNode* getFocusNode(JNIEnv *env, jobject obj, + const CachedFrame** frame) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentFocus(frame) : 0; +} + +static const CachedInput* getInputCandidate(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedFrame* frame; + const CachedNode* cursor = root->currentCursor(&frame); + if (!cursor || !cursor->wantsKeyEvents()) + cursor = root->currentFocus(&frame); + return cursor ? frame->textInput(cursor) : 0; +} + +static jboolean nativePageShouldHandleShiftAndArrows(JNIEnv *env, jobject obj) +{ + const CachedNode* focus = getFocusNode(env, obj); + if (!focus) return false; + // Plugins handle shift and arrows whether or not they have focus. + if (focus->isPlugin()) return true; + const CachedNode* cursor = getCursorNode(env, obj); + // ContentEditable nodes should only receive shift and arrows if they have + // both the cursor and the focus. + return cursor && cursor->nodePointer() == focus->nodePointer() + && cursor->isContentEditable(); +} + +static jobject nativeCursorNodeBounds(JNIEnv *env, jobject obj) +{ + const CachedFrame* frame; + const CachedNode* node = getCursorNode(env, obj, &frame); + WebCore::IntRect bounds = node ? node->bounds(frame) + : WebCore::IntRect(0, 0, 0, 0); + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, bounds.x(), + bounds.y(), bounds.right(), bounds.bottom()); + env->DeleteLocalRef(rectClass); + return rect; +} + +static jint nativeCursorNodePointer(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + return reinterpret_cast<int>(node ? node->nodePointer() : 0); +} + +static jobject nativeCursorPosition(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + const CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + WebCore::IntPoint pos = WebCore::IntPoint(0, 0); + if (root) + root->getSimulatedMousePosition(&pos); + jclass pointClass = env->FindClass("android/graphics/Point"); + jmethodID init = env->GetMethodID(pointClass, "<init>", "(II)V"); + jobject point = env->NewObject(pointClass, init, pos.x(), pos.y()); + env->DeleteLocalRef(pointClass); + return point; +} + +static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj) +{ + int L, T, R, B; + GraphicsJNI::get_jrect(env, obj, &L, &T, &R, &B); + return WebCore::IntRect(L, T, R - L, B - T); +} + +static bool nativeCursorIntersects(JNIEnv *env, jobject obj, jobject visRect) +{ + const CachedFrame* frame; + const CachedNode* node = getCursorNode(env, obj, &frame); + return node ? node->bounds(frame).intersects( + jrect_to_webrect(env, visRect)) : false; +} + +static bool nativeCursorIsAnchor(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + return node ? node->isAnchor() : false; +} + +static bool nativeCursorIsTextInput(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + return node ? node->isTextInput() : false; +} + +static jobject nativeCursorText(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + if (!node) + return 0; + WTF::String value = node->getExport(); + return wtfStringToJstring(env, value); +} + +static void nativeDebugDump(JNIEnv *env, jobject obj) +{ +#if DUMP_NAV_CACHE + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->debugDump(); +#endif +} + +static jint nativeDraw(JNIEnv *env, jobject obj, jobject canv, jint color, + jint extras, jboolean split) { + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + return reinterpret_cast<jint>(GET_NATIVE_VIEW(env, obj)->draw(canvas, color, extras, split)); +} + +static jint nativeGetDrawGLFunction(JNIEnv *env, jobject obj, jobject jrect, jobject jviewrect, + jfloat scale, jint extras) { + WebCore::IntRect viewRect; + if (jrect == NULL) { + viewRect = WebCore::IntRect(); + } else { + viewRect = jrect_to_webrect(env, jrect); + } + WebView *wvInstance = GET_NATIVE_VIEW(env, obj); + GLDrawFunctor* functor = new GLDrawFunctor(wvInstance, &android::WebView::drawGL, + viewRect, scale, extras); + wvInstance->setFunctor((Functor*) functor); + + WebCore::IntRect webViewRect; + if (jviewrect == NULL) { + webViewRect = WebCore::IntRect(); + } else { + webViewRect = jrect_to_webrect(env, jviewrect); + } + functor->updateViewRect(webViewRect); + + return (jint)functor; +} + +static void nativeUpdateDrawGLFunction(JNIEnv *env, jobject obj, jobject jrect, jobject jviewrect) { + WebView *wvInstance = GET_NATIVE_VIEW(env, obj); + if (wvInstance != NULL) { + GLDrawFunctor* functor = (GLDrawFunctor*) wvInstance->getFunctor(); + if (functor != NULL) { + WebCore::IntRect viewRect; + if (jrect == NULL) { + viewRect = WebCore::IntRect(); + } else { + viewRect = jrect_to_webrect(env, jrect); + } + functor->updateRect(viewRect); + + WebCore::IntRect webViewRect; + if (jviewrect == NULL) { + webViewRect = WebCore::IntRect(); + } else { + webViewRect = jrect_to_webrect(env, jviewrect); + } + functor->updateViewRect(webViewRect); + } + } +} + +static bool nativeEvaluateLayersAnimations(JNIEnv *env, jobject obj) +{ +#if USE(ACCELERATED_COMPOSITING) + LayerAndroid* root = GET_NATIVE_VIEW(env, obj)->compositeRoot(); + if (root) + return root->evaluateAnimations(); +#endif + return false; +} + +static void nativeSetBaseLayer(JNIEnv *env, jobject obj, jint layer, jobject inval, + jboolean showVisualIndicator, + jboolean isPictureAfterFirstLayout) +{ + BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(layer); + SkRegion invalRegion; + if (inval) + invalRegion = *GraphicsJNI::getNativeRegion(env, inval); + GET_NATIVE_VIEW(env, obj)->setBaseLayer(layerImpl, invalRegion, showVisualIndicator, + isPictureAfterFirstLayout); +} + +static void nativeReplaceBaseContent(JNIEnv *env, jobject obj, jint content) +{ + PictureSet* set = reinterpret_cast<PictureSet*>(content); + GET_NATIVE_VIEW(env, obj)->replaceBaseContent(set); +} + +static void nativeCopyBaseContentToPicture(JNIEnv *env, jobject obj, jobject pict) +{ + SkPicture* picture = GraphicsJNI::getNativePicture(env, pict); + GET_NATIVE_VIEW(env, obj)->copyBaseContentToPicture(picture); +} + +static bool nativeHasContent(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->hasContent(); +} + +static jobject nativeImageURI(JNIEnv *env, jobject obj, jint x, jint y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WTF::String uri = view->imageURI(x, y); + return wtfStringToJstring(env, uri); +} + +static jint nativeFocusCandidateFramePointer(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedFrame* frame = 0; + const CachedNode* cursor = root->currentCursor(&frame); + if (!cursor || !cursor->wantsKeyEvents()) + (void) root->currentFocus(&frame); + return reinterpret_cast<int>(frame ? frame->framePointer() : 0); +} + +static bool nativeFocusCandidateIsPassword(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input && input->getType() == CachedInput::PASSWORD; +} + +static bool nativeFocusCandidateIsRtlText(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->isRtlText() : false; +} + +static bool nativeFocusCandidateIsTextInput(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusCandidate(env, obj, 0); + return node ? node->isTextInput() : false; +} + +static jint nativeFocusCandidateMaxLength(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->maxLength() : false; +} + +static jint nativeFocusCandidateIsAutoComplete(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->autoComplete() : false; +} + +static jobject nativeFocusCandidateName(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + if (!input) + return 0; + const WTF::String& name = input->name(); + return wtfStringToJstring(env, name); +} + +static jobject createJavaRect(JNIEnv* env, int x, int y, int right, int bottom) +{ + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, x, y, right, bottom); + env->DeleteLocalRef(rectClass); + return rect; +} + +static jobject nativeFocusCandidateNodeBounds(JNIEnv *env, jobject obj) +{ + const CachedFrame* frame; + const CachedNode* node = getFocusCandidate(env, obj, &frame); + WebCore::IntRect bounds = node ? node->bounds(frame) + : WebCore::IntRect(0, 0, 0, 0); + return createJavaRect(env, bounds.x(), bounds.y(), bounds.right(), bounds.bottom()); +} + +static jobject nativeFocusCandidatePaddingRect(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + if (!input) + return 0; + // Note that the Java Rect is being used to pass four integers, rather than + // being used as an actual rectangle. + return createJavaRect(env, input->paddingLeft(), input->paddingTop(), + input->paddingRight(), input->paddingBottom()); +} + +static jint nativeFocusCandidatePointer(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusCandidate(env, obj, 0); + return reinterpret_cast<int>(node ? node->nodePointer() : 0); +} + +static jobject nativeFocusCandidateText(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusCandidate(env, obj, 0); + if (!node) + return 0; + WTF::String value = node->getExport(); + return wtfStringToJstring(env, value); +} + +static int nativeFocusCandidateLineHeight(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->lineHeight() : 0; +} + +static jfloat nativeFocusCandidateTextSize(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->textSize() : 0.f; +} + +static int nativeFocusCandidateType(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + if (!input) + return CachedInput::NONE; + + if (input->isTextArea()) + return CachedInput::TEXT_AREA; + + return input->getType(); +} + +static bool nativeFocusIsPlugin(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusNode(env, obj); + return node ? node->isPlugin() : false; +} + +static jobject nativeFocusNodeBounds(JNIEnv *env, jobject obj) +{ + const CachedFrame* frame; + const CachedNode* node = getFocusNode(env, obj, &frame); + WebCore::IntRect bounds = node ? node->bounds(frame) + : WebCore::IntRect(0, 0, 0, 0); + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, bounds.x(), + bounds.y(), bounds.right(), bounds.bottom()); + env->DeleteLocalRef(rectClass); + return rect; +} + +static jint nativeFocusNodePointer(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusNode(env, obj); + return node ? reinterpret_cast<int>(node->nodePointer()) : 0; +} + +static bool nativeCursorWantsKeyEvents(JNIEnv* env, jobject jwebview) { + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->cursorWantsKeyEvents(); +} + +static void nativeHideCursor(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->hideCursor(); +} + +static void nativeInstrumentReport(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounter::reportNow(); +#endif +} + +static void nativeSelectBestAt(JNIEnv *env, jobject obj, jobject jrect) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::IntRect rect = jrect_to_webrect(env, jrect); + view->selectBestAt(rect); +} + +static void nativeSelectAt(JNIEnv *env, jobject obj, jint x, jint y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::IntRect rect = IntRect(x, y , 1, 1); + view->selectBestAt(rect); + if (view->hasCursorNode()) + view->showCursorUntimed(); +} + +static jobject nativeLayerBounds(JNIEnv* env, jobject obj, jint jlayer) +{ + SkRect r; +#if USE(ACCELERATED_COMPOSITING) + LayerAndroid* layer = (LayerAndroid*) jlayer; + r = layer->bounds(); +#else + r.setEmpty(); +#endif + SkIRect irect; + r.round(&irect); + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, irect.fLeft, irect.fTop, + irect.fRight, irect.fBottom); + env->DeleteLocalRef(rectClass); + return rect; +} + +static jobject nativeSubtractLayers(JNIEnv* env, jobject obj, jobject jrect) +{ + SkIRect irect = jrect_to_webrect(env, jrect); +#if USE(ACCELERATED_COMPOSITING) + LayerAndroid* root = GET_NATIVE_VIEW(env, obj)->compositeRoot(); + if (root) { + SkRect rect; + rect.set(irect); + rect = root->subtractLayers(rect); + rect.round(&irect); + } +#endif + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, irect.fLeft, irect.fTop, + irect.fRight, irect.fBottom); + env->DeleteLocalRef(rectClass); + return rect; +} + +static jint nativeTextGeneration(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->textGeneration() : 0; +} + +static bool nativePointInNavCache(JNIEnv *env, jobject obj, + int x, int y, int slop) +{ + return GET_NATIVE_VIEW(env, obj)->pointInNavCache(x, y, slop); +} + +static bool nativeMotionUp(JNIEnv *env, jobject obj, + int x, int y, int slop) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->motionUp(x, y, slop); +} + +static bool nativeHasCursorNode(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->hasCursorNode(); +} + +static bool nativeHasFocusNode(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->hasFocusNode(); +} + +static bool nativeMoveCursor(JNIEnv *env, jobject obj, + int key, int count, bool ignoreScroll) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + DBG_NAV_LOGD("env=%p obj=%p view=%p", env, obj, view); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->moveCursor(key, count, ignoreScroll); +} + +static void nativeRecordButtons(JNIEnv* env, jobject obj, bool hasFocus, + bool pressed, bool invalidate) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->nativeRecordButtons(hasFocus, pressed, invalidate); +} + +static void nativeSetFindIsUp(JNIEnv *env, jobject obj, jboolean isUp) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->setFindIsUp(isUp); +} + +static void nativeSetFindIsEmpty(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setFindIsEmpty(); +} + +static void nativeShowCursorTimed(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->showCursorTimed(); +} + +static void nativeSetHeightCanMeasure(JNIEnv *env, jobject obj, bool measure) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeSetHeightCanMeasure"); + view->setHeightCanMeasure(measure); +} + +static jobject nativeGetCursorRingBounds(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find Rect class!"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + LOG_ASSERT(init, "Could not find constructor for Rect"); + WebCore::IntRect webRect; + view->cursorRingBounds(&webRect); + jobject rect = env->NewObject(rectClass, init, webRect.x(), + webRect.y(), webRect.right(), webRect.bottom()); + env->DeleteLocalRef(rectClass); + return rect; +} + +static int nativeFindAll(JNIEnv *env, jobject obj, jstring findLower, + jstring findUpper, jboolean sameAsLastSearch) +{ + // If one or the other is null, do not search. + if (!(findLower && findUpper)) + return 0; + // Obtain the characters for both the lower case string and the upper case + // string representing the same word. + const jchar* findLowerChars = env->GetStringChars(findLower, 0); + const jchar* findUpperChars = env->GetStringChars(findUpper, 0); + // If one or the other is null, do not search. + if (!(findLowerChars && findUpperChars)) { + if (findLowerChars) + env->ReleaseStringChars(findLower, findLowerChars); + if (findUpperChars) + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindAll"); + CachedRoot* root = view->getFrameCache(WebView::AllowNewer); + if (!root) { + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + int length = env->GetStringLength(findLower); + // If the lengths of the strings do not match, then they are not the same + // word, so do not search. + if (!length || env->GetStringLength(findUpper) != length) { + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + int width = root->documentWidth(); + int height = root->documentHeight(); + // Create a FindCanvas, which allows us to fake draw into it so we can + // figure out where our search string is rendered (and how many times). + FindCanvas canvas(width, height, (const UChar*) findLowerChars, + (const UChar*) findUpperChars, length << 1); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + canvas.setBitmapDevice(bitmap); + root->draw(canvas); + WTF::Vector<MatchInfo>* matches = canvas.detachMatches(); + // With setMatches, the WebView takes ownership of matches + view->setMatches(matches, sameAsLastSearch); + + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return canvas.found(); +} + +static void nativeFindNext(JNIEnv *env, jobject obj, bool forward) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindNext"); + view->findNext(forward); +} + +static int nativeFindIndex(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindIndex"); + return view->currentMatchIndex(); +} + +static void nativeUpdateCachedTextfield(JNIEnv *env, jobject obj, jstring updatedText, jint generation) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeUpdateCachedTextfield"); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return; + const CachedNode* cachedFocusNode = root->currentFocus(); + if (!cachedFocusNode || !cachedFocusNode->isTextInput()) + return; + WTF::String webcoreString = jstringToWtfString(env, updatedText); + (const_cast<CachedNode*>(cachedFocusNode))->setExport(webcoreString); + root->setTextGeneration(generation); + checkException(env); +} + +static jint nativeGetBlockLeftEdge(JNIEnv *env, jobject obj, jint x, jint y, + jfloat scale) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + if (!view) + return -1; + return view->getBlockLeftEdge(x, y, scale); +} + +static void nativeDestroy(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOGD("nativeDestroy view: %p", view); + LOG_ASSERT(view, "view not set in nativeDestroy"); + delete view; +} + +static void nativeStopGL(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->stopGL(); +} + +static bool nativeMoveCursorToNextTextInput(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return false; + const CachedNode* current = root->currentCursor(); + if (!current || !current->isTextInput()) + current = root->currentFocus(); + if (!current || !current->isTextInput()) + return false; + const CachedFrame* frame; + const CachedNode* next = root->nextTextField(current, &frame); + if (!next) + return false; + const WebCore::IntRect& bounds = next->bounds(frame); + root->rootHistory()->setMouseBounds(bounds); + view->getWebViewCore()->updateCursorBounds(root, frame, next); + view->showCursorUntimed(); + root->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(next)); + view->sendMoveFocus(static_cast<WebCore::Frame*>(frame->framePointer()), + static_cast<WebCore::Node*>(next->nodePointer())); + if (!next->isInLayer()) + view->scrollRectOnScreen(bounds); + view->getWebViewCore()->m_moveGeneration++; + return true; +} + +static int nativeMoveGeneration(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) + return 0; + return view->moveGeneration(); +} + +static void nativeMoveSelection(JNIEnv *env, jobject obj, int x, int y) +{ + GET_NATIVE_VIEW(env, obj)->moveSelection(x, y); +} + +static void nativeResetSelection(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->resetSelection(); +} + +static jobject nativeSelectableText(JNIEnv* env, jobject obj) +{ + IntPoint pos = GET_NATIVE_VIEW(env, obj)->selectableText(); + jclass pointClass = env->FindClass("android/graphics/Point"); + jmethodID init = env->GetMethodID(pointClass, "<init>", "(II)V"); + jobject point = env->NewObject(pointClass, init, pos.x(), pos.y()); + env->DeleteLocalRef(pointClass); + return point; +} + +static void nativeSelectAll(JNIEnv* env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->selectAll(); +} + +static void nativeSetExtendSelection(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setExtendSelection(); +} + +static jboolean nativeStartSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->startSelection(x, y); +} + +static jboolean nativeWordSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->wordSelection(x, y); +} + +static void nativeExtendSelection(JNIEnv *env, jobject obj, int x, int y) +{ + GET_NATIVE_VIEW(env, obj)->extendSelection(x, y); +} + +static jobject nativeGetSelection(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + String selection = view->getSelection(); + return wtfStringToJstring(env, selection); +} + +static jboolean nativeHitSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->hitSelection(x, y); +} + +static jint nativeSelectionX(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->selectionX(); +} + +static jint nativeSelectionY(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->selectionY(); +} + +static void nativeSetSelectionPointer(JNIEnv *env, jobject obj, jboolean set, + jfloat scale, jint x, jint y) +{ + GET_NATIVE_VIEW(env, obj)->setSelectionPointer(set, scale, x, y); +} + +#ifdef ANDROID_DUMP_DISPLAY_TREE +static void dumpToFile(const char text[], void* file) { + fwrite(text, 1, strlen(text), reinterpret_cast<FILE*>(file)); + fwrite("\n", 1, 1, reinterpret_cast<FILE*>(file)); +} +#endif + +static void nativeDumpDisplayTree(JNIEnv* env, jobject jwebview, jstring jurl) +{ +#ifdef ANDROID_DUMP_DISPLAY_TREE + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + + if (view && view->getWebViewCore()) { + FILE* file = fopen(DISPLAY_TREE_LOG_FILE, "w"); + if (file) { + SkFormatDumper dumper(dumpToFile, file); + // dump the URL + if (jurl) { + const char* str = env->GetStringUTFChars(jurl, 0); + SkDebugf("Dumping %s to %s\n", str, DISPLAY_TREE_LOG_FILE); + dumpToFile(str, file); + env->ReleaseStringUTFChars(jurl, str); + } + // now dump the display tree + SkDumpCanvas canvas(&dumper); + // this will playback the picture into the canvas, which will + // spew its contents to the dumper + view->draw(&canvas, 0, 0, false); + // we're done with the file now + fwrite("\n", 1, 1, file); + fclose(file); + } +#if USE(ACCELERATED_COMPOSITING) + const LayerAndroid* rootLayer = view->compositeRoot(); + if (rootLayer) { + FILE* file = fopen(LAYERS_TREE_LOG_FILE,"w"); + if (file) { + rootLayer->dumpLayers(file, 0); + fclose(file); + } + } +#endif + } +#endif +} + +static int nativeScrollableLayer(JNIEnv* env, jobject jwebview, jint x, jint y, + jobject rect, jobject bounds) +{ + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + SkIRect nativeRect, nativeBounds; + int id = view->scrollableLayer(x, y, &nativeRect, &nativeBounds); + if (rect) + GraphicsJNI::irect_to_jrect(nativeRect, env, rect); + if (bounds) + GraphicsJNI::irect_to_jrect(nativeBounds, env, bounds); + return id; +} + +static bool nativeScrollLayer(JNIEnv* env, jobject obj, jint layerId, jint x, + jint y) +{ +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + WebView* view = GET_NATIVE_VIEW(env, obj); + LayerAndroid* root = view->compositeRoot(); + if (!root) + return false; + LayerAndroid* layer = root->findById(layerId); + if (!layer || !layer->contentIsScrollable()) + return false; + return static_cast<ScrollableLayerAndroid*>(layer)->scrollTo(x, y); +#endif + return false; +} + +static void nativeSetExpandedTileBounds(JNIEnv*, jobject, jboolean enabled) +{ + TilesManager::instance()->setExpandedTileBounds(enabled); +} + +/* + * JNI registration + */ +static JNINativeMethod gJavaWebViewMethods[] = { + { "nativeCacheHitFramePointer", "()I", + (void*) nativeCacheHitFramePointer }, + { "nativeCacheHitIsPlugin", "()Z", + (void*) nativeCacheHitIsPlugin }, + { "nativeCacheHitNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeCacheHitNodeBounds }, + { "nativeCacheHitNodePointer", "()I", + (void*) nativeCacheHitNodePointer }, + { "nativeClearCursor", "()V", + (void*) nativeClearCursor }, + { "nativeCreate", "(ILjava/lang/String;Landroid/content/res/AssetManager;)V", + (void*) nativeCreate }, + { "nativeCursorFramePointer", "()I", + (void*) nativeCursorFramePointer }, + { "nativePageShouldHandleShiftAndArrows", "()Z", + (void*) nativePageShouldHandleShiftAndArrows }, + { "nativeCursorNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeCursorNodeBounds }, + { "nativeCursorNodePointer", "()I", + (void*) nativeCursorNodePointer }, + { "nativeCursorIntersects", "(Landroid/graphics/Rect;)Z", + (void*) nativeCursorIntersects }, + { "nativeCursorIsAnchor", "()Z", + (void*) nativeCursorIsAnchor }, + { "nativeCursorIsTextInput", "()Z", + (void*) nativeCursorIsTextInput }, + { "nativeCursorPosition", "()Landroid/graphics/Point;", + (void*) nativeCursorPosition }, + { "nativeCursorText", "()Ljava/lang/String;", + (void*) nativeCursorText }, + { "nativeCursorWantsKeyEvents", "()Z", + (void*)nativeCursorWantsKeyEvents }, + { "nativeDebugDump", "()V", + (void*) nativeDebugDump }, + { "nativeDestroy", "()V", + (void*) nativeDestroy }, + { "nativeDraw", "(Landroid/graphics/Canvas;IIZ)I", + (void*) nativeDraw }, + { "nativeGetDrawGLFunction", "(Landroid/graphics/Rect;Landroid/graphics/Rect;FI)I", + (void*) nativeGetDrawGLFunction }, + { "nativeUpdateDrawGLFunction", "(Landroid/graphics/Rect;Landroid/graphics/Rect;)V", + (void*) nativeUpdateDrawGLFunction }, + { "nativeDumpDisplayTree", "(Ljava/lang/String;)V", + (void*) nativeDumpDisplayTree }, + { "nativeEvaluateLayersAnimations", "()Z", + (void*) nativeEvaluateLayersAnimations }, + { "nativeExtendSelection", "(II)V", + (void*) nativeExtendSelection }, + { "nativeFindAll", "(Ljava/lang/String;Ljava/lang/String;Z)I", + (void*) nativeFindAll }, + { "nativeFindNext", "(Z)V", + (void*) nativeFindNext }, + { "nativeFindIndex", "()I", + (void*) nativeFindIndex}, + { "nativeFocusCandidateFramePointer", "()I", + (void*) nativeFocusCandidateFramePointer }, + { "nativeFocusCandidateHasNextTextfield", "()Z", + (void*) focusCandidateHasNextTextfield }, + { "nativeFocusCandidateIsPassword", "()Z", + (void*) nativeFocusCandidateIsPassword }, + { "nativeFocusCandidateIsRtlText", "()Z", + (void*) nativeFocusCandidateIsRtlText }, + { "nativeFocusCandidateIsTextInput", "()Z", + (void*) nativeFocusCandidateIsTextInput }, + { "nativeFocusCandidateLineHeight", "()I", + (void*) nativeFocusCandidateLineHeight }, + { "nativeFocusCandidateMaxLength", "()I", + (void*) nativeFocusCandidateMaxLength }, + { "nativeFocusCandidateIsAutoComplete", "()Z", + (void*) nativeFocusCandidateIsAutoComplete }, + { "nativeFocusCandidateName", "()Ljava/lang/String;", + (void*) nativeFocusCandidateName }, + { "nativeFocusCandidateNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeFocusCandidateNodeBounds }, + { "nativeFocusCandidatePaddingRect", "()Landroid/graphics/Rect;", + (void*) nativeFocusCandidatePaddingRect }, + { "nativeFocusCandidatePointer", "()I", + (void*) nativeFocusCandidatePointer }, + { "nativeFocusCandidateText", "()Ljava/lang/String;", + (void*) nativeFocusCandidateText }, + { "nativeFocusCandidateTextSize", "()F", + (void*) nativeFocusCandidateTextSize }, + { "nativeFocusCandidateType", "()I", + (void*) nativeFocusCandidateType }, + { "nativeFocusIsPlugin", "()Z", + (void*) nativeFocusIsPlugin }, + { "nativeFocusNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeFocusNodeBounds }, + { "nativeFocusNodePointer", "()I", + (void*) nativeFocusNodePointer }, + { "nativeGetCursorRingBounds", "()Landroid/graphics/Rect;", + (void*) nativeGetCursorRingBounds }, + { "nativeGetSelection", "()Ljava/lang/String;", + (void*) nativeGetSelection }, + { "nativeHasCursorNode", "()Z", + (void*) nativeHasCursorNode }, + { "nativeHasFocusNode", "()Z", + (void*) nativeHasFocusNode }, + { "nativeHideCursor", "()V", + (void*) nativeHideCursor }, + { "nativeHitSelection", "(II)Z", + (void*) nativeHitSelection }, + { "nativeImageURI", "(II)Ljava/lang/String;", + (void*) nativeImageURI }, + { "nativeInstrumentReport", "()V", + (void*) nativeInstrumentReport }, + { "nativeLayerBounds", "(I)Landroid/graphics/Rect;", + (void*) nativeLayerBounds }, + { "nativeMotionUp", "(III)Z", + (void*) nativeMotionUp }, + { "nativeMoveCursor", "(IIZ)Z", + (void*) nativeMoveCursor }, + { "nativeMoveCursorToNextTextInput", "()Z", + (void*) nativeMoveCursorToNextTextInput }, + { "nativeMoveGeneration", "()I", + (void*) nativeMoveGeneration }, + { "nativeMoveSelection", "(II)V", + (void*) nativeMoveSelection }, + { "nativePointInNavCache", "(III)Z", + (void*) nativePointInNavCache }, + { "nativeRecordButtons", "(ZZZ)V", + (void*) nativeRecordButtons }, + { "nativeResetSelection", "()V", + (void*) nativeResetSelection }, + { "nativeSelectableText", "()Landroid/graphics/Point;", + (void*) nativeSelectableText }, + { "nativeSelectAll", "()V", + (void*) nativeSelectAll }, + { "nativeSelectBestAt", "(Landroid/graphics/Rect;)V", + (void*) nativeSelectBestAt }, + { "nativeSelectAt", "(II)V", + (void*) nativeSelectAt }, + { "nativeSelectionX", "()I", + (void*) nativeSelectionX }, + { "nativeSelectionY", "()I", + (void*) nativeSelectionY }, + { "nativeSetExtendSelection", "()V", + (void*) nativeSetExtendSelection }, + { "nativeSetFindIsEmpty", "()V", + (void*) nativeSetFindIsEmpty }, + { "nativeSetFindIsUp", "(Z)V", + (void*) nativeSetFindIsUp }, + { "nativeSetHeightCanMeasure", "(Z)V", + (void*) nativeSetHeightCanMeasure }, + { "nativeSetBaseLayer", "(ILandroid/graphics/Region;ZZ)V", + (void*) nativeSetBaseLayer }, + { "nativeReplaceBaseContent", "(I)V", + (void*) nativeReplaceBaseContent }, + { "nativeCopyBaseContentToPicture", "(Landroid/graphics/Picture;)V", + (void*) nativeCopyBaseContentToPicture }, + { "nativeHasContent", "()Z", + (void*) nativeHasContent }, + { "nativeSetSelectionPointer", "(ZFII)V", + (void*) nativeSetSelectionPointer }, + { "nativeShowCursorTimed", "()V", + (void*) nativeShowCursorTimed }, + { "nativeStartSelection", "(II)Z", + (void*) nativeStartSelection }, + { "nativeStopGL", "()V", + (void*) nativeStopGL }, + { "nativeSubtractLayers", "(Landroid/graphics/Rect;)Landroid/graphics/Rect;", + (void*) nativeSubtractLayers }, + { "nativeTextGeneration", "()I", + (void*) nativeTextGeneration }, + { "nativeUpdateCachedTextfield", "(Ljava/lang/String;I)V", + (void*) nativeUpdateCachedTextfield }, + { "nativeWordSelection", "(II)Z", + (void*) nativeWordSelection }, + { "nativeGetBlockLeftEdge", "(IIF)I", + (void*) nativeGetBlockLeftEdge }, + { "nativeScrollableLayer", "(IILandroid/graphics/Rect;Landroid/graphics/Rect;)I", + (void*) nativeScrollableLayer }, + { "nativeScrollLayer", "(III)Z", + (void*) nativeScrollLayer }, + { "nativeSetExpandedTileBounds", "(Z)V", + (void*) nativeSetExpandedTileBounds }, +}; + +int registerWebView(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/WebView"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebView"); + gWebViewField = env->GetFieldID(clazz, "mNativeClass", "I"); + LOG_ASSERT(gWebViewField, "Unable to find android/webkit/WebView.mNativeClass"); + env->DeleteLocalRef(clazz); + + return jniRegisterNativeMethods(env, "android/webkit/WebView", gJavaWebViewMethods, NELEM(gJavaWebViewMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/plugins/ANPBitmapInterface.cpp b/Source/WebKit/android/plugins/ANPBitmapInterface.cpp new file mode 100644 index 0000000..4c6ad7c --- /dev/null +++ b/Source/WebKit/android/plugins/ANPBitmapInterface.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2009, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" +#include "SkColorPriv.h" + +static bool anp_getPixelPacking(ANPBitmapFormat fmt, ANPPixelPacking* packing) { + switch (fmt) { + case kRGBA_8888_ANPBitmapFormat: + if (packing) { + packing->AShift = SK_A32_SHIFT; + packing->ABits = SK_A32_BITS; + packing->RShift = SK_R32_SHIFT; + packing->RBits = SK_R32_BITS; + packing->GShift = SK_G32_SHIFT; + packing->GBits = SK_G32_BITS; + packing->BShift = SK_B32_SHIFT; + packing->BBits = SK_B32_BITS; + } + return true; + case kRGB_565_ANPBitmapFormat: + if (packing) { + packing->AShift = 0; + packing->ABits = 0; + packing->RShift = SK_R16_SHIFT; + packing->RBits = SK_R16_BITS; + packing->GShift = SK_G16_SHIFT; + packing->GBits = SK_G16_BITS; + packing->BShift = SK_B16_SHIFT; + packing->BBits = SK_B16_BITS; + } + return true; + default: + break; + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPBitmapInterfaceV0_Init(ANPInterface* value) { + ANPBitmapInterfaceV0* i = reinterpret_cast<ANPBitmapInterfaceV0*>(value); + + ASSIGN(i, getPixelPacking); +} diff --git a/Source/WebKit/android/plugins/ANPCanvasInterface.cpp b/Source/WebKit/android/plugins/ANPCanvasInterface.cpp new file mode 100644 index 0000000..d6d89ff --- /dev/null +++ b/Source/WebKit/android/plugins/ANPCanvasInterface.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2008, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" + +static ANPCanvas* anp_newCanvas(const ANPBitmap* bitmap) { + SkBitmap bm; + return new ANPCanvas(*SkANP::SetBitmap(&bm, *bitmap)); +} + +static void anp_deleteCanvas(ANPCanvas* canvas) { + delete canvas; +} + +static void anp_save(ANPCanvas* canvas) { + canvas->skcanvas->save(); +} + +static void anp_restore(ANPCanvas* canvas) { + canvas->skcanvas->restore(); +} + +static void anp_translate(ANPCanvas* canvas, float tx, float ty) { + canvas->skcanvas->translate(SkFloatToScalar(tx), SkFloatToScalar(ty)); +} + +static void anp_scale(ANPCanvas* canvas, float sx, float sy) { + canvas->skcanvas->scale(SkFloatToScalar(sx), SkFloatToScalar(sy)); +} + +static void anp_rotate(ANPCanvas* canvas, float degrees) { + canvas->skcanvas->rotate(SkFloatToScalar(degrees)); +} + +static void anp_skew(ANPCanvas* canvas, float kx, float ky) { + canvas->skcanvas->skew(SkFloatToScalar(kx), SkFloatToScalar(ky)); +} + +static void anp_clipRect(ANPCanvas* canvas, const ANPRectF* rect) { + SkRect r; + canvas->skcanvas->clipRect(*SkANP::SetRect(&r, *rect)); +} + +static void anp_clipPath(ANPCanvas* canvas, const ANPPath* path) { + canvas->skcanvas->clipPath(*path); +} +static void anp_concat(ANPCanvas* canvas, const ANPMatrix* matrix) { + canvas->skcanvas->concat(*matrix); +} + +static void anp_getTotalMatrix(ANPCanvas* canvas, ANPMatrix* matrix) { + const SkMatrix& src = canvas->skcanvas->getTotalMatrix(); + *matrix = *reinterpret_cast<const ANPMatrix*>(&src); +} + +static bool anp_getLocalClipBounds(ANPCanvas* canvas, ANPRectF* r, + bool antialias) { + SkRect bounds; + if (canvas->skcanvas->getClipBounds(&bounds, + antialias ? SkCanvas::kAA_EdgeType : SkCanvas::kBW_EdgeType)) { + SkANP::SetRect(r, bounds); + return true; + } + return false; +} + +static bool anp_getDeviceClipBounds(ANPCanvas* canvas, ANPRectI* r) { + const SkRegion& clip = canvas->skcanvas->getTotalClip(); + if (!clip.isEmpty()) { + SkANP::SetRect(r, clip.getBounds()); + return true; + } + return false; +} + +static void anp_drawColor(ANPCanvas* canvas, ANPColor color) { + canvas->skcanvas->drawColor(color); +} + +static void anp_drawPaint(ANPCanvas* canvas, const ANPPaint* paint) { + canvas->skcanvas->drawPaint(*paint); +} + +static void anp_drawLine(ANPCanvas* canvas, float x0, float y0, + float x1, float y1, const ANPPaint* paint) { + canvas->skcanvas->drawLine(SkFloatToScalar(x0), SkFloatToScalar(y0), + SkFloatToScalar(x1), SkFloatToScalar(y1), *paint); +} + +static void anp_drawRect(ANPCanvas* canvas, const ANPRectF* rect, + const ANPPaint* paint) { + SkRect r; + canvas->skcanvas->drawRect(*SkANP::SetRect(&r, *rect), *paint); +} + +static void anp_drawOval(ANPCanvas* canvas, const ANPRectF* rect, + const ANPPaint* paint) { + SkRect r; + canvas->skcanvas->drawOval(*SkANP::SetRect(&r, *rect), *paint); +} + +static void anp_drawPath(ANPCanvas* canvas, const ANPPath* path, + const ANPPaint* paint) { + canvas->skcanvas->drawPath(*path, *paint); +} + +static void anp_drawText(ANPCanvas* canvas, const void* text, uint32_t length, + float x, float y, const ANPPaint* paint) { + canvas->skcanvas->drawText(text, length, + SkFloatToScalar(x), SkFloatToScalar(y), + *paint); +} + +static void anp_drawPosText(ANPCanvas* canvas, const void* text, + uint32_t byteLength, const float xy[], const ANPPaint* paint) { + canvas->skcanvas->drawPosText(text, byteLength, + reinterpret_cast<const SkPoint*>(xy), *paint); +} + +static void anp_drawBitmap(ANPCanvas* canvas, const ANPBitmap* bitmap, + float x, float y, const ANPPaint* paint) { + SkBitmap bm; + canvas->skcanvas->drawBitmap(*SkANP::SetBitmap(&bm, *bitmap), + SkFloatToScalar(x), SkFloatToScalar(y), + paint); +} + +static void anp_drawBitmapRect(ANPCanvas* canvas, const ANPBitmap* bitmap, + const ANPRectI* src, const ANPRectF* dst, + const ANPPaint* paint) { + SkBitmap bm; + SkRect dstR; + SkIRect srcR, *srcPtr = NULL; + + if (src) { + srcPtr = SkANP::SetRect(&srcR, *src); + } + canvas->skcanvas->drawBitmapRect(*SkANP::SetBitmap(&bm, *bitmap), srcPtr, + *SkANP::SetRect(&dstR, *dst), paint); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPCanvasInterfaceV0_Init(ANPInterface* value) { + ANPCanvasInterfaceV0* i = reinterpret_cast<ANPCanvasInterfaceV0*>(value); + + ASSIGN(i, newCanvas); + ASSIGN(i, deleteCanvas); + ASSIGN(i, save); + ASSIGN(i, restore); + ASSIGN(i, translate); + ASSIGN(i, scale); + ASSIGN(i, rotate); + ASSIGN(i, skew); + ASSIGN(i, clipRect); + ASSIGN(i, clipPath); + ASSIGN(i, concat); + ASSIGN(i, getTotalMatrix); + ASSIGN(i, getLocalClipBounds); + ASSIGN(i, getDeviceClipBounds); + ASSIGN(i, drawColor); + ASSIGN(i, drawPaint); + ASSIGN(i, drawLine); + ASSIGN(i, drawRect); + ASSIGN(i, drawOval); + ASSIGN(i, drawPath); + ASSIGN(i, drawText); + ASSIGN(i, drawPosText); + ASSIGN(i, drawBitmap); + ASSIGN(i, drawBitmapRect); +} diff --git a/Source/WebKit/android/plugins/ANPEventInterface.cpp b/Source/WebKit/android/plugins/ANPEventInterface.cpp new file mode 100644 index 0000000..2fdf159 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPEventInterface.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2009, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" +#include "WebViewCore.h" +#include "PluginView.h" +#include "PluginWidgetAndroid.h" + +#include "JavaSharedClient.h" + +using namespace android; + +struct WrappedANPEvent { + WebViewCore* fWVC; + PluginWidgetAndroid* fPWA; + ANPEvent fEvent; +}; + +/* Its possible we may be called after the plugin that initiated the event + has been torn-down. Thus we check that the assicated webviewcore and + pluginwidget are still active before dispatching the event. + */ +static void send_anpevent(void* data) { + WrappedANPEvent* wrapper = static_cast<WrappedANPEvent*>(data); + WebViewCore* core = wrapper->fWVC; + PluginWidgetAndroid* widget = wrapper->fPWA; + + // be sure we're still alive before delivering the event + if (WebViewCore::isInstance(core) && core->isPlugin(widget)) { + widget->sendEvent(wrapper->fEvent); + } + delete wrapper; +} + +static void anp_postEvent(NPP instance, const ANPEvent* event) { + if (instance && instance->ndata && event) { + PluginView* pluginView = static_cast<PluginView*>(instance->ndata); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + WebViewCore* wvc = pluginWidget->webViewCore(); + + WrappedANPEvent* wrapper = new WrappedANPEvent; + // recored these, and recheck that they are valid before delivery + // in send_anpevent + wrapper->fWVC = pluginWidget->webViewCore(); + wrapper->fPWA = pluginWidget; + // make a copy of the event + wrapper->fEvent = *event; + JavaSharedClient::EnqueueFunctionPtr(send_anpevent, wrapper); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPEventInterfaceV0_Init(ANPInterface* value) { + ANPEventInterfaceV0* i = reinterpret_cast<ANPEventInterfaceV0*>(value); + + ASSIGN(i, postEvent); +} diff --git a/Source/WebKit/android/plugins/ANPKeyCodes.h b/Source/WebKit/android/plugins/ANPKeyCodes.h new file mode 100644 index 0000000..969679f --- /dev/null +++ b/Source/WebKit/android/plugins/ANPKeyCodes.h @@ -0,0 +1,229 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef ANPKeyCodes_DEFINED +#define ANPKeyCodes_DEFINED + +/* List the key codes that are set to a plugin in the ANPKeyEvent. + + These exactly match the values in android/view/KeyEvent.java and the + corresponding .h file android/keycodes.h. +*/ +enum ANPKeyCodes { + kUnknown_ANPKeyCode = 0, + + kSoftLeft_ANPKeyCode = 1, + kSoftRight_ANPKeyCode = 2, + kHome_ANPKeyCode = 3, + kBack_ANPKeyCode = 4, + kCall_ANPKeyCode = 5, + kEndCall_ANPKeyCode = 6, + k0_ANPKeyCode = 7, + k1_ANPKeyCode = 8, + k2_ANPKeyCode = 9, + k3_ANPKeyCode = 10, + k4_ANPKeyCode = 11, + k5_ANPKeyCode = 12, + k6_ANPKeyCode = 13, + k7_ANPKeyCode = 14, + k8_ANPKeyCode = 15, + k9_ANPKeyCode = 16, + kStar_ANPKeyCode = 17, + kPound_ANPKeyCode = 18, + kDpadUp_ANPKeyCode = 19, + kDpadDown_ANPKeyCode = 20, + kDpadLeft_ANPKeyCode = 21, + kDpadRight_ANPKeyCode = 22, + kDpadCenter_ANPKeyCode = 23, + kVolumeUp_ANPKeyCode = 24, + kVolumeDown_ANPKeyCode = 25, + kPower_ANPKeyCode = 26, + kCamera_ANPKeyCode = 27, + kClear_ANPKeyCode = 28, + kA_ANPKeyCode = 29, + kB_ANPKeyCode = 30, + kC_ANPKeyCode = 31, + kD_ANPKeyCode = 32, + kE_ANPKeyCode = 33, + kF_ANPKeyCode = 34, + kG_ANPKeyCode = 35, + kH_ANPKeyCode = 36, + kI_ANPKeyCode = 37, + kJ_ANPKeyCode = 38, + kK_ANPKeyCode = 39, + kL_ANPKeyCode = 40, + kM_ANPKeyCode = 41, + kN_ANPKeyCode = 42, + kO_ANPKeyCode = 43, + kP_ANPKeyCode = 44, + kQ_ANPKeyCode = 45, + kR_ANPKeyCode = 46, + kS_ANPKeyCode = 47, + kT_ANPKeyCode = 48, + kU_ANPKeyCode = 49, + kV_ANPKeyCode = 50, + kW_ANPKeyCode = 51, + kX_ANPKeyCode = 52, + kY_ANPKeyCode = 53, + kZ_ANPKeyCode = 54, + kComma_ANPKeyCode = 55, + kPeriod_ANPKeyCode = 56, + kAltLeft_ANPKeyCode = 57, + kAltRight_ANPKeyCode = 58, + kShiftLeft_ANPKeyCode = 59, + kShiftRight_ANPKeyCode = 60, + kTab_ANPKeyCode = 61, + kSpace_ANPKeyCode = 62, + kSym_ANPKeyCode = 63, + kExplorer_ANPKeyCode = 64, + kEnvelope_ANPKeyCode = 65, + kNewline_ANPKeyCode = 66, + kDel_ANPKeyCode = 67, + kGrave_ANPKeyCode = 68, + kMinus_ANPKeyCode = 69, + kEquals_ANPKeyCode = 70, + kLeftBracket_ANPKeyCode = 71, + kRightBracket_ANPKeyCode = 72, + kBackslash_ANPKeyCode = 73, + kSemicolon_ANPKeyCode = 74, + kApostrophe_ANPKeyCode = 75, + kSlash_ANPKeyCode = 76, + kAt_ANPKeyCode = 77, + kNum_ANPKeyCode = 78, + kHeadSetHook_ANPKeyCode = 79, + kFocus_ANPKeyCode = 80, + kPlus_ANPKeyCode = 81, + kMenu_ANPKeyCode = 82, + kNotification_ANPKeyCode = 83, + kSearch_ANPKeyCode = 84, + kMediaPlayPause_ANPKeyCode = 85, + kMediaStop_ANPKeyCode = 86, + kMediaNext_ANPKeyCode = 87, + kMediaPrevious_ANPKeyCode = 88, + kMediaRewind_ANPKeyCode = 89, + kMediaFastForward_ANPKeyCode = 90, + kMute_ANPKeyCode = 91, + kPageUp_ANPKeyCode = 92, + kPageDown_ANPKeyCode = 93, + kPictsymbols_ANPKeyCode = 94, + kSwitchCharset_ANPKeyCode = 95, + kButtonA_ANPKeyCode = 96, + kButtonB_ANPKeyCode = 97, + kButtonC_ANPKeyCode = 98, + kButtonX_ANPKeyCode = 99, + kButtonY_ANPKeyCode = 100, + kButtonZ_ANPKeyCode = 101, + kButtonL1_ANPKeyCode = 102, + kButtonR1_ANPKeyCode = 103, + kButtonL2_ANPKeyCode = 104, + kButtonR2_ANPKeyCode = 105, + kButtonThumbL_ANPKeyCode = 106, + kButtonThumbR_ANPKeyCode = 107, + kButtonStart_ANPKeyCode = 108, + kButtonSelect_ANPKeyCode = 109, + kButtonMode_ANPKeyCode = 110, + kEscape_ANPKeyCode = 111, + kForwardDel_ANPKeyCode = 112, + kCtrlLeft_ANPKeyCode = 113, + kCtrlRight_ANPKeyCode = 114, + kCapsLock_ANPKeyCode = 115, + kScrollLock_ANPKeyCode = 116, + kMetaLeft_ANPKeyCode = 117, + kMetaRight_ANPKeyCode = 118, + kFunction_ANPKeyCode = 119, + kSysRq_ANPKeyCode = 120, + kBreak_ANPKeyCode = 121, + kMoveHome_ANPKeyCode = 122, + kMoveEnd_ANPKeyCode = 123, + kInsert_ANPKeyCode = 124, + kForward_ANPKeyCode = 125, + kMediaPlay_ANPKeyCode = 126, + kMediaPause_ANPKeyCode = 127, + kMediaClose_ANPKeyCode = 128, + kMediaEject_ANPKeyCode = 129, + kMediaRecord_ANPKeyCode = 130, + kF1_ANPKeyCode = 131, + kF2_ANPKeyCode = 132, + kF3_ANPKeyCode = 133, + kF4_ANPKeyCode = 134, + kF5_ANPKeyCode = 135, + kF6_ANPKeyCode = 136, + kF7_ANPKeyCode = 137, + kF8_ANPKeyCode = 138, + kF9_ANPKeyCode = 139, + kF10_ANPKeyCode = 140, + kF11_ANPKeyCode = 141, + kF12_ANPKeyCode = 142, + kNumLock_ANPKeyCode = 143, + kNumPad0_ANPKeyCode = 144, + kNumPad1_ANPKeyCode = 145, + kNumPad2_ANPKeyCode = 146, + kNumPad3_ANPKeyCode = 147, + kNumPad4_ANPKeyCode = 148, + kNumPad5_ANPKeyCode = 149, + kNumPad6_ANPKeyCode = 150, + kNumPad7_ANPKeyCode = 151, + kNumPad8_ANPKeyCode = 152, + kNumPad9_ANPKeyCode = 153, + kNumPadDivide_ANPKeyCode = 154, + kNumPadMultiply_ANPKeyCode = 155, + kNumPadSubtract_ANPKeyCode = 156, + kNumPadAdd_ANPKeyCode = 157, + kNumPadDot_ANPKeyCode = 158, + kNumPadComma_ANPKeyCode = 159, + kNumPadEnter_ANPKeyCode = 160, + kNumPadEquals_ANPKeyCode = 161, + kNumPadLeftParen_ANPKeyCode = 162, + kNumPadRightParen_ANPKeyCode = 163, + kVolumeMute_ANPKeyCode = 164, + kInfo_ANPKeyCode = 165, + kChannelUp_ANPKeyCode = 166, + kChannelDown_ANPKeyCode = 167, + kZoomIn_ANPKeyCode = 168, + kZoomOut_ANPKeyCode = 169, + kTv_ANPKeyCode = 170, + kWindow_ANPKeyCode = 171, + kGuide_ANPKeyCode = 172, + kDvr_ANPKeyCode = 173, + kBookmark_ANPKeyCode = 174, + kCaptions_ANPKeyCode = 175, + kSettings_ANPKeyCode = 176, + kTvPower_ANPKeyCode = 177, + kTvInput_ANPKeyCode = 178, + kStbPower_ANPKeyCode = 179, + kStbInput_ANPKeyCode = 180, + kAvrPower_ANPKeyCode = 181, + kAvrInput_ANPKeyCode = 182, + kProgRed_ANPKeyCode = 183, + kProgGreen_ANPKeyCode = 184, + kProgYellow_ANPKeyCode = 185, + kProgBlue_ANPKeyCode = 186, + kAppSwitch_ANPKeyCode = 187, + + // NOTE: If you add a new keycode here you must also add it to several other files. + // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. +}; + +#endif diff --git a/Source/WebKit/android/plugins/ANPLogInterface.cpp b/Source/WebKit/android/plugins/ANPLogInterface.cpp new file mode 100644 index 0000000..23a4ed6 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPLogInterface.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2008, 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 "webkitPlugin" + +#include "utils/Log.h" +#include "android_npapi.h" +#include <stdarg.h> + +static void anp_log(ANPLogType logType, const char format[], ...) { + va_list args; + va_start(args, format); + + android_LogPriority priority; + switch (logType) { + case kError_ANPLogType: + priority = ANDROID_LOG_ERROR; + break; + case kWarning_ANPLogType: + priority = ANDROID_LOG_WARN; + break; + case kDebug_ANPLogType: + priority = ANDROID_LOG_DEBUG; + break; + default: + priority = ANDROID_LOG_UNKNOWN; + break; + } + LOG_PRI_VA(priority, "plugin", format, args); + + va_end(args); +} + +void ANPLogInterfaceV0_Init(ANPInterface* value) { + ANPLogInterfaceV0* i = reinterpret_cast<ANPLogInterfaceV0*>(value); + + i->log = anp_log; +} diff --git a/Source/WebKit/android/plugins/ANPMatrixInterface.cpp b/Source/WebKit/android/plugins/ANPMatrixInterface.cpp new file mode 100644 index 0000000..f322315 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPMatrixInterface.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2009, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" + +#ifdef SK_SCALAR_IS_FIXED +static void fromFloat(SkScalar dst[], const float src[], int n) { + for (int i = 0; i < n; i++) { + dst[i] = SkFloatToScalar(src[i]); + } +} + +static void toFloat(float dst[], const SkScalar src[], int n) { + for (int i = 0; i < n; i++) { + dst[i] = SkScalarToFloat(src[i]); + } +} +#endif + +static ANPMatrix* anp_newMatrix() { + return new ANPMatrix; +} + +static void anp_deleteMatrix(ANPMatrix* matrix) { + delete matrix; +} + +static ANPMatrixFlag anp_getFlags(const ANPMatrix* matrix) { + return matrix->getType(); +} + +static void anp_copy(ANPMatrix* dst, const ANPMatrix* src) { + *dst = *src; +} + +static void anp_get3x3(const ANPMatrix* matrix, float dst[9]) { + for (int i = 0; i < 9; i++) { + dst[i] = SkScalarToFloat(matrix->get(i)); + } +} + +static void anp_set3x3(ANPMatrix* matrix, const float src[9]) { + for (int i = 0; i < 9; i++) { + matrix->set(i, SkFloatToScalar(src[i])); + } +} + +static void anp_setIdentity(ANPMatrix* matrix) { + matrix->reset(); +} + +static void anp_preTranslate(ANPMatrix* matrix, float tx, float ty) { + matrix->preTranslate(SkFloatToScalar(tx), SkFloatToScalar(ty)); +} + +static void anp_postTranslate(ANPMatrix* matrix, float tx, float ty) { + matrix->postTranslate(SkFloatToScalar(tx), SkFloatToScalar(ty)); +} + +static void anp_preScale(ANPMatrix* matrix, float sx, float sy) { + matrix->preScale(SkFloatToScalar(sx), SkFloatToScalar(sy)); +} + +static void anp_postScale(ANPMatrix* matrix, float sx, float sy) { + matrix->postScale(SkFloatToScalar(sx), SkFloatToScalar(sy)); +} + +static void anp_preSkew(ANPMatrix* matrix, float kx, float ky) { + matrix->preSkew(SkFloatToScalar(kx), SkFloatToScalar(ky)); +} + +static void anp_postSkew(ANPMatrix* matrix, float kx, float ky) { + matrix->postSkew(SkFloatToScalar(kx), SkFloatToScalar(ky)); +} + +static void anp_preRotate(ANPMatrix* matrix, float degrees) { + matrix->preRotate(SkFloatToScalar(degrees)); +} + +static void anp_postRotate(ANPMatrix* matrix, float degrees) { + matrix->postRotate(SkFloatToScalar(degrees)); +} + +static void anp_preConcat(ANPMatrix* matrix, const ANPMatrix* other) { + matrix->preConcat(*other); +} + +static void anp_postConcat(ANPMatrix* matrix, const ANPMatrix* other) { + matrix->postConcat(*other); +} + +static bool anp_invert(ANPMatrix* dst, const ANPMatrix* src) { + return src->invert(dst); +} + +static void anp_mapPoints(ANPMatrix* matrix, float dst[], const float src[], + int32_t count) { +#ifdef SK_SCALAR_IS_FLOAT + matrix->mapPoints(reinterpret_cast<SkPoint*>(dst), + reinterpret_cast<const SkPoint*>(src), count); +#else + const int N = 64; + SkPoint tmp[N]; + do { + int n = count; + if (n > N) { + n = N; + } + fromFloat(&tmp[0].fX, src, n*2); + matrix->mapPoints(tmp, n); + toFloat(dst, &tmp[0].fX, n*2); + count -= n; + } while (count > 0); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPMatrixInterfaceV0_Init(ANPInterface* value) { + ANPMatrixInterfaceV0* i = reinterpret_cast<ANPMatrixInterfaceV0*>(value); + + ASSIGN(i, newMatrix); + ASSIGN(i, deleteMatrix); + ASSIGN(i, getFlags); + ASSIGN(i, copy); + ASSIGN(i, get3x3); + ASSIGN(i, set3x3); + ASSIGN(i, setIdentity); + ASSIGN(i, preTranslate); + ASSIGN(i, postTranslate); + ASSIGN(i, preScale); + ASSIGN(i, postScale); + ASSIGN(i, preSkew); + ASSIGN(i, postSkew); + ASSIGN(i, preRotate); + ASSIGN(i, postRotate); + ASSIGN(i, preConcat); + ASSIGN(i, postConcat); + ASSIGN(i, invert); + ASSIGN(i, mapPoints); +} diff --git a/Source/WebKit/android/plugins/ANPOpenGLInterface.cpp b/Source/WebKit/android/plugins/ANPOpenGLInterface.cpp new file mode 100644 index 0000000..839ec17 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPOpenGLInterface.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2010, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" + +#include "ANPOpenGL_npapi.h" +#include "PluginView.h" +#include "PluginWidgetAndroid.h" +#include "MediaLayer.h" +#include "WebViewCore.h" +#include "Frame.h" +#include "Page.h" +#include "Chrome.h" +#include "ChromeClient.h" + +using namespace android; + +static WebCore::PluginView* pluginViewForInstance(NPP instance) { + if (instance && instance->ndata) + return static_cast<WebCore::PluginView*>(instance->ndata); + return WebCore::PluginView::currentPluginView(); +} + +static EGLContext anp_acquireContext(NPP instance) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + WebCore::MediaLayer* mediaLayer = pluginWidget->getLayer(); + + if (!mediaLayer) + return EGL_NO_CONTEXT; + + return mediaLayer->getTexture()->producerAcquireContext(); +} + +static ANPTextureInfo anp_lockTexture(NPP instance) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + WebCore::MediaLayer* mediaLayer = pluginWidget->getLayer(); + WebCore::DoubleBufferedTexture* texture = mediaLayer->getTexture(); + + // lock the texture and cache the internal info + WebCore::TextureInfo* info = texture->producerLock(); + mediaLayer->setCurrentTextureInfo(info); + + ANPTextureInfo anpInfo; + anpInfo.textureId = info->m_textureId; + anpInfo.width = (int32_t) info->m_width; + anpInfo.height = (int32_t) info->m_height; + anpInfo.internalFormat = info->m_internalFormat; + return anpInfo; +} + +static void anp_releaseTexture(NPP instance, const ANPTextureInfo* textureInfo) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + WebCore::MediaLayer* mediaLayer = pluginWidget->getLayer(); + WebCore::DoubleBufferedTexture* texture = mediaLayer->getTexture(); + + //copy the info into our internal structure + WebCore::TextureInfo* info = mediaLayer->getCurrentTextureInfo(); + info->m_textureId = textureInfo->textureId; + info->m_width = textureInfo->width; + info->m_height = textureInfo->height; + info->m_internalFormat = textureInfo->internalFormat; + + texture->producerReleaseAndSwap(); + + // invalidate the java view so that this content is drawn + pluginWidget->viewInvalidate(); +} + +static void anp_invertPluginContent(NPP instance, bool isContentInverted) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + WebCore::MediaLayer* mediaLayer = pluginWidget->getLayer(); + + mediaLayer->invertContents(isContentInverted); + + //force the layer to sync to the UI thread + WebViewCore* wvc = pluginWidget->webViewCore(); + if (wvc) + wvc->mainFrame()->page()->chrome()->client()->scheduleCompositingLayerSync(); +} + + + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPOpenGLInterfaceV0_Init(ANPInterface* v) { + ANPOpenGLInterfaceV0* i = reinterpret_cast<ANPOpenGLInterfaceV0*>(v); + + ASSIGN(i, acquireContext); + ASSIGN(i, lockTexture); + ASSIGN(i, releaseTexture); + ASSIGN(i, invertPluginContent); +} diff --git a/Source/WebKit/android/plugins/ANPOpenGL_npapi.h b/Source/WebKit/android/plugins/ANPOpenGL_npapi.h new file mode 100644 index 0000000..5aabbc4 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPOpenGL_npapi.h @@ -0,0 +1,63 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef ANPOpenGL_npapi_H +#define ANPOpenGL_npapi_H + +#include "android_npapi.h" +#include <EGL/egl.h> +#include <GLES2/gl2.h> + +/** + * TODO should we not use EGL and GL data types for ABI safety? + */ +struct ANPTextureInfo { + GLuint textureId; + uint32_t width; + uint32_t height; + GLenum internalFormat; +}; + +struct ANPOpenGLInterfaceV0 : ANPInterface { + /** + */ + EGLContext (*acquireContext)(NPP instance); + + /** + */ + ANPTextureInfo (*lockTexture)(NPP instance); + + /** + */ + void (*releaseTexture)(NPP instance, const ANPTextureInfo*); + + /** + * Invert the contents of the plugin on the y-axis. + * default is to not be inverted (i.e. use OpenGL coordinates) + */ + void (*invertPluginContent)(NPP instance, bool isContentInverted); +}; + +#endif //ANPOpenGL_npapi_H diff --git a/Source/WebKit/android/plugins/ANPPaintInterface.cpp b/Source/WebKit/android/plugins/ANPPaintInterface.cpp new file mode 100644 index 0000000..5c59df9 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPPaintInterface.cpp @@ -0,0 +1,212 @@ +/* + * Copyright 2008, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" +#include "SkTypeface.h" + +static ANPPaint* anp_newPaint() { + return new ANPPaint; +} + +static void anp_deletePaint(ANPPaint* paint) { + delete paint; +} + +static ANPPaintFlags anp_getFlags(const ANPPaint* paint) { + return paint->getFlags(); +} + +static void anp_setFlags(ANPPaint* paint, ANPPaintFlags flags) { + paint->setFlags(flags); +} + +static ANPColor anp_getColor(const ANPPaint* paint) { + return paint->getColor(); +} + +static void anp_setColor(ANPPaint* paint, ANPColor color) { + paint->setColor(color); +} + +static ANPPaintStyle anp_getStyle(const ANPPaint* paint) { + return paint->getStyle(); +} + +static void anp_setStyle(ANPPaint* paint, ANPPaintStyle style) { + paint->setStyle(static_cast<SkPaint::Style>(style)); +} + +static float anp_getStrokeWidth(const ANPPaint* paint) { + return SkScalarToFloat(paint->getStrokeWidth()); +} + +static float anp_getStrokeMiter(const ANPPaint* paint) { + return SkScalarToFloat(paint->getStrokeMiter()); +} + +static ANPPaintCap anp_getStrokeCap(const ANPPaint* paint) { + return paint->getStrokeCap(); +} + +static ANPPaintJoin anp_getStrokeJoin(const ANPPaint* paint) { + return paint->getStrokeJoin(); +} + +static void anp_setStrokeWidth(ANPPaint* paint, float width) { + paint->setStrokeWidth(SkFloatToScalar(width)); +} + +static void anp_setStrokeMiter(ANPPaint* paint, float miter) { + paint->setStrokeMiter(SkFloatToScalar(miter)); +} + +static void anp_setStrokeCap(ANPPaint* paint, ANPPaintCap cap) { + paint->setStrokeCap(static_cast<SkPaint::Cap>(cap)); +} + +static void anp_setStrokeJoin(ANPPaint* paint, ANPPaintJoin join) { + paint->setStrokeJoin(static_cast<SkPaint::Join>(join)); +} + +static ANPTextEncoding anp_getTextEncoding(const ANPPaint* paint) { + return paint->getTextEncoding(); +} + +static ANPPaintAlign anp_getTextAlign(const ANPPaint* paint) { + return paint->getTextAlign(); +} + +static float anp_getTextSize(const ANPPaint* paint) { + return SkScalarToFloat(paint->getTextSize()); +} + +static float anp_getTextScaleX(const ANPPaint* paint) { + return SkScalarToFloat(paint->getTextScaleX()); +} + +static float anp_getTextSkewX(const ANPPaint* paint) { + return SkScalarToFloat(paint->getTextSkewX()); +} + +static ANPTypeface* anp_getTypeface(const ANPPaint* paint) { + return reinterpret_cast<ANPTypeface*>(paint->getTypeface()); +} + +static void anp_setTextEncoding(ANPPaint* paint, ANPTextEncoding encoding) { + paint->setTextEncoding(static_cast<SkPaint::TextEncoding>(encoding)); +} + +static void anp_setTextAlign(ANPPaint* paint, ANPPaintAlign align) { + paint->setTextAlign(static_cast<SkPaint::Align>(align)); +} + +static void anp_setTextSize(ANPPaint* paint, float textSize) { + paint->setTextSize(SkFloatToScalar(textSize)); +} + +static void anp_setTextScaleX(ANPPaint* paint, float scaleX) { + paint->setTextScaleX(SkFloatToScalar(scaleX)); +} + +static void anp_setTextSkewX(ANPPaint* paint, float skewX) { + paint->setTextSkewX(SkFloatToScalar(skewX)); +} + +static void anp_setTypeface(ANPPaint* paint, ANPTypeface* tf) { + paint->setTypeface(tf); +} + +static float anp_measureText(ANPPaint* paint, const void* text, + uint32_t byteLength, ANPRectF* bounds) { + SkScalar w = paint->measureText(text, byteLength, + reinterpret_cast<SkRect*>(bounds)); + return SkScalarToFloat(w); +} + +/** Return the number of unichars specifed by the text. + If widths is not null, returns the array of advance widths for each + unichar. + If bounds is not null, returns the array of bounds for each unichar. + */ +static int anp_getTextWidths(ANPPaint* paint, const void* text, + uint32_t byteLength, float widths[], ANPRectF bounds[]) { + return paint->getTextWidths(text, byteLength, widths, + reinterpret_cast<SkRect*>(bounds)); +} + +static float anp_getFontMetrics(ANPPaint* paint, ANPFontMetrics* metrics) { + SkPaint::FontMetrics fm; + SkScalar spacing = paint->getFontMetrics(&fm); + if (metrics) { + metrics->fTop = SkScalarToFloat(fm.fTop); + metrics->fAscent = SkScalarToFloat(fm.fAscent); + metrics->fDescent = SkScalarToFloat(fm.fDescent); + metrics->fBottom = SkScalarToFloat(fm.fBottom); + metrics->fLeading = SkScalarToFloat(fm.fLeading); + } + return SkScalarToFloat(spacing); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPPaintInterfaceV0_Init(ANPInterface* value) { + ANPPaintInterfaceV0* i = reinterpret_cast<ANPPaintInterfaceV0*>(value); + + ASSIGN(i, newPaint); + ASSIGN(i, deletePaint); + ASSIGN(i, getFlags); + ASSIGN(i, setFlags); + ASSIGN(i, getColor); + ASSIGN(i, setColor); + ASSIGN(i, getStyle); + ASSIGN(i, setStyle); + ASSIGN(i, getStrokeWidth); + ASSIGN(i, getStrokeMiter); + ASSIGN(i, getStrokeCap); + ASSIGN(i, getStrokeJoin); + ASSIGN(i, setStrokeWidth); + ASSIGN(i, setStrokeMiter); + ASSIGN(i, setStrokeCap); + ASSIGN(i, setStrokeJoin); + ASSIGN(i, getTextEncoding); + ASSIGN(i, getTextAlign); + ASSIGN(i, getTextSize); + ASSIGN(i, getTextScaleX); + ASSIGN(i, getTextSkewX); + ASSIGN(i, getTypeface); + ASSIGN(i, setTextEncoding); + ASSIGN(i, setTextAlign); + ASSIGN(i, setTextSize); + ASSIGN(i, setTextScaleX); + ASSIGN(i, setTextSkewX); + ASSIGN(i, setTypeface); + ASSIGN(i, measureText); + ASSIGN(i, getTextWidths); + ASSIGN(i, getFontMetrics); +} diff --git a/Source/WebKit/android/plugins/ANPPathInterface.cpp b/Source/WebKit/android/plugins/ANPPathInterface.cpp new file mode 100644 index 0000000..69cabcf --- /dev/null +++ b/Source/WebKit/android/plugins/ANPPathInterface.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2009, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" + +static ANPPath* anp_newPath() { + return new ANPPath; +} + +static void anp_deletePath(ANPPath* path) { + delete path; +} + +static void anp_copy(ANPPath* dst, const ANPPath* src) { + *dst = *src; +} + +static bool anp_equal(const ANPPath* p0, const ANPPath* p1) { + return *p0 == *p1; +} + +static void anp_reset(ANPPath* path) { + path->reset(); +} + +static bool anp_isEmpty(const ANPPath* path) { + return path->isEmpty(); +} + +static void anp_getBounds(const ANPPath* path, ANPRectF* bounds) { + SkANP::SetRect(bounds, path->getBounds()); +} + +static void anp_moveTo(ANPPath* path, float x, float y) { + path->moveTo(SkFloatToScalar(x), SkFloatToScalar(y)); +} + +static void anp_lineTo(ANPPath* path, float x, float y) { + path->lineTo(SkFloatToScalar(x), SkFloatToScalar(y)); +} + +static void anp_quadTo(ANPPath* path, float x0, float y0, float x1, float y1) { + path->quadTo(SkFloatToScalar(x0), SkFloatToScalar(y0), + SkFloatToScalar(x1), SkFloatToScalar(y1)); +} + +static void anp_cubicTo(ANPPath* path, float x0, float y0, + float x1, float y1, float x2, float y2) { + path->cubicTo(SkFloatToScalar(x0), SkFloatToScalar(y0), + SkFloatToScalar(x1), SkFloatToScalar(y1), + SkFloatToScalar(x2), SkFloatToScalar(y2)); +} + +static void anp_close(ANPPath* path) { + path->close(); +} + +static void anp_offset(ANPPath* path, float dx, float dy, ANPPath* dst) { + path->offset(SkFloatToScalar(dx), SkFloatToScalar(dy), dst); +} + +static void anp_transform(ANPPath* src, const ANPMatrix* matrix, + ANPPath* dst) { + src->transform(*matrix, dst); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPPathInterfaceV0_Init(ANPInterface* value) { + ANPPathInterfaceV0* i = reinterpret_cast<ANPPathInterfaceV0*>(value); + + ASSIGN(i, newPath); + ASSIGN(i, deletePath); + ASSIGN(i, copy); + ASSIGN(i, equal); + ASSIGN(i, reset); + ASSIGN(i, isEmpty); + ASSIGN(i, getBounds); + ASSIGN(i, moveTo); + ASSIGN(i, lineTo); + ASSIGN(i, quadTo); + ASSIGN(i, cubicTo); + ASSIGN(i, close); + ASSIGN(i, offset); + ASSIGN(i, transform); +} diff --git a/Source/WebKit/android/plugins/ANPSoundInterface.cpp b/Source/WebKit/android/plugins/ANPSoundInterface.cpp new file mode 100644 index 0000000..c238872 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPSoundInterface.cpp @@ -0,0 +1,163 @@ +/* + * Copyright 2008, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "android_npapi.h" + +#include "SkTypes.h" +#include "media/AudioTrack.h" + +#include <system/audio.h> + +struct ANPAudioTrack { + void* mUser; + ANPAudioCallbackProc mProc; + android::AudioTrack* mTrack; +}; + +static ANPSampleFormat toANPFormat(int fm) { + switch (fm) { + case AUDIO_FORMAT_PCM_16_BIT: + return kPCM16Bit_ANPSampleFormat; + case AUDIO_FORMAT_PCM_8_BIT: + return kPCM8Bit_ANPSampleFormat; + default: + return kUnknown_ANPSamleFormat; + } +} + +static int fromANPFormat(ANPSampleFormat fm) { + switch (fm) { + case kPCM16Bit_ANPSampleFormat: + return AUDIO_FORMAT_PCM_16_BIT; + case kPCM8Bit_ANPSampleFormat: + return AUDIO_FORMAT_PCM_8_BIT; + default: + return AUDIO_FORMAT_INVALID; + } +} + +static void callbackProc(int event, void* user, void* info) { + ANPAudioTrack* track = reinterpret_cast<ANPAudioTrack*>(user); + + switch (event) { + case android::AudioTrack::EVENT_MORE_DATA: { + ANPAudioBuffer dst; + android::AudioTrack::Buffer* src; + + src = reinterpret_cast<android::AudioTrack::Buffer*>(info); + dst.bufferData = src->raw; + dst.channelCount = src->channelCount; + dst.format = toANPFormat(src->format); + dst.size = src->size; + track->mProc(kMoreData_ANPAudioEvent, track->mUser, &dst); + // return the updated size field + src->size = dst.size; + break; + } + case android::AudioTrack::EVENT_UNDERRUN: + track->mProc(kUnderRun_ANPAudioEvent, track->mUser, NULL); + break; + default: + SkDebugf("------ unknown audio event for plugin %d\n", event); + break; + } +} + +static ANPAudioTrack* ANPCreateTrack(uint32_t sampleRate, + ANPSampleFormat format, + int channelCount, + ANPAudioCallbackProc proc, + void* user) { + + ANPAudioTrack* track = new ANPAudioTrack; + + track->mUser = user; + track->mProc = proc; + track->mTrack = new android::AudioTrack(AUDIO_STREAM_MUSIC, + sampleRate, + fromANPFormat(format), + (channelCount > 1) ? AUDIO_CHANNEL_OUT_STEREO : AUDIO_CHANNEL_OUT_MONO, + 0, // frameCount + 0, // flags + callbackProc, + track, + 0); + + if (track->mTrack->initCheck() != 0) { // failure + delete track->mTrack; + delete track; + track = NULL; + } + return track; +} + +static void ANPDeleteTrack(ANPAudioTrack* track) { + if (track) { + delete track->mTrack; + delete track; + } +} + +static void ANPTrackStart(ANPAudioTrack* track) { + track->mTrack->start(); +} + +static void ANPTrackPause(ANPAudioTrack* track) { + track->mTrack->pause(); +} + +static void ANPTrackStop(ANPAudioTrack* track) { + track->mTrack->stop(); +} + +static bool ANPTrackIsStopped(ANPAudioTrack* track) { + return track->mTrack->stopped(); +} + +static uint32_t ANPTrackLatency(ANPAudioTrack* track) { + return track->mTrack->latency(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void ANPAudioTrackInterfaceV0_Init(ANPInterface* value) { + ANPAudioTrackInterfaceV0* si = reinterpret_cast<ANPAudioTrackInterfaceV0*>(value); + si->newTrack = ANPCreateTrack; + si->deleteTrack = ANPDeleteTrack; + si->start = ANPTrackStart; + si->pause = ANPTrackPause; + si->stop = ANPTrackStop; + si->isStopped = ANPTrackIsStopped; +} + +void ANPAudioTrackInterfaceV1_Init(ANPInterface* value) { + // initialize the functions from the previous interface + ANPAudioTrackInterfaceV0_Init(value); + // add any new functions or override existing functions + ANPAudioTrackInterfaceV1* si = reinterpret_cast<ANPAudioTrackInterfaceV1*>(value); + si->trackLatency = ANPTrackLatency; +} diff --git a/Source/WebKit/android/plugins/ANPSurfaceInterface.cpp b/Source/WebKit/android/plugins/ANPSurfaceInterface.cpp new file mode 100644 index 0000000..4b99b31 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPSurfaceInterface.cpp @@ -0,0 +1,174 @@ +/* + * Copyright 2009, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "ANPSurface_npapi.h" + +#include "PluginView.h" +#include "PluginWidgetAndroid.h" +#include "SkANP.h" +#include "android_graphics.h" +#include <JNIUtility.h> +#include <surfaceflinger/Surface.h> +#include <ui/Rect.h> +#include <ui/Region.h> +#include <utils/RefBase.h> + +using namespace android; + +// used to cache JNI method and field IDs for Surface Objects +static struct ANPSurfaceInterfaceJavaGlue { + bool initialized; + jmethodID getSurfaceHolder; + jmethodID getSurface; + jfieldID surfacePointer; +} gSurfaceJavaGlue; + +static inline sp<Surface> getSurface(JNIEnv* env, jobject view) { + if (!env || !view) { + return NULL; + } + + if (!gSurfaceJavaGlue.initialized) { + + jclass surfaceViewClass = env->FindClass("android/view/SurfaceView"); + gSurfaceJavaGlue.getSurfaceHolder = env->GetMethodID(surfaceViewClass, "getHolder", + "()Landroid/view/SurfaceHolder;"); + + jclass surfaceHolderClass = env->FindClass("android/view/SurfaceHolder"); + gSurfaceJavaGlue.getSurface = env->GetMethodID(surfaceHolderClass, "getSurface", + "()Landroid/view/Surface;"); + + jclass surfaceClass = env->FindClass("android/view/Surface"); + gSurfaceJavaGlue.surfacePointer = env->GetFieldID(surfaceClass, + ANDROID_VIEW_SURFACE_JNI_ID, "I"); + + env->DeleteLocalRef(surfaceClass); + env->DeleteLocalRef(surfaceViewClass); + env->DeleteLocalRef(surfaceHolderClass); + + gSurfaceJavaGlue.initialized = true; + } + + jobject holder = env->CallObjectMethod(view, gSurfaceJavaGlue.getSurfaceHolder); + jobject surface = env->CallObjectMethod(holder, gSurfaceJavaGlue.getSurface); + jint surfacePointer = env->GetIntField(surface, gSurfaceJavaGlue.surfacePointer); + + env->DeleteLocalRef(holder); + env->DeleteLocalRef(surface); + + return sp<Surface>((Surface*) surfacePointer); +} + +static inline ANPBitmapFormat convertPixelFormat(PixelFormat format) { + switch (format) { + case PIXEL_FORMAT_RGBA_8888: return kRGBA_8888_ANPBitmapFormat; + case PIXEL_FORMAT_RGB_565: return kRGB_565_ANPBitmapFormat; + default: return kUnknown_ANPBitmapFormat; + } +} + +static bool anp_lock(JNIEnv* env, jobject surfaceView, ANPBitmap* bitmap, ANPRectI* dirtyRect) { + if (!bitmap || !surfaceView) { + return false; + } + + sp<Surface> surface = getSurface(env, surfaceView); + + if (!bitmap || !Surface::isValid(surface)) { + return false; + } + + Region dirtyRegion; + if (dirtyRect) { + Rect rect(dirtyRect->left, dirtyRect->top, dirtyRect->right, dirtyRect->bottom); + if (!rect.isEmpty()) { + dirtyRegion.set(rect); + } + } else { + dirtyRegion.set(Rect(0x3FFF, 0x3FFF)); + } + + Surface::SurfaceInfo info; + status_t err = surface->lock(&info, &dirtyRegion); + if (err < 0) { + return false; + } + + // the surface may have expanded the dirty region so we must to pass that + // information back to the plugin. + if (dirtyRect) { + Rect dirtyBounds = dirtyRegion.getBounds(); + dirtyRect->left = dirtyBounds.left; + dirtyRect->right = dirtyBounds.right; + dirtyRect->top = dirtyBounds.top; + dirtyRect->bottom = dirtyBounds.bottom; + } + + ssize_t bpr = info.s * bytesPerPixel(info.format); + + bitmap->format = convertPixelFormat(info.format); + bitmap->width = info.w; + bitmap->height = info.h; + bitmap->rowBytes = bpr; + + if (info.w > 0 && info.h > 0) { + bitmap->baseAddr = info.bits; + } else { + bitmap->baseAddr = NULL; + return false; + } + + return true; +} + +static void anp_unlock(JNIEnv* env, jobject surfaceView) { + if (!surfaceView) { + return; + } + + sp<Surface> surface = getSurface(env, surfaceView); + + if (!Surface::isValid(surface)) { + return; + } + + surface->unlockAndPost(); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPSurfaceInterfaceV0_Init(ANPInterface* value) { + ANPSurfaceInterfaceV0* i = reinterpret_cast<ANPSurfaceInterfaceV0*>(value); + + ASSIGN(i, lock); + ASSIGN(i, unlock); + + // setup the java glue struct + gSurfaceJavaGlue.initialized = false; +} diff --git a/Source/WebKit/android/plugins/ANPSurface_npapi.h b/Source/WebKit/android/plugins/ANPSurface_npapi.h new file mode 100644 index 0000000..910a948 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPSurface_npapi.h @@ -0,0 +1,48 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef ANPSurface_npapi_H +#define ANPSurface_npapi_H + +#include "android_npapi.h" +#include <jni.h> + +struct ANPSurfaceInterfaceV0 : ANPInterface { + /** Locks the surface from manipulation by other threads and provides a bitmap + to be written to. The dirtyRect param specifies which portion of the + bitmap will be written to. If the dirtyRect is NULL then the entire + surface will be considered dirty. If the lock was successful the function + will return true and the bitmap will be set to point to a valid bitmap. + If not the function will return false and the bitmap will be set to NULL. + */ + bool (*lock)(JNIEnv* env, jobject surface, ANPBitmap* bitmap, ANPRectI* dirtyRect); + /** Given a locked surface handle (i.e. result of a successful call to lock) + the surface is unlocked and the contents of the bitmap, specifically + those inside the dirtyRect are written to the screen. + */ + void (*unlock)(JNIEnv* env, jobject surface); +}; + +#endif //ANPSurface_npapi_H diff --git a/Source/WebKit/android/plugins/ANPSystemInterface.cpp b/Source/WebKit/android/plugins/ANPSystemInterface.cpp new file mode 100644 index 0000000..7199635 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPSystemInterface.cpp @@ -0,0 +1,222 @@ +/* + * Copyright 2009, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" + +#include "ANPSystem_npapi.h" +#include "Frame.h" +#include "JavaSharedClient.h" +#include "PluginClient.h" +#include "PluginPackage.h" +#include "PluginView.h" +#include "PluginWidgetAndroid.h" +#include "Settings.h" +#include "SkString.h" +#include "WebViewCore.h" +#include <wtf/text/CString.h> + +#include <dirent.h> + +//#define PLUGIN_DEBUG_LOCAL // controls the printing of log messages +#include "PluginDebugAndroid.h" + +static const char* gApplicationDataDir = NULL; +static const char* gApplicationDataDirIncognito = NULL; + +using namespace android; + +static WebCore::PluginView* pluginViewForInstance(NPP instance) { + if (instance && instance->ndata) + return static_cast<WebCore::PluginView*>(instance->ndata); + return WebCore::PluginView::currentPluginView(); +} + +static const char* anp_getApplicationDataDirectory() { + if (NULL == gApplicationDataDir) { + PluginClient* client = JavaSharedClient::GetPluginClient(); + if (!client) + return NULL; + + WTF::String path = client->getPluginSharedDataDirectory(); + int length = path.length(); + if (length == 0) + return NULL; + + char* storage = (char*) malloc(length + 1); + if (NULL == storage) + return NULL; + + memcpy(storage, path.utf8().data(), length); + storage[length] = '\0'; + + static const char incognitoPath[] = "/incognito_plugins"; + char* incognitoStorage = (char*) malloc(length + strlen(incognitoPath) + 1); + + strcpy(incognitoStorage, storage); + strcat(incognitoStorage, incognitoPath); + + // save this assignment for last, so that if multiple threads call us + // (which should never happen), we never return an incomplete global. + // At worst, we would allocate storage for the path twice. + gApplicationDataDir = storage; + gApplicationDataDirIncognito = incognitoStorage; + } + + return gApplicationDataDir; +} + +static const char* anp_getApplicationDataDirectoryV2(NPP instance) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + + if (NULL == gApplicationDataDir) { + anp_getApplicationDataDirectory(); + } + + WebCore::Settings* settings = pluginWidget->webViewCore()->mainFrame()->settings(); + if (settings && settings->privateBrowsingEnabled()) { + // if this is an incognito view then check the path to see if it exists + // and if it is a directory, otherwise if it does not exist create it. + struct stat st; + if (stat(gApplicationDataDirIncognito, &st) == 0) { + if (!S_ISDIR(st.st_mode)) { + return NULL; + } + } else { + if (mkdir(gApplicationDataDirIncognito, S_IRWXU) != 0) { + return NULL; + } + } + + return gApplicationDataDirIncognito; + } + + return gApplicationDataDir; +} + +static jclass anp_loadJavaClass(NPP instance, const char* className) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + + jclass result; + result = pluginWidget->webViewCore()->getPluginClass(pluginView->plugin()->path(), + className); + return result; +} + +static void anp_setPowerState(NPP instance, ANPPowerState powerState) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + + pluginWidget->setPowerState(powerState); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPSystemInterfaceV0_Init(ANPInterface* v) { + ANPSystemInterfaceV0* i = reinterpret_cast<ANPSystemInterfaceV0*>(v); + + ASSIGN(i, getApplicationDataDirectory); + ASSIGN(i, loadJavaClass); +} + +void ANPSystemInterfaceV1_Init(ANPInterface* v) { + // initialize the functions from the previous interface + ANPSystemInterfaceV0_Init(v); + // add any new functions or override existing functions + ANPSystemInterfaceV1* i = reinterpret_cast<ANPSystemInterfaceV1*>(v); + ASSIGN(i, setPowerState); +} + +void ANPSystemInterfaceV2_Init(ANPInterface* v) { + // initialize the functions from the previous interface + ANPSystemInterfaceV1_Init(v); + // add any new functions or override existing functions + ANPSystemInterfaceV2* i = reinterpret_cast<ANPSystemInterfaceV2*>(v); + i->getApplicationDataDirectory = anp_getApplicationDataDirectoryV2; +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool isDirectory(const char* path) { + struct stat st; + return stat(path, &st) == 0 && S_ISDIR(st.st_mode); +} + +static void removeDirectory(const char* path) { + // create a pointer to a directory + DIR *dir = NULL; + dir = opendir(path); + if (!dir) + return; + + struct dirent* entry = 0; + while ((entry = readdir(dir))) { // while there is still something in the directory to list + if (!entry) + return; + + if (!strcmp(".", entry->d_name) || !strcmp("..", entry->d_name)) { + PLUGIN_LOG(". file: %s", entry->d_name); + continue; + } + + // concatenate the strings to get the complete path + static const char separator[] = "/"; + char* file = (char*) malloc(strlen(path) + strlen(separator) + strlen(entry->d_name) + 1); + strcpy(file, path); + strcat(file, separator); + strcat(file, entry->d_name); + + if (isDirectory(file) == true) { + PLUGIN_LOG("remove dir: %s", file); + removeDirectory(file); + } else { // it's a file, we can use remove + PLUGIN_LOG("remove file: %s", file); + remove(file); + } + + free(file); + } + + // clean up + closedir (dir); // close the directory + rmdir(path); // delete the directory +} + +void ANPSystemInterface_CleanupIncognito() { + PLUGIN_LOG("cleanup incognito plugin directory"); + + if (gApplicationDataDirIncognito == NULL) + anp_getApplicationDataDirectory(); + if (gApplicationDataDirIncognito == NULL) + return; + + // check to see if the directory exists and if so delete it + if (isDirectory(gApplicationDataDirIncognito)) + removeDirectory(gApplicationDataDirIncognito); +} diff --git a/Source/WebKit/android/plugins/ANPSystem_npapi.h b/Source/WebKit/android/plugins/ANPSystem_npapi.h new file mode 100644 index 0000000..835bc7c --- /dev/null +++ b/Source/WebKit/android/plugins/ANPSystem_npapi.h @@ -0,0 +1,72 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef ANPSystem_npapi_H +#define ANPSystem_npapi_H + +#include "android_npapi.h" +#include <jni.h> + +struct ANPSystemInterfaceV0 : ANPInterface { + /** Return the path name for the current Application's plugin data directory, + or NULL if not supported + */ + const char* (*getApplicationDataDirectory)(); + + /** A helper function to load java classes from the plugin's apk. The + function looks for a class given the fully qualified and null terminated + string representing the className. For example, + + const char* className = "com.android.mypackage.MyClass"; + + If the class cannot be found or there is a problem loading the class + NULL will be returned. + */ + jclass (*loadJavaClass)(NPP instance, const char* className); +}; + +enum ANPPowerStates { + kDefault_ANPPowerState = 0, + kScreenOn_ANPPowerState = 1 +}; +typedef int32_t ANPPowerState; + +struct ANPSystemInterfaceV1 : ANPSystemInterfaceV0 { + void (*setPowerState)(NPP instance, ANPPowerState powerState); +}; + +struct ANPSystemInterfaceV2 : ANPInterface { + /** Return the path name for the current Application's plugin data directory, + or NULL if not supported. This directory will change depending on whether + or not the plugin is found within an incognito tab. + */ + const char* (*getApplicationDataDirectory)(NPP instance); + + // redeclaration of existing features + jclass (*loadJavaClass)(NPP instance, const char* className); + void (*setPowerState)(NPP instance, ANPPowerState powerState); +}; + +#endif //ANPSystem_npapi_H diff --git a/Source/WebKit/android/plugins/ANPTypefaceInterface.cpp b/Source/WebKit/android/plugins/ANPTypefaceInterface.cpp new file mode 100644 index 0000000..99734a7 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPTypefaceInterface.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2008, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" +#include "SkFontHost.h" + +static ANPTypeface* anp_createFromName(const char name[], ANPTypefaceStyle s) { + SkTypeface* tf = SkTypeface::CreateFromName(name, + static_cast<SkTypeface::Style>(s)); + return reinterpret_cast<ANPTypeface*>(tf); +} + +static ANPTypeface* anp_createFromTypeface(const ANPTypeface* family, + ANPTypefaceStyle s) { + SkTypeface* tf = SkTypeface::CreateFromTypeface(family, + static_cast<SkTypeface::Style>(s)); + return reinterpret_cast<ANPTypeface*>(tf); +} + +static int32_t anp_getRefCount(const ANPTypeface* tf) { + return tf ? tf->getRefCnt() : 0; +} + +static void anp_ref(ANPTypeface* tf) { + SkSafeRef(tf); +} + +static void anp_unref(ANPTypeface* tf) { + SkSafeUnref(tf); +} + +static ANPTypefaceStyle anp_getStyle(const ANPTypeface* tf) { + SkTypeface::Style s = tf ? tf->style() : SkTypeface::kNormal; + return static_cast<ANPTypefaceStyle>(s); +} + +static int32_t anp_getFontPath(const ANPTypeface* tf, char fileName[], + int32_t length, int32_t* index) { + size_t size = SkFontHost::GetFileName(SkTypeface::UniqueID(tf), fileName, + length, index); + return static_cast<int32_t>(size); +} + +static const char* gFontDir; +#define FONT_DIR_SUFFIX "/fonts/" + +static const char* anp_getFontDirectoryPath() { + if (NULL == gFontDir) { + const char* root = getenv("ANDROID_ROOT"); + size_t len = strlen(root); + char* storage = (char*)malloc(len + sizeof(FONT_DIR_SUFFIX)); + if (NULL == storage) { + return NULL; + } + memcpy(storage, root, len); + memcpy(storage + len, FONT_DIR_SUFFIX, sizeof(FONT_DIR_SUFFIX)); + // save this assignment for last, so that if multiple threads call us + // (which should never happen), we never return an incomplete global. + // At worst, we would allocate storage for the path twice. + gFontDir = storage; + } + return gFontDir; +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPTypefaceInterfaceV0_Init(ANPInterface* v) { + ANPTypefaceInterfaceV0* i = reinterpret_cast<ANPTypefaceInterfaceV0*>(v); + + ASSIGN(i, createFromName); + ASSIGN(i, createFromTypeface); + ASSIGN(i, getRefCount); + ASSIGN(i, ref); + ASSIGN(i, unref); + ASSIGN(i, getStyle); + ASSIGN(i, getFontPath); + ASSIGN(i, getFontDirectoryPath); +} diff --git a/Source/WebKit/android/plugins/ANPVideoInterface.cpp b/Source/WebKit/android/plugins/ANPVideoInterface.cpp new file mode 100644 index 0000000..8eb9846 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPVideoInterface.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2011, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "ANPVideo_npapi.h" +#include "SkANP.h" + +#include "PluginView.h" +#include "PluginWidgetAndroid.h" +#include "MediaLayer.h" + +static WebCore::PluginView* pluginViewForInstance(NPP instance) { + if (instance && instance->ndata) + return static_cast<WebCore::PluginView*>(instance->ndata); + return WebCore::PluginView::currentPluginView(); +} + +static WebCore::MediaLayer* mediaLayerForInstance(NPP instance) { + WebCore::PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + return pluginWidget->getLayer(); +} + +static ANativeWindow* anp_acquireNativeWindow(NPP instance) { + WebCore::MediaLayer* mediaLayer = mediaLayerForInstance(instance); + + return mediaLayer->acquireNativeWindowForVideo(); +} + +static void anp_setWindowDimensions(NPP instance, const ANativeWindow* window, + const ANPRectF* dimensions) { + + WebCore::MediaLayer* mediaLayer = mediaLayerForInstance(instance); + if (!mediaLayer) + return; + + SkRect rect; + mediaLayer->setWindowDimensionsForVideo(window, *SkANP::SetRect(&rect, *dimensions)); +} + + +static void anp_releaseNativeWindow(NPP instance, ANativeWindow* window) { + WebCore::MediaLayer* mediaLayer = mediaLayerForInstance(instance); + if (!mediaLayer) + return; + + mediaLayer->releaseNativeWindowForVideo(window); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPVideoInterfaceV0_Init(ANPInterface* value) { + ANPVideoInterfaceV0* i = reinterpret_cast<ANPVideoInterfaceV0*>(value); + + ASSIGN(i, acquireNativeWindow); + ASSIGN(i, setWindowDimensions); + ASSIGN(i, releaseNativeWindow); +} diff --git a/Source/WebKit/android/plugins/ANPVideo_npapi.h b/Source/WebKit/android/plugins/ANPVideo_npapi.h new file mode 100644 index 0000000..18e0231 --- /dev/null +++ b/Source/WebKit/android/plugins/ANPVideo_npapi.h @@ -0,0 +1,61 @@ +/* + * Copyright 2011, 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. + */ + +#ifndef ANPVideo_npapi_H +#define ANPVideo_npapi_H + +#include "android_npapi.h" +#include <android/native_window.h> + +struct ANPVideoInterfaceV0 : ANPInterface { + + /** + * Constructs a new native window to be used for rendering video content. + * + * Subsequent calls will produce new windows, but may also return NULL after + * n attempts if the browser has reached it's limit. Further, if the browser + * is unable to acquire the window quickly it may also return NULL in order + * to not prevent the plugin from executing. A subsequent call will then + * return the window if it is avaiable. + * + * NOTE: The hardware may fail if you try to decode more than the allowable + * number of videos supported on that device. + */ + ANativeWindow* (*acquireNativeWindow)(NPP instance); + + /** + * Sets the rectangle that specifies where the video content is to be drawn. + * The dimensions are in document space. Further, if the rect is NULL the + * browser will not attempt to draw the window, therefore do not set the + * dimensions until you queue the first buffer in the window. + */ + void (*setWindowDimensions)(NPP instance, const ANativeWindow* window, const ANPRectF* dimensions); + + /** + */ + void (*releaseNativeWindow)(NPP instance, ANativeWindow* window); +}; + +#endif //ANPVideo_npapi_H diff --git a/Source/WebKit/android/plugins/ANPWindowInterface.cpp b/Source/WebKit/android/plugins/ANPWindowInterface.cpp new file mode 100644 index 0000000..a74616c --- /dev/null +++ b/Source/WebKit/android/plugins/ANPWindowInterface.cpp @@ -0,0 +1,103 @@ +/* + * Copyright 2008, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" +#include "WebViewCore.h" +#include "PluginView.h" +#include "PluginWidgetAndroid.h" + +static PluginView* pluginViewForInstance(NPP instance) { + if (instance && instance->ndata) + return static_cast<PluginView*>(instance->ndata); + return PluginView::currentPluginView(); +} + +static void anp_setVisibleRects(NPP instance, const ANPRectI rects[], int32_t count) { + PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + pluginWidget->setVisibleRects(rects, count); +} + +static void anp_clearVisibleRects(NPP instance) { + anp_setVisibleRects(instance, NULL, 0); +} + +static void anp_showKeyboard(NPP instance, bool value) { + PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + if(pluginWidget->hasFocus()) + pluginWidget->webViewCore()->requestKeyboard(value); +} + +static void anp_requestFullScreen(NPP instance) { + PluginView* pluginView = pluginViewForInstance(instance); + // call focusPluginElement() so that the pluginView receives keyboard events + pluginView->focusPluginElement(); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + pluginWidget->requestFullScreen(); +} + +static void anp_exitFullScreen(NPP instance) { + PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + pluginWidget->exitFullScreen(true); +} + +static void anp_requestCenterFitZoom(NPP instance) { + PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + pluginWidget->requestCenterFitZoom(); +} + +static ANPRectI anp_visibleRect(NPP instance) { + PluginView* pluginView = pluginViewForInstance(instance); + PluginWidgetAndroid* pluginWidget = pluginView->platformPluginWidget(); + return pluginWidget->visibleRect(); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPWindowInterfaceV0_Init(ANPInterface* value) { + ANPWindowInterfaceV0* i = reinterpret_cast<ANPWindowInterfaceV0*>(value); + + ASSIGN(i, setVisibleRects); + ASSIGN(i, clearVisibleRects); + ASSIGN(i, showKeyboard); + ASSIGN(i, requestFullScreen); + ASSIGN(i, exitFullScreen); + ASSIGN(i, requestCenterFitZoom); +} + +void ANPWindowInterfaceV1_Init(ANPInterface* value) { + // initialize the functions from the previous interface + ANPWindowInterfaceV0_Init(value); + // add any new functions or override existing functions + ANPWindowInterfaceV1* i = reinterpret_cast<ANPWindowInterfaceV1*>(value); + ASSIGN(i, visibleRect); +} diff --git a/Source/WebKit/android/plugins/PluginDebugAndroid.cpp b/Source/WebKit/android/plugins/PluginDebugAndroid.cpp new file mode 100644 index 0000000..3958714 --- /dev/null +++ b/Source/WebKit/android/plugins/PluginDebugAndroid.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2010, 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. + */ + +#include "PluginDebugAndroid.h" +#include "utils/Log.h" +#include "utils/SystemClock.h" +#include <stdarg.h> + +#define ARRAY_COUNT(array) static_cast<int32_t>(sizeof(array) / sizeof(array[0])) + +// used for key, mouse, and touch inputs +static const char* const inputActions[] = { + "down", + "up", + "move", /* touch only */ + "cancel", /* touch only */ + "longPress", /* touch only */ + "doubleTap" /* touch only */ +}; + +static const char* const lifecycleActions[] = { + "kPause_ANPLifecycleAction", + "kResume_ANPLifecycleAction", + "kGainFocus_ANPLifecycleAction", + "kLoseFocus_ANPLifecycleAction", + "kFreeMemory_ANPLifecycleAction", + "kOnLoad_ANPLifecycleAction", + "kEnterFullScreen_ANPLifecycleAction", + "kExitFullScreen_ANPLifecycleAction", + "kOnScreen_ANPLifecycleAction", + "kOffScreen_ANPLifecycleAction" +}; + +void anp_logPlugin(const char format[], ...) { + va_list args; + va_start(args, format); + LOG_PRI_VA(ANDROID_LOG_DEBUG, "webkit_plugin", format, args); + va_end(args); +} + +void anp_logPluginEvent(void* npp, const ANPEvent* evt, int16_t returnVal, int elapsedTime) { + + switch(evt->eventType) { + + case kNull_ANPEventType: + PLUGIN_LOG("%p EVENT::NULL", npp); + break; + + case kKey_ANPEventType: + if(evt->data.key.action < ARRAY_COUNT(inputActions)) { + anp_logPlugin("%p EVENT::KEY[%d] time=%d action=%s code=%d vcode=%d unichar=%d repeat=%d mods=%x", + npp, returnVal, elapsedTime, inputActions[evt->data.key.action], + evt->data.key.nativeCode, evt->data.key.virtualCode, + evt->data.key.unichar, evt->data.key.repeatCount, + evt->data.key.modifiers); + } else { + PLUGIN_LOG("%p EVENT::KEY[%d] unknown action", npp, returnVal); + } + break; + + case kMouse_ANPEventType: + if(evt->data.mouse.action < ARRAY_COUNT(inputActions)) { + anp_logPlugin("%p EVENT::MOUSE[%d] time=%d action=%s [%d %d]", npp, + returnVal, elapsedTime, inputActions[evt->data.mouse.action], + evt->data.touch.x, evt->data.touch.y); + } else { + anp_logPlugin("%p EVENT::MOUSE[%d] unknown action", npp, returnVal); + } + break; + + case kTouch_ANPEventType: + if(evt->data.touch.action < ARRAY_COUNT(inputActions)) { + + anp_logPlugin("%p EVENT::TOUCH[%d] time=%d action=%s [%d %d]", + npp, returnVal, elapsedTime, + inputActions[evt->data.touch.action], evt->data.touch.x, + evt->data.touch.y); + } else { + anp_logPlugin("%p EVENT::TOUCH[%d] unknown action", npp, returnVal); + } + break; + + case kDraw_ANPEventType: + if (evt->data.draw.model == kBitmap_ANPDrawingModel) { + anp_logPlugin("%p EVENT::DRAW bitmap time=%d format=%d clip=[%d,%d,%d,%d]", + npp, elapsedTime, evt->data.draw.data.bitmap.format, + evt->data.draw.clip.left, evt->data.draw.clip.top, + evt->data.draw.clip.right, evt->data.draw.clip.bottom); + } else if (evt->data.draw.model == kOpenGL_ANPDrawingModel) { + anp_logPlugin("%p EVENT::DRAW openGL time=%d dimensions=[%d,%d]", + npp, elapsedTime, evt->data.draw.data.surface.width, + evt->data.draw.data.surface.height); + } else { + anp_logPlugin("%p EVENT::DRAW unknown drawing model", npp); + } + break; + + case kLifecycle_ANPEventType: + if(evt->data.lifecycle.action < ARRAY_COUNT(lifecycleActions)) { + anp_logPlugin("%p EVENT::LIFECYCLE time=%d action=%s", npp, elapsedTime, + lifecycleActions[evt->data.lifecycle.action]); + } else { + anp_logPlugin("%p EVENT::LIFECYCLE unknown action", npp); + } + break; + + case kCustom_ANPEventType: + anp_logPlugin("%p EVENT::CUSTOM time=%d", npp, elapsedTime); + break; + + default: + anp_logPlugin("%p EVENT::UNKNOWN", npp); + break; + } +} diff --git a/Source/WebKit/android/plugins/PluginDebugAndroid.h b/Source/WebKit/android/plugins/PluginDebugAndroid.h new file mode 100644 index 0000000..5002882 --- /dev/null +++ b/Source/WebKit/android/plugins/PluginDebugAndroid.h @@ -0,0 +1,58 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef PLUGIN_DEBUG_ANDROID_H__ +#define PLUGIN_DEBUG_ANDROID_H__ + +#include "android_npapi.h" + +// Define PLUGIN_DEBUG_LOCAL in an individual C++ file to enable for +// that file only. + +// Define PLUGIN_DEBUG_GLOBAL to 1 to turn plug-in debug for all +// Android plug-in code in this directory. +#define PLUGIN_DEBUG_GLOBAL 0 + +#if PLUGIN_DEBUG_GLOBAL || defined(PLUGIN_DEBUG_LOCAL) +# define PLUGIN_LOG(FORMAT, ARGS...) do { anp_logPlugin(FORMAT, ## ARGS); } while(0) +# define PLUGIN_LOG_EVENT(NPP, EVT, RET, TIME) do { anp_logPluginEvent(NPP, EVT, RET, TIME); } while(0) + +/* Logs the given character array and optional arguments. All log entries use + the DEBUG priority and use the same "webkit_plugin" log tag. + */ +void anp_logPlugin(const char format[], ...); +/* Logs a user readable description of a plugin event. The relevant contents of + each event are logged, as well as the value returned by the plugin instance + and how long the instance took to process the event (in milliseconds). + */ +void anp_logPluginEvent(void* npp, const ANPEvent* event, int16_t returnVal, int elapsedTime); + +#else +# define PLUGIN_LOG(A, B...) do { } while(0) +# define PLUGIN_LOG_EVENT(NPP, EVT, RET, TIME) do { } while(0) + +#endif + +#endif // defined(PLUGIN_DEBUG_ANDROID_H__) diff --git a/Source/WebKit/android/plugins/PluginTimer.cpp b/Source/WebKit/android/plugins/PluginTimer.cpp new file mode 100644 index 0000000..dfa7272 --- /dev/null +++ b/Source/WebKit/android/plugins/PluginTimer.cpp @@ -0,0 +1,135 @@ +/* + * Copyright 2009, The Android Open Source Project + * Copyright (C) 2008 Google Inc. All rights reserved. + * + * 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. + */ + +#include "config.h" +#include "PluginTimer.h" +#include "RefPtr.h" + +namespace WebCore { + + static uint32_t gTimerID; + + PluginTimer::PluginTimer(PluginTimer** list, NPP instance, bool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) + : m_list(list), + m_instance(instance), + m_timerFunc(timerFunc), + m_repeat(repeat), + m_unscheduled(false) + { + m_timerID = ++gTimerID; + + m_next = *list; + if (m_next) { + m_next->m_prev = this; + } + m_prev = 0; + *list = this; + relaxAdoptionRequirement(); + } + + PluginTimer::~PluginTimer() + { + if (m_next) { + m_next->m_prev = m_prev; + } + if (m_prev) { + m_prev->m_next = m_next; + } else { + *m_list = m_next; + } + } + + void PluginTimer::fired() + { + // ensure the timer cannot be deleted until this method completes + RefPtr<PluginTimer> protector(this); + + if (!m_unscheduled) + m_timerFunc(m_instance, m_timerID); + + // remove the timer if it is a one-shot timer (!m_repeat) or if is a + // repeating timer that has been unscheduled. In either case we must + // ensure that the refcount is 2 or greater since the PluginTimerList + // could have been deleted by the timerFunc and we must ensure that we + // do not double delete. + if ((!m_repeat || m_unscheduled) && refCount() > 1) + deref(); // mark the timer for deletion as it is no longer needed + } + + // may return null if timerID is not found + PluginTimer* PluginTimer::Find(PluginTimer* list, uint32_t timerID) + { + PluginTimer* curr = list; + while (curr) { + if (curr->m_timerID == timerID) { + break; + } + curr = curr->m_next; + } + return curr; + } + + /////////////////////////////////////////////////////////////////////////// + + PluginTimerList::~PluginTimerList() + { + PluginTimer* curr = m_list; + PluginTimer* next; + while (curr) { + next = curr->next(); + curr->deref(); + curr = next; + } + } + + uint32_t PluginTimerList::schedule(NPP instance, uint32_t interval, bool repeat, + void (*proc)(NPP npp, uint32_t timerID)) + { + PluginTimer* timer = new PluginTimer(&m_list, instance, repeat, proc); + + double dinterval = interval * 0.001; // milliseconds to seconds + if (repeat) { + timer->startRepeating(dinterval); + } else { + timer->startOneShot(dinterval); + } + return timer->timerID(); + } + + void PluginTimerList::unschedule(NPP instance, uint32_t timerID) + { + // Although it looks like simply deleting the timer would work here + // (stop() will be executed by the dtor), we cannot do this, as + // the plugin can call us while we are in the fired() method, + // (when we execute the timerFunc callback). Deleting the object + // we are in would then be a rather bad move... + PluginTimer* timer = PluginTimer::Find(m_list, timerID); + if (timer) + timer->unschedule(); + } + +} // namespace WebCore diff --git a/Source/WebKit/android/plugins/PluginTimer.h b/Source/WebKit/android/plugins/PluginTimer.h new file mode 100644 index 0000000..20c0816 --- /dev/null +++ b/Source/WebKit/android/plugins/PluginTimer.h @@ -0,0 +1,82 @@ +/* + * Copyright 2009, The Android Open Source Project + * Copyright (C) 2008 Google Inc. All rights reserved. + * + * 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. + */ + +#ifndef PluginTimer_H +#define PluginTimer_H + +#include "RefCounted.h" +#include "Timer.h" +#include "npapi.h" + +namespace WebCore { + + class PluginTimerList; + + class PluginTimer : public TimerBase, public RefCounted<PluginTimer> { + public: + PluginTimer(PluginTimer** list, NPP instance, bool repeat, + void (*proc)(NPP npp, uint32_t timerID)); + virtual ~PluginTimer(); + + uint32_t timerID() const { return m_timerID; } + + void unschedule() { m_unscheduled = true; } + + static PluginTimer* Find(PluginTimer* list, uint32_t timerID); + + private: + // override from TimerBase + virtual void fired(); + + PluginTimer* next() const { return m_next; } + friend class PluginTimerList; + + PluginTimer** m_list; + PluginTimer* m_prev; + PluginTimer* m_next; + NPP m_instance; + void (*m_timerFunc)(NPP, uint32_t); + uint32_t m_timerID; + bool m_repeat; + bool m_unscheduled; + }; + + class PluginTimerList { + public: + PluginTimerList() : m_list(0) {} + ~PluginTimerList(); + + uint32_t schedule(NPP instance, uint32_t interval, bool repeat, + void (*proc)(NPP npp, uint32_t timerID)); + void unschedule(NPP instance, uint32_t timerID); + + private: + PluginTimer* m_list; + }; + +} // namespace WebCore + +#endif diff --git a/Source/WebKit/android/plugins/PluginViewBridgeAndroid.cpp b/Source/WebKit/android/plugins/PluginViewBridgeAndroid.cpp new file mode 100644 index 0000000..2be9dc3 --- /dev/null +++ b/Source/WebKit/android/plugins/PluginViewBridgeAndroid.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2008, 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. + */ + +#include "config.h" +#include "PluginViewBridgeAndroid.h" + +namespace WebCore { + + void PluginViewBridgeAndroid::draw(GraphicsContext* gc, + const IntRect& rect) {} + + bool PluginViewBridgeAndroid::forPluginView() const { + return true; + } + +} diff --git a/Source/WebKit/android/plugins/PluginViewBridgeAndroid.h b/Source/WebKit/android/plugins/PluginViewBridgeAndroid.h new file mode 100644 index 0000000..5d16f46 --- /dev/null +++ b/Source/WebKit/android/plugins/PluginViewBridgeAndroid.h @@ -0,0 +1,48 @@ +/* + * Copyright 2009, The Android Open Source Project + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Collabora Ltd. All rights reserved. + * + * 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. + */ + +#ifndef PluginViewBridgeAndroid_H +#define PluginViewBridgeAndroid_H + +#include "PluginView.h" +#include "WebCoreViewBridge.h" + +namespace WebCore { + + // (Dummy for now) WebCoreViewBridge associated with a PluginView Widget. + class PluginViewBridgeAndroid : public WebCoreViewBridge { + public: + PluginViewBridgeAndroid() {} + + // overrides + virtual void draw(GraphicsContext* gc, const IntRect& rect); + virtual bool forPluginView() const; + }; + +} // namespace WebCore + +#endif diff --git a/Source/WebKit/android/plugins/PluginWidgetAndroid.cpp b/Source/WebKit/android/plugins/PluginWidgetAndroid.cpp new file mode 100644 index 0000000..b8a10cc --- /dev/null +++ b/Source/WebKit/android/plugins/PluginWidgetAndroid.cpp @@ -0,0 +1,690 @@ +/* + * Copyright 2008, 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. + */ + +#include "config.h" +#include "PluginWidgetAndroid.h" + +#if ENABLE(TOUCH_EVENTS) +#include "ChromeClient.h" +#endif +#include "Document.h" +#include "Element.h" +#include "Frame.h" +#include "Page.h" +#include "PluginPackage.h" +#include "PluginView.h" +#include "PluginWidgetAndroid.h" +#include "ScrollView.h" +#include "SkANP.h" +#include "SkFlipPixelRef.h" +#include "SkString.h" +#include "SkTime.h" +#include "WebViewCore.h" +#include "android_graphics.h" +#include <JNIUtility.h> + +// #define PLUGIN_DEBUG_LOCAL // controls the printing of log messages +#define DEBUG_EVENTS 0 // logs event contents, return value, and processing time +#define DEBUG_VISIBLE_RECTS 0 // temporary debug printfs and fixes + +// this include statement must follow the declaration of PLUGIN_DEBUG_LOCAL +#include "PluginDebugAndroid.h" + +PluginWidgetAndroid::PluginWidgetAndroid(WebCore::PluginView* view) + : m_pluginView(view) { + m_flipPixelRef = NULL; + m_core = NULL; + m_drawingModel = kBitmap_ANPDrawingModel; + m_eventFlags = 0; + m_pluginWindow = NULL; + m_requestedVisibleRectCount = 0; + m_requestedVisibleRect.setEmpty(); + m_visibleDocRect.setEmpty(); + m_pluginBounds.setEmpty(); + m_hasFocus = false; + m_isFullScreen = false; + m_visible = false; + m_cachedZoomLevel = 0; + m_embeddedView = NULL; + m_embeddedViewAttached = false; + m_acceptEvents = false; + m_isSurfaceClippedOut = false; + m_layer = 0; + m_powerState = kDefault_ANPPowerState; +} + +PluginWidgetAndroid::~PluginWidgetAndroid() { + PLUGIN_LOG("%p Deleting Plugin", m_pluginView->instance()); + m_acceptEvents = false; + if (m_core) { + setPowerState(kDefault_ANPPowerState); + m_core->removePlugin(this); + if (m_isFullScreen) { + exitFullScreen(true); + } + if (m_embeddedView) { + m_core->destroySurface(m_embeddedView); + } + } + + // cleanup any remaining JNI References + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (m_embeddedView) { + env->DeleteGlobalRef(m_embeddedView); + } + + SkSafeUnref(m_flipPixelRef); + + if (m_layer) + m_layer->unref(); +} + +void PluginWidgetAndroid::init(android::WebViewCore* core) { + m_core = core; + m_core->addPlugin(this); + m_acceptEvents = true; + PLUGIN_LOG("%p Initialized Plugin", m_pluginView->instance()); +} + +static SkBitmap::Config computeConfig(bool isTransparent) { + return isTransparent ? SkBitmap::kARGB_8888_Config + : SkBitmap::kRGB_565_Config; +} + +void PluginWidgetAndroid::setWindow(NPWindow* window, bool isTransparent) { + + // store the reference locally for easy lookup + m_pluginWindow = window; + + // make a copy of the previous bounds + SkIRect oldPluginBounds = m_pluginBounds; + + // keep a local copy of the plugin bounds because the m_pluginWindow pointer + // gets updated values prior to this method being called + m_pluginBounds.set(m_pluginWindow->x, m_pluginWindow->y, + m_pluginWindow->x + m_pluginWindow->width, + m_pluginWindow->y + m_pluginWindow->height); + + PLUGIN_LOG("%p PluginBounds (%d,%d,%d,%d)", m_pluginView->instance(), + m_pluginBounds.fLeft, m_pluginBounds.fTop, + m_pluginBounds.fRight, m_pluginBounds.fBottom); + + const bool boundsChanged = m_pluginBounds != oldPluginBounds; + + //TODO hack to ensure that we grab the most recent screen dimensions and scale + ANPRectI screenCoords; + m_core->getVisibleScreen(screenCoords); + float scale = m_core->scale(); + bool scaleChanged = m_cachedZoomLevel != scale; + setVisibleScreen(screenCoords, scale); + + // if the scale changed then setVisibleScreen will call this function and + // this call will potentially fire a duplicate draw event + if (!scaleChanged) { + sendSizeAndVisibilityEvents(boundsChanged); + } + layoutSurface(boundsChanged); + + if (m_drawingModel != kSurface_ANPDrawingModel) { + SkSafeUnref(m_flipPixelRef); + m_flipPixelRef = new SkFlipPixelRef(computeConfig(isTransparent), + window->width, window->height); + } +} + +bool PluginWidgetAndroid::setDrawingModel(ANPDrawingModel model) { + + if (model == kOpenGL_ANPDrawingModel && m_layer == 0) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject webview = m_core->getWebViewJavaObject(); + jobject weakWebViewRef = 0; + if (webview) + weakWebViewRef = env->NewWeakGlobalRef(webview); + m_layer = new WebCore::MediaLayer(weakWebViewRef); + } + else if (model != kOpenGL_ANPDrawingModel && m_layer != 0) { + m_layer->unref(); + m_layer = 0; + } + + if (m_drawingModel != model) { + // Trigger layer computation in RenderLayerCompositor + m_pluginView->getElement()->setNeedsStyleRecalc(SyntheticStyleChange); + } + + m_drawingModel = model; + return true; +} + +// returned rect is in the page coordinate +bool PluginWidgetAndroid::isDirty(SkIRect* rect) const { + // nothing to report if we haven't had setWindow() called yet + if (NULL == m_flipPixelRef) { + return false; + } + + const SkRegion& dirty = m_flipPixelRef->dirtyRgn(); + if (dirty.isEmpty()) { + return false; + } else { + if (rect) { + *rect = dirty.getBounds(); + rect->offset(m_pluginWindow->x, m_pluginWindow->y); + } + return true; + } +} + +void PluginWidgetAndroid::inval(const WebCore::IntRect& rect, + bool signalRedraw) { + // nothing to do if we haven't had setWindow() called yet. m_flipPixelRef + // will also be null if this is a Surface model. + if (NULL == m_flipPixelRef) { + return; + } + + m_flipPixelRef->inval(rect); + + if (signalRedraw && m_flipPixelRef->isDirty()) { + m_core->invalPlugin(this); + } +} + +void PluginWidgetAndroid::viewInvalidate() { + WebCore::IntRect rect(m_pluginBounds.fLeft, m_pluginBounds.fTop, + m_pluginBounds.width(), m_pluginBounds.height()); + m_core->viewInvalidate(rect); +} + +void PluginWidgetAndroid::draw(SkCanvas* canvas) { + if (NULL == m_flipPixelRef || !m_flipPixelRef->isDirty()) { + return; + } + + SkAutoFlipUpdate update(m_flipPixelRef); + const SkBitmap& bitmap = update.bitmap(); + const SkRegion& dirty = update.dirty(); + + ANPEvent event; + SkANP::InitEvent(&event, kDraw_ANPEventType); + + event.data.draw.model = m_drawingModel; + SkANP::SetRect(&event.data.draw.clip, dirty.getBounds()); + + switch (m_drawingModel) { + case kBitmap_ANPDrawingModel: { + WebCore::PluginPackage* pkg = m_pluginView->plugin(); + NPP instance = m_pluginView->instance(); + + if (SkANP::SetBitmap(&event.data.draw.data.bitmap, + bitmap) && + pkg->pluginFuncs()->event(instance, &event)) { + + if (canvas && m_pluginWindow) { + SkBitmap bm(bitmap); + bm.setPixelRef(m_flipPixelRef); + canvas->drawBitmap(bm, 0, 0); + } + } + break; + } + default: + break; + } +} + +void PluginWidgetAndroid::setSurfaceClip(const SkIRect& clip) { + + if (m_drawingModel != kSurface_ANPDrawingModel) + return; + + /* don't display surfaces that are either entirely clipped or only 1x1 in + size. It appears that when an element is absolutely positioned and has + been completely clipped in CSS that webkit still sends a clip of 1x1. + */ + bool clippedOut = (clip.width() <= 1 && clip.height() <= 1); + if(clippedOut != m_isSurfaceClippedOut) { + m_isSurfaceClippedOut = clippedOut; + layoutSurface(); + } +} + +void PluginWidgetAndroid::layoutSurface(bool pluginBoundsChanged) { + + if (m_drawingModel != kSurface_ANPDrawingModel) + return; + if (!m_pluginWindow) + return; + + + bool displayPlugin = m_pluginView->isVisible() && !m_isSurfaceClippedOut; + PLUGIN_LOG("%p DisplayPlugin[%d] visible=[%d] clipped=[%d]", + m_pluginView->instance(), displayPlugin, + m_pluginView->isVisible(), m_isSurfaceClippedOut); + + // if the surface does not exist then create a new surface + if (!m_embeddedView && displayPlugin) { + + WebCore::PluginPackage* pkg = m_pluginView->plugin(); + NPP instance = m_pluginView->instance(); + + jobject pluginSurface; + pkg->pluginFuncs()->getvalue(instance, kJavaSurface_ANPGetValue, + static_cast<void*>(&pluginSurface)); + + jobject tempObj = m_core->addSurface(pluginSurface, + m_pluginWindow->x, m_pluginWindow->y, + m_pluginWindow->width, m_pluginWindow->height); + + if (tempObj) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + m_embeddedView = env->NewGlobalRef(tempObj); + m_embeddedViewAttached = true; + } + // if the view is unattached but visible then attach it + } else if (m_embeddedView && !m_embeddedViewAttached && displayPlugin && !m_isFullScreen) { + m_core->updateSurface(m_embeddedView, m_pluginWindow->x, m_pluginWindow->y, + m_pluginWindow->width, m_pluginWindow->height); + m_embeddedViewAttached = true; + // if the view is attached but invisible then remove it + } else if (m_embeddedView && m_embeddedViewAttached && !displayPlugin) { + m_core->destroySurface(m_embeddedView); + m_embeddedViewAttached = false; + // if the plugin's bounds have changed and it's visible then update it + } else if (pluginBoundsChanged && displayPlugin && !m_isFullScreen) { + m_core->updateSurface(m_embeddedView, m_pluginWindow->x, m_pluginWindow->y, + m_pluginWindow->width, m_pluginWindow->height); + + } +} + +int16_t PluginWidgetAndroid::sendEvent(const ANPEvent& evt) { + if (!m_acceptEvents) + return 0; + WebCore::PluginPackage* pkg = m_pluginView->plugin(); + NPP instance = m_pluginView->instance(); + // "missing" plugins won't have these + if (pkg && instance) { + + // if the plugin is gaining focus then update our state now to allow + // the plugin's event handler to perform actions that require focus + if (evt.eventType == kLifecycle_ANPEventType && + evt.data.lifecycle.action == kGainFocus_ANPLifecycleAction) { + m_hasFocus = true; + } + +#if DEBUG_EVENTS + SkMSec startTime = SkTime::GetMSecs(); +#endif + + // make a localCopy since the actual plugin may not respect its constness, + // and so we don't want our caller to have its param modified + ANPEvent localCopy = evt; + int16_t result = pkg->pluginFuncs()->event(instance, &localCopy); + +#if DEBUG_EVENTS + SkMSec endTime = SkTime::GetMSecs(); + PLUGIN_LOG_EVENT(instance, &evt, result, endTime - startTime); +#endif + + // if the plugin is losing focus then delay the update of our state + // until after we notify the plugin and allow them to perform actions + // that may require focus + if (evt.eventType == kLifecycle_ANPEventType && + evt.data.lifecycle.action == kLoseFocus_ANPLifecycleAction) { + m_hasFocus = false; + } + + return result; + } + return 0; +} + +void PluginWidgetAndroid::updateEventFlags(ANPEventFlags flags) { + + // if there are no differences then immediately return + if (m_eventFlags == flags) { + return; + } + + Document* doc = m_pluginView->parentFrame()->document(); +#if ENABLE(TOUCH_EVENTS) + if((m_eventFlags ^ flags) & kTouch_ANPEventFlag) { + if (flags & kTouch_ANPEventFlag) + doc->addListenerTypeIfNeeded(eventNames().touchstartEvent); + } +#endif + + m_eventFlags = flags; +} + +bool PluginWidgetAndroid::isAcceptingEvent(ANPEventFlag flag) { + return m_eventFlags & flag; +} + +void PluginWidgetAndroid::sendSizeAndVisibilityEvents(const bool updateDimensions) { + // TODO update the bitmap size based on the zoom? (for kBitmap_ANPDrawingModel) + + const float zoomLevel = m_core->scale(); + + // notify the plugin of the new size + if (m_drawingModel == kOpenGL_ANPDrawingModel && updateDimensions && m_pluginWindow) { + PLUGIN_LOG("%s (%d,%d)[%f]", __FUNCTION__, m_pluginWindow->width, + m_pluginWindow->height, zoomLevel); + ANPEvent event; + SkANP::InitEvent(&event, kDraw_ANPEventType); + event.data.draw.model = kOpenGL_ANPDrawingModel; + event.data.draw.data.surface.width = m_pluginWindow->width * zoomLevel; + event.data.draw.data.surface.height = m_pluginWindow->height * zoomLevel; + sendEvent(event); + } + + bool visible = SkIRect::Intersects(m_visibleDocRect, m_pluginBounds); + if(m_visible != visible) { + +#if DEBUG_VISIBLE_RECTS + PLUGIN_LOG("%p changeVisiblity[%d] pluginBounds(%d,%d,%d,%d)", + m_pluginView->instance(), visible, + m_pluginBounds.fLeft, m_pluginBounds.fTop, + m_pluginBounds.fRight, m_pluginBounds.fBottom); +#endif + + // change the visibility + m_visible = visible; + // send the event + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = visible ? kOnScreen_ANPLifecycleAction + : kOffScreen_ANPLifecycleAction; + sendEvent(event); + } +} + +void PluginWidgetAndroid::setVisibleScreen(const ANPRectI& visibleDocRect, float zoom) { +#if DEBUG_VISIBLE_RECTS + PLUGIN_LOG("%s (%d,%d,%d,%d)[%f]", __FUNCTION__, visibleDocRect.left, + visibleDocRect.top, visibleDocRect.right, + visibleDocRect.bottom, zoom); +#endif + int oldScreenW = m_visibleDocRect.width(); + int oldScreenH = m_visibleDocRect.height(); + + const bool zoomChanged = m_cachedZoomLevel != zoom; + + // make local copies of the parameters + m_cachedZoomLevel = zoom; + m_visibleDocRect.set(visibleDocRect.left, + visibleDocRect.top, + visibleDocRect.right, + visibleDocRect.bottom); + + int newScreenW = m_visibleDocRect.width(); + int newScreenH = m_visibleDocRect.height(); + + // if the screen dimensions have changed by more than 5 pixels in either + // direction then recompute the plugin's visible rectangle + if (abs(oldScreenW - newScreenW) > 5 || abs(oldScreenH - newScreenH) > 5) { + PLUGIN_LOG("%s VisibleDoc old=[%d,%d] new=[%d,%d] ", __FUNCTION__, + oldScreenW, oldScreenH, newScreenW, newScreenH); + computeVisiblePluginRect(); + } + + sendSizeAndVisibilityEvents(zoomChanged); +} + +ANPRectI PluginWidgetAndroid::visibleRect() { + + SkIRect visibleRect; + visibleRect.setEmpty(); + + // compute the interesection of the visible screen and the plugin + bool visible = visibleRect.intersect(m_visibleDocRect, m_pluginBounds); + if (visible) { + // convert from absolute coordinates to the plugin's relative coordinates + visibleRect.offset(-m_pluginBounds.fLeft, -m_pluginBounds.fTop); + } + + // convert from SkRect to ANPRect + ANPRectI result; + memcpy(&result, &visibleRect, sizeof(ANPRectI)); + return result; +} + +void PluginWidgetAndroid::setVisibleRects(const ANPRectI rects[], int32_t count) { +#if DEBUG_VISIBLE_RECTS + PLUGIN_LOG("%s count=%d", __FUNCTION__, count); +#endif + // ensure the count does not exceed our allocated space + if (count > MAX_REQUESTED_RECTS) + count = MAX_REQUESTED_RECTS; + + // store the values in member variables + m_requestedVisibleRectCount = count; + memcpy(m_requestedVisibleRects, rects, count * sizeof(rects[0])); + +#if DEBUG_VISIBLE_RECTS // FIXME: this fixes bad data from the plugin + // take it out once plugin supplies better data + for (int index = 0; index < count; index++) { + PLUGIN_LOG("%s [%d](%d,%d,%d,%d)", __FUNCTION__, index, + m_requestedVisibleRects[index].left, + m_requestedVisibleRects[index].top, + m_requestedVisibleRects[index].right, + m_requestedVisibleRects[index].bottom); + if (m_requestedVisibleRects[index].left == + m_requestedVisibleRects[index].right) { + m_requestedVisibleRects[index].right += 1; + } + if (m_requestedVisibleRects[index].top == + m_requestedVisibleRects[index].bottom) { + m_requestedVisibleRects[index].bottom += 1; + } + } +#endif + computeVisiblePluginRect(); +} + +void PluginWidgetAndroid::computeVisiblePluginRect() { + + // ensure the visibleDocRect has been set (i.e. not equal to zero) + if (m_visibleDocRect.isEmpty() || !m_pluginWindow || m_requestedVisibleRectCount < 1) + return; + + // create a rect that will contain as many of the rects that will fit on screen + SkIRect visibleRect; + visibleRect.setEmpty(); + + for (int counter = 0; counter < m_requestedVisibleRectCount; counter++) { + + ANPRectI* rect = &m_requestedVisibleRects[counter]; + + // create skia rect for easier manipulation and convert it to page coordinates + SkIRect pluginRect; + pluginRect.set(rect->left, rect->top, rect->right, rect->bottom); + pluginRect.offset(m_pluginWindow->x, m_pluginWindow->y); + + // ensure the rect falls within the plugin's bounds + if (!m_pluginBounds.contains(pluginRect)) { +#if DEBUG_VISIBLE_RECTS + PLUGIN_LOG("%s (%d,%d,%d,%d) !contain (%d,%d,%d,%d)", __FUNCTION__, + m_pluginBounds.fLeft, m_pluginBounds.fTop, + m_pluginBounds.fRight, m_pluginBounds.fBottom, + pluginRect.fLeft, pluginRect.fTop, + pluginRect.fRight, pluginRect.fBottom); + // assume that the desired outcome is to clamp to the container + if (pluginRect.intersect(m_pluginBounds)) { + visibleRect = pluginRect; + } +#endif + continue; + } + + // combine this new rect with the higher priority rects + pluginRect.join(visibleRect); + + // check to see if the new rect could be made to fit within the screen + // bounds. If this is the highest priority rect then attempt to center + // even if it doesn't fit on the screen. + if (counter > 0 && (m_visibleDocRect.width() < pluginRect.width() || + m_visibleDocRect.height() < pluginRect.height())) + break; + + // set the new visible rect + visibleRect = pluginRect; + } + + m_requestedVisibleRect = visibleRect; + scrollToVisiblePluginRect(); +} + +void PluginWidgetAndroid::scrollToVisiblePluginRect() { + + if (!m_hasFocus || m_requestedVisibleRect.isEmpty() || m_visibleDocRect.isEmpty()) { +#if DEBUG_VISIBLE_RECTS + PLUGIN_LOG("%s call m_hasFocus=%d m_requestedVisibleRect.isEmpty()=%d" + " m_visibleDocRect.isEmpty()=%d", __FUNCTION__, m_hasFocus, + m_requestedVisibleRect.isEmpty(), m_visibleDocRect.isEmpty()); +#endif + return; + } + // if the entire rect is already visible then we don't need to scroll + if (m_visibleDocRect.contains(m_requestedVisibleRect)) + return; + + // find the center of the visibleRect in document coordinates + int rectCenterX = m_requestedVisibleRect.fLeft + m_requestedVisibleRect.width()/2; + int rectCenterY = m_requestedVisibleRect.fTop + m_requestedVisibleRect.height()/2; + + // find document coordinates for center of the visible screen + int visibleDocCenterX = m_visibleDocRect.fLeft + m_visibleDocRect.width()/2; + int visibleDocCenterY = m_visibleDocRect.fTop + m_visibleDocRect.height()/2; + + //compute the delta of the two points and scale to screen coordinates + int deltaX = rectCenterX - visibleDocCenterX; + int deltaY = rectCenterY - visibleDocCenterY; + + ScrollView* scrollView = m_pluginView->parent(); + android::WebViewCore* core = android::WebViewCore::getWebViewCore(scrollView); +#if DEBUG_VISIBLE_RECTS + PLUGIN_LOG("%s call scrollBy (%d,%d)", __FUNCTION__, deltaX, deltaY); +#endif + core->scrollTo(rectCenterX, rectCenterY, true); +} + +void PluginWidgetAndroid::requestFullScreen() { + if (m_isFullScreen) { + return; + } + + if (!m_embeddedView && m_drawingModel == kOpenGL_ANPDrawingModel) { + WebCore::PluginPackage* pkg = m_pluginView->plugin(); + NPP instance = m_pluginView->instance(); + + jobject pluginSurface; + pkg->pluginFuncs()->getvalue(instance, kJavaSurface_ANPGetValue, + static_cast<void*>(&pluginSurface)); + + // create the surface, but do not add it to the view hierarchy + jobject tempObj = m_core->createSurface(pluginSurface); + + if (tempObj) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + m_embeddedView = env->NewGlobalRef(tempObj); + m_embeddedViewAttached = false; + } + } + + if (!m_embeddedView) { + return; + } + + // send event to notify plugin of full screen change + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kEnterFullScreen_ANPLifecycleAction; + sendEvent(event); + + // remove the embedded surface from the view hierarchy + if (m_drawingModel != kOpenGL_ANPDrawingModel) + m_core->destroySurface(m_embeddedView); + + // add the full screen view + m_core->showFullScreenPlugin(m_embeddedView, m_pluginView->instance()); + m_isFullScreen = true; +} + +void PluginWidgetAndroid::exitFullScreen(bool pluginInitiated) { + if (!m_isFullScreen || !m_embeddedView) { + return; + } + + // remove the full screen surface from the view hierarchy + if (pluginInitiated) { + m_core->hideFullScreenPlugin(); + } + + // add the embedded view back + if (m_drawingModel != kOpenGL_ANPDrawingModel) + m_core->updateSurface(m_embeddedView, m_pluginWindow->x, m_pluginWindow->y, + m_pluginWindow->width, m_pluginWindow->height); + + // send event to notify plugin of full screen change + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kExitFullScreen_ANPLifecycleAction; + sendEvent(event); + + m_isFullScreen = false; +} + +void PluginWidgetAndroid::requestCenterFitZoom() { + m_core->centerFitRect(m_pluginWindow->x, m_pluginWindow->y, + m_pluginWindow->width, m_pluginWindow->height); +} + +void PluginWidgetAndroid::setPowerState(ANPPowerState powerState) { + if(m_powerState == powerState) + return; + + // cleanup the old power state + switch (m_powerState) { + case kDefault_ANPPowerState: + break; + case kScreenOn_ANPPowerState: + m_core->keepScreenOn(false); + break; + } + + // setup the new power state + switch (powerState) { + case kDefault_ANPPowerState: + break; + case kScreenOn_ANPPowerState: + m_core->keepScreenOn(true); + break; + } + + m_powerState = powerState; +} + diff --git a/Source/WebKit/android/plugins/PluginWidgetAndroid.h b/Source/WebKit/android/plugins/PluginWidgetAndroid.h new file mode 100644 index 0000000..5d586b1 --- /dev/null +++ b/Source/WebKit/android/plugins/PluginWidgetAndroid.h @@ -0,0 +1,219 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef PluginWidgetAndroid_H +#define PluginWidgetAndroid_H + +#include "android_npapi.h" +#include "ANPSystem_npapi.h" +#include "IntPoint.h" +#include "IntRect.h" +#include "MediaLayer.h" +#include "SkRect.h" +#include <jni.h> + +namespace WebCore { + class PluginView; +} + +namespace android { + class PluginSurface; + class WebViewCore; +} + +class SkCanvas; +class SkFlipPixelRef; + +/* + This is our extended state in a PluginView. This object is created and + kept insync with the PluginView, but is also available to WebViewCore + to allow its draw() method to be called from outside of the PluginView. + */ +struct PluginWidgetAndroid { + // initialize with our host pluginview. This will delete us when it is + // destroyed. + PluginWidgetAndroid(WebCore::PluginView* view); + ~PluginWidgetAndroid(); + + WebCore::PluginView* pluginView() const { return m_pluginView; } + + // Needed by PluginSurface to manage the java SurfaceView. + android::WebViewCore* webViewCore() const { return m_core; } + + /* Can't determine our core at construction time, so PluginView calls this + as soon as it has a parent. + */ + void init(android::WebViewCore*); + /* Called each time the PluginView gets a new size or position. + */ + void setWindow(NPWindow* window, bool isTransparent); + + /* Called whenever the plugin itself requests a new drawing model. If the + hardware does not support the requested model then false is returned, + otherwise true is returned. + */ + bool setDrawingModel(ANPDrawingModel); + + /* Called to check if the plugin is running in "windowed" mode (i.e. surface + view). + */ + bool isSurfaceDrawingModel() const { return kSurface_ANPDrawingModel == m_drawingModel; } + + bool isOpenGLDrawingModel() const { return kOpenGL_ANPDrawingModel == m_drawingModel; } + + /* Returns true (and optionally updates rect with the dirty bounds in the + page coordinate) if the plugin has invalidate us. + */ + bool isDirty(SkIRect* dirtyBounds = NULL) const; + /* Called by PluginView to invalidate a portion of the plugin area (in + local plugin coordinates). If signalRedraw is true, this also triggers + a subsequent call to draw(NULL). + */ + void inval(const WebCore::IntRect&, bool signalRedraw); + + /* Called to draw into the plugin's bitmap. If canvas is non-null, the + bitmap itself is then drawn into the canvas. + */ + void draw(SkCanvas* canvas = NULL); + + /* Send this event to the plugin instance. A non-zero value will be + returned if the plugin handled the event. + */ + int16_t sendEvent(const ANPEvent&); + + /* Update the plugins event flags. If a flag is set to true then the plugin + wants to be notified of events of this type. + */ + void updateEventFlags(ANPEventFlags); + + /* Called to check if a plugin wants to accept a given event type. It + returns true if the plugin wants the events and false otherwise. + */ + bool isAcceptingEvent(ANPEventFlag); + + /* Notify the plugin of the currently visible screen coordinates (document + space) and the current zoom level. + */ + void setVisibleScreen(const ANPRectI& visibleScreenRect, float zoom); + + /** Returns a rectangle representing the visible area of the plugin on + screen. The coordinates are relative to the size of the plugin in the + document and will not be negative or exceed the plugin's size. + */ + ANPRectI visibleRect(); + + /** Registers a set of rectangles that the plugin would like to keep on + screen. The rectangles are listed in order of priority with the highest + priority rectangle in location rects[0]. The browser will attempt to keep + as many of the rectangles on screen as possible and will scroll them into + view in response to the invocation of this method and other various events. + The count specifies how many rectangles are in the array. If the count is + zero it signals the plugin that any existing rectangles should be cleared + and no rectangles will be tracked. + */ + void setVisibleRects(const ANPRectI rects[], int32_t count); + + /** Called when a plugin wishes to enter into full screen mode. It invokes + the plugin's Java class (defined in the plugin's apk manifest), which is + called asynchronously and provides a View to be displayed full screen. + */ + void requestFullScreen(); + + /** Called when a plugin wishes to exit from full screen mode. As a result, + the plugin's full-screen view is discarded by the view system. It is also + called in order to notify the native code that the browser has discarded + the view. + */ + void exitFullScreen(bool pluginInitiated); + + bool inFullScreen() { return m_isFullScreen; } + + /** Called to check if a plugin currently has document focus, which is + required for certain operations (e.g. show/hide keyboard). It returns + true if the plugin currently has focus and false otherwise. + */ + bool hasFocus() const { return m_hasFocus; } + + /** Called to ensure the surface is being correctly displayed within the + view hierarchy. For instance, if the visibility of the plugin has + changed then we need to ensure the surface is added or removed from the + view system. + */ + void layoutSurface(bool pluginBoundsChanged = false); + + /** send the surface the currently visible portion of the plugin. This is not + the portion of the plugin visible on the screen but rather the portion of + the plugin that is not obscured by other HTML content. + */ + void setSurfaceClip(const SkIRect& clip); + + /** Called when a plugin wishes to be zoomed and centered in the current view. + */ + void requestCenterFitZoom(); + + WebCore::MediaLayer* getLayer() const { return m_layer; } + + void setPowerState(ANPPowerState powerState); + + void viewInvalidate(); + +private: + void computeVisiblePluginRect(); + void scrollToVisiblePluginRect(); + void sendSizeAndVisibilityEvents(const bool updateDimensions); + + WebCore::MediaLayer* m_layer; + + WebCore::PluginView* m_pluginView; + android::WebViewCore* m_core; + SkFlipPixelRef* m_flipPixelRef; + ANPDrawingModel m_drawingModel; + ANPEventFlags m_eventFlags; + NPWindow* m_pluginWindow; + SkIRect m_pluginBounds; // relative to the page + SkIRect m_visibleDocRect; // relative to the page + SkIRect m_requestedVisibleRect; // relative to the page + bool m_hasFocus; + bool m_isFullScreen; + bool m_visible; + float m_cachedZoomLevel; // used for comparison only + jobject m_embeddedView; + bool m_embeddedViewAttached; + bool m_acceptEvents; + bool m_isSurfaceClippedOut; + ANPPowerState m_powerState; + + /* We limit the number of rectangles to minimize storage and ensure adequate + speed. + */ + enum { + MAX_REQUESTED_RECTS = 5, + }; + + ANPRectI m_requestedVisibleRects[MAX_REQUESTED_RECTS]; + int32_t m_requestedVisibleRectCount; +}; + +#endif diff --git a/Source/WebKit/android/plugins/SkANP.cpp b/Source/WebKit/android/plugins/SkANP.cpp new file mode 100644 index 0000000..720387d --- /dev/null +++ b/Source/WebKit/android/plugins/SkANP.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2008, 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" +#include <wtf/CurrentTime.h> + +SkRect* SkANP::SetRect(SkRect* dst, const ANPRectF& src) { + dst->set(SkFloatToScalar(src.left), + SkFloatToScalar(src.top), + SkFloatToScalar(src.right), + SkFloatToScalar(src.bottom)); + return dst; +} + +SkIRect* SkANP::SetRect(SkIRect* dst, const ANPRectI& src) { + dst->set(src.left, src.top, src.right, src.bottom); + return dst; +} + +ANPRectI* SkANP::SetRect(ANPRectI* dst, const SkIRect& src) { + dst->left = src.fLeft; + dst->top = src.fTop; + dst->right = src.fRight; + dst->bottom = src.fBottom; + return dst; +} + +ANPRectF* SkANP::SetRect(ANPRectF* dst, const SkRect& src) { + dst->left = SkScalarToFloat(src.fLeft); + dst->top = SkScalarToFloat(src.fTop); + dst->right = SkScalarToFloat(src.fRight); + dst->bottom = SkScalarToFloat(src.fBottom); + return dst; +} + +SkBitmap* SkANP::SetBitmap(SkBitmap* dst, const ANPBitmap& src) { + SkBitmap::Config config = SkBitmap::kNo_Config; + + switch (src.format) { + case kRGBA_8888_ANPBitmapFormat: + config = SkBitmap::kARGB_8888_Config; + break; + case kRGB_565_ANPBitmapFormat: + config = SkBitmap::kRGB_565_Config; + break; + default: + break; + } + + dst->setConfig(config, src.width, src.height, src.rowBytes); + dst->setPixels(src.baseAddr); + return dst; +} + +bool SkANP::SetBitmap(ANPBitmap* dst, const SkBitmap& src) { + if (!(dst->baseAddr = src.getPixels())) { + SkDebugf("SkANP::SetBitmap - getPixels() returned null\n"); + return false; + } + + switch (src.config()) { + case SkBitmap::kARGB_8888_Config: + dst->format = kRGBA_8888_ANPBitmapFormat; + break; + case SkBitmap::kRGB_565_Config: + dst->format = kRGB_565_ANPBitmapFormat; + break; + default: + SkDebugf("SkANP::SetBitmap - unsupported src.config %d\n", src.config()); + return false; + } + + dst->width = src.width(); + dst->height = src.height(); + dst->rowBytes = src.rowBytes(); + return true; +} + +void SkANP::InitEvent(ANPEvent* event, ANPEventType et) { + event->inSize = sizeof(ANPEvent); + event->eventType = et; +} diff --git a/Source/WebKit/android/plugins/SkANP.h b/Source/WebKit/android/plugins/SkANP.h new file mode 100644 index 0000000..5c2a936 --- /dev/null +++ b/Source/WebKit/android/plugins/SkANP.h @@ -0,0 +1,79 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef SkANP_DEFINED +#define SkANP_DEFINED + +#include "android_npapi.h" +#include "SkCanvas.h" +#include "SkMatrix.h" +#include "SkPaint.h" +#include "SkPath.h" +#include "SkTypeface.h" + +struct ANPMatrix : SkMatrix { +}; + +struct ANPPath : SkPath { +}; + +struct ANPPaint : SkPaint { +}; + +struct ANPTypeface : SkTypeface { +}; + +struct ANPCanvas { + SkCanvas* skcanvas; + + // draw into the specified bitmap + explicit ANPCanvas(const SkBitmap& bm) { + skcanvas = new SkCanvas(bm); + } + + // redirect all drawing to the specific SkCanvas + explicit ANPCanvas(SkCanvas* other) { + skcanvas = other; + skcanvas->ref(); + } + + ~ANPCanvas() { + skcanvas->unref(); + } +}; + +class SkANP { +public: + static SkRect* SetRect(SkRect* dst, const ANPRectF& src); + static SkIRect* SetRect(SkIRect* dst, const ANPRectI& src); + static ANPRectI* SetRect(ANPRectI* dst, const SkIRect& src); + static ANPRectF* SetRect(ANPRectF* dst, const SkRect& src); + static SkBitmap* SetBitmap(SkBitmap* dst, const ANPBitmap& src); + static bool SetBitmap(ANPBitmap* dst, const SkBitmap& src); + + static void InitEvent(ANPEvent* event, ANPEventType et); +}; + +#endif diff --git a/Source/WebKit/android/plugins/SurfaceCallback.h b/Source/WebKit/android/plugins/SurfaceCallback.h new file mode 100644 index 0000000..945fc3f --- /dev/null +++ b/Source/WebKit/android/plugins/SurfaceCallback.h @@ -0,0 +1,42 @@ +/* + * Copyright 2009, The Android Open Source Project + * Copyright (C) 2008 Google Inc. All rights reserved. + * + * 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. + */ + +#ifndef SurfaceCallback_H +#define SurfaceCallback_H + +namespace android { + + class SurfaceCallback { + public: + virtual ~SurfaceCallback() {} + virtual void surfaceCreated() = 0; + virtual void surfaceChanged(int format, int width, int height) = 0; + virtual void surfaceDestroyed() = 0; + }; + +} // namespace android + +#endif diff --git a/Source/WebKit/android/plugins/android_npapi.h b/Source/WebKit/android/plugins/android_npapi.h new file mode 100644 index 0000000..b0c3765 --- /dev/null +++ b/Source/WebKit/android/plugins/android_npapi.h @@ -0,0 +1,987 @@ +/* + * Copyright 2009, 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. + */ + +/* Defines the android-specific types and functions as part of npapi + + In particular, defines the window and event types that are passed to + NPN_GetValue, NPP_SetWindow and NPP_HandleEvent. + + To minimize what native libraries the plugin links against, some + functionality is provided via function-ptrs (e.g. time, sound) + */ + +#ifndef android_npapi_H +#define android_npapi_H + +#include <stdint.h> + +#include "npapi.h" + +/////////////////////////////////////////////////////////////////////////////// +// General types + +enum ANPBitmapFormats { + kUnknown_ANPBitmapFormat = 0, + kRGBA_8888_ANPBitmapFormat = 1, + kRGB_565_ANPBitmapFormat = 2 +}; +typedef int32_t ANPBitmapFormat; + +struct ANPPixelPacking { + uint8_t AShift; + uint8_t ABits; + uint8_t RShift; + uint8_t RBits; + uint8_t GShift; + uint8_t GBits; + uint8_t BShift; + uint8_t BBits; +}; + +struct ANPBitmap { + void* baseAddr; + ANPBitmapFormat format; + int32_t width; + int32_t height; + int32_t rowBytes; +}; + +struct ANPRectF { + float left; + float top; + float right; + float bottom; +}; + +struct ANPRectI { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +}; + +struct ANPCanvas; +struct ANPMatrix; +struct ANPPaint; +struct ANPPath; +struct ANPRegion; +struct ANPTypeface; + +enum ANPMatrixFlags { + kIdentity_ANPMatrixFlag = 0, + kTranslate_ANPMatrixFlag = 0x01, + kScale_ANPMatrixFlag = 0x02, + kAffine_ANPMatrixFlag = 0x04, + kPerspective_ANPMatrixFlag = 0x08, +}; +typedef uint32_t ANPMatrixFlag; + +/////////////////////////////////////////////////////////////////////////////// +// NPN_GetValue + +/** queries for a specific ANPInterface. + + Maybe called with NULL for the NPP instance + + NPN_GetValue(inst, interface_enum, ANPInterface*) + */ +#define kLogInterfaceV0_ANPGetValue ((NPNVariable)1000) +#define kAudioTrackInterfaceV0_ANPGetValue ((NPNVariable)1001) +#define kCanvasInterfaceV0_ANPGetValue ((NPNVariable)1002) +#define kMatrixInterfaceV0_ANPGetValue ((NPNVariable)1003) +#define kPaintInterfaceV0_ANPGetValue ((NPNVariable)1004) +#define kPathInterfaceV0_ANPGetValue ((NPNVariable)1005) +#define kTypefaceInterfaceV0_ANPGetValue ((NPNVariable)1006) +#define kWindowInterfaceV0_ANPGetValue ((NPNVariable)1007) +#define kBitmapInterfaceV0_ANPGetValue ((NPNVariable)1008) +#define kSurfaceInterfaceV0_ANPGetValue ((NPNVariable)1009) +#define kSystemInterfaceV0_ANPGetValue ((NPNVariable)1010) +#define kEventInterfaceV0_ANPGetValue ((NPNVariable)1011) + +#define kAudioTrackInterfaceV1_ANPGetValue ((NPNVariable)1012) +#define kOpenGLInterfaceV0_ANPGetValue ((NPNVariable)1013) +#define kWindowInterfaceV1_ANPGetValue ((NPNVariable)1014) +#define kVideoInterfaceV0_ANPGetValue ((NPNVariable)1015) +#define kSystemInterfaceV1_ANPGetValue ((NPNVariable)1016) + +#define kSystemInterfaceV2_ANPGetValue ((NPNVariable)1017) + +/** queries for the drawing models supported on this device. + + NPN_GetValue(inst, kSupportedDrawingModel_ANPGetValue, uint32_t* bits) + */ +#define kSupportedDrawingModel_ANPGetValue ((NPNVariable)2000) + +/** queries for the context (android.content.Context) of the plugin. If no + instance is specified the application's context is returned. If the instance + is given then the context returned is identical to the context used to + create the webview in which that instance resides. + + NOTE: Holding onto a non-application context after your instance has been + destroyed will cause a memory leak. Refer to the android documentation to + determine what context is best suited for your particular scenario. + + NPN_GetValue(inst, kJavaContext_ANPGetValue, jobject context) + */ +#define kJavaContext_ANPGetValue ((NPNVariable)2001) + +/////////////////////////////////////////////////////////////////////////////// +// NPN_SetValue + +/** Request to set the drawing model. SetValue will return false if the drawing + model is not supported or has insufficient information for configuration. + + NPN_SetValue(inst, kRequestDrawingModel_ANPSetValue, (void*)foo_ANPDrawingModel) + */ +#define kRequestDrawingModel_ANPSetValue ((NPPVariable)1000) + +/** These are used as bitfields in ANPSupportedDrawingModels_EnumValue, + and as-is in ANPRequestDrawingModel_EnumValue. The drawing model determines + how to interpret the ANPDrawingContext provided in the Draw event and how + to interpret the NPWindow->window field. + */ +enum ANPDrawingModels { + /** Draw into a bitmap from the browser thread in response to a Draw event. + NPWindow->window is reserved (ignore) + */ + kBitmap_ANPDrawingModel = 1 << 0, + /** Draw into a surface (e.g. raster, openGL, etc.) using the Java surface + interface. When this model is used the browser will invoke the Java + class specified in the plugin's apk manifest. From that class the browser + will invoke the appropriate method to return an an instance of a android + Java View. The instance is then embedded in the html. The plugin can then + manipulate the view as it would any normal Java View in android. + + Unlike the bitmap model, a surface model is opaque so no html content + behind the plugin will be visible. Unless the plugin needs to be + transparent the surface model should be chosen over the bitmap model as + it will have better performance. + + Further, a plugin can manipulate some surfaces in native code using the + ANPSurfaceInterface. This interface can be used to manipulate Java + objects that extend Surface.class by allowing them to access the + surface's underlying bitmap in native code. For instance, if a raster + surface is used the plugin can lock, draw directly into the bitmap, and + unlock the surface in native code without making JNI calls to the Java + surface object. + */ + kSurface_ANPDrawingModel = 1 << 1, + kOpenGL_ANPDrawingModel = 1 << 2, +}; +typedef int32_t ANPDrawingModel; + +/** Request to receive/disable events. If the pointer is NULL then all flags will + be disabled. Otherwise, the event type will be enabled iff its corresponding + bit in the EventFlags bit field is set. + + NPN_SetValue(inst, ANPAcceptEvents, (void*)EventFlags) + */ +#define kAcceptEvents_ANPSetValue ((NPPVariable)1001) + +/** The EventFlags are a set of bits used to determine which types of events the + plugin wishes to receive. For example, if the value is 0x03 then both key + and touch events will be provided to the plugin. + */ +enum ANPEventFlag { + kKey_ANPEventFlag = 0x01, + kTouch_ANPEventFlag = 0x02, +}; +typedef uint32_t ANPEventFlags; + +/////////////////////////////////////////////////////////////////////////////// +// NPP_GetValue + +/** Requests that the plugin return a java surface to be displayed. This will + only be used if the plugin has choosen the kSurface_ANPDrawingModel. + + NPP_GetValue(inst, kJavaSurface_ANPGetValue, jobject surface) + */ +#define kJavaSurface_ANPGetValue ((NPPVariable)2000) + + +/////////////////////////////////////////////////////////////////////////////// +// ANDROID INTERFACE DEFINITIONS + +/** Interfaces provide additional functionality to the plugin via function ptrs. + Once an interface is retrieved, it is valid for the lifetime of the plugin + (just like browserfuncs). + + All ANPInterfaces begin with an inSize field, which must be set by the + caller (plugin) with the number of bytes allocated for the interface. + e.g. SomeInterface si; si.inSize = sizeof(si); browser->getvalue(..., &si); + */ +struct ANPInterface { + uint32_t inSize; // size (in bytes) of this struct +}; + +enum ANPLogTypes { + kError_ANPLogType = 0, // error + kWarning_ANPLogType = 1, // warning + kDebug_ANPLogType = 2 // debug only (informational) +}; +typedef int32_t ANPLogType; + +struct ANPLogInterfaceV0 : ANPInterface { + /** dumps printf messages to the log file + e.g. interface->log(instance, kWarning_ANPLogType, "value is %d", value); + */ + void (*log)(ANPLogType, const char format[], ...); +}; + +struct ANPBitmapInterfaceV0 : ANPInterface { + /** Returns true if the specified bitmap format is supported, and if packing + is non-null, sets it to the packing info for that format. + */ + bool (*getPixelPacking)(ANPBitmapFormat, ANPPixelPacking* packing); +}; + +struct ANPMatrixInterfaceV0 : ANPInterface { + /** Return a new identity matrix + */ + ANPMatrix* (*newMatrix)(); + /** Delete a matrix previously allocated by newMatrix() + */ + void (*deleteMatrix)(ANPMatrix*); + + ANPMatrixFlag (*getFlags)(const ANPMatrix*); + + void (*copy)(ANPMatrix* dst, const ANPMatrix* src); + + /** Return the matrix values in a float array (allcoated by the caller), + where the values are treated as follows: + w = x * [6] + y * [7] + [8]; + x' = (x * [0] + y * [1] + [2]) / w; + y' = (x * [3] + y * [4] + [5]) / w; + */ + void (*get3x3)(const ANPMatrix*, float[9]); + /** Initialize the matrix from values in a float array, + where the values are treated as follows: + w = x * [6] + y * [7] + [8]; + x' = (x * [0] + y * [1] + [2]) / w; + y' = (x * [3] + y * [4] + [5]) / w; + */ + void (*set3x3)(ANPMatrix*, const float[9]); + + void (*setIdentity)(ANPMatrix*); + void (*preTranslate)(ANPMatrix*, float tx, float ty); + void (*postTranslate)(ANPMatrix*, float tx, float ty); + void (*preScale)(ANPMatrix*, float sx, float sy); + void (*postScale)(ANPMatrix*, float sx, float sy); + void (*preSkew)(ANPMatrix*, float kx, float ky); + void (*postSkew)(ANPMatrix*, float kx, float ky); + void (*preRotate)(ANPMatrix*, float degrees); + void (*postRotate)(ANPMatrix*, float degrees); + void (*preConcat)(ANPMatrix*, const ANPMatrix*); + void (*postConcat)(ANPMatrix*, const ANPMatrix*); + + /** Return true if src is invertible, and if so, return its inverse in dst. + If src is not invertible, return false and ignore dst. + */ + bool (*invert)(ANPMatrix* dst, const ANPMatrix* src); + + /** Transform the x,y pairs in src[] by this matrix, and store the results + in dst[]. The count parameter is treated as the number of pairs in the + array. It is legal for src and dst to point to the same memory, but + illegal for the two arrays to partially overlap. + */ + void (*mapPoints)(ANPMatrix*, float dst[], const float src[], + int32_t count); +}; + +struct ANPPathInterfaceV0 : ANPInterface { + /** Return a new path */ + ANPPath* (*newPath)(); + + /** Delete a path previously allocated by ANPPath() */ + void (*deletePath)(ANPPath*); + + /** Make a deep copy of the src path, into the dst path (already allocated + by the caller). + */ + void (*copy)(ANPPath* dst, const ANPPath* src); + + /** Returns true if the two paths are the same (i.e. have the same points) + */ + bool (*equal)(const ANPPath* path0, const ANPPath* path1); + + /** Remove any previous points, initializing the path back to empty. */ + void (*reset)(ANPPath*); + + /** Return true if the path is empty (has no lines, quads or cubics). */ + bool (*isEmpty)(const ANPPath*); + + /** Return the path's bounds in bounds. */ + void (*getBounds)(const ANPPath*, ANPRectF* bounds); + + void (*moveTo)(ANPPath*, float x, float y); + void (*lineTo)(ANPPath*, float x, float y); + void (*quadTo)(ANPPath*, float x0, float y0, float x1, float y1); + void (*cubicTo)(ANPPath*, float x0, float y0, float x1, float y1, + float x2, float y2); + void (*close)(ANPPath*); + + /** Offset the src path by [dx, dy]. If dst is null, apply the + change directly to the src path. If dst is not null, write the + changed path into dst, and leave the src path unchanged. In that case + dst must have been previously allocated by the caller. + */ + void (*offset)(ANPPath* src, float dx, float dy, ANPPath* dst); + + /** Transform the path by the matrix. If dst is null, apply the + change directly to the src path. If dst is not null, write the + changed path into dst, and leave the src path unchanged. In that case + dst must have been previously allocated by the caller. + */ + void (*transform)(ANPPath* src, const ANPMatrix*, ANPPath* dst); +}; + +/** ANPColor is always defined to have the same packing on all platforms, and + it is always unpremultiplied. + + This is in contrast to 32bit format(s) in bitmaps, which are premultiplied, + and their packing may vary depending on the platform, hence the need for + ANPBitmapInterface::getPixelPacking() + */ +typedef uint32_t ANPColor; +#define ANPColor_ASHIFT 24 +#define ANPColor_RSHIFT 16 +#define ANPColor_GSHIFT 8 +#define ANPColor_BSHIFT 0 +#define ANP_MAKE_COLOR(a, r, g, b) \ + (((a) << ANPColor_ASHIFT) | \ + ((r) << ANPColor_RSHIFT) | \ + ((g) << ANPColor_GSHIFT) | \ + ((b) << ANPColor_BSHIFT)) + +enum ANPPaintFlag { + kAntiAlias_ANPPaintFlag = 1 << 0, + kFilterBitmap_ANPPaintFlag = 1 << 1, + kDither_ANPPaintFlag = 1 << 2, + kUnderlineText_ANPPaintFlag = 1 << 3, + kStrikeThruText_ANPPaintFlag = 1 << 4, + kFakeBoldText_ANPPaintFlag = 1 << 5, +}; +typedef uint32_t ANPPaintFlags; + +enum ANPPaintStyles { + kFill_ANPPaintStyle = 0, + kStroke_ANPPaintStyle = 1, + kFillAndStroke_ANPPaintStyle = 2 +}; +typedef int32_t ANPPaintStyle; + +enum ANPPaintCaps { + kButt_ANPPaintCap = 0, + kRound_ANPPaintCap = 1, + kSquare_ANPPaintCap = 2 +}; +typedef int32_t ANPPaintCap; + +enum ANPPaintJoins { + kMiter_ANPPaintJoin = 0, + kRound_ANPPaintJoin = 1, + kBevel_ANPPaintJoin = 2 +}; +typedef int32_t ANPPaintJoin; + +enum ANPPaintAligns { + kLeft_ANPPaintAlign = 0, + kCenter_ANPPaintAlign = 1, + kRight_ANPPaintAlign = 2 +}; +typedef int32_t ANPPaintAlign; + +enum ANPTextEncodings { + kUTF8_ANPTextEncoding = 0, + kUTF16_ANPTextEncoding = 1, +}; +typedef int32_t ANPTextEncoding; + +enum ANPTypefaceStyles { + kBold_ANPTypefaceStyle = 1 << 0, + kItalic_ANPTypefaceStyle = 1 << 1 +}; +typedef uint32_t ANPTypefaceStyle; + +typedef uint32_t ANPFontTableTag; + +struct ANPFontMetrics { + /** The greatest distance above the baseline for any glyph (will be <= 0) */ + float fTop; + /** The recommended distance above the baseline (will be <= 0) */ + float fAscent; + /** The recommended distance below the baseline (will be >= 0) */ + float fDescent; + /** The greatest distance below the baseline for any glyph (will be >= 0) */ + float fBottom; + /** The recommended distance to add between lines of text (will be >= 0) */ + float fLeading; +}; + +struct ANPTypefaceInterfaceV0 : ANPInterface { + /** Return a new reference to the typeface that most closely matches the + requested name and style. Pass null as the name to return + the default font for the requested style. Will never return null + + The 5 generic font names "serif", "sans-serif", "monospace", "cursive", + "fantasy" are recognized, and will be mapped to their logical font + automatically by this call. + + @param name May be NULL. The name of the font family. + @param style The style (normal, bold, italic) of the typeface. + @return reference to the closest-matching typeface. Caller must call + unref() when they are done with the typeface. + */ + ANPTypeface* (*createFromName)(const char name[], ANPTypefaceStyle); + + /** Return a new reference to the typeface that most closely matches the + requested typeface and specified Style. Use this call if you want to + pick a new style from the same family of the existing typeface. + If family is NULL, this selects from the default font's family. + + @param family May be NULL. The name of the existing type face. + @param s The style (normal, bold, italic) of the type face. + @return reference to the closest-matching typeface. Call must call + unref() when they are done. + */ + ANPTypeface* (*createFromTypeface)(const ANPTypeface* family, + ANPTypefaceStyle); + + /** Return the owner count of the typeface. A newly created typeface has an + owner count of 1. When the owner count is reaches 0, the typeface is + deleted. + */ + int32_t (*getRefCount)(const ANPTypeface*); + + /** Increment the owner count on the typeface + */ + void (*ref)(ANPTypeface*); + + /** Decrement the owner count on the typeface. When the count goes to 0, + the typeface is deleted. + */ + void (*unref)(ANPTypeface*); + + /** Return the style bits for the specified typeface + */ + ANPTypefaceStyle (*getStyle)(const ANPTypeface*); + + /** Some fonts are stored in files. If that is true for the fontID, then + this returns the byte length of the full file path. If path is not null, + then the full path is copied into path (allocated by the caller), up to + length bytes. If index is not null, then it is set to the truetype + collection index for this font, or 0 if the font is not in a collection. + + Note: getFontPath does not assume that path is a null-terminated string, + so when it succeeds, it only copies the bytes of the file name and + nothing else (i.e. it copies exactly the number of bytes returned by the + function. If the caller wants to treat path[] as a C string, it must be + sure that it is allocated at least 1 byte larger than the returned size, + and it must copy in the terminating 0. + + If the fontID does not correspond to a file, then the function returns + 0, and the path and index parameters are ignored. + + @param fontID The font whose file name is being queried + @param path Either NULL, or storage for receiving up to length bytes + of the font's file name. Allocated by the caller. + @param length The maximum space allocated in path (by the caller). + Ignored if path is NULL. + @param index Either NULL, or receives the TTC index for this font. + If the font is not a TTC, then will be set to 0. + @return The byte length of th font's file name, or 0 if the font is not + baked by a file. + */ + int32_t (*getFontPath)(const ANPTypeface*, char path[], int32_t length, + int32_t* index); + + /** Return a UTF8 encoded path name for the font directory, or NULL if not + supported. If returned, this string address will be valid for the life + of the plugin instance. It will always end with a '/' character. + */ + const char* (*getFontDirectoryPath)(); +}; + +struct ANPPaintInterfaceV0 : ANPInterface { + /** Return a new paint object, which holds all of the color and style + attributes that affect how things (geometry, text, bitmaps) are drawn + in a ANPCanvas. + + The paint that is returned is not tied to any particular plugin + instance, but it must only be accessed from one thread at a time. + */ + ANPPaint* (*newPaint)(); + void (*deletePaint)(ANPPaint*); + + ANPPaintFlags (*getFlags)(const ANPPaint*); + void (*setFlags)(ANPPaint*, ANPPaintFlags); + + ANPColor (*getColor)(const ANPPaint*); + void (*setColor)(ANPPaint*, ANPColor); + + ANPPaintStyle (*getStyle)(const ANPPaint*); + void (*setStyle)(ANPPaint*, ANPPaintStyle); + + float (*getStrokeWidth)(const ANPPaint*); + float (*getStrokeMiter)(const ANPPaint*); + ANPPaintCap (*getStrokeCap)(const ANPPaint*); + ANPPaintJoin (*getStrokeJoin)(const ANPPaint*); + void (*setStrokeWidth)(ANPPaint*, float); + void (*setStrokeMiter)(ANPPaint*, float); + void (*setStrokeCap)(ANPPaint*, ANPPaintCap); + void (*setStrokeJoin)(ANPPaint*, ANPPaintJoin); + + ANPTextEncoding (*getTextEncoding)(const ANPPaint*); + ANPPaintAlign (*getTextAlign)(const ANPPaint*); + float (*getTextSize)(const ANPPaint*); + float (*getTextScaleX)(const ANPPaint*); + float (*getTextSkewX)(const ANPPaint*); + void (*setTextEncoding)(ANPPaint*, ANPTextEncoding); + void (*setTextAlign)(ANPPaint*, ANPPaintAlign); + void (*setTextSize)(ANPPaint*, float); + void (*setTextScaleX)(ANPPaint*, float); + void (*setTextSkewX)(ANPPaint*, float); + + /** Return the typeface ine paint, or null if there is none. This does not + modify the owner count of the returned typeface. + */ + ANPTypeface* (*getTypeface)(const ANPPaint*); + + /** Set the paint's typeface. If the paint already had a non-null typeface, + its owner count is decremented. If the new typeface is non-null, its + owner count is incremented. + */ + void (*setTypeface)(ANPPaint*, ANPTypeface*); + + /** Return the width of the text. If bounds is not null, return the bounds + of the text in that rectangle. + */ + float (*measureText)(ANPPaint*, const void* text, uint32_t byteLength, + ANPRectF* bounds); + + /** Return the number of unichars specifed by the text. + If widths is not null, returns the array of advance widths for each + unichar. + If bounds is not null, returns the array of bounds for each unichar. + */ + int (*getTextWidths)(ANPPaint*, const void* text, uint32_t byteLength, + float widths[], ANPRectF bounds[]); + + /** Return in metrics the spacing values for text, respecting the paint's + typeface and pointsize, and return the spacing between lines + (descent - ascent + leading). If metrics is NULL, it will be ignored. + */ + float (*getFontMetrics)(ANPPaint*, ANPFontMetrics* metrics); +}; + +struct ANPCanvasInterfaceV0 : ANPInterface { + /** Return a canvas that will draw into the specified bitmap. Note: the + canvas copies the fields of the bitmap, so it need not persist after + this call, but the canvas DOES point to the same pixel memory that the + bitmap did, so the canvas should not be used after that pixel memory + goes out of scope. In the case of creating a canvas to draw into the + pixels provided by kDraw_ANPEventType, those pixels are only while + handling that event. + + The canvas that is returned is not tied to any particular plugin + instance, but it must only be accessed from one thread at a time. + */ + ANPCanvas* (*newCanvas)(const ANPBitmap*); + void (*deleteCanvas)(ANPCanvas*); + + void (*save)(ANPCanvas*); + void (*restore)(ANPCanvas*); + void (*translate)(ANPCanvas*, float tx, float ty); + void (*scale)(ANPCanvas*, float sx, float sy); + void (*rotate)(ANPCanvas*, float degrees); + void (*skew)(ANPCanvas*, float kx, float ky); + void (*concat)(ANPCanvas*, const ANPMatrix*); + void (*clipRect)(ANPCanvas*, const ANPRectF*); + void (*clipPath)(ANPCanvas*, const ANPPath*); + + /** Return the current matrix on the canvas + */ + void (*getTotalMatrix)(ANPCanvas*, ANPMatrix*); + /** Return the current clip bounds in local coordinates, expanding it to + account for antialiasing edge effects if aa is true. If the + current clip is empty, return false and ignore the bounds argument. + */ + bool (*getLocalClipBounds)(ANPCanvas*, ANPRectF* bounds, bool aa); + /** Return the current clip bounds in device coordinates in bounds. If the + current clip is empty, return false and ignore the bounds argument. + */ + bool (*getDeviceClipBounds)(ANPCanvas*, ANPRectI* bounds); + + void (*drawColor)(ANPCanvas*, ANPColor); + void (*drawPaint)(ANPCanvas*, const ANPPaint*); + void (*drawLine)(ANPCanvas*, float x0, float y0, float x1, float y1, + const ANPPaint*); + void (*drawRect)(ANPCanvas*, const ANPRectF*, const ANPPaint*); + void (*drawOval)(ANPCanvas*, const ANPRectF*, const ANPPaint*); + void (*drawPath)(ANPCanvas*, const ANPPath*, const ANPPaint*); + void (*drawText)(ANPCanvas*, const void* text, uint32_t byteLength, + float x, float y, const ANPPaint*); + void (*drawPosText)(ANPCanvas*, const void* text, uint32_t byteLength, + const float xy[], const ANPPaint*); + void (*drawBitmap)(ANPCanvas*, const ANPBitmap*, float x, float y, + const ANPPaint*); + void (*drawBitmapRect)(ANPCanvas*, const ANPBitmap*, + const ANPRectI* src, const ANPRectF* dst, + const ANPPaint*); +}; + +struct ANPWindowInterfaceV0 : ANPInterface { + /** Registers a set of rectangles that the plugin would like to keep on + screen. The rectangles are listed in order of priority with the highest + priority rectangle in location rects[0]. The browser will attempt to keep + as many of the rectangles on screen as possible and will scroll them into + view in response to the invocation of this method and other various events. + The count specifies how many rectangles are in the array. If the count is + zero it signals the browser that any existing rectangles should be cleared + and no rectangles will be tracked. + */ + void (*setVisibleRects)(NPP instance, const ANPRectI rects[], int32_t count); + /** Clears any rectangles that are being tracked as a result of a call to + setVisibleRects. This call is equivalent to setVisibleRect(inst, NULL, 0). + */ + void (*clearVisibleRects)(NPP instance); + /** Given a boolean value of true the device will be requested to provide + a keyboard. A value of false will result in a request to hide the + keyboard. Further, the on-screen keyboard will not be displayed if a + physical keyboard is active. + */ + void (*showKeyboard)(NPP instance, bool value); + /** Called when a plugin wishes to enter into full screen mode. The plugin's + Java class (defined in the plugin's apk manifest) will be called + asynchronously to provide a View object to be displayed full screen. + */ + void (*requestFullScreen)(NPP instance); + /** Called when a plugin wishes to exit from full screen mode. As a result, + the plugin's full screen view will be discarded by the view system. + */ + void (*exitFullScreen)(NPP instance); + /** Called when a plugin wishes to be zoomed and centered in the current view. + */ + void (*requestCenterFitZoom)(NPP instance); +}; + +struct ANPWindowInterfaceV1 : ANPWindowInterfaceV0 { + /** Returns a rectangle representing the visible area of the plugin on + screen. The coordinates are relative to the size of the plugin in the + document and therefore will never be negative or exceed the plugin's size. + */ + ANPRectI (*visibleRect)(NPP instance); +}; + +/////////////////////////////////////////////////////////////////////////////// + +enum ANPSampleFormats { + kUnknown_ANPSamleFormat = 0, + kPCM16Bit_ANPSampleFormat = 1, + kPCM8Bit_ANPSampleFormat = 2 +}; +typedef int32_t ANPSampleFormat; + +/** The audio buffer is passed to the callback proc to request more samples. + It is owned by the system, and the callback may read it, but should not + maintain a pointer to it outside of the scope of the callback proc. + */ +struct ANPAudioBuffer { + // RO - repeat what was specified in newTrack() + int32_t channelCount; + // RO - repeat what was specified in newTrack() + ANPSampleFormat format; + /** This buffer is owned by the caller. Inside the callback proc, up to + "size" bytes of sample data should be written into this buffer. The + address is only valid for the scope of a single invocation of the + callback proc. + */ + void* bufferData; + /** On input, specifies the maximum number of bytes that can be written + to "bufferData". On output, specifies the actual number of bytes that + the callback proc wrote into "bufferData". + */ + uint32_t size; +}; + +enum ANPAudioEvents { + /** This event is passed to the callback proc when the audio-track needs + more sample data written to the provided buffer parameter. + */ + kMoreData_ANPAudioEvent = 0, + /** This event is passed to the callback proc if the audio system runs out + of sample data. In this event, no buffer parameter will be specified + (i.e. NULL will be passed to the 3rd parameter). + */ + kUnderRun_ANPAudioEvent = 1 +}; +typedef int32_t ANPAudioEvent; + +/** Called to feed sample data to the track. This will be called in a separate + thread. However, you may call trackStop() from the callback (but you + cannot delete the track). + + For example, when you have written the last chunk of sample data, you can + immediately call trackStop(). This will take effect after the current + buffer has been played. + + The "user" parameter is the same value that was passed to newTrack() + */ +typedef void (*ANPAudioCallbackProc)(ANPAudioEvent event, void* user, + ANPAudioBuffer* buffer); + +struct ANPAudioTrack; // abstract type for audio tracks + +struct ANPAudioTrackInterfaceV0 : ANPInterface { + /** Create a new audio track, or NULL on failure. The track is initially in + the stopped state and therefore ANPAudioCallbackProc will not be called + until the track is started. + */ + ANPAudioTrack* (*newTrack)(uint32_t sampleRate, // sampling rate in Hz + ANPSampleFormat, + int channelCount, // MONO=1, STEREO=2 + ANPAudioCallbackProc, + void* user); + /** Deletes a track that was created using newTrack. The track can be + deleted in any state and it waits for the ANPAudioCallbackProc thread + to exit before returning. + */ + void (*deleteTrack)(ANPAudioTrack*); + + void (*start)(ANPAudioTrack*); + void (*pause)(ANPAudioTrack*); + void (*stop)(ANPAudioTrack*); + /** Returns true if the track is not playing (e.g. pause or stop was called, + or start was never called. + */ + bool (*isStopped)(ANPAudioTrack*); +}; + +struct ANPAudioTrackInterfaceV1 : ANPAudioTrackInterfaceV0 { + /** Returns the track's latency in milliseconds. */ + uint32_t (*trackLatency)(ANPAudioTrack*); +}; + +/////////////////////////////////////////////////////////////////////////////// +// DEFINITION OF VALUES PASSED THROUGH NPP_HandleEvent + +enum ANPEventTypes { + kNull_ANPEventType = 0, + kKey_ANPEventType = 1, + /** Mouse events are triggered by either clicking with the navigational pad + or by tapping the touchscreen (if the kDown_ANPTouchAction is handled by + the plugin then no mouse event is generated). The kKey_ANPEventFlag has + to be set to true in order to receive these events. + */ + kMouse_ANPEventType = 2, + /** Touch events are generated when the user touches on the screen. The + kTouch_ANPEventFlag has to be set to true in order to receive these + events. + */ + kTouch_ANPEventType = 3, + /** Only triggered by a plugin using the kBitmap_ANPDrawingModel. This event + signals that the plugin needs to redraw itself into the provided bitmap. + */ + kDraw_ANPEventType = 4, + kLifecycle_ANPEventType = 5, + + /** This event type is completely defined by the plugin. + When creating an event, the caller must always set the first + two fields, the remaining data is optional. + ANPEvent evt; + evt.inSize = sizeof(ANPEvent); + evt.eventType = kCustom_ANPEventType + // other data slots are optional + evt.other[] = ...; + To post a copy of the event, call + eventInterface->postEvent(myNPPInstance, &evt); + That call makes a copy of the event struct, and post that on the event + queue for the plugin. + */ + kCustom_ANPEventType = 6, + /** MultiTouch events are generated when the user touches on the screen. The + kTouch_ANPEventFlag has to be set to true in order to receive these + events. This type is a replacement for the older kTouch_ANPEventType. + */ + kMultiTouch_ANPEventType = 7, +}; +typedef int32_t ANPEventType; + +enum ANPKeyActions { + kDown_ANPKeyAction = 0, + kUp_ANPKeyAction = 1, +}; +typedef int32_t ANPKeyAction; + +#include "ANPKeyCodes.h" +typedef int32_t ANPKeyCode; + +enum ANPKeyModifiers { + kAlt_ANPKeyModifier = 1 << 0, + kShift_ANPKeyModifier = 1 << 1, +}; +// bit-field containing some number of ANPKeyModifier bits +typedef uint32_t ANPKeyModifier; + +enum ANPMouseActions { + kDown_ANPMouseAction = 0, + kUp_ANPMouseAction = 1, +}; +typedef int32_t ANPMouseAction; + +enum ANPTouchActions { + /** This occurs when the user first touches on the screen. As such, this + action will always occur prior to any of the other touch actions. If + the plugin chooses to not handle this action then no other events + related to that particular touch gesture will be generated. + */ + kDown_ANPTouchAction = 0, + kUp_ANPTouchAction = 1, + kMove_ANPTouchAction = 2, + kCancel_ANPTouchAction = 3, + // The web view will ignore the return value from the following actions + kLongPress_ANPTouchAction = 4, + kDoubleTap_ANPTouchAction = 5, +}; +typedef int32_t ANPTouchAction; + +enum ANPLifecycleActions { + /** The web view containing this plugin has been paused. See documentation + on the android activity lifecycle for more information. + */ + kPause_ANPLifecycleAction = 0, + /** The web view containing this plugin has been resumed. See documentation + on the android activity lifecycle for more information. + */ + kResume_ANPLifecycleAction = 1, + /** The plugin has focus and is now the recipient of input events (e.g. key, + touch, etc.) + */ + kGainFocus_ANPLifecycleAction = 2, + /** The plugin has lost focus and will not receive any input events until it + regains focus. This event is always preceded by a GainFocus action. + */ + kLoseFocus_ANPLifecycleAction = 3, + /** The browser is running low on available memory and is requesting that + the plugin free any unused/inactive resources to prevent a performance + degradation. + */ + kFreeMemory_ANPLifecycleAction = 4, + /** The page has finished loading. This happens when the page's top level + frame reports that it has completed loading. + */ + kOnLoad_ANPLifecycleAction = 5, + /** The browser is honoring the plugin's request to go full screen. Upon + returning from this event the browser will resize the plugin's java + surface to full-screen coordinates. + */ + kEnterFullScreen_ANPLifecycleAction = 6, + /** The browser has exited from full screen mode. Immediately prior to + sending this event the browser has resized the plugin's java surface to + its original coordinates. + */ + kExitFullScreen_ANPLifecycleAction = 7, + /** The plugin is visible to the user on the screen. This event will always + occur after a kOffScreen_ANPLifecycleAction event. + */ + kOnScreen_ANPLifecycleAction = 8, + /** The plugin is no longer visible to the user on the screen. This event + will always occur prior to an kOnScreen_ANPLifecycleAction event. + */ + kOffScreen_ANPLifecycleAction = 9, +}; +typedef uint32_t ANPLifecycleAction; + +struct TouchPoint { + int32_t id; + float x; // relative to your "window" (0...width) + float y; // relative to your "window" (0...height) + float pressure; + float size; // normalized to a value between 0...1 +}; + +/* This is what is passed to NPP_HandleEvent() */ +struct ANPEvent { + uint32_t inSize; // size of this struct in bytes + ANPEventType eventType; + // use based on the value in eventType + union { + struct { + ANPKeyAction action; + ANPKeyCode nativeCode; + int32_t virtualCode; // windows virtual key code + ANPKeyModifier modifiers; + int32_t repeatCount; // 0 for initial down (or up) + int32_t unichar; // 0 if there is no value + } key; + struct { + ANPMouseAction action; + int32_t x; // relative to your "window" (0...width) + int32_t y; // relative to your "window" (0...height) + } mouse; + struct { + ANPTouchAction action; + ANPKeyModifier modifiers; + int32_t x; // relative to your "window" (0...width) + int32_t y; // relative to your "window" (0...height) + } touch; + struct { + ANPLifecycleAction action; + } lifecycle; + struct { + ANPDrawingModel model; + // relative to (0,0) in top-left of your plugin + ANPRectI clip; + // use based on the value in model + union { + ANPBitmap bitmap; + struct { + int32_t width; + int32_t height; + } surface; + } data; + } draw; + struct { + int64_t timestamp; + int32_t id; + ANPTouchAction action; + int32_t pointerCount; + TouchPoint* touchPoint; + } multiTouch; + int32_t other[8]; + } data; +}; + +struct ANPEventInterfaceV0 : ANPInterface { + /** Post a copy of the specified event to the plugin. The event will be + delivered to the plugin in its main thread (the thread that receives + other ANPEvents). If, after posting before delivery, the NPP instance + is torn down, the event will be discarded. + */ + void (*postEvent)(NPP inst, const ANPEvent* event); +}; + + +#endif diff --git a/Source/WebKit/android/smoke/MessageThread.cpp b/Source/WebKit/android/smoke/MessageThread.cpp new file mode 100644 index 0000000..48f2222 --- /dev/null +++ b/Source/WebKit/android/smoke/MessageThread.cpp @@ -0,0 +1,146 @@ +/* + * Copyright 2010, 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 "MessageThread" + +#include "config.h" + +#include <sys/time.h> +#include <time.h> + +#include "MessageThread.h" +#include "ScriptController.h" + +#include <utils/Log.h> + +namespace android { + +static bool compareMessages(const Message& msg1, + const Message& msg2, + bool memberIsNull) { + return (msg1.object() == msg2.object() && + (memberIsNull || msg1.member() == msg2.member())); +} + +bool MessageQueue::hasMessages(const Message& message) { + AutoMutex lock(m_mutex); + + static const Message::GenericMemberFunction nullMember = NULL; + const bool memberIsNull = message.member() == nullMember; + + for (list<Message*>::iterator it = m_messages.begin(); + it != m_messages.end(); ++it) { + Message* m = *it; + if (compareMessages(message, *m, memberIsNull)) + return true; + } + return false; +} + +void MessageQueue::remove(const Message& message) { + AutoMutex lock(m_mutex); + + static const Message::GenericMemberFunction nullMember = NULL; + const bool memberIsNull = message.member() == nullMember; + + for (list<Message*>::iterator it = m_messages.begin(); + it != m_messages.end(); ++it) { + Message* m = *it; + if (compareMessages(message, *m, memberIsNull)) { + it = m_messages.erase(it); + delete m; + } + } +} + +void MessageQueue::post(Message* message) { + AutoMutex lock(m_mutex); + + double when = message->m_when; + LOG_ASSERT(when > 0, "Message time may not be 0"); + + list<Message*>::iterator it; + for (it = m_messages.begin(); it != m_messages.end(); ++it) { + Message* m = *it; + if (when < m->m_when) { + break; + } + } + m_messages.insert(it, message); + m_condition.signal(); +} + +void MessageQueue::postAtFront(Message* message) { + AutoMutex lock(m_mutex); + message->m_when = 0; + m_messages.push_front(message); +} + +Message* MessageQueue::next() { + AutoMutex lock(m_mutex); + while (true) { + if (m_messages.empty()) { + // No messages, wait until another arrives + m_condition.wait(m_mutex); + } + Message* next = m_messages.front(); + double now = WTF::currentTimeMS(); + double diff = next->m_when - now; + if (diff > 0) { + // Not time for this message yet, wait the difference in nanos + m_condition.waitRelative(m_mutex, + static_cast<nsecs_t>(diff * 1000000) /* nanos */); + } else { + // Time for this message to run. + m_messages.pop_front(); + return next; + } + } +} + +bool MessageThread::threadLoop() { + WebCore::ScriptController::initializeThreading(); + + while (true) { + Message* message = m_queue.next(); + if (message != NULL) { + message->run(); + } + } + return false; +} + +// Global thread object obtained by messageThread(). +static sp<MessageThread> gMessageThread; + +MessageThread* messageThread() { + if (gMessageThread == NULL) { + gMessageThread = new MessageThread(); + gMessageThread->run("WebCoreThread"); + } + return gMessageThread.get(); +} + +} // namespace android diff --git a/Source/WebKit/android/smoke/MessageThread.h b/Source/WebKit/android/smoke/MessageThread.h new file mode 100644 index 0000000..ca0115b --- /dev/null +++ b/Source/WebKit/android/smoke/MessageThread.h @@ -0,0 +1,108 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef ANDROID_WEBKIT_MESSAGETHREAD_H +#define ANDROID_WEBKIT_MESSAGETHREAD_H + +#include <list> + +#include "MessageTypes.h" + +#include <utils/threads.h> + +using std::list; + +namespace android { + +class MessageQueue { +public: + MessageQueue() {} + + // Return true if the queue has messages with the given object and member + // function. If member is null, return true if the message has the same + // object. + template <class T> + bool hasMessages(T* object, void (T::*member)(void)); + + // Remove all messages with the given object and member function. If + // member is null, remove all messages with the given object. + template <class T> + void remove(T* object, void (T::*member)(void)); + + // Post a new message to the queue. + void post(Message* closure); + + // Post a new message at the front of the queue. + void postAtFront(Message* closure); + + // Obtain the next message. Blocks until either a new message arrives or + // we reach the time of the next message. + Message* next(); + +private: + bool hasMessages(const Message& message); + void remove(const Message& message); + + list<Message*> m_messages; + Mutex m_mutex; + Condition m_condition; +}; + +template <class T> +bool MessageQueue::hasMessages(T* object, void (T::*member)(void)) { + MemberFunctionMessage<T, void> message(object, member); + return hasMessages(message); +} + +template <class T> +void MessageQueue::remove(T* object, void (T::*member)(void)) { + MemberFunctionMessage<T, void> message(object, member); + remove(message); +} + +class MessageThread : public Thread { +public: + MessageQueue& queue() { return m_queue; } + +private: + MessageThread() : Thread(true /* canCallJava */) {} + + virtual bool threadLoop(); + + MessageQueue m_queue; + // Used for thread initialization + Mutex m_mutex; + Condition m_condition; + + friend MessageThread* messageThread(); +}; + +// Get (possibly creating) the global MessageThread object used to pass +// messages to WebCore. +MessageThread* messageThread(); + +} // namespace android + +#endif // ANDROID_WEBKIT_MESSAGETHREAD_H diff --git a/Source/WebKit/android/smoke/MessageTypes.h b/Source/WebKit/android/smoke/MessageTypes.h new file mode 100644 index 0000000..7da6cb8 --- /dev/null +++ b/Source/WebKit/android/smoke/MessageTypes.h @@ -0,0 +1,159 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef ANDROID_WEBKIT_MESSAGETYPES_H_ +#define ANDROID_WEBKIT_MESSAGETYPES_H_ + +#include <wtf/CurrentTime.h> + +// TODO(phanna): autogenerate these types! + +namespace android { + +// Forward declared for friendship! +class MessageQueue; + +// Removes the reference from the typename so we store the actual value in the +// closure. +template <typename T> struct remove_reference { typedef T type; }; +template <typename T> struct remove_reference<T&> { typedef T type; }; + +// Prevent the compiler from inferring the type. +template <typename T> struct identity { typedef T type; }; + +// Message base class. Defines the public run() method and contains generic +// object and member function variables for use in MessageQueue. +// +// Note: The template subclass MemberFunctionMessage casts its object and +// member function to the generic void* and Message::* types. During run(), +// each template specialization downcasts to the original type and invokes the +// correct function. This may seem dangerous but the compiler enforces +// correctness in NewMessage and in the template constructor. +class Message { +public: + typedef void (Message::*GenericMemberFunction)(void); + + virtual ~Message() {} + virtual void run() = 0; + + // The wall time that the message is supposed to run. + double m_when; + + void* object() const { return m_object; } + GenericMemberFunction member() const { return m_member; } + +protected: + Message(void* object, GenericMemberFunction member, long delay = 0) + : m_object(object) + , m_member(member) { + m_when = WTF::currentTimeMS() + delay; + } + + // Downcast back to the original template params in run(). Also accessed + // by MessageQueue to compare messages. + void* m_object; + GenericMemberFunction m_member; + +private: + // Disallow copy + Message(const Message&); +}; + +// Forward declaration for partial specialization. +template <class T, typename A1> +class MemberFunctionMessage; + +template <class T> +class MemberFunctionMessage<T, void> : public Message { +private: + typedef void (T::*MemberSignature)(); + +public: + inline MemberFunctionMessage(T* object, + MemberSignature member, + long delay = 0) + : Message(reinterpret_cast<void*>(object), + reinterpret_cast<GenericMemberFunction>(member), + delay) {} + + virtual void run() { + MemberSignature member = reinterpret_cast<MemberSignature>(m_member); + (reinterpret_cast<T*>(m_object)->*member)(); + delete this; + } +}; + +template <class T> +inline Message* NewMessage(T* object, void (T::*member)()) { + return new MemberFunctionMessage<T, void>(object, member); +} + +template <class T> +inline Message* NewDelayedMessage(T* object, void (T::*member)(), long delay) { + return new MemberFunctionMessage<T, void>(object, member, delay); +} + +template <class T, typename A1> +class MemberFunctionMessage : public Message { +private: + typedef void (T::*MemberSignature)(A1); + +public: + inline MemberFunctionMessage(T* object, + MemberSignature member, + A1 arg1, + long delay = 0) + : Message(reinterpret_cast<void*>(object), + reinterpret_cast<GenericMemberFunction>(member), + delay) + , m_arg1(arg1) {} + + virtual void run() { + MemberSignature member = reinterpret_cast<MemberSignature>(m_member); + (reinterpret_cast<T*>(m_object)->*member)(m_arg1); + delete this; + } + +private: + typename remove_reference<A1>::type m_arg1; +}; + +template <class T, typename A1> +inline Message* NewMessage(T* object, void (T::*member)(A1), + typename identity<A1>::type arg1) { + return new MemberFunctionMessage<T, A1>( + object, member, arg1); +} + +template <class T, typename A1> +inline Message* NewDelayedMessage(T* object, void (T::*member)(A1), + typename identity<A1>::type arg1, long delay) { + return new MemberFunctionMessage<T, A1>(object, member, arg1, delay); +} + +} // namespace android + + +#endif // ANDROID_WEBKIT_MESSAGETYPES_H_ diff --git a/Source/WebKit/android/wds/Command.cpp b/Source/WebKit/android/wds/Command.cpp new file mode 100644 index 0000000..bd8536f --- /dev/null +++ b/Source/WebKit/android/wds/Command.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 2008, 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 "wds" +#include "config.h" + +#include "AndroidLog.h" +#include "Command.h" +#include "Connection.h" +#include "DebugServer.h" +#include "Frame.h" +#include "RenderTreeAsText.h" +#include "RenderView.h" +#include "WebViewCore.h" +#include <utils/Log.h> +#include <wtf/text/CString.h> + +#if ENABLE(WDS) + +using namespace WebCore; + +namespace android { + +namespace WDS { + +//------------------------------------------------------------------------------ +// Actual commands -- XXX should be moved somewhere else +//------------------------------------------------------------------------------ +static bool callDumpRenderTree(const Frame* frame, const Connection* conn) { + CString str = externalRepresentation(frame->contentRenderer()).latin1(); + conn->write(str.data(), str.length()); + return true; +} + +static bool callDumpDomTree(const Frame* frame, const Connection* conn) { + WebViewCore::getWebViewCore(frame->view())->dumpDomTree(true); + + FILE* f = fopen(DOM_TREE_LOG_FILE, "r"); + if (!f) { + conn->write("Dom tree written to logcat\n"); + } else { + char buf[512]; + while (true) { + int nread = fread(buf, 1, sizeof(buf), f); + if (nread <= 0) + break; + conn->write(buf, nread); + } + fclose(f); + } + return true; +} + +class WebCoreHandler : public Handler { +public: + virtual void post(TargetThreadFunction func, void* v) const { + callOnMainThread(func, v); + } +}; +static WebCoreHandler s_webcoreHandler; + +//------------------------------------------------------------------------------ +// End command section +//------------------------------------------------------------------------------ + +class InternalCommand : public Command { +public: + InternalCommand(const Command* comm, const Frame* frame, + const Connection* connection) + : Command(*comm) + , m_frame(frame) + , m_connection(connection) {} + virtual ~InternalCommand() { delete m_connection; } + + void doCommand() const { + LOGD("Executing command '%s' (%s)", m_name, m_description); + if (!m_dispatch(m_frame, m_connection)) + // XXX: Have useful failure messages + m_connection->write("EPIC FAIL!\n", 11); + } + +private: + const Frame* m_frame; + const Connection* m_connection; +}; + +static void commandDispatcher(void* v) { + InternalCommand* c = static_cast<InternalCommand*>(v); + c->doCommand(); + delete c; +} + +void Command::dispatch() { + m_handler.post(commandDispatcher, this); +} + +Vector<const Command*>* Command::s_commands; + +void Command::Init() { + // Do not initialize twice. + if (s_commands) + return; + // XXX: Move this somewhere else. + s_commands = new Vector<const Command*>(); + s_commands->append(new Command("DDOM", "Dump Dom Tree", + callDumpDomTree, s_webcoreHandler)); + s_commands->append(new Command("DDRT", "Dump Render Tree", + callDumpRenderTree, s_webcoreHandler)); +} + +Command* Command::Find(const Connection* conn) { + char buf[COMMAND_LENGTH]; + if (conn->read(buf, sizeof(buf)) != COMMAND_LENGTH) + return NULL; + + // Linear search of commands. TODO: binary search when more commands are + // added. + Vector<const Command*>::const_iterator i = s_commands->begin(); + Vector<const Command*>::const_iterator end = s_commands->end(); + while (i != end) { + if (strncmp(buf, (*i)->name(), sizeof(buf)) == 0) + return new InternalCommand(*i, server()->getFrame(0), conn); + i++; + } + return NULL; +} + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/wds/Command.h b/Source/WebKit/android/wds/Command.h new file mode 100644 index 0000000..90861d4 --- /dev/null +++ b/Source/WebKit/android/wds/Command.h @@ -0,0 +1,108 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef WDS_COMMAND_H +#define WDS_COMMAND_H + +#include "wtf/MainThread.h" +#include "wtf/Vector.h" + +namespace WebCore { +class Frame; +} + +using namespace WTF; +using namespace WebCore; + +namespace android { + +// WebCore Debug Server +namespace WDS { + +class Connection; + +// Command identifier length +#define COMMAND_LENGTH 4 + +// The dispatcher function called with a Frame for context and the established +// connection to the client. The connection can be used to read and write to the +// client application. Return true on successful completion of the command, +// return false to indicate failure. +typedef bool (*DispatchFunction)(const Frame*, const Connection*); + +// Note: Although the type is named MainThreadFunction, it may not always be +// the main thread. The type is generic enough to reuse here but named +// something more appropriate. +typedef MainThreadFunction TargetThreadFunction; + +// Helper class to dipatch functions on a particular thread. +class Handler { +public: + virtual ~Handler() {} + virtual void post(TargetThreadFunction, void*) const = 0; +}; + +// Class for containing information about particular commands. +class Command { +public: + Command(const char* name, const char* desc, const DispatchFunction func, + const Handler& handler) + : m_name(name) + , m_description(desc) + , m_dispatch(func) + , m_handler(handler) {} + Command(const Command& comm) + : m_name(comm.m_name) + , m_description(comm.m_description) + , m_dispatch(comm.m_dispatch) + , m_handler(comm.m_handler) {} + virtual ~Command() {} + + // Initialize the debug server commands + static void Init(); + + // Find the command specified by the client request. + static Command* Find(const Connection* conn); + + // Dispatch this command + void dispatch(); + + const char* name() const { return m_name; } + +protected: + const char* m_name; + const char* m_description; + const DispatchFunction m_dispatch; + +private: + const Handler& m_handler; + static Vector<const Command*>* s_commands; +}; + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/wds/Connection.cpp b/Source/WebKit/android/wds/Connection.cpp new file mode 100644 index 0000000..d7e55ac --- /dev/null +++ b/Source/WebKit/android/wds/Connection.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2008, 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 "wds" +#include "config.h" + +#include "DebugServer.h" // used for ENABLE_WDS +#include "Connection.h" +#include <arpa/inet.h> +#include <string.h> +#include <utils/Log.h> + +#if ENABLE(WDS) + +#define MAX_CONNECTION_QUEUE 5 +#define log_errno(x) LOGE("%s: %d", x, strerror(errno)) + +namespace android { + +namespace WDS { + +bool Socket::open() { + m_fd = socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + log_errno("Failed to create file descriptor"); + return false; + } + return true; +} + +bool ConnectionServer::connect(short port) { + if (!m_socket.open()) + return false; + int fd = m_socket.fd(); + + // Build our sockaddr_in structure use to listen to incoming connections + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + // Try to bind to the given port + if (bind(fd, (sockaddr*) &addr, sizeof(addr)) < 0) { + log_errno("Failed to bind to local host"); + return false; + } + + // Try to listen + if (listen(fd, MAX_CONNECTION_QUEUE) < 0) { + log_errno("Failed to listen"); + return false; + } + + return true; +} + +Connection* ConnectionServer::accept() const { + int conn = ::accept(m_socket.fd(), NULL, NULL); + if (conn < 0) { + log_errno("Accept failed"); + return NULL; + } + return new Connection(conn); +} + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/wds/Connection.h b/Source/WebKit/android/wds/Connection.h new file mode 100644 index 0000000..d67179e --- /dev/null +++ b/Source/WebKit/android/wds/Connection.h @@ -0,0 +1,86 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef WDS_CONNECTION_H +#define WDS_CONNECTION_H + +#include <unistd.h> +#include <sys/socket.h> + +namespace android { + +namespace WDS { + +class Socket { +public: + Socket(): m_fd(-1) {} + Socket(int fd): m_fd(fd) {} + ~Socket() { + if (m_fd != -1) { + shutdown(m_fd, SHUT_RDWR); + close(m_fd); + } + } + // Open a new socket using PF_INET and SOCK_STREAM + bool open(); + int fd() const { return m_fd; } +private: + int m_fd; +}; + +class Connection { +public: + Connection(int conn): m_socket(conn) {} + int read(char buf[], size_t length) const { + return recv(m_socket.fd(), buf, length, 0); + } + int write(const char buf[], size_t length) const { + return send(m_socket.fd(), buf, length, 0); + } + int write(const char buf[]) const { + return write(buf, strlen(buf)); + } +private: + Socket m_socket; +}; + +class ConnectionServer { +public: + ConnectionServer() {} + + // Establish a connection to the local host on the given port. + bool connect(short port); + + // Blocks on the established socket until a new connection arrives. + Connection* accept() const; +private: + Socket m_socket; +}; + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/wds/DebugServer.cpp b/Source/WebKit/android/wds/DebugServer.cpp new file mode 100644 index 0000000..f33a65b --- /dev/null +++ b/Source/WebKit/android/wds/DebugServer.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2008, 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 "wds" +#include "config.h" + +#include "Command.h" +#include "Connection.h" +#include "DebugServer.h" +#include "wtf/MainThread.h" +#include "wtf/Threading.h" +#include <arpa/inet.h> +#include <cutils/properties.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <utils/Log.h> + +#if ENABLE(WDS) + +#define DEFAULT_PORT 9999 +#define log_errno(x) LOGE("%s: %d", x, strerror(errno)) + +namespace android { + +namespace WDS { + +static DebugServer* s_server = NULL; + +// Main thread function for createThread +static void* mainThread(void* v) { + DebugServer* server = static_cast<DebugServer*>(v); + server->start(); + delete server; + s_server = NULL; + return NULL; +} + +DebugServer* server() { + if (s_server == NULL) + s_server = new DebugServer(); + return s_server; +} + +DebugServer::DebugServer() { + // Read webcore.wds.enable to determine if the debug server should run + char buf[PROPERTY_VALUE_MAX]; + int ret = property_get("webcore.wds.enable", buf, NULL); + if (ret != -1 && strcmp(buf, "1") == 0) { + LOGD("WDS Enabled"); + m_threadId = createThread(mainThread, this, "WDS"); + } + // Initialize the available commands. + Command::Init(); +} + +void DebugServer::start() { + LOGD("DebugServer thread started"); + + ConnectionServer cs; + if (!cs.connect(DEFAULT_PORT)) { + LOGE("Failed to start the server socket connection"); + return; + } + + while (true ) { + LOGD("Waiting for incoming connections..."); + Connection* conn = cs.accept(); + if (!conn) { + log_errno("Failed to accept new connections"); + return; + } + LOGD("...Connection established"); + + Command* c = Command::Find(conn); + if (!c) { + LOGE("Could not find matching command"); + delete conn; + } else { + // Dispatch the command, it will handle cleaning up the connection + // when finished. + c->dispatch(); + } + } + + LOGD("DebugServer thread finished"); +} + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/wds/DebugServer.h b/Source/WebKit/android/wds/DebugServer.h new file mode 100644 index 0000000..92edad9 --- /dev/null +++ b/Source/WebKit/android/wds/DebugServer.h @@ -0,0 +1,77 @@ +/* + * Copyright 2008, 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. + */ + +#ifndef DEBUGSERVER_H +#define DEBUGSERVER_H + +// Turn on the wds feature in webkit +#define ENABLE_WDS 0 + +#include "wtf/Threading.h" +#include "wtf/Vector.h" + +// Forward declarations. +namespace WebCore { + class Frame; +} + +using namespace WTF; +using namespace WebCore; + +namespace android { + +// WebCore Debug Server +namespace WDS { + +class DebugServer : WTFNoncopyable::Noncopyable { +public: + void start(); + void addFrame(Frame* frame) { + m_frames.append(frame); + } + void removeFrame(Frame* frame) { + size_t i = m_frames.find(frame); + if (i != notFound) + m_frames.remove(i); + } + Frame* getFrame(unsigned idx) { + if (idx < m_frames.size()) + return m_frames.at(idx); + return NULL; + } +private: + DebugServer(); + WTF::Vector<Frame*> m_frames; + ThreadIdentifier m_threadId; + friend DebugServer* server(); +}; + +DebugServer* server(); + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/wds/client/AdbConnection.cpp b/Source/WebKit/android/wds/client/AdbConnection.cpp new file mode 100644 index 0000000..465f9c3 --- /dev/null +++ b/Source/WebKit/android/wds/client/AdbConnection.cpp @@ -0,0 +1,237 @@ +/* + * Copyright 2009, 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 "wdsclient" + +#include "AdbConnection.h" +#include "ClientUtils.h" +#include "Device.h" +#include <arpa/inet.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <utils/Log.h> + +void AdbConnection::close() { + if (m_fd != -1) { + shutdown(m_fd, SHUT_RDWR); + ::close(m_fd); + m_fd = -1; + } +} + +// Default adb port +#define ADB_PORT 5037 + +bool AdbConnection::connect() { + // Some commands (host:devices for example) close the connection so we call + // connect after the response. + close(); + + m_fd = socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + log_errno("Failed to create socket for connecting to adb"); + return false; + } + + // Create the socket address struct + sockaddr_in adb; + createTcpSocket(adb, ADB_PORT); + + // Connect to adb + if (::connect(m_fd, (sockaddr*) &adb, sizeof(adb)) < 0) { + log_errno("Failed to connect to adb"); + return false; + } + + // Connected + return true; +} + +// Adb protocol stuff +#define MAX_COMMAND_LENGTH 1024 +#define PAYLOAD_LENGTH 4 +#define PAYLOAD_FORMAT "%04X" + +bool AdbConnection::sendRequest(const char* fmt, ...) const { + if (m_fd == -1) { + LOGE("Connection is closed"); + return false; + } + + // Build the command (service) + char buf[MAX_COMMAND_LENGTH]; + va_list args; + va_start(args, fmt); + int res = vsnprintf(buf, MAX_COMMAND_LENGTH, fmt, args); + va_end(args); + + LOGV("Sending command: %04X%.*s", res, res, buf); + + // Construct the payload length + char payloadLen[PAYLOAD_LENGTH + 1]; + snprintf(payloadLen, sizeof(payloadLen), PAYLOAD_FORMAT, res); + + // First, send the payload length + if (send(m_fd, payloadLen, PAYLOAD_LENGTH, 0) < 0) { + log_errno("Failure when sending payload"); + return false; + } + + // Send the actual command + if (send(m_fd, buf, res, 0) < 0) { + log_errno("Failure when sending command"); + return false; + } + + // Check for the OKAY from adb + return checkOkayResponse(); +} + +static void printFailureMessage(int fd) { + // Grab the payload length + char lenStr[PAYLOAD_LENGTH + 1]; + int payloadLen = recv(fd, lenStr, sizeof(lenStr) - 1, 0); + LOG_ASSERT(payloadLen == PAYLOAD_LENGTH, "Incorrect payload size"); + lenStr[PAYLOAD_LENGTH] = 0; + + // Parse the hex payload + payloadLen = strtol(lenStr, NULL, 16); + if (payloadLen < 0) + return; + + // Grab the message + char* msg = new char[payloadLen + 1]; // include null-terminator + int res = recv(fd, msg, payloadLen, 0); + if (res < 0) { + log_errno("Failure reading failure message from adb"); + return; + } else if (res != payloadLen) { + LOGE("Incorrect payload length %d - expected %d", res, payloadLen); + return; + } + msg[res] = 0; + + // Tell somebody about it + LOGE("Received failure from adb: %s", msg); + + // Cleanup + delete[] msg; +} + +#define ADB_RESPONSE_LENGTH 4 + +bool AdbConnection::checkOkayResponse() const { + LOG_ASSERT(m_fd != -1, "Connection has been closed!"); + + char buf[ADB_RESPONSE_LENGTH]; + int res = recv(m_fd, buf, sizeof(buf), 0); + if (res < 0) { + log_errno("Failure reading response from adb"); + return false; + } + + // Check for a response other than OKAY/FAIL + if ((res == ADB_RESPONSE_LENGTH) && (strncmp(buf, "OKAY", res) == 0)) { + LOGV("Command OKAY"); + return true; + } else if (strncmp(buf, "FAIL", ADB_RESPONSE_LENGTH) == 0) { + // Something happened, print out the reason for failure + printFailureMessage(m_fd); + return false; + } + LOGE("Incorrect response from adb - '%.*s'", res, buf); + return false; +} + +void AdbConnection::clearDevices() { + for (unsigned i = 0; i < m_devices.size(); i++) + delete m_devices.editItemAt(i); + m_devices.clear(); +} + +const DeviceList& AdbConnection::getDeviceList() { + // Clear the current device list + clearDevices(); + + if (m_fd == -1) { + LOGE("Connection is closed"); + return m_devices; + } + + // Try to send the device list request + if (!sendRequest("host:devices")) { + LOGE("Failed to get device list from adb"); + return m_devices; + } + + // Get the payload length + char lenStr[PAYLOAD_LENGTH + 1]; + int res = recv(m_fd, lenStr, sizeof(lenStr) - 1, 0); + if (res < 0) { + log_errno("Failure to read payload size of device list"); + return m_devices; + } + lenStr[PAYLOAD_LENGTH] = 0; + + // Parse the hex payload + int payloadLen = strtol(lenStr, NULL, 16); + if (payloadLen < 0) + return m_devices; + + // Grab the list of devices. The format is as follows: + // <serialno><tab><state><newline> + char* msg = new char[payloadLen + 1]; + res = recv(m_fd, msg, payloadLen, 0); + if (res < 0) { + log_errno("Failure reading the device list"); + return m_devices; + } else if (res != payloadLen) { + LOGE("Incorrect payload length %d - expected %d", res, payloadLen); + return m_devices; + } + msg[res] = 0; + + char serial[32]; + char state[32]; + int numRead; + char* ptr = msg; + while (sscanf(ptr, "%31s\t%31s\n%n", serial, state, &numRead) > 1) { + Device::DeviceType t = Device::DEVICE; + static const char emulator[] = "emulator-"; + if (strncmp(serial, emulator, sizeof(emulator) - 1) == 0) + t = Device::EMULATOR; + LOGV("Adding device %s (%s)", serial, state); + m_devices.add(new Device(serial, t, this)); + + // Reset for the next line + ptr += numRead; + } + // Cleanup + delete[] msg; + + return m_devices; +} diff --git a/Source/WebKit/android/wds/client/AdbConnection.h b/Source/WebKit/android/wds/client/AdbConnection.h new file mode 100644 index 0000000..58bad67 --- /dev/null +++ b/Source/WebKit/android/wds/client/AdbConnection.h @@ -0,0 +1,47 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef WDS_ADB_CONNECTION_H +#define WDS_ADB_CONNECTION_H + +#include "DeviceList.h" + +class AdbConnection { +public: + AdbConnection() : m_fd(-1) {} + ~AdbConnection() { clearDevices(); } + void close(); + bool connect(); + bool sendRequest(const char* fmt, ...) const; + const DeviceList& getDeviceList(); + +private: + bool checkOkayResponse() const; + void clearDevices(); + DeviceList m_devices; + int m_fd; +}; + +#endif diff --git a/Source/WebKit/android/wds/client/Android.mk b/Source/WebKit/android/wds/client/Android.mk new file mode 100644 index 0000000..db79dd4 --- /dev/null +++ b/Source/WebKit/android/wds/client/Android.mk @@ -0,0 +1,39 @@ +## +## Copyright 2008, 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. +## + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + AdbConnection.cpp \ + ClientUtils.cpp \ + Device.cpp \ + main.cpp + +LOCAL_STATIC_LIBRARIES := liblog libutils libcutils + +LOCAL_MODULE:= wdsclient + +include $(BUILD_HOST_EXECUTABLE) diff --git a/Source/WebKit/android/wds/client/ClientUtils.cpp b/Source/WebKit/android/wds/client/ClientUtils.cpp new file mode 100644 index 0000000..f8ca889 --- /dev/null +++ b/Source/WebKit/android/wds/client/ClientUtils.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2009, 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. + */ + +#include "ClientUtils.h" +#include <arpa/inet.h> +#include <string.h> + +void createTcpSocket(sockaddr_in& addr, short port) { + memset(&addr, 0, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); +} diff --git a/Source/WebKit/android/wds/client/ClientUtils.h b/Source/WebKit/android/wds/client/ClientUtils.h new file mode 100644 index 0000000..5da1624 --- /dev/null +++ b/Source/WebKit/android/wds/client/ClientUtils.h @@ -0,0 +1,46 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef WDS_CLIENT_UTILS_H +#define WDS_CLIENT_UTILS_H + +#include <arpa/inet.h> + +/* + * included for sockaddr_in structure, AF_INET definiton and etc. + */ +#ifdef __FreeBSD__ +#include <netinet/in.h> +#include <sys/socket.h> +#endif + +// Callers need to include Log.h and errno.h to use this macro +#define log_errno(str) LOGE("%s: %s", str, strerror(errno)) + +// Fill in the sockaddr_in structure for binding to the localhost on the given +// port +void createTcpSocket(sockaddr_in& addr, short port); + +#endif diff --git a/Source/WebKit/android/wds/client/Device.cpp b/Source/WebKit/android/wds/client/Device.cpp new file mode 100644 index 0000000..789a89d --- /dev/null +++ b/Source/WebKit/android/wds/client/Device.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2009, 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. + */ + +#include "AdbConnection.h" +#include "Device.h" + +bool Device::sendRequest(const char* req) const { + return m_connection->sendRequest("host-serial:%s:%s", m_name, req); +} diff --git a/Source/WebKit/android/wds/client/Device.h b/Source/WebKit/android/wds/client/Device.h new file mode 100644 index 0000000..39d4b12 --- /dev/null +++ b/Source/WebKit/android/wds/client/Device.h @@ -0,0 +1,62 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef WDS_DEVICE_H +#define WDS_DEVICE_H + +#include <stdlib.h> + +class AdbConnection; + +class Device { +public: + // Type of device. + // TODO: Add simulator support + enum DeviceType { + NONE = -1, + EMULATOR, + DEVICE + }; + + // Takes ownership of name + Device(char* name, DeviceType type, const AdbConnection* conn) + : m_connection(conn) + , m_name(strdup(name)) + , m_type(type) {} + ~Device() { free(m_name); } + + const char* name() const { return m_name; } + DeviceType type() const { return m_type; } + + // Send a request to this device. + bool sendRequest(const char* req) const; + +private: + const AdbConnection* m_connection; + char* m_name; + DeviceType m_type; +}; + +#endif diff --git a/Source/WebKit/android/wds/client/DeviceList.h b/Source/WebKit/android/wds/client/DeviceList.h new file mode 100644 index 0000000..45bfb87 --- /dev/null +++ b/Source/WebKit/android/wds/client/DeviceList.h @@ -0,0 +1,35 @@ +/* + * Copyright 2009, 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. + */ + +#ifndef WDS_DEVICE_LIST_H +#define WDS_DEVICE_LIST_H + +#include <utils/Vector.h> + +class Device; + +typedef android::Vector<Device*> DeviceList; + +#endif diff --git a/Source/WebKit/android/wds/client/main.cpp b/Source/WebKit/android/wds/client/main.cpp new file mode 100644 index 0000000..1c7d856 --- /dev/null +++ b/Source/WebKit/android/wds/client/main.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2008, 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 "wdsclient" + +#include "AdbConnection.h" +#include "ClientUtils.h" +#include "Device.h" +#include <arpa/inet.h> +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <utils/Log.h> + +#define DEFAULT_WDS_PORT 9999 +#define STR(x) #x +#define XSTR(x) STR(x) +#define PORT_STR XSTR(DEFAULT_WDS_PORT) + +int wds_open() { + // Create the structure for connecting to the forwarded 9999 port + sockaddr_in addr; + createTcpSocket(addr, DEFAULT_WDS_PORT); + + // Create our socket + int fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + log_errno("Failed to create file descriptor"); + return -1; + } + // Connect to the remote wds server thread + if (connect(fd, (sockaddr*)&addr, sizeof(addr)) < 0) { + log_errno("Failed to connect to remote debug server"); + return -1; + } + return fd; +} + +// Clean up the file descriptor and connections +void wds_close(int fd) { + if (fd != -1) { + shutdown(fd, SHUT_RDWR); + close(fd); + } +} + +int main(int argc, char** argv) { + + Device::DeviceType type = Device::NONE; + + if (argc <= 1) { + LOGE("wdsclient takes at least 1 argument"); + return 1; + } else { + // Parse the options, look for -e or -d to choose a device. + while (true) { + int c = getopt(argc, argv, "ed"); + if (c == -1) + break; + switch (c) { + case 'e': + type = Device::EMULATOR; + break; + case 'd': + type = Device::DEVICE; + break; + default: + break; + } + } + if (optind == argc) { + LOGE("No command specified"); + return 1; + } + } + + // Do the initial connection. + AdbConnection conn; + conn.connect(); + + const DeviceList& devices = conn.getDeviceList(); + // host:devices closes the connection, reconnect + conn.connect(); + + // No device specified and more than one connected, bail + if (type == Device::NONE && devices.size() > 1) { + LOGE("More than one device/emulator, please specify with -e or -d"); + return 1; + } else if (devices.size() == 0) { + LOGE("No devices connected"); + return 1; + } + + // Find the correct device + const Device* device = NULL; + if (type == Device::NONE) + device = devices[0]; // grab the only one + else { + // Search for a matching device type + for (unsigned i = 0; i < devices.size(); i++) { + if (devices[i]->type() == type) { + device = devices[i]; + break; + } + } + } + + if (!device) { + LOGE("No device found!"); + return 1; + } + + // Forward tcp:9999 + if (!device->sendRequest("forward:tcp:" PORT_STR ";tcp:" PORT_STR)) { + LOGE("Failed to send forwarding request"); + return 1; + } + + LOGV("Connecting to localhost port " PORT_STR); + + const char* command = argv[optind]; + int commandLen = strlen(command); +#define WDS_COMMAND_LENGTH 4 + if (commandLen != WDS_COMMAND_LENGTH) { + LOGE("Commands must be 4 characters '%s'", command); + return 1; + } + + // Open the wds connection + int wdsFd = wds_open(); + if (wdsFd == -1) + return 1; + + // Send the command specified + send(wdsFd, command, WDS_COMMAND_LENGTH, 0); // commands are 4 bytes + + // Read and display the response + char response[256]; + int res = 0; + while ((res = recv(wdsFd, response, sizeof(response), 0)) > 0) + printf("%.*s", res, response); + printf("\n\n"); + + // Shutdown + wds_close(wdsFd); + + return 0; +} |