diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
commit | 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch) | |
tree | 11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebKit/android | |
parent | 648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff) | |
download | external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebKit/android')
116 files changed, 29332 insertions, 0 deletions
diff --git a/WebKit/android/AndroidLog.h b/WebKit/android/AndroidLog.h new file mode 100644 index 0000000..e62153b --- /dev/null +++ b/WebKit/android/AndroidLog.h @@ -0,0 +1,47 @@ +/* + * 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 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 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 "/data/data/com.android.browser/displayTree.txt" + +#endif /* ANDROIDLOG_H_ */ diff --git a/WebKit/android/JavaVM/jni.h b/WebKit/android/JavaVM/jni.h new file mode 100644 index 0000000..93c73f3 --- /dev/null +++ b/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 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 _JNI_COVER_H_ +#define _JNI_COVER_H_ + +#include "nativehelper/jni.h" +#define AttachCurrentThread(a, b) AttachCurrentThread((JNIEnv**) a, b) + +#endif diff --git a/WebKit/android/RenderSkinAndroid.cpp b/WebKit/android/RenderSkinAndroid.cpp new file mode 100644 index 0000000..a261b27 --- /dev/null +++ b/WebKit/android/RenderSkinAndroid.cpp @@ -0,0 +1,71 @@ +/* + * 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 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" + +#include "config.h" +#include "RenderSkinAndroid.h" +#include "RenderSkinButton.h" +#include "RenderSkinCombo.h" +#include "RenderSkinRadio.h" +#include "SkImageDecoder.h" + +#include "utils/AssetManager.h" +#include "utils/Asset.h" + +namespace WebCore { + +RenderSkinAndroid::RenderSkinAndroid() + : m_height(0) + , m_width(0) +{} + +void RenderSkinAndroid::Init(android::AssetManager* am) +{ + RenderSkinButton::Init(am); + RenderSkinCombo::Init(am); + RenderSkinRadio::Init(am); +} + +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/WebKit/android/RenderSkinAndroid.h b/WebKit/android/RenderSkinAndroid.h new file mode 100644 index 0000000..c693dd1 --- /dev/null +++ b/WebKit/android/RenderSkinAndroid.h @@ -0,0 +1,92 @@ +/* + * 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 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 RenderSkinAndroid_h +#define RenderSkinAndroid_h + +namespace android { + class AssetManager; +} + +class SkBitmap; + +namespace WebCore { +class Node; +class PlatformGraphicsContext; + +/* RenderSkinAndroid is the base class for all RenderSkins. Form elements each have a + * subclass for drawing themselves. + */ +class RenderSkinAndroid +{ +public: + RenderSkinAndroid(); + virtual ~RenderSkinAndroid() {} + + enum State { + kDisabled, + kNormal, + kFocused, + kPressed, + + kNumStates + }; + + /** + * Initialize the Android skinning system. The AssetManager may be used to find resources used + * in rendering. + */ + static void Init(android::AssetManager*); + + /* 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); + + /* draw() tells the skin to draw itself, and returns true if the skin needs + * a redraw to animations, false otherwise + */ + virtual bool draw(PlatformGraphicsContext*) { return false; } + + /* notifyState() checks to see if the element is checked, focused, and enabled + * it must be implemented in the subclass + */ + virtual void notifyState(Node* element) { } + + /* setDim() tells the skin its width and height + */ + virtual void setDim(int width, int height) { m_width = width; m_height = height; } + +protected: + int m_height; + int m_width; + +}; + +} // WebCore + +#endif + diff --git a/WebKit/android/RenderSkinButton.cpp b/WebKit/android/RenderSkinButton.cpp new file mode 100644 index 0000000..c878cb9 --- /dev/null +++ b/WebKit/android/RenderSkinButton.cpp @@ -0,0 +1,107 @@ +/* + * 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 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" + +#include "config.h" +#include "android_graphics.h" +#include "Document.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinButton.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include "SkRect.h" +#include <utils/Debug.h> +#include <utils/Log.h> + +struct PatchData { + const char* name; + int8_t outset, margin; +}; + +static const PatchData gFiles[] = + { + { "res/drawable/btn_default_normal_disable.9.png", 2, 7 }, + { "res/drawable/btn_default_normal.9.png", 2, 7 }, + { "res/drawable/btn_default_selected.9.png", 2, 7 }, + { "res/drawable/btn_default_pressed.9.png", 2, 7 } + }; + +static SkBitmap gButton[sizeof(gFiles)/sizeof(gFiles[0])]; +static bool gDecoded; + +namespace WebCore { + +void RenderSkinButton::Init(android::AssetManager* am) +{ + static bool gInited; + if (gInited) + return; + + gInited = true; + gDecoded = true; + for (size_t i = 0; i < sizeof(gFiles)/sizeof(gFiles[0]); i++) { + if (!RenderSkinAndroid::DecodeBitmap(am, gFiles[i].name, &gButton[i])) { + gDecoded = false; + LOGD("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw"); + break; + } + } + + // 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) +{ + // 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; + } + + // Ensure that the state is within the valid range of our array. + SkASSERT(static_cast<unsigned>(newState) < + static_cast<unsigned>(RenderSkinAndroid::kNumStates)); + + // Set up the ninepatch information for drawing. + SkRect bounds; + android_setrect(&bounds, r); + const PatchData& pd = gFiles[newState]; + int marginValue = pd.margin + pd.outset; + + SkIRect margin; + + margin.set(marginValue, marginValue, marginValue, marginValue); + + // Draw to the canvas. + SkNinePatch::DrawNine(canvas, bounds, gButton[newState], margin); +} + +} //WebCore + diff --git a/WebKit/android/RenderSkinButton.h b/WebKit/android/RenderSkinButton.h new file mode 100644 index 0000000..55eb2da --- /dev/null +++ b/WebKit/android/RenderSkinButton.h @@ -0,0 +1,52 @@ +/* + * 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 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 RenderSkinButton_h +#define RenderSkinButton_h + +#include "RenderSkinAndroid.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. + */ + static void Init(android::AssetManager*); + /** + * 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& , RenderSkinAndroid::State); +}; + +} // WebCore +#endif + diff --git a/WebKit/android/RenderSkinCombo.cpp b/WebKit/android/RenderSkinCombo.cpp new file mode 100644 index 0000000..902f2c0 --- /dev/null +++ b/WebKit/android/RenderSkinCombo.cpp @@ -0,0 +1,71 @@ +/* + * 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 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 "RenderSkinCombo.h" + +#include "Document.h" +#include "Node.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" + +namespace WebCore { + +static SkBitmap s_bitmap[2]; // Collection of assets for a combo box +static bool s_decoded; // True if all assets were decoded +static const int s_margin = 2; +static const SkIRect s_mar = { s_margin, s_margin, + RenderSkinCombo::extraWidth(), s_margin }; + +RenderSkinCombo::RenderSkinCombo() +{ +} + +void RenderSkinCombo::Init(android::AssetManager* am) +{ + if (s_decoded) + return; + // Maybe short circuiting is fine, since I don't even draw if one state is not decoded properly + // but is that necessary in the final version? + s_decoded = RenderSkinAndroid::DecodeBitmap(am, "images/combobox-noHighlight.png", &s_bitmap[kNormal]); + s_decoded = RenderSkinAndroid::DecodeBitmap(am, "images/combobox-disabled.png", &s_bitmap[kDisabled]) && s_decoded; +} + + +bool RenderSkinCombo::Draw(SkCanvas* canvas, Node* element, int x, int y, int width, int height) +{ + if (!s_decoded) + return true; + State state = element && element->isEnabled() ? kNormal : kDisabled; + if (height < (s_margin<<1) + 1) { + height = (s_margin<<1) + 1; + } + SkRect bounds; + bounds.set(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + width), SkIntToScalar(y + height)); + SkNinePatch::DrawNine(canvas, bounds, s_bitmap[state], s_mar); + return false; +} + +} //WebCore diff --git a/WebKit/android/RenderSkinCombo.h b/WebKit/android/RenderSkinCombo.h new file mode 100644 index 0000000..b5321ff --- /dev/null +++ b/WebKit/android/RenderSkinCombo.h @@ -0,0 +1,65 @@ +/* + * 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 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 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: + RenderSkinCombo(); + virtual ~RenderSkinCombo() {} + + /** + * Initialize the class before use. Uses the AssetManager to initialize any bitmaps the class may use. + */ + static void Init(android::AssetManager*); + + /** + * 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; } + +private: + + static const int arrowMargin = 22; +}; + +} // WebCore + +#endif diff --git a/WebKit/android/RenderSkinRadio.cpp b/WebKit/android/RenderSkinRadio.cpp new file mode 100644 index 0000000..2fca175 --- /dev/null +++ b/WebKit/android/RenderSkinRadio.cpp @@ -0,0 +1,86 @@ +/* + * 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 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 "RenderSkinRadio.h" + +#include "android_graphics.h" +#include "Document.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinAndroid.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkRect.h" + +static const char* checks[] = { "res/drawable/checkbox_off_background.png", "res/drawable/checkbox_on_background.png", + "res/drawable/radiobutton_off_background.png", "res/drawable/radiobutton_on_background.png"}; +static const SkScalar SIZE = SkIntToScalar(19); // Default height and width - corresponds with the bitmap - perhaps we should query the bitmap. + +namespace WebCore { + +static SkBitmap s_bitmap[4]; +static bool s_decoded; + +void RenderSkinRadio::Init(android::AssetManager* am) +{ + if (s_decoded) + return; + s_decoded = RenderSkinAndroid::DecodeBitmap(am, checks[0], &s_bitmap[0]); + s_decoded = RenderSkinAndroid::DecodeBitmap(am, checks[1], &s_bitmap[1]) && s_decoded; + s_decoded = RenderSkinAndroid::DecodeBitmap(am, checks[2], &s_bitmap[2]) && s_decoded; + s_decoded = RenderSkinAndroid::DecodeBitmap(am, checks[3], &s_bitmap[3]) && s_decoded; +} + +void RenderSkinRadio::Draw(SkCanvas* canvas, Node* element, const IntRect& ir, + bool isCheckBox) +{ + if (!s_decoded || !element) { + return; + } + SkRect r; + android_setrect(&r, ir); + int saveLayerCount = 0; + int saveScaleCount = 0; + if (!element->isEnabled()) { + saveLayerCount = canvas->saveLayerAlpha(&r, 0x80); + } + SkScalar width = r.width(); + if (SIZE != width) { + SkScalar scale = SkScalarDiv(width, SIZE); + saveScaleCount = canvas->scale(scale, scale); + } + canvas->drawBitmap(s_bitmap[element->isChecked() + 2*(!isCheckBox)], + r.fLeft, r.fTop, NULL); + if (saveLayerCount != 0) { + canvas->restoreToCount(saveLayerCount); + } else if (saveScaleCount != 0) { + canvas->restoreToCount(saveScaleCount); + } + return; +} + +} //WebCore + diff --git a/WebKit/android/RenderSkinRadio.h b/WebKit/android/RenderSkinRadio.h new file mode 100644 index 0000000..f70098f --- /dev/null +++ b/WebKit/android/RenderSkinRadio.h @@ -0,0 +1,59 @@ +/* + * 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 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 RenderSkinRadio_h +#define RenderSkinRadio_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*); + + /** + * 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/WebKit/android/TimeCounter.cpp b/WebKit/android/TimeCounter.cpp new file mode 100644 index 0000000..0dc0f3e --- /dev/null +++ b/WebKit/android/TimeCounter.cpp @@ -0,0 +1,156 @@ +/* + * 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" + +#include "config.h" +#include "TimeCounter.h" + +#include "CString.h" +#include "Cache.h" +#include "KURL.h" +#include "SystemTime.h" +#include <runtime/JSGlobalObject.h> +#include <utils/Log.h> + +using namespace WebCore; + +namespace android { + +#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]; + +static const char* timeCounterNames[] = { + "css parsing", + "javascript", + "calculate style", + "Java callback (frame bridge)", + "parsing (may include calcStyle or Java callback)", + "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 = get_thread_msec(); + 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) +{ + String urlString = url; + int totalTime = static_cast<int>((currentTime() - sStartTotalTime) * 1000); + int threadTime = get_thread_msec() - 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); +} + +void TimeCounter::reportNow() +{ + double current = currentTime(); + uint32_t currentThread = get_thread_msec(); + 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 = get_thread_msec(); +} + +void TimeCounter::start(enum Type type) +{ + uint32_t time = get_thread_msec(); + if (sRecordWebCoreTime) { + sStartWebCoreThreadTime = time; + sRecordWebCoreTime = false; + } + sStartTime[type] = time; +} + +#endif + +} diff --git a/WebKit/android/TimeCounter.h b/WebKit/android/TimeCounter.h new file mode 100644 index 0000000..d1b2dff --- /dev/null +++ b/WebKit/android/TimeCounter.h @@ -0,0 +1,98 @@ +/* + * 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 TIME_COUNTER_H +#define TIME_COUNTER_H + +#ifdef ANDROID_INSTRUMENT + +#include "SystemTime.h" + +namespace WebCore { + +class KURL; + +} + +namespace android { + +class TimeCounter { +public: + enum Type { + // function base counters + CSSTimeCounter, + JavaScriptTimeCounter, + 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); + 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(WebCore::get_thread_msec()) {} + ~TimeCounterAuto() { + uint32_t time = WebCore::get_thread_msec(); + TimeCounter::sEndWebCoreThreadTime = time; + TimeCounter::sTotalTimeUsed[m_type] += time - m_startTime; + TimeCounter::sCounter[m_type]++; + } +private: + TimeCounter::Type m_type; + uint32_t m_startTime; +}; + +} +#endif + +#endif diff --git a/WebKit/android/TimerClient.h b/WebKit/android/TimerClient.h new file mode 100644 index 0000000..09f9fc0 --- /dev/null +++ b/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 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 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; + }; + +} +#endif + diff --git a/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp b/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp new file mode 100644 index 0000000..abc6a32 --- /dev/null +++ b/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp @@ -0,0 +1,257 @@ +/* + * 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 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" + +#include "config.h" + +#include "ChromeClientAndroid.h" +#include "CString.h" +#include "Document.h" +#include "PlatformString.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "Page.h" +#include "Screen.h" +#include "ScriptController.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreViewBridge.h" +#include "WebViewCore.h" +#include "WindowFeatures.h" +#include "Settings.h" + +namespace android { + +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() +{ + // only seems to be used for dashboard regions, so just return 1 + return 1; +} + +void ChromeClientAndroid::focus() { + ASSERT(m_webFrame); + // Ask the application to focus this WebView. + m_webFrame->requestFocus(); +} +void ChromeClientAndroid::unfocus() { notImplemented(); } + +bool ChromeClientAndroid::canTakeFocus(FocusDirection) { notImplemented(); return false; } +void ChromeClientAndroid::takeFocus(FocusDirection) { notImplemented(); } + +Page* ChromeClientAndroid::createWindow(Frame* frame, const FrameLoadRequest&, + const WindowFeatures& features) +{ + 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 + + WTF::PassRefPtr<WebCore::Screen> screen = WebCore::Screen::create(frame); + bool dialog = features.dialog || !features.resizable + || (features.heightSet && features.height < screen.get()->height() + && features.widthSet && features.width < screen.get()->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, + frame->script()->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(); } + +// This function is called by the JavaScript bindings to print usually an error to +// a message console. +void ChromeClientAndroid::addMessageToConsole(const String& message, unsigned int lineNumber, const String& sourceID) { + notImplemented(); + LOGD("Console: %s line: %d source: %s\n", message.latin1().data(), lineNumber, sourceID.latin1().data()); +} + +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(false); + // 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()->removeAllEventListenersFromAllNodes(); + // 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() { return true; } + +bool ChromeClientAndroid::tabsToLinks() const { return false; } + +IntRect ChromeClientAndroid::windowResizerRect() const { return IntRect(0, 0, 0, 0); } + +// new to change 38068 (Nov 6, 2008) +void ChromeClientAndroid::repaint(const IntRect& rect, bool contentChanged, + bool immediate, bool repaintContentOnly) { + notImplemented(); +// was in ScrollViewAndroid::update() : needs to be something like: +// android::WebViewCore::getWebViewCore(this)->contentInvalidate(rect); +} + +// 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(); +} + +// new to change 38068 (Nov 6, 2008) +// in place of view()->containingWindow(), webkit now uses view()->hostWindow()->platformWindow() +PlatformWidget ChromeClientAndroid::platformWindow() const { + Page* page = m_webFrame->page(); + Frame* mainFrame = page->mainFrame(); + FrameView* view = mainFrame->view(); + PlatformWidget viewBridge = view->platformWidget(); + return viewBridge; +} + +void ChromeClientAndroid::mouseDidMoveOverElement(const HitTestResult&, unsigned int) {} +void ChromeClientAndroid::setToolTip(const String&) {} +void ChromeClientAndroid::print(Frame*) {} + +void ChromeClientAndroid::exceededDatabaseQuota(Frame*, const String&) {} + +// new to change 38068 (Nov 6, 2008) +void ChromeClientAndroid::runOpenPanel(Frame*, PassRefPtr<FileChooser>) { notImplemented(); } + +} diff --git a/WebKit/android/WebCoreSupport/ChromeClientAndroid.h b/WebKit/android/WebCoreSupport/ChromeClientAndroid.h new file mode 100644 index 0000000..60b3a8f --- /dev/null +++ b/WebKit/android/WebCoreSupport/ChromeClientAndroid.h @@ -0,0 +1,120 @@ +/* + * 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 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 ChromeClientAndroid_h +#define ChromeClientAndroid_h + +#include "ChromeClient.h" + +using namespace WebCore; + +namespace android { + class WebFrame; + + class ChromeClientAndroid : public ChromeClient { + public: + ChromeClientAndroid() : m_webFrame(NULL) {} + virtual void chromeDestroyed(); + + virtual void setWindowRect(const FloatRect&); + virtual FloatRect windowRect(); + + virtual FloatRect pageRect(); + + virtual float scaleFactor(); + + virtual void focus(); + virtual void unfocus(); + + virtual bool canTakeFocus(FocusDirection); + virtual void takeFocus(FocusDirection); + + // 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&); + 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(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 repaint(const IntRect&, bool contentChanged, bool immediate = false, bool repaintContentOnly = false); + 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 PlatformWidget platformWindow() const; + // End methods used by HostWindow. + + virtual void mouseDidMoveOverElement(const HitTestResult&, unsigned int); + + virtual void setToolTip(const String&); + + virtual void print(Frame*); + + virtual void exceededDatabaseQuota(Frame*, const String&); + + virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>); + + // Android-specific + void setWebFrame(android::WebFrame* webframe); + private: + android::WebFrame* m_webFrame; + }; + +} + +#endif diff --git a/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.cpp b/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.cpp new file mode 100644 index 0000000..7aabfc9 --- /dev/null +++ b/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.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 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 "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(); } + +} diff --git a/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.h b/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.h new file mode 100644 index 0000000..1860e4e --- /dev/null +++ b/WebKit/android/WebCoreSupport/ContextMenuClientAndroid.h @@ -0,0 +1,50 @@ +/* + * 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 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 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(); +}; + +} // namespace WebCore + +#endif // ContextMenuClientAndroid_h diff --git a/WebKit/android/WebCoreSupport/DragClientAndroid.cpp b/WebKit/android/WebCoreSupport/DragClientAndroid.cpp new file mode 100644 index 0000000..64406e7 --- /dev/null +++ b/WebKit/android/WebCoreSupport/DragClientAndroid.cpp @@ -0,0 +1,45 @@ +/* + * 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 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" + +#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/WebKit/android/WebCoreSupport/DragClientAndroid.h b/WebKit/android/WebCoreSupport/DragClientAndroid.h new file mode 100644 index 0000000..5f0548f --- /dev/null +++ b/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 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 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/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp b/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp new file mode 100644 index 0000000..fb71fa2 --- /dev/null +++ b/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp @@ -0,0 +1,241 @@ +/* + * 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 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" + +#include "config.h" +#include "Editor.h" +#include "EditorClientAndroid.h" +#include "Event.h" +#include "EventNames.h" +#include "FocusController.h" +#include "Frame.h" +#include "KeyboardCodes.h" +#include "KeyboardEvent.h" +#include "NotImplemented.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformString.h" + +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::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&, Vector<String>&) {} +bool EditorClientAndroid::spellingUIIsShowing() { return false; } +void EditorClientAndroid::checkGrammarOfString(unsigned short const*, int, Vector<GrammarDetail>&, int*, int*) {} +void EditorClientAndroid::checkSpellingOfString(unsigned short const*, int, int*, int*) {} +void EditorClientAndroid::textFieldDidEndEditing(Element*) {} +void EditorClientAndroid::textDidChangeInTextArea(Element*) {} +void EditorClientAndroid::textDidChangeInTextField(Element*) {} +void EditorClientAndroid::textFieldDidBeginEditing(Element*) {} +void EditorClientAndroid::ignoreWordInSpellDocument(String const&) {} +void EditorClientAndroid::respondToChangedSelection() {} +bool EditorClientAndroid::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool) { return m_notFromClick; } +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*) {} + +} diff --git a/WebKit/android/WebCoreSupport/EditorClientAndroid.h b/WebKit/android/WebCoreSupport/EditorClientAndroid.h new file mode 100644 index 0000000..fc35761 --- /dev/null +++ b/WebKit/android/WebCoreSupport/EditorClientAndroid.h @@ -0,0 +1,113 @@ +/* + * 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 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 EditorClientAndroid_h +#define EditorClientAndroid_h + +#include "EditorClient.h" +#include "Page.h" + +using namespace WebCore; + +namespace android { + +class EditorClientAndroid : public EditorClient { +public: + EditorClientAndroid() { m_notFromClick = true; } + virtual void pageDestroyed(); + + virtual bool shouldDeleteRange(Range*); + virtual bool shouldShowDeleteInterface(HTMLElement*); + virtual bool smartInsertDeleteEnabled(); + 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 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 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 void checkGrammarOfString(const UChar*, int length, 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&, Vector<String>& guesses); + virtual bool shouldMoveRangeAfterDelete(Range*, Range*); + virtual void setInputMethodState(bool); + + virtual void handleKeyboardEvent(KeyboardEvent*); + virtual void handleInputMethodKeydown(KeyboardEvent*); + // Android specific: + void setPage(Page* page) { m_page = page; } + void setFromClick(bool fromClick) { m_notFromClick = !fromClick; } +private: + Page* m_page; + bool m_notFromClick; +}; + +} + +#endif diff --git a/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp b/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp new file mode 100644 index 0000000..8782132 --- /dev/null +++ b/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp @@ -0,0 +1,1106 @@ +/* + * 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 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" + +#include "config.h" + +#include "android_graphics.h" +#include "CString.h" +#include "DocumentLoader.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "GraphicsContext.h" +// HTMLFormElement needed for a bad include +#include "HTMLFormElement.h" +#include "HTMLFrameOwnerElement.h" +#include "IconDatabase.h" +#include "MIMETypeRegistry.h" +#include "NotImplemented.h" +#include "Page.h" +#include "PlatformGraphicsContext.h" +#include "PlatformString.h" +#include "PluginDatabase.h" +#include "PluginView.h" +#ifdef ANDROID_PLUGINS +// Removed. +#else +#include "PluginViewBridgeAndroid.h" +#endif +#include "ProgressTracker.h" +#include "RenderPart.h" +#include "ResourceError.h" +#include "SelectionController.h" +#include "SkCanvas.h" +#include "SkRect.h" +#include "TextEncoding.h" +#include "Document.h" +#include "FrameView.h" +#include "HistoryItem.h" +#include "ResourceHandle.h" +#include "ResourceHandleInternal.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreResourceLoader.h" +#include "WebHistory.h" +#include "WebIconDatabase.h" +#include "WebFrameView.h" +#include "WebViewCore.h" +#include "Settings.h" + +#include <utils/AssetManager.h> + +extern android::AssetManager* globalAssetManager(); + +namespace android { + +static const int EXTRA_LAYOUT_DELAY = 1000; + +// FIXME: Need some data for how big this should be. +#define MAX_SESSION_HISTORY 50 +static WTF::Vector<KURL> gSessionHistory; + +bool historyContains(const UChar* chars, unsigned len) { + const KURL url(String(chars, len)); + WTF::Vector<KURL>::const_iterator end = gSessionHistory.end(); + for (WTF::Vector<KURL>::const_iterator i = gSessionHistory.begin(); i != end; ++i) { + if (equalIgnoringRef(url, *i)) + return true; + } + return false; +} + +FrameLoaderClientAndroid::FrameLoaderClientAndroid(WebFrame* webframe) + : m_frame(NULL) + , m_webFrame(webframe) { + 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; +} + +bool FrameLoaderClientAndroid::hasFrameView() const { + // FIXME, + // need to revisit for sub-frame case + return true; +} + +bool FrameLoaderClientAndroid::privateBrowsingEnabled() const { + // FIXME, are we going to support private browsing? + notImplemented(); + return false; +} + +void FrameLoaderClientAndroid::makeRepresentation(DocumentLoader*) { + // don't use representation + verifiedOk(); +} + +void FrameLoaderClientAndroid::forceLayout() { + ASSERT(m_frame); + m_frame->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(); +} + +void FrameLoaderClientAndroid::detachedFromParent4() { + // FIXME, ready to release view + notImplemented(); +} + +void FrameLoaderClientAndroid::loadedFromPageCache() { + // don't support page cache + verifiedOk(); +} + +// 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(); +} + +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::dispatchWillClose() { + notImplemented(); +} + +void FrameLoaderClientAndroid::dispatchDidReceiveIcon() { + ASSERT(m_frame); + if (m_frame->tree() && m_frame->tree()->parent()) + return; + WebCore::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) + icon = WebCore::iconDatabase()->iconForPageURL( + m_frame->loader()->originalRequestURL().string(), + 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::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() { + ASSERT(m_frame); + WebViewCore::getWebViewCore(m_frame->view())->updateFrameGeneration(m_frame); +} + +static void loadDataIntoFrame(Frame* frame, const String& url, + const String& data) { + ResourceRequest request(url); + CString cstr = data.utf8(); + RefPtr<WebCore::SharedBuffer> buf = WebCore::SharedBuffer::create(cstr.data(), cstr.length()); + SubstituteData subData(buf, String("text/html"), String("utf-8"), + request.url()); + frame->loader()->load(request, subData); +} + +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 + WebFrame::RAW_RES_ID id = WebFrame::NODOMAIN; + if ((error.errorCode() == ErrorFile || + error.errorCode() == ErrorFileNotFound) && + (!error.localizedDescription().isEmpty())) { + id = WebFrame::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. + loadDataIntoFrame(m_frame, error.failingURL(), s); + + // Delete the asset. + delete a; +} + +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); + m_frame->document()->setExtraLayoutDelay(EXTRA_LAYOUT_DELAY); + // FIXME: Need to figure out if we need didLayout or didFirstLayout + // see WebViewCore::didLayout + if (!m_frame->tree()->parent()) { + // Only need to notify Java side for the top frame + WebViewCore::getWebViewCore(m_frame->view())->didFirstLayout(); + } +} + +Frame* FrameLoaderClientAndroid::dispatchCreatePage() { + 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&) { + ASSERT(m_frame); + ASSERT(func); + // 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()) { + // Server wants to override our normal policy. + if (TreatAsAttachment(content_disposition)) { + // 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; + } + } else { + // 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; + } + } + // 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; + (m_frame->loader()->*func)(action); +} + +void FrameLoaderClientAndroid::dispatchDecidePolicyForNewWindowAction(FramePolicyFunction func, + const NavigationAction&, const ResourceRequest& req, + PassRefPtr<FormState> formState, const String& frameName) { + ASSERT(m_frame); + // 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(req)) + (m_frame->loader()->*func)(PolicyUse); + else + (m_frame->loader()->*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 (action.type() == NavigationTypeFormResubmitted) { + m_webFrame->decidePolicyForFormResubmission(func); + return; + } else { + (m_frame->loader()->*func)(PolicyUse); + } +} + +void FrameLoaderClientAndroid::dispatchWillSubmitForm(FramePolicyFunction func, PassRefPtr<FormState>) { + ASSERT(m_frame); + ASSERT(func); + (m_frame->loader()->*func)(PolicyUse); +} + +void FrameLoaderClientAndroid::dispatchDidLoadMainResource(DocumentLoader*) { + notImplemented(); +} + +void FrameLoaderClientAndroid::revertToProvisionalState(DocumentLoader*) { + notImplemented(); +} + +void FrameLoaderClientAndroid::setMainDocumentError(DocumentLoader* docLoader, const ResourceError& error) { + ASSERT(m_frame); + if (!error.isNull() && error.errorCode() >= InternalErrorLast) + m_webFrame->reportError(error.errorCode(), + error.localizedDescription(), error.failingURL()); +} + +void FrameLoaderClientAndroid::clearUnarchivingState(DocumentLoader*) { + notImplemented(); +} + +// 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() { + if (!m_frame->tree()->parent()) { + // only need to notify Java for the top frame + WebViewCore::getWebViewCore(m_frame->view())->notifyProgressFinished(); + } +} + +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) + committedLoad(docLoader, 0, 0); +} + +void FrameLoaderClientAndroid::finalSetupForReplace(DocumentLoader*) { + notImplemented(); +} + +void FrameLoaderClientAndroid::updateGlobalHistoryForStandardLoad(const KURL& url) { + ASSERT(m_frame); + const String& str = url.string(); + if (!historyContains(str.characters(), str.length())) { + if (gSessionHistory.size() == MAX_SESSION_HISTORY) + gSessionHistory.remove(0); + gSessionHistory.append(url); + } + m_webFrame->updateVisitedHistory(url, false); +} + +void FrameLoaderClientAndroid::updateGlobalHistoryForReload(const KURL& url) { + ASSERT(m_frame); + m_webFrame->updateVisitedHistory(url, true); +} + +bool FrameLoaderClientAndroid::shouldGoToHistoryItem(HistoryItem* item) const { + // hmmm, seems like we might do a more thoughtful check + ASSERT(m_frame); + return item != NULL; +} + +void FrameLoaderClientAndroid::committedLoad(DocumentLoader* loader, const char* data, int length) { + ASSERT(m_frame); + String encoding = loader->overrideEncoding(); + bool userChosen = !encoding.isNull(); + if (encoding.isNull()) + encoding = loader->response().textEncodingName(); + loader->frameLoader()->setEncoding(encoding, userChosen); + Document *doc = m_frame->document(); + if (doc) { + loader->frameLoader()->addData(data, length); + } +} + +ResourceError FrameLoaderClientAndroid::cancelledError(const ResourceRequest& request) { + return ResourceError(String(), InternalErrorCancelled, String(), String()); +} + +ResourceError FrameLoaderClientAndroid::cannotShowURLError(const ResourceRequest& request) { + return ResourceError(String(), InternalErrorCannotShowUrl, String(), String()); +} + +ResourceError FrameLoaderClientAndroid::interruptForPolicyChangeError(const ResourceRequest& request) { + return ResourceError(String(), InternalErrorInterrupted, String(), String()); +} + +ResourceError FrameLoaderClientAndroid::cannotShowMIMETypeError(const ResourceResponse& request) { + return ResourceError(String(), InternalErrorCannotShowMimeType, String(), String()); +} + +ResourceError FrameLoaderClientAndroid::fileDoesNotExistError(const ResourceResponse& request) { + return ResourceError(String(), InternalErrorFileDoesNotExist, String(), String()); +} + +ResourceError FrameLoaderClientAndroid::pluginWillHandleLoadError(const ResourceResponse& request) { + return ResourceError(String(), InternalErrorPluginWillHandleLoadError, String(), String()); +} + +bool FrameLoaderClientAndroid::shouldFallBack(const ResourceError&) { + notImplemented(); + return false; +} + +void FrameLoaderClientAndroid::setDefersLoading(bool) { + notImplemented(); +} + +bool FrameLoaderClientAndroid::willUseArchive(ResourceLoader*, const ResourceRequest&, + const KURL& originalURL) const { + lowPriority_notImplemented(); + return false; +} + +bool FrameLoaderClientAndroid::isArchiveLoadPending(ResourceLoader*) const { + lowPriority_notImplemented(); + return false; +} + +void FrameLoaderClientAndroid::cancelPendingArchiveLoad(ResourceLoader*) { + notImplemented(); +} + +void FrameLoaderClientAndroid::clearArchivedResources() { + notImplemented(); +} + +bool FrameLoaderClientAndroid::canHandleRequest(const ResourceRequest& request) const { + ASSERT(m_frame); + // Don't allow hijacking of intrapage navigation + if (WebCore::equalIgnoringRef(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; + + if (m_webFrame->canHandleRequest(request)) { +#ifdef ANDROID_META_SUPPORT + // reset metadata settings for the top frame as they are not preserved cross page + if (!m_frame->tree()->parent() && m_frame->settings()) + m_frame->settings()->resetMetadataSettings(); +#endif + return true; + } + return false; +} + +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) || + PluginDatabase::installedPlugins()->isMIMETypeRegistered(mimeType)) + return true; + 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); + m_frame->loader()->setPreviousHistoryItem(0); +} + +void FrameLoaderClientAndroid::saveViewStateToItem(HistoryItem* item) { +#ifdef ANDROID_HISTORY_CLIENT + ASSERT(m_frame); + ASSERT(item); + // We should have added a bridge when the child item was added to its + // parent. + WebHistoryItem* bridge = item->bridge(); + ASSERT(bridge); + // store the current scale (only) for the top frame + if (!m_frame->tree()->parent()) { + bridge->setScale(WebViewCore::getWebViewCore(m_frame->view())->scale()); + } + + WebCore::notifyHistoryItemChanged(item); +#endif +} + +void FrameLoaderClientAndroid::restoreViewState() { +#ifdef ANDROID_HISTORY_CLIENT + WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_frame->view()); + HistoryItem* item = m_frame->loader()->currentHistoryItem(); + // restore the scale (only) for the top frame + if (!m_frame->tree()->parent()) { + webViewCore->restoreScale(item->bridge()->scale()); + } +#endif +} + +#ifdef ANDROID_HISTORY_CLIENT +void FrameLoaderClientAndroid::dispatchDidAddHistoryItem(HistoryItem* item) const { + ASSERT(m_frame); + m_webFrame->addHistoryItem(item); +} + +void FrameLoaderClientAndroid::dispatchDidRemoveHistoryItem(HistoryItem* item, int index) const { + ASSERT(m_frame); + m_webFrame->removeHistoryItem(index); +} + +void FrameLoaderClientAndroid::dispatchDidChangeHistoryIndex( + BackForwardList* list) const { + ASSERT(m_frame); + m_webFrame->updateHistoryIndex(list->backListCount()); +} +#endif + +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() { + ASSERT(m_frame); + m_frame->loader()->detachChildren(); +} + +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); +} + +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 + WebCoreResourceLoader* loader = handle->getInternal()->m_loader; + 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. + FrameView* frameView = new WebCore::FrameView(newFrame); + WebFrameView* webFrameView = new WebFrameView(frameView, + WebViewCore::getWebViewCore(parent->view())); + // frameView Retains webFrameView, so call Release for webFrameView + Release(webFrameView); + // Attach the frameView to the newFrame. + newFrame->setView(frameView); + // setView() refs the frameView so call deref on the frameView + frameView->deref(); + 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 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(); + if (memcmp(data, slash_v_slash, sizeof(slash_v_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)) + 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) +{ + return url.host().endsWith("youtube.com") && isValidYouTubeVideo(url.path()) + && equalIgnoringCase(mimeType, "application/x-shockwave-flash"); +} + +Widget* FrameLoaderClientAndroid::createPlugin( + const IntSize& size, + Element* element, + const KURL& url, + const WTF::Vector<String, 0u>& names, + const WTF::Vector<String, 0u>& values, + const String& mimeType, + bool loadManually) { + // Create an iframe for youtube urls. + if (isYouTubeUrl(url, mimeType)) { + RefPtr<Frame> frame = createFrame(KURL(), String(), + static_cast<HTMLFrameOwnerElement*>(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(), + "file:///android_asset/webkit/", s); + return frame->view(); + } + return NULL; + } +#ifdef ANDROID_PLUGINS + return PluginView::create(m_frame, + size, + element, + url, + names, + values, + mimeType, + loadManually); +#else + return NULL; +#endif +} + +void FrameLoaderClientAndroid::redirectDataToPlugin(Widget* pluginWidget) { + // don't support plugin yet + notImplemented(); +} + +Widget* FrameLoaderClientAndroid::createJavaAppletWidget(const IntSize&, Element*, + const KURL& baseURL, const WTF::Vector<String>& paramNames, + const WTF::Vector<String>& paramValues) { + // don't support widget yet + notImplemented(); + return 0; +} + +// 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) { + if (mimeType.length() == 0) + { + // Guess the mimeType from the extension + if (url.hasPath()) + { + String path = url.path(); + int lastIndex = path.reverseFind('.'); + static const String image("image/"); + if (lastIndex >= 0) + { + String mime(path.substring(lastIndex + 1)); + mime.insert(image, 0); + if (Image::supportsType(mime)) + return ObjectContentImage; + } + } + return ObjectContentFrame; + } + if (equalIgnoringCase(mimeType, "text/html") || + equalIgnoringCase(mimeType, "text/xml") || + equalIgnoringCase(mimeType, "text/") || + equalIgnoringCase(mimeType, "application/xml") || + equalIgnoringCase(mimeType, "application/xhtml+xml") || + equalIgnoringCase(mimeType, "application/x-javascript")) + return ObjectContentFrame; + if (Image::supportsType(mimeType)) + return ObjectContentImage; + // Use OtherPlugin so embed and object tags draw the null plugin view + return ObjectContentOtherPlugin; +} + +// 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::windowObjectCleared() { + 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); +} + +// 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(pageUrl); + if (equalIgnoringRef(u, m_frame->loader()->url())) { + dispatchDidReceiveIcon(); + } +} + +// functions new to Feb-19 tip of tree merge: +// According to the changelog: +// The very Mac-centric "makeDocumentView", "setDocumentViewFromCachedPage", +// and "saveDocumentViewToCachedPage" become "transitionToCommittedForNewPage", +// "transitionToCommittedFromCachedPage", and "savePlatformDataToCachedPage" +// accordingly +void FrameLoaderClientAndroid::savePlatformDataToCachedPage(CachedPage*) { + // don't support page cache + verifiedOk(); +} + +void FrameLoaderClientAndroid::transitionToCommittedFromCachedPage(CachedPage*) { + // don't support page cache + verifiedOk(); +} + +void FrameLoaderClientAndroid::transitionToCommittedForNewPage() { + ASSERT(m_frame); + if (m_frame->settings() && !m_frame->settings()->usesPageCache()) { + m_webFrame->transitionToCommitted(m_frame); + return; + } + + // Remember the old WebFrameView + WebFrameView* webFrameView = static_cast<WebFrameView*> ( + m_frame->view()->platformWidget()); + Retain(webFrameView); + + // Remove the old FrameView + m_frame->setView(NULL); + + // Create a new FrameView and associate it with the saved webFrameView + FrameView* view = new FrameView(m_frame); + webFrameView->setView(view); + + Release(webFrameView); + + // Give the new FrameView to the Frame + m_frame->setView(view); + + // Deref since FrameViews are created with a ref of 1 + view->deref(); + + if (m_frame->ownerRenderer()) + m_frame->ownerRenderer()->setWidget(view); + + m_frame->view()->initScrollbars(); + + m_webFrame->transitionToCommitted(m_frame); +} + +// new as of webkit 34152 +void FrameLoaderClientAndroid::updateGlobalHistory(const KURL& url) { + m_webFrame->updateVisitedHistory(url, false); +} + +} diff --git a/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h b/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h new file mode 100644 index 0000000..58b296e --- /dev/null +++ b/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h @@ -0,0 +1,242 @@ +/* + * 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 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 FrameLoaderClientAndroid_h +#define FrameLoaderClientAndroid_h + +#include "CacheBuilder.h" +#include "FrameLoaderClient.h" +#include "ResourceResponse.h" +#include "WebIconDatabase.h" + +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 bool hasFrameView() const; // ditto + + virtual bool privateBrowsingEnabled() const; + + virtual void makeRepresentation(DocumentLoader*); + virtual void forceLayout(); + virtual void forceLayoutForNonHTML(); + + virtual void setCopiesOnScroll(); + + virtual void detachedFromParent2(); + virtual void detachedFromParent3(); + virtual void detachedFromParent4(); + + virtual void loadedFromPageCache(); + + virtual void assignIdentifierToInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&); + + virtual void dispatchWillSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse); + 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 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 Frame* dispatchCreatePage(); + 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 clearUnarchivingState(DocumentLoader*); + + 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 finalSetupForReplace(DocumentLoader*); + + virtual void updateGlobalHistory(const KURL&); + virtual void updateGlobalHistoryForStandardLoad(const KURL&); + virtual void updateGlobalHistoryForReload(const KURL&); + virtual bool shouldGoToHistoryItem(HistoryItem*) const; +#ifdef ANDROID_HISTORY_CLIENT + virtual void dispatchDidAddHistoryItem(HistoryItem*) const; + virtual void dispatchDidRemoveHistoryItem(HistoryItem*, int) const; + virtual void dispatchDidChangeHistoryIndex(BackForwardList*) const; +#endif + + 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 void setDefersLoading(bool); + + virtual bool willUseArchive(ResourceLoader*, const ResourceRequest&, const KURL& originalURL) const; + virtual bool isArchiveLoadPending(ResourceLoader*) const; + virtual void cancelPendingArchiveLoad(ResourceLoader*); + virtual void clearArchivedResources(); + + virtual bool canHandleRequest(const ResourceRequest&) const; + virtual bool canShowMIMEType(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&); + + virtual String userAgent(const KURL&); + + 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 Widget* createPlugin(const IntSize&, Element*, const KURL&, + const WTF::Vector<WebCore::String, 0u>&, const WTF::Vector<String, 0u>&, + const String&, bool); + virtual void redirectDataToPlugin(Widget* pluginWidget); + + virtual Widget* createJavaAppletWidget(const IntSize&, Element*, const KURL& baseURL, const Vector<String>& paramNames, const Vector<String>& paramValues); + + virtual ObjectContentType objectContentType(const KURL& url, const String& mimeType); + virtual String overrideMediaType() const; + + virtual void windowObjectCleared(); + + virtual void didPerformFirstNavigation() const; + virtual void registerForIconNotification(bool listen = true); + + virtual void savePlatformDataToCachedPage(CachedPage*); + virtual void transitionToCommittedFromCachedPage(CachedPage*); + virtual void transitionToCommittedForNewPage(); + + // 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; } + private: + CacheBuilder m_cacheBuilder; + Frame* m_frame; + WebFrame* m_webFrame; + + 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/WebKit/android/WebCoreSupport/InspectorClientAndroid.h b/WebKit/android/WebCoreSupport/InspectorClientAndroid.h new file mode 100644 index 0000000..9eb85e5 --- /dev/null +++ b/WebKit/android/WebCoreSupport/InspectorClientAndroid.h @@ -0,0 +1,64 @@ +/* + * 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 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 InspectorClientAndroid_h +#define InspectorClientAndroid_h + +#include "InspectorClient.h" + +namespace android { + +class InspectorClientAndroid : public InspectorClient { +public: + virtual ~InspectorClientAndroid() { } + + virtual void inspectorDestroyed() { delete this; } + + virtual Page* createPage() { return NULL; } + + virtual String localizedStringsURL() { return String(); } + + virtual void showWindow() {} + virtual void closeWindow() {} + + virtual void attachWindow() {} + virtual void detachWindow() {} + + virtual void setAttachedWindowHeight(unsigned height) {} + + virtual void highlight(Node*) {} + virtual void hideHighlight() {} + + virtual void inspectedURLChanged(const String& newURL) {} + + // new as of 38068 + virtual void populateSetting(const String&, InspectorController::Setting&) {} + virtual void storeSetting(const String&, const InspectorController::Setting&) {} + virtual void removeSetting(const String&) {} +}; + +} + +#endif diff --git a/WebKit/android/jni/JavaBridge.cpp b/WebKit/android/jni/JavaBridge.cpp new file mode 100644 index 0000000..eefeea5 --- /dev/null +++ b/WebKit/android/jni/JavaBridge.cpp @@ -0,0 +1,319 @@ +/* + * 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 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 "webcoreglue" + +#include <config.h> +#include <wtf/Platform.h> + +#include "Cache.h" +#include "CookieClient.h" +#include "JavaSharedClient.h" +#include "KURL.h" +#include "NetworkStateNotifier.h" +#include "Timer.h" +#include "TimerClient.h" +#include "jni_utility.h" +#include "WebCoreJni.h" + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#include <jni.h> +#include <JNIHelp.h> +#include <SkImageRef_GlobalPool.h> +#include <SkUtils.h> +#include <utils/misc.h> + +// maximum bytes used to cache decoded images +// (not including big images using ashmem) +#define IMAGE_POOL_BUDGET (512 * 1024) + +namespace android { + +// ---------------------------------------------------------------------------- + +static jfieldID gJavaBridge_ObjectID; + +// ---------------------------------------------------------------------------- + +class JavaBridge : public TimerClient, public CookieClient +{ +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, WebCore::KURL const& docURL, WebCore::String const& value); + virtual WebCore::String cookies(WebCore::KURL const& url); + virtual bool cookiesEnabled(); + + //////////////////////////////////////////// + + virtual void setSharedTimerCallback(void (*f)()); + + //////////////////////////////////////////// + + 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 SetDeferringTimers(JNIEnv* env, jobject obj, jboolean defer); + static void ServiceFuncPtrQueue(JNIEnv*); + +private: + jobject mJavaObject; + jmethodID mSetSharedTimer; + jmethodID mStopSharedTimer; + jmethodID mSetCookies; + jmethodID mCookies; + jmethodID mCookiesEnabled; + jmethodID mSignalFuncPtrQueue; +}; + +static void (*sSharedTimerFiredCallback)(); +static JavaBridge* gJavaBridge; + +JavaBridge::JavaBridge(JNIEnv* env, jobject obj) +{ + mJavaObject = adoptGlobalRef(env, 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;Ljava/lang/String;)V"); + mCookies = env->GetMethodID(clazz, "cookies", "(Ljava/lang/String;)Ljava/lang/String;"); + mCookiesEnabled = env->GetMethodID(clazz, "cookiesEnabled", "()Z"); + mSignalFuncPtrQueue = env->GetMethodID(clazz, "signalServiceFuncPtrQueue", "()V"); + + 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"); + + JavaSharedClient::SetTimerClient(this); + JavaSharedClient::SetCookieClient(this); + gJavaBridge = this; +} + +JavaBridge::~JavaBridge() +{ + if (mJavaObject) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteGlobalRef(mJavaObject); + mJavaObject = 0; + } + + JavaSharedClient::SetTimerClient(NULL); + JavaSharedClient::SetCookieClient(NULL); +} + +void +JavaBridge::setSharedTimer(long long timemillis) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mSetSharedTimer, timemillis); +} + +void +JavaBridge::stopSharedTimer() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mStopSharedTimer); +} + +void +JavaBridge::setCookies(WebCore::KURL const& url, WebCore::KURL const& docUrl, WebCore::String const& value) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + const WebCore::String& urlStr = url.string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + const WebCore::String& docUrlStr = docUrl.string(); + jstring jDocUrlStr = env->NewString(docUrlStr.characters(), docUrlStr.length()); + jstring jValueStr = env->NewString(value.characters(), value.length()); + + AutoJObject obj = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mSetCookies, jUrlStr, jDocUrlStr, jValueStr); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jDocUrlStr); + env->DeleteLocalRef(jValueStr); +} + +WebCore::String +JavaBridge::cookies(WebCore::KURL const& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + const WebCore::String& urlStr = url.string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + + AutoJObject obj = getRealObject(env, mJavaObject); + jstring string = (jstring)(env->CallObjectMethod(obj.get(), mCookies, jUrlStr)); + + WebCore::String ret = to_string(env, string); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(string); + return ret; +} + +bool +JavaBridge::cookiesEnabled() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = getRealObject(env, mJavaObject); + jboolean ret = env->CallBooleanMethod(obj.get(), mCookiesEnabled); + return (ret != 0); +} + +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 = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mSignalFuncPtrQueue); +} + +// ---------------------------------------------------------------------------- + +// visible to Shared +void AndroidSignalServiceFuncPtrQueue() +{ + gJavaBridge->signalServiceFuncPtrQueue(); +} + +// ---------------------------------------------------------------------------- + +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); + SkImageRef_GlobalPool::SetRAMBudget(IMAGE_POOL_BUDGET); + LOGV("--- set ImageRef budget %d\n", SkImageRef_GlobalPool::GetRAMBudget()); +} + +void JavaBridge::SetNetworkOnLine(JNIEnv* env, jobject obj, jboolean online) +{ + WebCore::networkStateNotifier().networkStateChange(online); +} + +void JavaBridge::SetDeferringTimers(JNIEnv* env, jobject obj, jboolean defer) +{ + WebCore::setDeferringTimers(defer); +} + +void JavaBridge::ServiceFuncPtrQueue(JNIEnv*) +{ + JavaSharedClient::ServiceFunctionPtrQueue(); +} + +// ---------------------------------------------------------------------------- + +/* + * 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 }, + { "setDeferringTimers", "(Z)V", + (void*) JavaBridge::SetDeferringTimers }, + { "nativeServiceFuncPtrQueue", "()V", + (void*) JavaBridge::ServiceFuncPtrQueue }, +}; + +int register_javabridge(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"); + + return jniRegisterNativeMethods(env, "android/webkit/JWebCoreJavaBridge", + gWebCoreJavaBridgeMethods, NELEM(gWebCoreJavaBridgeMethods)); +} + +} /* namespace android */ diff --git a/WebKit/android/jni/JavaSharedClient.cpp b/WebKit/android/jni/JavaSharedClient.cpp new file mode 100644 index 0000000..f115f62 --- /dev/null +++ b/WebKit/android/jni/JavaSharedClient.cpp @@ -0,0 +1,111 @@ +/* + * 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 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 "JavaSharedClient.h" +#define LOG_TAG "JavaSharedClient" +#include "utils/Log.h" +#include "SkDeque.h" +#include "SkThread.h" + +namespace android { + void AndroidSignalServiceFuncPtrQueue(); + + TimerClient* JavaSharedClient::GetTimerClient() + { + //LOG_ASSERT(gTimerClient != NULL, "gTimerClient not initialized!!!"); + return gTimerClient; + } + + CookieClient* JavaSharedClient::GetCookieClient() + { + //LOG_ASSERT(gCookieClient != NULL, "gCookieClient not initialized!!!"); + return gCookieClient; + } + + void JavaSharedClient::SetTimerClient(TimerClient* client) + { + //LOG_ASSERT(gTimerClient == NULL || client == NULL, "gTimerClient already set, aborting..."); + gTimerClient = client; + } + + void JavaSharedClient::SetCookieClient(CookieClient* client) + { + //LOG_ASSERT(gCookieClient == NULL || client == NULL, "gCookieClient already set, aborting..."); + gCookieClient = client; + } + + TimerClient* JavaSharedClient::gTimerClient = NULL; + CookieClient* JavaSharedClient::gCookieClient = 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(); + + android::AndroidSignalServiceFuncPtrQueue(); + } + + void JavaSharedClient::ServiceFunctionPtrQueue() + { + for (;;) { + void (*proc)(void*); + void* payload; + 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 (NULL != rec) { + proc = rec->fProc; + payload = rec->fPayload; + gFuncPtrQ.pop_front(); + } + gFuncPtrQMutex.release(); + + if (NULL == rec) { + break; + } + proc(payload); + } + } +} diff --git a/WebKit/android/jni/JavaSharedClient.h b/WebKit/android/jni/JavaSharedClient.h new file mode 100644 index 0000000..05788e1 --- /dev/null +++ b/WebKit/android/jni/JavaSharedClient.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 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 JAVA_SHARED_CLIENT_H +#define JAVA_SHARED_CLIENT_H + +namespace android { + + class TimerClient; + class CookieClient; + + class JavaSharedClient + { + public: + static TimerClient* GetTimerClient(); + static CookieClient* GetCookieClient(); + + static void SetTimerClient(TimerClient* client); + static void SetCookieClient(CookieClient* 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; + }; +} +#endif diff --git a/WebKit/android/jni/PictureSet.cpp b/WebKit/android/jni/PictureSet.cpp new file mode 100644 index 0000000..65aac8f --- /dev/null +++ b/WebKit/android/jni/PictureSet.cpp @@ -0,0 +1,665 @@ +/* + * 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 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_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 "SystemTime.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; + pictureAndBounds.mPicture->safeRef(); + pictureAndBounds.mWroteElapsed = false; + mPictures.append(pictureAndBounds); +} + +void PictureSet::add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split) +{ + 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); + picture->safeRef(); + /* 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}; + 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) { + working->mPicture->safeUnref(); + 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(); + working->mPicture->safeUnref(); + } + 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); + pathClip.computeBounds(&pathBounds, SkPath::kFast_BoundsType); + } else { + pathBounds.set(area.getBounds()); + canvas->clipRect(pathBounds); + } + canvas->translate(pathBounds.fLeft, pathBounds.fTop); + canvas->save(); + uint32_t startTime = WebCore::get_thread_msec(); + canvas->drawPicture(*working->mPicture); + size_t elapsed = working->mElapsed = WebCore::get_thread_msec() - 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 void commonDrawBitmap(const SkBitmap& bitmap, + 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 + } + + 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 (emptyPicture(working->mPicture) == false) + 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; + working->mPicture->safeUnref(); + 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) +{ + mPictures[i].mPicture->safeUnref(); + mPictures[i].mPicture = 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); + } 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); + 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); + 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/WebKit/android/jni/PictureSet.h b/WebKit/android/jni/PictureSet.h new file mode 100644 index 0000000..ae250b0 --- /dev/null +++ b/WebKit/android/jni/PictureSet.h @@ -0,0 +1,98 @@ +/* + * 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 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 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); + 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 + }; + void add(const Pictures* temp); + WTF::Vector<Pictures> mPictures; + int mHeight; + int mWidth; + }; +} + +#endif diff --git a/WebKit/android/jni/WebCoreFrameBridge.cpp b/WebKit/android/jni/WebCoreFrameBridge.cpp new file mode 100644 index 0000000..e14a534 --- /dev/null +++ b/WebKit/android/jni/WebCoreFrameBridge.cpp @@ -0,0 +1,1234 @@ +/* + * 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 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 "webcoreglue" + +#include <config.h> +#include <wtf/Platform.h> + +#include "android_graphics.h" +#include "Arena.h" +#include "AtomicString.h" +#include "Cache.h" +#include "ChromeClientAndroid.h" +#include "ContextMenuClientAndroid.h" +#include "CString.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "DragClientAndroid.h" +#include "EditorClientAndroid.h" +#include "Element.h" +#include "Font.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GCController.h" +#include "GraphicsContext.h" +#include "HistoryItem.h" +#include "HTMLElement.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "IconDatabase.h" +#include "Image.h" +#include "InspectorClientAndroid.h" +#include "JSDOMWindow.h" +#include <runtime/InitializeThreading.h> +#include <runtime/JSLock.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 "ScriptController.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SubstituteData.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" +#include "WebCoreResourceLoader.h" +#include "WebHistory.h" +#include "WebIconDatabase.h" +#include "WebFrameView.h" +#include "WebViewCore.h" +#include "wds/DebugServer.h" + +#include <runtime_root.h> +#include <runtime_object.h> +#include <jni_utility.h> +#include "jni.h" +#include "jni_instance.h" + +#include <JNIHelp.h> +#include <SkGraphics.h> +#include <SkImageRef_GlobalPool.h> +#include <utils/misc.h> +#include <utils/AssetManager.h> +#include <android_runtime/android_util_AssetManager.h> + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +namespace android { + +// ---------------------------------------------------------------------------- + +#define WEBCORE_MEMORY_CAP 15 * 1024 * 1024 + +// ---------------------------------------------------------------------------- + +struct WebFrame::JavaBrowserFrame +{ + jobject mObj; + jobject mHistoryList; // WebBackForwardList object + jmethodID mStartLoadingResource; + jmethodID mLoadStarted; + jmethodID mTransitionToCommitted; + jmethodID mLoadFinished; + jmethodID mReportError; + jmethodID mSetTitle; + jmethodID mWindowObjectCleared; + jmethodID mSetProgress; + jmethodID mDidReceiveIcon; + jmethodID mUpdateVisitedHistory; + jmethodID mHandleUrl; + jmethodID mCreateWindow; + jmethodID mCloseWindow; + jmethodID mDecidePolicyForFormResubmission; + jmethodID mRequestFocus; + jmethodID mGetRawResFilename; + 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 = adoptGlobalRef(env, obj); + mJavaFrame->mHistoryList = adoptGlobalRef(env, historyList); + mJavaFrame->mStartLoadingResource = env->GetMethodID(clazz, "startLoadingResource", + "(ILjava/lang/String;Ljava/lang/String;Ljava/util/HashMap;[BIZZ)Landroid/webkit/LoadListener;"); + 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->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;"); + + LOG_ASSERT(mJavaFrame->mStartLoadingResource, "Could not find method startLoadingResource"); + 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->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"); + + mUserAgent = WebCore::String(); + mUserInitiatedClick = false; +} + +WebFrame::~WebFrame() +{ + if (mJavaFrame->mObj) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteGlobalRef(mJavaFrame->mObj); + env->DeleteGlobalRef(mJavaFrame->mHistoryList); + mJavaFrame->mObj = 0; + } + delete mJavaFrame; +} + +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 = env->NewString((unsigned short *)i->first.characters(), i->first.length()); + jstring val = env->NewString((unsigned short *)i->second.characters(), i->second.length()); + if (key && val) { + env->CallObjectMethod(hashMap, put, key, val); + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + } + env->DeleteLocalRef(mapClass); + + return hashMap; +} + +WebCoreResourceLoader* +WebFrame::startLoadingResource(WebCore::ResourceHandle* loader, + const WebCore::ResourceRequest& request, + bool isHighPriority, bool synchronous) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: startLoadingResource(%p, %s)", + loader, request.url().string().ascii().data()); + + WebCore::String method = request.httpMethod(); + WebCore::HTTPHeaderMap headers = request.httpHeaderFields(); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WebCore::String urlStr = request.url().string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + jstring jMethodStr = NULL; + if (!method.isEmpty()) + jMethodStr = env->NewString(method.characters(), method.length()); + jbyteArray jPostDataStr = NULL; + WebCore::FormData* formdata = request.httpBody(); + if (formdata) { + // 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(); + for (size_t i = 0; i < n; ++i) { + const WebCore::FormDataElement& e = elements[i]; + if (e.m_type == WebCore::FormDataElement::data) { + size += e.m_data.size(); + } + } + + // 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; + } + } + env->ReleaseByteArrayElements(jPostDataStr, bytes, 0); + } + } + } + + jobject jHeaderMap = createJavaMapFromHTTPHeaders(env, headers); + + // Convert the WebCore Cache Policy to a WebView Cache Policy. + int cacheMode = 0; // WebView.LOAD_NORMAL + switch (request.cachePolicy()) { + case WebCore::ReloadIgnoringCacheData: + cacheMode = 2; // WebView.LOAD_NO_CACHE + break; + case WebCore::ReturnCacheDataDontLoad: + cacheMode = 3; // WebView.LOAD_CACHE_ONLY + break; + case WebCore::ReturnCacheDataElseLoad: + cacheMode = 1; // WebView.LOAD_CACHE_ELSE_NETWORK + break; + case WebCore::UseProtocolCachePolicy: + default: + break; + } + + LOGV("::WebCore:: startLoadingResource %s with cacheMode %d", urlStr.ascii().data(), cacheMode); + + + jobject jLoadListener = + env->CallObjectMethod(mJavaFrame->frame(env).get(), mJavaFrame->mStartLoadingResource, + (int)loader, jUrlStr, jMethodStr, jHeaderMap, + jPostDataStr, cacheMode, isHighPriority, synchronous); + + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jMethodStr); + env->DeleteLocalRef(jPostDataStr); + env->DeleteLocalRef(jHeaderMap); + if (checkException(env)) + return NULL; + + WebCoreResourceLoader* h = NULL; + if (jLoadListener) + h = new WebCoreResourceLoader(env, jLoadListener); + env->DeleteLocalRef(jLoadListener); + return h; +} + +void +WebFrame::reportError(int errorCode, const WebCore::String& description, + const WebCore::String& failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: reportError(%d, %s)", errorCode, description.ascii().data()); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + + jstring descStr = env->NewString((unsigned short*)description.characters(), description.length()); + jstring failUrl = env->NewString((unsigned short*)failingUrl.characters(), failingUrl.length()); + 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 + const WebCore::KURL& url = frame->loader()->activeDocumentLoader()->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::FrameLoadTypeSame || + (loadType == WebCore::FrameLoadTypeRedirectWithLockedHistory && + !isMainFrame)) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WebCore::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 = env->NewString((unsigned short*)urlString.characters(), urlString.length()); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mLoadStarted, urlStr, favicon, + (int)loadType, isMainFrame); + checkException(env); + env->DeleteLocalRef(urlStr); + if (favicon) + env->DeleteLocalRef(favicon); +} + +void +WebFrame::transitionToCommitted(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = JSC::Bindings::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 = JSC::Bindings::getJNIEnv(); + WebCore::FrameLoader* loader = frame->loader(); + const WebCore::KURL& url = loader->activeDocumentLoader()->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(); + WebCore::String urlString(url.string()); + jstring urlStr = env->NewString((unsigned short*)urlString.characters(), urlString.length()); + 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 = JSC::Bindings::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 = JSC::Bindings::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 = JSC::Bindings::getJNIEnv(); + WebHistory::UpdateHistoryIndex(mJavaFrame->history(env), newIndex); +} + +void +WebFrame::setTitle(const WebCore::String& title) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif +#ifndef NDEBUG + LOGV("setTitle(%s)", title.ascii().data()); +#endif + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jTitleStr = env->NewString((unsigned short *)title.characters(), title.length()); + + 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 = JSC::Bindings::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 = JSC::Bindings::getJNIEnv(); + int progress = (int) (100 * newProgress); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetProgress, progress); + checkException(env); +} + +const WebCore::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 = JSC::Bindings::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::updateVisitedHistory(const WebCore::KURL& url, bool reload) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + WebCore::String urlStr(url.string()); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUrlStr = env->NewString((unsigned short*)urlStr.characters(), urlStr.length()); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mUpdateVisitedHistory, jUrlStr, reload); + checkException(env); +} + +bool +WebFrame::canHandleRequest(const WebCore::ResourceRequest& request) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + // Internal loads are ok but any request that is due to a user hitting a key + // should be checked. + bool userGesture = false; +#ifdef ANDROID_USER_GESTURE + userGesture = request.userGesture(); +#endif + // always handle "POST" in place + if (equalIgnoringCase(request.httpMethod(), "POST")) + return true; + WebCore::KURL requestUrl = request.url(); + if (!mUserInitiatedClick && !userGesture && + (requestUrl.protocolIs("http") || requestUrl.protocolIs("https") || + requestUrl.protocolIs("file") || requestUrl.protocolIs("about") || + requestUrl.protocolIs("javascript"))) + return true; + WebCore::String url(request.url().string()); + // Empty urls should not be sent to java + if (url.isEmpty()) + return true; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + + // 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); + return (ret == 0); +} + +WebCore::Frame* +WebFrame::createWindow(bool dialog, bool userGesture) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = JSC::Bindings::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 = JSC::Bindings::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 = JSC::Bindings::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 = JSC::Bindings::getJNIEnv(); + PolicyFunctionWrapper* p = new PolicyFunctionWrapper; + p->func = func; + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDecidePolicyForFormResubmission, p); +} + +WebCore::String +WebFrame::getRawResourceFilename(RAW_RES_ID id) const +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring ret = (jstring) env->CallObjectMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mGetRawResFilename, (int)id); + + return to_string(env, ret); +} + +// ---------------------------------------------------------------------------- +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!"); + + (pFrame->loader()->*(pFunc->func))((WebCore::PolicyAction)decision); +} + +static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAssetManager, jobject historyList) +{ + JSC::initializeThreading(); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + ChromeClientAndroid* chromeC = new ChromeClientAndroid; + EditorClientAndroid* editorC = new EditorClientAndroid; + WebCore::ContextMenuClient* contextMenuC = new ContextMenuClientAndroid; + WebCore::DragClient* dragC = new DragClientAndroid; + InspectorClientAndroid* inspectorC = new InspectorClientAndroid; + // Create a new page + WebCore::Page* page = new WebCore::Page(chromeC, contextMenuC, editorC, dragC, inspectorC); + // css files without explicit MIMETYPE is treated as generic text files in + // the Java side. So we can't enforce CSS MIMETYPE. + page->settings()->setEnforceCSSMIMETypeInStrictMode(false); + /* TODO: Don't turn on PageCache until we can restore the ScrollView State. + * This caused bug http://b/issue?id=1202983 + page->settings()->setUsesPageCache(true); + // 10 is a random number chosen because it is small enough to give the user + // a good back/forward page cache without allowing the page cache to get too + // big. + WebCore::pageCache()->setCapacity(10); + */ + 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 chromeC 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); + + // Create a FrameView + WebCore::FrameView* frameView = new WebCore::FrameView(frame); + // Create a WebFrameView + WebFrameView* webFrameView = new WebFrameView(frameView, 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); + frameView->deref(); + + // Set the frame to active to turn on keyboard focus. + frame->init(); + frame->selection()->setFocused(true); + + // Allow local access to file:/// and substitute data + WebCore::FrameLoader::setLocalLoadPolicy( + WebCore::FrameLoader::AllowLocalLoadsForLocalAndSubstituteData); + + LOGV("::WebCore:: createFrame %p", frame); + + // Set the mNativeFrame field in Frame + SET_NATIVE_FRAME(env, obj, (int)frame); + + // Setup the asset manager. + AssetManager* am = assetManagerForJavaObject(env, jAssetManager); + // Initialize our skinning classes + WebCore::RenderSkinAndroid::Init(am); +} + +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) +{ +#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!"); + + WebCore::String webcoreUrl = to_string(env, url); + WebCore::ResourceRequest request(webcoreUrl); + LOGV("LoadUrl %s", webcoreUrl.latin1().data()); + pFrame->loader()->load(request); +} + + +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(to_string(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, + to_string(env, mimeType), to_string(env, encoding), + WebCore::KURL(to_string(env, failUrl))); + + // Perform the load + pFrame->loader()->load(request, substituteData); +} + +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(); +} + +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 + WebCore::String renderDump = WebCore::externalRepresentation(pFrame->contentRenderer()); + unsigned len = renderDump.length(); + if (!len) + return NULL; + return env->NewString(renderDump.characters(), len); +} + +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!"); + + WebCore::Element *documentElement = pFrame->document()->documentElement(); + if (!documentElement) + return NULL; + WebCore::String renderDump = ((WebCore::HTMLElement*)documentElement)->innerText(); + renderDump.append("\n"); + unsigned len = renderDump.length(); + if (!len) + return NULL; + return env->NewString((unsigned short*)renderDump.characters(), len); +} + +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) + loader->reloadAllowingStaleData(loader->documentLoader()->overrideEncoding()); + else + loader->reload(); +} + +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->loader()->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!"); + + JSC::JSValue* r = + pFrame->loader()->executeScript(to_string(env, script), true); + WebCore::String result = WebCore::String(); + if (r) { + // note: r->getString() returns a UString. + result = WebCore::String(r->isString() ? r->getString() : + r->toString(pFrame->script()->globalObject()->globalExec())); + } + + unsigned len = result.length(); + if (len == 0) + return NULL; + return env->NewString((unsigned short*)result.characters(), len); +} + +static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer, + jobject javascriptObj, jstring interfaceName) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = (WebCore::Frame*)nativeFramePointer; + LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!"); + + JavaVM* vm; + env->GetJavaVM(&vm); + LOGV("::WebCore:: addJSInterface: %p", pFrame); + + // Copied from qwebframe.cpp + JSC::JSLock lock(false); + WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame); + JSC::Bindings::RootObject *root = pFrame->script()->bindingRootObject(); + if (window) { + JSC::Bindings::setJavaVM(vm); + // Add the binding to JS environment + JSC::ExecState* exec = window->globalExec(); + JSC::JSObject *addedObject = JSC::Bindings::Instance::createRuntimeObject( + exec, JSC::Bindings::JavaInstance::create(javascriptObj, root)); + // Add the binding name to the window's table of child objects. + JSC::PutPropertySlot slot; + window->put(exec, + JSC::Identifier(exec, to_string(env, interfaceName)), + addedObject, slot); + } +} + +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 ClearCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + 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); + } + // force JavaScript to GC when clear cache + WebCore::gcController().garbageCollectSoon(); + // clear image cache + SkImageRef_GlobalPool::SetRAMUsed(0); +} + +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(); + while (node && !found) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = + ((WebCore::HTMLFormElement*)node)->formElements; + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + if (((WebCore::HTMLInputElement*)e)->inputType() == + WebCore::HTMLInputElement::PASSWORD) + 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; + + WebCore::String username, password; + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WebCore::Node* node = form->firstItem(); + while (node && !found) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = + ((WebCore::HTMLFormElement*)node)->formElements; + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->inputType() == WebCore::HTMLInputElement::PASSWORD) + password = input->value(); + else if (input->inputType() == WebCore::HTMLInputElement::TEXT) + username = input->value(); + if (!username.isNull() && !password.isNull()) + found = true; + } + } + node = form->nextItem(); + } + if (found) { + jclass stringClass = env->FindClass("java/lang/String"); + strArray = env->NewObjectArray(2, stringClass, NULL); + env->SetObjectArrayElement(strArray, 0, env->NewString((unsigned short *) + username.characters(), username.length())); + env->SetObjectArrayElement(strArray, 1, env->NewString((unsigned short *) + password.characters(), password.length())); + } + 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) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = + ((WebCore::HTMLFormElement*)node)->formElements; + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->inputType() == WebCore::HTMLInputElement::PASSWORD) + passwordEle = input; + else if (input->inputType() == WebCore::HTMLInputElement::TEXT) + usernameEle = input; + if (usernameEle != NULL && passwordEle != NULL) + found = true; + } + } + node = form->nextItem(); + } + if (found) { + usernameEle->setValue(to_string(env, username)); + passwordEle->setValue(to_string(env, password)); + } +} + +static jobject GetFormTextData(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "GetFormTextData must take a valid frame pointer!"); + jobject hashMap = NULL; + + WTF::PassRefPtr<WebCore::HTMLCollection> collection = pFrame->document()->forms(); + if (collection->length() > 0) { + 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"); + 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"); + + static const WebCore::AtomicString text("text"); + static const WebCore::AtomicString off("off"); + + WebCore::HTMLFormElement* form; + WebCore::HTMLInputElement* input; + for (WebCore::Node* node = collection->firstItem(); node; node = collection->nextItem()) { + form = static_cast<WebCore::HTMLFormElement*>(node); + if (form->autoComplete()) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = form->formElements; + size_t size = elements.size(); + for (size_t i = 0; i < size; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->type() == text) { + if (e->hasAttribute(WebCore::HTMLNames::autocompleteAttr)) { + const WebCore::AtomicString& attr = e->getAttribute(WebCore::HTMLNames::autocompleteAttr); + if (attr == off) + continue; + } + input = (WebCore::HTMLInputElement*) e; + WebCore::String value = input->value(); + int len = value.length(); + if (len) { + const WebCore::AtomicString& name = input->name(); + jstring key = env->NewString((jchar *)name.characters(), name.length()); + jstring val = env->NewString((jchar *)value.characters(), len); + LOG_ASSERT(key && val, "name or value not set"); + env->CallObjectMethod(hashMap, put, key, val); + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + } + } + } + } + env->DeleteLocalRef(mapClass); + + } + return hashMap; +} + +// ---------------------------------------------------------------------------- + +/* + * 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 }, + { "stopLoading", "()V", + (void*) StopLoading }, + { "nativeLoadUrl", "(Ljava/lang/String;)V", + (void*) LoadUrl }, + { "nativeLoadData", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (void*) LoadData }, + { "externalRepresentation", "()Ljava/lang/String;", + (void*) ExternalRepresentation }, + { "documentAsText", "()Ljava/lang/String;", + (void*) DocumentAsText }, + { "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 }, + { "getFormTextData", "()Ljava/util/HashMap;", + (void*) GetFormTextData } +}; + +int register_webframe(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"); + + return jniRegisterNativeMethods(env, "android/webkit/BrowserFrame", + gBrowserFrameNativeMethods, NELEM(gBrowserFrameNativeMethods)); +} + +} /* namespace android */ + diff --git a/WebKit/android/jni/WebCoreFrameBridge.h b/WebKit/android/jni/WebCoreFrameBridge.h new file mode 100644 index 0000000..f554117 --- /dev/null +++ b/WebKit/android/jni/WebCoreFrameBridge.h @@ -0,0 +1,134 @@ +/* + * 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 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. + */ + +// TODO: change name to WebFrame.h + +#ifndef WEBFRAME_H +#define WEBFRAME_H + +#include "FrameLoaderClient.h" +#include "PlatformString.h" +#include "WebCoreRefObject.h" +#include <jni.h> + +namespace WebCore { + class HistoryItem; + class Image; + class Page; + class RenderPart; + class ResourceHandle; + class ResourceRequest; +} + +namespace android { + +class WebCoreResourceLoader; +class WebViewCore; + +// one instance of WebFrame per Page for calling into Java's BrowserFrame +class WebFrame : public WebCoreRefObject { + public: + // these ids need to be in sync with the constants in BrowserFrame.java + enum RAW_RES_ID { + NODOMAIN = 1, + LOADERROR, + }; + WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* page); + ~WebFrame(); + + // helper function + static WebFrame* getWebFrame(const WebCore::Frame* frame); + + virtual WebCoreResourceLoader* startLoadingResource(WebCore::ResourceHandle*, + const WebCore::ResourceRequest& request, + bool isHighPriority, + bool synchronous); + + void reportError(int errorCode, const WebCore::String& description, + const WebCore::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 WebCore::String& title); + + void windowObjectCleared(WebCore::Frame* frame); + + void setProgress(float newProgress); + + const WebCore::String userAgentForURL(const WebCore::KURL* url); + + void didReceiveIcon(WebCore::Image* icon); + + 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(WebCore::String userAgent) { mUserAgent = userAgent; } + + WebCore::String getRawResourceFilename(RAW_RES_ID) const; + + /** + * When the user initiates a click (via trackball, enter-press, or touch), + * we set mUserInitiatedClick 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. + * We also check it to determine whether or not to allow webkit to request + * a scroll. If it was user initated, the scroll is allowed. + */ + void setUserInitiatedClick(bool userInitiatedClick) { mUserInitiatedClick = userInitiatedClick; } + + bool userInitiatedClick() { return mUserInitiatedClick; } + + WebCore::Page* page() const { return mPage; } + +private: + struct JavaBrowserFrame; + JavaBrowserFrame* mJavaFrame; + WebCore::Page* mPage; + WebCore::String mUserAgent; + bool mUserInitiatedClick; +}; + +} // namespace android + +#endif // WEBFRAME_H diff --git a/WebKit/android/jni/WebCoreJni.cpp b/WebKit/android/jni/WebCoreJni.cpp new file mode 100644 index 0000000..f9d9cc9 --- /dev/null +++ b/WebKit/android/jni/WebCoreJni.cpp @@ -0,0 +1,155 @@ +/* + * 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 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 "webcoreglue" + +#include "config.h" + +#include "WebCoreJni.h" +#include "jni_utility.h" +#include <jni.h> +#include <utils/Log.h> + +namespace android { + +extern int register_webframe(JNIEnv*); +extern int register_javabridge(JNIEnv*); +extern int register_resource_loader(JNIEnv*); +extern int register_webviewcore(JNIEnv*); +extern int register_webhistory(JNIEnv*); +extern int register_webicondatabase(JNIEnv*); +extern int register_websettings(JNIEnv*); +extern int register_webview(JNIEnv*); + +// Class, constructor, and get method on WeakReference +jclass gWeakRefClass; +jmethodID gWeakRefInit; +jmethodID gWeakRefGet; + +jobject adoptGlobalRef(JNIEnv* env, jobject obj) +{ + // Create a WeakReference object + jobject ref = env->NewObject(gWeakRefClass, gWeakRefInit, obj); + // Increment the ref count of the WeakReference + ref = env->NewGlobalRef(ref); + return ref; +} + +AutoJObject getRealObject(JNIEnv* env, jobject obj) +{ + jobject real = env->CallObjectMethod(obj, gWeakRefGet); + if (!real) + LOGE("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. +WebCore::String to_string(JNIEnv* env, jstring str) +{ + if (!str || !env) + return WebCore::String(); + const jchar* s = env->GetStringChars(str, NULL); + if (!s) + return WebCore::String(); + WebCore::String ret(s, env->GetStringLength(str)); + env->ReleaseStringChars(str, s); + checkException(env); + return ret; +} + +} + +struct RegistrationMethod { + const char* name; + int (*func)(JNIEnv*); +}; + +static RegistrationMethod gWebCoreRegMethods[] = { + { "JavaBridge", android::register_javabridge }, + { "WebFrame", android::register_webframe }, + { "WebCoreResourceLoader", android::register_resource_loader }, + { "WebViewCore", android::register_webviewcore }, + { "WebHistory", android::register_webhistory }, + { "WebIconDatabase", android::register_webicondatabase }, + { "WebSettings", android::register_websettings }, + { "WebView", android::register_webview } +}; + +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!"); + + // Instantiate the WeakReference fields. + android::gWeakRefClass = env->FindClass("java/lang/ref/WeakReference"); + LOG_ASSERT(android::gWeakRefClass, "Could not find WeakReference"); + android::gWeakRefInit = env->GetMethodID(android::gWeakRefClass, + "<init>", "(Ljava/lang/Object;)V"); + LOG_ASSERT(android::gWeakRefInit, + "Could not find constructor for WeakReference"); + android::gWeakRefGet = env->GetMethodID(android::gWeakRefClass, "get", + "()Ljava/lang/Object;"); + LOG_ASSERT(android::gWeakRefInit, + "Could not find get method for WeakReference"); + + 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; +} diff --git a/WebKit/android/jni/WebCoreJni.h b/WebKit/android/jni/WebCoreJni.h new file mode 100644 index 0000000..d6e48c7 --- /dev/null +++ b/WebKit/android/jni/WebCoreJni.h @@ -0,0 +1,74 @@ +/* + * 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 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 ANDROID_WEBKIT_WEBCOREJNI_H +#define ANDROID_WEBKIT_WEBCOREJNI_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() { + if (m_obj) + m_env->DeleteLocalRef(m_obj); + } + jobject get() const { + return m_obj; + } + JNIEnv* env() const { + return m_env; + } +private: + 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 WeakReference returned as an +// AutoJObject. +AutoJObject getRealObject(JNIEnv*, jobject); + +// Convert the given jobject to a WeakReference and create a new global +// reference to that WeakReference. +jobject adoptGlobalRef(JNIEnv*, jobject); + +// Helper method for check java exceptions. Returns true if an exception +// occurred and logs the exception. +bool checkException(JNIEnv* env); + +// Create a WebCore::String object from a jstring object. +WebCore::String to_string(JNIEnv* env, jstring str); + +} + +#endif diff --git a/WebKit/android/jni/WebCoreRefObject.h b/WebKit/android/jni/WebCoreRefObject.h new file mode 100644 index 0000000..3e9f840 --- /dev/null +++ b/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 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 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/WebKit/android/jni/WebCoreResourceLoader.cpp b/WebKit/android/jni/WebCoreResourceLoader.cpp new file mode 100644 index 0000000..d4eda81 --- /dev/null +++ b/WebKit/android/jni/WebCoreResourceLoader.cpp @@ -0,0 +1,333 @@ +/* + * 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 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 "webcoreglue" + +#include <config.h> +#include <wtf/Platform.h> + +#include "jni_utility.h" +#include "WebCoreResourceLoader.h" +#include "SkUtils.h" + +#include "CString.h" +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceHandleInternal.h" +#include "ResourceResponse.h" +#include "WebCoreJni.h" + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#include <utils/misc.h> +#include <JNIHelp.h> +#include <SkTypes.h> +#include <stdlib.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct resourceloader_t { + jfieldID mObject; + jmethodID mCancelMethodID; + jmethodID mDownloadFileMethodID; + jmethodID mWillLoadFromCacheMethodID; +} 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 + +WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener) +{ + 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); +} + +/* +* 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) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WebCore::String urlStr = url.string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + bool val = env->CallStaticBooleanMethod(resourceLoader, + gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr); + checkException(env); + 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) { + WebCore::String valStr = to_string(env, val); + if (!valStr.isEmpty()) + response->setHTTPHeaderField(to_string(env, key), valStr); + } +} + +jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode, + jstring statusText, jstring mimeType, jlong expectedLength, + jstring encoding, jlong expireTime) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOG_ASSERT(url, "Must have a url in the response!"); + WebCore::KURL kurl(to_string(env, url)); + WebCore::String encodingStr; + WebCore::String mimeTypeStr; + if (mimeType) { + mimeTypeStr = to_string(env, mimeType); + LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data()); + } + if (encoding) { + encodingStr = to_string(env, encoding); + LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data()); + } + WebCore::ResourceResponse* response = new WebCore::ResourceResponse( + kurl, mimeTypeStr, (long long)expectedLength, + encodingStr, WebCore::String()); + response->setHTTPStatusCode(statusCode); + if (statusText) { + WebCore::String status = to_string(env, statusText); + response->setHTTPStatusText(status); + LOGV("Response setStatusText: %s", status.latin1().data()); + } + // FIXME: This assumes that time_t is a long and that long is the same size as int. + if ((unsigned long)expireTime > INT_MAX) + expireTime = INT_MAX; + response->setExpirationDate((time_t)expireTime); + 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); +} + +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->request(); + WebCore::KURL url(WebCore::KURL(to_string(env, baseUrl)), + to_string(env, redirectTo)); + r.setURL(url); + if (r.httpMethod() == "POST") { + r.setHTTPMethod("GET"); + r.clearHTTPReferrer(); + r.setHTTPBody(0); + r.setHTTPContentType(""); + } + 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; + } + handle->client()->willSendRequest(handle, r, *response); + delete response; + WebCore::String s = url.string(); + return env->NewString((unsigned short*)s.characters(), s.length()); +} + +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, + to_string(env, failingUrl), to_string(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;J)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 register_resource_loader(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.mWillLoadFromCacheMethodID = + env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;)Z"); + LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL, + "Could not find static method willLoadFromCache on LoadListener"); + + return jniRegisterNativeMethods(env, "android/webkit/LoadListener", + gResourceloaderMethods, NELEM(gResourceloaderMethods)); +} + +} /* namespace android */ diff --git a/WebKit/android/jni/WebCoreResourceLoader.h b/WebKit/android/jni/WebCoreResourceLoader.h new file mode 100644 index 0000000..5dd5abe --- /dev/null +++ b/WebKit/android/jni/WebCoreResourceLoader.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 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 ANDROID_WEBKIT_RESOURCELOADLISTENER_H +#define ANDROID_WEBKIT_RESOURCELOADLISTENER_H + +#include "KURL.h" + +#include "WebCoreRefObject.h" +#include <jni.h> + +namespace android { + +class WebCoreResourceLoader : public WebCoreRefObject +{ +public: + WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener); + virtual ~WebCoreResourceLoader(); + + /** + * Call to java to cancel the current load. + */ + void cancel(); + + /** + * Call to java to download the current load rather than feed it + * back to WebCore + */ + void downloadFile(); + + /** + * Call to java to find out if this URL is in the cache + */ + static bool willLoadFromCache(const WebCore::KURL& url); + + // Native jni functions + static void SetResponseHeader(JNIEnv*, jobject, jint, jstring, jstring); + static jint CreateResponse(JNIEnv*, jobject, jstring, jint, jstring, + jstring, jlong, jstring, jlong); + 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); + +private: + jobject mJLoader; +}; + +} // end namespace android + +#endif diff --git a/WebKit/android/jni/WebCoreViewBridge.h b/WebKit/android/jni/WebCoreViewBridge.h new file mode 100644 index 0000000..db79a37 --- /dev/null +++ b/WebKit/android/jni/WebCoreViewBridge.h @@ -0,0 +1,90 @@ +/* + * 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 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 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() : + mBounds(0,0,0,0), + m_windowBounds(0,0,0,0) + {} + virtual ~WebCoreViewBridge() {} + + virtual void draw(WebCore::GraphicsContext* ctx, + const WebCore::IntRect& rect) = 0; + + const WebCore::IntRect& getBounds() const + { + return mBounds; + } + + const WebCore::IntRect& getWindowBounds() const + { + return m_windowBounds; + } + + void setSize(int w, int h) + { + mBounds.setWidth(w); + mBounds.setHeight(h); + } + + void setLocation(int x, int y) + { + mBounds.setX(x); + mBounds.setY(y); + } + + void setWindowBounds(int x, int y, int h, int v) + { + m_windowBounds = WebCore::IntRect(x, y, h, v); + } + + int width() const { return mBounds.width(); } + int height() const { return mBounds.height(); } + int locX() const { return mBounds.x(); } + int locY() const { return mBounds.y(); } + + virtual bool forFrameView() const { return false; } + virtual bool forPluginView() const { return false; } + +private: + WebCore::IntRect mBounds; + WebCore::IntRect m_windowBounds; +}; + +#endif // WEBCORE_VIEW_BRIDGE_H diff --git a/WebKit/android/jni/WebFrameView.cpp b/WebKit/android/jni/WebFrameView.cpp new file mode 100644 index 0000000..f9a9a5b --- /dev/null +++ b/WebKit/android/jni/WebFrameView.cpp @@ -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 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 "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. + SkRect r; + android_setrect(&r, transRect); + + canvas->save(); + canvas->translate(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y())); + canvas->clipRect(r); + } + 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/WebKit/android/jni/WebFrameView.h b/WebKit/android/jni/WebFrameView.h new file mode 100644 index 0000000..2347071 --- /dev/null +++ b/WebKit/android/jni/WebFrameView.h @@ -0,0 +1,61 @@ +/* + * 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 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 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); + + virtual bool forFrameView() const { return true; } + + private: + WebCore::FrameView* mFrameView; + WebViewCore* mWebViewCore; + }; + +} // namespace android + +#endif // WEB_FRAMEVIEW_H diff --git a/WebKit/android/jni/WebHistory.cpp b/WebKit/android/jni/WebHistory.cpp new file mode 100644 index 0000000..8a75230 --- /dev/null +++ b/WebKit/android/jni/WebHistory.cpp @@ -0,0 +1,882 @@ +/* + * 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 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 "webhistory" + +#include <config.h> +#include <wtf/OwnPtr.h> +#include <wtf/Platform.h> + +#include "WebHistory.h" + +#include "BackForwardList.h" +#include "CString.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "HistoryItem.h" +#include "Page.h" +#include "TextEncoding.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" +#include "jni_utility.h" + +#include <JNIHelp.h> +#include <SkUtils.h> +#include <utils/misc.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; + jfieldID mCurrentIndex; +} 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::BackForwardList* list = 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(NULL); + // 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()->setCurrentHistoryItem(current); + 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->childItemWithName(child->tree()->name()); + child->loader()->setCurrentHistoryItem(item); + // Append the first child to the queue if it exists. + if (WebCore::Frame* f = child->tree()->firstChild()) + frameQueue.append(f); + 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()->currentHistoryItem(); + } + } + } +} + +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; + + // Set the current index in the list. + WebCore::BackForwardList* list = pFrame->page()->backForwardList(); + WebCore::HistoryItem* currentItem = list->entries()[index].get(); + list->goToItem(currentItem); + + // Update the current and previous history item. + WebCore::FrameLoader* loader = pFrame->loader(); + loader->setCurrentHistoryItem(currentItem); + loader->setPreviousHistoryItem(list->backItem()); + + // Update the request with the current item's info. + WebCore::ResourceRequest& request = loader->documentLoader()->request(); + request.setURL(currentItem->url()); + request.setMainDocumentURL(currentItem->url()); + // Reload the current page + loader->reloadAllowingStaleData(loader->documentLoader()->overrideEncoding()); +} + +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(); +#ifdef ANDROID_HISTORY_CLIENT + RefPtr<WebHistoryItem> bridge = new WebHistoryItem(env, obj, newItem.get()); + newItem->setBridge(bridge.get()); +#endif + // 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); +#ifdef ANDROID_HISTORY_CLIENT + bridge->setActive(); +#endif + + // Add the new item to the back/forward list. + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + pFrame->page()->backForwardList()->addItem(newItem); + +#ifdef ANDROID_HISTORY_CLIENT + // Update the item. + bridge->updateHistoryItem(newItem.get()); +#endif +} + +// 7 empty strings + no document state + children count = 9 unsigned values +// 1 char for isTargetItem +// ANDROID_HISTORY_CLIENT adds 1 int for scale. +#ifdef ANDROID_HISTORY_CLIENT +#define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 10 + sizeof(char))) +#else +#define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 9 + sizeof(char))) +#endif + +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. +#ifdef ANDROID_HISTORY_CLIENT + LOG_ASSERT(item->bridge(), "Why don't we have a bridge object here?"); +#endif + 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) { + mObject = adoptGlobalRef(env, obj); + mScale = 100; + mActive = false; + mParent = NULL; + mHistoryItem = item; +} + +WebHistoryItem::~WebHistoryItem() { + if (mObject) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + env->DeleteGlobalRef(mObject); + } +} + +void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { +#ifdef ANDROID_HISTORY_CLIENT + // Do not want to update during inflation. + if (!mActive) + return; + WebHistoryItem* webItem = this; + // Now we need to update the top-most WebHistoryItem based on the top-most + // HistoryItem. + if (mParent) { + webItem = mParent.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(); + } + 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->mObject); + if (!realItem.get()) + return; + + const WebCore::String& urlString = item->urlString(); + jstring urlStr = NULL; + if (!urlString.isNull()) + urlStr = env->NewString((unsigned short*)urlString.characters(), urlString.length()); + const WebCore::String& originalUrlString = item->originalURLString(); + jstring originalUrlStr = NULL; + if (!originalUrlString.isNull()) { + originalUrlStr = env->NewString( + (unsigned short*) originalUrlString.characters(), + originalUrlString.length()); + } + const WebCore::String& titleString = item->title(); + jstring titleStr = NULL; + if (!titleString.isNull()) + titleStr = env->NewString((unsigned short*)titleString.characters(), titleString.length()); + + // 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; + WebCore::String url = item->urlString(); + if (item->url().hasRef()) { + 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); +#endif +} + +static void historyItemChanged(WebCore::HistoryItem* item) { +#ifdef ANDROID_HISTORY_CLIENT + LOG_ASSERT(item, + "historyItemChanged called with a null item"); + if (item->bridge()) + item->bridge()->updateHistoryItem(item); +#endif +} + +void WebHistory::AddItem(const AutoJObject& list, WebCore::HistoryItem* item) +{ +#ifdef ANDROID_HISTORY_CLIENT + 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); + + // 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); +#endif +} + +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()->SetIntField(list.get(), gWebBackForwardList.mCurrentIndex, newIndex); +} + +static void write_string(WTF::Vector<char>& v, const WebCore::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 referrer + write_string(v, item->formReferrer()); + + // Form data + const WebCore::FormData* formData = item->formData(); + if (formData) + write_string(v, formData->flattenToString()); + else + write_string(v, WebCore::String()); // Empty constructor does not allocate a buffer. + + // Target + write_string(v, item->target()); + +#ifdef ANDROID_HISTORY_CLIENT + WebHistoryItem* bridge = item->bridge(); + LOG_ASSERT(bridge, "We should have a bridge here!"); + // Screen scale + int scale = bridge->scale(); + LOGV("Writing scale %d", scale); + v.append((char*)&scale, sizeof(int)); +#endif + + // Document state + const WTF::Vector<WebCore::String>& docState = item->documentState(); + WTF::Vector<WebCore::String>::const_iterator end = docState.end(); + unsigned stateSize = docState.size(); + LOGV("Writing docState %d", stateSize); + v.append((char*)&stateSize, sizeof(unsigned)); + for (WTF::Vector<WebCore::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(); +#ifdef ANDROID_HISTORY_CLIENT + LOG_ASSERT(parent->bridge(), + "The parent item should have a bridge object!"); + if (!item->bridge()) { + WebHistoryItem* bridge = new 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. + LOG_ASSERT(parent->bridge()->parent() == NULL || + item->bridge()->parent() == parent->bridge(), + "Somehow this item has an incorrect parent"); + item->bridge()->setParent(parent->bridge()); + } +#endif + 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. + WebCore::String formContentType; + WebCore::String formReferrer; + 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 referrer + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Referrer %d %.*s", l, l, data); + if (data + l < end) + formReferrer = 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; + } + if (end - data < sizeofUnsigned) + return false; + + // Set up the form info + if (formData != NULL) { + WebCore::ResourceRequest r; + r.setHTTPMethod("POST"); + r.setHTTPContentType(formContentType); + r.setHTTPReferrer(formReferrer); + 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; + +#ifdef ANDROID_HISTORY_CLIENT + WebHistoryItem* bridge = newItem->bridge(); + LOG_ASSERT(bridge, "There should be a bridge object during inflate"); + // Read the screen scale + memcpy(&l, data, sizeofUnsigned); + LOGV("Screen scale %d", l); + bridge->setScale(l); + data += sizeofUnsigned; + if (end - data < sizeofUnsigned) + return false; +#endif + + // 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<WebCore::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(); +#ifdef ANDROID_HISTORY_CLIENT + // Set a bridge that will not call into java. + child->setBridge(new WebHistoryItem(bridge)); +#endif + // Read the child item. + if (!read_item_recursive(child.get(), pData, end - data)) { + child.clear(); + return false; + } +#ifdef ANDROID_HISTORY_CLIENT + child->bridge()->setActive(); +#endif + 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(); +#ifdef ANDROID_HISTORY_CLIENT + testItem->setBridge(new WebHistoryItem(NULL)); +#endif + 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 referrer + 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 referrer 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!"); +#ifdef ANDROID_HISTORY_CLIENT + offset += 4; // Scale +#endif + // 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!"); + +#ifdef ANDROID_HISTORY_CLIENT + offset = 36; +#else + offset = 28; +#endif + // 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 register_webhistory(JNIEnv* env) +{ +#ifdef ANDROID_HISTORY_CLIENT + // Get notified of all changes to history items. + WebCore::notifyHistoryItemChanged = historyItemChanged; +#endif +#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"); + + // Find the WebBackForwardList object, the addHistoryItem and + // removeHistoryItem methods and the mCurrentIndex field. + 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.mCurrentIndex = env->GetFieldID(clazz, "mCurrentIndex", "I"); + LOG_ASSERT(gWebBackForwardList.mCurrentIndex, "Could not find field mCurrentIndex"); + + 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/WebKit/android/jni/WebHistory.h b/WebKit/android/jni/WebHistory.h new file mode 100644 index 0000000..40dc8f8 --- /dev/null +++ b/WebKit/android/jni/WebHistory.h @@ -0,0 +1,76 @@ +/* + * 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 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 ANDROID_WEBKIT_WEBHISTORY_H +#define ANDROID_WEBKIT_WEBHISTORY_H + +#include <jni.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + class HistoryItem; +} + +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); +}; + +class WebHistoryItem : public WTF::RefCounted<WebHistoryItem> { +public: + WebHistoryItem(WebHistoryItem* parent) + : mParent(parent) + , mObject(NULL) + , mScale(100) + , mActive(false) + , mHistoryItem(NULL) {} + WebHistoryItem(JNIEnv*, jobject, WebCore::HistoryItem*); + ~WebHistoryItem(); + void updateHistoryItem(WebCore::HistoryItem* item); + void setScale(int s) { mScale = s; } + void setActive() { mActive = true; } + void setParent(WebHistoryItem* parent) { mParent = parent; } + WebHistoryItem* parent() { return mParent.get(); } + int scale() { return mScale; } + WebCore::HistoryItem* historyItem() { return mHistoryItem; } +private: + RefPtr<WebHistoryItem> mParent; + jobject mObject; + int mScale; + bool mActive; + WebCore::HistoryItem* mHistoryItem; +}; + +}; + +#endif diff --git a/WebKit/android/jni/WebIconDatabase.cpp b/WebKit/android/jni/WebIconDatabase.cpp new file mode 100644 index 0000000..c982c78 --- /dev/null +++ b/WebKit/android/jni/WebIconDatabase.cpp @@ -0,0 +1,236 @@ +/* + * 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 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 "favicons" + +#include <config.h> +#include <wtf/Platform.h> + +#include "WebIconDatabase.h" + +#include "IconDatabase.h" +#include "Image.h" +#include "IntRect.h" +#include "JavaSharedClient.h" +#include "jni_utility.h" +#include "KURL.h" +#include "WebCoreJni.h" + +#include <pthread.h> +#include "GraphicsJNI.h" +#include <SkBitmap.h> +#include <SkImageDecoder.h> +#include <SkTemplates.h> +#include <utils/misc.h> +#include <JNIHelp.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 WebCore::String& pageURL) +{ + // If there are no clients currently, drop this message. + if (mClients.size() == 0) + return; + + // Attempt to attach to the current vm. + JavaVM* vm = JSC::Bindings::getJavaVM(); + JavaVMAttachArgs args; + + args.version = JNI_VERSION_1_4; + args.name = "IconDatabase"; + args.group = NULL; + + JNIEnv* env; + bool threadIsAttached = true; + if (vm->AttachCurrentThread(&env, (void*) &args) != JNI_OK) { + LOGE("Could not attach IconDatabase thread to the VM"); + threadIsAttached = false; + } + + mNotificationsMutex.lock(); + mNotifications.append(pageURL); + if (!mDeliveryRequested) { + if (threadIsAttached) { + mDeliveryRequested = true; + JavaSharedClient::EnqueueFunctionPtr(DeliverNotifications, this); + } + } + mNotificationsMutex.unlock(); +} + +// Called in the WebCore thread +void WebIconDatabase::RegisterForIconNotification(WebIconDatabaseClient* client) +{ + gIconDatabaseClient->mClientsMutex.lock(); + gIconDatabaseClient->mClients.append(client); + gIconDatabaseClient->mClientsMutex.unlock(); +} + +// Called in the WebCore thread +void WebIconDatabase::UnregisterForIconNotification(WebIconDatabaseClient* client) +{ + WebIconDatabase* db = gIconDatabaseClient; + db->mClientsMutex.lock(); + for (unsigned i = 0; i < db->mClients.size(); ++i) { + if (db->mClients[i] == client) { + db->mClients.remove(i); + break; + } + } + db->mClientsMutex.unlock(); +} + +// 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<WebCore::String> queue; + mNotificationsMutex.lock(); + queue.swap(mNotifications); + mDeliveryRequested = false; + mNotificationsMutex.unlock(); + + // Swap the clients queue + Vector<WebIconDatabaseClient*> clients; + mClientsMutex.lock(); + clients.swap(mClients); + mClientsMutex.unlock(); + + 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"); + WebCore::String pathStr = to_string(env, path); + LOGV("Opening WebIconDatabase file '%s'", pathStr.latin1().data()); + bool res = iconDb->open(pathStr); + if (!res) + LOGE("Open failed!"); +} + +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"); + WebCore::String urlStr = to_string(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"); + WebCore::String urlStr = to_string(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"); + WebCore::String urlStr = to_string(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 register_webicondatabase(JNIEnv* env) +{ + jclass webIconDB = env->FindClass("android/webkit/WebIconDatabase"); + LOG_ASSERT(webIconDB, "Unable to find class android.webkit.WebIconDatabase"); + + return jniRegisterNativeMethods(env, "android/webkit/WebIconDatabase", + gWebIconDatabaseMethods, NELEM(gWebIconDatabaseMethods)); +} + +} diff --git a/WebKit/android/jni/WebIconDatabase.h b/WebKit/android/jni/WebIconDatabase.h new file mode 100644 index 0000000..c88842c --- /dev/null +++ b/WebKit/android/jni/WebIconDatabase.h @@ -0,0 +1,77 @@ +/* + * 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 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 ANDROID_WEBKIT_WEBICONDATABASE_H +#define ANDROID_WEBKIT_WEBICONDATABASE_H + +#include "IconDatabaseClient.h" +#include "utils/threads.h" +#include "wtf/Vector.h" + +#include <jni.h> + +namespace WebCore { + class Image; + class String; +} + +namespace android { + + class WebIconDatabaseClient { + public: + virtual ~WebIconDatabaseClient() {} + virtual void didAddIconForPageUrl(const WebCore::String& pageUrl) = 0; + }; + + class WebIconDatabase : public WebCore::IconDatabaseClient { + public: + WebIconDatabase() : mDeliveryRequested(false) {} + // IconDatabaseClient method + virtual void dispatchDidAddIconForPageURL(const WebCore::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 and a mutex to protect it. + Vector<WebIconDatabaseClient*> mClients; + android::Mutex mClientsMutex; + + // Queue of page urls that have received an icon. + Vector<WebCore::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/WebKit/android/jni/WebSettings.cpp b/WebKit/android/jni/WebSettings.cpp new file mode 100644 index 0000000..407544a --- /dev/null +++ b/WebKit/android/jni/WebSettings.cpp @@ -0,0 +1,356 @@ +/* + * 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 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 "websettings" + +#include <config.h> +#include <wtf/Platform.h> + +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "DocLoader.h" +#include "Page.h" +#include "RenderTable.h" +#ifdef ANDROID_PLUGINS +#include "PlatformString.h" +#include "PluginDatabase.h" +#endif +#include "Settings.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <utils/misc.h> + +namespace WebCore { +// Defined in FileSystemAndroid.cpp +extern String sPluginPath; +} + +namespace android { + +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;"); + 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 + mJavaScriptEnabled = env->GetFieldID(clazz, "mJavaScriptEnabled", "Z"); + mPluginsEnabled = env->GetFieldID(clazz, "mPluginsEnabled", "Z"); +#ifdef ANDROID_PLUGINS + mPluginsPath = env->GetFieldID(clazz, "mPluginsPath", "Ljava/lang/String;"); +#endif + mJavaScriptCanOpenWindowsAutomatically = env->GetFieldID(clazz, + "mJavaScriptCanOpenWindowsAutomatically", "Z"); + mUseWideViewport = env->GetFieldID(clazz, "mUseWideViewport", "Z"); + mSupportMultipleWindows = env->GetFieldID(clazz, "mSupportMultipleWindows", "Z"); + mShrinksStandaloneImagesToFit = env->GetFieldID(clazz, "mShrinksStandaloneImagesToFit", "Z"); + mUseDoubleTree = env->GetFieldID(clazz, "mUseDoubleTree", "Z"); + + 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(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(mJavaScriptEnabled, "Could not find field mJavaScriptEnabled"); + LOG_ASSERT(mPluginsEnabled, "Could not find field mPluginsEnabled"); +#ifdef ANDROID_PLUGINS + LOG_ASSERT(mPluginsPath, "Could not find field mPluginsPath"); +#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(mUseDoubleTree, "Could not find field mUseDoubleTree"); + + jclass c = env->FindClass("java/lang/Enum"); + LOG_ASSERT(c, "Could not find Enum class!"); + mOrdinal = env->GetMethodID(c, "ordinal", "()I"); + LOG_ASSERT(mOrdinal, "Could not find method ordinal"); + c = env->FindClass("android/webkit/WebSettings$TextSize"); + LOG_ASSERT(c, "Could not find TextSize enum"); + mTextSizeValue = env->GetFieldID(c, "value", "I"); + } + + // Field ids + jfieldID mLayoutAlgorithm; + jfieldID mTextSize; + jfieldID mStandardFontFamily; + jfieldID mFixedFontFamily; + jfieldID mSansSerifFontFamily; + jfieldID mSerifFontFamily; + jfieldID mCursiveFontFamily; + jfieldID mFantasyFontFamily; + jfieldID mDefaultTextEncoding; + jfieldID mUserAgent; + jfieldID mMinimumFontSize; + jfieldID mMinimumLogicalFontSize; + jfieldID mDefaultFontSize; + jfieldID mDefaultFixedFontSize; + jfieldID mLoadsImagesAutomatically; +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + jfieldID mBlockNetworkImage; +#endif + jfieldID mJavaScriptEnabled; + jfieldID mPluginsEnabled; +#ifdef ANDROID_PLUGINS + jfieldID mPluginsPath; +#endif + jfieldID mJavaScriptCanOpenWindowsAutomatically; + jfieldID mUseWideViewport; + jfieldID mSupportMultipleWindows; + jfieldID mShrinksStandaloneImagesToFit; + jfieldID mUseDoubleTree; + + // Ordinal() method and value field for enums + jmethodID mOrdinal; + jfieldID mTextSizeValue; +}; + +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); +} + +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::DocLoader* docLoader = pFrame->document()->docLoader(); + +#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()->updateStyleSelector(); + 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->zoomFactor() != zoomFactor) + pFrame->setZoomFactor(zoomFactor, /*isTextOnly*/true); + + jstring str = (jstring)env->GetObjectField(obj, gFieldIds->mStandardFontFamily); + s->setStandardFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFixedFontFamily); + s->setFixedFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSansSerifFontFamily); + s->setSansSerifFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSerifFontFamily); + s->setSerifFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mCursiveFontFamily); + s->setCursiveFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFantasyFontFamily); + s->setFantasyFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mDefaultTextEncoding); + s->setDefaultTextEncodingName(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mUserAgent); + WebFrame::getWebFrame(pFrame)->setUserAgent(to_string(env, str)); + + 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) + docLoader->setAutoLoadImages(true); + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + flag = env->GetBooleanField(obj, gFieldIds->mBlockNetworkImage); + s->setBlockNetworkImage(flag); + if(!flag) + docLoader->setBlockNetworkImage(false); +#endif + + flag = env->GetBooleanField(obj, gFieldIds->mJavaScriptEnabled); + s->setJavaScriptEnabled(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mPluginsEnabled); + s->setPluginsEnabled(flag); + +#ifdef ANDROID_PLUGINS + ::WebCore::PluginDatabase *pluginDatabase = + ::WebCore::PluginDatabase::installedPlugins(); + str = (jstring)env->GetObjectField(obj, gFieldIds->mPluginsPath); + if (str) { + WebCore::String pluginsPath = to_string(env, str); + // When a new browser Tab is created, the corresponding + // Java WebViewCore object will sync (with the native + // side) its associated WebSettings at initialization + // time. However, at that point, the WebSettings object's + // mPluginsPaths member is set to the empty string. The + // real plugin path will be set later by the tab and the + // WebSettings will be synced again. + // + // There is no point in instructing WebCore's + // PluginDatabase instance to set the plugin path to the + // empty string. Furthermore, if the PluginDatabase + // instance is already initialized, setting the path to + // the empty string will cause the PluginDatabase to + // forget about the plugin files it has already + // inspected. When the path is subsequently set to the + // correct value, the PluginDatabase will attempt to load + // and initialize plugins that are already loaded and + // initialized. + if (pluginsPath.length()) { + s->setPluginsPath(pluginsPath); + // Set the plugin directories to this single entry. + Vector< ::WebCore::String > paths(1); + paths[0] = pluginsPath; + pluginDatabase->setPluginDirectories(paths); + // Set the home directory for plugin temporary files + WebCore::sPluginPath = paths[0]; + // Reload plugins. We call Page::refreshPlugins() instead + // of pluginDatabase->refresh(), as we need to ensure that + // the list of mimetypes exposed by the browser are also + // updated. + WebCore::Page::refreshPlugins(false); + } + } +#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); +#if USE(LOW_BANDWIDTH_DISPLAY) + flag = env->GetBooleanField(obj, gFieldIds->mUseDoubleTree); + pFrame->loader()->setUseLowBandwidthDisplay(flag); +#endif + } +}; + +//------------------------------------------------------------- +// JNI registration +//------------------------------------------------------------- + +static JNINativeMethod gWebSettingsMethods[] = { + { "nativeSync", "(I)V", + (void*) WebSettings::Sync } +}; + +int register_websettings(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/WebSettings"); + LOG_ASSERT(clazz, "Unable to find class WebSettings!"); + gFieldIds = new FieldIds(env, clazz); + return jniRegisterNativeMethods(env, "android/webkit/WebSettings", + gWebSettingsMethods, NELEM(gWebSettingsMethods)); +} + +} diff --git a/WebKit/android/jni/WebViewCore.cpp b/WebKit/android/jni/WebViewCore.cpp new file mode 100644 index 0000000..d9f9cec --- /dev/null +++ b/WebKit/android/jni/WebViewCore.cpp @@ -0,0 +1,2582 @@ +/* + * 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 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 "webcoreglue" + +#include <config.h> +#include "WebViewCore.h" + +#include "AtomicString.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "Color.h" +#include "Document.h" +#include "Element.h" +#include "Editor.h" +#include "EditorClientAndroid.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Font.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "GraphicsJNI.h" +#include "HitTestResult.h" +#include "HTMLAnchorElement.h" +#include "HTMLAreaElement.h" +#include "HTMLElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMapElement.h" +#include "HTMLNames.h" +#include "HTMLOptGroupElement.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "HTMLTextAreaElement.h" +#include "InlineTextBox.h" +#include <JNIHelp.h> +#include "KeyboardCodes.h" +#include "Node.h" +#include "Page.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformString.h" +#include "PluginInfoStore.h" +#include "PluginWidgetAndroid.h" +#include "Position.h" +#include "ProgressTracker.h" +#include "RenderLayer.h" +#include "RenderText.h" +#include "RenderTextControl.h" +#include "RenderThemeAndroid.h" +#include "RenderView.h" +#include "ResourceRequest.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SkTemplates.h" +#include "SkTypes.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkUtils.h" +#include "StringImpl.h" +#include "SystemTime.h" +#include "Text.h" +#include "TypingCommand.h" +#include "WebCoreFrameBridge.h" +#include "WebFrameView.h" +#include "HistoryItem.h" +#include "android_graphics.h" +#include <ui/KeycodeLabels.h> +#include "jni_utility.h" + +#if DEBUG_NAV_UI +#include "SkTime.h" +#endif + +#if ENABLE(TOUCH_EVENTS) // Android +#include "PlatformTouchEvent.h" +#endif + +#ifdef ANDROID_DOM_LOGGING +#include "AndroidLog.h" +#include "RenderTreeAsText.h" +#include "CString.h" + +FILE* gDomTreeFile = 0; +FILE* gRenderTreeFile = 0; +#endif + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.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 { + +// ---------------------------------------------------------------------------- + +#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_webView; +} gWebViewCoreFields; + +// ---------------------------------------------------------------------------- + +struct WebViewCore::JavaGlue { + jobject m_obj; + jmethodID m_spawnScrollTo; + jmethodID m_scrollTo; + jmethodID m_scrollBy; + jmethodID m_contentDraw; + jmethodID m_requestListBox; + jmethodID m_requestSingleListBox; + jmethodID m_jsAlert; + jmethodID m_jsConfirm; + jmethodID m_jsPrompt; + jmethodID m_jsUnload; + jmethodID m_didFirstLayout; + jmethodID m_sendMarkNodeInvalid; + jmethodID m_sendNotifyFocusSet; + jmethodID m_sendNotifyProgressFinished; + jmethodID m_sendRecomputeFocus; + jmethodID m_sendViewInvalidate; + jmethodID m_updateTextfield; + jmethodID m_restoreScale; + jmethodID m_needTouchEvents; + 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::gFrameGenerationMutex; +Mutex WebViewCore::gRecomputeFocusMutex; +Mutex WebViewCore::gButtonMutex; +Mutex WebViewCore::m_contentMutex; + +WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* mainframe) + : m_pluginInvalTimer(this, &WebViewCore::pluginInvalTimerFired) +{ + m_mainFrame = mainframe; + + m_popupReply = 0; + m_buildGeneration = 0; + m_moveGeneration = 0; + m_generation = 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_scale = 1; + + 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 = adoptGlobalRef(env, javaWebViewCore); + m_javaGlue->m_spawnScrollTo = GetJMethod(env, clazz, "contentSpawnScrollTo", "(II)V"); + m_javaGlue->m_scrollTo = GetJMethod(env, clazz, "contentScrollTo", "(II)V"); + m_javaGlue->m_scrollBy = GetJMethod(env, clazz, "contentScrollBy", "(IIZ)V"); + m_javaGlue->m_contentDraw = GetJMethod(env, clazz, "contentDraw", "()V"); + m_javaGlue->m_requestListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[Z[I)V"); + m_javaGlue->m_requestSingleListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[ZI)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_didFirstLayout = GetJMethod(env, clazz, "didFirstLayout", "()V"); + m_javaGlue->m_sendMarkNodeInvalid = GetJMethod(env, clazz, "sendMarkNodeInvalid", "(I)V"); + m_javaGlue->m_sendNotifyFocusSet = GetJMethod(env, clazz, "sendNotifyFocusSet", "()V"); + m_javaGlue->m_sendNotifyProgressFinished = GetJMethod(env, clazz, "sendNotifyProgressFinished", "()V"); + m_javaGlue->m_sendRecomputeFocus = GetJMethod(env, clazz, "sendRecomputeFocus", "()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_restoreScale = GetJMethod(env, clazz, "restoreScale", "(I)V"); + m_javaGlue->m_needTouchEvents = GetJMethod(env, clazz, "needTouchEvents", "(Z)V"); + + env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this); + + m_scrollOffsetX = m_scrollOffsetY = 0; + + reset(true); +} + +WebViewCore::~WebViewCore() +{ + // Release the focused view + Release(m_popupReply); + + if (m_javaGlue->m_obj) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteGlobalRef(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) +{ + WebFrameView* webFrameView = static_cast<WebFrameView*>(view->platformWidget()); + 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_lastFocusedBounds = WebCore::IntRect(0,0,0,0); + clearContent(); + m_updatedFrameCache = true; + m_frameCacheOutOfDate = true; + m_blockFocusChange = false; + m_snapAnchorNode = 0; + m_useReplay = false; + m_skipContentDraw = false; + m_findIsUp = false; + m_domtree_version = 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(); + + WebCore::Frame* child = f->tree()->firstChild(); + bool success = true; + while (child) { + success &= layoutIfNeededRecursive(child); + child = child->tree()->nextSibling(); + } + + return success && !v->needsLayout(); +} + +void WebViewCore::recordPicture(SkPicture* picture) +{ + // if there is no document yet, just return + if (!m_mainFrame->document()) + return; + // Call layout to ensure that the contentWidth and contentHeight are correct + if (!layoutIfNeededRecursive(m_mainFrame)) + return; + // draw into the picture's recording canvas + WebCore::FrameView* view = m_mainFrame->view(); + 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, INT_MAX, INT_MAX)); + + 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 (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; + // 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(); + 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); + picture->safeUnref(); + } + // 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); + m_frameCacheOutOfDate = true; +} + +void WebViewCore::checkNavCache() +{ + CacheBuilder& builder = FrameLoaderClientAndroid::get(m_mainFrame) + ->getCacheBuilder(); + WebCore::Node* oldFocusNode = builder.currentFocus(); + WebCore::IntRect oldBounds = oldFocusNode ? + oldFocusNode->getRect() : WebCore::IntRect(0,0,0,0); + DBG_NAV_LOGD("m_lastFocused=%p oldFocusNode=%p" + " m_lastFocusedBounds={%d,%d,%d,%d} oldBounds={%d,%d,%d,%d}", + m_lastFocused, oldFocusNode, + m_lastFocusedBounds.x(), m_lastFocusedBounds.y(), + m_lastFocusedBounds.width(), m_lastFocusedBounds.height(), + oldBounds.x(), oldBounds.y(), oldBounds.width(), oldBounds.height()); + unsigned latestVersion = m_mainFrame->document()->domTreeVersion(); + if (m_lastFocused != oldFocusNode || m_lastFocusedBounds != oldBounds + || m_findIsUp || latestVersion != m_domtree_version) { + m_lastFocused = oldFocusNode; + m_lastFocusedBounds = oldBounds; + DBG_NAV_LOGD("call updateFrameCache m_domtree_version=%d latest=%d", + m_domtree_version, latestVersion); + m_domtree_version = latestVersion; + updateFrameCache(); + } +} + +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++; + } + } +} + +void WebViewCore::clearContent() +{ + DBG_SET_LOG(""); + m_contentMutex.lock(); + m_content.clear(); + m_contentMutex.unlock(); + m_addInval.setEmpty(); +} + +void WebViewCore::copyContentToPicture(SkPicture* picture) +{ + DBG_SET_LOG("start"); + m_contentMutex.lock(); + PictureSet copyContent = PictureSet(m_content); + m_contentMutex.unlock(); + + int w = copyContent.width(); + int h = copyContent.height(); + copyContent.draw(picture->beginRecording(w, h, PICT_RECORD_FLAGS)); + picture->endRecording(); + DBG_SET_LOG("end"); +} + +bool WebViewCore::drawContent(SkCanvas* canvas, SkColor color) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewUIDrawTimeCounter); +#endif + DBG_SET_LOG("start"); + m_contentMutex.lock(); + PictureSet copyContent = PictureSet(m_content); + m_contentMutex.unlock(); + int sc = canvas->save(SkCanvas::kClip_SaveFlag); + SkRect clip; + clip.set(0, 0, copyContent.width(), copyContent.height()); + canvas->clipRect(clip, SkRegion::kDifference_Op); + canvas->drawColor(color); + canvas->restoreToCount(sc); + bool tookTooLong = copyContent.draw(canvas); + m_contentMutex.lock(); + m_content.setDrawTimes(copyContent); + m_contentMutex.unlock(); + DBG_SET_LOG("end"); + return tookTooLong; +} + +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); + 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())); + + 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__); +} + +bool WebViewCore::recordContent(SkRegion* region, SkIPoint* point) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreRecordTimeCounter); +#endif + DBG_SET_LOG("start"); + m_contentMutex.lock(); + PictureSet contentCopy(m_content); + m_contentMutex.unlock(); + recordPictureSet(&contentCopy); + float progress = (float) m_mainFrame->page()->progress()->estimatedProgress(); + if (progress > 0.0f && progress < 1.0f && contentCopy.isEmpty()) { + DBG_SET_LOGD("empty (progress=%g)", progress); + return false; + } + region->set(m_addInval); + m_addInval.setEmpty(); + m_contentMutex.lock(); + contentCopy.setDrawTimes(m_content); + m_content.set(contentCopy); + point->fX = m_content.width(); + point->fY = m_content.height(); + m_contentMutex.unlock(); + 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 true; +} + +void WebViewCore::splitContent() +{ + bool layoutSuceeded = layoutIfNeededRecursive(m_mainFrame); + LOG_ASSERT(layoutSuceeded, "Can never be called recursively"); + PictureSet tempPictureSet; + m_contentMutex.lock(); + m_content.split(&tempPictureSet); + m_contentMutex.unlock(); + rebuildPictureSet(&tempPictureSet); + m_contentMutex.lock(); + m_content.set(tempPictureSet); + m_contentMutex.unlock(); +} + +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(), animate ? m_javaGlue->m_spawnScrollTo : m_javaGlue->m_scrollTo, x, y); + checkException(env); +} + +void WebViewCore::sendMarkNodeInvalid(WebCore::Node* node) +{ + 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_sendMarkNodeInvalid, (int) node); + checkException(env); +} + +void WebViewCore::sendNotifyFocusSet() +{ + 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_sendNotifyFocusSet); + 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::sendRecomputeFocus() +{ + 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_sendRecomputeFocus); + 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::scrollBy(int dx, int dy, bool animate) +{ + if (!(dx | dy)) + return; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_scrollBy, + dx, dy, animate); + 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::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, max; + android_setrect(&rect, r); + max.set(0, 0, INT_MAX, INT_MAX); + if (!rect.intersect(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::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!"); + + const WebCore::KURL& url = m_mainFrame->loader()->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data()); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_didFirstLayout); + checkException(env); + + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + m_history.setDidFirstLayout(true); +} + +void WebViewCore::restoreScale(int scale) +{ + 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); + 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) // Android + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_needTouchEvents, need); + checkException(env); +#endif +} + +void WebViewCore::notifyFocusSet() +{ + sendNotifyFocusSet(); +} + +void WebViewCore::notifyProgressFinished() +{ + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + 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"); + } + this->scrollBy(dx, dy, true); +} + +void WebViewCore::setScrollOffset(int dx, int dy) +{ + DBG_NAV_LOGD("{%d,%d}", dx, dy); + 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); + m_mainFrame->sendScrollEvent(); + } +} + +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 screenWidth, int scale, int realScreenWidth, int screenHeight) +{ + WebCoreViewBridge* window = m_mainFrame->view()->platformWidget(); + int ow = window->width(); + int oh = window->height(); + window->setSize(width, height); + int osw = m_screenWidth; + DBG_NAV_LOGD("old:(w=%d,h=%d,sw=%d,scale=%d) new:(w=%d,h=%d,sw=%d,scale=%d)", + ow, oh, osw, m_scale, width, height, screenWidth, scale); + m_screenWidth = screenWidth; + m_scale = scale; + m_maxXScroll = screenWidth >> 2; + m_maxYScroll = (screenWidth * height / width) >> 2; + if (ow != width || oh != height || osw != screenWidth) { + WebCore::RenderObject *r = m_mainFrame->contentRenderer(); + DBG_NAV_LOGD("renderer=%p view=(w=%d,h=%d)", r, + realScreenWidth, screenHeight); + if (r) { + // get current screen center position + WebCore::IntPoint screenCenter = WebCore::IntPoint( + m_scrollOffsetX + (realScreenWidth >> 1), + m_scrollOffsetY + (screenHeight >> 1)); + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()-> + hitTestResultAtPoint(screenCenter, false); + WebCore::Node* node = hitTestResult.innerNode(); + WebCore::IntRect bounds; + WebCore::IntPoint offset; + 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()); + offset = WebCore::IntPoint(screenCenter.x() - bounds.x(), + screenCenter.y() - bounds.y()); + if (offset.x() < 0 || offset.x() > realScreenWidth || + offset.y() < 0 || offset.y() > screenHeight) + { + DBG_NAV_LOGD("offset out of bounds:(x=%d,y=%d)", + offset.x(), offset.y()); + node = 0; + } + } + r->setNeedsLayoutAndPrefWidthsRecalc(); + m_mainFrame->forceLayout(true); + // scroll to restore current screen center + if (!node) + return; + const WebCore::IntRect& newBounds = node->getRect(); + DBG_NAV_LOGD("nb:(x=%d,y=%d,w=%d," + "h=%d,ns=%d)", newBounds.x(), newBounds.y(), + newBounds.width(), newBounds.height()); + scrollBy(newBounds.x() - bounds.x(), newBounds.y() - bounds.y(), + false); + } + } +} + +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 + if (useFile) + gRenderTreeFile = fopen(RENDER_TREE_LOG_FILE, "w"); + WebCore::CString renderDump = WebCore::externalRepresentation(m_mainFrame->contentRenderer()).utf8(); + const char* data = renderDump.data(); + int length = renderDump.length(); + int last = 0; + for (int i = 0; i < length; i++) { + if (data[i] == '\n') { + if (i != last) { + char* chunk = new char[i - last + 1]; + strncpy(chunk, (data + last), i - last); + chunk[i - last] = '\0'; + DUMP_RENDER_LOGD("%s", chunk); + } + last = i + 1; + } + } + if (gRenderTreeFile) { + fclose(gRenderTreeFile); + gRenderTreeFile = 0; + } +#endif +} + +void WebViewCore::dumpNavTree() +{ +#if DUMP_NAV_CACHE + FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder().mDebug.print(); +#endif +} + +WebCore::String WebViewCore::retrieveHref(WebCore::Frame* frame, WebCore::Node* node) +{ + CacheBuilder& builder = FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder(); + if (!builder.validNode(frame, node)) + return WebCore::String(); + if (!node->hasTagName(WebCore::HTMLNames::aTag)) + return WebCore::String(); + WebCore::HTMLAnchorElement* anchor = static_cast<WebCore::HTMLAnchorElement*>(node); + return anchor->href(); +} + +bool WebViewCore::prepareFrameCache() +{ + if (!m_frameCacheOutOfDate) { + DBG_NAV_LOG("!m_frameCacheOutOfDate"); + return false; + } +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreBuildNavTimeCounter); +#endif + m_frameCacheOutOfDate = false; +#if DEBUG_NAV_UI + DBG_NAV_LOG("m_frameCacheOutOfDate was true"); + m_now = SkTime::GetMSecs(); +#endif + m_temp = new CachedRoot(); + m_temp->init(m_mainFrame, &m_history); + m_temp->setGeneration(++m_buildGeneration); + CacheBuilder& builder = FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder(); + 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); + return true; +} + +void WebViewCore::releaseFrameCache(bool newCache) +{ + if (!newCache) { + DBG_NAV_LOG("!newCache"); + return; + } + 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(); + notifyFocusSet(); + // it's tempting to send an invalidate here, but it's a bad idea + // the cache is now up to date, but the focus is not -- the event + // may need to be recomputed from the prior history. An invalidate + // will draw the stale location causing the ring to flash at the wrong place. +} + +void WebViewCore::updateFrameCache() +{ + m_useReplay = false; + releaseFrameCache(prepareFrameCache()); +} + +void WebViewCore::removeFrameGeneration(WebCore::Frame* frame) +{ + DBG_NAV_LOGD("frame=%p m_generation=%d", frame, m_generation); + gFrameGenerationMutex.lock(); + int last = m_frameGenerations.size() - 1; + for (int index = 0; index <= last; index++) { + if (m_frameGenerations[index].m_frame == frame) { + DBG_NAV_LOGD("index=%d last=%d", index, last); + if (index != last) + m_frameGenerations[index] = m_frameGenerations[last]; + m_frameGenerations.removeLast(); + break; + } + } + gFrameGenerationMutex.unlock(); +} + +void WebViewCore::updateFrameGeneration(WebCore::Frame* frame) +{ + DBG_NAV_LOGD("frame=%p m_generation=%d", frame, m_generation); + gFrameGenerationMutex.lock(); + ++m_buildGeneration; + for (size_t index = 0; index < m_frameGenerations.size(); index++) { + if (m_frameGenerations[index].m_frame == frame) { + DBG_NAV_LOG("replace"); + m_frameGenerations[index].m_generation = m_buildGeneration; + goto done; + } + } + { + FrameGen frameGen = {frame, m_buildGeneration}; + m_frameGenerations.append(frameGen); + DBG_NAV_LOG("append"); + } +done: + gFrameGenerationMutex.unlock(); +} + +int WebViewCore::retrieveFrameGeneration(WebCore::Frame* frame) +{ + int result = INT_MAX; + gFrameGenerationMutex.lock(); + for (size_t index = 0; index < m_frameGenerations.size(); index++) { + if (m_frameGenerations[index].m_frame == frame) { + result = m_frameGenerations[index].m_generation; + break; + } + } + gFrameGenerationMutex.unlock(); + DBG_NAV_LOGD("frame=%p m_generation=%d result=%d", frame, m_generation, result); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// + +void WebViewCore::addPlugin(PluginWidgetAndroid* w) +{ + SkDebugf("----------- addPlugin %p", w); + *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); + } +} + +void WebViewCore::invalPlugin(PluginWidgetAndroid* w) +{ + const double PLUGIN_INVAL_DELAY = 0; // should this be non-zero? + + 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(); + w->localToPageCoords(&dirty); + 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::setFinalFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, bool block) +{ + DBG_NAV_LOGD("frame=%p node=%p x=%d y=%d", frame, node, x, y); + bool result = finalKitFocus(frame, node, x, y, false); + if (block) { + m_blockFocusChange = true; + if (!result && node) + touchUp(m_touchGeneration, 0, 0, 0, x, y, 0, true, true); + } +} + +void WebViewCore::setKitFocus(int moveGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus) +{ + DBG_NAV_LOGD("m_moveGeneration=%d moveGeneration=%d" + " buildGeneration=%d frame=%p node=%p x=%d y=%d", + m_moveGeneration, moveGeneration, buildGeneration, frame, node, x, y); + if (m_blockFocusChange) { + DBG_NAV_LOG("m_blockFocusChange"); + return; + } + 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 + } + if (!commonKitFocus(moveGeneration, buildGeneration, frame, node, x, y, + ignoreNullFocus)) + return; + m_lastGeneration = moveGeneration; +} + +bool WebViewCore::commonKitFocus(int generation, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus) +{ + DBG_NAV_LOGD("generation=%d buildGeneration=%d frame=%p" + " node=%p x=%d y=%d", generation, buildGeneration, frame, node, x, y); + m_useReplay = true; + bool newCache = prepareFrameCache(); // must wait for possible recompute before using + if (m_moveGeneration > generation) { + DBG_NAV_LOGD("m_moveGeneration=%d > generation=%d", + m_moveGeneration, generation); + releaseFrameCache(newCache); + return false; // short-circuit if a newer move has already been generated + } + // if the nav cache has been rebuilt since this focus request was generated, + // send a request back to the UI side to recompute the kit-side focus + if (m_buildGeneration > buildGeneration || (node && !FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder().validNode(frame, node))) { + DBG_NAV_LOGD("m_buildGeneration=%d > buildGeneration=%d", + m_buildGeneration, buildGeneration); + gRecomputeFocusMutex.lock(); + bool first = !m_recomputeEvents.size(); + m_recomputeEvents.append(generation); + gRecomputeFocusMutex.unlock(); + releaseFrameCache(newCache); + if (first) + sendRecomputeFocus(); + return false; + } + releaseFrameCache(newCache); + if (!node && ignoreNullFocus) + return true; + finalKitFocus(frame, node, x, y, false); + return true; +} + +// Update mouse position and may change focused node. +// If donotChangeDOMFocus is true, the function does not changed focused node +// in the DOM tree. Changing the focus in DOM may trigger onblur event +// handler on the current focused node before firing mouse up and down events. +bool WebViewCore::finalKitFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, bool donotChangeDOMFocus) +{ + CacheBuilder& builder = FrameLoaderClientAndroid:: + get(m_mainFrame)->getCacheBuilder(); + if (!frame || builder.validNode(frame, NULL) == false) + frame = m_mainFrame; + WebCore::Node* oldFocusNode = builder.currentFocus(); + // 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, WebCore::currentTime()); + frame->eventHandler()->handleMouseMoveEvent(mouseEvent); + bool valid = builder.validNode(frame, node); + if (!donotChangeDOMFocus) { + WebCore::Document* oldDoc = oldFocusNode ? oldFocusNode->document() : 0; + if (!node) { + if (oldFocusNode) + oldDoc->setFocusedNode(0); + return false; + } else if (!valid) { + DBG_NAV_LOGD("sendMarkNodeInvalid node=%p", node); + sendMarkNodeInvalid(node); + if (oldFocusNode) + oldDoc->setFocusedNode(0); + return false; + } + // If we jump frames (docs), kill the focus on the old doc + if (oldFocusNode && node->document() != oldDoc) { + oldDoc->setFocusedNode(0); + } + if (!node->isTextNode()) + static_cast<WebCore::Element*>(node)->focus(false); + if (node->document()->focusedNode() != node) { + // This happens when Element::focus() fails as we may try to set the + // focus to a node which WebCore doesn't recognize as a focusable node. + // So we need to do some extra work, as it does in Element::focus(), + // besides calling Document::setFocusedNode. + if (oldFocusNode) { + // copied from clearSelectionIfNeeded in FocusController.cpp + WebCore::SelectionController* s = oldDoc->frame()->selection(); + if (!s->isNone()) + s->clear(); + } + //setFocus on things that WebCore doesn't recognize as supporting focus + //for instance, if there is an onclick element that does not support focus + node->document()->setFocusedNode(node); + } + } else { // !donotChangeDOMFocus + if (!node || !valid) + return false; + } + + DBG_NAV_LOGD("setFocusedNode node=%p", node); + builder.setLastFocus(node); + m_lastFocused = node; + m_lastFocusedBounds = node->getRect(); + return true; +} + +// helper function to find the frame that has focus +static WebCore::Frame* FocusedFrame(WebCore::Frame* frame) +{ + if (!frame) + return 0; + WebCore::Node* focusNode = FrameLoaderClientAndroid::get(frame)->getCacheBuilder().currentFocus(); + if (!focusNode) + return 0; + WebCore::Document* doc = focusNode->document(); + if (!doc) + return 0; + return doc->frame(); +} + +static WebCore::RenderTextControl* FocusedTextControl(WebCore::Frame* frame) +{ + WebCore::Node* focusNode = FrameLoaderClientAndroid::get(frame)->getCacheBuilder().currentFocus(); + WebCore::RenderObject* renderer = focusNode->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + return static_cast<WebCore::RenderTextControl*>(renderer); + } + return 0; +} + +WebCore::Frame* WebViewCore::changedKitFocus(WebCore::Frame* frame, + WebCore::Node* node, int x, int y) +{ + if (!frame || !node) + return m_mainFrame; + WebCore::Node* current = FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder().currentFocus(); + if (current == node) + return frame; + return finalKitFocus(frame, node, x, y, false) ? frame : m_mainFrame; +} + +static int findTextBoxIndex(WebCore::Node* node, const WebCore::IntPoint& pt) +{ + if (!node->isTextNode()) { + DBG_NAV_LOGD("node=%p pt=(%d,%d) isText=false", node, pt.x(), pt.y()); + return -2; // error + } + WebCore::RenderText* renderText = (WebCore::RenderText*) node->renderer(); + if (!renderText) { + DBG_NAV_LOGD("node=%p pt=(%d,%d) renderText=0", node, pt.x(), pt.y()); + return -3; // error + } + int renderX, renderY; + renderText->absolutePosition(renderX, renderY); + WebCore::InlineTextBox *textBox = renderText->firstTextBox(); + int globalX, globalY; + CacheBuilder::GetGlobalOffset(node, &globalX, &globalY); + int x = pt.x() - globalX; + int y = pt.y() - globalY; + do { + int textBoxStart = textBox->start(); + int textBoxEnd = textBoxStart + textBox->len(); + if (textBoxEnd <= textBoxStart) + continue; + WebCore::IntRect bounds = textBox->selectionRect(renderX, renderY, + textBoxStart, textBoxEnd); + if (!bounds.contains(x, y)) + continue; + int offset = textBox->offsetForPosition(x - renderX); +#if DEBUG_NAV_UI + int prior = offset > 0 ? textBox->positionForOffset(offset - 1) : -1; + int current = textBox->positionForOffset(offset); + int next = textBox->positionForOffset(offset + 1); + DBG_NAV_LOGD( + "offset=%d pt.x=%d globalX=%d renderX=%d x=%d " + "textBox->x()=%d textBox->start()=%d prior=%d current=%d next=%d", + offset, pt.x(), globalX, renderX, x, + textBox->xPos(), textBox->start(), prior, current, next + ); +#endif + return textBox->start() + offset; + } while ((textBox = textBox->nextTextBox())); + return -1; // couldn't find point, may have walked off end +} + +static inline bool isPunctuation(UChar c) +{ + return WTF::Unicode::category(c) & (0 + | WTF::Unicode::Punctuation_Dash + | WTF::Unicode::Punctuation_Open + | WTF::Unicode::Punctuation_Close + | WTF::Unicode::Punctuation_Connector + | WTF::Unicode::Punctuation_Other + | WTF::Unicode::Punctuation_InitialQuote + | WTF::Unicode::Punctuation_FinalQuote + ); +} + +static int centerX(const SkIRect& rect) +{ + return (rect.fLeft + rect.fRight) >> 1; +} + +static int centerY(const SkIRect& rect) +{ + return (rect.fTop + rect.fBottom) >> 1; +} + +WebCore::String WebViewCore::getSelection(SkRegion* selRgn) +{ + SkRegion::Iterator iter(*selRgn); + // FIXME: switch this to use StringBuilder instead + WebCore::String result; + WebCore::Node* lastStartNode = 0; + int lastStartEnd = -1; + UChar lastChar = 0xffff; + for (; !iter.done(); iter.next()) { + const SkIRect& rect = iter.rect(); + DBG_NAV_LOGD("rect=(%d, %d, %d, %d)", rect.fLeft, rect.fTop, + rect.fRight, rect.fBottom); + int cy = centerY(rect); + WebCore::IntPoint startPt = WebCore::IntPoint(rect.fLeft + 1, cy); + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()-> + hitTestResultAtPoint(startPt, false); + WebCore::Node* node = hitTestResult.innerNode(); + if (!node) { + DBG_NAV_LOG("!node"); + return result; + } + WebCore::IntPoint endPt = WebCore::IntPoint(rect.fRight - 2, cy); + hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(endPt, false); + WebCore::Node* endNode = hitTestResult.innerNode(); + if (!endNode) { + DBG_NAV_LOG("!endNode"); + return result; + } + int start = findTextBoxIndex(node, startPt); + if (start < 0) + continue; + int end = findTextBoxIndex(endNode, endPt); + if (end < -1) // use node if endNode is not valid + endNode = node; + if (end <= 0) + end = static_cast<WebCore::Text*>(endNode)->string()->length(); + DBG_NAV_LOGD("node=%p start=%d endNode=%p end=%d", node, start, endNode, end); + WebCore::Node* startNode = node; + do { + if (!node->isTextNode()) + continue; + if (node->getRect().isEmpty()) + continue; + WebCore::Text* textNode = static_cast<WebCore::Text*>(node); + WebCore::StringImpl* string = textNode->string(); + if (!string->length()) + continue; + const UChar* chars = string->characters(); + int newLength = node == endNode ? end : string->length(); + if (node == startNode) { + #if DEBUG_NAV_UI + if (node == lastStartNode) + DBG_NAV_LOGD("start=%d last=%d", start, lastStartEnd); + #endif + if (node == lastStartNode && start < lastStartEnd) + break; // skip rect if text overlaps already written text + lastStartNode = node; + lastStartEnd = newLength - start; + } + if (newLength < start) { + DBG_NAV_LOGD("newLen=%d < start=%d", newLength, start); + break; + } + if (!isPunctuation(chars[start])) + result.append(' '); + result.append(chars + start, newLength - start); + start = 0; + } while (node != endNode && (node = node->traverseNextNode())); + } + result = result.simplifyWhiteSpace().stripWhiteSpace(); +#if DUMP_NAV_CACHE + { + char buffer[256]; + CacheBuilder::Debug debug; + debug.init(buffer, sizeof(buffer)); + debug.print("copy: "); + debug.wideString(result); + DUMP_NAV_LOGD("%s", buffer); + } +#endif + return result; +} + +static void selectInFrame(WebCore::Frame* frame, int start, int end) +{ + WebCore::Document* doc = frame->document(); + if (!doc) + return; + + WebCore::Node* focus = doc->focusedNode(); + if (!focus) + return; + + WebCore::RenderObject* renderer = focus->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + WebCore::RenderTextControl* rtc = static_cast<WebCore::RenderTextControl*>(renderer); + if (start > end) { + int temp = start; + start = end; + end = temp; + } + rtc->setSelectionRange(start, end); + frame->revealSelection(); + } +} + +WebCore::Frame* WebViewCore::setSelection(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, int start, int end) +{ + // FIXME: Consider using a generation number to avoid doing this many more times than necessary. + frame = changedKitFocus(frame, node, x, y); + if (!frame) + return 0; + selectInFrame(frame, start, end); + return frame; +} + +// Shortcut for no modifier keys +#define NO_MODIFIER_KEYS (static_cast<WebCore::PlatformKeyboardEvent::ModifierKey>(0)) + +WebCore::Frame* WebViewCore::deleteSelection(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, int start, int end) +{ + frame = setSelection(frame, node, x, y, start, end); + if (start != end) { + WebCore::PlatformKeyboardEvent downEvent(kKeyCodeDel, WebCore::VK_BACK, + WebCore::PlatformKeyboardEvent::KeyDown, 0, NO_MODIFIER_KEYS); + frame->eventHandler()->keyEvent(downEvent); + WebCore::PlatformKeyboardEvent upEvent(kKeyCodeDel, WebCore::VK_BACK, + WebCore::PlatformKeyboardEvent::KeyUp, 0, NO_MODIFIER_KEYS); + frame->eventHandler()->keyEvent(upEvent); + } + return frame; +} + +void WebViewCore::replaceTextfieldText(WebCore::Frame* frame, WebCore::Node* node, int x, int y, + int oldStart, int oldEnd, jstring replace, int start, int end) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + + WebCore::String webcoreString = to_string(env, replace); + frame = setSelection(frame, node, x, y, oldStart, oldEnd); + WebCore::TypingCommand::insertText(frame->document(), webcoreString, false); + selectInFrame(frame, start, end); +} + +void WebViewCore::passToJs(WebCore::Frame* frame, WebCore::Node* node, int x, int y, int generation, + jstring currentText, int keyCode, int keyValue, bool down, bool cap, bool fn, bool sym) +{ + frame = changedKitFocus(frame, node, x, y); + // Construct the ModifierKey value + int mods = 0; + if (cap) { + mods |= WebCore::PlatformKeyboardEvent::ShiftKey; + } + if (fn) { + mods |= WebCore::PlatformKeyboardEvent::AltKey; + } + if (sym) { + mods |= WebCore::PlatformKeyboardEvent::CtrlKey; + } + WebCore::PlatformKeyboardEvent event(keyCode, keyValue, + down ? WebCore::PlatformKeyboardEvent::KeyDown : + WebCore::PlatformKeyboardEvent::KeyUp, + 0, static_cast<WebCore::PlatformKeyboardEvent::ModifierKey>(mods)); + // Block text field updates during a key press. + m_blockTextfieldUpdates = true; + frame->eventHandler()->keyEvent(event); + m_blockTextfieldUpdates = false; + m_textGeneration = generation; + + WebCore::Node* currentFocus = FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder().currentFocus(); + // Make sure we have the same focus and it is a text field. + if (node == currentFocus && currentFocus) { + WebCore::RenderObject* renderer = currentFocus->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + WebCore::RenderTextControl* renderText = static_cast<WebCore::RenderTextControl*>(renderer); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WebCore::String current = to_string(env, currentText); + WebCore::String test = renderText->text(); + // If the text changed during the key event, update the UI text field. + if (test != current) + updateTextfield(currentFocus, false, test); + } + } +} + +void WebViewCore::saveDocumentState(WebCore::Frame* frame) +{ + if (!FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder() + .validNode(frame, 0)) + frame = m_mainFrame; + WebCore::HistoryItem *item = frame->loader()->currentHistoryItem(); + + // 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()); + } +} + +// Convert a WebCore::String into an array of characters where the first +// character represents the length, for easy conversion to java. +static uint16_t* stringConverter(const WebCore::String& text) +{ + size_t length = text.length(); + uint16_t* itemName = new uint16_t[length+1]; + itemName[0] = (uint16_t)length; + uint16_t* firstChar = &(itemName[1]); + memcpy((void*)firstChar, text.characters(), sizeof(UChar)*length); + return itemName; +} + +// Response to dropdown created for a listbox. +class ListBoxReply : public WebCoreReply { +public: + ListBoxReply(WebCore::HTMLSelectElement* select, WebCore::Frame* frame, WebViewCore* view) + : m_select(select) + , m_frame(frame) + , m_viewImpl(view) + {} + + // Response used if the listbox only allows single selection. + // index is listIndex of the selected item, or -1 if nothing is selected. + virtual void replyInt(int index) + { + if (-2 == index) { + // Special value for cancel. Do nothing. + return; + } + // If the select element no longer exists, do to a page change, etc, silently return. + if (!m_select || !FrameLoaderClientAndroid::get(m_viewImpl->m_mainFrame)->getCacheBuilder().validNode(m_frame, m_select)) + return; + if (-1 == index) { + if (m_select->selectedIndex() != -1) { +#ifdef ANDROID_DESELECT_SELECT + m_select->deselectItems(); +#endif + m_select->onChange(); + m_viewImpl->contentInvalidate(m_select->getRect()); + } + return; + } + WebCore::HTMLOptionElement* option = static_cast<WebCore::HTMLOptionElement*>( + m_select->item(m_select->listToOptionIndex(index))); + if (!option->selected()) { + option->setSelected(true); + m_select->onChange(); + m_viewImpl->contentInvalidate(m_select->getRect()); + } + } + + // Response if the listbox allows multiple selection. array stores the listIndices + // of selected positions. + virtual void replyIntArray(const int* array, int count) + { + // If the select element no longer exists, do to a page change, etc, silently return. + if (!m_select || !FrameLoaderClientAndroid::get(m_viewImpl->m_mainFrame)->getCacheBuilder().validNode(m_frame, m_select)) + return; +#ifdef ANDROID_DESELECT_SELECT + m_select->deselectItems(); +#endif + WebCore::HTMLOptionElement* option; + for (int i = 0; i < count; i++) { + option = static_cast<WebCore::HTMLOptionElement*>( + m_select->item(m_select->listToOptionIndex(array[i]))); + option->setSelected(true); + } + m_viewImpl->contentInvalidate(m_select->getRect()); + } +private: + // The select element associated with this listbox. + WebCore::HTMLSelectElement* m_select; + // The frame of this select element, to verify that it is valid. + WebCore::Frame* m_frame; + // For calling invalidate and checking the select element's validity + WebViewCore* m_viewImpl; +}; + +// 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::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. + jbooleanArray enabledArray = env->NewBooleanArray(enabledCount); + checkException(env); + jboolean* ptrArray = env->GetBooleanArrayElements(enabledArray, 0); + checkException(env); + for (size_t i = 0; i < enabledCount; i++) { + ptrArray[i] = enabled[i]; + } + env->ReleaseBooleanArrayElements(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(int keyCode, UChar32 unichar, int repeatCount, bool isShift, bool isAlt, bool isDown) +{ + DBG_NAV_LOGD("key: keyCode=%d", keyCode); + + WebCore::EventHandler* eventHandler = m_mainFrame->eventHandler(); + WebCore::Node* focusNode = FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder().currentFocus(); + if (focusNode) { + eventHandler = focusNode->document()->frame()->eventHandler(); + } + + int mods = 0; // PlatformKeyboardEvent::ModifierKey + if (isShift) { + mods |= WebCore::PlatformKeyboardEvent::ShiftKey; + } + if (isAlt) { + mods |= WebCore::PlatformKeyboardEvent::AltKey; + } + WebCore::PlatformKeyboardEvent evt(keyCode, unichar, + isDown ? WebCore::PlatformKeyboardEvent::KeyDown : WebCore::PlatformKeyboardEvent::KeyUp, + repeatCount, static_cast<WebCore::PlatformKeyboardEvent::ModifierKey> (mods)); + return eventHandler->keyEvent(evt); +} + +bool WebViewCore::click() { + bool keyHandled = false; + WebCore::Node* focusNode = FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder().currentFocus(); + if (focusNode) { + WebFrame::getWebFrame(m_mainFrame)->setUserInitiatedClick(true); + keyHandled = handleMouseClick(focusNode->document()->frame(), focusNode); + WebFrame::getWebFrame(m_mainFrame)->setUserInitiatedClick(false); + } + // match in setFinalFocus() + m_blockFocusChange = false; + return keyHandled; +} + +bool WebViewCore::handleTouchEvent(int action, int x, int y) +{ + bool preventDefault = false; + +#if ENABLE(TOUCH_EVENTS) // Android + WebCore::TouchEventType type = WebCore::TouchEventCancel; + switch (action) { + case 0: // MotionEvent.ACTION_DOWN + type = WebCore::TouchEventStart; + break; + case 1: // MotionEvent.ACTION_UP + type = WebCore::TouchEventEnd; + break; + case 2: // MotionEvent.ACTION_MOVE + type = WebCore::TouchEventMove; + break; + case 3: // MotionEvent.ACTION_CANCEL + type = WebCore::TouchEventCancel; + break; + } + WebCore::IntPoint pt(x - m_scrollOffsetX, y - m_scrollOffsetY); + WebCore::PlatformTouchEvent te(pt, pt, type); + preventDefault = m_mainFrame->eventHandler()->handleTouchEvent(te); +#endif + + return preventDefault; +} + +void WebViewCore::touchUp(int touchGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, int size, + bool isClick, bool retry) +{ + 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 + } + if (retry || isClick) + finalKitFocus(frame, node, x, y, true); // don't change DOM focus + else if (!commonKitFocus(touchGeneration, buildGeneration, + frame, node, x, y, false)) { + return; + } + m_lastGeneration = touchGeneration; + // If this is just a touch and not a click, we have already done the change in focus, + // so just leave the function now. + if (!isClick) + return; + if (frame) { + frame->loader()->resetMultipleFormSubmissionProtection(); + } + EditorClientAndroid* client = static_cast<EditorClientAndroid*>(m_mainFrame->editor()->client()); + client->setFromClick(true); + DBG_NAV_LOGD("touchGeneration=%d handleMouseClick frame=%p node=%p" + " x=%d y=%d", touchGeneration, frame, node, x, y); + handleMouseClick(frame, node); + client->setFromClick(false); +} + +bool WebViewCore::handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr) +{ + bool valid = framePtr == NULL || FrameLoaderClientAndroid::get( + m_mainFrame)->getCacheBuilder().validNode(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->setUserInitiatedClick(true); + WebCore::EventTargetNodeCast(nodePtr)->dispatchSimulatedClick(0, + true, true); + webFrame->setUserInitiatedClick(false); + return true; + } + WebCore::RenderObject* renderer = nodePtr->renderer(); + if (renderer && renderer->isMenuList()) { + WebCore::HTMLSelectElement* select = static_cast<WebCore::HTMLSelectElement*>(nodePtr); + const WTF::Vector<WebCore::HTMLElement*>& listItems = select->listItems(); + SkTDArray<const uint16_t*> names; + SkTDArray<int> enabledArray; + SkTDArray<int> selectedArray; + int size = listItems.size(); + bool multiple = select->multiple(); + for (int i = 0; i < size; i++) { + if (listItems[i]->hasTagName(WebCore::HTMLNames::optionTag)) { + WebCore::HTMLOptionElement* option = static_cast<WebCore::HTMLOptionElement*>(listItems[i]); + *names.append() = stringConverter(option->optionText()); + *enabledArray.append() = option->disabled() ? 0 : 1; + if (multiple && option->selected()) + *selectedArray.append() = i; + } else if (listItems[i]->hasTagName(WebCore::HTMLNames::optgroupTag)) { + WebCore::HTMLOptGroupElement* optGroup = static_cast<WebCore::HTMLOptGroupElement*>(listItems[i]); + *names.append() = stringConverter(optGroup->groupLabelText()); + *enabledArray.append() = 0; + if (multiple) + *selectedArray.append() = 0; + } + } + WebCoreReply* reply = new ListBoxReply(select, select->document()->frame(), this); + listBoxRequest(reply, names.begin(), size, enabledArray.begin(), enabledArray.count(), + multiple, selectedArray.begin(), multiple ? selectedArray.count() : + select->optionToListIndex(select->selectedIndex())); + return true; + } + } + if (!valid || !framePtr) + framePtr = m_mainFrame; + webFrame->setUserInitiatedClick(true); + DBG_NAV_LOGD("m_mousePos={%d,%d}", m_mousePos.x(), m_mousePos.y()); + WebCore::PlatformMouseEvent mouseDown(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventPressed, 1, false, false, false, false, + WebCore::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, + WebCore::currentTime()); + bool handled = framePtr->eventHandler()->handleMouseReleaseEvent(mouseUp); + webFrame->setUserInitiatedClick(false); + 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 = NULL; + } +} + +void WebViewCore::jsAlert(const WebCore::String& url, const WebCore::String& text) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = env->NewString((unsigned short *)text.characters(), text.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsAlert, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +bool WebViewCore::jsConfirm(const WebCore::String& url, const WebCore::String& text) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = env->NewString((unsigned short *)text.characters(), text.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + 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 WebCore::String& url, const WebCore::String& text, const WebCore::String& defaultValue, WebCore::String& result) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = env->NewString((unsigned short *)text.characters(), text.length()); + jstring jDefaultStr = env->NewString((unsigned short *)defaultValue.characters(), defaultValue.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + jstring returnVal = (jstring) env->CallObjectMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsPrompt, jUrlStr, jInputStr, jDefaultStr); + // If returnVal is null, it means that the user cancelled the dialog. + if (!returnVal) + return false; + + result = to_string(env, returnVal); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jDefaultStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); + return true; +} + +bool WebViewCore::jsUnload(const WebCore::String& url, const WebCore::String& message) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = env->NewString((unsigned short *)message.characters(), message.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + 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; +} + +AutoJObject +WebViewCore::getJavaObject() +{ + return getRealObject(JSC::Bindings::getJNIEnv(), m_javaGlue->m_obj); +} + +jobject +WebViewCore::getWebViewJavaObject() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + return env->GetObjectField(m_javaGlue->object(env).get(), gWebViewCoreFields.m_webView); +} + +void WebViewCore::updateTextfield(WebCore::Node* ptr, bool changeToPassword, + const WebCore::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; + } + int length = text.length(); + jstring string = env->NewString((unsigned short *) text.characters(), length); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateTextfield, + (int) ptr, false, string, m_textGeneration); + env->DeleteLocalRef(string); + checkException(env); +} + +void WebViewCore::setSnapAnchor(int x, int y) +{ + m_snapAnchorNode = 0; + if (!x && !y) { + return; + } + + WebCore::IntPoint point = WebCore::IntPoint(x, y); + WebCore::Node* node = m_mainFrame->eventHandler()->hitTestResultAtPoint(point, false).innerNode(); + if (node) { +// LOGD("found focus node name: %s, type %d\n", node->nodeName().utf8().data(), node->nodeType()); + while (node) { + if (node->hasTagName(WebCore::HTMLNames::divTag) || + node->hasTagName(WebCore::HTMLNames::tableTag)) { + m_snapAnchorNode = node; + return; + } +// LOGD("parent node name: %s, type %d\n", node->nodeName().utf8().data(), node->nodeType()); + node = node->parentNode(); + } + } +} + +void WebViewCore::snapToAnchor() +{ + if (m_snapAnchorNode) { + if (m_snapAnchorNode->inDocument()) { + int rx, ry; + m_snapAnchorNode->renderer()->absolutePosition(rx, ry); + scrollTo(rx, ry); + } else { + m_snapAnchorNode = 0; + } + } +} + +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); +} + +//---------------------------------------------------------------------- +// Native JNI methods +//---------------------------------------------------------------------- +static jstring WebCoreStringToJString(JNIEnv *env, WebCore::String string) +{ + int length = string.length(); + if (!length) + return 0; + jstring ret = env->NewString((jchar *)string.characters(), length); + env->DeleteLocalRef(ret); + return ret; +} + +static void SetSize(JNIEnv *env, jobject obj, jint width, jint height, + jint screenWidth, jfloat scale, jint realScreenWidth, jint screenHeight) +{ +#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"); + // convert the scale to an int + int s = (int) (scale * 100); + // a negative value indicates that we should not change the scale + if (scale < 0) + s = viewImpl->scale(); + + viewImpl->setSizeScreenWidthAndScale(width, height, screenWidth, s, + realScreenWidth, screenHeight); +} + +static void SetScrollOffset(JNIEnv *env, jobject obj, jint dx, jint dy) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); + + viewImpl->setScrollOffset(dx, dy); +} + +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 isDown) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in Key"); + + return viewImpl->key(keyCode, unichar, repeatCount, isShift, isAlt, isDown); +} + +static jboolean Click(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 Click"); + + return viewImpl->click(); +} + +static void DeleteSelection(JNIEnv *env, jobject obj, + jint frame, jint node, jint x, jint y, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeDeleteSelection()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeDeleteSelection"); + viewImpl->deleteSelection((WebCore::Frame*) frame, (WebCore::Node*) node, + x, y, start, end); +} + +static void SetSelection(JNIEnv *env, jobject obj, + jint frame, jint node, jint x, jint y, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeSetSelection()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeDeleteSelection"); + viewImpl->setSelection((WebCore::Frame*) frame, (WebCore::Node*) node, + x, y, start, end); +} + + +static void ReplaceTextfieldText(JNIEnv *env, jobject obj, + jint framePtr, jint nodePtr, jint x, jint y, jint oldStart, jint oldEnd, + jstring replace, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeReplaceTextfieldText()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeReplaceTextfieldText"); + viewImpl->replaceTextfieldText((WebCore::Frame*) framePtr, (WebCore::Node*) nodePtr, x, y, oldStart, + oldEnd, replace, start, end); +} + +static void PassToJs(JNIEnv *env, jobject obj, jint frame, jint node, + jint x, jint y, jint generation, jstring currentText, jint keyCode, + jint keyValue, jboolean down, jboolean cap, jboolean fn, jboolean sym) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativePassToJs()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativePassToJs"); + viewImpl->passToJs((WebCore::Frame*) frame, (WebCore::Node*) node, + x, y, generation, currentText, keyCode, keyValue, down, cap, fn, sym); +} + +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); +} + +static bool 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; + bool result = viewImpl->recordContent(nativeRegion, &nativePt); + GraphicsJNI::ipoint_to_jpoint(nativePt, env, pt); + return result; +} + +static void SplitContent(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->splitContent(); +} + +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) +{ +#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) == CacheBuilder::FOUND_COMPLETE; + jstring ret = 0; + if (success) { + ret = env->NewString((jchar*) addrChars + start, end - start); + env->DeleteLocalRef(ret); + } + env->ReleaseStringChars(addr, addrChars); + return ret; +} + +static jboolean HandleTouchEvent(JNIEnv *env, jobject obj, jint action, 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__); + return viewImpl->handleTouchEvent(action, x, y); +} + +static void TouchUp(JNIEnv *env, jobject obj, jint touchGeneration, + jint buildGeneration, jint frame, jint node, jint x, jint y, jint size, + jboolean isClick, jboolean retry) +{ +#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, buildGeneration, + (WebCore::Frame*) frame, (WebCore::Node*) node, x, y, size, isClick, retry); +} + +static jstring RetrieveHref(JNIEnv *env, jobject obj, jint frame, + jint node) +{ +#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::String result = viewImpl->retrieveHref((WebCore::Frame*) frame, + (WebCore::Node*) node); + if (!result.isEmpty()) + return WebCoreStringToJString(env, result); + return 0; +} + +static void SetFinalFocus(JNIEnv *env, jobject obj, jint frame, jint node, + jint x, jint y, jboolean block) +{ +#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->setFinalFocus((WebCore::Frame*) frame, (WebCore::Node*) node, x, + y, block); +} + +static void SetKitFocus(JNIEnv *env, jobject obj, jint moveGeneration, + jint buildGeneration, jint frame, jint node, jint x, jint y, + jboolean ignoreNullFocus) +{ +#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->setKitFocus(moveGeneration, buildGeneration, + (WebCore::Frame*) frame, (WebCore::Node*) node, x, y, + ignoreNullFocus); +} + +static void UnblockFocus(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->unblockFocus(); +} + +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->minPrefWidth(); + } + } + } + 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()); +#endif +} + +static void SetSnapAnchor(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__); + + viewImpl->setSnapAnchor(x, y); +} + +static void SnapToAnchor(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->snapToAnchor(); +} + +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 jstring GetSelection(JNIEnv *env, jobject obj, jobject selRgn) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + SkRegion* selectionRegion = GraphicsJNI::getNativeRegion(env, selRgn); + WebCore::String result = viewImpl->getSelection(selectionRegion); + if (!result.isEmpty()) + return WebCoreStringToJString(env, result); + return 0; +} + +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 RefreshPlugins(JNIEnv *env, + jobject obj, + jboolean reloadOpenPages) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + // Refresh the list of plugins, optionally reloading all open + // pages. + WebCore::refreshPlugins(reloadOpenPages); +} + +static void RegisterURLSchemeAsLocal(JNIEnv* env, jobject obj, jstring scheme) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebCore::FrameLoader::registerURLSchemeAsLocal(to_string(env, scheme)); +} + +static void CheckNavCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->checkNavCache(); +} + +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 CopyContentToPicture(JNIEnv *env, jobject obj, jobject pict) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + if (!viewImpl) + return; + SkPicture* picture = GraphicsJNI::getNativePicture(env, pict); + viewImpl->copyContentToPicture(picture); +} + +static bool DrawContent(JNIEnv *env, jobject obj, jobject canv, jint color) +{ + // Note: this is called from UI thread, don't count it for WebViewCoreTimeCounter + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + return viewImpl->drawContent(canvas, color); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gJavaWebViewCoreMethods[] = { + { "nativeCheckNavCache", "()V", + (void*) CheckNavCache }, + { "nativeClearContent", "()V", + (void*) ClearContent }, + { "nativeCopyContentToPicture", "(Landroid/graphics/Picture;)V", + (void*) CopyContentToPicture }, + { "nativeDrawContent", "(Landroid/graphics/Canvas;I)Z", + (void*) DrawContent } , + { "nativeKey", "(IIIZZZ)Z", + (void*) Key }, + { "nativeClick", "()Z", + (void*) Click }, + { "nativeSendListBoxChoices", "([ZI)V", + (void*) SendListBoxChoices }, + { "nativeSendListBoxChoice", "(I)V", + (void*) SendListBoxChoice }, + { "nativeSetSize", "(IIIFII)V", + (void*) SetSize }, + { "nativeSetScrollOffset", "(II)V", + (void*) SetScrollOffset }, + { "nativeSetGlobalBounds", "(IIII)V", + (void*) SetGlobalBounds }, + { "nativeSetSelection", "(IIIIII)V", + (void*) SetSelection } , + { "nativeDeleteSelection", "(IIIIII)V", + (void*) DeleteSelection } , + { "nativeReplaceTextfieldText", "(IIIIIILjava/lang/String;II)V", + (void*) ReplaceTextfieldText } , + { "passToJs", "(IIIIILjava/lang/String;IIZZZZ)V", + (void*) PassToJs } , + { "nativeSaveDocumentState", "(I)V", + (void*) SaveDocumentState }, + { "nativeFindAddress", "(Ljava/lang/String;)Ljava/lang/String;", + (void*) FindAddress }, + { "nativeHandleTouchEvent", "(III)Z", + (void*) HandleTouchEvent }, + { "nativeTouchUp", "(IIIIIIIZZ)V", + (void*) TouchUp }, + { "nativeRetrieveHref", "(II)Ljava/lang/String;", + (void*) RetrieveHref }, + { "nativeSetFinalFocus", "(IIIIZ)V", + (void*) SetFinalFocus }, + { "nativeSetKitFocus", "(IIIIIIZ)V", + (void*) SetKitFocus }, + { "nativeUnblockFocus", "()V", + (void*) UnblockFocus }, + { "nativeUpdateFrameCache", "()V", + (void*) UpdateFrameCache }, + { "nativeGetContentMinPrefWidth", "()I", + (void*) GetContentMinPrefWidth }, + { "nativeRecordContent", "(Landroid/graphics/Region;Landroid/graphics/Point;)Z", + (void*) RecordContent }, + { "setViewportSettingsFromNative", "()V", + (void*) SetViewportSettingsFromNative }, + { "nativeSetSnapAnchor", "(II)V", + (void*) SetSnapAnchor }, + { "nativeSnapToAnchor", "()V", + (void*) SnapToAnchor }, + { "nativeSplitContent", "()V", + (void*) SplitContent }, + { "nativeSetBackgroundColor", "(I)V", + (void*) SetBackgroundColor }, + { "nativeGetSelection", "(Landroid/graphics/Region;)Ljava/lang/String;", + (void*) GetSelection }, + { "nativeRefreshPlugins", "(Z)V", + (void*) RefreshPlugins }, + { "nativeRegisterURLSchemeAsLocal", "(Ljava/lang/String;)V", + (void*) RegisterURLSchemeAsLocal }, + { "nativeDumpDomTree", "(Z)V", + (void*) DumpDomTree }, + { "nativeDumpRenderTree", "(Z)V", + (void*) DumpRenderTree }, + { "nativeDumpNavTree", "()V", + (void*) DumpNavTree } +}; + +int register_webviewcore(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_webView = env->GetFieldID(widget, + "mWebView", "Landroid/webkit/WebView;"); + LOG_ASSERT(gWebViewCoreFields.m_webView, + "Unable to find android/webkit/WebViewCore.mWebView"); + + return jniRegisterNativeMethods(env, "android/webkit/WebViewCore", + gJavaWebViewCoreMethods, NELEM(gJavaWebViewCoreMethods)); +} + +} /* namespace android */ diff --git a/WebKit/android/jni/WebViewCore.h b/WebKit/android/jni/WebViewCore.h new file mode 100644 index 0000000..3743206 --- /dev/null +++ b/WebKit/android/jni/WebViewCore.h @@ -0,0 +1,420 @@ +/* + * 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 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 WEBVIEWCORE_H +#define WEBVIEWCORE_H + +#include "CacheBuilder.h" +#include "CachedHistory.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 <jni.h> +#include <ui/KeycodeLabels.h> + +namespace WebCore { + class AtomicString; + class Color; + class FrameView; + class HTMLSelectElement; + class RenderPart; + class RenderText; + class Node; + class RenderTextControl; + class ScrollView; + class TimerBase; +} + +struct PluginWidgetAndroid; +class SkPicture; +class SkIRect; + +namespace android { + + 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 + + /** + * 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); + + /** + * Scroll to the point x,y relative to the current position. + * @param x The relative x position. + * @param y The relative y position. + * @param animate If it is true, animate to the new scroll position + */ + void scrollBy(int x, int y, bool animate); + + /** + * Record the invalid rectangle + */ + void contentInvalidate(const WebCore::IntRect &rect); + + /** + * Satisfy any outstanding invalidates, so that the current state + * of the DOM is drawn. + */ + void contentDraw(); + + /** 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 focus was set after returning to prior page + * used to rebuild and display any changes in focus + */ + void notifyFocusSet(); + + /** + * 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 restore the screen width, which in turn restores + * the scale. + */ + void restoreScale(int); + + /** + * 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 WebCore::String& text); + + // JavaScript support + void jsAlert(const WebCore::String& url, const WebCore::String& text); + bool jsConfirm(const WebCore::String& url, const WebCore::String& text); + bool jsPrompt(const WebCore::String& url, const WebCore::String& message, + const WebCore::String& defaultValue, WebCore::String& result); + bool jsUnload(const WebCore::String& url, const WebCore::String& message); + + // + // Followings support calls from Java to native WebCore + // + + WebCore::String retrieveHref(WebCore::Frame* frame, WebCore::Node* node); + + WebCore::String getSelection(SkRegion* ); + + // Create a single picture to represent the drawn DOM (used by navcache) + void recordPicture(SkPicture* picture); + // Rebuild the nav cache if the dom changed + void checkNavCache(); + // 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); + void setFinalFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, bool block); + void setKitFocus(int moveGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus); + + // set the scroll amount that webview.java is currently showing + void setScrollOffset(int dx, int dy); + + void setGlobalBounds(int x, int y, int h, int v); + + void setSizeScreenWidthAndScale(int width, int height, int screenWidth, + int scale, int realScreenWidth, int screenHeight); + + /** + * Handle key events from Java. + * @return Whether keyCode was handled by this class. + */ + bool key(int keyCode, UChar32 unichar, int repeatCount, bool isShift, bool isAlt, bool isDown); + + /** + * Handle (mouse) click event from Java + */ + bool click(); + + /** + * Handle touch event + */ + bool handleTouchEvent(int action, int x, int y); + + /** + * Handle motionUp event from the UI thread (called touchUp in the + * WebCore thread). + */ + void touchUp(int touchGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + int size, bool isClick, bool retry); + + /** + * 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 there is no + * focus, or if start == end, silently fail, but set selection to that value. + * If start and end are out of order, swap them. + * Use the frame, node, x, and y to ensure that the correct node is focused. + * Return a frame. Convenience so replaceTextfieldText can use this function. + */ + WebCore::Frame* deleteSelection(WebCore::Frame* frame, WebCore::Node* node, int x, + int y,int start, int end); + + /** + * Set the selection of the currently focused textfield to (start, end). + * If start and end are out of order, swap them. + * Use the frame, node, x, and y to ensure that the correct node is focused. + * Return a frame. Convenience so deleteSelection can use this function. + */ + WebCore::Frame* setSelection(WebCore::Frame* frame, WebCore::Node* node, int x, + int y,int start, int end); + /** + * In the currenlty focused textfield, represented by frame, node, x, and y (which + * are used to ensure it has focus), 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(WebCore::Frame* frame, WebCore::Node* node, int x, int y, + int oldStart, int oldEnd, jstring replace, int start, int end); + void passToJs(WebCore::Frame* frame, WebCore::Node* node, int x, int y, int generation, + jstring currentText, int jKeyCode, int keyVal, bool down, bool cap, bool fn, bool sym); + + void saveDocumentState(WebCore::Frame* frame); + + // 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 setSnapAnchor(int x, int y); + void snapToAnchor(); + void unblockFocus() { m_blockFocusChange = false; } + void updateFrameCache(); + 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*); + void invalPlugin(PluginWidgetAndroid*); + void drawPlugins(); + + // Notify the Java side whether it needs to pass down the touch events + void needTouchEvents(bool); + + // other public functions + public: + void removeFrameGeneration(WebCore::Frame* ); + void updateFrameGeneration(WebCore::Frame* ); + + // reset the picture set to empty + void clearContent(); + + // flatten the picture set to a picture + void copyContentToPicture(SkPicture* ); + + // draw the picture set with the specified background color + bool drawContent(SkCanvas* , SkColor ); + + // record the inval area, and the picture size + bool recordContent(SkRegion* , SkIPoint* ); + int screenWidth() const { return m_screenWidth; } + int scale() const { return m_scale; } + WebCore::Frame* mainFrame() const { return m_mainFrame; } + + // utility to split slow parts of the picture set + void splitContent(); + + // these members are shared with webview.cpp + int retrieveFrameGeneration(WebCore::Frame* ); + static Mutex gFrameCacheMutex; + CachedRoot* m_frameCacheKit; // nav data being built by webcore + SkPicture* m_navPictureKit; + int m_generation; // copy of the number bumped by WebViewNative + 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_useReplay; + bool m_findIsUp; + static Mutex gRecomputeFocusMutex; + WTF::Vector<int> m_recomputeEvents; + // 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: + // 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); + + 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); + + friend class ListBoxReply; + struct FrameGen { + const WebCore::Frame* m_frame; + int m_generation; + }; + WTF::Vector<FrameGen> m_frameGenerations; + static Mutex gFrameGenerationMutex; + struct JavaGlue; + struct JavaGlue* m_javaGlue; + WebCore::Frame* m_mainFrame; + WebCoreReply* m_popupReply; + WebCore::Node* m_lastFocused; + WebCore::IntRect m_lastFocusedBounds; + static Mutex m_contentMutex; // protects ui/core thread pictureset access + PictureSet m_content; // the set of pictures to draw (accessed by UI too) + SkRegion m_addInval; // the accumulated inval region (not yet drawn) + // Used in passToJS to avoid updating the UI text field until after the + // key event has been processed. + bool m_blockTextfieldUpdates; + 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_buildGeneration; + 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_blockFocusChange; + int m_lastPassed; + int m_lastVelocity; + CachedHistory m_history; + WebCore::Node* m_snapAnchorNode; + int m_screenWidth; + int m_scale; + unsigned m_domtree_version; + + SkTDArray<PluginWidgetAndroid*> m_plugins; + WebCore::Timer<WebViewCore> m_pluginInvalTimer; + void pluginInvalTimerFired(WebCore::Timer<WebViewCore>*) { + this->drawPlugins(); + } + + WebCore::Frame* changedKitFocus(WebCore::Frame* frame, + WebCore::Node* node, int x, int y); + bool commonKitFocus(int generation, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus); + bool finalKitFocus(WebCore::Frame* frame, WebCore::Node* node, int x, int y, bool donotChangeDOMFocus); + void doMaxScroll(CacheBuilder::Direction dir); + SkPicture* rebuildPicture(const SkIRect& inval); + void rebuildPictureSet(PictureSet* ); + void sendMarkNodeInvalid(WebCore::Node* ); + void sendNotifyFocusSet(); + void sendNotifyProgressFinished(); + void sendRecomputeFocus(); + bool handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr); + bool prepareFrameCache(); + void releaseFrameCache(bool newCache); +#if DEBUG_NAV_UI + uint32_t m_now; +#endif + }; + +} // namespace android + +#endif // WEBVIEWCORE_H diff --git a/WebKit/android/nav/CacheBuilder.cpp b/WebKit/android/nav/CacheBuilder.cpp new file mode 100644 index 0000000..9150830 --- /dev/null +++ b/WebKit/android/nav/CacheBuilder.cpp @@ -0,0 +1,3076 @@ +/* + * 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 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 "CachedPrefix.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "Document.h" +#include "EventNames.h" +#include "EventTargetNode.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 "InlineTextBox.h" +#include "KURL.h" +#include "PluginView.h" +#include "RenderImage.h" +#include "RenderInline.h" +#include "RenderListBox.h" +#include "RenderSkinCombo.h" +#include "RenderTextControl.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(); +} + +#if DUMP_NAV_CACHE + +#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(", "); +} + +int CacheBuilder::Debug::flowBoxes(RenderFlow* flow, int ifIndex, int indent) { + char scratch[256]; + const InlineFlowBox* box = flow->firstLineBox(); + if (box == NULL) + return ifIndex; + do { + newLine(); + int i = snprintf(scratch, sizeof(scratch), "// render flow:%p" + " box:%p%.*s", flow, box, indent, " "); + for (; box; box = box->nextFlowBox()) { + i += snprintf(&scratch[i], sizeof(scratch) - i, + " [%d]:{%d, %d, %d, %d}", ++ifIndex, + box->xPos(), box->yPos(), box->width(), box->height()); + if (ifIndex % 4 == 0) + break; + } + print(scratch); + } while (box); + RenderObject const * const end = flow->lastChild(); + if (end == NULL) + return ifIndex; + indent += 2; + if (indent > 8) + indent = 8; + for (const RenderObject* renderer = flow->firstChild(); renderer != end; + renderer = renderer->nextSibling()) + { + if (renderer->isInlineFlow()) + ifIndex = flowBoxes((RenderFlow*) renderer, ifIndex, indent); + } + return ifIndex; +} + +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 (mBuffer[len] == '\\' || 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("#define TEST%s_RECTS NULL\n", name); + DUMP_NAV_LOGD("static int TEST%s_RECT_COUNT = 0; // 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; + EventTargetNode* elementTarget = node->isEventTargetNode() ? + (EventTargetNode*) node : NULL; + if (elementTarget) { + if (elementTarget->getEventListener(eventNames().clickEvent)) + properties.append("ONCLICK | "); + if (elementTarget->getEventListener(eventNames().mousedownEvent)) + properties.append("MOUSEDOWN | "); + if (elementTarget->getEventListener(eventNames().mouseupEvent)) + properties.append("MOUSEUP | "); + if (elementTarget->getEventListener(eventNames().mouseoverEvent)) + properties.append("MOUSEOVER | "); + if (elementTarget->getEventListener(eventNames().mouseoutEvent)) + properties.append("MOUSEOUT | "); + if (elementTarget->getEventListener(eventNames().keydownEvent)) + properties.append("KEYDOWN | "); + if (elementTarget->getEventListener(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 = Builder(frame)->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; + if (renderer) { + const IntRect& absB = renderer->absoluteBoundingBoxRect(); + snprintf(scratch, sizeof(scratch), ", {%d, %d, %d, %d}, %s" + ", %d},",absB.x(), absB.y(), absB.width(), absB.height(), + renderer->hasOverflowClip() ? "true" : "false", tabindex); + 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); + NamedAttrMap* 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) + renderTree(renderer, 0, node, count); + 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; + 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) == 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) { + int renderX, renderY; + renderText->absolutePosition(renderX, renderY); + IntRect rect = textBox->selectionRect(renderX, renderY, 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(), textBox->selectionHeight(), textBox->selectionTop()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d", + textBox->spaceAdd(), textBox->start(), textBox->textPos()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d, %d", + textBox->xPos(), textBox->yPos(), textBox->width(), textBox->height()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d }, // %d ", + textBox->baseline(), ++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", 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 (node->isEventTargetNode()) + 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::renderTree(RenderObject* renderer, int indent, + Node* child, int count) +{ + char scratch[256]; + Node* node = renderer->node(); + if (node != child) { + count = ParentIndex(child, count, node); + if (renderer->isRenderBlock() == false) + goto tryParent; + RenderBlock* renderBlock = (RenderBlock*) renderer; + if (renderBlock->hasColumns() == false) + goto tryParent; + Vector<IntRect>* rects = renderBlock->columnRects(); + newLine(indent); + snprintf(scratch, sizeof(scratch), "// render parent=%d", count); + print(scratch); + for (size_t x = 0; x < rects->size(); x++) { + const IntRect& rect = rects->at(x); + snprintf(scratch, sizeof(scratch), "(%d,%d,%d,%d) ", rect.x(), + rect.y(), rect.width(), rect.height()); + print(scratch); + } + } + { + newLine(indent); + RenderStyle* style = renderer->style(); + EVisibility vis = style->visibility(); + ASSERT(vis == VISIBLE || vis == HIDDEN || vis == COLLAPSE); + snprintf(scratch, sizeof(scratch), + "// render style visible:%s opacity:%g width:%d height:%d" + " hasBackground:%s isInlineFlow:%s isBlockFlow:%s" + " textOverflow:%s", + vis == VISIBLE ? "visible" : vis == HIDDEN ? "hidden" : "collapse", + style->opacity(), renderer->width(), renderer->height(), + style->hasBackground() ? "true" : "false", + renderer->isInlineFlow() ? "true" : "false", + renderer->isBlockFlow() ? "true" : "false", + style->textOverflow() ? "true" : "false" + ); + print(scratch); + newLine(indent); + const IntRect& oRect = renderer->overflowRect(true); + const IntRect& cRect = renderer->getOverflowClipRect(0,0); + snprintf(scratch, sizeof(scratch), + "// render xPos:%d yPos:%d overflowRect:{%d, %d, %d, %d} " + " getOverflowClipRect:{%d, %d, %d, %d} ", + renderer->xPos(), renderer->yPos(), + oRect.x(), oRect.y(), oRect.width(), oRect.height(), + cRect.x(), cRect.y(), cRect.width(), cRect.height() + ); + print(scratch); + if (renderer->isInlineFlow()) { + RenderFlow* renderFlow = (RenderFlow*) renderer; + int ifIndex = 0; + flowBoxes(renderFlow, ifIndex, 0); + } + } +tryParent: + RenderObject* parent = renderer->parent(); + if (parent) + renderTree(parent, indent + 2, node, count); +} + +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() +{ + mLastKnownFocus = NULL; + mAllowableTypes = ALL_CACHEDNODETYPES; +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gNavCacheLogFile = NULL; +#endif +} + +void CacheBuilder::adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds) +{ + int x = 0; + int y = 0; + int tx = track.mBounds.x(); + int ty = track.mBounds.y(); + int columnGap = track.mColumnGap; + size_t limit = track.mColumns->size(); + for (size_t index = 0; index < limit; index++) { + IntRect column = track.mColumns->at(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(); + } +} + +bool CacheBuilder::AnyChildIsClick(Node* node) +{ + Node* child = node->firstChild(); + while (child != NULL) { + if (child->isEventTargetNode()) { + EventTargetNode* target = (EventTargetNode*) child; + if (target->isFocusable() || + target->getEventListener(eventNames().clickEvent) || + target->getEventListener(eventNames().mousedownEvent) || + target->getEventListener(eventNames().mouseupEvent) || + target->getEventListener(eventNames().keydownEvent) || + target->getEventListener(eventNames().keyupEvent)) + 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); + EventTargetNode* target = (EventTargetNode*) node; + if (target->getEventListener(eventNames().mouseoverEvent) == NULL && + target->getEventListener(eventNames().mouseoutEvent) == NULL && + target->getEventListener(eventNames().keydownEvent) == NULL && + target->getEventListener(eventNames().keyupEvent) == NULL) + return false; + if (target->getEventListener(eventNames().clickEvent)) + return false; + if (target->getEventListener(eventNames().mousedownEvent)) + return false; + if (target->getEventListener(eventNames().mouseupEvent)) + return false; + return AnyChildIsClick(node); +} + +void CacheBuilder::buildCache(CachedRoot* root) +{ + Frame* frame = FrameAnd(this); + mLastKnownFocus = NULL; + m_areaBoundsMap.clear(); + BuildFrame(frame, frame, root, (CachedFrame*) root); + root->finishInit(); // set up frame parent pointers, child pointers + setData((CachedFrame*) root); +} + +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()) { + PluginView* pv = static_cast<PluginView*>(widget); + // check if this plugin really wants key events (TODO) + return true; + } + } + return false; +} + +// 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<Tracker> tracker(1); + { + Tracker* baseTracker = tracker.data(); // sentinel + bzero(baseTracker, sizeof(Tracker)); + baseTracker->mCachedNodeIndex = -1; + } + WTF::Vector<ClipColumnTracker> clipTracker(1); + { + ClipColumnTracker* baseTracker = clipTracker.data(); // sentinel + bzero(baseTracker, sizeof(ClipColumnTracker)); + } + WTF::Vector<TabIndexTracker> tabIndexTracker(1); + { + TabIndexTracker* baseTracker = tabIndexTracker.data(); // sentinel + bzero(baseTracker, sizeof(TabIndexTracker)); + } +#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(cachedParentNode); + Node* node = parent; + int cacheIndex = 1; + Node* focused = doc->focusedNode(); + if (focused) { + setLastFocus(focused); + cachedRoot->setFocusBounds(mLastKnownFocusBounds); + } + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); + while (walk.mMore || (node = node->traverseNextNode()) != NULL) { +#if DUMP_NAV_CACHE + nodeIndex++; +#endif + Tracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + while (node == last->mLastChild) { + if (CleanUpContainedNodes(cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + lastChildIndex = last->mCachedNodeIndex; + last = &tracker.last(); + } + if (node == last->mParentLastChild) + last->mParentLastChild = NULL; + do { + const ClipColumnTracker* lastClip = &clipTracker.last(); + if (node != lastClip->mLastChild) + break; + clipTracker.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.setChildFrameIndex(childFrameIndex); +#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 hasFocusRing = true; + if (nodeRenderer != NULL) { + RenderStyle* style = nodeRenderer->style(); + if (style->visibility() == HIDDEN) + continue; + if (nodeRenderer->isImage()) { // set all the area elements to have a link to their images + RenderImage* image = static_cast<RenderImage*>(nodeRenderer); + HTMLMapElement* map = image->imageMap(); + if (map) { + Node* node; + for (node = map->firstChild(); node; + node = node->traverseNextNode(map)) { + if (!node->hasTagName(HTMLNames::areaTag)) + continue; + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + m_areaBoundsMap.set(area, image); + } + } + } + isTransparent = style->hasBackground() == false; +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + hasFocusRing = style->tapHighlightColor().alpha() > 0; +#endif + } + bool more = walk.mMore; + walk.reset(); + // GetGlobalBounds(node, &bounds, false); + bool computeFocusRings = false; + bool hasClip = false; + bool hasMouseOver = false; + bool isAnchor = false; + bool isArea = node->hasTagName(HTMLNames::areaTag); + bool isInput = false; + bool isPassword = false; + bool isTextArea = false; + bool isTextField = false; + bool isRtlText = false; + bool isUnclipped = false; + bool isFocus = node == focused; + bool takesFocus = false; + bool wantsKeyEvents = false; + int maxLength = -1; + int textSize = 12; + int columnGap = 0; + TextDirection direction = LTR; + String name; + String exported; + CachedNodeType type = NORMAL_CACHEDNODETYPE; + IntRect bounds; + IntRect absBounds; + WTF::Vector<IntRect>* columns = NULL; + int minimumFocusableWidth = MINIMUM_FOCUSABLE_WIDTH; + int minimumFocusableHeight = MINIMUM_FOCUSABLE_HEIGHT; + if (isArea) { + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + bounds = getAreaRect(area); + 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(); + absBounds.move(globalOffsetX, globalOffsetY); + hasClip = nodeRenderer->hasOverflowClip(); + + if (checkForPluginViewThatWantsFocus(nodeRenderer)) { + bounds = absBounds; + isUnclipped = true; + takesFocus = true; + wantsKeyEvents = true; + goto keepNode; + } + if (nodeRenderer->isRenderBlock()) { + RenderBlock* renderBlock = (RenderBlock*) nodeRenderer; + if (renderBlock->hasColumns()) { + columns = renderBlock->columnRects(); +#ifdef ANDROID_EXPOSE_COLUMN_GAP + columnGap = renderBlock->columnGap(); +#endif + direction = renderBlock->style()->direction(); + } + } + if ((hasClip != false || columns != NULL) && lastChild) { + clipTracker.grow(clipTracker.size() + 1); + ClipColumnTracker& clip = clipTracker.last(); + clip.mBounds = absBounds; + clip.mLastChild = OneAfter(lastChild); + clip.mNode = node; + clip.mColumns = columns; + clip.mColumnGap = columnGap; + clip.mHasClip = hasClip; + clip.mDirection = direction; + if (columns != NULL) { + const IntRect& oRect = nodeRenderer->overflowRect(true); + clip.mBounds.move(oRect.x(), oRect.y()); + } + } + if (node->isTextNode() && mAllowableTypes != NORMAL_CACHEDNODETYPE) { + 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 = (CachedNodeType) 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.focusRings()) == false) + continue; + absBounds = bounds; + cachedNode.setBounds(bounds); + if (bounds.width() < MINIMUM_FOCUSABLE_WIDTH) + continue; + if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) + continue; + computeFocusRings = 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 = (HTMLInputElement*) node; + if (input->inputType() == HTMLInputElement::FILE) + continue; + isInput = true; + isTextField = input->isTextField(); + isPassword = input->inputType() == HTMLInputElement::PASSWORD; + maxLength = input->maxLength(); + name = String(input->name().string()); + isUnclipped = isTransparent; // can't detect if this is drawn on top (example: deviant.com login parts) + } else if (node->hasTagName(HTMLNames::textareaTag)) + isTextArea = true; + else if (node->hasTagName(HTMLNames::aTag)) { + const HTMLAnchorElement* anchorNode = + (const HTMLAnchorElement*) node; + if (!anchorNode->isFocusable() && !HasTriggerEvent(node)) + continue; + EventTargetNode* target = (EventTargetNode*) node; + if (target->disabled()) + continue; + hasMouseOver = target->getEventListener(eventNames().mouseoverEvent); + isAnchor = true; + KURL href = anchorNode->href(); + if (!href.isEmpty() && !href.protocolIs("javascript")) + // Set the exported string for all non-javascript anchors. + exported = href.string(); + } + if (isTextField || isTextArea) { + RenderTextControl* renderText = + static_cast<RenderTextControl*>(nodeRenderer); + if (isFocus) + cachedRoot->setSelection(renderText->selectionStart(), renderText->selectionEnd()); + exported = String(renderText->text()); + // FIXME: Would it be better to use (float) size()? + // 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(); + textSize = style->fontSize(); + isRtlText = style->direction() == RTL || + style->textAlign() == WebCore::RIGHT || + style->textAlign() == WebCore::WEBKIT_RIGHT; + } + minimumFocusableWidth += 4; + minimumFocusableHeight += 4; + } + takesFocus = true; + if (isAnchor) { + bounds = absBounds; + } else { + bool isFocusable = node->isKeyboardFocusable(NULL) || + node->isMouseFocusable() || node->isFocusable(); + if (isFocusable == false) { + if (node->isEventTargetNode() == false) + continue; + EventTargetNode* eventTargetNode = (EventTargetNode*) node; + if (eventTargetNode->disabled()) + continue; + bool overOrOut = HasOverOrOut(node); + bool hasTrigger = HasTriggerEvent(node); + if (overOrOut == false && hasTrigger == false) + continue; + takesFocus = hasTrigger; + } + bounds = node->getRect(); + // For Bank of America site + if (isTextField && nodeRenderer->paddingLeft() > 100) { + int paddingLeft = nodeRenderer->paddingLeft(); + int paddingTop = nodeRenderer->paddingTop(); + int x = bounds.x() + paddingLeft; + int y = bounds.y() + paddingTop; + int width = bounds.width() - paddingLeft - nodeRenderer->paddingRight(); + int height = bounds.height() - paddingTop - nodeRenderer->paddingBottom(); + bounds.setLocation(IntPoint(x, y)); + bounds.setSize(IntSize(width, height)); + } + if (bounds.width() < minimumFocusableWidth) + continue; + if (bounds.height() < minimumFocusableHeight) + continue; + bounds.move(globalOffsetX, globalOffsetY); + } + computeFocusRings = true; + keepNode: + cachedNode.init(node); + if (computeFocusRings == false) { + cachedNode.setBounds(bounds); + cachedNode.focusRings().append(bounds); + } else if (ConstructPartRects(node, bounds, cachedNode.boundsPtr(), + globalOffsetX, globalOffsetY, &cachedNode.focusRings()) == false) + continue; + keepTextNode: + 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); + continue; + } + const IntRect& parentClip = clipTrack.mBounds; + if (hasClip == false && isAnchor) + clip = parentClip; + else + clip.intersect(parentClip); + hasClip = true; + } + if (hasClip && cachedNode.clip(clip) == false) { + cachedNode.setBounds(clip); + cachedNode.focusRings().append(clip); + isUnclipped = true; + } + cachedNode.setNavableRects(); + cachedNode.setChildFrameIndex(-1); + cachedNode.setExport(exported); + cachedNode.setHasFocusRing(hasFocusRing); + cachedNode.setHasMouseOver(hasMouseOver); + cachedNode.setHitBounds(absBounds); + cachedNode.setIndex(cacheIndex); + cachedNode.setIsAnchor(isAnchor); + cachedNode.setIsArea(isArea); + cachedNode.setIsFocus(isFocus); + cachedNode.setIsInput(isInput); + cachedNode.setIsPassword(isPassword); + cachedNode.setIsRtlText(isRtlText); + cachedNode.setIsTextArea(isTextArea); + cachedNode.setIsTextField(isTextField); + cachedNode.setIsTransparent(isTransparent); + cachedNode.setIsUnclipped(isUnclipped); + cachedNode.setMaxLength(maxLength); + cachedNode.setName(name); + cachedNode.setParentIndex(last->mCachedNodeIndex); + if (last->mParentLastChild == NULL) + last->mParentLastChild = OneAfter(node->parentNode()->lastChild()); + cachedNode.setParentGroup(last->mParentLastChild); + cachedNode.setTabIndex(tabIndex); + cachedNode.setTextSize(textSize); + cachedNode.setType(type); + cachedNode.setWantsKeyEvents(wantsKeyEvents); +#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); + Tracker& working = tracker.last(); + working.mCachedNodeIndex = lastIndex; + working.mLastChild = OneAfter(lastChild); + working.mParentLastChild = OneAfter(node->parentNode()->lastChild()); + last = &tracker.at(tracker.size() - 2); + working.mSomeParentTakesFocus = last->mSomeParentTakesFocus | takesFocus; + } + } + cacheIndex++; + } + while (tracker.size() > 1) { + Tracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + if (CleanUpContainedNodes(cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + } +} + +bool CacheBuilder::CleanUpContainedNodes(CachedFrame* cachedFrame, + const Tracker* 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->focusRings().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 && + lastNode->isEventTargetNode() == true && + HasOverOrOut(lastNode) == true && + HasTriggerEvent(lastNode) == false; + if (cachedFrame->focusIndex() == lastChildIndex) + cachedFrame->setFocusIndex(last->mCachedNodeIndex); + if (onlyChildCached->parent() == lastCached) + onlyChildCached->setParentIndex(lastCached->parentIndex()); + if (outerIsMouseMoveOnly || onlyChild->isKeyboardFocusable(NULL)) + *lastCached = *onlyChildCached; + cachedFrame->removeLast(); + 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) +{ + FindState addressState; + FindReset(&addressState); + addressState.mWords[0] = addressState.mStarts[0] = chars; + 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 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->mBases[s->mWordCount] = baseChars; + s->mWords[s->mWordCount] = chars - s->mNumberCount; + s->mStarts[s->mWordCount] = s->mCurrentStart; + 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) { + 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; + memmove(s->mBases, &s->mBases[shift], (sizeof(s->mBases) / sizeof(s->mBases[0]) - shift) * sizeof(s->mBases[0])); + memmove(s->mWords, &s->mWords[shift], (sizeof(s->mWords) / sizeof(s->mWords[0]) - shift) * sizeof(s->mWords[0])); + memmove(s->mStarts, &s->mStarts[shift], (sizeof(s->mStarts) / sizeof(s->mStarts[0]) - shift) * sizeof(s->mStarts[0])); + 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->mBases[s->mWordCount] = baseChars; + s->mWords[s->mWordCount] = chars; + s->mStarts[s->mWordCount] = s->mCurrentStart; + 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->mBases[s->mWordCount] = baseChars; + s->mWords[s->mWordCount] = chars; + s->mStarts[s->mWordCount] = s->mCurrentStart; + 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; + 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; + 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: // minus two below skips city before state + for (int wordsIndex = s->mStateWord - 2; 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]; + 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) + return FOUND_NONE; + s->mStartResult = s->mWords[wordReduction] - s->mStarts[wordReduction]; + } + } + return FOUND_COMPLETE; + } + nextTest: + names += offset; + } + } + 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; + memmove(s->mBases, &s->mBases[shift], (sizeof(s->mBases) / sizeof(s->mBases[0]) - shift) * sizeof(s->mBases[0])); + memmove(s->mWords, &s->mWords[shift], (sizeof(s->mWords) / sizeof(s->mWords[0]) - shift) * sizeof(s->mWords[0])); + memmove(s->mStarts, &s->mStarts[shift], (sizeof(s->mStarts) / sizeof(s->mStarts[0]) - shift) * sizeof(s->mStarts[0])); + 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) const +{ + RenderImage* map = m_areaBoundsMap.get(area); + if (!map) + return IntRect(); + if (area->isDefault()) + return map->absoluteBoundingBoxRect(); + return area->getRect(map); +} + +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) +{ + EventTargetNode* target = (EventTargetNode*) node; + return target->getEventListener(eventNames().mouseoverEvent) || + target->getEventListener(eventNames().mouseoutEvent); + +} + +bool CacheBuilder::HasTriggerEvent(Node* node) +{ + EventTargetNode* target = (EventTargetNode*) node; + return target->getEventListener(eventNames().clickEvent) || + target->getEventListener(eventNames().mousedownEvent) || + target->getEventListener(eventNames().mouseupEvent) || + target->getEventListener(eventNames().keydownEvent) || + target->getEventListener(eventNames().keyupEvent); +} + +// #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; +} + +// does not find text to keep it fast +// (this assume text nodes are more rarely moved than other nodes) +Node* CacheBuilder::findByCenter(int x, int y) const +{ + DBG_NAV_LOGD("x=%d y=%d\n", x, y); + Frame* frame = FrameAnd(this); + Node* node = frame->document(); + ASSERT(node != NULL); + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); + while ((node = node->traverseNextNode()) != NULL) { + Frame* child = HasFrame(node); + if (child != NULL) { + if (child->document() == NULL) + continue; + CacheBuilder* cacheBuilder = Builder(child); + // if (cacheBuilder->mViewBounds.isEmpty()) + // continue; + Node* result = cacheBuilder->findByCenter(x, y); + if (result != NULL) + return result; + } + if (node->isTextNode()) + continue; + IntRect bounds; + if (node->hasTagName(HTMLNames::areaTag)) { + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + bounds = getAreaRect(area); + bounds.move(globalOffsetX, globalOffsetY); + } else + bounds = node->getRect(); + if (bounds.isEmpty()) + continue; + bounds.move(globalOffsetX, globalOffsetY); + if (x != bounds.x() + (bounds.width() >> 1)) + continue; + if (y != bounds.y() + (bounds.height() >> 1)) + continue; + if (node->isKeyboardFocusable(NULL)) + return node; + if (node->isMouseFocusable()) + return node; + if (node->isFocusable()) + return node; + if (node->isEventTargetNode() == false) + continue; + if (AnyIsClick(node)) + continue; + if (HasTriggerEvent(node) == false) + continue; + return node; + } + return NULL; +} + +bool CacheBuilder::isFocusableText(NodeWalk* walk, bool more, Node* node, + CachedNodeType* type, String* exported) const +{ + Text* textNode = static_cast<Text*>(node); + StringImpl* string = textNode->string(); + 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 = (CachedNodeType) (checkType << 1)) + { + if ((checkType & 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->string(); + 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 { + node = node->traverseNextNode(); + if (node == NULL || node->hasTagName(HTMLNames::aTag)) { + 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->string(); + baseChars = string->characters(); + inlineTextBox = baseInline; + start = inlineTextBox->start(); + finalNode: + findState.mEndResult = 0; + } while (true); +tryNextCheckType: + node = textNode; + baseInline = saveInline; + string = textNode->string(); + 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: + exported->insert(WebCore::String("mailto:"), 0); + break; + case PHONE_CACHEDNODETYPE: + exported->insert(WebCore::String("tel:"), 0); + break; + default: + break; + } + return true; + } +noTextMatch: + walk->reset(); + return false; +} + +bool CacheBuilder::IsMailboxChar(UChar ch) +{ + static const unsigned body[] = {0x03ff6000, 0x87fffffe, 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::outOfDate() +{ + Node* kitFocusNode = currentFocus(); + if (mLastKnownFocus != kitFocusNode) { + DBG_NAV_LOGD("%s\n", "mLastKnownFocus != kitFocusNode"); + return true; + } + if (kitFocusNode == NULL) + return false; + IntRect kitBounds = kitFocusNode->getRect(); + bool result = kitBounds != mLastKnownFocusBounds; + if (result == true) + DBG_NAV_LOGD("%s\n", "kitBounds != mLastKnownFocusBounds"); + return result; +} + +void CacheBuilder::setLastFocus(Node* node) +{ + ASSERT(node); + mLastKnownFocus = node; + mLastKnownFocusBounds = node->getRect(); +} + +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; +} + +bool CacheBuilder::validNode(void* matchFrame, void* matchNode) const +{ + Frame* frame = FrameAnd(this); + if (matchFrame == frame) { + if (matchNode == NULL) + return true; + Node* node = frame->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 = frame->tree()->firstChild(); + while (child) { + bool result = Builder(child)->validNode(matchFrame, matchNode); + if (result) + return result; + child = child->tree()->nextSibling(); + } +#if DEBUG_NAV_UI + if (frame->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) +{ + 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; + if (test->isTextNode()) { + RenderText* renderText = (RenderText*) renderer; + InlineTextBox *textBox = renderText->firstTextBox(); + if (textBox == NULL) + 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 (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(); + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + continue; + } + if (renderer->hasOverflowClip() == false) { + if (nodeIsAnchor && test->hasTagName(HTMLNames::divTag)) { + IntRect bounds = renderer->absoluteBoundingBoxRect(); // x, y fixup done by AddPartRect + int left = bounds.x() + renderer->paddingLeft() + + renderer->borderLeft(); + int top = bounds.y() + renderer->paddingTop() + + renderer->borderTop(); + int right = bounds.right() - renderer->paddingRight() + - renderer->borderRight(); + int bottom = bounds.bottom() - renderer->paddingBottom() + - 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->string(); + const UChar* chars = string->characters(); + int renderX, renderY; + renderText->absolutePosition(renderX, renderY); + do { + int textBoxStart = textBox->start(); + int textBoxEnd = textBoxStart + textBox->len(); + if (textBoxEnd <= start) + continue; + if (textBoxEnd > relEnd) + textBoxEnd = relEnd; + IntRect bounds = textBox->selectionRect(renderX, renderY, + 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 (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/WebKit/android/nav/CacheBuilder.h b/WebKit/android/nav/CacheBuilder.h new file mode 100644 index 0000000..07040e2 --- /dev/null +++ b/WebKit/android/nav/CacheBuilder.h @@ -0,0 +1,272 @@ +/* + * 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 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 CacheBuilder_H +#define CacheBuilder_H + +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" +#include "TextDirection.h" +#include "wtf/HashMap.h" +#include "wtf/Vector.h" + +#define NAVIGATION_MAX_PHONE_LENGTH 14 + +using namespace WebCore; + +namespace WebCore { + +class Document; +class Frame; +class HTMLAreaElement; +class InlineTextBox; +class Node; +class PlatformGraphicsContext; +class RenderFlow; +class RenderImage; +class RenderObject; +class RenderLayer; +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_CACHEDNODETYPES; } + void buildCache(CachedRoot* root); + static bool ConstructPartRects(Node* node, const IntRect& bounds, + IntRect* focusBounds, int x, int y, WTF::Vector<IntRect>* result); + Node* currentFocus() const; + void disallowAddressDetection() { mAllowableTypes = (CachedNodeType) ( + mAllowableTypes & ~ADDRESS_CACHEDNODETYPE); } + void disallowEmailDetection() { mAllowableTypes = (CachedNodeType) ( + mAllowableTypes & ~EMAIL_CACHEDNODETYPE); } + void disallowPhoneDetection() { mAllowableTypes = (CachedNodeType) ( + mAllowableTypes & ~PHONE_CACHEDNODETYPE); } + static FoundState FindAddress(const UChar* , unsigned length, int* start, int* end); + Node* findByCenter(int x, int y) const; + static void GetGlobalOffset(Frame* , int* x, int * y); + static void GetGlobalOffset(Node* , int* x, int * y); + bool outOfDate(); + void setLastFocus(Node* ); + bool validNode(void* framePtr, void* nodePtr) const; +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]; + CachedNodeType mStoreType; + 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* 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; + }; + struct ClipColumnTracker { + IntRect mBounds; + Node* mLastChild; + Node* mNode; + WTF::Vector<IntRect>* mColumns; + int mColumnGap; + TextDirection mDirection; + bool mHasClip; + }; + struct TabIndexTracker { + int mTabIndex; + Node* mLastChild; + }; + struct Tracker { + int mCachedNodeIndex; + int mTabIndex; + Node* mLastChild; + Node* mParentLastChild; + bool mSomeParentTakesFocus; + }; + void adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds); + 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); + void BuildFrame(Frame* root, Frame* frame, + CachedRoot* cachedRoot, CachedFrame* cachedFrame); + bool CleanUpContainedNodes(CachedFrame* cachedFrame, + const Tracker* 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* ); + IntRect getAreaRect(const HTMLAreaElement* area) const; + 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* ); + Node* tryFocus(Direction direction); + Node* trySegment(Direction direction, int mainStart, int mainEnd); + Node* mLastKnownFocus; + IntRect mLastKnownFocusBounds; + CachedNodeType mAllowableTypes; + WTF::HashMap<const HTMLAreaElement* , RenderImage* > m_areaBoundsMap; +#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); + int flowBoxes(RenderFlow* flow, int ifIndex, int indent); + 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 renderTree(RenderObject* , int indent, Node* , int count); + 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/WebKit/android/nav/CachedDebug.h b/WebKit/android/nav/CachedDebug.h new file mode 100644 index 0000000..3127112 --- /dev/null +++ b/WebKit/android/nav/CachedDebug.h @@ -0,0 +1,74 @@ +/* + * 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 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 CachedDebug_H +#define CachedDebug_H + +#ifndef DUMP_NAV_CACHE +#ifdef NDEBUG +#define DUMP_NAV_CACHE 0 +#else +#define DUMP_NAV_CACHE 1 +#endif +#endif + +#ifndef DEBUG_NAV_UI +#ifdef NDEBUG +#define DEBUG_NAV_UI 0 +#else +#define DEBUG_NAV_UI 1 +#endif +#endif + +#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 DBG_NAV_LOGD_THROTTLE(format, ...) ((void)0) +#define DEBUG_NAV_UI_LOGD(...) ((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) +#else +#define DUMP_NAV_LOGD(...) LOGD(__VA_ARGS__) +#endif +#else +#define DUMP_NAV_LOGD(...) ((void)0) +#endif + +#endif diff --git a/WebKit/android/nav/CachedFrame.cpp b/WebKit/android/nav/CachedFrame.cpp new file mode 100644 index 0000000..4db9e40 --- /dev/null +++ b/WebKit/android/nav/CachedFrame.cpp @@ -0,0 +1,1318 @@ +/* + * 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 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 "CachedPrefix.h" +#include "CachedHistory.h" +#include "CachedNode.h" +#include "CachedRoot.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 { + +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::checkVisited(const CachedNode* node, Direction direction) const +{ + return history()->checkVisited(node, direction); +} + +void CachedFrame::clearFocus() +{ + if (mFocus < 0) + return; + CachedNode& focus = mCachedNodes[mFocus]; + focus.clearFocus(this); + mFocus = -1; +} + +// the thing that sucks is that when you're on a link, you want to navigate next door to a link just like this one, but can't make it +// so with all my other sucky compares, maybe there needs to be one that prefers links that are aligned with the current focus... + +// 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 CachedNode* focus) const +{ + if (testData.mNode->tabIndex() != bestData.mNode->tabIndex()) { + if (testData.mNode->tabIndex() < bestData.mNode->tabIndex() + || (focus && focus->tabIndex() < bestData.mNode->tabIndex())) { + testData.mNode->setCondition(CachedNode::HIGHER_TAB_INDEX); + return REJECT_TEST; + } + return TEST_IS_BEST; + } +// start here; + // if the test minor axis line intersects the line segment between focus 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_FOCUS); + return REJECT_TEST; + } + return TEST_IS_BEST; + } + if (testData.mInNav) { + if (bestData.mMajorDelta < testData.mMajorDelta) { + testData.mNode->setCondition(CachedNode::CLOSER_IN_FOCUS); + 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; +// SkFixed focusMultiplier = SK_Fixed1; +// if (focus != NULL) { +// if (testData.mMajorDelta < bestData.mMajorDelta) { +// // use bestData.mMajorDelta, +// } else if (bestData.mMajorDelta < testData.mMajorDelta) { +// +// } + 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.mFocusChild != bestData.mFocusChild) { + if (bestData.mFocusChild) { + testData.mNode->setCondition(CachedNode::IN_FOCUS_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::currentFocus(const CachedFrame** framePtr) const +{ + if (framePtr) + *framePtr = this; + if (mFocus < 0) + return NULL; + const CachedNode* result = &mCachedNodes[mFocus]; + const CachedFrame* frame = hasFrame(result); + if (frame != NULL) + return frame->currentFocus(framePtr); + (const_cast<CachedNode*>(result))->fixUpFocusRects(); + 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, const CachedNode** directHit, const CachedFrame** framePtr, int* x, int* y) 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.mMouseBounds = testData.mNodeBounds = test->getBounds(); + bool checkForHidden = true; + for (size_t part = 0; part < parts; part++) { + if (test->focusRings().at(part).intersects(rect)) { + if (checkForHidden && mRoot->maskIfHidden(&testData) == true) + break; + checkForHidden = false; + WebCore::IntRect testRect = test->focusRings().at(part); + testRect.intersect(testData.mMouseBounds); + if (testRect.contains(center)) { + // We have a direct hit. + if (*directHit == NULL) { + *directHit = test; + *framePtr = this; + *x = center.x(); + *y = center.y(); + } else { + // We have hit another one before + const CachedNode* d = *directHit; + if (d->getBounds().contains(testRect)) { + // This rectangle is inside the other one, so it is + // the best one. + *directHit = test; + *framePtr = this; + } + } + } + if (NULL != *directHit) { + // If we have a direct hit already, there is no need to + // calculate the distances, or check the other focusring parts + break; + } + 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; + 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 (*best > distance) { + *best = distance; + 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, directHit, + framePtr, x, y); + if (NULL != frameResult) + result = frameResult; + } + if (NULL != *directHit) { + result = *directHit; + } + 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, + int* best, const CachedFrame** framePtr, int* x, int* y) 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; + const WebCore::IntRect& testRect = test->hitBounds(); + if (testRect.intersects(rect) == false) + continue; + BestData testData; + testData.mNode = test; + testData.mMouseBounds = testData.mNodeBounds = testRect; + if (mRoot->maskIfHidden(&testData) == true) + continue; + const WebCore::IntRect& bounds = testData.mMouseBounds; + WebCore::IntPoint testCenter = WebCore::IntPoint(bounds.x() + + (bounds.width() >> 1), bounds.y() + (bounds.height() >> 1)); + int dx = testCenter.x() - center.x(); + int dy = testCenter.y() - center.y(); + int distance = dx * dx + dy * dy; + if (*best <= distance) + continue; + *best = distance; + result = test; + *framePtr = this; + const WebCore::IntRect& focusRect = test->focusRings().at(0); + *x = focusRect.x() + (focusRect.width() >> 1); + *y = focusRect.y() + (focusRect.height() >> 1); + } + for (const CachedFrame* frame = mCachedFrames.begin(); + frame != mCachedFrames.end(); frame++) { + const CachedNode* frameResult = frame->findBestHitAt(rect, best, + framePtr, x, y); + if (NULL != frameResult) + result = frameResult; + } + return result; +} + +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->isFocusable(*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->focusRings().at(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; + bestData->mMouseBounds = bestData->mNodeBounds = + test->focusRings().at(part); + 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); + } + } + } + } +} + +bool CachedFrame::finishInit() +{ + CachedNode* lastCached = lastNode(); + lastCached->setLast(); + CachedFrame* child = mCachedFrames.begin(); + while (child != mCachedFrames.end()) { + child->mParent = this; + if (child->finishInit()) + setFocusIndex(child->indexInParent()); + child++; + } + return focusIndex() > 0; +} + +const CachedNode* CachedFrame::frameDown(const CachedNode* test, const CachedNode* limit, BestData* bestData, + const CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameDown, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, DOWN) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setDownDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + if (checkVisited(innerData.mNode, DOWN)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, DOWN)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(focus == NULL || bestData->mNode != focus); + // does the best contain something (or, is it contained by an area which is not the focus?) + // 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 CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameLeft, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, LEFT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setLeftDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + 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(focus == NULL || bestData->mNode != focus); + return bestData->mNode; +} + +int CachedFrame::frameNodeCommon(BestData& testData, const CachedNode* test, BestData* bestData, BestData* originalData, + const CachedNode* focus) const +{ + testData.mFrame = this; + testData.mNode = test; + test->clearCondition(); + if (test->disabled()) { + testData.mNode->setCondition(CachedNode::DISABLED); + return REJECT_TEST; + } + if (mRoot->scrolledBounds().intersects(test->bounds()) == false) { + testData.mNode->setCondition(CachedNode::FOCUSABLE); + return REJECT_TEST; + } +// if (isFocusable(test, &testData.mNodeBounds, walk) == false) { +// testData.mNode->setCondition(CachedNode::FOCUSABLE); +// return REJECT_TEST; +// } +// + if (test == focus) { + testData.mNode->setCondition(CachedNode::NOT_FOCUS_NODE); + return REJECT_TEST; + } +// if (test->bounds().contains(mRoot->focusBounds())) { +// testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); +// return REJECT_TEST; +// } + void* par = focus ? focus->parentGroup() : NULL; + testData.mFocusChild = test->parentGroup() == par; +#if 0 // not debugged + if (focus && focus->hasMouseOver() && test->hasMouseOver() == false && + focus->bounds().contains(test->bounds())) + return REJECT_TEST; +#endif + if (bestData->mNode == NULL) + return TEST_IS_BEST; +#if 0 // not debugged + if (focus && focus->hasMouseOver() && test->hasMouseOver() == false && + focus->bounds().contains(test->bounds())) + return REJECT_TEST; + if (test->hasMouseOver() != bestData->mNode->hasMouseOver()) { + if (test->hasMouseOver()) { + if (test->bounds().contains(bestData->mNode->bounds())) { + const_cast<CachedNode*>(bestData->mNode)->setDisabled(true); + bestData->mNode = NULL; // force part tests to be ignored, yet still set up remaining test data for later comparison + return TEST_IS_BEST; + } + } else { + if (bestData->mNode->bounds().contains(test->bounds())) { + test->setCondition(CachedNode::ANCHOR_IN_ANCHOR); + return REJECT_TEST; + } + } + } +#endif + if (focus && testData.mNode->parentIndex() != bestData->mNode->parentIndex()) { + int focusParentIndex = focus->parentIndex(); + if (focusParentIndex >= 0) { + if (bestData->mNode->parentIndex() == focusParentIndex) + return REJECT_TEST; + if (testData.mNode->parentIndex() == focusParentIndex) + 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 CachedNode* focus) const +{ + if (testData.mNodeBounds.contains(mRoot->focusBounds())) { + testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + return REJECT_TEST; + } + testData.setDistances(); + if (bestData->mNode != NULL) { + int compared = compare(testData, *bestData, focus); + 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 CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameRight, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, RIGHT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setRightDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + if (checkVisited(innerData.mNode, RIGHT)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, RIGHT)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(focus == NULL || bestData->mNode != focus); + return bestData->mNode; +} + +const CachedNode* CachedFrame::frameUp(const CachedNode* test, const CachedNode* limit, BestData* bestData, + const CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameUp, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, UP) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setUpDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + 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(focus == NULL || bestData->mNode != focus); + return bestData->mNode; +} + +const CachedFrame* CachedFrame::hasFrame(const CachedNode* node) const +{ + return node->isFrame() ? &mCachedFrames[node->childFrameIndex()] : NULL; +} + +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; + mFocus = -1; + mFrame = frame; + mParent = NULL; // set up parents after stretchy arrays are set up + mIndex = childFrameIndex; +} + +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(); +} + +bool CachedFrame::moveInFrame(MoveInDirection moveInDirection, + const CachedNode* test, BestData* bestData, + const CachedNode* focus) 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, focus); + return true; +} + +const WebCore::IntRect& CachedFrame::_navBounds() const +{ + return history()->navBounds(); +} + +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(); + } +} + +bool CachedFrame::sameFrame(const CachedFrame* test) const +{ + ASSERT(test); + if (mIndex != test->mIndex) + return false; + if (mIndex == -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::setFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y) +{ + if (NULL == node) { + const_cast<CachedRoot*>(mRoot)->setCachedFocus(NULL, NULL); + return true; + } + if (mFrame != frame) { + for (CachedFrame* testF = mCachedFrames.begin(); testF != mCachedFrames.end(); + testF++) { + if (testF->setFocus(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(); + WTF::Vector<WebCore::IntRect>& focusRings = test->focusRings(); + for (size_t part = 0; part < partMax; part++) { + const WebCore::IntRect& testBounds = focusRings.at(part); + if (testBounds.contains(x, y) == false) + continue; + if (test->isFocus()) { + DBG_NAV_LOGD("already set? test=%d frame=%p node=%p x=%d y=%d", + test->index(), frame, node, x, y); + return true; + } + const_cast<CachedRoot*>(mRoot)->setCachedFocus(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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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 focus + // prefer leftmost center + // if left and right > 0, test node subsumes focus + mNavDelta = left; + mNavDelta2 = right; +} + +void CachedFrame::BestData::setNavOverlap(int span, int left, int right) +{ + mNavOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; // if left or right < 0, test node is not in umbra of focus + 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) +{ + mWorkingOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; // if left or right < 0, test node is not in umbra of focus + 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(); + DUMP_NAV_LOGD("// }; // end of nodes\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 mIndex=%d;\n", b->mIndex); + DUMP_NAV_LOGD("// const CachedRoot* mRoot=%p;\n", b->mRoot); + DUMP_NAV_LOGD("// int mFocus=%d;\n", b->mFocus); +} + +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/WebKit/android/nav/CachedFrame.h b/WebKit/android/nav/CachedFrame.h new file mode 100644 index 0000000..8e77470 --- /dev/null +++ b/WebKit/android/nav/CachedFrame.h @@ -0,0 +1,228 @@ +/* + * 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 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 CachedFrame_H +#define CachedFrame_H + +#include "CachedNode.h" +#include "IntRect.h" +#include "SkFixed.h" +#include "wtf/Vector.h" + +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 + }; + CachedFrame() {} + void add(CachedNode& node) { mCachedNodes.append(node); } + void addFrame(CachedFrame& child) { mCachedFrames.append(child); } + bool checkVisited(const CachedNode* , CachedFrame::Direction ) const; + size_t childCount() { return mCachedFrames.size(); } + void clearFocus(); + 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, + const CachedNode** , const CachedFrame** , int* x, int* y) const; + const CachedFrame* findBestFrameAt(int x, int y) const; + const CachedNode* findBestHitAt(const WebCore::IntRect& , + int* best, const CachedFrame** , int* x, int* y) const; + bool finishInit(); + CachedFrame* firstChild() { return mCachedFrames.begin(); } + const CachedFrame* firstChild() const { return mCachedFrames.begin(); } + int focusIndex() const { return mFocus; } + void* framePointer() const { return mFrame; } + CachedNode* getIndex(int index) { return index >= 0 ? + &mCachedNodes[index] : NULL; } + const CachedFrame* hasFrame(const CachedNode* node) const; + int indexInParent() const { return mIndex; } + void init(const CachedRoot* root, int index, WebCore::Frame* frame); + const CachedFrame* lastChild() const { return &mCachedFrames.last(); } + CachedNode* lastNode() { return &mCachedNodes.last(); } + CachedFrame* lastChild() { return &mCachedFrames.last(); } + const CachedFrame* parent() const { return mParent; } + CachedFrame* parent() { return mParent; } + bool sameFrame(const CachedFrame* ) const; + void removeLast() { mCachedNodes.removeLast(); } + void resetClippedOut(); + void setContentsSize(int width, int height) { mContents.setWidth(width); + mContents.setHeight(height); } + void setData(); + bool setFocus(WebCore::Frame* , WebCore::Node* , int x, int y); + void setFocusIndex(int focusIndex) const { mFocus = focusIndex; } + void setLocalViewBounds(const WebCore::IntRect& bounds) { mLocalViewBounds = bounds; } + int size() { return mCachedNodes.size(); } + const CachedNode* validDocument() const; +protected: + struct BestData { + WebCore::IntRect mNodeBounds; + WebCore::IntRect mMouseBounds; + 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 mFocusChild; + 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* ); + static SkFixed Overlap(int span, int left, int right); + void reset() { mNode = NULL; } + int right() const { return bounds().right(); } + 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(); } + }; + typedef const CachedNode* (CachedFrame::*MoveInDirection)( + const CachedNode* test, const CachedNode* limit, BestData* bestData, + const CachedNode* focus) 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 + CachedNode* focus) const; + void findClosest(BestData* , Direction original, Direction test, + WebCore::IntRect* clip) const; + int frameNodeCommon(BestData& testData, const CachedNode* test, + BestData* bestData, BestData* originalData, + const CachedNode* focus) const; + int framePartCommon(BestData& testData, const CachedNode* test, + BestData* bestData, const CachedNode* focus) const; + const CachedNode* frameDown(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + const CachedNode* frameLeft(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + const CachedNode* frameRight(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + const CachedNode* frameUp(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + int minWorkingHorizontal() const; + int minWorkingVertical() const; + int maxWorkingHorizontal() const; + int maxWorkingVertical() const; + bool moveInFrame(MoveInDirection , const CachedNode* test, BestData* best, + const CachedNode* focus) const; + const WebCore::IntRect& _navBounds() const; + WebCore::IntRect mContents; + WebCore::IntRect mLocalViewBounds; + WebCore::IntRect mViewBounds; + WTF::Vector<CachedNode> mCachedNodes; + WTF::Vector<CachedFrame> mCachedFrames; + void* mFrame; // WebCore::Frame*, used only to compare pointers + CachedFrame* mParent; + int mIndex; // index within parent's array of children, or -1 if root + const CachedRoot* mRoot; + mutable int mFocus; +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/WebKit/android/nav/CachedHistory.cpp b/WebKit/android/nav/CachedHistory.cpp new file mode 100644 index 0000000..f75f237 --- /dev/null +++ b/WebKit/android/nav/CachedHistory.cpp @@ -0,0 +1,203 @@ +/* + * 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 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 "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; + mFocusIsInput = false; + mPriorIsInput = false; + mDidFirstLayout = false; + mPriorMove = mLastMove = CachedFrame::UNINITIALIZED; + mMinWorkingHorizontal = mMinWorkingVertical = INT_MIN; + mMaxWorkingHorizontal = mMaxWorkingVertical = INT_MAX; +} + +void CachedHistory::setWorking(CachedFrame::Direction newMove, + const CachedNode* focus, 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 (focus != NULL || mLastMove != CachedFrame::UNINITIALIZED) { + mPriorMove = mLastMove; + mLastMove = newMove; + } + const WebCore::IntRect* navBounds = &mNavBounds; + if (focus != NULL) { + WebCore::IntRect focusBounds; + focus->getBounds(&focusBounds); + if (focusBounds.isEmpty() == false) + mNavBounds = focusBounds; + mPriorIsInput = mFocusIsInput; + mFocusIsInput = focus->isInput(); // focus->localName() == "input"; + } + 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(); + } + } else if (mPriorIsInput) { + if (newAxis == CachedFrame::UP_DOWN) { + if (mPriorBounds.x() == mMinWorkingVertical && mPriorBounds.right() == mMaxWorkingVertical) { + mMinWorkingVertical = navBounds->x(); + mMaxWorkingVertical = navBounds->right(); + } + } else { + if (mPriorBounds.y() == mMinWorkingHorizontal && mPriorBounds.bottom() == mMaxWorkingHorizontal) { + mMinWorkingHorizontal = navBounds->y(); + mMaxWorkingHorizontal = navBounds->bottom(); + } + } + } + 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(mNavBounds); + DEBUG_PRINT_RECT(mPriorBounds); + DEBUG_PRINT_BOOL(mDirectionChange); + DEBUG_PRINT_BOOL(mFocusIsInput); + DEBUG_PRINT_BOOL(mPriorIsInput); + 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/WebKit/android/nav/CachedHistory.h b/WebKit/android/nav/CachedHistory.h new file mode 100644 index 0000000..e48d44b --- /dev/null +++ b/WebKit/android/nav/CachedHistory.h @@ -0,0 +1,90 @@ +/* + * 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 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 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 setNavBounds(const WebCore::IntRect& loc) { mNavBounds = loc; } + void setWorking(CachedFrame::Direction , const CachedNode* focus, + 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 focus ring is partially visible + WebCore::IntRect mNavBounds; // focus ring bounds plus optional keystroke movement + WebCore::IntRect mPriorBounds; // prior chosen focus ring (for reversing narrowing) + bool mDirectionChange; + bool mFocusIsInput; // defer max/min to non-focus node if focus is too broad + bool mPriorIsInput; // defer max/min to non-focus node if focus is too broad + 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/WebKit/android/nav/CachedNode.cpp b/WebKit/android/nav/CachedNode.cpp new file mode 100644 index 0000000..b786677 --- /dev/null +++ b/WebKit/android/nav/CachedNode.cpp @@ -0,0 +1,346 @@ +/* + * 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 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 "CachedPrefix.h" +#include "CachedFrame.h" +#include "CachedHistory.h" +#include "Node.h" +#include "PlatformString.h" + +#include "android_graphics.h" +#include "CachedNode.h" + +namespace android { + +void CachedNode::clearFocus(CachedFrame* parent) +{ + if (isFrame()) { + CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this)); + child->clearFocus(); + } + mIsFocus = 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, &mFocusRing); +} + +#define OVERLAP 3 + +void CachedNode::fixUpFocusRects() +{ + if (mFixedUpFocusRects) + return; + mFixedUpFocusRects = true; + if (mNavableRects <= 1) + return; +#if DEBUG_NAV_UI + { + WebCore::IntRect* boundsPtr = mFocusRing.begin() - 1; + const WebCore::IntRect* const boundsEnd = mFocusRing.begin() + mFocusRing.size(); + while (++boundsPtr < boundsEnd) + LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, boundsPtr - mFocusRing.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 = mFocusRing.size(); + WebCore::IntRect* unitBoundsPtr = mFocusRing.begin() - 1; + const WebCore::IntRect* const unitBoundsEnd = mFocusRing.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 = mFocusRing.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 = mFocusRing.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__, mFocusRing.size(), + candidate.x(), candidate.y(), candidate.width(), candidate.height()); +#endif + mFocusRing.append(candidate); + again = true; + goto tryAgain; + nextCheck: + continue; + } + } +tryAgain: + ; + } while (again); +} + + +void CachedNode::focusRingBounds(WebCore::IntRect* bounds) const +{ + int partMax = mNavableRects; + ASSERT(partMax > 0); + *bounds = mFocusRing[0]; + for (int partIndex = 1; partIndex < partMax; partIndex++) + bounds->unite(mFocusRing[partIndex]); + bounds->inflate(FOCUS_RING_HIT_TEST_RADIUS); +} + +void CachedNode::init(WebCore::Node* node) +{ + bzero(this, sizeof(CachedNode)); + mExport = WebCore::String(); + mName = WebCore::String(); + mNode = node; + mParentIndex = mChildFrameIndex = -1; + mType = android::NORMAL_CACHEDNODETYPE; +} + +void CachedNode::move(int x, int y) +{ + mBounds.move(x, y); + // mHitTestBounds will be moved by caller + WebCore::IntRect* first = mFocusRing.begin(); + WebCore::IntRect* last = first + mFocusRing.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 = mFocusRing[outerIndex]; + int innerIndex = 0; + do { + const WebCore::IntRect& innerBounds = other->mFocusRing[innerIndex]; + if (innerBounds.contains(outerBounds)) + return true; + } while (++innerIndex < innerMax); + } while (++outerIndex < outerMax); + return false; +} + +#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_FOCUS: return "CLOSER_IN_FOCUS"; break; + case CLOSER_OVERLAP: return "CLOSER_OVERLAP"; break; + case CLOSER_TOP: return "CLOSER_TOP"; break; + case FOCUSABLE: return "FOCUSABLE"; 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_FOCUS: return "IN_FOCUS"; break; + case IN_FOCUS_CHILDREN: return "IN_FOCUS_CHILDREN"; break; + case NOT_ENCLOSING_FOCUS: return "NOT_ENCLOSING_FOCUS"; break; + // case NOT_FOCUS_CHILD: return "NOT_FOCUS_CHILD"; break; + case NOT_FOCUS_NODE: return "NOT_FOCUS_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; + 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)) + scratch[index++] = *ch++; + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); + index = snprintf(scratch, sizeof(scratch), "// char* mName=\""); + ch = b->mName.characters(); + while (ch && *ch && index < sizeof(scratch)) + scratch[index++] = *ch++; + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); + DEBUG_PRINT_RECT(mBounds); + DEBUG_PRINT_RECT(mHitBounds); + const WTF::Vector<WebCore::IntRect>& rects = b->focusRings(); + size_t size = rects.size(); + DUMP_NAV_LOGD("// IntRect focusRings={ // size=%d\n", size); + for (size_t i = 0; i < size; i++) + DUMP_NAV_LOGD(" // {%d, %d, %d, %d}, // %d\n", rects[i].x(), rects[i].y(), + rects[i].width(), rects[i].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 mChildFrameIndex=%d;\n", b->mChildFrameIndex); + DUMP_NAV_LOGD("// int mIndex=%d;\n", b->mIndex); + DUMP_NAV_LOGD("// int mMaxLength=%d;\n", b->mMaxLength); + DUMP_NAV_LOGD("// int mNavableRects=%d;\n", b->mNavableRects); + DUMP_NAV_LOGD("// int mParentIndex=%d;\n", b->mParentIndex); + DUMP_NAV_LOGD("// int mTextSize=%d;\n", b->mTextSize); + DUMP_NAV_LOGD("// int mTabIndex=%d;\n", b->mTabIndex); + 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(mFixedUpFocusRects); + DEBUG_PRINT_BOOL(mHasFocusRing); + DEBUG_PRINT_BOOL(mHasMouseOver); + DEBUG_PRINT_BOOL(mIsAnchor); + DEBUG_PRINT_BOOL(mIsArea); + DEBUG_PRINT_BOOL(mIsFocus); + DEBUG_PRINT_BOOL(mIsInput); + DEBUG_PRINT_BOOL(mIsParentAnchor); + DEBUG_PRINT_BOOL(mIsPassword); + DEBUG_PRINT_BOOL(mIsRtlText); + DEBUG_PRINT_BOOL(mIsTextArea); + DEBUG_PRINT_BOOL(mIsTextField); + DEBUG_PRINT_BOOL(mIsTransparent); + DEBUG_PRINT_BOOL(mIsUnclipped); + DEBUG_PRINT_BOOL(mLast); + DEBUG_PRINT_BOOL(mWantsKeyEvents); + DUMP_NAV_LOGD("\n"); +} + +#endif + +} diff --git a/WebKit/android/nav/CachedNode.h b/WebKit/android/nav/CachedNode.h new file mode 100644 index 0000000..aa64982 --- /dev/null +++ b/WebKit/android/nav/CachedNode.h @@ -0,0 +1,235 @@ +/* + * 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 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 CachedNode_H +#define CachedNode_H + +#include "AtomicString.h" +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" +#include "wtf/Vector.h" + +namespace WebCore { + class Node; +} + +namespace android { + +class CachedFrame; + +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_FOCUS, + CLOSER_OVERLAP, + CLOSER_TOP, + FOCUSABLE, + FURTHER, + IN_UMBRA, + IN_WORKING, + LEFTMOST, + 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_FOCUS, + IN_FOCUS_CHILDREN, + NOT_ENCLOSING_FOCUS, + // NOT_FOCUS_CHILD, + NOT_FOCUS_NODE, + OUTSIDE_OF_BEST, // containership + OUTSIDE_OF_ORIGINAL, // containership + 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 + } + + const WebCore::IntRect& bounds() const { return mBounds; } + WebCore::IntRect* boundsPtr() { return &mBounds; } + int childFrameIndex() const { return mChildFrameIndex; } + void clearCondition() const { mCondition = NOT_REJECTED; } + void clearFocus(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; } + bool disabled() const { return mDisabled; } + const CachedNode* document() const { return &this[-mIndex]; } + void fixUpFocusRects(); + void focusRingBounds(WebCore::IntRect* ) const; + WTF::Vector<WebCore::IntRect>& focusRings() { return mFocusRing; } + const WTF::Vector<WebCore::IntRect>& focusRings() const { return mFocusRing; } + const WebCore::IntRect& getBounds() const { return mBounds; } + void getBounds(WebCore::IntRect* bounds) const { *bounds = mBounds; } + const WebCore::String& getExport() const { return mExport; } + bool hasFocusRing() const { return mHasFocusRing; } + bool hasMouseOver() const { return mHasMouseOver; } + const WebCore::IntRect& hitBounds() const { return mHitBounds; } + int index() const { return mIndex; } + void init(WebCore::Node* node); + bool isAnchor() const { return mIsAnchor; } + bool isArea() const { return mIsArea; } + bool isFocus() const { return mIsFocus; } + bool isFocusable(const WebCore::IntRect& clip) const { + return clip.intersects(mBounds); + } + bool isFrame() const { return mChildFrameIndex >= 0 ; } + bool isInput() const { return mIsInput; } + bool isPassword() const { return mIsPassword; } + bool isRtlText() const { return mIsRtlText; } + bool isTextArea() const { return mIsTextArea; } + bool isTextField() const { return mIsTextField; } + bool isTransparent() const { return mIsTransparent; } + bool isUnclipped() const { return mIsUnclipped; } + bool isWantsKeyEvents() const { return mWantsKeyEvents; } + + int maxLength() const { return mMaxLength; }; + void move(int x, int y); + const WebCore::String& name() const { return mName; } + int navableRects() const { return mNavableRects; } + void* nodePointer() const { return mNode; } + bool noSecondChance() const { return mCondition > SECOND_CHANCE_END; } + const CachedNode* parent() const { return document() + mParentIndex; } + void* parentGroup() const { return mParentGroup; } + int parentIndex() const { return mParentIndex; } + bool partRectsContains(const CachedNode* other) const; + void reset(); + void setBounds(const WebCore::IntRect& bounds) { mBounds = bounds; } + void setChildFrameIndex(int index) { mChildFrameIndex = index; } + void setClippedOut(bool clipped) { mClippedOut = clipped; } + void setCondition(Condition condition) const { mCondition = condition; } + void setDisabled(bool disabled) { mDisabled = disabled; } + void setExport(const WebCore::String& exported) { mExport = exported; } + void setHasFocusRing(bool hasFocusRing) { mHasFocusRing = hasFocusRing; } + void setHasMouseOver(bool hasMouseOver) { mHasMouseOver = hasMouseOver; } + void setHitBounds(const WebCore::IntRect& bounds) { mHitBounds = bounds; } + void setIndex(int index) { mIndex = index; } + void setIsAnchor(bool isAnchor) { mIsAnchor = isAnchor; } + void setIsArea(bool isArea) { mIsArea = isArea; } + void setIsFocus(bool isFocus) { mIsFocus = isFocus; } + void setIsInput(bool isInput) { mIsInput = isInput; } + void setIsParentAnchor(bool isAnchor) { mIsParentAnchor = isAnchor; } + void setIsPassword(bool isPassword) { mIsPassword = isPassword; } + void setIsRtlText(bool isRtlText) { mIsRtlText = isRtlText; } + void setIsTextArea(bool isTextArea) { mIsTextArea = isTextArea; } + void setIsTextField(bool isTextField) { mIsTextField = isTextField; } + void setIsTransparent(bool isTransparent) { mIsTransparent = isTransparent; } + void setIsUnclipped(bool unclipped) { mIsUnclipped = unclipped; } + void setLast() { mLast = true; } + void setMaxLength(int maxLength) { mMaxLength = maxLength; } + void setName(const WebCore::String& name) { mName = name; } + void setNavableRects() { mNavableRects = mFocusRing.size(); } + void setParentGroup(void* group) { mParentGroup = group; } + void setParentIndex(int parent) { mParentIndex = parent; } + void setTabIndex(int index) { mTabIndex = index; } + void setTextSize(int textSize) { mTextSize = textSize; } + void setType(CachedNodeType type) { mType = type; } + void setWantsKeyEvents(bool wantsKeys) { mWantsKeyEvents = wantsKeys; } + int tabIndex() const { return mTabIndex; } + const CachedNode* traverseNextNode() const { return mLast ? NULL : &this[1]; } + int textSize() const { return mTextSize; } + CachedNodeType type() const { return mType; } +private: + WebCore::String mExport; + WebCore::String mName; + WebCore::IntRect mBounds; + WebCore::IntRect mHitBounds; + WTF::Vector<WebCore::IntRect> mFocusRing; + void* mNode; // WebCore::Node*, only used to match pointers + void* mParentGroup; // WebCore::Node*, only used to match pointers + int mChildFrameIndex; // set to -1 if node is not a frame + int mIndex; // index of itself, to find first in array (document) + int mMaxLength; + int mNavableRects; // FIXME: could be bitfield once I limit max number of rects + int mParentIndex; + int mTextSize; + int mTabIndex; + mutable Condition mCondition : 5; // why the node was not chosen on the first pass + CachedNodeType mType : 3; + bool mClippedOut : 1; + bool mDisabled : 1; + bool mFixedUpFocusRects : 1; + bool mHasFocusRing : 1; + bool mHasMouseOver : 1; + bool mIsAnchor : 1; + bool mIsArea : 1; + bool mIsFocus : 1; + bool mIsInput : 1; + bool mIsParentAnchor : 1; + bool mIsPassword : 1; + bool mIsRtlText : 1; + bool mIsTextArea : 1; + bool mIsTextField : 1; + bool mIsTransparent : 1; + bool mIsUnclipped : 1; + bool mLast : 1; // true if this is the last node in a group + bool mWantsKeyEvents : 1; // true for nodes like plugins +#ifdef BROWSER_DEBUG +public: + WebCore::Node* webCoreNode() const { return (WebCore::Node*) mNode; } + bool mDisplayMeasure; + mutable bool mInCompare; + // mutable int mCondition; + 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/WebKit/android/nav/CachedNodeType.h b/WebKit/android/nav/CachedNodeType.h new file mode 100644 index 0000000..07346ac --- /dev/null +++ b/WebKit/android/nav/CachedNodeType.h @@ -0,0 +1,41 @@ +/* + * 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 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 CachedNodeType_H +#define CachedNodeType_H + +namespace android { + +enum CachedNodeType { + NORMAL_CACHEDNODETYPE = 0, + ADDRESS_CACHEDNODETYPE = 1, + EMAIL_CACHEDNODETYPE = 2, + PHONE_CACHEDNODETYPE = 4, + ALL_CACHEDNODETYPES = 7 +}; + +} + +#endif diff --git a/WebKit/android/nav/CachedPrefix.h b/WebKit/android/nav/CachedPrefix.h new file mode 100644 index 0000000..b466771 --- /dev/null +++ b/WebKit/android/nav/CachedPrefix.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 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 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 + +#endif diff --git a/WebKit/android/nav/CachedRoot.cpp b/WebKit/android/nav/CachedRoot.cpp new file mode 100644 index 0000000..4a50c80 --- /dev/null +++ b/WebKit/android/nav/CachedRoot.cpp @@ -0,0 +1,1087 @@ +/* + * 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 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 "CachedPrefix.h" +#include "CachedHistory.h" +#include "CachedNode.h" +#include "SkBitmap.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPixelRef.h" +#include "SkRegion.h" + +#include "CachedRoot.h" + +#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 + }; + + 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 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 && !defined BROWSER_DEBUG + 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" + }; +#endif + +#define kMargin 16 +#define kSlop 2 + +class BoundsCheck : public CommonCheck { +public: + BoundsCheck() { + mAllDrawnIn.setEmpty(); + mLastAll.setEmpty(); + mLastOver.setEmpty(); + } + + static int Area(SkIRect test) { + return test.width() * test.height(); + } + + void checkLast() { + if (mAllDrawnIn.isEmpty()) + return; + if (mLastAll.isEmpty() || Area(mLastAll) < Area(mAllDrawnIn)) { + mLastAll = mAllDrawnIn; + mDrawnOver.setEmpty(); + } + mAllDrawnIn.setEmpty(); + } + + bool hidden() { + return (mLastAll.isEmpty() && mLastOver.isEmpty()) || + mDrawnOver.contains(mBounds); + } + + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) + return false; + bool interestingType = mType == kDrawBitmap_Type || + mType == kDrawRect_Type || isTextType(mType); + if (SkIRect::Intersects(mBounds, rect) == false) { +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s (no intersect) rect={%d,%d,%d,%d} mType=%s\n", __FUNCTION__, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + TypeNames[mType]); +#endif + if (interestingType) + checkLast(); + return false; + } + if (interestingType == false) + return false; + if (mBoundsSlop.contains(rect) || + (mBounds.fLeft == rect.fLeft && mBounds.fRight == rect.fRight && + mBounds.fTop >= rect.fTop && mBounds.fBottom <= rect.fBottom) || + (mBounds.fTop == rect.fTop && mBounds.fBottom == rect.fBottom && + mBounds.fLeft >= rect.fLeft && mBounds.fRight <= rect.fRight)) { + mDrawnOver.setEmpty(); + mAllDrawnIn.join(rect); +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s (contains) rect={%d,%d,%d,%d}" + " mAllDrawnIn={%d,%d,%d,%d} mType=%s\n", __FUNCTION__, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + mAllDrawnIn.fLeft, mAllDrawnIn.fTop, mAllDrawnIn.fRight, mAllDrawnIn.fBottom, + TypeNames[mType]); +#endif + } else { + checkLast(); + if (!isTextType(mType)) { + if ( +#if 0 +// should the opaqueness of the bitmap disallow its ability to draw over? +// not sure that this test is needed + (mType != kDrawBitmap_Type || + (mIsOpaque && mAllOpaque)) && +#endif + mLastAll.isEmpty() == false) + mDrawnOver.op(rect, SkRegion::kUnion_Op); + } else { +// FIXME +// sometimes the text is not drawn entirely inside the focus area, even though +// it is the correct text. Until I figure out why, I allow text drawn at the +// end that is not covered up by something else to represent the focusable link +// example that triggers this that should be figured out: +// http://cdn.labpixies.com/campaigns/blackjack/blackjack.html?lang=en&country=US&libs=assets/feature/core +// ( http://tinyurl.com/ywsyzb ) + mLastOver = rect; + } +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const SkIRect& drawnOver = mDrawnOver.getBounds(); + LOGD("%s (overlaps) rect={%d,%d,%d,%d}" + " mDrawnOver={%d,%d,%d,%d} mType=%s mIsOpaque=%s mAllOpaque=%s\n", __FUNCTION__, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + drawnOver.fLeft, drawnOver.fTop, drawnOver.fRight, drawnOver.fBottom, + TypeNames[mType], mIsOpaque ? "true" : "false", mAllOpaque ? "true" : "false"); +#endif + } + return false; + } + + SkIRect mBounds; + SkIRect mBoundsSlop; + SkRegion mDrawnOver; + SkIRect mLastOver; + SkIRect mAllDrawnIn; + SkIRect mLastAll; +}; + +class BoundsCanvas : public SkCanvas { +public: + + BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) { + mTransparentLayer = 0; + setBounder(bounder); + } + + virtual ~BoundsCanvas() { + setBounder(NULL); + } + + virtual void drawPaint(const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPaint_Type); + SkCanvas::drawPaint(paint); + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPoints_Type); + SkCanvas::drawPoints(mode, count, pts, paint); + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawRect_Type); + SkCanvas::drawRect(rect, paint); + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPath_Type); + SkCanvas::drawPath(path, paint); + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawBitmap_Type); + mBounder.setIsOpaque(bitmap.isOpaque()); + SkCanvas::commonDrawBitmap(bitmap, matrix, paint); + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint = NULL) { + mBounder.setType(CommonCheck::kDrawSprite_Type); + mBounder.setIsOpaque(bitmap.isOpaque()); + SkCanvas::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); + SkCanvas::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); + SkCanvas::drawPosText(text, byteLength, pos, paint); + 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); + SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint); + if (mBounder.mUnion.isEmpty()) + 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); + SkCanvas::drawTextOnPath(text, byteLength, path, matrix, paint); + mBounder.doRect(CommonCheck::kDrawTextOnPath_Type); + } + + virtual void drawPicture(SkPicture& picture) { + mBounder.setType(CommonCheck::kDrawPicture_Type); + SkCanvas::drawPicture(picture); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) { + int depth = SkCanvas::saveLayer(bounds, paint, flags); + if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) { + mTransparentLayer = depth; + mBounder.setAllOpaque(false); + } + return depth; + } + + virtual void restore() { + int depth = getSaveCount(); + if (depth == mTransparentLayer) { + mTransparentLayer = 0; + mBounder.setAllOpaque(true); + } + SkCanvas::restore(); + } + + int mTransparentLayer; + CommonCheck& mBounder; +}; + +/* +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 SkCanvas { +public: + ImageCanvas(SkBounder* bounder) : mURI(NULL) { + setBounder(bounder); + } + +// 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 SkMatrix& , const SkPaint& ) { + SkPixelRef* pixelRef = bitmap.pixelRef(); + if (pixelRef != NULL) { + mURI = pixelRef->getURI(); + } + } + + 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; +}; + +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; + } + newNode->focusRingBounds(&newOutset); + } + 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; +} + + +int CachedRoot::checkForCenter(int x, int y) const +{ + int width = mViewBounds.width(); + 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(*mPicture); + 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); + checker.translate(SkIntToScalar(-mViewBounds.x() - + (xDelta < 0 ? xDelta : 0)), SkIntToScalar(-mViewBounds.y())); + checker.drawPicture(*mPicture); + *xDeltaPtr = jiggleCheck.jiggle(); +} + +const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* x, int* y) const +{ + int best = INT_MAX; + (const_cast<CachedRoot*>(this))->resetClippedOut(); + const CachedNode* directHit = NULL; + const CachedNode* node = findBestAt(rect, &best, &directHit, framePtr, x, y); + DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), + node == NULL ? NULL : node->nodePointer()); + if (node == NULL) { + node = findBestHitAt(rect, &best, 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::focusLocation() const +{ + const WebCore::IntRect& bounds = mHistory->mNavBounds; + return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1), + bounds.y() + (bounds.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; +} + +void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds; + point->setX(mouseBounds.x() + (mouseBounds.width() >> 1)); + point->setY(mouseBounds.y() + (mouseBounds.height() >> 1)); +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + LOGD("%s mHistory->mNavBounds={%d,%d,%d,%d} " + "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}\n", __FUNCTION__, + 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(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.bottom() > viewBottom && viewBottom < mContents.height()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navTop = mHistory->mNavBounds.y(); + int scrollBottom; + if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) { + mScrolledBounds.setHeight(scrollBottom - navTop); + mScrolledBounds.setY(navTop); + } + } + frameDown(test, NULL, bestData, currentFocus()); + 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(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.x() < viewLeft && viewLeft > mContents.x()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navRight = mHistory->mNavBounds.right(); + int scrollLeft; + if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x())) + mScrolledBounds.setWidth(navRight - scrollLeft); + } + frameLeft(test, NULL, bestData, currentFocus()); + return true; +} + + +void CachedRoot::innerMove(const CachedNode* node, BestData* bestData, + Direction direction, WebCore::IntPoint* scroll, bool firstCall) +{ + bestData->reset(); + mFocusChild = false; + bool outOfFocus = mFocus < 0; + bool firstTime = mHistory->didFirstLayout() && outOfFocus; +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s mHistory->didFirstLayout()=%s && mFocus=%d\n", __FUNCTION__, + mHistory->didFirstLayout() ? "true" : "false", mFocus); +#endif + if (firstTime) + mHistory->reset(); + const CachedNode* focus = currentFocus(); + mHistory->setWorking(direction, focus, mViewBounds); + mFocusBounds = WebCore::IntRect(0, 0, 0, 0); + if (focus != NULL) + focus->getBounds(&mFocusBounds); + bool findClosest = false; + if (mScrollOnly == false) { + switch (direction) { + case LEFT: + if (outOfFocus) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(), + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerLeft(node, bestData); + break; + case RIGHT: + if (outOfFocus) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1, + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerRight(node, bestData); + break; + case UP: + if (outOfFocus) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), + mViewBounds.bottom(), mViewBounds.width(), 1); + findClosest = innerUp(node, bestData); + break; + case DOWN: + if (outOfFocus) + 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->mMouseBounds = bestData->mNodeBounds; + if (adjustForScroll(bestData, direction, scroll, findClosest)) + return; + if (bestData->mNode != NULL) { + mHistory->addToVisited(bestData->mNode, direction); + mHistory->mNavBounds = mFocusBounds = bestData->mNodeBounds; + mHistory->mMouseBounds = bestData->mMouseBounds; + } 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(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.right() > viewRight && viewRight < mContents.width()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navLeft = mHistory->mNavBounds.x(); + int scrollRight; + if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) { + mScrolledBounds.setWidth(scrollRight - navLeft); + mScrolledBounds.setX(navLeft); + } + } + frameRight(test, NULL, bestData, currentFocus()); + 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(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.y() < viewTop && viewTop > mContents.y()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navBottom = mHistory->mNavBounds.bottom(); + int scrollTop; + if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y())) + mScrolledBounds.setHeight(navBottom - scrollTop); + } + frameUp(test, NULL, bestData, currentFocus()); + return true; +} + +WebCore::String CachedRoot::imageURI(int x, int y) const +{ + ImageCheck imageCheck; + ImageCanvas checker(&imageCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(-x), SkIntToScalar(-y)); + checker.drawPicture(*mPicture); + return WebCore::String(checker.mURI); +} + +bool CachedRoot::maskIfHidden(BestData* best) const +{ + if (mPicture == NULL) { +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s missing picture\n", __FUNCTION__); +#endif + return false; + } + const CachedNode* bestNode = best->mNode; + if (bestNode->isUnclipped()) + return false; + // given the picture matching this nav cache + // create an SkBitmap with dimensions of the focus intersected w/ extended view + const WebCore::IntRect& nodeBounds = bestNode->getBounds(); + WebCore::IntRect bounds = nodeBounds; + bounds.intersect(mScrolledBounds); + int leftMargin = bounds.x() == nodeBounds.x() ? kMargin : 0; + int topMargin = bounds.y() == nodeBounds.y() ? kMargin : 0; + int rightMargin = bounds.right() == nodeBounds.right() ? kMargin : 0; + int bottomMargin = bounds.bottom() == nodeBounds.bottom() ? kMargin : 0; + bool unclipped = (leftMargin & topMargin & rightMargin & bottomMargin) != 0; + WebCore::IntRect marginBounds = nodeBounds; + marginBounds.inflate(kMargin); + marginBounds.intersect(mScrolledBounds); + BoundsCheck boundsCheck; + BoundsCanvas checker(&boundsCheck); + boundsCheck.mBounds.set(leftMargin, topMargin, + leftMargin + bounds.width(), topMargin + bounds.height()); + boundsCheck.mBoundsSlop = boundsCheck.mBounds; + boundsCheck.mBoundsSlop.inset(-kSlop, -kSlop); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, marginBounds.width(), + marginBounds.height()); + checker.setBitmapDevice(bitmap); + // insert probes to be called when the data corresponding to this focus ring is drawn + // need to know if focus ring was generated by text, image, or parent (like div) + // ? need to know (like imdb menu bar) to give up sometimes (when?) + checker.translate(SkIntToScalar(leftMargin - bounds.x()), + SkIntToScalar(topMargin - bounds.y())); + checker.drawPicture(*mPicture); + boundsCheck.checkLast(); + // was it not drawn or clipped out? + if (boundsCheck.hidden()) { // if hidden, return false so that nav can try again + CachedNode* node = const_cast<CachedNode*>(best->mNode); +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const SkIRect& m = boundsCheck.mBounds; + const SkIRect& s = boundsCheck.mBoundsSlop; + LOGD("%s hidden node:%p (%d) mBounds={%d,%d,%d,%d} mBoundsSlop=" + "{%d,%d,%d,%d}\n", __FUNCTION__, node, node->index(), + m.fLeft, m.fTop, m.fRight, m.fBottom, + s.fLeft, s.fTop, s.fRight, s.fBottom); + const SkIRect& o = boundsCheck.mDrawnOver.getBounds(); + const SkIRect& l = boundsCheck.mLastAll; + const SkIRect& u = boundsCheck.mUnion; + LOGD("%s hidden mDrawnOver={%d,%d,%d,%d} mLastAll={%d,%d,%d,%d}" + " mUnion={%d,%d,%d,%d}\n", __FUNCTION__, + o.fLeft, o.fTop, o.fRight, o.fBottom, + l.fLeft, l.fTop, l.fRight, l.fBottom, + u.fLeft, u.fTop, u.fRight, u.fBottom); + const SkIRect& a = boundsCheck.mAllDrawnIn; + const WebCore::IntRect& c = mScrolledBounds; + const WebCore::IntRect& b = nodeBounds; + LOGD("%s hidden mAllDrawnIn={%d,%d,%d,%d} mScrolledBounds={%d,%d,%d,%d}" + " nodeBounds={%d,%d,%d,%d}\n", __FUNCTION__, + a.fLeft, a.fTop, a.fRight, a.fBottom, + c.x(), c.y(), c.right(), c.bottom(), + b.x(), b.y(), b.right(), b.bottom()); + LOGD("%s bits.mWidth=%d bits.mHeight=%d transX=%d transY=%d\n", __FUNCTION__, + marginBounds.width(),marginBounds.height(), + kMargin - bounds.x(), kMargin - bounds.y()); +#endif + node->setDisabled(true); + node->setClippedOut(unclipped == false); + 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 + const SkIRect& over = boundsCheck.mDrawnOver.getBounds(); + if (over.isEmpty() == false) { +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + SkIRect orig = boundsCheck.mBounds; +#endif + SkIRect& base = boundsCheck.mBounds; + if (base.fLeft < over.fRight && base.fRight > over.fRight) + base.fLeft = over.fRight; + else if (base.fRight > over.fLeft && base.fLeft < over.fLeft) + base.fRight = over.fLeft; + if (base.fTop < over.fBottom && base.fBottom > over.fBottom) + base.fTop = over.fBottom; + else if (base.fBottom > over.fTop && base.fTop < over.fTop) + base.fBottom = over.fTop; +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const SkIRect& modded = boundsCheck.mBounds; + LOGD("%s partially occluded node:%p (%d) old:{%d,%d,%d,%d} new:{%d,%d,%d,%d}\n", + __FUNCTION__, best->mNode, best->mNode->index(), + orig.fLeft, orig.fTop, orig.fRight, orig.fBottom, + base.fLeft, base.fTop, base.fRight, base.fBottom); +#endif + best->mMouseBounds = WebCore::IntRect(bounds.x() + base.fLeft - kMargin, + bounds.y() + base.fTop - kMargin, base.width(), base.height()); + } + return false; +} + +const CachedNode* CachedRoot::moveFocus(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); + *framePtr = bestData.mFrame; + return const_cast<CachedNode*>(bestData.mNode); +} + +void CachedRoot::reset() +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0); + mMaxXScroll = mMaxYScroll = 0; + mSelectionStart = mSelectionEnd = -1; + mScrollOnly = false; + mFocusBounds = WebCore::IntRect(0, 0, 0, 0); +} + +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) +{ +#if !defined NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const CachedNode* focus = currentFocus(); + WebCore::IntRect bounds; + if (focus) + bounds = focus->bounds(); + LOGD("%s old focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}\n", __FUNCTION__, + focus ? focus->index() : 0, + focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif + clearFocus(); + if (node == NULL) + return; + node->setIsFocus(true); + ASSERT(node->isFrame() == false); + frame->setFocusIndex(node - frame->document()); + ASSERT(frame->focusIndex() > 0 && frame->focusIndex() < (int) frame->size()); + CachedFrame* parent; + while ((parent = frame->parent()) != NULL) { + parent->setFocusIndex(frame->indexInParent()); + frame = parent; + } +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + focus = currentFocus(); + bounds = WebCore::IntRect(0, 0, 0, 0); + if (focus) + bounds = focus->bounds(); + LOGD("%s new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}\n", __FUNCTION__, + focus ? focus->index() : 0, + focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif +} + +void CachedRoot::setupScrolledBounds() const +{ + mScrolledBounds = mViewBounds; +} + +#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); + DEBUG_PRINT_RECT(mFocusBounds); + DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n", + b->mMaxXScroll, b->mMaxYScroll); + DEBUG_PRINT_BOOL(mFocusChild); +#ifdef DUMP_NAV_CACHE_USING_PRINTF + if (gNavCacheLogFile) + fclose(gNavCacheLogFile); + gNavCacheLogFile = NULL; + gWriteLogMutex.unlock(); +#endif +} + +#endif + +} diff --git a/WebKit/android/nav/CachedRoot.h b/WebKit/android/nav/CachedRoot.h new file mode 100644 index 0000000..ab1b823 --- /dev/null +++ b/WebKit/android/nav/CachedRoot.h @@ -0,0 +1,115 @@ +/* + * 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 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 CachedRoot_H +#define CachedRoot_H + +#include "CachedFrame.h" +#include "IntPoint.h" +#include "SkPicture.h" + +class SkRect; + +namespace android { + +class CachedHistory; +class CachedNode; + +class CachedRoot : public CachedFrame { +public: + bool adjustForScroll(BestData* , Direction , WebCore::IntPoint* scrollPtr, + bool findClosest); + int checkForCenter(int x, int y) const; + void checkForJiggle(int* ) const; + int documentHeight() { return mContents.height(); } + int documentWidth() { return mContents.width(); } + const CachedNode* findAt(const WebCore::IntRect& , const CachedFrame** , + int* x, int* y) const; + const WebCore::IntRect& focusBounds() const { return mFocusBounds; } + bool focusChild() const { return mFocusChild; } + WebCore::IntPoint focusLocation() const; + int generation() const { return mGeneration; } + SkPicture* getPicture() { return mPicture; } + int getAndResetSelectionEnd(); + int getAndResetSelectionStart(); +// const WebCore::IntRect& navClipBounds() const { return mClippedBounds; } + void getSimulatedMousePosition(WebCore::IntPoint* ); +// bool hasNavClipBounds() { return mClippedBounds.isEmpty() == false; } + 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; + WebCore::String imageURI(int x, int y) const; + bool maskIfHidden(BestData* ) const; + const CachedNode* moveFocus(Direction , const CachedFrame** , WebCore::IntPoint* scroll); + void reset(); +// void resetNavClipBounds() { mClippedBounds = WebCore::IntRect(-1, -1, 0, 0); } + CachedHistory* rootHistory() const { return mHistory; } + bool scrollDelta(WebCore::IntRect& focusRingBounds, Direction , int* delta); + const WebCore::IntRect& scrolledBounds() const { return mScrolledBounds; } + void setCachedFocus(CachedFrame* , CachedNode* ); + void setFocusBounds(const WebCore::IntRect& r) { mFocusBounds = r; } + void setGeneration(int generation) { mGeneration = generation; } + void setTextGeneration(int textGeneration) { mTextGeneration = textGeneration; } + void setFocusChild(bool state) const { mFocusChild = state; } + void setMaxScroll(int x, int y) { mMaxXScroll = x; mMaxYScroll = y; } +// void setNavClipBounds(const WebCore::IntRect& r) { mClippedBounds = r; } + void setPicture(SkPicture* picture) { mPicture = picture; } + void setScrollOnly(bool state) { mScrollOnly = state; } + void setSelection(int start, int end) { mSelectionStart = start; mSelectionEnd = end; } + void setupScrolledBounds() const; + void setVisibleRect(const WebCore::IntRect& r) { mViewBounds = r; } + int textGeneration() const { return mTextGeneration; } + int width() const { return mPicture ? mPicture->width() : 0; } +private: + CachedHistory* mHistory; + SkPicture* mPicture; + WebCore::IntRect mFocusBounds; // chosen focus ring + mutable WebCore::IntRect mScrolledBounds; // view bounds + amount visible as result of scroll + int mGeneration; + 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; + mutable bool mFocusChild; // temporary state set if walked nodes are children of focus + bool mScrollOnly; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedRoot* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/WebKit/android/nav/FindCanvas.cpp b/WebKit/android/nav/FindCanvas.cpp new file mode 100644 index 0000000..24b0129 --- /dev/null +++ b/WebKit/android/nav/FindCanvas.cpp @@ -0,0 +1,467 @@ +/* + * 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 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 "FindCanvas.h" + +#include "SkRect.h" + +// MatchInfo methods +//////////////////////////////////////////////////////////////////////////////// + +MatchInfo::MatchInfo() { + m_picture = 0; +} + +MatchInfo::~MatchInfo() { + m_picture->safeUnref(); +} + +MatchInfo::MatchInfo(const MatchInfo& src) { + m_location = src.m_location; + m_picture = src.m_picture; + m_picture->safeRef(); +} + +void MatchInfo::set(const SkRegion& region, SkPicture* pic) { + m_picture->safeUnref(); + 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& 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) { + + setBounder(&mBounder); + mOutset = -SkIntToScalar(2); + mMatches = new WTF::Vector<MatchInfo>(); + mWorkingIndex = 0; + mWorkingCanvas = 0; + mWorkingPicture = 0; +} + +FindCanvas::~FindCanvas() { + setBounder(NULL); + /* Just in case getAndClear was not called. */ + delete mMatches; + mWorkingPicture->safeUnref(); +} + +// 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::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); + rect.inset(mOutset, mOutset); + // We need an SkIRect for SkRegion operations. + SkIRect iRect; + rect.roundOut(&iRect); + // If the rectangle is partially clipped, assume that the text is + // not visible, so skip this match. + if (getTotalClip().contains(iRect)) { + 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 + } else { + // This match was clipped out, so begin looking at the next + // character from our hidden match + index = matchIndex; + } + // 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); + // Only save a partial if it is in the current clip. + if (getTotalClip().contains(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); + mMatches->last().set(region, mWorkingPicture); +} + +void FindCanvas::resetWorkingCanvas() { + mWorkingPicture->unref(); + mWorkingPicture = 0; + // Do not need to reset mWorkingCanvas itself because we only access it via + // getWorkingCanvas. +} diff --git a/WebKit/android/nav/FindCanvas.h b/WebKit/android/nav/FindCanvas.h new file mode 100644 index 0000000..5d79b4c --- /dev/null +++ b/WebKit/android/nav/FindCanvas.h @@ -0,0 +1,207 @@ +/* + * 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 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 Find_Canvas_h +#define Find_Canvas_h + +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkRegion.h" +#include "SkTDArray.h" +#include "icu/unicode/umachine.h" +#include "wtf/Vector.h" + +class SkRect; +class SkTypeface; + +// 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. + void set(const SkRegion& region, SkPicture* pic); +private: + MatchInfo& operator=(MatchInfo& src); + SkRegion m_location; + SkPicture* m_picture; +}; + +// 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) { + } + + int found() const { return mNumFound; } + + // 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; + size_t mLength; + FindBounder mBounder; + int mNumFound; + SkScalar mOutset; + SkTDArray<GlyphSet> mGlyphSets; + + SkPicture* mWorkingPicture; + SkCanvas* mWorkingCanvas; + SkRegion mWorkingRegion; + int mWorkingIndex; +}; + +#endif // Find_Canvas_h + diff --git a/WebKit/android/nav/SelectText.cpp b/WebKit/android/nav/SelectText.cpp new file mode 100644 index 0000000..7bdbf14 --- /dev/null +++ b/WebKit/android/nav/SelectText.cpp @@ -0,0 +1,263 @@ +/* + * 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 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 "webcoreglue" + +#include "CachedPrefix.h" +#include "SelectText.h" +#include "SkBitmap.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkMatrix.h" +#include "SkPicture.h" +#include "SkPoint.h" +#include "SkRect.h" +#include "SkRegion.h" + +class CommonCheck : public SkBounder { +public: + CommonCheck() : mMatrix(NULL), mPaint(NULL) {} + + virtual void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y) { + mMatrix = &matrix; + mPaint = &paint; + mY = y; + mBase = mBottom = mTop = INT_MAX; + } + + int base() { + if (mBase == INT_MAX) { + SkPoint result; + mMatrix->mapXY(0, mY, &result); + mBase = SkScalarFloor(result.fY); + } + return mBase; + } + + 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; + } + + 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; + } + +protected: + const SkMatrix* mMatrix; + const SkPaint* mPaint; + int mBase; + int mBottom; + int mTop; + SkScalar mY; +}; + +class FirstCheck : public CommonCheck { +public: + FirstCheck(int x, int y) + : mDistance(INT_MAX), mFocusX(x), mFocusY(y) { + mBestBounds.setEmpty(); + } + + const SkIRect& bestBounds() { + DBG_NAV_LOGD("mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d", + mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, + mBestBounds.fBottom, mTop, mBottom); + return mBestBounds; + } + + void offsetBounds(int dx, int dy) { + mBestBounds.offset(dx, dy); + } + + virtual bool onIRect(const SkIRect& rect) { + int dx = ((rect.fLeft + rect.fRight) >> 1) - mFocusX; + int dy = ((top() + bottom()) >> 1) - mFocusY; + int distance = dx * dx + dy * dy; +#ifdef EXTRA_NOISY_LOGGING + if (distance < 500 || abs(distance - mDistance) < 500) + DBG_NAV_LOGD("distance=%d mDistance=%d", distance, mDistance); +#endif + if (mDistance > distance) { + mDistance = distance; + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("mBestBounds={%d,%d,r=%d,b=%d}", + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom); +#endif + } + return false; + } +protected: + SkIRect mBestBounds; + int mDistance; + int mFocusX; + int mFocusY; +}; + +class MultilineBuilder : public CommonCheck { +public: + MultilineBuilder(const SkIRect& start, const SkIRect& end, int dx, int dy, + SkRegion* region) + : mStart(start), mEnd(end), mSelectRegion(region), mCapture(false) { + mLast.setEmpty(); + mLastBase = INT_MAX; + mStart.offset(-dx, -dy); + mEnd.offset(-dx, -dy); + } + + virtual bool onIRect(const SkIRect& rect) { + bool captureLast = false; + if ((rect.fLeft == mStart.fLeft && rect.fRight == mStart.fRight && + top() == mStart.fTop && bottom() == mStart.fBottom) || + (rect.fLeft == mEnd.fLeft && rect.fRight == mEnd.fRight && + top() == mEnd.fTop && bottom() == mEnd.fBottom)) { + captureLast = mCapture; + mCapture ^= true; + } + if (mCapture || captureLast) { + SkIRect full; + full.set(rect.fLeft, top(), rect.fRight, bottom()); + if ((mLast.fTop < base() && mLast.fBottom >= base()) + || (mLastBase <= full.fBottom && mLastBase > full.fTop)) { + if (full.fLeft > mLast.fRight) + full.fLeft = mLast.fRight; + else if (full.fRight < mLast.fLeft) + full.fRight = mLast.fLeft; + } + mSelectRegion->op(full, SkRegion::kUnion_Op); + mLast = full; + mLastBase = base(); + if (mStart == mEnd) + mCapture = false; + } + return false; + } +protected: + SkIRect mStart; + SkIRect mEnd; + SkIRect mLast; + int mLastBase; + SkRegion* mSelectRegion; + bool mCapture; +}; + +class TextCanvas : public SkCanvas { +public: + + TextCanvas(CommonCheck* bounder, const SkPicture& picture, const SkIRect& area) + : mBounder(*bounder) { + setBounder(bounder); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, area.width(), + area.height()); + setBitmapDevice(bitmap); + translate(SkIntToScalar(-area.fLeft), SkIntToScalar(-area.fTop)); + } + + virtual ~TextCanvas() { + setBounder(NULL); + } + + 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 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); + SkCanvas::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); + SkCanvas::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; +}; + +void CopyPaste::buildSelection(const SkPicture& picture, const SkIRect& area, + const SkIRect& selStart, const SkIRect& selEnd, 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, selEnd, area.fLeft, area.fTop, region); + TextCanvas checker(&builder, picture, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + region->translate(area.fLeft, area.fTop); +} + +SkIRect CopyPaste::findClosest(const SkPicture& picture, const SkIRect& area, + int x, int y) { + FirstCheck _check(x - area.fLeft, y - area.fTop); + DBG_NAV_LOGD("area=(%d, %d, %d, %d) x=%d y=%d", area.fLeft, area.fTop, + area.fRight, area.fBottom, x, y); + TextCanvas checker(&_check, picture, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + _check.offsetBounds(area.fLeft, area.fTop); + return _check.bestBounds(); +} diff --git a/WebKit/android/nav/SelectText.h b/WebKit/android/nav/SelectText.h new file mode 100644 index 0000000..b991198 --- /dev/null +++ b/WebKit/android/nav/SelectText.h @@ -0,0 +1,42 @@ +/* + * 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 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 SELECT_TEXT_H +#define SELECT_TEXT_H + +class SkPicture; +struct SkIRect; +struct SkIPoint; +class SkRegion; + +class CopyPaste { +public: + static void buildSelection(const SkPicture& , const SkIRect& area, + const SkIRect& selStart, const SkIRect& selEnd, SkRegion* region); + static SkIRect findClosest(const SkPicture& , const SkIRect& area, + int x, int y); +}; + +#endif diff --git a/WebKit/android/nav/WebView.cpp b/WebKit/android/nav/WebView.cpp new file mode 100644 index 0000000..52bb6a5 --- /dev/null +++ b/WebKit/android/nav/WebView.cpp @@ -0,0 +1,2322 @@ +/* + * 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 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 "webviewglue" + +#include <config.h> + +#include "android_graphics.h" +#include "AndroidLog.h" +#include "AtomicString.h" +#include "CachedFrame.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "FindCanvas.h" +#include "Frame.h" +#include "GraphicsJNI.h" +#include "IntPoint.h" +#include "IntRect.h" +#include "Node.h" +#include "PlatformGraphicsContext.h" +#include "PlatformString.h" +#include "SelectText.h" +#include "SkBlurMaskFilter.h" +#include "SkCanvas.h" +#include "SkCornerPathEffect.h" +#include "SkDumpCanvas.h" +#include "SkPath.h" +#include "SkPicture.h" +#include "SkPixelXorXfermode.h" +#include "SkRect.h" +#include "SkTime.h" +#include "WebCoreJni.h" +#include "WebViewCore.h" +#include "jni_utility.h" + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#ifdef GET_NATIVE_VIEW +#undef GET_NATIVE_VIEW +#endif + +#define GET_NATIVE_VIEW(env, obj) ((WebView*)env->GetIntField(obj, gWebViewField)) + +#include <ui/KeycodeLabels.h> +#include <JNIHelp.h> +#include <jni.h> + +#define REPLAY_BUFFER_SIZE 4096 + +namespace android { + +struct CommonParams { + enum Trigger { + NoData, + ClearFocusParams, + FirstMoveFocusParams, + MoveFocusParams, + MotionUpParams + } m_trigger; + int m_generation; +}; + +struct CacheParams { + void setFocus(const CachedNode* node, + const CachedFrame* frame, const CachedRoot* root, + const WebCore::IntPoint& focusLocation) + { + m_node = (WebCore::Node*) (node ? node->nodePointer() : 0); + m_frame = (WebCore::Frame*) (node ? frame->framePointer() : 0); + m_x = focusLocation.x(); + m_y = focusLocation.y(); + } + + WebCore::Node* m_node; + WebCore::Frame* m_frame; + int m_x; + int m_y; +}; + +struct ClearFocusParams { + CommonParams d; + CacheParams c; + int m_x; + int m_y; +}; + +struct MotionUpParams { + CommonParams d; + int m_x; + int m_y; + int m_slop; + bool m_isClick; +}; + +struct FirstMoveFocusParams { + CommonParams d; + int m_keyCode; + int m_count; + bool m_ignoreScroll; +}; + +struct MoveFocusParams { + FirstMoveFocusParams d; + CacheParams c; + void* m_sentFocus; + WebCore::IntRect m_sentBounds; + WebCore::IntRect m_visibleRect; + CachedHistory m_history; // FIXME: make this a subset + int m_xMax; + int m_yMax; +}; + +typedef MoveFocusParams LargestParams; + +#if DEBUG_NAV_UI +static const char* TriggerNames[] = { + "*** no data ! ***", + "clearFocus", + "firstMoveFocus", + "moveFocus", + "motionUp" +}; +#endif + +class FocusReplay { +public: +FocusReplay() : m_start(m_buffer), m_end(m_buffer), m_lastGeneration(0) +{ +} + +// find the most recent common data +void add(const CommonParams& data, size_t len) +{ + DBG_NAV_LOGD("m_start=%d m_end=%d trigger=%s moveGeneration=%d", m_start - m_buffer, + m_end - m_buffer, TriggerNames[data.m_trigger], data.m_generation); + m_lastGeneration = data.m_generation; + char* limit = m_buffer + sizeof(m_buffer); + int used = m_end - m_start; + if (used < 0) + used += sizeof(m_buffer); + int needed = (int) len - ((int) sizeof(m_buffer) - used); + if (needed >= 0) + reclaim(++needed); + if (m_end + len <= limit) { + memcpy(m_end, (void*) &data, len); + m_end += len; + DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); + return; + } + size_t partial = limit - m_end; + memcpy(m_end, (void*) &data, partial); + const void* remainder = (const void*) ((const char*) &data + partial); + partial = len - partial; + memcpy(m_buffer, remainder, partial); + m_end = m_buffer + partial; + DBG_NAV_LOGD("wrap m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); +} + +int count() +{ + DBG_NAV_LOGD("m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); + if (m_start == m_end) + return 0; + char* limit = m_buffer + sizeof(m_buffer); + char* saveStart = m_start; + int result = 0; + while (true) { + ++result; + m_start += triggerSize(); + if (m_start == m_end) + break; + if (m_start < limit) + continue; + m_start -= sizeof(m_buffer); + if (m_start == m_end) + break; + } + m_start = saveStart; + DBG_NAV_LOGD("count=%d", result); + return result; +} + +void discard(int generation) +{ + DBG_NAV_LOGD("generation=%d", generation); + LargestParams storage; + const CommonParams& params = storage.d.d; + char* pos = position(); + retrieve(&storage.d.d); + if (params.m_generation > generation) { + DBG_NAV_LOGD("params.m_generation=%d > generation=%d", + params.m_generation, generation); + rewind(pos); + DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); + return; + } + LOG_ASSERT(params.m_generation == generation, "params.m_generation != generation"); + DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); +} + +int lastAdd() +{ + return m_lastGeneration; +} + +char* position() +{ + return m_start; +} + +int retrieve(CommonParams* data) +{ + if (m_end == m_start) { + // changed from LOGD to LOGV, as it always fires when I click to center + // text (mrr) + LOGV("%s *** no data to retrieve (error condition) ***", __FUNCTION__); + data->m_trigger = CommonParams::NoData; + return data->m_generation = INT_MAX; + } + DBG_NAV_LOGD("m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); + char* limit = m_buffer + sizeof(m_buffer); + size_t size = triggerSize(); + if (m_start < m_end) { + LOG_ASSERT((size_t) (m_end - m_start) >= size, "m_end - m_start < size"); + memcpy(data, m_start, size); + m_start += size; + } else { + int partial = limit - m_start; + if (partial > (int) size) + partial = size; + memcpy(data, m_start, partial); + m_start += partial; + void* remainder = (void*) ((char*) data + partial); + partial = size - partial; + if (partial > 0) { + memcpy(remainder, m_buffer, partial); + m_start = m_buffer + partial; + LOG_ASSERT(m_start <= m_end, "m_start > m_end"); + } + } + if (m_start == limit) { + m_start = m_buffer; + if (m_end == limit) + m_end = m_buffer; + } + DBG_NAV_LOGD("m_start=%d m_end=%d trigger=%s moveGeneration=%d", + m_start - m_buffer, m_end - m_buffer, TriggerNames[data->m_trigger], + data->m_generation); + return data->m_generation; +} + +void rewind(char* pos) +{ + m_start = pos; +} + +private: +void reclaim(int needed) +{ + DBG_NAV_LOGD("needed=%d", needed); + char* limit = m_buffer + sizeof(m_buffer); + do { + size_t size = triggerSize(); + m_start += size; + needed -= size; + if (m_start >= limit) { + m_start = m_buffer + (m_start - limit); + if (m_end == limit) + m_end = m_buffer; + } + } while (needed > 0 && m_start != m_end); + DBG_NAV_LOGD("m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); +} + +size_t triggerSize() +{ + LOG_ASSERT(m_start != m_end, "m_start == m_end"); + char* limit = m_buffer + sizeof(m_buffer); + LOG_ASSERT(m_start + sizeof(CommonParams::Trigger) <= limit, "trigger not in limit"); + CommonParams::Trigger trigger; + memcpy(&trigger, m_start, sizeof(trigger)); + switch (trigger) { + case CommonParams::ClearFocusParams: + return sizeof(ClearFocusParams); + case CommonParams::FirstMoveFocusParams: + return sizeof(FirstMoveFocusParams); + case CommonParams::MoveFocusParams: + return sizeof(MoveFocusParams); + case CommonParams::MotionUpParams: + return sizeof(MotionUpParams); + default: + LOG_ASSERT(0, "trigger undefined"); + } + return 0; +} + +char m_buffer[REPLAY_BUFFER_SIZE]; +char* m_start; +char* m_end; +int m_lastGeneration; +}; // end of helper class ReplayFocus + +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 OutOfFocusFix { + DoNothing, + ClearTextEntry, + UpdateTextEntry +}; + +struct JavaGlue { + jobject m_obj; + jmethodID m_clearTextEntry; + jmethodID m_overrideLoading; + jmethodID m_scrollBy; + jmethodID m_sendFinalFocus; + jmethodID m_sendKitFocus; + jmethodID m_sendMotionUp; + jmethodID m_setFocusData; + jmethodID m_getScaledMaxXScroll; + jmethodID m_getScaledMaxYScroll; + jmethodID m_getVisibleRect; + jmethodID m_updateTextEntry; + jmethodID m_displaySoftKeyboard; + jmethodID m_viewInvalidate; + jmethodID m_viewInvalidateRect; + jmethodID m_postInvalidateDelayed; + jfieldID m_rectLeft; + jfieldID m_rectTop; + jmethodID m_rectWidth; + jmethodID m_rectHeight; + jfieldID m_focusNode; + jmethodID m_setAll; + AutoJObject object(JNIEnv* env) { + return getRealObject(env, m_obj); + } +} m_javaGlue; + +WebView(JNIEnv* env, jobject javaWebView, int viewImpl) +{ + jclass clazz = env->FindClass("android/webkit/WebView"); + // m_javaGlue = new JavaGlue; + m_javaGlue.m_obj = adoptGlobalRef(env, javaWebView); + m_javaGlue.m_scrollBy = GetJMethod(env, clazz, "setContentScrollBy", "(IIZ)V"); + m_javaGlue.m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()V"); + m_javaGlue.m_overrideLoading = GetJMethod(env, clazz, "overrideLoading", "(Ljava/lang/String;)V"); + m_javaGlue.m_sendFinalFocus = GetJMethod(env, clazz, "sendFinalFocus", "(IIII)V"); + m_javaGlue.m_sendKitFocus = GetJMethod(env, clazz, "sendKitFocus", "()V"); + m_javaGlue.m_sendMotionUp = GetJMethod(env, clazz, "sendMotionUp", "(IIIIIIIZZ)V"); + m_javaGlue.m_setFocusData = GetJMethod(env, clazz, "setFocusData", "(IIIIIIZ)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_updateTextEntry = GetJMethod(env, clazz, "updateTextEntry", "()V"); + m_javaGlue.m_displaySoftKeyboard = GetJMethod(env, clazz, "displaySoftKeyboard", "()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"); + 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"); + + // Set up class for updateFocusNode + jclass focusnodeClass = env->FindClass("android/webkit/WebView$FocusNode"); + LOG_ASSERT(focusnodeClass, "Could not find FocusNode class!"); + m_javaGlue.m_focusNode = env->GetFieldID(clazz, "mFocusNode", "Landroid/webkit/WebView$FocusNode;"); + m_javaGlue.m_setAll = GetJMethod(env, focusnodeClass, "setAll", "(ZZZZZIIIIIIIILjava/lang/String;Ljava/lang/String;I)V"); + env->DeleteLocalRef(focusnodeClass); + + env->SetIntField(javaWebView, gWebViewField, (jint)this); + m_viewImpl = (WebViewCore*) viewImpl; + m_frameCacheUI = 0; + m_navPictureUI = 0; + m_invalidNode = 0; + m_generation = 0; + m_heightCanMeasure = false; + m_followedLink = false; + m_lastDx = 0; + m_lastDxTime = 0; + m_ringAnimationEnd = 0; + m_selStart.setEmpty(); + m_selEnd.setEmpty(); + m_matches = 0; + m_hasCurrentLocation = false; + m_isFindPaintSetUp = false; +} + +~WebView() +{ + if (m_javaGlue.m_obj) + { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteGlobalRef(m_javaGlue.m_obj); + m_javaGlue.m_obj = 0; + } + delete m_frameCacheUI; + delete m_navPictureUI; + if (m_matches) + delete m_matches; +} + +void clearFocus(int x, int y, bool inval) +{ + DBG_NAV_LOGD("x=%d y=%d inval=%s", x, y, + inval ? "true" : "false"); + clearTextEntry(); + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + const CachedFrame* oldFrame = 0; + const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); + WebCore::IntPoint focusLocation = WebCore::IntPoint(0, 0); + setFocusData(root->generation(), 0, 0, x, y, !oldFocusNode); + sendKitFocus(); + if (oldFocusNode) { + DBG_NAV_LOG("oldFocusNode"); + focusLocation = root->focusLocation(); + root->setCachedFocus(0, 0); + if (inval) + viewInvalidate(); + } + ClearFocusParams params; + params.d.m_trigger = CommonParams::ClearFocusParams; + params.d.m_generation = m_generation; + params.c.setFocus(oldFocusNode, oldFrame, root, focusLocation); + params.m_x = x; + params.m_y = y; + m_replay.add(params.d, sizeof(params)); +} + +void clearTextEntry() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_clearTextEntry); + checkException(env); +} + +#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 focus 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 focused or pressed state +void nativeRecordButtons(bool hasFocus, bool pressed, bool invalidate) +{ + bool focusIsButton = false; + const CachedNode* cachedFocus = 0; + // Lock the mutex, since we now share with the WebCore thread. + m_viewImpl->gButtonMutex.lock(); + if (m_viewImpl->m_buttons.size()) { + // Find the focused node so we can determine which node has focus, and + // therefore which state to paint them in. + // FIXME: In a future change, we should keep track of whether the focus + // has changed to short circuit (note that we would still need to update + // if we received new buttons from the WebCore thread). + WebCore::Node* focus = 0; + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + cachedFocus = root->currentFocus(); + if (cachedFocus) + focus = (WebCore::Node*) cachedFocus->nodePointer(); + } + + // Traverse the array, and update each button, depending on whether it + // is focused. + Container* end = m_viewImpl->m_buttons.end(); + for (Container* ptr = m_viewImpl->m_buttons.begin(); ptr != end; ptr++) { + WebCore::RenderSkinAndroid::State state; + if (ptr->matches(focus)) { + focusIsButton = true; + // If the WebView is out of focus/window focus, set the state to + // normal, but still keep track of the fact that the focus is a + // button + if (!hasFocus) { + state = WebCore::RenderSkinAndroid::kNormal; + } else if (m_followedLink || pressed) { + state = WebCore::RenderSkinAndroid::kPressed; + } else { + state = WebCore::RenderSkinAndroid::kFocused; + } + } else { + state = WebCore::RenderSkinAndroid::kNormal; + } + ptr->updateFocusState(state); + } + } + m_viewImpl->gButtonMutex.unlock(); + if (invalidate && cachedFocus && focusIsButton) { + const WebCore::IntRect& b = cachedFocus->getBounds(); + viewInvalidateRect(b.x(), b.y(), b.right(), b.bottom()); + } +} + +// These two functions separate out the particular look of the drawn find +// matches from the code that draws them. This function sets up the paints that +// are used to draw the matches. +void 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; +} + +// Draw the match specified by region to the canvas. +void 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); +} + +// 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 drawMatches(SkCanvas* canvas) +{ + if (!m_matches || !m_matches->size()) { + return; + } + if (m_findIndex >= m_matches->size()) { + m_findIndex = 0; + } + const MatchInfo& matchInfo = (*m_matches)[m_findIndex]; + const SkRegion& currentMatchRegion = matchInfo.getLocation(); + const SkIRect& currentMatchBounds = currentMatchRegion.getBounds(); + int left = currentMatchBounds.fLeft; + int top = currentMatchBounds.fTop; + int right = currentMatchBounds.fRight; + int bottom = currentMatchBounds.fBottom; + WebCore::IntRect visible; + getVisibleRect(&visible); + // Check to make sure that the highlighted match is on screen. If not, + // scroll it onscreen and return. + int dx = 0; + if (left < visible.x()) { + dx = left - visible.x(); + // Only scroll right if the entire width can fit on screen. + } else if (right > visible.right() && right - left < visible.width()) { + dx = right - visible.right(); + } + int dy = 0; + if (top < visible.y()) { + dy = top - visible.y(); + // Only scroll down if the entire height can fit on screen + } else if (bottom > visible.bottom() && bottom - top < visible.height()) { + dy = bottom - visible.bottom(); + } + if ((dx|dy)) { + scrollBy(dx, dy); + viewInvalidate(); + return; + } + // Set up the paints used for drawing the matches + if (!m_isFindPaintSetUp) + setUpFindPaint(); + + // Draw the current match + drawMatch(currentMatchRegion, canvas, true); + // Now draw the picture, so that it shows up on top of the rectangle + canvas->drawPicture(*matchInfo.getPicture()); + + // Draw the rest + unsigned numberOfMatches = m_matches->size(); + if (numberOfMatches > 1 + && numberOfMatches < MAX_NUMBER_OF_MATCHES_TO_DRAW) { + SkIRect visibleIRect; + android_setrect(&visibleIRect, visible); + for(unsigned i = 0; i < numberOfMatches; i++) { + // The current match has already been drawn + if (i == m_findIndex) + 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) + || !region.intersects(visibleIRect)) + continue; + drawMatch(region, canvas, false); + } + } +} + +void drawFocusRing(SkCanvas* canvas) +{ + const CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + m_followedLink = false; + return; + } + const CachedNode* node = root->currentFocus(); + if (!node) { + DBG_NAV_LOG("!node"); + m_followedLink = false; + return; + } + if (!node->hasFocusRing()) { + DBG_NAV_LOG("!node->hasFocusRing()"); + return; + } + const WTF::Vector<WebCore::IntRect>& rings = node->focusRings(); + if (!rings.size()) { + DBG_NAV_LOG("!rings.size()"); + return; + } + + bool isButton = false; + m_viewImpl->gButtonMutex.lock(); + // If this is a button drawn by us (rather than webkit) do not draw the + // focus ring, since its focus will be shown by a change in what we draw. + // Should be in sync with recordButtons, since that will be called + // before this. + if (m_viewImpl->m_buttons.size() > 0) { + WebCore::Node* focusPointer = (WebCore::Node*) node->nodePointer(); + Container* end = m_viewImpl->m_buttons.end(); + for (Container* ptr = m_viewImpl->m_buttons.begin(); ptr != end; ptr++) { + if (ptr->matches(focusPointer)) { + isButton = true; + break; + } + } + } + m_viewImpl->gButtonMutex.unlock(); + WebCore::IntRect bounds = node->bounds(); + bounds.inflate(SkScalarCeil(FOCUS_RING_OUTER_DIAMETER)); + SkRect sbounds; + android_setrect(&sbounds, bounds); + if (canvas->quickReject(sbounds, SkCanvas::kAA_EdgeType)) { + DBG_NAV_LOG("canvas->quickReject"); + m_followedLink = false; + return; + } + FocusRing::Flavor flavor = FocusRing::NORMAL_FLAVOR; + if (!isButton) { + flavor = node->type() != NORMAL_CACHEDNODETYPE ? + FocusRing::FAKE_FLAVOR : node->nodePointer() == m_invalidNode ? + FocusRing::INVALID_FLAVOR : FocusRing::NORMAL_FLAVOR; + if (flavor != FocusRing::INVALID_FLAVOR && m_followedLink) { + flavor = (FocusRing::Flavor) (flavor + FocusRing::NORMAL_ANIMATING); + } +#if DEBUG_NAV_UI + const WebCore::IntRect& ring = rings[0]; + DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p) flavor=%s rings=%d" + " (%d, %d, %d, %d)", node->index(), node->nodePointer(), + flavor == FocusRing::FAKE_FLAVOR ? "FAKE_FLAVOR" : + flavor == FocusRing::INVALID_FLAVOR ? "INVALID_FLAVOR" : + flavor == FocusRing::NORMAL_ANIMATING ? "NORMAL_ANIMATING" : + flavor == FocusRing::FAKE_ANIMATING ? "FAKE_ANIMATING" : "NORMAL_FLAVOR", + rings.size(), ring.x(), ring.y(), ring.width(), ring.height()); +#endif + } + if (isButton || flavor >= FocusRing::NORMAL_ANIMATING) { + SkMSec time = SkTime::GetMSecs(); + if (time < m_ringAnimationEnd) { + // views assume that inval bounds coordinates are non-negative + bounds.intersect(WebCore::IntRect(0, 0, INT_MAX, INT_MAX)); + postInvalidateDelayed(m_ringAnimationEnd - time, bounds); + } else { + m_followedLink = false; + flavor = (FocusRing::Flavor) (flavor - FocusRing::NORMAL_ANIMATING); + } + } + if (!isButton) + FocusRing::DrawRing(canvas, rings, flavor); +} + +OutOfFocusFix fixOutOfDateFocus(bool useReplay) +{ + if (!m_frameCacheUI) { + DBG_NAV_LOG("!m_frameCacheUI"); + return DoNothing; + } + const CachedFrame* cachedFrame = 0; + const CachedNode* cachedFocusNode = m_frameCacheUI->currentFocus(&cachedFrame); + if (!cachedFocusNode) { + DBG_NAV_LOG("!cachedFocusNode"); + return DoNothing; + } + CachedRoot* webRoot = m_viewImpl->m_frameCacheKit; + if (!webRoot) { + DBG_NAV_LOG("!webRoot"); + return DoNothing; + } + int uiWidth = m_frameCacheUI->width(); + int webWidth = webRoot->width(); + if (uiWidth != webWidth) { + DBG_NAV_LOGD("uiWidth=%d webWidth=%d", uiWidth, webWidth); + return DoNothing; // allow text inputs to preserve their state + } else { + const WebCore::IntRect& cachedBounds = m_frameCacheUI->focusBounds(); + const CachedFrame* webFrame = 0; + const CachedNode* webFocusNode = webRoot->currentFocus(&webFrame); + DBG_NAV_LOGD("cachedBounds=(%d,%d,w=%d,h=%d) cachedFrame=%p (%d)" + " webFocusNode=%p (%d) webFrame=%p (%d)", + cachedBounds.x(), cachedBounds.y(), + cachedBounds.width(), cachedBounds.height(), + cachedFrame, cachedFrame ? cachedFrame->indexInParent() : -1, + webFocusNode, webFocusNode ? webFocusNode->index() : -1, + webFrame, webFrame ? webFrame->indexInParent() : -1); + if (webFocusNode && webFrame && webFrame->sameFrame(cachedFrame)) { + if (useReplay && !m_replay.count()) { + DBG_NAV_LOG("!m_replay.count()"); + return DoNothing; + } + if (webFocusNode->index() == cachedFocusNode->index()) { + DBG_NAV_LOG("index =="); + return DoNothing; + } + const WebCore::IntRect& webBounds = webRoot->focusBounds(); + DBG_NAV_LOGD("webBounds=(%d,%d,w=%d,h=%d)", + webBounds.x(), webBounds.y(), + webBounds.width(), webBounds.height()); + if (cachedBounds.contains(webBounds)) { + DBG_NAV_LOG("contains"); + return DoNothing; + } + if (webBounds.contains(cachedBounds)) { + DBG_NAV_LOG("webBounds contains"); + return DoNothing; + } + } + const CachedFrame* foundFrame = 0; + int x, y; + const CachedNode* found = findAt(webRoot, cachedBounds, &foundFrame, &x, &y); +#if DEBUG_NAV_UI + DBG_NAV_LOGD("found=%p (%d) frame=%p (%d)", + found, found ? found->index() : -1, + foundFrame, foundFrame ? foundFrame->indexInParent() : -1); + if (found) { + WebCore::IntRect newBounds = found->bounds(); + DBG_NAV_LOGD("found=(%d,%d,w=%d,h=%d) x=%d y=%d", + newBounds.x(), newBounds.y(), newBounds.width(), + newBounds.height(), x, y); + } +#endif + webRoot->setCachedFocus(const_cast<CachedFrame*>(foundFrame), + const_cast<CachedNode*>(found)); + if (found) + webRoot->rootHistory()->setNavBounds(found->bounds()); + WebCore::Frame* framePointer = foundFrame ? (WebCore::Frame*) foundFrame->framePointer() : 0; + WebCore::Node* nodePointer = found ? (WebCore::Node*) found->nodePointer() : 0; + setFocusData(webRoot->generation(), framePointer, nodePointer, x, y, !found); + sendFinalFocus(framePointer, nodePointer, x, y); + if (found && (found->isTextArea() || found->isTextField())) + return UpdateTextEntry; + } +checkOldFocus: + return cachedFocusNode->isTextArea() || cachedFocusNode->isTextField() ? ClearTextEntry : DoNothing; +} + +bool focusIsTextArea(FrameCachePermission allowNewer) +{ + CachedRoot* root = getFrameCache(allowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* focus = root->currentFocus(); + if (!focus) + return false; + return focus->isTextArea() || focus->isTextField(); +} + +void focusRingBounds(WebCore::IntRect* bounds) +{ + DBG_NAV_LOGD("%s", ""); + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedNode* cachedNode = root->currentFocus(); + if (cachedNode) { + cachedNode->focusRingBounds(bounds); + DBG_NAV_LOGD("bounds={%d,%d,%d,%d}", bounds->x(), bounds->y(), + bounds->width(), bounds->height()); + return; + } + } + *bounds = WebCore::IntRect(0, 0, 0, 0); +} + +CachedRoot* getFrameCache(FrameCachePermission allowNewer) +{ + if (!m_viewImpl->m_updatedFrameCache) + return m_frameCacheUI; + m_viewImpl->gRecomputeFocusMutex.lock(); + bool recomputeInProgress = m_viewImpl->m_recomputeEvents.size() > 0; + m_viewImpl->gRecomputeFocusMutex.unlock(); + if (allowNewer != AllowNewest && recomputeInProgress) + return m_frameCacheUI; + if (allowNewer == DontAllowNewer && m_viewImpl->m_lastGeneration < m_generation) + return m_frameCacheUI; + DBG_NAV_LOGD("%s", "m_viewImpl->m_updatedFrameCache == true"); + m_viewImpl->gFrameCacheMutex.lock(); + OutOfFocusFix fix = DoNothing; + if (allowNewer != DontAllowNewer) + fix = fixOutOfDateFocus(m_viewImpl->m_useReplay); + delete m_frameCacheUI; + delete 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 (fix == UpdateTextEntry) + updateTextEntry(); + else if (fix == ClearTextEntry) + clearTextEntry(); + 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; +} + +void getVisibleRect(WebCore::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); + int left = (int) env->GetIntField(jRect, m_javaGlue.m_rectLeft); + checkException(env); + rect->setX(left); + int top = (int) env->GetIntField(jRect, m_javaGlue.m_rectTop); + checkException(env); + rect->setY(top); + int width = (int) env->CallIntMethod(jRect, m_javaGlue.m_rectWidth); + checkException(env); + rect->setWidth(width); + int height = (int) env->CallIntMethod(jRect, m_javaGlue.m_rectHeight); + checkException(env); + rect->setHeight(height); + env->DeleteLocalRef(jRect); + checkException(env); +} + +static CachedFrame::Direction KeyToDirection(KeyCode keyCode) +{ + switch (keyCode) { + case kKeyCodeDpadRight: + DBG_NAV_LOGD("keyCode=%s", "right"); + return CachedFrame::RIGHT; + case kKeyCodeDpadLeft: + DBG_NAV_LOGD("keyCode=%s", "left"); + return CachedFrame::LEFT; + case kKeyCodeDpadDown: + DBG_NAV_LOGD("keyCode=%s", "down"); + return CachedFrame::DOWN; + case kKeyCodeDpadUp: + DBG_NAV_LOGD("keyCode=%s", "up"); + return CachedFrame::UP; + default: + LOGD("------- bad key sent to WebView::moveFocus"); + return CachedFrame::UNINITIALIZED; + } +} + +bool invalidFrame(WebCore::Frame* frame, const CachedRoot* root) +{ + if (!frame) + return false; + int frameBuild = m_viewImpl->retrieveFrameGeneration(frame); + int rootBuild = root->generation(); + return frameBuild > rootBuild; +} + +WebCore::String imageURI(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + return root ? root->imageURI(x, y) : WebCore::String(); +} + +bool focusNodeWantsKeyEvents() +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedNode* focus = root->currentFocus(); + if (focus) { + return focus->isWantsKeyEvents(); + } + } + return false; +} + +/* returns true if the key had no effect (neither scrolled nor changed focus) */ +bool moveFocus(int keyCode, int count, bool ignoreScroll, bool inval, + void* lastSentFocus, const WebCore::IntRect* lastSentBounds) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + setFocusData(0, 0, 0, 0, 0, true); + sendKitFocus(); // will build cache and retry + FirstMoveFocusParams params; + params.d.m_trigger = CommonParams::FirstMoveFocusParams; + params.d.m_generation = m_generation; + params.m_keyCode = keyCode; + params.m_count = count; + params.m_ignoreScroll = ignoreScroll; + m_replay.add(params.d, sizeof(params)); + return true; + } + + CachedFrame::Direction direction = KeyToDirection((KeyCode) keyCode); + const CachedFrame* cachedFrame, * oldFrame = 0; + const CachedNode* focus = root->currentFocus(&oldFrame); + WebCore::IntPoint focusLocation = root->focusLocation(); + DBG_NAV_LOGD("old focus %d (nativeNode=%p) focusLocation={%d, %d}", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : 0, focusLocation.x(), focusLocation.y()); + WebCore::IntRect visibleRect; + getVisibleRect(&visibleRect); + DBG_NAV_LOGD("getVisibleRect %d,%d,%d,%d", + visibleRect.x(), visibleRect.y(), visibleRect.width(), visibleRect.height()); + root->setVisibleRect(visibleRect); + int xMax = getScaledMaxXScroll(); + int yMax = getScaledMaxYScroll(); + root->setMaxScroll(xMax, yMax); + CachedHistory savedHistory = *root->rootHistory(); + bool oldNodeIsTextArea = focusIsTextArea(DontAllowNewer); + const CachedNode* cachedNode = 0; + int dx = 0; + int dy = 0; + int counter = count; + if (!focus || !focus->isInput() || !m_followedLink) + root->setScrollOnly(m_followedLink); + while (--counter >= 0) { + WebCore::IntPoint scroll = WebCore::IntPoint(0, 0); + cachedNode = root->moveFocus(direction, &cachedFrame, &scroll); + dx += scroll.x(); + dy += scroll.y(); + } + DBG_NAV_LOGD("new focus %d (nativeNode=%p) focusLocation={%d, %d}", + cachedNode ? cachedNode->index() : 0, + cachedNode ? cachedNode->nodePointer() : 0, root->focusLocation().x(), + root->focusLocation().y()); + // If !m_heightCanMeasure (such as in the browser), we want to scroll no + // matter what + if (!ignoreScroll && (!m_heightCanMeasure || + !cachedNode || + (focus && focus->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(); + ignoreScroll = true; // if move re-executes, don't scroll the second time + } + bool result = false; + if (cachedNode) { + WebCore::IntPoint pos; + root->setCachedFocus((CachedFrame*) cachedFrame, (CachedNode*) cachedNode); + root->getSimulatedMousePosition(&pos); + if (lastSentFocus == cachedNode->nodePointer() && lastSentBounds && + *lastSentBounds == cachedNode->bounds()) + { + sendFinalFocus((WebCore::Frame*) cachedFrame->framePointer(), + (WebCore::Node*) cachedNode->nodePointer(), pos.x(), pos.y()); + } else { + setFocusData(root->generation(), + (WebCore::Frame*) cachedFrame->framePointer(), + (WebCore::Node*) cachedNode->nodePointer(), pos.x(), pos.y(), + true); + sendKitFocus(); + if (inval) + viewInvalidate(); + MoveFocusParams params; + params.d.d.m_trigger = CommonParams::MoveFocusParams; + params.d.d.m_generation = m_generation; + params.c.setFocus(focus, oldFrame, root, focusLocation); + params.m_sentFocus = cachedNode->nodePointer(); + params.m_sentBounds = cachedNode->bounds(); + params.m_visibleRect = visibleRect; + params.m_history = savedHistory; + DBG_NAV_LOGD("history.mDidFirstLayout=%s", + params.m_history.didFirstLayout() ? "true" : "false"); + params.m_xMax = xMax; + params.m_yMax = yMax; + params.d.m_keyCode = keyCode; + params.d.m_count = count; + params.d.m_ignoreScroll = ignoreScroll; + m_replay.add(params.d.d, sizeof(params)); + } + } else { + if (visibleRect.intersects(root->focusBounds()) == false) { + setFocusData(root->generation(), 0, 0, 0, 0, true); + sendKitFocus(); // will build cache and retry + } + FirstMoveFocusParams params; + params.d.m_trigger = CommonParams::FirstMoveFocusParams; + params.d.m_generation = m_generation; + params.m_keyCode = keyCode; + params.m_count = count; + params.m_ignoreScroll = ignoreScroll; + m_replay.add(params.d, sizeof(params)); + 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; + } + if (focusIsTextArea(DontAllowNewer)) + updateTextEntry(); + else if (oldNodeIsTextArea) + clearTextEntry(); + return result; +} + +void notifyFocusSet(FrameCachePermission inEditingMode) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + // make sure the mFocusData in WebView.java is in sync with WebView.cpp + const CachedFrame* frame = 0; + const CachedNode* node = root->currentFocus(&frame); + const WebCore::IntPoint& focusLocation = root->focusLocation(); + setFocusData(root->generation(), + frame ? (WebCore::Frame*) frame->framePointer() : 0, + node ? (WebCore::Node*) node->nodePointer() : 0, + focusLocation.x(), focusLocation.y(), false); + } + + if (focusIsTextArea(inEditingMode)) + updateTextEntry(); + else if (inEditingMode) + clearTextEntry(); +#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 +} + +void notifyProgressFinished() +{ + DBG_NAV_LOGD("focusIsTextArea=%d", focusIsTextArea(DontAllowNewer)); + updateTextEntry(); +#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 +} + +void recomputeFocus() +{ + int generation; + do { + m_viewImpl->gRecomputeFocusMutex.lock(); + if (!m_viewImpl->m_recomputeEvents.size()) { + m_viewImpl->gRecomputeFocusMutex.unlock(); + return; + } + generation = m_viewImpl->m_recomputeEvents.first(); + m_viewImpl->m_recomputeEvents.remove(0); + m_viewImpl->gRecomputeFocusMutex.unlock(); + DBG_NAV_LOGD("generation=%d", generation); + CachedRoot* root = getFrameCache(AllowNewest); + if (!root) { + DBG_NAV_LOG("!root"); + return; + } + LargestParams storage; + const CommonParams& params = storage.d.d; + char* pos = m_replay.position(); + while (m_replay.retrieve(&storage.d.d) < generation) + DBG_NAV_LOGD("dropped ", params.m_generation); + if (params.m_generation > generation) { + DBG_NAV_LOGD("params.m_generation=%d > generation=%d", + params.m_generation, generation); + m_replay.rewind(pos); + return; + } + int lastAdd = m_replay.lastAdd(); + do { + LOG_ASSERT(params.m_trigger != CommonParams::NoData, "expected data"); + bool inval = generation == m_generation; + switch (params.m_trigger) { + case CommonParams::ClearFocusParams: { + const ClearFocusParams& sParams = *(ClearFocusParams*) &storage; + const CacheParams& cParams = sParams.c; + if (invalidFrame(cParams.m_frame, root)) { + DBG_NAV_LOGD("dropped %s generation=%d", + TriggerNames[params.m_trigger], generation); + return; + } + root->setFocus(cParams.m_frame, cParams.m_node, cParams.m_x, cParams.m_y); + clearFocus(sParams.m_x, sParams.m_y, inval); + DBG_NAV_LOGD("clearFocus(x,y)={%d,%d}", sParams.m_x, sParams.m_y); + } break; + case CommonParams::MotionUpParams: { + const MotionUpParams& mParams = *(MotionUpParams*) &storage; + // const CacheParams& cParams = mParams.c; + // if (invalidFrame(cParams.m_frame, root) == false) + // root->setFocus(cParams.m_frame, cParams.m_node, + // cParams.m_x, cParams.m_y); + motionUp(mParams.m_x, mParams.m_y, mParams.m_slop, mParams.m_isClick, inval, true); + DBG_NAV_LOGD("motionUp m_x=%d m_y=%d", mParams.m_x, mParams.m_y); + } break; + case CommonParams::FirstMoveFocusParams: { + if (invalidFrame((WebCore::Frame*) root->framePointer(), root)) { + DBG_NAV_LOGD("dropped %s generation=%d", + TriggerNames[params.m_trigger], generation); + return; + } + const FirstMoveFocusParams& fParams = *(FirstMoveFocusParams*) &storage; + DBG_NAV_LOGD("first moveFocus keyCode=%d count=%d" + " ignoreScroll=%s", fParams.m_keyCode, fParams.m_count, + fParams.m_ignoreScroll ? "true" : "false"); + moveFocus(fParams.m_keyCode, fParams.m_count, + fParams.m_ignoreScroll, inval, 0, 0); + } break; + case CommonParams::MoveFocusParams: { + const MoveFocusParams& mParams = *(MoveFocusParams*) &storage; + const CacheParams& cParams = mParams.c; + if (invalidFrame(cParams.m_frame, root)) { + DBG_NAV_LOGD("dropped %s generation=%d", + TriggerNames[params.m_trigger], generation); + return; + } + DBG_NAV_LOGD("moveFocus keyCode=%d count=%d ignoreScroll=%s " + "history.mDidFirstLayout=%s", mParams.d.m_keyCode, + mParams.d.m_count, mParams.d.m_ignoreScroll ? "true" : "false", + mParams.m_history.didFirstLayout() ? "true" : "false"); + if (!root->setFocus(cParams.m_frame, cParams.m_node, + cParams.m_x, cParams.m_y)) { + DBG_NAV_LOGD("can't restore focus frame=%p node=%p", + "x=%d y=%d %s", cParams.m_frame, cParams.m_node, + cParams.m_x, cParams.m_y, TriggerNames[params.m_trigger]); + return; + } + root->setVisibleRect(mParams.m_visibleRect); + root->setMaxScroll(mParams.m_xMax, mParams.m_yMax); + *root->rootHistory() = mParams.m_history; + moveFocus(mParams.d.m_keyCode, mParams.d.m_count, + mParams.d.m_ignoreScroll, inval, + mParams.m_sentFocus, &mParams.m_sentBounds); + } break; + default: + LOG_ASSERT(0, "unknown trigger"); + } + if (params.m_generation >= lastAdd) + break; + root = getFrameCache(DontAllowNewer); // re-execution may have retrieved newer cache + m_replay.retrieve(&storage.d.d); + DBG_NAV_LOGD("continuation m_generation %d", params.m_generation); + } while (true); + } while (true); +} + +void resetFocus() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + root->setCachedFocus(0, 0); +} + +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; + WebCore::IntRect visibleRect; + getVisibleRect(&visibleRect); + root->setVisibleRect(visibleRect); + return root->findAt(rect, framePtr, rxPtr, ryPtr); +} + +void selectBestAt(const WebCore::IntRect& rect) +{ + const CachedFrame* frame; + int rx, ry; + CachedRoot* root = getFrameCache(DontAllowNewer); + const CachedNode* node = findAt(root, rect, &frame, &rx, &ry); + int rootGeneration = root ? root->generation() : 0; + setFocusData(rootGeneration, + frame ? (WebCore::Frame*) frame->framePointer() : 0, + node ? (WebCore::Node*) node->nodePointer() : 0, rx, ry, false); + if (!node) { + DBG_NAV_LOGD("no nodes found root=%p", root); + if (root) { + root->clearFocus(); + root->setCachedFocus(0, 0); + } + sendKitFocus(); + viewInvalidate(); + clearTextEntry(); + return; + } + DBG_NAV_LOGD("CachedNode:%p (%d)", node, node->index()); + const CachedFrame* oldFrame = 0; + const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); + bool oldNodeIsTextArea = focusIsTextArea(DontAllowNewer); + root->setCachedFocus(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(node)); + viewInvalidate(); + if (focusIsTextArea(DontAllowNewer)) + updateTextEntry(); + else if (oldNodeIsTextArea) + clearTextEntry(); +} + +WebCore::IntRect getNavBounds() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return WebCore::IntRect(0, 0, 0, 0); + return root->rootHistory()->navBounds(); +} + +void setNavBounds(const WebCore::IntRect& rect) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + root->rootHistory()->setNavBounds(rect); +} + +void markNodeInvalid(WebCore::Node* node) +{ + DBG_NAV_LOGD("node=%p", node); + m_invalidNode = node; + viewInvalidate(); +} + +bool motionUp(int x, int y, int slop, bool isClick, bool inval, bool retry) +{ + bool pageScrolled = false; + m_followedLink = false; + const CachedFrame* frame; + WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop, slop * 2, slop * 2); + int rx, ry; + CachedRoot* root = getFrameCache(AllowNewer); + const CachedNode* result = findAt(root, rect, &frame, &rx, &ry); + if (!result) { + DBG_NAV_LOGD("no nodes found root=%p", root); + int rootGeneration = 0; + if (root) { + root->clearFocus(); + rootGeneration = root->generation(); + if (!retry) { // scroll first time only + int dx = root->checkForCenter(x, y); + if (dx) { + scrollBy(dx, 0); + retry = true; // don't recompute later since we scrolled + pageScrolled = true; + } + } + } + sendMotionUp(rootGeneration, frame ? + (WebCore::Frame*) frame->framePointer() : 0, + 0, x, y, slop, isClick, retry); + if (inval) + viewInvalidate(); + if (!retry) { + MotionUpParams params; + params.d.m_trigger = CommonParams::MotionUpParams; + params.d.m_generation = m_generation; + params.m_x = x; + params.m_y = y; + params.m_slop = slop; + params.m_isClick = isClick; + m_replay.add(params.d, sizeof(params)); + } + clearTextEntry(); + return pageScrolled; + } + DBG_NAV_LOGD("CachedNode:%p (%d) x=%d y=%d rx=%d ry=%d", result, + result->index(), x, y, rx, ry); + // const CachedFrame* oldFrame = 0; + // const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); + // WebCore::IntPoint focusLocation = root->focusLocation(); + bool oldNodeIsTextArea = !retry && focusIsTextArea(DontAllowNewer); + root->setCachedFocus(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(result)); + bool newNodeIsTextArea = focusIsTextArea(DontAllowNewer); + CachedNodeType type = result->type(); + if (type == NORMAL_CACHEDNODETYPE || newNodeIsTextArea) { + sendMotionUp(root->generation(), + frame ? (WebCore::Frame*) frame->framePointer() : 0, + result ? (WebCore::Node*) result->nodePointer() : 0, rx, ry, + slop, isClick, retry); + if (inval) + viewInvalidate(); + if (!retry) { + MotionUpParams params; + params.d.m_trigger = CommonParams::MotionUpParams; + params.d.m_generation = m_generation; + params.m_x = x; + params.m_y = y; + params.m_slop = slop; + params.m_isClick = isClick; + // params.c.setFocus(oldFocusNode, oldFrame, root, focusLocation); + m_replay.add(params.d, sizeof(params)); + } + } else if (inval) + viewInvalidate(); + if (newNodeIsTextArea) { + updateTextEntry(); + displaySoftKeyboard(); + } else { + if (isClick) { + setFollowedLink(true); + if (type != NORMAL_CACHEDNODETYPE) { + overrideUrlLoading(result->getExport()); + } + } + if (oldNodeIsTextArea) + clearTextEntry(); + } + return pageScrolled; +} + +void overrideUrlLoading(const WebCore::String& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jName = env->NewString((jchar*) url.characters(), url.length()); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_overrideLoading, jName); + env->DeleteLocalRef(jName); +} + +void setFindIsUp(bool up) +{ + m_viewImpl->m_findIsUp = up; + if (!up) + m_hasCurrentLocation = false; +} + +void setFollowedLink(bool followed) +{ + if ((m_followedLink = followed) != false) { + m_ringAnimationEnd = SkTime::GetMSecs() + 500; + viewInvalidate(); + } +} + +void setHeightCanMeasure(bool measure) +{ + m_heightCanMeasure = measure; +} + +SkIRect m_selStart, m_selEnd; +SkRegion m_selRegion; +#define MIN_ARROW_DISTANCE (20 * 20) + +void moveSelection(int x, int y, bool extendSelection) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + const SkPicture& picture = *m_navPictureUI; + WebCore::IntRect r; + getVisibleRect(&r); + SkIRect area; + area.set(r.x(), r.y(), r.right(), r.bottom()); + if (!extendSelection) + m_selStart = m_selEnd = CopyPaste::findClosest(picture, area, x, y); + else + m_selEnd = CopyPaste::findClosest(picture, area, x, y); + DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)" + " m_selEnd=(%d, %d, %d, %d)", x, y, 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); +} + +const SkRegion& getSelection() +{ + return m_selRegion; +} + +void drawSelection(SkCanvas* canvas, int x, int y, bool extendSelection) +{ + if (!extendSelection) { + int dx = x - m_selStart.fLeft; + dx *= dx; + int otherX = x - m_selStart.fRight; + if (dx > (otherX *= otherX)) + dx = otherX; + int dy = y - m_selStart.fTop; + int dist = dx * dx + dy * dy; + if (dist > MIN_ARROW_DISTANCE) + drawSelectionArrow(canvas, x, y); + else + drawSelectionPointer(canvas, x, y, true); + } else { + drawSelectionRegion(canvas); + drawSelectionPointer(canvas, x, y, false); + } +} + +void drawSelectionRegion(SkCanvas* canvas) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + WebCore::IntRect r; + getVisibleRect(&r); + SkIRect area; + area.set(r.x(), r.y(), r.right(), r.bottom()); + m_selRegion.setEmpty(); + CopyPaste::buildSelection(*m_navPictureUI, area, m_selStart, m_selEnd, &m_selRegion); + SkPath path; + m_selRegion.getBoundaryPath(&path); + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SkColorSetARGB(0x40, 255, 51, 204)); + canvas->drawPath(path, paint); +} + +void drawSelectionPointer(SkCanvas* canvas, int x, int y, bool gridded) +{ + SkPath path; + getSelectionCaret(&path); + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLACK); + SkPixelXorXfermode xorMode(SK_ColorWHITE); + paint.setXfermode(&xorMode); + int sc = canvas->save(); + if (gridded) { + bool useLeft = x <= (m_selStart.fLeft + m_selStart.fRight) >> 1; + canvas->translate(SkIntToScalar(useLeft ? m_selStart.fLeft : + m_selStart.fRight), SkIntToScalar(m_selStart.fTop)); + } else + canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); + canvas->drawPath(path, paint); + canvas->restoreToCount(sc); +} + +void drawSelectionArrow(SkCanvas* canvas, int x, int y) +{ + SkPath path; + getSelectionArrow(&path); + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLACK); + paint.setStrokeWidth(SK_Scalar1 * 2); + int sc = canvas->save(); + canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); + canvas->drawPath(path, paint); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorWHITE); + canvas->drawPath(path, paint); + canvas->restoreToCount(sc); +} + +void 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(SkIntToScalar(arrow[index]), SkIntToScalar(arrow[index + 1])); + path->close(); +} + +void getSelectionCaret(SkPath* path) +{ + SkScalar height = SkIntToScalar(m_selStart.fBottom - m_selStart.fTop); + SkScalar dist = height / 4; + path->lineTo(0, height); + SkScalar bottom = height + dist; + path->lineTo(-dist, bottom); + SkScalar edge = bottom - SK_Scalar1/2; + path->moveTo(-dist, edge); + path->lineTo(dist, edge); + path->moveTo(dist, bottom); + path->lineTo(0, height); +} + +void sendFinalFocus(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); + 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_sendFinalFocus, + (jint) framePtr, (jint) nodePtr, x, y); + checkException(env); +} + +void sendKitFocus() +{ + 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_sendKitFocus); + checkException(env); +} + +void sendMotionUp(int buildGeneration, + WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y, int slop, + bool isClick, bool retry) +{ + m_viewImpl->m_touchGeneration = m_viewImpl->m_generation = ++m_generation; + DBG_NAV_LOGD("buildGeneration=%d m_generation=%d framePtr=%p nodePtr=%p" + " x=%d y=%d slop=%d", buildGeneration, + m_generation, framePtr, nodePtr, x, y, slop); + 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, + buildGeneration, (jint) framePtr, (jint) nodePtr, x, y, slop, isClick, retry); + checkException(env); +} + +void setFocusData(int buildGeneration, WebCore::Frame* framePtr, + WebCore::Node* nodePtr, int x, int y, bool ignoreNullFocus) +{ + m_viewImpl->m_moveGeneration = m_viewImpl->m_generation = ++m_generation; + DBG_NAV_LOGD("moveGeneration=%d buildGeneration=%d framePtr=%p nodePtr=%p" + " x=%d y=%d", m_generation, buildGeneration, framePtr, nodePtr, x, y); + 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_setFocusData, m_generation, + buildGeneration, (jint) framePtr, (jint) nodePtr, x, y, ignoreNullFocus); + checkException(env); +} + +// 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 inline 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; +} + +void findNext(bool forward) +{ + if (!m_matches || !m_matches->size()) + 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(); + viewInvalidate(); +} + +// With this call, WebView takes ownership of matches, and is responsible for +// deleting it. +void 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; + viewInvalidate(); + 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; + } + viewInvalidate(); +} + +void 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(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_scrollBy, + dx, dy, true); + checkException(env); +} + +bool updateFocusNode(JNIEnv* env) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedFrame* cachedFrame = 0; + const CachedNode* cachedFocusNode = root->currentFocus(&cachedFrame); + if (!cachedFocusNode) { + DBG_NAV_LOG("!cachedFocusNode"); + return false; + } + DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p)", + cachedFocusNode->index(), + cachedFocusNode->nodePointer()); + jobject focusnode = env->GetObjectField(m_javaGlue.object(env).get(), m_javaGlue.m_focusNode); + LOG_ASSERT(focusnode, "Could not find WebView's FocusNode"); + + bool isTextArea = cachedFocusNode->isTextArea(); + bool isTextField = cachedFocusNode->isTextField(); + int maxLength; + jstring jName; + if (isTextField) { + maxLength = cachedFocusNode->maxLength(); + const WebCore::String& name = cachedFocusNode->name(); + jName = env->NewString((jchar*)name.characters(), name.length()); + } else { + maxLength = -1; + jName = 0; + } + WebCore::IntRect bounds = cachedFocusNode->bounds(); + WebCore::String value = cachedFocusNode->getExport(); + jstring val = !value.isEmpty() ? env->NewString((jchar *)value.characters(), value.length()) : 0; + env->CallVoidMethod(focusnode, m_javaGlue.m_setAll, isTextField, isTextArea, cachedFocusNode->isPassword(), + cachedFocusNode->isAnchor(), cachedFocusNode->isRtlText(), maxLength, cachedFocusNode->textSize(), + bounds.x(), bounds.y(), bounds.right(), bounds.bottom(), (int)(cachedFocusNode->nodePointer()), + (int)(cachedFrame->framePointer()), val, jName, root->textGeneration()); + env->DeleteLocalRef(val); + env->DeleteLocalRef(focusnode); + if (isTextField) + env->DeleteLocalRef(jName); + return true; +} + +void updateTextEntry() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_updateTextEntry); + checkException(env); +} + +void displaySoftKeyboard() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_displaySoftKeyboard); + 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); +} + +private: // local state for WebView + // private to getFrameCache(); other functions operate in a different thread + CachedRoot* m_frameCacheUI; // navigation data ready for use + FocusReplay m_replay; + WebViewCore* m_viewImpl; + WebCore::Node* m_invalidNode; + int m_generation; // associate unique ID with sent kit focus to match with ui + SkPicture* m_navPictureUI; + bool m_followedLink; + SkMSec m_ringAnimationEnd; + // Corresponds to the same-named boolean on the java side. + bool m_heightCanMeasure; + int m_lastDx; + SkMSec m_lastDxTime; + 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; +}; // end of WebView class + +/* + * Native JNI methods + */ +static jstring WebCoreStringToJString(JNIEnv *env, WebCore::String string) +{ + int length = string.length(); + if (!length) + return 0; + jstring ret = env->NewString((jchar *)string.characters(), length); + env->DeleteLocalRef(ret); + return ret; +} + +static void nativeClearFocus(JNIEnv *env, jobject obj, int x, int y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->clearFocus(x, y, true); +} + +static void nativeCreate(JNIEnv *env, jobject obj, int viewImpl) +{ + WebView* webview = new WebView(env, obj, viewImpl); + // NEED THIS OR SOMETHING LIKE IT! + //Release(obj); +} + +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 void nativeDrawMatches(JNIEnv *env, jobject obj, jobject canv) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawMatches(canvas); +} + +static void nativeDrawFocusRing(JNIEnv *env, jobject obj, + jobject canv) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawFocusRing(canvas); +} + +static void nativeDrawSelection(JNIEnv *env, jobject obj, + jobject canv, jint x, jint y, bool ex) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawSelection(canvas, x, y, ex); +} + +static void nativeDrawSelectionRegion(JNIEnv *env, jobject obj, jobject canv) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawSelectionRegion(canvas); +} + +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__); + WebCore::String uri = view->imageURI(x, y); + jstring ret = 0; + unsigned len = uri.length(); + if (len) { + ret = env->NewString((jchar*) uri.characters(), len); + env->DeleteLocalRef(ret); + } + return ret; +} + +static bool nativeFocusNodeWantsKeyEvents(JNIEnv* env, jobject jwebview) { + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->focusNodeWantsKeyEvents(); +} + +static void nativeInstrumentReport(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounter::reportNow(); +#endif +} + +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 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 nativeMarkNodeInvalid(JNIEnv *env, jobject obj, int node) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->markNodeInvalid((WebCore::Node*) node); +} + +static bool nativeMotionUp(JNIEnv *env, jobject obj, + int x, int y, int slop, bool isClick) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->motionUp(x, y, slop, isClick, true, false); +} + +static bool nativeUpdateFocusNode(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->updateFocusNode(env); +} + +static bool nativeMoveFocus(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->moveFocus(key, count, ignoreScroll, true, 0, 0); +} + +static void nativeNotifyFocusSet(JNIEnv *env, jobject obj, bool inEditingMode) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->notifyFocusSet((WebView::FrameCachePermission) inEditingMode); +} + +static void nativeRecomputeFocus(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->recomputeFocus(); +} + +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 nativeResetFocus(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->resetFocus(); +} + +static void nativeSetFindIsDown(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->setFindIsUp(false); +} + +static void nativeSetFollowedLink(JNIEnv *env, jobject obj, bool followed) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->setFollowedLink(followed); +} + +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 nativeGetFocusRingBounds(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->focusRingBounds(&webRect); + jobject rect = env->NewObject(rectClass, init, webRect.x(), + webRect.y(), webRect.right(), webRect.bottom()); + return rect; +} + +static jobject nativeGetNavBounds(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->getNavBounds(); + jobject rect = env->NewObject(rectClass, init, webRect.x(), + webRect.y(), webRect.right(), webRect.bottom()); + return rect; +} + +static void nativeSetNavBounds(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->setNavBounds(rect); +} + +static int nativeFindAll(JNIEnv *env, jobject obj, jstring findLower, + jstring findUpper) +{ + // 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"); + view->setFindIsUp(true); + 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); + canvas.drawPicture(*(root->getPicture())); + WTF::Vector<MatchInfo>* matches = canvas.detachMatches(); + // With setMatches, the WebView takes ownership of matches + view->setMatches(matches); + + 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 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->isTextField() && !cachedFocusNode->isTextArea())) + return; + WebCore::String webcoreString = to_string(env, updatedText); + (const_cast<CachedNode*>(cachedFocusNode))->setExport(webcoreString); + root->setTextGeneration(generation); + checkException(env); +} + +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 nativeMoveSelection(JNIEnv *env, jobject obj, int x, int y, bool ex) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->moveSelection(x, y, ex); +} + +static jobject nativeGetSelection(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return GraphicsJNI::createRegion(env, new SkRegion(view->getSelection())); +} + +#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__); + + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (root) { + SkPicture* picture = root->getPicture(); + if (picture) { + 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 + picture->draw(&canvas); + // we're done with the file now + fwrite("\n", 1, 1, file); + fclose(file); + } + } + } +#endif +} + +/* + * JNI registration + */ +static JNINativeMethod gJavaWebViewMethods[] = { + { "nativeFindAll", "(Ljava/lang/String;Ljava/lang/String;)I", + (void*) nativeFindAll }, + { "nativeFindNext", "(Z)V", + (void*) nativeFindNext }, + { "nativeClearFocus", "(II)V", + (void*) nativeClearFocus }, + { "nativeCreate", "(I)V", + (void*) nativeCreate }, + { "nativeDebugDump", "()V", + (void*) nativeDebugDump }, + { "nativeDestroy", "()V", + (void*) nativeDestroy }, + { "nativeDrawMatches", "(Landroid/graphics/Canvas;)V", + (void*) nativeDrawMatches }, + { "nativeDrawFocusRing", "(Landroid/graphics/Canvas;)V", + (void*) nativeDrawFocusRing }, + { "nativeDrawSelection", "(Landroid/graphics/Canvas;IIZ)V", + (void*) nativeDrawSelection }, + { "nativeDrawSelectionRegion", "(Landroid/graphics/Canvas;)V", + (void*) nativeDrawSelectionRegion }, + { "nativeUpdateFocusNode", "()Z", + (void*) nativeUpdateFocusNode }, + { "nativeGetFocusRingBounds", "()Landroid/graphics/Rect;", + (void*) nativeGetFocusRingBounds }, + { "nativeGetNavBounds", "()Landroid/graphics/Rect;", + (void*) nativeGetNavBounds }, + { "nativeInstrumentReport", "()V", + (void*) nativeInstrumentReport }, + { "nativeMarkNodeInvalid", "(I)V", + (void*) nativeMarkNodeInvalid }, + { "nativeMotionUp", "(IIIZ)Z", + (void*) nativeMotionUp }, + { "nativeMoveFocus", "(IIZ)Z", + (void*) nativeMoveFocus }, + { "nativeNotifyFocusSet", "(Z)V", + (void*) nativeNotifyFocusSet }, + { "nativeRecomputeFocus", "()V", + (void*) nativeRecomputeFocus }, + { "nativeRecordButtons", "(ZZZ)V", + (void*) nativeRecordButtons }, + { "nativeResetFocus", "()V", + (void*) nativeResetFocus }, + { "nativeSelectBestAt", "(Landroid/graphics/Rect;)V", + (void*) nativeSelectBestAt }, + { "nativeSetFindIsDown", "()V", + (void*) nativeSetFindIsDown }, + { "nativeSetFollowedLink", "(Z)V", + (void*) nativeSetFollowedLink }, + { "nativeSetHeightCanMeasure", "(Z)V", + (void*) nativeSetHeightCanMeasure }, + { "nativeSetNavBounds", "(Landroid/graphics/Rect;)V", + (void*) nativeSetNavBounds }, + { "nativeImageURI", "(II)Ljava/lang/String;", + (void*) nativeImageURI }, + { "nativeFocusNodeWantsKeyEvents", "()Z", + (void*)nativeFocusNodeWantsKeyEvents }, + { "nativeUpdateCachedTextfield", "(Ljava/lang/String;I)V", + (void*) nativeUpdateCachedTextfield }, + { "nativeMoveSelection", "(IIZ)V", + (void*) nativeMoveSelection }, + { "nativeGetSelection", "()Landroid/graphics/Region;", + (void*) nativeGetSelection }, + { "nativeDumpDisplayTree", "(Ljava/lang/String;)V", + (void*) nativeDumpDisplayTree } +}; + +int register_webview(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"); + + return jniRegisterNativeMethods(env, "android/webkit/WebView", gJavaWebViewMethods, NELEM(gJavaWebViewMethods)); +} + +} // namespace android diff --git a/WebKit/android/plugins/ANPCanvasInterface.cpp b/WebKit/android/plugins/ANPCanvasInterface.cpp new file mode 100644 index 0000000..ba79691 --- /dev/null +++ b/WebKit/android/plugins/ANPCanvasInterface.cpp @@ -0,0 +1,187 @@ +/* + * 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 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. + */ + +// 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_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_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, getTotalMatrix); + ASSIGN(i, getLocalClipBounds); + ASSIGN(i, getDeviceClipBounds); + ASSIGN(i, drawColor); + ASSIGN(i, drawPaint); + ASSIGN(i, drawRect); + ASSIGN(i, drawOval); + ASSIGN(i, drawPath); + ASSIGN(i, drawText); + ASSIGN(i, drawPosText); + ASSIGN(i, drawBitmap); + ASSIGN(i, drawBitmapRect); +} + diff --git a/WebKit/android/plugins/ANPKeyCodes.h b/WebKit/android/plugins/ANPKeyCodes.h new file mode 100644 index 0000000..5213f57 --- /dev/null +++ b/WebKit/android/plugins/ANPKeyCodes.h @@ -0,0 +1,125 @@ +/* + * 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 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 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 include/ui/KeycodeLabels.h, which contains more than + I want to publish to plugin authors. +*/ +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 +}; + +#endif + diff --git a/WebKit/android/plugins/ANPLogInterface.cpp b/WebKit/android/plugins/ANPLogInterface.cpp new file mode 100644 index 0000000..6ccca0a --- /dev/null +++ b/WebKit/android/plugins/ANPLogInterface.cpp @@ -0,0 +1,61 @@ +/* + * 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 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 "plugin" + +#include "utils/Log.h" +#include "android_npapi.h" +#include <stdarg.h> + +static void anp_log(NPP inst, 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/WebKit/android/plugins/ANPMatrixInterface.cpp b/WebKit/android/plugins/ANPMatrixInterface.cpp new file mode 100644 index 0000000..815b954 --- /dev/null +++ b/WebKit/android/plugins/ANPMatrixInterface.cpp @@ -0,0 +1,168 @@ +/* + * 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. + */ + +// 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/WebKit/android/plugins/ANPPaintInterface.cpp b/WebKit/android/plugins/ANPPaintInterface.cpp new file mode 100644 index 0000000..4b561f0 --- /dev/null +++ b/WebKit/android/plugins/ANPPaintInterface.cpp @@ -0,0 +1,213 @@ +/* + * 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 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. + */ + +// 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/WebKit/android/plugins/ANPSoundInterface.cpp b/WebKit/android/plugins/ANPSoundInterface.cpp new file mode 100644 index 0000000..6b019d1 --- /dev/null +++ b/WebKit/android/plugins/ANPSoundInterface.cpp @@ -0,0 +1,149 @@ +/* + * 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 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "android_npapi.h" + +#include "media/AudioTrack.h" + +struct ANPAudioTrack { + void* mUser; + ANPAudioCallbackProc mProc; + android::AudioTrack* mTrack; +}; + +static ANPSampleFormat toANPFormat(int fm) { + switch (fm) { + case android::AudioSystem::PCM_16_BIT: + return kPCM16Bit_ANPSampleFormat; + case android::AudioSystem::PCM_8_BIT: + return kPCM8Bit_ANPSampleFormat; + default: + return kUnknown_ANPSamleFormat; + } +} + +static android::AudioSystem::audio_format fromANPFormat(ANPSampleFormat fm) { + switch (fm) { + case kPCM16Bit_ANPSampleFormat: + return android::AudioSystem::PCM_16_BIT; + case kPCM8Bit_ANPSampleFormat: + return android::AudioSystem::PCM_8_BIT; + default: + return android::AudioSystem::INVALID_FORMAT; + } +} + +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(android::AudioSystem::MUSIC, + sampleRate, + fromANPFormat(format), + channelCount, + 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(); +} + +/////////////////////////////////////////////////////////////////////////////// + +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; +} + diff --git a/WebKit/android/plugins/ANPTypefaceInterface.cpp b/WebKit/android/plugins/ANPTypefaceInterface.cpp new file mode 100644 index 0000000..5878f3f --- /dev/null +++ b/WebKit/android/plugins/ANPTypefaceInterface.cpp @@ -0,0 +1,74 @@ +/* + * 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 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" + +static ANPTypeface* anp_createFromName(const char name[], ANPTypefaceStyle s) { + SkTypeface* tf = SkTypeface::Create(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) { + tf->safeRef(); +} + +static void anp_unref(ANPTypeface* tf) { + tf->safeUnref(); +} + +static ANPTypefaceStyle anp_getStyle(const ANPTypeface* tf) { + SkTypeface::Style s = tf ? tf->getStyle() : SkTypeface::kNormal; + return static_cast<ANPTypefaceStyle>(s); +} + +/////////////////////////////////////////////////////////////////////////////// + +#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); +} + diff --git a/WebKit/android/plugins/ANPWindowInterface.cpp b/WebKit/android/plugins/ANPWindowInterface.cpp new file mode 100644 index 0000000..4aa862b --- /dev/null +++ b/WebKit/android/plugins/ANPWindowInterface.cpp @@ -0,0 +1,62 @@ +/* + * 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 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.h" + +static bool anp_lockRect(void* window, const ANPRectI* inval, + ANPBitmap* bitmap) { + if (window) { + // todo + return true; + } + return false; +} + +static bool anp_lockRegion(void* window, const ANPRegion* inval, + ANPBitmap* bitmap) { + if (window) { + // todo + return true; + } + return false; +} + +static void anp_unlock(void* window) { +} + +/////////////////////////////////////////////////////////////////////////////// + +#define ASSIGN(obj, name) (obj)->name = anp_##name + +void ANPWindowInterfaceV0_Init(ANPInterface* value) { + ANPWindowInterfaceV0* i = reinterpret_cast<ANPWindowInterfaceV0*>(value); + + ASSIGN(i, lockRect); + ASSIGN(i, lockRegion); + ASSIGN(i, unlock); +} + diff --git a/WebKit/android/plugins/PluginDebugAndroid.h b/WebKit/android/plugins/PluginDebugAndroid.h new file mode 100644 index 0000000..992e953 --- /dev/null +++ b/WebKit/android/plugins/PluginDebugAndroid.h @@ -0,0 +1,46 @@ +/* + * 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 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 PLUGIN_DEBUG_ANDROID_H__ +#define PLUGIN_DEBUG_ANDROID_H__ + +#ifdef ANDROID_PLUGINS + +// 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 direectory. +#define PLUGIN_DEBUG_GLOBAL 0 + +#if PLUGIN_DEBUG_GLOBAL || defined(PLUGIN_DEBUG_LOCAL) +# define PLUGIN_LOG(A, B...) do { LOGI( A , ## B ); } while(0) +#else +# define PLUGIN_LOG(A, B...) do { } while(0) +#endif + +#endif + +#endif // defined(PLUGIN_DEBUG_ANDROID_H__) diff --git a/WebKit/android/plugins/PluginTimer.cpp b/WebKit/android/plugins/PluginTimer.cpp new file mode 100644 index 0000000..1097330 --- /dev/null +++ b/WebKit/android/plugins/PluginTimer.cpp @@ -0,0 +1,112 @@ +/* + * 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 "PluginTimer.h" + +namespace WebCore { + + static uint32 gTimerID; + + PluginTimer::PluginTimer(PluginTimer** list, NPP instance, bool repeat, + void (*timerFunc)(NPP npp, uint32 timerID)) + : m_list(list), + m_instance(instance), + m_timerFunc(timerFunc), + m_repeat(repeat) + { + m_timerID = ++gTimerID; + + m_next = *list; + if (m_next) { + m_next->m_prev = this; + } + m_prev = 0; + *list = this; + } + + 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() + { + m_timerFunc(m_instance, m_timerID); + if (!m_repeat) { + delete this; + } + } + + // may return null if timerID is not found + PluginTimer* PluginTimer::Find(PluginTimer* list, uint32 timerID) + { + PluginTimer* curr = list; + while (curr) { + if (curr->m_timerID == timerID) { + break; + } + curr = curr->m_next; + } + return curr; + } + + /////////////////////////////////////////////////////////////////////////// + + PluginTimerList::~PluginTimerList() + { + while (m_list) { + delete m_list; + } + } + + uint32 PluginTimerList::schedule(NPP instance, uint32 interval, bool repeat, + void (*proc)(NPP npp, uint32 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 timerID) + { + delete PluginTimer::Find(m_list, timerID); + } + +} // namespace WebCore diff --git a/WebKit/android/plugins/PluginTimer.h b/WebKit/android/plugins/PluginTimer.h new file mode 100644 index 0000000..ccb49da --- /dev/null +++ b/WebKit/android/plugins/PluginTimer.h @@ -0,0 +1,78 @@ +/* + * 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 PluginTimer_H +#define PluginTimer_H + +#include "Timer.h" +#include "npapi.h" + +namespace WebCore { + + class PluginTimerList; + + class PluginTimer : public TimerBase { + public: + PluginTimer(PluginTimer** list, NPP instance, bool repeat, + void (*proc)(NPP npp, uint32 timerID)); + virtual ~PluginTimer(); + + uint32 timerID() const { return m_timerID; } + + static PluginTimer* Find(PluginTimer* list, uint32 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); + uint32 m_timerID; + bool m_repeat; + }; + + class PluginTimerList { + public: + PluginTimerList() : m_list(0) {} + ~PluginTimerList(); + + uint32 schedule(NPP instance, uint32 interval, bool repeat, + void (*proc)(NPP npp, uint32 timerID)); + void unschedule(NPP instance, uint32 timerID); + + private: + PluginTimer* m_list; + }; + +} // namespace WebCore + +#endif diff --git a/WebKit/android/plugins/PluginViewBridgeAndroid.cpp b/WebKit/android/plugins/PluginViewBridgeAndroid.cpp new file mode 100644 index 0000000..945115a --- /dev/null +++ b/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 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 "PluginViewBridgeAndroid.h" + +namespace WebCore { + + void PluginViewBridgeAndroid::draw(GraphicsContext* gc, + const IntRect& rect) {} + + bool PluginViewBridgeAndroid::forPluginView() const { + return true; + } + +} diff --git a/WebKit/android/plugins/PluginViewBridgeAndroid.h b/WebKit/android/plugins/PluginViewBridgeAndroid.h new file mode 100644 index 0000000..e17a2dc --- /dev/null +++ b/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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 APPLE COMPUTER, INC. ``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 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/WebKit/android/plugins/PluginWidgetAndroid.cpp b/WebKit/android/plugins/PluginWidgetAndroid.cpp new file mode 100644 index 0000000..ea13a0c --- /dev/null +++ b/WebKit/android/plugins/PluginWidgetAndroid.cpp @@ -0,0 +1,146 @@ +/* + * 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 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 "android_graphics.h" +#include "PluginPackage.h" +#include "PluginView.h" +#include "PluginWidgetAndroid.h" +#include "SkANP.h" +#include "SkFlipPixelRef.h" +#include "WebViewCore.h" + +PluginWidgetAndroid::PluginWidgetAndroid(WebCore::PluginView* view) + : m_pluginView(view) { + m_flipPixelRef = NULL; + m_core = NULL; + m_drawingModel = kBitmap_ANPDrawingModel; + m_x = m_y = 0; +} + +PluginWidgetAndroid::~PluginWidgetAndroid() { + if (m_core) { + m_core->removePlugin(this); + } + m_flipPixelRef->safeUnref(); +} + +void PluginWidgetAndroid::init(android::WebViewCore* core) { + m_core = core; + m_core->addPlugin(this); +} + +static SkBitmap::Config computeConfig(bool isTransparent) { + return isTransparent ? SkBitmap::kARGB_8888_Config + : SkBitmap::kRGB_565_Config; +} + +void PluginWidgetAndroid::setWindow(int x, int y, int width, int height, + bool isTransparent) { + m_x = x; + m_y = y; + m_flipPixelRef->safeUnref(); + m_flipPixelRef = new SkFlipPixelRef(computeConfig(isTransparent), + width, height); +} + +void PluginWidgetAndroid::setDrawingModel(ANPDrawingModel model) { + m_drawingModel = model; +} + +void PluginWidgetAndroid::localToPageCoords(SkIRect* rect) const { + rect->offset(m_x, m_y); +} + +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(); + } + return true; + } +} + +void PluginWidgetAndroid::inval(const WebCore::IntRect& rect, + bool signalRedraw) { + // nothing to do if we haven't had setWindow() called yet + if (NULL == m_flipPixelRef) { + return; + } + + SkIRect r; + m_flipPixelRef->inval(*android_setrect(&r, rect)); + + if (signalRedraw && m_flipPixelRef->isDirty()) { + m_core->invalPlugin(this); + } +} + +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.drawContext.model = m_drawingModel; + SkANP::SetRect(&event.data.drawContext.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.drawContext.data.bitmap, + bitmap) && + pkg->pluginFuncs()->event(instance, &event)) { + + if (canvas) { + SkBitmap bm(bitmap); + bm.setPixelRef(m_flipPixelRef); + canvas->drawBitmap(bm, SkIntToScalar(m_x), + SkIntToScalar(m_y), NULL); + } + } + break; + } + default: + break; + } +} + diff --git a/WebKit/android/plugins/PluginWidgetAndroid.h b/WebKit/android/plugins/PluginWidgetAndroid.h new file mode 100644 index 0000000..f5e9363 --- /dev/null +++ b/WebKit/android/plugins/PluginWidgetAndroid.h @@ -0,0 +1,94 @@ +/* + * 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 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 PluginWidgetAndroid_H +#define PluginWidgetAndroid_H + +#include "android_npapi.h" + +namespace WebCore { + class PluginView; +} + +namespace android { + 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(); + + /* 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(int x, int y, int width, int height, bool isTransparent); + /* Called whenever the plugin itself requests a new drawing model + */ + void setDrawingModel(ANPDrawingModel); + + /* Utility method to convert from local (plugin) coordinates to docuemnt + coordinates. Needed (for instance) to convert the dirty rectangle into + document coordinates to inturn inval the screen. + */ + void localToPageCoords(SkIRect*) const; + + /* Returns true (and optionally updates rect with the dirty bounds) 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); + +private: + WebCore::PluginView* m_pluginView; + android::WebViewCore* m_core; + SkFlipPixelRef* m_flipPixelRef; + ANPDrawingModel m_drawingModel; + int m_x; + int m_y; +}; + +#endif diff --git a/WebKit/android/plugins/SkANP.cpp b/WebKit/android/plugins/SkANP.cpp new file mode 100644 index 0000000..3912f99 --- /dev/null +++ b/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 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. + */ + +// must include config.h first for webkit to fiddle with new/delete +#include "config.h" +#include "SkANP.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/WebKit/android/plugins/SkANP.h b/WebKit/android/plugins/SkANP.h new file mode 100644 index 0000000..f319c9b --- /dev/null +++ b/WebKit/android/plugins/SkANP.h @@ -0,0 +1,80 @@ +/* + * 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 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 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/WebKit/android/plugins/android_npapi.h b/WebKit/android/plugins/android_npapi.h new file mode 100644 index 0000000..f64c8ce --- /dev/null +++ b/WebKit/android/plugins/android_npapi.h @@ -0,0 +1,620 @@ +/* + * 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 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. + */ + +/* 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 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 kTypefaceInterfaceV0_ANPGetValue ((NPNVariable)1005) +#define kWindowInterfaceV0_ANPGetValue ((NPNVariable)1006) + +/* queries for which drawing model is desired (for the draw event) + + Should be called inside NPP_New(...) + + NPN_GetValue(inst, ANPSupportedDrawingModel_EnumValue, uint32_t* bits) + */ +#define kSupportedDrawingModel_ANPGetValue ((NPNVariable)2000) + +/////////////////////////////////////////////////////////////////////////////// +// NPN_GetValue + +/** Reqeust to set the drawing model. + + NPN_SetValue(inst, ANPRequestDrawingModel_EnumValue, (void*)foo_DrawingModel) + */ +#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 = 0, +}; +typedef int32_t ANPDrawingModel; + +/* Interfaces provide additional functionality to the plugin via function ptrs. + Once an interface is retrived, 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)(NPP instance, ANPLogType, const char format[], ...); +}; + +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); +}; + +typedef uint32_t ANPColor; +#define ANP_MAKE_COLOR(a, r, g, b) \ + (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) + +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; + +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 + + @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*); +}; + +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 (*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 { + /** Given the window field from the NPWindow struct, and an optional rect + describing the subset of the window that will be drawn to (may be null) + return true if the bitmap for that window can be accessed, and if so, + fill out the specified ANPBitmap to point to the window's pixels. + + When drawing is complete, call unlock(window) + */ + bool (*lockRect)(void* window, const ANPRectI* inval, ANPBitmap*); + /** The same as lockRect, but takes a region instead of a rect to specify + the area that will be changed/drawn. + */ + bool (*lockRegion)(void* window, const ANPRegion* inval, ANPBitmap*); + /** Given a successful call to lock(window, inval, &bitmap), call unlock + to release access to the pixels, and allow the browser to display the + results. If lock returned false, unlock should not be called. + */ + void (*unlock)(void* window); +}; + +/////////////////////////////////////////////////////////////////////////////// + +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. + */ + ANPAudioTrack* (*newTrack)(uint32_t sampleRate, // sampling rate in Hz + ANPSampleFormat, + int channelCount, // MONO=1, STEREO=2 + ANPAudioCallbackProc, + void* user); + 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*); +}; + +/////////////////////////////////////////////////////////////////////////////// +// HandleEvent + +enum ANPEventTypes { + kNull_ANPEventType = 0, + kKey_ANPEventType = 1, + kTouch_ANPEventType = 2, + kDraw_ANPEventType = 3, +}; +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 ANPTouchActions { + kDown_ANPTouchAction = 0, + kUp_ANPTouchAction = 1, +}; +typedef int32_t ANPTouchAction; + +struct ANPDrawContext { + ANPDrawingModel model; + // relative to (0,0) in top-left of your plugin + ANPRectI clip; + // use based on the value in model + union { + ANPBitmap bitmap; + } data; +}; + +/* 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 { + ANPTouchAction action; + ANPKeyModifier modifiers; + int32_t x; // relative to your "window" (0...width) + int32_t y; // relative to your "window" (0...height) + } touch; + ANPDrawContext drawContext; + int32_t other[8]; + } data; +}; + +#endif + diff --git a/WebKit/android/plugins/sample/Android.mk b/WebKit/android/plugins/sample/Android.mk new file mode 100644 index 0000000..328ddc5 --- /dev/null +++ b/WebKit/android/plugins/sample/Android.mk @@ -0,0 +1,50 @@ +## +## +## 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 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 \ + PluginObject.cpp \ + pluginGraphics.cpp + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH) \ + external/webkit/WebCore/bridge \ + external/webkit/WebCore/plugins \ + external/webkit/WebCore/platform/android/JavaVM \ + external/webkit/WebKit/android/plugins + +LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) +LOCAL_CFLAGS += -fvisibility=hidden +LOCAL_PRELINK_MODULE:=false +LOCAL_MODULE_CLASS := SHARED_LIBRARIES + +LOCAL_MODULE:= browsertestplugin + +include $(BUILD_SHARED_LIBRARY) + diff --git a/WebKit/android/plugins/sample/PluginObject.cpp b/WebKit/android/plugins/sample/PluginObject.cpp new file mode 100644 index 0000000..5499072 --- /dev/null +++ b/WebKit/android/plugins/sample/PluginObject.cpp @@ -0,0 +1,178 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include "main.h" +#include "PluginObject.h" + +static void pluginInvalidate(NPObject *obj); +static bool pluginHasProperty(NPObject *obj, NPIdentifier name); +static bool pluginHasMethod(NPObject *obj, NPIdentifier name); +static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant); +static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant); +static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result); +static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result); +static NPObject *pluginAllocate(NPP npp, NPClass *theClass); +static void pluginDeallocate(NPObject *obj); +static bool pluginRemoveProperty(NPObject *npobj, NPIdentifier name); +static bool pluginEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count); + + + +static NPClass pluginClass = { + NP_CLASS_STRUCT_VERSION, + pluginAllocate, + pluginDeallocate, + pluginInvalidate, + pluginHasMethod, + pluginInvoke, + pluginInvokeDefault, + pluginHasProperty, + pluginGetProperty, + pluginSetProperty, + pluginRemoveProperty, + pluginEnumerate +}; + +NPClass *getPluginClass(void) +{ + return &pluginClass; +} + +static bool identifiersInitialized = false; + +#define ID_TESTFILE_PROPERTY 0 +#define NUM_PROPERTY_IDENTIFIERS 1 + +static NPIdentifier pluginPropertyIdentifiers[NUM_PROPERTY_IDENTIFIERS]; +static const NPUTF8 *pluginPropertyIdentifierNames[NUM_PROPERTY_IDENTIFIERS] = { + "testfile" +}; + +#define ID_GETTESTFILE_METHOD 0 +#define NUM_METHOD_IDENTIFIERS 1 + +static NPIdentifier pluginMethodIdentifiers[NUM_METHOD_IDENTIFIERS]; +static const NPUTF8 *pluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = { + "getTestFile" +}; + +static void initializeIdentifiers(void) +{ + browser->getstringidentifiers(pluginPropertyIdentifierNames, NUM_PROPERTY_IDENTIFIERS, pluginPropertyIdentifiers); + browser->getstringidentifiers(pluginMethodIdentifierNames, NUM_METHOD_IDENTIFIERS, pluginMethodIdentifiers); +} + +static bool pluginHasProperty(NPObject *obj, NPIdentifier name) +{ + int i; + for (i = 0; i < NUM_PROPERTY_IDENTIFIERS; i++) + if (name == pluginPropertyIdentifiers[i]) + return true; + return false; +} + +static bool pluginHasMethod(NPObject *obj, NPIdentifier name) +{ + int i; + for (i = 0; i < NUM_METHOD_IDENTIFIERS; i++) + if (name == pluginMethodIdentifiers[i]) + return true; + return false; +} + +static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant) +{ + PluginObject *plugin = (PluginObject *)obj; + if (name == pluginPropertyIdentifiers[ID_TESTFILE_PROPERTY]) { + BOOLEAN_TO_NPVARIANT(true, *variant); + return true; + } + return false; +} + +static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant) +{ + return false; +} + +static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + PluginObject *plugin = (PluginObject *)obj; + if (name == pluginMethodIdentifiers[ID_GETTESTFILE_METHOD]) { + return true; + } + return false; +} + +static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + return false; +} + +static void pluginInvalidate(NPObject *obj) +{ + // Release any remaining references to JavaScript objects. +} + +static NPObject *pluginAllocate(NPP npp, NPClass *theClass) +{ + PluginObject *newInstance = (PluginObject*) malloc(sizeof(PluginObject)); + newInstance->header._class = theClass; + newInstance->header.referenceCount = 1; + + if (!identifiersInitialized) { + identifiersInitialized = true; + initializeIdentifiers(); + } + + newInstance->npp = npp; + + return &newInstance->header; +} + +static void pluginDeallocate(NPObject *obj) +{ + free(obj); +} + +static bool pluginRemoveProperty(NPObject *npobj, NPIdentifier name) +{ + return false; +} + +static bool pluginEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count) +{ + return false; +} + diff --git a/WebKit/android/plugins/sample/PluginObject.h b/WebKit/android/plugins/sample/PluginObject.h new file mode 100644 index 0000000..ae8963d --- /dev/null +++ b/WebKit/android/plugins/sample/PluginObject.h @@ -0,0 +1,70 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation, + modification or redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in + this original Apple software (the "Apple Software"), to use, reproduce, modify and + redistribute the Apple Software, with or without modifications, in source and/or binary + forms; provided that if you redistribute the Apple Software in its entirety and without + modifications, you must retain this notice and the following text and disclaimers in all + such redistributions of the Apple Software. Neither the name, trademarks, service marks + or logos of Apple Computer, Inc. may be used to endorse or promote products derived from + the Apple Software without specific prior written permission from Apple. Except as expressly + stated in this notice, no other rights or licenses, express or implied, are granted by Apple + herein, including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PluginObject__DEFINED +#define PluginObject__DEFINED + +#include "npapi.h" + +struct ANPCanvas; +struct ANPAudioTrack; + +class Animation { +public: + Animation(NPP inst) : m_inst(inst) {} + virtual ~Animation() {} + virtual void draw(ANPCanvas*) = 0; + + NPP inst() const { return m_inst; } + +private: + NPP m_inst; +}; + +typedef struct PluginObject { + NPObject header; + NPP npp; + NPWindow* window; + Animation* anim; + ANPAudioTrack* track; + int32_t mUnichar; + + bool mTestTimers; + uint32_t mStartTime; + uint32_t mPrevTime; + int mTimerCount; +} PluginObject; + +NPClass *getPluginClass(void); + +#endif // PluginObject__DEFINED diff --git a/WebKit/android/plugins/sample/main.cpp b/WebKit/android/plugins/sample/main.cpp new file mode 100644 index 0000000..e34cee6 --- /dev/null +++ b/WebKit/android/plugins/sample/main.cpp @@ -0,0 +1,435 @@ +/* + * 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 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 <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "main.h" +#include "PluginObject.h" +#include "pluginGraphics.h" +#include "android_npapi.h" + +NPNetscapeFuncs* browser; +#define EXPORT __attribute__((visibility("default"))) + +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, + char* argn[], char* argv[], NPSavedData* saved); +NPError NPP_Destroy(NPP instance, NPSavedData** save); +NPError NPP_SetWindow(NPP instance, NPWindow* window); +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason); +int32 NPP_WriteReady(NPP instance, NPStream* stream); +int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, + void* buffer); +void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname); +void NPP_Print(NPP instance, NPPrint* platformPrint); +int16 NPP_HandleEvent(NPP instance, void* event); +void NPP_URLNotify(NPP instance, const char* URL, NPReason reason, + void* notifyData); +NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value); +NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value); + +extern "C" { +EXPORT NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env, void *application_context); +EXPORT NPError NP_GetValue(NPP instance, NPPVariable variable, void *value); +EXPORT const char* NP_GetMIMEDescription(void); +EXPORT void NP_Shutdown(void); +}; + +ANPAudioTrackInterfaceV0 gSoundI; +ANPCanvasInterfaceV0 gCanvasI; +ANPLogInterfaceV0 gLogI; +ANPPaintInterfaceV0 gPaintI; +ANPTypefaceInterfaceV0 gTypefaceI; + +#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) + +NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env, void *application_context) +{ + // Make sure we have a function table equal or larger than we are built against. + if (browserFuncs->size < sizeof(NPNetscapeFuncs)) { + return NPERR_GENERIC_ERROR; + } + + // Copy the function table (structure) + browser = (NPNetscapeFuncs*) malloc(sizeof(NPNetscapeFuncs)); + memcpy(browser, browserFuncs, sizeof(NPNetscapeFuncs)); + + // Build the plugin function table + pluginFuncs->version = 11; + pluginFuncs->size = sizeof(pluginFuncs); + pluginFuncs->newp = NPP_New; + pluginFuncs->destroy = NPP_Destroy; + pluginFuncs->setwindow = NPP_SetWindow; + pluginFuncs->newstream = NPP_NewStream; + pluginFuncs->destroystream = NPP_DestroyStream; + pluginFuncs->asfile = NPP_StreamAsFile; + pluginFuncs->writeready = NPP_WriteReady; + pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write; + pluginFuncs->print = NPP_Print; + pluginFuncs->event = NPP_HandleEvent; + pluginFuncs->urlnotify = NPP_URLNotify; + pluginFuncs->getvalue = NPP_GetValue; + pluginFuncs->setvalue = NPP_SetValue; + + static const struct { + NPNVariable v; + uint32_t size; + ANPInterface* i; + } gPairs[] = { + { kLogInterfaceV0_ANPGetValue, sizeof(gLogI), &gLogI }, + { kCanvasInterfaceV0_ANPGetValue, sizeof(gCanvasI), &gCanvasI }, + { kPaintInterfaceV0_ANPGetValue, sizeof(gPaintI), &gPaintI }, + { kTypefaceInterfaceV0_ANPGetValue, sizeof(gPaintI), &gTypefaceI }, + { kAudioTrackInterfaceV0_ANPGetValue, sizeof(gSoundI), &gSoundI }, + }; + for (size_t i = 0; i < ARRAY_COUNT(gPairs); i++) { + gPairs[i].i->inSize = gPairs[i].size; + NPError err = browser->getvalue(NULL, gPairs[i].v, gPairs[i].i); + if (err) { + return err; + } + } + + return NPERR_NO_ERROR; +} + +void NP_Shutdown(void) +{ + +} + +const char *NP_GetMIMEDescription(void) +{ + return "application/x-testplugin:tst:Test plugin mimetype is application/x-testplugin"; +} + +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, + char* argn[], char* argv[], NPSavedData* saved) +{ + PluginObject *obj = NULL; + + // Scripting functions appeared in NPAPI version 14 + if (browser->version >= 14) { + instance->pdata = browser->createobject (instance, getPluginClass()); + obj = static_cast<PluginObject*>(instance->pdata); + bzero(obj, sizeof(*obj)); + } + + uint32_t bits; + NPError err = browser->getvalue(instance, kSupportedDrawingModel_ANPGetValue, &bits); + if (err) { + gLogI.log(instance, kError_ANPLogType, "supported model err %d", err); + return err; + } + + ANPDrawingModel model = kBitmap_ANPDrawingModel; + + int count = argc; + for (int i = 0; i < count; i++) { + if (!strcmp(argn[i], "DrawingModel")) { + if (!strcmp(argv[i], "Bitmap")) { + model = kBitmap_ANPDrawingModel; + } + if (!strcmp(argv[i], "Canvas")) { + // obj->mTestTimers = true; + } + gLogI.log(instance, kDebug_ANPLogType, "------ %p DrawingModel is %d", instance, model); + break; + } + } + + // comment this out to draw via bitmaps (the default) + err = browser->setvalue(instance, kRequestDrawingModel_ANPSetValue, + reinterpret_cast<void*>(model)); + if (err) { + gLogI.log(instance, kError_ANPLogType, "request model %d err %d", model, err); + } + return err; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) +{ + PluginObject *obj = (PluginObject*) instance->pdata; + delete obj->anim; + gSoundI.deleteTrack(obj->track); + + return NPERR_NO_ERROR; +} + +static void timer_oneshot(NPP instance, uint32 timerID) { + gLogI.log(instance, kDebug_ANPLogType, "-------- oneshot timer\n"); +} + +static int gTimerRepeatCount; +static void timer_repeat(NPP instance, uint32 timerID) { + + gLogI.log(instance, kDebug_ANPLogType, "-------- repeat timer %d\n", + gTimerRepeatCount); + if (--gTimerRepeatCount == 0) { + browser->unscheduletimer(instance, timerID); + } +} + +static void timer_neverfires(NPP instance, uint32 timerID) { + gLogI.log(instance, kError_ANPLogType, "-------- timer_neverfires!!!\n"); +} + +#define TIMER_INTERVAL 50 + +static void timer_latency(NPP instance, uint32 timerID) { + PluginObject *obj = (PluginObject*) instance->pdata; + + obj->mTimerCount += 1; + + uint32_t now = getMSecs(); + uint32_t interval = now - obj->mPrevTime; + + uint32_t dur = now - obj->mStartTime; + uint32_t expectedDur = obj->mTimerCount * TIMER_INTERVAL; + int32_t drift = dur - expectedDur; + int32_t aveDrift = drift / obj->mTimerCount; + + obj->mPrevTime = now; + + gLogI.log(instance, kDebug_ANPLogType, + "-------- latency test: [%3d] interval %d expected %d, total %d expected %d, drift %d ave %d\n", + obj->mTimerCount, interval, TIMER_INTERVAL, dur, expectedDur, + drift, aveDrift); +} + +NPError NPP_SetWindow(NPP instance, NPWindow* window) +{ + PluginObject *obj = (PluginObject*) instance->pdata; + + // Do nothing if browser didn't support NPN_CreateObject which would have created the PluginObject. + if (obj != NULL) { + obj->window = window; + } + + static bool gTestTimers; + if (!gTestTimers) { + gTestTimers = true; + // test for bogus timerID + browser->unscheduletimer(instance, 999999); + // test oneshot + browser->scheduletimer(instance, 100, false, timer_oneshot); + // test repeat + gTimerRepeatCount = 10; + browser->scheduletimer(instance, 50, true, timer_repeat); + // test unschedule immediately + uint32 id = browser->scheduletimer(instance, 100, false, timer_neverfires); + browser->unscheduletimer(instance, id); + // test double unschedlue (should be no-op) + browser->unscheduletimer(instance, id); + } + + if (obj->mTestTimers) { + browser->scheduletimer(instance, TIMER_INTERVAL, true, timer_latency); + obj->mStartTime = obj->mPrevTime = getMSecs(); + obj->mTestTimers = false; + } + + browser->invalidaterect(instance, NULL); + + return NPERR_NO_ERROR; +} + + +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype) +{ + *stype = NP_ASFILEONLY; + return NPERR_NO_ERROR; +} + +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) +{ + return NPERR_NO_ERROR; +} + +int32 NPP_WriteReady(NPP instance, NPStream* stream) +{ + return 0; +} + +int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer) +{ + return 0; +} + +void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname) +{ +} + +void NPP_Print(NPP instance, NPPrint* platformPrint) +{ + +} + +struct SoundPlay { + NPP instance; + ANPAudioTrack* track; + FILE* file; +}; + +static void audioCallback(ANPAudioEvent evt, void* user, ANPAudioBuffer* buffer) { + switch (evt) { + case kMoreData_ANPAudioEvent: { + SoundPlay* play = reinterpret_cast<SoundPlay*>(user); + size_t amount = fread(buffer->bufferData, 1, buffer->size, play->file); + buffer->size = amount; + if (amount == 0) { + gSoundI.stop(play->track); + fclose(play->file); + play->file = NULL; + // need to notify our main thread to delete the track now + } + break; + } + default: + break; + } +} + +static ANPAudioTrack* createTrack(NPP instance, const char path[]) { + FILE* f = fopen(path, "r"); + gLogI.log(instance, kWarning_ANPLogType, "--- path %s FILE %p", path, f); + if (NULL == f) { + return NULL; + } + SoundPlay* play = new SoundPlay; + play->file = f; + play->track = gSoundI.newTrack(44100, kPCM16Bit_ANPSampleFormat, 2, audioCallback, play); + if (NULL == play->track) { + fclose(f); + delete play; + return NULL; + } + return play->track; +} + +int16 NPP_HandleEvent(NPP instance, void* event) +{ + PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata); + const ANPEvent* evt = reinterpret_cast<const ANPEvent*>(event); + + switch (evt->eventType) { + case kDraw_ANPEventType: + switch (evt->data.drawContext.model) { + case kBitmap_ANPDrawingModel: + drawPlugin(instance, evt->data.drawContext.data.bitmap, + evt->data.drawContext.clip); + return 1; + default: + break; // unknown drawing model + } + + case kKey_ANPEventType: + gLogI.log(instance, kDebug_ANPLogType, "---- %p Key action=%d" + " code=%d vcode=%d unichar=%d repeat=%d mods=%x", instance, + evt->data.key.action, + evt->data.key.nativeCode, + evt->data.key.virtualCode, + evt->data.key.unichar, + evt->data.key.repeatCount, + evt->data.key.modifiers); + if (evt->data.key.action == kDown_ANPKeyAction) { + obj->mUnichar = evt->data.key.unichar; + browser->invalidaterect(instance, NULL); + } + return 1; + + case kTouch_ANPEventType: + gLogI.log(instance, kDebug_ANPLogType, "---- %p Touch action=%d [%d %d]", + instance, evt->data.touch.action, evt->data.touch.x, + evt->data.touch.y); + if (kUp_ANPTouchAction == evt->data.touch.action) { + if (obj->track) { + if (gSoundI.isStopped(obj->track)) { + gSoundI.start(obj->track); + } else { + gSoundI.pause(obj->track); + } + } else { + obj->track = createTrack(instance, "/sdcard/sample.snd"); + gLogI.log(instance, kDebug_ANPLogType, "track %p %d", + obj->track, gSoundI.isStopped(obj->track)); + gSoundI.start(obj->track); + gLogI.log(instance, kDebug_ANPLogType, "track %p %d", + obj->track, gSoundI.isStopped(obj->track)); + } + } + return 1; + + default: + break; + } + return 0; // unknown or unhandled event +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData) +{ + +} + +EXPORT NPError NP_GetValue(NPP instance, NPPVariable variable, void *value) { + + if (variable == NPPVpluginNameString) { + const char **str = (const char **)value; + *str = "Test Plugin"; + return NPERR_NO_ERROR; + } + + if (variable == NPPVpluginDescriptionString) { + const char **str = (const char **)value; + *str = "Description of Test Plugin"; + return NPERR_NO_ERROR; + } + + return NPERR_GENERIC_ERROR; +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value) +{ + if (variable == NPPVpluginScriptableNPObject) { + void **v = (void **)value; + PluginObject *obj = (PluginObject*) instance->pdata; + + if (obj) + browser->retainobject((NPObject*)obj); + + *v = obj; + return NPERR_NO_ERROR; + } + + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value) +{ + return NPERR_GENERIC_ERROR; +} + diff --git a/WebKit/android/plugins/sample/main.h b/WebKit/android/plugins/sample/main.h new file mode 100644 index 0000000..8bf520e --- /dev/null +++ b/WebKit/android/plugins/sample/main.h @@ -0,0 +1,30 @@ +/* + * 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 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 <npapi.h> +#include <npfunctions.h> +#include <npruntime.h> + +extern NPNetscapeFuncs* browser; diff --git a/WebKit/android/plugins/sample/pluginGraphics.cpp b/WebKit/android/plugins/sample/pluginGraphics.cpp new file mode 100644 index 0000000..ffa43e5 --- /dev/null +++ b/WebKit/android/plugins/sample/pluginGraphics.cpp @@ -0,0 +1,181 @@ +/* + * 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 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 "pluginGraphics.h" + +#include "android_npapi.h" +#include <stdio.h> +#include <sys/time.h> +#include <time.h> +#include <math.h> +#include <string.h> + +extern NPNetscapeFuncs* browser; +extern ANPLogInterfaceV0 gLogI; +extern ANPCanvasInterfaceV0 gCanvasI; +extern ANPPaintInterfaceV0 gPaintI; +extern ANPTypefaceInterfaceV0 gTypefaceI; + +static void inval(NPP instance) { + browser->invalidaterect(instance, NULL); +} + +static uint16 rnd16(float x, int inset) { + int ix = (int)roundf(x) + inset; + if (ix < 0) { + ix = 0; + } + return static_cast<uint16>(ix); +} + +static void inval(NPP instance, const ANPRectF& r, bool doAA) { + const int inset = doAA ? -1 : 0; + + PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata); + NPRect inval; + inval.left = rnd16(r.left, inset); + inval.top = rnd16(r.top, inset); + inval.right = rnd16(r.right, -inset); + inval.bottom = rnd16(r.bottom, -inset); + browser->invalidaterect(instance, &inval); +} + +uint32_t getMSecs() { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint32_t) (tv.tv_sec * 1000 + tv.tv_usec / 1000 ); // microseconds to milliseconds +} + +/////////////////////////////////////////////////////////////////////////////// + +class BallAnimation : public Animation { +public: + BallAnimation(NPP inst); + virtual ~BallAnimation(); + virtual void draw(ANPCanvas*); +private: + float m_x; + float m_y; + float m_dx; + float m_dy; + + ANPRectF m_oval; + ANPPaint* m_paint; + + static const float SCALE = 0.1; +}; + +BallAnimation::BallAnimation(NPP inst) : Animation(inst) { + m_x = m_y = 0; + m_dx = 7 * SCALE; + m_dy = 5 * SCALE; + + memset(&m_oval, 0, sizeof(m_oval)); + + m_paint = gPaintI.newPaint(); + gPaintI.setFlags(m_paint, gPaintI.getFlags(m_paint) | kAntiAlias_ANPPaintFlag); + gPaintI.setColor(m_paint, 0xFFFF0000); + gPaintI.setTextSize(m_paint, 24); + + ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle); + gPaintI.setTypeface(m_paint, tf); + gTypefaceI.unref(tf); +} + +BallAnimation::~BallAnimation() { + gPaintI.deletePaint(m_paint); +} + +static void bounce(float* x, float* dx, const float max) { + *x += *dx; + if (*x < 0) { + *x = 0; + if (*dx < 0) { + *dx = -*dx; + } + } else if (*x > max) { + *x = max; + if (*dx > 0) { + *dx = -*dx; + } + } +} + +void BallAnimation::draw(ANPCanvas* canvas) { + NPP instance = this->inst(); + PluginObject *obj = (PluginObject*) instance->pdata; + const float OW = 20; + const float OH = 20; + + inval(instance, m_oval, true); // inval the old + m_oval.left = m_x; + m_oval.top = m_y; + m_oval.right = m_x + OW; + m_oval.bottom = m_y + OH; + inval(instance, m_oval, true); // inval the new + + gCanvasI.drawColor(canvas, 0xFFFFFFFF); + + gPaintI.setColor(m_paint, 0xFFFF0000); + gCanvasI.drawOval(canvas, &m_oval, m_paint); + + bounce(&m_x, &m_dx, obj->window->width - OW); + bounce(&m_y, &m_dy, obj->window->height - OH); + + if (obj->mUnichar) { + ANPFontMetrics fm; + gPaintI.getFontMetrics(m_paint, &fm); + + gPaintI.setColor(m_paint, 0xFF0000FF); + char c = static_cast<char>(obj->mUnichar); + gCanvasI.drawText(canvas, &c, 1, 10, -fm.fTop, m_paint); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void drawPlugin(NPP instance, const ANPBitmap& bitmap, const ANPRectI& clip) { + ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap); + + ANPRectF clipR; + clipR.left = clip.left; + clipR.top = clip.top; + clipR.right = clip.right; + clipR.bottom = clip.bottom; + gCanvasI.clipRect(canvas, &clipR); + + drawPlugin(instance, canvas); + + gCanvasI.deleteCanvas(canvas); +} + +void drawPlugin(NPP instance, ANPCanvas* canvas) { + PluginObject *obj = (PluginObject*) instance->pdata; + if (obj->anim == NULL) { + obj->anim = new BallAnimation(instance); + } + obj->anim->draw(canvas); +} + diff --git a/WebKit/android/plugins/sample/pluginGraphics.h b/WebKit/android/plugins/sample/pluginGraphics.h new file mode 100644 index 0000000..4dceb26 --- /dev/null +++ b/WebKit/android/plugins/sample/pluginGraphics.h @@ -0,0 +1,40 @@ +/* + * 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 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 "main.h" // for NPAPI definitions +#include "PluginObject.h" + +#ifndef pluginGraphics__DEFINED +#define pluginGraphics__DEFINED + +struct ANPBitmap; +struct ANPCanvas; +struct ANPRectI; + +void drawPlugin(NPP instance, const ANPBitmap& bitmap, const ANPRectI& clip); +void drawPlugin(NPP instance, ANPCanvas*); +uint32_t getMSecs(); + +#endif // pluginGraphics__DEFINED diff --git a/WebKit/android/sort.cpp b/WebKit/android/sort.cpp new file mode 100644 index 0000000..de7aa6c --- /dev/null +++ b/WebKit/android/sort.cpp @@ -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 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" + +namespace std +{ + void sort(const void** start, const void** end, const void** temp, Comparator comp) + { + size_t endlen = end - start; + size_t midlen = endlen / 2; + const void** mid = start + midlen; + if (midlen > 1) + sort(start, mid, temp, comp); + if (end - mid > 1) + sort(mid, end, temp, comp); + memcpy(temp, start, midlen * sizeof(*start)); + size_t i = 0; + size_t j = midlen; + size_t off = 0; + while (i < midlen && j < endlen) + start[off++] = (*comp)(start[j], temp[i]) ? start[j++] : temp[i++]; + if (i < midlen) + memcpy(&start[off], &temp[i], (midlen - i) * sizeof(*start)); + } + + void sort(const void** start, const void** end, Comparator comp) { + if (end - start > 1) { + const void** temp = new sortType[(end - start) / 2]; + sort(start, end, temp, comp); + delete[] temp; + } + } +} diff --git a/WebKit/android/stl/algorithm b/WebKit/android/stl/algorithm new file mode 100644 index 0000000..8255def --- /dev/null +++ b/WebKit/android/stl/algorithm @@ -0,0 +1,274 @@ +#ifdef __cplusplus + +/* + * + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * + * Copyright (c) 1996-1998 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + + // extracted from stl_algobase.h + // full STL is not compatible with the ARM build + // a limited number of STL functions is used by webkit: swap, max, and min are + // included below for webkit compatibility + +#ifdef __GLIBCPP_INTERNAL_ALGOBASE_H +#error "real STL defined" +#endif + +#ifndef __ANDROID_ALGORITHM +#define __ANDROID_ALGORITHM + +#ifndef __ANDROID_LIMITS +#include <limits> +#endif + +#ifndef _CPP_UTILITY +#include <utility> +#endif + +#include <SkScalar.h> // for SK_ScalarNaN +#ifdef PREFIX_FOR_WEBCORE +#include <SkTSearch.h> +namespace WebCore { + class InlineTextBox; + class RenderLayer; +} +#endif + +#include <float.h> +#include <math.h> +#include <stdint.h> + +#ifndef WCHAR_MAX + #define WCHAR_MAX 0xFFFF +#endif + +namespace JSC { + class ProfileNode; +} + +namespace WTF { + template <typename T> class RefPtr; +} + +namespace std +{ + template<typename _Tp> + inline void + swap(_Tp& __a, _Tp& __b) + { + _Tp __tmp = __a; + __a = __b; + __b = __tmp; + } + + #undef min + #undef max + + template<typename _Tp> + inline const _Tp& + min(const _Tp& __a, const _Tp& __b) + { + return __b < __a ? __b : __a; + } + + template<typename _Tp> + inline const _Tp& + max(const _Tp& __a, const _Tp& __b) + { + return __a < __b ? __b : __a; + } + +template <class _InputIter, class _OutputIter> +inline _OutputIter copy(_InputIter __first, _InputIter __last, + _OutputIter __result) +{ + for (size_t __n = __last - __first; __n > 0; --__n) { + *__result = *__first; + ++__first; + ++__result; + } + return __result; +} + +template <class _ForwardIter, class _Tp> +void fill(_ForwardIter __first, _ForwardIter __last, const _Tp& __value) { + for ( ; __first != __last; ++__first) + *__first = __value; +} + +#ifndef UINTPTR_MAX +#define UINTPTR_MAX UINT32_MAX +#endif + +#ifndef UINT32_MAX +#define UINT32_MAX (0xffffffff) +#endif + +template <typename T> +struct numeric_limits { + /// Returns the minimum value for type T. + static inline T min (void) { return (T(0)); } + /// Returns the minimum value for type T. + static inline T max (void) { return (T(0)); } + static const bool is_signed = false; ///< True if the type is signed. + static const bool is_integer = false; ///< True if stores an exact value. + static const bool is_integral = false; ///< True if fixed size and cast-copyable. +}; + +template <typename T> +struct numeric_limits<T*> { + static inline T* min (void) { return (NULL); } + static inline T* max (void) { return (UINTPTR_MAX); } + static const bool is_signed = false; + static const bool is_integer = true; + static const bool is_integral = true; +}; + +#define _NUMERIC_LIMITS(type, minVal, maxVal, quietNaN, bSigned, bInteger, bIntegral) \ +template <> \ +struct numeric_limits<type> { \ + static inline type infinity (void) { return (maxVal); } \ + static inline type min (void) { return (minVal); } \ + static inline type max (void) { return (maxVal); } \ + static inline type quiet_NaN() { return (quietNaN); } \ + static const bool is_signed = bSigned; \ + static const bool is_integer = bInteger; \ + static const bool is_integral = bIntegral; \ +} + +//-------------------------------------------------------------------------------------- +// type min max NaN signed integer integral +//-------------------------------------------------------------------------------------- +_NUMERIC_LIMITS (bool, false, true, 0, false, true, true); +_NUMERIC_LIMITS (char, SCHAR_MIN, SCHAR_MAX, 0, true, true, true); +_NUMERIC_LIMITS (int, INT_MIN, INT_MAX, 0, true, true, true); +_NUMERIC_LIMITS (short, SHRT_MIN, SHRT_MAX, 0, true, true, true); +_NUMERIC_LIMITS (long, LONG_MIN, LONG_MAX, 0, true, true, true); +#if HAVE_THREE_CHAR_TYPES +_NUMERIC_LIMITS (signed char, SCHAR_MIN, SCHAR_MAX, 0, true, true, true); +#endif +_NUMERIC_LIMITS (unsigned char, 0, UCHAR_MAX, 0, false, true, true); +_NUMERIC_LIMITS (unsigned int, 0, UINT_MAX, 0, false, true, true); +_NUMERIC_LIMITS (unsigned short,0, USHRT_MAX, 0, false, true, true); +_NUMERIC_LIMITS (unsigned long, 0, ULONG_MAX, 0, false, true, true); +_NUMERIC_LIMITS (wchar_t, 0, WCHAR_MAX, 0, false, true, true); +_NUMERIC_LIMITS (float, FLT_MIN, FLT_MAX, SK_ScalarNaN, true, false, true); +_NUMERIC_LIMITS (double, DBL_MIN, DBL_MAX, SK_ScalarNaN, true, false, true); +_NUMERIC_LIMITS (long double, LDBL_MIN, LDBL_MAX, SK_ScalarNaN, true, false, true); +#ifdef HAVE_LONG_LONG +_NUMERIC_LIMITS (long long, LLONG_MIN, LLONG_MAX, 0, true, true, true); +_NUMERIC_LIMITS (unsigned long long, 0, ULLONG_MAX, 0, false, true, true); +#endif +//-------------------------------------------------------------------------------------- + +typedef int ptrdiff_t; + +typedef bool (* Comparator)(const void*, const void*); +extern void sort(const void** start, const void** end, Comparator comp); + +#ifdef PREFIX_FOR_WEBCORE + typedef const void* sortType; + + inline bool binary_search(const unsigned short* const base, + const unsigned short* const end, + short target) + { + return SkTSearch<unsigned short>(base, end - base, target, sizeof(unsigned short)) >= 0; + } + + + inline void sort (WebCore::InlineTextBox** start, WebCore::InlineTextBox**end, + bool (* comp)(const WebCore::InlineTextBox*, const WebCore::InlineTextBox*)) + { + sort((const void**) start, (const void**) end, (Comparator) comp); + } + + template<typename P> inline void stable_sort(P** start, P** end, + bool (* comp)(P*, P*)) + { + sort((const void**) start, (const void**) end, (Comparator) comp); + } + + template<typename P> inline void stable_sort(P** start, P** end, + bool (& comp)(const P*, const P*)) + { + sort((const void**) start, (const void**) end, (Comparator) &comp); + } + + template<typename P> void stable_sort(P* start, P* end, P* temp, + bool (& comp)(const P&, const P&)) { + size_t endlen = end - start; + size_t midlen = endlen / 2; + P* mid = start + midlen; + if (midlen > 1) + stable_sort(start, mid, temp, comp); + if (end - mid > 1) + stable_sort(mid, end, temp, comp); + memcpy(temp, start, midlen * sizeof(*start)); + size_t i = 0; + size_t j = midlen; + size_t off = 0; + while (i < midlen && j < endlen) { + P* dst = (comp)(start[j], temp[i]) ? &start[j++] : &temp[i++]; + memcpy(&start[off++], dst, sizeof(*start)); + } + if (i < midlen) + memcpy(&start[off], &temp[i], (midlen - i) * sizeof(*start)); + } + + template<typename P> void stable_sort(P* start, P* end, + bool (& comp)(const P&, const P&)) { + if (end - start > 1) { + size_t count = (end - start) / 2; + P* temp = static_cast<P*>(malloc(count * sizeof(P))); + stable_sort(start, end, temp, comp); + free(temp); + } + } + + template<typename P> void stable_sort(P* start, P* end, + bool (& comp)(P, P)) { + stable_sort(start, end, (bool (&)(const P&, const P&))(comp)); + } + + class ostream { + int this_class_intentionally_left_empty; + }; +#endif + +typedef bool (* ProfileComparator)(const WTF::RefPtr<JSC::ProfileNode>&, + const WTF::RefPtr<JSC::ProfileNode>&); + +inline void sort(WTF::RefPtr<JSC::ProfileNode>* start, + WTF::RefPtr<JSC::ProfileNode>* end, ProfileComparator comp) +{ + sort((const void**) start, (const void**) end, (Comparator) comp); +} + +} + +#endif + +#endif // __cplusplus + diff --git a/WebKit/android/stl/concept_checks.h b/WebKit/android/stl/concept_checks.h new file mode 100644 index 0000000..72cc138 --- /dev/null +++ b/WebKit/android/stl/concept_checks.h @@ -0,0 +1,812 @@ +/* + * Copyright 2009, The Android Open Source Project + * Copyright (c) 1999 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + +#ifndef __CONCEPT_CHECKS_H +#define __CONCEPT_CHECKS_H + +/* + Use these macro like assertions, but they assert properties + on types (usually template arguments). In technical terms they + verify whether a type "models" a "concept". + + This set of requirements and the terminology used here is derived + from the book "Generic Programming and the STL" by Matt Austern + (Addison Wesley). For further information please consult that + book. The requirements also are intended to match the ANSI/ISO C++ + standard. + + This file covers the basic concepts and the iterator concepts. + There are several other files that provide the requirements + for the STL containers: + container_concepts.h + sequence_concepts.h + assoc_container_concepts.h + + Jeremy Siek, 1999 + + TO DO: + - some issues with regards to concept classification and mutability + including AssociativeContianer -> ForwardContainer + and SortedAssociativeContainer -> ReversibleContainer + - HashedAssociativeContainer + - Allocator + - Function Object Concepts + + */ + +#ifndef __STL_USE_CONCEPT_CHECKS + +// Some compilers lack the features that are necessary for concept checks. +// On those compilers we define the concept check macros to do nothing. +#define __STL_REQUIRES(__type_var, __concept) do {} while(0) +#define __STL_CLASS_REQUIRES(__type_var, __concept) \ + static int __##__type_var##_##__concept +#define __STL_CONVERTIBLE(__type_x, __type_y) do {} while(0) +#define __STL_REQUIRES_SAME_TYPE(__type_x, __type_y) do {} while(0) +#define __STL_CLASS_REQUIRES_SAME_TYPE(__type_x, __type_y) \ + static int __##__type_x##__type_y##_require_same_type +#define __STL_GENERATOR_CHECK(__func, __ret) do {} while(0) +#define __STL_CLASS_GENERATOR_CHECK(__func, __ret) \ + static int __##__func##__ret##_generator_check +#define __STL_UNARY_FUNCTION_CHECK(__func, __ret, __arg) do {} while(0) +#define __STL_CLASS_UNARY_FUNCTION_CHECK(__func, __ret, __arg) \ + static int __##__func##__ret##__arg##_unary_function_check +#define __STL_BINARY_FUNCTION_CHECK(__func, __ret, __first, __second) \ + do {} while(0) +#define __STL_CLASS_BINARY_FUNCTION_CHECK(__func, __ret, __first, __second) \ + static int __##__func##__ret##__first##__second##_binary_function_check +#define __STL_REQUIRES_BINARY_OP(__opname, __ret, __first, __second) \ + do {} while(0) +#define __STL_CLASS_REQUIRES_BINARY_OP(__opname, __ret, __first, __second) \ + static int __##__opname##__ret##__first##__second##_require_binary_op + +#else /* __STL_USE_CONCEPT_CHECKS */ + +// This macro tests whether the template argument "__type_var" +// satisfies the requirements of "__concept". Here is a list of concepts +// that we know how to check: +// _Allocator +// _Assignable +// _DefaultConstructible +// _EqualityComparable +// _LessThanComparable +// _TrivialIterator +// _InputIterator +// _OutputIterator +// _ForwardIterator +// _BidirectionalIterator +// _RandomAccessIterator +// _Mutable_TrivialIterator +// _Mutable_ForwardIterator +// _Mutable_BidirectionalIterator +// _Mutable_RandomAccessIterator + +#define __STL_REQUIRES(__type_var, __concept) \ +do { \ + void (*__x)( __type_var ) = __concept##_concept_specification< __type_var >\ + ::__concept##_requirement_violation; __x = __x; } while (0) + +// Use this to check whether type X is convertible to type Y +#define __STL_CONVERTIBLE(__type_x, __type_y) \ +do { \ + void (*__x)( __type_x , __type_y ) = _STL_CONVERT_ERROR< __type_x , \ + __type_y >::__type_X_is_not_convertible_to_type_Y; \ + __x = __x; } while (0) + +// Use this to test whether two template arguments are the same type +#define __STL_REQUIRES_SAME_TYPE(__type_x, __type_y) \ +do { \ + void (*__x)( __type_x , __type_y ) = _STL_SAME_TYPE_ERROR< __type_x, \ + __type_y >::__type_X_not_same_as_type_Y; \ + __x = __x; } while (0) + + +// function object checks +#define __STL_GENERATOR_CHECK(__func, __ret) \ +do { \ + __ret (*__x)( __func&) = \ + _STL_GENERATOR_ERROR< \ + __func, __ret>::__generator_requirement_violation; \ + __x = __x; } while (0) + + +#define __STL_UNARY_FUNCTION_CHECK(__func, __ret, __arg) \ +do { \ + __ret (*__x)( __func&, const __arg& ) = \ + _STL_UNARY_FUNCTION_ERROR< \ + __func, __ret, __arg>::__unary_function_requirement_violation; \ + __x = __x; } while (0) + + +#define __STL_BINARY_FUNCTION_CHECK(__func, __ret, __first, __second) \ +do { \ + __ret (*__x)( __func&, const __first&, const __second& ) = \ + _STL_BINARY_FUNCTION_ERROR< \ + __func, __ret, __first, __second>::__binary_function_requirement_violation; \ + __x = __x; } while (0) + + +#define __STL_REQUIRES_BINARY_OP(__opname, __ret, __first, __second) \ + do { \ + __ret (*__x)( __first&, __second& ) = _STL_BINARY##__opname##_ERROR< \ + __ret, __first, __second>::__binary_operator_requirement_violation; \ + __ret (*__y)( const __first&, const __second& ) = \ + _STL_BINARY##__opname##_ERROR< __ret, __first, __second>:: \ + __const_binary_operator_requirement_violation; \ + __y = __y; __x = __x; } while (0) + + +#ifdef __STL_NO_FUNCTION_PTR_IN_CLASS_TEMPLATE + +#define __STL_CLASS_REQUIRES(__type_var, __concept) +#define __STL_CLASS_REQUIRES_SAME_TYPE(__type_x, __type_y) +#define __STL_CLASS_GENERATOR_CHECK(__func, __ret) +#define __STL_CLASS_UNARY_FUNCTION_CHECK(__func, __ret, __arg) +#define __STL_CLASS_BINARY_FUNCTION_CHECK(__func, __ret, __first, __second) +#define __STL_CLASS_REQUIRES_BINARY_OP(__opname, __ret, __first, __second) + +#else + +// Use this macro inside of template classes, where you would +// like to place requirements on the template arguments to the class +// Warning: do not pass pointers and such (e.g. T*) in as the __type_var, +// since the type_var is used to construct identifiers. Instead typedef +// the pointer type, then use the typedef name for the __type_var. +#define __STL_CLASS_REQUIRES(__type_var, __concept) \ + typedef void (* __func##__type_var##__concept)( __type_var ); \ + template <__func##__type_var##__concept _Tp1> \ + struct __dummy_struct_##__type_var##__concept { }; \ + static __dummy_struct_##__type_var##__concept< \ + __concept##_concept_specification< \ + __type_var>::__concept##_requirement_violation> \ + __dummy_ptr_##__type_var##__concept + + +#define __STL_CLASS_REQUIRES_SAME_TYPE(__type_x, __type_y) \ + typedef void (* __func_##__type_x##__type_y##same_type)( __type_x, \ + __type_y ); \ + template < __func_##__type_x##__type_y##same_type _Tp1> \ + struct __dummy_struct_##__type_x##__type_y##_same_type { }; \ + static __dummy_struct_##__type_x##__type_y##_same_type< \ + _STL_SAME_TYPE_ERROR<__type_x, __type_y>::__type_X_not_same_as_type_Y> \ + __dummy_ptr_##__type_x##__type_y##_same_type + + +#define __STL_CLASS_GENERATOR_CHECK(__func, __ret) \ + typedef __ret (* __f_##__func##__ret##_generator)( __func& ); \ + template <__f_##__func##__ret##_generator _Tp1> \ + struct __dummy_struct_##__func##__ret##_generator { }; \ + static __dummy_struct_##__func##__ret##_generator< \ + _STL_GENERATOR_ERROR< \ + __func, __ret>::__generator_requirement_violation> \ + __dummy_ptr_##__func##__ret##_generator + + +#define __STL_CLASS_UNARY_FUNCTION_CHECK(__func, __ret, __arg) \ + typedef __ret (* __f_##__func##__ret##__arg##_unary_check)( __func&, \ + const __arg& ); \ + template <__f_##__func##__ret##__arg##_unary_check _Tp1> \ + struct __dummy_struct_##__func##__ret##__arg##_unary_check { }; \ + static __dummy_struct_##__func##__ret##__arg##_unary_check< \ + _STL_UNARY_FUNCTION_ERROR< \ + __func, __ret, __arg>::__unary_function_requirement_violation> \ + __dummy_ptr_##__func##__ret##__arg##_unary_check + + +#define __STL_CLASS_BINARY_FUNCTION_CHECK(__func, __ret, __first, __second) \ + typedef __ret (* __f_##__func##__ret##__first##__second##_binary_check)( __func&, const __first&,\ + const __second& ); \ + template <__f_##__func##__ret##__first##__second##_binary_check _Tp1> \ + struct __dummy_struct_##__func##__ret##__first##__second##_binary_check { }; \ + static __dummy_struct_##__func##__ret##__first##__second##_binary_check< \ + _STL_BINARY_FUNCTION_ERROR<__func, __ret, __first, __second>:: \ + __binary_function_requirement_violation> \ + __dummy_ptr_##__func##__ret##__first##__second##_binary_check + + +#define __STL_CLASS_REQUIRES_BINARY_OP(__opname, __ret, __first, __second) \ + typedef __ret (* __f_##__func##__ret##__first##__second##_binary_op)(const __first&, \ + const __second& ); \ + template <__f_##__func##__ret##__first##__second##_binary_op _Tp1> \ + struct __dummy_struct_##__func##__ret##__first##__second##_binary_op { }; \ + static __dummy_struct_##__func##__ret##__first##__second##_binary_op< \ + _STL_BINARY##__opname##_ERROR<__ret, __first, __second>:: \ + __binary_operator_requirement_violation> \ + __dummy_ptr_##__func##__ret##__first##__second##_binary_op + +#endif + +/* helper class for finding non-const version of a type. Need to have + something to assign to etc. when testing constant iterators. */ + +template <class _Tp> +struct _Mutable_trait { + typedef _Tp _Type; +}; +template <class _Tp> +struct _Mutable_trait<const _Tp> { + typedef _Tp _Type; +}; + + +/* helper function for avoiding compiler warnings about unused variables */ +template <class _Type> +void __sink_unused_warning(_Type) { } + +template <class _TypeX, class _TypeY> +struct _STL_CONVERT_ERROR { + static void + __type_X_is_not_convertible_to_type_Y(_TypeX __x, _TypeY) { + _TypeY __y = __x; + __sink_unused_warning(__y); + } +}; + + +template <class _Type> struct __check_equal { }; + +template <class _TypeX, class _TypeY> +struct _STL_SAME_TYPE_ERROR { + static void + __type_X_not_same_as_type_Y(_TypeX , _TypeY ) { + __check_equal<_TypeX> t1 = __check_equal<_TypeY>(); + } +}; + + +// Some Functon Object Checks + +template <class _Func, class _Ret> +struct _STL_GENERATOR_ERROR { + static _Ret __generator_requirement_violation(_Func& __f) { + return __f(); + } +}; + +template <class _Func> +struct _STL_GENERATOR_ERROR<_Func, void> { + static void __generator_requirement_violation(_Func& __f) { + __f(); + } +}; + + +template <class _Func, class _Ret, class _Arg> +struct _STL_UNARY_FUNCTION_ERROR { + static _Ret + __unary_function_requirement_violation(_Func& __f, + const _Arg& __arg) { + return __f(__arg); + } +}; + +template <class _Func, class _Arg> +struct _STL_UNARY_FUNCTION_ERROR<_Func, void, _Arg> { + static void + __unary_function_requirement_violation(_Func& __f, + const _Arg& __arg) { + __f(__arg); + } +}; + +template <class _Func, class _Ret, class _First, class _Second> +struct _STL_BINARY_FUNCTION_ERROR { + static _Ret + __binary_function_requirement_violation(_Func& __f, + const _First& __first, + const _Second& __second) { + return __f(__first, __second); + } +}; + +template <class _Func, class _First, class _Second> +struct _STL_BINARY_FUNCTION_ERROR<_Func, void, _First, _Second> { + static void + __binary_function_requirement_violation(_Func& __f, + const _First& __first, + const _Second& __second) { + __f(__first, __second); + } +}; + + +#define __STL_DEFINE_BINARY_OP_CHECK(_OP, _NAME) \ +template <class _Ret, class _First, class _Second> \ +struct _STL_BINARY##_NAME##_ERROR { \ + static _Ret \ + __const_binary_operator_requirement_violation(const _First& __first, \ + const _Second& __second) { \ + return __first _OP __second; \ + } \ + static _Ret \ + __binary_operator_requirement_violation(_First& __first, \ + _Second& __second) { \ + return __first _OP __second; \ + } \ +} + +__STL_DEFINE_BINARY_OP_CHECK(==, _OP_EQUAL); +__STL_DEFINE_BINARY_OP_CHECK(!=, _OP_NOT_EQUAL); +__STL_DEFINE_BINARY_OP_CHECK(<, _OP_LESS_THAN); +__STL_DEFINE_BINARY_OP_CHECK(<=, _OP_LESS_EQUAL); +__STL_DEFINE_BINARY_OP_CHECK(>, _OP_GREATER_THAN); +__STL_DEFINE_BINARY_OP_CHECK(>=, _OP_GREATER_EQUAL); +__STL_DEFINE_BINARY_OP_CHECK(+, _OP_PLUS); +__STL_DEFINE_BINARY_OP_CHECK(*, _OP_TIMES); +__STL_DEFINE_BINARY_OP_CHECK(/, _OP_DIVIDE); +__STL_DEFINE_BINARY_OP_CHECK(-, _OP_SUBTRACT); +__STL_DEFINE_BINARY_OP_CHECK(%, _OP_MOD); +// ... + +// TODO, add unary operators (prefix and postfix) + +/* + The presence of this class is just to trick EDG into displaying + these error messages before any other errors. Without the + classes, the errors in the functions get reported after + other class errors deep inside the library. The name + choice just makes for an eye catching error message :) + */ +struct _STL_ERROR { + + template <class _Type> + static _Type + __default_constructor_requirement_violation(_Type) { + return _Type(); + } + template <class _Type> + static _Type + __assignment_operator_requirement_violation(_Type __a) { + __a = __a; + return __a; + } + template <class _Type> + static _Type + __copy_constructor_requirement_violation(_Type __a) { + _Type __c(__a); + return __c; + } + template <class _Type> + static _Type + __const_parameter_required_for_copy_constructor(_Type /* __a */, + const _Type& __b) { + _Type __c(__b); + return __c; + } + template <class _Type> + static _Type + __const_parameter_required_for_assignment_operator(_Type __a, + const _Type& __b) { + __a = __b; + return __a; + } + template <class _Type> + static _Type + __less_than_comparable_requirement_violation(_Type __a, _Type __b) { + if (__a < __b || __a > __b || __a <= __b || __a >= __b) return __a; + return __b; + } + template <class _Type> + static _Type + __equality_comparable_requirement_violation(_Type __a, _Type __b) { + if (__a == __b || __a != __b) return __a; + return __b; + } + template <class _Iterator> + static void + __dereference_operator_requirement_violation(_Iterator __i) { + __sink_unused_warning(*__i); + } + template <class _Iterator> + static void + __dereference_operator_and_assignment_requirement_violation(_Iterator __i) { + *__i = *__i; + } + template <class _Iterator> + static void + __preincrement_operator_requirement_violation(_Iterator __i) { + ++__i; + } + template <class _Iterator> + static void + __postincrement_operator_requirement_violation(_Iterator __i) { + __i++; + } + template <class _Iterator> + static void + __predecrement_operator_requirement_violation(_Iterator __i) { + --__i; + } + template <class _Iterator> + static void + __postdecrement_operator_requirement_violation(_Iterator __i) { + __i--; + } + template <class _Iterator, class _Type> + static void + __postincrement_operator_and_assignment_requirement_violation(_Iterator __i, + _Type __t) { + *__i++ = __t; + } + template <class _Iterator, class _Distance> + static _Iterator + __iterator_addition_assignment_requirement_violation(_Iterator __i, + _Distance __n) { + __i += __n; + return __i; + } + template <class _Iterator, class _Distance> + static _Iterator + __iterator_addition_requirement_violation(_Iterator __i, _Distance __n) { + __i = __i + __n; + __i = __n + __i; + return __i; + } + template <class _Iterator, class _Distance> + static _Iterator + __iterator_subtraction_assignment_requirement_violation(_Iterator __i, + _Distance __n) { + __i -= __n; + return __i; + } + template <class _Iterator, class _Distance> + static _Iterator + __iterator_subtraction_requirement_violation(_Iterator __i, _Distance __n) { + __i = __i - __n; + return __i; + } + template <class _Iterator, class _Distance> + static _Distance + __difference_operator_requirement_violation(_Iterator __i, _Iterator __j, + _Distance __n) { + __n = __i - __j; + return __n; + } + template <class _Exp, class _Type, class _Distance> + static _Type + __element_access_operator_requirement_violation(_Exp __x, _Type*, + _Distance __n) { + return __x[__n]; + } + template <class _Exp, class _Type, class _Distance> + static void + __element_assignment_operator_requirement_violation(_Exp __x, + _Type* __t, + _Distance __n) { + __x[__n] = *__t; + } + +}; /* _STL_ERROR */ + +/* Associated Type Requirements */ + +__STL_BEGIN_NAMESPACE +template <class _Iterator> struct iterator_traits; +__STL_END_NAMESPACE + +template <class _Iter> +struct __value_type_type_definition_requirement_violation { + typedef typename __STD::iterator_traits<_Iter>::value_type value_type; +}; + +template <class _Iter> +struct __difference_type_type_definition_requirement_violation { + typedef typename __STD::iterator_traits<_Iter>::difference_type + difference_type; +}; + +template <class _Iter> +struct __reference_type_definition_requirement_violation { + typedef typename __STD::iterator_traits<_Iter>::reference reference; +}; + +template <class _Iter> +struct __pointer_type_definition_requirement_violation { + typedef typename __STD::iterator_traits<_Iter>::pointer pointer; +}; + +template <class _Iter> +struct __iterator_category_type_definition_requirement_violation { + typedef typename __STD::iterator_traits<_Iter>::iterator_category + iterator_category; +}; + +/* Assignable Requirements */ + + +template <class _Type> +struct _Assignable_concept_specification { + static void _Assignable_requirement_violation(_Type __a) { + _STL_ERROR::__assignment_operator_requirement_violation(__a); + _STL_ERROR::__copy_constructor_requirement_violation(__a); + _STL_ERROR::__const_parameter_required_for_copy_constructor(__a,__a); + _STL_ERROR::__const_parameter_required_for_assignment_operator(__a,__a); + } +}; + +/* DefaultConstructible Requirements */ + + +template <class _Type> +struct _DefaultConstructible_concept_specification { + static void _DefaultConstructible_requirement_violation(_Type __a) { + _STL_ERROR::__default_constructor_requirement_violation(__a); + } +}; + +/* EqualityComparable Requirements */ + +template <class _Type> +struct _EqualityComparable_concept_specification { + static void _EqualityComparable_requirement_violation(_Type __a) { + _STL_ERROR::__equality_comparable_requirement_violation(__a, __a); + } +}; + +/* LessThanComparable Requirements */ +template <class _Type> +struct _LessThanComparable_concept_specification { + static void _LessThanComparable_requirement_violation(_Type __a) { + _STL_ERROR::__less_than_comparable_requirement_violation(__a, __a); + } +}; + +/* TrivialIterator Requirements */ + +template <class _TrivialIterator> +struct _TrivialIterator_concept_specification { +static void +_TrivialIterator_requirement_violation(_TrivialIterator __i) { + typedef typename + __value_type_type_definition_requirement_violation<_TrivialIterator>:: + value_type __T; + // Refinement of Assignable + _Assignable_concept_specification<_TrivialIterator>:: + _Assignable_requirement_violation(__i); + // Refinement of DefaultConstructible + _DefaultConstructible_concept_specification<_TrivialIterator>:: + _DefaultConstructible_requirement_violation(__i); + // Refinement of EqualityComparable + _EqualityComparable_concept_specification<_TrivialIterator>:: + _EqualityComparable_requirement_violation(__i); + // Valid Expressions + _STL_ERROR::__dereference_operator_requirement_violation(__i); +} +}; + +template <class _TrivialIterator> +struct _Mutable_TrivialIterator_concept_specification { +static void +_Mutable_TrivialIterator_requirement_violation(_TrivialIterator __i) { + _TrivialIterator_concept_specification<_TrivialIterator>:: + _TrivialIterator_requirement_violation(__i); + // Valid Expressions + _STL_ERROR::__dereference_operator_and_assignment_requirement_violation(__i); +} +}; + +/* InputIterator Requirements */ + +template <class _InputIterator> +struct _InputIterator_concept_specification { +static void +_InputIterator_requirement_violation(_InputIterator __i) { + // Refinement of TrivialIterator + _TrivialIterator_concept_specification<_InputIterator>:: + _TrivialIterator_requirement_violation(__i); + // Associated Types + __difference_type_type_definition_requirement_violation<_InputIterator>(); + __reference_type_definition_requirement_violation<_InputIterator>(); + __pointer_type_definition_requirement_violation<_InputIterator>(); + __iterator_category_type_definition_requirement_violation<_InputIterator>(); + // Valid Expressions + _STL_ERROR::__preincrement_operator_requirement_violation(__i); + _STL_ERROR::__postincrement_operator_requirement_violation(__i); +} +}; + +/* OutputIterator Requirements */ + +template <class _OutputIterator> +struct _OutputIterator_concept_specification { +static void +_OutputIterator_requirement_violation(_OutputIterator __i) { + // Refinement of Assignable + _Assignable_concept_specification<_OutputIterator>:: + _Assignable_requirement_violation(__i); + // Associated Types + __iterator_category_type_definition_requirement_violation<_OutputIterator>(); + // Valid Expressions + _STL_ERROR::__dereference_operator_requirement_violation(__i); + _STL_ERROR::__preincrement_operator_requirement_violation(__i); + _STL_ERROR::__postincrement_operator_requirement_violation(__i); + _STL_ERROR:: + __postincrement_operator_and_assignment_requirement_violation(__i, *__i); +} +}; + +/* ForwardIterator Requirements */ + +template <class _ForwardIterator> +struct _ForwardIterator_concept_specification { +static void +_ForwardIterator_requirement_violation(_ForwardIterator __i) { + // Refinement of InputIterator + _InputIterator_concept_specification<_ForwardIterator>:: + _InputIterator_requirement_violation(__i); +} +}; + +template <class _ForwardIterator> +struct _Mutable_ForwardIterator_concept_specification { +static void +_Mutable_ForwardIterator_requirement_violation(_ForwardIterator __i) { + _ForwardIterator_concept_specification<_ForwardIterator>:: + _ForwardIterator_requirement_violation(__i); + // Refinement of OutputIterator + _OutputIterator_concept_specification<_ForwardIterator>:: + _OutputIterator_requirement_violation(__i); +} +}; + +/* BidirectionalIterator Requirements */ + +template <class _BidirectionalIterator> +struct _BidirectionalIterator_concept_specification { +static void +_BidirectionalIterator_requirement_violation(_BidirectionalIterator __i) { + // Refinement of ForwardIterator + _ForwardIterator_concept_specification<_BidirectionalIterator>:: + _ForwardIterator_requirement_violation(__i); + // Valid Expressions + _STL_ERROR::__predecrement_operator_requirement_violation(__i); + _STL_ERROR::__postdecrement_operator_requirement_violation(__i); +} +}; + +template <class _BidirectionalIterator> +struct _Mutable_BidirectionalIterator_concept_specification { +static void +_Mutable_BidirectionalIterator_requirement_violation( + _BidirectionalIterator __i) +{ + _BidirectionalIterator_concept_specification<_BidirectionalIterator>:: + _BidirectionalIterator_requirement_violation(__i); + // Refinement of mutable_ForwardIterator + _Mutable_ForwardIterator_concept_specification<_BidirectionalIterator>:: + _Mutable_ForwardIterator_requirement_violation(__i); + typedef typename + __value_type_type_definition_requirement_violation< + _BidirectionalIterator>::value_type __T; + typename _Mutable_trait<__T>::_Type* __tmp_ptr = 0; + // Valid Expressions + _STL_ERROR:: + __postincrement_operator_and_assignment_requirement_violation(__i, + *__tmp_ptr); +} +}; + +/* RandomAccessIterator Requirements */ + +template <class _RandAccIter> +struct _RandomAccessIterator_concept_specification { +static void +_RandomAccessIterator_requirement_violation(_RandAccIter __i) { + // Refinement of BidirectionalIterator + _BidirectionalIterator_concept_specification<_RandAccIter>:: + _BidirectionalIterator_requirement_violation(__i); + // Refinement of LessThanComparable + _LessThanComparable_concept_specification<_RandAccIter>:: + _LessThanComparable_requirement_violation(__i); + typedef typename + __value_type_type_definition_requirement_violation<_RandAccIter> + ::value_type + value_type; + typedef typename + __difference_type_type_definition_requirement_violation<_RandAccIter> + ::difference_type + _Dist; + typedef typename _Mutable_trait<_Dist>::_Type _MutDist; + + // Valid Expressions + _STL_ERROR::__iterator_addition_assignment_requirement_violation(__i, + _MutDist()); + _STL_ERROR::__iterator_addition_requirement_violation(__i, + _MutDist()); + _STL_ERROR:: + __iterator_subtraction_assignment_requirement_violation(__i, + _MutDist()); + _STL_ERROR::__iterator_subtraction_requirement_violation(__i, + _MutDist()); + _STL_ERROR::__difference_operator_requirement_violation(__i, __i, + _MutDist()); + typename _Mutable_trait<value_type>::_Type* __dummy_ptr = 0; + _STL_ERROR::__element_access_operator_requirement_violation(__i, + __dummy_ptr, + _MutDist()); +} +}; + +template <class _RandAccIter> +struct _Mutable_RandomAccessIterator_concept_specification { +static void +_Mutable_RandomAccessIterator_requirement_violation(_RandAccIter __i) +{ + _RandomAccessIterator_concept_specification<_RandAccIter>:: + _RandomAccessIterator_requirement_violation(__i); + // Refinement of mutable_BidirectionalIterator + _Mutable_BidirectionalIterator_concept_specification<_RandAccIter>:: + _Mutable_BidirectionalIterator_requirement_violation(__i); + typedef typename + __value_type_type_definition_requirement_violation<_RandAccIter> + ::value_type + value_type; + typedef typename + __difference_type_type_definition_requirement_violation<_RandAccIter> + ::difference_type + _Dist; + + typename _Mutable_trait<value_type>::_Type* __tmp_ptr = 0; + // Valid Expressions + _STL_ERROR::__element_assignment_operator_requirement_violation(__i, + __tmp_ptr, _Dist()); +} +}; + +#define __STL_TYPEDEF_REQUIREMENT(__REQUIREMENT) \ +template <class Type> \ +struct __##__REQUIREMENT##__typedef_requirement_violation { \ + typedef typename Type::__REQUIREMENT __REQUIREMENT; \ +} + +__STL_TYPEDEF_REQUIREMENT(value_type); +__STL_TYPEDEF_REQUIREMENT(difference_type); +__STL_TYPEDEF_REQUIREMENT(size_type); +__STL_TYPEDEF_REQUIREMENT(reference); +__STL_TYPEDEF_REQUIREMENT(const_reference); +__STL_TYPEDEF_REQUIREMENT(pointer); +__STL_TYPEDEF_REQUIREMENT(const_pointer); + + +template <class _Alloc> +struct _Allocator_concept_specification { +static void +_Allocator_requirement_violation(_Alloc __a) { + // Refinement of DefaultConstructible + _DefaultConstructible_concept_specification<_Alloc>:: + _DefaultConstructible_requirement_violation(__a); + // Refinement of EqualityComparable + _EqualityComparable_concept_specification<_Alloc>:: + _EqualityComparable_requirement_violation(__a); + // Associated Types + __value_type__typedef_requirement_violation<_Alloc>(); + __difference_type__typedef_requirement_violation<_Alloc>(); + __size_type__typedef_requirement_violation<_Alloc>(); + __reference__typedef_requirement_violation<_Alloc>(); + __const_reference__typedef_requirement_violation<_Alloc>(); + __pointer__typedef_requirement_violation<_Alloc>(); + __const_pointer__typedef_requirement_violation<_Alloc>(); + typedef typename _Alloc::value_type _Tp; + //__STL_REQUIRES_SAME_TYPE(typename _Alloc::__STL_TEMPLATE rebind<_Tp>::other, + // _Alloc); +} +}; + +#endif /* __STL_USE_CONCEPT_CHECKS */ + +#endif /* __CONCEPT_CHECKS_H */ + +// Local Variables: +// mode:C++ +// End: diff --git a/WebKit/android/stl/cstring b/WebKit/android/stl/cstring new file mode 100644 index 0000000..77c9175 --- /dev/null +++ b/WebKit/android/stl/cstring @@ -0,0 +1,128 @@ +// -*- C++ -*- forwarding header. + +// Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 +// Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 2, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING. If not, write to the Free +// Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. + +// As a special exception, you may use this file as part of a free software +// library without restriction. Specifically, if other files instantiate +// templates or use macros or inline functions from this file, or you compile +// this file and link it with other files to produce an executable, this +// file does not by itself cause the resulting executable to be covered by +// the GNU General Public License. This exception does not however +// invalidate any other reasons why the executable file might be covered by +// the GNU General Public License. + +// +// ISO C++ 14882: 20.4.6 C library +// + +/** @file cstring + * This is a Standard C++ Library file. You should @c #include this file + * in your programs, rather than any of the "*.h" implementation files. + * + * This is the C++ version of the Standard C Library header @c string.h, + * and its contents are (mostly) the same as that header, but are all + * contained in the namespace @c std. + */ + +#ifndef _GLIBCXX_CSTRING +#define _GLIBCXX_CSTRING 1 + +#pragma GCC system_header + +#include <cstddef> + +#include <string.h> + +// Get rid of those macros defined in <string.h> in lieu of real functions. +#undef memcpy +#undef memmove +#undef strcpy +#undef strncpy +#undef strcat +#undef strncat +#undef memcmp +#undef strcmp +#undef strcoll +#undef strncmp +#undef strxfrm +#undef memchr +#undef strchr +#undef strcspn +#undef strpbrk +#undef strrchr +#undef strspn +#undef strstr +#undef strtok +#undef memset +#undef strerror +#undef strlen + +namespace std +{ + using ::memcpy; + using ::memmove; + using ::strcpy; + using ::strncpy; + using ::strcat; + using ::strncat; + using ::memcmp; + using ::strcmp; +// using ::strcoll; + using ::strncmp; +// using ::strxfrm; + using ::strcspn; + using ::strspn; + using ::strtok; + using ::memset; + using ::strerror; + using ::strlen; + + using ::memchr; + + inline void* + memchr(void* __p, int __c, size_t __n) + { return memchr(const_cast<const void*>(__p), __c, __n); } + + using ::strchr; + + inline char* + strchr(char* __s1, int __n) + { return __builtin_strchr(const_cast<const char*>(__s1), __n); } + + using ::strpbrk; + + inline char* + strpbrk(char* __s1, const char* __s2) + { return __builtin_strpbrk(const_cast<const char*>(__s1), __s2); } + + using ::strrchr; + + inline char* + strrchr(char* __s1, int __n) + { return __builtin_strrchr(const_cast<const char*>(__s1), __n); } + + using ::strstr; + + inline char* + strstr(char* __s1, const char* __s2) + { return __builtin_strstr(const_cast<const char*>(__s1), __s2); } +} + +#endif diff --git a/WebKit/android/stl/heap.h b/WebKit/android/stl/heap.h new file mode 100644 index 0000000..70d2249 --- /dev/null +++ b/WebKit/android/stl/heap.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2009, The Android Open Source Project + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * Copyright (c) 1997 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + +#ifndef __SGI_STL_HEAP_H +#define __SGI_STL_HEAP_H + +#include <concept_checks.h> +#include <stl_config.h> +#include <stl_heap.h> + +#ifdef __STL_USE_NAMESPACES + +using __STD::push_heap; +using __STD::pop_heap; +using __STD::make_heap; +using __STD::sort_heap; + +#endif /* __STL_USE_NAMESPACES */ + + +#endif /* __SGI_STL_HEAP_H */ + +// Local Variables: +// mode:C++ +// End: diff --git a/WebKit/android/stl/limits b/WebKit/android/stl/limits new file mode 100644 index 0000000..672e605 --- /dev/null +++ b/WebKit/android/stl/limits @@ -0,0 +1,31 @@ +/* + * 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 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 __ANDROID_LIMITS +#define __ANDROID_LIMITS + +#include <limits.h> + +#endif // __ANDROID_LIMITS diff --git a/WebKit/android/stl/memory b/WebKit/android/stl/memory new file mode 100644 index 0000000..b224c33 --- /dev/null +++ b/WebKit/android/stl/memory @@ -0,0 +1,132 @@ +/* + * Copyright (c) 1997-1999 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + */ + +//minimal file to get build to work + +#ifdef _CPP_MEMORY +#error "real STL defined" +#endif + +#ifndef __ANDROID_MEMORY +#define __ANDROID_MEMORY +//we good to do this? +#define __STL_MEMBER_TEMPLATES + +#define __STL_NOTHROW + +/*#if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && \*/ + +#if defined(__STL_MEMBER_TEMPLATES) +template<class _Tp1> struct auto_ptr_ref { + _Tp1* _M_ptr; + auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {} +}; +#endif + +namespace std { + +template <class _Tp> class auto_ptr { +private: + _Tp* _M_ptr; + +public: + typedef _Tp element_type; + + explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {} + auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {} + +#ifdef __STL_MEMBER_TEMPLATES + template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW + : _M_ptr(__a.release()) {} +#endif /* __STL_MEMBER_TEMPLATES */ + + auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW { + if (&__a != this) { + delete _M_ptr; + _M_ptr = __a.release(); + } + return *this; + } + +#ifdef __STL_MEMBER_TEMPLATES + template <class _Tp1> + auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW { + if (__a.get() != this->get()) { + delete _M_ptr; + _M_ptr = __a.release(); + } + return *this; + } +#endif /* __STL_MEMBER_TEMPLATES */ + + // Note: The C++ standard says there is supposed to be an empty throw + // specification here, but omitting it is standard conforming. Its + // presence can be detected only if _Tp::~_Tp() throws, but (17.4.3.6/2) + // this is prohibited. + ~auto_ptr() { delete _M_ptr; } + + _Tp& operator*() const __STL_NOTHROW { + return *_M_ptr; + } + _Tp* operator->() const __STL_NOTHROW { + return _M_ptr; + } + _Tp* get() const __STL_NOTHROW { + return _M_ptr; + } + _Tp* release() __STL_NOTHROW { + _Tp* __tmp = _M_ptr; + _M_ptr = 0; + return __tmp; + } + void reset(_Tp* __p = 0) __STL_NOTHROW { + if (__p != _M_ptr) { + delete _M_ptr; + _M_ptr = __p; + } + } + +/*#if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && \*/ +#if defined(__STL_MEMBER_TEMPLATES) + +public: + auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW + : _M_ptr(__ref._M_ptr) {} + + auto_ptr& operator=(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW { + if (__ref._M_ptr != this->get()) { + delete _M_ptr; + _M_ptr = __ref._M_ptr; + } + return *this; + } + + template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW + { return auto_ptr_ref<_Tp1>(this->release()); } + template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW + { return auto_ptr<_Tp1>(this->release()); } + +#endif /* auto ptr conversions && member templates */ + +}; + +} //namespace std + +#endif /* __ANDROID_MEMORY */ + + +// Local Variables: +// mode:C++ +// End: + diff --git a/WebKit/android/stl/new.h b/WebKit/android/stl/new.h new file mode 100644 index 0000000..599a890 --- /dev/null +++ b/WebKit/android/stl/new.h @@ -0,0 +1,25 @@ +/* + * 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 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. + */ + diff --git a/WebKit/android/stl/stl_config.h b/WebKit/android/stl/stl_config.h new file mode 100644 index 0000000..448babc --- /dev/null +++ b/WebKit/android/stl/stl_config.h @@ -0,0 +1,577 @@ +/* + * + * Copyright 2009, The Android Open Source Project + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * Copyright (c) 1997 + * Silicon Graphics + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + */ + +#ifndef __STL_CONFIG_H +# define __STL_CONFIG_H + +// Flags: +// * __STL_NO_BOOL: defined if the compiler doesn't have bool as a builtin +// type. +// * __STL_HAS_WCHAR_T: defined if the compier has wchar_t as a builtin type. +// * __STL_NO_DRAND48: defined if the compiler doesn't have the drand48 +// function. +// * __STL_STATIC_TEMPLATE_MEMBER_BUG: defined if the compiler can't handle +// static members of template classes. +// * __STL_STATIC_CONST_INIT_BUG: defined if the compiler can't handle a +// constant-initializer in the declaration of a static const data member +// of integer type. (See section 9.4.2, paragraph 4, of the C++ standard.) +// * __STL_CLASS_PARTIAL_SPECIALIZATION: defined if the compiler supports +// partial specialization of template classes. +// * __STL_PARTIAL_SPECIALIZATION_SYNTAX: defined if the compiler +// supports partial specialization syntax for full specialization of +// class templates. (Even if it doesn't actually support partial +// specialization itself.) +// * __STL_FUNCTION_TMPL_PARTIAL_ORDER: defined if the compiler supports +// partial ordering of function templates. (a.k.a partial specialization +// of function templates.) +// * __STL_MEMBER_TEMPLATES: defined if the compiler supports template +// member functions of classes. +// * __STL_MEMBER_TEMPLATE_CLASSES: defined if the compiler supports +// nested classes that are member templates of other classes. +// * __STL_TEMPLATE_FRIENDS: defined if the compiler supports templatized +// friend declarations. +// * __STL_EXPLICIT_FUNCTION_TMPL_ARGS: defined if the compiler +// supports calling a function template by providing its template +// arguments explicitly. +// * __STL_LIMITED_DEFAULT_TEMPLATES: defined if the compiler is unable +// to handle default template parameters that depend on previous template +// parameters. +// * __STL_NON_TYPE_TMPL_PARAM_BUG: defined if the compiler has trouble with +// function template argument deduction for non-type template parameters. +// * __SGI_STL_NO_ARROW_OPERATOR: defined if the compiler is unable +// to support the -> operator for iterators. +// * __STL_DEFAULT_CONSTRUCTOR_BUG: defined if T() does not work properly +// when T is a builtin type. +// * __STL_USE_EXCEPTIONS: defined if the compiler (in the current compilation +// mode) supports exceptions. +// * __STL_USE_NAMESPACES: defined if the compiler has the necessary +// support for namespaces. +// * __STL_NO_EXCEPTION_HEADER: defined if the compiler does not have a +// standard-conforming header <exception>. +// * __STL_NO_BAD_ALLOC: defined if the compiler does not have a <new> +// header, or if <new> does not contain a bad_alloc class. If a bad_alloc +// class exists, it is assumed to be in namespace std. +// * __STL_SGI_THREADS: defined if this is being compiled for an SGI IRIX +// system in multithreaded mode, using native SGI threads instead of +// pthreads. +// * __STL_WIN32THREADS: defined if this is being compiled on a WIN32 +// compiler in multithreaded mode. +// * __STL_PTHREADS: defined if we should use portable pthreads +// synchronization. +// * __STL_UITHREADS: defined if we should use UI / solaris / UnixWare threads +// synchronization. UIthreads are similar to pthreads, but are based +// on an earlier version of the Posix threads standard. +// * __STL_LONG_LONG if the compiler has long long and unsigned long long +// types. (They're not in the C++ standard, but they are expected to be +// included in the forthcoming C9X standard.) +// * __STL_THREADS is defined if thread safety is needed. +// * __STL_VOLATILE is defined to be "volatile" if threads are being +// used, and the empty string otherwise. +// * __STL_USE_CONCEPT_CHECKS enables some extra compile-time error +// checking to make sure that user-defined template arguments satisfy +// all of the appropriate requirements. This may result in more +// comprehensible error messages. It incurs no runtime overhead. This +// feature requires member templates and partial specialization. +// * __STL_NO_USING_CLAUSE_IN_CLASS: The compiler does not handle "using" +// clauses inside of class definitions. +// * __STL_NO_FRIEND_TEMPLATE_CLASS: The compiler does not handle friend +// declaractions where the friend is a template class. +// * __STL_NO_FUNCTION_PTR_IN_CLASS_TEMPLATE: The compiler does not +// support the use of a function pointer type as the argument +// for a template. +// * __STL_MEMBER_TEMPLATE_KEYWORD: standard C++ requires the template +// keyword in a few new places (14.2.4). This flag is set for +// compilers that support (and require) this usage. + + +// User-settable macros that control compilation: +// * __STL_USE_SGI_ALLOCATORS: if defined, then the STL will use older +// SGI-style allocators, instead of standard-conforming allocators, +// even if the compiler supports all of the language features needed +// for standard-conforming allocators. +// * __STL_NO_NAMESPACES: if defined, don't put the library in namespace +// std, even if the compiler supports namespaces. +// * __STL_NO_RELOPS_NAMESPACE: if defined, don't put the relational +// operator templates (>, <=. >=, !=) in namespace std::rel_ops, even +// if the compiler supports namespaces and partial ordering of +// function templates. +// * __STL_ASSERTIONS: if defined, then enable runtime checking through the +// __stl_assert macro. +// * _PTHREADS: if defined, use Posix threads for multithreading support. +// * _UITHREADS:if defined, use SCO/Solaris/UI threads for multithreading +// support +// * _NOTHREADS: if defined, don't use any multithreading support. +// * _STL_NO_CONCEPT_CHECKS: if defined, disables the error checking that +// we get from __STL_USE_CONCEPT_CHECKS. +// * __STL_USE_NEW_IOSTREAMS: if defined, then the STL will use new, +// standard-conforming iostreams (e.g. the <iosfwd> header). If not +// defined, the STL will use old cfront-style iostreams (e.g. the +// <iostream.h> header). + +// Other macros defined by this file: + +// * bool, true, and false, if __STL_NO_BOOL is defined. +// * typename, as a null macro if it's not already a keyword. +// * explicit, as a null macro if it's not already a keyword. +// * namespace-related macros (__STD, __STL_BEGIN_NAMESPACE, etc.) +// * exception-related macros (__STL_TRY, __STL_UNWIND, etc.) +// * __stl_assert, either as a test or as a null macro, depending on +// whether or not __STL_ASSERTIONS is defined. + +# if defined(_PTHREADS) && !defined(_NOTHREADS) +# define __STL_PTHREADS +# endif + +# if defined(_UITHREADS) && !defined(_PTHREADS) && !defined(_NOTHREADS) +# define __STL_UITHREADS +# endif + +# if defined(__sgi) && !defined(__GNUC__) +# include <standards.h> +# if !defined(_BOOL) +# define __STL_NO_BOOL +# endif +# if defined(_MIPS_SIM) && _MIPS_SIM == _ABIO32 +# define __STL_STATIC_CONST_INIT_BUG +# endif +# if defined(_WCHAR_T_IS_KEYWORD) +# define __STL_HAS_WCHAR_T +# endif +# if !defined(_TYPENAME_IS_KEYWORD) +# define __STL_NEED_TYPENAME +# endif +# ifdef _PARTIAL_SPECIALIZATION_OF_CLASS_TEMPLATES +# define __STL_CLASS_PARTIAL_SPECIALIZATION +# endif +# if (_COMPILER_VERSION >= 730) && defined(_MIPS_SIM) && _MIPS_SIM != _ABIO32 +# define __STL_FUNCTION_TMPL_PARTIAL_ORDER +# endif +# ifdef _MEMBER_TEMPLATES +# define __STL_MEMBER_TEMPLATES +# define __STL_TEMPLATE_FRIENDS +# define __STL_MEMBER_TEMPLATE_CLASSES +# endif +# if defined(_MEMBER_TEMPLATE_KEYWORD) +# define __STL_MEMBER_TEMPLATE_KEYWORD +# endif +# if defined(_STANDARD_C_PLUS_PLUS) +# define __STL_EXPLICIT_FUNCTION_TMPL_ARGS +# endif +# if (_COMPILER_VERSION >= 730) && defined(_MIPS_SIM) && _MIPS_SIM != _ABIO32 +# define __STL_MEMBER_TEMPLATE_KEYWORD +# endif +# if COMPILER_VERSION < 720 || (defined(_MIPS_SIM) && _MIPS_SIM == _ABIO32) +# define __STL_DEFAULT_CONSTRUCTOR_BUG +# endif +# if !defined(_EXPLICIT_IS_KEYWORD) +# define __STL_NEED_EXPLICIT +# endif +# ifdef __EXCEPTIONS +# define __STL_USE_EXCEPTIONS +# endif +# if (_COMPILER_VERSION >= 721) && defined(_NAMESPACES) +# define __STL_HAS_NAMESPACES +# endif +# if (_COMPILER_VERSION < 721) || \ + !defined(__STL_HAS_NAMESPACES) || defined(__STL_NO_NAMESPACES) +# define __STL_NO_EXCEPTION_HEADER +# endif +# if _COMPILER_VERSION < 730 || !defined(_STANDARD_C_PLUS_PLUS) || \ + !defined(_NAMESPACES) +# define __STL_NO_BAD_ALLOC +# endif +# if !defined(_NOTHREADS) && !defined(__STL_PTHREADS) +# define __STL_SGI_THREADS +# endif +# if defined(_LONGLONG) && defined(_SGIAPI) && _SGIAPI +# define __STL_LONG_LONG +# endif +# if _COMPILER_VERSION >= 730 && defined(_STANDARD_C_PLUS_PLUS) +# define __STL_USE_NEW_IOSTREAMS +# endif +# if _COMPILER_VERSION >= 730 && defined(_STANDARD_C_PLUS_PLUS) +# define __STL_CAN_THROW_RANGE_ERRORS +# endif +# if _COMPILER_VERSION >= 730 && defined(_STANDARD_C_PLUS_PLUS) +# define __SGI_STL_USE_AUTO_PTR_CONVERSIONS +# endif +# endif + + +/* + * Jochen Schlick '1999 - added new #defines (__STL)_UITHREADS (for + * providing SCO / Solaris / UI thread support) + * - added the necessary defines for the SCO UDK 7 + * compiler (and its template friend behavior) + * - all UDK7 specific STL changes are based on the + * macro __USLC__ being defined + */ +// SCO UDK 7 compiler (UnixWare 7x, OSR 5, UnixWare 2x) +# if defined(__USLC__) +# define __STL_HAS_WCHAR_T +# define __STL_CLASS_PARTIAL_SPECIALIZATION +# define __STL_PARTIAL_SPECIALIZATION_SYNTAX +# define __STL_FUNCTION_TMPL_PARTIAL_ORDER +# define __STL_MEMBER_TEMPLATES +# define __STL_MEMBER_TEMPLATE_CLASSES +# define __STL_USE_EXCEPTIONS +# define __STL_HAS_NAMESPACES +# define __STL_USE_NAMESPACES +# define __STL_LONG_LONG +# if defined(_REENTRANT) +# define _UITHREADS /* if UnixWare < 7.0.1 */ +# define __STL_UITHREADS +// use the following defines instead of the UI threads defines when +// you want to use POSIX threads +//# define _PTHREADS /* only if UnixWare >=7.0.1 */ +//# define __STL_PTHREADS +# endif +# endif + + + +# ifdef __GNUC__ +# if __GNUC__ == 2 && __GNUC_MINOR__ <= 7 +# define __STL_STATIC_TEMPLATE_MEMBER_BUG +# endif +# if __GNUC__ < 2 +# define __STL_NEED_TYPENAME +# define __STL_NEED_EXPLICIT +# endif +# if __GNUC__ == 2 && __GNUC_MINOR__ <= 8 +# define __STL_NO_EXCEPTION_HEADER +# define __STL_NO_BAD_ALLOC +# endif +# if __GNUC__ == 2 && __GNUC_MINOR__ >= 8 || __GNUC__ > 2 +# define __STL_CLASS_PARTIAL_SPECIALIZATION +# define __STL_FUNCTION_TMPL_PARTIAL_ORDER +# define __STL_EXPLICIT_FUNCTION_TMPL_ARGS +# define __STL_MEMBER_TEMPLATES +# define __STL_CAN_THROW_RANGE_ERRORS + // g++ 2.8.1 supports member template functions, but not member + // template nested classes. +# if __GNUC_MINOR__ >= 9 || __GNUC__ > 2 +# define __STL_MEMBER_TEMPLATE_CLASSES +# define __STL_TEMPLATE_FRIENDS +# define __SGI_STL_USE_AUTO_PTR_CONVERSIONS +# define __STL_HAS_NAMESPACES +//# define __STL_USE_NEW_IOSTREAMS +# endif +# endif +# define __STL_DEFAULT_CONSTRUCTOR_BUG +# ifdef __EXCEPTIONS +# define __STL_USE_EXCEPTIONS +# endif +# ifdef _REENTRANT +# define __STL_PTHREADS +# endif +# if (__GNUC__ < 2) || (__GNUC__ == 2 && __GNUC_MINOR__ < 95) +# define __STL_NO_FUNCTION_PTR_IN_CLASS_TEMPLATE +# endif +# endif + +# if defined(__SUNPRO_CC) +# define __STL_NO_BOOL +# define __STL_NEED_TYPENAME +# define __STL_NEED_EXPLICIT +# define __STL_USE_EXCEPTIONS +# ifdef _REENTRANT +# define __STL_PTHREADS +# endif +# define __SGI_STL_NO_ARROW_OPERATOR +# define __STL_PARTIAL_SPECIALIZATION_SYNTAX +# define __STL_NO_EXCEPTION_HEADER +# define __STL_NO_BAD_ALLOC +# endif + +# if defined(__COMO__) +# define __STL_MEMBER_TEMPLATES +# define __STL_MEMBER_TEMPLATE_CLASSES +# define __STL_TEMPLATE_FRIENDS +# define __STL_CLASS_PARTIAL_SPECIALIZATION +# define __STL_USE_EXCEPTIONS +# define __STL_HAS_NAMESPACES +# endif + +// Intel compiler, which uses the EDG front end. +# if defined(__ICL) +# define __STL_LONG_LONG +# define __STL_MEMBER_TEMPLATES +# define __STL_MEMBER_TEMPLATE_CLASSES +# define __STL_TEMPLATE_FRIENDS +# define __STL_FUNCTION_TMPL_PARTIAL_ORDER +# define __STL_CLASS_PARTIAL_SPECIALIZATION +# define __STL_NO_DRAND48 +# define __STL_HAS_NAMESPACES +# define __STL_USE_EXCEPTIONS +# define __STL_MEMBER_TEMPLATE_KEYWORD +# ifdef _CPPUNWIND +# define __STL_USE_EXCEPTIONS +# endif +# ifdef _MT +# define __STL_WIN32THREADS +# endif +# endif + +// Mingw32, egcs compiler using the Microsoft C runtime +# if defined(__MINGW32__) +# define __STL_NO_DRAND48 +# ifdef _MT +# define __STL_WIN32THREADS +# endif +# endif + +// Cygwin32, egcs compiler on MS Windows +# if defined(__CYGWIN__) +# define __STL_NO_DRAND48 +# endif + + + +// Microsoft compiler. +# if defined(_MSC_VER) && !defined(__ICL) && !defined(__MWERKS__) +# define __STL_NO_DRAND48 +# define __STL_STATIC_CONST_INIT_BUG +# define __STL_NEED_TYPENAME +# define __STL_NO_USING_CLAUSE_IN_CLASS +# define __STL_NO_FRIEND_TEMPLATE_CLASS +# if _MSC_VER < 1100 /* 1000 is version 4.0, 1100 is 5.0, 1200 is 6.0. */ +# define __STL_NEED_EXPLICIT +# define __STL_NO_BOOL +# define __STL_NO_BAD_ALLOC +# endif +# if _MSC_VER > 1000 +# include <yvals.h> +# define __STL_DONT_USE_BOOL_TYPEDEF +# endif +# define __STL_NON_TYPE_TMPL_PARAM_BUG +# define __SGI_STL_NO_ARROW_OPERATOR +# define __STL_DEFAULT_CONSTRUCTOR_BUG +# ifdef _CPPUNWIND +# define __STL_USE_EXCEPTIONS +# endif +# ifdef _MT +# define __STL_WIN32THREADS +# endif +# if _MSC_VER >= 1200 +# define __STL_PARTIAL_SPECIALIZATION_SYNTAX +# define __STL_HAS_NAMESPACES +# define __STL_CAN_THROW_RANGE_ERRORS +# define NOMINMAX +# undef min +# undef max +// disable warning 'initializers put in unrecognized initialization area' +# pragma warning ( disable : 4075 ) +// disable warning 'empty controlled statement found' +# pragma warning ( disable : 4390 ) +// disable warning 'debug symbol greater than 255 chars' +# pragma warning ( disable : 4786 ) +# endif +# if _MSC_VER < 1100 +# define __STL_NO_EXCEPTION_HEADER +# define __STL_NO_BAD_ALLOC +# endif + // Because of a Microsoft front end bug, we must not provide a + // namespace qualifier when declaring a friend function. +# define __STD_QUALIFIER +# endif + +# if defined(__BORLANDC__) +# define __STL_NO_BAD_ALLOC +# define __STL_NO_DRAND48 +# define __STL_DEFAULT_CONSTRUCTOR_BUG +# if __BORLANDC__ >= 0x540 /* C++ Builder 4.0 */ +# define __STL_CLASS_PARTIAL_SPECIALIZATION +# define __STL_FUNCTION_TMPL_PARTIAL_ORDER +# define __STL_EXPLICIT_FUNCTION_TMPL_ARGS +# define __STL_MEMBER_TEMPLATES +# define __STL_TEMPLATE_FRIENDS +# else +# define __STL_NEED_TYPENAME +# define __STL_LIMITED_DEFAULT_TEMPLATES +# define __SGI_STL_NO_ARROW_OPERATOR +# define __STL_NON_TYPE_TMPL_PARAM_BUG +# endif +# ifdef _CPPUNWIND +# define __STL_USE_EXCEPTIONS +# endif +# ifdef __MT__ +# define __STL_WIN32THREADS +# endif +# endif + +# if defined(__STL_NO_BOOL) && !defined(__STL_DONT_USE_BOOL_TYPEDEF) + typedef int bool; +# define true 1 +# define false 0 +# endif + +# ifdef __STL_NEED_TYPENAME +# define typename +# endif + +# ifdef __STL_LIMITED_DEFAULT_TEMPLATES +# define __STL_DEPENDENT_DEFAULT_TMPL(_Tp) +# else +# define __STL_DEPENDENT_DEFAULT_TMPL(_Tp) = _Tp +# endif + +# ifdef __STL_MEMBER_TEMPLATE_KEYWORD +# define __STL_TEMPLATE template +# else +# define __STL_TEMPLATE +# endif + +# ifdef __STL_NEED_EXPLICIT +# define explicit +# endif + +# ifdef __STL_EXPLICIT_FUNCTION_TMPL_ARGS +# define __STL_NULL_TMPL_ARGS <> +# else +# define __STL_NULL_TMPL_ARGS +# endif + +# if defined(__STL_CLASS_PARTIAL_SPECIALIZATION) \ + || defined (__STL_PARTIAL_SPECIALIZATION_SYNTAX) +# define __STL_TEMPLATE_NULL template<> +# else +# define __STL_TEMPLATE_NULL +# endif + +// Use standard-conforming allocators if we have the necessary language +// features. __STL_USE_SGI_ALLOCATORS is a hook so that users can +// disable new-style allocators, and continue to use the same kind of +// allocators as before, without having to edit library headers. +# if defined(__STL_CLASS_PARTIAL_SPECIALIZATION) && \ + defined(__STL_MEMBER_TEMPLATES) && \ + defined(__STL_MEMBER_TEMPLATE_CLASSES) && \ + !defined(__STL_NO_BOOL) && \ + !defined(__STL_NON_TYPE_TMPL_PARAM_BUG) && \ + !defined(__STL_LIMITED_DEFAULT_TEMPLATES) && \ + !defined(__STL_USE_SGI_ALLOCATORS) +# define __STL_USE_STD_ALLOCATORS +# endif + +# ifndef __STL_DEFAULT_ALLOCATOR +# ifdef __STL_USE_STD_ALLOCATORS +# define __STL_DEFAULT_ALLOCATOR(T) allocator< T > +# else +# define __STL_DEFAULT_ALLOCATOR(T) alloc +# endif +# endif + +// __STL_NO_NAMESPACES is a hook so that users can disable namespaces +// without having to edit library headers. __STL_NO_RELOPS_NAMESPACE is +// a hook so that users can disable the std::rel_ops namespace, keeping +// the relational operator template in namespace std, without having to +// edit library headers. +# if defined(__STL_HAS_NAMESPACES) && !defined(__STL_NO_NAMESPACES) +# define __STL_USE_NAMESPACES +# define __STD std +# define __STL_BEGIN_NAMESPACE namespace std { +# define __STL_END_NAMESPACE } +# if defined(__STL_FUNCTION_TMPL_PARTIAL_ORDER) && \ + !defined(__STL_NO_RELOPS_NAMESPACE) +# define __STL_USE_NAMESPACE_FOR_RELOPS +# define __STL_BEGIN_RELOPS_NAMESPACE namespace std { namespace rel_ops { +# define __STL_END_RELOPS_NAMESPACE } } +# define __STD_RELOPS std::rel_ops +# else /* Use std::rel_ops namespace */ +# define __STL_USE_NAMESPACE_FOR_RELOPS +# define __STL_BEGIN_RELOPS_NAMESPACE namespace std { +# define __STL_END_RELOPS_NAMESPACE } +# define __STD_RELOPS std +# endif /* Use std::rel_ops namespace */ +# else +# define __STD +# define __STL_BEGIN_NAMESPACE +# define __STL_END_NAMESPACE +# undef __STL_USE_NAMESPACE_FOR_RELOPS +# define __STL_BEGIN_RELOPS_NAMESPACE +# define __STL_END_RELOPS_NAMESPACE +# define __STD_RELOPS +# undef __STL_USE_NAMESPACES +# endif + +// Some versions of the EDG front end sometimes require an explicit +// namespace spec where they shouldn't. This macro facilitates that. +// If the bug becomes irrelevant, then all uses of __STD_QUALIFIER +// should be removed. The 7.3 beta SGI compiler has this bug, but the +// MR version is not expected to have it. + +# if defined(__STL_USE_NAMESPACES) && !defined(__STD_QUALIFIER) +# define __STD_QUALIFIER std:: +# else +# define __STD_QUALIFIER +# endif + +# ifdef __STL_USE_EXCEPTIONS +# define __STL_TRY try +# define __STL_CATCH_ALL catch(...) +# define __STL_THROW(x) throw x +# define __STL_RETHROW throw +# define __STL_NOTHROW throw() +# define __STL_UNWIND(action) catch(...) { action; throw; } +# else +# define __STL_TRY +# define __STL_CATCH_ALL if (false) +# define __STL_THROW(x) +# define __STL_RETHROW +# define __STL_NOTHROW +# define __STL_UNWIND(action) +# endif + +#ifdef __STL_ASSERTIONS +# include <stdio.h> +# define __stl_assert(expr) \ + if (!(expr)) { fprintf(stderr, "%s:%d STL assertion failure: %s\n", \ + __FILE__, __LINE__, # expr); abort(); } +#else +# define __stl_assert(expr) +#endif + +#if defined(__STL_WIN32THREADS) || defined(__STL_SGI_THREADS) \ + || defined(__STL_PTHREADS) || defined(__STL_UITHREADS) +# define __STL_THREADS +# define __STL_VOLATILE volatile +#else +# define __STL_VOLATILE +#endif + +#if defined(__STL_CLASS_PARTIAL_SPECIALIZATION) \ + && defined(__STL_MEMBER_TEMPLATES) \ + && !defined(_STL_NO_CONCEPT_CHECKS) +# define __STL_USE_CONCEPT_CHECKS +#endif + + +#endif /* __STL_CONFIG_H */ + +// Local Variables: +// mode:C++ +// End: diff --git a/WebKit/android/stl/stl_heap.h b/WebKit/android/stl/stl_heap.h new file mode 100644 index 0000000..9e3cf13 --- /dev/null +++ b/WebKit/android/stl/stl_heap.h @@ -0,0 +1,298 @@ +/* + * + * Copyright 2009, The Android Open Source Project + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * Copyright (c) 1997 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + +/* NOTE: This is an internal header file, included by other STL headers. + * You should not attempt to use it directly. + */ + +#ifndef __SGI_STL_INTERNAL_HEAP_H +#define __SGI_STL_INTERNAL_HEAP_H + +__STL_BEGIN_NAMESPACE + +#if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) +#pragma set woff 1209 +#endif + +// Heap-manipulation functions: push_heap, pop_heap, make_heap, sort_heap. + +template <class _RandomAccessIterator, class _Distance, class _Tp> +void +__push_heap(_RandomAccessIterator __first, + _Distance __holeIndex, _Distance __topIndex, _Tp __value) +{ + _Distance __parent = (__holeIndex - 1) / 2; + while (__holeIndex > __topIndex && *(__first + __parent) < __value) { + *(__first + __holeIndex) = *(__first + __parent); + __holeIndex = __parent; + __parent = (__holeIndex - 1) / 2; + } + *(__first + __holeIndex) = __value; +} + +template <class _RandomAccessIterator, class _Distance, class _Tp> +inline void +__push_heap_aux(_RandomAccessIterator __first, + _RandomAccessIterator __last, _Distance*, _Tp*) +{ + __push_heap(__first, _Distance((__last - __first) - 1), _Distance(0), + _Tp(*(__last - 1))); +} + +template <class _RandomAccessIterator> +inline void +push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, + _LessThanComparable); + __push_heap_aux(__first, __last, + __DISTANCE_TYPE(__first), __VALUE_TYPE(__first)); +} + +template <class _RandomAccessIterator, class _Distance, class _Tp, + class _Compare> +void +__push_heap(_RandomAccessIterator __first, _Distance __holeIndex, + _Distance __topIndex, _Tp __value, _Compare __comp) +{ + _Distance __parent = (__holeIndex - 1) / 2; + while (__holeIndex > __topIndex && __comp(*(__first + __parent), __value)) { + *(__first + __holeIndex) = *(__first + __parent); + __holeIndex = __parent; + __parent = (__holeIndex - 1) / 2; + } + *(__first + __holeIndex) = __value; +} + +template <class _RandomAccessIterator, class _Compare, + class _Distance, class _Tp> +inline void +__push_heap_aux(_RandomAccessIterator __first, + _RandomAccessIterator __last, _Compare __comp, + _Distance*, _Tp*) +{ + __push_heap(__first, _Distance((__last - __first) - 1), _Distance(0), + _Tp(*(__last - 1)), __comp); +} + +template <class _RandomAccessIterator, class _Compare> +inline void +push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, + _Compare __comp) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + __push_heap_aux(__first, __last, __comp, + __DISTANCE_TYPE(__first), __VALUE_TYPE(__first)); +} + +template <class _RandomAccessIterator, class _Distance, class _Tp> +void +__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, + _Distance __len, _Tp __value) +{ + _Distance __topIndex = __holeIndex; + _Distance __secondChild = 2 * __holeIndex + 2; + while (__secondChild < __len) { + if (*(__first + __secondChild) < *(__first + (__secondChild - 1))) + __secondChild--; + *(__first + __holeIndex) = *(__first + __secondChild); + __holeIndex = __secondChild; + __secondChild = 2 * (__secondChild + 1); + } + if (__secondChild == __len) { + *(__first + __holeIndex) = *(__first + (__secondChild - 1)); + __holeIndex = __secondChild - 1; + } + __push_heap(__first, __holeIndex, __topIndex, __value); +} + +template <class _RandomAccessIterator, class _Tp, class _Distance> +inline void +__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, + _RandomAccessIterator __result, _Tp __value, _Distance*) +{ + *__result = *__first; + __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value); +} + +template <class _RandomAccessIterator, class _Tp> +inline void +__pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, + _Tp*) +{ + __pop_heap(__first, __last - 1, __last - 1, + _Tp(*(__last - 1)), __DISTANCE_TYPE(__first)); +} + +template <class _RandomAccessIterator> +inline void pop_heap(_RandomAccessIterator __first, + _RandomAccessIterator __last) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, + _LessThanComparable); + __pop_heap_aux(__first, __last, __VALUE_TYPE(__first)); +} + +template <class _RandomAccessIterator, class _Distance, + class _Tp, class _Compare> +void +__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, + _Distance __len, _Tp __value, _Compare __comp) +{ + _Distance __topIndex = __holeIndex; + _Distance __secondChild = 2 * __holeIndex + 2; + while (__secondChild < __len) { + if (__comp(*(__first + __secondChild), *(__first + (__secondChild - 1)))) + __secondChild--; + *(__first + __holeIndex) = *(__first + __secondChild); + __holeIndex = __secondChild; + __secondChild = 2 * (__secondChild + 1); + } + if (__secondChild == __len) { + *(__first + __holeIndex) = *(__first + (__secondChild - 1)); + __holeIndex = __secondChild - 1; + } + __push_heap(__first, __holeIndex, __topIndex, __value, __comp); +} + +template <class _RandomAccessIterator, class _Tp, class _Compare, + class _Distance> +inline void +__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, + _RandomAccessIterator __result, _Tp __value, _Compare __comp, + _Distance*) +{ + *__result = *__first; + __adjust_heap(__first, _Distance(0), _Distance(__last - __first), + __value, __comp); +} + +template <class _RandomAccessIterator, class _Tp, class _Compare> +inline void +__pop_heap_aux(_RandomAccessIterator __first, + _RandomAccessIterator __last, _Tp*, _Compare __comp) +{ + __pop_heap(__first, __last - 1, __last - 1, _Tp(*(__last - 1)), __comp, + __DISTANCE_TYPE(__first)); +} + +template <class _RandomAccessIterator, class _Compare> +inline void +pop_heap(_RandomAccessIterator __first, + _RandomAccessIterator __last, _Compare __comp) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + __pop_heap_aux(__first, __last, __VALUE_TYPE(__first), __comp); +} + +template <class _RandomAccessIterator, class _Tp, class _Distance> +void +__make_heap(_RandomAccessIterator __first, + _RandomAccessIterator __last, _Tp*, _Distance*) +{ + if (__last - __first < 2) return; + _Distance __len = __last - __first; + _Distance __parent = (__len - 2)/2; + + while (true) { + __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent))); + if (__parent == 0) return; + __parent--; + } +} + +template <class _RandomAccessIterator> +inline void +make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, + _LessThanComparable); + __make_heap(__first, __last, + __VALUE_TYPE(__first), __DISTANCE_TYPE(__first)); +} + +template <class _RandomAccessIterator, class _Compare, + class _Tp, class _Distance> +void +__make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, + _Compare __comp, _Tp*, _Distance*) +{ + if (__last - __first < 2) return; + _Distance __len = __last - __first; + _Distance __parent = (__len - 2)/2; + + while (true) { + __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent)), + __comp); + if (__parent == 0) return; + __parent--; + } +} + +template <class _RandomAccessIterator, class _Compare> +inline void +make_heap(_RandomAccessIterator __first, + _RandomAccessIterator __last, _Compare __comp) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + __make_heap(__first, __last, __comp, + __VALUE_TYPE(__first), __DISTANCE_TYPE(__first)); +} + +template <class _RandomAccessIterator> +void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + __STL_REQUIRES(typename iterator_traits<_RandomAccessIterator>::value_type, + _LessThanComparable); + while (__last - __first > 1) + pop_heap(__first, __last--); +} + +template <class _RandomAccessIterator, class _Compare> +void +sort_heap(_RandomAccessIterator __first, + _RandomAccessIterator __last, _Compare __comp) +{ + __STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator); + while (__last - __first > 1) + pop_heap(__first, __last--, __comp); +} + +#if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) +#pragma reset woff 1209 +#endif + +__STL_END_NAMESPACE + +#endif /* __SGI_STL_INTERNAL_HEAP_H */ + +// Local Variables: +// mode:C++ +// End: diff --git a/WebKit/android/stl/stl_iterator_base.h b/WebKit/android/stl/stl_iterator_base.h new file mode 100644 index 0000000..746d1d4 --- /dev/null +++ b/WebKit/android/stl/stl_iterator_base.h @@ -0,0 +1,274 @@ +/* + * + * Copyright 2009, The Android Open Source Project + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * + * Copyright (c) 1996-1998 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + +/* NOTE: This is an internal header file, included by other STL headers. + * You should not attempt to use it directly. + */ + +#ifndef __SGI_STL_INTERNAL_ITERATOR_BASE_H +#define __SGI_STL_INTERNAL_ITERATOR_BASE_H + +// This file contains all of the general iterator-related utilities. +// The internal file stl_iterator.h contains predefined iterators, +// such as front_insert_iterator and istream_iterator. + +#include <concept_checks.h> + +struct input_iterator_tag {}; +struct output_iterator_tag {}; +struct forward_iterator_tag : public input_iterator_tag {}; +struct bidirectional_iterator_tag : public forward_iterator_tag {}; +struct random_access_iterator_tag : public bidirectional_iterator_tag {}; + +// The base classes input_iterator, output_iterator, forward_iterator, +// bidirectional_iterator, and random_access_iterator are not part of +// the C++ standard. (They have been replaced by struct iterator.) +// They are included for backward compatibility with the HP STL. + +template <class _Tp, class _Distance> struct input_iterator { + typedef input_iterator_tag iterator_category; + typedef _Tp value_type; + typedef _Distance difference_type; + typedef _Tp* pointer; + typedef _Tp& reference; +}; + +struct output_iterator { + typedef output_iterator_tag iterator_category; + typedef void value_type; + typedef void difference_type; + typedef void pointer; + typedef void reference; +}; + +template <class _Tp, class _Distance> struct forward_iterator { + typedef forward_iterator_tag iterator_category; + typedef _Tp value_type; + typedef _Distance difference_type; + typedef _Tp* pointer; + typedef _Tp& reference; +}; + + +template <class _Tp, class _Distance> struct bidirectional_iterator { + typedef bidirectional_iterator_tag iterator_category; + typedef _Tp value_type; + typedef _Distance difference_type; + typedef _Tp* pointer; + typedef _Tp& reference; +}; + +template <class _Tp, class _Distance> struct random_access_iterator { + typedef random_access_iterator_tag iterator_category; + typedef _Tp value_type; + typedef _Distance difference_type; + typedef _Tp* pointer; + typedef _Tp& reference; +}; + +template <class _Category, class _Tp, class _Distance = ptrdiff_t, + class _Pointer = _Tp*, class _Reference = _Tp&> +struct iterator { + typedef _Category iterator_category; + typedef _Tp value_type; + typedef _Distance difference_type; + typedef _Pointer pointer; + typedef _Reference reference; +}; + +template <class _Iterator> +struct iterator_traits { + typedef typename _Iterator::iterator_category iterator_category; + typedef typename _Iterator::value_type value_type; + typedef typename _Iterator::difference_type difference_type; + typedef typename _Iterator::pointer pointer; + typedef typename _Iterator::reference reference; +}; + +template <class _Tp> +struct iterator_traits<_Tp*> { + typedef random_access_iterator_tag iterator_category; + typedef _Tp value_type; + typedef ptrdiff_t difference_type; + typedef _Tp* pointer; + typedef _Tp& reference; +}; + +template <class _Tp> +struct iterator_traits<const _Tp*> { + typedef random_access_iterator_tag iterator_category; + typedef _Tp value_type; + typedef ptrdiff_t difference_type; + typedef const _Tp* pointer; + typedef const _Tp& reference; +}; + +// The overloaded functions iterator_category, distance_type, and +// value_type are not part of the C++ standard. (They have been +// replaced by struct iterator_traits.) They are included for +// backward compatibility with the HP STL. + +// We introduce internal names for these functions. + +template <class _Iter> +inline typename iterator_traits<_Iter>::iterator_category +__iterator_category(const _Iter&) +{ + typedef typename iterator_traits<_Iter>::iterator_category _Category; + return _Category(); +} + +template <class _Iter> +inline typename iterator_traits<_Iter>::difference_type* +__distance_type(const _Iter&) +{ + return static_cast<typename iterator_traits<_Iter>::difference_type*>(0); +} + +template <class _Iter> +inline typename iterator_traits<_Iter>::value_type* +__value_type(const _Iter&) +{ + return static_cast<typename iterator_traits<_Iter>::value_type*>(0); +} + +template <class _Iter> +inline typename iterator_traits<_Iter>::iterator_category +iterator_category(const _Iter& __i) { return __iterator_category(__i); } + + +template <class _Iter> +inline typename iterator_traits<_Iter>::difference_type* +distance_type(const _Iter& __i) { return __distance_type(__i); } + +template <class _Iter> +inline typename iterator_traits<_Iter>::value_type* +value_type(const _Iter& __i) { return __value_type(__i); } + +#define __ITERATOR_CATEGORY(__i) __iterator_category(__i) +#define __DISTANCE_TYPE(__i) __distance_type(__i) +#define __VALUE_TYPE(__i) __value_type(__i) + +template <class _InputIterator, class _Distance> +inline void __distance(_InputIterator __first, _InputIterator __last, + _Distance& __n, input_iterator_tag) +{ + while (__first != __last) { ++__first; ++__n; } +} + +template <class _RandomAccessIterator, class _Distance> +inline void __distance(_RandomAccessIterator __first, + _RandomAccessIterator __last, + _Distance& __n, random_access_iterator_tag) +{ + __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator); + __n += __last - __first; +} + +template <class _InputIterator, class _Distance> +inline void distance(_InputIterator __first, + _InputIterator __last, _Distance& __n) +{ + __STL_REQUIRES(_InputIterator, _InputIterator); + __distance(__first, __last, __n, iterator_category(__first)); +} + +#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION + +template <class _InputIterator> +inline typename iterator_traits<_InputIterator>::difference_type +__distance(_InputIterator __first, _InputIterator __last, input_iterator_tag) +{ + typename iterator_traits<_InputIterator>::difference_type __n = 0; + while (__first != __last) { + ++__first; ++__n; + } + return __n; +} + +template <class _RandomAccessIterator> +inline typename iterator_traits<_RandomAccessIterator>::difference_type +__distance(_RandomAccessIterator __first, _RandomAccessIterator __last, + random_access_iterator_tag) { + __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator); + return __last - __first; +} + +template <class _InputIterator> +inline typename iterator_traits<_InputIterator>::difference_type +distance(_InputIterator __first, _InputIterator __last) { + typedef typename iterator_traits<_InputIterator>::iterator_category + _Category; + __STL_REQUIRES(_InputIterator, _InputIterator); + return __distance(__first, __last, _Category()); +} + +#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */ + +template <class _InputIter, class _Distance> +inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) { + while (__n--) ++__i; +} + +#if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) +#pragma set woff 1183 +#endif + +template <class _BidirectionalIterator, class _Distance> +inline void __advance(_BidirectionalIterator& __i, _Distance __n, + bidirectional_iterator_tag) { + __STL_REQUIRES(_BidirectionalIterator, _BidirectionalIterator); + if (__n >= 0) + while (__n--) ++__i; + else + while (__n++) --__i; +} + +#if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) +#pragma reset woff 1183 +#endif + +template <class _RandomAccessIterator, class _Distance> +inline void __advance(_RandomAccessIterator& __i, _Distance __n, + random_access_iterator_tag) { + __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator); + __i += __n; +} + +template <class _InputIterator, class _Distance> +inline void advance(_InputIterator& __i, _Distance __n) { + __STL_REQUIRES(_InputIterator, _InputIterator); + __advance(__i, __n, iterator_category(__i)); +} + +#endif /* __SGI_STL_INTERNAL_ITERATOR_BASE_H */ + + + +// Local Variables: +// mode:C++ +// End: diff --git a/WebKit/android/stl/strings.h b/WebKit/android/stl/strings.h new file mode 100644 index 0000000..599a890 --- /dev/null +++ b/WebKit/android/stl/strings.h @@ -0,0 +1,25 @@ +/* + * 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 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. + */ + diff --git a/WebKit/android/wds/Command.cpp b/WebKit/android/wds/Command.cpp new file mode 100644 index 0000000..e150d4f --- /dev/null +++ b/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 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 "wds" +#include "config.h" + +#include "AndroidLog.h" +#include "CString.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> + +#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/WebKit/android/wds/Command.h b/WebKit/android/wds/Command.h new file mode 100644 index 0000000..753486d --- /dev/null +++ b/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 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 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/WebKit/android/wds/Connection.cpp b/WebKit/android/wds/Connection.cpp new file mode 100644 index 0000000..1afd00c --- /dev/null +++ b/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 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 "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/WebKit/android/wds/Connection.h b/WebKit/android/wds/Connection.h new file mode 100644 index 0000000..ce88316 --- /dev/null +++ b/WebKit/android/wds/Connection.h @@ -0,0 +1,85 @@ +/* + * 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 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 WDS_CONNECTION_H +#define WDS_CONNECTION_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/WebKit/android/wds/DebugServer.cpp b/WebKit/android/wds/DebugServer.cpp new file mode 100644 index 0000000..0990452 --- /dev/null +++ b/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 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 "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/WebKit/android/wds/DebugServer.h b/WebKit/android/wds/DebugServer.h new file mode 100644 index 0000000..a0a2aa8 --- /dev/null +++ b/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 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 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(); + Vector<Frame*> m_frames; + ThreadIdentifier m_threadId; + friend DebugServer* server(); +}; + +DebugServer* server(); + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/WebKit/android/wds/client/AdbConnection.cpp b/WebKit/android/wds/client/AdbConnection.cpp new file mode 100644 index 0000000..f83ad2f --- /dev/null +++ b/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 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 "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/WebKit/android/wds/client/AdbConnection.h b/WebKit/android/wds/client/AdbConnection.h new file mode 100644 index 0000000..433d7ea --- /dev/null +++ b/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 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 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/WebKit/android/wds/client/Android.mk b/WebKit/android/wds/client/Android.mk new file mode 100644 index 0000000..e6bf7cf --- /dev/null +++ b/WebKit/android/wds/client/Android.mk @@ -0,0 +1,41 @@ +## +## +## 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 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 := \ + AdbConnection.cpp \ + ClientUtils.cpp \ + Device.cpp \ + main.cpp + +LOCAL_STATIC_LIBRARIES := liblog libutils libcutils + +LOCAL_MODULE:= wdsclient + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/WebKit/android/wds/client/ClientUtils.cpp b/WebKit/android/wds/client/ClientUtils.cpp new file mode 100644 index 0000000..03514f6 --- /dev/null +++ b/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 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 "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/WebKit/android/wds/client/ClientUtils.h b/WebKit/android/wds/client/ClientUtils.h new file mode 100644 index 0000000..0aa068e --- /dev/null +++ b/WebKit/android/wds/client/ClientUtils.h @@ -0,0 +1,38 @@ +/* + * 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 WDS_CLIENT_UTILS_H +#define WDS_CLIENT_UTILS_H + +#include <arpa/inet.h> + +// 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/WebKit/android/wds/client/Device.cpp b/WebKit/android/wds/client/Device.cpp new file mode 100644 index 0000000..6c36979 --- /dev/null +++ b/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 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 "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/WebKit/android/wds/client/Device.h b/WebKit/android/wds/client/Device.h new file mode 100644 index 0000000..df076cb --- /dev/null +++ b/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 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 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/WebKit/android/wds/client/DeviceList.h b/WebKit/android/wds/client/DeviceList.h new file mode 100644 index 0000000..0b278d2 --- /dev/null +++ b/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 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 WDS_DEVICE_LIST_H +#define WDS_DEVICE_LIST_H + +#include <utils/Vector.h> + +class Device; + +typedef android::Vector<Device*> DeviceList; + +#endif diff --git a/WebKit/android/wds/client/main.cpp b/WebKit/android/wds/client/main.cpp new file mode 100644 index 0000000..bc8df86 --- /dev/null +++ b/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 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 "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; +} |