diff options
Diffstat (limited to 'Source/WebKit/android')
118 files changed, 20857 insertions, 6627 deletions
diff --git a/Source/WebKit/android/AndroidLog.h b/Source/WebKit/android/AndroidLog.h index f034d35..3d3eaaa 100644 --- a/Source/WebKit/android/AndroidLog.h +++ b/Source/WebKit/android/AndroidLog.h @@ -26,25 +26,17 @@ #ifndef AndroidLog_h #define AndroidLog_h -#ifndef LOG_TAG -#define LOG_TAG __FILE__ -#endif - -#include <cutils/log.h> -#include <utils/Trace.h> -#include <wtf/CurrentTime.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 ALOGD(__VA_ARGS__); } + 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 ALOGD(__VA_ARGS__); } + fprintf(gRenderTreeFile, __VA_ARGS__); else LOGD(__VA_ARGS__); } #else #define DUMP_DOM_LOGD(...) ((void)0) #define DUMP_RENDER_LOGD(...) ((void)0) @@ -53,28 +45,4 @@ extern FILE* gRenderTreeFile; #define DISPLAY_TREE_LOG_FILE "/sdcard/displayTree.txt" #define LAYERS_TREE_LOG_FILE "/sdcard/layersTree.plist" -#define FLOAT_RECT_FORMAT "[x=%.2f,y=%.2f,w=%.2f,h=%.2f]" -#define FLOAT_RECT_ARGS(fr) fr.x(), fr.y(), fr.width(), fr.height() -#define INT_RECT_FORMAT "[x=%d,y=%d,w=%d,h=%d]" -#define INT_RECT_ARGS(ir) ir.x(), ir.y(), ir.width(), ir.height() - -#define TRACE_METHOD() android::ScopedTrace __st(ATRACE_TAG_WEBVIEW, __func__); - -#define TIME_METHOD() MethodTimer __method_timer(__func__) -class MethodTimer { -public: - MethodTimer(const char* name) - : m_methodName(name) - { - m_startTime = currentTimeMS(); - } - virtual ~MethodTimer() { - double duration = currentTimeMS() - m_startTime; - ALOGD("%s took %.2fms", m_methodName, duration); - } -private: - const char* m_methodName; - double m_startTime; -}; - #endif // AndroidLog_h diff --git a/Source/WebKit/android/RenderSkinAndroid.cpp b/Source/WebKit/android/RenderSkinAndroid.cpp index 9529624..4a9ce68 100644 --- a/Source/WebKit/android/RenderSkinAndroid.cpp +++ b/Source/WebKit/android/RenderSkinAndroid.cpp @@ -27,11 +27,14 @@ #include "config.h" #include "RenderSkinAndroid.h" +#include "RenderSkinButton.h" +#include "RenderSkinCombo.h" #include "RenderSkinMediaButton.h" +#include "RenderSkinRadio.h" #include "SkImageDecoder.h" -#include <androidfw/AssetManager.h> -#include <androidfw/Asset.h> +#include <utils/AssetManager.h> +#include <utils/Asset.h> namespace WebCore { @@ -40,6 +43,7 @@ RenderSkinAndroid::Resolution RenderSkinAndroid::s_drawableResolution = RenderSk RenderSkinAndroid::~RenderSkinAndroid() { + delete m_button; } RenderSkinAndroid::RenderSkinAndroid(String drawableDirectory) { @@ -52,6 +56,7 @@ RenderSkinAndroid::RenderSkinAndroid(String drawableDirectory) s_drawableDirectory = drawableDirectory; } + m_button = new RenderSkinButton(drawableDirectory); } bool RenderSkinAndroid::DecodeBitmap(android::AssetManager* am, const char* fileName, SkBitmap* bitmap) @@ -60,14 +65,14 @@ bool RenderSkinAndroid::DecodeBitmap(android::AssetManager* am, const char* file if (!asset) { asset = am->openNonAsset(fileName, android::Asset::ACCESS_BUFFER); if (!asset) { - ALOGD("RenderSkinAndroid: File \"%s\" not found.\n", fileName); + LOGD("RenderSkinAndroid: File \"%s\" not found.\n", fileName); return false; } } bool success = SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(), bitmap); if (!success) { - ALOGD("RenderSkinAndroid: Failed to decode %s\n", fileName); + LOGD("RenderSkinAndroid: Failed to decode %s\n", fileName); } delete asset; diff --git a/Source/WebKit/android/RenderSkinAndroid.h b/Source/WebKit/android/RenderSkinAndroid.h index 1d3820d..bbc327d 100644 --- a/Source/WebKit/android/RenderSkinAndroid.h +++ b/Source/WebKit/android/RenderSkinAndroid.h @@ -36,6 +36,7 @@ class SkBitmap; namespace WebCore { class Node; +class RenderSkinButton; class RenderSkinAndroid { @@ -68,9 +69,12 @@ public: static String DrawableDirectory() { return s_drawableDirectory; } static Resolution DrawableResolution() { return s_drawableResolution; } + RenderSkinButton* renderSkinButton() const { return m_button; } + private: static String s_drawableDirectory; static Resolution s_drawableResolution; + RenderSkinButton* m_button; }; } // WebCore diff --git a/Source/WebKit/android/RenderSkinButton.cpp b/Source/WebKit/android/RenderSkinButton.cpp new file mode 100644 index 0000000..11e2fa8 --- /dev/null +++ b/Source/WebKit/android/RenderSkinButton.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "WebCore" + +#include "config.h" +#include "android_graphics.h" +#include "Document.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinButton.h" +#include "RenderSkinNinePatch.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include "SkRect.h" +#include <utils/Asset.h> +#include <utils/AssetManager.h> +#include <utils/Debug.h> +#include <utils/Log.h> +#include <utils/ResourceTypes.h> +#include <wtf/text/CString.h> + +extern android::AssetManager* globalAssetManager(); + +static const char* gFiles[] = { + "btn_default_disabled_holo.9.png", + "btn_default_normal_holo.9.png", + "btn_default_focused_holo.9.png", + "btn_default_pressed_holo.9.png" + }; + +namespace WebCore { + +RenderSkinButton::RenderSkinButton(String drawableDirectory) + : m_decoded(false) + , m_decodingAttempted(false) + , m_drawableDirectory(drawableDirectory) +{ + // 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::decode() +{ + m_decodingAttempted = true; + + android::AssetManager* am = globalAssetManager(); + + for (size_t i = 0; i < 4; i++) { + String path = m_drawableDirectory; + path.append(String(gFiles[i])); + if (!RenderSkinNinePatch::decodeAsset(am, path.utf8().data(), &m_buttons[i])) { + m_decoded = false; + LOGE("RenderSkinButton::decode: button assets failed to decode\n\tWebView buttons will not draw"); + return; + } + } + m_decoded = true; +} + +void RenderSkinButton::draw(SkCanvas* canvas, const IntRect& r, + RenderSkinAndroid::State newState) +{ + if (!m_decodingAttempted) + decode(); + + // If we failed to decode, do nothing. This way the browser still works, + // and webkit will still draw the label and layout space for us. + if (!m_decoded) { + return; + } + + // Ensure that the state is within the valid range of our array. + SkASSERT(static_cast<unsigned>(newState) < + static_cast<unsigned>(RenderSkinAndroid::kNumStates)); + + RenderSkinNinePatch::DrawNinePatch(canvas, SkRect(r), m_buttons[newState]); +} + +} //WebCore diff --git a/Source/WebKit/android/RenderSkinButton.h b/Source/WebKit/android/RenderSkinButton.h new file mode 100644 index 0000000..83c57dd --- /dev/null +++ b/Source/WebKit/android/RenderSkinButton.h @@ -0,0 +1,55 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderSkinButton_h +#define RenderSkinButton_h + +#include "RenderSkinAndroid.h" +#include "RenderSkinNinePatch.h" + +class SkCanvas; + +namespace WebCore { +class IntRect; + +class RenderSkinButton { +public: + RenderSkinButton(String drawableDirectory); + /** + * Draw the skin to the canvas, using the rectangle for its bounds and the + * State to determine which skin to use, i.e. focused or not focused. + */ + void draw(SkCanvas* , const IntRect& , RenderSkinAndroid::State); + + void decode(); +private: + bool m_decoded; + bool m_decodingAttempted; + NinePatch m_buttons[4]; + String m_drawableDirectory; +}; + +} // WebCore +#endif diff --git a/Source/WebKit/android/RenderSkinCombo.cpp b/Source/WebKit/android/RenderSkinCombo.cpp new file mode 100644 index 0000000..1711cfa --- /dev/null +++ b/Source/WebKit/android/RenderSkinCombo.cpp @@ -0,0 +1,213 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderSkinCombo.h" + +#include "Document.h" +#include "Element.h" +#include "Node.h" +#include "NodeRenderStyle.h" +#include "RenderStyle.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include <utils/AssetManager.h> +#include <wtf/text/CString.h> + +extern android::AssetManager* globalAssetManager(); + +namespace WebCore { + +// Indicates if the entire asset is being drawn, or if the border is being +// excluded and just the arrow drawn. +enum BorderStyle { + FullAsset, + NoBorder, + BorderStyleCount // Keep at the end. +}; + +// There are 2.5 different concepts of a 'border' here, which results +// in rather a lot of magic constants. + +// Firstly, we have the extra padding that webkit needs to know about, +// which defines how much bigger this element is made by the +// asset. This is actually a bit broader than the actual border on the +// asset, to make things look less cramped. The border is the same +// width on all sides, except on the right when it's significantly +// wider to allow for the arrow. +const int RenderSkinCombo::arrowMargin[ResolutionCount] = { + 16, // Medium resolution + 25, // High resolution + 34 // Extra high resolution +}; +const int RenderSkinCombo::padMargin[ResolutionCount] = { + 1, // Medium resolution + 1, // High resolution + 1 // Extra high resolution +}; + +namespace { +// Then we have the borders used for the 9-patch stretch. The +// rectangle at the centre of these borders is entirely below and to +// the left of the arrow in the asset. Hence the border widths are the +// same for the bottom and left, but are different for the top. The +// right hand border width happens to be the same as arrowMargin +// defined above. +const int stretchMargin[RenderSkinAndroid::ResolutionCount] = { // border width for the bottom and left of the 9-patch + 2, // Medium resolution + 2, // High resolution + 3 // Extra high resolution + +}; +const int stretchTop[RenderSkinAndroid::ResolutionCount] = { // border width for the top of the 9-patch + 16, // Medium resolution + 23, // High resolution + 32 // Extra high resolution +}; + +// Finally, if the border is defined by the CSS, we only draw the +// arrow and not the border. We do this by drawing the relevant subset +// of the bitmap, which must now be precisely determined by what's in +// the asset with no extra padding to make things look properly +// spaced. The border to remove at the top, right and bottom of the +// image is the same as stretchMargin above, but we need to know the width +// of the arrow. +const int arrowWidth[RenderSkinAndroid::ResolutionCount] = { + 18, // Medium resolution + 27, // High resolution + 36 // Extra high resolution +}; + +// scale factors for various resolutions +const float scaleFactor[RenderSkinAndroid::ResolutionCount] = { + 1.0f, // medium res + 1.5f, // high res + 2.0f // extra high res +}; + +// Store the calculated 9 patch margins for each border style. +SkIRect margin[BorderStyleCount]; + +SkBitmap bitmaps[2][BorderStyleCount]; // Collection of assets for a combo box - 2 states (enabled/disabled) +bool isDecodingAttempted = false; // True if we've tried to decode the assets +bool isDecoded = false; // True if all assets were decoded + +} // namespace + +int RenderSkinCombo::minHeight() { + return SkScalarRound(stretchTop[RenderSkinAndroid::DrawableResolution()] + / scaleFactor[RenderSkinAndroid::DrawableResolution()]); +} + +void RenderSkinCombo::Decode() +{ + if (isDecodingAttempted) + return; + + isDecodingAttempted = true; + isDecoded = false; + + android::AssetManager* am = globalAssetManager(); + + String drawableDirectory = RenderSkinAndroid::DrawableDirectory(); + Resolution res = RenderSkinAndroid::DrawableResolution(); + + isDecoded = RenderSkinAndroid::DecodeBitmap(am, (drawableDirectory + "combobox_nohighlight.png").utf8().data(), &bitmaps[kNormal][FullAsset]); + isDecoded &= RenderSkinAndroid::DecodeBitmap(am, (drawableDirectory + "combobox_disabled.png").utf8().data(), &bitmaps[kDisabled][FullAsset]); + + int width = bitmaps[kNormal][FullAsset].width(); + int height = bitmaps[kNormal][FullAsset].height(); + SkIRect subset; + subset.set(width - arrowWidth[res], 0, width, height); + bitmaps[kNormal][FullAsset].extractSubset(&bitmaps[kNormal][NoBorder], subset); + bitmaps[kDisabled][FullAsset].extractSubset(&bitmaps[kDisabled][NoBorder], subset); + + // Calculate 9 patch margins. + SkIRect fullAssetMargin; + fullAssetMargin.fLeft = stretchMargin[res]; + fullAssetMargin.fTop = stretchMargin[res]; + fullAssetMargin.fRight = arrowMargin[res] + stretchMargin[res]; + fullAssetMargin.fBottom = stretchTop[res]; + + SkIRect noBorderMargin; + noBorderMargin.fLeft = 0; + noBorderMargin.fTop = stretchTop[res]; + noBorderMargin.fRight = 0; + noBorderMargin.fBottom = stretchMargin[res]; + + margin[FullAsset] = fullAssetMargin; + margin[NoBorder] = noBorderMargin; +} + +bool RenderSkinCombo::Draw(SkCanvas* canvas, Node* element, int x, int y, int width, int height) +{ + if (!isDecodingAttempted) + Decode(); + + if (!isDecoded) + return true; + + int resolution = RenderSkinAndroid::DrawableResolution(); + State state = (element->isElementNode() && static_cast<Element*>(element)->isEnabledFormControl()) ? kNormal : kDisabled; + height = std::max(height, (stretchMargin[resolution] * 2)); + + SkRect bounds; + BorderStyle drawBorder = FullAsset; + + bounds.set(SkIntToScalar(x+1), SkIntToScalar(y+1), SkIntToScalar(x + width-1), SkIntToScalar(y + height-1)); + RenderStyle* style = element->renderStyle(); + SkPaint paint; + paint.setColor(style->visitedDependentColor(CSSPropertyBackgroundColor).rgb()); + canvas->drawRect(bounds, paint); + + bounds.set(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + width), SkIntToScalar(y + height)); + + // If this is an appearance where RenderTheme::paint returns true + // without doing anything, this means that + // RenderBox::PaintBoxDecorationWithSize will end up painting the + // border, so we shouldn't paint a border here. + if (style->appearance() == MenulistButtonPart || + style->appearance() == ListboxPart || + style->appearance() == TextFieldPart || + style->appearance() == TextAreaPart) { + bounds.fLeft += SkIntToScalar(width - RenderSkinCombo::extraWidth()); + bounds.fRight -= SkIntToScalar(style->borderRightWidth()); + bounds.fTop += SkIntToScalar(style->borderTopWidth()); + bounds.fBottom -= SkIntToScalar(style->borderBottomWidth()); + drawBorder = NoBorder; + } + float scale = scaleFactor[resolution]; + bounds.fLeft = bounds.fLeft * scale; + bounds.fRight = bounds.fRight * scale; + bounds.fTop = bounds.fTop * scale; + bounds.fBottom = bounds.fBottom * scale; + int count = canvas->save(); + canvas->scale(1.0f / scale, 1.0f / scale); + SkNinePatch::DrawNine(canvas, bounds, bitmaps[state][drawBorder], margin[drawBorder]); + canvas->restoreToCount(count); + return false; +} + +} // namspace WebCore diff --git a/Source/WebKit/android/RenderSkinCombo.h b/Source/WebKit/android/RenderSkinCombo.h new file mode 100644 index 0000000..a11faac --- /dev/null +++ b/Source/WebKit/android/RenderSkinCombo.h @@ -0,0 +1,62 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderSkinCombo_h +#define RenderSkinCombo_h + +#include "RenderSkinAndroid.h" +#include "SkRect.h" + +class SkCanvas; + +namespace WebCore { + +// This is very similar to RenderSkinButton - maybe they should be the same class? +class RenderSkinCombo : public RenderSkinAndroid +{ +public: + + static void Decode(); + /** + * 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[RenderSkinAndroid::DrawableResolution()]; } + static int minHeight(); + static int padding() { return padMargin[RenderSkinAndroid::DrawableResolution()]; } + + +private: + const static int arrowMargin[ResolutionCount]; + const static int padMargin[ResolutionCount]; +}; + +} // WebCore + +#endif diff --git a/Source/WebKit/android/RenderSkinMediaButton.cpp b/Source/WebKit/android/RenderSkinMediaButton.cpp index b3aa57d..ef4b313 100644 --- a/Source/WebKit/android/RenderSkinMediaButton.cpp +++ b/Source/WebKit/android/RenderSkinMediaButton.cpp @@ -36,7 +36,7 @@ #include "SkCanvas.h" #include "SkNinePatch.h" #include "SkRect.h" -#include <androidfw/AssetManager.h> +#include <utils/AssetManager.h> #include <utils/Debug.h> #include <utils/Log.h> #include <wtf/text/CString.h> @@ -82,22 +82,19 @@ void RenderSkinMediaButton::Decode() String path = drawableDirectory + gFiles[i].name; if (!RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &gButton[i])) { gDecodingFailed = true; - ALOGD("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw"); + LOGD("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw"); break; } } } void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonType, - bool translucent, RenderObject* o, bool drawBackground) + bool translucent, RenderObject* o) { if (!gDecoded) { Decode(); } - if (!canvas) - return; - // 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 (gDecodingFailed) @@ -105,6 +102,7 @@ void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonT bool drawsNinePatch = false; bool drawsImage = true; + bool drawsBackgroundColor = true; int ninePatchIndex = 0; int imageIndex = 0; @@ -138,11 +136,13 @@ void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonT case SPINNER_INNER: case VIDEO: { + drawsBackgroundColor = false; imageIndex = buttonType + 1; break; } case BACKGROUND_SLIDER: { + drawsBackgroundColor = false; drawsImage = false; break; } @@ -155,6 +155,7 @@ void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonT } case SLIDER_THUMB: { + drawsBackgroundColor = false; imageMargin = 0; imageIndex = buttonType + 1; break; @@ -163,7 +164,7 @@ void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonT return; } - if (drawBackground) { + if (drawsBackgroundColor) { canvas->drawRect(r, paint); } diff --git a/Source/WebKit/android/RenderSkinMediaButton.h b/Source/WebKit/android/RenderSkinMediaButton.h index 484b90c..d8b7c8d 100644 --- a/Source/WebKit/android/RenderSkinMediaButton.h +++ b/Source/WebKit/android/RenderSkinMediaButton.h @@ -42,7 +42,7 @@ public: * State to determine which skin to use, i.e. focused or not focused. */ static void Draw(SkCanvas* , const IntRect& , int buttonType, bool translucent = false, - RenderObject* o = 0, bool drawBackground = true); + RenderObject* o = 0); /** * Button types */ diff --git a/Source/WebKit/android/RenderSkinNinePatch.cpp b/Source/WebKit/android/RenderSkinNinePatch.cpp index 9ef0b4a..faa9dc4 100644 --- a/Source/WebKit/android/RenderSkinNinePatch.cpp +++ b/Source/WebKit/android/RenderSkinNinePatch.cpp @@ -16,18 +16,18 @@ #include "config.h" -#include "NinePatchPeeker.h" #include "RenderSkinNinePatch.h" +#include "NinePatchPeeker.h" #include "SkCanvas.h" #include "SkImageDecoder.h" #include "SkNinePatch.h" #include "SkRect.h" #include "SkStream.h" #include "SkTemplates.h" -#include <androidfw/Asset.h> -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> +#include <utils/Asset.h> +#include <utils/AssetManager.h> #include <utils/Log.h> +#include <utils/ResourceTypes.h> class SkPaint; class SkRegion; @@ -53,7 +53,7 @@ bool RenderSkinNinePatch::decodeAsset(AssetManager* am, const char* filename, Ni SkImageDecoder* decoder = SkImageDecoder::Factory(&stream); if (!decoder) { asset->close(); - ALOGE("RenderSkinNinePatch::Failed to create an image decoder"); + LOGE("RenderSkinNinePatch::Failed to create an image decoder"); return false; } @@ -68,13 +68,13 @@ bool RenderSkinNinePatch::decodeAsset(AssetManager* am, const char* filename, Ni decoder->setPeeker(&peeker); if (!decoder->decode(&stream, &ninepatch->m_bitmap, prefConfig, mode, true)) { asset->close(); - ALOGE("RenderSkinNinePatch::Failed to decode nine patch asset"); + LOGE("RenderSkinNinePatch::Failed to decode nine patch asset"); return false; } asset->close(); - if (!peeker.fPatch) { - ALOGE("RenderSkinNinePatch::Patch data not valid"); + if (!peeker.fPatchIsValid) { + LOGE("RenderSkinNinePatch::Patch data not valid"); return false; } void** data = &ninepatch->m_serializedPatchData; diff --git a/Source/WebKit/android/RenderSkinNinePatch.h b/Source/WebKit/android/RenderSkinNinePatch.h index 8cda795..e4db260 100644 --- a/Source/WebKit/android/RenderSkinNinePatch.h +++ b/Source/WebKit/android/RenderSkinNinePatch.h @@ -18,7 +18,7 @@ #define RenderSkinNinePatch_h #include "SkBitmap.h" -#include "androidfw/Asset.h" +#include "utils/Asset.h" namespace android { class AssetManager; diff --git a/Source/WebKit/android/RenderSkinRadio.cpp b/Source/WebKit/android/RenderSkinRadio.cpp new file mode 100644 index 0000000..3c29818 --- /dev/null +++ b/Source/WebKit/android/RenderSkinRadio.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "RenderSkinRadio.h" + +#include "android_graphics.h" +#include "Document.h" +#include "Element.h" +#include "InputElement.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinAndroid.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkRect.h" +#include <utils/AssetManager.h> +#include <wtf/text/CString.h> + +extern android::AssetManager* globalAssetManager(); + +static const char* checks[] = { "btn_check_off_holo.png", + "btn_check_on_holo.png", + "btn_radio_off_holo.png", + "btn_radio_on_holo.png"}; +// Matches the width of the bitmap +static SkScalar s_bitmapWidth; + +namespace WebCore { + +static SkBitmap s_bitmap[4]; +static bool s_decodingAttempted = false; +static bool s_decoded = false; + +void RenderSkinRadio::Decode() { + if (s_decodingAttempted) + return; + + s_decodingAttempted = true; + s_decoded = false; + + android::AssetManager* am = globalAssetManager(); + String drawableDir = RenderSkinAndroid::DrawableDirectory(); + for (int i = 0; i < 4; i++) { + String path = drawableDir + checks[i]; + if (!RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &s_bitmap[i])) + return; + } + s_decoded = true; + s_bitmapWidth = SkIntToScalar(s_bitmap[0].width()); +} + +void RenderSkinRadio::Draw(SkCanvas* canvas, Node* element, const IntRect& ir, + bool isCheckBox) +{ + if (!element) + return; + + if (!s_decodingAttempted) + Decode(); + + if (!s_decoded) + return; + + SkRect r(ir); + // Set up a paint to with filtering to look better. + SkPaint paint; + paint.setFlags(SkPaint::kFilterBitmap_Flag); + int saveScaleCount = 0; + + if (!element->isElementNode() || + !static_cast<Element*>(element)->isEnabledFormControl()) { + paint.setAlpha(0x80); + } + SkScalar width = r.width(); + SkScalar scale = SkScalarDiv(width, s_bitmapWidth); + saveScaleCount = canvas->save(); + canvas->translate(r.fLeft, r.fTop); + canvas->scale(scale, scale); + + bool checked = false; + if (InputElement* inputElement = element->toInputElement()) { + checked = inputElement->isChecked(); + } + + canvas->drawBitmap(s_bitmap[checked + 2*(!isCheckBox)], + 0, 0, &paint); + canvas->restoreToCount(saveScaleCount); +} + +} //WebCore diff --git a/Source/WebKit/android/jni/AndroidHitTestResult.h b/Source/WebKit/android/RenderSkinRadio.h index 5bbfc6b..34101cf 100644 --- a/Source/WebKit/android/jni/AndroidHitTestResult.h +++ b/Source/WebKit/android/RenderSkinRadio.h @@ -1,5 +1,5 @@ /* - * Copyright 2012, The Android Open Source Project + * 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 @@ -23,45 +23,39 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AndroidHitTestResult_h -#define AndroidHitTestResult_h +#ifndef RenderSkinRadio_h +#define RenderSkinRadio_h -#include "content/content_detector.h" -#include "Element.h" -#include "HitTestResult.h" -#include "IntRect.h" -#include "wtf/Vector.h" +#include "PlatformString.h" -#include <jni.h> +class SkCanvas; namespace android { + class AssetManager; +} -class WebViewCore; +namespace WebCore { -class AndroidHitTestResult +class Node; +class IntRect; + +/* RenderSkin for a radio button or a checkbox + */ +class RenderSkinRadio { public: - AndroidHitTestResult(WebViewCore*, WebCore::HitTestResult&); - ~AndroidHitTestResult() {} - - WebCore::HitTestResult& hitTestResult() { return m_hitTestResult; } - Vector<WebCore::IntRect>& highlightRects() { return m_highlightRects; } + static void SetDrawableDirectory(String drawableDirectory); - void setURLElement(WebCore::Element* element); - void buildHighlightRects(); - void searchContentDetectors(); + // Perform lazy decoding the first time this a radio/checkbox is needed. + static void Decode(); - jobject createJavaObject(JNIEnv*); - -private: - Vector<WebCore::IntRect> enclosingParentRects(WebCore::Node* node); - - WebViewCore* m_webViewCore; - WebCore::HitTestResult m_hitTestResult; - Vector<WebCore::IntRect> m_highlightRects; - ContentDetector::Result m_searchResult; + /** + * 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); }; -} // namespace android - -#endif // AndroidHitTestResult_h +} // WebCore +#endif diff --git a/Source/WebKit/android/TimeCounter.cpp b/Source/WebKit/android/TimeCounter.cpp new file mode 100644 index 0000000..2393f8a --- /dev/null +++ b/Source/WebKit/android/TimeCounter.cpp @@ -0,0 +1,198 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "WebCore" + +#include "config.h" +#include "TimeCounter.h" + +#include "MemoryCache.h" +#include "KURL.h" +#include "Node.h" +#include "SystemTime.h" +#include "StyleBase.h" +#include <sys/time.h> +#include <time.h> +#include <utils/Log.h> +#include <wtf/CurrentTime.h> +#include <wtf/text/CString.h> + +#if USE(JSC) +#include "JSDOMWindow.h" +#include <runtime/JSGlobalObject.h> +#include <runtime/JSLock.h> +#endif + +using namespace WebCore; +using namespace WTF; +using namespace JSC; + +namespace android { + +uint32_t getThreadMsec() +{ +#if defined(HAVE_POSIX_CLOCKS) + struct timespec tm; + + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); + return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000; +#else + struct timeval now; + struct timezone zone; + + gettimeofday(&now, &zone); + return now.tv_sec * 1000LL + now.tv_usec / 1000; +#endif +} + +#ifdef ANDROID_INSTRUMENT + +static double sStartTotalTime; +static uint32_t sStartThreadTime; +static double sLastTotalTime; +static uint32_t sLastThreadTime; + +uint32_t TimeCounter::sStartWebCoreThreadTime; +uint32_t TimeCounter::sEndWebCoreThreadTime; +bool TimeCounter::sRecordWebCoreTime; +uint32_t TimeCounter::sTotalTimeUsed[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sLastTimeUsed[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sCounter[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sLastCounter[TimeCounter::TotalTimeCounterCount]; +uint32_t TimeCounter::sStartTime[TimeCounter::TotalTimeCounterCount]; + +int QemuTracerAuto::reentry_count = 0; + +static const char* timeCounterNames[] = { + "css parsing", + "javascript", + "javascript init", + "javascript parsing", + "javascript execution", + "calculate style", + "Java callback (frame bridge)", + "parsing (may include calcStyle, Java callback or inline script execution)", + "layout", + "native 1 (frame bridge)", + "native 2 (resource load)", + "native 3 (shared timer)", + "build nav (webview core)", + "record content (webview core)", + "native 4 (webview core)", + "draw content (webview ui)", +}; + +void TimeCounter::record(enum Type type, const char* functionName) +{ + recordNoCounter(type, functionName); + sCounter[type]++; +} + +void TimeCounter::recordNoCounter(enum Type type, const char* functionName) +{ + uint32_t time = sEndWebCoreThreadTime = getThreadMsec(); + uint32_t elapsed = time - sStartTime[type]; + sTotalTimeUsed[type] += elapsed; + if (elapsed > 1000) + LOGW("***** %s() used %d ms\n", functionName, elapsed); +} + +void TimeCounter::report(const KURL& url, int live, int dead, size_t arenaSize) +{ + String urlString = url; + int totalTime = static_cast<int>((currentTime() - sStartTotalTime) * 1000); + int threadTime = getThreadMsec() - sStartThreadTime; + LOGD("*-* Total load time: %d ms, thread time: %d ms for %s\n", + totalTime, threadTime, urlString.utf8().data()); + for (Type type = (Type) 0; type < TotalTimeCounterCount; type + = (Type) (type + 1)) { + char scratch[256]; + int index = sprintf(scratch, "*-* Total %s time: %d ms", + timeCounterNames[type], sTotalTimeUsed[type]); + if (sCounter[type] > 0) + sprintf(&scratch[index], " called %d times", sCounter[type]); + LOGD("%s", scratch); + } + LOGD("Current cache has %d bytes live and %d bytes dead", live, dead); + LOGD("Current render arena takes %d bytes", arenaSize); +#if USE(JSC) + JSLock lock(false); + Heap::Statistics jsHeapStatistics = JSDOMWindow::commonJSGlobalData()->heap.statistics(); + LOGD("Current JavaScript heap size is %d and has %d bytes free", + jsHeapStatistics.size, jsHeapStatistics.free); +#endif + LOGD("Current CSS styles use %d bytes", StyleBase::reportStyleSize()); + LOGD("Current DOM nodes use %d bytes", WebCore::Node::reportDOMNodesSize()); +} + +void TimeCounter::reportNow() +{ + double current = currentTime(); + uint32_t currentThread = getThreadMsec(); + int elapsedTime = static_cast<int>((current - sLastTotalTime) * 1000); + int elapsedThreadTime = currentThread - sLastThreadTime; + LOGD("*-* Elapsed time: %d ms, ui thread time: %d ms, webcore thread time:" + " %d ms\n", elapsedTime, elapsedThreadTime, sEndWebCoreThreadTime - + sStartWebCoreThreadTime); + for (Type type = (Type) 0; type < TotalTimeCounterCount; type + = (Type) (type + 1)) { + if (sTotalTimeUsed[type] == sLastTimeUsed[type]) + continue; + char scratch[256]; + int index = sprintf(scratch, "*-* Diff %s time: %d ms", + timeCounterNames[type], sTotalTimeUsed[type] - sLastTimeUsed[type]); + if (sCounter[type] > sLastCounter[type]) + sprintf(&scratch[index], " called %d times", sCounter[type] + - sLastCounter[type]); + LOGD("%s", scratch); + } + memcpy(sLastTimeUsed, sTotalTimeUsed, sizeof(sTotalTimeUsed)); + memcpy(sLastCounter, sCounter, sizeof(sCounter)); + sLastTotalTime = current; + sLastThreadTime = currentThread; + sRecordWebCoreTime = true; +} + +void TimeCounter::reset() { + bzero(sTotalTimeUsed, sizeof(sTotalTimeUsed)); + bzero(sCounter, sizeof(sCounter)); + LOGD("*-* Start browser instrument\n"); + sStartTotalTime = currentTime(); + sStartThreadTime = getThreadMsec(); +} + +void TimeCounter::start(enum Type type) +{ + uint32_t time = getThreadMsec(); + if (sRecordWebCoreTime) { + sStartWebCoreThreadTime = time; + sRecordWebCoreTime = false; + } + sStartTime[type] = time; +} + +#endif // ANDROID_INSTRUMENT + +} diff --git a/Source/WebKit/android/TimeCounter.h b/Source/WebKit/android/TimeCounter.h new file mode 100644 index 0000000..ecede27 --- /dev/null +++ b/Source/WebKit/android/TimeCounter.h @@ -0,0 +1,120 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TimeCounter_h +#define TimeCounter_h + +#include "hardware_legacy/qemu_tracing.h" + +namespace WebCore { + +class KURL; + +} + +namespace android { + +uint32_t getThreadMsec(); + +#ifdef ANDROID_INSTRUMENT + +class TimeCounter { +public: + enum Type { + // function base counters + CSSParseTimeCounter, + JavaScriptTimeCounter, + JavaScriptInitTimeCounter, + JavaScriptParseTimeCounter, + JavaScriptExecuteTimeCounter, + CalculateStyleTimeCounter, + JavaCallbackTimeCounter, + ParsingTimeCounter, + LayoutTimeCounter, + // file base counters + NativeCallbackTimeCounter, // WebCoreFrameBridge.cpp + ResourceTimeCounter, // WebCoreResourceLoader.cpp + SharedTimerTimeCounter, // JavaBridge.cpp + WebViewCoreBuildNavTimeCounter, + WebViewCoreRecordTimeCounter, + WebViewCoreTimeCounter, // WebViewCore.cpp + WebViewUIDrawTimeCounter, + TotalTimeCounterCount + }; + + static void record(enum Type type, const char* functionName); + static void recordNoCounter(enum Type type, const char* functionName); + static void report(const WebCore::KURL& , int live, int dead, size_t arenaSize); + static void reportNow(); + static void reset(); + static void start(enum Type type); +private: + static uint32_t sStartWebCoreThreadTime; + static uint32_t sEndWebCoreThreadTime; + static bool sRecordWebCoreTime; + static uint32_t sTotalTimeUsed[TotalTimeCounterCount]; + static uint32_t sLastTimeUsed[TotalTimeCounterCount]; + static uint32_t sCounter[TotalTimeCounterCount]; + static uint32_t sLastCounter[TotalTimeCounterCount]; + static uint32_t sStartTime[TotalTimeCounterCount]; + friend class TimeCounterAuto; +}; + +class TimeCounterAuto { +public: + TimeCounterAuto(TimeCounter::Type type) : + m_type(type), m_startTime(getThreadMsec()) {} + ~TimeCounterAuto() { + uint32_t time = getThreadMsec(); + TimeCounter::sEndWebCoreThreadTime = time; + TimeCounter::sTotalTimeUsed[m_type] += time - m_startTime; + TimeCounter::sCounter[m_type]++; + } +private: + TimeCounter::Type m_type; + uint32_t m_startTime; +}; + +class QemuTracerAuto { +public: + QemuTracerAuto() { + if (!reentry_count) + qemu_start_tracing(); + reentry_count++; + } + + ~QemuTracerAuto() { + reentry_count--; + if (!reentry_count) + qemu_stop_tracing(); + } +private: + static int reentry_count; +}; +#endif // ANDROID_INSTRUMENT + +} + +#endif diff --git a/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp index 907dc3c..ab5fcb0 100644 --- a/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp +++ b/Source/WebKit/android/WebCoreSupport/ChromeClientAndroid.cpp @@ -95,7 +95,7 @@ void ChromeClientAndroid::scheduleCompositingLayerSync() m_needsLayerSync = true; WebViewCore* webViewCore = WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view()); if (webViewCore) - webViewCore->contentDraw(); + webViewCore->layersDraw(); } void ChromeClientAndroid::setNeedsOneShotDrawingSynchronization() @@ -161,15 +161,8 @@ void ChromeClientAndroid::focus() } void ChromeClientAndroid::unfocus() { notImplemented(); } -bool ChromeClientAndroid::canTakeFocus(FocusDirection direction) -{ - return android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->chromeCanTakeFocus(direction); -} - -void ChromeClientAndroid::takeFocus(FocusDirection direction) -{ - android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->chromeTakeFocus(direction); -} +bool ChromeClientAndroid::canTakeFocus(FocusDirection) { notImplemented(); return false; } +void ChromeClientAndroid::takeFocus(FocusDirection) { notImplemented(); } void ChromeClientAndroid::focusedNodeChanged(Node* node) { @@ -188,10 +181,10 @@ Page* ChromeClientAndroid::createWindow(Frame* frame, const FrameLoadRequest&, return frame->page(); #endif - FloatRect window = windowRect(); + const WebCoreViewBridge* bridge = frame->view()->platformWidget(); bool dialog = features.dialog || !features.resizable - || (features.heightSet && features.height < window.height() - && features.widthSet && features.width < window.width()) + || (features.heightSet && features.height < bridge->height() + && features.widthSet && features.width < bridge->width()) || (!features.menuBarVisible && !features.statusBarVisible && !features.toolBarVisible && !features.locationBarVisible && !features.scrollbarsVisible); @@ -238,7 +231,11 @@ void ChromeClientAndroid::addMessageToConsole(MessageSource, MessageType, Messag android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->addMessageToConsole(message, lineNumber, sourceID, msgLevel); } -void ChromeClientAndroid::formDidBlur(const WebCore::Node* node) { notImplemented(); } +void ChromeClientAndroid::formDidBlur(const WebCore::Node* node) +{ + android::WebViewCore::getWebViewCore(m_webFrame->page()->mainFrame()->view())->formDidBlur(node); +} + bool ChromeClientAndroid::canRunBeforeUnloadConfirmPanel() { return true; } bool ChromeClientAndroid::runBeforeUnloadConfirmPanel(const String& message, Frame* frame) { String url = frame->document()->documentURI(); @@ -301,7 +298,7 @@ bool ChromeClientAndroid::shouldInterruptJavaScript() { KeyboardUIMode ChromeClientAndroid::keyboardUIMode() { - return KeyboardAccessTabsToLinks; + return KeyboardAccessDefault; } IntRect ChromeClientAndroid::windowResizerRect() const { return IntRect(0, 0, 0, 0); } @@ -641,11 +638,6 @@ void ChromeClientAndroid::enterFullscreenForNode(Node* node) void ChromeClientAndroid::exitFullscreenForNode(Node* node) { - FrameView* frameView = m_webFrame->page()->mainFrame()->view(); - android::WebViewCore* core = android::WebViewCore::getWebViewCore(frameView); - if (core) - core->exitFullscreenVideo(); - return; } #endif diff --git a/Source/WebKit/android/WebCoreSupport/ChromiumIncludes.h b/Source/WebKit/android/WebCoreSupport/ChromiumIncludes.h index 1c898a0..022511a 100644 --- a/Source/WebKit/android/WebCoreSupport/ChromiumIncludes.h +++ b/Source/WebKit/android/WebCoreSupport/ChromiumIncludes.h @@ -23,46 +23,31 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -// All source files wishing to include Chromium headers must include this file -// and must not incude Chromium headers directly. - #ifndef ChromiumIncludes_h #define ChromiumIncludes_h #include "config.h" -// Both WebKit and Chromium define LOG. In AOSP, the framework also defines -// LOG. To avoid conflicts, we undefine LOG before including Chromium code, -// then define it back to the WebKit macro. +// Include all external/chromium files in this file so the problems with the LOG +// and LOG_ASSERT defines can be handled in one place. + +// Undefine LOG and LOG_ASSERT before including chrome code, and if they were +// defined attempt to set the macros to the Android logging macros (which are +// the only ones that actually log). + #ifdef LOG -#define LOG_WAS_DEFINED +#define LOG_WAS_DEFINED LOG #undef LOG #endif -// In AOSP, the framework still uses LOG_ASSERT (as well as ALOG_ASSERT), which -// conflicts with Chromium's LOG_ASSERT. So we undefine LOG_ASSERT to allow the -// Chromium implementation to be picked up. We also redefine ALOG_ASSERT to the -// underlying framework implementation without using LOG_ASSERT. -// TODO: Remove this once LOG_ASSERT is removed from the framework in AOSP. +#ifdef LOG_ASSERT +#define LOG_ASSERT_WAS_DEFINED LOG_ASSERT #undef LOG_ASSERT -#undef ALOG_ASSERT -// Copied from log.h. -#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__) - -// Chromium won't build without NDEBUG set, so we set it for all source files -// that use Chromium code. This means that if NDEBUG was previously unset, we -// have to redefine ASSERT() to a no-op, as this is enabled in debug builds. -// Unfortunately, ASSERT() is defined from config.h, so we can't get in first. -#ifndef NDEBUG -#define NDEBUG 1 -#undef ASSERT -#define ASSERT(assertion) (void(0)) #endif #include <android/net/android_network_library_impl.h> #include <android/jni/jni_utils.h> #include <base/callback.h> -#include <base/lazy_instance.h> #include <base/memory/ref_counted.h> #include <base/message_loop_proxy.h> #include <base/openssl_util.h> @@ -115,15 +100,13 @@ #endif #undef LOG -// If LOG was defined, restore it to the WebKit macro. -#ifdef LOG_WAS_DEFINED -// If LOG was defined, JOIN_LOG_CHANNEL_WITH_PREFIX must be too. -// Copied from Assertions.h. -#if LOG_DISABLED -#define LOG(channel, ...) ((void)0) -#else -#define LOG(channel, ...) WTFLog(&JOIN_LOG_CHANNEL_WITH_PREFIX(LOG_CHANNEL_PREFIX, channel), __VA_ARGS__) +#if defined(LOG_WAS_DEFINED) && defined(LOG_PRI) +#define LOG(priority, tag, ...) LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) #endif + +#undef LOG_ASSERT +#if defined(LOG_ASSERT_WAS_DEFINED) && defined(LOG_FATAL_IF) +#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__) #endif #endif diff --git a/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp index 042c227..785f0a8 100644 --- a/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp +++ b/Source/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp @@ -228,22 +228,8 @@ void EditorClientAndroid::checkGrammarOfString(unsigned short const*, int, WTF:: void EditorClientAndroid::checkSpellingOfString(unsigned short const*, int, int*, int*) {} String EditorClientAndroid::getAutoCorrectSuggestionForMisspelledWord(const String&) { return String(); } void EditorClientAndroid::textFieldDidEndEditing(Element*) {} -void EditorClientAndroid::textDidChangeInTextArea(Element* element) -{ - Frame* frame = m_page->focusController()->focusedOrMainFrame(); - if (!frame || !frame->view()) - return; - WebViewCore* webViewCore = WebViewCore::getWebViewCore(frame->view()); - webViewCore->updateTextSizeAndScroll(element); -} -void EditorClientAndroid::textDidChangeInTextField(Element* element) -{ - Frame* frame = m_page->focusController()->focusedOrMainFrame(); - if (!frame || !frame->view()) - return; - WebViewCore* webViewCore = WebViewCore::getWebViewCore(frame->view()); - webViewCore->updateTextSizeAndScroll(element); -} +void EditorClientAndroid::textDidChangeInTextArea(Element*) {} +void EditorClientAndroid::textDidChangeInTextField(Element*) {} void EditorClientAndroid::textFieldDidBeginEditing(Element*) {} void EditorClientAndroid::ignoreWordInSpellDocument(String const&) {} diff --git a/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp index ac5cd9d..0be31eb 100644 --- a/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp +++ b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp @@ -70,6 +70,7 @@ #include "SkRect.h" #include "TextEncoding.h" #include "WebCoreFrameBridge.h" +#include "WebCoreResourceLoader.h" #include "WebHistory.h" #include "WebIconDatabase.h" #include "WebFrameView.h" @@ -78,7 +79,7 @@ #include "autofill/WebAutofill.h" #include "android_graphics.h" -#include <androidfw/AssetManager.h> +#include <utils/AssetManager.h> #include <wtf/text/CString.h> #define verifiedOk() // Verified that we don't need to implement this. @@ -266,11 +267,11 @@ void FrameLoaderClientAndroid::dispatchDidReceiveIcon() { // 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) { - ALOGV("Received icon (%p) for %s", icon, + LOGV("Received icon (%p) for %s", icon, url.utf8().data()); m_webFrame->didReceiveIcon(icon); } else { - ALOGV("Icon data for %s unavailable, registering for notification...", + LOGV("Icon data for %s unavailable, registering for notification...", url.utf8().data()); registerForIconNotification(); } @@ -422,9 +423,7 @@ void FrameLoaderClientAndroid::dispatchDidFirstLayout() { // so that about:blank will update the screen. if (!m_frame->tree()->parent()) { // Only need to notify Java side for the top frame - WebViewCore* core = WebViewCore::getWebViewCore(m_frame->view()); - if (core) - core->didFirstLayout(); + WebViewCore::getWebViewCore(m_frame->view())->didFirstLayout(); } } @@ -947,11 +946,11 @@ void FrameLoaderClientAndroid::transitionToCommittedForNewPage() { Retain(webViewCore); // Save the old WebFrameView's bounds and apply them to the new WebFrameView - RefPtr<WebCore::FrameView> oldFrameView = m_frame->view(); - WebFrameView* oldWebFrameView = static_cast<WebFrameView*> (oldFrameView->platformWidget()); - IntRect bounds; - if (oldWebFrameView) - bounds = oldWebFrameView->getBounds(); + WebFrameView* oldWebFrameView = static_cast<WebFrameView*> (m_frame->view()->platformWidget()); + IntRect bounds = oldWebFrameView->getBounds(); + IntRect visBounds = oldWebFrameView->getVisibleBounds(); + IntRect windowBounds = oldWebFrameView->getWindowBounds(); + WebCore::FrameView* oldFrameView = oldWebFrameView->view(); const float oldZoomFactor = oldFrameView->frame()->textZoomFactor(); m_frame->createView(bounds.size(), oldFrameView->baseBackgroundColor(), oldFrameView->isTransparent(), oldFrameView->fixedLayoutSize(), oldFrameView->useFixedLayout()); @@ -959,19 +958,21 @@ void FrameLoaderClientAndroid::transitionToCommittedForNewPage() { m_frame->setTextZoomFactor(oldZoomFactor); } - if (oldWebFrameView) { - IntRect visBounds = oldWebFrameView->getVisibleBounds(); - IntRect windowBounds = oldWebFrameView->getWindowBounds(); - // Create a new WebFrameView for the new FrameView - WebFrameView* newFrameView = new WebFrameView(m_frame->view(), webViewCore); - newFrameView->setLocation(bounds.x(), bounds.y()); - newFrameView->setSize(bounds.width(), bounds.height()); - newFrameView->setVisibleSize(visBounds.width(), visBounds.height()); - newFrameView->setWindowBounds(windowBounds.x(), windowBounds.y(), windowBounds.width(), windowBounds.height()); - // newFrameView attaches itself to FrameView which Retains the reference, so - // call Release for newFrameView - Release(newFrameView); - } + // Create a new WebFrameView for the new FrameView + WebFrameView* newFrameView = new WebFrameView(m_frame->view(), webViewCore); + +#if ENABLE(ANDROID_OVERFLOW_SCROLL) +#else + webViewCore->clearContent(); +#endif + + newFrameView->setLocation(bounds.x(), bounds.y()); + newFrameView->setSize(bounds.width(), bounds.height()); + newFrameView->setVisibleSize(visBounds.width(), visBounds.height()); + newFrameView->setWindowBounds(windowBounds.x(), windowBounds.y(), windowBounds.width(), windowBounds.height()); + // newFrameView attaches itself to FrameView which Retains the reference, so + // call Release for newFrameView + Release(newFrameView); // WebFrameView Retains webViewCore, so call Release for webViewCore Release(webViewCore); @@ -1007,11 +1008,15 @@ WTF::PassRefPtr<WebCore::Frame> FrameLoaderClientAndroid::createFrame(const KURL newFrame->tree()->setName(name); // Create a new FrameView and WebFrameView for the child frame to draw into. RefPtr<FrameView> frameView = FrameView::create(newFrame); + WebFrameView* webFrameView = new WebFrameView(frameView.get(), + WebViewCore::getWebViewCore(parent->view())); + // frameView Retains webFrameView, so call Release for webFrameView + Release(webFrameView); // Attach the frameView to the newFrame. newFrame->setView(frameView); newFrame->init(); newFrame->selection()->setFocused(true); - ALOGV("::WebCore:: createSubFrame returning %p", newFrame); + 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()) @@ -1326,8 +1331,8 @@ void FrameLoaderClientAndroid::dispatchDidClearWindowObjectInWorld(DOMWrapperWor return; ASSERT(m_frame); - ALOGV("::WebCore:: windowObjectCleared called on frame %p for %s\n", - m_frame, m_frame->document()->url().string().ascii().data()); + LOGV("::WebCore:: windowObjectCleared called on frame %p for %s\n", + m_frame, m_frame->loader()->url().string().ascii().data()); m_webFrame->windowObjectCleared(m_frame); } diff --git a/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h index 6eb4745..2464c58 100644 --- a/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h +++ b/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.h @@ -26,6 +26,7 @@ #ifndef FrameLoaderClientAndroid_h #define FrameLoaderClientAndroid_h +#include "CacheBuilder.h" #include "FrameLoaderClient.h" #include "ResourceResponse.h" #include "WebIconDatabase.h" @@ -199,12 +200,14 @@ namespace android { virtual void documentElementAvailable(); virtual void didPerformFirstNavigation() const; - // TODO: Implement +#if USE(V8) + // TODO(benm): Implement virtual void didCreateScriptContextForFrame() { } virtual void didDestroyScriptContextForFrame() { } virtual void didCreateIsolatedScriptContext() { } - virtual bool allowScriptExtension(const String& extensionName, int extensionGroup) { return false; } + virtual bool allowScriptExtension(const String& extensionName, int extensionGroup) { return false; } +#endif virtual void registerForIconNotification(bool listen = true); @@ -215,6 +218,9 @@ namespace android { // WebIconDatabaseClient api virtual void didAddIconForPageUrl(const String& pageUrl); + // FIXME: this doesn't really go here, but it's better than Frame + CacheBuilder& getCacheBuilder() { return m_cacheBuilder; } + void enableOnDemandPlugins() { m_onDemandPluginsEnabled = true; } void dispatchDidChangeIcons(); @@ -223,6 +229,7 @@ namespace android { virtual void didSaveToPageCache() { } virtual void didRestoreFromPageCache() { } private: + CacheBuilder m_cacheBuilder; Frame* m_frame; WebFrame* m_webFrame; PluginManualLoader* m_manualLoader; @@ -258,6 +265,7 @@ namespace android { ErrorFileNotFound = -14, ErrorTooManyRequests = -15 }; + friend class CacheBuilder; }; } diff --git a/Source/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp b/Source/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp index 1607b0e..339e91b 100644 --- a/Source/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp +++ b/Source/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp @@ -29,6 +29,10 @@ #if ENABLE(VIDEO) #include "BaseLayerAndroid.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" #include "GraphicsContext.h" #include "SkiaUtils.h" #include "TilesManager.h" @@ -164,19 +168,6 @@ void MediaPlayerPrivate::onEnded() m_networkState = MediaPlayer::Idle; } -void MediaPlayerPrivate::onRequestPlay() -{ - play(); -} - -void MediaPlayerPrivate::onRestoreState() -{ - if (!m_paused) { - //Kick off a JNI call to start the video. - play(); - } -} - void MediaPlayerPrivate::onPaused() { m_paused = true; @@ -220,6 +211,11 @@ public: if (!env || !m_url.length() || !m_glue->m_javaProxy) return; + // We only play video fullscreen on Android, so stop sites playing fullscreen video in the onload handler. + Frame* frame = m_player->frameView()->frame(); + if (frame && !frame->loader()->documentLoader()->wasOnloadHandled()) + return; + m_paused = false; m_player->playbackStateChanged(); @@ -260,9 +256,7 @@ public: if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef())) return; - SkCanvas* canvas = ctxt->platformContext()->getCanvas(); - if (!canvas) - return; + SkCanvas* canvas = ctxt->platformContext()->mCanvas; // We paint with the following rules in mind: // - only downscale the poster, never upscale // - maintain the natural aspect ratio of the poster @@ -299,8 +293,7 @@ public: m_player->durationChanged(); m_player->sizeChanged(); TilesManager::instance()->videoLayerManager()->updateVideoLayerSize( - m_player->platformLayer()->uniqueId(), width * height, - width / (float)height); + m_player->platformLayer()->uniqueId(), width*height); } virtual bool hasAudio() const { return false; } // do not display the audio UI @@ -541,14 +534,6 @@ static void OnEnded(JNIEnv* env, jobject obj, int pointer) } } -static void OnRequestPlay(JNIEnv* env, jobject obj, int pointer) -{ - if (pointer) { - WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); - player->onRequestPlay(); - } -} - static void OnPaused(JNIEnv* env, jobject obj, int pointer) { if (pointer) { @@ -585,15 +570,6 @@ static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) } } -static void OnRestoreState(JNIEnv* env, jobject obj, int pointer) -{ - if (pointer) { - WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); - player->onRestoreState(); - } -} - - // This is called on the UI thread only. // The video layers are composited on the webkit thread and then copied over // to the UI thread with the same ID. For rendering, we are only using the @@ -617,9 +593,14 @@ static bool SendSurfaceTexture(JNIEnv* env, jobject obj, jobject surfTex, BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(baseLayer); if (!layerImpl) return false; + if (!layerImpl->countChildren()) + return false; + LayerAndroid* compositedRoot = static_cast<LayerAndroid*>(layerImpl->getChild(0)); + if (!compositedRoot) + return false; VideoLayerAndroid* videoLayer = - static_cast<VideoLayerAndroid*>(layerImpl->findById(videoLayerId)); + static_cast<VideoLayerAndroid*>(compositedRoot->findById(videoLayerId)); if (!videoLayer) return false; @@ -651,8 +632,6 @@ static JNINativeMethod g_MediaPlayerMethods[] = { (void*) OnPaused }, { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V", (void*) OnPosterFetched }, - { "nativeOnRestoreState", "(I)V", - (void*) OnRestoreState }, { "nativeSendSurfaceTexture", "(Landroid/graphics/SurfaceTexture;IIII)Z", (void*) SendSurfaceTexture }, { "nativeOnTimeupdate", "(II)V", @@ -666,8 +645,6 @@ static JNINativeMethod g_MediaAudioPlayerMethods[] = { (void*) OnEnded }, { "nativeOnPrepared", "(IIII)V", (void*) OnPrepared }, - { "nativeOnRequestPlay", "(I)V", - (void*) OnRequestPlay }, { "nativeOnTimeupdate", "(II)V", (void*) OnTimeupdate }, }; diff --git a/Source/WebKit/android/WebCoreSupport/MemoryUsage.cpp b/Source/WebKit/android/WebCoreSupport/MemoryUsage.cpp index 592fad3..32cdebf 100644 --- a/Source/WebKit/android/WebCoreSupport/MemoryUsage.cpp +++ b/Source/WebKit/android/WebCoreSupport/MemoryUsage.cpp @@ -29,7 +29,9 @@ #include <malloc.h> #include <wtf/CurrentTime.h> +#if USE(V8) #include <v8.h> +#endif // USE(V8) using namespace WTF; @@ -57,10 +59,12 @@ int MemoryUsageCache::getCachedMemoryUsage(bool forceFresh) struct mallinfo minfo = mallinfo(); m_cachedMemoryUsage = (minfo.hblkhd + minfo.arena) >> 20; +#if USE(V8) v8::HeapStatistics stat; v8::V8::GetHeapStatistics(&stat); unsigned v8Usage = stat.total_heap_size() >> 20; m_cachedMemoryUsage += v8Usage; +#endif // USE(V8) m_cacheTime = currentTimeMS(); return m_cachedMemoryUsage; diff --git a/Source/WebKit/android/WebCoreSupport/PlatformBridge.cpp b/Source/WebKit/android/WebCoreSupport/PlatformBridge.cpp index 9b8aac8..27fe208 100644 --- a/Source/WebKit/android/WebCoreSupport/PlatformBridge.cpp +++ b/Source/WebKit/android/WebCoreSupport/PlatformBridge.cpp @@ -35,22 +35,18 @@ #include "KeyGeneratorClient.h" #include "MemoryUsage.h" #include "PluginView.h" -#include "RenderLayer.h" -#include "RenderView.h" #include "Settings.h" #include "WebCookieJar.h" #include "WebRequestContext.h" #include "WebViewCore.h" #include "npruntime.h" -#include <gui/SurfaceComposerClient.h> +#include <surfaceflinger/SurfaceComposerClient.h> #include <ui/DisplayInfo.h> #include <ui/PixelFormat.h> #include <wtf/android/AndroidThreading.h> #include <wtf/MainThread.h> -#include <algorithm> - using namespace android; namespace WebCore { @@ -75,40 +71,62 @@ String PlatformBridge::getSignedPublicKeyAndChallengeString(unsigned index, cons void PlatformBridge::setCookies(const Document* document, const KURL& url, const String& value) { +#if USE(CHROME_NETWORK_STACK) std::string cookieValue(value.utf8().data()); GURL cookieGurl(url.string().utf8().data()); bool isPrivateBrowsing = document->settings() && document->settings()->privateBrowsingEnabled(); - WebCookieJar* cookieJar = WebCookieJar::get(isPrivateBrowsing); - if (cookieJar->allowCookies()) - cookieJar->cookieStore()->SetCookie(cookieGurl, cookieValue); + WebCookieJar::get(isPrivateBrowsing)->cookieStore()->SetCookie(cookieGurl, cookieValue); +#else + CookieClient* client = JavaSharedClient::GetCookieClient(); + if (!client) + return; + + client->setCookies(url, value); +#endif } String PlatformBridge::cookies(const Document* document, const KURL& url) { +#if USE(CHROME_NETWORK_STACK) GURL cookieGurl(url.string().utf8().data()); bool isPrivateBrowsing = document->settings() && document->settings()->privateBrowsingEnabled(); - WebCookieJar* cookieJar = WebCookieJar::get(isPrivateBrowsing); - String cookieString; - if (cookieJar->allowCookies()) { - std::string cookies = cookieJar->cookieStore()->GetCookies(cookieGurl); - cookieString = cookies.c_str(); - } + std::string cookies = WebCookieJar::get(isPrivateBrowsing)->cookieStore()->GetCookies(cookieGurl); + String cookieString(cookies.c_str()); return cookieString; +#else + CookieClient* client = JavaSharedClient::GetCookieClient(); + if (!client) + return String(); + + return client->cookies(url); +#endif } bool PlatformBridge::cookiesEnabled(const Document* document) { +#if USE(CHROME_NETWORK_STACK) bool isPrivateBrowsing = document->settings() && document->settings()->privateBrowsingEnabled(); return WebCookieJar::get(isPrivateBrowsing)->allowCookies(); +#else + CookieClient* client = JavaSharedClient::GetCookieClient(); + if (!client) + return false; + + return client->cookiesEnabled(); +#endif } NPObject* PlatformBridge::pluginScriptableObject(Widget* widget) { +#if USE(V8) if (!widget->isPluginView()) return 0; PluginView* pluginView = static_cast<PluginView*>(widget); return pluginView->getNPObject(); +#else + return 0; +#endif } bool PlatformBridge::isWebViewPaused(const WebCore::FrameView* frameView) @@ -157,11 +175,15 @@ int PlatformBridge::screenHeightInDocCoord(const WebCore::FrameView* frameView) String PlatformBridge::computeDefaultLanguage() { +#if USE(CHROME_NETWORK_STACK) String acceptLanguages = WebRequestContext::acceptLanguage(); size_t length = acceptLanguages.find(','); if (length == std::string::npos) length = acceptLanguages.length(); return acceptLanguages.substring(0, length); +#else + return "en"; +#endif } void PlatformBridge::updateViewport(FrameView* frameView) @@ -177,15 +199,10 @@ void PlatformBridge::updateTextfield(FrameView* frameView, Node* nodePtr, bool c } void PlatformBridge::setScrollPosition(ScrollView* scrollView, int x, int y) { - FrameView* frameView = scrollView->frameView(); - if (!frameView) return; // Check to make sure the view is the main FrameView. android::WebViewCore *webViewCore = android::WebViewCore::getWebViewCore(scrollView); - if (webViewCore->mainFrame()->view() == scrollView) { - x = std::max(0, std::min(frameView->contentsWidth(), x)); - y = std::max(0, std::min(frameView->contentsHeight(), y)); + if (webViewCore->mainFrame()->view() == scrollView) webViewCore->scrollTo(x, y); - } } int PlatformBridge::lowMemoryUsageMB() diff --git a/Source/WebKit/android/WebCoreSupport/ResourceLoaderAndroid.cpp b/Source/WebKit/android/WebCoreSupport/ResourceLoaderAndroid.cpp index 92c39b8..7f54810 100644 --- a/Source/WebKit/android/WebCoreSupport/ResourceLoaderAndroid.cpp +++ b/Source/WebKit/android/WebCoreSupport/ResourceLoaderAndroid.cpp @@ -29,6 +29,7 @@ #include "Frame.h" #include "FrameLoaderClientAndroid.h" #include "WebCoreFrameBridge.h" +#include "WebCoreResourceLoader.h" #include "WebUrlLoader.h" #include "WebViewCore.h" @@ -41,13 +42,18 @@ PassRefPtr<ResourceLoaderAndroid> ResourceLoaderAndroid::start( { // Called on main thread FrameLoaderClientAndroid* clientAndroid = static_cast<FrameLoaderClientAndroid*>(client); +#if USE(CHROME_NETWORK_STACK) WebViewCore* webViewCore = WebViewCore::getWebViewCore(clientAndroid->getFrame()->view()); bool isMainFrame = !(clientAndroid->getFrame()->tree() && clientAndroid->getFrame()->tree()->parent()); return WebUrlLoader::start(client, handle, request, isMainResource, isMainFrame, isSync, webViewCore->webRequestContext()); +#else + return clientAndroid->webFrame()->startLoadingResource(handle, request, isMainResource, isSync); +#endif } bool ResourceLoaderAndroid::willLoadFromCache(const WebCore::KURL& url, int64_t identifier) { +#if USE(CHROME_NETWORK_STACK) // This method is used to determine if a POST request can be repeated from // cache, but you cannot really know until you actually try to read from the // cache. Even if we checked now, something else could come along and wipe @@ -57,6 +63,9 @@ bool ResourceLoaderAndroid::willLoadFromCache(const WebCore::KURL& url, int64_t // reload. Then in FrameLoaderClientImpl::dispatchWillSendRequest, we // fix-up the cache policy of the request to force a load from the cache. return true; +#else + return WebCoreResourceLoader::willLoadFromCache(url, identifier); +#endif } } diff --git a/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.cpp b/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.cpp index d846daf..77e3c32 100644 --- a/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.cpp +++ b/Source/WebKit/android/WebCoreSupport/UrlInterceptResponse.cpp @@ -30,7 +30,6 @@ #include "UrlInterceptResponse.h" #include "WebCoreJni.h" -#include <ScopedLocalRef.h> #include <utils/Log.h> namespace android { @@ -39,14 +38,15 @@ class JavaInputStreamWrapper { public: JavaInputStreamWrapper(JNIEnv* env, jobject inputStream) : m_inputStream(env->NewGlobalRef(inputStream)) - , m_buffer(NULL) { - LOG_ALWAYS_FATAL_IF(!m_inputStream); - ScopedLocalRef<jclass> inputStreamClass(env, env->FindClass("java/io/InputStream")); - LOG_ALWAYS_FATAL_IF(!inputStreamClass.get()); - m_read = env->GetMethodID(inputStreamClass.get(), "read", "([B)I"); + , m_buffer(0) { + LOG_ALWAYS_FATAL_IF(!inputStream); + jclass inputStreamClass = env->FindClass("java/io/InputStream"); + LOG_ALWAYS_FATAL_IF(!inputStreamClass); + m_read = env->GetMethodID(inputStreamClass, "read", "([B)I"); LOG_ALWAYS_FATAL_IF(!m_read); - m_close = env->GetMethodID(inputStreamClass.get(), "close", "()V"); + m_close = env->GetMethodID(inputStreamClass, "close", "()V"); LOG_ALWAYS_FATAL_IF(!m_close); + env->DeleteLocalRef(inputStreamClass); } ~JavaInputStreamWrapper() { @@ -63,10 +63,10 @@ public: JNIEnv* env = JSC::Bindings::getJNIEnv(); // Initialize our read buffer to the capacity of out. if (!m_buffer) { - ScopedLocalRef<jbyteArray> buffer_local(env, env->NewByteArray(out->capacity())); - m_buffer = static_cast<jbyteArray>(env->NewGlobalRef(buffer_local.get())); + m_buffer = env->NewByteArray(out->capacity()); + m_buffer = (jbyteArray) env->NewGlobalRef(m_buffer); } - int size = env->CallIntMethod(m_inputStream, m_read, m_buffer); + int size = (int) env->CallIntMethod(m_inputStream, m_read, m_buffer); if (checkException(env) || size < 0) return; // Copy from m_buffer to out. @@ -82,32 +82,40 @@ private: }; UrlInterceptResponse::UrlInterceptResponse(JNIEnv* env, jobject response) { - ScopedLocalRef<jclass> javaResponse(env, env->FindClass("android/webkit/WebResourceResponse")); - LOG_ALWAYS_FATAL_IF(!javaResponse.get()); - jfieldID mimeType = env->GetFieldID(javaResponse.get(), "mMimeType", "Ljava/lang/String;"); + jclass javaResponse = env->FindClass("android/webkit/WebResourceResponse"); + LOG_ALWAYS_FATAL_IF(!javaResponse); + jfieldID mimeType = env->GetFieldID(javaResponse, "mMimeType", + "Ljava/lang/String;"); LOG_ALWAYS_FATAL_IF(!mimeType); - jfieldID encoding = env->GetFieldID(javaResponse.get(), "mEncoding", "Ljava/lang/String;"); + jfieldID encoding = env->GetFieldID(javaResponse, "mEncoding", + "Ljava/lang/String;"); LOG_ALWAYS_FATAL_IF(!encoding); - jfieldID inputStream = env->GetFieldID(javaResponse.get(), "mInputStream", "Ljava/io/InputStream;"); + jfieldID inputStream = env->GetFieldID(javaResponse, "mInputStream", + "Ljava/io/InputStream;"); LOG_ALWAYS_FATAL_IF(!inputStream); - ScopedLocalRef<jobject> stream(env, env->GetObjectField(response, inputStream)); - if (stream.get()) - m_inputStream.set(new JavaInputStreamWrapper(env, stream.get())); + jobject stream = env->GetObjectField(response, inputStream); + if (stream) + m_inputStream.set(new JavaInputStreamWrapper(env, stream)); - ScopedLocalRef<jstring> mimeStr(env, static_cast<jstring>(env->GetObjectField(response, mimeType))); - ScopedLocalRef<jstring> encodingStr(env, static_cast<jstring>(env->GetObjectField(response, encoding))); + jstring mimeStr = (jstring) env->GetObjectField(response, mimeType); + jstring encodingStr = (jstring) env->GetObjectField(response, encoding); - if (mimeStr.get()) { - const char* s = env->GetStringUTFChars(mimeStr.get(), NULL); - m_mimeType.assign(s, env->GetStringUTFLength(mimeStr.get())); - env->ReleaseStringUTFChars(mimeStr.get(), s); + if (mimeStr) { + const char* s = env->GetStringUTFChars(mimeStr, NULL); + m_mimeType.assign(s, env->GetStringUTFLength(mimeStr)); + env->ReleaseStringUTFChars(mimeStr, s); } - if (encodingStr.get()) { - const char* s = env->GetStringUTFChars(encodingStr.get(), NULL); - m_encoding.assign(s, env->GetStringUTFLength(encodingStr.get())); - env->ReleaseStringUTFChars(encodingStr.get(), s); + if (encodingStr) { + const char* s = env->GetStringUTFChars(encodingStr, NULL); + m_encoding.assign(s, env->GetStringUTFLength(encodingStr)); + env->ReleaseStringUTFChars(encodingStr, s); } + + env->DeleteLocalRef(javaResponse); + env->DeleteLocalRef(stream); + env->DeleteLocalRef(mimeStr); + env->DeleteLocalRef(encodingStr); } UrlInterceptResponse::~UrlInterceptResponse() { diff --git a/Source/WebKit/android/WebCoreSupport/V8Counters.cpp b/Source/WebKit/android/WebCoreSupport/V8Counters.cpp new file mode 100644 index 0000000..d164f9a --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/V8Counters.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifdef ANDROID_INSTRUMENT + +#define LOG_TAG "WebCore" + +#include "config.h" +#include "V8Counters.h" + +#include "NotImplemented.h" +#include <utils/Log.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringHash.h> + +#if USE(V8) + +namespace WebCore { + +V8Counters::Counter::Counter(bool isHistogram) + : m_count(0), m_sampleTotal(0), m_isHistogram(isHistogram) { } + +void V8Counters::Counter::addSample(int sample) +{ + m_count++; + m_sampleTotal += sample; +} + +HashMap<String, V8Counters::Counter*> V8Counters::m_counters; + +// static +int* V8Counters::counterForName(const char* name) +{ + Counter* counter = m_counters.get(name); + if (!counter) { + counter = new Counter(false); + m_counters.add(name, counter); + } + return *counter; +} + +// static +void* V8Counters::createHistogram(const char* name, int min, int max, + size_t buckets) +{ + Counter* counter = new Counter(true); + m_counters.add(name, counter); + return counter; +} + +// static +void V8Counters::addHistogramSample(void* histogram, int sample) +{ + Counter* counter = reinterpret_cast<Counter*>(histogram); + counter->addSample(sample); +} + +// static +void V8Counters::initCounters() +{ + static bool isInitialized = false; + if (!isInitialized) { + v8::V8::SetCounterFunction(counterForName); + v8::V8::SetCreateHistogramFunction(createHistogram); + v8::V8::SetAddHistogramSampleFunction(addHistogramSample); + isInitialized = true; + } +} + +// static +void V8Counters::dumpCounters() +{ + LOGD("+----------------------------------------+-------------+\n"); + LOGD("| Name | Value |\n"); + LOGD("+----------------------------------------+-------------+\n"); + typedef HashMap<String, V8Counters::Counter*>::iterator CounterIterator; + for (CounterIterator iter = m_counters.begin(); iter != m_counters.end(); ++iter) { + Counter* counter = iter->second; + if (counter->isHistogram()) { + LOGD("| c:%-36s | %11i |\n", iter->first.latin1().data(), counter->count()); + LOGD("| t:%-36s | %11i |\n", iter->first.latin1().data(), counter->sampleTotal()); + } else { + LOGD("| %-38s | %11i |\n", iter->first.latin1().data(), counter->count()); + } + } + LOGD("+----------------------------------------+-------------+\n"); +} + +} + +#endif // ANDROID_INSTRUMENT + +#endif // USE(V8) diff --git a/Source/WebKit/android/WebCoreSupport/V8Counters.h b/Source/WebKit/android/WebCoreSupport/V8Counters.h new file mode 100644 index 0000000..499b856 --- /dev/null +++ b/Source/WebKit/android/WebCoreSupport/V8Counters.h @@ -0,0 +1,77 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef V8Counters_h +#define V8Counters_h + +#if USE(V8) + +#ifdef ANDROID_INSTRUMENT + +#include <PlatformString.h> +#include <v8.h> +#include <wtf/HashMap.h> + +namespace WebCore { + +class V8Counters { +public: + // Counter callbacks, see v8.h + static int* counterForName(const char* name); + + static void* createHistogram(const char* name, + int min, + int max, + size_t buckets); + + static void addHistogramSample(void* histogram, int sample); + + static void initCounters(); + static void dumpCounters(); +private: + class Counter { + public: + Counter(bool isHistogram); + + int count() { return m_count; } + int sampleTotal() { return m_sampleTotal; } + bool isHistogram() { return m_isHistogram; } + void addSample(int32_t sample); + + operator int*() { return &m_count; } + private: + int m_count; + int m_sampleTotal; + bool m_isHistogram; + }; + + static HashMap<String, Counter*> m_counters; +}; + +} + +#endif // ANDROID_INSTRUMENT +#endif // USE(V8) +#endif // V8Counters_h diff --git a/Source/WebKit/android/WebCoreSupport/WebCache.cpp b/Source/WebKit/android/WebCoreSupport/WebCache.cpp index 82e3b27..9b505ee 100644 --- a/Source/WebKit/android/WebCoreSupport/WebCache.cpp +++ b/Source/WebKit/android/WebCoreSupport/WebCache.cpp @@ -30,7 +30,7 @@ #include "WebCoreJni.h" #include "WebRequestContext.h" #include "WebUrlLoaderClient.h" -#include "net/http/http_network_session.h" + #include <wtf/text/CString.h> using namespace WTF; @@ -131,21 +131,6 @@ void WebCache::clear() thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::clearImpl)); } -void WebCache::certTrustChanged() -{ - base::Thread* thread = WebUrlLoaderClient::ioThread(); - if (thread) - thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::certTrustChangedImpl)); -} - -void WebCache::certTrustChangedImpl() -{ - net::HttpNetworkSession* session = m_cache->GetSession(); - if (session) - session->cert_verifier()->ClearCache(); - m_cache->CloseAllConnections(); -} - void WebCache::closeIdleConnections() { base::Thread* thread = WebUrlLoaderClient::ioThread(); diff --git a/Source/WebKit/android/WebCoreSupport/WebCache.h b/Source/WebKit/android/WebCoreSupport/WebCache.h index ad57d88..c3b623d 100644 --- a/Source/WebKit/android/WebCoreSupport/WebCache.h +++ b/Source/WebKit/android/WebCoreSupport/WebCache.h @@ -48,7 +48,7 @@ public: net::HttpCache* cache() { return m_cache.get(); } net::ProxyConfigServiceAndroid* proxy() { return m_proxyConfigService; } void closeIdleConnections(); - void certTrustChanged(); + private: WebCache(bool isPrivateBrowsing); @@ -60,7 +60,6 @@ private: // For closeIdleConnections void closeIdleImpl(); - void certTrustChangedImpl(); // For getEntry() void getEntryImpl(); diff --git a/Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp b/Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp index 0af3cc2..1dc4637 100644 --- a/Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp +++ b/Source/WebKit/android/WebCoreSupport/WebCookieJar.cpp @@ -98,7 +98,7 @@ static std::string databaseDirectory(bool isPrivateBrowsing) return databaseFilePath; } -static scoped_refptr<WebCookieJar>* instance(bool isPrivateBrowsing) +scoped_refptr<WebCookieJar>* instance(bool isPrivateBrowsing) { static scoped_refptr<WebCookieJar> regularInstance; static scoped_refptr<WebCookieJar> privateInstance; @@ -127,16 +127,10 @@ void WebCookieJar::cleanup(bool isPrivateBrowsing) } WebCookieJar::WebCookieJar(const std::string& databaseFilePath) - : m_cookieStoreInitialized(false) - , m_databaseFilePath(databaseFilePath) - , m_allowCookies(true) {} - -void WebCookieJar::initCookieStore() { - MutexLocker lock(m_cookieStoreInitializeMutex); - if (m_cookieStoreInitialized) - return; + : m_allowCookies(true) +{ // Setup the permissions for the file - const char* cDatabasePath = m_databaseFilePath.c_str(); + const char* cDatabasePath = databaseFilePath.c_str(); mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; if (access(cDatabasePath, F_OK) == 0) chmod(cDatabasePath, mode); @@ -146,10 +140,9 @@ void WebCookieJar::initCookieStore() { close(fd); } - FilePath cookiePath(cDatabasePath); + FilePath cookiePath(databaseFilePath.c_str()); m_cookieDb = new SQLitePersistentCookieStore(cookiePath); m_cookieStore = new net::CookieMonster(m_cookieDb.get(), 0); - m_cookieStoreInitialized = true; } bool WebCookieJar::allowCookies() @@ -164,6 +157,13 @@ void WebCookieJar::setAllowCookies(bool allow) m_allowCookies = allow; } +int WebCookieJar::getNumCookiesInDatabase() +{ + if (!m_cookieStore) + return 0; + return m_cookieStore->GetCookieMonster()->GetAllCookies().size(); +} + // From CookiePolicy in chromium int WebCookieJar::CanGetCookies(const GURL&, const GURL&) const { @@ -178,17 +178,6 @@ int WebCookieJar::CanSetCookie(const GURL&, const GURL&, const std::string&) con return m_allowCookies ? net::OK : net::ERR_ACCESS_DENIED; } -net::CookieStore* WebCookieJar::cookieStore() -{ - initCookieStore(); - return m_cookieStore.get(); -} - -int WebCookieJar::getNumCookiesInDatabase() -{ - return cookieStore()->GetCookieMonster()->GetAllCookies().size(); -} - class FlushSemaphore : public base::RefCounted<FlushSemaphore> { public: diff --git a/Source/WebKit/android/WebCoreSupport/WebCookieJar.h b/Source/WebKit/android/WebCoreSupport/WebCookieJar.h index 7ade9d0..b6490af 100644 --- a/Source/WebKit/android/WebCoreSupport/WebCookieJar.h +++ b/Source/WebKit/android/WebCoreSupport/WebCookieJar.h @@ -55,10 +55,9 @@ public: static bool acceptFileSchemeCookies(); static void setAcceptFileSchemeCookies(bool); - // TODO // Instead of this it would probably be better to add the cookie methods // here so the rest of WebKit doesn't have to know about Chromium classes - net::CookieStore* cookieStore(); + net::CookieStore* cookieStore() { return m_cookieStore.get(); } net::CookiePolicy* cookiePolicy() { return this; } // Get the number of cookies that have actually been saved to flash. @@ -67,13 +66,7 @@ public: private: WebCookieJar(const std::string& databaseFilePath); - void initCookieStore(); -private: - bool m_cookieStoreInitialized; - WTF::Mutex m_cookieStoreInitializeMutex; - - const std::string m_databaseFilePath; scoped_refptr<SQLitePersistentCookieStore> m_cookieDb; scoped_refptr<net::CookieStore> m_cookieStore; bool m_allowCookies; diff --git a/Source/WebKit/android/WebCoreSupport/WebRequest.cpp b/Source/WebKit/android/WebCoreSupport/WebRequest.cpp index 9d16378..90b0939 100644 --- a/Source/WebKit/android/WebCoreSupport/WebRequest.cpp +++ b/Source/WebKit/android/WebCoreSupport/WebRequest.cpp @@ -30,7 +30,6 @@ #include "MainThread.h" #include "UrlInterceptResponse.h" #include "WebCoreFrameBridge.h" -#include "WebCoreJni.h" #include "WebRequestContext.h" #include "WebResourceRequest.h" #include "WebUrlLoaderClient.h" @@ -39,7 +38,7 @@ #include <cutils/log.h> #include <openssl/x509.h> #include <string> -#include <androidfw/AssetManager.h> +#include <utils/AssetManager.h> extern android::AssetManager* globalAssetManager(); @@ -59,28 +58,12 @@ while (0) namespace android { namespace { -const int kInitialReadBufSize = 32768; -const char* kXRequestedWithHeader = "X-Requested-With"; - -struct RequestPackageName { - std::string value; - RequestPackageName(); -}; - -RequestPackageName::RequestPackageName() { - JNIEnv* env = JSC::Bindings::getJNIEnv(); - jclass bridgeClass = env->FindClass("android/webkit/JniUtil"); - jmethodID method = env->GetStaticMethodID(bridgeClass, "getPackageName", "()Ljava/lang/String;"); - value = jstringToStdString(env, static_cast<jstring>(env->CallStaticObjectMethod(bridgeClass, method))); - env->DeleteLocalRef(bridgeClass); -} - -base::LazyInstance<RequestPackageName> s_packageName(base::LINKER_INITIALIZED); - + const int kInitialReadBufSize = 32768; } WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest) : m_urlLoader(loader) + , m_androidUrl(false) , m_url(webResourceRequest.url()) , m_userAgent(webResourceRequest.userAgent()) , m_loadState(Created) @@ -96,7 +79,6 @@ WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& web m_request = new net::URLRequest(gurl, this); m_request->SetExtraRequestHeaders(webResourceRequest.requestHeaders()); - m_request->SetExtraRequestHeaderByName(kXRequestedWithHeader, s_packageName.Get().value, false); m_request->set_referrer(webResourceRequest.referrer()); m_request->set_method(webResourceRequest.method()); m_request->set_load_flags(webResourceRequest.loadFlags()); @@ -107,6 +89,7 @@ WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& web WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest, UrlInterceptResponse* intercept) : m_urlLoader(loader) , m_interceptResponse(intercept) + , m_androidUrl(true) , m_url(webResourceRequest.url()) , m_userAgent(webResourceRequest.userAgent()) , m_loadState(Created) @@ -291,9 +274,15 @@ void WebRequest::handleInterceptedURL() // Get the MIME type from the URL. "text/html" is a last resort, hopefully overridden. std::string mimeType("text/html"); if (mime == "") { - // Get the MIME type from the file extension, if any. - FilePath path(m_url); - net::GetMimeTypeFromFile(path, &mimeType); + // Gmail appends the MIME to the end of the URL, with a ? separator. + size_t mimeTypeIndex = m_url.find_last_of('?'); + if (mimeTypeIndex != std::string::npos) { + mimeType.assign(m_url.begin() + mimeTypeIndex + 1, m_url.end()); + } else { + // Get the MIME type from the file extension, if any. + FilePath path(m_url); + net::GetMimeTypeFromFile(path, &mimeType); + } } else { // Set from the intercept response. mimeType = mime; diff --git a/Source/WebKit/android/WebCoreSupport/WebRequest.h b/Source/WebKit/android/WebCoreSupport/WebRequest.h index d9054e9..285e577 100644 --- a/Source/WebKit/android/WebCoreSupport/WebRequest.h +++ b/Source/WebKit/android/WebCoreSupport/WebRequest.h @@ -107,6 +107,7 @@ private: OwnPtr<net::URLRequest> m_request; scoped_refptr<net::IOBuffer> m_networkBuffer; scoped_ptr<UrlInterceptResponse> m_interceptResponse; + bool m_androidUrl; std::string m_url; std::string m_userAgent; LoadState m_loadState; diff --git a/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp b/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp index 5df0ed2..43037ab 100644 --- a/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp +++ b/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp @@ -37,7 +37,6 @@ #include "WebRequest.h" #include "WebResourceRequest.h" -#include <utils/Log.h> #include <wtf/text/CString.h> using base::Lock; @@ -157,11 +156,11 @@ WebUrlLoaderClient::WebUrlLoaderClient(WebFrame* webFrame, WebCore::ResourceHand break; #if ENABLE(BLOB) case FormDataElement::encodedBlob: - ALOG_ASSERT(false, "Unexpected use of FormDataElement::encodedBlob"); + LOG_ASSERT(false, "Unexpected use of FormDataElement::encodedBlob"); break; #endif // ENABLE(BLOB) default: - ALOG_ASSERT(false, "Unexpected default case in WebUrlLoaderClient.cpp"); + LOG_ASSERT(false, "Unexpected default case in WebUrlLoaderClient.cpp"); break; } } @@ -200,7 +199,7 @@ bool WebUrlLoaderClient::start(bool isMainResource, bool isMainFrame, bool sync, syncCondition()->TimedWait(base::TimeDelta::FromSeconds(kCallbackWaitingTime)); if (m_queue.empty()) { - ALOGE("Synchronous request timed out after %d seconds for the %dth try, URL: %s", + LOGE("Synchronous request timed out after %d seconds for the %dth try, URL: %s", kCallbackWaitingTime, num_timeout, m_request->getUrl().c_str()); num_timeout++; if (num_timeout >= kMaxNumTimeout) { @@ -254,7 +253,7 @@ void WebUrlLoaderClient::downloadFile() if (!m_isCertMimeType) cancel(); } else { - ALOGE("Unexpected call to downloadFile() before didReceiveResponse(). URL: %s", m_request->getUrl().c_str()); + LOGE("Unexpected call to downloadFile() before didReceiveResponse(). URL: %s", m_request->getUrl().c_str()); // TODO: Turn off asserts crashing before release // http://b/issue?id=2951985 CRASH(); diff --git a/Source/WebKit/android/WebCoreSupport/WebViewClientError.cpp b/Source/WebKit/android/WebCoreSupport/WebViewClientError.cpp index 1857e9c..260c76e 100644 --- a/Source/WebKit/android/WebCoreSupport/WebViewClientError.cpp +++ b/Source/WebKit/android/WebCoreSupport/WebViewClientError.cpp @@ -79,7 +79,6 @@ WebViewClientError ToWebViewClientError(net::Error error) { case ERR_CONNECTION_ABORTED: case ERR_CONNECTION_FAILED: case ERR_SOCKET_NOT_CONNECTED: - case ERR_CACHE_MISS: return ERROR_CONNECT; case ERR_ADDRESS_INVALID: diff --git a/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp b/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp index 2969252..e837244 100644 --- a/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp +++ b/Source/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp @@ -562,7 +562,7 @@ void FormManager::ExtractForms(Frame* frame) { ResetFrame(frame); - WTF::RefPtr<HTMLCollection> web_forms = frame->document()->forms(); + WTF::PassRefPtr<HTMLCollection> web_forms = frame->document()->forms(); for (size_t i = 0; i < web_forms->length(); ++i) { // Owned by |form_elements|. diff --git a/Source/WebKit/android/benchmark/Android.mk b/Source/WebKit/android/benchmark/Android.mk new file mode 100644 index 0000000..5b189e1 --- /dev/null +++ b/Source/WebKit/android/benchmark/Android.mk @@ -0,0 +1,41 @@ +## +## +## Copyright 2009, The Android Open Source Project +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions +## are met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in the +## documentation and/or other materials provided with the distribution. +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +## EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR +## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +## PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +## OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +## + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main.cpp + +# Pull the webkit definitions from the base webkit makefile. +LOCAL_SHARED_LIBRARIES := libwebcore $(WEBKIT_SHARED_LIBRARIES) +LOCAL_LDLIBS := $(WEBKIT_LDLIBS) + +LOCAL_MODULE := webcore_test + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/Source/WebKit/android/benchmark/Intercept.cpp b/Source/WebKit/android/benchmark/Intercept.cpp new file mode 100644 index 0000000..deffac2 --- /dev/null +++ b/Source/WebKit/android/benchmark/Intercept.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcore_test" +#include "config.h" + +#include "Base64.h" +#include "HTTPParsers.h" +#include "Intercept.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "TextEncoding.h" + +#include <utils/Log.h> +#include <wtf/HashMap.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringHash.h> + +PassRefPtr<WebCore::ResourceLoaderAndroid> MyResourceLoader::create( + ResourceHandle* handle, String url) +{ + return adoptRef<WebCore::ResourceLoaderAndroid>( + new MyResourceLoader(handle, url)); +} + +void MyResourceLoader::handleRequest() +{ + if (protocolIs(m_url, "data")) + loadData(m_url.substring(5)); // 5 for data: + else if (protocolIs(m_url, "file")) + loadFile(m_url.substring(7)); // 7 for file:// +} + +void MyResourceLoader::loadData(const String& data) +{ + LOGD("Loading data (%s) ...", data.latin1().data()); + ResourceHandleClient* client = m_handle->client(); + int index = data.find(','); + if (index == -1) { + client->cannotShowURL(m_handle); + return; + } + + String mediaType = data.substring(0, index); + String base64 = data.substring(index + 1); + + bool decode = mediaType.endsWith(";base64", false); + if (decode) + mediaType = mediaType.left(mediaType.length() - 7); // 7 for base64; + + if (mediaType.isEmpty()) + mediaType = "text/plain;charset=US-ASCII"; + + String mimeType = extractMIMETypeFromMediaType(mediaType); + String charset = extractCharsetFromMediaType(mediaType); + + ResourceResponse response; + response.setMimeType(mimeType); + + if (decode) { + base64 = decodeURLEscapeSequences(base64); + response.setTextEncodingName(charset); + client->didReceiveResponse(m_handle, response); + + // FIXME: This is annoying. WebCore's Base64 decoder chokes on spaces. + // That is correct with strict decoding but html authors (particularly + // the acid3 authors) put spaces in the data which should be ignored. + // Remove them here before sending to the decoder. + Vector<char> in; + CString str = base64.latin1(); + const char* chars = str.data(); + unsigned i = 0; + while (i < str.length()) { + char c = chars[i]; + // Don't send spaces or control characters. + if (c != ' ' && c != '\n' && c != '\t' && c != '\b' + && c != '\f' && c != '\r') + in.append(chars[i]); + i++; + } + Vector<char> out; + if (base64Decode(in, out) && out.size() > 0) + client->didReceiveData(m_handle, out.data(), out.size(), 0); + } else { + base64 = decodeURLEscapeSequences(base64, TextEncoding(charset)); + response.setTextEncodingName("UTF-16"); + client->didReceiveResponse(m_handle, response); + if (base64.length() > 0) + client->didReceiveData(m_handle, (const char*)base64.characters(), + base64.length() * sizeof(UChar), 0); + } + client->didFinishLoading(m_handle, 0); +} +static String mimeTypeForExtension(const String& file) +{ + static HashMap<String, String, CaseFoldingHash> extensionToMime; + if (extensionToMime.isEmpty()) { + extensionToMime.set("txt", "text/plain"); + extensionToMime.set("html", "text/html"); + extensionToMime.set("htm", "text/html"); + extensionToMime.set("png", "image/png"); + extensionToMime.set("jpeg", "image/jpeg"); + extensionToMime.set("jpg", "image/jpeg"); + extensionToMime.set("gif", "image/gif"); + extensionToMime.set("ico", "image/x-icon"); + extensionToMime.set("js", "text/javascript"); + } + int dot = file.reverseFind('.'); + String mime("text/plain"); + if (dot != -1) { + String ext = file.substring(dot + 1); + if (extensionToMime.contains(ext)) + mime = extensionToMime.get(ext); + } + return mime; +} + +void MyResourceLoader::loadFile(const String& file) +{ + LOGD("Loading file (%s) ...", file.latin1().data()); + FILE* f = fopen(file.latin1().data(), "r"); + ResourceHandleClient* client = m_handle->client(); + if (!f) { + client->didFail(m_handle, + ResourceError("", -14, file, "Could not open file")); + } else { + ResourceResponse response; + response.setTextEncodingName("utf-8"); + response.setMimeType(mimeTypeForExtension(file)); + client->didReceiveResponse(m_handle, response); + char buf[512]; + while (true) { + int res = fread(buf, 1, sizeof(buf), f); + if (res <= 0) + break; + client->didReceiveData(m_handle, buf, res, 0); + } + fclose(f); + client->didFinishLoading(m_handle, 0); + } +} + +PassRefPtr<WebCore::ResourceLoaderAndroid> MyWebFrame::startLoadingResource( + ResourceHandle* handle, const ResourceRequest& req, bool ignore, + bool ignore2) +{ + RefPtr<WebCore::ResourceLoaderAndroid> loader = + MyResourceLoader::create(handle, req.url().string()); + m_requests.append(loader); + if (!m_timer.isActive()) + m_timer.startOneShot(0); + return loader.release(); +} + +void MyWebFrame::timerFired(Timer<MyWebFrame>*) +{ + LOGD("Handling requests..."); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> > reqs; + reqs.swap(m_requests); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> >::iterator i = reqs.begin(); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> >::iterator end = reqs.end(); + for (; i != end; i++) + static_cast<MyResourceLoader*>((*i).get())->handleRequest(); + + LOGD("...done"); +} diff --git a/Source/WebKit/android/benchmark/Intercept.h b/Source/WebKit/android/benchmark/Intercept.h new file mode 100644 index 0000000..6694dba --- /dev/null +++ b/Source/WebKit/android/benchmark/Intercept.h @@ -0,0 +1,82 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef Intercept_h +#define Intercept_h + +#include "MyJavaVM.h" +#include "PlatformString.h" +#include "Timer.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreResourceLoader.h" +#include <JNIUtility.h> +#include <wtf/Vector.h> + +namespace WebCore { + class Page; + class ResourceHandle; + class ResourceRequest; +} + +using namespace android; +using namespace WebCore; +using namespace WTF; + +class MyResourceLoader : public WebCoreResourceLoader { +public: + static PassRefPtr<WebCore::ResourceLoaderAndroid> create( + ResourceHandle* handle, String url); + void handleRequest(); + +private: + MyResourceLoader(ResourceHandle* handle, String url) + : WebCoreResourceLoader(JSC::Bindings::getJNIEnv(), MY_JOBJECT) + , m_handle(handle) + , m_url(url) {} + + void loadData(const String&); + void loadFile(const String&); + ResourceHandle* m_handle; + String m_url; +}; + +class MyWebFrame : public WebFrame { +public: + MyWebFrame(Page* page) + : WebFrame(JSC::Bindings::getJNIEnv(), MY_JOBJECT, MY_JOBJECT, page) + , m_timer(this, &MyWebFrame::timerFired) {} + + virtual PassRefPtr<WebCore::ResourceLoaderAndroid> startLoadingResource( + ResourceHandle* handle, const ResourceRequest& req, bool, bool); + + virtual bool canHandleRequest(const ResourceRequest&) { return true; } + +private: + void timerFired(Timer<MyWebFrame>*); + Vector<RefPtr<WebCore::ResourceLoaderAndroid> > m_requests; + Timer<MyWebFrame> m_timer; +}; + +#endif diff --git a/Source/WebKit/android/benchmark/MyJavaVM.cpp b/Source/WebKit/android/benchmark/MyJavaVM.cpp new file mode 100644 index 0000000..574c745 --- /dev/null +++ b/Source/WebKit/android/benchmark/MyJavaVM.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "MyJavaVM.h" + +#include <JNIUtility.h> +#include <jni.h> + +static JNIEnv* s_env; +static JavaVM* s_jvm; + +// JavaVM functions +jint vm_attachCurrentThread(JavaVM*, JNIEnv** env, void*) { + *env = s_env; + return JNI_OK; +} + +// JNIEnv functions +jobject env_callObjectMethodV(JNIEnv*, jobject, jmethodID, va_list) { + return MY_JOBJECT; +} +void env_callVoidMethodV(JNIEnv*, jobject, jmethodID, va_list) {} +void env_deleteRef(JNIEnv*, jobject) {} +jboolean env_exceptionCheck(JNIEnv*) { + return false; +} +jclass env_findClass(JNIEnv*, const char*) { + return (jclass) 1; +} +jbyte* env_getByteArrayElements(JNIEnv*, jbyteArray, jboolean*) { + return NULL; +} +jmethodID env_getMethodID(JNIEnv*, jclass, const char*, const char*) { + return (jmethodID) 1; +} +jclass env_getObjectClass(JNIEnv*, jobject) { + return (jclass) 1; +} +static const char* s_fakeString = "Fake Java String"; +const jchar* env_getStringChars(JNIEnv*, jstring, jboolean* isCopy) { + if (isCopy) + *isCopy = false; + return (const jchar*)s_fakeString; +} +jsize env_getStringLength(JNIEnv*, jstring) { + return sizeof(s_fakeString) - 1; +} +jbyteArray env_newByteArray(JNIEnv*, jsize) { + return (jbyteArray) 1; +} +jobject env_newRef(JNIEnv*, jobject obj) { + return obj; +} +jobject env_newObjectV(JNIEnv*, jclass, jmethodID, va_list) { + return MY_JOBJECT; +} +jstring env_newString(JNIEnv*, const jchar*, jsize) { + return (jstring) 1; +} +void env_releaseByteArrayElements(JNIEnv*, jbyteArray, jbyte*, jint) {} +void env_releaseStringChars(JNIEnv*, jstring, const jchar*) {} +void env_setByteArrayRegion(JNIEnv*, jbyteArray, jsize, jsize, const jbyte*) {} +void env_setIntField(JNIEnv*, jobject, jfieldID, jint) {} + +void InitializeJavaVM() { + // First, create the fake vm + s_jvm = new JavaVM; + JNIInvokeInterface* i = new JNIInvokeInterface; + memset(i, 0, sizeof(JNIInvokeInterface)); + s_jvm->functions = i; + + // Now, assign the functions of the vm to our fake ones. + i->AttachCurrentThread = vm_attachCurrentThread; + + // Create the fake env next + s_env = new JNIEnv; + JNINativeInterface* n = new JNINativeInterface; + memset(n, 0, sizeof(JNINativeInterface)); + s_env->functions = n; + + // Point the functions we care about to out fake ones. + n->CallObjectMethodV = env_callObjectMethodV; + n->CallVoidMethodV = env_callVoidMethodV; + n->DeleteLocalRef = env_deleteRef; + n->DeleteGlobalRef = env_deleteRef; + n->DeleteWeakGlobalRef = env_deleteRef; + n->ExceptionCheck = env_exceptionCheck; + n->FindClass = env_findClass; + n->GetByteArrayElements = env_getByteArrayElements; + n->GetMethodID = env_getMethodID; + n->GetObjectClass = env_getObjectClass; + n->GetStringChars = env_getStringChars; + n->GetStringLength = env_getStringLength; + n->NewByteArray = env_newByteArray; + n->NewLocalRef = env_newRef; + n->NewGlobalRef = env_newRef; + n->NewWeakGlobalRef = env_newRef; + n->NewObjectV = env_newObjectV; + n->NewString = env_newString; + n->ReleaseByteArrayElements = env_releaseByteArrayElements; + n->ReleaseStringChars = env_releaseStringChars; + n->SetByteArrayRegion = env_setByteArrayRegion; + n->SetIntField = env_setIntField; + + // Tell WebCore about the vm + JSC::Bindings::setJavaVM(s_jvm); +} diff --git a/Source/WebKit/android/benchmark/MyJavaVM.h b/Source/WebKit/android/benchmark/MyJavaVM.h new file mode 100644 index 0000000..3092161 --- /dev/null +++ b/Source/WebKit/android/benchmark/MyJavaVM.h @@ -0,0 +1,34 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MyJavaVM_h +#define MyJavaVM_h + +// Make it 1 just to appease any assertions or checks for valid objects +#define MY_JOBJECT ((jobject) 1) + +void InitializeJavaVM(); + +#endif diff --git a/Source/WebKit/android/benchmark/main.cpp b/Source/WebKit/android/benchmark/main.cpp new file mode 100644 index 0000000..fcb797d --- /dev/null +++ b/Source/WebKit/android/benchmark/main.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcore_test" + +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <utils/Log.h> + +namespace android { +extern void benchmark(const char*, int, int ,int); +} + +int main(int argc, char** argv) { + int width = 800; + int height = 600; + int reloadCount = 0; + while (true) { + int c = getopt(argc, argv, "d:r:"); + if (c == -1) + break; + else if (c == 'd') { + char* x = strchr(optarg, 'x'); + if (x) { + width = atoi(optarg); + height = atoi(x + 1); + LOGD("Rendering page at %dx%d", width, height); + } + } else if (c == 'r') { + reloadCount = atoi(optarg); + if (reloadCount < 0) + reloadCount = 0; + LOGD("Reloading %d times", reloadCount); + } + } + if (optind >= argc) { + LOGE("Please supply a file to read\n"); + return 1; + } + + android::benchmark(argv[optind], reloadCount, width, height); +} diff --git a/Source/WebKit/android/content/PhoneEmailDetector.cpp b/Source/WebKit/android/content/PhoneEmailDetector.cpp deleted file mode 100644 index ca97a71..0000000 --- a/Source/WebKit/android/content/PhoneEmailDetector.cpp +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright (C) 2011 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 AND ITS CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR 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 OR ITS CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (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" - -#undef WEBKIT_IMPLEMENTATION -#undef LOG - -#include "base/utf_string_conversions.h" -#include "net/base/escape.h" -#include "PhoneEmailDetector.h" -#include "Settings.h" -#include "WebString.h" - -#define LOG_TAG "PhoneNumberDetector" -#include <cutils/log.h> - -#define PHONE_PATTERN "(200) /-.\\ 100 -. 0000" - -static const char kTelSchemaPrefix[] = "tel:"; -static const char kEmailSchemaPrefix[] = "mailto:"; - -void FindReset(FindState* state); -void FindResetNumber(FindState* state); -FoundState FindPartialNumber(const UChar* chars, unsigned length, - FindState* s); -struct FindState; - -static FoundState FindPartialEMail(const UChar* , unsigned length, FindState* ); -static bool IsDomainChar(UChar ch); -static bool IsMailboxChar(UChar ch); - -PhoneEmailDetector::PhoneEmailDetector() - : m_foundResult(FOUND_NONE) -{ -} - -bool PhoneEmailDetector::IsEnabled(const WebKit::WebHitTestInfo& hit_test) -{ - WebCore::Settings* settings = GetSettings(hit_test); - if (!settings) - return false; - m_isPhoneDetectionEnabled = settings->formatDetectionTelephone(); - m_isEmailDetectionEnabled = settings->formatDetectionEmail(); - return m_isEmailDetectionEnabled || m_isPhoneDetectionEnabled; -} - -bool PhoneEmailDetector::FindContent(const string16::const_iterator& begin, - const string16::const_iterator& end, - size_t* start_pos, - size_t* end_pos) -{ - FindReset(&m_findState); - m_foundResult = FOUND_NONE; - if (m_isPhoneDetectionEnabled) - m_foundResult = FindPartialNumber(begin, end - begin, &m_findState); - if (m_foundResult == FOUND_COMPLETE) - m_prefix = kTelSchemaPrefix; - else { - FindReset(&m_findState); - if (m_isEmailDetectionEnabled) - m_foundResult = FindPartialEMail(begin, end - begin, &m_findState); - m_prefix = kEmailSchemaPrefix; - } - *start_pos = m_findState.mStartResult; - *end_pos = m_findState.mEndResult; - return m_foundResult == FOUND_COMPLETE; -} - -std::string PhoneEmailDetector::GetContentText(const WebKit::WebRange& range) -{ - if (m_foundResult == FOUND_COMPLETE) { - if (m_prefix == kTelSchemaPrefix) - return UTF16ToUTF8(m_findState.mStore); - else - return UTF16ToUTF8(range.toPlainText()); - } - return std::string(); -} - -GURL PhoneEmailDetector::GetIntentURL(const std::string& content_text) -{ - return GURL(m_prefix + - EscapeQueryParamValue(content_text, true)); -} - -void FindReset(FindState* state) -{ - memset(state, 0, sizeof(FindState)); - state->mCurrent = ' '; - FindResetNumber(state); -} - -void FindResetNumber(FindState* state) -{ - state->mOpenParen = false; - state->mPattern = (char*) PHONE_PATTERN; - state->mStorePtr = state->mStore; -} - -FoundState 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 = 0; - string16 search16(chars, length); - std::string searchSpace = UTF16ToUTF8(search16); - 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; -} - -FoundState 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; -} - -bool IsDomainChar(UChar ch) -{ - static const unsigned body[] = {0x03ff6000, 0x07fffffe, 0x07fffffe}; // 0-9 . - A-Z a-z - ch -= 0x20; - if (ch > 'z' - 0x20) - return false; - return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; -} - -bool IsMailboxChar(UChar ch) -{ - // According to http://en.wikipedia.org/wiki/Email_address - // ! # $ % & ' * + - . / 0-9 = ? - // A-Z ^ _ - // ` a-z { | } ~ - static const unsigned body[] = {0xa3ffecfa, 0xc7fffffe, 0x7fffffff}; - ch -= 0x20; - if (ch > '~' - 0x20) - return false; - return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; -} diff --git a/Source/WebKit/android/content/PhoneEmailDetector.h b/Source/WebKit/android/content/PhoneEmailDetector.h deleted file mode 100644 index 74ff5b1..0000000 --- a/Source/WebKit/android/content/PhoneEmailDetector.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2011 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 AND ITS CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR 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 OR ITS CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (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 "content/content_detector.h" -#include "PlatformString.h" - -#define NAVIGATION_MAX_PHONE_LENGTH 14 - -struct FindState { - int mStartResult; - int mEndResult; - char* mPattern; - UChar mStore[NAVIGATION_MAX_PHONE_LENGTH + 1]; - UChar* mStorePtr; - UChar mBackOne; - UChar mBackTwo; - UChar mCurrent; - bool mOpenParen; - bool mInitialized; - bool mContinuationNode; -}; - -enum FoundState { - FOUND_NONE, - FOUND_PARTIAL, - FOUND_COMPLETE -}; - -// Searches for phone numbers (US only) or email addresses based off of the navcache code -class PhoneEmailDetector : public ContentDetector { -public: - PhoneEmailDetector(); - virtual ~PhoneEmailDetector() {} - -private: - // Implementation of ContentDetector. - virtual bool FindContent(const string16::const_iterator& begin, - const string16::const_iterator& end, - size_t* start_pos, - size_t* end_pos); - - virtual std::string GetContentText(const WebKit::WebRange& range); - virtual GURL GetIntentURL(const std::string& content_text); - virtual size_t GetMaximumContentLength() { - return NAVIGATION_MAX_PHONE_LENGTH * 4; - } - virtual bool IsEnabled(const WebKit::WebHitTestInfo& hit_test) OVERRIDE; - - DISALLOW_COPY_AND_ASSIGN(PhoneEmailDetector); - - FindState m_findState; - FoundState m_foundResult; - const char* m_prefix; - // TODO: This shouldn't be done like this. PhoneEmailDetector should be - // refactored into two pieces and follow the IsEnabled style. This will - // only work because we always call IsEnabled before FindContent - bool m_isPhoneDetectionEnabled; - bool m_isEmailDetectionEnabled; -}; diff --git a/Source/WebKit/android/content/address_detector.cpp b/Source/WebKit/android/content/address_detector.cpp deleted file mode 100644 index f6657d9..0000000 --- a/Source/WebKit/android/content/address_detector.cpp +++ /dev/null @@ -1,945 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" - -// Magic pretend-to-be-a-chromium-build flags -#undef WEBKIT_IMPLEMENTATION -#undef LOG - -#include "content/address_detector.h" - -#include <bitset> - -#include "base/utf_string_conversions.h" -#include "net/base/escape.h" -#include "Settings.h" -#include "WebString.h" - -#include <wtf/HashSet.h> -#include <wtf/Noncopyable.h> -#include <wtf/text/StringHash.h> -#include <wtf/text/WTFString.h> - -namespace { - -// Prefix used for geographical address intent URIs. -static const char kAddressSchemaPrefix[] = "geo:0,0?q="; - -// Maximum text length to be searched for address detection. -static const size_t kMaxAddressLength = 500; - -// Minimum number of words in an address after the house number -// before a state is expected to be found. -// A value too high can miss short addresses. -const size_t kMinAddressWords = 3; - -// Maximum number of words allowed in an address between the house number -// and the state, both not included. -const size_t kMaxAddressWords = 12; - -// Maximum number of lines allowed in an address between the house number -// and the state, both not included. -const size_t kMaxAddressLines = 5; - -// Maximum length allowed for any address word between the house number -// and the state, both not included. -const size_t kMaxAddressNameWordLength = 25; - -// Maximum number of words after the house number in which the location name -// should be found. -const size_t kMaxLocationNameDistance = 4; - -// Number of digits for a valid zip code. -const size_t kZipDigits = 5; - -// Number of digits for a valid zip code in the Zip Plus 4 format. -const size_t kZipPlus4Digits = 9; - -// Maximum number of digits of a house number, including possible hyphens. -const size_t kMaxHouseDigits = 5; - -// Additional characters used as new line delimiters. -const char16 kNewlineDelimiters[] = { - ',', - '*', - 0x2022, // Unicode bullet - 0, -}; - -char16 SafePreviousChar(const string16::const_iterator& it, - const string16::const_iterator& begin) { - if (it == begin) - return ' '; - return *(it - 1); -} - -char16 SafeNextChar(const string16::const_iterator& it, - const string16::const_iterator& end) { - if (it == end) - return ' '; - return *(it + 1); -} - -bool WordLowerCaseEqualsASCII(string16::const_iterator word_begin, - string16::const_iterator word_end, const char* ascii_to_match) { - for (string16::const_iterator it = word_begin; it != word_end; - ++it, ++ascii_to_match) { - if (!*ascii_to_match || base::ToLowerASCII(*it) != *ascii_to_match) - return false; - } - return *ascii_to_match == 0 || *ascii_to_match == ' '; -} - -bool LowerCaseEqualsASCIIWithPlural(string16::const_iterator word_begin, - string16::const_iterator word_end, const char* ascii_to_match, - bool allow_plural) { - for (string16::const_iterator it = word_begin; it != word_end; - ++it, ++ascii_to_match) { - if (!*ascii_to_match && allow_plural && *it == 's' && it + 1 == word_end) - return true; - - if (!*ascii_to_match || base::ToLowerASCII(*it) != *ascii_to_match) - return false; - } - return *ascii_to_match == 0; -} - -} // anonymous namespace - - -AddressDetector::AddressDetector() { -} - -AddressDetector::~AddressDetector() { -} - -std::string AddressDetector::GetContentText(const WebKit::WebRange& range) { - // Get the address and replace unicode bullets with commas. - string16 address_16 = CollapseWhitespace(range.toPlainText(), false); - std::replace(address_16.begin(), address_16.end(), - static_cast<char16>(0x2022), static_cast<char16>(',')); - return UTF16ToUTF8(address_16); -} - -GURL AddressDetector::GetIntentURL(const std::string& content_text) { - return GURL(kAddressSchemaPrefix + - EscapeQueryParamValue(content_text, true)); -} - -size_t AddressDetector::GetMaximumContentLength() { - return kMaxAddressLength; -} - -bool AddressDetector::IsEnabled(const WebKit::WebHitTestInfo& hit_test) { - WebCore::Settings* settings = GetSettings(hit_test); - return settings && settings->formatDetectionAddress(); -} - -bool AddressDetector::FindContent(const string16::const_iterator& begin, - const string16::const_iterator& end, size_t* start_pos, size_t* end_pos) { - HouseNumberParser house_number_parser; - - // Keep going through the input string until a potential house number is - // detected. Start tokenizing the following words to find a valid - // street name within a word range. Then, find a state name followed - // by a valid zip code for that state. Also keep a look for any other - // possible house numbers to continue from in case of no match and for - // state names not followed by a zip code (e.g. New York, NY 10000). - const string16 newline_delimiters = kNewlineDelimiters; - const string16 delimiters = kWhitespaceUTF16 + newline_delimiters; - for (string16::const_iterator it = begin; it != end; ) { - Word house_number; - if (!house_number_parser.Parse(it, end, &house_number)) - return false; - - String16Tokenizer tokenizer(house_number.end, end, delimiters); - tokenizer.set_options(String16Tokenizer::RETURN_DELIMS); - - std::vector<Word> words; - words.push_back(house_number); - - bool found_location_name = false; - bool continue_on_house_number = true; - size_t next_house_number_word = 0; - size_t num_lines = 1; - - // Don't include the house number in the word count. - size_t next_word = 1; - for (; next_word <= kMaxAddressWords + 1; ++next_word) { - - // Extract a new word from the tokenizer. - if (next_word == words.size()) { - do { - if (!tokenizer.GetNext()) - return false; - - // Check the number of address lines. - if (tokenizer.token_is_delim() && newline_delimiters.find( - *tokenizer.token_begin()) != string16::npos) { - ++num_lines; - } - } while (tokenizer.token_is_delim()); - - if (num_lines > kMaxAddressLines) - break; - - words.push_back(Word(tokenizer.token_begin(), tokenizer.token_end())); - } - - // Check the word length. If too long, don't try to continue from - // the next house number as no address can hold this word. - const Word& current_word = words[next_word]; - DCHECK_GT(std::distance(current_word.begin, current_word.end), 0); - size_t current_word_length = std::distance( - current_word.begin, current_word.end); - if (current_word_length > kMaxAddressNameWordLength) { - continue_on_house_number = false; - break; - } - - // Check if the new word is a valid house number. - // This is used to properly resume parsing in case the maximum number - // of words is exceeded. - if (next_house_number_word == 0 && - house_number_parser.Parse(current_word.begin, current_word.end, NULL)) { - next_house_number_word = next_word; - continue; - } - - // Look for location names in the words after the house number. - // A range limitation is introduced to avoid matching - // anything that starts with a number before a legitimate address. - if (next_word <= kMaxLocationNameDistance && - IsValidLocationName(current_word)) { - found_location_name = true; - continue; - } - - // Don't count the house number. - if (next_word > kMinAddressWords) { - // Looking for the state is likely to add new words to the list while - // checking for multi-word state names. - size_t state_first_word = next_word; - size_t state_last_word, state_index; - if (FindStateStartingInWord(&words, state_first_word, &state_last_word, - &tokenizer, &state_index)) { - - // A location name should have been found at this point. - if (!found_location_name) - break; - - // Explicitly exclude "et al", as "al" is a valid state code. - if (current_word_length == 2 && words.size() > 2) { - const Word& previous_word = words[state_first_word - 1]; - if (previous_word.end - previous_word.begin == 2 && - LowerCaseEqualsASCII(previous_word.begin, previous_word.end, - "et") && - LowerCaseEqualsASCII(current_word.begin, current_word.end, - "al")) - break; - } - - // Extract one more word from the tokenizer if not already available. - size_t zip_word = state_last_word + 1; - if (zip_word == words.size()) { - do { - if (!tokenizer.GetNext()) { - // Zip is optional - *start_pos = words[0].begin - begin; - *end_pos = words[state_last_word].end - begin; - return true; - } - } while (tokenizer.token_is_delim()); - words.push_back(Word(tokenizer.token_begin(), - tokenizer.token_end())); - } - - // Check the parsing validity and state range of the zip code. - next_word = state_last_word; - if (!IsZipValid(words[zip_word], state_index)) - continue; - - *start_pos = words[0].begin - begin; - *end_pos = words[zip_word].end - begin; - return true; - } - } - } - - // Avoid skipping too many words because of a non-address number - // at the beginning of the contents to parse. - if (continue_on_house_number && next_house_number_word > 0) { - it = words[next_house_number_word].begin; - } else { - DCHECK(!words.empty()); - next_word = std::min(next_word, words.size() - 1); - it = words[next_word].end; - } - } - - return false; -} - -bool AddressDetector::HouseNumberParser::IsPreDelimiter( - char16 character) { - return character == ':' || IsPostDelimiter(character); -} - -bool AddressDetector::HouseNumberParser::IsPostDelimiter( - char16 character) { - return IsWhitespace(character) || strchr(",\"'", character); -} - -void AddressDetector::HouseNumberParser::RestartOnNextDelimiter() { - ResetState(); - for (; it_ != end_ && !IsPreDelimiter(*it_); ++it_) {} -} - -void AddressDetector::HouseNumberParser::AcceptChars(size_t num_chars) { - size_t offset = std::min(static_cast<size_t>(std::distance(it_, end_)), - num_chars); - it_ += offset; - result_chars_ += offset; -} - -void AddressDetector::HouseNumberParser::SkipChars(size_t num_chars) { - it_ += std::min(static_cast<size_t>(std::distance(it_, end_)), num_chars); -} - -void AddressDetector::HouseNumberParser::ResetState() { - num_digits_ = 0; - result_chars_ = 0; -} - -bool AddressDetector::HouseNumberParser::CheckFinished(Word* word) const { - // There should always be a number after a hyphen. - if (result_chars_ == 0 || SafePreviousChar(it_, begin_) == '-') - return false; - - if (word) { - word->begin = it_ - result_chars_; - word->end = it_; - } - return true; -} - -bool AddressDetector::HouseNumberParser::Parse( - const string16::const_iterator& begin, - const string16::const_iterator& end, Word* word) { - it_ = begin_ = begin; - end_ = end; - ResetState(); - - // Iterations only used as a fail-safe against any buggy infinite loops. - size_t iterations = 0; - size_t max_iterations = end - begin + 1; - for (; it_ != end_ && iterations < max_iterations; ++iterations) { - - // Word finished case. - if (IsPostDelimiter(*it_)) { - if (CheckFinished(word)) - return true; - else if (result_chars_) - ResetState(); - - SkipChars(1); - continue; - } - - // More digits. There should be no more after a letter was found. - if (IsAsciiDigit(*it_)) { - if (num_digits_ >= kMaxHouseDigits) { - RestartOnNextDelimiter(); - } else { - AcceptChars(1); - ++num_digits_; - } - continue; - } - - if (IsAsciiAlpha(*it_)) { - // Handle special case 'one'. - if (result_chars_ == 0) { - if (it_ + 3 <= end_ && LowerCaseEqualsASCII(it_, it_ + 3, "one")) - AcceptChars(3); - else - RestartOnNextDelimiter(); - continue; - } - - // There should be more than 1 character because of result_chars. - DCHECK_GT(result_chars_, 0U); - DCHECK_NE(it_, begin_); - char16 previous = SafePreviousChar(it_, begin_); - if (IsAsciiDigit(previous)) { - // Check cases like '12A'. - char16 next = SafeNextChar(it_, end_); - if (IsPostDelimiter(next)) { - AcceptChars(1); - continue; - } - - // Handle cases like 12a, 1st, 2nd, 3rd, 7th. - if (IsAsciiAlpha(next)) { - char16 last_digit = previous; - char16 first_letter = base::ToLowerASCII(*it_); - char16 second_letter = base::ToLowerASCII(next); - bool is_teen = SafePreviousChar(it_ - 1, begin_) == '1' && - num_digits_ == 2; - - switch (last_digit - '0') { - case 1: - if ((first_letter == 's' && second_letter == 't') || - (first_letter == 't' && second_letter == 'h' && is_teen)) { - AcceptChars(2); - continue; - } - break; - - case 2: - if ((first_letter == 'n' && second_letter == 'd') || - (first_letter == 't' && second_letter == 'h' && is_teen)) { - AcceptChars(2); - continue; - } - break; - - case 3: - if ((first_letter == 'r' && second_letter == 'd') || - (first_letter == 't' && second_letter == 'h' && is_teen)) { - AcceptChars(2); - continue; - } - break; - - case 0: - // Explicitly exclude '0th'. - if (num_digits_ == 1) - break; - - case 4: - case 5: - case 6: - case 7: - case 8: - case 9: - if (first_letter == 't' && second_letter == 'h') { - AcceptChars(2); - continue; - } - break; - - default: - NOTREACHED(); - } - } - } - - RestartOnNextDelimiter(); - continue; - } - - if (*it_ == '-' && num_digits_ > 0) { - AcceptChars(1); - ++num_digits_; - continue; - } - - RestartOnNextDelimiter(); - SkipChars(1); - } - - if (iterations >= max_iterations) - return false; - - return CheckFinished(word); -} - -bool AddressDetector::FindStateStartingInWord(WordList* words, - size_t state_first_word, size_t* state_last_word, - String16Tokenizer* tokenizer, size_t* state_index) { - - // Bitmasks containing the allowed suffixes for 2-letter state codes. - static const int state_two_letter_suffix[23] = { - 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]. - }; - - // Accumulative number of states for the 2-letter code indexed by the first. - static const int state_two_letter_accumulative[24] = { - 0, 5, 5, 8, 10, 10, 12, 14, - 15, 19, 19, 21, 22, 32, 40, 43, - 46, 46, 47, 49, 51, 52, 55, 59 - }; - - // State names sorted alphabetically with their lengths. - // There can be more than one possible name for a same state if desired. - static const struct StateNameInfo { - const char* string; - char first_word_length; - char length; - char state_index; // Relative to two-character code alphabetical order. - } state_names[59] = { - { "alabama", 7, 7, 1 }, { "alaska", 6, 6, 0 }, - { "american samoa", 8, 14, 3 }, { "arizona", 7, 7, 4 }, - { "arkansas", 8, 8, 2 }, - { "california", 10, 10, 5 }, { "colorado", 8, 8, 6 }, - { "connecticut", 11, 11, 7 }, { "delaware", 8, 8, 9 }, - { "district of columbia", 8, 20, 8 }, - { "federated states of micronesia", 9, 30, 11 }, { "florida", 7, 7, 10 }, - { "guam", 4, 4, 13 }, { "georgia", 7, 7, 12 }, - { "hawaii", 6, 6, 14 }, - { "idaho", 5, 5, 16 }, { "illinois", 8, 8, 17 }, { "indiana", 7, 7, 18 }, - { "iowa", 4, 4, 15 }, - { "kansas", 6, 6, 19 }, { "kentucky", 8, 8, 20 }, - { "louisiana", 9, 9, 21 }, - { "maine", 5, 5, 24 }, { "marshall islands", 8, 16, 25 }, - { "maryland", 8, 8, 23 }, { "massachusetts", 13, 13, 22 }, - { "michigan", 8, 8, 26 }, { "minnesota", 9, 9, 27 }, - { "mississippi", 11, 11, 30 }, { "missouri", 8, 8, 28 }, - { "montana", 7, 7, 31 }, - { "nebraska", 8, 8, 34 }, { "nevada", 6, 6, 38 }, - { "new hampshire", 3, 13, 35 }, { "new jersey", 3, 10, 36 }, - { "new mexico", 3, 10, 37 }, { "new york", 3, 8, 39 }, - { "north carolina", 5, 14, 32 }, { "north dakota", 5, 12, 33 }, - { "northern mariana islands", 8, 24, 29 }, - { "ohio", 4, 4, 40 }, { "oklahoma", 8, 8, 41 }, { "oregon", 6, 6, 42 }, - { "palau", 5, 5, 45 }, { "pennsylvania", 12, 12, 43 }, - { "puerto rico", 6, 11, 44 }, - { "rhode island", 5, 5, 46 }, - { "south carolina", 5, 14, 47 }, { "south dakota", 5, 12, 48 }, - { "tennessee", 9, 9, 49 }, { "texas", 5, 5, 50 }, - { "utah", 4, 4, 51 }, - { "vermont", 7, 7, 54 }, { "virgin islands", 6, 14, 53 }, - { "virginia", 8, 8, 52 }, - { "washington", 10, 10, 55 }, { "west virginia", 4, 13, 57 }, - { "wisconsin", 9, 9, 56 }, { "wyoming", 7, 7, 58 } - }; - - // Accumulative number of states for sorted names indexed by the first letter. - // Required a different one since there are codes that don't share their - // first letter with the name of their state (MP = Northern Mariana Islands). - static const int state_names_accumulative[24] = { - 0, 5, 5, 8, 10, 10, 12, 14, - 15, 19, 19, 21, 22, 31, 40, 43, - 46, 46, 47, 49, 51, 52, 55, 59 - }; - - DCHECK_EQ(state_names_accumulative[arraysize(state_names_accumulative) - 1], - static_cast<int>(ARRAYSIZE_UNSAFE(state_names))); - - const Word& first_word = words->at(state_first_word); - int length = first_word.end - first_word.begin; - if (length < 2 || !IsAsciiAlpha(*first_word.begin)) - return false; - - // No state names start with x, y, z. - char16 first_letter = base::ToLowerASCII(*first_word.begin); - if (first_letter > 'w') - return false; - - DCHECK(first_letter >= 'a'); - int first_index = first_letter - 'a'; - - // Look for two-letter state names. - if (length == 2 && IsAsciiAlpha(*(first_word.begin + 1))) { - char16 second_letter = base::ToLowerASCII(*(first_word.begin + 1)); - DCHECK(second_letter >= 'a'); - - int second_index = second_letter - 'a'; - if (!(state_two_letter_suffix[first_index] & (1 << second_index))) - return false; - - std::bitset<32> previous_suffixes = state_two_letter_suffix[first_index] & - ((1 << second_index) - 1); - *state_last_word = state_first_word; - *state_index = state_two_letter_accumulative[first_index] + - previous_suffixes.count(); - return true; - } - - // Look for full state names by their first letter. Discard by length. - for (int state = state_names_accumulative[first_index]; - state < state_names_accumulative[first_index + 1]; ++state) { - if (state_names[state].first_word_length != length) - continue; - - bool state_match = false; - size_t state_word = state_first_word; - for (int pos = 0; true; ) { - if (!WordLowerCaseEqualsASCII(words->at(state_word).begin, - words->at(state_word).end, &state_names[state].string[pos])) - break; - - pos += words->at(state_word).end - words->at(state_word).begin + 1; - if (pos >= state_names[state].length) { - state_match = true; - break; - } - - // Ran out of words, extract more from the tokenizer. - if (++state_word == words->size()) { - do { - if (!tokenizer->GetNext()) - break; - } while (tokenizer->token_is_delim()); - words->push_back(Word(tokenizer->token_begin(), tokenizer->token_end())); - } - } - - if (state_match) { - *state_last_word = state_word; - *state_index = state_names[state].state_index; - return true; - } - } - - return false; -} - -bool AddressDetector::IsZipValid(const Word& word, size_t state_index) { - size_t length = word.end - word.begin; - if (length != kZipDigits && length != kZipPlus4Digits + 1) - return false; - - for (string16::const_iterator it = word.begin; it != word.end; ++it) { - size_t pos = it - word.begin; - if (IsAsciiDigit(*it) || (*it == '-' && pos == kZipDigits)) - continue; - return false; - } - return IsZipValidForState(word, state_index); -} - -bool AddressDetector::IsZipValidForState(const Word& word, size_t state_index) -{ - enum USState { - AP = -4, // AP (military base in the Pacific) - AA = -3, // AA (military base inside the US) - AE = -2, // AE (military base outside the US) - XX = -1, // (not in use) - AK = 0, // AK Alaska - AL = 1, // AL Alabama - AR = 2, // AR Arkansas - AS = 3, // AS American Samoa - AZ = 4, // AZ Arizona - CA = 5, // CA California - CO = 6, // CO Colorado - CT = 7, // CT Connecticut - DC = 8, // DC District of Columbia - DE = 9, // DE Delaware - FL = 10, // FL Florida - FM = 11, // FM Federated States of Micronesia - GA = 12, // GA Georgia - GU = 13, // GU Guam - HI = 14, // HI Hawaii - IA = 15, // IA Iowa - ID = 16, // ID Idaho - IL = 17, // IL Illinois - IN = 18, // IN Indiana - KS = 19, // KS Kansas - KY = 20, // KY Kentucky - LA = 21, // LA Louisiana - MA = 22, // MA Massachusetts - MD = 23, // MD Maryland - ME = 24, // ME Maine - MH = 25, // MH Marshall Islands - MI = 26, // MI Michigan - MN = 27, // MN Minnesota - MO = 28, // MO Missouri - MP = 29, // MP Northern Mariana Islands - MS = 30, // MS Mississippi - MT = 31, // MT Montana - NC = 32, // NC North Carolina - ND = 33, // ND North Dakota - NE = 34, // NE Nebraska - NH = 35, // NH New Hampshire - NJ = 36, // NJ New Jersey - NM = 37, // NM New Mexico - NV = 38, // NV Nevada - NY = 39, // NY New York - OH = 40, // OH Ohio - OK = 41, // OK Oklahoma - OR = 42, // OR Oregon - PA = 43, // PA Pennsylvania - PR = 44, // PR Puerto Rico - PW = 45, // PW Palau - RI = 46, // RI Rhode Island - SC = 47, // SC South Carolina - SD = 48, // SD South Dakota - TN = 49, // TN Tennessee - TX = 50, // TX Texas - UT = 51, // UT Utah - VA = 52, // VA Virginia - VI = 53, // VI Virgin Islands - VT = 54, // VT Vermont - WA = 55, // WA Washington - WI = 56, // WI Wisconsin - WV = 57, // WV West Virginia - WY = 58, // WY Wyoming - }; - - static const USState stateForZipPrefix[] = { - // 0 1 2 3 4 5 6 7 8 9 - XX, XX, XX, XX, XX, NY, PR, PR, VI, PR, // 000-009 - MA, MA, MA, MA, MA, MA, MA, MA, MA, MA, // 010-019 - MA, MA, MA, MA, MA, MA, MA, MA, RI, RI, // 020-029 - NH, NH, NH, NH, NH, NH, NH, NH, NH, ME, // 030-039 - ME, ME, ME, ME, ME, ME, ME, ME, ME, ME, // 040-049 - VT, VT, VT, VT, VT, MA, VT, VT, VT, VT, // 050-059 - CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, // 060-069 - NJ, NJ, NJ, NJ, NJ, NJ, NJ, NJ, NJ, NJ, // 070-079 - NJ, NJ, NJ, NJ, NJ, NJ, NJ, NJ, NJ, NJ, // 080-089 - AE, AE, AE, AE, AE, AE, AE, AE, AE, XX, // 090-099 - NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, // 100-109 - NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, // 110-119 - NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, // 120-129 - NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, // 130-139 - NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, // 140-149 - PA, PA, PA, PA, PA, PA, PA, PA, PA, PA, // 150-159 - PA, PA, PA, PA, PA, PA, PA, PA, PA, PA, // 160-169 - PA, PA, PA, PA, PA, PA, PA, PA, PA, PA, // 170-179 - PA, PA, PA, PA, PA, PA, PA, PA, PA, PA, // 180-189 - PA, PA, PA, PA, PA, PA, PA, DE, DE, DE, // 190-199 - DC, VA, DC, DC, DC, DC, MD, MD, MD, MD, // 200-209 - MD, MD, MD, XX, MD, MD, MD, MD, MD, MD, // 210-219 - VA, VA, VA, VA, VA, VA, VA, VA, VA, VA, // 220-229 - VA, VA, VA, VA, VA, VA, VA, VA, VA, VA, // 230-239 - VA, VA, VA, VA, VA, VA, VA, WV, WV, WV, // 240-249 - WV, WV, WV, WV, WV, WV, WV, WV, WV, WV, // 250-259 - WV, WV, WV, WV, WV, WV, WV, WV, WV, XX, // 260-269 - NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, // 270-279 - NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, // 280-289 - SC, SC, SC, SC, SC, SC, SC, SC, SC, SC, // 290-299 - GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, // 300-309 - GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, // 310-319 - FL, FL, FL, FL, FL, FL, FL, FL, FL, FL, // 320-329 - FL, FL, FL, FL, FL, FL, FL, FL, FL, FL, // 330-339 - AA, FL, FL, XX, FL, XX, FL, FL, XX, FL, // 340-349 - AL, AL, AL, XX, AL, AL, AL, AL, AL, AL, // 350-359 - AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, // 360-369 - TN, TN, TN, TN, TN, TN, TN, TN, TN, TN, // 370-379 - TN, TN, TN, TN, TN, TN, MS, MS, MS, MS, // 380-389 - MS, MS, MS, MS, MS, MS, MS, MS, GA, GA, // 390-399 - KY, KY, KY, KY, KY, KY, KY, KY, KY, KY, // 400-409 - KY, KY, KY, KY, KY, KY, KY, KY, KY, XX, // 410-419 - KY, KY, KY, KY, KY, KY, KY, KY, XX, XX, // 420-429 - OH, OH, OH, OH, OH, OH, OH, OH, OH, OH, // 430-439 - OH, OH, OH, OH, OH, OH, OH, OH, OH, OH, // 440-449 - OH, OH, OH, OH, OH, OH, OH, OH, OH, OH, // 450-459 - IN, IN, IN, IN, IN, IN, IN, IN, IN, IN, // 460-469 - IN, IN, IN, IN, IN, IN, IN, IN, IN, IN, // 470-479 - MI, MI, MI, MI, MI, MI, MI, MI, MI, MI, // 480-489 - MI, MI, MI, MI, MI, MI, MI, MI, MI, MI, // 490-499 - IA, IA, IA, IA, IA, IA, IA, IA, IA, IA, // 500-509 - IA, IA, IA, IA, IA, IA, IA, XX, XX, XX, // 510-519 - IA, IA, IA, IA, IA, IA, IA, IA, IA, XX, // 520-529 - WI, WI, WI, XX, WI, WI, XX, WI, WI, WI, // 530-539 - WI, WI, WI, WI, WI, WI, WI, WI, WI, WI, // 540-549 - MN, MN, XX, MN, MN, MN, MN, MN, MN, MN, // 550-559 - MN, MN, MN, MN, MN, MN, MN, MN, XX, DC, // 560-569 - SD, SD, SD, SD, SD, SD, SD, SD, XX, XX, // 570-579 - ND, ND, ND, ND, ND, ND, ND, ND, ND, XX, // 580-589 - MT, MT, MT, MT, MT, MT, MT, MT, MT, MT, // 590-599 - IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 600-609 - IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 610-619 - IL, XX, IL, IL, IL, IL, IL, IL, IL, IL, // 620-629 - MO, MO, XX, MO, MO, MO, MO, MO, MO, MO, // 630-639 - MO, MO, XX, XX, MO, MO, MO, MO, MO, MO, // 640-649 - MO, MO, MO, MO, MO, MO, MO, MO, MO, XX, // 650-659 - KS, KS, KS, XX, KS, KS, KS, KS, KS, KS, // 660-669 - KS, KS, KS, KS, KS, KS, KS, KS, KS, KS, // 670-679 - NE, NE, XX, NE, NE, NE, NE, NE, NE, NE, // 680-689 - NE, NE, NE, NE, XX, XX, XX, XX, XX, XX, // 690-699 - LA, LA, XX, LA, LA, LA, LA, LA, LA, XX, // 700-709 - LA, LA, LA, LA, LA, XX, AR, AR, AR, AR, // 710-719 - AR, AR, AR, AR, AR, AR, AR, AR, AR, AR, // 720-729 - OK, OK, XX, TX, OK, OK, OK, OK, OK, OK, // 730-739 - OK, OK, XX, OK, OK, OK, OK, OK, OK, OK, // 740-749 - TX, TX, TX, TX, TX, TX, TX, TX, TX, TX, // 750-759 - TX, TX, TX, TX, TX, TX, TX, TX, TX, TX, // 760-769 - TX, XX, TX, TX, TX, TX, TX, TX, TX, TX, // 770-779 - TX, TX, TX, TX, TX, TX, TX, TX, TX, TX, // 780-789 - TX, TX, TX, TX, TX, TX, TX, TX, TX, TX, // 790-799 - CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, // 800-809 - CO, CO, CO, CO, CO, CO, CO, XX, XX, XX, // 810-819 - WY, WY, WY, WY, WY, WY, WY, WY, WY, WY, // 820-829 - WY, WY, ID, ID, ID, ID, ID, ID, ID, XX, // 830-839 - UT, UT, UT, UT, UT, UT, UT, UT, XX, XX, // 840-849 - AZ, AZ, AZ, AZ, XX, AZ, AZ, AZ, XX, AZ, // 850-859 - AZ, XX, XX, AZ, AZ, AZ, XX, XX, XX, XX, // 860-869 - NM, NM, NM, NM, NM, NM, XX, NM, NM, NM, // 870-879 - NM, NM, NM, NM, NM, TX, XX, XX, XX, NV, // 880-889 - NV, NV, XX, NV, NV, NV, XX, NV, NV, XX, // 890-899 - CA, CA, CA, CA, CA, CA, CA, CA, CA, XX, // 900-909 - CA, CA, CA, CA, CA, CA, CA, CA, CA, CA, // 910-919 - CA, CA, CA, CA, CA, CA, CA, CA, CA, XX, // 920-929 - CA, CA, CA, CA, CA, CA, CA, CA, CA, CA, // 930-939 - CA, CA, CA, CA, CA, CA, CA, CA, CA, CA, // 940-949 - CA, CA, CA, CA, CA, CA, CA, CA, CA, CA, // 950-959 - CA, CA, AP, AP, AP, AP, AP, HI, HI, GU, // 960-969 - OR, OR, OR, OR, OR, OR, OR, OR, OR, OR, // 970-979 - WA, WA, WA, WA, WA, WA, WA, XX, WA, WA, // 980-989 - WA, WA, WA, WA, WA, AK, AK, AK, AK, AK, // 990-999 - }; - - if (!word.begin || !word.end || (word.end - word.begin) < 3) - return false; - const char16* zipPtr = word.begin; - if (zipPtr[0] < '0' || zipPtr[0] > '9' || - zipPtr[1] < '0' || zipPtr[1] > '9' || - zipPtr[2] < '0' || zipPtr[2] > '9') - return false; - - int zip = zipPtr[0] - '0'; - zip *= 10; - zip += zipPtr[1] - '0'; - zip *= 10; - zip += zipPtr[2] - '0'; - return stateForZipPrefix[zip] == (int) state_index; -} - -static const char* s_rawStreetSuffixes[] = { - "allee", "alley", "ally", "aly", - "anex", "annex", "anx", "arc", "arcade", "av", "ave", "aven", "avenu", - "avenue", "avn", "avnue", "bayoo", "bayou", "bch", "beach", "bend", - "bg", "bgs", "blf", "blfs", "bluf", "bluff", "bluffs", "blvd", "bnd", - "bot", "bottm", "bottom", "boul", "boulevard", "boulv", "br", "branch", - "brdge", "brg", "bridge", "brk", "brks", "brnch", "brook", "brooks", - "btm", "burg", "burgs", "byp", "bypa", "bypas", "bypass", "byps", "byu", - "camp", "canyn", "canyon", "cape", "causeway", "causway", "cen", "cent", - "center", "centers", "centr", "centre", "cir", "circ", "circl", - "circle", "circles", "cirs", "ck", "clb", "clf", "clfs", "cliff", - "cliffs", "club", "cmn", "cmp", "cnter", "cntr", "cnyn", "common", - "cor", "corner", "corners", "cors", "course", "court", "courts", "cove", - "coves", "cp", "cpe", "cr", "crcl", "crcle", "crecent", "creek", "cres", - "crescent", "cresent", "crest", "crk", "crossing", "crossroad", - "crscnt", "crse", "crsent", "crsnt", "crssing", "crssng", "crst", "crt", - "cswy", "ct", "ctr", "ctrs", "cts", "curv", "curve", "cv", "cvs", "cyn", - "dale", "dam", "div", "divide", "dl", "dm", "dr", "driv", "drive", - "drives", "drs", "drv", "dv", "dvd", "est", "estate", "estates", "ests", - "exp", "expr", "express", "expressway", "expw", "expy", "ext", - "extension", "extensions", "extn", "extnsn", "exts", "fall", "falls", - "ferry", "field", "fields", "flat", "flats", "fld", "flds", "fls", - "flt", "flts", "ford", "fords", "forest", "forests", "forg", "forge", - "forges", "fork", "forks", "fort", "frd", "frds", "freeway", "freewy", - "frg", "frgs", "frk", "frks", "frry", "frst", "frt", "frway", "frwy", - "fry", "ft", "fwy", "garden", "gardens", "gardn", "gateway", "gatewy", - "gatway", "gdn", "gdns", "glen", "glens", "gln", "glns", "grden", - "grdn", "grdns", "green", "greens", "grn", "grns", "grov", "grove", - "groves", "grv", "grvs", "gtway", "gtwy", "harb", "harbor", "harbors", - "harbr", "haven", "havn", "hbr", "hbrs", "height", "heights", "hgts", - "highway", "highwy", "hill", "hills", "hiway", "hiwy", "hl", "hllw", - "hls", "hollow", "hollows", "holw", "holws", "hrbor", "ht", "hts", - "hvn", "hway", "hwy", "inlet", "inlt", "is", "island", "islands", - "isle", "isles", "islnd", "islnds", "iss", "jct", "jction", "jctn", - "jctns", "jcts", "junction", "junctions", "junctn", "juncton", "key", - "keys", "knl", "knls", "knol", "knoll", "knolls", "ky", "kys", "la", - "lake", "lakes", "land", "landing", "lane", "lanes", "lck", "lcks", - "ldg", "ldge", "lf", "lgt", "lgts", "light", "lights", "lk", "lks", - "ln", "lndg", "lndng", "loaf", "lock", "locks", "lodg", "lodge", "loop", - "loops", "mall", "manor", "manors", "mdw", "mdws", "meadow", "meadows", - "medows", "mews", "mill", "mills", "mission", "missn", "ml", "mls", - "mnr", "mnrs", "mnt", "mntain", "mntn", "mntns", "motorway", "mount", - "mountain", "mountains", "mountin", "msn", "mssn", "mt", "mtin", "mtn", - "mtns", "mtwy", "nck", "neck", "opas", "orch", "orchard", "orchrd", - "oval", "overpass", "ovl", "park", "parks", "parkway", "parkways", - "parkwy", "pass", "passage", "path", "paths", "pike", "pikes", "pine", - "pines", "pk", "pkway", "pkwy", "pkwys", "pky", "pl", "place", "plain", - "plaines", "plains", "plaza", "pln", "plns", "plz", "plza", "pne", - "pnes", "point", "points", "port", "ports", "pr", "prairie", "prarie", - "prk", "prr", "prt", "prts", "psge", "pt", "pts", "rad", "radial", - "radiel", "radl", "ramp", "ranch", "ranches", "rapid", "rapids", "rd", - "rdg", "rdge", "rdgs", "rds", "real", "rest", "ridge", "ridges", "riv", "river", - "rivr", "rnch", "rnchs", "road", "roads", "route", "row", "rpd", "rpds", - "rst", "rte", "rue", "run", "rvr", "shl", "shls", "shoal", "shoals", - "shoar", "shoars", "shore", "shores", "shr", "shrs", "skwy", "skyway", - "smt", "spg", "spgs", "spng", "spngs", "spring", "springs", "sprng", - "sprngs", "spur", "spurs", "sq", "sqr", "sqre", "sqrs", "sqs", "squ", - "square", "squares", "st", "sta", "station", "statn", "stn", "str", - "stra", "strav", "strave", "straven", "stravenue", "stravn", "stream", - "street", "streets", "streme", "strm", "strt", "strvn", "strvnue", - "sts", "sumit", "sumitt", "summit", "ter", "terr", "terrace", - "throughway", "tpk", "tpke", "tr", "trace", "traces", "track", "tracks", - "trafficway", "trail", "trails", "trak", "trce", "trfy", "trk", "trks", - "trl", "trls", "trnpk", "trpk", "trwy", "tunel", "tunl", "tunls", - "tunnel", "tunnels", "tunnl", "turnpike", "turnpk", "un", "underpass", - "union", "unions", "uns", "upas", "valley", "valleys", "vally", "vdct", - "via", "viadct", "viaduct", "view", "views", "vill", "villag", - "village", "villages", "ville", "villg", "villiage", "vis", "vist", - "vista", "vl", "vlg", "vlgs", "vlly", "vly", "vlys", "vst", "vsta", - "vw", "vws", "walk", "walks", "wall", "way", "ways", "well", "wells", - "wl", "wls", "wy", "xing", "xrd", - 0, -}; - -bool AddressDetector::IsValidLocationName(const Word& word) { - using namespace WTF; - static HashSet<String> streetNames; - if (!streetNames.size()) { - const char** suffixes = s_rawStreetSuffixes; - while (const char* suffix = *suffixes) { - int index = suffix[0] - 'a'; - streetNames.add(suffix); - suffixes++; - } - } - char16 first_letter = base::ToLowerASCII(*word.begin); - if (first_letter > 'z' || first_letter < 'a') - return false; - int index = first_letter - 'a'; - int length = std::distance(word.begin, word.end); - if (*word.end == '.') - length--; - String value(word.begin, length); - return streetNames.contains(value.lower()); -} diff --git a/Source/WebKit/android/content/address_detector.h b/Source/WebKit/android/content/address_detector.h deleted file mode 100644 index 6dc4ce8..0000000 --- a/Source/WebKit/android/content/address_detector.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef CONTENT_RENDERER_ANDROID_ADDRESS_DETECTOR_H_ -#define CONTENT_RENDERER_ANDROID_ADDRESS_DETECTOR_H_ -#pragma once - -#include "build/build_config.h" // Needed for OS_ANDROID - -#if defined(OS_ANDROID) - -#include <vector> - -#include "base/string_tokenizer.h" -#include "base/string_util.h" -#include "content/content_detector.h" - -// Finds a geographical address (currently US only) in the given text string. -class AddressDetector : public ContentDetector { - public: - AddressDetector(); - virtual ~AddressDetector(); - - // Implementation of ContentDetector. - virtual bool FindContent(const string16::const_iterator& begin, - const string16::const_iterator& end, - size_t* start_pos, - size_t* end_pos) OVERRIDE; - - private: - friend class AddressDetectorTest; - - virtual std::string GetContentText(const WebKit::WebRange& range) OVERRIDE; - virtual GURL GetIntentURL(const std::string& content_text) OVERRIDE; - virtual size_t GetMaximumContentLength() OVERRIDE; - virtual bool IsEnabled(const WebKit::WebHitTestInfo& hit_test) OVERRIDE; - - // Internal structs and classes. Required to be visible by the unit tests. - struct Word { - string16::const_iterator begin; - string16::const_iterator end; - - Word() {} - Word(const string16::const_iterator& begin_it, - const string16::const_iterator& end_it) - : begin(begin_it), - end(end_it) { - DCHECK(begin_it <= end_it); - } - }; - - class HouseNumberParser { - public: - HouseNumberParser() {} - - bool Parse(const string16::const_iterator& begin, - const string16::const_iterator& end, - Word* word); - - private: - static inline bool IsPreDelimiter(char16 character); - static inline bool IsPostDelimiter(char16 character); - inline void RestartOnNextDelimiter(); - - inline bool CheckFinished(Word* word) const; - inline void AcceptChars(size_t num_chars); - inline void SkipChars(size_t num_chars); - inline void ResetState(); - - // Iterators to the beginning, current position and ending of the string - // being currently parsed. - string16::const_iterator begin_; - string16::const_iterator it_; - string16::const_iterator end_; - - // Number of digits found in the current result candidate. - size_t num_digits_; - - // Number of characters previous to the current iterator that belong - // to the current result candidate. - size_t result_chars_; - - DISALLOW_COPY_AND_ASSIGN(HouseNumberParser); - }; - - typedef std::vector<Word> WordList; - typedef StringTokenizerT<string16, string16::const_iterator> - String16Tokenizer; - - static bool FindStateStartingInWord(WordList* words, - size_t state_first_word, - size_t* state_last_word, - String16Tokenizer* tokenizer, - size_t* state_index); - - static bool IsValidLocationName(const Word& word); - static bool IsZipValid(const Word& word, size_t state_index); - static bool IsZipValidForState(const Word& word, size_t state_index); - - DISALLOW_COPY_AND_ASSIGN(AddressDetector); -}; - -#endif // defined(OS_ANDROID) - -#endif // CONTENT_RENDERER_ANDROID_ADDRESS_DETECTOR_H_ diff --git a/Source/WebKit/android/content/content_detector.cpp b/Source/WebKit/android/content/content_detector.cpp deleted file mode 100644 index e423207..0000000 --- a/Source/WebKit/android/content/content_detector.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" - -// Magic pretend-to-be-a-chromium-build flags -#undef WEBKIT_IMPLEMENTATION -#undef LOG - -#include "content/content_detector.h" - -#include "public/android/WebDOMTextContentWalker.h" -#include "public/android/WebHitTestInfo.h" - -#include "Document.h" -#include "Node.h" -#include "Page.h" -#include "Settings.h" - -using WebKit::WebDOMTextContentWalker; -using WebKit::WebRange; - -ContentDetector::Result ContentDetector::FindTappedContent( - const WebKit::WebHitTestInfo& hit_test) { - if (!IsEnabled(hit_test)) - return Result(); - WebKit::WebRange range = FindContentRange(hit_test); - if (range.isNull()) - return Result(); - - std::string text = GetContentText(range); - GURL intent_url = GetIntentURL(text); - return Result(range, text, intent_url); -} - -WebRange ContentDetector::FindContentRange( - const WebKit::WebHitTestInfo& hit_test) { - WebDOMTextContentWalker content_walker(hit_test, GetMaximumContentLength()); - string16 content = content_walker.content(); - if (content.empty()) - return WebRange(); - - size_t selected_offset = content_walker.hitOffsetInContent(); - for (size_t start_offset = 0; start_offset < content.length();) { - size_t relative_start, relative_end; - if (!FindContent(content.begin() + start_offset, - content.end(), &relative_start, &relative_end)) { - break; - } else { - size_t content_start = start_offset + relative_start; - size_t content_end = start_offset + relative_end; - DCHECK(content_end <= content.length()); - - if (selected_offset >= content_start && selected_offset < content_end) { - WebRange range = content_walker.contentOffsetsToRange( - content_start, content_end); - DCHECK(!range.isNull()); - return range; - } else { - start_offset += relative_end; - } - } - } - - return WebRange(); -} - -WebCore::Settings* ContentDetector::GetSettings(const WebKit::WebHitTestInfo& hit_test) { - if (!hit_test.node() || !hit_test.node()->document()) - return 0; - return hit_test.node()->document()->page()->settings(); -} diff --git a/Source/WebKit/android/content/content_detector.h b/Source/WebKit/android/content/content_detector.h deleted file mode 100644 index 270928d..0000000 --- a/Source/WebKit/android/content/content_detector.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef CONTENT_RENDERER_ANDROID_CONTENT_DETECTOR_H_ -#define CONTENT_RENDERER_ANDROID_CONTENT_DETECTOR_H_ -#pragma once - -#include "build/build_config.h" // Needed for OS_ANDROID - -#if defined(OS_ANDROID) - -#include "base/string_util.h" -#include "googleurl/src/gurl.h" -#include "public/WebRange.h" - -namespace WebKit { -class WebHitTestInfo; -} - -namespace WebCore { -class Settings; -} - -// Base class for text-based content detectors. -class ContentDetector { - public: - - // Holds the content detection results. - struct Result { - bool valid; // Flag indicating if the result is valid. - WebKit::WebRange range; // Range describing the content boundaries. - std::string text; // Processed text of the content. - GURL intent_url; // URL of the intent that should process this content. - - Result() : valid(false) {} - - Result(const WebKit::WebRange& range, - const std::string& text, - const GURL& intent_url) - : valid(true), - range(range), - text(text), - intent_url(intent_url) {} - }; - - virtual ~ContentDetector() {} - - // Returns a WebKit range delimiting the contents found around the tapped - // position. If no content is found a null range will be returned. - Result FindTappedContent(const WebKit::WebHitTestInfo& hit_test); - - protected: - // Parses the input string defined by the begin/end iterators returning true - // if the desired content is found. The start and end positions relative to - // the input iterators are returned in start_pos and end_pos. - // The end position is assumed to be non-inclusive. - virtual bool FindContent(const string16::const_iterator& begin, - const string16::const_iterator& end, - size_t* start_pos, - size_t* end_pos) = 0; - - virtual bool IsEnabled(const WebKit::WebHitTestInfo& hit_test) = 0; - WebCore::Settings* GetSettings(const WebKit::WebHitTestInfo& hit_test); - - // Extracts and processes the text of the detected content. - virtual std::string GetContentText(const WebKit::WebRange& range) = 0; - - // Returns the intent URL that should process the content, if any. - virtual GURL GetIntentURL(const std::string& content_text) = 0; - - // Returns the maximum length of text to be extracted around the tapped - // position in order to search for content. - virtual size_t GetMaximumContentLength() = 0; - - ContentDetector() {} - WebKit::WebRange FindContentRange(const WebKit::WebHitTestInfo& hit_test); - - DISALLOW_COPY_AND_ASSIGN(ContentDetector); -}; - -#endif // defined(OS_ANDROID) - -#endif // CONTENT_RENDERER_ANDROID_CONTENT_DETECTOR_H_ diff --git a/Source/WebKit/android/jni/AndroidHitTestResult.cpp b/Source/WebKit/android/jni/AndroidHitTestResult.cpp deleted file mode 100644 index a135c42..0000000 --- a/Source/WebKit/android/jni/AndroidHitTestResult.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2012, The Android Open Source Project - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#define LOG_TAG "AndroidHitTestResult" - -#include "config.h" -#include "AndroidHitTestResult.h" - -#include "content/address_detector.h" -#include "content/PhoneEmailDetector.h" -#include "android/WebHitTestInfo.h" -#include "Document.h" -#include "Element.h" -#include "Frame.h" -#include "HitTestResult.h" -#include "KURL.h" -#include "LayerAndroid.h" -#include "PlatformString.h" -#include "Range.h" -#include "RenderLayer.h" -#include "RenderLayerBacking.h" -#include "RenderObject.h" -#include "WebCoreJni.h" -#include "WebViewCore.h" - -#include <cutils/log.h> -#include <JNIHelp.h> -#include <JNIUtility.h> - -namespace android { - -using namespace WebCore; - -static bool gJniInitialized = false; -static struct { - jmethodID m_Init; - jfieldID m_LinkUrl; - jfieldID m_AnchorText; - jfieldID m_ImageUrl; - jfieldID m_AltDisplayString; - jfieldID m_Title; - jfieldID m_Editable; - jfieldID m_TouchRects; - jfieldID m_TapHighlightColor; - jfieldID m_EnclosingParentRects; - jfieldID m_HasFocus; - jfieldID m_IntentUrl; -} gHitTestGlue; - -struct field { - jclass m_class; - const char *m_fieldName; - const char *m_fieldType; - jfieldID *m_jfield; -}; - -static void InitJni(JNIEnv* env) -{ - if (gJniInitialized) - return; - - jclass rectClass = env->FindClass("android/graphics/Rect"); - ALOG_ASSERT(rectClass, "Could not find android/graphics/Rect"); - jclass hitTestClass = env->FindClass("android/webkit/WebViewCore$WebKitHitTest"); - ALOG_ASSERT(hitTestClass, "Could not find android/webkit/WebViewCore$WebKitHitTest"); - - gHitTestGlue.m_Init = env->GetMethodID(hitTestClass, "<init>", "()V"); - ALOG_ASSERT(gHitTestGlue.m_Init, "Could not find init method on android/webkit/WebViewCore$WebKitHitTest"); - - field fields[] = { - { hitTestClass, "mTouchRects", "[Landroid/graphics/Rect;", &gHitTestGlue.m_TouchRects }, - { hitTestClass, "mEditable", "Z", &gHitTestGlue.m_Editable }, - { hitTestClass, "mLinkUrl", "Ljava/lang/String;", &gHitTestGlue.m_LinkUrl }, - { hitTestClass, "mIntentUrl", "Ljava/lang/String;", &gHitTestGlue.m_IntentUrl }, - { hitTestClass, "mAnchorText", "Ljava/lang/String;", &gHitTestGlue.m_AnchorText }, - { hitTestClass, "mImageUrl", "Ljava/lang/String;", &gHitTestGlue.m_ImageUrl }, - { hitTestClass, "mAltDisplayString", "Ljava/lang/String;", &gHitTestGlue.m_AltDisplayString }, - { hitTestClass, "mTitle", "Ljava/lang/String;", &gHitTestGlue.m_Title }, - { hitTestClass, "mTapHighlightColor", "I", &gHitTestGlue.m_TapHighlightColor }, - { hitTestClass, "mEnclosingParentRects", "[Landroid/graphics/Rect;", &gHitTestGlue.m_EnclosingParentRects }, - { hitTestClass, "mHasFocus", "Z", &gHitTestGlue.m_HasFocus }, - {0, 0, 0, 0}, - }; - - for (int i = 0; fields[i].m_jfield; i++) { - field *f = &fields[i]; - jfieldID field = env->GetFieldID(f->m_class, f->m_fieldName, f->m_fieldType); - ALOG_ASSERT(field, "Can't find %s", f->m_fieldName); - *(f->m_jfield) = field; - } - - gJniInitialized = true; -} - -AndroidHitTestResult::AndroidHitTestResult(WebViewCore* webViewCore, WebCore::HitTestResult& hitTestResult) - : m_webViewCore(webViewCore) - , m_hitTestResult(hitTestResult) -{ - buildHighlightRects(); -} - -void AndroidHitTestResult::setURLElement(Element* element) -{ - m_hitTestResult.setURLElement(element); - buildHighlightRects(); -} - -void AndroidHitTestResult::buildHighlightRects() -{ - m_highlightRects.clear(); - Node* node = m_hitTestResult.URLElement(); - if (!node || !node->renderer()) - node = m_hitTestResult.innerNode(); - if (!node || !node->renderer()) - return; - if (!WebViewCore::nodeIsClickableOrFocusable(node)) - return; - Frame* frame = node->document()->frame(); - IntPoint frameOffset = m_webViewCore->convertGlobalContentToFrameContent(IntPoint(), frame); - RenderObject* renderer = node->renderer(); - Vector<FloatQuad> quads; - if (renderer->isInline()) - renderer->absoluteFocusRingQuads(quads); - if (!quads.size()) - renderer->absoluteQuads(quads); // No fancy rings, grab a bounding box - for (size_t i = 0; i < quads.size(); i++) { - IntRect boundingBox = quads[i].enclosingBoundingBox(); - boundingBox.move(-frameOffset.x(), -frameOffset.y()); - m_highlightRects.append(boundingBox); - } -} - -void AndroidHitTestResult::searchContentDetectors() -{ - AddressDetector address; - PhoneEmailDetector phoneEmail; - Node* node = m_hitTestResult.innerNode(); - if (!node || !node->isTextNode()) - return; - if (!m_hitTestResult.absoluteLinkURL().isEmpty()) - return; - WebKit::WebHitTestInfo webHitTest(m_hitTestResult); - m_searchResult = address.FindTappedContent(webHitTest); - if (!m_searchResult.valid) { - m_searchResult = phoneEmail.FindTappedContent(webHitTest); - } - if (m_searchResult.valid) { - m_highlightRects.clear(); - RefPtr<Range> range = (PassRefPtr<Range>) m_searchResult.range; - range->textRects(m_highlightRects, true); - } -} - -void setStringField(JNIEnv* env, jobject obj, jfieldID field, const String& str) -{ - jstring jstr = wtfStringToJstring(env, str, false); - env->SetObjectField(obj, field, jstr); - env->DeleteLocalRef(jstr); -} - -void setStringField(JNIEnv* env, jobject obj, jfieldID field, const GURL& url) -{ - jstring jstr = stdStringToJstring(env, url.spec(), false); - env->SetObjectField(obj, field, jstr); - env->DeleteLocalRef(jstr); -} - -void setRectArray(JNIEnv* env, jobject obj, jfieldID field, Vector<IntRect> &rects) -{ - jobjectArray array = intRectVectorToRectArray(env, rects); - env->SetObjectField(obj, field, array); - env->DeleteLocalRef(array); -} - -// Some helper macros specific to setting hitTest fields -#define _SET(jtype, jfield, value) env->Set ## jtype ## Field(hitTest, gHitTestGlue.m_ ## jfield, value) -#define SET_BOOL(jfield, value) _SET(Boolean, jfield, value) -#define SET_STRING(jfield, value) setStringField(env, hitTest, gHitTestGlue.m_ ## jfield, value) -#define SET_INT(jfield, value) _SET(Int, jfield, value) - -jobject AndroidHitTestResult::createJavaObject(JNIEnv* env) -{ - InitJni(env); - jclass hitTestClass = env->FindClass("android/webkit/WebViewCore$WebKitHitTest"); - ALOG_ASSERT(hitTestClass, "Could not find android/webkit/WebViewCore$WebKitHitTest"); - - jobject hitTest = env->NewObject(hitTestClass, gHitTestGlue.m_Init); - setRectArray(env, hitTest, gHitTestGlue.m_TouchRects, m_highlightRects); - - Vector<IntRect> rects = enclosingParentRects(m_hitTestResult.innerNode()); - setRectArray(env, hitTest, gHitTestGlue.m_EnclosingParentRects, rects); - - SET_BOOL(Editable, m_hitTestResult.isContentEditable()); - SET_STRING(LinkUrl, m_hitTestResult.absoluteLinkURL().string()); - if (m_searchResult.valid) - SET_STRING(IntentUrl, m_searchResult.intent_url); - SET_STRING(ImageUrl, m_hitTestResult.absoluteImageURL().string()); - SET_STRING(AltDisplayString, m_hitTestResult.altDisplayString()); - TextDirection titleTextDirection; - SET_STRING(Title, m_hitTestResult.title(titleTextDirection)); - if (m_hitTestResult.URLElement()) { - Element* urlElement = m_hitTestResult.URLElement(); - SET_STRING(AnchorText, urlElement->innerText()); - if (urlElement->renderer()) { - SET_INT(TapHighlightColor, - urlElement->renderer()->style()->tapHighlightColor().rgb()); - } - } - Node* focusedNode = m_webViewCore->focusedFrame()->document()->focusedNode(); - SET_BOOL(HasFocus, - focusedNode == m_hitTestResult.URLElement() - || focusedNode == m_hitTestResult.innerNode() - || focusedNode == m_hitTestResult.innerNonSharedNode()); - - env->DeleteLocalRef(hitTestClass); - - return hitTest; -} - -Vector<IntRect> AndroidHitTestResult::enclosingParentRects(Node* node) -{ - int count = 0; - int lastX = 0; - Vector<IntRect> rects; - - while (node && count < 5) { - RenderObject* render = node->renderer(); - if (!render || render->isBody()) - break; - - IntPoint frameOffset = m_webViewCore->convertGlobalContentToFrameContent(IntPoint(), - node->document()->frame()); - IntRect rect = render->absoluteBoundingBoxRect(); - rect.move(-frameOffset.x(), -frameOffset.y()); - if (count == 0 || rect.x() != lastX) { - rects.append(rect); - lastX = rect.x(); - count++; - } - - node = node->parentNode(); - } - - return rects; -} - -} /* namespace android */ diff --git a/Source/WebKit/android/jni/CacheManager.cpp b/Source/WebKit/android/jni/CacheManager.cpp index b34776d..f600d00 100644 --- a/Source/WebKit/android/jni/CacheManager.cpp +++ b/Source/WebKit/android/jni/CacheManager.cpp @@ -25,6 +25,8 @@ #include "config.h" +#if USE(CHROME_NETWORK_STACK) + #include "ChromiumIncludes.h" #include "WebCache.h" #include "WebCoreJni.h" @@ -131,10 +133,12 @@ int registerCacheManager(JNIEnv* env) { #ifndef NDEBUG jclass cacheManager = env->FindClass(javaCacheManagerClass); - ALOG_ASSERT(cacheManager, "Unable to find class"); + LOG_ASSERT(cacheManager, "Unable to find class"); env->DeleteLocalRef(cacheManager); #endif return jniRegisterNativeMethods(env, javaCacheManagerClass, gCacheManagerMethods, NELEM(gCacheManagerMethods)); } } // namespace android + +#endif // USE(CHROME_NETWORK_STACK) diff --git a/Source/WebKit/android/jni/CookieManager.cpp b/Source/WebKit/android/jni/CookieManager.cpp index 5db1e16..f8c2dee 100644 --- a/Source/WebKit/android/jni/CookieManager.cpp +++ b/Source/WebKit/android/jni/CookieManager.cpp @@ -35,35 +35,54 @@ using namespace net; namespace android { -// JNI for android.webkit.CookieManagerClassic -static const char* javaCookieManagerClass = "android/webkit/CookieManagerClassic"; +// JNI for android.webkit.CookieManager +static const char* javaCookieManagerClass = "android/webkit/CookieManager"; static bool acceptCookie(JNIEnv*, jobject) { +#if USE(CHROME_NETWORK_STACK) // This is a static method which gets the cookie policy for all WebViews. We // always apply the same configuration to the contexts for both regular and // private browsing, so expect the same result here. bool regularAcceptCookies = WebCookieJar::get(false)->allowCookies(); ASSERT(regularAcceptCookies == WebCookieJar::get(true)->allowCookies()); return regularAcceptCookies; +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return false; +#endif } static jstring getCookie(JNIEnv* env, jobject, jstring url, jboolean privateBrowsing) { +#if USE(CHROME_NETWORK_STACK) GURL gurl(jstringToStdString(env, url)); CookieOptions options; options.set_include_httponly(); std::string cookies = WebCookieJar::get(privateBrowsing)->cookieStore()->GetCookieMonster()->GetCookiesWithOptions(gurl, options); return stdStringToJstring(env, cookies); +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return jstring(); +#endif } static bool hasCookies(JNIEnv*, jobject, jboolean privateBrowsing) { +#if USE(CHROME_NETWORK_STACK) return WebCookieJar::get(privateBrowsing)->getNumCookiesInDatabase() > 0; +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return false; +#endif } static void removeAllCookie(JNIEnv*, jobject) { +#if USE(CHROME_NETWORK_STACK) WebCookieJar::get(false)->cookieStore()->GetCookieMonster()->DeleteAll(true); // This will lazily create a new private browsing context. However, if the // context doesn't already exist, there's no need to create it, as cookies @@ -75,62 +94,84 @@ static void removeAllCookie(JNIEnv*, jobject) // The Java code removes cookies directly from the backing database, so we do the same, // but with a NULL callback so it's asynchronous. WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->FlushStore(NULL); +#endif } static void removeExpiredCookie(JNIEnv*, jobject) { +#if USE(CHROME_NETWORK_STACK) // This simply forces a GC. The getters delete expired cookies so won't return expired cookies anyway. WebCookieJar::get(false)->cookieStore()->GetCookieMonster()->GetAllCookies(); WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->GetAllCookies(); +#endif } static void removeSessionCookies(WebCookieJar* cookieJar) { +#if USE(CHROME_NETWORK_STACK) CookieMonster* cookieMonster = cookieJar->cookieStore()->GetCookieMonster(); CookieList cookies = cookieMonster->GetAllCookies(); for (CookieList::const_iterator iter = cookies.begin(); iter != cookies.end(); ++iter) { if (iter->IsSessionCookie()) cookieMonster->DeleteCanonicalCookie(*iter); } +#endif } static void removeSessionCookie(JNIEnv*, jobject) { +#if USE(CHROME_NETWORK_STACK) removeSessionCookies(WebCookieJar::get(false)); removeSessionCookies(WebCookieJar::get(true)); +#endif } static void setAcceptCookie(JNIEnv*, jobject, jboolean accept) { +#if USE(CHROME_NETWORK_STACK) // This is a static method which configures the cookie policy for all // WebViews, so we configure the contexts for both regular and private // browsing. WebCookieJar::get(false)->setAllowCookies(accept); WebCookieJar::get(true)->setAllowCookies(accept); +#endif } static void setCookie(JNIEnv* env, jobject, jstring url, jstring value, jboolean privateBrowsing) { +#if USE(CHROME_NETWORK_STACK) GURL gurl(jstringToStdString(env, url)); std::string line(jstringToStdString(env, value)); CookieOptions options; options.set_include_httponly(); WebCookieJar::get(privateBrowsing)->cookieStore()->GetCookieMonster()->SetCookieWithOptions(gurl, line, options); +#endif } static void flushCookieStore(JNIEnv*, jobject) { +#if USE(CHROME_NETWORK_STACK) WebCookieJar::flush(); +#endif } static bool acceptFileSchemeCookies(JNIEnv*, jobject) { +#if USE(CHROME_NETWORK_STACK) return WebCookieJar::acceptFileSchemeCookies(); +#else + // File scheme cookies are always accepted with the Android HTTP stack. + return true; +#endif } static void setAcceptFileSchemeCookies(JNIEnv*, jobject, jboolean accept) { +#if USE(CHROME_NETWORK_STACK) WebCookieJar::setAcceptFileSchemeCookies(accept); +#else + // File scheme cookies are always accepted with the Android HTTP stack. +#endif } static JNINativeMethod gCookieManagerMethods[] = { @@ -151,7 +192,7 @@ int registerCookieManager(JNIEnv* env) { #ifndef NDEBUG jclass cookieManager = env->FindClass(javaCookieManagerClass); - ALOG_ASSERT(cookieManager, "Unable to find class"); + LOG_ASSERT(cookieManager, "Unable to find class"); env->DeleteLocalRef(cookieManager); #endif return jniRegisterNativeMethods(env, javaCookieManagerClass, gCookieManagerMethods, NELEM(gCookieManagerMethods)); diff --git a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp index b69c2af..8beb372 100644 --- a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp +++ b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp @@ -46,7 +46,7 @@ DeviceMotionAndOrientationManager::DeviceMotionAndOrientationManager(WebViewCore { } -void DeviceMotionAndOrientationManager::setUseMock() +void DeviceMotionAndOrientationManager::useMock() { m_useMock = true; } @@ -124,9 +124,9 @@ static WebViewCore* getWebViewCore(JNIEnv* env, jobject webViewCoreObject) return reinterpret_cast<WebViewCore*>(env->GetIntField(webViewCoreObject, nativeClassField)); } -static void setUseMock(JNIEnv* env, jobject, jobject webViewCoreObject) +static void useMock(JNIEnv* env, jobject, jobject webViewCoreObject) { - getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->setUseMock(); + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->useMock(); } static void onMotionChange(JNIEnv* env, jobject, jobject webViewCoreObject, bool canProvideX, double x, bool canProvideY, double y, bool canProvideZ, double z, double interval) @@ -151,7 +151,7 @@ static void onOrientationChange(JNIEnv* env, jobject, jobject webViewCoreObject, } static JNINativeMethod gDeviceMotionAndOrientationManagerMethods[] = { - { "nativeSetUseMock", "(Landroid/webkit/WebViewCore;)V", (void*) setUseMock }, + { "nativeUseMock", "(Landroid/webkit/WebViewCore;)V", (void*) useMock }, { "nativeOnMotionChange", "(Landroid/webkit/WebViewCore;ZDZDZDD)V", (void*) onMotionChange }, { "nativeSetMockOrientation", "(Landroid/webkit/WebViewCore;ZDZDZD)V", (void*) setMockOrientation }, { "nativeOnOrientationChange", "(Landroid/webkit/WebViewCore;ZDZDZD)V", (void*) onOrientationChange } @@ -161,7 +161,7 @@ int registerDeviceMotionAndOrientationManager(JNIEnv* env) { #ifndef NDEBUG jclass deviceMotionAndOrientationManager = env->FindClass(javaDeviceMotionAndOrientationManagerClass); - ALOG_ASSERT(deviceMotionAndOrientationManager, "Unable to find class"); + LOG_ASSERT(deviceMotionAndOrientationManager, "Unable to find class"); env->DeleteLocalRef(deviceMotionAndOrientationManager); #endif diff --git a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h index cc6821d..44463c1 100644 --- a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h +++ b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h @@ -46,7 +46,7 @@ class DeviceMotionAndOrientationManager { public: DeviceMotionAndOrientationManager(WebViewCore*); - void setUseMock(); + void useMock(); void setMockMotion(PassRefPtr<WebCore::DeviceMotionData>); void onMotionChange(PassRefPtr<WebCore::DeviceMotionData>); void setMockOrientation(PassRefPtr<WebCore::DeviceOrientation>); diff --git a/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp b/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp index 295b347..a366601 100755 --- a/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp +++ b/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp @@ -99,10 +99,10 @@ static JNINativeMethod gGeolocationPermissionsMethods[] = { int registerGeolocationPermissions(JNIEnv* env) { - const char* kGeolocationPermissionsClass = "android/webkit/GeolocationPermissionsClassic"; + const char* kGeolocationPermissionsClass = "android/webkit/GeolocationPermissions"; #ifndef NDEBUG jclass geolocationPermissions = env->FindClass(kGeolocationPermissionsClass); - ALOG_ASSERT(geolocationPermissions, "Unable to find class"); + LOG_ASSERT(geolocationPermissions, "Unable to find class"); env->DeleteLocalRef(geolocationPermissions); #endif diff --git a/Source/WebKit/android/jni/JavaBridge.cpp b/Source/WebKit/android/jni/JavaBridge.cpp index 9f89ccd..68eb367 100644 --- a/Source/WebKit/android/jni/JavaBridge.cpp +++ b/Source/WebKit/android/jni/JavaBridge.cpp @@ -41,6 +41,9 @@ #include "PluginDatabase.h" #include "Timer.h" #include "TimerClient.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif #include "WebCache.h" #include "WebCoreJni.h" @@ -145,15 +148,15 @@ JavaBridge::JavaBridge(JNIEnv* env, jobject obj) mResolveFilePathForContentUri = env->GetMethodID(clazz, "resolveFilePathForContentUri", "(Ljava/lang/String;)Ljava/lang/String;"); env->DeleteLocalRef(clazz); - ALOG_ASSERT(mSetSharedTimer, "Could not find method setSharedTimer"); - ALOG_ASSERT(mStopSharedTimer, "Could not find method stopSharedTimer"); - ALOG_ASSERT(mSetCookies, "Could not find method setCookies"); - ALOG_ASSERT(mCookies, "Could not find method cookies"); - ALOG_ASSERT(mCookiesEnabled, "Could not find method cookiesEnabled"); - ALOG_ASSERT(mGetPluginDirectories, "Could not find method getPluginDirectories"); - ALOG_ASSERT(mGetPluginSharedDataDirectory, "Could not find method getPluginSharedDataDirectory"); - ALOG_ASSERT(mGetKeyStrengthList, "Could not find method getKeyStrengthList"); - ALOG_ASSERT(mGetSignedPublicKey, "Could not find method getSignedPublicKey"); + LOG_ASSERT(mSetSharedTimer, "Could not find method setSharedTimer"); + LOG_ASSERT(mStopSharedTimer, "Could not find method stopSharedTimer"); + LOG_ASSERT(mSetCookies, "Could not find method setCookies"); + LOG_ASSERT(mCookies, "Could not find method cookies"); + LOG_ASSERT(mCookiesEnabled, "Could not find method cookiesEnabled"); + LOG_ASSERT(mGetPluginDirectories, "Could not find method getPluginDirectories"); + LOG_ASSERT(mGetPluginSharedDataDirectory, "Could not find method getPluginSharedDataDirectory"); + LOG_ASSERT(mGetKeyStrengthList, "Could not find method getKeyStrengthList"); + LOG_ASSERT(mGetSignedPublicKey, "Could not find method getSignedPublicKey"); JavaSharedClient::SetTimerClient(this); JavaSharedClient::SetCookieClient(this); @@ -280,7 +283,7 @@ JavaBridge::getPluginSharedDataDirectory() void JavaBridge::setSharedTimerCallback(void (*f)()) { - ALOG_ASSERT(!sSharedTimerFiredCallback || sSharedTimerFiredCallback==f, + LOG_ASSERT(!sSharedTimerFiredCallback || sSharedTimerFiredCallback==f, "Shared timer callback may already be set or null!"); sSharedTimerFiredCallback = f; @@ -360,8 +363,8 @@ void JavaBridge::Finalize(JNIEnv* env, jobject obj) { JavaBridge* javaBridge = (JavaBridge*) (env->GetIntField(obj, gJavaBridge_ObjectID)); - ALOG_ASSERT(javaBridge, "Finalize should not be called twice for the same java bridge!"); - ALOGV("webcore_javabridge::nativeFinalize(%p)\n", javaBridge); + 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); } @@ -370,7 +373,16 @@ void JavaBridge::Finalize(JNIEnv* env, jobject obj) 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) @@ -469,10 +481,12 @@ void JavaBridge::RemovePackageName(JNIEnv* env, jobject obj, jstring packageName void JavaBridge::UpdateProxy(JNIEnv* env, jobject obj, jstring newProxy, jstring newExList) { +#if USE(CHROME_NETWORK_STACK) std::string proxy = jstringToStdString(env, newProxy); std::string exList = jstringToStdString(env, newExList); WebCache::get(false)->proxy()->UpdateProxySettings(proxy, exList); WebCache::get(true)->proxy()->UpdateProxySettings(proxy, exList); +#endif } diff --git a/Source/WebKit/android/jni/JavaSharedClient.cpp b/Source/WebKit/android/jni/JavaSharedClient.cpp index 4d073c2..4f40355 100644 --- a/Source/WebKit/android/jni/JavaSharedClient.cpp +++ b/Source/WebKit/android/jni/JavaSharedClient.cpp @@ -88,12 +88,12 @@ namespace android { FileSystemClient* JavaSharedClient::gFileSystemClient = NULL; /////////////////////////////////////////////////////////////////////////// - + struct FuncPtrRec { void (*fProc)(void* payload); void* fPayload; }; - + static SkMutex gFuncPtrQMutex; static SkDeque gFuncPtrQ(sizeof(FuncPtrRec)); @@ -105,34 +105,33 @@ namespace android { FuncPtrRec* rec = (FuncPtrRec*)gFuncPtrQ.push_back(); rec->fProc = proc; rec->fPayload = payload; - + gFuncPtrQMutex.release(); - + gTimerClient->signalServiceFuncPtrQueue(); } void JavaSharedClient::ServiceFunctionPtrQueue() { - // Don't let execution block the WebViewCore thread for too long. - void (*proc)(void*) = 0; - void* payload = 0; - const FuncPtrRec* rec; - - // we have to copy the proc/payload (if present). we do this so we - // don't call the proc inside the mutex (possible deadlock!) - gFuncPtrQMutex.acquire(); - rec = (const FuncPtrRec*)gFuncPtrQ.front(); - if (rec) { - proc = rec->fProc; - payload = rec->fPayload; - gFuncPtrQ.pop_front(); - } - bool scheduleAdditionalCall = (gFuncPtrQ.count() > 0); - gFuncPtrQMutex.release(); - - if (rec) + for (;;) { + void (*proc)(void*) = 0; + void* payload = 0; + const FuncPtrRec* rec; + + // we have to copy the proc/payload (if present). we do this so we + // don't call the proc inside the mutex (possible deadlock!) + gFuncPtrQMutex.acquire(); + rec = (const FuncPtrRec*)gFuncPtrQ.front(); + if (rec) { + proc = rec->fProc; + payload = rec->fPayload; + gFuncPtrQ.pop_front(); + } + gFuncPtrQMutex.release(); + + if (!rec) + break; proc(payload); - if (scheduleAdditionalCall) - gTimerClient->signalServiceFuncPtrQueue(); + } } } diff --git a/Source/WebKit/android/jni/JniUtil.cpp b/Source/WebKit/android/jni/JniUtil.cpp new file mode 100644 index 0000000..ee1e3f9 --- /dev/null +++ b/Source/WebKit/android/jni/JniUtil.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "ChromiumIncludes.h" +#include <JNIHelp.h> + +namespace android { + +static const char* javaJniUtilClass = "android/webkit/JniUtil"; + +static bool useChromiumHttpStack(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + return true; +#else + return false; +#endif +} + +static JNINativeMethod gJniUtilMethods[] = { + { "nativeUseChromiumHttpStack", "()Z", (void*) useChromiumHttpStack }, +}; + +int registerJniUtil(JNIEnv* env) +{ +#ifndef NDEBUG + jclass jniUtil = env->FindClass(javaJniUtilClass); + LOG_ASSERT(jniUtil, "Unable to find class"); + env->DeleteLocalRef(jniUtil); +#endif + return jniRegisterNativeMethods(env, javaJniUtilClass, gJniUtilMethods, NELEM(gJniUtilMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/MIMETypeRegistry.cpp b/Source/WebKit/android/jni/MIMETypeRegistry.cpp index 2734aeb..cbfef6c 100644 --- a/Source/WebKit/android/jni/MIMETypeRegistry.cpp +++ b/Source/WebKit/android/jni/MIMETypeRegistry.cpp @@ -44,11 +44,11 @@ String MIMETypeRegistry::getMIMETypeForExtension(const String& ext) ASSERT(isMainThread()); JNIEnv* env = JSC::Bindings::getJNIEnv(); jclass mimeClass = env->FindClass("android/webkit/MimeTypeMap"); - ALOG_ASSERT(mimeClass, "Could not find class MimeTypeMap"); + LOG_ASSERT(mimeClass, "Could not find class MimeTypeMap"); jmethodID mimeTypeFromExtension = env->GetStaticMethodID(mimeClass, "mimeTypeFromExtension", "(Ljava/lang/String;)Ljava/lang/String;"); - ALOG_ASSERT(mimeTypeFromExtension, + LOG_ASSERT(mimeTypeFromExtension, "Could not find method mimeTypeFromExtension"); jstring extString = wtfStringToJstring(env, ext); jobject mimeType = env->CallStaticObjectMethod(mimeClass, diff --git a/Source/WebKit/android/jni/MockGeolocation.cpp b/Source/WebKit/android/jni/MockGeolocation.cpp index 250953f..1370715 100755 --- a/Source/WebKit/android/jni/MockGeolocation.cpp +++ b/Source/WebKit/android/jni/MockGeolocation.cpp @@ -74,7 +74,7 @@ int registerMockGeolocation(JNIEnv* env) { #ifndef NDEBUG jclass mockGeolocation = env->FindClass(javaMockGeolocationClass); - ALOG_ASSERT(mockGeolocation, "Unable to find class"); + LOG_ASSERT(mockGeolocation, "Unable to find class"); env->DeleteLocalRef(mockGeolocation); #endif diff --git a/Source/WebKit/android/jni/PicturePile.cpp b/Source/WebKit/android/jni/PicturePile.cpp deleted file mode 100644 index ccdfa59..0000000 --- a/Source/WebKit/android/jni/PicturePile.cpp +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright 2012, The Android Open Source Project - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#define LOG_TAG "PicturePile" -#define LOG_NDEBUG 1 - -#include "config.h" -#include "PicturePile.h" - -#include "AndroidLog.h" -#include "FloatRect.h" -#include "GraphicsContext.h" -#include "PlatformGraphicsContextSkia.h" -#include "SkCanvas.h" -#include "SkNWayCanvas.h" -#include "SkPicture.h" -#include "SkPixelRef.h" -#include "SkRect.h" -#include "SkRegion.h" - -#define ENABLE_PRERENDERED_INVALS true -#define MAX_OVERLAP_COUNT 2 -#define MAX_OVERLAP_AREA .7 - -namespace WebCore { - -static SkIRect toSkIRect(const IntRect& rect) { - return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()); -} - -static IntRect extractClipBounds(SkCanvas* canvas, const IntSize& size) { - SkRect clip; - canvas->getClipBounds(&clip); - clip.intersect(0, 0, size.width(), size.height()); - return enclosingIntRect(clip); -} - -PicturePile::PicturePile(const PicturePile& other) - : m_size(other.m_size) - , m_pile(other.m_pile) - , m_webkitInvals(other.m_webkitInvals) -{ -} - -PicturePile::PicturePile(SkPicture* picture) -{ - m_size = IntSize(picture->width(), picture->height()); - PictureContainer pc(IntRect(0, 0, m_size.width(), m_size.height())); - pc.picture = picture; - pc.dirty = false; - m_pile.append(pc); -} - -void PicturePile::draw(SkCanvas* canvas) -{ - /* Loop down recursively, subtracting the previous clip from the SkRegion, - * stopping when the SkRegion is empty. This will still draw back-to-front, - * but it will clip out anything obscured. For performance reasons we use - * the rect bounds of the SkRegion for the clip, so this still can't be - * used for translucent surfaces - */ - TRACE_METHOD(); - IntRect clipBounds = extractClipBounds(canvas, m_size); - SkRegion clipRegion(toSkIRect(clipBounds)); - drawWithClipRecursive(canvas, clipRegion, m_pile.size() - 1); -} - -void PicturePile::clearPrerenders() -{ - for (size_t i = 0; i < m_pile.size(); i++) - m_pile[i].prerendered.clear(); -} - -void PicturePile::drawWithClipRecursive(SkCanvas* canvas, SkRegion& clipRegion, - int index) -{ - // TODO: Add some debug visualizations of this - if (index < 0 || clipRegion.isEmpty()) - return; - PictureContainer& pc = m_pile[index]; - IntRect intersection = clipRegion.getBounds(); - intersection.intersect(pc.area); - if (pc.picture && !intersection.isEmpty()) { - clipRegion.op(intersection, SkRegion::kDifference_Op); - drawWithClipRecursive(canvas, clipRegion, index - 1); - int saved = canvas->save(); - canvas->clipRect(intersection); - canvas->translate(pc.area.x(), pc.area.y()); - canvas->drawPicture(*pc.picture); - canvas->restoreToCount(saved); - } else - drawWithClipRecursive(canvas, clipRegion, index - 1); -} - -// Used by WebViewCore -void PicturePile::invalidate(const IntRect& dirtyRect) -{ - // This will typically happen if the document has been resized but we haven't - // drawn yet. As the first draw after a size change will do a full inval anyway, - // don't bother tracking individual rects - // TODO: Instead of clipping here, we should take the invals as given - // and when the size changes just inval the deltas. This prevents a full - // redraw for a page that grows - IntRect inval = dirtyRect; - inval.intersect(IntRect(0, 0, m_size.width(), m_size.height())); - if (inval.isEmpty()) { - ALOGV("Rejecting inval " INT_RECT_FORMAT, INT_RECT_ARGS(dirtyRect)); - return; - } - // TODO: Support multiple non-intersecting webkit invals - if (m_webkitInvals.size()) - m_webkitInvals[0].unite(inval); - else - m_webkitInvals.append(inval); -} - -void PicturePile::setSize(const IntSize& size) -{ - if (m_size == size) - return; - m_size = size; - // TODO: See above about just adding invals for new content - m_pile.clear(); - m_webkitInvals.clear(); - if (!size.isEmpty()) { - IntRect area(0, 0, size.width(), size.height()); - m_webkitInvals.append(area); - m_pile.append(area); - } -} - -void PicturePile::updatePicturesIfNeeded(PicturePainter* painter) -{ - applyWebkitInvals(); - for (size_t i = 0; i < m_pile.size(); i++) { - PictureContainer& pc = m_pile[i]; - if (pc.dirty) - updatePicture(painter, pc); - } -} - -void PicturePile::updatePicture(PicturePainter* painter, PictureContainer& pc) -{ - /* The ref counting here is a bit unusual. What happens is begin/end recording - * will ref/unref the recording canvas. However, 'canvas' might be pointing - * at an SkNWayCanvas instead of the recording canvas, which needs to be - * unref'd. Thus what we do is ref the recording canvas so that we can - * always unref whatever canvas we have at the end. - */ - TRACE_METHOD(); - SkPicture* picture = new SkPicture(); - SkCanvas* canvas = picture->beginRecording(pc.area.width(), pc.area.height(), - SkPicture::kUsePathBoundsForClip_RecordingFlag); - SkSafeRef(canvas); - canvas->translate(-pc.area.x(), -pc.area.y()); - IntRect drawArea = pc.area; - if (pc.prerendered.get()) { - SkCanvas* prerender = painter->createPrerenderCanvas(pc.prerendered.get()); - if (!prerender) { - ALOGV("Failed to create prerendered for " INT_RECT_FORMAT, - INT_RECT_ARGS(pc.prerendered->area)); - pc.prerendered.clear(); - } else { - drawArea.unite(pc.prerendered->area); - SkNWayCanvas* nwayCanvas = new SkNWayCanvas(drawArea.width(), drawArea.height()); - nwayCanvas->translate(-drawArea.x(), -drawArea.y()); - nwayCanvas->addCanvas(canvas); - nwayCanvas->addCanvas(prerender); - SkSafeUnref(canvas); - SkSafeUnref(prerender); - canvas = nwayCanvas; - } - } - WebCore::PlatformGraphicsContextSkia pgc(canvas); - WebCore::GraphicsContext gc(&pgc); - ALOGV("painting picture: " INT_RECT_FORMAT, INT_RECT_ARGS(drawArea)); - painter->paintContents(&gc, drawArea); - SkSafeUnref(canvas); - picture->endRecording(); - - SkSafeUnref(pc.picture); - pc.picture = picture; - pc.dirty = false; -} - -void PicturePile::reset() -{ - m_size = IntSize(0,0); - m_pile.clear(); - m_webkitInvals.clear(); -} - -void PicturePile::applyWebkitInvals() -{ - m_dirtyRegion.setEmpty(); - if (!m_webkitInvals.size()) - return; - // Build the invals (TODO: Support multiple inval regions) - IntRect inval = m_webkitInvals[0]; - m_dirtyRegion.setRect(toSkIRect(inval)); - for (size_t i = 1; i < m_webkitInvals.size(); i++) { - inval.unite(m_webkitInvals[i]); - m_dirtyRegion.op(toSkIRect(m_webkitInvals[i]), SkRegion::kUnion_Op); - } - m_webkitInvals.clear(); - ALOGV("Webkit inval: " INT_RECT_FORMAT, INT_RECT_ARGS(inval)); - if (inval.isEmpty()) - return; - - // Find the overlaps - Vector<int> overlaps; - for (size_t i = 0; i < m_pile.size(); i++) { - PictureContainer& pc = m_pile[i]; - if (pc.area.contains(inval)) { - if (pc.dirty) { - ALOGV("Found already dirty intersection"); - return; - } - if (pc.area == inval) { - appendToPile(inval); - return; - } - // Don't count the base surface as an overlap - if (pc.area.size() != m_size) - overlaps.append(i); - } else if (pc.area.intersects(inval)) - overlaps.append(i); - } - - if (overlaps.size() >= MAX_OVERLAP_COUNT) { - ALOGV("Exceeds overlap count"); - IntRect overlap = inval; - for (int i = (int) overlaps.size() - 1; i >= 0; i--) { - overlap.unite(m_pile[overlaps[i]].area); - m_pile.remove(overlaps[i]); - } - float overlapArea = overlap.width() * overlap.height(); - float totalArea = m_size.width() * m_size.height(); - if (overlapArea / totalArea > MAX_OVERLAP_AREA) - overlap = IntRect(0, 0, m_size.width(), m_size.height()); - appendToPile(overlap, inval); - return; - } - - // Append! - appendToPile(inval); -} - -void PicturePile::appendToPile(const IntRect& inval, const IntRect& originalInval) -{ - ALOGV("Adding inval " INT_RECT_FORMAT " for original inval " INT_RECT_FORMAT, - INT_RECT_ARGS(inval), INT_RECT_ARGS(originalInval)); - // Remove any entries this obscures - for (int i = (int) m_pile.size() - 1; i >= 0; i--) { - if (inval.contains(m_pile[i].area)) - m_pile.remove(i); - } - PictureContainer container(inval); - if (ENABLE_PRERENDERED_INVALS) { - container.prerendered = PrerenderedInval::create(originalInval.isEmpty() - ? inval : originalInval); - } - m_pile.append(container); -} - -PrerenderedInval* PicturePile::prerenderedInvalForArea(const IntRect& area) -{ - for (int i = (int) m_pile.size() - 1; i >= 0; i--) { - if (m_pile[i].area.intersects(area)) { - RefPtr<PrerenderedInval> inval = m_pile[i].prerendered; - if (inval.get() && inval->area.contains(area)) - return inval.get(); - return 0; - } - } - return 0; -} - -} // namespace WebCore diff --git a/Source/WebKit/android/jni/PicturePile.h b/Source/WebKit/android/jni/PicturePile.h deleted file mode 100644 index b28a792..0000000 --- a/Source/WebKit/android/jni/PicturePile.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2012, The Android Open Source Project - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef PicturePile_h -#define PicturePile_h - -#include "IntRect.h" -#include "IntSize.h" -#include "PrerenderedInval.h" -#include "SkBitmap.h" -#include "SkRegion.h" -#include "SkRefCnt.h" - -#include <wtf/PassRefPtr.h> -#include <wtf/RefCounted.h> -#include <wtf/ThreadSafeRefCounted.h> -#include <wtf/Vector.h> - -class SkPicture; -class SkCanvas; - -namespace WebCore { - -class GraphicsContext; - -class PicturePainter { -public: - virtual void paintContents(GraphicsContext* gc, IntRect& dirty) = 0; - virtual SkCanvas* createPrerenderCanvas(PrerenderedInval* prerendered) - { - return 0; - } - virtual ~PicturePainter() {} -}; - -class PictureContainer { -public: - SkPicture* picture; - IntRect area; - bool dirty; - RefPtr<PrerenderedInval> prerendered; - - PictureContainer(const IntRect& area) - : picture(0) - , area(area) - , dirty(true) - {} - - PictureContainer(const PictureContainer& other) - : picture(other.picture) - , area(other.area) - , dirty(other.dirty) - , prerendered(other.prerendered) - { - SkSafeRef(picture); - } - - ~PictureContainer() - { - SkSafeUnref(picture); - } -}; - -class PicturePile { -public: - PicturePile() {} - PicturePile(const PicturePile& other); - PicturePile(SkPicture* picture); - - const IntSize& size() { return m_size; } - - void clearPrerenders(); - - // used by PicturePileLayerContents - void draw(SkCanvas* canvas); - - // Used by WebViewCore - void invalidate(const IntRect& dirtyRect); - void setSize(const IntSize& size); - void updatePicturesIfNeeded(PicturePainter* painter); - void reset(); - SkRegion& dirtyRegion() { return m_dirtyRegion; } - PrerenderedInval* prerenderedInvalForArea(const IntRect& area); - -private: - void applyWebkitInvals(); - void updatePicture(PicturePainter* painter, PictureContainer& container); - void appendToPile(const IntRect& inval, const IntRect& originalInval = IntRect()); - void drawWithClipRecursive(SkCanvas* canvas, SkRegion& clipRegion, int index); - - IntSize m_size; - Vector<PictureContainer> m_pile; - Vector<IntRect> m_webkitInvals; - SkRegion m_dirtyRegion; -}; - -} // namespace android - -#endif // PicturePile_h diff --git a/Source/WebKit/android/jni/PictureSet.cpp b/Source/WebKit/android/jni/PictureSet.cpp new file mode 100644 index 0000000..4d9d16c --- /dev/null +++ b/Source/WebKit/android/jni/PictureSet.cpp @@ -0,0 +1,1236 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_NDEBUG 0 +#define LOG_TAG "pictureset" + +//#include <config.h> +#include "CachedPrefix.h" +#include "android_graphics.h" +#include "PictureSet.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkRegion.h" +#include "SkStream.h" +#include "TimeCounter.h" + +#define MAX_DRAW_TIME 100 +#define MIN_SPLITTABLE 400 +#define MAX_ADDITIONAL_AREA 0.65 +#define MAX_ADDITIONAL_PICTURES 32 + +#define BUCKET_SIZE 1024 +#define MAX_BUCKET_COUNT_X 16 +#define MAX_BUCKET_COUNT_Y 64 + +#include <wtf/CurrentTime.h> + +#include <cutils/log.h> +#include <wtf/text/CString.h> + +#undef XLOGC +#define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "PictureSet", __VA_ARGS__) + +#ifdef DEBUG + +#undef XLOG +#define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "PictureSet", __VA_ARGS__) + +#else + +#undef XLOG +#define XLOG(...) + +#endif // DEBUG + +#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() : +#ifdef FAST_PICTURESET + mBucketSizeX(BUCKET_SIZE), mBucketSizeY(BUCKET_SIZE), + mBucketCountX(0), mBucketCountY(0), +#endif + mHeight(0), mWidth(0) +{ + setDimensions(0, 0); + mBaseArea = mAdditionalArea = 0; +} + +PictureSet::PictureSet(SkPicture* picture) : +#ifdef FAST_PICTURESET + mBucketSizeX(BUCKET_SIZE), mBucketSizeY(BUCKET_SIZE), + mBucketCountX(0), mBucketCountY(0), +#endif + mHeight(0), mWidth(0) +{ + mBaseArea = mAdditionalArea = 0; + if (!picture) { + setDimensions(0, 0); + return; + } + setDimensions(picture->width(), picture->height()); + mBaseArea = mWidth * mHeight; +#ifdef FAST_PICTURESET + SkIRect area; + area.set(0, 0, mWidth, mHeight); + splitAdd(area); + WTF::Vector<Bucket*>* buckets = bucketsToUpdate(); + for (unsigned int i = 0; i < buckets->size(); i++) { + Bucket* bucket = (*buckets)[i]; + for (unsigned int j = 0; j < bucket->size(); j++) { + BucketPicture& bucketPicture = (*bucket)[j]; + const SkIRect& inval = bucketPicture.mRealArea; + SkPicture *splitPicture = new SkPicture(); + SkCanvas *canvas = splitPicture->beginRecording( + inval.width(), inval.height(), + SkPicture::kUsePathBoundsForClip_RecordingFlag); + canvas->translate(-inval.fLeft, -inval.fTop); + picture->draw(canvas); + splitPicture->endRecording(); + SkSafeUnref(bucketPicture.mPicture); + bucketPicture.mPicture = splitPicture; + } + } + buckets->clear(); +#else + Pictures pictureAndBounds; + pictureAndBounds.mPicture = picture; + SkSafeRef(pictureAndBounds.mPicture); + pictureAndBounds.mEmpty = false; + pictureAndBounds.mArea.setRect(0, 0, mWidth, mHeight); + pictureAndBounds.mSplit = false; + pictureAndBounds.mBase = true; + pictureAndBounds.mElapsed = 0; + pictureAndBounds.mWroteElapsed = false; + mPictures.append(pictureAndBounds); +#endif // FAST_PICTURESET +} + +PictureSet::~PictureSet() +{ + clear(); +} + +#ifdef FAST_PICTURESET +#else +void PictureSet::add(const Pictures* temp) +{ + Pictures pictureAndBounds = *temp; + SkSafeRef(pictureAndBounds.mPicture); + pictureAndBounds.mWroteElapsed = false; + mPictures.append(pictureAndBounds); +} +#endif // FAST_PICTURESET + +void PictureSet::add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split) +{ + if (area.isRect()) { +#ifdef FAST_PICTURESET + splitAdd(area.getBounds()); +#else + add(area, picture, elapsed, split, false); +#endif // FAST_PICTURESET + } else { + SkRegion::Iterator cliperator(area); + while (!cliperator.done()) { + SkIRect ir = cliperator.rect(); +#ifdef FAST_PICTURESET + splitAdd(ir); +#else + SkRegion newArea; + newArea.setRect(ir); + add(newArea, picture, elapsed, split, false); +#endif // FAST_PICTURESET + cliperator.next(); + } + } +} + +#ifdef FAST_PICTURESET + +Bucket* PictureSet::getBucket(int x, int y) +{ + // only create buckets for valid, positive coordinates, ignore and return + // NULL otherwise + if (x < 0 || y < 0) + return 0; + + BucketPosition position(x+1, y+1); + if (!mBuckets.contains(position)) { + XLOG("PictureSet::getBucket(%d, %d) adding new bucket", x, y); + Bucket* bucket = new Bucket(); + mBuckets.add(position, bucket); + } + return mBuckets.get(position); +} + +void PictureSet::displayBucket(Bucket* bucket) +{ + BucketPicture* first = bucket->begin(); + BucketPicture* last = bucket->end(); + for (BucketPicture* current = first; current != last; current++) { + XLOGC("- in %x, bucketPicture %d,%d,%d,%d - %dx%d, picture: %x, base: %x", + bucket, + current->mArea.fLeft, + current->mArea.fTop, + current->mArea.fRight, + current->mArea.fBottom, + current->mArea.width(), + current->mArea.height(), + current->mPicture, + current->mBase); + } +} + +void PictureSet::displayBuckets() +{ + XLOGC("\n\n****** DISPLAY BUCKETS ON PictureSet %x ******", this); + for (BucketMap::iterator iter = mBuckets.begin(); iter != mBuckets.end(); ++iter) { + XLOGC("\n*** Bucket %x for %d, %d", iter->second, iter->first.first, iter->first.second); + displayBucket(iter->second); + } + XLOGC("\n****** END OF DISPLAY BUCKETS ******\n\n"); +} + +// When we receive an inval in a Bucket, we try to see if we intersect with +// existing invals/pictures in the Bucket. +void PictureSet::addToBucket(Bucket* bucket, int dx, int dy, SkIRect& rect) +{ + bool resetBase = false; + + SkIRect totalArea = rect; + BucketPicture* first = bucket->begin(); + BucketPicture* last = bucket->end(); + + // If the inval covers a large area of the base inval, let's repaint the + // entire bucket. + if (rect.width() * rect.height() > MAX_ADDITIONAL_AREA * mBucketSizeX * mBucketSizeY) + resetBase = true; + + // let's gather all the BucketPicture intersecting with the new invalidated + // area, collect their area and remove their picture + for (BucketPicture* current = first; current != last; current++) { + bool remove = resetBase; + bool intersect = false; + + if (!remove) + intersect = SkIRect::Intersects(current->mArea, rect); + // If the current picture is not a base, and we intersect, remove it + if (!remove && !current->mBase && intersect) + remove = true; + // If the current picture is a base, check if the new inval completely + // contains the base, and if so remove it. + if (!remove && current->mBase && rect.contains(current->mArea)) + remove = true; + // If the current picture is a base and it intersects, + // also check that it fully covers the bucket -- otherwise, + // let's aggregate it with the new inval. + if (!remove && current->mBase && intersect + && (current->mArea.width() < mBucketSizeX || current->mArea.height() < mBucketSizeY)) { + remove = true; + } + + if (remove) { + totalArea.join(current->mArea); + current->mBase = false; + current->mArea.setEmpty(); + SkSafeUnref(current->mPicture); + current->mPicture = 0; + } + } + + // Now, let's add the new BucketPicture to the list, with the correct + // area that needs to be repainted + SkRegion region; + SkIRect area = totalArea; + area.offset(dx, dy); + BucketPicture picture = { 0, totalArea, area, false }; + + bucket->append(picture); + + first = bucket->begin(); + last = bucket->end(); + + bool clearUp = false; + if (last - first > MAX_ADDITIONAL_PICTURES) { + // too many pictures in the bucket, let's collapse + clearUp = true; + } + + float bucketBaseArea = 0; + float bucketAdditionalArea = 0; + for (BucketPicture* current = first; current != last; current++) { + float area = current->mArea.width() * current->mArea.height(); + if (current->mBase) + bucketBaseArea += area; + else + bucketAdditionalArea += area; + } + + if (bucketBaseArea > 0 && bucketBaseArea * MAX_ADDITIONAL_AREA <= bucketAdditionalArea) { + // additional area too large, not worth maintaining + clearUp = true; + } + + // To clear things up, we just need to mark the pictures' area as empty + // We only keep the base surface. + if (clearUp) { + for (BucketPicture* current = first; current != last; current++) { + if (!current->mBase) + current->mArea.setEmpty(); + SkSafeUnref(current->mPicture); + current->mPicture = 0; + } + } + + // let's do a pass to collapse out empty areas + BucketPicture* writer = first; + for (BucketPicture* current = first; current != last; current++) { + if (current && current->mArea.isEmpty()) + continue; + *writer++ = *current; + } + + bucket->shrink(writer - first); + + // let's recompute the bases + first = bucket->begin(); + last = bucket->end(); + SkRegion drawn; + drawn.setEmpty(); + for (BucketPicture* current = first; current != last; current++) { + if (drawn.contains(current->mArea) == false) { + current->mBase = true; + } + drawn.op(current->mArea, SkRegion::kUnion_Op); + } +} + +void PictureSet::gatherBucketsForArea(WTF::Vector<Bucket*>& list, const SkIRect& rect) +{ + XLOG("\n--- gatherBucketsForArea for rect %d, %d, %d, %d (%d x %d)", + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + rect.width(), rect.height()); + + if (!mBucketSizeX || !mBucketSizeY) { + XLOGC("PictureSet::gatherBucketsForArea() called with bad bucket size: x=%d y=%d", + mBucketSizeX, mBucketSizeY); + return; + } + + int x = rect.fLeft; + int y = rect.fTop; + int firstTileX = rect.fLeft / mBucketSizeX; + int firstTileY = rect.fTop / mBucketSizeY; + int lastTileX = rect.fRight / mBucketSizeX; + int lastTileY = rect.fBottom / mBucketSizeY; + + for (int i = firstTileX; i <= lastTileX; i++) { + for (int j = firstTileY; j <= lastTileY; j++) { + Bucket* bucket = getBucket(i, j); + XLOG("gather bucket %x for %d, %d", bucket, i+1, j+1); + if (bucket) + list.append(bucket); + } + } +} + +// When we receive a new inval rect, we first find the Buckets that intersect +// with it; then we split the original inval into a serie of invals (one for +// each Bucket we intersect with). We then send that inval to the Bucket. +void PictureSet::splitAdd(const SkIRect& rect) +{ + XLOG("\n--- splitAdd for rect %d, %d, %d, %d (%d x %d)", + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + rect.width(), rect.height()); + + if (!mBucketSizeX || !mBucketSizeY) { + XLOGC("PictureSet::splitAdd() called with bad bucket size: x=%d y=%d", + mBucketSizeX, mBucketSizeY); + return; + } + + // TODO: reuse gatherBucketsForArea() (change Bucket to be a class) + int x = rect.fLeft; + int y = rect.fTop; + int firstTileX = rect.fLeft / mBucketSizeX; + int firstTileY = rect.fTop / mBucketSizeY; + int lastTileX = rect.fRight / mBucketSizeX; + int lastTileY = rect.fBottom / mBucketSizeY; + + XLOG("--- firstTile(%d, %d) lastTile(%d, %d)", + firstTileX, firstTileY, + lastTileX, lastTileY); + + for (int i = firstTileX; i <= lastTileX; i++) { + for (int j = firstTileY; j <= lastTileY; j++) { + Bucket* bucket = getBucket(i, j); + if (!bucket) + continue; + + SkIRect newRect; + int deltaX = i * mBucketSizeX; + int deltaY = j * mBucketSizeY; + int left = (i == firstTileX) ? rect.fLeft - deltaX : 0; + int top = (j == firstTileY) ? rect.fTop - deltaY : 0; + int right = (i == lastTileX) ? rect.fRight % mBucketSizeX : mBucketSizeX; + int bottom = (j == lastTileY) ? rect.fBottom % mBucketSizeY : mBucketSizeY; + + newRect.set(left, top, right, bottom); + addToBucket(bucket, deltaX, deltaY, newRect); + mUpdatedBuckets.append(bucket); + } + } + + XLOG("--- splitAdd DONE\n"); +} + +#endif // FAST_PICTURESET + +// This function is used to maintain the list of Pictures. +// Pictures contain an SkPicture covering a specific area; some +// Pictures are "base" Pictures -- i.e. there is no Pictures +// underneath them. +// The idea here is to keep a balance between the number of Pictures +// we have (more Pictures slow us down) and the area of Pictures that +// need to be repainted (obviously, smaller areas are better). +// To do so, we try to not update/repaint the base pictures -- by +// construction, they usually cover a large area (the entire page). +// We only reset a base picture if the new invalidated area entirely +// contains it. +// Most of the time we thus work on smaller pictures on top of the +// base ones; We compute the total area of all pictures intersecting +// with the passed invalidated area (as they would need to be invalidated), +// and use that as the basis for the correct area we want to invalidate +// (we then can simply delete the pictures we intersect with). +// In addition, we do a couple of things to limit the total number of pictures +// we keep in the list: +// - if the total area of additional textures reach 65% of the base pictures, +// we delete the additional pictures and mark the base pictures as +// needing a full repaint +// - we limit the number of pictures to 32 -- above that, we do the same +// things (deleting additional pictures + full repaint of base pictures) +#ifdef FAST_PICTURESET +#else +void PictureSet::add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split, bool empty) +{ + bool checkForNewBases = false; + + Pictures* first = mPictures.begin(); + Pictures* last = mPictures.end(); +#ifdef DEBUG + XLOG("--- before adding the new inval ---"); + for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { + SkIRect currentArea = working->mArea.getBounds(); + XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) base: %c", + working - first, + currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, + currentArea.width(), currentArea.height(), + working->mArea.isRect() ? 'Y' : 'N', + working->mBase ? 'Y' : 'N'); + } + XLOG("----------------------------------"); +#endif + + // let's gather all the Pictures intersecting with the new invalidated + // area, collect their area and remove their picture + SkIRect totalArea = area.getBounds(); + for (Pictures* working = first; working != last; working++) { + SkIRect inval = area.getBounds(); + bool remove = false; + if (!working->mBase && working->mArea.intersects(inval)) + remove = true; + if (working->mBase) { + SkIRect baseArea = working->mArea.getBounds(); + if (area.contains(baseArea)) { + remove = true; + checkForNewBases = true; + } + } + + if (remove) { + SkIRect currentArea = working->mArea.getBounds(); + if (working->mBase) + mBaseArea -= currentArea.width() * currentArea.height(); + else + mAdditionalArea -= currentArea.width() * currentArea.height(); + + totalArea.join(currentArea); + XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) intersects with the new inval area (%d, %d, %d, %d - %d x %d) (isRect? %c, we remove it", + working - first, + currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, + currentArea.width(), currentArea.height(), + working->mArea.isRect() ? 'Y' : 'N', + inval.fLeft, inval.fTop, inval.fRight, inval.fBottom, + inval.width(), inval.height(), + area.isRect() ? 'Y' : 'N'); + working->mArea.setEmpty(); + SkSafeUnref(working->mPicture); + working->mPicture = 0; + } + } + + // Now we can add the new Picture to the list, with the correct area + // that need to be repainted + SkRegion collect; + collect.setRect(totalArea); + Pictures pictureAndBounds = {collect, 0, collect.getBounds(), + elapsed, split, false, false, empty}; + +#ifdef FAST_PICTURESET + if (mPictures.size() == 0) + checkForNewBases = true; +#endif + + mPictures.append(pictureAndBounds); + mAdditionalArea += totalArea.width() * totalArea.height(); + last = mPictures.end(); + first = mPictures.begin(); + + // Then, let's see if we have to clear up the pictures in order to keep + // the total number of pictures under our limit + bool clearUp = false; + if (last - first > MAX_ADDITIONAL_PICTURES) { + XLOG("--- too many pictures, only keeping the bases : %d", last - first); + clearUp = true; + } + + if (!clearUp) { + if (mBaseArea > 0 && mBaseArea * MAX_ADDITIONAL_AREA <= mAdditionalArea) { + XLOG("+++ the sum of the additional area is > %.2f\% of the base Area (%.2f (%.2f) <= %.2f", + MAX_ADDITIONAL_AREA * 100, mBaseArea * 0.65, mBaseArea, mAdditionalArea); + clearUp = true; + } + } + + if (clearUp) { + for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { + if (!working->mBase) + working->mArea.setEmpty(); + SkSafeUnref(working->mPicture); + working->mPicture = 0; + } + } + +#ifdef DEBUG + XLOG("--- after adding the new inval, but before collapsing ---"); + for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { + SkIRect currentArea = working->mArea.getBounds(); + XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) base: %c", + working - first, + currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, + currentArea.width(), currentArea.height(), + working->mArea.isRect() ? 'Y' : 'N', + working->mBase ? 'Y' : 'N'); + } + XLOG("----------------------------------"); + XLOG("let's collapse..."); +#endif + + // Finally, let's do a pass to collapse out empty regions + Pictures* writer = first; + for (Pictures* working = first; working != last; working++) { + if (working && working->mArea.isEmpty()) + continue; + *writer++ = *working; + } + XLOG("shiking of %d elements", writer - first); + mPictures.shrink(writer - first); + +#ifdef DEBUG + XLOG("--- after adding the new inval ---"); + for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { + SkIRect currentArea = working->mArea.getBounds(); + XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) base: %c picture %x", + working - first, + currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, + currentArea.width(), currentArea.height(), + working->mArea.isRect() ? 'Y' : 'N', + working->mBase ? 'Y' : 'N', working->mPicture); + } + XLOG("----------------------------------"); +#endif + + // Base pictures might have been removed/added -- let's recompute them + SkRegion drawn; + if (checkForNewBases) { + drawn.setEmpty(); + Pictures* last = mPictures.end(); + XLOG("checkForNewBases..."); + for (Pictures* working = mPictures.begin(); working != last; working++) { + SkRegion& area = working->mArea; + const SkIRect& a = area.getBounds(); + if (drawn.contains(working->mArea) == false) { + working->mBase = true; + float area = a.width() * a.height(); + mBaseArea += area; + mAdditionalArea -= area; + } + drawn.op(working->mArea, SkRegion::kUnion_Op); + } + } +} +#endif // FAST_PICTURESET + +void PictureSet::setDimensions(int width, int height, SkRegion* inval) +{ + // Note that setDimensions() may be called by our ctor and should behave accordingly + 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); + bool clearCache = false; + if (inval) { + if (mWidth == width && height > mHeight) { // only grew vertically + SkIRect rect; + rect.set(0, mHeight, width, height); + inval->op(rect, SkRegion::kUnion_Op); + } else { + clearCache = true; + inval->setRect(0, 0, width, height); + } + } +#ifdef FAST_PICTURESET + // First figure out how large each bucket would be if we used all of the buckets + int tmpSizeX = (width + MAX_BUCKET_COUNT_X - 1) / MAX_BUCKET_COUNT_X; + int tmpSizeY = (height + MAX_BUCKET_COUNT_Y - 1) / MAX_BUCKET_COUNT_Y; + + // Then round the bucket size up to the nearest chunk + int bucketSizeX = ((tmpSizeX - 1) / BUCKET_SIZE + 1) * BUCKET_SIZE; + int bucketSizeY = ((tmpSizeY - 1) / BUCKET_SIZE + 1) * BUCKET_SIZE; + + int bucketCountX = (width + bucketSizeX - 1) / bucketSizeX; + int bucketCountY = (height + bucketSizeY - 1) / bucketSizeY; + + // Clear the cache if the horizontal bucket count changed or the vertical + // count shrank + if (bucketCountX != mBucketCountX || bucketCountY < mBucketCountY) + clearCache = true; + + // Or if the bucket size changed + if (bucketSizeX != mBucketSizeX || bucketSizeY != mBucketSizeY) + clearCache = true; + + XLOG("old width=%d height=%d bucketSizeX=%d bucketSizeY=%d bucketCountX=%d bucketCountY=%d clearCache=%d", + mWidth, mHeight, mBucketSizeX, mBucketSizeY, mBucketCountX, mBucketCountY, clearCache); + XLOG("new width=%d height=%d bucketSizeX=%d bucketSizeY=%d bucketCountX=%d bucketCountY=%d clearCache=%d", + width, height, bucketSizeX, bucketSizeY, bucketCountX, bucketCountY, clearCache); +#endif + if (clearCache) + clear(); + mWidth = width; + mHeight = height; +#ifdef FAST_PICTURESET + mBucketSizeX = bucketSizeX; + mBucketSizeY = bucketSizeY; + mBucketCountX = bucketCountX; + mBucketCountY = bucketCountY; +#endif +} + +void PictureSet::clear() +{ + DBG_SET_LOG(""); +#ifdef FAST_PICTURESET + for (BucketMap::iterator iter = mBuckets.begin(); iter != mBuckets.end(); ++iter) { + Bucket* bucket = iter->second; + BucketPicture* first = bucket->begin(); + BucketPicture* last = bucket->end(); + for (BucketPicture* current = first; current != last; current++) { + SkSafeUnref(current->mPicture); + current->mPicture = 0; + } + bucket->clear(); + } + mBuckets.clear(); + mBucketSizeX = mBucketSizeY = BUCKET_SIZE; +#else + Pictures* last = mPictures.end(); + for (Pictures* working = mPictures.begin(); working != last; working++) { + working->mArea.setEmpty(); + SkSafeUnref(working->mPicture); + } + mPictures.clear(); +#endif // FAST_PICTURESET + mWidth = mHeight = 0; +} + +bool PictureSet::draw(SkCanvas* canvas) +{ +#ifdef FAST_PICTURESET + XLOG("PictureSet %x draw on canvas %x", this, canvas); + SkRect bounds; + if (canvas->getClipBounds(&bounds) == false) + return false; + SkIRect irect; + bounds.roundOut(&irect); + + WTF::Vector<Bucket*> list; + gatherBucketsForArea(list, irect); + + XLOG("PictureSet draw on canvas %x, we have %d buckets", canvas, list.size()); + for (unsigned int i = 0; i < list.size(); i++) { + Bucket* bucket = list[i]; + XLOG("We paint using bucket %x with %d pictures", bucket, bucket->size()); + for (unsigned int j = 0; j < bucket->size(); j++) { + BucketPicture& picture = bucket->at(j); + if (!picture.mPicture) + continue; + int saved = canvas->save(); + SkRect pathBounds; + pathBounds.set(picture.mRealArea); + XLOG("[%d/%d] draw on canvas with clip %d, %d, %d, %d - %d x %d", + j, bucket->size(), + picture.mRealArea.fLeft, + picture.mRealArea.fTop, + picture.mRealArea.fRight, + picture.mRealArea.fBottom, + picture.mRealArea.width(), + picture.mRealArea.height()); + canvas->clipRect(pathBounds); + canvas->translate(pathBounds.fLeft, pathBounds.fTop); + canvas->save(); + canvas->drawPicture(*picture.mPicture); + canvas->restoreToCount(saved); + } + } + return false; + +#else + + validate(__FUNCTION__); + Pictures* first = mPictures.begin(); + Pictures* last = mPictures.end(); + Pictures* working; + SkRect bounds; + if (canvas->getClipBounds(&bounds) == false) + return false; + SkIRect irect; + bounds.roundOut(&irect); + for (working = last; working != first; ) { + --working; + if (working->mArea.contains(irect)) { +#if PICTURE_SET_DEBUG + const SkIRect& b = working->mArea.getBounds(); + DBG_SET_LOGD("contains working->mArea={%d,%d,%d,%d}" + " irect={%d,%d,%d,%d}", b.fLeft, b.fTop, b.fRight, b.fBottom, + irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); +#endif + first = working; + break; + } + } + DBG_SET_LOGD("%p first=%d last=%d", this, first - mPictures.begin(), + last - mPictures.begin()); + uint32_t maxElapsed = 0; + for (working = first; working != last; working++) { + const SkRegion& area = working->mArea; + if (area.quickReject(irect)) { +#if PICTURE_SET_DEBUG + const SkIRect& b = area.getBounds(); + DBG_SET_LOGD("[%d] %p quickReject working->mArea={%d,%d,%d,%d}" + " irect={%d,%d,%d,%d}", working - first, working, + b.fLeft, b.fTop, b.fRight, b.fBottom, + irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); +#endif + working->mElapsed = 0; + continue; + } + int saved = canvas->save(); + SkRect pathBounds; + if (area.isComplex()) { + SkPath pathClip; + area.getBoundaryPath(&pathClip); + canvas->clipPath(pathClip); + pathBounds = pathClip.getBounds(); + } else { + pathBounds.set(area.getBounds()); + canvas->clipRect(pathBounds); + } + canvas->translate(pathBounds.fLeft, pathBounds.fTop); + canvas->save(); + uint32_t startTime = getThreadMsec(); + canvas->drawPicture(*working->mPicture); + size_t elapsed = working->mElapsed = getThreadMsec() - startTime; + working->mWroteElapsed = true; + if (maxElapsed < elapsed && (pathBounds.width() >= MIN_SPLITTABLE || + pathBounds.height() >= MIN_SPLITTABLE)) + maxElapsed = elapsed; + canvas->restoreToCount(saved); +#define DRAW_TEST_IMAGE 01 +#if DRAW_TEST_IMAGE && PICTURE_SET_DEBUG + SkColor color = 0x3f000000 | (0xffffff & (unsigned) working); + canvas->drawColor(color); + SkPaint paint; + color ^= 0x00ffffff; + paint.setColor(color); + char location[256]; + for (int x = area.getBounds().fLeft & ~0x3f; + x < area.getBounds().fRight; x += 0x40) { + for (int y = area.getBounds().fTop & ~0x3f; + y < area.getBounds().fBottom; y += 0x40) { + int len = snprintf(location, sizeof(location) - 1, "(%d,%d)", x, y); + canvas->drawText(location, len, x, y, paint); + } + } +#endif + DBG_SET_LOGD("[%d] %p working->mArea={%d,%d,%d,%d} elapsed=%d base=%s", + working - first, working, + area.getBounds().fLeft, area.getBounds().fTop, + area.getBounds().fRight, area.getBounds().fBottom, + working->mElapsed, working->mBase ? "true" : "false"); + } + // dump(__FUNCTION__); + return maxElapsed >= MAX_DRAW_TIME; +#endif // FAST_PICTURESET +} + +void PictureSet::dump(const char* label) const +{ +#if PICTURE_SET_DUMP + DBG_SET_LOGD("%p %s (%d) (w=%d,h=%d)", this, label, mPictures.size(), + mWidth, mHeight); + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + const SkIRect& bounds = working->mArea.getBounds(); + const SkIRect& unsplit = working->mUnsplit; + MeasureStream measure; + if (working->mPicture != NULL) + working->mPicture->serialize(&measure); + LOGD(" [%d]" + " mArea.bounds={%d,%d,r=%d,b=%d}" + " mPicture=%p" + " mUnsplit={%d,%d,r=%d,b=%d}" + " mElapsed=%d" + " mSplit=%s" + " mWroteElapsed=%s" + " mBase=%s" + " pict-size=%d", + working - mPictures.begin(), + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, + working->mPicture, + unsplit.fLeft, unsplit.fTop, unsplit.fRight, unsplit.fBottom, + working->mElapsed, working->mSplit ? "true" : "false", + working->mWroteElapsed ? "true" : "false", + working->mBase ? "true" : "false", + measure.mTotal); + } +#endif +} + +class IsEmptyBounder : public SkBounder { + virtual bool onIRect(const SkIRect& rect) { + return false; + } +}; + +class IsEmptyCanvas : public SkCanvas { +public: + IsEmptyCanvas(SkBounder* bounder, SkPicture* picture) : + mPicture(picture), mEmpty(true) { + setBounder(bounder); + } + + void notEmpty() { + mEmpty = false; + mPicture->abortPlayback(); + } + + virtual bool clipPath(const SkPath&, SkRegion::Op) { + // this can be expensive to actually do, and doesn't affect the + // question of emptiness, so we make it a no-op + return true; + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& , const SkPaint& ) { + if (bitmap.width() <= 1 || bitmap.height() <= 1) + return; + DBG_SET_LOGD("abort {%d,%d}", bitmap.width(), bitmap.height()); + notEmpty(); + } + + virtual void drawPaint(const SkPaint& paint) { + } + + virtual void drawPath(const SkPath& , const SkPaint& paint) { + DBG_SET_LOG("abort"); + notEmpty(); + } + + virtual void drawPoints(PointMode , size_t , const SkPoint [], + const SkPaint& paint) { + } + + virtual void drawRect(const SkRect& , const SkPaint& paint) { + // wait for visual content + if (paint.getColor() != SK_ColorWHITE) + notEmpty(); + } + + virtual void drawSprite(const SkBitmap& , int , int , + const SkPaint* paint = NULL) { + DBG_SET_LOG("abort"); + notEmpty(); + } + + virtual void drawText(const void* , size_t byteLength, SkScalar , + SkScalar , const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPosText(const void* , size_t byteLength, + const SkPoint [], const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPosTextH(const void* , size_t byteLength, + const SkScalar [], SkScalar , + const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawTextOnPath(const void* , size_t byteLength, + const SkPath& , const SkMatrix* , + const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPicture(SkPicture& picture) { + SkCanvas::drawPicture(picture); + } + + SkPicture* mPicture; + bool mEmpty; +}; + +bool PictureSet::emptyPicture(SkPicture* picture) const +{ + IsEmptyBounder isEmptyBounder; + IsEmptyCanvas checker(&isEmptyBounder, picture); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, mWidth, mHeight); + checker.setBitmapDevice(bitmap); + checker.drawPicture(*picture); + return checker.mEmpty; +} + +bool PictureSet::isEmpty() const +{ +#ifdef FAST_PICTURESET + // For now, just assume the pictureset is *not* empty + // if the hashmap contains something + for (BucketMap::const_iterator iter = mBuckets.begin(); iter != mBuckets.end(); ++iter) { + if (iter->second->size() > 0) + return false; + } + return true; +#else + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + if (!working->mEmpty) + return false; + } + return true; +#endif // FAST_PICTURESET +} + +void PictureSet::set(const PictureSet& src) +{ + DBG_SET_LOGD("start %p src=%p", this, &src); + clear(); + setDimensions(src.mWidth, src.mHeight); +#ifdef FAST_PICTURESET + XLOG("\n--- set picture ---"); + for (BucketMap::const_iterator iter = src.mBuckets.begin(); + iter != src.mBuckets.end(); ++iter) { + Bucket* sourceBucket = iter->second; + Bucket* targetBucket = getBucket(iter->first.first-1, iter->first.second-1); + BucketPicture* first = sourceBucket->begin(); + BucketPicture* last = sourceBucket->end(); + XLOG("set from bucket %x (%d, %d), %d pictures", sourceBucket, + iter->first.first, iter->first.second, sourceBucket->size()); + for (BucketPicture* current = first; current != last; current++) { + XLOG("set picture %x from bucket %x in bucket %x (%d, %d)", + current->mPicture, sourceBucket, targetBucket, + iter->first.first, iter->first.second); + SkSafeRef(current->mPicture); + BucketPicture picture = { current->mPicture, current->mArea, + current->mRealArea, current->mBase }; + targetBucket->append(picture); + } + } + XLOG("--- DONE set picture ---\n"); +#else + 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"); +#endif // FAST_PICTURESET +} + +#ifdef FAST_PICTURESET +#else + +bool PictureSet::reuseSubdivided(const SkRegion& inval) +{ + validate(__FUNCTION__); + + if (inval.isComplex()) + return false; + Pictures* working, * last = mPictures.end(); + const SkIRect& invalBounds = inval.getBounds(); + bool steal = false; + for (working = mPictures.begin(); working != last; working++) { + if (working->mSplit && invalBounds == working->mUnsplit) { + steal = true; + continue; + } + if (steal == false) + continue; + SkRegion temp = SkRegion(inval); + temp.op(working->mArea, SkRegion::kIntersect_Op); + if (temp.isEmpty() || temp == working->mArea) + continue; + return false; + } + if (steal == false) + return false; + for (working = mPictures.begin(); working != last; working++) { + if ((working->mSplit == false || invalBounds != working->mUnsplit) && + inval.contains(working->mArea) == false) + continue; + SkSafeUnref(working->mPicture); + working->mPicture = NULL; + } + return true; +} + +void PictureSet::setDrawTimes(const PictureSet& src) +{ + validate(__FUNCTION__); + if (mWidth != src.mWidth || mHeight != src.mHeight) + return; + Pictures* last = mPictures.end(); + Pictures* working = mPictures.begin(); + if (working == last) + return; + const Pictures* srcLast = src.mPictures.end(); + const Pictures* srcWorking = src.mPictures.begin(); + for (; srcWorking != srcLast; srcWorking++) { + if (srcWorking->mWroteElapsed == false) + continue; + while ((srcWorking->mArea != working->mArea || + srcWorking->mPicture != working->mPicture)) { + if (++working == last) + return; + } + DBG_SET_LOGD("%p [%d] [%d] {%d,%d,r=%d,b=%d} working->mElapsed=%d <- %d", + this, working - mPictures.begin(), srcWorking - src.mPictures.begin(), + working->mArea.getBounds().fLeft, working->mArea.getBounds().fTop, + working->mArea.getBounds().fRight, working->mArea.getBounds().fBottom, + working->mElapsed, srcWorking->mElapsed); + working->mElapsed = srcWorking->mElapsed; + } +} + +void PictureSet::setPicture(size_t i, SkPicture* p) +{ + SkSafeUnref(mPictures[i].mPicture); + mPictures[i].mPicture = p; + mPictures[i].mEmpty = emptyPicture(p); +} + +void PictureSet::split(PictureSet* out) const +{ + dump(__FUNCTION__); + DBG_SET_LOGD("%p", this); + SkIRect totalBounds; + out->mWidth = mWidth; + out->mHeight = mHeight; + totalBounds.set(0, 0, mWidth, mHeight); + SkRegion* total = new SkRegion(totalBounds); + const Pictures* last = mPictures.end(); + const Pictures* working; + uint32_t balance = 0; + int multiUnsplitFastPictures = 0; // > 1 has more than 1 + for (working = mPictures.begin(); working != last; working++) { + if (working->mElapsed >= MAX_DRAW_TIME || working->mSplit) + continue; + if (++multiUnsplitFastPictures > 1) + break; + } + for (working = mPictures.begin(); working != last; working++) { + uint32_t elapsed = working->mElapsed; + if (elapsed < MAX_DRAW_TIME) { + bool split = working->mSplit; + DBG_SET_LOGD("elapsed=%d working=%p total->getBounds()=" + "{%d,%d,r=%d,b=%d} split=%s", elapsed, working, + total->getBounds().fLeft, total->getBounds().fTop, + total->getBounds().fRight, total->getBounds().fBottom, + split ? "true" : "false"); + if (multiUnsplitFastPictures <= 1 || split) { + total->op(working->mArea, SkRegion::kDifference_Op); + out->add(working->mArea, working->mPicture, elapsed, split, + working->mEmpty); + } else if (balance < elapsed) + balance = elapsed; + continue; + } + total->op(working->mArea, SkRegion::kDifference_Op); + const SkIRect& bounds = working->mArea.getBounds(); + int width = bounds.width(); + int height = bounds.height(); + int across = 1; + int down = 1; + while (height >= MIN_SPLITTABLE || width >= MIN_SPLITTABLE) { + if (height >= width) { + height >>= 1; + down <<= 1; + } else { + width >>= 1; + across <<= 1 ; + } + if ((elapsed >>= 1) < MAX_DRAW_TIME) + break; + } + width = bounds.width(); + height = bounds.height(); + int top = bounds.fTop; + for (int indexY = 0; indexY < down; ) { + int bottom = bounds.fTop + height * ++indexY / down; + int left = bounds.fLeft; + for (int indexX = 0; indexX < across; ) { + int right = bounds.fLeft + width * ++indexX / across; + SkIRect cBounds; + cBounds.set(left, top, right, bottom); + out->add(SkRegion(cBounds), (across | down) != 1 ? NULL : + working->mPicture, elapsed, true, + (across | down) != 1 ? false : working->mEmpty); + left = right; + } + top = bottom; + } + } + DBG_SET_LOGD("%p w=%d h=%d total->isEmpty()=%s multiUnsplitFastPictures=%d", + this, mWidth, mHeight, total->isEmpty() ? "true" : "false", + multiUnsplitFastPictures); + if (!total->isEmpty() && multiUnsplitFastPictures > 1) + out->add(*total, NULL, balance, false, false); + delete total; + validate(__FUNCTION__); + out->dump("split-out"); +} + +#endif // FAST_PICTURESET + +bool PictureSet::validate(const char* funct) const +{ +#ifdef FAST_PICTURESET + return true; +#else + 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; +#endif // FAST_PICTURESET +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/PictureSet.h b/Source/WebKit/android/jni/PictureSet.h new file mode 100644 index 0000000..fe47361 --- /dev/null +++ b/Source/WebKit/android/jni/PictureSet.h @@ -0,0 +1,145 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PictureSet_h +#define PictureSet_h + +#define PICTURE_SET_DUMP 0 +#define PICTURE_SET_DEBUG 0 +#define PICTURE_SET_VALIDATE 0 + +#if PICTURE_SET_DEBUG +#define DBG_SET_LOG(message) LOGD("%s %s", __FUNCTION__, message) +#define DBG_SET_LOGD(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#define DEBUG_SET_UI_LOGD(...) LOGD(__VA_ARGS__) +#else +#define DBG_SET_LOG(message) ((void)0) +#define DBG_SET_LOGD(format, ...) ((void)0) +#define DEBUG_SET_UI_LOGD(...) ((void)0) +#endif + +#include "jni.h" +#include "SkRegion.h" +#include <wtf/Vector.h> +#include <wtf/HashMap.h> + +// #define FAST_PICTURESET // use a hierarchy of pictures + +class SkCanvas; +class SkPicture; +class SkIRect; + +namespace android { + +#ifdef FAST_PICTURESET + struct BucketPicture { + SkPicture* mPicture; + SkIRect mArea; + SkIRect mRealArea; + bool mBase; + }; + + typedef std::pair<int, int> BucketPosition; + typedef WTF::Vector<BucketPicture> Bucket; + typedef WTF::HashMap<BucketPosition , Bucket* > BucketMap; +#endif + + class PictureSet { + public: + PictureSet(); + PictureSet(const PictureSet& src) { set(src); } + PictureSet(SkPicture* picture); + virtual ~PictureSet(); + +#ifdef FAST_PICTURESET + void displayBucket(Bucket* bucket); + void displayBuckets(); + WTF::Vector<Bucket*>* bucketsToUpdate() { return &mUpdatedBuckets; } + Bucket* getBucket(int x, int y); + void addToBucket(Bucket* bucket, int dx, int dy, SkIRect& rect); + void gatherBucketsForArea(WTF::Vector<Bucket*>& list, const SkIRect& rect); + void splitAdd(const SkIRect& rect); +#endif + + void add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split); + + // Update mWidth/mHeight, and adds any additional inval region + void setDimensions(int width, int height, SkRegion* inval = 0); + 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 + void set(const PictureSet& ); + +#ifdef FAST_PICTURESET +#else + void add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split, bool empty); + const SkIRect& bounds(size_t i) const { + return mPictures[i].mArea.getBounds(); } + bool reuseSubdivided(const SkRegion& ); + void setPicture(size_t i, SkPicture* p); + void setDrawTimes(const PictureSet& ); + size_t size() const { return mPictures.size(); } + void split(PictureSet* result) const; + bool upToDate(size_t i) const { return mPictures[i].mPicture != NULL; } +#endif + 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 + +#ifdef FAST_PICTURESET + BucketMap mBuckets; + WTF::Vector<Bucket*> mUpdatedBuckets; + int mBucketSizeX; + int mBucketSizeY; + int mBucketCountX; + int mBucketCountY; +#else + struct Pictures { + SkRegion mArea; + SkPicture* mPicture; + SkIRect mUnsplit; + uint32_t mElapsed; + bool mSplit : 8; + bool mWroteElapsed : 8; + bool mBase : 8; // true if nothing is drawn underneath this + bool mEmpty : 8; // true if the picture only draws white + }; + void add(const Pictures* temp); + WTF::Vector<Pictures> mPictures; +#endif + float mBaseArea; + float mAdditionalArea; + int mHeight; + int mWidth; + }; +} + +#endif diff --git a/Source/WebKit/android/jni/ViewStateSerializer.cpp b/Source/WebKit/android/jni/ViewStateSerializer.cpp index 02ddca6..6b473f5 100644 --- a/Source/WebKit/android/jni/ViewStateSerializer.cpp +++ b/Source/WebKit/android/jni/ViewStateSerializer.cpp @@ -23,23 +23,15 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#define LOG_TAG "ViewStateSerializer" -#define LOG_NDEBUG 1 - #include "config.h" #include "BaseLayerAndroid.h" #include "CreateJavaOutputStreamAdaptor.h" -#include "FixedPositioning.h" #include "ImagesManager.h" -#include "IFrameContentLayerAndroid.h" -#include "IFrameLayerAndroid.h" #include "Layer.h" #include "LayerAndroid.h" -#include "LayerContent.h" -#include "PictureLayerContent.h" +#include "PictureSet.h" #include "ScrollableLayerAndroid.h" -#include "SkFlattenable.h" #include "SkPicture.h" #include "TilesManager.h" @@ -47,13 +39,24 @@ #include <JNIHelp.h> #include <jni.h> +#ifdef DEBUG + +#undef XLOG +#define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "ViewStateSerializer", __VA_ARGS__) + +#else + +#undef XLOG +#define XLOG(...) + +#endif // DEBUG + namespace android { enum LayerTypes { LTNone = 0, LTLayerAndroid = 1, LTScrollableLayerAndroid = 2, - LTFixedLayerAndroid = 3 }; static bool nativeSerializeViewState(JNIEnv* env, jobject, jint jbaseLayer, @@ -69,14 +72,16 @@ static bool nativeSerializeViewState(JNIEnv* env, jobject, jint jbaseLayer, #else stream->write32(0); #endif + SkPicture picture; + PictureSet* content = baseLayer->content(); + baseLayer->drawCanvas(picture.beginRecording(content->width(), content->height(), + SkPicture::kUsePathBoundsForClip_RecordingFlag)); + picture.endRecording(); if (!stream) return false; - if (baseLayer->content()) - baseLayer->content()->serialize(stream); - else - return false; + picture.serialize(stream); int childCount = baseLayer->countChildren(); - ALOGV("BaseLayer has %d child(ren)", childCount); + XLOG("BaseLayer has %d child(ren)", childCount); stream->write32(childCount); for (int i = 0; i < childCount; i++) { LayerAndroid* layer = static_cast<LayerAndroid*>(baseLayer->getChild(i)); @@ -86,28 +91,23 @@ static bool nativeSerializeViewState(JNIEnv* env, jobject, jint jbaseLayer, return true; } -static BaseLayerAndroid* nativeDeserializeViewState(JNIEnv* env, jobject, jint version, - jobject jstream, jbyteArray jstorage) +static BaseLayerAndroid* nativeDeserializeViewState(JNIEnv* env, jobject, jobject jstream, + jbyteArray jstorage) { SkStream* stream = CreateJavaInputStreamAdaptor(env, jstream, jstorage); if (!stream) return 0; + BaseLayerAndroid* layer = new BaseLayerAndroid(); Color color = stream->readU32(); - SkPicture* picture = new SkPicture(stream); - PictureLayerContent* content = new PictureLayerContent(picture); - - BaseLayerAndroid* layer = new BaseLayerAndroid(content); +#if USE(ACCELERATED_COMPOSITING) layer->setBackgroundColor(color); - - SkRegion dirtyRegion; - dirtyRegion.setRect(0, 0, content->width(), content->height()); - layer->markAsDirty(dirtyRegion); - - SkSafeUnref(content); +#endif + SkPicture* picture = new SkPicture(stream); + layer->setContent(picture); SkSafeUnref(picture); int childCount = stream->readS32(); for (int i = 0; i < childCount; i++) { - LayerAndroid* childLayer = deserializeLayer(version, stream); + LayerAndroid* childLayer = deserializeLayer(stream); if (childLayer) layer->addChild(childLayer); } @@ -242,20 +242,20 @@ void readTransformationMatrix(SkStream *stream, TransformationMatrix& matrix) void serializeLayer(LayerAndroid* layer, SkWStream* stream) { if (!layer) { - ALOGV("NULL layer!"); + XLOG("NULL layer!"); stream->write8(LTNone); return; } if (layer->isMedia() || layer->isVideo()) { - ALOGV("Layer isn't supported for serialization: isMedia: %s, isVideo: %s", + XLOG("Layer isn't supported for serialization: isMedia: %s, isVideo: %s", layer->isMedia() ? "true" : "false", layer->isVideo() ? "true" : "false"); stream->write8(LTNone); return; } - LayerTypes type = LTLayerAndroid; - if (layer->contentIsScrollable()) - type = LTScrollableLayerAndroid; + LayerTypes type = layer->contentIsScrollable() + ? LTScrollableLayerAndroid + : LTLayerAndroid; stream->write8(type); // Start with Layer fields @@ -272,43 +272,20 @@ void serializeLayer(LayerAndroid* layer, SkWStream* stream) // Next up, LayerAndroid fields stream->writeBool(layer->m_haveClip); - stream->writeBool(layer->isPositionFixed()); + stream->writeBool(layer->m_isFixed); stream->writeBool(layer->m_backgroundColorSet); - stream->writeBool(layer->isIFrame()); - - // With the current LayerAndroid hierarchy, LayerAndroid doesn't have - // those fields anymore. Let's keep the current serialization format for - // now and output blank fields... not great, but probably better than - // dealing with multiple versions. - if (layer->fixedPosition()) { - FixedPositioning* fixedPosition = layer->fixedPosition(); - writeSkLength(stream, fixedPosition->m_fixedLeft); - writeSkLength(stream, fixedPosition->m_fixedTop); - writeSkLength(stream, fixedPosition->m_fixedRight); - writeSkLength(stream, fixedPosition->m_fixedBottom); - writeSkLength(stream, fixedPosition->m_fixedMarginLeft); - writeSkLength(stream, fixedPosition->m_fixedMarginTop); - writeSkLength(stream, fixedPosition->m_fixedMarginRight); - writeSkLength(stream, fixedPosition->m_fixedMarginBottom); - writeSkRect(stream, fixedPosition->m_fixedRect); - stream->write32(fixedPosition->m_renderLayerPos.x()); - stream->write32(fixedPosition->m_renderLayerPos.y()); - } else { - SkLength length; - SkRect rect; - writeSkLength(stream, length); // fixedLeft - writeSkLength(stream, length); // fixedTop - writeSkLength(stream, length); // fixedRight - writeSkLength(stream, length); // fixedBottom - writeSkLength(stream, length); // fixedMarginLeft - writeSkLength(stream, length); // fixedMarginTop - writeSkLength(stream, length); // fixedMarginRight - writeSkLength(stream, length); // fixedMarginBottom - writeSkRect(stream, rect); // fixedRect - stream->write32(0); // renderLayerPos.x() - stream->write32(0); // renderLayerPos.y() - } - + stream->writeBool(layer->m_isIframe); + writeSkLength(stream, layer->m_fixedLeft); + writeSkLength(stream, layer->m_fixedTop); + writeSkLength(stream, layer->m_fixedRight); + writeSkLength(stream, layer->m_fixedBottom); + writeSkLength(stream, layer->m_fixedMarginLeft); + writeSkLength(stream, layer->m_fixedMarginTop); + writeSkLength(stream, layer->m_fixedMarginRight); + writeSkLength(stream, layer->m_fixedMarginBottom); + writeSkRect(stream, layer->m_fixedRect); + stream->write32(layer->m_renderLayerPos.x()); + stream->write32(layer->m_renderLayerPos.y()); stream->writeBool(layer->m_backfaceVisibility); stream->writeBool(layer->m_visible); stream->write32(layer->m_backgroundColor); @@ -328,10 +305,10 @@ void serializeLayer(LayerAndroid* layer, SkWStream* stream) stream->write32(buffer.size()); buffer.writeToStream(stream); } - bool hasRecordingPicture = layer->m_content != 0 && !layer->m_content->isEmpty(); + bool hasRecordingPicture = layer->m_recordingPicture != 0; stream->writeBool(hasRecordingPicture); if (hasRecordingPicture) - layer->m_content->serialize(stream); + layer->m_recordingPicture->serialize(stream); // TODO: support m_animations (maybe?) stream->write32(0); // placeholder for m_animations.size(); writeTransformationMatrix(stream, layer->m_transform); @@ -350,7 +327,7 @@ void serializeLayer(LayerAndroid* layer, SkWStream* stream) serializeLayer(layer->getChild(i), stream); } -LayerAndroid* deserializeLayer(int version, SkStream* stream) +LayerAndroid* deserializeLayer(SkStream* stream) { int type = stream->readU8(); if (type == LTNone) @@ -362,7 +339,7 @@ LayerAndroid* deserializeLayer(int version, SkStream* stream) else if (type == LTScrollableLayerAndroid) layer = new ScrollableLayerAndroid((RenderLayer*) 0); else { - ALOGV("Unexpected layer type: %d, aborting!", type); + XLOG("Unexpected layer type: %d, aborting!", type); return 0; } @@ -377,55 +354,20 @@ LayerAndroid* deserializeLayer(int version, SkStream* stream) // LayerAndroid fields layer->m_haveClip = stream->readBool(); - - // Keep the legacy serialization/deserialization format... - bool isFixed = stream->readBool(); - + layer->m_isFixed = stream->readBool(); layer->m_backgroundColorSet = stream->readBool(); - - bool isIframe = stream->readBool(); - // If we are a scrollable layer android, we are an iframe content - if (isIframe && type == LTScrollableLayerAndroid) { - IFrameContentLayerAndroid* iframeContent = new IFrameContentLayerAndroid(*layer); - layer->unref(); - layer = iframeContent; - } else if (isIframe) { // otherwise we are just the iframe (we use it to compute offset) - IFrameLayerAndroid* iframe = new IFrameLayerAndroid(*layer); - layer->unref(); - layer = iframe; - } - - if (isFixed) { - FixedPositioning* fixedPosition = new FixedPositioning(layer); - - fixedPosition->m_fixedLeft = readSkLength(stream); - fixedPosition->m_fixedTop = readSkLength(stream); - fixedPosition->m_fixedRight = readSkLength(stream); - fixedPosition->m_fixedBottom = readSkLength(stream); - fixedPosition->m_fixedMarginLeft = readSkLength(stream); - fixedPosition->m_fixedMarginTop = readSkLength(stream); - fixedPosition->m_fixedMarginRight = readSkLength(stream); - fixedPosition->m_fixedMarginBottom = readSkLength(stream); - fixedPosition->m_fixedRect = readSkRect(stream); - fixedPosition->m_renderLayerPos.setX(stream->readS32()); - fixedPosition->m_renderLayerPos.setY(stream->readS32()); - - layer->setFixedPosition(fixedPosition); - } else { - // Not a fixed element, bypass the values in the stream - readSkLength(stream); // fixedLeft - readSkLength(stream); // fixedTop - readSkLength(stream); // fixedRight - readSkLength(stream); // fixedBottom - readSkLength(stream); // fixedMarginLeft - readSkLength(stream); // fixedMarginTop - readSkLength(stream); // fixedMarginRight - readSkLength(stream); // fixedMarginBottom - readSkRect(stream); // fixedRect - stream->readS32(); // renderLayerPos.x() - stream->readS32(); // renderLayerPos.y() - } - + layer->m_isIframe = stream->readBool(); + layer->m_fixedLeft = readSkLength(stream); + layer->m_fixedTop = readSkLength(stream); + layer->m_fixedRight = readSkLength(stream); + layer->m_fixedBottom = readSkLength(stream); + layer->m_fixedMarginLeft = readSkLength(stream); + layer->m_fixedMarginTop = readSkLength(stream); + layer->m_fixedMarginRight = readSkLength(stream); + layer->m_fixedMarginBottom = readSkLength(stream); + layer->m_fixedRect = readSkRect(stream); + layer->m_renderLayerPos.setX(stream->readS32()); + layer->m_renderLayerPos.setY(stream->readS32()); layer->m_backfaceVisibility = stream->readBool(); layer->m_visible = stream->readBool(); layer->m_backgroundColor = stream->readU32(); @@ -446,11 +388,7 @@ LayerAndroid* deserializeLayer(int version, SkStream* stream) } bool hasRecordingPicture = stream->readBool(); if (hasRecordingPicture) { - SkPicture* picture = new SkPicture(stream); - PictureLayerContent* content = new PictureLayerContent(picture); - layer->setContent(content); - SkSafeUnref(content); - SkSafeUnref(picture); + layer->m_recordingPicture = new SkPicture(stream); } int animationCount = stream->readU32(); // TODO: Support (maybe?) readTransformationMatrix(stream, layer->m_transform); @@ -466,11 +404,12 @@ LayerAndroid* deserializeLayer(int version, SkStream* stream) } int childCount = stream->readU32(); for (int i = 0; i < childCount; i++) { - LayerAndroid *childLayer = deserializeLayer(version, stream); + LayerAndroid *childLayer = deserializeLayer(stream); if (childLayer) layer->addChild(childLayer); } - ALOGV("Created layer with id %d", layer->uniqueId()); + layer->needsRepaint(); + XLOG("Created layer with id %d", layer->uniqueId()); return layer; } @@ -480,7 +419,7 @@ LayerAndroid* deserializeLayer(int version, SkStream* stream) static JNINativeMethod gSerializerMethods[] = { { "nativeSerializeViewState", "(ILjava/io/OutputStream;[B)Z", (void*) nativeSerializeViewState }, - { "nativeDeserializeViewState", "(ILjava/io/InputStream;[B)I", + { "nativeDeserializeViewState", "(Ljava/io/InputStream;[B)I", (void*) nativeDeserializeViewState }, }; diff --git a/Source/WebKit/android/jni/WebCoreFrameBridge.cpp b/Source/WebKit/android/jni/WebCoreFrameBridge.cpp index 4ce3d8e..d53ddb6 100644 --- a/Source/WebKit/android/jni/WebCoreFrameBridge.cpp +++ b/Source/WebKit/android/jni/WebCoreFrameBridge.cpp @@ -60,8 +60,6 @@ #include "IconDatabase.h" #include "Image.h" #include "InspectorClientAndroid.h" -#include "JavaNPObjectV8.h" -#include "JavaInstanceJobjectV8.h" #include "KURL.h" #include "Page.h" #include "PageCache.h" @@ -83,6 +81,7 @@ #include "WebArchiveAndroid.h" #include "WebCache.h" #include "WebCoreJni.h" +#include "WebCoreResourceLoader.h" #include "WebHistory.h" #include "WebIconDatabase.h" #include "WebFrameView.h" @@ -100,13 +99,30 @@ #include <android_runtime/android_util_AssetManager.h> #include <openssl/x509.h> #include <utils/misc.h> -#include <androidfw/AssetManager.h> +#include <utils/AssetManager.h> #include <wtf/CurrentTime.h> #include <wtf/Platform.h> #include <wtf/text/AtomicString.h> #include <wtf/text/CString.h> #include <wtf/text/StringBuilder.h> +#if USE(JSC) +#include "GCController.h" +#include "JSDOMWindow.h" +#include "JavaInstanceJSC.h" +#include <runtime_object.h> +#include <runtime_root.h> +#include <runtime/JSLock.h> +#elif USE(V8) +#include "JavaNPObjectV8.h" +#include "JavaInstanceJobjectV8.h" +#include "V8Counters.h" +#endif // USE(JSC) + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + #if ENABLE(WEB_AUTOFILL) #include "autofill/WebAutofill.h" #endif @@ -228,6 +244,8 @@ WebFrame::WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* mJavaFrame = new JavaBrowserFrame; mJavaFrame->mObj = env->NewWeakGlobalRef(obj); mJavaFrame->mHistoryList = env->NewWeakGlobalRef(historyList); + mJavaFrame->mStartLoadingResource = env->GetMethodID(clazz, "startLoadingResource", + "(ILjava/lang/String;Ljava/lang/String;Ljava/util/HashMap;[BJIZZZLjava/lang/String;Ljava/lang/String;)Landroid/webkit/LoadListener;"); mJavaFrame->mMaybeSavePassword = env->GetMethodID(clazz, "maybeSavePassword", "([BLjava/lang/String;Ljava/lang/String;)V"); mJavaFrame->mShouldInterceptRequest = @@ -283,39 +301,41 @@ WebFrame::WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); env->DeleteLocalRef(clazz); - ALOG_ASSERT(mJavaFrame->mMaybeSavePassword, "Could not find method maybeSavePassword"); - ALOG_ASSERT(mJavaFrame->mShouldInterceptRequest, "Could not find method shouldInterceptRequest"); - ALOG_ASSERT(mJavaFrame->mLoadStarted, "Could not find method loadStarted"); - ALOG_ASSERT(mJavaFrame->mTransitionToCommitted, "Could not find method transitionToCommitted"); - ALOG_ASSERT(mJavaFrame->mLoadFinished, "Could not find method loadFinished"); - ALOG_ASSERT(mJavaFrame->mReportError, "Could not find method reportError"); - ALOG_ASSERT(mJavaFrame->mSetTitle, "Could not find method setTitle"); - ALOG_ASSERT(mJavaFrame->mWindowObjectCleared, "Could not find method windowObjectCleared"); - ALOG_ASSERT(mJavaFrame->mSetProgress, "Could not find method setProgress"); - ALOG_ASSERT(mJavaFrame->mDidReceiveIcon, "Could not find method didReceiveIcon"); - ALOG_ASSERT(mJavaFrame->mDidReceiveTouchIconUrl, "Could not find method didReceiveTouchIconUrl"); - ALOG_ASSERT(mJavaFrame->mUpdateVisitedHistory, "Could not find method updateVisitedHistory"); - ALOG_ASSERT(mJavaFrame->mHandleUrl, "Could not find method handleUrl"); - ALOG_ASSERT(mJavaFrame->mCreateWindow, "Could not find method createWindow"); - ALOG_ASSERT(mJavaFrame->mCloseWindow, "Could not find method closeWindow"); - ALOG_ASSERT(mJavaFrame->mDecidePolicyForFormResubmission, "Could not find method decidePolicyForFormResubmission"); - ALOG_ASSERT(mJavaFrame->mRequestFocus, "Could not find method requestFocus"); - ALOG_ASSERT(mJavaFrame->mGetRawResFilename, "Could not find method getRawResFilename"); - ALOG_ASSERT(mJavaFrame->mDensity, "Could not find method density"); - ALOG_ASSERT(mJavaFrame->mGetFileSize, "Could not find method getFileSize"); - ALOG_ASSERT(mJavaFrame->mGetFile, "Could not find method getFile"); - ALOG_ASSERT(mJavaFrame->mDidReceiveAuthenticationChallenge, "Could not find method didReceiveAuthenticationChallenge"); - ALOG_ASSERT(mJavaFrame->mReportSslCertError, "Could not find method reportSslCertError"); - ALOG_ASSERT(mJavaFrame->mRequestClientCert, "Could not find method requestClientCert"); - ALOG_ASSERT(mJavaFrame->mDownloadStart, "Could not find method downloadStart"); - ALOG_ASSERT(mJavaFrame->mDidReceiveData, "Could not find method didReceiveData"); - ALOG_ASSERT(mJavaFrame->mDidFinishLoading, "Could not find method didFinishLoading"); - ALOG_ASSERT(mJavaFrame->mSetCertificate, "Could not find method setCertificate"); - ALOG_ASSERT(mJavaFrame->mShouldSaveFormData, "Could not find method shouldSaveFormData"); - ALOG_ASSERT(mJavaFrame->mSaveFormData, "Could not find method saveFormData"); - ALOG_ASSERT(mJavaFrame->mAutoLogin, "Could not find method autoLogin"); + LOG_ASSERT(mJavaFrame->mStartLoadingResource, "Could not find method startLoadingResource"); + LOG_ASSERT(mJavaFrame->mMaybeSavePassword, "Could not find method maybeSavePassword"); + LOG_ASSERT(mJavaFrame->mShouldInterceptRequest, "Could not find method shouldInterceptRequest"); + LOG_ASSERT(mJavaFrame->mLoadStarted, "Could not find method loadStarted"); + LOG_ASSERT(mJavaFrame->mTransitionToCommitted, "Could not find method transitionToCommitted"); + LOG_ASSERT(mJavaFrame->mLoadFinished, "Could not find method loadFinished"); + LOG_ASSERT(mJavaFrame->mReportError, "Could not find method reportError"); + LOG_ASSERT(mJavaFrame->mSetTitle, "Could not find method setTitle"); + LOG_ASSERT(mJavaFrame->mWindowObjectCleared, "Could not find method windowObjectCleared"); + LOG_ASSERT(mJavaFrame->mSetProgress, "Could not find method setProgress"); + LOG_ASSERT(mJavaFrame->mDidReceiveIcon, "Could not find method didReceiveIcon"); + LOG_ASSERT(mJavaFrame->mDidReceiveTouchIconUrl, "Could not find method didReceiveTouchIconUrl"); + LOG_ASSERT(mJavaFrame->mUpdateVisitedHistory, "Could not find method updateVisitedHistory"); + LOG_ASSERT(mJavaFrame->mHandleUrl, "Could not find method handleUrl"); + LOG_ASSERT(mJavaFrame->mCreateWindow, "Could not find method createWindow"); + LOG_ASSERT(mJavaFrame->mCloseWindow, "Could not find method closeWindow"); + LOG_ASSERT(mJavaFrame->mDecidePolicyForFormResubmission, "Could not find method decidePolicyForFormResubmission"); + LOG_ASSERT(mJavaFrame->mRequestFocus, "Could not find method requestFocus"); + LOG_ASSERT(mJavaFrame->mGetRawResFilename, "Could not find method getRawResFilename"); + LOG_ASSERT(mJavaFrame->mDensity, "Could not find method density"); + LOG_ASSERT(mJavaFrame->mGetFileSize, "Could not find method getFileSize"); + LOG_ASSERT(mJavaFrame->mGetFile, "Could not find method getFile"); + LOG_ASSERT(mJavaFrame->mDidReceiveAuthenticationChallenge, "Could not find method didReceiveAuthenticationChallenge"); + LOG_ASSERT(mJavaFrame->mReportSslCertError, "Could not find method reportSslCertError"); + LOG_ASSERT(mJavaFrame->mRequestClientCert, "Could not find method requestClientCert"); + LOG_ASSERT(mJavaFrame->mDownloadStart, "Could not find method downloadStart"); + LOG_ASSERT(mJavaFrame->mDidReceiveData, "Could not find method didReceiveData"); + LOG_ASSERT(mJavaFrame->mDidFinishLoading, "Could not find method didFinishLoading"); + LOG_ASSERT(mJavaFrame->mSetCertificate, "Could not find method setCertificate"); + LOG_ASSERT(mJavaFrame->mShouldSaveFormData, "Could not find method shouldSaveFormData"); + LOG_ASSERT(mJavaFrame->mSaveFormData, "Could not find method saveFormData"); + LOG_ASSERT(mJavaFrame->mAutoLogin, "Could not find method autoLogin"); mUserAgent = WTF::String(); + mUserInitiatedAction = false; mBlockNetworkLoads = false; m_renderSkins = 0; } @@ -342,14 +362,14 @@ WebFrame* WebFrame::getWebFrame(const WebCore::Frame* frame) static jobject createJavaMapFromHTTPHeaders(JNIEnv* env, const WebCore::HTTPHeaderMap& map) { jclass mapClass = env->FindClass("java/util/HashMap"); - ALOG_ASSERT(mapClass, "Could not find HashMap class!"); + LOG_ASSERT(mapClass, "Could not find HashMap class!"); jmethodID init = env->GetMethodID(mapClass, "<init>", "(I)V"); - ALOG_ASSERT(init, "Could not find constructor for HashMap"); + LOG_ASSERT(init, "Could not find constructor for HashMap"); jobject hashMap = env->NewObject(mapClass, init, map.size()); - ALOG_ASSERT(hashMap, "Could not create a new HashMap"); + LOG_ASSERT(hashMap, "Could not create a new HashMap"); jmethodID put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - ALOG_ASSERT(put, "Could not find put method on HashMap"); + 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) { @@ -396,10 +416,104 @@ private: int m_size; }; +PassRefPtr<WebCore::ResourceLoaderAndroid> +WebFrame::startLoadingResource(WebCore::ResourceHandle* loader, + const WebCore::ResourceRequest& request, + bool mainResource, + bool synchronous) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: startLoadingResource(%p, %s)", + loader, request.url().string().latin1().data()); + + JNIEnv* env = getJNIEnv(); + AutoJObject javaFrame = mJavaFrame->frame(env); + if (!javaFrame.get()) + return 0; + + WTF::String method = request.httpMethod(); + WebCore::HTTPHeaderMap headers = request.httpHeaderFields(); + + WTF::String urlStr = request.url().string(); + int colon = urlStr.find(':'); + bool allLower = true; + for (int index = 0; index < colon; index++) { + UChar ch = urlStr[index]; + if (!WTF::isASCIIAlpha(ch)) + break; + allLower &= WTF::isASCIILower(ch); + if (index == colon - 1 && !allLower) { + urlStr = urlStr.substring(0, colon).lower() + + urlStr.substring(colon); + } + } + LOGV("%s lower=%s", __FUNCTION__, urlStr.latin1().data()); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jstring jMethodStr = NULL; + if (!method.isEmpty()) + jMethodStr = wtfStringToJstring(env, method); + WebCore::FormData* formdata = request.httpBody(); + jbyteArray jPostDataStr = getPostData(request); + jobject jHeaderMap = createJavaMapFromHTTPHeaders(env, headers); + + // Convert the WebCore Cache Policy to a WebView Cache Policy. + int cacheMode = 0; // WebSettings.LOAD_NORMAL + switch (request.cachePolicy()) { + case WebCore::ReloadIgnoringCacheData: + cacheMode = 2; // WebSettings.LOAD_NO_CACHE + break; + case WebCore::ReturnCacheDataDontLoad: + cacheMode = 3; // WebSettings.LOAD_CACHE_ONLY + break; + case WebCore::ReturnCacheDataElseLoad: + cacheMode = 1; // WebSettings.LOAD_CACHE_ELSE_NETWORK + break; + case WebCore::UseProtocolCachePolicy: + default: + break; + } + + LOGV("::WebCore:: startLoadingResource %s with cacheMode %d", urlStr.ascii().data(), cacheMode); + + ResourceHandleInternal* loaderInternal = loader->getInternal(); + jstring jUsernameString = loaderInternal->m_user.isEmpty() ? + NULL : wtfStringToJstring(env, loaderInternal->m_user); + jstring jPasswordString = loaderInternal->m_pass.isEmpty() ? + NULL : wtfStringToJstring(env, loaderInternal->m_pass); + + bool isUserGesture = UserGestureIndicator::processingUserGesture(); + jobject jLoadListener = + env->CallObjectMethod(javaFrame.get(), mJavaFrame->mStartLoadingResource, + (int)loader, jUrlStr, jMethodStr, jHeaderMap, + jPostDataStr, formdata ? formdata->identifier(): 0, + cacheMode, mainResource, isUserGesture, + synchronous, jUsernameString, jPasswordString); + + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jMethodStr); + env->DeleteLocalRef(jPostDataStr); + env->DeleteLocalRef(jHeaderMap); + env->DeleteLocalRef(jUsernameString); + env->DeleteLocalRef(jPasswordString); + if (checkException(env)) + return 0; + + PassRefPtr<WebCore::ResourceLoaderAndroid> h; + if (jLoadListener) + h = WebCoreResourceLoader::create(env, jLoadListener); + env->DeleteLocalRef(jLoadListener); + return h; +} + UrlInterceptResponse* WebFrame::shouldInterceptRequest(const WTF::String& url) { - ALOGV("::WebCore:: shouldInterceptRequest(%s)", url.latin1().data()); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: shouldInterceptRequest(%s)", url.latin1().data()); JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); @@ -420,7 +534,10 @@ void WebFrame::reportError(int errorCode, const WTF::String& description, const WTF::String& failingUrl) { - ALOGV("::WebCore:: reportError(%d, %s)", errorCode, description.ascii().data()); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: reportError(%d, %s)", errorCode, description.ascii().data()); JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -436,6 +553,7 @@ WebFrame::reportError(int errorCode, const WTF::String& description, WTF::String WebFrame::convertIDNToUnicode(const WebCore::KURL& url) { WTF::String converted = url.string(); +#if USE(CHROME_NETWORK_STACK) const WTF::String host = url.host(); if (host.find("xn--") == notFound) // no punycode IDN found. return converted; @@ -448,12 +566,16 @@ WebFrame::convertIDNToUnicode(const WebCore::KURL& url) { newUrl.setHost(convertedHost); converted = newUrl.string(); } +#endif return converted; } void WebFrame::loadStarted(WebCore::Frame* frame) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -467,7 +589,7 @@ WebFrame::loadStarted(WebCore::Frame* frame) const WebCore::KURL& url = documentLoader->url(); if (url.isEmpty()) return; - ALOGV("::WebCore:: loadStarted %s", url.string().ascii().data()); + LOGV("::WebCore:: loadStarted %s", url.string().ascii().data()); bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); WebCore::FrameLoadType loadType = frame->loader()->loadType(); @@ -487,7 +609,7 @@ WebFrame::loadStarted(WebCore::Frame* frame) WebCore::Image* icon = WebCore::iconDatabase().synchronousIconForPageURL(urlString, WebCore::IntSize(16, 16)); if (icon) favicon = webcoreImageToJavaBitmap(env, icon); - ALOGV("favicons", "Starting load with icon %p for %s", icon, url.string().utf8().data()); + LOGV("favicons", "Starting load with icon %p for %s", icon, url.string().utf8().data()); } jstring urlStr = wtfStringToJstring(env, urlString); @@ -511,6 +633,9 @@ WebFrame::loadStarted(WebCore::Frame* frame) void WebFrame::transitionToCommitted(WebCore::Frame* frame) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -525,6 +650,9 @@ WebFrame::transitionToCommitted(WebCore::Frame* frame) void WebFrame::didFinishLoad(WebCore::Frame* frame) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -539,7 +667,7 @@ WebFrame::didFinishLoad(WebCore::Frame* frame) const WebCore::KURL& url = documentLoader->url(); if (url.isEmpty()) return; - ALOGV("::WebCore:: didFinishLoad %s", url.string().ascii().data()); + LOGV("::WebCore:: didFinishLoad %s", url.string().ascii().data()); bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); WebCore::FrameLoadType loadType = loader->loadType(); @@ -553,7 +681,10 @@ WebFrame::didFinishLoad(WebCore::Frame* frame) void WebFrame::addHistoryItem(WebCore::HistoryItem* item) { - ALOGV("::WebCore:: addHistoryItem"); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: addHistoryItem"); JNIEnv* env = getJNIEnv(); WebHistory::AddItem(mJavaFrame->history(env), item); } @@ -561,7 +692,10 @@ WebFrame::addHistoryItem(WebCore::HistoryItem* item) void WebFrame::removeHistoryItem(int index) { - ALOGV("::WebCore:: removeHistoryItem at %d", index); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: removeHistoryItem at %d", index); JNIEnv* env = getJNIEnv(); WebHistory::RemoveItem(mJavaFrame->history(env), index); } @@ -569,7 +703,10 @@ WebFrame::removeHistoryItem(int index) void WebFrame::updateHistoryIndex(int newIndex) { - ALOGV("::WebCore:: updateHistoryIndex to %d", newIndex); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: updateHistoryIndex to %d", newIndex); JNIEnv* env = getJNIEnv(); WebHistory::UpdateHistoryIndex(mJavaFrame->history(env), newIndex); } @@ -577,8 +714,11 @@ WebFrame::updateHistoryIndex(int newIndex) void WebFrame::setTitle(const WTF::String& title) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif #ifndef NDEBUG - ALOGV("setTitle(%s)", title.ascii().data()); + LOGV("setTitle(%s)", title.ascii().data()); #endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); @@ -595,7 +735,10 @@ WebFrame::setTitle(const WTF::String& title) void WebFrame::windowObjectCleared(WebCore::Frame* frame) { - ALOGV("::WebCore:: windowObjectCleared"); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: windowObjectCleared"); JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -608,6 +751,9 @@ WebFrame::windowObjectCleared(WebCore::Frame* frame) void WebFrame::setProgress(float newProgress) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -627,7 +773,10 @@ WebFrame::userAgentForURL(const WebCore::KURL* url) void WebFrame::didReceiveIcon(WebCore::Image* icon) { - ALOG_ASSERT(icon, "DidReceiveIcon called without an image!"); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOG_ASSERT(icon, "DidReceiveIcon called without an image!"); JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -645,6 +794,9 @@ WebFrame::didReceiveIcon(WebCore::Image* icon) void WebFrame::didReceiveTouchIconURL(const WTF::String& url, bool precomposed) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -660,6 +812,9 @@ WebFrame::didReceiveTouchIconURL(const WTF::String& url, bool precomposed) void WebFrame::updateVisitedHistory(const WebCore::KURL& url, bool reload) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -676,6 +831,9 @@ WebFrame::updateVisitedHistory(const WebCore::KURL& url, bool reload) bool WebFrame::canHandleRequest(const WebCore::ResourceRequest& request) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -716,6 +874,9 @@ WebFrame::shouldSaveFormData() WebCore::Frame* WebFrame::createWindow(bool dialog, bool userGesture) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -729,6 +890,9 @@ WebFrame::createWindow(bool dialog, bool userGesture) void WebFrame::requestFocus() const { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -740,6 +904,9 @@ WebFrame::requestFocus() const void WebFrame::closeWindow(WebViewCore* webViewCore) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif assert(webViewCore); JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); @@ -758,6 +925,9 @@ struct PolicyFunctionWrapper { void WebFrame::decidePolicyForFormResubmission(WebCore::FramePolicyFunction func) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -791,9 +961,13 @@ WebFrame::density() const return dpi; } +#if USE(CHROME_NETWORK_STACK) void WebFrame::didReceiveAuthenticationChallenge(WebUrlLoaderClient* client, const std::string& host, const std::string& realm, bool useCachedCredentials, bool suppressDialog) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -811,6 +985,9 @@ WebFrame::didReceiveAuthenticationChallenge(WebUrlLoaderClient* client, const st void WebFrame::reportSslCertError(WebUrlLoaderClient* client, int error, const std::string& cert, const std::string& url) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -831,6 +1008,9 @@ WebFrame::reportSslCertError(WebUrlLoaderClient* client, int error, const std::s void WebFrame::requestClientCert(WebUrlLoaderClient* client, const std::string& hostAndPort) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); int jHandle = reinterpret_cast<int>(client); @@ -844,6 +1024,9 @@ WebFrame::requestClientCert(WebUrlLoaderClient* client, const std::string& hostA void WebFrame::downloadStart(const std::string& url, const std::string& userAgent, const std::string& contentDisposition, const std::string& mimetype, long long contentLength) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -864,6 +1047,9 @@ WebFrame::downloadStart(const std::string& url, const std::string& userAgent, co void WebFrame::didReceiveData(const char* data, int size) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -878,6 +1064,9 @@ WebFrame::didReceiveData(const char* data, int size) { void WebFrame::didFinishLoading() { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -889,6 +1078,9 @@ WebFrame::didFinishLoading() { void WebFrame::setCertificate(const std::string& cert) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -902,9 +1094,13 @@ void WebFrame::setCertificate(const std::string& cert) checkException(env); } +#endif // USE(CHROME_NETWORK_STACK) void WebFrame::autoLogin(const std::string& loginHeader) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimerCoutner::JavaCallbackTimeCounter); +#endif JNIEnv* env = getJNIEnv(); AutoJObject javaFrame = mJavaFrame->frame(env); if (!javaFrame.get()) @@ -978,7 +1174,7 @@ void WebFrame::maybeSavePassword(WebCore::Frame* frame, const WebCore::ResourceR bool WebFrame::getUsernamePasswordFromDom(WebCore::Frame* frame, WTF::String& username, WTF::String& password) { bool found = false; - WTF::RefPtr<WebCore::HTMLCollection> form = frame->document()->forms(); + WTF::PassRefPtr<WebCore::HTMLCollection> form = frame->document()->forms(); WebCore::Node* node = form->firstItem(); while (node && !found && !node->namespaceURI().isNull() && !node->namespaceURI().isEmpty()) { @@ -1075,10 +1271,13 @@ jbyteArray WebFrame::getPostData(const WebCore::ResourceRequest& request) 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); - ALOG_ASSERT(pFrame, "nativeCallPolicyFunction must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeCallPolicyFunction must take a valid frame pointer!"); PolicyFunctionWrapper* pFunc = (PolicyFunctionWrapper*)func; - ALOG_ASSERT(pFunc, "nativeCallPolicyFunction must take a valid function pointer!"); + LOG_ASSERT(pFunc, "nativeCallPolicyFunction must take a valid function pointer!"); // If we are resending the form then we should reset the multiple submission protection. if (decision == WebCore::PolicyUse) @@ -1091,9 +1290,17 @@ static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAss { ScriptController::initializeThreading(); +#if USE(CHROME_NETWORK_STACK) // needs to be called before any other chromium code initChromium(); +#endif +#ifdef ANDROID_INSTRUMENT +#if USE(V8) + V8Counters::initCounters(); +#endif + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif // Create a new page ChromeClientAndroid* chromeC = new ChromeClientAndroid; EditorClientAndroid* editorC = new EditorClientAndroid; @@ -1155,7 +1362,7 @@ static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAss WebCore::SecurityOrigin::setLocalLoadPolicy( WebCore::SecurityOrigin::AllowLocalLoadsForLocalAndSubstituteData); - ALOGV("::WebCore:: createFrame %p", frame); + LOGV("::WebCore:: createFrame %p", frame); // Set the mNativeFrame field in Frame SET_NATIVE_FRAME(env, obj, (int)frame); @@ -1163,7 +1370,7 @@ static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAss String directory = webFrame->getRawResourceFilename( WebCore::PlatformBridge::DrawableDir); if (directory.isEmpty()) - ALOGE("Can't find the drawable directory"); + LOGE("Can't find the drawable directory"); else { // Initialize our skinning classes webFrame->setRenderSkins(new WebCore::RenderSkinAndroid(directory)); @@ -1177,10 +1384,13 @@ static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAss static void DestroyFrame(JNIEnv* env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "nativeDestroyFrame must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeDestroyFrame must take a valid frame pointer!"); - ALOGV("::WebCore:: deleting frame %p", pFrame); + LOGV("::WebCore:: deleting frame %p", pFrame); WebCore::FrameView* view = pFrame->view(); view->ref(); @@ -1205,8 +1415,11 @@ static void DestroyFrame(JNIEnv* env, jobject obj) static void LoadUrl(JNIEnv *env, jobject obj, jstring url, jobject headers) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!"); WTF::String webcoreUrl = jstringToWtfString(env, url); WebCore::KURL kurl(WebCore::KURL(), webcoreUrl); @@ -1250,14 +1463,17 @@ static void LoadUrl(JNIEnv *env, jobject obj, jstring url, jobject headers) env->DeleteLocalRef(set); env->DeleteLocalRef(mapClass); } - ALOGV("LoadUrl %s", kurl.string().latin1().data()); + LOGV("LoadUrl %s", kurl.string().latin1().data()); pFrame->loader()->load(request, false); } static void PostUrl(JNIEnv *env, jobject obj, jstring url, jbyteArray postData) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "nativePostUrl must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativePostUrl must take a valid frame pointer!"); WebCore::KURL kurl(WebCore::KURL(), jstringToWtfString(env, url)); WebCore::ResourceRequest request(kurl); @@ -1275,7 +1491,7 @@ static void PostUrl(JNIEnv *env, jobject obj, jstring url, jbyteArray postData) env->ReleaseByteArrayElements(postData, bytes, 0); } - ALOGV("PostUrl %s", kurl.string().latin1().data()); + LOGV("PostUrl %s", kurl.string().latin1().data()); WebCore::FrameLoadRequest frameRequest(pFrame->document()->securityOrigin(), request); pFrame->loader()->loadFrameRequest(frameRequest, false, false, 0, 0, WebCore::SendReferrer); } @@ -1283,19 +1499,22 @@ static void PostUrl(JNIEnv *env, jobject obj, jstring url, jbyteArray postData) 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); - ALOG_ASSERT(pFrame, "nativeLoadData must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeLoadData must take a valid frame pointer!"); // Setup the resource request WebCore::ResourceRequest request(jstringToWtfString(env, baseUrl)); // Setup the substituteData - WTF::CString cData = jstringToWtfString(env, data).utf8(); - const char* dataStr = cData.data(); - WTF::RefPtr<WebCore::SharedBuffer> sharedBuffer = + const char* dataStr = env->GetStringUTFChars(data, NULL); + WTF::PassRefPtr<WebCore::SharedBuffer> sharedBuffer = WebCore::SharedBuffer::create(); - ALOG_ASSERT(dataStr, "nativeLoadData has a null data string."); - sharedBuffer->append(dataStr, strlen(dataStr)); // copy dataStr + LOG_ASSERT(dataStr, "nativeLoadData has a null data string."); + sharedBuffer->append(dataStr, strlen(dataStr)); + env->ReleaseStringUTFChars(data, dataStr); WebCore::SubstituteData substituteData(sharedBuffer, jstringToWtfString(env, mimeType), jstringToWtfString(env, encoding), @@ -1307,9 +1526,12 @@ static void LoadData(JNIEnv *env, jobject obj, jstring baseUrl, jstring data, static void StopLoading(JNIEnv *env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "nativeStopLoading must take a valid frame pointer!"); - ALOGV("::WebCore:: stopLoading %p", pFrame); + 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(); @@ -1357,7 +1579,7 @@ static jstring SaveWebArchive(JNIEnv *env, jobject obj, jstring basename, jboole { #if ENABLE(WEB_ARCHIVE) WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "nativeSaveWebArchive must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeSaveWebArchive must take a valid frame pointer!"); String mimeType = pFrame->loader()->documentLoader()->mainResource()->mimeType(); if ((mimeType != "text/html") && (mimeType != "application/xhtml+xml")) return NULL; @@ -1375,7 +1597,7 @@ static jstring SaveWebArchive(JNIEnv *env, jobject obj, jstring basename, jboole } if (filename.isNull() || filename.isEmpty()) { - ALOGD("saveWebArchive: Failed to select a filename to save."); + LOGD("saveWebArchive: Failed to select a filename to save."); releaseCharactersForJStringInEnv(env, basename, basenameNative); return NULL; } @@ -1383,7 +1605,7 @@ static jstring SaveWebArchive(JNIEnv *env, jobject obj, jstring basename, jboole const int noCompression = 0; xmlTextWriterPtr writer = xmlNewTextWriterFilename(filename.utf8().data(), noCompression); if (writer == NULL) { - ALOGD("saveWebArchive: Failed to initialize xml writer."); + LOGD("saveWebArchive: Failed to initialize xml writer."); releaseCharactersForJStringInEnv(env, basename, basenameNative); return NULL; } @@ -1404,8 +1626,11 @@ static jstring SaveWebArchive(JNIEnv *env, jobject obj, jstring basename, jboole static jstring ExternalRepresentation(JNIEnv *env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "android_webcore_nativeExternalRepresentation must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "android_webcore_nativeExternalRepresentation must take a valid frame pointer!"); // Request external representation of the render tree WTF::String renderDump = WebCore::externalRepresentation(pFrame); @@ -1436,8 +1661,11 @@ static StringBuilder FrameAsText(WebCore::Frame *pFrame, jboolean dumpChildFrame static jstring DocumentAsText(JNIEnv *env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); WTF::String renderDump = FrameAsText(pFrame, false /* dumpChildFrames */).toString(); return wtfStringToJstring(env, renderDump); @@ -1445,8 +1673,11 @@ static jstring DocumentAsText(JNIEnv *env, jobject obj) static jstring ChildFramesAsText(JNIEnv *env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); StringBuilder renderDumpBuilder; for (unsigned i = 0; i < pFrame->tree()->childCount(); ++i) { @@ -1458,8 +1689,11 @@ static jstring ChildFramesAsText(JNIEnv *env, jobject obj) 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); - ALOG_ASSERT(pFrame, "nativeReload must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeReload must take a valid frame pointer!"); WebCore::FrameLoader* loader = pFrame->loader(); if (allowStale) { @@ -1475,8 +1709,11 @@ static void Reload(JNIEnv *env, jobject obj, jboolean allowStale) 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); - ALOG_ASSERT(pFrame, "nativeGoBackOrForward must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeGoBackOrForward must take a valid frame pointer!"); if (pos == 1) pFrame->page()->goForward(); @@ -1488,8 +1725,11 @@ static void GoBackOrForward(JNIEnv *env, jobject obj, jint 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); - ALOG_ASSERT(pFrame, "stringByEvaluatingJavaScriptFromString must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "stringByEvaluatingJavaScriptFromString must take a valid frame pointer!"); WebCore::ScriptValue value = pFrame->script()->executeScript(jstringToWtfString(env, script), true); @@ -1503,16 +1743,32 @@ static jobject StringByEvaluatingJavaScriptFromString(JNIEnv *env, jobject obj, // Wrap the JavaInstance used when binding custom javascript interfaces. Use a // weak reference so that the gc can collect the WebView. Override virtualBegin // and virtualEnd and swap the weak reference for the real object. +#if USE(JSC) +class WeakJavaInstance : public JavaInstance { +#elif USE(V8) class WeakJavaInstance : public JavaInstanceJobject { +#endif public: +#if USE(JSC) + static PassRefPtr<WeakJavaInstance> create(jobject obj, PassRefPtr<RootObject> root) + { + return adoptRef(new WeakJavaInstance(obj, root)); + } +#elif USE(V8) static PassRefPtr<WeakJavaInstance> create(jobject obj) { return adoptRef(new WeakJavaInstance(obj)); } +#endif private: +#if USE(JSC) + WeakJavaInstance(jobject instance, PassRefPtr<RootObject> rootObject) + : JavaInstance(instance, rootObject) +#elif USE(V8) WeakJavaInstance(jobject instance) : JavaInstanceJobject(instance) +#endif , m_beginEndDepth(0) { JNIEnv* env = getJNIEnv(); @@ -1524,7 +1780,7 @@ private: } ~WeakJavaInstance() { - ALOG_ASSERT(!m_beginEndDepth, "Unbalanced calls to WeakJavaInstance::begin() / end()"); + LOG_ASSERT(!m_beginEndDepth, "Unbalanced calls to WeakJavaInstance::begin() / end()"); JNIEnv* env = getJNIEnv(); // The JavaInstance destructor attempts to delete the global ref stored // in m_instance. Since we replaced it in our constructor with a weak @@ -1562,7 +1818,11 @@ private: } private: +#if USE(JSC) + typedef JavaInstance INHERITED; +#elif USE(V8) typedef JavaInstanceJobject INHERITED; +#endif jweak m_weakRef; // The current depth of nested calls to virtualBegin and virtualEnd. int m_beginEndDepth; @@ -1571,17 +1831,42 @@ private: static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer, jobject javascriptObj, jstring interfaceName) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = 0; if (nativeFramePointer == 0) pFrame = GET_NATIVE_FRAME(env, obj); else pFrame = (WebCore::Frame*)nativeFramePointer; - ALOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!"); JavaVM* vm; env->GetJavaVM(&vm); - ALOGV("::WebCore:: addJSInterface: %p", pFrame); - + LOGV("::WebCore:: addJSInterface: %p", pFrame); + +#if USE(JSC) + // Copied from qwebframe.cpp + JSC::JSLock lock(JSC::SilenceAssertionsOnly); + WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame, mainThreadNormalWorld()); + if (window) { + RootObject *root = pFrame->script()->bindingRootObject(); + setJavaVM(vm); + // Add the binding to JS environment + JSC::ExecState* exec = window->globalExec(); + JSC::JSObject* addedObject = WeakJavaInstance::create(javascriptObj, + root)->createRuntimeObject(exec); + const jchar* s = env->GetStringChars(interfaceName, NULL); + if (s) { + // Add the binding name to the window's table of child objects. + JSC::PutPropertySlot slot; + window->put(exec, JSC::Identifier(exec, (const UChar *)s, + env->GetStringLength(interfaceName)), addedObject, slot); + env->ReleaseStringChars(interfaceName, s); + checkException(env); + } + } +#elif USE(V8) if (pFrame) { RefPtr<JavaInstance> addedObject = WeakJavaInstance::create(javascriptObj); const char* name = getCharactersFromJStringInEnv(env, interfaceName); @@ -1602,6 +1887,24 @@ static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePoi NPN_ReleaseObject(npObject); releaseCharactersForJString(interfaceName, name); } +#endif + +} + +static void SetCacheDisabled(JNIEnv *env, jobject obj, jboolean disabled) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::memoryCache()->setDisabled(disabled); +} + +static jboolean CacheDisabled(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + return WebCore::memoryCache()->disabled(); } static void ClearWebCoreCache() @@ -1623,32 +1926,59 @@ static void ClearWebCoreCache() static void ClearWebViewCache() { +#if USE(CHROME_NETWORK_STACK) WebCache::get(false /*privateBrowsing*/)->clear(); +#else + // The Android network stack provides a WebView cache in CacheManager.java. + // Clearing this is handled entirely Java-side. +#endif } static void ClearCache(JNIEnv *env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#if USE(JSC) + JSC::JSLock lock(false); + JSC::Heap::Statistics jsHeapStatistics = WebCore::JSDOMWindow::commonJSGlobalData()->heap.statistics(); + LOGD("About to gc and JavaScript heap size is %d and has %d bytes free", + jsHeapStatistics.size, jsHeapStatistics.free); +#endif // USE(JSC) + LOGD("About to clear cache and current cache has %d bytes live and %d bytes dead", + memoryCache()->getLiveSize(), memoryCache()->getDeadSize()); +#endif // ANDROID_INSTRUMENT ClearWebCoreCache(); ClearWebViewCache(); +#if USE(JSC) + // force JavaScript to GC when clear cache + WebCore::gcController().garbageCollectSoon(); +#elif USE(V8) WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); pFrame->script()->lowMemoryNotification(); +#endif // USE(JSC) } static jboolean DocumentHasImages(JNIEnv *env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "DocumentHasImages must take a valid frame pointer!"); + 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); - ALOG_ASSERT(pFrame, "HasPasswordField must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "HasPasswordField must take a valid frame pointer!"); bool found = false; - WTF::RefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); WebCore::Node* node = form->firstItem(); // Null/Empty namespace means that node is not created in HTMLFormElement // class, but just normal Element class. @@ -1671,8 +2001,11 @@ static jboolean HasPasswordField(JNIEnv *env, jobject obj) static jobjectArray GetUsernamePassword(JNIEnv *env, jobject obj) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOG_ASSERT(pFrame, "GetUsernamePassword must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "GetUsernamePassword must take a valid frame pointer!"); jobjectArray strArray = NULL; WTF::String username; WTF::String password; @@ -1689,13 +2022,16 @@ static jobjectArray GetUsernamePassword(JNIEnv *env, jobject obj) 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); - ALOG_ASSERT(pFrame, "SetUsernamePassword must take a valid frame pointer!"); + LOG_ASSERT(pFrame, "SetUsernamePassword must take a valid frame pointer!"); WebCore::HTMLInputElement* usernameEle = NULL; WebCore::HTMLInputElement* passwordEle = NULL; bool found = false; - WTF::RefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); WebCore::Node* node = form->firstItem(); while (node && !found && !node->namespaceURI().isNull() && !node->namespaceURI().isEmpty()) { @@ -1735,14 +2071,14 @@ WebFrame::saveFormData(HTMLFormElement* form) if (form->autoComplete()) { JNIEnv* env = getJNIEnv(); jclass mapClass = env->FindClass("java/util/HashMap"); - ALOG_ASSERT(mapClass, "Could not find HashMap class!"); + LOG_ASSERT(mapClass, "Could not find HashMap class!"); jmethodID init = env->GetMethodID(mapClass, "<init>", "(I)V"); - ALOG_ASSERT(init, "Could not find constructor for HashMap"); + LOG_ASSERT(init, "Could not find constructor for HashMap"); jobject hashMap = env->NewObject(mapClass, init, 1); - ALOG_ASSERT(hashMap, "Could not create a new HashMap"); + LOG_ASSERT(hashMap, "Could not create a new HashMap"); jmethodID put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - ALOG_ASSERT(put, "Could not find put method on HashMap"); + LOG_ASSERT(put, "Could not find put method on HashMap"); WTF::Vector<WebCore::FormAssociatedElement*> elements = form->associatedElements(); size_t size = elements.size(); for (size_t i = 0; i < size; i++) { @@ -1757,7 +2093,7 @@ WebFrame::saveFormData(HTMLFormElement* form) const WTF::AtomicString& name = input->name(); jstring key = wtfStringToJstring(env, name); jstring val = wtfStringToJstring(env, value); - ALOG_ASSERT(key && val, "name or value not set"); + LOG_ASSERT(key && val, "name or value not set"); env->CallObjectMethod(hashMap, put, key, val); env->DeleteLocalRef(key); env->DeleteLocalRef(val); @@ -1773,8 +2109,11 @@ WebFrame::saveFormData(HTMLFormElement* form) static void OrientationChanged(JNIEnv *env, jobject obj, int orientation) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); - ALOGV("Sending orientation: %d", orientation); + LOGV("Sending orientation: %d", orientation); pFrame->sendOrientationChangeEvent(orientation); } @@ -1787,9 +2126,8 @@ static jboolean GetShouldStartScrolledRight(JNIEnv *env, jobject obj, if (document) { RenderStyle* style = document->renderer()->style(); WritingMode writingMode = style->writingMode(); - ALOG_ASSERT(writingMode != WebCore::BottomToTopWritingMode, - "BottomToTopWritingMode isn't possible in any " - "language and cannot be specified in w3c writing-mode."); + LOG_ASSERT(writingMode != WebCore::BottomToTopWritingMode, + "BottomToTopWritingMode isn't supported"); if (writingMode == WebCore::RightToLeftWritingMode) startScrolledRight = true; // vertical-rl pages start scrolled right else if (writingMode == WebCore::TopToBottomWritingMode) @@ -1798,6 +2136,8 @@ static jboolean GetShouldStartScrolledRight(JNIEnv *env, jobject obj, return startScrolledRight; } +#if USE(CHROME_NETWORK_STACK) + static void AuthenticationProceed(JNIEnv *env, jobject obj, int handle, jstring jUsername, jstring jPassword) { WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); @@ -1824,12 +2164,39 @@ static void SslCertErrorCancel(JNIEnv *env, jobject obj, int handle, int cert_er client->cancelSslCertError(cert_error); } -static scoped_refptr<net::X509Certificate> getX509Cert(JNIEnv *env, jobjectArray chain) +static void SslClientCert(JNIEnv *env, jobject obj, int handle, jbyteArray pkey, jobjectArray chain) { + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + if (pkey == NULL || chain == NULL) { + client->sslClientCert(NULL, NULL); + return; + } + + // Based on Android's NativeCrypto_SSL_use_PrivateKey + ScopedByteArrayRO pkeyBytes(env, pkey); + if (pkeyBytes.get() == NULL) { + client->sslClientCert(NULL, NULL); + return; + } + + base::ScopedOpenSSL<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free> pkcs8; + const unsigned char* pkeyChars = reinterpret_cast<const unsigned char*>(pkeyBytes.get()); + pkcs8.reset(d2i_PKCS8_PRIV_KEY_INFO(NULL, &pkeyChars, pkeyBytes.size())); + if (!pkcs8.get()) { + client->sslClientCert(NULL, NULL); + return; + } + base::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> privateKey(EVP_PKCS82PKEY(pkcs8.get())); + if (!privateKey.get()) { + client->sslClientCert(NULL, NULL); + return; + } + // Based on Android's NativeCrypto_SSL_use_certificate int length = env->GetArrayLength(chain); if (length == 0) { - return NULL; + client->sslClientCert(NULL, NULL); + return; } base::ScopedOpenSSL<X509, X509_free> first; @@ -1838,17 +2205,20 @@ static scoped_refptr<net::X509Certificate> getX509Cert(JNIEnv *env, jobjectArray ScopedLocalRef<jbyteArray> cert(env, reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(chain, i))); if (cert.get() == NULL) { - return NULL; + client->sslClientCert(NULL, NULL); + return; } ScopedByteArrayRO certBytes(env, cert.get()); if (certBytes.get() == NULL) { - return NULL; + client->sslClientCert(NULL, NULL); + return; } const char* data = reinterpret_cast<const char*>(certBytes.get()); int length = certBytes.size(); X509* x509 = net::X509Certificate::CreateOSCertHandleFromBytes(data, length); if (x509 == NULL) { - return NULL; + client->sslClientCert(NULL, NULL); + return; } if (i == 0) { first.reset(x509); @@ -1861,39 +2231,10 @@ static scoped_refptr<net::X509Certificate> getX509Cert(JNIEnv *env, jobjectArray for (size_t i = 0; i < rest.size(); i++) { certChain[i] = rest[i]->get(); } - return net::X509Certificate::CreateFromHandle(first.get(), - net::X509Certificate::SOURCE_FROM_NETWORK, - certChain); -} - -static void SslClientCertPKCS8(JNIEnv *env, jobject obj, int handle, jbyteArray pkey, jobjectArray chain) -{ - WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); - if (pkey == NULL || chain == NULL) { - client->sslClientCert(NULL, NULL); - return; - } - - // Based on Android's NativeCrypto_SSL_use_PrivateKey - ScopedByteArrayRO pkeyBytes(env, pkey); - if (pkeyBytes.get() == NULL) { - client->sslClientCert(NULL, NULL); - return; - } - - base::ScopedOpenSSL<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free> pkcs8; - const unsigned char* pkeyChars = reinterpret_cast<const unsigned char*>(pkeyBytes.get()); - pkcs8.reset(d2i_PKCS8_PRIV_KEY_INFO(NULL, &pkeyChars, pkeyBytes.size())); - if (!pkcs8.get()) { - client->sslClientCert(NULL, NULL); - return; - } - base::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> privateKey(EVP_PKCS82PKEY(pkcs8.get())); - if (!privateKey.get()) { - client->sslClientCert(NULL, NULL); - return; - } - scoped_refptr<net::X509Certificate> certificate = getX509Cert(env, chain); + net::X509Certificate* certificate + = net::X509Certificate::CreateFromHandle(first.get(), + net::X509Certificate::SOURCE_FROM_NETWORK, + certChain); if (certificate == NULL) { client->sslClientCert(NULL, NULL); return; @@ -1901,22 +2242,33 @@ static void SslClientCertPKCS8(JNIEnv *env, jobject obj, int handle, jbyteArray client->sslClientCert(privateKey.release(), certificate); } -static void SslClientCertCtx(JNIEnv *env, jobject obj, int handle, jint ctx, jobjectArray chain) +#else + +static void AuthenticationProceed(JNIEnv *env, jobject obj, int handle, jstring jUsername, jstring jPassword) { - WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); - EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(static_cast<uintptr_t>(ctx)); - if (pkey == NULL || chain == NULL) { - client->sslClientCert(NULL, NULL); - return; - } - scoped_refptr<net::X509Certificate> certificate = getX509Cert(env, chain); - if (certificate == NULL) { - client->sslClientCert(NULL, NULL); - return; - } - CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY); - client->sslClientCert(pkey, certificate); + LOGW("Chromium authentication API called, but libchromium is not available"); +} + +static void AuthenticationCancel(JNIEnv *env, jobject obj, int handle) +{ + LOGW("Chromium authentication API called, but libchromium is not available"); +} + +static void SslCertErrorProceed(JNIEnv *env, jobject obj, int handle) +{ + LOGW("Chromium SSL API called, but libchromium is not available"); +} + +static void SslCertErrorCancel(JNIEnv *env, jobject obj, int handle, int cert_error) +{ + LOGW("Chromium SSL API called, but libchromium is not available"); +} + +static void SslClientCert(JNIEnv *env, jobject obj, int handle, jbyteArray privateKey, jobjectArray chain) +{ + LOGW("Chromium SSL API called, but libchromium is not available"); } +#endif // USE(CHROME_NETWORK_STACK) // ---------------------------------------------------------------------------- @@ -1956,6 +2308,10 @@ static JNINativeMethod gBrowserFrameNativeMethods[] = { { "stringByEvaluatingJavaScriptFromString", "(Ljava/lang/String;)Ljava/lang/String;", (void*) StringByEvaluatingJavaScriptFromString }, + { "setCacheDisabled", "(Z)V", + (void*) SetCacheDisabled }, + { "cacheDisabled", "()Z", + (void*) CacheDisabled }, { "clearCache", "()V", (void*) ClearCache }, { "documentHasImages", "()Z", @@ -1976,10 +2332,8 @@ static JNINativeMethod gBrowserFrameNativeMethods[] = { (void*) SslCertErrorProceed }, { "nativeSslCertErrorCancel", "(II)V", (void*) SslCertErrorCancel }, - { "nativeSslClientCert", "(II[[B)V", - (void*) SslClientCertCtx }, { "nativeSslClientCert", "(I[B[[B)V", - (void*) SslClientCertPKCS8 }, + (void*) SslClientCert }, { "nativeGetShouldStartScrolledRight", "(I)Z", (void*) GetShouldStartScrolledRight }, }; @@ -1987,9 +2341,9 @@ static JNINativeMethod gBrowserFrameNativeMethods[] = { int registerWebFrame(JNIEnv* env) { jclass clazz = env->FindClass("android/webkit/BrowserFrame"); - ALOG_ASSERT(clazz, "Cannot find BrowserFrame"); + LOG_ASSERT(clazz, "Cannot find BrowserFrame"); gFrameField = env->GetFieldID(clazz, "mNativeFrame", "I"); - ALOG_ASSERT(gFrameField, "Cannot find mNativeFrame on BrowserFrame"); + LOG_ASSERT(gFrameField, "Cannot find mNativeFrame on BrowserFrame"); env->DeleteLocalRef(clazz); return jniRegisterNativeMethods(env, "android/webkit/BrowserFrame", diff --git a/Source/WebKit/android/jni/WebCoreFrameBridge.h b/Source/WebKit/android/jni/WebCoreFrameBridge.h index 30c1d83..eaee63c 100644 --- a/Source/WebKit/android/jni/WebCoreFrameBridge.h +++ b/Source/WebKit/android/jni/WebCoreFrameBridge.h @@ -64,6 +64,10 @@ class WebFrame : public WebCoreRefObject { // helper function static WebFrame* getWebFrame(const WebCore::Frame* frame); + virtual PassRefPtr<WebCore::ResourceLoaderAndroid> startLoadingResource(WebCore::ResourceHandle*, + const WebCore::ResourceRequest& request, bool mainResource, + bool synchronous); + UrlInterceptResponse* shouldInterceptRequest(const WTF::String& url); void reportError(int errorCode, const WTF::String& description, @@ -113,6 +117,7 @@ class WebFrame : public WebCoreRefObject { float density() const; +#if USE(CHROME_NETWORK_STACK) void didReceiveAuthenticationChallenge(WebUrlLoaderClient*, const std::string& host, const std::string& realm, bool useCachedCredentials, bool suppressDialog); void reportSslCertError(WebUrlLoaderClient* client, int cert_error, const std::string& cert, const std::string& url); void requestClientCert(WebUrlLoaderClient* client, const std::string& hostAndPort); @@ -120,6 +125,7 @@ class WebFrame : public WebCoreRefObject { void didReceiveData(const char* data, int size); void didFinishLoading(); void setCertificate(const std::string& cert); +#endif void maybeSavePassword(WebCore::Frame* frame, const WebCore::ResourceRequest& request); @@ -128,6 +134,13 @@ class WebFrame : public WebCoreRefObject { // application. void autoLogin(const std::string& loginHeader); + /** + * When the user initiates a click, we set mUserInitiatedAction to true. + * If a load happens due to this click, then we ask the application if it wants + * to override the load. Otherwise, we attempt to load the resource internally. + */ + void setUserInitiatedAction(bool userInitiatedAction) { mUserInitiatedAction = userInitiatedAction; } + WebCore::Page* page() const { return mPage; } // Currently used only by the chrome net stack. A similar field is used by @@ -156,6 +169,7 @@ class WebFrame : public WebCoreRefObject { WebCore::Page* mPage; WTF::String mUserAgent; bool mBlockNetworkLoads; + bool mUserInitiatedAction; WebCore::RenderSkinAndroid* m_renderSkins; }; diff --git a/Source/WebKit/android/jni/WebCoreJni.cpp b/Source/WebKit/android/jni/WebCoreJni.cpp index 72ded59..2a07999 100644 --- a/Source/WebKit/android/jni/WebCoreJni.cpp +++ b/Source/WebKit/android/jni/WebCoreJni.cpp @@ -26,9 +26,7 @@ #define LOG_TAG "webcoreglue" #include "config.h" -#include "IntRect.h" #include "WebCoreJni.h" -#include "wtf/Vector.h" #include "NotImplemented.h" #include <JNIUtility.h> @@ -40,7 +38,7 @@ namespace android { AutoJObject getRealObject(JNIEnv* env, jobject obj) { jobject real = env->NewLocalRef(obj); - ALOG_ASSERT(real, "The real object has been deleted!"); + LOG_ASSERT(real, "The real object has been deleted!"); return AutoJObject(env, real); } @@ -52,7 +50,7 @@ bool checkException(JNIEnv* env) { if (env->ExceptionCheck() != 0) { - ALOGE("*** Uncaught exception returned from Java call!\n"); + LOGE("*** Uncaught exception returned from Java call!\n"); env->ExceptionDescribe(); return true; } @@ -79,6 +77,8 @@ jstring wtfStringToJstring(JNIEnv* env, const WTF::String& str, bool validOnZero return length || validOnZeroLength ? env->NewString(str.characters(), length) : 0; } + +#if USE(CHROME_NETWORK_STACK) string16 jstringToString16(JNIEnv* env, jstring jstr) { if (!jstr || !env) @@ -112,37 +112,6 @@ jstring stdStringToJstring(JNIEnv* env, const std::string& str, bool validOnZero return !str.empty() || validOnZeroLength ? env->NewStringUTF(str.c_str()) : 0; } -jobject intRectToRect(JNIEnv* env, const WebCore::IntRect& rect) -{ - jclass rectClass = env->FindClass("android/graphics/Rect"); - ALOG_ASSERT(rectClass, "Could not find android/graphics/Rect"); - jmethodID rectInit = env->GetMethodID(rectClass, "<init>", "(IIII)V"); - ALOG_ASSERT(rectInit, "Could not find init method on Rect"); - jobject jrect = env->NewObject(rectClass, rectInit, rect.x(), rect.y(), - rect.maxX(), rect.maxY()); - env->DeleteLocalRef(rectClass); - return jrect; -} - -jobjectArray intRectVectorToRectArray(JNIEnv* env, Vector<WebCore::IntRect>& rects) -{ - jclass rectClass = env->FindClass("android/graphics/Rect"); - ALOG_ASSERT(rectClass, "Could not find android/graphics/Rect"); - jmethodID rectInit = env->GetMethodID(rectClass, "<init>", "(IIII)V"); - ALOG_ASSERT(rectInit, "Could not find init method on Rect"); - jobjectArray array = env->NewObjectArray(rects.size(), rectClass, 0); - ALOG_ASSERT(array, "Could not create a Rect array"); - for (size_t i = 0; i < rects.size(); i++) { - jobject rect = env->NewObject(rectClass, rectInit, - rects[i].x(), rects[i].y(), - rects[i].maxX(), rects[i].maxY()); - if (rect) { - env->SetObjectArrayElement(array, i, rect); - env->DeleteLocalRef(rect); - } - } - env->DeleteLocalRef(rectClass); - return array; -} +#endif } diff --git a/Source/WebKit/android/jni/WebCoreJni.h b/Source/WebKit/android/jni/WebCoreJni.h index e8cc6ea..0f77cc6 100644 --- a/Source/WebKit/android/jni/WebCoreJni.h +++ b/Source/WebKit/android/jni/WebCoreJni.h @@ -27,7 +27,6 @@ #define WebCoreJni_h #include "ChromiumIncludes.h" -#include "IntRect.h" #include "PlatformString.h" #include <jni.h> @@ -82,6 +81,7 @@ WTF::String jstringToWtfString(JNIEnv*, jstring); // an empty WTF String returns 0. jstring wtfStringToJstring(JNIEnv*, const WTF::String&, bool validOnZeroLength = false); +#if USE(CHROME_NETWORK_STACK) string16 jstringToString16(JNIEnv*, jstring); std::string jstringToStdString(JNIEnv*, jstring); @@ -89,10 +89,8 @@ std::string jstringToStdString(JNIEnv*, jstring); // passing in an empty std::string will result in an empty jstring. Otherwise // an empty std::string returns 0. jstring stdStringToJstring(JNIEnv*, const std::string&, bool validOnZeroLength = false); +#endif -jobjectArray intRectVectorToRectArray(JNIEnv*, Vector<WebCore::IntRect>&); - -jobject intRectToRect(JNIEnv* env, const WebCore::IntRect& rect); } #endif diff --git a/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp b/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp index ec052f1..bb71bf5 100644 --- a/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp +++ b/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp @@ -62,6 +62,8 @@ #include "WebCoreViewBridge.h" #include "WebFrameView.h" #include "WebViewCore.h" +#include "benchmark/Intercept.h" +#include "benchmark/MyJavaVM.h" #include <JNIUtility.h> #include <jni.h> @@ -73,6 +75,7 @@ namespace android { extern int registerWebFrame(JNIEnv*); extern int registerJavaBridge(JNIEnv*); +extern int registerJniUtil(JNIEnv*); extern int registerResourceLoader(JNIEnv*); extern int registerWebViewCore(JNIEnv*); extern int registerWebHistory(JNIEnv*); @@ -91,7 +94,9 @@ extern int registerMediaPlayerVideo(JNIEnv*); #endif extern int registerDeviceMotionAndOrientationManager(JNIEnv*); extern int registerCookieManager(JNIEnv*); +#if USE(CHROME_NETWORK_STACK) extern int registerCacheManager(JNIEnv*); +#endif } @@ -102,11 +107,13 @@ struct RegistrationMethod { static RegistrationMethod gWebCoreRegMethods[] = { { "JavaBridge", android::registerJavaBridge }, + { "JniUtil", android::registerJniUtil }, { "WebFrame", android::registerWebFrame }, + { "WebCoreResourceLoader", android::registerResourceLoader }, { "WebViewCore", android::registerWebViewCore }, { "WebHistory", android::registerWebHistory }, { "WebIconDatabase", android::registerWebIconDatabase }, - { "WebSettingsClassic", android::registerWebSettings }, + { "WebSettings", android::registerWebSettings }, #if ENABLE(DATABASE) { "WebStorage", android::registerWebStorage }, #endif @@ -120,7 +127,9 @@ static RegistrationMethod gWebCoreRegMethods[] = { #endif { "DeviceMotionAndOrientationManager", android::registerDeviceMotionAndOrientationManager }, { "CookieManager", android::registerCookieManager }, +#if USE(CHROME_NETWORK_STACK) { "CacheManager", android::registerCacheManager }, +#endif }; EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) @@ -132,16 +141,16 @@ EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { - ALOGE("GetEnv failed!"); + LOGE("GetEnv failed!"); return result; } - ALOG_ASSERT(env, "Could not retrieve the env!"); + LOG_ASSERT(env, "Could not retrieve the env!"); const RegistrationMethod* method = gWebCoreRegMethods; const RegistrationMethod* end = method + sizeof(gWebCoreRegMethods)/sizeof(RegistrationMethod); while (method != end) { if (method->func(env) < 0) { - ALOGE("%s registration failed!", method->name); + LOGE("%s registration failed!", method->name); return result; } method++; @@ -153,3 +162,160 @@ EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) return JNI_VERSION_1_4; } + +class MyJavaSharedClient : public TimerClient, public CookieClient { +public: + MyJavaSharedClient() : m_hasTimer(false) {} + virtual void setSharedTimer(long long timemillis) { m_hasTimer = true; } + virtual void stopSharedTimer() { m_hasTimer = false; } + virtual void setSharedTimerCallback(void (*f)()) { m_func = f; } + virtual void signalServiceFuncPtrQueue() {} + + // Cookie methods that do nothing. + virtual void setCookies(const KURL&, const String&) {} + virtual String cookies(const KURL&) { return ""; } + virtual bool cookiesEnabled() { return false; } + + bool m_hasTimer; + void (*m_func)(); +}; + +static void historyItemChanged(HistoryItem* i) { + if (i->bridge()) + i->bridge()->updateHistoryItem(i); +} + +namespace android { + +EXPORT void benchmark(const char* url, int reloadCount, int width, int height) { + ScriptController::initializeThreading(); + + // Setting this allows data: urls to load from a local file. + SecurityOrigin::setLocalLoadPolicy(SecurityOrigin::AllowLocalLoadsForAll); + + // Create the fake JNIEnv and JavaVM + InitializeJavaVM(); + + // The real function is private to libwebcore but we know what it does. + notifyHistoryItemChanged = historyItemChanged; + + // Implement the shared timer callback + MyJavaSharedClient client; + JavaSharedClient::SetTimerClient(&client); + JavaSharedClient::SetCookieClient(&client); + + // Create the page with all the various clients + ChromeClientAndroid* chrome = new ChromeClientAndroid; + EditorClientAndroid* editor = new EditorClientAndroid; + DeviceMotionClientAndroid* deviceMotion = new DeviceMotionClientAndroid; + DeviceOrientationClientAndroid* deviceOrientation = new DeviceOrientationClientAndroid; + WebCore::Page::PageClients pageClients; + pageClients.chromeClient = chrome; + pageClients.contextMenuClient = new ContextMenuClientAndroid; + pageClients.editorClient = editor; + pageClients.dragClient = new DragClientAndroid; + pageClients.inspectorClient = new InspectorClientAndroid; + pageClients.deviceMotionClient = deviceMotion; + pageClients.deviceOrientationClient = deviceOrientation; + WebCore::Page* page = new WebCore::Page(pageClients); + editor->setPage(page); + + // Create MyWebFrame that intercepts network requests + MyWebFrame* webFrame = new MyWebFrame(page); + webFrame->setUserAgent("Performance testing"); // needs to be non-empty + chrome->setWebFrame(webFrame); + // ChromeClientAndroid maintains the reference. + Release(webFrame); + + // Create the Frame and the FrameLoaderClient + FrameLoaderClientAndroid* loader = new FrameLoaderClientAndroid(webFrame); + RefPtr<Frame> frame = Frame::create(page, NULL, loader); + loader->setFrame(frame.get()); + + // Build our View system, resize it to the given dimensions and release our + // references. Note: We keep a referenec to frameView so we can layout and + // draw later without risk of it being deleted. + WebViewCore* webViewCore = new WebViewCore(JSC::Bindings::getJNIEnv(), + MY_JOBJECT, frame.get()); + RefPtr<FrameView> frameView = FrameView::create(frame.get()); + WebFrameView* webFrameView = new WebFrameView(frameView.get(), webViewCore); + frame->setView(frameView); + frameView->resize(width, height); + Release(webViewCore); + Release(webFrameView); + + // Initialize the frame and turn of low-bandwidth display (it fails an + // assertion in the Cache code) + frame->init(); + frame->selection()->setFocused(true); + frame->page()->focusController()->setFocused(true); + + deviceMotion->setWebViewCore(webViewCore); + deviceOrientation->setWebViewCore(webViewCore); + + // Set all the default settings the Browser normally uses. + Settings* s = frame->settings(); +#ifdef ANDROID_LAYOUT + s->setLayoutAlgorithm(Settings::kLayoutNormal); // Normal layout for now +#endif + s->setStandardFontFamily("sans-serif"); + s->setFixedFontFamily("monospace"); + s->setSansSerifFontFamily("sans-serif"); + s->setSerifFontFamily("serif"); + s->setCursiveFontFamily("cursive"); + s->setFantasyFontFamily("fantasy"); + s->setMinimumFontSize(8); + s->setMinimumLogicalFontSize(8); + s->setDefaultFontSize(16); + s->setDefaultFixedFontSize(13); + s->setLoadsImagesAutomatically(true); + s->setJavaScriptEnabled(true); + s->setDefaultTextEncodingName("latin1"); + s->setPluginsEnabled(false); + s->setShrinksStandaloneImagesToFit(false); +#ifdef ANDROID_LAYOUT + s->setUseWideViewport(false); +#endif + + // Finally, load the actual data + ResourceRequest req(url); + frame->loader()->load(req, false); + + do { + // Layout the page and service the timer + frame->view()->layout(); + while (client.m_hasTimer) { + client.m_func(); + JavaSharedClient::ServiceFunctionPtrQueue(); + } + JavaSharedClient::ServiceFunctionPtrQueue(); + + // Layout more if needed. + while (frame->view()->needsLayout()) + frame->view()->layout(); + JavaSharedClient::ServiceFunctionPtrQueue(); + + if (reloadCount) + frame->loader()->reload(true); + } while (reloadCount--); + + // Draw into an offscreen bitmap + SkBitmap bmp; + bmp.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bmp.allocPixels(); + SkCanvas canvas(bmp); + PlatformGraphicsContext ctx(&canvas); + GraphicsContext gc(&ctx); + frame->view()->paintContents(&gc, IntRect(0, 0, width, height)); + + // Write the bitmap to the sdcard + SkImageEncoder* enc = SkImageEncoder::Create(SkImageEncoder::kPNG_Type); + enc->encodeFile("/sdcard/webcore_test.png", bmp, 100); + delete enc; + + // Tear down the world. + frame->loader()->detachFromParent(); + delete page; +} + +} // namespace android diff --git a/Source/WebKit/android/jni/WebCoreResourceLoader.cpp b/Source/WebKit/android/jni/WebCoreResourceLoader.cpp new file mode 100644 index 0000000..f9acc97 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreResourceLoader.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include "config.h" +#include "WebCoreResourceLoader.h" + +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceHandleInternal.h" +#include "ResourceResponse.h" +#include "SkUtils.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SkTypes.h> +#include <stdlib.h> +#include <utils/misc.h> +#include <wtf/Platform.h> +#include <wtf/text/CString.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct resourceloader_t { + jfieldID mObject; + jmethodID mCancelMethodID; + jmethodID mDownloadFileMethodID; + jmethodID mWillLoadFromCacheMethodID; + jmethodID mPauseLoadMethodID; +} gResourceLoader; + +// ---------------------------------------------------------------------------- + +#define GET_NATIVE_HANDLE(env, obj) ((WebCore::ResourceHandle*)env->GetIntField(obj, gResourceLoader.mObject)) +#define SET_NATIVE_HANDLE(env, obj, handle) (env->SetIntField(obj, gResourceLoader.mObject, handle)) + +//----------------------------------------------------------------------------- +// ResourceLoadHandler + +PassRefPtr<WebCore::ResourceLoaderAndroid> WebCoreResourceLoader::create(JNIEnv *env, jobject jLoadListener) +{ + return adoptRef<WebCore::ResourceLoaderAndroid>(new WebCoreResourceLoader(env, jLoadListener)); +} + +WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener) + : mPausedLoad(false) +{ + mJLoader = env->NewGlobalRef(jLoadListener); +} + +WebCoreResourceLoader::~WebCoreResourceLoader() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + SET_NATIVE_HANDLE(env, mJLoader, 0); + env->DeleteGlobalRef(mJLoader); + mJLoader = 0; +} + +void WebCoreResourceLoader::cancel() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mCancelMethodID); + checkException(env); +} + +void WebCoreResourceLoader::downloadFile() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mDownloadFileMethodID); + checkException(env); +} + +void WebCoreResourceLoader::pauseLoad(bool pause) +{ + if (mPausedLoad == pause) + return; + + mPausedLoad = pause; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mPauseLoadMethodID, pause); + checkException(env); +} + +/* +* This static method is called to check to see if a POST response is in +* the cache. This may be slow, but is only used during a navigation to +* a POST response. +*/ +bool WebCoreResourceLoader::willLoadFromCache(const WebCore::KURL& url, int64_t identifier) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WTF::String urlStr = url.string(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + bool val = env->CallStaticBooleanMethod(resourceLoader, gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr, identifier); + checkException(env); + env->DeleteLocalRef(resourceLoader); + env->DeleteLocalRef(jUrlStr); + + return val; +} + +// ---------------------------------------------------------------------------- +void WebCoreResourceLoader::SetResponseHeader(JNIEnv* env, jobject obj, jint nativeResponse, jstring key, jstring val) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + LOG_ASSERT(response, "nativeSetResponseHeader must take a valid response pointer!"); + + LOG_ASSERT(key, "How did a null value become a key?"); + if (val) + response->setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, val)); +} + +jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode, + jstring statusText, jstring mimeType, jlong expectedLength, + jstring encoding) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOG_ASSERT(url, "Must have a url in the response!"); + WebCore::KURL kurl(WebCore::ParsedURLString, jstringToWtfString(env, url)); + WTF::String encodingStr; + WTF::String mimeTypeStr; + if (mimeType) { + mimeTypeStr = jstringToWtfString(env, mimeType); + LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data()); + } + if (encoding) { + encodingStr = jstringToWtfString(env, encoding); + LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data()); + } + WebCore::ResourceResponse* response = new WebCore::ResourceResponse( + kurl, mimeTypeStr, (long long)expectedLength, + encodingStr, WTF::String()); + response->setHTTPStatusCode(statusCode); + if (statusText) { + WTF::String status = jstringToWtfString(env, statusText); + response->setHTTPStatusText(status); + LOGV("Response setStatusText: %s", status.latin1().data()); + } + return (int)response; +} + +void WebCoreResourceLoader::ReceivedResponse(JNIEnv* env, jobject obj, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeReceivedResponse must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + LOG_ASSERT(response, "nativeReceivedResponse must take a valid resource pointer!"); + handle->client()->didReceiveResponse(handle, *response); + // As the client makes a copy of the response, delete it here. + delete response; +} + +void WebCoreResourceLoader::AddData(JNIEnv* env, jobject obj, jbyteArray dataArray, jint length) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader data(%d)", length); + + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeAddData must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + SkAutoMemoryUsageProbe mup("android_webcore_resourceloader_nativeAddData"); + + bool result = false; + jbyte * data = env->GetByteArrayElements(dataArray, NULL); + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + handle->client()->didReceiveData(handle, (const char *)data, length, length); + env->ReleaseByteArrayElements(dataArray, data, JNI_ABORT); +} + +void WebCoreResourceLoader::Finished(JNIEnv* env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader finished"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeFinished must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + handle->client()->didFinishLoading(handle, 0); +} + +jstring WebCoreResourceLoader::RedirectedToUrl(JNIEnv* env, jobject obj, + jstring baseUrl, jstring redirectTo, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader redirectedToUrl"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeRedirectedToUrl must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return NULL; + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + WebCore::ResourceRequest r = handle->firstRequest(); + WebCore::KURL url(WebCore::KURL(WebCore::ParsedURLString, jstringToWtfString(env, baseUrl)), + jstringToWtfString(env, redirectTo)); + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + // If the url fails to resolve the relative path, return null. + if (url.protocol().isEmpty()) { + delete response; + return NULL; + } else { + // Ensure the protocol is lowercase. + url.setProtocol(url.protocol().lower()); + } + // Set the url after updating the protocol. + r.setURL(url); + if (r.httpMethod() == "POST") { + r.setHTTPMethod("GET"); + r.clearHTTPReferrer(); + r.setHTTPBody(0); + r.setHTTPContentType(""); + } + handle->client()->willSendRequest(handle, r, *response); + delete response; + return wtfStringToJstring(env, url.string()); +} + +void WebCoreResourceLoader::Error(JNIEnv* env, jobject obj, jint id, jstring description, + jstring failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader error"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeError must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + handle->client()->didFail(handle, WebCore::ResourceError("", id, + jstringToWtfString(env, failingUrl), jstringToWtfString(env, description))); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gResourceloaderMethods[] = { + /* name, signature, funcPtr */ + { "nativeSetResponseHeader", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) WebCoreResourceLoader::SetResponseHeader }, + { "nativeCreateResponse", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JLjava/lang/String;)I", + (void*) WebCoreResourceLoader::CreateResponse }, + { "nativeReceivedResponse", "(I)V", + (void*) WebCoreResourceLoader::ReceivedResponse }, + { "nativeAddData", "([BI)V", + (void*) WebCoreResourceLoader::AddData }, + { "nativeFinished", "()V", + (void*) WebCoreResourceLoader::Finished }, + { "nativeRedirectedToUrl", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;", + (void*) WebCoreResourceLoader::RedirectedToUrl }, + { "nativeError", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) WebCoreResourceLoader::Error } +}; + +int registerResourceLoader(JNIEnv* env) +{ + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + LOG_FATAL_IF(resourceLoader == NULL, + "Unable to find class android/webkit/LoadListener"); + + gResourceLoader.mObject = + env->GetFieldID(resourceLoader, "mNativeLoader", "I"); + LOG_FATAL_IF(gResourceLoader.mObject == NULL, + "Unable to find android/webkit/LoadListener.mNativeLoader"); + + gResourceLoader.mCancelMethodID = + env->GetMethodID(resourceLoader, "cancel", "()V"); + LOG_FATAL_IF(gResourceLoader.mCancelMethodID == NULL, + "Could not find method cancel on LoadListener"); + + gResourceLoader.mDownloadFileMethodID = + env->GetMethodID(resourceLoader, "downloadFile", "()V"); + LOG_FATAL_IF(gResourceLoader.mDownloadFileMethodID == NULL, + "Could not find method downloadFile on LoadListener"); + + gResourceLoader.mPauseLoadMethodID = + env->GetMethodID(resourceLoader, "pauseLoad", "(Z)V"); + LOG_FATAL_IF(gResourceLoader.mPauseLoadMethodID == NULL, + "Could not find method pauseLoad on LoadListener"); + + gResourceLoader.mWillLoadFromCacheMethodID = + env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;J)Z"); + LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL, + "Could not find static method willLoadFromCache on LoadListener"); + + env->DeleteLocalRef(resourceLoader); + + return jniRegisterNativeMethods(env, "android/webkit/LoadListener", + gResourceloaderMethods, NELEM(gResourceloaderMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebCoreResourceLoader.h b/Source/WebKit/android/jni/WebCoreResourceLoader.h new file mode 100644 index 0000000..0e34a5b --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreResourceLoader.h @@ -0,0 +1,78 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WebCoreResourceLoader_h +#define WebCoreResourceLoader_h + +#include <KURL.h> +#include <ResourceLoaderAndroid.h> +#include <jni.h> + +namespace android { + +class WebCoreResourceLoader : public WebCore::ResourceLoaderAndroid +{ +public: + static PassRefPtr<WebCore::ResourceLoaderAndroid> create(JNIEnv *env, jobject jLoadListener); + virtual ~WebCoreResourceLoader(); + + /** + * Call to java to cancel the current load. + */ + virtual void cancel(); + + /** + * Call to java to download the current load rather than feed it + * back to WebCore + */ + virtual void downloadFile(); + + virtual void pauseLoad(bool); + + /** + * Call to java to find out if this URL is in the cache + */ + static bool willLoadFromCache(const WebCore::KURL& url, int64_t identifier); + + // Native jni functions + static void SetResponseHeader(JNIEnv*, jobject, jint, jstring, jstring); + static jint CreateResponse(JNIEnv*, jobject, jstring, jint, jstring, + jstring, jlong, jstring); + static void ReceivedResponse(JNIEnv*, jobject, jint); + static void AddData(JNIEnv*, jobject, jbyteArray, jint); + static void Finished(JNIEnv*, jobject); + static jstring RedirectedToUrl(JNIEnv*, jobject, jstring, jstring, jint); + static void Error(JNIEnv*, jobject, jint, jstring, jstring); + +protected: + WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener); +private: + jobject mJLoader; + bool mPausedLoad; +}; + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/jni/WebCoreViewBridge.h b/Source/WebKit/android/jni/WebCoreViewBridge.h index cca4ca5..b04a0f9 100644 --- a/Source/WebKit/android/jni/WebCoreViewBridge.h +++ b/Source/WebKit/android/jni/WebCoreViewBridge.h @@ -48,7 +48,7 @@ public: virtual ~WebCoreViewBridge() { } - virtual void draw(WebCore::GraphicsContext* ctx, + virtual void draw(WebCore::GraphicsContext* ctx, const WebCore::IntRect& rect) = 0; const WebCore::IntRect& getBounds() const diff --git a/Source/WebKit/android/jni/WebFrameView.cpp b/Source/WebKit/android/jni/WebFrameView.cpp index 10e31dc..8e5eac4 100644 --- a/Source/WebKit/android/jni/WebFrameView.cpp +++ b/Source/WebKit/android/jni/WebFrameView.cpp @@ -54,16 +54,51 @@ WebFrameView::~WebFrameView() { Release(mWebViewCore); } -void WebFrameView::draw(WebCore::GraphicsContext* gc, const WebCore::IntRect& rect) { +void WebFrameView::draw(WebCore::GraphicsContext* ctx, const WebCore::IntRect& rect) { WebCore::Frame* frame = mFrameView->frame(); - if (frame->contentRenderer()) - mFrameView->paintContents(gc, rect); - else { - // FIXME: I'm not entirely sure this ever happens or is needed - gc->setFillColor(WebCore::Color::white, WebCore::ColorSpaceDeviceRGB); - gc->fillRect(rect); + if (NULL == frame->contentRenderer()) { + // We only do this if there is nothing else to draw. + // If there is a renderer, it will fill the bg itself, so we don't want to + // double-draw (slow) + SkCanvas* canvas = ctx->platformContext()->mCanvas; + canvas->drawColor(SK_ColorWHITE); + } else if (frame->tree()->parent()) { + // Note: this code was moved from FrameLoaderClientAndroid + // + // For subframe, create a new translated rect from the given rectangle. + WebCore::IntRect transRect(rect); + // In Frame::markAllMatchesForText(), it does a fake paint. So we need + // to handle the case where platformContext() is null. However, we still + // want to call paint, since WebKit must have called the paint for a reason. + SkCanvas* canvas = ctx->platformContext() ? ctx->platformContext()->mCanvas : NULL; + if (canvas) { + const WebCore::IntRect& bounds = getBounds(); + + // Grab the intersection of transRect and the frame's bounds. + transRect.intersect(bounds); + if (transRect.isEmpty()) + return; + + // Move the transRect into the frame's local coordinates. + transRect.move(-bounds.x(), -bounds.y()); + + // Translate the canvas, add a clip. + canvas->save(); + canvas->translate(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y())); + canvas->clipRect(transRect); + } + mFrameView->paintContents(ctx, transRect); + if (canvas) + canvas->restore(); + } else { + mFrameView->paintContents(ctx, rect); } } +void WebFrameView::setView(WebCore::FrameView* frameView) { + mFrameView = frameView; + mFrameView->setPlatformWidget(this); +} + } // namespace android diff --git a/Source/WebKit/android/jni/WebFrameView.h b/Source/WebKit/android/jni/WebFrameView.h index ac81afe..117b603 100644 --- a/Source/WebKit/android/jni/WebFrameView.h +++ b/Source/WebKit/android/jni/WebFrameView.h @@ -47,6 +47,8 @@ namespace android { return mWebViewCore; } + void setView(WebCore::FrameView* frameView); + WebCore::FrameView* view() const { return mFrameView; } diff --git a/Source/WebKit/android/jni/WebHistory.cpp b/Source/WebKit/android/jni/WebHistory.cpp index b6972e4..aa74b81 100644 --- a/Source/WebKit/android/jni/WebHistory.cpp +++ b/Source/WebKit/android/jni/WebHistory.cpp @@ -28,7 +28,6 @@ #include "config.h" #include "WebHistory.h" -#include "AndroidLog.h" #include "BackForwardList.h" #include "BackForwardListImpl.h" #include "DocumentLoader.h" @@ -36,7 +35,6 @@ #include "FrameLoader.h" #include "FrameLoaderClientAndroid.h" #include "FrameTree.h" -#include "GraphicsJNI.h" #include "HistoryItem.h" #include "IconDatabase.h" #include "Page.h" @@ -56,13 +54,16 @@ namespace android { // Forward declarations -static void writeItem(WTF::Vector<char>& vector, WebCore::HistoryItem* item); -static void writeChildrenRecursive(WTF::Vector<char>& vector, WebCore::HistoryItem* parent); -static bool readItemRecursive(WebCore::HistoryItem* child, const char** pData, int length); +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 { @@ -77,7 +78,7 @@ struct WebBackForwardListFields { static void WebHistoryClose(JNIEnv* env, jobject obj, jint frame) { - ALOG_ASSERT(frame, "Close needs a valid Frame pointer!"); + LOG_ASSERT(frame, "Close needs a valid Frame pointer!"); WebCore::Frame* pFrame = (WebCore::Frame*)frame; WebCore::BackForwardListImpl* list = static_cast<WebCore::BackForwardListImpl*>(pFrame->page()->backForwardList()); @@ -144,7 +145,7 @@ static void WebHistoryClose(JNIEnv* env, jobject obj, jint frame) static void WebHistoryRestoreIndex(JNIEnv* env, jobject obj, jint frame, jint index) { - ALOG_ASSERT(frame, "RestoreState needs a valid Frame pointer!"); + LOG_ASSERT(frame, "RestoreState needs a valid Frame pointer!"); WebCore::Frame* pFrame = (WebCore::Frame*)frame; WebCore::Page* page = pFrame->page(); WebCore::HistoryItem* currentItem = @@ -155,10 +156,10 @@ static void WebHistoryRestoreIndex(JNIEnv* env, jobject obj, jint frame, jint in page->goToItem(currentItem, FrameLoadTypeIndexedBackForward); } -static jint WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray data) +static void WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray data) { - ALOG_ASSERT(frame, "Inflate needs a valid frame pointer!"); - ALOG_ASSERT(data, "Inflate needs a valid data pointer!"); + 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); @@ -167,7 +168,7 @@ static jint WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray d // Inflate the history tree into one HistoryItem or null if the inflation // failed. RefPtr<WebCore::HistoryItem> newItem = WebCore::HistoryItem::create(); - WebHistoryItem* bridge = new WebHistoryItem(newItem.get()); + WebHistoryItem* bridge = new WebHistoryItem(env, obj, newItem.get()); newItem->setBridge(bridge); // Inflate the item recursively. If it fails, that is ok. We'll have an @@ -177,7 +178,7 @@ static jint WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray d // 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); - readItemRecursive(newItem.get(), &ptr, (int)size); + read_item_recursive(newItem.get(), &ptr, (int)size); env->ReleaseByteArrayElements(data, const_cast<jbyte*>(bytes), JNI_ABORT); bridge->setActive(); @@ -187,110 +188,49 @@ static jint WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray d // Update the item. bridge->updateHistoryItem(newItem.get()); - // Ref here because Java expects to adopt the reference, and as such will not - // call ref on it. However, setBridge has also adopted the reference - // TODO: This is confusing as hell, clean up ownership and have setBridge - // take a RefPtr instead of a raw ptr and calling adoptRef on it - bridge->ref(); - return reinterpret_cast<jint>(bridge); -} - -static void WebHistoryRef(JNIEnv* env, jobject obj, jint ptr) -{ - if (ptr) - reinterpret_cast<WebHistoryItem*>(ptr)->ref(); -} - -static void WebHistoryUnref(JNIEnv* env, jobject obj, jint ptr) -{ - if (ptr) - reinterpret_cast<WebHistoryItem*>(ptr)->deref(); -} - -static jobject WebHistoryGetTitle(JNIEnv* env, jobject obj, jint ptr) -{ - if (!ptr) - return 0; - WebHistoryItem* item = reinterpret_cast<WebHistoryItem*>(ptr); - MutexLocker locker(item->m_lock); - return wtfStringToJstring(env, item->m_title, false); -} - -static jobject WebHistoryGetUrl(JNIEnv* env, jobject obj, jint ptr) -{ - if (!ptr) - return 0; - WebHistoryItem* item = reinterpret_cast<WebHistoryItem*>(ptr); - MutexLocker locker(item->m_lock); - return wtfStringToJstring(env, item->m_url, false); -} - -static jobject WebHistoryGetOriginalUrl(JNIEnv* env, jobject obj, jint ptr) -{ - if (!ptr) - return 0; - WebHistoryItem* item = reinterpret_cast<WebHistoryItem*>(ptr); - MutexLocker locker(item->m_lock); - return wtfStringToJstring(env, item->m_originalUrl, false); -} - -static jobject WebHistoryGetFlattenedData(JNIEnv* env, jobject obj, jint ptr) -{ - if (!ptr) - return 0; - - WebHistoryItem* item = reinterpret_cast<WebHistoryItem*>(ptr); - MutexLocker locker(item->m_lock); - - if (!item->m_dataCached) { - // Try to create a new java byte array. - jbyteArray b = env->NewByteArray(item->m_data.size()); - if (!b) - return NULL; - - // Write our flattened data to the java array. - env->SetByteArrayRegion(b, 0, item->m_data.size(), - (const jbyte*)item->m_data.data()); - item->m_dataCached = env->NewGlobalRef(b); - env->DeleteLocalRef(b); - } - return item->m_dataCached; -} - -static jobject WebHistoryGetFavicon(JNIEnv* env, jobject obj, jint ptr) -{ - if (!ptr) - return 0; - WebHistoryItem* item = reinterpret_cast<WebHistoryItem*>(ptr); - MutexLocker locker(item->m_lock); - if (!item->m_faviconCached && item->m_favicon) { - jobject favicon = GraphicsJNI::createBitmap(env, - item->m_favicon, - false, NULL); - item->m_favicon = 0; // Framework now owns the pointer - item->m_faviconCached = env->NewGlobalRef(favicon); - env->DeleteLocalRef(favicon); - } - return item->m_faviconCached; } // 6 empty strings + no document state + children count + 2 scales = 10 unsigned values // 1 char for isTargetItem. #define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 10 + sizeof(char))) -void WebHistory::Flatten(JNIEnv* env, WTF::Vector<char>& vector, WebCore::HistoryItem* item) +jbyteArray WebHistory::Flatten(JNIEnv* env, WTF::Vector<char>& v, WebCore::HistoryItem* item) { if (!item) - return; + return NULL; // Reserve a vector of chars with an initial size of HISTORY_MIN_SIZE. - vector.reserveCapacity(HISTORY_MIN_SIZE); + v.reserveCapacity(HISTORY_MIN_SIZE); // Write the top-level history item and then write all the children // recursively. - ALOG_ASSERT(item->bridge(), "Why don't we have a bridge object here?"); - writeItem(vector, item); - writeChildrenRecursive(vector, item); + LOG_ASSERT(item->bridge(), "Why don't we have a bridge object here?"); + write_item(v, item); + write_children_recursive(v, item); + + // Try to create a new java byte array. + jbyteArray b = env->NewByteArray(v.size()); + if (!b) + return NULL; + + // Write our flattened data to the java array. + env->SetByteArrayRegion(b, 0, v.size(), (const jbyte*)v.data()); + return b; +} + +WebHistoryItem::WebHistoryItem(JNIEnv* env, jobject obj, + WebCore::HistoryItem* item) : WebCore::AndroidWebHistoryBridge(item) { + m_object = env->NewWeakGlobalRef(obj); + m_parent = 0; +} + +WebHistoryItem::~WebHistoryItem() { + if (m_object) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + env->DeleteWeakGlobalRef(m_object); + } } void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { @@ -306,7 +246,7 @@ void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { // 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(). - ALOGW("Can't updateHistoryItem as the top HistoryItem is gone"); + LOGW("Can't updateHistoryItem as the top HistoryItem is gone"); return; } while (webItem->parent()) @@ -316,7 +256,7 @@ void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { // If a HistoryItem only exists for page cache, it is possible that // the parent HistoryItem destroyed before the child HistoryItem. If // it happens, skip updating. - ALOGW("Can't updateHistoryItem as the top HistoryItem is gone"); + LOGW("Can't updateHistoryItem as the top HistoryItem is gone"); return; } } @@ -324,15 +264,23 @@ void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { if (!env) return; - MutexLocker locker(webItem->m_lock); + // Don't do anything if the item has been gc'd already + AutoJObject realItem = getRealObject(env, webItem->m_object); + if (!realItem.get()) + return; - // TODO: Figure out if we can't just use item->urlString() instead... const WTF::String urlString = WebFrame::convertIDNToUnicode(item->url()); - webItem->m_url = urlString.threadsafeCopy(); + jstring urlStr = NULL; + if (!urlString.isNull()) + urlStr = wtfStringToJstring(env, urlString); const WTF::String originalUrlString = WebFrame::convertIDNToUnicode(item->originalURL()); - webItem->m_originalUrl = originalUrlString.threadsafeCopy(); + jstring originalUrlStr = NULL; + if (!originalUrlString.isNull()) + originalUrlStr = wtfStringToJstring(env, originalUrlString); const WTF::String& titleString = item->title(); - webItem->m_title = titleString.threadsafeCopy(); + jstring titleStr = NULL; + if (!titleString.isNull()) + titleStr = wtfStringToJstring(env, titleString); // Try to get the favicon from the history item. For some pages like Grand // Prix, there are history items with anchors. If the icon fails for the @@ -346,41 +294,24 @@ void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { // FIXME: This method should not be used from outside WebCore and will be removed. // http://trac.webkit.org/changeset/81484 WebCore::Image* icon = WebCore::iconDatabase().synchronousIconForPageURL(url, WebCore::IntSize(16, 16)); - delete webItem->m_favicon; - webItem->m_favicon = webcoreImageToSkBitmap(icon); - if (webItem->m_faviconCached) { - env->DeleteGlobalRef(webItem->m_faviconCached); - webItem->m_faviconCached = 0; - } - webItem->m_data.clear(); - WebHistory::Flatten(env, webItem->m_data, item); - if (webItem->m_dataCached) { - env->DeleteGlobalRef(webItem->m_dataCached); - webItem->m_dataCached = 0; - } -} - -WebHistoryItem::~WebHistoryItem() -{ - delete m_favicon; - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env) { - ALOGW("Failed to get JNIEnv*! Potential memory leak!"); - return; - } - if (m_faviconCached) { - env->DeleteGlobalRef(m_faviconCached); - m_faviconCached = 0; - } - if (m_dataCached) { - env->DeleteGlobalRef(m_dataCached); - m_dataCached = 0; - } + if (icon) + favicon = webcoreImageToJavaBitmap(env, icon); + + WTF::Vector<char> data; + jbyteArray array = WebHistory::Flatten(env, data, item); + env->CallVoidMethod(realItem.get(), gWebHistoryItem.mUpdate, urlStr, + originalUrlStr, titleStr, favicon, array); + env->DeleteLocalRef(urlStr); + env->DeleteLocalRef(originalUrlStr); + env->DeleteLocalRef(titleStr); + if (favicon) + env->DeleteLocalRef(favicon); + env->DeleteLocalRef(array); } static void historyItemChanged(WebCore::HistoryItem* item) { - ALOG_ASSERT(item, "historyItemChanged called with a null item"); + LOG_ASSERT(item, "historyItemChanged called with a null item"); if (item->bridge()) item->bridge()->updateHistoryItem(item); @@ -388,22 +319,22 @@ static void historyItemChanged(WebCore::HistoryItem* item) { void WebHistory::AddItem(const AutoJObject& list, WebCore::HistoryItem* item) { - ALOG_ASSERT(item, "newItem must take a valid HistoryItem!"); + 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(); - // Create the bridge, make it active, and attach it to the item. - WebHistoryItem* bridge = new WebHistoryItem(item); - bridge->setActive(); - item->setBridge(bridge); // Allocate a blank WebHistoryItem jclass clazz = env->FindClass("android/webkit/WebHistoryItem"); - jobject newItem = env->NewObject(clazz, gWebHistoryItem.mInit, - reinterpret_cast<int>(bridge)); + jobject newItem = env->NewObject(clazz, gWebHistoryItem.mInit); env->DeleteLocalRef(clazz); + // Create the bridge, make it active, and attach it to the item. + WebHistoryItem* bridge = new WebHistoryItem(env, newItem, item); + bridge->setActive(); + item->setBridge(bridge); + // Update the history item which will flatten the data and call update on // the java item. bridge->updateHistoryItem(item); @@ -427,7 +358,7 @@ void WebHistory::UpdateHistoryIndex(const AutoJObject& list, int newIndex) list.env()->CallVoidMethod(list.get(), gWebBackForwardList.mSetCurrentIndex, newIndex); } -static void writeString(WTF::Vector<char>& vector, const WTF::String& str) +static void write_string(WTF::Vector<char>& v, const WTF::String& str) { unsigned strLen = str.length(); // Only do work if the string has data. @@ -435,96 +366,96 @@ static void writeString(WTF::Vector<char>& vector, const WTF::String& str) // 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 = vector.size() + sizeof(unsigned); + 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. - vector.grow(length); + v.grow(length); // Grab the position to write to. - char* data = vector.begin() + vectorLen; + char* data = v.begin() + vectorLen; // Write the actual string int l = SkUTF16_ToUTF8(str.characters(), strLen, data); - ALOGV("Writing string %d %.*s", l, l, 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. - vector.shrink(vectorLen + l); + v.shrink(vectorLen + l); } else - vector.append((char*)&strLen, sizeof(unsigned)); + v.append((char*)&strLen, sizeof(unsigned)); } -static void writeItem(WTF::Vector<char>& vector, WebCore::HistoryItem* item) +static void write_item(WTF::Vector<char>& v, WebCore::HistoryItem* item) { // Original url - writeString(vector, item->originalURLString()); + write_string(v, item->originalURLString()); // Url - writeString(vector, item->urlString()); + write_string(v, item->urlString()); // Title - writeString(vector, item->title()); + write_string(v, item->title()); // Form content type - writeString(vector, item->formContentType()); + write_string(v, item->formContentType()); // Form data const WebCore::FormData* formData = item->formData(); if (formData) { - writeString(vector, formData->flattenToString()); + write_string(v, formData->flattenToString()); // save the identifier as it is not included in the flatten data int64_t id = formData->identifier(); - vector.append((char*)&id, sizeof(int64_t)); + v.append((char*)&id, sizeof(int64_t)); } else - writeString(vector, WTF::String()); // Empty constructor does not allocate a buffer. + write_string(v, WTF::String()); // Empty constructor does not allocate a buffer. // Target - writeString(vector, item->target()); + write_string(v, item->target()); AndroidWebHistoryBridge* bridge = item->bridge(); - ALOG_ASSERT(bridge, "We should have a bridge here!"); + LOG_ASSERT(bridge, "We should have a bridge here!"); // Screen scale const float scale = bridge->scale(); - ALOGV("Writing scale %f", scale); - vector.append((char*)&scale, sizeof(float)); + LOGV("Writing scale %f", scale); + v.append((char*)&scale, sizeof(float)); const float textWrapScale = bridge->textWrapScale(); - ALOGV("Writing text wrap scale %f", textWrapScale); - vector.append((char*)&textWrapScale, sizeof(float)); + LOGV("Writing text wrap scale %f", textWrapScale); + v.append((char*)&textWrapScale, sizeof(float)); // Scroll position. const int scrollX = item->scrollPoint().x(); - vector.append((char*)&scrollX, sizeof(int)); + v.append((char*)&scrollX, sizeof(int)); const int scrollY = item->scrollPoint().y(); - vector.append((char*)&scrollY, sizeof(int)); + v.append((char*)&scrollY, sizeof(int)); // Document state const WTF::Vector<WTF::String>& docState = item->documentState(); WTF::Vector<WTF::String>::const_iterator end = docState.end(); unsigned stateSize = docState.size(); - ALOGV("Writing docState %d", stateSize); - vector.append((char*)&stateSize, sizeof(unsigned)); + LOGV("Writing docState %d", stateSize); + v.append((char*)&stateSize, sizeof(unsigned)); for (WTF::Vector<WTF::String>::const_iterator i = docState.begin(); i != end; ++i) { - writeString(vector, *i); + write_string(v, *i); } // Is target item - ALOGV("Writing isTargetItem %d", item->isTargetItem()); - vector.append((char)item->isTargetItem()); + LOGV("Writing isTargetItem %d", item->isTargetItem()); + v.append((char)item->isTargetItem()); // Children count unsigned childCount = item->children().size(); - ALOGV("Writing childCount %d", childCount); - vector.append((char*)&childCount, sizeof(unsigned)); + LOGV("Writing childCount %d", childCount); + v.append((char*)&childCount, sizeof(unsigned)); } -static void writeChildrenRecursive(WTF::Vector<char>& vector, WebCore::HistoryItem* parent) +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(); - ALOG_ASSERT(parent->bridge(), + LOG_ASSERT(parent->bridge(), "The parent item should have a bridge object!"); if (!item->bridge()) { WebHistoryItem* bridge = new WebHistoryItem(static_cast<WebHistoryItem*>(parent->bridge())); @@ -536,223 +467,116 @@ static void writeChildrenRecursive(WTF::Vector<char>& vector, WebCore::HistoryIt // parent must not have a parent bridge. WebHistoryItem* bridge = static_cast<WebHistoryItem*>(item->bridge()); WebHistoryItem* parentBridge = static_cast<WebHistoryItem*>(parent->bridge()); - ALOG_ASSERT(parentBridge->parent() == 0 || + LOG_ASSERT(parentBridge->parent() == 0 || bridge->parent() == parentBridge, "Somehow this item has an incorrect parent"); bridge->setParent(parentBridge); } - writeItem(vector, item); - writeChildrenRecursive(vector, item); - } -} - -bool readUnsigned(const char*& data, const char* end, unsigned& result, const char* dbgLabel = 0); -bool readInt(const char*& data, const char* end, int& result, const char* dbgLabel = 0); -bool readInt64(const char*& data, const char* end, int64_t& result, const char* dbgLabel = 0); -bool readFloat(const char*& data, const char* end, float& result, const char* dbgLabel = 0); -bool readBool(const char*& data, const char* end, bool& result, const char* dbgLabel = 0); -bool readString(const char*& data, const char* end, String& result, const char* dbgLabel = 0); - -bool readUnsigned(const char*& data, const char* end, unsigned& result, const char* dbgLabel) -{ - // Check if we have enough data left to continue. - if ((end < data) || (static_cast<size_t>(end - data) < sizeof(unsigned))) { - ALOGW("\tNot enough data to read unsigned; tag=\"%s\" end=%p data=%p", - dbgLabel ? dbgLabel : "<no tag>", end, data); - return false; - } - - memcpy(&result, data, sizeof(unsigned)); - data += sizeof(unsigned); - if (dbgLabel) - ALOGV("Reading %-16s %u", dbgLabel, result); - return true; -} - -bool readInt(const char*& data, const char* end, int& result, const char* dbgLabel) -{ - // Check if we have enough data left to continue. - if ((end < data) || (static_cast<size_t>(end - data) < sizeof(int))) { - ALOGW("Not enough data to read int; tag=\"%s\" end=%p data=%p", - dbgLabel ? dbgLabel : "<no tag>", end, data); - return false; + write_item(v, item); + write_children_recursive(v, item); } - - memcpy(&result, data, sizeof(int)); - data += sizeof(int); - if (dbgLabel) - ALOGV("Reading %-16s %d", dbgLabel, result); - return true; } -bool readInt64(const char*& data, const char* end, int64_t& result, const char* dbgLabel) -{ - // Check if we have enough data left to continue. - if ((end < data) || (static_cast<size_t>(end - data) < sizeof(int64_t))) { - ALOGW("Not enough data to read int64_t; tag=\"%s\" end=%p data=%p", - dbgLabel ? dbgLabel : "<no tag>", end, data); - return false; - } - - memcpy(&result, data, sizeof(int64_t)); - data += sizeof(int64_t); - if (dbgLabel) - ALOGV("Reading %-16s %ll", dbgLabel, result); - return true; -} - -bool readFloat(const char*& data, const char* end, float& result, const char* dbgLabel) -{ - // Check if we have enough data left to continue. - if ((end < data) || (static_cast<size_t>(end - data) < sizeof(float))) { - ALOGW("Not enough data to read float; tag=\"%s\" end=%p data=%p", - dbgLabel ? dbgLabel : "<no tag>", end, data); - return false; - } - - memcpy(&result, data, sizeof(float)); - data += sizeof(float); - if (dbgLabel) - ALOGV("Reading %-16s %f", dbgLabel, result); - return true; -} - -// Note that the return value indicates success or failure, while the result -// parameter indicates the read value of the bool -bool readBool(const char*& data, const char* end, bool& result, const char* dbgLabel) -{ - // Check if we have enough data left to continue. - if ((end < data) || (static_cast<size_t>(end - data) < sizeof(char))) { - ALOGW("Not enough data to read bool; tag=\"%s\" end=%p data=%p", - dbgLabel ? dbgLabel : "<no tag>", end, data); - return false; - } - - char c; - memcpy(&c, data, sizeof(char)); - data += sizeof(char); - if (dbgLabel) - ALOGV("Reading %-16s %d", dbgLabel, c); - result = c; - - // Valid bool results are 0 or 1 - if ((c != 0) && (c != 1)) { - ALOGW("Invalid value for bool; tag=\"%s\" end=%p data=%p c=%u", - dbgLabel ? dbgLabel : "<no tag>", end, data, c); - return false; - } - - return true; -} - -bool readString(const char*& data, const char* end, String& result, const char* dbgLabel) -{ - unsigned stringLength; - if (!readUnsigned(data, end, stringLength)) { - ALOGW("Not enough data to read string length; tag=\"%s\" end=%p data=%p", - dbgLabel ? dbgLabel : "<no tag>", end, data); - return false; - } - - if (dbgLabel) - ALOGV("Reading %-16s %d %.*s", dbgLabel, stringLength, stringLength, data); - - // If length was 0, there will be no string content, but still return true - if (!stringLength) { - result = String(); - return true; - } - - if ((end < data) || ((unsigned)(end - data) < stringLength)) { - ALOGW("Not enough data to read content; tag=\"%s\" end=%p data=%p stringLength=%u", - dbgLabel ? dbgLabel : "<no tag>", end, data, stringLength); - return false; - } - - const unsigned MAX_REASONABLE_STRING_LENGTH = 10000; - if (stringLength > MAX_REASONABLE_STRING_LENGTH) { - ALOGW("String length is suspiciously large (>%d); tag=\"%s\" end=%p data=%p stringLength=%u", - MAX_REASONABLE_STRING_LENGTH, dbgLabel ? dbgLabel : "<no tag>", - end, data, stringLength); - } - - bool decodeFailed = false; - static const WebCore::TextEncoding& encoding = WebCore::UTF8Encoding(); - result = encoding.decode(data, stringLength, true, decodeFailed); - if (decodeFailed) { - ALOGW("Decode failed, tag=\"%s\" end=%p data=%p stringLength=%u content=\"%s\"", - dbgLabel ? dbgLabel : "<no tag>", end, data, stringLength, - result.utf8().data()); - return false; - } - - if (stringLength > MAX_REASONABLE_STRING_LENGTH) { - ALOGW("\tdecodeFailed=%d (flag is ignored) content=\"%s\"", - decodeFailed, result.utf8().data()); - } - - data += stringLength; - return true; -} - -static bool readItemRecursive(WebCore::HistoryItem* newItem, +static bool read_item_recursive(WebCore::HistoryItem* newItem, const char** pData, int length) { - if (!pData || length < HISTORY_MIN_SIZE) { - ALOGW("readItemRecursive() bad params; pData=%p length=%d", pData, length); + if (!pData || length < HISTORY_MIN_SIZE) return false; - } + const WebCore::TextEncoding& e = WebCore::UTF8Encoding(); const char* data = *pData; const char* end = data + length; - String content; + int sizeofUnsigned = (int)sizeof(unsigned); // Read the original url - if (readString(data, end, content, "Original url")) - newItem->setOriginalURLString(content); - else + // Read the expected length of the string. + unsigned 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 - if (readString(data, end, content, "Url")) - newItem->setURLString(content); - else + 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 - if (readString(data, end, content, "Title")) - newItem->setTitle(content); - else + 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. - // Read the form content type WTF::String formContentType; - if (!readString(data, end, formContentType, "Content type")) - return false; + WTF::PassRefPtr<WebCore::FormData> formData = NULL; - // Read the form data size - unsigned formDataSize; - if (!readUnsigned(data, end, formDataSize, "Form data size")) + // Read the form content type + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Content type %d %.*s", l, l, data); + if (data + l < end) + formContentType = e.decode(data, l); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) return false; // Read the form data - WTF::RefPtr<WebCore::FormData> formData; - if (formDataSize) { - ALOGV("Reading Form data %d %.*s", formDataSize, formDataSize, data); - if ((end < data) || ((size_t)(end - data) < formDataSize)) { - ALOGW("\tNot enough data to read form data; returning"); + 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; - } - formData = WebCore::FormData::create(data, formDataSize); - data += formDataSize; + data += l; // Read the identifier - int64_t id; - if (!readInt64(data, end, id, "Form id")) - return false; - if (id) - formData->setIdentifier(id); + { + int64_t id; + int size = (int)sizeof(int64_t); + memcpy(&id, data, size); + data += size; + if (id) + formData->setIdentifier(id); + } } + if (end - data < sizeofUnsigned) + return false; // Set up the form info if (formData != NULL) { @@ -764,76 +588,112 @@ static bool readItemRecursive(WebCore::HistoryItem* newItem, } // Read the target - if (readString(data, end, content, "Target")) - newItem->setTarget(content); - else + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Target %d %.*s", l, l, data); + if (data + l < end) + newItem->setTarget(e.decode(data, l)); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) return false; AndroidWebHistoryBridge* bridge = newItem->bridge(); - ALOG_ASSERT(bridge, "There should be a bridge object during inflate"); - - // Read the screen scale + LOG_ASSERT(bridge, "There should be a bridge object during inflate"); float fValue; - if (readFloat(data, end, fValue, "Screen scale")) - bridge->setScale(fValue); - else - return false; + // Read the screen scale + memcpy(&fValue, data, sizeof(float)); + LOGV("Screen scale %f", fValue); + bridge->setScale(fValue); + data += sizeof(float); + memcpy(&fValue, data, sizeofUnsigned); + LOGV("Text wrap scale %f", fValue); + bridge->setTextWrapScale(fValue); + data += sizeof(float); - // Read the text wrap scale - if (readFloat(data, end, fValue, "Text wrap scale")) - bridge->setTextWrapScale(fValue); - else + if (end - data < sizeofUnsigned) return false; // Read scroll position. - int scrollX; - if (!readInt(data, end, scrollX, "Scroll pos x")) - return false; - int scrollY; - if (!readInt(data, end, scrollY, "Scroll pos y")) - return false; + int scrollX = 0; + memcpy(&scrollX, data, sizeofUnsigned); + data += sizeofUnsigned; + int scrollY = 0; + memcpy(&scrollY, data, sizeofUnsigned); + data += sizeofUnsigned; newItem->setScrollPoint(IntPoint(scrollX, scrollY)); - // Read the document state - unsigned docStateCount; - if (!readUnsigned(data, end, docStateCount, "Doc state count")) + if (end - data < sizeofUnsigned) return false; - if (docStateCount) { + + // Read the document state + memcpy(&l, data, sizeofUnsigned); + LOGV("Document state %d", l); + data += sizeofUnsigned; + if (l) { + // Check if we have enough data to at least parse the sizes of each + // document state string. + if (data + l * sizeofUnsigned >= end) + return false; // Create a new vector and reserve enough space for the document state. WTF::Vector<WTF::String> docState; - docState.reserveCapacity(docStateCount); - while (docStateCount--) { - // Read a document state string - if (readString(data, end, content, "Document state")) - docState.append(content); + 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 - bool c; - if (readBool(data, end, c, "Target item")) - newItem->setIsTargetItem(c); - else + // 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 - unsigned count; - if (!readUnsigned(data, end, count, "Child count")) - return false; + memcpy(&l, data, sizeofUnsigned); + LOGV("Child count %d", l); + data += sizeofUnsigned; *pData = data; - if (count) { - while (count--) { + 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::RefPtr<WebCore::HistoryItem> child = WebCore::HistoryItem::create(); + WTF::PassRefPtr<WebCore::HistoryItem> child = WebCore::HistoryItem::create(); // Set a bridge that will not call into java. child->setBridge(new WebHistoryItem(static_cast<WebHistoryItem*>(bridge))); // Read the child item. - if (!readItemRecursive(child.get(), pData, end - data)) + if (!read_item_recursive(child.get(), pData, end - data)) { + child.clear(); return false; + } child->bridge()->setActive(); newItem->addChildItem(child); } @@ -849,86 +709,83 @@ static bool readItemRecursive(WebCore::HistoryItem* newItem, // 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 unitTest() +static void unit_test() { - ALOGD("Entering history unit test!"); + LOGD("Entering history unit test!"); const char* test1 = new char[0]; WTF::RefPtr<WebCore::HistoryItem> item = WebCore::HistoryItem::create(); WebCore::HistoryItem* testItem = item.get(); testItem->setBridge(new WebHistoryItem(0)); - ALOG_ASSERT(!readItemRecursive(testItem, &test1, 0), "0 length array should fail!"); + LOG_ASSERT(!read_item_recursive(testItem, &test1, 0), "0 length array should fail!"); delete[] test1; const char* test2 = new char[2]; - ALOG_ASSERT(!readItemRecursive(testItem, &test2, 2), "Small array should fail!"); + LOG_ASSERT(!read_item_recursive(testItem, &test2, 2), "Small array should fail!"); delete[] test2; - ALOG_ASSERT(!readItemRecursive(testItem, NULL, HISTORY_MIN_SIZE), "Null data should fail!"); + 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; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length originalUrl should fail!"); + 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; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length url should fail!"); + 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; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length title should fail!"); + 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; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length contentType should fail!"); + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length contentType should fail!"); // Form data offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length form data should fail!"); + 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; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length target should fail!"); - offset += 4; // Screen scale - offset += 4; // Text wrap scale - offset += 4; // Scroll pos x - offset += 4; // Scroll pos y + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length target should fail!"); + offset += 4; // Scale // Document state offset += 4; memset(test3, 0, HISTORY_MIN_SIZE); ptr = (const char*)test3; *(int*)(test3 + offset) = 4000; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length document state should fail!"); + 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) = '!'; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "IsTargetItem should fail with ! as the value!"); + 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; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 kids should fail!"); + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 kids should fail!"); + offset = 36; // Test document state - offset = 40; 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; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE + sizeof(unsigned)), "1 20 length document state string should fail!"); + 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)); @@ -936,9 +793,8 @@ static void unitTest() *(int*)(test3 + offset) = 2; *(int*)(test3 + offset + 4) = 0; *(int*)(test3 + offset + 8) = 20; - ALOG_ASSERT(!readItemRecursive(testItem, &ptr, HISTORY_MIN_SIZE + 2 * sizeof(unsigned) ), "2 20 length document state string should fail!"); + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE + 2 * sizeof(unsigned) ), "2 20 length document state string should fail!"); delete[] test3; - ALOGD("Leaving history unit test!"); } #endif @@ -953,22 +809,8 @@ static JNINativeMethod gWebBackForwardListMethods[] = { }; static JNINativeMethod gWebHistoryItemMethods[] = { - { "inflate", "(I[B)I", - (void*) WebHistoryInflate }, - { "nativeRef", "(I)V", - (void*) WebHistoryRef }, - { "nativeUnref", "(I)V", - (void*) WebHistoryUnref }, - { "nativeGetTitle", "(I)Ljava/lang/String;", - (void*) WebHistoryGetTitle }, - { "nativeGetUrl", "(I)Ljava/lang/String;", - (void*) WebHistoryGetUrl }, - { "nativeGetOriginalUrl", "(I)Ljava/lang/String;", - (void*) WebHistoryGetOriginalUrl }, - { "nativeGetFlattenedData", "(I)[B", - (void*) WebHistoryGetFlattenedData }, - { "nativeGetFavicon", "(I)Landroid/graphics/Bitmap;", - (void*) WebHistoryGetFavicon }, + { "inflate", "(I[B)V", + (void*) WebHistoryInflate } }; int registerWebHistory(JNIEnv* env) @@ -976,27 +818,35 @@ int registerWebHistory(JNIEnv* env) // Get notified of all changes to history items. WebCore::notifyHistoryItemChanged = historyItemChanged; #ifdef UNIT_TEST - unitTest(); + unit_test(); #endif // Find WebHistoryItem, its constructor, and the update method. jclass clazz = env->FindClass("android/webkit/WebHistoryItem"); - ALOG_ASSERT(clazz, "Unable to find class android/webkit/WebHistoryItem"); - gWebHistoryItem.mInit = env->GetMethodID(clazz, "<init>", "(I)V"); - ALOG_ASSERT(gWebHistoryItem.mInit, "Could not find WebHistoryItem constructor"); - + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebHistoryItem"); + gWebHistoryItem.mInit = env->GetMethodID(clazz, "<init>", "()V"); + LOG_ASSERT(gWebHistoryItem.mInit, "Could not find WebHistoryItem constructor"); + gWebHistoryItem.mUpdate = env->GetMethodID(clazz, "update", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/graphics/Bitmap;[B)V"); + LOG_ASSERT(gWebHistoryItem.mUpdate, "Could not find method update in WebHistoryItem"); + + // Find the field ids for mTitle and mUrl. + gWebHistoryItem.mTitle = env->GetFieldID(clazz, "mTitle", "Ljava/lang/String;"); + LOG_ASSERT(gWebHistoryItem.mTitle, "Could not find field mTitle in WebHistoryItem"); + gWebHistoryItem.mUrl = env->GetFieldID(clazz, "mUrl", "Ljava/lang/String;"); + LOG_ASSERT(gWebHistoryItem.mUrl, "Could not find field mUrl in WebHistoryItem"); env->DeleteLocalRef(clazz); // Find the WebBackForwardList object and method. clazz = env->FindClass("android/webkit/WebBackForwardList"); - ALOG_ASSERT(clazz, "Unable to find class android/webkit/WebBackForwardList"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebBackForwardList"); gWebBackForwardList.mAddHistoryItem = env->GetMethodID(clazz, "addHistoryItem", "(Landroid/webkit/WebHistoryItem;)V"); - ALOG_ASSERT(gWebBackForwardList.mAddHistoryItem, "Could not find method addHistoryItem"); + LOG_ASSERT(gWebBackForwardList.mAddHistoryItem, "Could not find method addHistoryItem"); gWebBackForwardList.mRemoveHistoryItem = env->GetMethodID(clazz, "removeHistoryItem", "(I)V"); - ALOG_ASSERT(gWebBackForwardList.mRemoveHistoryItem, "Could not find method removeHistoryItem"); + LOG_ASSERT(gWebBackForwardList.mRemoveHistoryItem, "Could not find method removeHistoryItem"); gWebBackForwardList.mSetCurrentIndex = env->GetMethodID(clazz, "setCurrentIndex", "(I)V"); - ALOG_ASSERT(gWebBackForwardList.mSetCurrentIndex, "Could not find method setCurrentIndex"); + LOG_ASSERT(gWebBackForwardList.mSetCurrentIndex, "Could not find method setCurrentIndex"); env->DeleteLocalRef(clazz); int result = jniRegisterNativeMethods(env, "android/webkit/WebBackForwardList", diff --git a/Source/WebKit/android/jni/WebHistory.h b/Source/WebKit/android/jni/WebHistory.h index e89959c..fc0b340 100644 --- a/Source/WebKit/android/jni/WebHistory.h +++ b/Source/WebKit/android/jni/WebHistory.h @@ -28,12 +28,8 @@ #include "AndroidWebHistoryBridge.h" -#include "PlatformString.h" -#include "SkBitmap.h" - #include <jni.h> #include <wtf/RefCounted.h> -#include <wtf/Threading.h> #include <wtf/Vector.h> namespace android { @@ -42,7 +38,7 @@ class AutoJObject; class WebHistory { public: - static void Flatten(JNIEnv*, WTF::Vector<char>&, WebCore::HistoryItem*); + 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); @@ -55,36 +51,16 @@ class WebHistoryItem : public WebCore::AndroidWebHistoryBridge { public: WebHistoryItem(WebHistoryItem* parent) : WebCore::AndroidWebHistoryBridge(0) - , m_favicon(0) - , m_faviconCached(0) - , m_dataCached(0) , m_parent(parent) - {} - WebHistoryItem(WebCore::HistoryItem* item) - : WebCore::AndroidWebHistoryBridge(item) - , m_favicon(0) - , m_faviconCached(0) - , m_dataCached(0) - , m_parent(0) - {} + , m_object(NULL) { } + WebHistoryItem(JNIEnv*, jobject, WebCore::HistoryItem*); ~WebHistoryItem(); void updateHistoryItem(WebCore::HistoryItem* item); void setParent(WebHistoryItem* parent) { m_parent = parent; } WebHistoryItem* parent() const { return m_parent.get(); } - - // TODO: This is ugly. Really the whole bindings of WebHistoryItem needs to be - // cleaned up, but this will do for now - WTF::Mutex m_lock; - String m_url; - String m_originalUrl; - String m_title; - SkBitmap* m_favicon; - WTF::Vector<char> m_data; - jobject m_faviconCached; - jobject m_dataCached; - private: RefPtr<WebHistoryItem> m_parent; + jweak m_object; }; }; diff --git a/Source/WebKit/android/jni/WebIconDatabase.cpp b/Source/WebKit/android/jni/WebIconDatabase.cpp index e9219df..d5f8947 100644 --- a/Source/WebKit/android/jni/WebIconDatabase.cpp +++ b/Source/WebKit/android/jni/WebIconDatabase.cpp @@ -50,31 +50,18 @@ namespace android { -SkBitmap* webcoreImageToSkBitmap(WebCore::Image* icon) +jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon) { if (!icon) - return 0; + return NULL; + SkBitmap bm; WebCore::SharedBuffer* buffer = icon->data(); - if (!buffer) - return 0; - SkBitmap* bm = new SkBitmap; - if (!SkImageDecoder::DecodeMemory(buffer->data(), buffer->size(), bm, - SkBitmap::kNo_Config, - SkImageDecoder::kDecodePixels_Mode) - || bm->isNull() || !bm->width() || !bm->height() - || bm->config() == SkBitmap::kNo_Config) { - delete bm; - return 0; - } - return bm; -} + if (!buffer || !SkImageDecoder::DecodeMemory(buffer->data(), buffer->size(), + &bm, SkBitmap::kNo_Config, + SkImageDecoder::kDecodePixels_Mode)) + return NULL; -jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon) -{ - SkBitmap* bm = webcoreImageToSkBitmap(icon); - if (!bm) - return 0; - return GraphicsJNI::createBitmap(env, bm, false, NULL); + return GraphicsJNI::createBitmap(env, new SkBitmap(bm), false, NULL); } static WebIconDatabase* gIconDatabaseClient = new WebIconDatabase(); @@ -181,7 +168,7 @@ static void Open(JNIEnv* env, jobject obj, jstring path) return; iconDb.setEnabled(true); iconDb.setClient(gIconDatabaseClient); - ALOG_ASSERT(path, "No path given to nativeOpen"); + LOG_ASSERT(path, "No path given to nativeOpen"); WTF::String pathStr = jstringToWtfString(env, path); WTF::CString fullPath = WebCore::pathByAppendingComponent(pathStr, WebCore::IconDatabase::defaultDatabaseFilename()).utf8(); @@ -198,12 +185,12 @@ static void Open(JNIEnv* env, jobject obj, jstring path) } } if (didSetPermissions) { - ALOGV("Opening WebIconDatabase file '%s'", pathStr.latin1().data()); + LOGV("Opening WebIconDatabase file '%s'", pathStr.latin1().data()); bool res = iconDb.open(pathStr, WebCore::IconDatabase::defaultDatabaseFilename()); if (!res) - ALOGE("Open failed!"); + LOGE("Open failed!"); } else - ALOGE("Failed to set permissions on '%s'", fullPath.data()); + LOGE("Failed to set permissions on '%s'", fullPath.data()); } static void Close(JNIEnv* env, jobject obj) @@ -213,37 +200,37 @@ static void Close(JNIEnv* env, jobject obj) static void RemoveAllIcons(JNIEnv* env, jobject obj) { - ALOGV("Removing all icons"); + LOGV("Removing all icons"); WebCore::iconDatabase().removeAllIcons(); } static jobject IconForPageUrl(JNIEnv* env, jobject obj, jstring url) { - ALOG_ASSERT(url, "No url given to iconForPageUrl"); + LOG_ASSERT(url, "No url given to iconForPageUrl"); WTF::String urlStr = jstringToWtfString(env, url); // FIXME: This method should not be used from outside WebCore and will be removed. // http://trac.webkit.org/changeset/81484 WebCore::Image* icon = WebCore::iconDatabase().synchronousIconForPageURL(urlStr, WebCore::IntSize(16, 16)); - ALOGV("Retrieving icon for '%s' %p", urlStr.latin1().data(), icon); + LOGV("Retrieving icon for '%s' %p", urlStr.latin1().data(), icon); return webcoreImageToJavaBitmap(env, icon); } static void RetainIconForPageUrl(JNIEnv* env, jobject obj, jstring url) { - ALOG_ASSERT(url, "No url given to retainIconForPageUrl"); + LOG_ASSERT(url, "No url given to retainIconForPageUrl"); WTF::String urlStr = jstringToWtfString(env, url); - ALOGV("Retaining icon for '%s'", urlStr.latin1().data()); + LOGV("Retaining icon for '%s'", urlStr.latin1().data()); WebCore::iconDatabase().retainIconForPageURL(urlStr); } static void ReleaseIconForPageUrl(JNIEnv* env, jobject obj, jstring url) { - ALOG_ASSERT(url, "No url given to releaseIconForPageUrl"); + LOG_ASSERT(url, "No url given to releaseIconForPageUrl"); WTF::String urlStr = jstringToWtfString(env, url); - ALOGV("Releasing icon for '%s'", urlStr.latin1().data()); + LOGV("Releasing icon for '%s'", urlStr.latin1().data()); WebCore::iconDatabase().releaseIconForPageURL(urlStr); } @@ -268,12 +255,12 @@ static JNINativeMethod gWebIconDatabaseMethods[] = { int registerWebIconDatabase(JNIEnv* env) { #ifndef NDEBUG - jclass webIconDatabase = env->FindClass("android/webkit/WebIconDatabaseClassic"); - ALOG_ASSERT(webIconDatabase, "Unable to find class android.webkit.WebIconDatabaseClassic"); + jclass webIconDatabase = env->FindClass("android/webkit/WebIconDatabase"); + LOG_ASSERT(webIconDatabase, "Unable to find class android.webkit.WebIconDatabase"); env->DeleteLocalRef(webIconDatabase); #endif - return jniRegisterNativeMethods(env, "android/webkit/WebIconDatabaseClassic", + return jniRegisterNativeMethods(env, "android/webkit/WebIconDatabase", gWebIconDatabaseMethods, NELEM(gWebIconDatabaseMethods)); } diff --git a/Source/WebKit/android/jni/WebIconDatabase.h b/Source/WebKit/android/jni/WebIconDatabase.h index 7b7c937..3011b9f 100644 --- a/Source/WebKit/android/jni/WebIconDatabase.h +++ b/Source/WebKit/android/jni/WebIconDatabase.h @@ -28,7 +28,6 @@ #include "IconDatabaseClient.h" #include "PlatformString.h" -#include "SkBitmap.h" #include "utils/threads.h" #include <jni.h> #include <wtf/Vector.h> @@ -74,7 +73,6 @@ namespace android { bool mDeliveryRequested; }; - SkBitmap* webcoreImageToSkBitmap(WebCore::Image* icon); jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon); }; diff --git a/Source/WebKit/android/jni/WebSettings.cpp b/Source/WebKit/android/jni/WebSettings.cpp index 467da3d..dd847e8 100644 --- a/Source/WebKit/android/jni/WebSettings.cpp +++ b/Source/WebKit/android/jni/WebSettings.cpp @@ -49,7 +49,9 @@ #include "Settings.h" #include "WebCoreFrameBridge.h" #include "WebCoreJni.h" +#if USE(V8) #include "WorkerContextExecutionProxy.h" +#endif #include "WebRequestContext.h" #include "WebViewCore.h" @@ -120,9 +122,6 @@ struct FieldIds { mGeolocationEnabled = env->GetFieldID(clazz, "mGeolocationEnabled", "Z"); mGeolocationDatabasePath = env->GetFieldID(clazz, "mGeolocationDatabasePath", "Ljava/lang/String;"); mXSSAuditorEnabled = env->GetFieldID(clazz, "mXSSAuditorEnabled", "Z"); -#if ENABLE(LINK_PREFETCH) - mLinkPrefetchEnabled = env->GetFieldID(clazz, "mLinkPrefetchEnabled", "Z"); -#endif mJavaScriptCanOpenWindowsAutomatically = env->GetFieldID(clazz, "mJavaScriptCanOpenWindowsAutomatically", "Z"); mUseWideViewport = env->GetFieldID(clazz, "mUseWideViewport", "Z"); @@ -135,8 +134,8 @@ struct FieldIds { mPageCacheCapacity = env->GetFieldID(clazz, "mPageCacheCapacity", "I"); #if ENABLE(WEB_AUTOFILL) mAutoFillEnabled = env->GetFieldID(clazz, "mAutoFillEnabled", "Z"); - mAutoFillProfile = env->GetFieldID(clazz, "mAutoFillProfile", "Landroid/webkit/WebSettingsClassic$AutoFillProfile;"); - jclass autoFillProfileClass = env->FindClass("android/webkit/WebSettingsClassic$AutoFillProfile"); + mAutoFillProfile = env->GetFieldID(clazz, "mAutoFillProfile", "Landroid/webkit/WebSettings$AutoFillProfile;"); + jclass autoFillProfileClass = env->FindClass("android/webkit/WebSettings$AutoFillProfile"); mAutoFillProfileFullName = env->GetFieldID(autoFillProfileClass, "mFullName", "Ljava/lang/String;"); mAutoFillProfileEmailAddress = env->GetFieldID(autoFillProfileClass, "mEmailAddress", "Ljava/lang/String;"); mAutoFillProfileCompanyName = env->GetFieldID(autoFillProfileClass, "mCompanyName", "Ljava/lang/String;"); @@ -149,57 +148,57 @@ struct FieldIds { mAutoFillProfilePhoneNumber = env->GetFieldID(autoFillProfileClass, "mPhoneNumber", "Ljava/lang/String;"); env->DeleteLocalRef(autoFillProfileClass); #endif +#if USE(CHROME_NETWORK_STACK) mOverrideCacheMode = env->GetFieldID(clazz, "mOverrideCacheMode", "I"); - mPasswordEchoEnabled = env->GetFieldID(clazz, "mPasswordEchoEnabled", "Z"); - - ALOG_ASSERT(mLayoutAlgorithm, "Could not find field mLayoutAlgorithm"); - ALOG_ASSERT(mTextSize, "Could not find field mTextSize"); - ALOG_ASSERT(mStandardFontFamily, "Could not find field mStandardFontFamily"); - ALOG_ASSERT(mFixedFontFamily, "Could not find field mFixedFontFamily"); - ALOG_ASSERT(mSansSerifFontFamily, "Could not find field mSansSerifFontFamily"); - ALOG_ASSERT(mSerifFontFamily, "Could not find field mSerifFontFamily"); - ALOG_ASSERT(mCursiveFontFamily, "Could not find field mCursiveFontFamily"); - ALOG_ASSERT(mFantasyFontFamily, "Could not find field mFantasyFontFamily"); - ALOG_ASSERT(mDefaultTextEncoding, "Could not find field mDefaultTextEncoding"); - ALOG_ASSERT(mGetUserAgentString, "Could not find method getUserAgentString"); - ALOG_ASSERT(mGetAcceptLanguage, "Could not find method getAcceptLanguage"); - ALOG_ASSERT(mMinimumFontSize, "Could not find field mMinimumFontSize"); - ALOG_ASSERT(mMinimumLogicalFontSize, "Could not find field mMinimumLogicalFontSize"); - ALOG_ASSERT(mDefaultFontSize, "Could not find field mDefaultFontSize"); - ALOG_ASSERT(mDefaultFixedFontSize, "Could not find field mDefaultFixedFontSize"); - ALOG_ASSERT(mLoadsImagesAutomatically, "Could not find field mLoadsImagesAutomatically"); +#endif + + LOG_ASSERT(mLayoutAlgorithm, "Could not find field mLayoutAlgorithm"); + LOG_ASSERT(mTextSize, "Could not find field mTextSize"); + LOG_ASSERT(mStandardFontFamily, "Could not find field mStandardFontFamily"); + LOG_ASSERT(mFixedFontFamily, "Could not find field mFixedFontFamily"); + LOG_ASSERT(mSansSerifFontFamily, "Could not find field mSansSerifFontFamily"); + LOG_ASSERT(mSerifFontFamily, "Could not find field mSerifFontFamily"); + LOG_ASSERT(mCursiveFontFamily, "Could not find field mCursiveFontFamily"); + LOG_ASSERT(mFantasyFontFamily, "Could not find field mFantasyFontFamily"); + LOG_ASSERT(mDefaultTextEncoding, "Could not find field mDefaultTextEncoding"); + LOG_ASSERT(mGetUserAgentString, "Could not find method getUserAgentString"); + LOG_ASSERT(mGetAcceptLanguage, "Could not find method getAcceptLanguage"); + 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 - ALOG_ASSERT(mBlockNetworkImage, "Could not find field mBlockNetworkImage"); + LOG_ASSERT(mBlockNetworkImage, "Could not find field mBlockNetworkImage"); #endif - ALOG_ASSERT(mBlockNetworkLoads, "Could not find field mBlockNetworkLoads"); - ALOG_ASSERT(mJavaScriptEnabled, "Could not find field mJavaScriptEnabled"); - ALOG_ASSERT(mAllowUniversalAccessFromFileURLs, + LOG_ASSERT(mBlockNetworkLoads, "Could not find field mBlockNetworkLoads"); + LOG_ASSERT(mJavaScriptEnabled, "Could not find field mJavaScriptEnabled"); + LOG_ASSERT(mAllowUniversalAccessFromFileURLs, "Could not find field mAllowUniversalAccessFromFileURLs"); - ALOG_ASSERT(mAllowFileAccessFromFileURLs, + LOG_ASSERT(mAllowFileAccessFromFileURLs, "Could not find field mAllowFileAccessFromFileURLs"); - ALOG_ASSERT(mPluginState, "Could not find field mPluginState"); + LOG_ASSERT(mPluginState, "Could not find field mPluginState"); #if ENABLE(OFFLINE_WEB_APPLICATIONS) - ALOG_ASSERT(mAppCacheEnabled, "Could not find field mAppCacheEnabled"); - ALOG_ASSERT(mAppCachePath, "Could not find field mAppCachePath"); - ALOG_ASSERT(mAppCacheMaxSize, "Could not find field mAppCacheMaxSize"); + LOG_ASSERT(mAppCacheEnabled, "Could not find field mAppCacheEnabled"); + LOG_ASSERT(mAppCachePath, "Could not find field mAppCachePath"); + LOG_ASSERT(mAppCacheMaxSize, "Could not find field mAppCacheMaxSize"); #endif #if ENABLE(WORKERS) - ALOG_ASSERT(mWorkersEnabled, "Could not find field mWorkersEnabled"); + LOG_ASSERT(mWorkersEnabled, "Could not find field mWorkersEnabled"); #endif - ALOG_ASSERT(mJavaScriptCanOpenWindowsAutomatically, + LOG_ASSERT(mJavaScriptCanOpenWindowsAutomatically, "Could not find field mJavaScriptCanOpenWindowsAutomatically"); - ALOG_ASSERT(mUseWideViewport, "Could not find field mUseWideViewport"); - ALOG_ASSERT(mSupportMultipleWindows, "Could not find field mSupportMultipleWindows"); - ALOG_ASSERT(mShrinksStandaloneImagesToFit, "Could not find field mShrinksStandaloneImagesToFit"); - ALOG_ASSERT(mMaximumDecodedImageSize, "Could not find field mMaximumDecodedImageSize"); - ALOG_ASSERT(mUseDoubleTree, "Could not find field mUseDoubleTree"); - ALOG_ASSERT(mPageCacheCapacity, "Could not find field mPageCacheCapacity"); - ALOG_ASSERT(mPasswordEchoEnabled, "Could not find field mPasswordEchoEnabled"); + LOG_ASSERT(mUseWideViewport, "Could not find field mUseWideViewport"); + LOG_ASSERT(mSupportMultipleWindows, "Could not find field mSupportMultipleWindows"); + LOG_ASSERT(mShrinksStandaloneImagesToFit, "Could not find field mShrinksStandaloneImagesToFit"); + LOG_ASSERT(mMaximumDecodedImageSize, "Could not find field mMaximumDecodedImageSize"); + LOG_ASSERT(mUseDoubleTree, "Could not find field mUseDoubleTree"); + LOG_ASSERT(mPageCacheCapacity, "Could not find field mPageCacheCapacity"); jclass enumClass = env->FindClass("java/lang/Enum"); - ALOG_ASSERT(enumClass, "Could not find Enum class!"); + LOG_ASSERT(enumClass, "Could not find Enum class!"); mOrdinal = env->GetMethodID(enumClass, "ordinal", "()I"); - ALOG_ASSERT(mOrdinal, "Could not find method ordinal"); + LOG_ASSERT(mOrdinal, "Could not find method ordinal"); env->DeleteLocalRef(enumClass); } @@ -258,9 +257,6 @@ struct FieldIds { jfieldID mGeolocationEnabled; jfieldID mGeolocationDatabasePath; jfieldID mXSSAuditorEnabled; -#if ENABLE(LINK_PREFETCH) - jfieldID mLinkPrefetchEnabled; -#endif #if ENABLE(DATABASE) || ENABLE(DOM_STORAGE) jfieldID mDatabasePath; jfieldID mDatabasePathHasBeenSet; @@ -279,8 +275,9 @@ struct FieldIds { jfieldID mAutoFillProfileCountry; jfieldID mAutoFillProfilePhoneNumber; #endif +#if USE(CHROME_NETWORK_STACK) jfieldID mOverrideCacheMode; - jfieldID mPasswordEchoEnabled; +#endif }; static struct FieldIds* gFieldIds; @@ -326,7 +323,7 @@ public: static void Sync(JNIEnv* env, jobject obj, jint frame) { WebCore::Frame* pFrame = (WebCore::Frame*)frame; - ALOG_ASSERT(pFrame, "%s must take a valid frame pointer!", __FUNCTION__); + LOG_ASSERT(pFrame, "%s must take a valid frame pointer!", __FUNCTION__); WebCore::Settings* s = pFrame->settings(); if (!s) return; @@ -342,7 +339,7 @@ public: pFrame->document()->styleSelectorChanged(WebCore::RecalcStyleImmediately); if (pFrame->document()->renderer()) { recursiveCleanupForFullLayout(pFrame->document()->renderer()); - ALOG_ASSERT(pFrame->view(), "No view for this frame when trying to relayout"); + 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 @@ -380,6 +377,7 @@ public: str = (jstring)env->CallObjectMethod(obj, gFieldIds->mGetUserAgentString); WebFrame::getWebFrame(pFrame)->setUserAgent(jstringToWtfString(env, str)); +#if USE(CHROME_NETWORK_STACK) WebViewCore::getWebViewCore(pFrame->view())->setWebRequestContextUserAgent(); jint cacheMode = env->GetIntField(obj, gFieldIds->mOverrideCacheMode); @@ -387,6 +385,7 @@ public: str = (jstring)env->CallObjectMethod(obj, gFieldIds->mGetAcceptLanguage); WebRequestContext::setAcceptLanguage(jstringToWtfString(env, str)); +#endif jint size = env->GetIntField(obj, gFieldIds->mMinimumFontSize); s->setMinimumFontSize(size); @@ -424,11 +423,6 @@ public: flag = env->GetBooleanField(obj, gFieldIds->mAllowFileAccessFromFileURLs); s->setAllowFileAccessFromFileURLs(flag); - // Hyperlink auditing (the ping attribute) has similar privacy - // considerations as does the running of JavaScript, so to keep the UI - // simpler, we leverage the same setting. - s->setHyperlinkAuditingEnabled(flag); - // ON = 0 // ON_DEMAND = 1 // OFF = 2 @@ -440,38 +434,22 @@ public: #endif #if ENABLE(OFFLINE_WEB_APPLICATIONS) - // We only enable AppCache if it's been enabled with a call to - // setAppCacheEnabled() and if a valid path has been supplied to - // setAppCachePath(). Note that the path is applied to all WebViews - // whereas enabling is applied per WebView. - - // WebCore asserts that the path is only set once. Since the path is - // shared between WebViews, we can't do the required checks to guard - // against this in the Java WebSettings. - bool isPathValid = false; - if (cacheStorage().cacheDirectory().isNull()) { - str = static_cast<jstring>(env->GetObjectField(obj, gFieldIds->mAppCachePath)); - // Check for non-null string as an optimization, as this is the common case. - if (str) { - String path = jstringToWtfString(env, str); - ALOG_ASSERT(!path.empty(), "Java side should never send empty string for AppCache path"); + flag = env->GetBooleanField(obj, gFieldIds->mAppCacheEnabled); + s->setOfflineWebApplicationCacheEnabled(flag); + str = (jstring)env->GetObjectField(obj, gFieldIds->mAppCachePath); + if (str) { + String path = jstringToWtfString(env, str); + if (path.length() && cacheStorage().cacheDirectory().isNull()) { + cacheStorage().setCacheDirectory(path); // This database is created on the first load. If the file // doesn't exist, we create it and set its permissions. The // filename must match that in ApplicationCacheStorage.cpp. String filename = pathByAppendingComponent(path, "ApplicationCache.db"); - int fd = open(filename.utf8().data(), O_CREAT, permissionFlags660); - if (fd >= 0) { + int fd = open(filename.utf8().data(), O_CREAT | O_EXCL, permissionFlags660); + if (fd >= 0) close(fd); - cacheStorage().setCacheDirectory(path); - isPathValid = true; - } } - } else - isPathValid = true; - - flag = env->GetBooleanField(obj, gFieldIds->mAppCacheEnabled); - s->setOfflineWebApplicationCacheEnabled(flag && isPathValid); - + } jlong maxsize = env->GetLongField(obj, gFieldIds->mAppCacheMaxSize); cacheStorage().setMaximumSize(maxsize); #endif @@ -569,11 +547,6 @@ public: flag = env->GetBooleanField(obj, gFieldIds->mXSSAuditorEnabled); s->setXSSAuditorEnabled(flag); -#if ENABLE(LINK_PREFETCH) - flag = env->GetBooleanField(obj, gFieldIds->mLinkPrefetchEnabled); - s->setLinkPrefetchEnabled(flag); -#endif - size = env->GetIntField(obj, gFieldIds->mPageCacheCapacity); if (size > 0) { s->setUsesPageCache(true); @@ -612,10 +585,6 @@ public: // This is required to enable the XMLTreeViewer when loading an XML document that // has no style attached to it. http://trac.webkit.org/changeset/79799 s->setDeveloperExtrasEnabled(true); - s->setSpatialNavigationEnabled(true); - bool echoPassword = env->GetBooleanField(obj, - gFieldIds->mPasswordEchoEnabled); - s->setPasswordEchoEnabled(echoPassword); } }; @@ -631,11 +600,11 @@ static JNINativeMethod gWebSettingsMethods[] = { int registerWebSettings(JNIEnv* env) { - jclass clazz = env->FindClass("android/webkit/WebSettingsClassic"); - ALOG_ASSERT(clazz, "Unable to find class WebSettingsClassic!"); + jclass clazz = env->FindClass("android/webkit/WebSettings"); + LOG_ASSERT(clazz, "Unable to find class WebSettings!"); gFieldIds = new FieldIds(env, clazz); env->DeleteLocalRef(clazz); - return jniRegisterNativeMethods(env, "android/webkit/WebSettingsClassic", + return jniRegisterNativeMethods(env, "android/webkit/WebSettings", gWebSettingsMethods, NELEM(gWebSettingsMethods)); } diff --git a/Source/WebKit/android/jni/WebStorage.cpp b/Source/WebKit/android/jni/WebStorage.cpp index 66a3517..9ce207d 100644 --- a/Source/WebKit/android/jni/WebStorage.cpp +++ b/Source/WebKit/android/jni/WebStorage.cpp @@ -174,12 +174,12 @@ static JNINativeMethod gWebStorageMethods[] = { int registerWebStorage(JNIEnv* env) { #ifndef NDEBUG - jclass webStorage = env->FindClass("android/webkit/WebStorageClassic"); - ALOG_ASSERT(webStorage, "Unable to find class android.webkit.WebStorageClassic"); + jclass webStorage = env->FindClass("android/webkit/WebStorage"); + LOG_ASSERT(webStorage, "Unable to find class android.webkit.WebStorage"); env->DeleteLocalRef(webStorage); #endif - return jniRegisterNativeMethods(env, "android/webkit/WebStorageClassic", + return jniRegisterNativeMethods(env, "android/webkit/WebStorage", gWebStorageMethods, NELEM(gWebStorageMethods)); } diff --git a/Source/WebKit/android/jni/WebViewCore.cpp b/Source/WebKit/android/jni/WebViewCore.cpp index f17e573..1b53a6e 100644 --- a/Source/WebKit/android/jni/WebViewCore.cpp +++ b/Source/WebKit/android/jni/WebViewCore.cpp @@ -29,9 +29,10 @@ #include "WebViewCore.h" #include "AccessibilityObject.h" -#include "AndroidHitTestResult.h" #include "Attribute.h" -#include "content/address_detector.h" +#include "BaseLayerAndroid.h" +#include "CachedNode.h" +#include "CachedRoot.h" #include "Chrome.h" #include "ChromeClientAndroid.h" #include "ChromiumIncludes.h" @@ -42,7 +43,6 @@ #include "CSSValueKeywords.h" #include "DatabaseTracker.h" #include "Document.h" -#include "DocumentMarkerController.h" #include "DOMWindow.h" #include "DOMSelection.h" #include "Element.h" @@ -53,7 +53,6 @@ #include "ExceptionCode.h" #include "FocusController.h" #include "Font.h" -#include "FontCache.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClientAndroid.h" @@ -62,7 +61,6 @@ #include "Geolocation.h" #include "GraphicsContext.h" #include "GraphicsJNI.h" -#include "GraphicsOperationCollection.h" #include "HTMLAnchorElement.h" #include "HTMLAreaElement.h" #include "HTMLElement.h" @@ -80,7 +78,6 @@ #include "HitTestRequest.h" #include "HitTestResult.h" #include "InlineTextBox.h" -#include "KeyboardEvent.h" #include "MemoryUsage.h" #include "NamedNodeMap.h" #include "Navigator.h" @@ -88,8 +85,6 @@ #include "NodeList.h" #include "Page.h" #include "PageGroup.h" -#include "PictureLayerContent.h" -#include "PicturePileLayerContent.h" #include "PlatformKeyboardEvent.h" #include "PlatformString.h" #include "PluginWidgetAndroid.h" @@ -98,7 +93,6 @@ #include "ProgressTracker.h" #include "Range.h" #include "RenderBox.h" -#include "RenderImage.h" #include "RenderInline.h" #include "RenderLayer.h" #include "RenderPart.h" @@ -109,44 +103,39 @@ #include "ResourceRequest.h" #include "RuntimeEnabledFeatures.h" #include "SchemeRegistry.h" -#include "ScopedLocalRef.h" -#include "ScriptController.h" #include "SelectionController.h" -#include "SelectText.h" #include "Settings.h" #include "SkANP.h" #include "SkTemplates.h" #include "SkTDArray.h" #include "SkTypes.h" #include "SkCanvas.h" -#include "SkGraphics.h" #include "SkPicture.h" #include "SkUtils.h" #include "Text.h" -#include "TextIterator.h" -#include "TilesManager.h" #include "TypingCommand.h" #include "WebCache.h" #include "WebCoreFrameBridge.h" -#include "WebCoreJni.h" #include "WebFrameView.h" #include "WindowsKeyboardCodes.h" #include "android_graphics.h" #include "autofill/WebAutofill.h" #include "htmlediting.h" #include "markup.h" -#include "visible_units.h" #include <JNIHelp.h> #include <JNIUtility.h> -#include <androidfw/KeycodeLabels.h> -#include <cutils/properties.h> -#include <v8.h> +#include <ui/KeycodeLabels.h> #include <wtf/CurrentTime.h> #include <wtf/text/AtomicString.h> -#include <wtf/text/CString.h> #include <wtf/text/StringImpl.h> +#if USE(V8) +#include "ScriptController.h" +#include "V8Counters.h" +#include <wtf/text/CString.h> +#endif + #if DEBUG_NAV_UI #include "SkTime.h" #endif @@ -164,85 +153,34 @@ FILE* gDomTreeFile = 0; FILE* gRenderTreeFile = 0; #endif -#include "BaseLayerAndroid.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif #if USE(ACCELERATED_COMPOSITING) #include "GraphicsLayerAndroid.h" #include "RenderLayerCompositor.h" #endif -#define FOREGROUND_TIMER_INTERVAL 0.004 // 4ms -#define BACKGROUND_TIMER_INTERVAL 1.0 // 1s +#if USE(V8) +#include <v8.h> +#endif -// How many ms to wait for the scroll to "settle" before we will consider doing -// prerenders -#define PRERENDER_AFTER_SCROLL_DELAY 750 +// In some cases, too many invalidations passed to the UI will slow us down. +// Limit ourselves to 32 rectangles, past this just send the area bounds to the UI. +// see WebViewCore::recordPictureSet(). +#define MAX_INVALIDATIONS 32 -#define TOUCH_FLAG_HIT_HANDLER 0x1 -#define TOUCH_FLAG_PREVENT_DEFAULT 0x2 +/* 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 { -// Copied from CacheBuilder, not sure if this is needed/correct -IntRect getAreaRect(const HTMLAreaElement* area) -{ - Node* node = area->document(); - while ((node = node->traverseNextNode()) != NULL) { - RenderObject* renderer = node->renderer(); - if (renderer && renderer->isRenderImage()) { - RenderImage* image = static_cast<RenderImage*>(renderer); - HTMLMapElement* map = image->imageMap(); - if (map) { - Node* n; - for (n = map->firstChild(); n; - n = n->traverseNextNode(map)) { - if (n == area) { - if (area->isDefault()) - return image->absoluteBoundingBoxRect(); - return area->computeRect(image); - } - } - } - } - } - return IntRect(); -} - -// Copied from CacheBuilder, not sure if this is needed/correct -// TODO: See if this is even needed (I suspect not), and if not remove it -bool validNode(Frame* startFrame, void* matchFrame, - void* matchNode) -{ - if (matchFrame == startFrame) { - if (matchNode == NULL) - return true; - Node* node = startFrame->document(); - while (node != NULL) { - if (node == matchNode) { - const IntRect& rect = node->hasTagName(HTMLNames::areaTag) ? - getAreaRect(static_cast<HTMLAreaElement*>(node)) : node->getRect(); - // Consider nodes with empty rects that are not at the origin - // to be valid, since news.google.com has valid nodes like this - if (rect.x() == 0 && rect.y() == 0 && rect.isEmpty()) - return false; - return true; - } - node = node->traverseNextNode(); - } - return false; - } - Frame* child = startFrame->tree()->firstChild(); - while (child) { - bool result = validNode(child, matchFrame, matchNode); - if (result) - return result; - child = child->tree()->nextSibling(); - } - return false; -} - static SkTDArray<WebViewCore*> gInstanceList; void WebViewCore::addInstance(WebViewCore* inst) { @@ -251,7 +189,7 @@ void WebViewCore::addInstance(WebViewCore* inst) { void WebViewCore::removeInstance(WebViewCore* inst) { int index = gInstanceList.find(inst); - ALOG_ASSERT(index >= 0, "RemoveInstance inst not found"); + LOG_ASSERT(index >= 0, "RemoveInstance inst not found"); if (index >= 0) { gInstanceList.removeShuffle(index); } @@ -304,6 +242,8 @@ bool WebViewCore::isSupportedMediaMimeType(const WTF::String& mimeType) { // ---------------------------------------------------------------------------- +#define GET_NATIVE_VIEW(env, obj) ((WebViewCore*)env->GetIntField(obj, gWebViewCoreFields.m_nativeClass)) + // Field ids for WebViewCore struct WebViewCoreFields { jfieldID m_nativeClass; @@ -314,6 +254,7 @@ struct WebViewCoreFields { jfieldID m_viewportMaximumScale; jfieldID m_viewportUserScalable; jfieldID m_viewportDensityDpi; + jfieldID m_webView; jfieldID m_drawIsPaused; jfieldID m_lowMemoryUsageMb; jfieldID m_highMemoryUsageMb; @@ -326,6 +267,7 @@ struct WebViewCore::JavaGlue { jweak m_obj; jmethodID m_scrollTo; jmethodID m_contentDraw; + jmethodID m_layersDraw; jmethodID m_requestListBox; jmethodID m_openFileChooser; jmethodID m_requestSingleListBox; @@ -334,18 +276,17 @@ struct WebViewCore::JavaGlue { jmethodID m_jsPrompt; jmethodID m_jsUnload; jmethodID m_jsInterrupt; - jmethodID m_getWebView; jmethodID m_didFirstLayout; jmethodID m_updateViewport; jmethodID m_sendNotifyProgressFinished; jmethodID m_sendViewInvalidate; jmethodID m_updateTextfield; jmethodID m_updateTextSelection; - jmethodID m_updateTextSizeAndScroll; jmethodID m_clearTextEntry; jmethodID m_restoreScale; jmethodID m_needTouchEvents; jmethodID m_requestKeyboard; + jmethodID m_requestKeyboardWithSelection; jmethodID m_exceededDatabaseQuota; jmethodID m_reachedMaxAppCacheSize; jmethodID m_populateVisitedLinks; @@ -354,7 +295,7 @@ struct WebViewCore::JavaGlue { jmethodID m_getDeviceMotionService; jmethodID m_getDeviceOrientationService; jmethodID m_addMessageToConsole; - jmethodID m_focusNodeChanged; + jmethodID m_formDidBlur; jmethodID m_getPluginClass; jmethodID m_showFullScreenPlugin; jmethodID m_hideFullScreenPlugin; @@ -364,17 +305,14 @@ struct WebViewCore::JavaGlue { jmethodID m_destroySurface; jmethodID m_getContext; jmethodID m_keepScreenOn; + jmethodID m_sendFindAgain; jmethodID m_showRect; jmethodID m_centerFitRect; jmethodID m_setScrollbarModes; jmethodID m_setInstallableWebApp; jmethodID m_enterFullscreenForVideoLayer; - jmethodID m_exitFullscreenVideo; jmethodID m_setWebTextViewAutoFillable; jmethodID m_selectAt; - jmethodID m_initEditField; - jmethodID m_chromeCanTakeFocus; - jmethodID m_chromeTakeFocus; AutoJObject object(JNIEnv* env) { // We hold a weak reference to the Java WebViewCore to avoid memeory // leaks due to circular references when WebView.destroy() is not @@ -387,23 +325,6 @@ struct WebViewCore::JavaGlue { } }; -struct WebViewCore::TextFieldInitDataGlue { - jmethodID m_constructor; - jfieldID m_fieldPointer; - jfieldID m_text; - jfieldID m_type; - jfieldID m_isSpellCheckEnabled; - jfieldID m_isTextFieldNext; - jfieldID m_isTextFieldPrev; - jfieldID m_isAutoCompleteEnabled; - jfieldID m_name; - jfieldID m_label; - jfieldID m_maxLength; - jfieldID m_contentBounds; - jfieldID m_nodeLayerId; - jfieldID m_contentRect; -}; - /* * WebViewCore Implementation */ @@ -411,37 +332,58 @@ struct WebViewCore::TextFieldInitDataGlue { static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) { jmethodID m = env->GetMethodID(clazz, name, signature); - ALOG_ASSERT(m, "Could not find method %s", name); + LOG_ASSERT(m, "Could not find method %s", name); return m; } +Mutex WebViewCore::gFrameCacheMutex; +Mutex WebViewCore::gCursorBoundsMutex; + WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* mainframe) - : m_touchGeneration(0) + : m_frameCacheKit(0) + , m_navPictureKit(0) + , m_moveGeneration(0) + , m_touchGeneration(0) , m_lastGeneration(0) + , m_updatedFrameCache(true) + , m_findIsUp(false) + , m_hasCursorBounds(false) + , m_cursorBounds(WebCore::IntRect(0, 0, 0, 0)) + , m_cursorHitBounds(WebCore::IntRect(0, 0, 0, 0)) + , m_cursorFrame(0) + , m_cursorLocation(WebCore::IntPoint(0, 0)) + , m_cursorNode(0) , m_javaGlue(new JavaGlue) - , m_textFieldInitDataGlue(new TextFieldInitDataGlue) , m_mainFrame(mainframe) , m_popupReply(0) + , m_lastFocused(0) + , m_lastFocusedBounds(WebCore::IntRect(0,0,0,0)) + , m_blurringNodePointer(0) + , m_lastFocusedSelStart(0) + , m_lastFocusedSelEnd(0) , m_blockTextfieldUpdates(false) , m_focusBoundsChanged(false) , m_skipContentDraw(false) , m_textGeneration(0) + , m_temp(0) + , m_tempPict(0) , m_maxXScroll(320/4) , m_maxYScroll(240/4) , m_scrollOffsetX(0) , m_scrollOffsetY(0) , m_mousePos(WebCore::IntPoint(0,0)) + , m_frameCacheOutOfDate(true) + , m_progressDone(false) , m_screenWidth(320) , m_screenHeight(240) , m_textWrapWidth(320) , m_scale(1.0f) + , m_domtree_version(0) + , m_check_domtree_version(true) , m_groupForVisitedLinks(0) , m_isPaused(false) , m_cacheMode(0) - , m_fullscreenVideoMode(false) - , m_matchCount(0) - , m_activeMatchIndex(0) - , m_activeMatch(0) + , m_shouldPaintCaret(true) , m_pluginInvalTimer(this, &WebViewCore::pluginInvalTimerFired) , m_screenOnCounter(0) , m_currentNodeDomNavigationAxis(0) @@ -449,35 +391,36 @@ WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* m #if ENABLE(TOUCH_EVENTS) , m_forwardingTouchEvents(false) #endif +#if USE(CHROME_NETWORK_STACK) , m_webRequestContext(0) - , m_prerenderEnabled(false) +#endif { - ALOG_ASSERT(m_mainFrame, "Uh oh, somehow a frameview was made without an initial frame!"); + LOG_ASSERT(m_mainFrame, "Uh oh, somehow a frameview was made without an initial frame!"); jclass clazz = env->GetObjectClass(javaWebViewCore); m_javaGlue->m_obj = env->NewWeakGlobalRef(javaWebViewCore); m_javaGlue->m_scrollTo = GetJMethod(env, clazz, "contentScrollTo", "(IIZZ)V"); m_javaGlue->m_contentDraw = GetJMethod(env, clazz, "contentDraw", "()V"); + m_javaGlue->m_layersDraw = GetJMethod(env, clazz, "layersDraw", "()V"); m_javaGlue->m_requestListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[I[I)V"); - m_javaGlue->m_openFileChooser = GetJMethod(env, clazz, "openFileChooser", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + m_javaGlue->m_openFileChooser = GetJMethod(env, clazz, "openFileChooser", "(Ljava/lang/String;)Ljava/lang/String;"); m_javaGlue->m_requestSingleListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[II)V"); m_javaGlue->m_jsAlert = GetJMethod(env, clazz, "jsAlert", "(Ljava/lang/String;Ljava/lang/String;)V"); m_javaGlue->m_jsConfirm = GetJMethod(env, clazz, "jsConfirm", "(Ljava/lang/String;Ljava/lang/String;)Z"); m_javaGlue->m_jsPrompt = GetJMethod(env, clazz, "jsPrompt", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); m_javaGlue->m_jsUnload = GetJMethod(env, clazz, "jsUnload", "(Ljava/lang/String;Ljava/lang/String;)Z"); m_javaGlue->m_jsInterrupt = GetJMethod(env, clazz, "jsInterrupt", "()Z"); - m_javaGlue->m_getWebView = GetJMethod(env, clazz, "getWebView", "()Landroid/webkit/WebView;"); m_javaGlue->m_didFirstLayout = GetJMethod(env, clazz, "didFirstLayout", "(Z)V"); m_javaGlue->m_updateViewport = GetJMethod(env, clazz, "updateViewport", "()V"); m_javaGlue->m_sendNotifyProgressFinished = GetJMethod(env, clazz, "sendNotifyProgressFinished", "()V"); m_javaGlue->m_sendViewInvalidate = GetJMethod(env, clazz, "sendViewInvalidate", "(IIII)V"); m_javaGlue->m_updateTextfield = GetJMethod(env, clazz, "updateTextfield", "(IZLjava/lang/String;I)V"); - m_javaGlue->m_updateTextSelection = GetJMethod(env, clazz, "updateTextSelection", "(IIIII)V"); - m_javaGlue->m_updateTextSizeAndScroll = GetJMethod(env, clazz, "updateTextSizeAndScroll", "(IIIII)V"); + m_javaGlue->m_updateTextSelection = GetJMethod(env, clazz, "updateTextSelection", "(IIII)V"); m_javaGlue->m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()V"); m_javaGlue->m_restoreScale = GetJMethod(env, clazz, "restoreScale", "(FF)V"); m_javaGlue->m_needTouchEvents = GetJMethod(env, clazz, "needTouchEvents", "(Z)V"); m_javaGlue->m_requestKeyboard = GetJMethod(env, clazz, "requestKeyboard", "(Z)V"); + m_javaGlue->m_requestKeyboardWithSelection = GetJMethod(env, clazz, "requestKeyboardWithSelection", "(IIII)V"); m_javaGlue->m_exceededDatabaseQuota = GetJMethod(env, clazz, "exceededDatabaseQuota", "(Ljava/lang/String;Ljava/lang/String;JJ)V"); m_javaGlue->m_reachedMaxAppCacheSize = GetJMethod(env, clazz, "reachedMaxAppCacheSize", "(J)V"); m_javaGlue->m_populateVisitedLinks = GetJMethod(env, clazz, "populateVisitedLinks", "()V"); @@ -486,7 +429,7 @@ WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* m m_javaGlue->m_getDeviceMotionService = GetJMethod(env, clazz, "getDeviceMotionService", "()Landroid/webkit/DeviceMotionService;"); m_javaGlue->m_getDeviceOrientationService = GetJMethod(env, clazz, "getDeviceOrientationService", "()Landroid/webkit/DeviceOrientationService;"); m_javaGlue->m_addMessageToConsole = GetJMethod(env, clazz, "addMessageToConsole", "(Ljava/lang/String;ILjava/lang/String;I)V"); - m_javaGlue->m_focusNodeChanged = GetJMethod(env, clazz, "focusNodeChanged", "(ILandroid/webkit/WebViewCore$WebKitHitTest;)V"); + m_javaGlue->m_formDidBlur = GetJMethod(env, clazz, "formDidBlur", "(I)V"); m_javaGlue->m_getPluginClass = GetJMethod(env, clazz, "getPluginClass", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;"); m_javaGlue->m_showFullScreenPlugin = GetJMethod(env, clazz, "showFullScreenPlugin", "(Landroid/webkit/ViewManager$ChildView;II)V"); m_javaGlue->m_hideFullScreenPlugin = GetJMethod(env, clazz, "hideFullScreenPlugin", "()V"); @@ -496,40 +439,20 @@ WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* m m_javaGlue->m_destroySurface = GetJMethod(env, clazz, "destroySurface", "(Landroid/webkit/ViewManager$ChildView;)V"); m_javaGlue->m_getContext = GetJMethod(env, clazz, "getContext", "()Landroid/content/Context;"); m_javaGlue->m_keepScreenOn = GetJMethod(env, clazz, "keepScreenOn", "(Z)V"); + m_javaGlue->m_sendFindAgain = GetJMethod(env, clazz, "sendFindAgain", "()V"); m_javaGlue->m_showRect = GetJMethod(env, clazz, "showRect", "(IIIIIIFFFF)V"); m_javaGlue->m_centerFitRect = GetJMethod(env, clazz, "centerFitRect", "(IIII)V"); m_javaGlue->m_setScrollbarModes = GetJMethod(env, clazz, "setScrollbarModes", "(II)V"); m_javaGlue->m_setInstallableWebApp = GetJMethod(env, clazz, "setInstallableWebApp", "()V"); #if ENABLE(VIDEO) m_javaGlue->m_enterFullscreenForVideoLayer = GetJMethod(env, clazz, "enterFullscreenForVideoLayer", "(ILjava/lang/String;)V"); - m_javaGlue->m_exitFullscreenVideo = GetJMethod(env, clazz, "exitFullscreenVideo", "()V"); #endif m_javaGlue->m_setWebTextViewAutoFillable = GetJMethod(env, clazz, "setWebTextViewAutoFillable", "(ILjava/lang/String;)V"); m_javaGlue->m_selectAt = GetJMethod(env, clazz, "selectAt", "(II)V"); - m_javaGlue->m_initEditField = GetJMethod(env, clazz, "initEditField", "(IIILandroid/webkit/WebViewCore$TextFieldInitData;)V"); - m_javaGlue->m_chromeCanTakeFocus = GetJMethod(env, clazz, "chromeCanTakeFocus", "(I)Z"); - m_javaGlue->m_chromeTakeFocus = GetJMethod(env, clazz, "chromeTakeFocus", "(I)V"); env->DeleteLocalRef(clazz); env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this); - jclass tfidClazz = env->FindClass("android/webkit/WebViewCore$TextFieldInitData"); - m_textFieldInitDataGlue->m_fieldPointer = env->GetFieldID(tfidClazz, "mFieldPointer", "I"); - m_textFieldInitDataGlue->m_text = env->GetFieldID(tfidClazz, "mText", "Ljava/lang/String;"); - m_textFieldInitDataGlue->m_type = env->GetFieldID(tfidClazz, "mType", "I"); - m_textFieldInitDataGlue->m_isSpellCheckEnabled = env->GetFieldID(tfidClazz, "mIsSpellCheckEnabled", "Z"); - m_textFieldInitDataGlue->m_isTextFieldNext = env->GetFieldID(tfidClazz, "mIsTextFieldNext", "Z"); - m_textFieldInitDataGlue->m_isTextFieldPrev = env->GetFieldID(tfidClazz, "mIsTextFieldPrev", "Z"); - m_textFieldInitDataGlue->m_isAutoCompleteEnabled = env->GetFieldID(tfidClazz, "mIsAutoCompleteEnabled", "Z"); - m_textFieldInitDataGlue->m_name = env->GetFieldID(tfidClazz, "mName", "Ljava/lang/String;"); - m_textFieldInitDataGlue->m_label = env->GetFieldID(tfidClazz, "mLabel", "Ljava/lang/String;"); - m_textFieldInitDataGlue->m_maxLength = env->GetFieldID(tfidClazz, "mMaxLength", "I"); - m_textFieldInitDataGlue->m_contentBounds = env->GetFieldID(tfidClazz, "mContentBounds", "Landroid/graphics/Rect;"); - m_textFieldInitDataGlue->m_nodeLayerId = env->GetFieldID(tfidClazz, "mNodeLayerId", "I"); - m_textFieldInitDataGlue->m_contentRect = env->GetFieldID(tfidClazz, "mContentRect", "Landroid/graphics/Rect;"); - m_textFieldInitDataGlue->m_constructor = GetJMethod(env, tfidClazz, "<init>", "()V"); - env->DeleteLocalRef(tfidClazz); - PageGroup::setShouldTrackVisitedLinks(true); clearContent(); @@ -540,23 +463,22 @@ WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* m WebViewCore::addInstance(this); +#if USE(CHROME_NETWORK_STACK) AndroidNetworkLibraryImpl::InitWithApplicationContext(env, 0); +#endif - // increase the font cache size beyond the standard system setting - SkGraphics::SetFontCacheLimit(1572864); // 1572864 bytes == 1.5 MB - +#if USE(V8) // Static initialisation of certain important V8 static data gets performed at system startup when // libwebcore gets loaded. We now need to associate the WebCore thread with V8 to complete // initialisation. v8::V8::Initialize(); +#endif // Configure any RuntimeEnabled features that we need to change from their default now. // See WebCore/bindings/generic/RuntimeEnabledFeatures.h // HTML5 History API RuntimeEnabledFeatures::setPushStateEnabled(true); - if (m_mainFrame) - m_mainFrame->settings()->setMinDOMTimerInterval(FOREGROUND_TIMER_INTERVAL); } WebViewCore::~WebViewCore() @@ -572,40 +494,24 @@ WebViewCore::~WebViewCore() m_javaGlue->m_obj = 0; } delete m_javaGlue; + delete m_frameCacheKit; + delete m_navPictureKit; } WebViewCore* WebViewCore::getWebViewCore(const WebCore::FrameView* view) { - if (!view) - return 0; - if (view->platformWidget()) - return static_cast<WebFrameView*>(view->platformWidget())->webViewCore(); - Frame* frame = view->frame(); - while (Frame* parent = frame->tree()->parent()) - frame = parent; - WebFrameView* webFrameView = 0; - if (frame && frame->view()) - webFrameView = static_cast<WebFrameView*>(frame->view()->platformWidget()); - if (!webFrameView) - return 0; - return webFrameView->webViewCore(); + return getWebViewCore(static_cast<const WebCore::ScrollView*>(view)); } WebViewCore* WebViewCore::getWebViewCore(const WebCore::ScrollView* view) { if (!view) return 0; - if (view->platformWidget()) - return static_cast<WebFrameView*>(view->platformWidget())->webViewCore(); - const FrameView* frameView = 0; - if (view->isFrameView()) - frameView = static_cast<const FrameView*>(view); - else { - frameView = static_cast<const FrameView*>(view->root()); - if (!frameView) - return 0; - } - return getWebViewCore(frameView); + + WebFrameView* webFrameView = static_cast<WebFrameView*>(view->platformWidget()); + if (!webFrameView) + return 0; + return webFrameView->webViewCore(); } static bool layoutIfNeededRecursive(WebCore::Frame* f) @@ -616,35 +522,84 @@ static bool layoutIfNeededRecursive(WebCore::Frame* f) WebCore::FrameView* v = f->view(); if (!v) return true; - v->updateLayoutAndStyleIfNeededRecursive(); - return !v->needsLayout(); + + if (v->needsLayout()) + v->layout(f->tree()->parent()); + + WebCore::Frame* child = f->tree()->firstChild(); + bool success = true; + while (child) { + success &= layoutIfNeededRecursive(child); + child = child->tree()->nextSibling(); + } + + return success && !v->needsLayout(); } -WebCore::Node* WebViewCore::currentFocus() +CacheBuilder& WebViewCore::cacheBuilder() { - return focusedFrame()->document()->focusedNode(); + return FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder(); } -void WebViewCore::layout() +WebCore::Node* WebViewCore::currentFocus() { - TRACE_METHOD(); + return cacheBuilder().currentFocus(); +} +void WebViewCore::recordPicture(SkPicture* picture) +{ // if there is no document yet, just return if (!m_mainFrame->document()) { - ALOGV("!m_mainFrame->document()"); + DBG_NAV_LOG("no document"); + return; + } + // Call layout to ensure that the contentWidth and contentHeight are correct + if (!layoutIfNeededRecursive(m_mainFrame)) { + DBG_NAV_LOG("layout failed"); return; } + // draw into the picture's recording canvas + WebCore::FrameView* view = m_mainFrame->view(); + DBG_NAV_LOGD("view=(w=%d,h=%d)", view->contentsWidth(), + view->contentsHeight()); + SkAutoPictureRecord arp(picture, view->contentsWidth(), + view->contentsHeight(), PICT_RECORD_FLAGS); + SkAutoMemoryUsageProbe mup(__FUNCTION__); + + WebCore::PlatformGraphicsContext pgc(arp.getRecordingCanvas()); + WebCore::GraphicsContext gc(&pgc); + view->platformWidget()->draw(&gc, WebCore::IntRect(0, 0, + view->contentsWidth(), view->contentsHeight())); +} +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; + { // collect WebViewCoreRecordTimeCounter after layoutIfNeededRecursive +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreRecordTimeCounter); +#endif + // if the webkit page dimensions changed, discard the pictureset and redraw. WebCore::FrameView* view = m_mainFrame->view(); int width = view->contentsWidth(); @@ -695,8 +650,6 @@ void WebViewCore::layout() // If the new total is larger than the content, resize the view to include // all the content. if (!contentRect.contains(total)) { - // TODO: Does this ever happen? Is this needed now that we don't flatten - // frames? // Resize the view to change the overflow clip. view->resize(total.fRight, total.fBottom); @@ -705,27 +658,159 @@ void WebViewCore::layout() view->forceLayout(); // Relayout similar to above - layoutIfNeededRecursive(m_mainFrame); + m_skipContentDraw = true; + bool success = layoutIfNeededRecursive(m_mainFrame); + m_skipContentDraw = false; + if (!success) + return; + + // Set the computed content width + width = view->contentsWidth(); + height = view->contentsHeight(); } -} -void WebViewCore::recordPicturePile() -{ - // if the webkit page dimensions changed, discard the pictureset and redraw. - WebCore::FrameView* view = m_mainFrame->view(); - int width = view ? view->contentsWidth() : 0; - int height = view ? view->contentsHeight() : 0; + if (cacheBuilder().pictureSetDisabled()) + content->clear(); + +#if USE(ACCELERATED_COMPOSITING) + // The invals are not always correct when the content size has changed. For + // now, let's just reset the inval so that it invalidates the entire content + // -- the pictureset will be fully repainted, tiles will be marked dirty and + // will have to be repainted. + + // FIXME: the webkit invals ought to have been enough... + if (content->width() != width || content->height() != height) { + SkIRect r; + r.fLeft = 0; + r.fTop = 0; + r.fRight = width; + r.fBottom = height; + m_addInval.setRect(r); + } +#endif - m_content.setSize(IntSize(width, height)); + content->setDimensions(width, height, &m_addInval); + + // Add the current inval rects to the PictureSet, and rebuild it. + content->add(m_addInval, 0, 0, false); + + // If we have too many invalidations, just get the area bounds + SkRegion::Iterator iterator(m_addInval); + int nbInvals = 0; + while (!iterator.done()) { + iterator.next(); + nbInvals++; + if (nbInvals > MAX_INVALIDATIONS) + break; + } + if (nbInvals > MAX_INVALIDATIONS) { + SkIRect r = m_addInval.getBounds(); + m_addInval.setRect(r); + } // Rebuild the pictureset (webkit repaint) - m_content.updatePicturesIfNeeded(this); + rebuildPictureSet(content); + } // WebViewCoreRecordTimeCounter + + WebCore::Node* oldFocusNode = currentFocus(); + m_frameCacheOutOfDate = true; + WebCore::IntRect oldBounds; + int oldSelStart = 0; + int oldSelEnd = 0; + if (oldFocusNode) { + oldBounds = oldFocusNode->getRect(); + RenderObject* renderer = oldFocusNode->renderer(); + if (renderer && (renderer->isTextArea() || renderer->isTextField())) { + WebCore::RenderTextControl* rtc = + static_cast<WebCore::RenderTextControl*>(renderer); + oldSelStart = rtc->selectionStart(); + oldSelEnd = rtc->selectionEnd(); + } + } else + oldBounds = WebCore::IntRect(0,0,0,0); + unsigned latestVersion = 0; + if (m_check_domtree_version) { + // as domTreeVersion only increment, we can just check the sum to see + // whether we need to update the frame cache + for (Frame* frame = m_mainFrame; frame; frame = frame->tree()->traverseNext()) { + const Document* doc = frame->document(); + latestVersion += doc->domTreeVersion() + doc->styleVersion(); + } + } + DBG_NAV_LOGD("m_lastFocused=%p oldFocusNode=%p" + " m_lastFocusedBounds={%d,%d,%d,%d} oldBounds={%d,%d,%d,%d}" + " m_lastFocusedSelection={%d,%d} oldSelection={%d,%d}" + " m_check_domtree_version=%s latestVersion=%d m_domtree_version=%d", + m_lastFocused, oldFocusNode, + m_lastFocusedBounds.x(), m_lastFocusedBounds.y(), + m_lastFocusedBounds.width(), m_lastFocusedBounds.height(), + oldBounds.x(), oldBounds.y(), oldBounds.width(), oldBounds.height(), + m_lastFocusedSelStart, m_lastFocusedSelEnd, oldSelStart, oldSelEnd, + m_check_domtree_version ? "true" : "false", + latestVersion, m_domtree_version); + if (m_lastFocused == oldFocusNode && m_lastFocusedBounds == oldBounds + && m_lastFocusedSelStart == oldSelStart + && m_lastFocusedSelEnd == oldSelEnd + && !m_findIsUp + && (!m_check_domtree_version || latestVersion == m_domtree_version)) + { + return; + } + m_focusBoundsChanged |= m_lastFocused == oldFocusNode + && m_lastFocusedBounds != oldBounds; + m_lastFocused = oldFocusNode; + m_lastFocusedBounds = oldBounds; + m_lastFocusedSelStart = oldSelStart; + m_lastFocusedSelEnd = oldSelEnd; + m_domtree_version = latestVersion; + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + if (m_findIsUp) { + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue->object(env); + if (javaObject.get()) { + env->CallVoidMethod(javaObject.get(), m_javaGlue->m_sendFindAgain); + checkException(env); + } + } +} + +// note: updateCursorBounds is called directly by the WebView thread +// This needs to be called each time we call CachedRoot::setCursor() with +// non-null CachedNode/CachedFrame, since otherwise the WebViewCore's data +// about the cursor is incorrect. When we call setCursor(0,0), we need +// to set hasCursorBounds to false. +void WebViewCore::updateCursorBounds(const CachedRoot* root, + const CachedFrame* cachedFrame, const CachedNode* cachedNode) +{ + LOG_ASSERT(root, "updateCursorBounds: root cannot be null"); + LOG_ASSERT(cachedNode, "updateCursorBounds: cachedNode cannot be null"); + LOG_ASSERT(cachedFrame, "updateCursorBounds: cachedFrame cannot be null"); + gCursorBoundsMutex.lock(); + m_hasCursorBounds = !cachedNode->isHidden(); + // If m_hasCursorBounds is false, we never look at the other + // values, so do not bother setting them. + if (m_hasCursorBounds) { + WebCore::IntRect bounds = cachedNode->bounds(cachedFrame); + if (m_cursorBounds != bounds) + DBG_NAV_LOGD("new cursor bounds=(%d,%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + m_cursorBounds = bounds; + m_cursorHitBounds = cachedNode->hitBounds(cachedFrame); + m_cursorFrame = cachedFrame->framePointer(); + root->getSimulatedMousePosition(&m_cursorLocation); + m_cursorNode = cachedNode->nodePointer(); + } + gCursorBoundsMutex.unlock(); } void WebViewCore::clearContent() { - m_content.reset(); - updateLocale(); + DBG_SET_LOG(""); + m_content.clear(); + m_addInval.setEmpty(); + m_rebuildInval.setEmpty(); } bool WebViewCore::focusBoundsChanged() @@ -735,82 +820,75 @@ bool WebViewCore::focusBoundsChanged() return result; } -void WebViewCore::paintContents(WebCore::GraphicsContext* gc, WebCore::IntRect& dirty) +SkPicture* WebViewCore::rebuildPicture(const SkIRect& inval) { WebCore::FrameView* view = m_mainFrame->view(); - if (!view) { - gc->setFillColor(WebCore::Color::white, WebCore::ColorSpaceDeviceRGB); - gc->fillColor(); - return; - } + int width = view->contentsWidth(); + int height = view->contentsHeight(); + SkPicture* picture = new SkPicture(); + SkAutoPictureRecord arp(picture, width, height, PICT_RECORD_FLAGS); + SkAutoMemoryUsageProbe mup(__FUNCTION__); + SkCanvas* recordingCanvas = arp.getRecordingCanvas(); + WebCore::PlatformGraphicsContext pgc(recordingCanvas); + WebCore::GraphicsContext gc(&pgc); IntPoint origin = view->minimumScrollPosition(); - IntRect drawArea = dirty; - gc->translate(-origin.x(), -origin.y()); - drawArea.move(origin.x(), origin.y()); - view->platformWidget()->draw(gc, drawArea); -} + WebCore::IntRect drawArea(inval.fLeft + origin.x(), inval.fTop + origin.y(), + inval.width(), inval.height()); + recordingCanvas->translate(-drawArea.x(), -drawArea.y()); + recordingCanvas->save(); + view->platformWidget()->draw(&gc, drawArea); + m_rebuildInval.op(inval, SkRegion::kUnion_Op); + DBG_SET_LOGD("m_rebuildInval={%d,%d,r=%d,b=%d}", + m_rebuildInval.getBounds().fLeft, m_rebuildInval.getBounds().fTop, + m_rebuildInval.getBounds().fRight, m_rebuildInval.getBounds().fBottom); -void WebViewCore::setPrerenderingEnabled(bool enable) -{ - MutexLocker locker(m_prerenderLock); - m_prerenderEnabled = enable; + return picture; } -bool WebViewCore::prerenderingEnabled() +void WebViewCore::rebuildPictureSet(PictureSet* pictureSet) { - MutexLocker locker(m_prerenderLock); - return m_prerenderEnabled; + WebCore::FrameView* view = m_mainFrame->view(); + +#ifdef FAST_PICTURESET + WTF::Vector<Bucket*>* buckets = pictureSet->bucketsToUpdate(); + + for (unsigned int i = 0; i < buckets->size(); i++) { + Bucket* bucket = (*buckets)[i]; + for (unsigned int j = 0; j < bucket->size(); j++) { + BucketPicture& bucketPicture = (*bucket)[j]; + const SkIRect& inval = bucketPicture.mRealArea; + SkPicture* picture = rebuildPicture(inval); + SkSafeUnref(bucketPicture.mPicture); + bucketPicture.mPicture = picture; + } + } + buckets->clear(); +#else + 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__); +#endif } -SkCanvas* WebViewCore::createPrerenderCanvas(PrerenderedInval* prerendered) +bool WebViewCore::updateLayers(LayerAndroid* layers) { - // Has WebView disabled prerenders (not attached, etc...)? - if (!prerenderingEnabled()) - return 0; - // Does this WebView have focus? - if (!m_mainFrame->page()->focusController()->isActive()) - return 0; - // Are we scrolling? - if (currentTimeMS() - m_scrollSetTime < PRERENDER_AFTER_SCROLL_DELAY) - return 0; - // Do we have anything to render? - if (prerendered->area.isEmpty()) - return 0; - FloatRect scaleTemp(m_scrollOffsetX, m_scrollOffsetY, m_screenWidth, m_screenHeight); - scaleTemp.scale(m_scale); - IntRect visibleTileClip = enclosingIntRect(scaleTemp); - FloatRect scaledArea = prerendered->area; - scaledArea.scale(m_scale); - IntRect enclosingScaledArea = enclosingIntRect(scaledArea); - if (enclosingScaledArea.isEmpty()) - return 0; - // "round out" the screen to tile boundaries so that we can clip yet still - // cover any visible tiles with the prerender - int tw = TilesManager::tileWidth(); - int th = TilesManager::tileHeight(); - float left = tw * (int) (visibleTileClip.x() / tw); - float top = th * (int) (visibleTileClip.y() / th); - float right = tw * (int) ceilf(visibleTileClip.maxX() / (float) tw); - float bottom = th * (int) ceilf(visibleTileClip.maxY() / (float) th); - visibleTileClip = IntRect(left, top, right - left, bottom - top); - enclosingScaledArea.intersect(visibleTileClip); - if (enclosingScaledArea.isEmpty()) - return 0; - prerendered->screenArea = enclosingScaledArea; - FloatRect enclosingDocArea(enclosingScaledArea); - enclosingDocArea.scale(1 / m_scale); - prerendered->area = enclosingIntRect(enclosingDocArea); - if (prerendered->area.isEmpty()) - return 0; - prerendered->bitmap.setConfig(SkBitmap::kARGB_8888_Config, - enclosingScaledArea.width(), - enclosingScaledArea.height()); - prerendered->bitmap.allocPixels(); - SkCanvas* bitmapCanvas = new SkCanvas(prerendered->bitmap); - bitmapCanvas->scale(m_scale, m_scale); - bitmapCanvas->translate(-enclosingDocArea.x(), -enclosingDocArea.y()); - return bitmapCanvas; + // We update the layers + ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client()); + GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync()); + if (root) { + LayerAndroid* updatedLayer = root->contentLayer(); + return layers->updateWithTree(updatedLayer); + } + return true; } void WebViewCore::notifyAnimationStarted() @@ -824,113 +902,93 @@ void WebViewCore::notifyAnimationStarted() } -BaseLayerAndroid* WebViewCore::createBaseLayer(GraphicsLayerAndroid* root) +BaseLayerAndroid* WebViewCore::createBaseLayer(SkRegion* region) { - // We set the background color - Color background = Color::white; + BaseLayerAndroid* base = new BaseLayerAndroid(); + base->setContent(m_content); - bool bodyHasFixedBackgroundImage = false; - bool bodyHasCSSBackground = false; + m_skipContentDraw = true; + bool layoutSucceeded = layoutIfNeededRecursive(m_mainFrame); + m_skipContentDraw = false; + // Layout only fails if called during a layout. + LOG_ASSERT(layoutSucceeded, "Can never be called recursively"); +#if USE(ACCELERATED_COMPOSITING) + // We set the background color if (m_mainFrame && m_mainFrame->document() && m_mainFrame->document()->body()) { - Document* document = m_mainFrame->document(); RefPtr<RenderStyle> style = document->styleForElementIgnoringPendingStylesheets(document->body()); if (style->hasBackground()) { - background = style->visitedDependentColor(CSSPropertyBackgroundColor); - bodyHasCSSBackground = true; - } - WebCore::FrameView* view = m_mainFrame->view(); - if (view) { - Color viewBackground = view->baseBackgroundColor(); - background = bodyHasCSSBackground ? viewBackground.blend(background) : viewBackground; + Color color = style->visitedDependentColor(CSSPropertyBackgroundColor); + if (color.isValid() && color.alpha() > 0) + base->setBackgroundColor(color); } - if (style->hasFixedBackgroundImage()) { - Image* backgroundImage = FixedBackgroundImageLayerAndroid::GetCachedImage(style); - if (backgroundImage && backgroundImage->width() > 1 && backgroundImage->height() > 1) - bodyHasFixedBackgroundImage = true; - } - } - - PicturePileLayerContent* content = new PicturePileLayerContent(m_content); - m_content.clearPrerenders(); - - BaseLayerAndroid* realBase = 0; - LayerAndroid* base = 0; - - //If we have a fixed background image on the body element, the fixed image - // will be contained in the PictureSet (the content object), and the foreground - //of the body element will be moved to a layer. - //In that case, let's change the hierarchy to obtain: - // - //BaseLayerAndroid - // \- FixedBackgroundBaseLayerAndroid (fixed positioning) - // \- ForegroundBaseLayerAndroid - // \- root layer (webkit composited tree) - - if (bodyHasFixedBackgroundImage) { - base = new ForegroundBaseLayerAndroid(0); - base->setSize(content->width(), content->height()); - - Document* document = m_mainFrame->document(); - RefPtr<RenderStyle> style = document->styleForElementIgnoringPendingStylesheets(document->body()); - - FixedBackgroundImageLayerAndroid* baseBackground = - new FixedBackgroundImageLayerAndroid(style, content->width(), content->height()); - - realBase = new BaseLayerAndroid(0); - realBase->setSize(content->width(), content->height()); - realBase->addChild(baseBackground); - realBase->addChild(base); - baseBackground->unref(); - base->unref(); - } else { - realBase = new BaseLayerAndroid(content); - base = realBase; } - realBase->setBackgroundColor(background); - - SkSafeUnref(content); - // We update the layers + ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client()); + GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync()); if (root) { LayerAndroid* copyLayer = new LayerAndroid(*root->contentLayer()); base->addChild(copyLayer); copyLayer->unref(); root->contentLayer()->clearDirtyRegion(); } +#endif - return realBase; + return base; } -BaseLayerAndroid* WebViewCore::recordContent(SkIPoint* point) +BaseLayerAndroid* WebViewCore::recordContent(SkRegion* region, SkIPoint* point) { - m_skipContentDraw = true; - layout(); - ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client()); - GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync()); - m_skipContentDraw = false; - recordPicturePile(); - - BaseLayerAndroid* baseLayer = createBaseLayer(root); - - baseLayer->markAsDirty(m_content.dirtyRegion()); - m_content.dirtyRegion().setEmpty(); + DBG_SET_LOG("start"); + // If there is a pending style recalculation, just return. + if (m_mainFrame->document()->isPendingStyleRecalc()) { + DBG_SET_LOGD("recordContent: pending style recalc, ignoring."); + return 0; + } + float progress = (float) m_mainFrame->page()->progress()->estimatedProgress(); + m_progressDone = progress <= 0.0f || progress >= 1.0f; + recordPictureSet(&m_content); + if (!m_progressDone && m_content.isEmpty()) { + DBG_SET_LOGD("empty (progress=%g)", progress); + return 0; + } + region->set(m_addInval); + m_addInval.setEmpty(); #if USE(ACCELERATED_COMPOSITING) #else - baseLayer->markAsDirty(m_rebuildInval); + region->op(m_rebuildInval, SkRegion::kUnion_Op); #endif - point->fX = m_content.size().width(); - point->fY = m_content.size().height(); + m_rebuildInval.setEmpty(); + point->fX = m_content.width(); + point->fY = m_content.height(); + DBG_SET_LOGD("region={%d,%d,r=%d,b=%d}", region->getBounds().fLeft, + region->getBounds().fTop, region->getBounds().fRight, + region->getBounds().fBottom); + DBG_SET_LOG("end"); - return baseLayer; + return createBaseLayer(region); +} + +void WebViewCore::splitContent(PictureSet* content) +{ +#ifdef FAST_PICTURESET +#else + bool layoutSucceeded = layoutIfNeededRecursive(m_mainFrame); + LOG_ASSERT(layoutSucceeded, "Can never be called recursively"); + content->split(&m_content); + rebuildPictureSet(&m_content); + content->set(m_content); +#endif // FAST_PICTURESET } void WebViewCore::scrollTo(int x, int y, bool animate) { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + 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(); AutoJObject javaObject = m_javaGlue->object(env); @@ -943,7 +1001,7 @@ void WebViewCore::scrollTo(int x, int y, bool animate) void WebViewCore::sendNotifyProgressFinished() { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue->object(env); if (!javaObject.get()) @@ -954,7 +1012,7 @@ void WebViewCore::sendNotifyProgressFinished() void WebViewCore::viewInvalidate(const WebCore::IntRect& rect) { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue->object(env); if (!javaObject.get()) @@ -975,12 +1033,26 @@ void WebViewCore::contentDraw() checkException(env); } +void WebViewCore::layersDraw() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue->object(env); + if (!javaObject.get()) + return; + env->CallVoidMethod(javaObject.get(), m_javaGlue->m_layersDraw); + checkException(env); +} + void WebViewCore::contentInvalidate(const WebCore::IntRect &r) { - IntPoint origin = m_mainFrame->view()->minimumScrollPosition(); - IntRect dirty = r; - dirty.move(-origin.x(), -origin.y()); - m_content.invalidate(dirty); + DBG_SET_LOGD("rect={%d,%d,w=%d,h=%d}", r.x(), r.y(), r.width(), r.height()); + SkIRect rect(r); + if (!rect.intersect(0, 0, INT_MAX, INT_MAX)) + return; + m_addInval.op(rect, SkRegion::kUnion_Op); + DBG_SET_LOGD("m_addInval={%d,%d,r=%d,b=%d}", + m_addInval.getBounds().fLeft, m_addInval.getBounds().fTop, + m_addInval.getBounds().fRight, m_addInval.getBounds().fBottom); if (!m_skipContentDraw) contentDraw(); } @@ -1000,9 +1072,19 @@ void WebViewCore::offInvalidate(const WebCore::IntRect &r) 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() { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + 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(); AutoJObject javaObject = m_javaGlue->object(env); @@ -1012,7 +1094,7 @@ void WebViewCore::didFirstLayout() const WebCore::KURL& url = m_mainFrame->document()->url(); if (url.isEmpty()) return; - ALOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data()); + LOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data()); WebCore::FrameLoadType loadType = m_mainFrame->loader()->loadType(); @@ -1026,11 +1108,17 @@ void WebViewCore::didFirstLayout() // a newly-loaded page. || loadType == WebCore::FrameLoadTypeSame); checkException(env); + + DBG_NAV_LOG("call updateFrameCache"); + m_check_domtree_version = false; + updateFrameCache(); + m_history.setDidFirstLayout(true); } void WebViewCore::updateViewport() { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + 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(); AutoJObject javaObject = m_javaGlue->object(env); @@ -1042,7 +1130,8 @@ void WebViewCore::updateViewport() void WebViewCore::restoreScale(float scale, float textWrapScale) { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + 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(); AutoJObject javaObject = m_javaGlue->object(env); @@ -1054,7 +1143,8 @@ void WebViewCore::restoreScale(float scale, float textWrapScale) void WebViewCore::needTouchEvents(bool need) { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + 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) JNIEnv* env = JSC::Bindings::getJNIEnv(); @@ -1072,9 +1162,26 @@ void WebViewCore::needTouchEvents(bool need) #endif } +void WebViewCore::requestKeyboardWithSelection(const WebCore::Node* node, + int selStart, int selEnd) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue->object(env); + if (!javaObject.get()) + return; + env->CallVoidMethod(javaObject.get(), + m_javaGlue->m_requestKeyboardWithSelection, + reinterpret_cast<int>(node), selStart, selEnd, m_textGeneration); + checkException(env); +} + void WebViewCore::requestKeyboard(bool showKeyboard) { - ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + 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(); AutoJObject javaObject = m_javaGlue->object(env); @@ -1086,15 +1193,42 @@ void WebViewCore::requestKeyboard(bool showKeyboard) void WebViewCore::notifyProgressFinished() { + m_check_domtree_version = true; sendNotifyProgressFinished(); } -void WebViewCore::setScrollOffset(bool sendScrollEvent, int dx, int dy) +void WebViewCore::doMaxScroll(CacheBuilder::Direction dir) +{ + int dx = 0, dy = 0; + + switch (dir) { + case CacheBuilder::LEFT: + dx = -m_maxXScroll; + break; + case CacheBuilder::UP: + dy = -m_maxYScroll; + break; + case CacheBuilder::RIGHT: + dx = m_maxXScroll; + break; + case CacheBuilder::DOWN: + dy = m_maxYScroll; + break; + case CacheBuilder::UNINITIALIZED: + default: + LOG_ASSERT(0, "unexpected focus selector"); + } + WebCore::FrameView* view = m_mainFrame->view(); + this->scrollTo(view->scrollX() + dx, view->scrollY() + dy, true); +} + +void WebViewCore::setScrollOffset(int moveGeneration, bool sendScrollEvent, int dx, int dy) { + DBG_NAV_LOGD("{%d,%d} m_scrollOffset=(%d,%d), sendScrollEvent=%d", dx, dy, + m_scrollOffsetX, m_scrollOffsetY, sendScrollEvent); if (m_scrollOffsetX != dx || m_scrollOffsetY != dy) { m_scrollOffsetX = dx; m_scrollOffsetY = dy; - m_scrollSetTime = currentTimeMS(); // The visible rect is located within our coordinate space so it // contains the actual scroll position. Setting the location makes hit // testing work correctly. @@ -1125,10 +1259,19 @@ void WebViewCore::setScrollOffset(bool sendScrollEvent, int dx, int dy) // update the currently visible screen sendPluginVisibleScreen(); } + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + IntPoint location = m_cursorLocation; + gCursorBoundsMutex.unlock(); + if (!hasCursorBounds) + return; + moveMouseIfLatest(moveGeneration, frame, location.x(), location.y()); } void WebViewCore::setGlobalBounds(int x, int y, int h, int v) { + DBG_NAV_LOGD("{%d,%d}", x, y); m_mainFrame->view()->platformWidget()->setWindowBounds(x, y, h, v); } @@ -1147,6 +1290,9 @@ void WebViewCore::setSizeScreenWidthAndScale(int width, int height, int osw = m_screenWidth; int osh = m_screenHeight; int otw = m_textWrapWidth; + float oldScale = m_scale; + DBG_NAV_LOGD("old:(w=%d,h=%d,sw=%d,scale=%g) new:(w=%d,h=%d,sw=%d,scale=%g)", + ow, oh, osw, m_scale, width, height, screenWidth, scale); m_screenWidth = screenWidth; m_screenHeight = screenHeight; m_textWrapWidth = textWrapWidth; @@ -1165,8 +1311,11 @@ void WebViewCore::setSizeScreenWidthAndScale(int width, int height, if (ow != width || (!ignoreHeight && oh != height) || reflow) { WebCore::RenderObject *r = m_mainFrame->contentRenderer(); + DBG_NAV_LOGD("renderer=%p view=(w=%d,h=%d)", r, + screenWidth, screenHeight); if (r) { WebCore::IntPoint anchorPoint = WebCore::IntPoint(anchorX, anchorY); + DBG_NAV_LOGD("anchorX=%d anchorY=%d", anchorX, anchorY); RefPtr<WebCore::Node> node; WebCore::IntRect bounds; WebCore::IntPoint offset; @@ -1178,19 +1327,11 @@ void WebViewCore::setSizeScreenWidthAndScale(int width, int height, m_mainFrame->eventHandler()->hitTestResultAtPoint( anchorPoint, false); node = hitTestResult.innerNode(); - if (node && !node->isTextNode()) { - // If the hitTestResultAtPoint didn't find a suitable node - // for anchoring, try again with some slop. - static const int HIT_SLOP = 30; - anchorPoint.move(HIT_SLOP, HIT_SLOP); - hitTestResult = - m_mainFrame->eventHandler()->hitTestResultAtPoint( - anchorPoint, false); - node = hitTestResult.innerNode(); - } } if (node) { bounds = node->getRect(); + DBG_NAV_LOGD("ob:(x=%d,y=%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); // sites like nytimes.com insert a non-standard tag <nyt_text> // in the html. If it is the HitTestResult, it may have zero // width and height. In this case, use its parent node. @@ -1198,6 +1339,8 @@ void WebViewCore::setSizeScreenWidthAndScale(int width, int height, node = node->parentOrHostNode(); if (node) { bounds = node->getRect(); + DBG_NAV_LOGD("found a zero width node and use its parent, whose ob:(x=%d,y=%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); } } } @@ -1218,6 +1361,9 @@ void WebViewCore::setSizeScreenWidthAndScale(int width, int height, // scroll to restore current screen center if (node && node->inDocument()) { const WebCore::IntRect& newBounds = node->getRect(); + DBG_NAV_LOGD("nb:(x=%d,y=%d,w=%d," + "h=%d)", newBounds.x(), newBounds.y(), + newBounds.width(), newBounds.height()); if ((osw && osh && bounds.width() && bounds.height()) && (bounds != newBounds)) { WebCore::FrameView* view = m_mainFrame->view(); @@ -1294,6 +1440,13 @@ void WebViewCore::dumpRenderTree(bool useFile) #endif } +void WebViewCore::dumpNavTree() +{ +#if DUMP_NAV_CACHE + cacheBuilder().mDebug.print(); +#endif +} + HTMLElement* WebViewCore::retrieveElement(int x, int y, const QualifiedName& tagName) { @@ -1302,12 +1455,12 @@ HTMLElement* WebViewCore::retrieveElement(int x, int y, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(1, 1)); if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) { - ALOGE("Should not happen: no in document Node found"); + LOGE("Should not happen: no in document Node found"); return 0; } const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); if (list.isEmpty()) { - ALOGE("Should not happen: no rect-based-test nodes found"); + LOGE("Should not happen: no rect-based-test nodes found"); return 0; } Node* node = hitTestResult.innerNode(); @@ -1316,6 +1469,9 @@ HTMLElement* WebViewCore::retrieveElement(int x, int y, || !element->hasTagName(tagName))) { element = element->parentNode(); } + DBG_NAV_LOGD("node=%p element=%p x=%d y=%d nodeName=%s tagName=%s", node, + element, x, y, node->nodeName().utf8().data(), + element ? ((Element*) element)->tagName().utf8().data() : "<none>"); return static_cast<WebCore::HTMLElement*>(element); } @@ -1333,10 +1489,8 @@ HTMLImageElement* WebViewCore::retrieveImageElement(int x, int y) WTF::String WebViewCore::retrieveHref(int x, int y) { - // TODO: This is expensive, cache - HitTestResult result = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), - false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(1, 1)); - return result.absoluteLinkURL(); + WebCore::HTMLAnchorElement* anchor = retrieveAnchorElement(x, y); + return anchor ? anchor->href() : WTF::String(); } WTF::String WebViewCore::retrieveAnchorText(int x, int y) @@ -1347,16 +1501,14 @@ WTF::String WebViewCore::retrieveAnchorText(int x, int y) WTF::String WebViewCore::retrieveImageSource(int x, int y) { - // TODO: This is expensive, cache - HitTestResult result = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), - false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(1, 1)); - return result.absoluteImageURL(); + HTMLImageElement* image = retrieveImageElement(x, y); + return image ? image->src().string() : WTF::String(); } WTF::String WebViewCore::requestLabel(WebCore::Frame* frame, WebCore::Node* node) { - if (node && validNode(m_mainFrame, frame, node)) { + if (node && CacheBuilder::validNode(m_mainFrame, frame, node)) { RefPtr<WebCore::NodeList> list = node->document()->getElementsByTagName("label"); unsigned length = list->length(); for (unsigned i = 0; i < length; i++) { @@ -1380,18 +1532,17 @@ WTF::String WebViewCore::requestLabel(WebCore::Frame* frame, static bool isContentEditable(const WebCore::Node* node) { - if (!node) - return false; - return node->isContentEditable(); + if (!node) return false; + return node->document()->frame()->selection()->isContentEditable(); } // Returns true if the node is a textfield, textarea, or contentEditable static bool isTextInput(const WebCore::Node* node) { - if (!node) - return false; if (isContentEditable(node)) return true; + if (!node) + return false; WebCore::RenderObject* renderer = node->renderer(); return renderer && (renderer->isTextField() || renderer->isTextArea()); } @@ -1409,9 +1560,107 @@ void WebViewCore::revealSelection() focusedFrame->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); } +void WebViewCore::updateCacheOnNodeChange() +{ + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + Node* node = (Node*) m_cursorNode; + IntRect bounds = m_cursorHitBounds; + gCursorBoundsMutex.unlock(); + if (!hasCursorBounds || !node) + return; + if (CacheBuilder::validNode(m_mainFrame, frame, node)) { + RenderObject* renderer = node->renderer(); + if (renderer && renderer->style()->visibility() != HIDDEN) { + IntRect absBox = renderer->absoluteBoundingBoxRect(); + int globalX, globalY; + CacheBuilder::GetGlobalOffset(frame, &globalX, &globalY); + absBox.move(globalX, globalY); + if (absBox == bounds) + return; + DBG_NAV_LOGD("absBox=(%d,%d,%d,%d) bounds=(%d,%d,%d,%d)", + absBox.x(), absBox.y(), absBox.width(), absBox.height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + } + } + DBG_NAV_LOGD("updateFrameCache node=%p", node); + updateFrameCache(); +} + +void WebViewCore::updateFrameCache() +{ + if (!m_frameCacheOutOfDate) { + DBG_NAV_LOG("!m_frameCacheOutOfDate"); + return; + } + + // If there is a pending style recalculation, do not update the frame cache. + // Until the recalculation is complete, there may be internal objects that + // are in an inconsistent state (such as font pointers). + // In any event, there's not much point to updating the cache while a style + // recalculation is pending, since it will simply have to be updated again + // once the recalculation is complete. + // TODO: Do we need to reschedule an update for after the style is recalculated? + if (m_mainFrame && m_mainFrame->document() && m_mainFrame->document()->isPendingStyleRecalc()) { + LOGW("updateFrameCache: pending style recalc, ignoring."); + return; + } +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreBuildNavTimeCounter); +#endif + m_frameCacheOutOfDate = false; + m_temp = new CachedRoot(); + m_temp->init(m_mainFrame, &m_history); +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* graphicsLayer = graphicsRootLayer(); + if (graphicsLayer) + m_temp->setRootLayer(graphicsLayer->contentLayer()); +#endif + CacheBuilder& builder = cacheBuilder(); + WebCore::Settings* settings = m_mainFrame->page()->settings(); + builder.allowAllTextDetection(); +#ifdef ANDROID_META_SUPPORT + if (settings) { + if (!settings->formatDetectionAddress()) + builder.disallowAddressDetection(); + if (!settings->formatDetectionEmail()) + builder.disallowEmailDetection(); + if (!settings->formatDetectionTelephone()) + builder.disallowPhoneDetection(); + } +#endif + builder.buildCache(m_temp); + m_tempPict = new SkPicture(); + recordPicture(m_tempPict); + m_temp->setPicture(m_tempPict); + m_temp->setTextGeneration(m_textGeneration); + WebCoreViewBridge* window = m_mainFrame->view()->platformWidget(); + m_temp->setVisibleRect(WebCore::IntRect(m_scrollOffsetX, + m_scrollOffsetY, window->width(), window->height())); + gFrameCacheMutex.lock(); + delete m_frameCacheKit; + delete m_navPictureKit; + m_frameCacheKit = m_temp; + m_navPictureKit = m_tempPict; + m_updatedFrameCache = true; +#if DEBUG_NAV_UI + const CachedNode* cachedFocusNode = m_frameCacheKit->currentFocus(); + DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p)", + cachedFocusNode ? cachedFocusNode->index() : 0, + cachedFocusNode ? cachedFocusNode->nodePointer() : 0); +#endif + gFrameCacheMutex.unlock(); +} + +void WebViewCore::updateFrameCacheIfLoading() +{ + if (!m_check_domtree_version) + updateFrameCache(); +} + struct TouchNodeData { - Node* mUrlNode; - Node* mInnerNode; + Node* mNode; IntRect mBounds; }; @@ -1419,8 +1668,6 @@ struct TouchNodeData { static IntRect getAbsoluteBoundingBox(Node* node) { IntRect rect; RenderObject* render = node->renderer(); - if (!render) - return rect; if (render->isRenderInline()) rect = toRenderInline(render)->linesVisualOverflowBoundingBox(); else if (render->isBox()) @@ -1428,395 +1675,30 @@ static IntRect getAbsoluteBoundingBox(Node* node) { else if (render->isText()) rect = toRenderText(render)->linesBoundingBox(); else - ALOGE("getAbsoluteBoundingBox failed for node %p, name %s", node, render->renderName()); - FloatPoint absPos = render->localToAbsolute(FloatPoint(), false, true); + LOGE("getAbsoluteBoundingBox failed for node %p, name %s", node, render->renderName()); + FloatPoint absPos = render->localToAbsolute(); rect.move(absPos.x(), absPos.y()); return rect; } -WebCore::Frame* WebViewCore::focusedFrame() const -{ - return m_mainFrame->page()->focusController()->focusedOrMainFrame(); -} - -VisiblePosition WebViewCore::visiblePositionForContentPoint(int x, int y) -{ - return visiblePositionForContentPoint(IntPoint(x, y)); -} - -VisiblePosition WebViewCore::visiblePositionForContentPoint(const IntPoint& point) -{ - // Hit test of this kind required for this to work inside input fields - HitTestRequest request(HitTestRequest::Active - | HitTestRequest::MouseMove - | HitTestRequest::ReadOnly - | HitTestRequest::IgnoreClipping); - HitTestResult result(point); - focusedFrame()->document()->renderView()->layer()->hitTest(request, result); - - // Matching the logic in MouseEventWithHitTestResults::targetNode() - Node* node = result.innerNode(); - if (!node) - return VisiblePosition(); - Element* element = node->parentElement(); - if (!node->inDocument() && element && element->inDocument()) - node = element; - - return node->renderer()->positionForPoint(result.localPoint()); -} - -bool WebViewCore::selectWordAt(int x, int y) -{ - HitTestResult hoverResult; - moveMouse(x, y, &hoverResult); - if (hoverResult.innerNode()) { - Node* node = hoverResult.innerNode(); - Frame* frame = node->document()->frame(); - Page* page = m_mainFrame->document()->page(); - page->focusController()->setFocusedFrame(frame); - } - - IntPoint point = convertGlobalContentToFrameContent(IntPoint(x, y)); - - // Hit test of this kind required for this to work inside input fields - HitTestRequest request(HitTestRequest::Active); - HitTestResult result(point); - - focusedFrame()->document()->renderView()->layer()->hitTest(request, result); - - // Matching the logic in MouseEventWithHitTestResults::targetNode() - Node* node = result.innerNode(); - if (!node) - return false; - Element* element = node->parentElement(); - if (!node->inDocument() && element && element->inDocument()) - node = element; - - SelectionController* sc = focusedFrame()->selection(); - bool wordSelected = false; - if (!sc->contains(point) && (node->isContentEditable() || node->isTextNode()) && !result.isLiveLink() - && node->dispatchEvent(Event::create(eventNames().selectstartEvent, true, true))) { - VisiblePosition pos(node->renderer()->positionForPoint(result.localPoint())); - wordSelected = selectWordAroundPosition(node->document()->frame(), pos); - } - return wordSelected; -} - -bool WebViewCore::selectWordAroundPosition(Frame* frame, VisiblePosition pos) -{ - VisibleSelection selection(pos); - selection.expandUsingGranularity(WordGranularity); - SelectionController* selectionController = frame->selection(); - - bool wordSelected = false; - if (selectionController->shouldChangeSelection(selection)) { - bool allWhitespaces = true; - RefPtr<Range> firstRange = selection.firstRange(); - String text = firstRange.get() ? firstRange->text() : ""; - for (size_t i = 0; i < text.length(); ++i) { - if (!isSpaceOrNewline(text[i])) { - allWhitespaces = false; - break; - } - } - if (allWhitespaces) { - VisibleSelection emptySelection(pos); - selectionController->setSelection(emptySelection); - } else { - selectionController->setSelection(selection); - wordSelected = true; - } - } - return wordSelected; -} - -int WebViewCore::platformLayerIdFromNode(Node* node, LayerAndroid** outLayer) -{ - if (!node || !node->renderer()) - return -1; - RenderLayer* renderLayer = node->renderer()->enclosingLayer(); - while (renderLayer && !renderLayer->isComposited()) - renderLayer = renderLayer->parent(); - if (!renderLayer || !renderLayer->isComposited()) - return -1; - GraphicsLayer* graphicsLayer = renderLayer->backing()->graphicsLayer(); - if (!graphicsLayer) - return -1; - GraphicsLayerAndroid* agl = static_cast<GraphicsLayerAndroid*>(graphicsLayer); - LayerAndroid* layer = agl->foregroundLayer(); - if (!layer) - layer = agl->contentLayer(); - if (!layer) - return -1; - if (outLayer) - *outLayer = layer; - return layer->uniqueId(); -} - -void WebViewCore::layerToAbsoluteOffset(const LayerAndroid* layer, IntPoint& offset) -{ - while (layer) { - const SkPoint& pos = layer->getPosition(); - offset.move(pos.fX, pos.fY); - const IntPoint& scroll = layer->getScrollOffset(); - offset.move(-scroll.x(), -scroll.y()); - layer = static_cast<LayerAndroid*>(layer->getParent()); - } -} - -void WebViewCore::setSelectionCaretInfo(SelectText* selectTextContainer, - const WebCore::Position& pos, const IntPoint& frameOffset, - SelectText::HandleId handleId, int caretRectOffset, EAffinity affinity) -{ - Node* node = pos.anchorNode(); - LayerAndroid* layer = 0; - int layerId = platformLayerIdFromNode(node, &layer); - selectTextContainer->setCaretLayerId(handleId, layerId); - IntPoint offset = frameOffset; - layerToAbsoluteOffset(layer, offset); - RenderObject* r = node->renderer(); - RenderText* renderText = toRenderText(r); - int caretOffset; - InlineBox* inlineBox; - pos.getInlineBoxAndOffset(affinity, inlineBox, caretOffset); - IntRect caretRect = renderText->localCaretRect(inlineBox, caretOffset); - FloatPoint absoluteOffset = renderText->localToAbsolute(caretRect.location()); - caretRect.setX(absoluteOffset.x() - offset.x() + caretRectOffset); - caretRect.setY(absoluteOffset.y() - offset.y()); - selectTextContainer->setCaretRect(handleId, caretRect); - selectTextContainer->setTextRect(handleId, - positionToTextRect(pos, affinity, offset)); -} - -bool WebViewCore::isLtr(const Position& position) -{ - InlineBox* inlineBox = 0; - int caretOffset = 0; - position.getInlineBoxAndOffset(DOWNSTREAM, inlineBox, caretOffset); - bool isLtr; - if (inlineBox) - isLtr = inlineBox->isLeftToRightDirection(); - else - isLtr = position.primaryDirection() == LTR; - return isLtr; -} - -SelectText* WebViewCore::createSelectText(const VisibleSelection& selection) -{ - bool isCaret = selection.isCaret(); - if (selection.isNone() || (!selection.isContentEditable() && isCaret) - || !selection.start().anchorNode() - || !selection.start().anchorNode()->renderer() - || !selection.end().anchorNode() - || !selection.end().anchorNode()->renderer()) - return 0; - - RefPtr<Range> range = selection.firstRange(); - Node* startContainer = range->startContainer(); - Node* endContainer = range->endContainer(); - - if (!startContainer || !endContainer) - return 0; - if (!isCaret && startContainer == endContainer - && range->startOffset() == range->endOffset()) - return 0; - - IntPoint frameOffset = convertGlobalContentToFrameContent(IntPoint()); - SelectText* selectTextContainer = new SelectText(); - if (isCaret) { - setSelectionCaretInfo(selectTextContainer, selection.start(), frameOffset, - SelectText::LeftHandle, 0, selection.affinity()); - setSelectionCaretInfo(selectTextContainer, selection.start(), frameOffset, - SelectText::RightHandle, 0, selection.affinity()); - } else { - bool ltr = isLtr(selection.start()); - Position left = ltr ? selection.start() : selection.end(); - Position right = ltr ? selection.end() : selection.start(); - int leftOffset = isLtr(left) ? 0 : -1; - int rightOffset = isLtr(right) ? 0 : -1; - setSelectionCaretInfo(selectTextContainer, left, frameOffset, - SelectText::LeftHandle, leftOffset, selection.affinity()); - setSelectionCaretInfo(selectTextContainer, right, frameOffset, - SelectText::RightHandle, rightOffset, selection.affinity()); - - Node* stopNode = range->pastLastNode(); - for (Node* node = range->firstNode(); node != stopNode; node = node->traverseNextNode()) { - RenderObject* r = node->renderer(); - if (!r || !r->isText() || r->style()->visibility() != VISIBLE) - continue; - RenderText* renderText = toRenderText(r); - int startOffset = node == startContainer ? range->startOffset() : 0; - int endOffset = node == endContainer ? range->endOffset() : numeric_limits<int>::max(); - LayerAndroid* layer = 0; - int layerId = platformLayerIdFromNode(node, &layer); - Vector<IntRect> rects; - renderText->absoluteRectsForRange(rects, startOffset, endOffset, true); - selectTextContainer->addHighlightRegion(layer, rects, frameOffset); - } - } - selectTextContainer->setText(range->text()); - return selectTextContainer; -} - -IntRect WebViewCore::positionToTextRect(const Position& position, - EAffinity affinity, const WebCore::IntPoint& offset) -{ - IntRect textRect; - InlineBox* inlineBox; - int offsetIndex; - position.getInlineBoxAndOffset(affinity, inlineBox, offsetIndex); - if (inlineBox && inlineBox->isInlineTextBox()) { - InlineTextBox* box = static_cast<InlineTextBox*>(inlineBox); - RootInlineBox* root = box->root(); - RenderText* renderText = box->textRenderer(); - int left = root->logicalLeft(); - int width = root->logicalWidth(); - int top = root->selectionTop(); - int height = root->selectionHeight(); - - if (!renderText->style()->isHorizontalWritingMode()) { - swap(left, top); - swap(width, height); - } - FloatPoint origin(left, top); - FloatPoint absoluteOrigin = renderText->localToAbsolute(origin); - - textRect.setX(absoluteOrigin.x() - offset.x()); - textRect.setWidth(width); - textRect.setY(absoluteOrigin.y() - offset.y()); - textRect.setHeight(height); - } - return textRect; -} - -IntPoint WebViewCore::convertGlobalContentToFrameContent(const IntPoint& point, WebCore::Frame* frame) -{ - if (!frame) frame = focusedFrame(); - IntPoint frameOffset(-m_scrollOffsetX, -m_scrollOffsetY); - frameOffset = frame->view()->windowToContents(frameOffset); - return IntPoint(point.x() + frameOffset.x(), point.y() + frameOffset.y()); -} - -Position WebViewCore::trimSelectionPosition(const Position &start, const Position& stop) -{ - int direction = comparePositions(start, stop); - if (direction == 0) - return start; - bool forward = direction < 0; - EAffinity affinity = forward ? DOWNSTREAM : UPSTREAM; - bool move; - Position pos = start; - bool movedTooFar = false; - do { - move = true; - Node* node = pos.anchorNode(); - if (node && node->isTextNode() && node->renderer()) { - RenderText *textRenderer = toRenderText(node->renderer()); - move = !textRenderer->textLength(); - } - if (move) { - Position nextPos = forward ? pos.next() : pos.previous(); - movedTooFar = nextPos.isNull() || pos == nextPos - || ((comparePositions(nextPos, stop) < 0) != forward); - pos = nextPos; - } - } while (move && !movedTooFar); - if (movedTooFar) - pos = stop; - return pos; -} - -void WebViewCore::selectText(int startX, int startY, int endX, int endY) -{ - SelectionController* sc = focusedFrame()->selection(); - IntPoint startPoint = convertGlobalContentToFrameContent(IntPoint(startX, startY)); - VisiblePosition startPosition(visiblePositionForContentPoint(startPoint)); - IntPoint endPoint = convertGlobalContentToFrameContent(IntPoint(endX, endY)); - VisiblePosition endPosition(visiblePositionForContentPoint(endPoint)); - - if (startPosition.isNull() || endPosition.isNull()) - return; - - // Ensure startPosition is before endPosition - if (comparePositions(startPosition, endPosition) > 0) - swap(startPosition, endPosition); - - if (sc->isContentEditable()) { - startPosition = sc->selection().visibleStart().honorEditableBoundaryAtOrAfter(startPosition); - endPosition = sc->selection().visibleEnd().honorEditableBoundaryAtOrBefore(endPosition); - if (startPosition.isNull() || endPosition.isNull()) { - return; - } - } - - // Ensure startPosition is not at end of block - if (startPosition != endPosition && isEndOfBlock(startPosition)) { - VisiblePosition nextStartPosition(startPosition.next()); - if (!nextStartPosition.isNull()) - startPosition = nextStartPosition; - } - // Ensure endPosition is not at start of block - if (startPosition != endPosition && isStartOfBlock(endPosition)) { - VisiblePosition prevEndPosition(endPosition.previous()); - if (!prevEndPosition.isNull()) - endPosition = prevEndPosition; - } - - Position start = startPosition.deepEquivalent(); - Position end = endPosition.deepEquivalent(); - start = trimSelectionPosition(start, end); - end = trimSelectionPosition(end, start); - VisibleSelection selection(start, end); - // Only allow changes between caret positions or to text selection. - bool selectChangeAllowed = (!selection.isCaret() || sc->isCaret()); - if (selectChangeAllowed && sc->shouldChangeSelection(selection)) - sc->setSelection(selection); -} - -bool WebViewCore::nodeIsClickableOrFocusable(Node* node) -{ - if (!node) - return false; - if (node->disabled()) - return false; - if (!node->inDocument()) - return false; - if (!node->renderer() || node->renderer()->style()->visibility() != VISIBLE) - return false; - return node->supportsFocus() - || node->hasEventListeners(eventNames().clickEvent) - || node->hasEventListeners(eventNames().mousedownEvent) - || node->hasEventListeners(eventNames().mouseupEvent) - || node->hasEventListeners(eventNames().mouseoverEvent); -} - // get the highlight rectangles for the touch point (x, y) with the slop -AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool doMoveMouse) +Vector<IntRect> WebViewCore::getTouchHighlightRects(int x, int y, int slop) { - if (doMoveMouse) - moveMouse(x, y, 0, true); + Vector<IntRect> rects; + m_mousePos = IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY); HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(slop, slop)); - AndroidHitTestResult androidHitResult(this, hitTestResult); if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) { - ALOGE("Should not happen: no in document Node found"); - return androidHitResult; + LOGE("Should not happen: no in document Node found"); + return rects; } const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); if (list.isEmpty()) { - ALOGE("Should not happen: no rect-based-test nodes found"); - return androidHitResult; + LOGE("Should not happen: no rect-based-test nodes found"); + return rects; } Frame* frame = hitTestResult.innerNode()->document()->frame(); Vector<TouchNodeData> nodeDataList; - if (hitTestResult.innerNode() != hitTestResult.innerNonSharedNode() - && hitTestResult.innerNode()->hasTagName(WebCore::HTMLNames::areaTag)) { - HTMLAreaElement* area = static_cast<HTMLAreaElement*>(hitTestResult.innerNode()); - androidHitResult.hitTestResult().setURLElement(area); - androidHitResult.highlightRects().append(area->computeRect( - hitTestResult.innerNonSharedNode()->renderer())); - return androidHitResult; - } ListHashSet<RefPtr<Node> >::const_iterator last = list.end(); for (ListHashSet<RefPtr<Node> >::const_iterator it = list.begin(); it != last; ++it) { // TODO: it seems reasonable to not search across the frame. Isn't it? @@ -1826,12 +1708,14 @@ AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool do // traverse up the tree to find the first node that needs highlight bool found = false; Node* eventNode = it->get(); - Node* innerNode = eventNode; while (eventNode) { RenderObject* render = eventNode->renderer(); if (render && (render->isBody() || render->isRenderView())) break; - if (nodeIsClickableOrFocusable(eventNode)) { + if (eventNode->supportsFocus() + || eventNode->hasEventListeners(eventNames().clickEvent) + || eventNode->hasEventListeners(eventNames().mousedownEvent) + || eventNode->hasEventListeners(eventNames().mouseupEvent)) { found = true; break; } @@ -1863,7 +1747,7 @@ AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool do Vector<TouchNodeData>::const_iterator nlast = nodeDataList.end(); for (Vector<TouchNodeData>::const_iterator n = nodeDataList.begin(); n != nlast; ++n) { // found the same node, skip it - if (eventNode == n->mUrlNode) { + if (eventNode == n->mNode) { found = false; break; } @@ -1904,19 +1788,16 @@ AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool do } if (!found) { TouchNodeData newNode; - newNode.mUrlNode = eventNode; + newNode.mNode = eventNode; newNode.mBounds = rect; - newNode.mInnerNode = innerNode; nodeDataList.append(newNode); } } - if (!nodeDataList.size()) { - androidHitResult.searchContentDetectors(); - return androidHitResult; - } + if (!nodeDataList.size()) + return rects; // finally select the node with the largest overlap with the fat point TouchNodeData final; - final.mUrlNode = 0; + final.mNode = 0; IntPoint docPos = frame->view()->windowToContents(m_mousePos); IntRect testRect(docPos.x() - slop, docPos.y() - slop, 2 * slop + 1, 2 * slop + 1); int area = 0; @@ -1925,47 +1806,101 @@ AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool do IntRect rect = n->mBounds; rect.intersect(testRect); int a = rect.width() * rect.height(); - if (a > area || !final.mUrlNode) { + if (a > area) { final = *n; area = a; } } // now get the node's highlight rectangles in the page coordinate system - if (final.mUrlNode) { - // Update innerNode and innerNonSharedNode - androidHitResult.hitTestResult().setInnerNode(final.mInnerNode); - androidHitResult.hitTestResult().setInnerNonSharedNode(final.mInnerNode); - if (final.mUrlNode->isElementNode()) { - // We found a URL element. Update the hitTestResult - androidHitResult.setURLElement(static_cast<Element*>(final.mUrlNode)); - } else { - androidHitResult.setURLElement(0); + if (final.mNode) { + IntPoint frameAdjust; + if (frame != m_mainFrame) { + frameAdjust = frame->view()->contentsToWindow(IntPoint()); + frameAdjust.move(m_scrollOffsetX, m_scrollOffsetY); } - Vector<IntRect>& highlightRects = androidHitResult.highlightRects(); - if (doMoveMouse && highlightRects.size() > 0) { - // adjust m_mousePos if it is not inside the returned highlight - // rectangles - IntRect foundIntersection; - IntRect inputRect = IntRect(x - slop, y - slop, - slop * 2 + 1, slop * 2 + 1); - for (size_t i = 0; i < highlightRects.size(); i++) { - IntRect& hr = highlightRects[i]; - IntRect test = inputRect; - test.intersect(hr); - if (!test.isEmpty()) { - foundIntersection = test; - break; + if (final.mNode->isLink()) { + // most of the links are inline instead of box style. So the bounding box is not + // a good representation for the highlights. Get the list of rectangles instead. + RenderObject* render = final.mNode->renderer(); + IntPoint offset = roundedIntPoint(render->localToAbsolute()); + render->absoluteRects(rects, offset.x() + frameAdjust.x(), offset.y() + frameAdjust.y()); + bool inside = false; + int distance = INT_MAX; + int newx = x, newy = y; + int i = rects.size(); + while (i--) { + if (rects[i].isEmpty()) { + rects.remove(i); + continue; + } + // check whether the point (x, y) is inside one of the rectangles. + if (inside) + continue; + if (rects[i].contains(x, y)) { + inside = true; + continue; + } + if (x >= rects[i].x() && x < rects[i].maxX()) { + if (y < rects[i].y()) { + if (rects[i].y() - y < distance) { + newx = x; + newy = rects[i].y(); + distance = rects[i].y() - y; + } + } else if (y >= rects[i].maxY()) { + if (y - rects[i].maxY() + 1 < distance) { + newx = x; + newy = rects[i].maxY() - 1; + distance = y - rects[i].maxY() + 1; + } + } + } else if (y >= rects[i].y() && y < rects[i].maxY()) { + if (x < rects[i].x()) { + if (rects[i].x() - x < distance) { + newx = rects[i].x(); + newy = y; + distance = rects[i].x() - x; + } + } else if (x >= rects[i].maxX()) { + if (x - rects[i].maxX() + 1 < distance) { + newx = rects[i].maxX() - 1; + newy = y; + distance = x - rects[i].maxX() + 1; + } + } } } - if (!foundIntersection.isEmpty() && !foundIntersection.contains(x, y)) { - IntPoint pt = foundIntersection.center(); - moveMouse(pt.x(), pt.y(), 0, true); + if (!rects.isEmpty()) { + if (!inside) { + // if neither x nor y has overlap, just pick the top/left of the first rectangle + if (newx == x && newy == y) { + newx = rects[0].x(); + newy = rects[0].y(); + } + m_mousePos.setX(newx - m_scrollOffsetX); + m_mousePos.setY(newy - m_scrollOffsetY); + DBG_NAV_LOGD("Move x/y from (%d, %d) to (%d, %d) scrollOffset is (%d, %d)", + x, y, m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, + m_scrollOffsetX, m_scrollOffsetY); + } + return rects; } } - } else { - androidHitResult.searchContentDetectors(); + IntRect rect = final.mBounds; + rect.move(frameAdjust.x(), frameAdjust.y()); + rects.append(rect); + // adjust m_mousePos if it is not inside the returned highlight rectangle + testRect.move(frameAdjust.x(), frameAdjust.y()); + testRect.intersect(rect); + if (!testRect.contains(x, y)) { + m_mousePos = testRect.center(); + m_mousePos.move(-m_scrollOffsetX, -m_scrollOffsetY); + DBG_NAV_LOGD("Move x/y from (%d, %d) to (%d, %d) scrollOffset is (%d, %d)", + x, y, m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, + m_scrollOffsetX, m_scrollOffsetY); + } } - return androidHitResult; + return rects; } /////////////////////////////////////////////////////////////////////////////// @@ -2136,46 +2071,70 @@ static PluginView* nodeIsPlugin(Node* node) { return 0; } +Node* WebViewCore::cursorNodeIsPlugin() { + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + Node* node = (Node*) m_cursorNode; + gCursorBoundsMutex.unlock(); + if (hasCursorBounds && CacheBuilder::validNode(m_mainFrame, frame, node) + && nodeIsPlugin(node)) { + return node; + } + return 0; +} + /////////////////////////////////////////////////////////////////////////////// +void WebViewCore::moveMouseIfLatest(int moveGeneration, + WebCore::Frame* frame, int x, int y) +{ + DBG_NAV_LOGD("m_moveGeneration=%d moveGeneration=%d" + " frame=%p x=%d y=%d", + m_moveGeneration, moveGeneration, frame, x, y); + if (m_moveGeneration > moveGeneration) { + DBG_NAV_LOGD("m_moveGeneration=%d > moveGeneration=%d", + m_moveGeneration, moveGeneration); + return; // short-circuit if a newer move has already been generated + } + m_lastGeneration = moveGeneration; + moveMouse(frame, x, y); +} + +void WebViewCore::moveFocus(WebCore::Frame* frame, WebCore::Node* node) +{ + DBG_NAV_LOGD("frame=%p node=%p", frame, node); + if (!node || !CacheBuilder::validNode(m_mainFrame, frame, node) + || !node->isElementNode()) + return; + // Code borrowed from FocusController::advanceFocus + WebCore::FocusController* focusController + = m_mainFrame->page()->focusController(); + WebCore::Document* oldDoc + = focusController->focusedOrMainFrame()->document(); + if (oldDoc->focusedNode() == node) + return; + if (node->document() != oldDoc) + oldDoc->setFocusedNode(0); + focusController->setFocusedFrame(frame); + static_cast<WebCore::Element*>(node)->focus(false); +} // Update mouse position -void WebViewCore::moveMouse(int x, int y, HitTestResult* hoveredNode, bool isClickCandidate) +void WebViewCore::moveMouse(WebCore::Frame* frame, int x, int y) { + DBG_NAV_LOGD("frame=%p x=%d y=%d scrollOffset=(%d,%d)", frame, + x, y, m_scrollOffsetX, m_scrollOffsetY); + if (!frame || !CacheBuilder::validNode(m_mainFrame, frame, 0)) + frame = m_mainFrame; // mouse event expects the position in the window coordinate m_mousePos = WebCore::IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY); - if (isClickCandidate) - m_mouseClickPos = m_mousePos; // validNode will still return true if the node is null, as long as we have // a valid frame. Do not want to make a call on frame unless it is valid. WebCore::PlatformMouseEvent mouseEvent(m_mousePos, m_mousePos, WebCore::NoButton, WebCore::MouseEventMoved, 1, false, false, false, false, WTF::currentTime()); - m_mainFrame->eventHandler()->handleMouseMoveEvent(mouseEvent, hoveredNode); -} - -Position WebViewCore::getPositionForOffset(Node* node, int offset) -{ - Position start = firstPositionInNode(node); - Position end = lastPositionInNode(node); - Document* document = node->document(); - PassRefPtr<Range> range = Range::create(document, start, end); - WebCore::CharacterIterator iterator(range.get()); - iterator.advance(offset); - return iterator.range()->startPosition(); -} - -void WebViewCore::setSelection(Node* node, int start, int end) -{ - RenderTextControl* control = toRenderTextControl(node); - if (control) - setSelectionRange(node, start, end); - else { - Position startPosition = getPositionForOffset(node, start); - Position endPosition = getPositionForOffset(node, end); - VisibleSelection selection(startPosition, endPosition); - SelectionController* selector = node->document()->frame()->selection(); - selector->setSelection(selection); - } + frame->eventHandler()->handleMouseMoveEvent(mouseEvent); + updateCacheOnNodeChange(); } void WebViewCore::setSelection(int start, int end) @@ -2183,22 +2142,28 @@ void WebViewCore::setSelection(int start, int end) WebCore::Node* focus = currentFocus(); if (!focus) return; - if (start > end) - swap(start, end); - + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) + return; + if (start > end) { + int temp = start; + start = end; + end = temp; + } // Tell our EditorClient that this change was generated from the UI, so it // does not need to echo it to the UI. EditorClientAndroid* client = static_cast<EditorClientAndroid*>( m_mainFrame->editor()->client()); client->setUiGeneratedSelectionChange(true); - setSelection(focus, start, end); - RenderTextControl* control = toRenderTextControl(focus); - if (start != end && control) { + setSelectionRange(focus, start, end); + if (start != end) { // Fire a select event. No event is sent when the selection reduces to // an insertion point + RenderTextControl* control = toRenderTextControl(renderer); control->selectionChanged(true); } client->setUiGeneratedSelectionChange(false); + WebCore::Frame* focusedFrame = focus->document()->frame(); bool isPasswordField = false; if (focus->isElementNode()) { WebCore::Element* element = static_cast<WebCore::Element*>(focus); @@ -2207,7 +2172,7 @@ void WebViewCore::setSelection(int start, int end) } // For password fields, this is done in the UI side via // bringPointIntoView, since the UI does the drawing. - if ((control && control->isTextArea()) || !isPasswordField) + if (renderer->isTextArea() || !isPasswordField) revealSelection(); } @@ -2232,7 +2197,7 @@ String WebViewCore::modifySelection(const int direction, const int axis) case AXIS_DOCUMENT: return modifySelectionDomNavigationAxis(selection, direction, axis); default: - ALOGE("Invalid navigation axis: %d", axis); + LOGE("Invalid navigation axis: %d", axis); return String(); } } @@ -2273,9 +2238,9 @@ String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, i // initialize the selection if necessary if (selection->rangeCount() == 0) { if (m_currentNodeDomNavigationAxis - && validNode(m_mainFrame, + && CacheBuilder::validNode(m_mainFrame, m_mainFrame, m_currentNodeDomNavigationAxis)) { - RefPtr<Range> rangeRef = + PassRefPtr<Range> rangeRef = selection->frame()->document()->createRange(); rangeRef->selectNode(m_currentNodeDomNavigationAxis, ec); m_currentNodeDomNavigationAxis = 0; @@ -2284,6 +2249,15 @@ String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, i selection->addRange(rangeRef.get()); } else if (currentFocus()) { selection->setPosition(currentFocus(), 0, ec); + } else if (m_cursorNode + && CacheBuilder::validNode(m_mainFrame, + m_mainFrame, m_cursorNode)) { + PassRefPtr<Range> rangeRef = + selection->frame()->document()->createRange(); + rangeRef->selectNode(reinterpret_cast<Node*>(m_cursorNode), ec); + if (ec) + return String(); + selection->addRange(rangeRef.get()); } else { selection->setPosition(body, 0, ec); } @@ -2482,13 +2456,13 @@ String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, i scrollNodeIntoView(m_mainFrame, selection->anchorNode()); // format markup for the visible content - RefPtr<Range> range = selection->getRangeAt(0, ec); + PassRefPtr<Range> range = selection->getRangeAt(0, ec); if (ec) return String(); IntRect bounds = range->boundingBox(); selectAt(bounds.center().x(), bounds.center().y()); markup = formatMarkup(selection); - ALOGV("Selection markup: %s", markup.utf8().data()); + LOGV("Selection markup: %s", markup.utf8().data()); return markup; } @@ -2696,7 +2670,7 @@ String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, in if (!m_currentNodeDomNavigationAxis) m_currentNodeDomNavigationAxis = currentFocus(); if (!m_currentNodeDomNavigationAxis - || !validNode(m_mainFrame, m_mainFrame, + || !CacheBuilder::validNode(m_mainFrame, m_mainFrame, m_currentNodeDomNavigationAxis)) m_currentNodeDomNavigationAxis = body; Node* currentNode = m_currentNodeDomNavigationAxis; @@ -2740,14 +2714,14 @@ String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, in if (direction == DIRECTION_FORWARD) currentNode = currentNode->lastDescendant(); } else { - ALOGE("Invalid axis: %d", axis); + LOGE("Invalid axis: %d", axis); return String(); } if (currentNode) { m_currentNodeDomNavigationAxis = currentNode; scrollNodeIntoView(m_mainFrame, currentNode); String selectionString = createMarkup(currentNode); - ALOGV("Selection markup: %s", selectionString.utf8().data()); + LOGV("Selection markup: %s", selectionString.utf8().data()); return selectionString; } return String(); @@ -2797,7 +2771,7 @@ bool WebViewCore::isVisible(Node* node) while (currentNode && currentNode != body) { RenderStyle* style = currentNode->computedStyle(); if (style && - (style->display() == WebCore::NONE || style->visibility() == WebCore::HIDDEN)) { + (style->display() == NONE || style->visibility() == HIDDEN)) { return false; } currentNode = currentNode->parentNode(); @@ -2809,7 +2783,7 @@ String WebViewCore::formatMarkup(DOMSelection* selection) { ExceptionCode ec = 0; String markup = String(); - RefPtr<Range> wholeRange = selection->getRangeAt(0, ec); + PassRefPtr<Range> wholeRange = selection->getRangeAt(0, ec); if (ec) return String(); if (!wholeRange->startContainer() || !wholeRange->startContainer()) @@ -2819,7 +2793,7 @@ String WebViewCore::formatMarkup(DOMSelection* selection) Node* firstNode = wholeRange->firstNode(); Node* pastLastNode = wholeRange->pastLastNode(); Node* currentNode = firstNode; - RefPtr<Range> currentRange; + PassRefPtr<Range> currentRange; while (currentNode != pastLastNode) { Node* nextNode = currentNode->traverseNextNode(); @@ -2896,6 +2870,7 @@ void WebViewCore::deleteSelection(int start, int end, int textGeneration) key(up); client->setUiGeneratedSelectionChange(false); m_textGeneration = textGeneration; + m_shouldPaintCaret = true; } void WebViewCore::replaceTextfieldText(int oldStart, @@ -2911,15 +2886,13 @@ void WebViewCore::replaceTextfieldText(int oldStart, EditorClientAndroid* client = static_cast<EditorClientAndroid*>( m_mainFrame->editor()->client()); client->setUiGeneratedSelectionChange(true); - if (replace.length()) - WebCore::TypingCommand::insertText(focus->document(), replace, - false); - else - WebCore::TypingCommand::deleteSelection(focus->document()); + WebCore::TypingCommand::insertText(focus->document(), replace, + false); client->setUiGeneratedSelectionChange(false); // setSelection calls revealSelection, so there is no need to do it here. setSelection(start, end); m_textGeneration = textGeneration; + m_shouldPaintCaret = true; } void WebViewCore::passToJs(int generation, const WTF::String& current, @@ -2927,6 +2900,13 @@ void WebViewCore::passToJs(int generation, const WTF::String& current, { WebCore::Node* focus = currentFocus(); if (!focus) { + DBG_NAV_LOG("!focus"); + clearTextEntry(); + return; + } + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { + DBG_NAV_LOGD("renderer==%p || not text", renderer); clearTextEntry(); return; } @@ -2941,36 +2921,42 @@ void WebViewCore::passToJs(int generation, const WTF::String& current, client->setUiGeneratedSelectionChange(false); m_blockTextfieldUpdates = false; m_textGeneration = generation; - WTF::String test = getInputText(focus); + WebCore::RenderTextControl* renderText = + static_cast<WebCore::RenderTextControl*>(renderer); + WTF::String test = renderText->text(); if (test != current) { // If the text changed during the key event, update the UI text field. updateTextfield(focus, false, test); + } else { + DBG_NAV_LOG("test == current"); } // Now that the selection has settled down, send it. updateTextSelection(); + m_shouldPaintCaret = true; } -WebCore::IntRect WebViewCore::scrollFocusedTextInput(float xPercent, int y) +void WebViewCore::scrollFocusedTextInput(float xPercent, int y) { WebCore::Node* focus = currentFocus(); if (!focus) { + DBG_NAV_LOG("!focus"); clearTextEntry(); - return WebCore::IntRect(); + return; } - WebCore::RenderTextControl* renderText = toRenderTextControl(focus); - if (!renderText) { + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { + DBG_NAV_LOGD("renderer==%p || not text", renderer); clearTextEntry(); - return WebCore::IntRect(); + return; } - + WebCore::RenderTextControl* renderText = + static_cast<WebCore::RenderTextControl*>(renderer); int x = (int) (xPercent * (renderText->scrollWidth() - renderText->clientWidth())); + DBG_NAV_LOGD("x=%d y=%d xPercent=%g scrollW=%d clientW=%d", x, y, + xPercent, renderText->scrollWidth(), renderText->clientWidth()); renderText->setScrollLeft(x); renderText->setScrollTop(y); - focus->document()->frame()->selection()->recomputeCaretRect(); - LayerAndroid* layer = 0; - platformLayerIdFromNode(focus, &layer); - return absoluteContentRect(focus, layer); } void WebViewCore::setFocusControllerActive(bool active) @@ -2980,7 +2966,7 @@ void WebViewCore::setFocusControllerActive(bool active) void WebViewCore::saveDocumentState(WebCore::Frame* frame) { - if (!validNode(m_mainFrame, frame, 0)) + if (!CacheBuilder::validNode(m_mainFrame, frame, 0)) frame = m_mainFrame; WebCore::HistoryItem *item = frame->loader()->history()->currentItem(); @@ -2996,9 +2982,9 @@ void WebViewCore::saveDocumentState(WebCore::Frame* frame) static jobjectArray makeLabelArray(JNIEnv* env, const uint16_t** labels, size_t count) { jclass stringClass = env->FindClass("java/lang/String"); - ALOG_ASSERT(stringClass, "Could not find java/lang/String"); + LOG_ASSERT(stringClass, "Could not find java/lang/String"); jobjectArray array = env->NewObjectArray(count, stringClass, 0); - ALOG_ASSERT(array, "Could not create new string array"); + 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]); @@ -3021,19 +3007,11 @@ void WebViewCore::openFileChooser(PassRefPtr<WebCore::FileChooser> chooser) return; WTF::String acceptType = chooser->acceptTypes(); - WTF::String capture; - -#if ENABLE(MEDIA_CAPTURE) - capture = chooser->capture(); -#endif - jstring jAcceptType = wtfStringToJstring(env, acceptType, true); - jstring jCapture = wtfStringToJstring(env, capture, true); jstring jName = (jstring) env->CallObjectMethod( - javaObject.get(), m_javaGlue->m_openFileChooser, jAcceptType, jCapture); + javaObject.get(), m_javaGlue->m_openFileChooser, jAcceptType); checkException(env); env->DeleteLocalRef(jAcceptType); - env->DeleteLocalRef(jCapture); WTF::String wtfString = jstringToWtfString(env, jName); env->DeleteLocalRef(jName); @@ -3045,7 +3023,7 @@ void WebViewCore::openFileChooser(PassRefPtr<WebCore::FileChooser> chooser) 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) { - ALOG_ASSERT(m_javaGlue->m_obj, "No java widget associated with this view!"); + LOG_ASSERT(m_javaGlue->m_obj, "No java widget associated with this view!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue->object(env); @@ -3104,15 +3082,14 @@ bool WebViewCore::key(const PlatformKeyboardEvent& event) { WebCore::EventHandler* eventHandler; WebCore::Node* focusNode = currentFocus(); + DBG_NAV_LOGD("keyCode=%s unichar=%d focusNode=%p", + event.keyIdentifier().utf8().data(), event.unichar(), focusNode); if (focusNode) { WebCore::Frame* frame = focusNode->document()->frame(); + WebFrame* webFrame = WebFrame::getWebFrame(frame); eventHandler = frame->eventHandler(); VisibleSelection old = frame->selection()->selection(); - EditorClientAndroid* client = static_cast<EditorClientAndroid*>( - m_mainFrame->editor()->client()); - client->setUiGeneratedSelectionChange(true); bool handled = eventHandler->keyEvent(event); - client->setUiGeneratedSelectionChange(false); if (isContentEditable(focusNode)) { // keyEvent will return true even if the contentEditable did not // change its selection. In the case that it does not, we want to @@ -3122,53 +3099,33 @@ bool WebViewCore::key(const PlatformKeyboardEvent& event) } return handled; } else { - eventHandler = focusedFrame()->eventHandler(); + eventHandler = m_mainFrame->eventHandler(); } return eventHandler->keyEvent(event); } -bool WebViewCore::chromeCanTakeFocus(FocusDirection direction) -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - AutoJObject javaObject = m_javaGlue->object(env); - if (!javaObject.get()) - return false; - return env->CallBooleanMethod(javaObject.get(), m_javaGlue->m_chromeCanTakeFocus, direction); -} - -void WebViewCore::chromeTakeFocus(FocusDirection direction) -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - AutoJObject javaObject = m_javaGlue->object(env); - if (!javaObject.get()) - return; - env->CallVoidMethod(javaObject.get(), m_javaGlue->m_chromeTakeFocus, direction); -} - -void WebViewCore::setInitialFocus(const WebCore::PlatformKeyboardEvent& platformEvent) -{ - Frame* frame = focusedFrame(); - Document* document = frame->document(); - if (document) - document->setFocusedNode(0); - FocusDirection direction; - switch (platformEvent.nativeVirtualKeyCode()) { - case AKEYCODE_DPAD_LEFT: - direction = FocusDirectionLeft; - break; - case AKEYCODE_DPAD_RIGHT: - direction = FocusDirectionRight; - break; - case AKEYCODE_DPAD_UP: - direction = FocusDirectionUp; - break; - default: - direction = FocusDirectionDown; - break; +// For when the user clicks the trackball, presses dpad center, or types into an +// unfocused textfield. In the latter case, 'fake' will be true +void WebViewCore::click(WebCore::Frame* frame, WebCore::Node* node, bool fake) { + if (!node) { + WebCore::IntPoint pt = m_mousePos; + pt.move(m_scrollOffsetX, m_scrollOffsetY); + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()-> + hitTestResultAtPoint(pt, false); + node = hitTestResult.innerNode(); + frame = node->document()->frame(); + DBG_NAV_LOGD("m_mousePos=(%d,%d) m_scrollOffset=(%d,%d) pt=(%d,%d)" + " node=%p", m_mousePos.x(), m_mousePos.y(), + m_scrollOffsetX, m_scrollOffsetY, pt.x(), pt.y(), node); + } + if (node) { + EditorClientAndroid* client + = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setShouldChangeSelectedRange(false); + handleMouseClick(frame, node, fake); + client->setShouldChangeSelectedRange(true); } - RefPtr<KeyboardEvent> webkitEvent = KeyboardEvent::create(platformEvent, 0); - m_mainFrame->page()->focusController()->setInitialFocus(direction, - webkitEvent.get()); } #if USE(ACCELERATED_COMPOSITING) @@ -3182,9 +3139,9 @@ GraphicsLayerAndroid* WebViewCore::graphicsRootLayer() const } #endif -int WebViewCore::handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState) +bool WebViewCore::handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState) { - int flags = 0; + bool preventDefault = false; #if USE(ACCELERATED_COMPOSITING) GraphicsLayerAndroid* rootLayer = graphicsRootLayer(); @@ -3225,10 +3182,18 @@ int WebViewCore::handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint> type = WebCore::TouchEnd; defaultTouchState = WebCore::PlatformTouchPoint::TouchStationary; break; + case 0x100: // WebViewCore.ACTION_LONGPRESS + type = WebCore::TouchLongPress; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; + case 0x200: // WebViewCore.ACTION_DOUBLETAP + type = WebCore::TouchDoubleTap; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; default: // We do not support other kinds of touch event inside WebCore // at the moment. - ALOGW("Java passed a touch event type that we do not support in WebCore: %d", action); + LOGW("Java passed a touch event type that we do not support in WebCore: %d", action); return 0; } @@ -3248,41 +3213,52 @@ int WebViewCore::handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint> } WebCore::PlatformTouchEvent te(ids, points, type, touchStates, metaState); - if (m_mainFrame->eventHandler()->handleTouchEvent(te)) - flags |= TOUCH_FLAG_PREVENT_DEFAULT; - if (te.hitTouchHandler()) - flags |= TOUCH_FLAG_HIT_HANDLER; + preventDefault = m_mainFrame->eventHandler()->handleTouchEvent(te); #endif #if USE(ACCELERATED_COMPOSITING) if (rootLayer) rootLayer->pauseDisplay(false); #endif - return flags; + return preventDefault; } -bool WebViewCore::performMouseClick() +void WebViewCore::touchUp(int touchGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y) { - WebCore::PlatformMouseEvent mouseDown(m_mouseClickPos, m_mouseClickPos, WebCore::LeftButton, - WebCore::MouseEventPressed, 1, false, false, false, false, - WTF::currentTime()); - // ignore the return from as it will return true if the hit point can trigger selection change - m_mainFrame->eventHandler()->handleMousePressEvent(mouseDown); - WebCore::PlatformMouseEvent mouseUp(m_mouseClickPos, m_mouseClickPos, WebCore::LeftButton, - WebCore::MouseEventReleased, 1, false, false, false, false, - WTF::currentTime()); - bool handled = m_mainFrame->eventHandler()->handleMouseReleaseEvent(mouseUp); - - WebCore::Node* focusNode = currentFocus(); - initializeTextInput(focusNode, false); - return handled; + if (touchGeneration == 0) { + // m_mousePos should be set in getTouchHighlightRects() + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(m_mousePos, false); + node = hitTestResult.innerNode(); + if (node) + frame = node->document()->frame(); + else + frame = 0; + DBG_NAV_LOGD("touch up on (%d, %d), scrollOffset is (%d, %d), node:%p, frame:%p", m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, m_scrollOffsetX, m_scrollOffsetY, node, frame); + } else { + if (m_touchGeneration > touchGeneration) { + DBG_NAV_LOGD("m_touchGeneration=%d > touchGeneration=%d" + " x=%d y=%d", m_touchGeneration, touchGeneration, x, y); + return; // short circuit if a newer touch has been generated + } + // This moves m_mousePos to the correct place, and handleMouseClick uses + // m_mousePos to determine where the click happens. + moveMouse(frame, x, y); + m_lastGeneration = touchGeneration; + } + if (frame && CacheBuilder::validNode(m_mainFrame, frame, 0)) { + frame->loader()->resetMultipleFormSubmissionProtection(); + } + DBG_NAV_LOGD("touchGeneration=%d handleMouseClick frame=%p node=%p" + " x=%d y=%d", touchGeneration, frame, node, x, y); + handleMouseClick(frame, node, false); } // Check for the "x-webkit-soft-keyboard" attribute. If it is there and // set to hidden, do not show the soft keyboard. Node passed as a parameter // must not be null. static bool shouldSuppressKeyboard(const WebCore::Node* node) { - ALOG_ASSERT(node, "node passed to shouldSuppressKeyboard cannot be null"); + LOG_ASSERT(node, "node passed to shouldSuppressKeyboard cannot be null"); const NamedNodeMap* attributes = node->attributes(); if (!attributes) return false; size_t length = attributes->length(); @@ -3294,160 +3270,84 @@ static bool shouldSuppressKeyboard(const WebCore::Node* node) { return false; } -WebViewCore::InputType WebViewCore::getInputType(Node* node) -{ - WebCore::RenderObject* renderer = node->renderer(); - if (!renderer) - return WebViewCore::NONE; - if (renderer->isTextArea()) - return WebViewCore::TEXT_AREA; - - if (node->hasTagName(WebCore::HTMLNames::inputTag)) { - HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); - if (htmlInput->isPasswordField()) - return WebViewCore::PASSWORD; - if (htmlInput->isSearchField()) - return WebViewCore::SEARCH; - if (htmlInput->isEmailField()) - return WebViewCore::EMAIL; - if (htmlInput->isNumberField()) - return WebViewCore::NUMBER; - if (htmlInput->isTelephoneField()) - return WebViewCore::TELEPHONE; - if (htmlInput->isTextField()) - return WebViewCore::NORMAL_TEXT_FIELD; - } - - if (node->isContentEditable()) - return WebViewCore::TEXT_AREA; - - return WebViewCore::NONE; -} - -int WebViewCore::getMaxLength(Node* node) -{ - int maxLength = -1; - if (node->hasTagName(WebCore::HTMLNames::inputTag)) { - HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); - maxLength = htmlInput->maxLength(); - } - return maxLength; -} - -String WebViewCore::getFieldName(Node* node) -{ - String name; - if (node->hasTagName(WebCore::HTMLNames::inputTag)) { - HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); - name = htmlInput->name(); - } - return name; -} - -bool WebViewCore::isSpellCheckEnabled(Node* node) -{ - bool isEnabled = true; - if (node->isElementNode()) { - WebCore::Element* element = static_cast<WebCore::Element*>(node); - isEnabled = element->isSpellCheckingEnabled(); - } - return isEnabled; -} - -bool WebViewCore::isAutoCompleteEnabled(Node* node) -{ - bool isEnabled = false; - if (node->hasTagName(WebCore::HTMLNames::inputTag)) { - HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); - isEnabled = htmlInput->autoComplete(); - } - return isEnabled; -} - -WebCore::IntRect WebViewCore::absoluteContentRect(WebCore::Node* node, - LayerAndroid* layer) -{ - IntRect contentRect; - if (node) { - RenderObject* render = node->renderer(); - if (render && render->isBox() && !render->isBody()) { - IntPoint offset = convertGlobalContentToFrameContent(IntPoint(), - node->document()->frame()); - WebViewCore::layerToAbsoluteOffset(layer, offset); - - RenderBox* renderBox = toRenderBox(render); - contentRect = renderBox->absoluteContentBox(); - contentRect.move(-offset.x(), -offset.y()); +// Common code for both clicking with the trackball and touchUp +// Also used when typing into a non-focused textfield to give the textfield focus, +// in which case, 'fake' is set to true +bool WebViewCore::handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr, bool fake) +{ + bool valid = !framePtr || CacheBuilder::validNode(m_mainFrame, framePtr, nodePtr); + WebFrame* webFrame = WebFrame::getWebFrame(m_mainFrame); + if (valid && nodePtr) { + // Need to special case area tags because an image map could have an area element in the middle + // so when attempting to get the default, the point chosen would be follow the wrong link. + if (nodePtr->hasTagName(WebCore::HTMLNames::areaTag)) { + webFrame->setUserInitiatedAction(true); + nodePtr->dispatchSimulatedClick(0, true, true); + webFrame->setUserInitiatedAction(false); + DBG_NAV_LOG("area"); + return true; } } - return contentRect; -} + if (!valid || !framePtr) + framePtr = m_mainFrame; + webFrame->setUserInitiatedAction(true); + WebCore::PlatformMouseEvent mouseDown(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventPressed, 1, false, false, false, false, + WTF::currentTime()); + // ignore the return from as it will return true if the hit point can trigger selection change + framePtr->eventHandler()->handleMousePressEvent(mouseDown); + WebCore::PlatformMouseEvent mouseUp(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventReleased, 1, false, false, false, false, + WTF::currentTime()); + bool handled = framePtr->eventHandler()->handleMouseReleaseEvent(mouseUp); + webFrame->setUserInitiatedAction(false); -jobject WebViewCore::createTextFieldInitData(Node* node) -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - TextFieldInitDataGlue* classDef = m_textFieldInitDataGlue; - ScopedLocalRef<jclass> clazz(env, - env->FindClass("android/webkit/WebViewCore$TextFieldInitData")); - jobject initData = env->NewObject(clazz.get(), classDef->m_constructor); - env->SetIntField(initData, classDef->m_fieldPointer, - reinterpret_cast<int>(node)); - ScopedLocalRef<jstring> inputText(env, - wtfStringToJstring(env, getInputText(node), true)); - env->SetObjectField(initData, classDef->m_text, inputText.get()); - env->SetIntField(initData, classDef->m_type, getInputType(node)); - env->SetBooleanField(initData, classDef->m_isSpellCheckEnabled, - isSpellCheckEnabled(node)); - Document* document = node->document(); - PlatformKeyboardEvent tab(AKEYCODE_TAB, 0, 0, false, false, false, false); - PassRefPtr<KeyboardEvent> tabEvent = - KeyboardEvent::create(tab, document->defaultView()); - env->SetBooleanField(initData, classDef->m_isTextFieldNext, - isTextInput(document->nextFocusableNode(node, tabEvent.get()))); - env->SetBooleanField(initData, classDef->m_isTextFieldPrev, - isTextInput(document->previousFocusableNode(node, tabEvent.get()))); - env->SetBooleanField(initData, classDef->m_isAutoCompleteEnabled, - isAutoCompleteEnabled(node)); - ScopedLocalRef<jstring> fieldName(env, - wtfStringToJstring(env, getFieldName(node), false)); - env->SetObjectField(initData, classDef->m_name, fieldName.get()); - ScopedLocalRef<jstring> label(env, - wtfStringToJstring(env, requestLabel(document->frame(), node), false)); - env->SetObjectField(initData, classDef->m_label, label.get()); - env->SetIntField(initData, classDef->m_maxLength, getMaxLength(node)); - LayerAndroid* layer = 0; - int layerId = platformLayerIdFromNode(node, &layer); - IntRect bounds = absoluteContentRect(node, layer); - ScopedLocalRef<jobject> jbounds(env, intRectToRect(env, bounds)); - env->SetObjectField(initData, classDef->m_contentBounds, jbounds.get()); - env->SetIntField(initData, classDef->m_nodeLayerId, layerId); - IntRect contentRect; - RenderTextControl* rtc = toRenderTextControl(node); - if (rtc) { - contentRect.setWidth(rtc->scrollWidth()); - contentRect.setHeight(rtc->scrollHeight()); - contentRect.move(-rtc->scrollLeft(), -rtc->scrollTop()); + // If the user clicked on a textfield, make the focusController active + // so we show the blinking cursor. + WebCore::Node* focusNode = currentFocus(); + DBG_NAV_LOGD("m_mousePos={%d,%d} focusNode=%p handled=%s", m_mousePos.x(), + m_mousePos.y(), focusNode, handled ? "true" : "false"); + if (focusNode) { + WebCore::RenderObject* renderer = focusNode->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + bool ime = !shouldSuppressKeyboard(focusNode) + && !(static_cast<WebCore::HTMLInputElement*>(focusNode))->readOnly(); + if (ime) { +#if ENABLE(WEB_AUTOFILL) + if (renderer->isTextField()) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(framePtr->page()->editorClient()); + WebAutofill* autoFill = editorC->getAutofill(); + autoFill->formFieldFocused(static_cast<HTMLFormControlElement*>(focusNode)); + } +#endif + if (!fake) { + RenderTextControl* rtc + = static_cast<RenderTextControl*> (renderer); + // Force an update of the navcache as this will fire off a + // message to WebView that *must* have an updated focus. + m_frameCacheOutOfDate = true; + updateFrameCache(); + requestKeyboardWithSelection(focusNode, rtc->selectionStart(), + rtc->selectionEnd()); + } + } else if (!fake) { + requestKeyboard(false); + } + } else if (!fake){ + // If the selection is contentEditable, show the keyboard so the + // user can type. Otherwise hide the keyboard because no text + // input is needed. + if (isContentEditable(focusNode)) { + requestKeyboard(true); + } else if (!nodeIsPlugin(focusNode)) { + clearTextEntry(); + } + } + } else if (!fake) { + // There is no focusNode, so the keyboard is not needed. + clearTextEntry(); } - ScopedLocalRef<jobject> jcontentRect(env, intRectToRect(env, contentRect)); - env->SetObjectField(initData, classDef->m_contentRect, jcontentRect.get()); - return initData; -} - -void WebViewCore::initEditField(Node* node) -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - AutoJObject javaObject = m_javaGlue->object(env); - if (!javaObject.get()) - return; - m_textGeneration = 0; - int start = 0; - int end = 0; - getSelectionOffsets(node, start, end); - SelectText* selectText = createSelectText(focusedFrame()->selection()->selection()); - ScopedLocalRef<jobject> initData(env, createTextFieldInitData(node)); - env->CallVoidMethod(javaObject.get(), m_javaGlue->m_initEditField, - start, end, reinterpret_cast<int>(selectText), initData.get()); - checkException(env); + return handled; } void WebViewCore::popupReply(int index) @@ -3468,99 +3368,28 @@ void WebViewCore::popupReply(const int* array, int count) } } -// This is a slightly modified Node::nextNodeConsideringAtomicNodes() with the -// extra constraint of limiting the search to inside a containing parent -WebCore::Node* nextNodeWithinParent(WebCore::Node* parent, WebCore::Node* start) +void WebViewCore::formDidBlur(const WebCore::Node* node) { - if (!isAtomicNode(start) && start->firstChild()) - return start->firstChild(); - if (start->nextSibling()) - return start->nextSibling(); - const Node *n = start; - while (n && !n->nextSibling()) { - n = n->parentNode(); - if (n == parent) - return 0; - } - if (n) - return n->nextSibling(); - return 0; + // If the blur is on a text input, keep track of the node so we can + // hide the soft keyboard when the new focus is set, if it is not a + // text input. + if (isTextInput(node)) + m_blurringNodePointer = reinterpret_cast<int>(node); } -void WebViewCore::initializeTextInput(WebCore::Node* node, bool fake) +void WebViewCore::focusNodeChanged(const WebCore::Node* newFocus) { - if (node) { - if (isTextInput(node)) { - bool showKeyboard = true; - initEditField(node); - WebCore::RenderTextControl* rtc = toRenderTextControl(node); - if (rtc && node->hasTagName(HTMLNames::inputTag)) { - HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node); - bool ime = !shouldSuppressKeyboard(node) && !inputElement->readOnly(); - if (ime) { -#if ENABLE(WEB_AUTOFILL) - if (rtc->isTextField()) { - Page* page = node->document()->page(); - EditorClient* editorClient = page->editorClient(); - EditorClientAndroid* androidEditor = - static_cast<EditorClientAndroid*>(editorClient); - WebAutofill* autoFill = androidEditor->getAutofill(); - autoFill->formFieldFocused(inputElement); - } -#endif - } else - showKeyboard = false; - } - if (!fake) - requestKeyboard(showKeyboard); - } else if (!fake && !nodeIsPlugin(node)) { - // not a text entry field, put away the keyboard. - clearTextEntry(); - } - } else if (!fake) { - // There is no focusNode, so the keyboard is not needed. - clearTextEntry(); - } -} - -void WebViewCore::focusNodeChanged(WebCore::Node* newFocus) -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - AutoJObject javaObject = m_javaGlue->object(env); - if (!javaObject.get()) - return; if (isTextInput(newFocus)) - initializeTextInput(newFocus, true); - HitTestResult focusHitResult; - focusHitResult.setInnerNode(newFocus); - focusHitResult.setInnerNonSharedNode(newFocus); - if (newFocus && newFocus->isLink() && newFocus->isElementNode()) { - focusHitResult.setURLElement(static_cast<Element*>(newFocus)); - if (newFocus->hasChildNodes() && !newFocus->hasTagName(HTMLNames::imgTag)) { - // Check to see if any of the children are images, and if so - // set them as the innerNode and innerNonSharedNode - // This will stop when it hits the first image. I'm not sure what - // should be done in the case of multiple images inside one anchor... - Node* nextNode = newFocus->firstChild(); - bool found = false; - while (nextNode) { - if (nextNode->hasTagName(HTMLNames::imgTag)) { - found = true; - break; - } - nextNode = nextNodeWithinParent(newFocus, nextNode); - } - if (found) { - focusHitResult.setInnerNode(nextNode); - focusHitResult.setInnerNonSharedNode(nextNode); - } - } + m_shouldPaintCaret = true; + else if (m_blurringNodePointer) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue->object(env); + if (!javaObject.get()) + return; + env->CallVoidMethod(javaObject.get(), m_javaGlue->m_formDidBlur, m_blurringNodePointer); + checkException(env); + m_blurringNodePointer = 0; } - AndroidHitTestResult androidHitTest(this, focusHitResult); - jobject jHitTestObj = androidHitTest.createJavaObject(env); - env->CallVoidMethod(javaObject.get(), m_javaGlue->m_focusNodeChanged, - reinterpret_cast<int>(newFocus), jHitTestObj); - env->DeleteLocalRef(jHitTestObj); } void WebViewCore::addMessageToConsole(const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceID, int msgLevel) { @@ -3759,57 +3588,7 @@ WebViewCore::getWebViewJavaObject() AutoJObject javaObject = m_javaGlue->object(env); if (!javaObject.get()) return 0; - return env->CallObjectMethod(javaObject.get(), m_javaGlue->m_getWebView); -} - -RenderTextControl* WebViewCore::toRenderTextControl(Node* node) -{ - RenderTextControl* rtc = 0; - RenderObject* renderer = node->renderer(); - if (renderer && renderer->isTextControl()) { - rtc = WebCore::toRenderTextControl(renderer); - } - return rtc; -} - -void WebViewCore::getSelectionOffsets(Node* node, int& start, int& end) -{ - RenderTextControl* rtc = toRenderTextControl(node); - if (rtc) { - start = rtc->selectionStart(); - end = rtc->selectionEnd(); - } else { - // It must be content editable field. - Document* document = node->document(); - Frame* frame = document->frame(); - SelectionController* selector = frame->selection(); - Position selectionStart = selector->start(); - Position selectionEnd = selector->end(); - Position startOfNode = firstPositionInNode(node); - RefPtr<Range> startRange = Range::create(document, startOfNode, - selectionStart); - start = TextIterator::rangeLength(startRange.get(), true); - RefPtr<Range> endRange = Range::create(document, startOfNode, - selectionEnd); - end = TextIterator::rangeLength(endRange.get(), true); - } -} - -String WebViewCore::getInputText(Node* node) -{ - String text; - WebCore::RenderTextControl* renderText = toRenderTextControl(node); - if (renderText) - text = renderText->text(); - else { - // It must be content editable field. - Position start = firstPositionInNode(node); - Position end = lastPositionInNode(node); - VisibleSelection allEditableText(start, end); - if (allEditableText.isRange()) - text = allEditableText.firstRange()->text(); - } - return text; + return env->GetObjectField(javaObject.get(), gWebViewCoreFields.m_webView); } void WebViewCore::updateTextSelection() @@ -3818,33 +3597,16 @@ void WebViewCore::updateTextSelection() AutoJObject javaObject = m_javaGlue->object(env); if (!javaObject.get()) return; - VisibleSelection selection = focusedFrame()->selection()->selection(); - int start = 0; - int end = 0; - if (selection.isCaretOrRange()) - getSelectionOffsets(selection.start().anchorNode(), start, end); - SelectText* selectText = createSelectText(selection); - env->CallVoidMethod(javaObject.get(), - m_javaGlue->m_updateTextSelection, reinterpret_cast<int>(currentFocus()), - start, end, m_textGeneration, reinterpret_cast<int>(selectText)); - checkException(env); -} - -void WebViewCore::updateTextSizeAndScroll(WebCore::Node* node) -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - AutoJObject javaObject = m_javaGlue->object(env); - if (!javaObject.get()) + WebCore::Node* focusNode = currentFocus(); + if (!focusNode) return; - RenderTextControl* rtc = toRenderTextControl(node); - if (!rtc) + RenderObject* renderer = focusNode->renderer(); + if (!renderer || (!renderer->isTextArea() && !renderer->isTextField())) return; - int width = rtc->scrollWidth(); - int height = rtc->contentHeight(); - int scrollX = rtc->scrollLeft(); - int scrollY = rtc->scrollTop(); - env->CallVoidMethod(javaObject.get(), m_javaGlue->m_updateTextSizeAndScroll, - reinterpret_cast<int>(node), width, height, scrollX, scrollY); + RenderTextControl* rtc = static_cast<RenderTextControl*>(renderer); + env->CallVoidMethod(javaObject.get(), + m_javaGlue->m_updateTextSelection, reinterpret_cast<int>(focusNode), + rtc->selectionStart(), rtc->selectionEnd(), m_textGeneration); checkException(env); } @@ -3888,18 +3650,11 @@ void WebViewCore::setBackgroundColor(SkColor c) // need (int) cast to find the right constructor WebCore::Color bcolor((int)SkColorGetR(c), (int)SkColorGetG(c), (int)SkColorGetB(c), (int)SkColorGetA(c)); - - if (view->baseBackgroundColor() == bcolor) - return; - view->setBaseBackgroundColor(bcolor); // Background color of 0 indicates we want a transparent background if (c == 0) view->setTransparent(true); - - //invalidate so the new color is shown - contentInvalidateAll(); } jclass WebViewCore::getPluginClass(const WTF::String& libName, const char* className) @@ -4025,6 +3780,21 @@ void WebViewCore::keepScreenOn(bool screenOn) { m_screenOnCounter--; } +bool WebViewCore::validNodeAndBounds(Frame* frame, Node* node, + const IntRect& originalAbsoluteBounds) +{ + bool valid = CacheBuilder::validNode(m_mainFrame, frame, node); + if (!valid) + return false; + RenderObject* renderer = node->renderer(); + if (!renderer) + return false; + IntRect absBounds = node->hasTagName(HTMLNames::areaTag) + ? CacheBuilder::getAreaRect(static_cast<HTMLAreaElement*>(node)) + : renderer->absoluteBoundingBoxRect(); + return absBounds == originalAbsoluteBounds; +} + void WebViewCore::showRect(int left, int top, int width, int height, int contentWidth, int contentHeight, float xPercentInDoc, float xPercentInView, float yPercentInDoc, float yPercentInView) @@ -4078,20 +3848,6 @@ void WebViewCore::enterFullscreenForVideoLayer(int layerId, const WTF::String& u return; jstring jUrlStr = wtfStringToJstring(env, url); env->CallVoidMethod(javaObject.get(), m_javaGlue->m_enterFullscreenForVideoLayer, layerId, jUrlStr); - m_fullscreenVideoMode = true; - checkException(env); -} - -void WebViewCore::exitFullscreenVideo() -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - AutoJObject javaObject = m_javaGlue->object(env); - if (!javaObject.get()) - return; - if (m_fullscreenVideoMode) { - env->CallVoidMethod(javaObject.get(), m_javaGlue->m_exitFullscreenVideo); - m_fullscreenVideoMode = false; - } checkException(env); } #endif @@ -4117,6 +3873,7 @@ bool WebViewCore::drawIsPaused() const return false; } +#if USE(CHROME_NETWORK_STACK) void WebViewCore::setWebRequestContextUserAgent() { // We cannot create a WebRequestContext, because we might not know it this is a private tab or not yet @@ -4144,6 +3901,7 @@ WebRequestContext* WebViewCore::webRequestContext() } return m_webRequestContext.get(); } +#endif void WebViewCore::scrollRenderLayer(int layer, const SkRect& rect) { @@ -4164,370 +3922,189 @@ void WebViewCore::scrollRenderLayer(int layer, const SkRect& rect) if (!owner) return; - if (owner->isRootLayer()) { - FrameView* view = owner->renderer()->frame()->view(); - IntPoint pt(rect.fLeft, rect.fTop); - view->setScrollPosition(pt); - } else + if (owner->stackingContext()) owner->scrollToOffset(rect.fLeft, rect.fTop); #endif } -Vector<VisibleSelection> WebViewCore::getTextRanges( - int startX, int startY, int endX, int endY) -{ - // These are the positions of the selection handles, - // which reside below the line that they are selecting. - // Use the vertical position higher, which will include - // the selected text. - startY--; - endY--; - VisiblePosition startSelect = visiblePositionForContentPoint(startX, startY); - VisiblePosition endSelect = visiblePositionForContentPoint(endX, endY); - Position start = startSelect.deepEquivalent(); - Position end = endSelect.deepEquivalent(); - Vector<VisibleSelection> ranges; - if (!start.isNull() && !end.isNull()) { - if (comparePositions(start, end) > 0) { - swap(start, end); // RTL start/end positions may be swapped - } - Position nextRangeStart = start; - Position previousRangeEnd; - do { - VisibleSelection selection(nextRangeStart, end); - ranges.append(selection); - previousRangeEnd = selection.end(); - nextRangeStart = nextCandidate(previousRangeEnd); - } while (comparePositions(previousRangeEnd, end) < 0); - } - return ranges; -} - -void WebViewCore::deleteText(int startX, int startY, int endX, int endY) -{ - Vector<VisibleSelection> ranges = - getTextRanges(startX, startY, endX, endY); - - EditorClientAndroid* client = static_cast<EditorClientAndroid*>( - m_mainFrame->editor()->client()); - client->setUiGeneratedSelectionChange(true); - - SelectionController* selector = m_mainFrame->selection(); - for (size_t i = 0; i < ranges.size(); i++) { - const VisibleSelection& selection = ranges[i]; - if (selection.isContentEditable()) { - selector->setSelection(selection, CharacterGranularity); - Document* document = selection.start().anchorNode()->document(); - WebCore::TypingCommand::deleteSelection(document, 0); - } - } - client->setUiGeneratedSelectionChange(false); -} - -void WebViewCore::insertText(const WTF::String &text) -{ - WebCore::Node* focus = currentFocus(); - if (!focus || !isTextInput(focus)) - return; - - Document* document = focus->document(); - - EditorClientAndroid* client = static_cast<EditorClientAndroid*>( - m_mainFrame->editor()->client()); - if (!client) - return; - client->setUiGeneratedSelectionChange(true); - WebCore::TypingCommand::insertText(document, text, - TypingCommand::PreventSpellChecking); - client->setUiGeneratedSelectionChange(false); -} - -void WebViewCore::resetFindOnPage() -{ - m_searchText.truncate(0); - m_matchCount = 0; - m_activeMatchIndex = 0; - m_activeMatch = 0; -} - -int WebViewCore::findTextOnPage(const WTF::String &text) -{ - resetFindOnPage(); // reset even if parameters are bad - - WebCore::Frame* frame = m_mainFrame; - if (!frame) - return 0; - - m_searchText = text; - FindOptions findOptions = WebCore::CaseInsensitive; - - do { - frame->document()->markers()->removeMarkers(DocumentMarker::TextMatch); - m_matchCount += frame->editor()->countMatchesForText(text, findOptions, - 0, true); - frame->editor()->setMarkedTextMatchesAreHighlighted(true); - frame = frame->tree()->traverseNextWithWrap(false); - } while (frame); - m_activeMatchIndex = m_matchCount - 1; // prime first findNext - return m_matchCount; -} - -int WebViewCore::findNextOnPage(bool forward) -{ - if (!m_mainFrame) - return -1; - if (!m_matchCount) - return -1; - - EditorClientAndroid* client = static_cast<EditorClientAndroid*>( - m_mainFrame->editor()->client()); - client->setUiGeneratedSelectionChange(true); - - // Clear previous active match. - if (m_activeMatch) { - m_mainFrame->document()->markers()->setMarkersActive( - m_activeMatch.get(), false); - } - - FindOptions findOptions = WebCore::CaseInsensitive - | WebCore::StartInSelection | WebCore::WrapAround; - if (!forward) - findOptions |= WebCore::Backwards; - - // Start from the previous active match. - if (m_activeMatch) { - m_mainFrame->selection()->setSelection(m_activeMatch.get()); - } - - bool found = m_mainFrame->editor()->findString(m_searchText, findOptions); - if (found) { - VisibleSelection selection(m_mainFrame->selection()->selection()); - if (selection.isNone() || selection.start() == selection.end()) { - // Temporary workaround for findString() refusing to select text - // marked "-webkit-user-select: none". - m_activeMatchIndex = 0; - m_activeMatch = 0; - } else { - // Mark current match "active". - if (forward) { - ++m_activeMatchIndex; - if (m_activeMatchIndex == m_matchCount) - m_activeMatchIndex = 0; - } else { - if (m_activeMatchIndex == 0) - m_activeMatchIndex = m_matchCount; - --m_activeMatchIndex; - } - m_activeMatch = selection.firstRange(); - m_mainFrame->document()->markers()->setMarkersActive( - m_activeMatch.get(), true); - m_mainFrame->selection()->revealSelection( - ScrollAlignment::alignCenterIfNeeded, true); - } - } - - // Clear selection so it doesn't display. - m_mainFrame->selection()->clear(); - client->setUiGeneratedSelectionChange(false); - return m_activeMatchIndex; -} - -String WebViewCore::getText(int startX, int startY, int endX, int endY) -{ - String text; - - Vector<VisibleSelection> ranges = - getTextRanges(startX, startY, endX, endY); - - for (size_t i = 0; i < ranges.size(); i++) { - const VisibleSelection& selection = ranges[i]; - if (selection.isRange()) { - PassRefPtr<Range> range = selection.firstRange(); - String textInRange = range->text(); - if (textInRange.length() > 0) { - if (text.length() > 0) - text.append('\n'); - text.append(textInRange); - } - } - } - - return text; -} - -/** - * Read the persistent locale. - */ -void WebViewCore::getLocale(String& language, String& region) -{ - char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX]; - - property_get("persist.sys.language", propLang, ""); - property_get("persist.sys.country", propRegn, ""); - if (*propLang == 0 && *propRegn == 0) { - /* Set to ro properties, default is en_US */ - property_get("ro.product.locale.language", propLang, "en"); - property_get("ro.product.locale.region", propRegn, "US"); - } - language = String(propLang, 2); - region = String(propRegn, 2); -} - -void WebViewCore::updateLocale() -{ - static String prevLang; - static String prevRegn; - String language; - String region; - - getLocale(language, region); - - if ((language != prevLang) || (region != prevRegn)) { - prevLang = language; - prevRegn = region; - GlyphPageTreeNode::resetRoots(); - fontCache()->invalidate(); - } -} - //---------------------------------------------------------------------- // Native JNI methods //---------------------------------------------------------------------- -static void RevealSelection(JNIEnv* env, jobject obj, jint nativeClass) +static void RevealSelection(JNIEnv *env, jobject obj) { - reinterpret_cast<WebViewCore*>(nativeClass)->revealSelection(); + GET_NATIVE_VIEW(env, obj)->revealSelection(); } -static jstring RequestLabel(JNIEnv* env, jobject obj, jint nativeClass, - int framePointer, int nodePointer) +static jstring RequestLabel(JNIEnv *env, jobject obj, int framePointer, + int nodePointer) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - return wtfStringToJstring(env, viewImpl->requestLabel( + return wtfStringToJstring(env, GET_NATIVE_VIEW(env, obj)->requestLabel( (WebCore::Frame*) framePointer, (WebCore::Node*) nodePointer)); } -static void ClearContent(JNIEnv* env, jobject obj, jint nativeClass) +static void ClearContent(JNIEnv *env, jobject obj) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); viewImpl->clearContent(); } -static void SetSize(JNIEnv* env, jobject obj, jint nativeClass, jint width, - jint height, jint textWrapWidth, jfloat scale, jint screenWidth, - jint screenHeight, jint anchorX, jint anchorY, jboolean ignoreHeight) +static void UpdateFrameCacheIfLoading(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->updateFrameCacheIfLoading(); +} + +static void SetSize(JNIEnv *env, jobject obj, jint width, jint height, + jint textWrapWidth, jfloat scale, jint screenWidth, jint screenHeight, + jint anchorX, jint anchorY, jboolean ignoreHeight) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOGV("webviewcore::nativeSetSize(%u %u)\n viewImpl: %p", (unsigned)width, (unsigned)height, viewImpl); - ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSetSize"); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOGV("webviewcore::nativeSetSize(%u %u)\n viewImpl: %p", (unsigned)width, (unsigned)height, viewImpl); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSetSize"); viewImpl->setSizeScreenWidthAndScale(width, height, textWrapWidth, scale, screenWidth, screenHeight, anchorX, anchorY, ignoreHeight); } -static void SetScrollOffset(JNIEnv* env, jobject obj, jint nativeClass, - jboolean sendScrollEvent, jint x, jint y) +static void SetScrollOffset(JNIEnv *env, jobject obj, jint gen, jboolean sendScrollEvent, jint x, jint y) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "need viewImpl"); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); - viewImpl->setScrollOffset(sendScrollEvent, x, y); + viewImpl->setScrollOffset(gen, sendScrollEvent, x, y); } -static void SetGlobalBounds(JNIEnv* env, jobject obj, jint nativeClass, - jint x, jint y, jint h, jint v) +static void SetGlobalBounds(JNIEnv *env, jobject obj, jint x, jint y, jint h, + jint v) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "need viewImpl"); +#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 nativeClass, jint keyCode, - jint unichar, jint repeatCount, jboolean isShift, jboolean isAlt, - jboolean isSym, jboolean isDown) +static jboolean Key(JNIEnv *env, jobject obj, jint keyCode, jint unichar, + jint repeatCount, jboolean isShift, jboolean isAlt, jboolean isSym, + jboolean isDown) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - return viewImpl->key(PlatformKeyboardEvent(keyCode, +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + return GET_NATIVE_VIEW(env, obj)->key(PlatformKeyboardEvent(keyCode, unichar, repeatCount, isDown, isShift, isAlt, isSym)); } -static void SetInitialFocus(JNIEnv* env, jobject obj, jint nativeClass, - jint keyDirection) +static void Click(JNIEnv *env, jobject obj, int framePtr, int nodePtr, jboolean fake) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - viewImpl->setInitialFocus(PlatformKeyboardEvent(keyDirection, - 0, 0, false, false, false, false)); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in Click"); + + viewImpl->click(reinterpret_cast<WebCore::Frame*>(framePtr), + reinterpret_cast<WebCore::Node*>(nodePtr), fake); } -static void ContentInvalidateAll(JNIEnv* env, jobject obj, jint nativeClass) +static void ContentInvalidateAll(JNIEnv *env, jobject obj) { - reinterpret_cast<WebViewCore*>(nativeClass)->contentInvalidateAll(); + GET_NATIVE_VIEW(env, obj)->contentInvalidateAll(); } -static void DeleteSelection(JNIEnv* env, jobject obj, jint nativeClass, - jint start, jint end, jint textGeneration) +static void DeleteSelection(JNIEnv *env, jobject obj, jint start, jint end, + jint textGeneration) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); viewImpl->deleteSelection(start, end, textGeneration); } -static void SetSelection(JNIEnv* env, jobject obj, jint nativeClass, - jint start, jint end) +static void SetSelection(JNIEnv *env, jobject obj, jint start, jint end) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); viewImpl->setSelection(start, end); } -static jstring ModifySelection(JNIEnv* env, jobject obj, jint nativeClass, - jint direction, jint granularity) +static jstring ModifySelection(JNIEnv *env, jobject obj, jint direction, jint granularity) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); String selectionString = viewImpl->modifySelection(direction, granularity); return wtfStringToJstring(env, selectionString); } -static void ReplaceTextfieldText(JNIEnv* env, jobject obj, jint nativeClass, +static void ReplaceTextfieldText(JNIEnv *env, jobject obj, jint oldStart, jint oldEnd, jstring replace, jint start, jint end, jint textGeneration) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); WTF::String webcoreString = jstringToWtfString(env, replace); viewImpl->replaceTextfieldText(oldStart, oldEnd, webcoreString, start, end, textGeneration); } -static void PassToJs(JNIEnv* env, jobject obj, jint nativeClass, +static void PassToJs(JNIEnv *env, jobject obj, jint generation, jstring currentText, jint keyCode, jint keyValue, jboolean down, jboolean cap, jboolean fn, jboolean sym) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif WTF::String current = jstringToWtfString(env, currentText); - reinterpret_cast<WebViewCore*>(nativeClass)->passToJs(generation, current, + GET_NATIVE_VIEW(env, obj)->passToJs(generation, current, PlatformKeyboardEvent(keyCode, keyValue, 0, down, cap, fn, sym)); } -static void ScrollFocusedTextInput(JNIEnv* env, jobject obj, jint nativeClass, - jfloat xPercent, jint y, jobject contentBounds) +static void ScrollFocusedTextInput(JNIEnv *env, jobject obj, jfloat xPercent, + jint y) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - IntRect bounds = viewImpl->scrollFocusedTextInput(xPercent, y); - if (contentBounds) - GraphicsJNI::irect_to_jrect(bounds, env, contentBounds); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->scrollFocusedTextInput(xPercent, y); } -static void SetFocusControllerActive(JNIEnv* env, jobject obj, jint nativeClass, - jboolean active) +static void SetFocusControllerActive(JNIEnv *env, jobject obj, jboolean active) { - ALOGV("webviewcore::nativeSetFocusControllerActive()\n"); - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSetFocusControllerActive"); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeSetFocusControllerActive()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSetFocusControllerActive"); viewImpl->setFocusControllerActive(active); } -static void SaveDocumentState(JNIEnv* env, jobject obj, jint nativeClass) +static void SaveDocumentState(JNIEnv *env, jobject obj, jint frame) { - ALOGV("webviewcore::nativeSaveDocumentState()\n"); - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSaveDocumentState"); - viewImpl->saveDocumentState(viewImpl->focusedFrame()); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeSaveDocumentState()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSaveDocumentState"); + viewImpl->saveDocumentState((WebCore::Frame*) frame); } void WebViewCore::addVisitedLink(const UChar* string, int length) @@ -4536,26 +4113,53 @@ void WebViewCore::addVisitedLink(const UChar* string, int length) m_groupForVisitedLinks->addVisitedLink(string, length); } -static void NotifyAnimationStarted(JNIEnv* env, jobject obj, jint nativeClass) +static bool UpdateLayers(JNIEnv *env, jobject obj, jint nativeClass, jint jbaseLayer) +{ + WebViewCore* viewImpl = (WebViewCore*) nativeClass; + BaseLayerAndroid* baseLayer = (BaseLayerAndroid*) jbaseLayer; + if (baseLayer) { + LayerAndroid* root = static_cast<LayerAndroid*>(baseLayer->getChild(0)); + if (root) + return viewImpl->updateLayers(root); + } + return true; +} + +static void NotifyAnimationStarted(JNIEnv *env, jobject obj, jint nativeClass) { WebViewCore* viewImpl = (WebViewCore*) nativeClass; viewImpl->notifyAnimationStarted(); } -static jint RecordContent(JNIEnv* env, jobject obj, jint nativeClass, jobject pt) +static jint RecordContent(JNIEnv *env, jobject obj, jobject region, jobject pt) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + SkRegion* nativeRegion = GraphicsJNI::getNativeRegion(env, region); SkIPoint nativePt; - BaseLayerAndroid* result = viewImpl->recordContent(&nativePt); + BaseLayerAndroid* result = viewImpl->recordContent(nativeRegion, &nativePt); GraphicsJNI::ipoint_to_jpoint(nativePt, env, pt); return reinterpret_cast<jint>(result); } -static void SendListBoxChoice(JNIEnv* env, jobject obj, jint nativeClass, - jint choice) +static void SplitContent(JNIEnv *env, jobject obj, jint content) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->splitContent(reinterpret_cast<PictureSet*>(content)); +} + +static void SendListBoxChoice(JNIEnv* env, jobject obj, jint choice) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSendListBoxChoice"); +#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); } @@ -4565,11 +4169,14 @@ static void SendListBoxChoice(JNIEnv* env, jobject obj, jint nativeClass, // number of items in the average multiple-select listbox. #define PREPARED_LISTBOX_STORAGE 10 -static void SendListBoxChoices(JNIEnv* env, jobject obj, jint nativeClass, - jbooleanArray jArray, jint size) +static void SendListBoxChoices(JNIEnv* env, jobject obj, jbooleanArray jArray, + jint size) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSendListBoxChoices"); +#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(); @@ -4583,19 +4190,21 @@ static void SendListBoxChoices(JNIEnv* env, jobject obj, jint nativeClass, viewImpl->popupReply(array, count); } -// TODO: Move this to WebView.cpp since it is only needed there -static jstring FindAddress(JNIEnv* env, jobject obj, jstring addr, - jboolean caseInsensitive) +static jstring FindAddress(JNIEnv *env, jobject obj, jstring addr, + jboolean caseInsensitive) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif if (!addr) return 0; int length = env->GetStringLength(addr); if (!length) return 0; const jchar* addrChars = env->GetStringChars(addr, 0); - size_t start, end; - AddressDetector detector; - bool success = detector.FindContent(addrChars, addrChars + length, &start, &end); + int start, end; + bool success = CacheBuilder::FindAddress(addrChars, length, + &start, &end, caseInsensitive) == CacheBuilder::FOUND_COMPLETE; jstring ret = 0; if (success) ret = env->NewString(addrChars + start, end - start); @@ -4603,12 +4212,15 @@ static jstring FindAddress(JNIEnv* env, jobject obj, jstring addr, return ret; } -static jint HandleTouchEvent(JNIEnv* env, jobject obj, jint nativeClass, - jint action, jintArray idArray, jintArray xArray, jintArray yArray, - jint count, jint actionIndex, jint metaState) +static jboolean HandleTouchEvent(JNIEnv *env, jobject obj, jint action, jintArray idArray, + jintArray xArray, jintArray yArray, + jint count, jint actionIndex, jint metaState) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); jint* ptrIdArray = env->GetIntArrayElements(idArray, 0); jint* ptrXArray = env->GetIntArrayElements(xArray, 0); jint* ptrYArray = env->GetIntArrayElements(yArray, 0); @@ -4626,53 +4238,105 @@ static jint HandleTouchEvent(JNIEnv* env, jobject obj, jint nativeClass, return viewImpl->handleTouchEvent(action, ids, points, actionIndex, metaState); } -static bool MouseClick(JNIEnv* env, jobject obj, jint nativeClass) +static void TouchUp(JNIEnv *env, jobject obj, jint touchGeneration, + jint frame, jint node, jint x, jint y) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - return viewImpl->performMouseClick(); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->touchUp(touchGeneration, + (WebCore::Frame*) frame, (WebCore::Node*) node, x, y); } -static jstring RetrieveHref(JNIEnv* env, jobject obj, jint nativeClass, - jint x, jint y) +static jstring RetrieveHref(JNIEnv *env, jobject obj, jint x, jint y) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); WTF::String result = viewImpl->retrieveHref(x, y); if (!result.isEmpty()) return wtfStringToJstring(env, result); return 0; } -static jstring RetrieveAnchorText(JNIEnv* env, jobject obj, jint nativeClass, - jint x, jint y) +static jstring RetrieveAnchorText(JNIEnv *env, jobject obj, jint x, jint y) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); WTF::String result = viewImpl->retrieveAnchorText(x, y); if (!result.isEmpty()) return wtfStringToJstring(env, result); return 0; } -static jstring RetrieveImageSource(JNIEnv* env, jobject obj, jint nativeClass, - jint x, jint y) +static jstring RetrieveImageSource(JNIEnv *env, jobject obj, jint x, jint y) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - WTF::String result = viewImpl->retrieveImageSource(x, y); + WTF::String result = GET_NATIVE_VIEW(env, obj)->retrieveImageSource(x, y); return !result.isEmpty() ? wtfStringToJstring(env, result) : 0; } -static void MoveMouse(JNIEnv* env, jobject obj, jint nativeClass, jint x, jint y) +static void StopPaintingCaret(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setShouldPaintCaret(false); +} + +static void MoveFocus(JNIEnv *env, jobject obj, jint framePtr, jint nodePtr) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); - viewImpl->moveMouse(x, 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->moveFocus((WebCore::Frame*) framePtr, (WebCore::Node*) nodePtr); } -static jint GetContentMinPrefWidth(JNIEnv* env, jobject obj, jint nativeClass) +static void MoveMouse(JNIEnv *env, jobject obj, jint frame, + jint x, jint y) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveMouse((WebCore::Frame*) frame, x, y); +} + +static void MoveMouseIfLatest(JNIEnv *env, jobject obj, jint moveGeneration, + jint frame, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveMouseIfLatest(moveGeneration, + (WebCore::Frame*) frame, x, y); +} + +static void UpdateFrameCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->updateFrameCache(); +} + +static jint GetContentMinPrefWidth(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); WebCore::Frame* frame = viewImpl->mainFrame(); if (frame) { @@ -4687,11 +4351,13 @@ static jint GetContentMinPrefWidth(JNIEnv* env, jobject obj, jint nativeClass) return 0; } -static void SetViewportSettingsFromNative(JNIEnv* env, jobject obj, - jint nativeClass) +static void SetViewportSettingsFromNative(JNIEnv *env, jobject obj) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); +#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) @@ -4708,49 +4374,66 @@ static void SetViewportSettingsFromNative(JNIEnv* env, jobject obj, #endif } -static void SetBackgroundColor(JNIEnv* env, jobject obj, jint nativeClass, - jint color) +static void SetBackgroundColor(JNIEnv *env, jobject obj, jint color) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); viewImpl->setBackgroundColor((SkColor) color); } -static void DumpDomTree(JNIEnv* env, jobject obj, jint nativeClass, - jboolean useFile) +static void DumpDomTree(JNIEnv *env, jobject obj, jboolean useFile) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + 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, jint nativeClass, - jboolean useFile) +static void DumpRenderTree(JNIEnv *env, jobject obj, jboolean useFile) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); viewImpl->dumpRenderTree(useFile); } -static void SetJsFlags(JNIEnv* env, jobject obj, jint nativeClass, jstring flags) +static void DumpNavTree(JNIEnv *env, jobject obj) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->dumpNavTree(); +} + +static void DumpV8Counters(JNIEnv*, jobject) { +#if USE(V8) +#ifdef ANDROID_INSTRUMENT + V8Counters::dumpCounters(); +#endif +#endif +} + +static void SetJsFlags(JNIEnv *env, jobject obj, jstring flags) +{ +#if USE(V8) WTF::String flagsString = jstringToWtfString(env, flags); WTF::CString utf8String = flagsString.utf8(); WebCore::ScriptController::setFlags(utf8String.data(), utf8String.length()); +#endif } // Called from the Java side to set a new quota for the origin or new appcache // max size in response to a notification that the original quota was exceeded or // that the appcache has reached its maximum size. -static void SetNewStorageLimit(JNIEnv* env, jobject obj, jint nativeClass, - jlong quota) -{ +static void SetNewStorageLimit(JNIEnv* env, jobject obj, jlong quota) { #if ENABLE(DATABASE) || ENABLE(OFFLINE_WEB_APPLICATIONS) - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); Frame* frame = viewImpl->mainFrame(); // The main thread is blocked awaiting this response, so now we can wake it @@ -4761,102 +4444,93 @@ static void SetNewStorageLimit(JNIEnv* env, jobject obj, jint nativeClass, } // Called from Java to provide a Geolocation permission state for the specified origin. -static void GeolocationPermissionsProvide(JNIEnv* env, jobject obj, - jint nativeClass, jstring origin, jboolean allow, jboolean remember) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); +static void GeolocationPermissionsProvide(JNIEnv* env, jobject obj, jstring origin, jboolean allow, jboolean remember) { + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); Frame* frame = viewImpl->mainFrame(); ChromeClientAndroid* chromeClient = static_cast<ChromeClientAndroid*>(frame->page()->chrome()->client()); chromeClient->provideGeolocationPermissions(jstringToWtfString(env, origin), allow, remember); } -static void RegisterURLSchemeAsLocal(JNIEnv* env, jobject obj, jint nativeClass, - jstring scheme) -{ +static void RegisterURLSchemeAsLocal(JNIEnv* env, jobject obj, jstring scheme) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif WebCore::SchemeRegistry::registerURLSchemeAsLocal(jstringToWtfString(env, scheme)); } -static bool FocusBoundsChanged(JNIEnv* env, jobject obj, jint nativeClass) +static bool FocusBoundsChanged(JNIEnv* env, jobject obj) { - return reinterpret_cast<WebViewCore*>(nativeClass)->focusBoundsChanged(); + return GET_NATIVE_VIEW(env, obj)->focusBoundsChanged(); } -static void SetIsPaused(JNIEnv* env, jobject obj, jint nativeClass, - jboolean isPaused) +static void SetIsPaused(JNIEnv* env, jobject obj, jboolean isPaused) { // tell the webcore thread to stop thinking while we do other work // (selection and scrolling). This has nothing to do with the lifecycle // pause and resume. - reinterpret_cast<WebViewCore*>(nativeClass)->setIsPaused(isPaused); + GET_NATIVE_VIEW(env, obj)->setIsPaused(isPaused); } -static void Pause(JNIEnv* env, jobject obj, jint nativeClass) +static void Pause(JNIEnv* env, jobject obj) { // This is called for the foreground tab when the browser is put to the // background (and also for any tab when it is put to the background of the // browser). The browser can only be killed by the system when it is in the // background, so saving the Geolocation permission state now ensures that // is maintained when the browser is killed. - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ChromeClient* chromeClient = viewImpl->mainFrame()->page()->chrome()->client(); + ChromeClient* chromeClient = GET_NATIVE_VIEW(env, obj)->mainFrame()->page()->chrome()->client(); ChromeClientAndroid* chromeClientAndroid = static_cast<ChromeClientAndroid*>(chromeClient); chromeClientAndroid->storeGeolocationPermissions(); - Frame* mainFrame = viewImpl->mainFrame(); + Frame* mainFrame = GET_NATIVE_VIEW(env, obj)->mainFrame(); for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext()) { Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); if (geolocation) geolocation->suspend(); } - if (mainFrame) - mainFrame->settings()->setMinDOMTimerInterval(BACKGROUND_TIMER_INTERVAL); - viewImpl->deviceMotionAndOrientationManager()->maybeSuspendClients(); + GET_NATIVE_VIEW(env, obj)->deviceMotionAndOrientationManager()->maybeSuspendClients(); ANPEvent event; SkANP::InitEvent(&event, kLifecycle_ANPEventType); event.data.lifecycle.action = kPause_ANPLifecycleAction; - viewImpl->sendPluginEvent(event); + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); - viewImpl->setIsPaused(true); + GET_NATIVE_VIEW(env, obj)->setIsPaused(true); } -static void Resume(JNIEnv* env, jobject obj, jint nativeClass) +static void Resume(JNIEnv* env, jobject obj) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - Frame* mainFrame = viewImpl->mainFrame(); + Frame* mainFrame = GET_NATIVE_VIEW(env, obj)->mainFrame(); for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext()) { Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); if (geolocation) geolocation->resume(); } - if (mainFrame) - mainFrame->settings()->setMinDOMTimerInterval(FOREGROUND_TIMER_INTERVAL); - viewImpl->deviceMotionAndOrientationManager()->maybeResumeClients(); + GET_NATIVE_VIEW(env, obj)->deviceMotionAndOrientationManager()->maybeResumeClients(); ANPEvent event; SkANP::InitEvent(&event, kLifecycle_ANPEventType); event.data.lifecycle.action = kResume_ANPLifecycleAction; - viewImpl->sendPluginEvent(event); + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); - viewImpl->setIsPaused(false); + GET_NATIVE_VIEW(env, obj)->setIsPaused(false); } -static void FreeMemory(JNIEnv* env, jobject obj, jint nativeClass) +static void FreeMemory(JNIEnv* env, jobject obj) { ANPEvent event; SkANP::InitEvent(&event, kLifecycle_ANPEventType); event.data.lifecycle.action = kFreeMemory_ANPLifecycleAction; - reinterpret_cast<WebViewCore*>(nativeClass)->sendPluginEvent(event); + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); } -static void ProvideVisitedHistory(JNIEnv* env, jobject obj, jint nativeClass, - jobject hist) +static void ProvideVisitedHistory(JNIEnv *env, jobject obj, jobject hist) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); jobjectArray array = static_cast<jobjectArray>(hist); @@ -4871,38 +4545,78 @@ static void ProvideVisitedHistory(JNIEnv* env, jobject obj, jint nativeClass, } } -static void PluginSurfaceReady(JNIEnv* env, jobject obj, jint nativeClass) +static void PluginSurfaceReady(JNIEnv* env, jobject obj) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); if (viewImpl) viewImpl->sendPluginSurfaceReady(); } // Notification from the UI thread that the plugin's full-screen surface has been discarded -static void FullScreenPluginHidden(JNIEnv* env, jobject obj, jint nativeClass, - jint npp) +static void FullScreenPluginHidden(JNIEnv* env, jobject obj, jint npp) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); PluginWidgetAndroid* plugin = viewImpl->getPluginWidget((NPP)npp); if (plugin) plugin->exitFullScreen(false); } -static jobject HitTest(JNIEnv* env, jobject obj, jint nativeClass, jint x, - jint y, jint slop, jboolean doMoveMouse) +static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj) +{ + int L, T, R, B; + GraphicsJNI::get_jrect(env, obj, &L, &T, &R, &B); + return WebCore::IntRect(L, T, R - L, B - T); +} + +static bool ValidNodeAndBounds(JNIEnv *env, jobject obj, int frame, int node, + jobject rect) +{ + IntRect nativeRect = jrect_to_webrect(env, rect); + return GET_NATIVE_VIEW(env, obj)->validNodeAndBounds( + reinterpret_cast<Frame*>(frame), + reinterpret_cast<Node*>(node), nativeRect); +} + +static jobject GetTouchHighlightRects(JNIEnv* env, jobject obj, jint x, jint y, jint slop) { - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); if (!viewImpl) return 0; - AndroidHitTestResult result = viewImpl->hitTestAtPoint(x, y, slop, doMoveMouse); - return result.createJavaObject(env); + Vector<IntRect> rects = viewImpl->getTouchHighlightRects(x, y, slop); + if (rects.isEmpty()) + return 0; + + jclass arrayClass = env->FindClass("java/util/ArrayList"); + LOG_ASSERT(arrayClass, "Could not find java/util/ArrayList"); + jmethodID init = env->GetMethodID(arrayClass, "<init>", "(I)V"); + LOG_ASSERT(init, "Could not find constructor for ArrayList"); + jobject array = env->NewObject(arrayClass, init, rects.size()); + LOG_ASSERT(array, "Could not create a new ArrayList"); + jmethodID add = env->GetMethodID(arrayClass, "add", "(Ljava/lang/Object;)Z"); + LOG_ASSERT(add, "Could not find add method on ArrayList"); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find android/graphics/Rect"); + jmethodID rectinit = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + LOG_ASSERT(rectinit, "Could not find init method on Rect"); + + for (size_t i = 0; i < rects.size(); i++) { + jobject rect = env->NewObject(rectClass, rectinit, rects[i].x(), + rects[i].y(), rects[i].maxX(), rects[i].maxY()); + if (rect) { + env->CallBooleanMethod(array, add, rect); + env->DeleteLocalRef(rect); + } + } + + env->DeleteLocalRef(rectClass); + env->DeleteLocalRef(arrayClass); + return array; } -static void AutoFillForm(JNIEnv* env, jobject obj, jint nativeClass, - jint queryId) +static void AutoFillForm(JNIEnv* env, jobject obj, jint queryId) { #if ENABLE(WEB_AUTOFILL) - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); if (!viewImpl) return; @@ -4915,87 +4629,19 @@ static void AutoFillForm(JNIEnv* env, jobject obj, jint nativeClass, #endif } -static void CloseIdleConnections(JNIEnv* env, jobject obj, jint nativeClass) +static void CloseIdleConnections(JNIEnv* env, jobject obj) { +#if USE(CHROME_NETWORK_STACK) WebCache::get(true)->closeIdleConnections(); WebCache::get(false)->closeIdleConnections(); +#endif } -static void nativeCertTrustChanged(JNIEnv *env, jobject obj) -{ - WebCache::get(true)->certTrustChanged(); - WebCache::get(false)->certTrustChanged(); -} - -static void ScrollRenderLayer(JNIEnv* env, jobject obj, jint nativeClass, - jint layer, jobject jRect) +static void ScrollRenderLayer(JNIEnv* env, jobject obj, jint layer, jobject jRect) { SkRect rect; GraphicsJNI::jrect_to_rect(env, jRect, &rect); - reinterpret_cast<WebViewCore*>(nativeClass)->scrollRenderLayer(layer, rect); -} - -static void DeleteText(JNIEnv* env, jobject obj, jint nativeClass, - jint startX, jint startY, jint endX, jint endY) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - viewImpl->deleteText(startX, startY, endX, endY); -} - -static void InsertText(JNIEnv* env, jobject obj, jint nativeClass, - jstring text) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - WTF::String wtfText = jstringToWtfString(env, text); - viewImpl->insertText(wtfText); -} - -static jobject GetText(JNIEnv* env, jobject obj, jint nativeClass, - jint startX, jint startY, jint endX, jint endY) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - WTF::String text = viewImpl->getText(startX, startY, endX, endY); - return text.isEmpty() ? 0 : wtfStringToJstring(env, text); -} - -static void SelectText(JNIEnv* env, jobject obj, jint nativeClass, - jint startX, jint startY, jint endX, jint endY) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - viewImpl->selectText(startX, startY, endX, endY); -} - -static void ClearSelection(JNIEnv* env, jobject obj, jint nativeClass) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - viewImpl->focusedFrame()->selection()->clear(); -} - -static bool SelectWordAt(JNIEnv* env, jobject obj, jint nativeClass, jint x, jint y) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - return viewImpl->selectWordAt(x, y); -} - -static void SelectAll(JNIEnv* env, jobject obj, jint nativeClass) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - viewImpl->focusedFrame()->selection()->selectAll(); -} - -static int FindAll(JNIEnv* env, jobject obj, jint nativeClass, - jstring text) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - WTF::String wtfText = jstringToWtfString(env, text); - return viewImpl->findTextOnPage(wtfText); -} - -static int FindNext(JNIEnv* env, jobject obj, jint nativeClass, - jboolean forward) -{ - WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); - return viewImpl->findNextOnPage(forward); + GET_NATIVE_VIEW(env, obj)->scrollRenderLayer(layer, rect); } // ---------------------------------------------------------------------------- @@ -5004,159 +4650,164 @@ static int FindNext(JNIEnv* env, jobject obj, jint nativeClass, * JNI registration. */ static JNINativeMethod gJavaWebViewCoreMethods[] = { - { "nativeClearContent", "(I)V", + { "nativeClearContent", "()V", (void*) ClearContent }, - { "nativeFocusBoundsChanged", "(I)Z", + { "nativeFocusBoundsChanged", "()Z", (void*) FocusBoundsChanged } , - { "nativeKey", "(IIIIZZZZ)Z", + { "nativeKey", "(IIIZZZZ)Z", (void*) Key }, - { "nativeContentInvalidateAll", "(I)V", + { "nativeClick", "(IIZ)V", + (void*) Click }, + { "nativeContentInvalidateAll", "()V", (void*) ContentInvalidateAll }, - { "nativeSendListBoxChoices", "(I[ZI)V", + { "nativeSendListBoxChoices", "([ZI)V", (void*) SendListBoxChoices }, - { "nativeSendListBoxChoice", "(II)V", + { "nativeSendListBoxChoice", "(I)V", (void*) SendListBoxChoice }, - { "nativeSetSize", "(IIIIFIIIIZ)V", + { "nativeSetSize", "(IIIFIIIIZ)V", (void*) SetSize }, { "nativeSetScrollOffset", "(IZII)V", (void*) SetScrollOffset }, - { "nativeSetGlobalBounds", "(IIIII)V", + { "nativeSetGlobalBounds", "(IIII)V", (void*) SetGlobalBounds }, - { "nativeSetSelection", "(III)V", + { "nativeSetSelection", "(II)V", (void*) SetSelection } , - { "nativeModifySelection", "(III)Ljava/lang/String;", + { "nativeModifySelection", "(II)Ljava/lang/String;", (void*) ModifySelection }, - { "nativeDeleteSelection", "(IIII)V", + { "nativeDeleteSelection", "(III)V", (void*) DeleteSelection } , - { "nativeReplaceTextfieldText", "(IIILjava/lang/String;III)V", + { "nativeReplaceTextfieldText", "(IILjava/lang/String;III)V", (void*) ReplaceTextfieldText } , + { "nativeMoveFocus", "(II)V", + (void*) MoveFocus }, { "nativeMoveMouse", "(III)V", (void*) MoveMouse }, - { "passToJs", "(IILjava/lang/String;IIZZZZ)V", + { "nativeMoveMouseIfLatest", "(IIII)V", + (void*) MoveMouseIfLatest }, + { "passToJs", "(ILjava/lang/String;IIZZZZ)V", (void*) PassToJs }, - { "nativeScrollFocusedTextInput", "(IFILandroid/graphics/Rect;)V", + { "nativeScrollFocusedTextInput", "(FI)V", (void*) ScrollFocusedTextInput }, - { "nativeSetFocusControllerActive", "(IZ)V", + { "nativeSetFocusControllerActive", "(Z)V", (void*) SetFocusControllerActive }, { "nativeSaveDocumentState", "(I)V", (void*) SaveDocumentState }, { "nativeFindAddress", "(Ljava/lang/String;Z)Ljava/lang/String;", (void*) FindAddress }, - { "nativeHandleTouchEvent", "(II[I[I[IIII)I", - (void*) HandleTouchEvent }, - { "nativeMouseClick", "(I)Z", - (void*) MouseClick }, - { "nativeRetrieveHref", "(III)Ljava/lang/String;", + { "nativeHandleTouchEvent", "(I[I[I[IIII)Z", + (void*) HandleTouchEvent }, + { "nativeTouchUp", "(IIIII)V", + (void*) TouchUp }, + { "nativeRetrieveHref", "(II)Ljava/lang/String;", (void*) RetrieveHref }, - { "nativeRetrieveAnchorText", "(III)Ljava/lang/String;", + { "nativeRetrieveAnchorText", "(II)Ljava/lang/String;", (void*) RetrieveAnchorText }, - { "nativeRetrieveImageSource", "(III)Ljava/lang/String;", + { "nativeRetrieveImageSource", "(II)Ljava/lang/String;", (void*) RetrieveImageSource }, - { "nativeGetContentMinPrefWidth", "(I)I", + { "nativeStopPaintingCaret", "()V", + (void*) StopPaintingCaret }, + { "nativeUpdateFrameCache", "()V", + (void*) UpdateFrameCache }, + { "nativeGetContentMinPrefWidth", "()I", (void*) GetContentMinPrefWidth }, + { "nativeUpdateLayers", "(II)Z", + (void*) UpdateLayers }, { "nativeNotifyAnimationStarted", "(I)V", (void*) NotifyAnimationStarted }, - { "nativeRecordContent", "(ILandroid/graphics/Point;)I", + { "nativeRecordContent", "(Landroid/graphics/Region;Landroid/graphics/Point;)I", (void*) RecordContent }, - { "setViewportSettingsFromNative", "(I)V", + { "setViewportSettingsFromNative", "()V", (void*) SetViewportSettingsFromNative }, - { "nativeSetBackgroundColor", "(II)V", + { "nativeSplitContent", "(I)V", + (void*) SplitContent }, + { "nativeSetBackgroundColor", "(I)V", (void*) SetBackgroundColor }, - { "nativeRegisterURLSchemeAsLocal", "(ILjava/lang/String;)V", + { "nativeRegisterURLSchemeAsLocal", "(Ljava/lang/String;)V", (void*) RegisterURLSchemeAsLocal }, - { "nativeDumpDomTree", "(IZ)V", + { "nativeDumpDomTree", "(Z)V", (void*) DumpDomTree }, - { "nativeDumpRenderTree", "(IZ)V", + { "nativeDumpRenderTree", "(Z)V", (void*) DumpRenderTree }, - { "nativeSetNewStorageLimit", "(IJ)V", + { "nativeDumpNavTree", "()V", + (void*) DumpNavTree }, + { "nativeDumpV8Counters", "()V", + (void*) DumpV8Counters }, + { "nativeSetNewStorageLimit", "(J)V", (void*) SetNewStorageLimit }, - { "nativeGeolocationPermissionsProvide", "(ILjava/lang/String;ZZ)V", + { "nativeGeolocationPermissionsProvide", "(Ljava/lang/String;ZZ)V", (void*) GeolocationPermissionsProvide }, - { "nativeSetIsPaused", "(IZ)V", (void*) SetIsPaused }, - { "nativePause", "(I)V", (void*) Pause }, - { "nativeResume", "(I)V", (void*) Resume }, - { "nativeFreeMemory", "(I)V", (void*) FreeMemory }, - { "nativeSetJsFlags", "(ILjava/lang/String;)V", (void*) SetJsFlags }, - { "nativeRequestLabel", "(III)Ljava/lang/String;", + { "nativeSetIsPaused", "(Z)V", (void*) SetIsPaused }, + { "nativePause", "()V", (void*) Pause }, + { "nativeResume", "()V", (void*) Resume }, + { "nativeFreeMemory", "()V", (void*) FreeMemory }, + { "nativeSetJsFlags", "(Ljava/lang/String;)V", (void*) SetJsFlags }, + { "nativeRequestLabel", "(II)Ljava/lang/String;", (void*) RequestLabel }, - { "nativeRevealSelection", "(I)V", (void*) RevealSelection }, - { "nativeProvideVisitedHistory", "(I[Ljava/lang/String;)V", + { "nativeRevealSelection", "()V", (void*) RevealSelection }, + { "nativeUpdateFrameCacheIfLoading", "()V", + (void*) UpdateFrameCacheIfLoading }, + { "nativeProvideVisitedHistory", "([Ljava/lang/String;)V", (void*) ProvideVisitedHistory }, - { "nativeFullScreenPluginHidden", "(II)V", + { "nativeFullScreenPluginHidden", "(I)V", (void*) FullScreenPluginHidden }, - { "nativePluginSurfaceReady", "(I)V", + { "nativePluginSurfaceReady", "()V", (void*) PluginSurfaceReady }, - { "nativeHitTest", "(IIIIZ)Landroid/webkit/WebViewCore$WebKitHitTest;", - (void*) HitTest }, - { "nativeAutoFillForm", "(II)V", + { "nativeValidNodeAndBounds", "(IILandroid/graphics/Rect;)Z", + (void*) ValidNodeAndBounds }, + { "nativeGetTouchHighlightRects", "(III)Ljava/util/ArrayList;", + (void*) GetTouchHighlightRects }, + { "nativeAutoFillForm", "(I)V", (void*) AutoFillForm }, - { "nativeScrollLayer", "(IILandroid/graphics/Rect;)V", + { "nativeScrollLayer", "(ILandroid/graphics/Rect;)V", (void*) ScrollRenderLayer }, - { "nativeCloseIdleConnections", "(I)V", + { "nativeCloseIdleConnections", "()V", (void*) CloseIdleConnections }, - { "nativeDeleteText", "(IIIII)V", - (void*) DeleteText }, - { "nativeInsertText", "(ILjava/lang/String;)V", - (void*) InsertText }, - { "nativeGetText", "(IIIII)Ljava/lang/String;", - (void*) GetText }, - { "nativeSelectText", "(IIIII)V", - (void*) SelectText }, - { "nativeClearTextSelection", "(I)V", - (void*) ClearSelection }, - { "nativeSelectWordAt", "(III)Z", - (void*) SelectWordAt }, - { "nativeSelectAll", "(I)V", - (void*) SelectAll }, - { "nativeCertTrustChanged","()V", - (void*) nativeCertTrustChanged }, - { "nativeFindAll", "(ILjava/lang/String;)I", - (void*) FindAll }, - { "nativeFindNext", "(IZ)I", - (void*) FindNext }, - { "nativeSetInitialFocus", "(II)V", (void*) SetInitialFocus }, }; int registerWebViewCore(JNIEnv* env) { jclass widget = env->FindClass("android/webkit/WebViewCore"); - ALOG_ASSERT(widget, + LOG_ASSERT(widget, "Unable to find class android/webkit/WebViewCore"); gWebViewCoreFields.m_nativeClass = env->GetFieldID(widget, "mNativeClass", "I"); - ALOG_ASSERT(gWebViewCoreFields.m_nativeClass, + LOG_ASSERT(gWebViewCoreFields.m_nativeClass, "Unable to find android/webkit/WebViewCore.mNativeClass"); gWebViewCoreFields.m_viewportWidth = env->GetFieldID(widget, "mViewportWidth", "I"); - ALOG_ASSERT(gWebViewCoreFields.m_viewportWidth, + LOG_ASSERT(gWebViewCoreFields.m_viewportWidth, "Unable to find android/webkit/WebViewCore.mViewportWidth"); gWebViewCoreFields.m_viewportHeight = env->GetFieldID(widget, "mViewportHeight", "I"); - ALOG_ASSERT(gWebViewCoreFields.m_viewportHeight, + LOG_ASSERT(gWebViewCoreFields.m_viewportHeight, "Unable to find android/webkit/WebViewCore.mViewportHeight"); gWebViewCoreFields.m_viewportInitialScale = env->GetFieldID(widget, "mViewportInitialScale", "I"); - ALOG_ASSERT(gWebViewCoreFields.m_viewportInitialScale, + LOG_ASSERT(gWebViewCoreFields.m_viewportInitialScale, "Unable to find android/webkit/WebViewCore.mViewportInitialScale"); gWebViewCoreFields.m_viewportMinimumScale = env->GetFieldID(widget, "mViewportMinimumScale", "I"); - ALOG_ASSERT(gWebViewCoreFields.m_viewportMinimumScale, + LOG_ASSERT(gWebViewCoreFields.m_viewportMinimumScale, "Unable to find android/webkit/WebViewCore.mViewportMinimumScale"); gWebViewCoreFields.m_viewportMaximumScale = env->GetFieldID(widget, "mViewportMaximumScale", "I"); - ALOG_ASSERT(gWebViewCoreFields.m_viewportMaximumScale, + LOG_ASSERT(gWebViewCoreFields.m_viewportMaximumScale, "Unable to find android/webkit/WebViewCore.mViewportMaximumScale"); gWebViewCoreFields.m_viewportUserScalable = env->GetFieldID(widget, "mViewportUserScalable", "Z"); - ALOG_ASSERT(gWebViewCoreFields.m_viewportUserScalable, + LOG_ASSERT(gWebViewCoreFields.m_viewportUserScalable, "Unable to find android/webkit/WebViewCore.mViewportUserScalable"); gWebViewCoreFields.m_viewportDensityDpi = env->GetFieldID(widget, "mViewportDensityDpi", "I"); - ALOG_ASSERT(gWebViewCoreFields.m_viewportDensityDpi, + LOG_ASSERT(gWebViewCoreFields.m_viewportDensityDpi, "Unable to find android/webkit/WebViewCore.mViewportDensityDpi"); + gWebViewCoreFields.m_webView = env->GetFieldID(widget, + "mWebView", "Landroid/webkit/WebView;"); + LOG_ASSERT(gWebViewCoreFields.m_webView, + "Unable to find android/webkit/WebViewCore.mWebView"); gWebViewCoreFields.m_drawIsPaused = env->GetFieldID(widget, "mDrawIsPaused", "Z"); - ALOG_ASSERT(gWebViewCoreFields.m_drawIsPaused, + LOG_ASSERT(gWebViewCoreFields.m_drawIsPaused, "Unable to find android/webkit/WebViewCore.mDrawIsPaused"); gWebViewCoreFields.m_lowMemoryUsageMb = env->GetFieldID(widget, "mLowMemoryUsageThresholdMb", "I"); gWebViewCoreFields.m_highMemoryUsageMb = env->GetFieldID(widget, "mHighMemoryUsageThresholdMb", "I"); diff --git a/Source/WebKit/android/jni/WebViewCore.h b/Source/WebKit/android/jni/WebViewCore.h index 5264f7f..a19a5b2 100644 --- a/Source/WebKit/android/jni/WebViewCore.h +++ b/Source/WebKit/android/jni/WebViewCore.h @@ -26,36 +26,29 @@ #ifndef WebViewCore_h #define WebViewCore_h +#include "CacheBuilder.h" +#include "CachedHistory.h" #include "DeviceMotionAndOrientationManager.h" #include "DOMSelection.h" #include "FileChooser.h" -#include "FocusDirection.h" -#include "HitTestResult.h" -#include "PicturePile.h" +#include "PictureSet.h" #include "PlatformGraphicsContext.h" -#include "Position.h" -#include "ScrollTypes.h" #include "SkColor.h" #include "SkTDArray.h" #include "SkRegion.h" -#include "Text.h" #include "Timer.h" #include "WebCoreRefObject.h" #include "WebCoreJni.h" #include "WebRequestContext.h" #include "android_npapi.h" -#include "VisiblePosition.h" -#include "SelectText.h" #include <jni.h> -#include <androidfw/KeycodeLabels.h> +#include <ui/KeycodeLabels.h> #include <ui/PixelFormat.h> #include <utils/threads.h> -#include <wtf/Threading.h> namespace WebCore { class Color; - class GraphicsOperationCollection; class FrameView; class HTMLAnchorElement; class HTMLElement; @@ -75,7 +68,6 @@ namespace WebCore { #if USE(ACCELERATED_COMPOSITING) namespace WebCore { class GraphicsLayerAndroid; - class LayerAndroid; } #endif @@ -104,8 +96,10 @@ namespace android { AXIS_DOCUMENT = 6 }; + class CachedFrame; + class CachedNode; + class CachedRoot; class ListBoxReply; - class AndroidHitTestResult; class WebCoreReply : public WebCoreRefObject { public: @@ -122,7 +116,7 @@ namespace android { }; // one instance of WebViewCore per page for calling into Java's WebViewCore - class WebViewCore : public WebCoreRefObject, public WebCore::PicturePainter { + class WebViewCore : public WebCoreRefObject { public: /** * Initialize the native WebViewCore with a JNI environment, a Java @@ -137,7 +131,13 @@ namespace android { // Followings are called from native WebCore to Java - void focusNodeChanged(WebCore::Node*); + /** + * Notification that a form was blurred. Pass a message to hide the + * keyboard if it was showing for that Node. + * @param Node The Node that blurred. + */ + void formDidBlur(const WebCore::Node*); + void focusNodeChanged(const WebCore::Node*); /** * Scroll to an absolute position. @@ -161,8 +161,13 @@ namespace android { */ void contentDraw(); + /** + * copy the layers to the UI side + */ + void layersDraw(); + #if USE(ACCELERATED_COMPOSITING) - WebCore::GraphicsLayerAndroid* graphicsRootLayer() const; + GraphicsLayerAndroid* graphicsRootLayer() const; #endif /** Invalidate the view/screen, NOT the content/DOM, but expressed in @@ -202,7 +207,7 @@ namespace android { * 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 WTF::String + * a password field, and ignore the String * @param text If changeToPassword is false, this is the new text that * should go into the textfield. */ @@ -217,12 +222,6 @@ namespace android { */ void updateTextSelection(); - /** - * Updates the java side with the node's content size and scroll - * position. - */ - void updateTextSizeAndScroll(WebCore::Node* node); - void clearTextEntry(); // JavaScript support void jsAlert(const WTF::String& url, const WTF::String& text); @@ -278,13 +277,12 @@ namespace android { jobject getDeviceMotionService(); jobject getDeviceOrientationService(); - void addMessageToConsole(const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceID, int msgLevel); + void addMessageToConsole(const String& message, unsigned int lineNumber, const String& sourceID, int msgLevel); /** * Tell the Java side of the scrollbar mode */ - void setScrollbarModes(WebCore::ScrollbarMode horizontalMode, - WebCore::ScrollbarMode verticalMode); + void setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode); // // Followings support calls from Java to native WebCore @@ -298,12 +296,16 @@ namespace android { // If the focus is a textfield (<input>), textarea, or contentEditable, // scroll the selection on screen (if necessary). void revealSelection(); + // Create a single picture to represent the drawn DOM (used by navcache) + void recordPicture(SkPicture* picture); - void moveMouse(int x, int y, WebCore::HitTestResult* hoveredNode = 0, - bool isClickCandidate = false); + void moveFocus(WebCore::Frame* frame, WebCore::Node* node); + void moveMouse(WebCore::Frame* frame, int x, int y); + void moveMouseIfLatest(int moveGeneration, + WebCore::Frame* frame, int x, int y); // set the scroll amount that webview.java is currently showing - void setScrollOffset(bool sendScrollEvent, int dx, int dy); + void setScrollOffset(int moveGeneration, bool sendScrollEvent, int dx, int dy); void setGlobalBounds(int x, int y, int h, int v); @@ -316,24 +318,31 @@ namespace android { * @return Whether keyCode was handled by this class. */ bool key(const WebCore::PlatformKeyboardEvent& event); - bool chromeCanTakeFocus(WebCore::FocusDirection direction); - void chromeTakeFocus(WebCore::FocusDirection direction); - void setInitialFocus(const WebCore::PlatformKeyboardEvent& event); + + /** + * Handle (trackball) click event / dpad center press from Java. + * Also used when typing into an unfocused textfield, in which case 'fake' + * will be true. + */ + void click(WebCore::Frame* frame, WebCore::Node* node, bool fake); /** * Handle touch event - * Returns an int with the following flags: - * bit 0: hit an event handler - * bit 1: preventDefault was called */ - int handleTouchEvent(int action, WTF::Vector<int>& ids, - WTF::Vector<WebCore::IntPoint>& points, - int actionIndex, int metaState); + bool handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState); /** - * Clicks the mouse at its current location + * Handle motionUp event from the UI thread (called touchUp in the + * WebCore thread). + * @param touchGeneration Generation number for touches so we can ignore + * touches when a newer one has been generated. + * @param frame Pointer to Frame containing the node that was touched. + * @param node Pointer to Node that was touched. + * @param x x-position of the touch. + * @param y y-position of the touch. */ - bool performMouseClick(); + void touchUp(int touchGeneration, WebCore::Frame* frame, + WebCore::Node* node, int x, int y); /** * Sets the index of the label from a popup @@ -363,11 +372,11 @@ namespace android { * direction - The direction in which to alter the selection. * granularity - The granularity of the selection modification. * - * returns - The selected HTML as a WTF::String. This is not a well formed + * returns - The selected HTML as a string. This is not a well formed * HTML, rather the selection annotated with the tags of all * intermediary elements it crosses. */ - WTF::String modifySelection(const int direction, const int granularity); + String modifySelection(const int direction, const int granularity); /** * Moves the selection to the given node in a given frame i.e. selects that node. @@ -377,11 +386,11 @@ namespace android { * frame - The frame in which to select is the node to be selected. * node - The node to be selected. * - * returns - The selected HTML as a WTF::String. This is not a well formed + * returns - The selected HTML as a string. This is not a well formed * HTML, rather the selection annotated with the tags of all * intermediary elements it crosses. */ - WTF::String moveSelection(WebCore::Frame* frame, WebCore::Node* node); + String moveSelection(WebCore::Frame* frame, WebCore::Node* node); /** * In the currently focused textfield, replace the characters from oldStart to oldEnd @@ -396,7 +405,7 @@ namespace android { /** * Scroll the focused textfield to (x, y) in document space */ - WebCore::IntRect scrollFocusedTextInput(float x, int y); + void scrollFocusedTextInput(float x, int y); /** * Set the FocusController's active and focused states, so that * the caret will draw (true) or not. @@ -416,9 +425,11 @@ namespace android { jobject getWebViewJavaObject(); void setBackgroundColor(SkColor c); - + void updateFrameCache(); + void updateCacheOnNodeChange(); void dumpDomTree(bool); void dumpRenderTree(bool); + void dumpNavTree(); /* We maintain a list of active plugins. The list is edited by the pluginview itself. The list is used to service invals to the plugin @@ -438,7 +449,7 @@ namespace android { void sendPluginSurfaceReady(); // send onLoad event to plugins who are descendents of the given frame - void notifyPluginsOnFrameLoad(const WebCore::Frame*); + void notifyPluginsOnFrameLoad(const Frame*); // gets a rect representing the current on-screen portion of the document void getVisibleScreen(ANPRectI&); @@ -449,6 +460,9 @@ namespace android { // lookup the plugin widget struct given an NPP PluginWidgetAndroid* getPluginWidget(NPP npp); + // return the cursorNode if it is a plugin + Node* cursorNodeIsPlugin(); + // Notify the Java side whether it needs to pass down the touch events void needTouchEvents(bool); @@ -483,6 +497,8 @@ namespace android { // Manages requests to keep the screen on while the WebView is visible void keepScreenOn(bool screenOn); + bool validNodeAndBounds(Frame* , Node* , const IntRect& ); + // Make the rect (left, top, width, height) visible. If it can be fully // fit, center it on the screen. Otherwise make sure the point specified // by (left + xPercentInDoc * width, top + yPercentInDoc * height) @@ -496,11 +512,7 @@ namespace android { void centerFitRect(int x, int y, int width, int height); // return a list of rects matching the touch point (x, y) with the slop - WTF::Vector<WebCore::IntRect> getTouchHighlightRects(int x, int y, int slop, - WebCore::Node** node, WebCore::HitTestResult* hitTestResult); - // This does a sloppy hit test - AndroidHitTestResult hitTestAtPoint(int x, int y, int slop, bool doMoveMouse = false); - static bool nodeIsClickableOrFocusable(WebCore::Node* node); + Vector<IntRect> getTouchHighlightRects(int x, int y, int slop); // Open a file chooser for selecting a file to upload void openFileChooser(PassRefPtr<WebCore::FileChooser> ); @@ -510,37 +522,31 @@ namespace android { bool focusBoundsChanged(); - // record content in a new BaseLayerAndroid, copying the layer tree as well - WebCore::BaseLayerAndroid* recordContent(SkIPoint* ); + // record the inval area, and the picture size + BaseLayerAndroid* recordContent(SkRegion* , SkIPoint* ); // This creates a new BaseLayerAndroid by copying the current m_content // and doing a copy of the layers. The layers' content may be updated // as we are calling layersSync(). - WebCore::BaseLayerAndroid* createBaseLayer(GraphicsLayerAndroid* root); - bool updateLayers(WebCore::LayerAndroid*); + BaseLayerAndroid* createBaseLayer(SkRegion*); + bool updateLayers(LayerAndroid*); void notifyAnimationStarted(); int textWrapWidth() const { return m_textWrapWidth; } float scale() const { return m_scale; } float textWrapScale() const { return m_screenWidth * m_scale / m_textWrapWidth; } WebCore::Frame* mainFrame() const { return m_mainFrame; } - WebCore::Frame* focusedFrame() const; - - void notifyWebAppCanBeInstalled(); + void updateCursorBounds(const CachedRoot* root, + const CachedFrame* cachedFrame, const CachedNode* cachedNode); + void updateFrameCacheIfLoading(); - void deleteText(int startX, int startY, int endX, int endY); - WTF::String getText(int startX, int startY, int endX, int endY); - void insertText(const WTF::String &text); + // utility to split slow parts of the picture set + void splitContent(PictureSet*); - // find on page - void resetFindOnPage(); - int findTextOnPage(const WTF::String &text); - int findNextOnPage(bool forward); - void updateMatchCount() const; + void notifyWebAppCanBeInstalled(); #if ENABLE(VIDEO) void enterFullscreenForVideoLayer(int layerId, const WTF::String& url); - void exitFullscreenVideo(); #endif void setWebTextViewAutoFillable(int queryId, const string16& previewSummary); @@ -550,15 +556,19 @@ namespace android { void listBoxRequest(WebCoreReply* reply, const uint16_t** labels, size_t count, const int enabled[], size_t enabledCount, bool multiple, const int selected[], size_t selectedCountOrSelection); + bool shouldPaintCaret() { return m_shouldPaintCaret; } + void setShouldPaintCaret(bool should) { m_shouldPaintCaret = should; } bool isPaused() const { return m_isPaused; } void setIsPaused(bool isPaused) { m_isPaused = isPaused; } bool drawIsPaused() const; // The actual content (without title bar) size in doc coordinate int screenWidth() const { return m_screenWidth; } int screenHeight() const { return m_screenHeight; } +#if USE(CHROME_NETWORK_STACK) void setWebRequestContextUserAgent(); void setWebRequestContextCacheMode(int mode); WebRequestContext* webRequestContext(); +#endif // Attempts to scroll the layer to the x,y coordinates of rect. The // layer is the id of the LayerAndroid. void scrollRenderLayer(int layer, const SkRect& rect); @@ -571,62 +581,35 @@ namespace android { // Check whether a media mimeType is supported in Android media framework. static bool isSupportedMediaMimeType(const WTF::String& mimeType); - /** - * Returns all text ranges consumed by the cursor points referred - * to by startX, startY, endX, and endY. The vector will be empty - * if no text is in the given area or if the positions are invalid. - */ - Vector<WebCore::VisibleSelection> getTextRanges( - int startX, int startY, int endX, int endY); - static int platformLayerIdFromNode(WebCore::Node* node, - WebCore::LayerAndroid** outLayer = 0); - void selectText(int startX, int startY, int endX, int endY); - bool selectWordAt(int x, int y); - - // Converts from the global content coordinates that WebView sends - // to frame-local content coordinates using the focused frame - WebCore::IntPoint convertGlobalContentToFrameContent(const WebCore::IntPoint& point, WebCore::Frame* frame = 0); - static void layerToAbsoluteOffset(const WebCore::LayerAndroid* layer, - WebCore::IntPoint& offset); - - // Retrieves the current locale from system properties - void getLocale(String& language, WTF::String& region); - - // Handles changes in system locale - void updateLocale(); - // these members are shared with webview.cpp + static Mutex gFrameCacheMutex; + CachedRoot* m_frameCacheKit; // nav data being built by webcore + SkPicture* m_navPictureKit; + int m_moveGeneration; // copy of state in WebViewNative triggered by move int m_touchGeneration; // copy of state in WebViewNative triggered by touch int m_lastGeneration; // last action using up to date cache + bool m_updatedFrameCache; + bool m_findIsUp; + bool m_hasCursorBounds; + WebCore::IntRect m_cursorBounds; + WebCore::IntRect m_cursorHitBounds; + void* m_cursorFrame; + IntPoint m_cursorLocation; + void* m_cursorNode; + static Mutex gCursorBoundsMutex; // end of shared members - void setPrerenderingEnabled(bool enable); - // internal functions private: - enum InputType { - NONE = -1, - NORMAL_TEXT_FIELD = 0, - TEXT_AREA = 1, - PASSWORD = 2, - SEARCH = 3, - EMAIL = 4, - NUMBER = 5, - TELEPHONE = 6, - URL = 7, - }; - + CacheBuilder& cacheBuilder(); WebCore::Node* currentFocus(); - void layout(); // 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 recordPicturePile(); + void recordPictureSet(PictureSet* master); - virtual void paintContents(WebCore::GraphicsContext* gc, WebCore::IntRect& dirty); - virtual SkCanvas* createPrerenderCanvas(WebCore::PrerenderedInval* prerendered); -#ifdef CONTEXT_RECORDING - WebCore::GraphicsOperationCollection* rebuildGraphicsOperationCollection(const SkIRect& inval); -#endif + void doMaxScroll(CacheBuilder::Direction dir); + SkPicture* rebuildPicture(const SkIRect& inval); + void rebuildPictureSet(PictureSet* ); void sendNotifyProgressFinished(); /* * Handle a mouse click, either from a touch or trackball press. @@ -635,127 +618,47 @@ namespace android { * @param fake This is a fake mouse click, used to put a textfield into focus. Do not * open the IME. */ + bool handleMouseClick(WebCore::Frame*, WebCore::Node*, bool fake); WebCore::HTMLAnchorElement* retrieveAnchorElement(int x, int y); WebCore::HTMLElement* retrieveElement(int x, int y, const WebCore::QualifiedName& ); WebCore::HTMLImageElement* retrieveImageElement(int x, int y); // below are members responsible for accessibility support - WTF::String modifySelectionTextNavigationAxis(WebCore::DOMSelection* selection, - int direction, int granularity); - WTF::String modifySelectionDomNavigationAxis(WebCore::DOMSelection* selection, - int direction, int granularity); - WebCore::Text* traverseNextContentTextNode(WebCore::Node* fromNode, - WebCore::Node* toNode, - int direction); - bool isVisible(WebCore::Node* node); - bool isHeading(WebCore::Node* node); - WTF::String formatMarkup(WebCore::DOMSelection* selection); + String modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int granularity); + String modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int granularity); + Text* traverseNextContentTextNode(Node* fromNode, Node* toNode ,int direction); + bool isVisible(Node* node); + bool isHeading(Node* node); + String formatMarkup(DOMSelection* selection); void selectAt(int x, int y); - void scrollNodeIntoView(WebCore::Frame* frame, WebCore::Node* node); - bool isContentTextNode(WebCore::Node* node); - WebCore::Node* getIntermediaryInputElement(WebCore::Node* fromNode, - WebCore::Node* toNode, - int direction); - bool isContentInputElement(WebCore::Node* node); - bool isDescendantOf(WebCore::Node* parent, WebCore::Node* node); - void advanceAnchorNode(WebCore::DOMSelection* selection, int direction, - WTF::String& markup, bool ignoreFirstNode, - WebCore::ExceptionCode& ec); - WebCore::Node* getNextAnchorNode(WebCore::Node* anchorNode, - bool skipFirstHack, int direction); - WebCore::Node* getImplicitBoundaryNode(WebCore::Node* node, - unsigned offset, int direction); - jobject createTextFieldInitData(WebCore::Node* node); - /** - * Calls into java to reset the text edit field with the - * current contents and selection. - */ - void initEditField(WebCore::Node* node); - - /** - * If node is not a text input field or if it explicitly requests - * not to have keyboard input, then the soft keyboard is closed. If - * it is a text input field then initEditField is called and - * auto-fill information is requested for HTML form input fields. - */ - void initializeTextInput(WebCore::Node* node, bool fake); - - /** - * Gets the input type a Node. NONE is returned if it isn't an - * input field. - */ - InputType getInputType(WebCore::Node* node); - - /** - * If node is an input field, the spellcheck value for the - * field is returned. Otherwise true is returned. - */ - static bool isSpellCheckEnabled(WebCore::Node* node); - - /** - * Returns the offsets of the selection area for both normal text - * fields and content editable fields. start and end are modified - * by this method. - */ - static void getSelectionOffsets(WebCore::Node* node, int& start, int& end); - /** - * Gets the plain text of the specified editable text field. node - * may be content-editable or a plain text fields. - */ - static WTF::String getInputText(WebCore::Node* node); - /** - * Gets the RenderTextControl for the given node if it has one. - * If its renderer isn't a RenderTextControl, then NULL is returned. - */ - static WebCore::RenderTextControl* toRenderTextControl(WebCore::Node *node); - /** - * Sets the selection for node's editable field to the offsets - * between start (inclusive) and end (exclusive). - */ - static void setSelection(WebCore::Node* node, int start, int end); - /** - * Returns the Position for the given offset for an editable - * field. The offset is relative to the node start. - */ - static WebCore::Position getPositionForOffset(WebCore::Node* node, int offset); - - WebCore::VisiblePosition visiblePositionForContentPoint(int x, int y); - WebCore::VisiblePosition visiblePositionForContentPoint(const WebCore::IntPoint& point); - bool selectWordAroundPosition(WebCore::Frame* frame, - WebCore::VisiblePosition pos); - SelectText* createSelectText(const WebCore::VisibleSelection&); - void setSelectionCaretInfo(SelectText* selectTextContainer, - const WebCore::Position& position, - const WebCore::IntPoint& frameOffset, - SelectText::HandleId handleId, int offset, - EAffinity affinity); - static int getMaxLength(WebCore::Node* node); - static WTF::String getFieldName(WebCore::Node* node); - static bool isAutoCompleteEnabled(WebCore::Node* node); - WebCore::IntRect absoluteContentRect(WebCore::Node* node, - WebCore::LayerAndroid* layer); - static WebCore::IntRect positionToTextRect(const WebCore::Position& position, - WebCore::EAffinity affinity, const WebCore::IntPoint& offset); - static bool isLtr(const WebCore::Position& position); - static WebCore::Position trimSelectionPosition( - const WebCore::Position& start, const WebCore::Position& stop); + void scrollNodeIntoView(Frame* frame, Node* node); + bool isContentTextNode(Node* node); + Node* getIntermediaryInputElement(Node* fromNode, Node* toNode, int direction); + bool isContentInputElement(Node* node); + bool isDescendantOf(Node* parent, Node* node); + void advanceAnchorNode(DOMSelection* selection, int direction, String& markup, bool ignoreFirstNode, ExceptionCode& ec); + Node* getNextAnchorNode(Node* anchorNode, bool skipFirstHack, int direction); + Node* getImplicitBoundaryNode(Node* node, unsigned offset, int direction); // called from constructor, to add this to a global list static void addInstance(WebViewCore*); // called from destructor, to remove this from a global list static void removeInstance(WebViewCore*); - bool prerenderingEnabled(); - friend class ListBoxReply; struct JavaGlue; struct JavaGlue* m_javaGlue; - struct TextFieldInitDataGlue; - struct TextFieldInitDataGlue* m_textFieldInitDataGlue; WebCore::Frame* m_mainFrame; WebCoreReply* m_popupReply; - WebCore::PicturePile m_content; // the set of pictures to draw + WebCore::Node* m_lastFocused; + WebCore::IntRect m_lastFocusedBounds; + int m_blurringNodePointer; + int m_lastFocusedSelStart; + int m_lastFocusedSelEnd; + PictureSet m_content; // the set of pictures to draw + SkRegion m_addInval; // the accumulated inval region (not yet drawn) + SkRegion m_rebuildInval; // the accumulated region for rebuilt pictures // Used in passToJS to avoid updating the UI text field until after the // key event has been processed. bool m_blockTextfieldUpdates; @@ -764,33 +667,26 @@ namespace android { // Passed in with key events to know when they were generated. Store it // with the cache so that we can ignore stale text changes. int m_textGeneration; + CachedRoot* m_temp; + SkPicture* m_tempPict; int m_maxXScroll; int m_maxYScroll; int m_scrollOffsetX; // webview.java's current scroll in X int m_scrollOffsetY; // webview.java's current scroll in Y - double m_scrollSetTime; // when the scroll was last set WebCore::IntPoint m_mousePos; - // This is the location at which we will click. This is tracked - // separately from m_mousePos, because m_mousePos may be updated - // in the interval between ACTION_UP and when the click fires since - // that occurs after a delay. This also works around potential hardware - // issues if we get onHoverEvents when using the touch screen, as that - // will nullify the slop checking we do in hitTest (aka, ACTION_DOWN) - WebCore::IntPoint m_mouseClickPos; + bool m_frameCacheOutOfDate; + bool m_progressDone; + CachedHistory m_history; int m_screenWidth; // width of the visible rect in document coordinates int m_screenHeight;// height of the visible rect in document coordinates int m_textWrapWidth; float m_scale; - WebCore::PageGroup* m_groupForVisitedLinks; + unsigned m_domtree_version; + bool m_check_domtree_version; + PageGroup* m_groupForVisitedLinks; bool m_isPaused; int m_cacheMode; - bool m_fullscreenVideoMode; - - // find on page data - WTF::String m_searchText; - int m_matchCount; - int m_activeMatchIndex; - RefPtr<WebCore::Range> m_activeMatch; + bool m_shouldPaintCaret; SkTDArray<PluginWidgetAndroid*> m_plugins; WebCore::Timer<WebViewCore> m_pluginInvalTimer; @@ -799,17 +695,17 @@ namespace android { } int m_screenOnCounter; - WebCore::Node* m_currentNodeDomNavigationAxis; + Node* m_currentNodeDomNavigationAxis; DeviceMotionAndOrientationManager m_deviceMotionAndOrientationManager; #if ENABLE(TOUCH_EVENTS) bool m_forwardingTouchEvents; #endif +#if USE(CHROME_NETWORK_STACK) scoped_refptr<WebRequestContext> m_webRequestContext; +#endif - WTF::Mutex m_prerenderLock; - bool m_prerenderEnabled; }; } // namespace android diff --git a/Source/WebKit/android/nav/CacheBuilder.cpp b/Source/WebKit/android/nav/CacheBuilder.cpp new file mode 100644 index 0000000..940991f --- /dev/null +++ b/Source/WebKit/android/nav/CacheBuilder.cpp @@ -0,0 +1,3194 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "ColumnInfo.h" +#include "Document.h" +#include "EventListener.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +//#include "GraphicsContext.h" +#include "HTMLAreaElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMapElement.h" +#include "HTMLNames.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "HTMLTextAreaElement.h" +#include "InlineTextBox.h" +#include "KURL.h" +#include "LayerAndroid.h" +#include "PluginView.h" +#include "RegisteredEventListener.h" +#include "RenderImage.h" +#include "RenderInline.h" +#include "RenderLayerBacking.h" +#include "RenderListBox.h" +#include "RenderSkinCombo.h" +#include "RenderTextControl.h" +#include "RenderView.h" +#include "RenderWidget.h" +#include "SkCanvas.h" +#include "SkPoint.h" +#include "Text.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreViewBridge.h" +#include "Widget.h" +#include <wtf/unicode/Unicode.h> + +#ifdef DUMP_NAV_CACHE_USING_PRINTF + FILE* gNavCacheLogFile = NULL; + android::Mutex gWriteLogMutex; +#endif + +#include "CacheBuilder.h" + +#define MINIMUM_FOCUSABLE_WIDTH 3 +#define MINIMUM_FOCUSABLE_HEIGHT 3 +#define MAXIMUM_FOCUS_RING_COUNT 32 + +namespace android { + +CacheBuilder* CacheBuilder::Builder(Frame* frame) { + return &((FrameLoaderClientAndroid*) frame->loader()->client())->getCacheBuilder(); +} + +Frame* CacheBuilder::FrameAnd(CacheBuilder* cacheBuilder) { + FrameLoaderClientAndroid* loader = (FrameLoaderClientAndroid*) + ((char*) cacheBuilder - OFFSETOF(FrameLoaderClientAndroid, m_cacheBuilder)); + return loader->getFrame(); +} + +Frame* CacheBuilder::FrameAnd(const CacheBuilder* cacheBuilder) { + FrameLoaderClientAndroid* loader = (FrameLoaderClientAndroid*) + ((char*) cacheBuilder - OFFSETOF(FrameLoaderClientAndroid, m_cacheBuilder)); + return loader->getFrame(); +} + +CacheBuilder::LayerTracker::~LayerTracker() { + // Check for a stacking context to prevent a crash in layers without a + // parent. + if (mRenderLayer && mRenderLayer->stackingContext()) + // Restore the scroll position of the layer. Does not affect layers + // without overflow scroll as the layer will not be scrolled. + mRenderLayer->scrollToOffset(mScroll.x(), mScroll.y()); +} + +#if DUMP_NAV_CACHE + +static bool hasEventListener(Node* node, const AtomicString& eventType) { + if (!node->isElementNode()) + return false; + Element* element = static_cast<Element*>(node); + EventListener* listener = element->getAttributeEventListener(eventType); + return 0 != listener; +} + +#define DEBUG_BUFFER_SIZE 256 +#define DEBUG_WRAP_SIZE 150 +#define DEBUG_WRAP_MAX 170 + +Frame* CacheBuilder::Debug::frameAnd() const { + CacheBuilder* nav = (CacheBuilder*) ((char*) this - OFFSETOF(CacheBuilder, mDebug)); + return CacheBuilder::FrameAnd(nav); +} + +void CacheBuilder::Debug::attr(const AtomicString& name, const AtomicString& value) { + if (name.isNull() || name.isEmpty() || value.isNull() || value.isEmpty()) + return; + uChar(name.characters(), name.length(), false); + print("="); + wideString(value.characters(), value.length(), false); + print(" "); +} + +void CacheBuilder::Debug::comma(const char* str) { + print(str); + print(", "); +} + +void CacheBuilder::Debug::flush() { + int len; + do { + int limit = mIndex; + if (limit < DEBUG_WRAP_SIZE) + return; + if (limit < DEBUG_WRAP_MAX) + len = limit; + else { + limit = DEBUG_WRAP_MAX; + len = DEBUG_WRAP_SIZE; + while (len < limit) { + char test = mBuffer[len]; + if (test < '/' || (test > '9' && test < 'A') || (test > 'Z' && test < 'a') || test > 'z') + break; + len++; + } + while (len > 0 && mBuffer[len - 1] == '\\') + len--; + while (mBuffer[len] == '"') + len++; + } + const char* prefix = mPrefix; + if (prefix[0] == '\"') { + // see if we're inside a quote + int quoteCount = 0; + for (int index = 0; index < len; index++) { + if (mBuffer[index] == '\\') { + index++; + continue; + } + if (mBuffer[index] == '\n') { + quoteCount = 0; + continue; + } + if (mBuffer[index] == '"') + quoteCount++; + } + if ((quoteCount & 1) == 0) + prefix = "\n"; + } + DUMP_NAV_LOGD("%.*s", len, mBuffer); + int copy = mIndex - len; + strcpy(mBuffer, prefix); + memcpy(&mBuffer[strlen(prefix)], &mBuffer[len], copy); + mIndex = strlen(prefix) + copy; + } while (true); +} + +void CacheBuilder::Debug::frameName(char*& namePtr, const char* max) const { + if (namePtr >= max) + return; + Frame* frame = frameAnd(); + Frame* parent = frame->tree()->parent(); + if (parent) + Builder(parent)->mDebug.frameName(namePtr, max); + const AtomicString& name = frame->tree()->name(); + if (name.length() == 0) + return; + unsigned index = 0; + if (name.startsWith(AtomicString("opener"))) + index = 6; + for (; index < name.length(); index++) { + char ch = name[index]; + if (ch <= ' ') + ch = '_'; + if (WTF::isASCIIAlphanumeric(ch) || ch == '_') + *namePtr++ = ch; + } +} + +void CacheBuilder::Debug::frames() { + Frame* frame = frameAnd(); + Document* doc = frame->document(); + if (doc == NULL) + return; + bool top = frame->tree()->parent() == NULL; + if (top) { +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gWriteLogMutex.lock(); + ASSERT(gNavCacheLogFile == NULL); + gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); +#endif + groups(); + } + Frame* child = frame->tree()->firstChild(); + bool hasChild = child != NULL; + if (top && hasChild) + DUMP_NAV_LOGD("\nnamespace TEST_SPACE {\n\n"); + while (child) { + Builder(child)->mDebug.frames(); + child = child->tree()->nextSibling(); + } + if (hasChild) { + child = frame->tree()->firstChild(); + while (child) { + char childName[256]; + char* childNamePtr = childName; + Builder(child)->mDebug.frameName(childNamePtr, childNamePtr + sizeof(childName) - 1); + *childNamePtr = '\0'; + if (child == frame->tree()->firstChild()) + DUMP_NAV_LOGD("DebugTestFrameGroup TEST%s_GROUP[] = {\n", childName); + Frame* next = child->tree()->nextSibling(); + Document* doc = child->document(); + if (doc != NULL) { + RenderObject* renderer = doc->renderer(); + if (renderer != NULL) { + RenderLayer* layer = renderer->enclosingLayer(); + if (layer != NULL) { + DUMP_NAV_LOGD("{ "); + DUMP_NAV_LOGD("TEST%s_RECTS, ", childName); + DUMP_NAV_LOGD("TEST%s_RECT_COUNT, ", childName); + DUMP_NAV_LOGD("TEST%s_RECTPARTS, ", childName); + DUMP_NAV_LOGD("TEST%s_BOUNDS,\n", childName); + DUMP_NAV_LOGD("TEST%s_WIDTH, ", childName); + DUMP_NAV_LOGD("TEST%s_HEIGHT,\n", childName); + DUMP_NAV_LOGD("0, 0, 0, 0,\n"); + DUMP_NAV_LOGD("TEST%s_FOCUS, ", childName); + Frame* grandChild = child->tree()->firstChild(); + if (grandChild) { + char grandChildName[256]; + char* grandChildNamePtr = grandChildName; + Builder(grandChild)->mDebug.frameName(grandChildNamePtr, + grandChildNamePtr + sizeof(grandChildName) - 1); + *grandChildNamePtr = '\0'; + DUMP_NAV_LOGD("TEST%s_GROUP, ", grandChildName); + DUMP_NAV_LOGD("sizeof(TEST%s_GROUP) / sizeof(DebugTestFrameGroup), ", grandChildName); + } else + DUMP_NAV_LOGD("NULL, 0, "); + DUMP_NAV_LOGD("\"%s\"\n", childName); + DUMP_NAV_LOGD("}%c\n", next ? ',' : ' '); + } + } + } + child = next; + } + DUMP_NAV_LOGD("};\n"); + } + if (top) { + if (hasChild) + DUMP_NAV_LOGD("\n} // end of namespace\n\n"); + char name[256]; + char* frameNamePtr = name; + frameName(frameNamePtr, frameNamePtr + sizeof(name) - 1); + *frameNamePtr = '\0'; + DUMP_NAV_LOGD("DebugTestFrameGroup TEST%s_GROUP = {\n", name); + DUMP_NAV_LOGD("TEST%s_RECTS, ", name); + DUMP_NAV_LOGD("TEST%s_RECT_COUNT, ", name); + DUMP_NAV_LOGD("TEST%s_RECTPARTS, ", name); + DUMP_NAV_LOGD("TEST%s_BOUNDS,\n", name); + DUMP_NAV_LOGD("TEST%s_WIDTH, ", name); + DUMP_NAV_LOGD("TEST%s_HEIGHT,\n", name); + DUMP_NAV_LOGD("TEST%s_MAX_H, ", name); + DUMP_NAV_LOGD("TEST%s_MIN_H, ", name); + DUMP_NAV_LOGD("TEST%s_MAX_V, ", name); + DUMP_NAV_LOGD("TEST%s_MIN_V,\n", name); + DUMP_NAV_LOGD("TEST%s_FOCUS, ", name); + if (hasChild) { + child = frame->tree()->firstChild(); + char childName[256]; + char* childNamePtr = childName; + Builder(child)->mDebug.frameName(childNamePtr, childNamePtr + sizeof(childName) - 1); + *childNamePtr = '\0'; + DUMP_NAV_LOGD("TEST_SPACE::TEST%s_GROUP, ", childName); + DUMP_NAV_LOGD("sizeof(TEST_SPACE::TEST%s_GROUP) / sizeof(DebugTestFrameGroup), \n" ,childName); + } else + DUMP_NAV_LOGD("NULL, 0, "); + DUMP_NAV_LOGD("\"%s\"\n", name); + DUMP_NAV_LOGD("};\n"); +#ifdef DUMP_NAV_CACHE_USING_PRINTF + if (gNavCacheLogFile) + fclose(gNavCacheLogFile); + gNavCacheLogFile = NULL; + gWriteLogMutex.unlock(); +#endif + } +} + +void CacheBuilder::Debug::init(char* buffer, size_t size) { + mBuffer = buffer; + mBufferSize = size; + mIndex = 0; + mPrefix = ""; +} + +void CacheBuilder::Debug::groups() { + Frame* frame = frameAnd(); + Frame* child = frame->tree()->firstChild(); + bool hasChild = child != NULL; + if (frame->tree()->parent() == NULL && hasChild) + DUMP_NAV_LOGD("namespace TEST_SPACE {\n\n"); + while (child) { + Builder(child)->mDebug.groups(); + child = child->tree()->nextSibling(); + } + if (frame->tree()->parent() == NULL && hasChild) + DUMP_NAV_LOGD("\n} // end of namespace\n\n"); + Document* doc = frame->document(); + char name[256]; + char* frameNamePtr = name; + frameName(frameNamePtr, frameNamePtr + sizeof(name) - 1); + *frameNamePtr = '\0'; + if (doc == NULL) { + DUMP_NAV_LOGD("// %s has no document\n", name); + return; + } + RenderObject* renderer = doc->renderer(); + if (renderer == NULL) { + DUMP_NAV_LOGD("// %s has no renderer\n", name); + return; + } + RenderLayer* layer = renderer->enclosingLayer(); + if (layer == NULL) { + DUMP_NAV_LOGD("// %s has no enclosingLayer\n", name); + return; + } + Node* node = doc; + Node* focus = doc->focusedNode(); + bool atLeastOne = false; + do { + if ((atLeastOne |= isFocusable(node)) != false) + break; + } while ((node = node->traverseNextNode()) != NULL); + int focusIndex = -1; + if (atLeastOne == false) { + DUMP_NAV_LOGD("static DebugTestNode TEST%s_RECTS[] = {\n" + "{{0, 0, 0, 0}, \"\", 0, -1, \"\", {0, 0, 0, 0}, false, 0}\n" + "};\n\n", name); + DUMP_NAV_LOGD("static int TEST%s_RECT_COUNT = 1;" + " // no focusable nodes\n", name); + DUMP_NAV_LOGD("#define TEST%s_RECTPARTS NULL\n", name); + } else { + node = doc; + int count = 1; + DUMP_NAV_LOGD("static DebugTestNode TEST%s_RECTS[] = {\n", name); + do { + String properties; + if (hasEventListener(node, eventNames().clickEvent)) + properties.append("ONCLICK | "); + if (hasEventListener(node, eventNames().mousedownEvent)) + properties.append("MOUSEDOWN | "); + if (hasEventListener(node, eventNames().mouseupEvent)) + properties.append("MOUSEUP | "); + if (hasEventListener(node, eventNames().mouseoverEvent)) + properties.append("MOUSEOVER | "); + if (hasEventListener(node, eventNames().mouseoutEvent)) + properties.append("MOUSEOUT | "); + if (hasEventListener(node, eventNames().keydownEvent)) + properties.append("KEYDOWN | "); + if (hasEventListener(node, eventNames().keyupEvent)) + properties.append("KEYUP | "); + if (CacheBuilder::HasFrame(node)) + properties.append("FRAME | "); + if (focus == node) { + properties.append("FOCUS | "); + focusIndex = count; + } + if (node->isKeyboardFocusable(NULL)) + properties.append("KEYBOARD_FOCUSABLE | "); + if (node->isMouseFocusable()) + properties.append("MOUSE_FOCUSABLE | "); + if (node->isFocusable()) + properties.append("SIMPLE_FOCUSABLE | "); + if (properties.isEmpty()) + properties.append("0"); + else + properties.truncate(properties.length() - 3); + IntRect rect = node->getRect(); + if (node->hasTagName(HTMLNames::areaTag)) + rect = getAreaRect(static_cast<HTMLAreaElement*>(node)); + char buffer[DEBUG_BUFFER_SIZE]; + memset(buffer, 0, sizeof(buffer)); + mBuffer = buffer; + mBufferSize = sizeof(buffer); + mPrefix = "\"\n\""; + mIndex = snprintf(buffer, sizeof(buffer), "{{%d, %d, %d, %d}, ", rect.x(), rect.y(), + rect.width(), rect.height()); + localName(node); + uChar(properties.characters(), properties.length(), false); + print(", "); + int parentIndex = ParentIndex(node, count, node->parentNode()); + char scratch[256]; + snprintf(scratch, sizeof(scratch), "%d", parentIndex); + comma(scratch); + Element* element = static_cast<Element*>(node); + if (node->isElementNode() && element->hasID()) + wideString(element->getIdAttribute()); + else if (node->isTextNode()) { + #if 01 // set to one to abbreviate text that can be omitted from the address detection code + if (rect.isEmpty() && node->textContent().length() > 100) { + wideString(node->textContent().characters(), 100, false); + snprintf(scratch, sizeof(scratch), "/* + %d bytes */", + node->textContent().length() - 100); + print(scratch); + } else + #endif + wideString(node->textContent().characters(), node->textContent().length(), true); + } else if (node->hasTagName(HTMLNames::aTag) || + node->hasTagName(HTMLNames::areaTag)) + { + HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(node); + wideString(anchor->href()); + } else if (node->hasTagName(HTMLNames::imgTag)) { + HTMLImageElement* image = static_cast<HTMLImageElement*>(node); + wideString(image->src()); + } else + print("\"\""); + RenderObject* renderer = node->renderer(); + int tabindex = node->isElementNode() ? node->tabIndex() : 0; + RenderLayer* layer = 0; + if (renderer) { + const IntRect& absB = renderer->absoluteBoundingBoxRect(); + bool hasLayer = renderer->hasLayer(); + layer = hasLayer ? toRenderBoxModelObject(renderer)->layer() : 0; + snprintf(scratch, sizeof(scratch), ", {%d, %d, %d, %d}, %s" + ", %d, %s, %s},", + absB.x(), absB.y(), absB.width(), absB.height(), + renderer->hasOverflowClip() ? "true" : "false", tabindex, + hasLayer ? "true" : "false", + hasLayer && layer->isComposited() ? "true" : "false"); + // TODO: add renderer->style()->visibility() + print(scratch); + } else + print(", {0, 0, 0, 0}, false, 0},"); + + flush(); + snprintf(scratch, sizeof(scratch), "// %d: ", count); + mPrefix = "\n// "; + print(scratch); + //print(renderer ? renderer->information().ascii() : "NO_RENDER_INFO"); + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + NamedNodeMap* attrs = element->attributes(); + unsigned length = attrs->length(); + if (length > 0) { + newLine(); + print("// attr: "); + for (unsigned i = 0; i < length; i++) { + Attribute* a = attrs->attributeItem(i); + attr(a->localName(), a->value()); + } + } + } + if (renderer) { + RenderStyle* style = renderer->style(); + snprintf(scratch, sizeof(scratch), "// renderStyle:" + " visibility=%s hasBackGround=%d" + " tapHighlightColor().alpha()=0x%02x" + " isTransparent()=%s", + style->visibility() == HIDDEN ? "HIDDEN" : "VISIBLE", + renderer->hasBackground(), style->tapHighlightColor().alpha(), + renderer->isTransparent() ? "true" : "false"); + newLine(); + print(scratch); + RenderBlock* renderBlock = static_cast<RenderBlock*>(renderer); + if (renderer->isRenderBlock() && renderBlock->hasColumns()) { + const RenderBox* box = static_cast<RenderBox*>(renderer); + const IntRect& oRect = box->visibleOverflowRect(); + snprintf(scratch, sizeof(scratch), "// renderBlock:" + " columnCount=%d columnGap=%d direction=%d" + " hasOverflowClip=%d overflow=(%d,%d,w=%d,h=%d)", + renderBlock->columnInfo()->columnCount(), renderBlock->columnGap(), + renderBlock->style()->direction(), renderer->hasOverflowClip(), + oRect.x(), oRect.y(), oRect.width(), oRect.height()); + newLine(); + print(scratch); + } + } + #if USE(ACCELERATED_COMPOSITING) + if (renderer && renderer->hasLayer()) { + RenderLayer* layer = toRenderBoxModelObject(renderer)->layer(); + RenderLayerBacking* back = layer->backing(); + GraphicsLayer* grLayer = back ? back->graphicsLayer() : 0; + LayerAndroid* aLayer = grLayer ? grLayer->platformLayer() : 0; + const SkPicture* pict = aLayer ? aLayer->picture() : 0; + const IntRect& r = renderer->absoluteBoundingBoxRect(); + snprintf(scratch, sizeof(scratch), "// layer:%p back:%p" + " gLayer:%p aLayer:%p pict:%p r:(%d,%d,w=%d,h=%d)", + layer, back, grLayer, aLayer, pict, r.x(), r.y(), + r.width(), r.height()); + newLine(); + print(scratch); + } + #endif + count++; + newLine(); + } while ((node = node->traverseNextNode()) != NULL); + DUMP_NAV_LOGD("}; // focusables = %d\n", count - 1); + DUMP_NAV_LOGD("\n"); + DUMP_NAV_LOGD("static int TEST%s_RECT_COUNT = %d;\n\n", name, count - 1); + // look for rects with multiple parts + node = doc; + count = 1; + bool hasRectParts = false; + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); + do { + IntRect rect; + bool _isFocusable = isFocusable(node) || (node->isTextNode() + && node->getRect().isEmpty() == false + ); + int nodeIndex = count++; + if (_isFocusable == false) + continue; + RenderObject* renderer = node->renderer(); + if (renderer == NULL) + continue; + WTF::Vector<IntRect> rects; + IntRect clipBounds = IntRect(0, 0, INT_MAX, INT_MAX); + IntRect focusBounds = IntRect(0, 0, INT_MAX, INT_MAX); + IntRect* rectPtr = &focusBounds; + int imageCount = 0; + if (node->isTextNode()) { + Text* textNode = (Text*) node; + if (CacheBuilder::ConstructTextRects(textNode, 0, textNode, + INT_MAX, globalOffsetX, globalOffsetY, rectPtr, + clipBounds, &rects) == false) + continue; + } else { + IntRect nodeBounds = node->getRect(); + if (CacheBuilder::ConstructPartRects(node, nodeBounds, rectPtr, + globalOffsetX, globalOffsetY, &rects, &imageCount) == false) + continue; + } + unsigned arraySize = rects.size(); + if (arraySize > 1 || (arraySize == 1 && (rectPtr->width() != rect.width())) || + rectPtr->height() != rect.height()) { + if (hasRectParts == false) { + DUMP_NAV_LOGD("static DebugTestRectPart TEST%s_RECTPARTS[] = {\n", name); + hasRectParts = true; + } + if (node->isTextNode() == false) { + unsigned rectIndex = 0; + for (; rectIndex < arraySize; rectIndex++) { + rectPtr = &rects.at(rectIndex); + DUMP_NAV_LOGD("{ %d, %d, %d, %d, %d }, // %d\n", nodeIndex, + rectPtr->x(), rectPtr->y(), rectPtr->width(), + rectPtr->height(), rectIndex + 1); + } + } else { + RenderText* renderText = (RenderText*) node->renderer(); + InlineTextBox* textBox = renderText->firstTextBox(); + unsigned rectIndex = 0; + while (textBox) { + FloatPoint pt = renderText->localToAbsolute(); + IntRect rect = textBox->selectionRect((int) pt.x(), (int) pt.y(), 0, INT_MAX); + mIndex = 0; + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, "{ %d, %d, %d, %d, %d", + nodeIndex, rect.x(), rect.y(), rect.width(), rect.height()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d", + textBox->len(), 0 /*textBox->selectionHeight()*/, + 0 /*textBox->selectionTop()*/); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d", + 0 /*textBox->spaceAdd()*/, textBox->start(), 0 /*textBox->textPos()*/); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d, %d", + textBox->x(), textBox->y(), textBox->logicalWidth(), textBox->logicalHeight()); + int baseline = textBox->renderer()->style(textBox->isFirstLineStyle())->font().ascent(); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d }, // %d ", + baseline, imageCount, ++rectIndex); + wideString(node->textContent().characters() + textBox->start(), textBox->len(), true); + DUMP_NAV_LOGD("%.*s\n", mIndex, mBuffer); + textBox = textBox->nextTextBox(); + } + } + } + } while ((node = node->traverseNextNode()) != NULL); + if (hasRectParts) + DUMP_NAV_LOGD("{0}\n};\n\n"); + else + DUMP_NAV_LOGD("static DebugTestRectPart* TEST%s_RECTPARTS = NULL;\n", name); + } + int contentsWidth = layer->width(); + int contentsHeight = layer->height(); + DUMP_NAV_LOGD("static int TEST%s_FOCUS = %d;\n", name, focusIndex); + DUMP_NAV_LOGD("static int TEST%s_WIDTH = %d;\n", name, contentsWidth); + DUMP_NAV_LOGD("static int TEST%s_HEIGHT = %d;\n\n", name, contentsHeight); +} + +bool CacheBuilder::Debug::isFocusable(Node* node) { + if (node->hasTagName(HTMLNames::areaTag)) + return true; + if (node->renderer() == false) + return false; + if (node->isKeyboardFocusable(NULL)) + return true; + if (node->isMouseFocusable()) + return true; + if (node->isFocusable()) + return true; + if (CacheBuilder::AnyIsClick(node)) + return false; + if (CacheBuilder::HasTriggerEvent(node)) + return true; + return false; +} + +void CacheBuilder::Debug::localName(Node* node) { + const AtomicString& local = node->localName(); + if (node->isTextNode()) + print("\"#text\""); + else + wideString(local.characters(), local.length(), false); + print(", "); +} + +void CacheBuilder::Debug::newLine(int indent) { + if (mPrefix[0] != '\n') + print(&mPrefix[0], 1); + flush(); + int lastnewline = mIndex - 1; + while (lastnewline >= 0 && mBuffer[lastnewline] != '\n') + lastnewline--; + lastnewline++; + char* buffer = mBuffer; + if (lastnewline > 0) { + DUMP_NAV_LOGD("%.*s", lastnewline, buffer); + mIndex -= lastnewline; + buffer += lastnewline; + } + size_t prefixLen = strlen(mPrefix); + int minPrefix = prefixLen - 1; + while (minPrefix >= 0 && mPrefix[minPrefix] != '\n') + minPrefix--; + minPrefix = prefixLen - minPrefix - 1; + if (mIndex > minPrefix) + DUMP_NAV_LOGD("%.*s\n", mIndex, buffer); + mIndex = 0; + setIndent(indent); +} + +int CacheBuilder::Debug::ParentIndex(Node* node, int count, Node* parent) +{ + if (parent == NULL) + return -1; + ASSERT(node != parent); + int result = count; + Node* previous = node; + do { + result--; + previous = previous->traversePreviousNode(); + } while (previous && previous != parent); + if (previous != NULL) + return result; + result = count; + do { + result++; + } while ((node = node->traverseNextNode()) != NULL && node != parent); + if (node != NULL) + return result; + ASSERT(0); + return -1; +} + +void CacheBuilder::Debug::print(const char* name) { + print(name, strlen(name)); +} + +void CacheBuilder::Debug::print(const char* name, unsigned len) { + do { + if (mIndex + len >= DEBUG_BUFFER_SIZE) + flush(); + int copyLen = mIndex + len < DEBUG_BUFFER_SIZE ? + len : DEBUG_BUFFER_SIZE - mIndex; + memcpy(&mBuffer[mIndex], name, copyLen); + mIndex += copyLen; + name += copyLen; + len -= copyLen; + } while (len > 0); + mBuffer[mIndex] = '\0'; +} + +void CacheBuilder::Debug::setIndent(int indent) +{ + char scratch[64]; + snprintf(scratch, sizeof(scratch), "%.*s", indent, + " "); + print(scratch); +} + +void CacheBuilder::Debug::uChar(const UChar* name, unsigned len, bool hex) { + const UChar* end = name + len; + bool wroteHex = false; + while (name < end) { + unsigned ch = *name++; + if (ch == '\t' || ch == '\n' || ch == '\r' || ch == 0xa0) + ch = ' '; + if (ch < ' ' || ch == 0x7f) { + if (hex) { + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, "\\x%02x", ch); + wroteHex = true; + } else + mBuffer[mIndex++] = '?'; + } else if (ch >= 0x80) { + if (hex) { + if (ch < 0x800) + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\\x%02x\\x%02x", ch >> 6 | 0xc0, (ch & 0x3f) | 0x80); + else + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\\x%02x\\x%02x\\x%02x", ch >> 12 | 0xe0, + (ch >> 6 & 0x3f) | 0x80, (ch & 0x3f) | 0x80); + wroteHex = true; + } else + mBuffer[mIndex++] = '?'; + } else { + if (wroteHex && WTF::isASCIIHexDigit((UChar) ch)) + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\" \""); + else if (ch == '"' || ch == '\\') + mBuffer[mIndex++] = '\\'; + mBuffer[mIndex++] = ch; + wroteHex = false; + } + if (mIndex + 1 >= DEBUG_BUFFER_SIZE) + flush(); + } + flush(); +} + +void CacheBuilder::Debug::validateFrame() { + Frame* frame = frameAnd(); + Page* page = frame->page(); + ASSERT(page); + ASSERT((int) page > 0x10000); + Frame* child = frame->tree()->firstChild(); + while (child) { + Builder(child)->mDebug.validateFrame(); + child = child->tree()->nextSibling(); + } +} + +void CacheBuilder::Debug::wideString(const UChar* chars, int length, bool hex) { + if (length == 0) + print("\"\""); + else { + print("\""); + uChar(chars, length, hex); + print("\""); + } +} + +void CacheBuilder::Debug::wideString(const String& str) { + wideString(str.characters(), str.length(), false); +} + +#endif // DUMP_NAV_CACHE + +CacheBuilder::CacheBuilder() +{ + mAllowableTypes = ALL_CACHEDNODE_BITS; +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gNavCacheLogFile = NULL; +#endif +} + +void CacheBuilder::adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds, RenderBlock* renderer) +{ + if (!renderer->hasColumns()) + return; + int x = 0; + int y = 0; + int tx = track.mBounds.x(); + int ty = track.mBounds.y(); + int columnGap = track.mColumnGap; + size_t limit = track.mColumnInfo->columnCount(); + for (size_t index = 0; index < limit; index++) { + IntRect column = renderer->columnRectAt(track.mColumnInfo, index); + column.move(tx, ty); + IntRect test = *bounds; + test.move(x, y); + if (column.contains(test)) { + if ((x | y) == 0) + return; + *bounds = test; + node->move(x, y); + return; + } + int xOffset = column.width() + columnGap; + x += track.mDirection == LTR ? xOffset : -xOffset; + y -= column.height(); + } +} + +// Checks if a node has one of event listener types. +bool CacheBuilder::NodeHasEventListeners(Node* node, AtomicString* eventTypes, int length) { + for (int i = 0; i < length; ++i) { + if (!node->getEventListeners(eventTypes[i]).isEmpty()) + return true; + } + return false; +} + +bool CacheBuilder::AnyChildIsClick(Node* node) +{ + AtomicString eventTypes[5] = { + eventNames().clickEvent, + eventNames().mousedownEvent, + eventNames().mouseupEvent, + eventNames().keydownEvent, + eventNames().keyupEvent + }; + + Node* child = node->firstChild(); + while (child != NULL) { + if (child->isFocusable() || + NodeHasEventListeners(child, eventTypes, 5)) + return true; + if (AnyChildIsClick(child)) + return true; + child = child->nextSibling(); + } + return false; +} + +bool CacheBuilder::AnyIsClick(Node* node) +{ + if (node->hasTagName(HTMLNames::bodyTag)) + return AnyChildIsClick(node); + + AtomicString eventTypeSetOne[4] = { + eventNames().mouseoverEvent, + eventNames().mouseoutEvent, + eventNames().keydownEvent, + eventNames().keyupEvent + }; + + if (!NodeHasEventListeners(node, eventTypeSetOne, 4)) + return false; + + AtomicString eventTypeSetTwo[3] = { + eventNames().clickEvent, + eventNames().mousedownEvent, + eventNames().mouseupEvent + }; + + if (NodeHasEventListeners(node, eventTypeSetTwo, 3)) + return false; + + return AnyChildIsClick(node); +} + +void CacheBuilder::buildCache(CachedRoot* root) +{ + Frame* frame = FrameAnd(this); + mPictureSetDisabled = false; + BuildFrame(frame, frame, root, (CachedFrame*) root); + root->finishInit(); // set up frame parent pointers, child pointers + setData((CachedFrame*) root); +} + +static Node* ParentWithChildren(Node* node) +{ + Node* parent = node; + while ((parent = parent->parentNode())) { + if (parent->childNodeCount() > 1) + return parent; + } + return 0; +} + +// FIXME +// Probably this should check for null instead of the caller. If the +// Tracker object is the last thing in the dom, checking for null in the +// caller in some cases fails to set up Tracker state which may be useful +// to the nodes parsed immediately after the tracked noe. +static Node* OneAfter(Node* node) +{ + Node* parent = node; + Node* sibling = NULL; + while ((parent = parent->parentNode()) != NULL) { + sibling = parent->nextSibling(); + if (sibling != NULL) + break; + } + return sibling; +} + +// return true if this renderer is really a pluinview, and it wants +// key-events (i.e. focus) +static bool checkForPluginViewThatWantsFocus(RenderObject* renderer) { + if (renderer->isWidget()) { + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (widget && (widget->isPluginView() || widget->isPluginViewBase())) { + // check if this plugin really wants key events (TODO) + return true; + } + } + return false; +} + +#if USE(ACCELERATED_COMPOSITING) +static void AddLayer(CachedFrame* frame, size_t index, const IntPoint& location, int id) +{ + DBG_NAV_LOGD("frame=%p index=%d loc=(%d,%d) id=%d", frame, index, + location.x(), location.y(), id); + CachedLayer cachedLayer; + cachedLayer.setCachedNodeIndex(index); + cachedLayer.setOffset(location); + cachedLayer.setUniqueId(id); + frame->add(cachedLayer); +} +#endif + +static int FindColorIndex(WTF::Vector<CachedColor>& colorTracker, + const CachedColor& cachedColor) +{ + CachedColor* work = colorTracker.begin() - 1; + CachedColor* end = colorTracker.end(); + while (++work < end) { + if (*work == cachedColor) + return work - colorTracker.begin(); + } + int result = colorTracker.size(); + colorTracker.grow(result + 1); + CachedColor& newColor = colorTracker.last(); + newColor = cachedColor; + return result; +} + +static void InitColor(CachedColor* color) +{ + color->setFillColor(RenderStyle::initialRingFillColor()); + color->setInnerWidth(RenderStyle::initialRingInnerWidth()); + color->setOuterWidth(RenderStyle::initialRingOuterWidth()); + color->setOutset(RenderStyle::initialRingOutset()); + color->setPressedInnerColor(RenderStyle::initialRingPressedInnerColor()); + color->setPressedOuterColor(RenderStyle::initialRingPressedOuterColor()); + color->setRadius(RenderStyle::initialRingRadius()); + color->setSelectedInnerColor(RenderStyle::initialRingSelectedInnerColor()); + color->setSelectedOuterColor(RenderStyle::initialRingSelectedOuterColor()); +} + +// when new focus is found, push it's parent on a stack + // as long as more focii are found with the same (grand) parent, note it + // (which only requires retrieving the last parent on the stack) +// when the parent's last child is found, pop the stack +// different from Tracker in that Tracker only pushes focii with children + +// making this work with focus - child focus - grandchild focus is tricky +// if I keep the generation number, I may be able to more quickly determine that +// a node is a grandchild of the focus's parent +// this additionally requires being able to find the grandchild's parent + +// keep nodes that are focusable +void CacheBuilder::BuildFrame(Frame* root, Frame* frame, + CachedRoot* cachedRoot, CachedFrame* cachedFrame) +{ + WTF::Vector<FocusTracker> tracker(1); // sentinel + { + FocusTracker* baseTracker = tracker.data(); + bzero(baseTracker, sizeof(FocusTracker)); + baseTracker->mCachedNodeIndex = -1; + } + WTF::Vector<LayerTracker> layerTracker(1); // sentinel + bzero(layerTracker.data(), sizeof(LayerTracker)); + WTF::Vector<ClipColumnTracker> clipTracker(1); // sentinel + bzero(clipTracker.data(), sizeof(ClipColumnTracker)); + WTF::Vector<TabIndexTracker> tabIndexTracker(1); // sentinel + bzero(tabIndexTracker.data(), sizeof(TabIndexTracker)); + WTF::Vector<CachedColor> colorTracker(1); + InitColor(colorTracker.data()); +#if DUMP_NAV_CACHE + char* frameNamePtr = cachedFrame->mDebug.mFrameName; + Builder(frame)->mDebug.frameName(frameNamePtr, frameNamePtr + + sizeof(cachedFrame->mDebug.mFrameName) - 1); + *frameNamePtr = '\0'; + int nodeIndex = 1; +#endif + NodeWalk walk; + Document* doc = frame->document(); + Node* parent = doc; + CachedNode cachedParentNode; + cachedParentNode.init(parent); +#if DUMP_NAV_CACHE + cachedParentNode.mDebug.mNodeIndex = nodeIndex; +#endif + cachedFrame->add(colorTracker[0]); + cachedFrame->add(cachedParentNode); + Node* node = parent; + int cacheIndex = 1; + int colorIndex = 0; // assume no special css ring colors + const void* lastStyleDataPtr = 0; + int textInputIndex = 0; + Node* focused = doc->focusedNode(); + if (focused) + cachedRoot->setFocusBounds(focused->getRect()); + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); +#if USE(ACCELERATED_COMPOSITING) + // The frame itself might be composited so we need to track the layer. Do + // not track the base frame's layer as the main content is draw as part of + // BaseLayerAndroid's picture. + if (frame != root && frame->contentRenderer() + && frame->contentRenderer()->usesCompositing() && node->lastChild()) + TrackLayer(layerTracker, frame->contentRenderer(), node->lastChild(), + globalOffsetX, globalOffsetY); +#endif + while (walk.mMore || (node = node->traverseNextNode()) != NULL) { +#if DUMP_NAV_CACHE + nodeIndex++; +#endif + FocusTracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + while (node == last->mLastChild) { + if (CleanUpContainedNodes(cachedRoot, cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + lastChildIndex = last->mCachedNodeIndex; + last = &tracker.last(); + } + do { + const ClipColumnTracker* lastClip = &clipTracker.last(); + if (node != lastClip->mLastChild) + break; + clipTracker.removeLast(); + } while (true); + do { + const LayerTracker* lastLayer = &layerTracker.last(); + if (node != lastLayer->mLastChild) + break; + layerTracker.removeLast(); + } while (true); + do { + const TabIndexTracker* lastTabIndex = &tabIndexTracker.last(); + if (node != lastTabIndex->mLastChild) + break; + tabIndexTracker.removeLast(); + } while (true); + Frame* child = HasFrame(node); + CachedNode cachedNode; + if (child != NULL) { + if (child->document() == NULL) + continue; + RenderObject* nodeRenderer = node->renderer(); + if (nodeRenderer != NULL && nodeRenderer->style()->visibility() == HIDDEN) + continue; + CachedFrame cachedChild; + cachedChild.init(cachedRoot, cacheIndex, child); + int childFrameIndex = cachedFrame->childCount(); + cachedFrame->addFrame(cachedChild); + cachedNode.init(node); + cachedNode.setIndex(cacheIndex++); + cachedNode.setDataIndex(childFrameIndex); + cachedNode.setType(FRAME_CACHEDNODETYPE); +#if DUMP_NAV_CACHE + cachedNode.mDebug.mNodeIndex = nodeIndex; + cachedNode.mDebug.mParentGroupIndex = Debug::ParentIndex( + node, nodeIndex, NULL); +#endif + cachedFrame->add(cachedNode); + CachedFrame* childPtr = cachedFrame->lastChild(); + BuildFrame(root, child, cachedRoot, childPtr); + continue; + } + int tabIndex = node->tabIndex(); + Node* lastChild = node->lastChild(); + if (tabIndex <= 0) + tabIndex = tabIndexTracker.last().mTabIndex; + else if (tabIndex > 0 && lastChild) { + DBG_NAV_LOGD("tabIndex=%d node=%p", tabIndex, node); + tabIndexTracker.grow(tabIndexTracker.size() + 1); + TabIndexTracker& indexTracker = tabIndexTracker.last(); + indexTracker.mTabIndex = tabIndex; + indexTracker.mLastChild = OneAfter(lastChild); + } + RenderObject* nodeRenderer = node->renderer(); + bool isTransparent = false; + bool hasCursorRing = true; + if (nodeRenderer != NULL) { + RenderStyle* style = nodeRenderer->style(); + if (style->visibility() == HIDDEN) + continue; + isTransparent = nodeRenderer->hasBackground() == false; +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + hasCursorRing = style->tapHighlightColor().alpha() > 0; +#endif +#if USE(ACCELERATED_COMPOSITING) + // If this renderer has its own layer and the layer is composited, + // start tracking it. + if (lastChild && nodeRenderer->hasLayer() && toRenderBoxModelObject(nodeRenderer)->layer()->backing()) + TrackLayer(layerTracker, nodeRenderer, lastChild, globalOffsetX, globalOffsetY); +#endif + } + bool more = walk.mMore; + walk.reset(); + // GetGlobalBounds(node, &bounds, false); + bool computeCursorRings = false; + bool hasClip = false; + bool hasMouseOver = false; + bool isUnclipped = false; + bool isFocus = node == focused; + bool takesFocus = false; + int columnGap = 0; + int imageCount = 0; + TextDirection direction = LTR; + String exported; + CachedNodeType type = NORMAL_CACHEDNODETYPE; + CachedColor cachedColor; + CachedInput cachedInput; + IntRect bounds; + IntRect absBounds; + IntRect originalAbsBounds; + ColumnInfo* columnInfo = NULL; + if (node->hasTagName(HTMLNames::areaTag)) { + type = AREA_CACHEDNODETYPE; + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + bounds = getAreaRect(area); + originalAbsBounds = bounds; + bounds.move(globalOffsetX, globalOffsetY); + absBounds = bounds; + isUnclipped = true; // FIXME: areamaps require more effort to detect + // assume areamaps are always visible for now + takesFocus = true; + goto keepNode; + } + if (nodeRenderer == NULL) + continue; + + // some common setup + absBounds = nodeRenderer->absoluteBoundingBoxRect(); + originalAbsBounds = absBounds; + absBounds.move(globalOffsetX, globalOffsetY); + hasClip = nodeRenderer->hasOverflowClip(); + + if (checkForPluginViewThatWantsFocus(nodeRenderer)) { + bounds = absBounds; + isUnclipped = true; + takesFocus = true; + type = PLUGIN_CACHEDNODETYPE; + goto keepNode; + } + // Only use the root contentEditable element + if (node->rendererIsEditable() && !node->parentOrHostNode()->rendererIsEditable()) { + bounds = absBounds; + takesFocus = true; + type = CONTENT_EDITABLE_CACHEDNODETYPE; + goto keepNode; + } + if (nodeRenderer->isRenderBlock()) { + RenderBlock* renderBlock = (RenderBlock*) nodeRenderer; + if (renderBlock->hasColumns()) { + columnInfo = renderBlock->columnInfo(); + columnGap = renderBlock->columnGap(); + direction = renderBlock->style()->direction(); + } + } + if ((hasClip != false || columnInfo != NULL) && lastChild) { + clipTracker.grow(clipTracker.size() + 1); + ClipColumnTracker& clip = clipTracker.last(); + clip.mBounds = absBounds; + clip.mLastChild = OneAfter(lastChild); + clip.mNode = node; + clip.mColumnInfo = columnInfo; + clip.mColumnGap = columnGap; + clip.mHasClip = hasClip; + clip.mDirection = direction; + if (columnInfo != NULL) { + const IntRect& oRect = ((RenderBox*)nodeRenderer)->visualOverflowRect(); + clip.mBounds.move(oRect.x(), oRect.y()); + } + } + if (node->isTextNode() && mAllowableTypes != NORMAL_CACHEDNODE_BITS) { + if (last->mSomeParentTakesFocus) // don't look at text inside focusable node + continue; + CachedNodeType checkType; + if (isFocusableText(&walk, more, node, &checkType, + &exported) == false) + continue; + #if DUMP_NAV_CACHE + { + char buffer[DEBUG_BUFFER_SIZE]; + mDebug.init(buffer, sizeof(buffer)); + mDebug.print("text link found: "); + mDebug.wideString(exported); + DUMP_NAV_LOGD("%s\n", buffer); + } + #endif + type = checkType; + // !!! test ! is the following line correctly needed for frames to work? + cachedNode.init(node); + const ClipColumnTracker& clipTrack = clipTracker.last(); + const IntRect& clip = clipTrack.mHasClip ? clipTrack.mBounds : + IntRect(0, 0, INT_MAX, INT_MAX); + if (ConstructTextRects((WebCore::Text*) node, walk.mStart, + (WebCore::Text*) walk.mFinalNode, walk.mEnd, globalOffsetX, + globalOffsetY, &bounds, clip, &cachedNode.mCursorRing) == false) + continue; + absBounds = bounds; + cachedNode.setBounds(bounds); + if (bounds.width() < MINIMUM_FOCUSABLE_WIDTH) + continue; + if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) + continue; + computeCursorRings = true; + isUnclipped = true; // FIXME: to hide or partially occlude synthesized links, each + // focus ring will also need the offset and length of characters + // used to produce it + goto keepTextNode; + } + if (node->hasTagName(WebCore::HTMLNames::inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (input->isTextField()) { + if (input->readOnly()) + continue; + type = TEXT_INPUT_CACHEDNODETYPE; + cachedInput.init(); + cachedInput.setAutoComplete(input->autoComplete()); + cachedInput.setSpellcheck(input->spellcheck()); + cachedInput.setFormPointer(input->form()); + cachedInput.setIsTextField(true); + exported = input->value().threadsafeCopy(); + cachedInput.setMaxLength(input->maxLength()); + cachedInput.setTypeFromElement(input); + // If this does not need to be threadsafe, we can use crossThreadString(). + // See http://trac.webkit.org/changeset/49160. + cachedInput.setName(input->name().string().threadsafeCopy()); + // can't detect if this is drawn on top (example: deviant.com login parts) + isUnclipped = isTransparent; + } else if (input->isInputTypeHidden()) + continue; + else if (input->isRadioButton() || input->isCheckbox()) + isTransparent = false; + } else if (node->hasTagName(HTMLNames::textareaTag)) { + HTMLTextAreaElement* area = static_cast<HTMLTextAreaElement*>(node); + if (area->readOnly()) + continue; + cachedInput.init(); + type = TEXT_INPUT_CACHEDNODETYPE; + cachedInput.setFormPointer(area->form()); + cachedInput.setIsTextArea(true); + cachedInput.setSpellcheck(area->spellcheck()); + exported = area->value().threadsafeCopy(); + } else if (node->hasTagName(HTMLNames::aTag)) { + const HTMLAnchorElement* anchorNode = + (const HTMLAnchorElement*) node; + if (!anchorNode->isFocusable() && !HasTriggerEvent(node)) + continue; + if (node->disabled()) + continue; + hasMouseOver = NodeHasEventListeners(node, &eventNames().mouseoverEvent, 1); + type = ANCHOR_CACHEDNODETYPE; + KURL href = anchorNode->href(); + if (!href.isEmpty() && !WebCore::protocolIsJavaScript(href.string())) + // Set the exported string for all non-javascript anchors. + exported = href.string().threadsafeCopy(); + } else if (node->hasTagName(HTMLNames::selectTag)) { + type = SELECT_CACHEDNODETYPE; + } + if (type == TEXT_INPUT_CACHEDNODETYPE) { + RenderTextControl* renderText = + static_cast<RenderTextControl*>(nodeRenderer); + if (isFocus) + cachedRoot->setSelection(renderText->selectionStart(), renderText->selectionEnd()); + // FIXME: Are we sure there will always be a style and font, and it's correct? + RenderStyle* style = nodeRenderer->style(); + if (style) { + isUnclipped |= !style->hasAppearance(); + int lineHeight = -1; + Length lineHeightLength = style->lineHeight(); + // If the lineHeight is negative, WebTextView will calculate it + // based on the text size, using the Paint. + // See RenderStyle.computedLineHeight. + if (lineHeightLength.isPositive()) + lineHeight = style->computedLineHeight(); + cachedInput.setLineHeight(lineHeight); + cachedInput.setTextSize(style->font().size()); + cachedInput.setIsRtlText(style->direction() == RTL + || style->textAlign() == WebCore::RIGHT + || style->textAlign() == WebCore::WEBKIT_RIGHT); + } + cachedInput.setPaddingLeft(renderText->paddingLeft() + renderText->borderLeft()); + cachedInput.setPaddingTop(renderText->paddingTop() + renderText->borderTop()); + cachedInput.setPaddingRight(renderText->paddingRight() + renderText->borderRight()); + cachedInput.setPaddingBottom(renderText->paddingBottom() + renderText->borderBottom()); + } + takesFocus = true; + bounds = absBounds; + if (type != ANCHOR_CACHEDNODETYPE) { + bool isFocusable = node->isKeyboardFocusable(NULL) || + node->isMouseFocusable() || node->isFocusable(); + if (isFocusable == false) { + if (node->disabled()) + continue; + bool overOrOut = HasOverOrOut(node); + bool hasTrigger = HasTriggerEvent(node); + if (overOrOut == false && hasTrigger == false) + continue; + takesFocus = hasTrigger; + } + } + computeCursorRings = true; + keepNode: + cachedNode.init(node); + if (computeCursorRings == false) { + cachedNode.setBounds(bounds); + cachedNode.mCursorRing.append(bounds); + } else if (ConstructPartRects(node, bounds, &cachedNode.mBounds, + globalOffsetX, globalOffsetY, &cachedNode.mCursorRing, + &imageCount) == false) + continue; + keepTextNode: + if (nodeRenderer) { // area tags' node->renderer() == 0 + RenderStyle* style = nodeRenderer->style(); + const void* styleDataPtr = style->ringData(); + // to save time, see if we're pointing to the same style data as before + if (lastStyleDataPtr != styleDataPtr) { + lastStyleDataPtr = styleDataPtr; + cachedColor.setFillColor(style->ringFillColor()); + cachedColor.setInnerWidth(style->ringInnerWidth()); + cachedColor.setOuterWidth(style->ringOuterWidth()); + cachedColor.setOutset(style->ringOutset()); + cachedColor.setPressedInnerColor(style->ringPressedInnerColor()); + cachedColor.setPressedOuterColor(style->ringPressedOuterColor()); + cachedColor.setRadius(style->ringRadius()); + cachedColor.setSelectedInnerColor(style->ringSelectedInnerColor()); + cachedColor.setSelectedOuterColor(style->ringSelectedOuterColor()); + int oldSize = colorTracker.size(); + colorIndex = FindColorIndex(colorTracker, cachedColor); + if (colorIndex == oldSize) + cachedFrame->add(cachedColor); + } + } else + colorIndex = 0; + IntRect clip = hasClip ? bounds : absBounds; + size_t clipIndex = clipTracker.size(); + if (clipTracker.last().mNode == node) + clipIndex -= 1; + while (--clipIndex > 0) { + const ClipColumnTracker& clipTrack = clipTracker.at(clipIndex); + if (clipTrack.mHasClip == false) { + adjustForColumns(clipTrack, &cachedNode, &absBounds, static_cast<RenderBlock*>(nodeRenderer)); + continue; + } + const IntRect& parentClip = clipTrack.mBounds; + if (hasClip == false && type == ANCHOR_CACHEDNODETYPE) + clip = parentClip; + else + clip.intersect(parentClip); + hasClip = true; + } + bool isInLayer = false; +#if USE(ACCELERATED_COMPOSITING) + // If this renderer has a composited parent layer (including itself), + // add the node to the cached layer. + // FIXME: does not work for area rects + RenderLayer* enclosingLayer = nodeRenderer->enclosingLayer(); + if (enclosingLayer && enclosingLayer->enclosingCompositingLayer()) { + LayerAndroid* layer = layerTracker.last().mLayer; + if (layer) { + const IntRect& layerClip = layerTracker.last().mBounds; + if (!layerClip.isEmpty() && !cachedNode.clip(layerClip)) { + DBG_NAV_LOGD("skipped on layer clip %d", cacheIndex); + continue; // skip this node if outside of the clip + } + isInLayer = true; + isUnclipped = true; // assume that layers do not have occluded nodes + hasClip = false; + AddLayer(cachedFrame, cachedFrame->size(), layerClip.location(), + layer->uniqueId()); + } + } +#endif + if (hasClip) { + if (clip.isEmpty()) + continue; // skip this node if clip prevents all drawing + else if (cachedNode.clip(clip) == false) + continue; // skip this node if outside of the clip + } + cachedNode.setColorIndex(colorIndex); + cachedNode.setExport(exported); + cachedNode.setHasCursorRing(hasCursorRing); + cachedNode.setHasMouseOver(hasMouseOver); + cachedNode.setHitBounds(absBounds); + cachedNode.setIndex(cacheIndex); + cachedNode.setIsFocus(isFocus); + cachedNode.setIsInLayer(isInLayer); + cachedNode.setIsTransparent(isTransparent); + cachedNode.setIsUnclipped(isUnclipped); + cachedNode.setOriginalAbsoluteBounds(originalAbsBounds); + cachedNode.setParentIndex(last->mCachedNodeIndex); + cachedNode.setParentGroup(ParentWithChildren(node)); + cachedNode.setSingleImage(imageCount == 1); + cachedNode.setTabIndex(tabIndex); + cachedNode.setType(type); + if (type == TEXT_INPUT_CACHEDNODETYPE) { + cachedFrame->add(cachedInput); + cachedNode.setDataIndex(textInputIndex); + textInputIndex++; + } else + cachedNode.setDataIndex(-1); +#if DUMP_NAV_CACHE + cachedNode.mDebug.mNodeIndex = nodeIndex; + cachedNode.mDebug.mParentGroupIndex = Debug::ParentIndex( + node, nodeIndex, (Node*) cachedNode.parentGroup()); +#endif + cachedFrame->add(cachedNode); + { + int lastIndex = cachedFrame->size() - 1; + if (node == focused) { + CachedNode* cachedNodePtr = cachedFrame->getIndex(lastIndex); + cachedRoot->setCachedFocus(cachedFrame, cachedNodePtr); + } + if (lastChild != NULL) { + tracker.grow(tracker.size() + 1); + FocusTracker& working = tracker.last(); + working.mCachedNodeIndex = lastIndex; + working.mLastChild = OneAfter(lastChild); + last = &tracker.at(tracker.size() - 2); + working.mSomeParentTakesFocus = last->mSomeParentTakesFocus | takesFocus; + } + } + cacheIndex++; + } + while (tracker.size() > 1) { + FocusTracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + if (CleanUpContainedNodes(cachedRoot, cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + } +} + +bool CacheBuilder::CleanUpContainedNodes(CachedRoot* cachedRoot, + CachedFrame* cachedFrame, const FocusTracker* last, int lastChildIndex) +{ + // if outer is body, disable outer + // or if there's more than one inner, disable outer + // or if inner is keyboard focusable, disable outer + // else disable inner by removing it + int childCount = lastChildIndex - last->mCachedNodeIndex; + if (childCount == 0) + return false; + CachedNode* lastCached = cachedFrame->getIndex(last->mCachedNodeIndex); + Node* lastNode = (Node*) lastCached->nodePointer(); + if ((childCount > 1 && lastNode->hasTagName(HTMLNames::selectTag) == false) || + lastNode->hasTagName(HTMLNames::bodyTag) || + lastNode->hasTagName(HTMLNames::formTag)) { + lastCached->setBounds(IntRect(0, 0, 0, 0)); + lastCached->mCursorRing.clear(); + return false; + } + CachedNode* onlyChildCached = cachedFrame->lastNode(); + Node* onlyChild = (Node*) onlyChildCached->nodePointer(); + bool outerIsMouseMoveOnly = + lastNode->isKeyboardFocusable(NULL) == false && + lastNode->isMouseFocusable() == false && + lastNode->isFocusable() == false && + HasOverOrOut(lastNode) == true && + HasTriggerEvent(lastNode) == false; + if (onlyChildCached->parent() == lastCached) + onlyChildCached->setParentIndex(lastCached->parentIndex()); + bool hasFocus = lastCached->isFocus() || onlyChildCached->isFocus(); + if (outerIsMouseMoveOnly || onlyChild->isKeyboardFocusable(NULL) + || onlyChildCached->isPlugin()) { + int index = lastCached->index(); + *lastCached = *onlyChildCached; + lastCached->setIndex(index); + CachedFrame* frame = cachedFrame->hasFrame(lastCached); + if (frame) + frame->setIndexInParent(index); + } + cachedFrame->removeLast(); + if (hasFocus) + cachedRoot->setCachedFocus(cachedFrame, cachedFrame->lastNode()); + return true; +} + +Node* CacheBuilder::currentFocus() const +{ + Frame* frame = FrameAnd(this); + Document* doc = frame->document(); + if (doc != NULL) { + Node* focus = doc->focusedNode(); + if (focus != NULL) + return focus; + } + Frame* child = frame->tree()->firstChild(); + while (child) { + CacheBuilder* cacheBuilder = Builder(child); + Node* focus = cacheBuilder->currentFocus(); + if (focus) + return focus; + child = child->tree()->nextSibling(); + } + return NULL; +} + +static bool strCharCmp(const char* matches, const UChar* test, int wordLength, + int wordCount) +{ + for (int index = 0; index < wordCount; index++) { + for (int inner = 0; inner < wordLength; inner++) { + if (matches[inner] != test[inner]) { + matches += wordLength; + goto next; + } + } + return true; +next: + ; + } + return false; +} + +static const int stateTwoLetter[] = { + 0x02060c00, // A followed by: [KLRSZ] + 0x00000000, // B + 0x00084001, // C followed by: [AOT] + 0x00000014, // D followed by: [CE] + 0x00000000, // E + 0x00001800, // F followed by: [LM] + 0x00100001, // G followed by: [AU] + 0x00000100, // H followed by: [I] + 0x00002809, // I followed by: [ADLN] + 0x00000000, // J + 0x01040000, // K followed by: [SY] + 0x00000001, // L followed by: [A] + 0x000ce199, // M followed by: [ADEHINOPST] + 0x0120129c, // N followed by: [CDEHJMVY] + 0x00020480, // O followed by: [HKR] + 0x00420001, // P followed by: [ARW] + 0x00000000, // Q + 0x00000100, // R followed by: [I] + 0x0000000c, // S followed by: [CD] + 0x00802000, // T followed by: [NX] + 0x00080000, // U followed by: [T] + 0x00080101, // V followed by: [AIT] + 0x01200101 // W followed by: [AIVY] +}; + +static const char firstIndex[] = { + 0, 5, 5, 8, 10, 10, 12, 14, + 15, 19, 19, 21, 22, 32, 40, 43, + 46, 46, 47, 49, 51, 52, 55, 59 +}; + +// from http://infolab.stanford.edu/~manku/bitcount/bitcount.html +#define TWO(c) (0x1u << (c)) +#define MASK(c) (((unsigned int)(-1)) / (TWO(TWO(c)) + 1u)) +#define COUNT(x,c) ((x) & MASK(c)) + (((x) >> (TWO(c))) & MASK(c)) + +int bitcount (unsigned int n) +{ + n = COUNT(n, 0); + n = COUNT(n, 1); + n = COUNT(n, 2); + n = COUNT(n, 3); + return COUNT(n, 4); +} + +#undef TWO +#undef MASK +#undef COUNT + +static bool isUnicodeSpace(UChar ch) +{ + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == 0xa0; +} + +static bool validZip(int stateIndex, const UChar* zipPtr) +{ + static const struct { + char mLow; + char mHigh; + char mException1; + char mException2; + } zipRange[] = { + { 99, 99, -1, -1 }, // AK Alaska + { 35, 36, -1, -1 }, // AL Alabama + { 71, 72, -1, -1 }, // AR Arkansas + { 96, 96, -1, -1 }, // AS American Samoa + { 85, 86, -1, -1 }, // AZ Arizona + { 90, 96, -1, -1 }, // CA California + { 80, 81, -1, -1 }, // CO Colorado + { 6, 6, -1, -1 }, // CT Connecticut + { 20, 20, -1, -1 }, // DC District of Columbia + { 19, 19, -1, -1 }, // DE Delaware + { 32, 34, -1, -1 }, // FL Florida + { 96, 96, -1, -1 }, // FM Federated States of Micronesia + { 30, 31, -1, -1 }, // GA Georgia + { 96, 96, -1, -1 }, // GU Guam + { 96, 96, -1, -1 }, // HI Hawaii + { 50, 52, -1, -1 }, // IA Iowa + { 83, 83, -1, -1 }, // ID Idaho + { 60, 62, -1, -1 }, // IL Illinois + { 46, 47, -1, -1 }, // IN Indiana + { 66, 67, 73, -1 }, // KS Kansas + { 40, 42, -1, -1 }, // KY Kentucky + { 70, 71, -1, -1 }, // LA Louisiana + { 1, 2, -1, -1 }, // MA Massachusetts + { 20, 21, -1, -1 }, // MD Maryland + { 3, 4, -1, -1 }, // ME Maine + { 96, 96, -1, -1 }, // MH Marshall Islands + { 48, 49, -1, -1 }, // MI Michigan + { 55, 56, -1, -1 }, // MN Minnesota + { 63, 65, -1, -1 }, // MO Missouri + { 96, 96, -1, -1 }, // MP Northern Mariana Islands + { 38, 39, -1, -1 }, // MS Mississippi + { 55, 56, -1, -1 }, // MT Montana + { 27, 28, -1, -1 }, // NC North Carolina + { 58, 58, -1, -1 }, // ND North Dakota + { 68, 69, -1, -1 }, // NE Nebraska + { 3, 4, -1, -1 }, // NH New Hampshire + { 7, 8, -1, -1 }, // NJ New Jersey + { 87, 88, 86, -1 }, // NM New Mexico + { 88, 89, 96, -1 }, // NV Nevada + { 10, 14, 0, 6 }, // NY New York + { 43, 45, -1, -1 }, // OH Ohio + { 73, 74, -1, -1 }, // OK Oklahoma + { 97, 97, -1, -1 }, // OR Oregon + { 15, 19, -1, -1 }, // PA Pennsylvania + { 6, 6, 0, 9 }, // PR Puerto Rico + { 96, 96, -1, -1 }, // PW Palau + { 2, 2, -1, -1 }, // RI Rhode Island + { 29, 29, -1, -1 }, // SC South Carolina + { 57, 57, -1, -1 }, // SD South Dakota + { 37, 38, -1, -1 }, // TN Tennessee + { 75, 79, 87, 88 }, // TX Texas + { 84, 84, -1, -1 }, // UT Utah + { 22, 24, 20, -1 }, // VA Virginia + { 6, 9, -1, -1 }, // VI Virgin Islands + { 5, 5, -1, -1 }, // VT Vermont + { 98, 99, -1, -1 }, // WA Washington + { 53, 54, -1, -1 }, // WI Wisconsin + { 24, 26, -1, -1 }, // WV West Virginia + { 82, 83, -1, -1 } // WY Wyoming + }; + + int zip = zipPtr[0] - '0'; + zip *= 10; + zip += zipPtr[1] - '0'; + int low = zipRange[stateIndex].mLow; + int high = zipRange[stateIndex].mHigh; + if (zip >= low && zip <= high) + return true; + if (zip == zipRange[stateIndex].mException1) + return true; + if (zip == zipRange[stateIndex].mException2) + return true; + return false; +} + +#define MAX_PLACE_NAME_LENGTH 25 // the longest allowable one word place name + +CacheBuilder::FoundState CacheBuilder::FindAddress(const UChar* chars, + unsigned length, int* start, int* end, bool caseInsensitive) +{ + FindState addressState; + FindReset(&addressState); + addressState.mWords[0] = addressState.mStarts[0] = chars; + addressState.mCaseInsensitive = caseInsensitive; + FoundState state = FindPartialAddress(chars, chars, length, &addressState); + if (state == FOUND_PARTIAL && addressState.mProgress == ZIP_CODE && + addressState.mNumberCount == 0) { + addressState.mProgress = FIND_STREET; + state = FindPartialAddress(NULL, NULL, 0, &addressState); + } + *start = addressState.mStartResult; + *end = addressState.mEndResult; + return state; +} + +CacheBuilder::FoundState CacheBuilder::FindPartialAddress(const UChar* baseChars, + const UChar* chars, unsigned length, FindState* s) +{ + // lower case letters are optional; trailing asterisk is optional 's' + static char const* const longStreetNames[] = { + "\x04" "LleY" "\x04" "NneX" "\x05" "RCade" "\x05" "VEnue" "\x06" "LAMEDA", // A + "\x04" "aYoU" "\x04" "eaCH" "\x03" "eND" "\x05" "LuFf*" "\x05" "oTtoM" + "\x08" "ouLeVarD" "\x05" "Ranch" "\x05" "RidGe" "\x05" "RooK*" + "\x04" "urG*" "\x05" "YPass" "\x07" "roadWAY", // B + "\x05" "AMINO" + "\x03" "amP" "\x05" "anYoN" "\x03" "aPE" "\x07" "auSeWaY" "\x06" "enTeR*" + "\x06" "IRcle*" "\x05" "LiFf*" "\x03" "LuB" "\x05" "oMmoN" "\x06" "ORner*" + "\x05" "ouRSE" "\x05" "ourT*" "\x04" "oVe*" "\x04" "ReeK" "\x07" "REScent" + "\x04" "ReST" "\x07" "ROSSING" "\x08" "ROSSROAD" "\x04" "URVe" + "\x05" "AMINO" "\x06" "IRCULO" "\x07" "REScent", // C + "\x03" "aLe" "\x02" "aM" "\x05" "iVide" "\x05" "Rive*", // D + "\x06" "STate*" "\x09" "XPresswaY" "\x09" "XTension*", // E + "\x04" "ALL*" "\x04" "eRrY" "\x05" "ieLD*" "\x04" "LaT*" "\x04" "oRD*" + "\x05" "oReST" "\x05" "oRGe*" "\x04" "oRK*" "\x03" "orT" "\x06" "reeWaY", // F + "\x06" "arDeN*" "\x06" "aTeWaY" "\x04" "LeN*" "\x05" "ReeN*" "\x05" "RoVe*", // G + "\x06" "arBoR*" "\x04" "aVeN" "\x06" "eighTS" "\x06" "ighWaY" "\x04" "iLl*" + "\x05" "OLloW", // H + "\x04" "NLeT" "\x06" "Sland*" "\x03" "SLE", // I + "\x08" "unCTion*", // J + "\x03" "eY*" "\x05" "NoLl*", // K + "\x04" "aKe*" "\x03" "AND" "\x06" "aNDinG" "\x03" "aNe" "\x05" "iGhT*" + "\x03" "oaF" "\x04" "oCK*" "\x04" "oDGe" "\x03" "OOP", // L + "\x03" "ALL" "\x05" "aNoR*" "\x06" "eaDoW*" "\x03" "EWS" "\x04" "iLl*" + "\x06" "iSsioN" "\x07" "oTorWaY" "\x04" "ounT" "\x08" "ounTaiN*", // M + "\x03" "eCK", // N + "\x06" "RCHard" "\x03" "VAL" "\x07" "verPASs", // O + "\x04" "ARK*" "\x07" "arKWaY*" "\x03" "ASS" "\x06" "aSsaGE" "\x03" "ATH" + "\x03" "IKE" "\x04" "iNE*" "\x04" "Lace" "\x05" "LaiN*" "\x04" "LaZa" + "\x05" "oinT*" "\x04" "oRT*" "\x06" "Rairie" "\x06" "RIVADA", // P + NULL, + "\x05" "ADiaL" "\x03" "AMP" "\x04" "aNCH" "\x05" "aPiD*" + "\x03" "eST" + "\x05" "iDGe*" "\x04" "IVer" "\x04" "oaD*" "\x04" "ouTE" "\x02" "OW" + "\x02" "UE" "\x02" "UN", // R + "\x05" "HoaL*" "\x05" "HoRe*" "\x05" "KyWaY" "\x06" "PrinG*" "\x04" "PUR*" + "\x06" "Quare*" "\x06" "TAtion" "\x08" "TRAvenue" "\x05" "TReaM" + "\x06" "Treet*" "\x05" "uMmiT" "\x07" "PeeDWaY", // S + "\x06" "ERrace" "\x09" "hRoughWaY" "\x04" "RaCE" "\x04" "RAcK" "\x09" "RaFficwaY" + "\x04" "RaiL" "\x05" "UNneL" "\x07" "urnPiKE", // T + "\x08" "nderPASs" "\x05" "Nion*", // U + "\x06" "aLleY*" "\x06" "IAduct" "\x04" "ieW*" "\x07" "iLlaGe*" "\x04" "iLle" + "\x04" "ISta", // V + "\x04" "ALK*" "\x03" "ALL" "\x03" "AY*" "\x04" "eLl*", // W + "\x03" "ING" "\x02" "RD", // X + }; + + static char const* const longStateNames[] = { + "\x8e" "la" "\x85" "bama" "\x02" "\x84" "ska" "\x01" "\x8f" "merican Samoa" "\x04" + "\x91" "r" "\x86" "izona" "\x05" "\x87" "kansas" "\x03", + NULL, + "\x8b" "alifornia" "\x06" "\x95" "o" "\x87" "lorado" "\x07" "\x8a" "nnecticut" "\x08", + "\x89" "elaware" "\x0a" "\x95" "istrict of Columbia" "\x09", + NULL, + "\x9f" "ederated States of Micronesia" "\x0c" "\x88" "lorida" "\x0b", + "\x85" "uam" "\x0e" "\x88" "eorgia" "\x0d", + "\x87" "awaii" "\x0f", + "\x86" "daho" "\x11" "\x89" "llinois" "\x12" "\x88" "ndiana" "\x13" "\x85" + "owa" "\x10", + NULL, + "\x87" "ansas" "\x14" "\x89" "entucky" "\x15", + "\x8a" "ouisiana" "\x16", + "\x86" "aine" "\x19" "\x99" "ar" "\x8e" "shall Islands" "\x1a" "\x86" "yland" "\x18" + "\x8e" "assachusetts" "\x17" "\x93" "i" "\x87" "chigan" "\x1b" + "\x88" "nnesota" "\x1c" "\x93" "iss" "\x88" "issippi" "\x1f" "\x85" + "ouri" "\x1d" "\x88" "ontana" "\x20", + "\x90" "e" "\x87" "braska" "\x23" "\x85" "vada" "\x27" "\xa5" "ew " "\x8a" + "Hampshire" "\x24" "\x87" "Jersey" "\x25" "\x87" "Mexico" "\x26" + "\x85" "York" "\x28" "\x98" "orth " "\x89" "Carolina" "\x21" "\x87" + "Dakota" "\x22" "\x99" "orthern Mariana Islands" "\x1e", + "\x85" "hio" "\x29" "\x89" "klahoma" "\x2a" "\x87" "regon" "\x2b", + "\x86" "alau" "\x2e" "\x8d" "ennsylvania" "\x2c" "\x8c" "uerto Rico" "\x2d", + NULL, + "\x8d" "hode Island" "\x2f", + "\x98" "outh " "\x89" "Carolina" "\x30" "\x87" "Dakota" "\x31", + "\x90" "e" "\x88" "nnessee" "\x32" "\x84" "xas" "\x33", + "\x85" "tah" "\x34", + "\x88" "ermont" "\x37" "\x94" "irgin" "\x89" " Islands" "\x36" "\x83" "ia" "\x35", + "\x8b" "ashington" "\x38" "\x8e" "est Virginia" "\x3a" "\x8a" "isconsin" "\x39" + "\x88" "yoming" "\x3b" + }; + +#if 0 // DEBUG_NAV_UI + static char const* const progressNames[] = { + "NO_ADDRESS", + "SKIP_TO_SPACE", + "HOUSE_NUMBER", + "NUMBER_TRAILING_SPACE", + "ADDRESS_LINE", + "STATE_NAME", + "SECOND_HALF", + "ZIP_CODE", + "PLUS_4", + "FIND_STREET" + }; +#endif + // strategy: US only support at first + // look for a 1 - 5 digit number for a street number (no support for 'One Microsoft Way') + // ignore if preceded by '#', Suite, Ste, Rm + // look for two or more words (up to 5? North Frank Lloyd Wright Blvd) + // note: "The Circle at North Hills St." has six words, and a lower 'at' -- allow at, by, of, in, the, and, ... ? + // if a word starts with a lowercase letter, no match + // allow: , . - # / (for 1/2) ' " + // don't look for street name type yet + // look for one or two delimiters to represent possible 2nd addr line and city name + // look for either full state name, or state two letters, and/or zip code (5 or 9 digits) + // now look for street suffix, either in full or abbreviated form, with optional 's' if there's an asterisk + + s->mCurrentStart = chars; + s->mEnd = chars + length; + int candIndex = 0; + bool retryState; + bool mustBeAllUpper = false; + bool secondHalf = false; + chars -= 1; + UChar ch = s->mCurrent; + while (++chars <= s->mEnd) { + UChar prior = ch; + ch = chars < s->mEnd ? *chars : ' '; + switch (s->mProgress) { + case NO_ADDRESS: + if (WTF::isASCIIDigit(ch) == false) { + if (ch != 'O') // letter 'O', not zero + continue; + if (s->mEnd - chars < 3) + continue; + prior = *++chars; + ch = *++chars; + if ((prior != 'n' || ch != 'e') && (prior != 'N' || ch != 'E')) + continue; + if (isUnicodeSpace(*++chars) == false) + continue; + s->mProgress = ADDRESS_LINE; + s->mStartResult = chars - 3 - s->mCurrentStart; + continue; + } + if (isUnicodeSpace(prior) == false) { + s->mProgress = SKIP_TO_SPACE; + continue; + } + s->mNumberCount = 1; + s->mProgress = HOUSE_NUMBER; + s->mStartResult = chars - s->mCurrentStart; + continue; + case SKIP_TO_SPACE: + if (isUnicodeSpace(ch) == false) + continue; + break; + case HOUSE_NUMBER: + if (WTF::isASCIIDigit(ch)) { + if (++s->mNumberCount >= 6) + s->mProgress = SKIP_TO_SPACE; + continue; + } + if (WTF::isASCIIUpper(ch)) { // allow one letter after house number, e.g. 12A SKOLFIELD PL, HARPSWELL, ME 04079 + if (WTF::isASCIIDigit(prior) == false) + s->mProgress = SKIP_TO_SPACE; + continue; + } + if (ch == '-') { + if (s->mNumberCount > 0) { // permit 21-23 ELM ST + ++s->mNumberCount; + continue; + } + } + s->mNumberCount = 0; + s->mProgress = NUMBER_TRAILING_SPACE; + case NUMBER_TRAILING_SPACE: + if (isUnicodeSpace(ch)) + continue; + if (0 && WTF::isASCIIDigit(ch)) { + s->mNumberCount = 1; + s->mProgress = HOUSE_NUMBER; + s->mStartResult = chars - s->mCurrentStart; + continue; + } + if (WTF::isASCIIDigit(ch) == false && WTF::isASCIIUpper(ch) == false) + break; + s->mProgress = ADDRESS_LINE; + case ADDRESS_LINE: + if (WTF::isASCIIAlpha(ch) || ch == '\'' || ch == '-' || ch == '&' || ch == '(' || ch == ')') { + if (++s->mLetterCount > 1) { + s->mNumberWords &= ~(1 << s->mWordCount); + continue; + } + if (s->mNumberCount >= 5) + break; +// FIXME: the test below was added to give up on a non-address, but it +// incorrectly discards addresses where the house number is in one node +// and the street name is in the next; I don't recall what the failing case +// is that suggested this fix. +// if (s->mWordCount == 0 && s->mContinuationNode) +// return FOUND_NONE; + s->newWord(baseChars, chars); + if (WTF::isASCIILower(ch) && s->mNumberCount == 0) + s->mFirstLower = chars; + s->mNumberCount = 0; + if (WTF::isASCIILower(ch) || (WTF::isASCIIAlpha(ch) == false && ch != '-')) + s->mNumberWords &= ~(1 << s->mWordCount); + s->mUnparsed = true; + continue; + } else if (s->mLetterCount >= MAX_PLACE_NAME_LENGTH) { + break; + } else if (s->mFirstLower != NULL) { + if (s->mCaseInsensitive) + goto resetWord; + size_t length = chars - s->mFirstLower; + if (length > 3) + break; + if (length == 3 && strCharCmp("and" "the", s->mFirstLower, 3, 2) == false) + break; + if (length == 2 && strCharCmp("at" "by" "el" "in" "of", s->mFirstLower, 2, 5) == false) + break; + goto resetWord; + } + if (ch == ',' || ch == '*') { // delimits lines + // asterisk as delimiter: http://www.sa.sc.edu/wellness/members.html + if (++s->mLineCount > 5) + break; + goto lookForState; + } + if (isUnicodeSpace(ch) || prior == '-') { + lookForState: + if (s->mUnparsed == false) + continue; + const UChar* candidate = s->mWords[s->mWordCount]; + UChar firstLetter = candidate[0]; + if (WTF::isASCIIUpper(firstLetter) == false && WTF::isASCIIDigit(firstLetter) == false) + goto resetWord; + s->mWordCount++; + if ((s->mWordCount == 2 && s->mNumberWords == 3 && WTF::isASCIIDigit(s->mWords[1][1])) || // choose second of 8888 333 Main + (s->mWordCount >= sizeof(s->mWords) / sizeof(s->mWords[0]) - 1)) { // subtract 1 since state names may have two parts + // search for simple number already stored since first potential house # didn't pan out + if (s->mNumberWords == 0) + break; + int shift = 0; + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + s->mNumberWords >>= ++shift; + if (s->mBases[0] != s->mBases[shift]) // if we're past the original node, bail + break; + s->shiftWords(shift); + s->mStartResult = s->mWords[0] - s->mStarts[0]; + s->mWordCount -= shift; + // FIXME: need to adjust lineCount to account for discarded delimiters + } + if (s->mWordCount < 4) + goto resetWord; + firstLetter -= 'A'; + if (firstLetter > 'W' - 'A') + goto resetWord; + UChar secondLetter = candidate[1]; + if (prior == '-') + s->mLetterCount--; // trim trailing dashes, to accept CA-94043 + if (s->mLetterCount == 2) { + secondLetter -= 'A'; + if (secondLetter > 'Z' - 'A') + goto resetWord; + if ((stateTwoLetter[firstLetter] & 1 << secondLetter) != 0) { + // special case to ignore 'et al' + if (strCharCmp("ET", s->mWords[s->mWordCount - 2], 2, 1) == false) { + s->mStateWord = s->mWordCount - 1; + s->mZipHint = firstIndex[firstLetter] + + bitcount(stateTwoLetter[firstLetter] & ((1 << secondLetter) - 1)); + goto foundStateName; + } + } + goto resetWord; + } + s->mStates = longStateNames[firstLetter]; + if (s->mStates == NULL) + goto resetWord; + mustBeAllUpper = false; + s->mProgress = STATE_NAME; + unsigned char section = s->mStates[0]; + ASSERT(section > 0x80); + s->mSectionLength = section & 0x7f; + candIndex = 1; + secondHalf = false; + s->mStateWord = s->mWordCount - 1; + goto stateName; + } + if (WTF::isASCIIDigit(ch)) { + if (s->mLetterCount == 0) { + if (++s->mNumberCount > 1) + continue; + if (s->mWordCount == 0 && s->mContinuationNode) + return FOUND_NONE; + s->newWord(baseChars, chars); + s->mNumberWords |= 1 << s->mWordCount; + s->mUnparsed = true; + } + continue; + } + if (ch == '.') { // optionally can follow letters + if (s->mLetterCount == 0) + break; + if (s->mNumberCount > 0) + break; + continue; + } + if (ch == '/') // between numbers (1/2) between words (12 Main / Ste 4d) + goto resetWord; + if (ch == '#') // can precede numbers, allow it to appear randomly + goto resetWord; + if (ch == '"') // sometimes parts of addresses are quoted (FIXME: cite an example here) + continue; + break; + case SECOND_HALF: + if (WTF::isASCIIAlpha(ch)) { + if (s->mLetterCount == 0) { + s->newWord(baseChars, chars); + s->mWordCount++; + } + s->mLetterCount++; + continue; + } + if (WTF::isASCIIDigit(ch) == false) { + if (s->mLetterCount > 0) { + s->mProgress = STATE_NAME; + candIndex = 0; + secondHalf = true; + goto stateName; + } + continue; + } + s->mProgress = ADDRESS_LINE; + goto resetState; + case STATE_NAME: + stateName: + // pick up length of first section + do { + int stateIndex = 1; + int skip = 0; + int prefix = 0; + bool subStr = false; + do { + unsigned char match = s->mStates[stateIndex]; + if (match >= 0x80) { + if (stateIndex == s->mSectionLength) + break; + subStr = true; + // if (skip > 0) + // goto foundStateName; + prefix = candIndex; + skip = match & 0x7f; + match = s->mStates[++stateIndex]; + } + UChar candChar = s->mWords[s->mWordCount - 1][candIndex]; + if (mustBeAllUpper && WTF::isASCIILower(candChar)) + goto skipToNext; + if (match != candChar) { + if (match != WTF::toASCIILower(candChar)) { + skipToNext: + if (subStr == false) + break; + if (stateIndex == s->mSectionLength) { + if (secondHalf) { + s->mProgress = ADDRESS_LINE; + goto resetState; + } + break; + } + stateIndex += skip; + skip = 0; + candIndex = prefix; + continue; // try next substring + } + mustBeAllUpper = true; + } + int nextindex = stateIndex + 1; + if (++candIndex >= s->mLetterCount && s->mStates[nextindex] == ' ') { + s->mProgress = SECOND_HALF; + s->mStates += nextindex; + s->mSectionLength -= nextindex; + goto resetWord; + } + if (nextindex + 1 == s->mSectionLength || skip == 2) { + s->mZipHint = s->mStates[nextindex] - 1; + goto foundStateName; + } + stateIndex += 1; + skip -= 1; + } while (true); + s->mStates += s->mSectionLength; + ASSERT(s->mStates[0] == 0 || (unsigned) s->mStates[0] > 0x80); + s->mSectionLength = s->mStates[0] & 0x7f; + candIndex = 1; + subStr = false; + } while (s->mSectionLength != 0); + s->mProgress = ADDRESS_LINE; + goto resetState; + foundStateName: + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + s->mProgress = ZIP_CODE; + // a couple of delimiters is an indication that the state name is good + // or, a non-space / non-alpha-digit is also good + s->mZipDelimiter = s->mLineCount > 2 + || isUnicodeSpace(ch) == false + || chars == s->mEnd; + if (WTF::isASCIIDigit(ch)) + s->mZipStart = chars; + goto resetState; + case ZIP_CODE: + if (WTF::isASCIIDigit(ch)) { + int count = ++s->mNumberCount; + if (count == 1) { + if (WTF::isASCIIDigit(prior)) + ++s->mNumberCount; + else + s->mZipStart = chars; + } + if (count <= 9) + continue; + } else if (isUnicodeSpace(ch)) { + if (s->mNumberCount == 0) { + s->mZipDelimiter = true; // two spaces delimit state name + continue; + } + } else if (ch == '-') { + if (s->mNumberCount == 5 && validZip(s->mZipHint, s->mZipStart)) { + s->mNumberCount = 0; + s->mProgress = PLUS_4; + continue; + } + if (s->mNumberCount == 0) + s->mZipDelimiter = true; + } else if (WTF::isASCIIAlpha(ch) == false) + s->mZipDelimiter = true; + else { + if (s->mLetterCount == 0) { + s->newWord(baseChars, chars); + s->mUnparsed = true; + } + ++s->mLetterCount; + } + if (s->mNumberCount == 5 || s->mNumberCount == 9) { + if (validZip(s->mZipHint, s->mZipStart) == false) + goto noZipMatch; + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + } else if (s->mZipDelimiter == false) { + noZipMatch: + --chars; + s->mProgress = ADDRESS_LINE; + continue; + } + s->mProgress = FIND_STREET; + goto findStreet; + case PLUS_4: + if (WTF::isASCIIDigit(ch)) { + if (++s->mNumberCount <= 4) + continue; + } + if (isUnicodeSpace(ch)) { + if (s->mNumberCount == 0) + continue; + } + if (s->mNumberCount == 4) { + if (WTF::isASCIIAlpha(ch) == false) { + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + } + } else if (s->mNumberCount != 0) + break; + s->mProgress = FIND_STREET; + case FIND_STREET: + findStreet: + retryState = false; + for (int wordsIndex = s->mStateWord - 1; wordsIndex >= 0; --wordsIndex) { + const UChar* test = s->mWords[wordsIndex]; + UChar letter = test[0]; + letter -= 'A'; + if (letter > 'X' - 'A') + continue; + const char* names = longStreetNames[letter]; + if (names == NULL) + continue; + int offset; + while ((offset = *names++) != 0) { + int testIndex = 1; + bool abbr = false; + for (int idx = 0; idx < offset; idx++) { + char nameLetter = names[idx]; + char testUpper = WTF::toASCIIUpper(test[testIndex]); + if (nameLetter == '*') { + if (testUpper == 'S') + testIndex++; + break; + } + bool fullOnly = WTF::isASCIILower(nameLetter); + nameLetter = WTF::toASCIIUpper(nameLetter); + if (testUpper == nameLetter) { + if (abbr && fullOnly) + goto nextTest; + testIndex++; + continue; + } + if (fullOnly == false) + goto nextTest; + abbr = true; + } + letter = &test[testIndex] < s->mEnds[wordsIndex] ? + test[testIndex] : ' '; + if (WTF::isASCIIAlpha(letter) == false && WTF::isASCIIDigit(letter) == false) { + if (s->mNumberWords != 0) { + int shift = 0; + int wordReduction = -1; + do { + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + if (shift > wordsIndex) + break; + wordReduction = shift; + } while (s->mNumberWords >> ++shift != 0); + if (wordReduction >= 0) { + if (s->mContinuationNode) { + if (retryState) + break; + return FOUND_NONE; + } + s->mStartResult = s->mWords[wordReduction] - s->mStarts[wordReduction]; + } + } + if (wordsIndex != s->mStateWord - 1) + return FOUND_COMPLETE; + retryState = true; + } + nextTest: + names += offset; + } + } + if (retryState) { + s->mProgress = ADDRESS_LINE; + s->mStates = NULL; + continue; + } + if (s->mNumberWords != 0) { + unsigned shift = 0; + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + s->mNumberWords >>= ++shift; + if (s->mBases[0] != s->mBases[shift]) + return FOUND_NONE; + s->shiftWords(shift); + s->mStartResult = s->mWords[0] - s->mStarts[0]; + s->mWordCount -= shift; + s->mProgress = ADDRESS_LINE; + --chars; + continue; + } + break; + } + if (s->mContinuationNode) + return FOUND_NONE; + s->mProgress = NO_ADDRESS; + s->mWordCount = s->mLineCount = 0; + s->mNumberWords = 0; + resetState: + s->mStates = NULL; + resetWord: + s->mNumberCount = s->mLetterCount = 0; + s->mFirstLower = NULL; + s->mUnparsed = false; + } + s->mCurrent = ch; + return s->mProgress == NO_ADDRESS ? FOUND_NONE : FOUND_PARTIAL; +} + +// Recogize common email patterns only. Currently has lots of state, walks text forwards and backwards -- will be +// a real challenge to adapt to walk text across multiple nodes, I imagine +// FIXME: it's too hard for the caller to call these incrementally -- it's probably best for this to +// either walk the node tree directly or make a callout to get the next or previous node, if there is one +// walking directly will avoid adding logic in caller to track the multiple partial or full nodes that compose this +// text pattern. +CacheBuilder::FoundState CacheBuilder::FindPartialEMail(const UChar* chars, unsigned length, + FindState* s) +{ + // the following tables were generated by tests/browser/focusNavigation/BrowserDebug.cpp + // hand-edit at your own risk + static const int domainTwoLetter[] = { + 0x02df797c, // a followed by: [cdefgilmnoqrstuwxz] + 0x036e73fb, // b followed by: [abdefghijmnorstvwyz] + 0x03b67ded, // c followed by: [acdfghiklmnorsuvxyz] + 0x02005610, // d followed by: [ejkmoz] + 0x001e00d4, // e followed by: [ceghrstu] + 0x00025700, // f followed by: [ijkmor] + 0x015fb9fb, // g followed by: [abdefghilmnpqrstuwy] + 0x001a3400, // h followed by: [kmnrtu] + 0x000f7818, // i followed by: [delmnoqrst] + 0x0000d010, // j followed by: [emop] + 0x0342b1d0, // k followed by: [eghimnprwyz] + 0x013e0507, // l followed by: [abcikrstuvy] + 0x03fffccd, // m followed by: [acdghklmnopqrstuvwxyz] + 0x0212c975, // n followed by: [acefgilopruz] + 0x00001000, // o followed by: [m] + 0x014e3cf1, // p followed by: [aefghklmnrstwy] + 0x00000001, // q followed by: [a] + 0x00504010, // r followed by: [eouw] + 0x032a7fdf, // s followed by: [abcdeghijklmnortvyz] + 0x026afeec, // t followed by: [cdfghjklmnoprtvwz] + 0x03041441, // u followed by: [agkmsyz] + 0x00102155, // v followed by: [aceginu] + 0x00040020, // w followed by: [fs] + 0x00000000, // x + 0x00180010, // y followed by: [etu] + 0x00401001, // z followed by: [amw] + }; + + static char const* const longDomainNames[] = { + "\x03" "ero" "\x03" "rpa", // aero, arpa + "\x02" "iz", // biz + "\x02" "at" "\x02" "om" "\x03" "oop", // cat, com, coop + NULL, // d + "\x02" "du", // edu + NULL, // f + "\x02" "ov", // gov + NULL, // h + "\x03" "nfo" "\x02" "nt", // info, int + "\x03" "obs", // jobs + NULL, // k + NULL, // l + "\x02" "il" "\x03" "obi" "\x05" "useum", // mil, mobi, museum + "\x03" "ame" "\x02" "et", // name, net + "\x02" "rg", // , org + "\x02" "ro", // pro + NULL, // q + NULL, // r + NULL, // s + "\x05" "ravel", // travel + NULL, // u + NULL, // v + NULL, // w + NULL, // x + NULL, // y + NULL, // z + }; + + const UChar* start = chars; + const UChar* end = chars + length; + while (chars < end) { + UChar ch = *chars++; + if (ch != '@') + continue; + const UChar* atLocation = chars - 1; + // search for domain + ch = *chars++ | 0x20; // convert uppercase to lower + if (ch < 'a' || ch > 'z') + continue; + while (chars < end) { + ch = *chars++; + if (IsDomainChar(ch) == false) + goto nextAt; + if (ch != '.') + continue; + UChar firstLetter = *chars++ | 0x20; // first letter of the domain + if (chars >= end) + return FOUND_NONE; // only one letter; must be at least two + firstLetter -= 'a'; + if (firstLetter > 'z' - 'a') + continue; // non-letter followed '.' + int secondLetterMask = domainTwoLetter[firstLetter]; + ch = *chars | 0x20; // second letter of the domain + ch -= 'a'; + if (ch >= 'z' - 'a') + continue; + bool secondMatch = (secondLetterMask & 1 << ch) != 0; + const char* wordMatch = longDomainNames[firstLetter]; + int wordIndex = 0; + while (wordMatch != NULL) { + int len = *wordMatch++; + char match; + do { + match = wordMatch[wordIndex]; + if (match < 0x20) + goto foundDomainStart; + if (chars[wordIndex] != match) + break; + wordIndex++; + } while (true); + wordMatch += len; + if (*wordMatch == '\0') + break; + wordIndex = 0; + } + if (secondMatch) { + wordIndex = 1; + foundDomainStart: + chars += wordIndex; + if (chars < end) { + ch = *chars; + if (ch != '.') { + if (IsDomainChar(ch)) + goto nextDot; + } else if (chars + 1 < end && IsDomainChar(chars[1])) + goto nextDot; + } + // found domain. Search backwards from '@' for beginning of email address + s->mEndResult = chars - start; + chars = atLocation; + if (chars <= start) + goto nextAt; + ch = *--chars; + if (ch == '.') + goto nextAt; // mailbox can't end in period + do { + if (IsMailboxChar(ch) == false) { + chars++; + break; + } + if (chars == start) + break; + ch = *--chars; + } while (true); + UChar firstChar = *chars; + if (firstChar == '.' || firstChar == '@') // mailbox can't start with period or be empty + goto nextAt; + s->mStartResult = chars - start; + return FOUND_COMPLETE; + } + nextDot: + ; + } +nextAt: + chars = atLocation + 1; + } + return FOUND_NONE; +} + +#define PHONE_PATTERN "(200) /-.\\ 100 -. 0000" // poor man's regex: parens optional, any one of punct, digit smallest allowed + +CacheBuilder::FoundState CacheBuilder::FindPartialNumber(const UChar* chars, unsigned length, + FindState* s) +{ + char* pattern = s->mPattern; + UChar* store = s->mStorePtr; + const UChar* start = chars; + const UChar* end = chars + length; + const UChar* lastDigit = NULL; + do { + bool initialized = s->mInitialized; + while (chars < end) { + if (initialized == false) { + s->mBackTwo = s->mBackOne; + s->mBackOne = s->mCurrent; + } + UChar ch = s->mCurrent = *chars; + do { + char patternChar = *pattern; + switch (patternChar) { + case '2': + if (initialized == false) { + s->mStartResult = chars - start; + initialized = true; + } + case '0': + case '1': + if (ch < patternChar || ch > '9') + goto resetPattern; + *store++ = ch; + pattern++; + lastDigit = chars; + goto nextChar; + case '\0': + if (WTF::isASCIIDigit(ch) == false) { + *store = '\0'; + goto checkMatch; + } + goto resetPattern; + case ' ': + if (ch == patternChar) + goto nextChar; + break; + case '(': + if (ch == patternChar) { + s->mStartResult = chars - start; + initialized = true; + s->mOpenParen = true; + } + goto commonPunctuation; + case ')': + if ((ch == patternChar) ^ s->mOpenParen) + goto resetPattern; + default: + commonPunctuation: + if (ch == patternChar) { + pattern++; + goto nextChar; + } + } + } while (++pattern); // never false + nextChar: + chars++; + } + break; +resetPattern: + if (s->mContinuationNode) + return FOUND_NONE; + FindResetNumber(s); + pattern = s->mPattern; + store = s->mStorePtr; + } while (++chars < end); +checkMatch: + if (WTF::isASCIIDigit(s->mBackOne != '1' ? s->mBackOne : s->mBackTwo)) + return FOUND_NONE; + *store = '\0'; + s->mStorePtr = store; + s->mPattern = pattern; + s->mEndResult = lastDigit - start + 1; + char pState = pattern[0]; + return pState == '\0' ? FOUND_COMPLETE : pState == '(' || (WTF::isASCIIDigit(pState) && WTF::isASCIIDigit(pattern[-1])) ? + FOUND_NONE : FOUND_PARTIAL; +} + +CacheBuilder::FoundState CacheBuilder::FindPhoneNumber(const UChar* chars, unsigned length, + int* start, int* end) +{ + FindState state; + FindReset(&state); + FoundState result = FindPartialNumber(chars, length, &state); + *start = state.mStartResult; + *end = state.mEndResult; + return result; +} + +void CacheBuilder::FindReset(FindState* state) +{ + memset(state, 0, sizeof(FindState)); + state->mCurrent = ' '; + FindResetNumber(state); +} + +void CacheBuilder::FindResetNumber(FindState* state) +{ + state->mOpenParen = false; + state->mPattern = (char*) PHONE_PATTERN; + state->mStorePtr = state->mStore; +} + +IntRect CacheBuilder::getAreaRect(const HTMLAreaElement* area) +{ + Node* node = area->document(); + while ((node = node->traverseNextNode()) != NULL) { + RenderObject* renderer = node->renderer(); + if (renderer && renderer->isRenderImage()) { + RenderImage* image = static_cast<RenderImage*>(renderer); + HTMLMapElement* map = image->imageMap(); + if (map) { + Node* n; + for (n = map->firstChild(); n; + n = n->traverseNextNode(map)) { + if (n == area) { + if (area->isDefault()) + return image->absoluteBoundingBoxRect(); + return area->computeRect(image); + } + } + } + } + } + return IntRect(); +} + +void CacheBuilder::GetGlobalOffset(Node* node, int* x, int * y) +{ + GetGlobalOffset(node->document()->frame(), x, y); +} + +void CacheBuilder::GetGlobalOffset(Frame* frame, int* x, int* y) +{ +// TIMER_PROBE(__FUNCTION__); + ASSERT(x); + ASSERT(y); + *x = 0; + *y = 0; + if (!frame->view()) + return; + Frame* parent; + while ((parent = frame->tree()->parent()) != NULL) { + const WebCore::IntRect& rect = frame->view()->platformWidget()->getBounds(); + *x += rect.x(); + *y += rect.y(); + frame = parent; + } + // TIMER_PROBE_END(); +} + +Frame* CacheBuilder::HasFrame(Node* node) +{ + RenderObject* renderer = node->renderer(); + if (renderer == NULL) + return NULL; + if (renderer->isWidget() == false) + return NULL; + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (widget == NULL) + return NULL; + if (widget->isFrameView() == false) + return NULL; + return static_cast<FrameView*>(widget)->frame(); +} + +bool CacheBuilder::HasOverOrOut(Node* node) +{ + // eventNames are thread-local data, I avoid using 'static' variable here. + AtomicString eventTypes[2] = { + eventNames().mouseoverEvent, + eventNames().mouseoutEvent + }; + + return NodeHasEventListeners(node, eventTypes, 2); +} + +bool CacheBuilder::HasTriggerEvent(Node* node) +{ + AtomicString eventTypes[5] = { + eventNames().clickEvent, + eventNames().mousedownEvent, + eventNames().mouseupEvent, + eventNames().keydownEvent, + eventNames().keyupEvent + }; + + return NodeHasEventListeners(node, eventTypes, 5); +} + +// #define EMAIL_PATTERN "x@y.d" // where 'x' is letters, numbers, and '-', '.', '_' ; 'y' is 'x' without the underscore, and 'd' is a valid domain +// - 0x2D . 0x2E 0-9 0x30-39 A-Z 0x41-5A _ 0x5F a-z 0x61-7A + +bool CacheBuilder::IsDomainChar(UChar ch) +{ + static const unsigned body[] = {0x03ff6000, 0x07fffffe, 0x07fffffe}; // 0-9 . - A-Z a-z + ch -= 0x20; + if (ch > 'z' - 0x20) + return false; + return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; +} + +bool CacheBuilder::isFocusableText(NodeWalk* walk, bool more, Node* node, + CachedNodeType* type, String* exported) const +{ + Text* textNode = static_cast<Text*>(node); + StringImpl* string = textNode->dataImpl(); + const UChar* baseChars = string->characters(); +// const UChar* originalBase = baseChars; + int length = string->length(); + int index = 0; + while (index < length && isUnicodeSpace(baseChars[index])) + index++; + if (index >= length) + return false; + if (more == false) { + walk->mStart = 0; + walk->mEnd = 0; + walk->mFinalNode = node; + walk->mLastInline = NULL; + } + // starting with this node, search forward for email, phone number, and address + // if any of the three is found, track it so that the remaining can be looked for later + FoundState state = FOUND_NONE; + RenderText* renderer = (RenderText*) node->renderer(); + bool foundBetter = false; + InlineTextBox* baseInline = walk->mLastInline != NULL ? walk->mLastInline : + renderer->firstTextBox(); + if (baseInline == NULL) + return false; + int start = walk->mEnd; + InlineTextBox* saveInline; + int baseStart, firstStart = start; + saveInline = baseInline; + baseStart = start; + for (CachedNodeType checkType = ADDRESS_CACHEDNODETYPE; + checkType <= PHONE_CACHEDNODETYPE; + checkType = static_cast<CachedNodeType>(checkType + 1)) + { + if ((1 << (checkType - 1) & mAllowableTypes) == 0) + continue; + InlineTextBox* inlineTextBox = baseInline; + FindState findState; + FindReset(&findState); + start = baseStart; + if (checkType == ADDRESS_CACHEDNODETYPE) { + findState.mBases[0] = baseChars; + findState.mWords[0] = baseChars + start; + findState.mStarts[0] = baseChars + start; + } + Node* lastPartialNode = NULL; + int lastPartialEnd = -1; + bool lastPartialMore = false; + bool firstPartial = true; + InlineTextBox* lastPartialInline = NULL; + do { + do { + const UChar* chars = baseChars + start; + length = inlineTextBox == NULL ? 0 : + inlineTextBox->end() - start + 1; + bool wasInitialized = findState.mInitialized; + switch (checkType) { + case ADDRESS_CACHEDNODETYPE: + state = FindPartialAddress(baseChars, chars, length, &findState); + break; + case EMAIL_CACHEDNODETYPE: + state = FindPartialEMail(chars, length, &findState); + break; + case PHONE_CACHEDNODETYPE: + state = FindPartialNumber(chars, length, &findState); + break; + default: + ASSERT(0); + } + findState.mInitialized = state != FOUND_NONE; + if (wasInitialized != findState.mInitialized) + firstStart = start; + if (state == FOUND_PARTIAL) { + lastPartialNode = node; + lastPartialEnd = findState.mEndResult + start; + lastPartialMore = firstPartial && + lastPartialEnd < (int) string->length(); + firstPartial = false; + lastPartialInline = inlineTextBox; + findState.mContinuationNode = true; + } else if (state == FOUND_COMPLETE) { + if (foundBetter == false || walk->mStart > findState.mStartResult) { + walk->mStart = findState.mStartResult + firstStart; + if (findState.mEndResult > 0) { + walk->mFinalNode = node; + walk->mEnd = findState.mEndResult + start; + walk->mMore = node == textNode && + walk->mEnd < (int) string->length(); + walk->mLastInline = inlineTextBox; + } else { + walk->mFinalNode = lastPartialNode; + walk->mEnd = lastPartialEnd; + walk->mMore = lastPartialMore; + walk->mLastInline = lastPartialInline; + } + *type = checkType; + if (checkType == PHONE_CACHEDNODETYPE) { + const UChar* store = findState.mStore; + *exported = String(store); + } else { + Node* temp = textNode; + length = 1; + start = walk->mStart; + exported->truncate(0); + do { + Text* tempText = static_cast<Text*>(temp); + StringImpl* string = tempText->dataImpl(); + int end = tempText == walk->mFinalNode ? + walk->mEnd : string->length(); + exported->append(String(string->substring( + start, end - start))); + ASSERT(end > start); + length += end - start + 1; + if (temp == walk->mFinalNode) + break; + start = 0; + do { + temp = temp->traverseNextNode(); + ASSERT(temp); + } while (temp->isTextNode() == false); + // add a space in between text nodes to avoid + // words collapsing together + exported->append(" "); + } while (true); + } + foundBetter = true; + } + goto tryNextCheckType; + } else if (findState.mContinuationNode) + break; + if (inlineTextBox == NULL) + break; + inlineTextBox = inlineTextBox->nextTextBox(); + if (inlineTextBox == NULL) + break; + start = inlineTextBox->start(); + if (state == FOUND_PARTIAL && node == textNode) + findState.mContinuationNode = false; + } while (true); + if (state == FOUND_NONE) + break; + // search for next text node, if any + Text* nextNode; + do { + do { + do { + if (node) + node = node->traverseNextNode(); + if (node == NULL || node->hasTagName(HTMLNames::aTag) + || node->hasTagName(HTMLNames::inputTag) + || node->hasTagName(HTMLNames::textareaTag)) { + if (state == FOUND_PARTIAL && + checkType == ADDRESS_CACHEDNODETYPE && + findState.mProgress == ZIP_CODE && + findState.mNumberCount == 0) { + baseChars = NULL; + inlineTextBox = NULL; + start = 0; + findState.mProgress = FIND_STREET; + goto finalNode; + } + goto tryNextCheckType; + } + } while (node->isTextNode() == false); + nextNode = static_cast<Text*>(node); + renderer = (RenderText*) nextNode->renderer(); + } while (renderer == NULL); + baseInline = renderer->firstTextBox(); + } while (baseInline == NULL); + string = nextNode->dataImpl(); + baseChars = string->characters(); + inlineTextBox = baseInline; + start = inlineTextBox->start(); + finalNode: + findState.mEndResult = 0; + } while (true); +tryNextCheckType: + node = textNode; + baseInline = saveInline; + string = textNode->dataImpl(); + baseChars = string->characters(); + } + if (foundBetter) { + CachedNodeType temp = *type; + switch (temp) { + case ADDRESS_CACHEDNODETYPE: { + static const char geoString[] = "geo:0,0?q="; + exported->insert(String(geoString), 0); + int index = sizeof(geoString) - 1; + String escapedComma("%2C"); + while ((index = exported->find(',', index)) >= 0) + exported->replace(index, 1, escapedComma); + } break; + case EMAIL_CACHEDNODETYPE: { + String encoded = WebCore::encodeWithURLEscapeSequences(*exported); + exported->swap(encoded); + exported->insert(WTF::String("mailto:"), 0); + } break; + case PHONE_CACHEDNODETYPE: + exported->insert(WTF::String("tel:"), 0); + break; + default: + break; + } + return true; + } +noTextMatch: + walk->reset(); + return false; +} + +bool CacheBuilder::IsMailboxChar(UChar ch) +{ + // According to http://en.wikipedia.org/wiki/Email_address + // ! # $ % & ' * + - . / 0-9 = ? + // A-Z ^ _ + // ` a-z { | } ~ + static const unsigned body[] = {0xa3ffecfa, 0xc7fffffe, 0x7fffffff}; + ch -= 0x20; + if (ch > '~' - 0x20) + return false; + return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; +} + +bool CacheBuilder::setData(CachedFrame* cachedFrame) +{ + Frame* frame = FrameAnd(this); + Document* doc = frame->document(); + if (doc == NULL) + return false; + RenderObject* renderer = doc->renderer(); + if (renderer == NULL) + return false; + RenderLayer* layer = renderer->enclosingLayer(); + if (layer == NULL) + return false; + if (!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->scrollWidth(), layer->scrollHeight()); + if (cachedFrame->childCount() == 0) + return true; + CachedFrame* lastCachedFrame = cachedFrame->lastChild(); + cachedFrame = cachedFrame->firstChild(); + do { + CacheBuilder* cacheBuilder = Builder((Frame* )cachedFrame->framePointer()); + cacheBuilder->setData(cachedFrame); + } while (cachedFrame++ != lastCachedFrame); + return true; +} + +#if USE(ACCELERATED_COMPOSITING) +void CacheBuilder::TrackLayer(WTF::Vector<LayerTracker>& layerTracker, + RenderObject* nodeRenderer, Node* lastChild, int offsetX, int offsetY) +{ + RenderLayer* layer = nodeRenderer->enclosingLayer(); + RenderLayerBacking* back = layer->backing(); + if (!back) + return; + GraphicsLayer* grLayer = back->graphicsLayer(); + if (back->hasContentsLayer()) + grLayer = back->foregroundLayer(); + if (!grLayer) + return; + LayerAndroid* aLayer = grLayer->platformLayer(); + if (!aLayer) + return; + IntPoint scroll(layer->scrollXOffset(), layer->scrollYOffset()); +#if ENABLE(ANDROID_OVERFLOW_SCROLL) + // If this is an overflow element, track the content layer. + if (layer->hasOverflowScroll() && aLayer->getChild(0)) + aLayer = aLayer->getChild(0)->getChild(0); + if (!aLayer) + return; + // Prevent a crash when scrolling a layer that does not have a parent. + if (layer->stackingContext()) + layer->scrollToOffset(0, 0); +#endif + layerTracker.grow(layerTracker.size() + 1); + LayerTracker& indexTracker = layerTracker.last(); + indexTracker.mLayer = aLayer; + indexTracker.mRenderLayer = layer; + indexTracker.mBounds = enclosingIntRect(aLayer->bounds()); + // Use the absolute location of the layer as the bounds location. This + // provides the original offset of nodes in the layer so that we can + // translate nodes between their original location and the layer's new + // location. + indexTracker.mBounds.setLocation(layer->absoluteBoundingBox().location()); + indexTracker.mBounds.move(offsetX, offsetY); + indexTracker.mScroll = scroll; + indexTracker.mLastChild = OneAfter(lastChild); + DBG_NAV_LOGD("layer=%p [%d] bounds=(%d,%d,w=%d,h=%d)", aLayer, + aLayer->uniqueId(), indexTracker.mBounds.x(), indexTracker.mBounds.y(), + indexTracker.mBounds.width(), indexTracker.mBounds.height()); +} +#endif + +bool CacheBuilder::validNode(Frame* startFrame, void* matchFrame, + void* matchNode) +{ + if (matchFrame == startFrame) { + if (matchNode == NULL) + return true; + Node* node = startFrame->document(); + while (node != NULL) { + if (node == matchNode) { + const IntRect& rect = node->hasTagName(HTMLNames::areaTag) ? + getAreaRect(static_cast<HTMLAreaElement*>(node)) : node->getRect(); + // Consider nodes with empty rects that are not at the origin + // to be valid, since news.google.com has valid nodes like this + if (rect.x() == 0 && rect.y() == 0 && rect.isEmpty()) + return false; + return true; + } + node = node->traverseNextNode(); + } + DBG_NAV_LOGD("frame=%p valid node=%p invalid\n", matchFrame, matchNode); + return false; + } + Frame* child = startFrame->tree()->firstChild(); + while (child) { + bool result = validNode(child, matchFrame, matchNode); + if (result) + return result; + child = child->tree()->nextSibling(); + } +#if DEBUG_NAV_UI + if (startFrame->tree()->parent() == NULL) + DBG_NAV_LOGD("frame=%p node=%p false\n", matchFrame, matchNode); +#endif + return false; +} + +static int Area(const IntRect& rect) +{ + return rect.width() * rect.height(); +} + +bool CacheBuilder::AddPartRect(IntRect& bounds, int x, int y, + WTF::Vector<IntRect>* result, IntRect* focusBounds) +{ + if (bounds.isEmpty()) + return true; + bounds.move(x, y); + if (bounds.maxX() <= 0 || bounds.maxY() <= 0) + return true; + IntRect* work = result->begin() - 1; + IntRect* end = result->end(); + while (++work < end) { + if (work->contains(bounds)) + return true; + if (bounds.contains(*work)) { + *work = bounds; + focusBounds->unite(bounds); + return true; + } + if ((bounds.x() != work->x() || bounds.width() != work->width()) && + (bounds.y() != work->y() || bounds.height() != work->height())) + continue; + IntRect test = *work; + test.unite(bounds); + if (Area(test) > Area(*work) + Area(bounds)) + continue; + *work = test; + focusBounds->unite(bounds); + return true; + } + if (result->size() >= MAXIMUM_FOCUS_RING_COUNT) + return false; + result->append(bounds); + if (focusBounds->isEmpty()) + *focusBounds = bounds; + else + focusBounds->unite(bounds); + return true; +} + +bool CacheBuilder::ConstructPartRects(Node* node, const IntRect& bounds, + IntRect* focusBounds, int x, int y, WTF::Vector<IntRect>* result, + int* imageCountPtr) +{ + WTF::Vector<ClipColumnTracker> clipTracker(1); + ClipColumnTracker* baseTracker = clipTracker.data(); // sentinel + bzero(baseTracker, sizeof(ClipColumnTracker)); + if (node->hasChildNodes() && node->hasTagName(HTMLNames::buttonTag) == false + && node->hasTagName(HTMLNames::selectTag) == false) { + // collect all text rects from first to last child + Node* test = node->firstChild(); + Node* last = NULL; + Node* prior = node; + while ((prior = prior->lastChild()) != NULL) + last = prior; + ASSERT(last != NULL); + bool nodeIsAnchor = node->hasTagName(HTMLNames::aTag); + do { + do { + const ClipColumnTracker* lastClip = &clipTracker.last(); + if (test != lastClip->mLastChild) + break; + clipTracker.removeLast(); + } while (true); + RenderObject* renderer = test->renderer(); + if (renderer == NULL) + continue; + EVisibility vis = renderer->style()->visibility(); + if (vis == HIDDEN) + continue; + bool hasClip = renderer->hasOverflowClip(); + size_t clipIndex = clipTracker.size(); + IntRect clipBounds = IntRect(0, 0, INT_MAX, INT_MAX); + if (hasClip || --clipIndex > 0) { + clipBounds = hasClip ? renderer->absoluteBoundingBoxRect() : + clipTracker.at(clipIndex).mBounds; // x, y fixup done by ConstructTextRect + } + if (test->isTextNode()) { + RenderText* renderText = (RenderText*) renderer; + InlineTextBox *textBox = renderText->firstTextBox(); + if (textBox == NULL) + continue; + if (ConstructTextRect((Text*) test, textBox, 0, INT_MAX, + x, y, focusBounds, clipBounds, result) == false) { + return false; + } + continue; + } + if (test->hasTagName(HTMLNames::imgTag)) { + IntRect bounds = test->getRect(); + bounds.intersect(clipBounds); + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + *imageCountPtr += 1; + continue; + } + if (hasClip == false) { + if (nodeIsAnchor && test->hasTagName(HTMLNames::divTag)) { + IntRect bounds = renderer->absoluteBoundingBoxRect(); // x, y fixup done by AddPartRect + RenderBox* renderBox = static_cast<RenderBox*>(renderer); + int left = bounds.x() + renderBox->paddingLeft() + renderBox->borderLeft(); + int top = bounds.y() + renderBox->paddingTop() + renderBox->borderTop(); + int right = bounds.maxX() - renderBox->paddingRight() - renderBox->borderRight(); + int bottom = bounds.maxY() - renderBox->paddingBottom() - renderBox->borderBottom(); + if (left >= right || top >= bottom) + continue; + bounds = IntRect(left, top, right - left, bottom - top); + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + } + continue; + } + Node* lastChild = test->lastChild(); + if (lastChild == NULL) + continue; + clipTracker.grow(clipTracker.size() + 1); + ClipColumnTracker& clip = clipTracker.last(); + clip.mBounds = renderer->absoluteBoundingBoxRect(); // x, y fixup done by ConstructTextRect + clip.mLastChild = OneAfter(lastChild); + clip.mNode = test; + } while (test != last && (test = test->traverseNextNode()) != NULL); + } + if (result->size() == 0 || focusBounds->width() < MINIMUM_FOCUSABLE_WIDTH + || focusBounds->height() < MINIMUM_FOCUSABLE_HEIGHT) { + if (bounds.width() < MINIMUM_FOCUSABLE_WIDTH) + return false; + if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) + return false; + result->append(bounds); + *focusBounds = bounds; + } + return true; +} + +static inline bool isNotSpace(UChar c) +{ + return c <= 0xA0 ? isUnicodeSpace(c) == false : + WTF::Unicode::direction(c) != WTF::Unicode::WhiteSpaceNeutral; +} + +bool CacheBuilder::ConstructTextRect(Text* textNode, + InlineTextBox* textBox, int start, int relEnd, int x, int y, + IntRect* focusBounds, const IntRect& clipBounds, WTF::Vector<IntRect>* result) +{ + RenderText* renderText = (RenderText*) textNode->renderer(); + EVisibility vis = renderText->style()->visibility(); + StringImpl* string = textNode->dataImpl(); + const UChar* chars = string->characters(); + FloatPoint pt = renderText->localToAbsolute(); + do { + int textBoxStart = textBox->start(); + int textBoxEnd = textBoxStart + textBox->len(); + if (textBoxEnd <= start) + continue; + if (textBoxEnd > relEnd) + textBoxEnd = relEnd; + IntRect bounds = textBox->selectionRect((int) pt.x(), (int) pt.y(), + start, textBoxEnd); + bounds.intersect(clipBounds); + if (bounds.isEmpty()) + continue; + bool drawable = false; + for (int index = start; index < textBoxEnd; index++) + if ((drawable |= isNotSpace(chars[index])) != false) + break; + if (drawable && vis != HIDDEN) { + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + } + if (textBoxEnd == relEnd) + break; + } while ((textBox = textBox->nextTextBox()) != NULL); + return true; +} + +bool CacheBuilder::ConstructTextRects(Text* node, int start, + Text* last, int end, int x, int y, IntRect* focusBounds, + const IntRect& clipBounds, WTF::Vector<IntRect>* result) +{ + result->clear(); + *focusBounds = IntRect(0, 0, 0, 0); + do { + RenderText* renderText = (RenderText*) node->renderer(); + int relEnd = node == last ? end : renderText->textLength(); + InlineTextBox *textBox = renderText->firstTextBox(); + if (textBox != NULL) { + do { + if ((int) textBox->end() >= start) + break; + } while ((textBox = textBox->nextTextBox()) != NULL); + if (textBox && ConstructTextRect(node, textBox, start, relEnd, + x, y, focusBounds, clipBounds, result) == false) + return false; + } + start = 0; + do { + if (node == last) + return true; + node = (Text*) node->traverseNextNode(); + ASSERT(node != NULL); + } while (node->isTextNode() == false || node->renderer() == NULL); + } while (true); +} + +} diff --git a/Source/WebKit/android/nav/CacheBuilder.h b/Source/WebKit/android/nav/CacheBuilder.h new file mode 100644 index 0000000..8f1cc72 --- /dev/null +++ b/Source/WebKit/android/nav/CacheBuilder.h @@ -0,0 +1,297 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CacheBuilder_h +#define CacheBuilder_h + +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" +#include "TextDirection.h" +#include <wtf/Forward.h> +#include <wtf/Vector.h> + +#define NAVIGATION_MAX_PHONE_LENGTH 14 + +using namespace WebCore; + +namespace WebCore { + +class ColumnInfo; +class Document; +class Frame; +class HTMLAreaElement; +class InlineTextBox; +class LayerAndroid; +class Node; +class PlatformGraphicsContext; +class RenderBlock; +class RenderFlow; +class RenderLayer; +class RenderObject; +class Text; + +} + +namespace android { + +class CachedFrame; +class CachedNode; +class CachedRoot; + +class CacheBuilder { +public: + enum Direction { + UNINITIALIZED = -1, + LEFT, + RIGHT, + UP, + DOWN, + DIRECTION_COUNT, + UP_DOWN = UP & DOWN, // mask and result + RIGHT_DOWN = RIGHT & DOWN, // mask and result + }; + enum FoundState { + FOUND_NONE, + FOUND_PARTIAL, + FOUND_COMPLETE + }; + CacheBuilder(); + void allowAllTextDetection() { mAllowableTypes = ALL_CACHEDNODE_BITS; } + void buildCache(CachedRoot* root); + static bool ConstructPartRects(Node* node, const IntRect& bounds, + IntRect* focusBounds, int x, int y, WTF::Vector<IntRect>* result, + int* imageCountPtr); + Node* currentFocus() const; + void disallowAddressDetection() { mAllowableTypes = (CachedNodeBits) ( + mAllowableTypes & ~ADDRESS_CACHEDNODE_BIT); } + void disallowEmailDetection() { mAllowableTypes = (CachedNodeBits) ( + mAllowableTypes & ~EMAIL_CACHEDNODE_BIT); } + void disallowPhoneDetection() { mAllowableTypes = (CachedNodeBits) ( + mAllowableTypes & ~PHONE_CACHEDNODE_BIT); } + static FoundState FindAddress(const UChar* , unsigned length, int* start, + int* end, bool caseInsensitive); + static IntRect getAreaRect(const HTMLAreaElement* area); + static void GetGlobalOffset(Frame* , int* x, int * y); + static void GetGlobalOffset(Node* , int* x, int * y); + bool pictureSetDisabled() { return mPictureSetDisabled; } + static bool validNode(Frame* startFrame, void* framePtr, void* nodePtr); +private: + enum AddressProgress { + NO_ADDRESS, + SKIP_TO_SPACE, + HOUSE_NUMBER, + NUMBER_TRAILING_SPACE, + ADDRESS_LINE, + STATE_NAME, + SECOND_HALF, + ZIP_CODE, + PLUS_4, + FIND_STREET + }; + struct NodeWalk { + NodeWalk() { reset(); } + int mStart; + int mEnd; + Node* mFinalNode; + InlineTextBox* mLastInline; + bool mMore; + void reset() { mMore = false; } + }; + struct BoundsPart { + IntRect mRect; + int mStart; + int mEnd; + }; + struct Bounds { + typedef bool (*FindText)(BoundsPart* result, InlineTextBox* , const String& match); + IntRect mNodeBounds; + BoundsPart mPart; + WTF::Vector<BoundsPart> mParts; + char mStore[NAVIGATION_MAX_PHONE_LENGTH + 1]; + int mPartIndex; + Node* mNode; + Node* mFinalNode; + void reset() { mNode = NULL; } + }; + struct FindState { + int mStartResult; + int mEndResult; + const UChar* mCurrentStart; + const UChar* mEnd; + AddressProgress mProgress; + int mNumberCount; + int mLetterCount; + unsigned mWordCount; + int mLineCount; + const UChar* mFirstLower; + const UChar* mZipStart; + const UChar* mBases[16]; // FIXME: random guess, maybe too small, maybe too big + const UChar* mWords[16]; + const UChar* mEnds[16]; + const UChar* mStarts[16]; // text is not necessarily contiguous + const char* mStates; + int mEndWord; + int mStateWord; + int mZipHint; + int mSectionLength; + unsigned mNumberWords; // must contain as many bits as mWords contains elements + char* mPattern; + UChar mStore[NAVIGATION_MAX_PHONE_LENGTH + 1]; + UChar* mStorePtr; + UChar mBackOne; + UChar mBackTwo; + UChar mCurrent; + bool mUnparsed; + bool mZipDelimiter; + bool mOpenParen; + bool mInitialized; + bool mContinuationNode; + bool mCaseInsensitive; + void shiftWords(int shift) { + memmove(mBases, &mBases[shift], (sizeof(mBases) / + sizeof(mBases[0]) - shift) * sizeof(mBases[0])); + memmove(mWords, &mWords[shift], (sizeof(mWords) / + sizeof(mWords[0]) - shift) * sizeof(mWords[0])); + memmove(mEnds, &mEnds[shift], (sizeof(mEnds) / + sizeof(mEnds[0]) - shift) * sizeof(mEnds[0])); + memmove(mStarts, &mStarts[shift], (sizeof(mStarts) / + sizeof(mStarts[0]) - shift) * sizeof(mStarts[0])); + } + void newWord(const UChar* baseChars, const UChar* chars) { + mBases[mWordCount] = baseChars; + mWords[mWordCount] = chars; + mEnds[mWordCount] = mEnd; + mStarts[mWordCount] = mCurrentStart; + } + }; + struct Tracker { + Node* mLastChild; + }; + struct ClipColumnTracker : Tracker { + Node* mNode; + IntRect mBounds; + ColumnInfo* mColumnInfo; + int mColumnGap; + TextDirection mDirection; + bool mHasClip; + }; + struct LayerTracker : Tracker { + LayerAndroid* mLayer; + RenderLayer* mRenderLayer; + IntRect mBounds; + IntPoint mScroll; + ~LayerTracker(); + }; + struct TabIndexTracker : Tracker { + int mTabIndex; + }; + struct FocusTracker : TabIndexTracker { + int mCachedNodeIndex; + bool mSomeParentTakesFocus; + }; + void adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds, RenderBlock*); + static bool AddPartRect(IntRect& bounds, int x, int y, + WTF::Vector<IntRect>* result, IntRect* focusBounds); + static bool AnyIsClick(Node* node); + static bool AnyChildIsClick(Node* node); + static bool NodeHasEventListeners(Node* node, AtomicString* eventTypes, int length); + void BuildFrame(Frame* root, Frame* frame, + CachedRoot* cachedRoot, CachedFrame* cachedFrame); + bool CleanUpContainedNodes(CachedRoot* cachedRoot, CachedFrame* cachedFrame, + const FocusTracker* last, int lastChildIndex); + static bool ConstructTextRect(Text* textNode, + InlineTextBox* textBox, int start, int relEnd, int x, int y, + IntRect* focusBounds, const IntRect& clip, WTF::Vector<IntRect>* result); + static bool ConstructTextRects(Text* node, int start, + Text* last, int end, int x, int y, IntRect* focusBounds, + const IntRect& clip, WTF::Vector<IntRect>* result); + static FoundState FindPartialAddress(const UChar* , const UChar* , unsigned length, FindState* ); + static FoundState FindPartialEMail(const UChar* , unsigned length, FindState* ); + static FoundState FindPartialNumber(const UChar* , unsigned length, FindState* ); + static FoundState FindPhoneNumber(const UChar* chars, unsigned length, int* start, int* end); + static void FindReset(FindState* ); + static void FindResetNumber(FindState* ); + static Frame* FrameAnd(CacheBuilder* focusNav); + static Frame* FrameAnd(const CacheBuilder* focusNav); + static CacheBuilder* Builder(Frame* ); + static Frame* HasFrame(Node* ); + static bool HasOverOrOut(Node* ); + static bool HasTriggerEvent(Node* ); + static bool IsDomainChar(UChar ch); + bool isFocusableText(NodeWalk* , bool oldMore, Node* , CachedNodeType* type, + String* exported) const; //returns true if it is focusable + static bool IsMailboxChar(UChar ch); + static bool IsRealNode(Frame* , Node* ); + int overlap(int left, int right); // returns distance scale factor as 16.16 scalar + bool setData(CachedFrame* ); +#if USE(ACCELERATED_COMPOSITING) + void TrackLayer(WTF::Vector<LayerTracker>& layerTracker, + RenderObject* nodeRenderer, Node* lastChild, int offsetX, int offsetY); +#endif + Node* tryFocus(Direction direction); + Node* trySegment(Direction direction, int mainStart, int mainEnd); + CachedNodeBits mAllowableTypes; + bool mPictureSetDisabled; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + void frameName(char*& namePtr, const char* max) const; + void init(char* buffer, size_t size); + static int ParentIndex(Node* node, int count, Node* parent); + void print() { frames(); } + void print(const char* name); + void wideString(const String& str); +private: + void attr(const AtomicString& name, const AtomicString& value); + void comma(const char* str); + void flush(); + Frame* frameAnd() const; + void frames(); + void groups(); + bool isFocusable(Node* node); + void localName(Node* node); + void newLine(int indent = 0); + void print(const char* name, unsigned len); + void setIndent(int ); + void uChar(const UChar* name, unsigned len, bool hex); + void validateFrame(); + void validateStringData(); + void wideString(const UChar* chars, int length, bool hex); + char* mBuffer; + size_t mBufferSize; + int mIndex; + const char* mPrefix; + int mMinPrefix; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedColor.cpp b/Source/WebKit/android/nav/CachedColor.cpp new file mode 100644 index 0000000..c610022 --- /dev/null +++ b/Source/WebKit/android/nav/CachedColor.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" +#include "CachedColor.h" + +namespace android { + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_COLOR(field) \ + DUMP_NAV_LOGD("// SkColor " #field "=0x%08x;\n", b->field) + +CachedColor* CachedColor::Debug::base() const { + CachedColor* nav = (CachedColor*) ((char*) this - OFFSETOF(CachedColor, mDebug)); + return nav; +} + +void CachedColor::Debug::print() const +{ + CachedColor* b = base(); + DEBUG_PRINT_COLOR(mFillColor); + DUMP_NAV_LOGD("// int mInnerWidth=%d;\n", b->mInnerWidth); + DUMP_NAV_LOGD("// int mOuterWidth=%d;\n", b->mOuterWidth); + DUMP_NAV_LOGD("// int mOutset=%d;\n", b->mOutset); + DEBUG_PRINT_COLOR(mPressedInnerColor); + DEBUG_PRINT_COLOR(mPressedOuterColor); + DUMP_NAV_LOGD("// int mRadius=%d;\n", b->mRadius); + DEBUG_PRINT_COLOR(mSelectedInnerColor); + DEBUG_PRINT_COLOR(mSelectedOuterColor); +} + +#endif + +} + diff --git a/Source/WebKit/android/nav/CachedColor.h b/Source/WebKit/android/nav/CachedColor.h new file mode 100644 index 0000000..2ba9b18 --- /dev/null +++ b/Source/WebKit/android/nav/CachedColor.h @@ -0,0 +1,87 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedColor_h +#define CachedColor_h + +#include "CachedDebug.h" +#include "Color.h" +#include "Length.h" +#include "SkColor.h" + +using namespace WebCore; + +namespace android { + +class CachedColor { +public: + CachedColor() { + // Initiaized to 0 in its array, so nothing to do in the + // constructor + } + bool operator==(const CachedColor& o) const { + return memcmp(&o, this, sizeof(this)) == 0; } + SkColor fillColor() const { return mFillColor; } + void init(); + int innerWidth() const { return mInnerWidth; } + int outerWidth() const { return mOuterWidth; } + int outset() const { return mOutset; } + SkColor pressedInnerColor() const { return mPressedInnerColor; } + SkColor pressedOuterColor() const { return mPressedOuterColor; } + int radius() const { return mRadius; } + SkColor selectedInnerColor() const { return mSelectedInnerColor; } + SkColor selectedOuterColor() const { return mSelectedOuterColor; } + void setFillColor(const Color& c) { mFillColor = c.rgb(); } + void setInnerWidth(Length l) { mInnerWidth = l.value(); } + void setOuterWidth(Length l) { mOuterWidth = l.value(); } + void setOutset(Length l) { mOutset = l.value(); } + void setPressedInnerColor(const Color& c) { mPressedInnerColor = c.rgb(); } + void setPressedOuterColor(const Color& c) { mPressedOuterColor = c.rgb(); } + void setRadius(Length l) { mRadius = l.value(); } + void setSelectedInnerColor(const Color& c) { mSelectedInnerColor = c.rgb(); } + void setSelectedOuterColor(const Color& c) { mSelectedOuterColor = c.rgb(); } +private: + SkColor mFillColor; + int mInnerWidth; + int mOuterWidth; + int mOutset; + SkColor mPressedInnerColor; + SkColor mPressedOuterColor; + int mRadius; + SkColor mSelectedInnerColor; + SkColor mSelectedOuterColor; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedColor* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedDebug.h b/Source/WebKit/android/nav/CachedDebug.h new file mode 100644 index 0000000..f77c07a --- /dev/null +++ b/Source/WebKit/android/nav/CachedDebug.h @@ -0,0 +1,72 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedDebug_h +#define CachedDebug_h + +#define DUMP_NAV_CACHE 0 +#define DEBUG_NAV_UI 0 +#define DEBUG_NAV_UI_VERBOSE 0 + +#if DEBUG_NAV_UI +#define DBG_NAV_LOG(message) LOGD("%s %s", __FUNCTION__, message) +#define DBG_NAV_LOGD(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#define DEBUG_NAV_UI_LOGD(...) LOGD(__VA_ARGS__) +#else +#define DBG_NAV_LOG(message) ((void)0) +#define DBG_NAV_LOGD(format, ...) ((void)0) +#define DEBUG_NAV_UI_LOGD(...) ((void)0) +#endif + +#if DEBUG_NAV_UI_VERBOSE +#define DBG_NAV_LOGV(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#else +#define DBG_NAV_LOGV(format, ...) ((void)0) +#endif + +#if DUMP_NAV_CACHE != 0 && !defined DUMP_NAV_CACHE_USING_PRINTF && defined NDEBUG +#define DUMP_NAV_CACHE_USING_PRINTF +#endif + +#if DUMP_NAV_CACHE +#ifdef DUMP_NAV_CACHE_USING_PRINTF +#include <stdio.h> +extern FILE* gNavCacheLogFile; +#define NAV_CACHE_LOG_FILE "/data/data/com.android.browser/navlog" +#define DUMP_NAV_LOGD(...) do { if (gNavCacheLogFile) \ + fprintf(gNavCacheLogFile, __VA_ARGS__); else LOGD(__VA_ARGS__); } while (false) +#define DUMP_NAV_LOGX(format, ...) do { if (gNavCacheLogFile) \ + fprintf(gNavCacheLogFile, format, __VA_ARGS__); \ + else LOGD("%s " format, __FUNCTION__, __VA_ARGS__); } while (false) +#else +#define DUMP_NAV_LOGD(...) LOGD(__VA_ARGS__) +#define DUMP_NAV_LOGX(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#endif +#else +#define DUMP_NAV_LOGD(...) ((void)0) +#define DUMP_NAV_LOGX(...) ((void)0) +#endif + +#endif diff --git a/Source/WebKit/android/nav/CachedFrame.cpp b/Source/WebKit/android/nav/CachedFrame.cpp new file mode 100644 index 0000000..c51944e --- /dev/null +++ b/Source/WebKit/android/nav/CachedFrame.cpp @@ -0,0 +1,1511 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" +#include "CachedHistory.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "LayerAndroid.h" + +#include "CachedFrame.h" + +#define OFFSETOF(type, field) ((char*)&(((type*)1)->field) - (char*)1) // avoids gnu warning + +#define MIN_OVERLAP 3 // if rects overlap by 2 pixels or fewer, treat them as non-intersecting + +namespace android { + +WebCore::IntRect CachedFrame::adjustBounds(const CachedNode* node, + const WebCore::IntRect& rect) const +{ + DBG_NAV_LOGV("node=%p [%d] rect=(%d,%d,w=%d,h=%d) view=(%d,%d,w=%d,h=%d)" + " local=(%d,%d,w=%d,h=%d) root=(%d,%d,w=%d,h=%d)", + node, node->index(), rect.x(), rect.y(), rect.width(), rect.height(), + mViewBounds.x(), mViewBounds.y(), + mViewBounds.width(), mViewBounds.height(), + mLocalViewBounds.x(), mLocalViewBounds.y(), + mLocalViewBounds.width(), mLocalViewBounds.height(), + mRoot->mViewBounds.x(), mRoot->mViewBounds.y(), + mRoot->mViewBounds.width(), mRoot->mViewBounds.height()); +#if USE(ACCELERATED_COMPOSITING) + if (!mRoot) + return rect; + + const CachedLayer* cachedLayer = layer(node); + if (!cachedLayer) + return rect; + + const WebCore::LayerAndroid* rootLayer = mRoot->rootLayer(); + const LayerAndroid* aLayer = cachedLayer->layer(rootLayer); + if (aLayer) + return cachedLayer->adjustBounds(rootLayer, rect); +#endif + return rect; +} + +bool CachedFrame::CheckBetween(Direction direction, const WebCore::IntRect& bestRect, + const WebCore::IntRect& prior, WebCore::IntRect* result) +{ + int left, top, width, height; + if (direction & UP_DOWN) { + top = direction == UP ? bestRect.maxY() : prior.maxY(); + 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.maxX(); + int testRight = bestRect.maxX(); + if (right < testRight) + right = testRight; + width = right - left; + } else { + left = direction == LEFT ? bestRect.maxX() : prior.maxX(); + 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.maxY(); + int testBottom = bestRect.maxY(); + if (bottom < testBottom) + bottom = testBottom; + height = bottom - top; + } + *result = WebCore::IntRect(left, top, width, height); + return true; +} + +bool CachedFrame::checkBetween(BestData* best, Direction direction) +{ + const WebCore::IntRect& bestRect = best->bounds(); + BestData test; + test.mDistance = INT_MAX; + test.mNode = NULL; + int index = direction; + int limit = index + DIRECTION_COUNT; + do { + WebCore::IntRect edges; + Direction check = (Direction) (index & DIRECTION_MASK); + if (CheckBetween(check, bestRect, + history()->priorBounds(), &edges) == false) + continue; + WebCore::IntRect clip = mRoot->scrolledBounds(); + clip.intersect(edges); + if (clip.isEmpty()) + continue; + findClosest(&test, direction, check, &clip); + if (test.mNode == NULL) + continue; + if (direction == check) + break; + } while (++index < limit); + if (test.mNode == NULL) + return false; + *best = test; + return true; +} + +bool CachedFrame::checkRings(const CachedNode* node, + const WebCore::IntRect& testBounds) const +{ + return mRoot->checkRings(picture(node), node, testBounds); +} + +bool CachedFrame::checkVisited(const CachedNode* node, Direction direction) const +{ + return history()->checkVisited(node, direction); +} + +void CachedFrame::clearCursor() +{ + DBG_NAV_LOGD("mCursorIndex=%d", mCursorIndex); + if (mCursorIndex < CURSOR_SET) + return; + CachedNode& cursor = mCachedNodes[mCursorIndex]; + cursor.clearCursor(this); + mCursorIndex = CURSOR_CLEARED; // initialized and explicitly cleared +} + +// returns 0 if test is preferable to best, 1 if not preferable, or -1 if unknown +int CachedFrame::compare(BestData& testData, const BestData& bestData) const +{ + if (testData.mNode->tabIndex() != bestData.mNode->tabIndex()) { + if (testData.mNode->tabIndex() < bestData.mNode->tabIndex() + || (mRoot->mCursor && mRoot->mCursor->tabIndex() < bestData.mNode->tabIndex())) { + testData.mNode->setCondition(CachedNode::HIGHER_TAB_INDEX); + return REJECT_TEST; + } + return TEST_IS_BEST; + } + // if the test minor axis line intersects the line segment between cursor + // center and best center, choose it + // give more weight to exact major axis alignment (rows, columns) + if (testData.mInNav != bestData.mInNav) { + if (bestData.mInNav) { + testData.mNode->setCondition(CachedNode::IN_CURSOR); + return REJECT_TEST; + } + return TEST_IS_BEST; + } + if (testData.mInNav) { + if (bestData.mMajorDelta < testData.mMajorDelta) { + testData.mNode->setCondition(CachedNode::CLOSER_IN_CURSOR); + return REJECT_TEST; + } + if (testData.mMajorDelta < bestData.mMajorDelta) + return TEST_IS_BEST; + } + if (testData.mMajorDelta < 0 && bestData.mMajorDelta >= 0) { + testData.mNode->setCondition(CachedNode::FURTHER); + return REJECT_TEST; + } + if ((testData.mMajorDelta ^ bestData.mMajorDelta) < 0) // one above, one below (or one left, one right) + return TEST_IS_BEST; + bool bestInWorking = bestData.inOrSubsumesWorking(); + bool testInWorking = testData.inOrSubsumesWorking(); + if (bestInWorking && testData.mWorkingOutside && testData.mNavOutside) { + testData.mNode->setCondition(CachedNode::IN_WORKING); + return REJECT_TEST; + } + if (testInWorking && bestData.mWorkingOutside && bestData.mNavOutside) + return TEST_IS_BEST; + bool bestInNav = directionChange() && bestData.inOrSubsumesNav(); + bool testInNav = directionChange() && testData.inOrSubsumesNav(); + if (bestInWorking == false && testInWorking == false) { + if (bestInNav && testData.mNavOutside) { + testData.mNode->setCondition(CachedNode::IN_UMBRA); + return REJECT_TEST; + } + if (testInNav && bestData.mNavOutside) + return TEST_IS_BEST; + } +#if 01 // hopefully butt test will remove need for this + if (testData.mCursorChild != bestData.mCursorChild) { + if (bestData.mCursorChild) { + testData.mNode->setCondition(CachedNode::IN_CURSOR_CHILDREN); + return REJECT_TEST; + } + return TEST_IS_BEST; + } +#endif + bool bestTestIn = (bestInWorking || bestInNav) && (testInWorking || testInNav); + bool testOverlap = bestTestIn || (testData.mWorkingOverlap != 0 && bestData.mWorkingOverlap == 0); + bool bestOverlap = bestTestIn || (testData.mWorkingOverlap == 0 && bestData.mWorkingOverlap != 0); +#if 01 // this isn't working? + if (testOverlap == bestOverlap) { + if (bestData.mMajorButt < 10 && testData.mMajorButt >= 40) { + testData.mNode->setCondition(CachedNode::BUTTED_UP); + return REJECT_TEST; + } + if (testData.mMajorButt < 10 && bestData.mMajorButt >= 40) + return TEST_IS_BEST; + } +#endif + if (bestOverlap && bestData.mMajorDelta < testData.mMajorDelta) { // choose closest major axis center + testData.mNode->setCondition(CachedNode::CLOSER); + return REJECT_TEST; + } + if (testOverlap && testData.mMajorDelta < bestData.mMajorDelta) + return TEST_IS_BEST; + if (bestOverlap && bestData.mMajorDelta2 < testData.mMajorDelta2) { + testData.mNode->setCondition(CachedNode::CLOSER_TOP); + return REJECT_TEST; + } + if (testOverlap && testData.mMajorDelta2 < bestData.mMajorDelta2) + return TEST_IS_BEST; +#if 01 + if (bestOverlap && ((bestData.mSideDistance <= 0 && testData.mSideDistance > 0) || + abs(bestData.mSideDistance) < abs(testData.mSideDistance))) { + testData.mNode->setCondition(CachedNode::LEFTMOST); + return REJECT_TEST; + } + if (testOverlap && ((testData.mSideDistance <= 0 && bestData.mSideDistance > 0) || + abs(testData.mSideDistance) < abs(bestData.mSideDistance))) + return TEST_IS_BEST; +// fix me : the following ASSERT fires -- not sure if this case should be handled or not +// ASSERT(bestOverlap == false && testOverlap == false); +#endif + SkFixed testMultiplier = testData.mWorkingOverlap > testData.mNavOverlap ? + testData.mWorkingOverlap : testData.mNavOverlap; + SkFixed bestMultiplier = bestData.mWorkingOverlap > bestData.mNavOverlap ? + bestData.mWorkingOverlap : bestData.mNavOverlap; + int testDistance = testData.mDistance; + int bestDistance = bestData.mDistance; +// start here; + // this fails if they're off by 1 + // try once again to implement sliding scale so that off by 1 is nearly like zero, + // and off by a lot causes sideDistance to have little or no effect + // try elliptical distance -- lengthen side contribution + // these ASSERTs should not fire, but do fire on mail.google.com + // can't debug yet, won't reproduce + ASSERT(testDistance >= 0); + ASSERT(bestDistance >= 0); + testDistance += testDistance; // multiply by 2 + testDistance *= testDistance; + bestDistance += bestDistance; // multiply by 2 + bestDistance *= bestDistance; + int side = testData.mSideDistance; + int negative = side < 0 && bestData.mSideDistance > 0; + side *= side; + if (negative) + side = -side; + testDistance += side; + side = bestData.mSideDistance; + negative = side < 0 && testData.mSideDistance > 0; + side *= side; + if (negative) + side = -side; + bestDistance += side; + if (testMultiplier > (SK_Fixed1 >> 1) || bestMultiplier > (SK_Fixed1 >> 1)) { // considerable working overlap? + testDistance = SkFixedMul(testDistance, bestMultiplier); + bestDistance = SkFixedMul(bestDistance, testMultiplier); + } + if (bestDistance < testDistance) { + testData.mNode->setCondition(CachedNode::CLOSER_OVERLAP); + return REJECT_TEST; + } + if (testDistance < bestDistance) + return TEST_IS_BEST; +#if 0 + int distance = testData.mDistance + testData.mSideDistance; + int best = bestData.mDistance + bestData.mSideDistance; + if (distance > best) { + testData.mNode->setCondition(CachedNode::CLOSER_RAW_DISTANCE); + return REJECT_TEST; + } + else if (distance < best) + return TEST_IS_BEST; + best = bestData.mSideDistance; + if (testData.mSideDistance > best) { + testData.mNode->setCondition(CachedNode::SIDE_DISTANCE); + return REJECT_TEST; + } + if (testData.mSideDistance < best) + return TEST_IS_BEST; +#endif + if (testData.mPreferred < bestData.mPreferred) { + testData.mNode->setCondition(CachedNode::PREFERRED); + return REJECT_TEST; + } + if (testData.mPreferred > bestData.mPreferred) + return TEST_IS_BEST; + return UNDECIDED; +} + +const CachedNode* CachedFrame::currentCursor(const CachedFrame** framePtr) const +{ + if (framePtr) + *framePtr = this; + if (mCursorIndex < CURSOR_SET) + return NULL; + const CachedNode* result = &mCachedNodes[mCursorIndex]; + const CachedFrame* frame = hasFrame(result); + if (frame != NULL) + return frame->currentCursor(framePtr); + (const_cast<CachedNode*>(result))->fixUpCursorRects(this); + return result; +} + +const CachedNode* CachedFrame::currentFocus(const CachedFrame** framePtr) const +{ + if (framePtr) + *framePtr = this; + if (mFocusIndex < 0) + return NULL; + const CachedNode* result = &mCachedNodes[mFocusIndex]; + const CachedFrame* frame = hasFrame(result); + if (frame != NULL) + return frame->currentFocus(framePtr); + return result; +} + +bool CachedFrame::directionChange() const +{ + return history()->directionChange(); +} + +#ifdef BROWSER_DEBUG +CachedNode* CachedFrame::find(WebCore::Node* node) // !!! probably debugging only +{ + for (CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) + if (node == test->webCoreNode()) + return test; + for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); + frame++) { + CachedNode* result = frame->find(node); + if (result != NULL) + return result; + } + return NULL; +} +#endif + +const CachedNode* CachedFrame::findBestAt(const WebCore::IntRect& rect, + int* best, bool* inside, const CachedNode** directHit, + const CachedFrame** directHitFramePtr, + const CachedFrame** framePtr, int* x, int* y, + bool checkForHiddenStart) const +{ + const CachedNode* result = NULL; + int rectWidth = rect.width(); + WebCore::IntPoint center = WebCore::IntPoint(rect.x() + (rectWidth >> 1), + rect.y() + (rect.height() >> 1)); + mRoot->setupScrolledBounds(); + for (const CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) { + if (test->disabled()) + continue; + size_t parts = test->navableRects(); + BestData testData; + testData.mNode = test; + testData.mFrame = this; + WebCore::IntRect bounds = test->bounds(this); + testData.setMouseBounds(bounds); + testData.setNodeBounds(bounds); + bool checkForHidden = checkForHiddenStart; + for (size_t part = 0; part < parts; part++) { + WebCore::IntRect testRect = test->ring(this, part); + if (testRect.intersects(rect)) { +#if DEBUG_NAV_UI + if (test->isInLayer()) { + DBG_NAV_LOGD("[%d] intersects=%s testRect=(%d,%d,w=%d,h=%d)" + " rect=(%d,%d,w=%d,h=%d)", test->index(), + testRect.intersects(rect) ? "true" : "false", + testRect.x(), testRect.y(), + testRect.width(), testRect.height(), + rect.x(), rect.y(), rect.width(), rect.height()); + } +#endif + if (checkForHidden && mRoot->maskIfHidden(&testData) == true) { + DBG_NAV_LOGD("hidden [%d]", test->index()); + break; + } + checkForHidden = false; + testRect.intersect(testData.mouseBounds()); + if (testRect.contains(center)) { + // We have a direct hit. + if (*directHit == NULL) { + DBG_NAV_LOGD("direct hit 1 [%d]", test->index()); + *directHit = test; + *directHitFramePtr = this; + IntRect r(center, IntSize(0, 0)); + *x = r.x(); + *y = r.y(); + } else { + DBG_NAV_LOGD("direct hit 2 [%d]", test->index()); + // We have hit another one before + const CachedNode* d = *directHit; + if (d->bounds(this).contains(testRect)) { + // This rectangle is inside the other one, so it is + // the best one. + *directHit = test; + *directHitFramePtr = this; + } + } + } + if (NULL != *directHit) { + // If we have a direct hit already, there is no need to + // calculate the distances, or check the other parts + break; + } + DBG_NAV_LOGD("indirect hit [%d]", test->index()); + WebCore::IntRect both = rect; + int smaller = testRect.width() < testRect.height() ? + testRect.width() : testRect.height(); + smaller -= rectWidth; + int inset = smaller < rectWidth ? smaller : rectWidth; + inset >>= 1; // inflate doubles the width decrease + if (inset > 1) + both.inflate(1 - inset); + both.intersect(testRect); + if (both.isEmpty()) + continue; + bool testInside = testRect.contains(center); + if (*inside && !testInside) + continue; + WebCore::IntPoint testCenter = WebCore::IntPoint(testRect.x() + + (testRect.width() >> 1), testRect.y() + (testRect.height() >> 1)); + int dx = testCenter.x() - center.x(); + int dy = testCenter.y() - center.y(); + int distance = dx * dx + dy * dy; + if ((!*inside && testInside) || *best >= distance) { + *best = distance; + *inside = testInside; + result = test; + *framePtr = this; + *x = both.x() + (both.width() >> 1); + *y = both.y() + (both.height() >> 1); + } + } + } + } + for (const CachedFrame* frame = mCachedFrames.begin(); + frame != mCachedFrames.end(); frame++) { + const CachedNode* frameResult = frame->findBestAt(rect, best, inside, + directHit, directHitFramePtr, framePtr, x, y, checkForHiddenStart); + if (NULL != frameResult) + result = frameResult; + } + if (NULL != *directHit) { + result = *directHit; + *framePtr = *directHitFramePtr; + } + return result; +} + +const CachedFrame* CachedFrame::findBestFrameAt(int x, int y) const +{ + if (mLocalViewBounds.contains(x, y) == false) + return NULL; + const CachedFrame* result = this; + for (const CachedFrame* frame = mCachedFrames.begin(); + frame != mCachedFrames.end(); frame++) { + const CachedFrame* frameResult = frame->findBestFrameAt(x, y); + if (NULL != frameResult) + result = frameResult; + } + return result; +} + +const CachedNode* CachedFrame::findBestHitAt(const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* x, int* y) const +{ + mRoot->setupScrolledBounds(); + for (const CachedFrame* frame = mCachedFrames.end() - 1; + frame != mCachedFrames.begin() - 1; frame--) { + const CachedNode* frameResult = frame->findBestHitAt(rect, + framePtr, x, y); + if (NULL != frameResult) + return frameResult; + } + for (const CachedNode* test = mCachedNodes.end() - 1; + test != mCachedNodes.begin() - 1; test--) { + if (test->disabled()) + continue; + WebCore::IntRect testRect = test->hitBounds(this); + if (testRect.intersects(rect) == false) + continue; + BestData testData; + testData.mNode = test; + testData.mFrame = this; + testData.setMouseBounds(testRect); + testData.setNodeBounds(testRect); + if (mRoot->maskIfHidden(&testData) == true) + continue; + DBG_NAV_LOGD("candidate %d rect=(%d,%d,r=%d,b=%d)" + " testRect=(%d,%d,r=%d,b=%d)", + test->index(), rect.x(), rect.y(), rect.maxX(), rect.maxY(), + testRect.x(), testRect.y(), testRect.maxX(), testRect.maxY()); + for (int i = 0; i < test->navableRects(); i++) { + WebCore::IntRect cursorRect = test->ring(this, i); + DBG_NAV_LOGD("candidate %d cursorRect=(%d,%d,r=%d,b=%d)", + i, cursorRect.x(), cursorRect.y(), cursorRect.maxX(), + cursorRect.maxY()); + if (cursorRect.intersects(rect)) { + WebCore::IntRect intersection(cursorRect); + intersection.intersect(rect); + *x = intersection.x() + (intersection.width() >> 1); + *y = intersection.y() + (intersection.height() >> 1); + *framePtr = this; + return test; + } + } + testRect.intersect(rect); + *x = testRect.x() + (testRect.width() >> 1); + *y = testRect.y() + (testRect.height() >> 1); + *framePtr = this; + return test; + } + return NULL; +} + +void CachedFrame::findClosest(BestData* bestData, Direction originalDirection, + Direction direction, WebCore::IntRect* clip) const +{ + const CachedNode* test = mCachedNodes.begin(); + while ((test = test->traverseNextNode()) != NULL) { + const CachedFrame* child = hasFrame(test); + if (child != NULL) { + const CachedNode* childDoc = child->validDocument(); + if (childDoc == NULL) + continue; + child->findClosest(bestData, originalDirection, direction, clip); + } + if (test->noSecondChance()) + continue; + if (test->isNavable(this, *clip) == false) + continue; + if (checkVisited(test, originalDirection) == false) + continue; + size_t partMax = test->navableRects(); + for (size_t part = 0; part < partMax; part++) { + WebCore::IntRect testBounds = test->ring(this, part); + if (clip->intersects(testBounds) == false) + continue; + if (clip->contains(testBounds) == false) { + if (direction & UP_DOWN) { +// if (testBounds.x() > clip->x() || testBounds.right() < clip->right()) +// continue; + testBounds.setX(clip->x()); + testBounds.setWidth(clip->width()); + } else { +// if (testBounds.y() > clip->y() || testBounds.bottom() < clip->bottom()) +// continue; + testBounds.setY(clip->y()); + testBounds.setHeight(clip->height()); + } + if (clip->contains(testBounds) == false) + continue; + } + int distance; + // seems like distance for UP for instance needs to be 'test top closest to + // clip bottom' -- keep the old code but try this instead + switch (direction) { +#if 0 + case LEFT: + distance = testBounds.x() - clip->x(); + break; + case RIGHT: + distance = clip->right() - testBounds.right(); + break; + case UP: + distance = testBounds.y() - clip->y(); + break; + case DOWN: + distance = clip->bottom() - testBounds.bottom(); + break; +#else + case LEFT: + distance = clip->maxX() - testBounds.x(); + break; + case RIGHT: + distance = testBounds.maxX() - clip->x(); + break; + case UP: + distance = clip->maxY() - testBounds.y(); + break; + case DOWN: + distance = testBounds.maxY() - clip->y(); + break; +#endif + default: + distance = 0; + ASSERT(false); + } + if (distance < bestData->mDistance) { + bestData->mNode = test; + bestData->mFrame = this; + bestData->mDistance = distance; + WebCore::IntRect rect = test->ring(this, part); + bestData->setMouseBounds(rect); + bestData->setNodeBounds(rect); + CachedHistory* cachedHistory = history(); + switch (direction) { + case LEFT: + bestData->setLeftDirection(cachedHistory); + break; + case RIGHT: + bestData->setRightDirection(cachedHistory); + break; + case UP: + bestData->setUpDirection(cachedHistory); + break; + case DOWN: + bestData->setDownDirection(cachedHistory); + break; + default: + ASSERT(0); + } + } + } + } +} + +void CachedFrame::finishInit() +{ + CachedNode* lastCached = lastNode(); + lastCached->setLast(); + CachedFrame* child = mCachedFrames.begin(); + while (child != mCachedFrames.end()) { + child->mParent = this; + child->finishInit(); + child++; + } + CachedFrame* frameParent; + if (mFocusIndex >= 0 && (frameParent = parent())) + frameParent->setFocusIndex(indexInParent()); +} + +const CachedNode* CachedFrame::frameDown(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameDown, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, DOWN) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setDownDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameDown(document(), test, &innerData); + if (checkVisited(innerData.mNode, DOWN)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, DOWN)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + // does the best contain something (or, is it contained by an area which is not the cursor?) + // if so, is the conainer/containee should have been chosen, but wasn't -- so there's a better choice + // in the doc list prior to this choice + // + return bestData->mNode; +} + +const CachedNode* CachedFrame::frameLeft(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameLeft, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, LEFT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setLeftDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameLeft(document(), test, &innerData); + if (checkVisited(innerData.mNode, LEFT)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, LEFT)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); // FIXME ??? left and up should use traversePreviousNode to choose reverse document order + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + return bestData->mNode; +} + +int CachedFrame::frameNodeCommon(BestData& testData, const CachedNode* test, + BestData* bestData, BestData* originalData) const +{ + testData.mFrame = this; + testData.mNode = test; + test->clearCondition(); + if (test->disabled()) { + testData.mNode->setCondition(CachedNode::DISABLED); + return REJECT_TEST; + } + WebCore::IntRect bounds = test->bounds(this); + if (bounds.isEmpty()) { + testData.mNode->setCondition(CachedNode::NAVABLE); + return REJECT_TEST; + } + if (mRoot->scrolledBounds().intersects(bounds) == false) { + testData.mNode->setCondition(CachedNode::NAVABLE); + return REJECT_TEST; + } + if (mRoot->rootLayer() && !test->isInLayer() + && !mRoot->baseUncovered().intersects(bounds)) { + testData.mNode->setCondition(CachedNode::UNDER_LAYER); + return REJECT_TEST; + } +// if (isNavable(test, &testData.mNodeBounds, walk) == false) { +// testData.mNode->setCondition(CachedNode::NAVABLE); +// return REJECT_TEST; +// } +// + if (test == mRoot->mCursor) { + testData.mNode->setCondition(CachedNode::NOT_CURSOR_NODE); + return REJECT_TEST; + } +// if (test->bounds().contains(mRoot->mCursorBounds)) { +// testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); +// return REJECT_TEST; +// } + void* par = mRoot->mCursor ? mRoot->mCursor->parentGroup() : NULL; + testData.mCursorChild = par ? test->parentGroup() == par : false; + if (bestData->mNode == NULL) + return TEST_IS_BEST; + if (mRoot->mCursor && testData.mNode->parentIndex() != bestData->mNode->parentIndex()) { + int cursorParentIndex = mRoot->mCursor->parentIndex(); + if (cursorParentIndex >= 0) { + if (bestData->mNode->parentIndex() == cursorParentIndex) + return REJECT_TEST; + if (testData.mNode->parentIndex() == cursorParentIndex) + return TEST_IS_BEST; + } + } + if (testData.mNode->parent() == bestData->mNode) { + testData.mNode->setCondition(CachedNode::CHILD); + return REJECT_TEST; + } + if (testData.mNode == bestData->mNode->parent()) + return TEST_IS_BEST; + int testInBest = testData.isContainer(bestData); /* -1 pick best over test, 0 no containership, 1 pick test over best */ + if (testInBest == 1) { + if (test->isArea() || bestData->mNode->isArea()) + return UNDECIDED; + bestData->mNode = NULL; // force part tests to be ignored, yet still set up remaining test data for later comparisons + return TEST_IS_BEST; + } + if (testInBest == -1) { + testData.mNode->setCondition(CachedNode::OUTSIDE_OF_BEST); + return REJECT_TEST; + } + if (originalData->mNode != NULL) { // test is best case + testInBest = testData.isContainer(originalData); + if (testInBest == -1) { /* test is inside best */ + testData.mNode->setCondition(CachedNode::OUTSIDE_OF_ORIGINAL); + return REJECT_TEST; + } + } + return UNDECIDED; +} + +int CachedFrame::framePartCommon(BestData& testData, + const CachedNode* test, BestData* bestData) const +{ + if (mRoot->mCursor + && testData.bounds().contains(mRoot->mCursorBounds) + && !test->wantsKeyEvents()) { + testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + testData.setDistances(); + if (bestData->mNode != NULL) { + int compared = compare(testData, *bestData); + if (compared == 0 && test->isArea() == false && bestData->mNode->isArea() == false) + goto pickTest; + if (compared >= 0) + return compared; + } +pickTest: + return -1; // pick test +} + +const CachedNode* CachedFrame::frameRight(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameRight, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, RIGHT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setRightDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameRight(document(), test, &innerData); + if (checkVisited(innerData.mNode, RIGHT)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, RIGHT)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + return bestData->mNode; +} + +const CachedNode* CachedFrame::frameUp(const CachedNode* test, + const CachedNode* limit, BestData* bestData) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameUp, test, bestData)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) + continue; + if (checkVisited(test, UP) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.setNodeBounds(test->ring(this, part)); + if (testData.setUpDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData); + if (result == REJECT_TEST) + continue; + if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger + BestData innerData = testData; + frameUp(document(), test, &innerData); + if (checkVisited(innerData.mNode, UP)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, UP)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); // FIXME ??? left and up should use traversePreviousNode to choose reverse document order + ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); + return bestData->mNode; +} + +CachedFrame* CachedFrame::hasFrame(const CachedNode* node) +{ + return node->isFrame() ? &mCachedFrames[node->childFrameIndex()] : NULL; +} + +void CachedFrame::hideCursor() +{ + DBG_NAV_LOGD("mCursorIndex=%d", mCursorIndex); + if (mCursorIndex < CURSOR_SET) + return; + CachedNode& cursor = mCachedNodes[mCursorIndex]; + cursor.hideCursor(this); +} + +CachedHistory* CachedFrame::history() const +{ + return mRoot->rootHistory(); +} + +void CachedFrame::init(const CachedRoot* root, int childFrameIndex, + WebCore::Frame* frame) +{ + mContents = WebCore::IntRect(0, 0, 0, 0); // fixed up for real in setData() + mLocalViewBounds = WebCore::IntRect(0, 0, 0, 0); + mViewBounds = WebCore::IntRect(0, 0, 0, 0); + mRoot = root; + mCursorIndex = CURSOR_UNINITIALIZED; // not explicitly cleared + mFocusIndex = -1; + mFrame = frame; + mParent = NULL; // set up parents after stretchy arrays are set up + mIndexInParent = childFrameIndex; +} + +#if USE(ACCELERATED_COMPOSITING) +const CachedLayer* CachedFrame::layer(const CachedNode* node) const +{ + if (!node->isInLayer()) + return 0; + CachedLayer test; + test.setCachedNodeIndex(node->index()); + return std::lower_bound(mCachedLayers.begin(), mCachedLayers.end(), test); +} +#endif + +WebCore::IntRect CachedFrame::localBounds(const CachedNode* node, + const WebCore::IntRect& rect) const +{ + DBG_NAV_LOGD("node=%p [%d] rect=(%d,%d,w=%d,h=%d)", + node, node->index(), rect.x(), rect.y(), rect.width(), rect.height()); +#if USE(ACCELERATED_COMPOSITING) + return layer(node)->localBounds(mRoot->rootLayer(), rect); +#else + return rect; +#endif +} + +int CachedFrame::minWorkingHorizontal() const +{ + return history()->minWorkingHorizontal(); +} + +int CachedFrame::minWorkingVertical() const +{ + return history()->minWorkingVertical(); +} + +int CachedFrame::maxWorkingHorizontal() const +{ + return history()->maxWorkingHorizontal(); +} + +int CachedFrame::maxWorkingVertical() const +{ + return history()->maxWorkingVertical(); +} + +const CachedNode* CachedFrame::nextTextField(const CachedNode* start, + const CachedFrame** framePtr, bool* startFound) const +{ + const CachedNode* test = mCachedNodes.begin(); + while ((test = test->traverseNextNode())) { + const CachedFrame* frame = hasFrame(test); + if (frame) { + if (!frame->validDocument()) + continue; + const CachedNode* node + = frame->nextTextField(start, framePtr, startFound); + if (node) + return node; + } else if (test->isTextInput()) { + if (test == start) + *startFound = true; + else if (*startFound) { + if (framePtr) + *framePtr = this; + return test; + } + } + } + return 0; +} + +bool CachedFrame::moveInFrame(MoveInDirection moveInDirection, + const CachedNode* test, BestData* bestData) const +{ + const CachedFrame* frame = hasFrame(test); + if (frame == NULL) + return false; // if it's not a frame, let the caller have another swing at it + const CachedNode* childDoc = frame->validDocument(); + if (childDoc == NULL) + return true; + (frame->*moveInDirection)(childDoc, NULL, bestData); + return true; +} + +const WebCore::IntRect& CachedFrame::_navBounds() const +{ + return history()->navBounds(); +} + +SkPicture* CachedFrame::picture(const CachedNode* node) const +{ +#if USE(ACCELERATED_COMPOSITING) + if (node->isInLayer()) + return layer(node)->picture(mRoot->rootLayer()); +#endif + return mRoot->mPicture; +} + +SkPicture* CachedFrame::picture(const CachedNode* node, int* xPtr, int* yPtr) const +{ +#if USE(ACCELERATED_COMPOSITING) + if (node->isInLayer()) { + const CachedLayer* cachedLayer = layer(node); + const LayerAndroid* rootLayer = mRoot->rootLayer(); + cachedLayer->toLocal(rootLayer, xPtr, yPtr); + return cachedLayer->picture(rootLayer); + } +#endif + return mRoot->mPicture; +} + +void CachedFrame::resetClippedOut() +{ + for (CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) + { + if (test->clippedOut()) { + test->setDisabled(false); + test->setClippedOut(false); + } + } + for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); + frame++) { + frame->resetClippedOut(); + } +} + +void CachedFrame::resetLayers() +{ +#if USE(ACCELERATED_COMPOSITING) + for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); + frame++) { + frame->resetLayers(); + } +#endif +} + +bool CachedFrame::sameFrame(const CachedFrame* test) const +{ + ASSERT(test); + if (mIndexInParent != test->mIndexInParent) + return false; + if (mIndexInParent == -1) // index within parent's array of children, or -1 if root + return true; + return mParent->sameFrame(test->mParent); +} + +void CachedFrame::setData() +{ + if (this != mRoot) { + mViewBounds = mLocalViewBounds; + mViewBounds.intersect(mRoot->mViewBounds); + } + int x, y; + if (parent() == NULL) + x = y = 0; + else { + x = mLocalViewBounds.x(); + y = mLocalViewBounds.y(); + } + mContents.setX(x); + mContents.setY(y); + CachedFrame* child = mCachedFrames.begin(); + while (child != mCachedFrames.end()) { + child->setData(); + child++; + } +} + +bool CachedFrame::setCursor(WebCore::Frame* frame, WebCore::Node* node, + int x, int y) +{ + if (NULL == node) { + const_cast<CachedRoot*>(mRoot)->setCursor(NULL, NULL); + return true; + } + if (mFrame != frame) { + for (CachedFrame* testF = mCachedFrames.begin(); testF != mCachedFrames.end(); + testF++) { + if (testF->setCursor(frame, node, x, y)) + return true; + } + DBG_NAV_LOGD("no frame frame=%p node=%p", frame, node); + return false; + } + bool first = true; + CachedNode const * const end = mCachedNodes.end(); + do { + for (CachedNode* test = mCachedNodes.begin(); test != end; test++) { + if (test->nodePointer() != node && first) + continue; + size_t partMax = test->navableRects(); + for (size_t part = 0; part < partMax; part++) { + WebCore::IntRect testBounds = test->ring(this, part); + if (testBounds.contains(x, y) == false) + continue; + if (test->isCursor()) { + DBG_NAV_LOGD("already set? test=%d frame=%p node=%p x=%d y=%d", + test->index(), frame, node, x, y); + return false; + } + const_cast<CachedRoot*>(mRoot)->setCursor(this, test); + return true; + } + } + DBG_NAV_LOGD("moved? frame=%p node=%p x=%d y=%d", frame, node, x, y); + } while ((first ^= true) == false); +failed: + DBG_NAV_LOGD("no match frame=%p node=%p", frame, node); + return false; +} + +const CachedNode* CachedFrame::validDocument() const +{ + const CachedNode* doc = document(); + return doc != NULL && mViewBounds.isEmpty() == false ? doc : NULL; +} + +bool CachedFrame::BestData::canBeReachedByAnotherDirection() +{ + if (mMajorButt > -MIN_OVERLAP) + return false; + mMajorButt = -mMajorButt; + return mNavOutside; +} + +int CachedFrame::BestData::isContainer(CachedFrame::BestData* other) +{ + int _x = x(); + int otherRight = other->right(); + if (_x >= otherRight) + return 0; // does not intersect + int _y = y(); + int otherBottom = other->bottom(); + if (_y >= otherBottom) + return 0; // does not intersect + int _right = right(); + int otherX = other->x(); + if (otherX >= _right) + return 0; // does not intersect + int _bottom = bottom(); + int otherY = other->y(); + if (otherY >= _bottom) + return 0; // does not intersect + int intoX = otherX - _x; + int intoY = otherY - _y; + int intoRight = otherRight - _right; + int intoBottom = otherBottom - _bottom; + bool contains = intoX >= 0 && intoY >= 0 && intoRight <= 0 && intoBottom <= 0; + if (contains && mNode->partRectsContains(other->mNode)) { +// if (mIsArea == false && hasMouseOver()) +// other->mMouseOver = mNode; + return mNode->isArea() ? 1 : -1; + } + bool containedBy = intoX <= 0 && intoY <= 0 && intoRight >= 0 && intoBottom >= 0; + if (containedBy && other->mNode->partRectsContains(mNode)) { +// if (other->mIsArea == false && other->hasMouseOver()) +// mMouseOver = other->mNode; + return other->mNode->isArea() ? -1 : 1; + } + return 0; +} + +// distance scale factor factor as a 16.16 scalar +SkFixed CachedFrame::BestData::Overlap(int span, int left, int right) +{ + unsigned result; + if (left > 0 && left < span && right > span) + result = (unsigned) left; + else if (right > 0 && right < span && left > span) + result = (unsigned) right; + else if (left > 0 && right > 0) + return SK_Fixed1; + else + return 0; + result = (result << 16) / (unsigned) span; // linear proportion, always less than fixed 1 + return (SkFixed) result; +// !!! experiment with weight -- enable if overlaps are preferred too much +// or reverse weighting if overlaps are preferred to little +// return (SkFixed) (result * result >> 16); // but fall off with square +} + +void CachedFrame::BestData::setDistances() +{ + mDistance = abs(mMajorDelta); + int sideDistance = mWorkingDelta; + if (mWorkingOverlap < SK_Fixed1) { + if (mPreferred > 0) + sideDistance = mWorkingDelta2; + } else if (sideDistance >= 0 && mWorkingDelta2 >=- 0) + sideDistance = 0; + else { + ASSERT(sideDistance <= 0 && mWorkingDelta2 <= 0); + if (sideDistance < mWorkingDelta2) + sideDistance = mWorkingDelta2; + } + // if overlap, smaller abs mWorkingDelta is better, smaller abs majorDelta is better + // if not overlap, positive mWorkingDelta is better + mSideDistance = sideDistance; +} + +bool CachedFrame::BestData::setDownDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = mNodeBounds.y() - navBounds.maxY(); + int testX = mNodeBounds.x(); + int testRight = mNodeBounds.maxX(); + setNavOverlap(navBounds.width(), navBounds.maxX() - 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.maxY() - mNodeBounds.maxY(); + setNavInclusion(testRight - navBounds.maxX(), navBounds.x() - testX); + bool subsumes = navBounds.height() > 0 && inOrSubsumesNav(); + if (inNavTop <= 0 && inNavBottom <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxV = history->maxWorkingVertical(); + int minV = history->minWorkingVertical(); + setWorkingOverlap(testRight - testX, maxV - testX, testRight - minV); + setWorkingInclusion(testRight - maxV, minV - testX); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavBottom >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavTop >= 0 && + inNavBottom > 0 && subsumes; + return false; +} + +bool CachedFrame::BestData::setLeftDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = navBounds.x() - mNodeBounds.maxX(); + int testY = mNodeBounds.y(); + int testBottom = mNodeBounds.maxY(); + setNavOverlap(navBounds.height(), navBounds.maxY() - testY, + testBottom - navBounds.y()); + if (canBeReachedByAnotherDirection()) { + mNode->setCondition(CachedNode::BEST_DIRECTION); + return REJECT_TEST; + } + int inNavRight = navBounds.maxX() - mNodeBounds.maxX(); + 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.maxY()); + bool subsumes = navBounds.width() > 0 && inOrSubsumesNav(); + if (inNavLeft <= 0 && inNavRight <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxH = history->maxWorkingHorizontal(); + int minH = history->minWorkingHorizontal(); + setWorkingOverlap(testBottom - testY, maxH - testY, testBottom - minH); + setWorkingInclusion(minH - testY, testBottom - maxH); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavLeft >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavLeft >= 0 && + inNavRight > 0 && subsumes; /* both L/R in or out */ + return false; +} + +bool CachedFrame::BestData::setRightDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = mNodeBounds.x() - navBounds.maxX(); + int testY = mNodeBounds.y(); + int testBottom = mNodeBounds.maxY(); + setNavOverlap(navBounds.height(), navBounds.maxY() - 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.maxX() - mNodeBounds.maxX(); + setNavInclusion(testBottom - navBounds.maxY(), navBounds.y() - testY); + bool subsumes = navBounds.width() > 0 && inOrSubsumesNav(); + if (inNavLeft <= 0 && inNavRight <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxH = history->maxWorkingHorizontal(); + int minH = history->minWorkingHorizontal(); + setWorkingOverlap(testBottom - testY, testBottom - minH, maxH - testY); + setWorkingInclusion(testBottom - maxH, minH - testY); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavRight >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavLeft >= 0 && + inNavRight > 0 && subsumes; /* both L/R in or out */ + return false; +} + +bool CachedFrame::BestData::setUpDirection(const CachedHistory* history) +{ + const WebCore::IntRect& navBounds = history->navBounds(); + mMajorButt = navBounds.y() - mNodeBounds.maxY(); + int testX = mNodeBounds.x(); + int testRight = mNodeBounds.maxX(); + setNavOverlap(navBounds.width(), navBounds.maxX() - testX, + testRight - navBounds.x()); + if (canBeReachedByAnotherDirection()) { + mNode->setCondition(CachedNode::BEST_DIRECTION); + return REJECT_TEST; + } + int inNavBottom = navBounds.maxY() - mNodeBounds.maxY(); + 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.maxX()); + bool subsumes = navBounds.height() > 0 && inOrSubsumesNav(); + if (inNavTop <= 0 && inNavBottom <= 0 && subsumes && !mNode->wantsKeyEvents()) { + mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); + return REJECT_TEST; + } + int maxV = history->maxWorkingVertical(); + int minV = history->minWorkingVertical(); + setWorkingOverlap(testRight - testX, testRight - minV, maxV - testX); + setWorkingInclusion(minV - testX, testRight - maxV); + if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavTop >= 0) { + mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); + return REJECT_TEST; + } + mInNav = history->directionChange() && inNavTop >= 0 && + inNavBottom > 0 && subsumes; /* both L/R in or out */ + return false; +} + +void CachedFrame::BestData::setNavInclusion(int left, int right) +{ + // if left and right <= 0, test node is completely in umbra of cursor + // prefer leftmost center + // if left and right > 0, test node subsumes cursor + mNavDelta = left; + mNavDelta2 = right; +} + +void CachedFrame::BestData::setNavOverlap(int span, int left, int right) +{ + // if left or right < 0, test node is not in umbra of cursor + mNavOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; + mNavOverlap = Overlap(span, left, right); // prefer smallest negative left +} + +void CachedFrame::BestData::setWorkingInclusion(int left, int right) +{ + mWorkingDelta = left; + mWorkingDelta2 = right; +} + +// distance scale factor factor as a 16.16 scalar +void CachedFrame::BestData::setWorkingOverlap(int span, int left, int right) +{ + // if left or right < 0, test node is not in umbra of cursor + mWorkingOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; + mWorkingOverlap = Overlap(span, left, right); + mPreferred = left <= 0 ? 0 : left; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_RECT(prefix, debugName, field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("%s DebugTestRect TEST%s_" #debugName "={%d, %d, %d, %d}; //" #field "\n", \ + prefix, mFrameName, r.x(), r.y(), r.width(), r.height()); } + +CachedFrame* CachedFrame::Debug::base() const { + CachedFrame* nav = (CachedFrame*) ((char*) this - OFFSETOF(CachedFrame, mDebug)); + return nav; +} + +void CachedFrame::Debug::print() const +{ + CachedFrame* b = base(); + DEBUG_PRINT_RECT("//", CONTENTS, mContents); + DEBUG_PRINT_RECT("", BOUNDS, mLocalViewBounds); + DEBUG_PRINT_RECT("//", VIEW, mViewBounds); + + DUMP_NAV_LOGD("// CachedNode mCachedNodes={ // count=%d\n", b->mCachedNodes.size()); + for (CachedNode* node = b->mCachedNodes.begin(); + node != b->mCachedNodes.end(); node++) { + node->mDebug.print(); + const CachedInput* input = b->textInput(node); + if (input) + input->mDebug.print(); + DUMP_NAV_LOGD("\n"); + } + DUMP_NAV_LOGD("// }; // end of nodes\n"); +#if USE(ACCELERATED_COMPOSITING) + DUMP_NAV_LOGD("// CachedLayer mCachedLayers={ // count=%d\n", b->mCachedLayers.size()); + for (CachedLayer* layer = b->mCachedLayers.begin(); + layer != b->mCachedLayers.end(); layer++) { + layer->mDebug.print(); + } + DUMP_NAV_LOGD("// }; // end of layers\n"); +#endif // USE(ACCELERATED_COMPOSITING) + DUMP_NAV_LOGD("// CachedColor mCachedColors={ // count=%d\n", b->mCachedColors.size()); + for (CachedColor* color = b->mCachedColors.begin(); + color != b->mCachedColors.end(); color++) { + color->mDebug.print(); + } + DUMP_NAV_LOGD("// }; // end of colors\n"); + DUMP_NAV_LOGD("// CachedFrame mCachedFrames={ // count=%d\n", b->mCachedFrames.size()); + for (CachedFrame* child = b->mCachedFrames.begin(); + child != b->mCachedFrames.end(); child++) + { + child->mDebug.print(); + } + DUMP_NAV_LOGD("// }; // end of child frames\n"); + DUMP_NAV_LOGD("// void* mFrame=(void*) %p;\n", b->mFrame); + DUMP_NAV_LOGD("// CachedFrame* mParent=%p;\n", b->mParent); + DUMP_NAV_LOGD("// int mIndexInParent=%d;\n", b->mIndexInParent); + DUMP_NAV_LOGD("// const CachedRoot* mRoot=%p;\n", b->mRoot); + DUMP_NAV_LOGD("// int mCursorIndex=%d;\n", b->mCursorIndex); + DUMP_NAV_LOGD("// int mFocusIndex=%d;\n", b->mFocusIndex); +} + +bool CachedFrame::Debug::validate(const CachedNode* node) const +{ + const CachedFrame* b = base(); + if (b->mCachedNodes.size() == 0) + return false; + if (node >= b->mCachedNodes.begin() && node < b->mCachedNodes.end()) + return true; + for (const CachedFrame* child = b->mCachedFrames.begin(); + child != b->mCachedFrames.end(); child++) + if (child->mDebug.validate(node)) + return true; + return false; +} + +#undef DEBUG_PRINT_RECT + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedFrame.h b/Source/WebKit/android/nav/CachedFrame.h new file mode 100644 index 0000000..da86521 --- /dev/null +++ b/Source/WebKit/android/nav/CachedFrame.h @@ -0,0 +1,287 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// FIXME: A file of this name already exists in WebCore/history. +// This file should be renamed. +#ifndef AndroidCachedFrame_h +#define AndroidCachedFrame_h + +#include "CachedColor.h" +#include "CachedInput.h" +#include "CachedLayer.h" +#include "CachedNode.h" +#include "IntRect.h" +#include "SkFixed.h" +#include "wtf/Vector.h" + +class SkPicture; + +namespace WebCore { + class Frame; + class Node; +} + +namespace android { + +class CachedHistory; +class CachedRoot; + + // first node referenced by cache is always document +class CachedFrame { +public: + enum Direction { + UNINITIALIZED = -1, + LEFT, + RIGHT, + UP, + DOWN, + DIRECTION_COUNT, + DIRECTION_MASK = DIRECTION_COUNT - 1, + UP_DOWN = UP & DOWN, // mask and result + RIGHT_DOWN = RIGHT & DOWN, // mask and result + }; + enum Compare { + UNDECIDED = -1, + TEST_IS_BEST, + REJECT_TEST + }; + enum CursorInit { + CURSOR_UNINITIALIZED = -2, + CURSOR_CLEARED = -1, + CURSOR_SET = 0 + }; + CachedFrame() {} + void add(CachedColor& color) { mCachedColors.append(color); } + void add(CachedInput& input) { mCachedTextInputs.append(input); } +#if USE(ACCELERATED_COMPOSITING) + void add(CachedLayer& layer) { mCachedLayers.append(layer); } +#endif + void add(CachedNode& node) { mCachedNodes.append(node); } + void addFrame(CachedFrame& child) { mCachedFrames.append(child); } + WebCore::IntRect adjustBounds(const CachedNode* , + const WebCore::IntRect& ) const; + bool checkRings(const CachedNode* node, + const WebCore::IntRect& testBounds) const; + bool checkVisited(const CachedNode* , CachedFrame::Direction ) const; + size_t childCount() { return mCachedFrames.size(); } + void clearCursor(); + const CachedColor& color(const CachedNode* node) const { + return mCachedColors[node->colorIndex()]; + } + const CachedNode* currentCursor() const { return currentCursor(NULL); } + const CachedNode* currentCursor(const CachedFrame** ) const; + const CachedNode* currentFocus() const { return currentFocus(NULL); } + const CachedNode* currentFocus(const CachedFrame** ) const; + bool directionChange() const; + const CachedNode* document() const { return mCachedNodes.begin(); } + bool empty() const { return mCachedNodes.size() < 2; } // must have 1 past doc + const CachedNode* findBestAt(const WebCore::IntRect& , int* best, + bool* inside, const CachedNode** , const CachedFrame** directFrame, + const CachedFrame** resultFrame, int* x, + int* y, bool checkForHidden) const; + const CachedFrame* findBestFrameAt(int x, int y) const; + const CachedNode* findBestHitAt(const WebCore::IntRect& , + const CachedFrame** , int* x, int* y) const; + void finishInit(); + CachedFrame* firstChild() { return mCachedFrames.begin(); } + const CachedFrame* firstChild() const { return mCachedFrames.begin(); } + void* framePointer() const { return mFrame; } + CachedNode* getIndex(int index) { return index >= 0 ? + &mCachedNodes[index] : NULL; } + const CachedFrame* hasFrame(const CachedNode* node) const { + return const_cast<CachedFrame*>(this)->hasFrame(node); + } + CachedFrame* hasFrame(const CachedNode* node); + void hideCursor(); + int indexInParent() const { return mIndexInParent; } + void init(const CachedRoot* root, int index, WebCore::Frame* frame); + const CachedFrame* lastChild() const { return &mCachedFrames.last(); } +#if USE(ACCELERATED_COMPOSITING) + const CachedLayer* lastLayer() const { return &mCachedLayers.last(); } +#endif + CachedNode* lastNode() { return &mCachedNodes.last(); } + CachedFrame* lastChild() { return &mCachedFrames.last(); } +#if USE(ACCELERATED_COMPOSITING) + const CachedLayer* layer(const CachedNode* ) const; + size_t layerCount() const { return mCachedLayers.size(); } +#endif + WebCore::IntRect localBounds(const CachedNode* , + const WebCore::IntRect& ) const; + const CachedFrame* parent() const { return mParent; } + CachedFrame* parent() { return mParent; } + SkPicture* picture(const CachedNode* ) const; + SkPicture* picture(const CachedNode* , int* xPtr, int* yPtr) const; + void resetLayers(); + bool sameFrame(const CachedFrame* ) const; + void removeLast() { mCachedNodes.removeLast(); } + void resetClippedOut(); + void setContentsSize(int width, int height) { mContents.setWidth(width); + mContents.setHeight(height); } + bool setCursor(WebCore::Frame* , WebCore::Node* , int x, int y); + void setCursorIndex(int index) { mCursorIndex = index; } + void setData(); + bool setFocus(WebCore::Frame* , WebCore::Node* , int x, int y); + void setFocusIndex(int index) { mFocusIndex = index; } + void setIndexInParent(int index) { mIndexInParent = index; } + void setLocalViewBounds(const WebCore::IntRect& bounds) { mLocalViewBounds = bounds; } + int size() { return mCachedNodes.size(); } + const CachedInput* textInput(const CachedNode* node) const { + return node->isTextInput() ? &mCachedTextInputs[node->textInputIndex()] + : 0; + } + const CachedNode* validDocument() const; +protected: + const CachedNode* nextTextField(const CachedNode* start, + const CachedFrame** framePtr, bool* found) const; + struct BestData { + int mDistance; + int mSideDistance; + int mMajorDelta; // difference of center of object + // used only when leading and trailing edges contain another set of edges + int mMajorDelta2; // difference of leading edge (only used when center is same) + int mMajorButt; // checks for next cell butting up against or close to previous one + int mWorkingDelta; + int mWorkingDelta2; + int mNavDelta; + int mNavDelta2; + const CachedFrame* mFrame; + const CachedNode* mNode; + SkFixed mWorkingOverlap; // this and below are fuzzy answers instead of bools + SkFixed mNavOverlap; + SkFixed mPreferred; + bool mCursorChild; + bool mInNav; + bool mNavOutside; + bool mWorkingOutside; + int bottom() const { return bounds().maxY(); } + const WebCore::IntRect& bounds() const { return mNodeBounds; } + bool canBeReachedByAnotherDirection(); + int height() const { return bounds().height(); } + bool inOrSubsumesNav() const { return (mNavDelta ^ mNavDelta2) >= 0; } + bool inOrSubsumesWorking() const { return (mWorkingDelta ^ mWorkingDelta2) >= 0; } + int isContainer(BestData* ); + const WebCore::IntRect& mouseBounds() const { return mMouseBounds; } + static SkFixed Overlap(int span, int left, int right); + void reset() { mNode = NULL; } + int right() const { return bounds().maxX(); } + void setMouseBounds(const WebCore::IntRect& b) { mMouseBounds = b; } + void setNodeBounds(const WebCore::IntRect& b) { mNodeBounds = b; } + void setDistances(); + bool setDownDirection(const CachedHistory* ); + bool setLeftDirection(const CachedHistory* ); + bool setRightDirection(const CachedHistory* ); + bool setUpDirection(const CachedHistory* ); + void setNavInclusion(int left, int right); + void setNavOverlap(int span, int left, int right); + void setWorkingInclusion(int left, int right); + void setWorkingOverlap(int span, int left, int right); + int width() const { return bounds().width(); } + int x() const { return bounds().x(); } + int y() const { return bounds().y(); } +private: // since computing these is complicated, protect them so that the + // are only written by appropriate helpers + WebCore::IntRect mMouseBounds; + WebCore::IntRect mNodeBounds; + }; + typedef const CachedNode* (CachedFrame::*MoveInDirection)( + const CachedNode* test, const CachedNode* limit, BestData* ) const; + void adjustToTextColumn(int* delta) const; + static bool CheckBetween(Direction , const WebCore::IntRect& bestRect, + const WebCore::IntRect& prior, WebCore::IntRect* result); + bool checkBetween(BestData* , Direction ); + int compare(BestData& testData, const BestData& bestData) const; + void findClosest(BestData* , Direction original, Direction test, + WebCore::IntRect* clip) const; + int frameNodeCommon(BestData& testData, const CachedNode* test, + BestData* bestData, BestData* originalData) const; + int framePartCommon(BestData& testData, const CachedNode* test, + BestData* ) const; + const CachedNode* frameDown(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + const CachedNode* frameLeft(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + const CachedNode* frameRight(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + const CachedNode* frameUp(const CachedNode* test, const CachedNode* limit, + BestData* ) const; + int minWorkingHorizontal() const; + int minWorkingVertical() const; + int maxWorkingHorizontal() const; + int maxWorkingVertical() const; + bool moveInFrame(MoveInDirection , const CachedNode* test, BestData* ) const; + const WebCore::IntRect& _navBounds() const; + WebCore::IntRect mContents; + WebCore::IntRect mLocalViewBounds; + WebCore::IntRect mViewBounds; + WTF::Vector<CachedColor> mCachedColors; + WTF::Vector<CachedNode> mCachedNodes; + WTF::Vector<CachedFrame> mCachedFrames; + WTF::Vector<CachedInput> mCachedTextInputs; +#if USE(ACCELERATED_COMPOSITING) + WTF::Vector<CachedLayer> mCachedLayers; +#endif + void* mFrame; // WebCore::Frame*, used only to compare pointers + CachedFrame* mParent; + int mCursorIndex; + int mFocusIndex; + int mIndexInParent; // index within parent's array of children, or -1 if root + const CachedRoot* mRoot; +private: + CachedHistory* history() const; +#ifdef BROWSER_DEBUG +public: + CachedNode* find(WebCore::Node* ); // !!! probably debugging only + int mDebugIndex; + int mDebugLoopbackOffset; +#endif +#if !defined NDEBUG || DUMP_NAV_CACHE +public: + class Debug { +public: + Debug() { +#if DUMP_NAV_CACHE + mFrameName[0] = '\0'; +#endif +#if !defined NDEBUG + mInUse = true; +#endif + } +#if !defined NDEBUG + ~Debug() { mInUse = false; } + bool mInUse; +#endif +#if DUMP_NAV_CACHE + CachedFrame* base() const; + void print() const; + bool validate(const CachedNode* ) const; + char mFrameName[256]; +#endif + } mDebug; +#endif +}; + +} + +#endif // AndroidCachedFrame_h diff --git a/Source/WebKit/android/nav/CachedHistory.cpp b/Source/WebKit/android/nav/CachedHistory.cpp new file mode 100644 index 0000000..d132cc3 --- /dev/null +++ b/Source/WebKit/android/nav/CachedHistory.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" +#include "CachedFrame.h" +#include "CachedNode.h" +#if DUMP_NAV_CACHE +#include "CachedRoot.h" +#endif + +#include "CachedHistory.h" + +namespace android { + +CachedHistory::CachedHistory() { + memset(this, 0, sizeof(CachedHistory)); // this assume the class has no virtuals + mLastMove = CachedFrame::UNINITIALIZED; + mPriorMove = CachedFrame::UNINITIALIZED; +} + + +void CachedHistory::addToVisited(const CachedNode* node, CachedFrame::Direction direction) +{ + memmove(&mVisited[1], &mVisited[0], sizeof(mVisited) - sizeof(mVisited[0])); + mVisited[0].mNode = node; + mVisited[0].mDirection = direction; +} + +bool CachedHistory::checkVisited(const CachedNode* node, CachedFrame::Direction direction) const +{ + // if the direction is unchanged and we've already visited this node, don't visit it again + int index = 0; + while (index < NAVIGATION_VISIT_DEPTH - 1) { + if (direction != mVisited[index].mDirection) + break; + index++; // compare with last direction, previous to last node (where the arrow took us from) + if (node == mVisited[index].mNode) + return false; + } + return true; +} + +void CachedHistory::pinMaxMin(const WebCore::IntRect& viewBounds) +{ + if (mMinWorkingHorizontal < viewBounds.y() || mMinWorkingHorizontal >= viewBounds.maxY()) + mMinWorkingHorizontal = viewBounds.y(); + if (mMaxWorkingHorizontal > viewBounds.maxY() || mMaxWorkingHorizontal <= viewBounds.y()) + mMaxWorkingHorizontal = viewBounds.maxY(); + if (mMinWorkingVertical < viewBounds.x() || mMinWorkingVertical >= viewBounds.maxX()) + mMinWorkingVertical = viewBounds.x(); + if (mMaxWorkingVertical > viewBounds.maxX() || mMaxWorkingVertical <= viewBounds.x()) + mMaxWorkingVertical = viewBounds.maxX(); +} + +void CachedHistory::reset() +{ + memset(mVisited, 0, sizeof(mVisited)); +// mLastScroll = 0; + mPriorBounds = WebCore::IntRect(0, 0, 0, 0); + mDirectionChange = false; + mDidFirstLayout = false; + mPriorMove = mLastMove = CachedFrame::UNINITIALIZED; + mMinWorkingHorizontal = mMinWorkingVertical = INT_MIN; + mMaxWorkingHorizontal = mMaxWorkingVertical = INT_MAX; +} + +void CachedHistory::setWorking(CachedFrame::Direction newMove, + const CachedFrame* cursorFrame, const CachedNode* cursor, + const WebCore::IntRect& viewBounds) +{ + CachedFrame::Direction lastAxis = (CachedFrame::Direction) (mLastMove & ~CachedFrame::RIGHT_DOWN); // up, left or uninitialized + CachedFrame::Direction newAxis = (CachedFrame::Direction) (newMove & ~CachedFrame::RIGHT_DOWN); + bool change = newAxis != lastAxis; + mDirectionChange = change && mLastMove != CachedFrame::UNINITIALIZED; + if (cursor != NULL || mLastMove != CachedFrame::UNINITIALIZED) { + mPriorMove = mLastMove; + mLastMove = newMove; + } + const WebCore::IntRect* navBounds = &mNavBounds; + if (cursor != NULL) { + WebCore::IntRect cursorBounds = cursor->bounds(cursorFrame); + if (cursorBounds.isEmpty() == false) + mNavBounds = cursorBounds; + } + if (change) { // uninitialized or change in direction + if (lastAxis != CachedFrame::LEFT && navBounds->height() > 0) { + mMinWorkingHorizontal = navBounds->y(); + mMaxWorkingHorizontal = navBounds->maxY(); + } + if (lastAxis != CachedFrame::UP && navBounds->width() > 0) { + mMinWorkingVertical = navBounds->x(); + mMaxWorkingVertical = navBounds->maxX(); + } + } + pinMaxMin(viewBounds); +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedHistory* CachedHistory::Debug::base() const { + CachedHistory* nav = (CachedHistory*) ((char*) this - OFFSETOF(CachedHistory, mDebug)); + return nav; +} + +const char* CachedHistory::Debug::direction(CachedFrame::Direction d) const +{ + switch (d) { + case CachedFrame::LEFT: return "LEFT"; break; + case CachedFrame::RIGHT: return "RIGHT"; break; + case CachedFrame::UP: return "UP"; break; + case CachedFrame::DOWN: return "DOWN"; break; + default: return "UNINITIALIZED"; + } +} + +void CachedHistory::Debug::print(CachedRoot* root) const +{ + CachedHistory* b = base(); + DUMP_NAV_LOGD("// Visited mVisited[]={\n"); + for (size_t i = 0; i < NAVIGATION_VISIT_DEPTH; i++) { + const Visited& visit = b->mVisited[i]; + const CachedNode* node = visit.mNode; + int index = root != NULL && root->CachedFrame::mDebug.validate(node) ? + node->index() : -1; + DUMP_NAV_LOGD(" // { 0x%p (%d), %s },\n", node, index, direction(visit.mDirection)); + } + DUMP_NAV_LOGD("// };\n"); +// DUMP_NAV_LOGD("// int mLastScroll=%d;\n", b->mLastScroll); + DEBUG_PRINT_RECT(mMouseBounds); + DEBUG_PRINT_RECT(mNavBounds); + DEBUG_PRINT_RECT(mPriorBounds); + DEBUG_PRINT_BOOL(mDirectionChange); + DEBUG_PRINT_BOOL(mDidFirstLayout); + DUMP_NAV_LOGD("// CachedFrame::Direction mLastMove=%s, mPriorMove=%s;\n", + direction(b->mLastMove), direction(b->mPriorMove)); + int max = b->mMaxWorkingHorizontal; + DUMP_NAV_LOGD("static int TEST_MAX_H = %d;\n", max); + int min = b->mMinWorkingHorizontal; + if (min == INT_MIN) + min++; + DUMP_NAV_LOGD("static int TEST_MIN_H = %d;\n", min); + max = b->mMaxWorkingVertical; + DUMP_NAV_LOGD("static int TEST_MAX_V = %d;\n", max); + min = b->mMinWorkingVertical; + if (min == INT_MIN) + min++; + DUMP_NAV_LOGD("static int TEST_MIN_V = %d;\n", min); + DUMP_NAV_LOGD("\n"); +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedHistory.h b/Source/WebKit/android/nav/CachedHistory.h new file mode 100644 index 0000000..96975ca --- /dev/null +++ b/Source/WebKit/android/nav/CachedHistory.h @@ -0,0 +1,89 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedHistory_h +#define CachedHistory_h + +#include "CachedFrame.h" + +#define NAVIGATION_VISIT_DEPTH 8 // the number of nodes last visited -- used to detect ping-ponging (number should be tuned) + +namespace android { + +class CachedRoot; + +// CachedHistory is maintained even if DOM is rebuilt by running script. +// It uses blind pointers for comparison in the previously visited nodes. +class CachedHistory { +public: + CachedHistory(); + void addToVisited(const CachedNode* , CachedFrame::Direction ); + bool checkVisited(const CachedNode* , CachedFrame::Direction ) const; + bool didFirstLayout() const { return mDidFirstLayout; } + bool directionChange() const { return mDirectionChange; } + int minWorkingHorizontal() const { return mMinWorkingHorizontal; } + int minWorkingVertical() const { return mMinWorkingVertical; } + int maxWorkingHorizontal() const { return mMaxWorkingHorizontal; } + int maxWorkingVertical() const { return mMaxWorkingVertical; } + const WebCore::IntRect& navBounds() const { return mNavBounds; } + const WebCore::IntRect& priorBounds() const { return mPriorBounds; } + void setDidFirstLayout(bool did) { mDidFirstLayout = did; } + void setMouseBounds(const WebCore::IntRect& loc) { mMouseBounds = loc; } + void setNavBounds(const WebCore::IntRect& loc) { mNavBounds = loc; } + void setWorking(CachedFrame::Direction , const CachedFrame* , + const CachedNode* , const WebCore::IntRect& viewBounds); + void reset(); +private: + void pinMaxMin(const WebCore::IntRect& viewBounds); + struct Visited { + const CachedNode* mNode; + CachedFrame::Direction mDirection; + } mVisited[NAVIGATION_VISIT_DEPTH]; + WebCore::IntRect mMouseBounds; // constricted bounds, if cursor ring is partially visible + WebCore::IntRect mNavBounds; // cursor ring bounds plus optional keystroke movement + WebCore::IntRect mPriorBounds; // prior chosen cursor ring (for reversing narrowing) + bool mDirectionChange; + bool mDidFirstLayout; // set true when page is newly laid out + CachedFrame::Direction mLastMove; + CachedFrame::Direction mPriorMove; + int mMinWorkingHorizontal; + int mMaxWorkingHorizontal; + int mMinWorkingVertical; + int mMaxWorkingVertical; + friend class CachedRoot; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedHistory* base() const; + const char* direction(CachedFrame::Direction d) const; + void print(CachedRoot* ) const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedInput.cpp b/Source/WebKit/android/nav/CachedInput.cpp new file mode 100644 index 0000000..a6a57ef --- /dev/null +++ b/Source/WebKit/android/nav/CachedInput.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" +#include "CachedInput.h" + +namespace android { + +void CachedInput::init() { + bzero(this, sizeof(CachedInput)); + mName = WTF::String(); +} + +void CachedInput::setTypeFromElement(WebCore::HTMLInputElement* element) +{ + ASSERT(element); + + if (element->isPasswordField()) + mType = PASSWORD; + else if (element->isSearchField()) + mType = SEARCH; + else if (element->isEmailField()) + mType = EMAIL; + else if (element->isNumberField()) + mType = NUMBER; + else if (element->isTelephoneField()) + mType = TELEPHONE; + else if (element->isURLField()) + mType = URL; + else + mType = NORMAL_TEXT_FIELD; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +CachedInput* CachedInput::Debug::base() const { + CachedInput* nav = (CachedInput*) ((char*) this - OFFSETOF(CachedInput, mDebug)); + return nav; +} + +static void printWebCoreString(const char* label, + const WTF::String& string) { + char scratch[256]; + size_t index = snprintf(scratch, sizeof(scratch), label); + const UChar* ch = string.characters(); + while (ch && *ch && index < sizeof(scratch)) { + UChar c = *ch++; + if (c < ' ' || c >= 0x7f) c = ' '; + scratch[index++] = c; + } + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); +} + +void CachedInput::Debug::print() const +{ + CachedInput* b = base(); + DEBUG_PRINT_BOOL(mAutoComplete); + DUMP_NAV_LOGD("// void* mForm=%p;\n", b->mForm); + printWebCoreString("// char* mName=\"", b->mName); + DUMP_NAV_LOGD("// int mMaxLength=%d;\n", b->mMaxLength); + DUMP_NAV_LOGD("// int mPaddingLeft=%d;\n", b->mPaddingLeft); + DUMP_NAV_LOGD("// int mPaddingTop=%d;\n", b->mPaddingTop); + DUMP_NAV_LOGD("// int mPaddingRight=%d;\n", b->mPaddingRight); + DUMP_NAV_LOGD("// int mPaddingBottom=%d;\n", b->mPaddingBottom); + DUMP_NAV_LOGD("// float mTextSize=%f;\n", b->mTextSize); + DUMP_NAV_LOGD("// int mLineHeight=%d;\n", b->mLineHeight); + DUMP_NAV_LOGD("// Type mType=%d;\n", b->mType); + DEBUG_PRINT_BOOL(mIsRtlText); + DEBUG_PRINT_BOOL(mIsTextField); + DEBUG_PRINT_BOOL(mIsTextArea); +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedInput.h b/Source/WebKit/android/nav/CachedInput.h new file mode 100644 index 0000000..77ae57b --- /dev/null +++ b/Source/WebKit/android/nav/CachedInput.h @@ -0,0 +1,115 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedInput_h +#define CachedInput_h + +#include "CachedDebug.h" +#include "HTMLInputElement.h" +#include "PlatformString.h" + +namespace android { + +class CachedInput { +public: + CachedInput() { + // Initiaized to 0 in its array, so nothing to do in the + // constructor + } + + enum Type { + NONE = -1, + NORMAL_TEXT_FIELD = 0, + TEXT_AREA = 1, + PASSWORD = 2, + SEARCH = 3, + EMAIL = 4, + NUMBER = 5, + TELEPHONE = 6, + URL = 7 + }; + + bool autoComplete() const { return mAutoComplete; } + void* formPointer() const { return mForm; } + void init(); + void setTypeFromElement(WebCore::HTMLInputElement*); + Type getType() const { return mType; } + bool isRtlText() const { return mIsRtlText; } + bool isTextField() const { return mIsTextField; } + bool isTextArea() const { return mIsTextArea; } + int lineHeight() const { return mLineHeight; } + int maxLength() const { return mMaxLength; }; + const WTF::String& name() const { return mName; } + int paddingBottom() const { return mPaddingBottom; } + int paddingLeft() const { return mPaddingLeft; } + int paddingRight() const { return mPaddingRight; } + int paddingTop() const { return mPaddingTop; } + void setAutoComplete(bool autoComplete) { mAutoComplete = autoComplete; } + void setFormPointer(void* form) { mForm = form; } + void setIsRtlText(bool isRtlText) { mIsRtlText = isRtlText; } + void setIsTextField(bool isTextField) { mIsTextField = isTextField; } + void setIsTextArea(bool isTextArea) { mIsTextArea = isTextArea; } + void setLineHeight(int height) { mLineHeight = height; } + void setMaxLength(int maxLength) { mMaxLength = maxLength; } + void setName(const WTF::String& name) { mName = name; } + void setPaddingBottom(int bottom) { mPaddingBottom = bottom; } + void setPaddingLeft(int left) { mPaddingLeft = left; } + void setPaddingRight(int right) { mPaddingRight = right; } + void setPaddingTop(int top) { mPaddingTop = top; } + void setSpellcheck(bool spellcheck) { mSpellcheck = spellcheck; } + void setTextSize(float textSize) { mTextSize = textSize; } + bool spellcheck() const { return mSpellcheck; } + float textSize() const { return mTextSize; } + +private: + + void* mForm; + int mLineHeight; + int mMaxLength; + WTF::String mName; + int mPaddingBottom; + int mPaddingLeft; + int mPaddingRight; + int mPaddingTop; + float mTextSize; + Type mType; + bool mAutoComplete : 1; + bool mSpellcheck : 1; + bool mIsRtlText : 1; + bool mIsTextField : 1; + bool mIsTextArea : 1; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedInput* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedLayer.cpp b/Source/WebKit/android/nav/CachedLayer.cpp new file mode 100644 index 0000000..f6dfb88 --- /dev/null +++ b/Source/WebKit/android/nav/CachedLayer.cpp @@ -0,0 +1,221 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" + +#include "CachedLayer.h" +#include "FloatRect.h" +#include "LayerAndroid.h" + +namespace android { + +#if USE(ACCELERATED_COMPOSITING) + +IntRect CachedLayer::adjustBounds(const LayerAndroid* root, + const IntRect& bounds) const +{ + const LayerAndroid* aLayer = layer(root); + if (!aLayer) { + DBG_NAV_LOGD("no layer in root=%p uniqueId=%d", root, mUniqueId); +#if DUMP_NAV_CACHE + if (root) + mDebug.printRootLayerAndroid(root); +#endif + return bounds; + } + FloatRect temp = bounds; + // First, remove the original offset from the bounds. + temp.move(-mOffset.x(), -mOffset.y()); + + // Next, add in the new position of the layer (could be different due to a + // fixed position layer). + FloatPoint position = getGlobalPosition(aLayer); + temp.move(position.x(), position.y()); + + // Add in any layer translation. + // FIXME: Should use bounds() and apply the entire transformation matrix. + const FloatPoint& translation = aLayer->translation(); + temp.move(translation.x(), translation.y()); + + SkRect clip; + aLayer->bounds(&clip); + + // Do not try to traverse the parent chain if this is the root as the parent + // will not be a LayerAndroid. + if (aLayer != root) { + LayerAndroid* parent = static_cast<LayerAndroid*>(aLayer->getParent()); + while (parent) { + SkRect pClip; + parent->bounds(&pClip); + + // Move our position into our parent's coordinate space. + clip.offset(pClip.fLeft, pClip.fTop); + // Clip our visible rectangle to the parent. + clip.intersect(pClip); + + // Stop at the root. + if (parent == root) + break; + parent = static_cast<LayerAndroid*>(parent->getParent()); + } + } + + // Intersect the result with the visible clip. + temp.intersect(clip); + + IntRect result = enclosingIntRect(temp); + + DBG_NAV_LOGV("root=%p aLayer=%p [%d]" + " bounds=(%d,%d,w=%d,h=%d) trans=(%g,%g) pos=(%f,%f)" + " offset=(%d,%d)" + " result=(%d,%d,w=%d,h=%d)", + root, aLayer, aLayer->uniqueId(), + bounds.x(), bounds.y(), bounds.width(), bounds.height(), + translation.x(), translation.y(), position.x(), position.y(), + mOffset.x(), mOffset.y(), + result.x(), result.y(), result.width(), result.height()); + return result; +} + +FloatPoint CachedLayer::getGlobalPosition(const LayerAndroid* aLayer) const +{ + SkPoint result = aLayer->getPosition(); + const Layer* parent = aLayer->getParent(); + while (parent) { + result += parent->getPosition(); + DBG_NAV_LOGV("result=(%g,%g) parent=%p [%d]", result.fX, result.fY, + parent, ((LayerAndroid*) parent)->uniqueId()); + parent = parent->getParent(); + } + return result; +} + +const LayerAndroid* CachedLayer::layer(const LayerAndroid* root) const +{ + if (!root) + return 0; + return root->findById(mUniqueId); +} + +// return bounds relative to the layer as recorded when walking the dom +IntRect CachedLayer::localBounds(const LayerAndroid* root, + const IntRect& bounds) const +{ + IntRect temp = bounds; + // Remove the original offset from the bounds. + temp.move(-mOffset.x(), -mOffset.y()); + +#if DEBUG_NAV_UI + const LayerAndroid* aLayer = layer(root); + DBG_NAV_LOGD("aLayer=%p [%d] bounds=(%d,%d,w=%d,h=%d) offset=(%d,%d)" + " result=(%d,%d,w=%d,h=%d)", + aLayer, aLayer ? aLayer->uniqueId() : 0, + bounds.x(), bounds.y(), bounds.width(), bounds.height(), + mOffset.x(), mOffset.y(), + temp.x(), temp.y(), temp.width(), temp.height()); +#endif + + return temp; +} + +SkPicture* CachedLayer::picture(const LayerAndroid* root) const +{ + const LayerAndroid* aLayer = layer(root); + if (!aLayer) + return 0; + DBG_NAV_LOGD("root=%p aLayer=%p [%d] picture=%p", + root, aLayer, aLayer->uniqueId(), aLayer->picture()); + return aLayer->picture(); +} + +void CachedLayer::toLocal(const LayerAndroid* root, int* xPtr, int* yPtr) const +{ + const LayerAndroid* aLayer = layer(root); + if (!aLayer) + return; + DBG_NAV_LOGD("root=%p aLayer=%p [%d]", root, aLayer, aLayer->uniqueId()); + SkRect localBounds; + aLayer->bounds(&localBounds); + *xPtr -= localBounds.fLeft; + *yPtr -= localBounds.fTop; +} + +#if DUMP_NAV_CACHE + +CachedLayer* CachedLayer::Debug::base() const { + return (CachedLayer*) ((char*) this - OFFSETOF(CachedLayer, mDebug)); +} + +void CachedLayer::Debug::print() const +{ + CachedLayer* b = base(); + DUMP_NAV_LOGD(" // int mCachedNodeIndex=%d;\n", b->mCachedNodeIndex); + DUMP_NAV_LOGD(" // int mOffset=(%d, %d);\n", + b->mOffset.x(), b->mOffset.y()); + DUMP_NAV_LOGD(" // int mUniqueId=%p;\n", b->mUniqueId); + DUMP_NAV_LOGD("%s\n", ""); +} + +#endif + +#if DUMP_NAV_CACHE + +int CachedLayer::Debug::spaces; + +void CachedLayer::Debug::printLayerAndroid(const LayerAndroid* layer) +{ + ++spaces; + SkRect bounds; + layer->bounds(&bounds); + DBG_NAV_LOGD("%.*s layer=%p [%d] (%g,%g,%g,%g)" + " position=(%g,%g) translation=(%g,%g) anchor=(%g,%g)" + " matrix=(%g,%g) childMatrix=(%g,%g) picture=%p clipped=%s" + " scrollable=%s\n", + spaces, " ", layer, layer->uniqueId(), + bounds.fLeft, bounds.fTop, bounds.width(), bounds.height(), + layer->getPosition().fX, layer->getPosition().fY, + layer->translation().x(), layer->translation().y(), + layer->getAnchorPoint().fX, layer->getAnchorPoint().fY, + layer->getMatrix().getTranslateX(), layer->getMatrix().getTranslateY(), + layer->getChildrenMatrix().getTranslateX(), + layer->getChildrenMatrix().getTranslateY(), + layer->picture(), layer->m_haveClip ? "true" : "false", + layer->contentIsScrollable() ? "true" : "false"); + for (int i = 0; i < layer->countChildren(); i++) + printLayerAndroid(layer->getChild(i)); + --spaces; +} + +void CachedLayer::Debug::printRootLayerAndroid(const LayerAndroid* layer) +{ + spaces = 0; + printLayerAndroid(layer); +} +#endif + +#endif // USE(ACCELERATED_COMPOSITING) + +} + diff --git a/Source/WebKit/android/nav/CachedLayer.h b/Source/WebKit/android/nav/CachedLayer.h new file mode 100644 index 0000000..fa427d2 --- /dev/null +++ b/Source/WebKit/android/nav/CachedLayer.h @@ -0,0 +1,86 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedLayer_h +#define CachedLayer_h + +#include "CachedDebug.h" +#include "IntRect.h" + +class SkPicture; + +namespace WebCore { + class FloatPoint; + class LayerAndroid; +} + +using namespace WebCore; + +namespace android { + +class CachedLayer { +public: +#if USE(ACCELERATED_COMPOSITING) + bool operator<(const CachedLayer& l) const { + return mCachedNodeIndex < l.mCachedNodeIndex; + } + // FIXME: adjustBounds should be renamed globalBounds or toGlobal + IntRect adjustBounds(const LayerAndroid* root, const IntRect& bounds) const; + int cachedNodeIndex() const { return mCachedNodeIndex; } + FloatPoint getGlobalPosition(const LayerAndroid* ) const; + const LayerAndroid* layer(const LayerAndroid* root) const; + IntRect localBounds(const LayerAndroid* root, const IntRect& bounds) const; + SkPicture* picture(const LayerAndroid* root) const; + void toLocal(const LayerAndroid* root, int* xPtr, int* yPtr) const; + void setCachedNodeIndex(int index) { mCachedNodeIndex = index; } + // Set the global position of the layer. This is recorded by the nav cache + // and corresponds to RenderLayer::absoluteBoundingBox() which is in + // document coordinates. This can be different from the global position of + // the layer if the layer is fixed positioned or scrollable. + void setOffset(const IntPoint& offset) { mOffset = offset; } + void setUniqueId(int uniqueId) { mUniqueId = uniqueId; } + int uniqueId() const { return mUniqueId; } +private: + int mCachedNodeIndex; + IntPoint mOffset; + int mUniqueId; + +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedLayer* base() const; + void print() const; + static void printLayerAndroid(const LayerAndroid* ); + static void printRootLayerAndroid(const LayerAndroid* ); + static int spaces; + } mDebug; +#endif +#endif // USE(ACCELERATED_COMPOSITING) +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedNode.cpp b/Source/WebKit/android/nav/CachedNode.cpp new file mode 100644 index 0000000..e500875 --- /dev/null +++ b/Source/WebKit/android/nav/CachedNode.cpp @@ -0,0 +1,431 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" +#include "android_graphics.h" +#include "CachedFrame.h" +#include "CachedHistory.h" +#include "Node.h" +#include "PlatformString.h" + +#include "CachedNode.h" + +namespace android { + +WebCore::IntRect CachedNode::bounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->adjustBounds(this, mBounds) : mBounds; +} + +void CachedNode::clearCursor(CachedFrame* parent) +{ + if (isFrame()) { + CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this)); + child->clearCursor(); + } + mIsCursor = false; +} + +bool CachedNode::Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner, + WTF::Vector<WebCore::IntRect>* rings) +{ + if (outer.contains(*inner)) + return true; +// DBG_NAV_LOGD("outer:{%d,%d,%d,%d} does not contain inner:{%d,%d,%d,%d}", +// outer.x(), outer.y(), outer.width(), outer.height(), +// inner->x(), inner->y(), inner->width(), inner->height()); + bool intersects = outer.intersects(*inner); + size_t size = intersects ? rings->size() : 0; + *inner = WebCore::IntRect(0, 0, 0, 0); + if (intersects) { + WebCore::IntRect * const start = rings->begin(); + WebCore::IntRect* ring = start + size - 1; + do { + ring->intersect(outer); + if (ring->isEmpty()) { + if ((size_t) (ring - start) != --size) + *ring = start[size]; + } else + inner->unite(*ring); + } while (ring-- != start); + } + rings->shrink(size); +// DBG_NAV_LOGD("size:%d", size); + return size != 0; +} + +bool CachedNode::clip(const WebCore::IntRect& bounds) +{ + return Clip(bounds, &mBounds, &mCursorRing); +} + + +void CachedNode::cursorRings(const CachedFrame* frame, + WTF::Vector<WebCore::IntRect>* rings) const +{ + rings->clear(); + for (unsigned index = 0; index < mCursorRing.size(); index++) + rings->append(ring(frame, index)); +} + +WebCore::IntRect CachedNode::cursorRingBounds(const CachedFrame* frame) const +{ + int partMax = navableRects(); + WebCore::IntRect bounds; + for (int partIndex = 0; partIndex < partMax; partIndex++) + bounds.unite(mCursorRing[partIndex]); + bounds.inflate(CURSOR_RING_HIT_TEST_RADIUS); + return mIsInLayer ? frame->adjustBounds(this, bounds) : bounds; +} + +#define OVERLAP 3 + +void CachedNode::fixUpCursorRects(const CachedFrame* frame) +{ + if (mFixedUpCursorRects) + return; + mFixedUpCursorRects = true; + // if the hit-test rect doesn't intersect any other rect, use it + if (mHitBounds != mBounds && mHitBounds.contains(mBounds) && + frame->checkRings(this, mHitBounds)) { + DBG_NAV_LOGD("use mHitBounds (%d,%d,%d,%d)", mHitBounds.x(), + mHitBounds.y(), mHitBounds.width(), mHitBounds.height()); + mUseHitBounds = true; + return; + } + if (navableRects() <= 1) + return; + // if there is more than 1 rect, and the bounds doesn't intersect + // any other cursor ring bounds, use it + IntRect sloppyBounds = mBounds; + sloppyBounds.inflate(2); // give it a couple of extra pixels + if (frame->checkRings(this, sloppyBounds)) { + DBG_NAV_LOGD("use mBounds (%d,%d,%d,%d)", mBounds.x(), + mBounds.y(), mBounds.width(), mBounds.height()); + mUseBounds = true; + return; + } +#if DEBUG_NAV_UI + { + WebCore::IntRect* boundsPtr = mCursorRing.begin() - 1; + const WebCore::IntRect* const boundsEnd = mCursorRing.begin() + mCursorRing.size(); + while (++boundsPtr < boundsEnd) + LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, boundsPtr - mCursorRing.begin(), + boundsPtr->x(), boundsPtr->y(), boundsPtr->width(), boundsPtr->height()); + } +#endif + // q: need to know when rects are for drawing and hit-testing, but not mouse down calcs? + bool again; + do { + again = false; + size_t size = mCursorRing.size(); + WebCore::IntRect* unitBoundsPtr = mCursorRing.begin() - 1; + const WebCore::IntRect* const unitBoundsEnd = mCursorRing.begin() + size; + while (++unitBoundsPtr < unitBoundsEnd) { + // any other unitBounds to the left or right of this one? + int unitTop = unitBoundsPtr->y(); + int unitBottom = unitBoundsPtr->maxY(); + int unitLeft = unitBoundsPtr->x(); + int unitRight = unitBoundsPtr->maxX(); + WebCore::IntRect* testBoundsPtr = mCursorRing.begin() - 1; + while (++testBoundsPtr < unitBoundsEnd) { + if (unitBoundsPtr == testBoundsPtr) + continue; + int testTop = testBoundsPtr->y(); + int testBottom = testBoundsPtr->maxY(); + int testLeft = testBoundsPtr->x(); + int testRight = testBoundsPtr->maxX(); + int candidateTop = unitTop > testTop ? unitTop : testTop; + int candidateBottom = unitBottom < testBottom ? unitBottom : testBottom; + int candidateLeft = unitRight < testLeft ? unitRight : testRight; + int candidateRight = unitRight > testLeft ? unitLeft : testLeft; + bool leftRight = true; + if (candidateTop + OVERLAP >= candidateBottom || + candidateLeft + OVERLAP >= candidateRight) { + candidateTop = unitBottom < testTop ? unitBottom : testBottom; + candidateBottom = unitBottom > testTop ? unitTop : testTop; + candidateLeft = unitLeft > testLeft ? unitLeft : testLeft; + candidateRight = unitRight < testRight ? unitRight : testRight; + if (candidateTop + OVERLAP >= candidateBottom || + candidateLeft + OVERLAP >= candidateRight) + continue; + leftRight = false; + } + // construct candidate to add + WebCore::IntRect candidate = WebCore::IntRect(candidateLeft, candidateTop, + candidateRight - candidateLeft, candidateBottom - candidateTop); + // does a different unit bounds intersect the candidate? if so, don't add + WebCore::IntRect* checkBoundsPtr = mCursorRing.begin() - 1; + while (++checkBoundsPtr < unitBoundsEnd) { + if (checkBoundsPtr->intersects(candidate) == false) + continue; + if (leftRight) { + if (candidateTop >= checkBoundsPtr->y() && + candidateBottom > checkBoundsPtr->maxY()) + candidateTop = checkBoundsPtr->maxY(); + else if (candidateTop < checkBoundsPtr->y() && + candidateBottom <= checkBoundsPtr->maxY()) + candidateBottom = checkBoundsPtr->y(); + else + goto nextCheck; + } else { + if (candidateLeft >= checkBoundsPtr->x() && + candidateRight > checkBoundsPtr->maxX()) + candidateLeft = checkBoundsPtr->maxX(); + else if (candidateLeft < checkBoundsPtr->x() && + candidateRight <= checkBoundsPtr->maxX()) + candidateRight = checkBoundsPtr->x(); + else + goto nextCheck; + } + } + candidate = WebCore::IntRect(candidateLeft, candidateTop, + candidateRight - candidateLeft, candidateBottom - candidateTop); + ASSERT(candidate.isEmpty() == false); +#if DEBUG_NAV_UI + LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, mCursorRing.size(), + candidate.x(), candidate.y(), candidate.width(), candidate.height()); +#endif + mCursorRing.append(candidate); + again = true; + goto tryAgain; + nextCheck: + continue; + } + } +tryAgain: + ; + } while (again); +} + + +void CachedNode::hideCursor(CachedFrame* parent) +{ + if (isFrame()) { + CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this)); + child->hideCursor(); + } + mIsHidden = true; +} + +WebCore::IntRect CachedNode::hitBounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->adjustBounds(this, mHitBounds) : mHitBounds; +} + +void CachedNode::init(WebCore::Node* node) +{ + bzero(this, sizeof(CachedNode)); + mExport = WTF::String(); + mNode = node; + mParentIndex = mDataIndex = -1; + mType = android::NORMAL_CACHEDNODETYPE; +} + +bool CachedNode::isTextField(const CachedFrame* frame) const +{ + const CachedInput* input = frame->textInput(this); + return input ? input->isTextField() : false; +} + +void CachedNode::localCursorRings(const CachedFrame* frame, + WTF::Vector<WebCore::IntRect>* rings) const +{ + rings->clear(); + for (unsigned index = 0; index < mCursorRing.size(); index++) + rings->append(localRing(frame, index)); +} + +WebCore::IntRect CachedNode::localBounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->localBounds(this, mBounds) : mBounds; +} + +WebCore::IntRect CachedNode::localHitBounds(const CachedFrame* frame) const +{ + return mIsInLayer ? frame->localBounds(this, mHitBounds) : mHitBounds; +} + +WebCore::IntRect CachedNode::localRing(const CachedFrame* frame, + size_t part) const +{ + const WebCore::IntRect& rect = mCursorRing.at(part); + return mIsInLayer ? frame->localBounds(this, rect) : rect; +} + +void CachedNode::move(int x, int y) +{ + mBounds.move(x, y); + // mHitTestBounds will be moved by caller + WebCore::IntRect* first = mCursorRing.begin(); + WebCore::IntRect* last = first + mCursorRing.size(); + --first; + while (++first != last) + first->move(x, y); +} + +bool CachedNode::partRectsContains(const CachedNode* other) const +{ + int outerIndex = 0; + int outerMax = navableRects(); + int innerMax = other->navableRects(); + do { + const WebCore::IntRect& outerBounds = mCursorRing[outerIndex]; + int innerIndex = 0; + do { + const WebCore::IntRect& innerBounds = other->mCursorRing[innerIndex]; + if (innerBounds.contains(outerBounds)) + return true; + } while (++innerIndex < innerMax); + } while (++outerIndex < outerMax); + return false; +} + +WebCore::IntRect CachedNode::ring(const CachedFrame* frame, size_t part) const +{ + const WebCore::IntRect& rect = mCursorRing.at(part); + return mIsInLayer ? frame->adjustBounds(this, rect) : rect; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedNode* CachedNode::Debug::base() const { + CachedNode* nav = (CachedNode*) ((char*) this - OFFSETOF(CachedNode, mDebug)); + return nav; +} + +const char* CachedNode::Debug::condition(Condition t) const +{ + switch (t) { + case NOT_REJECTED: return "NOT_REJECTED"; break; + case BUTTED_UP: return "BUTTED_UP"; break; + case CENTER_FURTHER: return "CENTER_FURTHER"; break; + case CLOSER: return "CLOSER"; break; + case CLOSER_IN_CURSOR: return "CLOSER_IN_CURSOR"; break; + case CLOSER_OVERLAP: return "CLOSER_OVERLAP"; break; + case CLOSER_TOP: return "CLOSER_TOP"; break; + case NAVABLE: return "NAVABLE"; break; + case FURTHER: return "FURTHER"; break; + case IN_UMBRA: return "IN_UMBRA"; break; + case IN_WORKING: return "IN_WORKING"; break; + case LEFTMOST: return "LEFTMOST"; break; + case OVERLAP_OR_EDGE_FURTHER: return "OVERLAP_OR_EDGE_FURTHER"; break; + case PREFERRED: return "PREFERRED"; break; + case ANCHOR_IN_ANCHOR: return "ANCHOR_IN_ANCHOR"; break; + case BEST_DIRECTION: return "BEST_DIRECTION"; break; + case CHILD: return "CHILD"; break; + case DISABLED: return "DISABLED"; break; + case HIGHER_TAB_INDEX: return "HIGHER_TAB_INDEX"; break; + case IN_CURSOR: return "IN_CURSOR"; break; + case IN_CURSOR_CHILDREN: return "IN_CURSOR_CHILDREN"; break; + case NOT_ENCLOSING_CURSOR: return "NOT_ENCLOSING_CURSOR"; break; + case NOT_CURSOR_NODE: return "NOT_CURSOR_NODE"; break; + case OUTSIDE_OF_BEST: return "OUTSIDE_OF_BEST"; break; + case OUTSIDE_OF_ORIGINAL: return "OUTSIDE_OF_ORIGINAL"; break; + default: return "???"; + } +} + +const char* CachedNode::Debug::type(android::CachedNodeType t) const +{ + switch (t) { + case NORMAL_CACHEDNODETYPE: return "NORMAL"; break; + case ADDRESS_CACHEDNODETYPE: return "ADDRESS"; break; + case EMAIL_CACHEDNODETYPE: return "EMAIL"; break; + case PHONE_CACHEDNODETYPE: return "PHONE"; break; + case ANCHOR_CACHEDNODETYPE: return "ANCHOR"; break; + case AREA_CACHEDNODETYPE: return "AREA"; break; + case FRAME_CACHEDNODETYPE: return "FRAME"; break; + case PLUGIN_CACHEDNODETYPE: return "PLUGIN"; break; + case TEXT_INPUT_CACHEDNODETYPE: return "INPUT"; break; + case SELECT_CACHEDNODETYPE: return "SELECT"; break; + case CONTENT_EDITABLE_CACHEDNODETYPE: return "CONTENT_EDITABLE"; break; + default: return "???"; + } +} + +void CachedNode::Debug::print() const +{ + CachedNode* b = base(); + char scratch[256]; + size_t index = snprintf(scratch, sizeof(scratch), "// char* mExport=\""); + const UChar* ch = b->mExport.characters(); + while (ch && *ch && index < sizeof(scratch)) { + UChar c = *ch++; + if (c < ' ' || c >= 0x7f) c = ' '; + scratch[index++] = c; + } + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); + DEBUG_PRINT_RECT(mBounds); + DEBUG_PRINT_RECT(mHitBounds); + DEBUG_PRINT_RECT(mOriginalAbsoluteBounds); + const WTF::Vector<WebCore::IntRect>* rects = &b->mCursorRing; + size_t size = rects->size(); + DUMP_NAV_LOGD("// IntRect cursorRings={ // size=%d\n", size); + for (size_t i = 0; i < size; i++) { + const WebCore::IntRect& rect = (*rects)[i]; + DUMP_NAV_LOGD(" // {%d, %d, %d, %d}, // %d\n", rect.x(), rect.y(), + rect.width(), rect.height(), i); + } + DUMP_NAV_LOGD("// };\n"); + DUMP_NAV_LOGD("// void* mNode=%p; // (%d) \n", b->mNode, mNodeIndex); + DUMP_NAV_LOGD("// void* mParentGroup=%p; // (%d) \n", b->mParentGroup, mParentGroupIndex); + DUMP_NAV_LOGD("// int mDataIndex=%d;\n", b->mDataIndex); + DUMP_NAV_LOGD("// int mIndex=%d;\n", b->mIndex); + DUMP_NAV_LOGD("// int navableRects()=%d;\n", b->navableRects()); + DUMP_NAV_LOGD("// int mParentIndex=%d;\n", b->mParentIndex); + DUMP_NAV_LOGD("// int mTabIndex=%d;\n", b->mTabIndex); + DUMP_NAV_LOGD("// int mColorIndex=%d;\n", b->mColorIndex); + DUMP_NAV_LOGD("// Condition mCondition=%s;\n", condition(b->mCondition)); + DUMP_NAV_LOGD("// Type mType=%s;\n", type(b->mType)); + DEBUG_PRINT_BOOL(mClippedOut); + DEBUG_PRINT_BOOL(mDisabled); + DEBUG_PRINT_BOOL(mFixedUpCursorRects); + DEBUG_PRINT_BOOL(mHasCursorRing); + DEBUG_PRINT_BOOL(mHasMouseOver); + DEBUG_PRINT_BOOL(mIsCursor); + DEBUG_PRINT_BOOL(mIsFocus); + DEBUG_PRINT_BOOL(mIsHidden); + DEBUG_PRINT_BOOL(mIsInLayer); + DEBUG_PRINT_BOOL(mIsParentAnchor); + DEBUG_PRINT_BOOL(mIsTransparent); + DEBUG_PRINT_BOOL(mIsUnclipped); + DEBUG_PRINT_BOOL(mLast); + DEBUG_PRINT_BOOL(mUseBounds); + DEBUG_PRINT_BOOL(mUseHitBounds); + DEBUG_PRINT_BOOL(mSingleImage); +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedNode.h b/Source/WebKit/android/nav/CachedNode.h new file mode 100644 index 0000000..321b7fd --- /dev/null +++ b/Source/WebKit/android/nav/CachedNode.h @@ -0,0 +1,245 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedNode_h +#define CachedNode_h + +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" + +#include <wtf/Vector.h> +#include <wtf/text/AtomicString.h> + +class SkPicture; + +namespace WebCore { + class Node; +} + +namespace android { + +class CachedFrame; +class CachedRoot; + +class CachedNode { +public: +// Nodes are rejected because either they are spacially not the best (first set) +// or because they have the wrong DOM attribute (in focus, a focused child, etc) +// findClosest() gives only spacially rejected nodes a second chance + enum Condition { // if bigger than 32, increase bitfield size below + // rejections that get a second chance + NOT_REJECTED = 0, + SECOND_CHANCE_START = NOT_REJECTED, // must be first in list + BUTTED_UP, + CENTER_FURTHER, + CLOSER, + CLOSER_IN_CURSOR, + CLOSER_OVERLAP, + CLOSER_TOP, + NAVABLE, + FURTHER, + IN_UMBRA, + IN_WORKING, + LEFTMOST, + NOT_ENCLOSING_CURSOR, + OVERLAP_OR_EDGE_FURTHER, + PREFERRED, // better overlap measure + SECOND_CHANCE_END = PREFERRED, // must be last in list + // rejections that don't get a second chance + ANCHOR_IN_ANCHOR, + BEST_DIRECTION, // can be reached by another direction + CHILD, + DISABLED, + HIGHER_TAB_INDEX, + IN_CURSOR, + IN_CURSOR_CHILDREN, + NOT_CURSOR_NODE, + OUTSIDE_OF_BEST, // containership + OUTSIDE_OF_ORIGINAL, // containership + UNDER_LAYER, + CONDITION_SIZE // FIXME: test that CONDITION_SIZE fits in mCondition + }; + CachedNode() { + // The node is initiaized to 0 in its array, so nothing to do in the + // constructor + } + + WebCore::IntRect bounds(const CachedFrame* ) const; + int childFrameIndex() const { return isFrame() ? mDataIndex : -1; } + void clearCondition() const { mCondition = NOT_REJECTED; } + void clearCursor(CachedFrame* ); + static bool Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner, + WTF::Vector<WebCore::IntRect>* rings); + bool clip(const WebCore::IntRect& ); + bool clippedOut() { return mClippedOut; } + int colorIndex() const { return mColorIndex; } + WebCore::IntRect cursorRingBounds(const CachedFrame* ) const; + void cursorRings(const CachedFrame* , WTF::Vector<WebCore::IntRect>* ) const; + bool disabled() const { return mDisabled; } + const CachedNode* document() const { return &this[-mIndex]; } + void fixUpCursorRects(const CachedFrame* frame); + const WTF::String& getExport() const { return mExport; } + bool hasCursorRing() const { return mHasCursorRing; } + bool hasMouseOver() const { return mHasMouseOver; } + void hideCursor(CachedFrame* ); + WebCore::IntRect hitBounds(const CachedFrame* ) const; + int index() const { return mIndex; } + void init(WebCore::Node* node); + bool isAnchor() const { return mType == ANCHOR_CACHEDNODETYPE; } + bool isContentEditable() const { return mType == CONTENT_EDITABLE_CACHEDNODETYPE; } + bool isCursor() const { return mIsCursor; } + bool isArea() const { return mType == AREA_CACHEDNODETYPE; } + bool isFocus() const { return mIsFocus; } + bool isFrame() const { return mType == FRAME_CACHEDNODETYPE; } + bool isHidden() const { return mIsHidden; } + bool isInLayer() const { return mIsInLayer; } + bool isNavable(const CachedFrame* frame, const WebCore::IntRect& clip) const { + return clip.intersects(bounds(frame)); + } + bool isPlugin() const { return mType == PLUGIN_CACHEDNODETYPE; } + bool isSelect() const { return mType == SELECT_CACHEDNODETYPE; } + bool isSyntheticLink() const { + return mType >= ADDRESS_CACHEDNODETYPE && mType <= PHONE_CACHEDNODETYPE; + } + bool isTextField(const CachedFrame*) const; + bool isTextInput() const { return mType == TEXT_INPUT_CACHEDNODETYPE; } + bool isTransparent() const { return mIsTransparent; } + bool isUnclipped() const { return mIsUnclipped; } + // localXXX functions are used only for drawing cursor rings + WebCore::IntRect localBounds(const CachedFrame* ) const; + void localCursorRings(const CachedFrame* , + WTF::Vector<WebCore::IntRect>* ) const; + WebCore::IntRect localHitBounds(const CachedFrame* ) const; + WebCore::IntRect localRing(const CachedFrame* , size_t part) const; + void move(int x, int y); + int navableRects() const { return mCursorRing.size(); } + void* nodePointer() const { return mNode; } + bool noSecondChance() const { return mCondition > SECOND_CHANCE_END; } + const WebCore::IntRect& originalAbsoluteBounds() const { + return mOriginalAbsoluteBounds; } + const CachedNode* parent() const { return document() + mParentIndex; } + void* parentGroup() const { return mParentGroup; } + int parentIndex() const { return mParentIndex; } + bool partRectsContains(const CachedNode* other) const; + const WebCore::IntRect& rawBounds() const { return mBounds; } + void reset(); + WebCore::IntRect ring(const CachedFrame* , size_t part) const; + const WTF::Vector<WebCore::IntRect>& rings() const { return mCursorRing; } + void setBounds(const WebCore::IntRect& bounds) { mBounds = bounds; } + void setClippedOut(bool clipped) { mClippedOut = clipped; } + void setColorIndex(int index) { mColorIndex = index; } + void setCondition(Condition condition) const { mCondition = condition; } + void setDataIndex(int index) { mDataIndex = index; } + void setDisabled(bool disabled) { mDisabled = disabled; } + void setExport(const WTF::String& exported) { mExport = exported; } + void setHasCursorRing(bool hasRing) { mHasCursorRing = hasRing; } + void setHasMouseOver(bool hasMouseOver) { mHasMouseOver = hasMouseOver; } + void setHitBounds(const WebCore::IntRect& bounds) { mHitBounds = bounds; } + void setOriginalAbsoluteBounds(const WebCore::IntRect& bounds) { + mOriginalAbsoluteBounds = bounds; } + void setIndex(int index) { mIndex = index; } + void setIsCursor(bool isCursor) { mIsCursor = isCursor; } + void setIsFocus(bool isFocus) { mIsFocus = isFocus; } + void setIsInLayer(bool isInLayer) { mIsInLayer = isInLayer; } + void setIsParentAnchor(bool isAnchor) { mIsParentAnchor = isAnchor; } + void setIsTransparent(bool isTransparent) { mIsTransparent = isTransparent; } + void setIsUnclipped(bool unclipped) { mIsUnclipped = unclipped; } + void setLast() { mLast = true; } + void setParentGroup(void* group) { mParentGroup = group; } + void setParentIndex(int parent) { mParentIndex = parent; } + void setSingleImage(bool single) { mSingleImage = single; } + void setTabIndex(int index) { mTabIndex = index; } + void setType(CachedNodeType type) { mType = type; } + void show() { mIsHidden = false; } + bool singleImage() const { return mSingleImage; } + int tabIndex() const { return mTabIndex; } + int textInputIndex() const { return isTextInput() ? mDataIndex : -1; } + const CachedNode* traverseNextNode() const { return mLast ? NULL : &this[1]; } + bool useBounds() const { return mUseBounds; } + bool useHitBounds() const { return mUseHitBounds; } + bool wantsKeyEvents() const { return isTextInput() || isPlugin() + || isContentEditable() || isFrame(); } +private: + friend class CacheBuilder; + WTF::String mExport; + WebCore::IntRect mBounds; + WebCore::IntRect mHitBounds; + WebCore::IntRect mOriginalAbsoluteBounds; + WTF::Vector<WebCore::IntRect> mCursorRing; + void* mNode; // WebCore::Node*, only used to match pointers + void* mParentGroup; // WebCore::Node*, only used to match pointers + int mDataIndex; // child frame if a frame; input data index; or -1 + int mIndex; // index of itself, to find first in array (document) + int mParentIndex; + int mTabIndex; + int mColorIndex; // index to ring color and other stylable properties + mutable Condition mCondition : 5; // why the node was not chosen on the first pass + CachedNodeType mType : 4; + bool mClippedOut : 1; + bool mDisabled : 1; + bool mFixedUpCursorRects : 1; + bool mHasCursorRing : 1; + bool mHasMouseOver : 1; + bool mIsCursor : 1; + bool mIsFocus : 1; + bool mIsHidden : 1; + bool mIsInLayer : 1; + bool mIsParentAnchor : 1; + bool mIsTransparent : 1; + bool mIsUnclipped : 1; + bool mLast : 1; // true if this is the last node in a group + bool mSingleImage : 1; + bool mUseBounds : 1; + bool mUseHitBounds : 1; +#ifdef BROWSER_DEBUG +public: + WebCore::Node* webCoreNode() const { return (WebCore::Node*) mNode; } + bool mDisplayMeasure; + mutable bool mInCompare; + int mSideDistance; + int mSecondSide; +#endif +#if DEBUG_NAV_UI || DUMP_NAV_CACHE +public: + class Debug { +public: + CachedNode* base() const; + const char* condition(Condition t) const; + void print() const; + const char* type(CachedNodeType t) const; +#if DUMP_NAV_CACHE + int mNodeIndex; + int mParentGroupIndex; +#endif + } mDebug; + friend class CachedNode::Debug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedNodeType.h b/Source/WebKit/android/nav/CachedNodeType.h new file mode 100644 index 0000000..f922946 --- /dev/null +++ b/Source/WebKit/android/nav/CachedNodeType.h @@ -0,0 +1,56 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedNodeType_h +#define CachedNodeType_h + +namespace android { + +enum CachedNodeType { + NORMAL_CACHEDNODETYPE, + ADDRESS_CACHEDNODETYPE, + EMAIL_CACHEDNODETYPE, + PHONE_CACHEDNODETYPE, + ANCHOR_CACHEDNODETYPE, + AREA_CACHEDNODETYPE, + FRAME_CACHEDNODETYPE, + PLUGIN_CACHEDNODETYPE, + TEXT_INPUT_CACHEDNODETYPE, + SELECT_CACHEDNODETYPE, + CONTENT_EDITABLE_CACHEDNODETYPE +}; + +enum CachedNodeBits { + NORMAL_CACHEDNODE_BITS = 0, + ADDRESS_CACHEDNODE_BIT = 1 << (ADDRESS_CACHEDNODETYPE - 1), + EMAIL_CACHEDNODE_BIT = 1 << (EMAIL_CACHEDNODETYPE - 1), + PHONE_CACHEDNODE_BIT = 1 << (PHONE_CACHEDNODETYPE - 1), + ALL_CACHEDNODE_BITS = ADDRESS_CACHEDNODE_BIT | EMAIL_CACHEDNODE_BIT + | PHONE_CACHEDNODE_BIT +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/CachedPrefix.h b/Source/WebKit/android/nav/CachedPrefix.h new file mode 100644 index 0000000..576aa4a --- /dev/null +++ b/Source/WebKit/android/nav/CachedPrefix.h @@ -0,0 +1,53 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedPrefix_h +#define CachedPrefix_h + +#ifndef LOG_TAG +#define LOG_TAG "navcache" +#endif + +#include "config.h" +#include "CachedDebug.h" + +#ifndef _LIBS_CUTILS_LOG_H + #ifdef LOG + #undef LOG + #endif + + #include <utils/Log.h> +#endif + +#define OFFSETOF(type, field) ((char*)&(((type*)1)->field) - (char*)1) // avoids gnu warning + +#ifndef BZERO_DEFINED +#define BZERO_DEFINED +// http://www.opengroup.org/onlinepubs/000095399/functions/bzero.html +// For maximum portability, it is recommended to replace the function call to bzero() as follows: +#define bzero(b,len) (memset((b), '\0', (len)), (void) 0) +#endif + +#endif diff --git a/Source/WebKit/android/nav/CachedRoot.cpp b/Source/WebKit/android/nav/CachedRoot.cpp new file mode 100644 index 0000000..2371c4f --- /dev/null +++ b/Source/WebKit/android/nav/CachedRoot.cpp @@ -0,0 +1,1815 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CachedPrefix.h" +#include "android_graphics.h" +#include "CachedHistory.h" +#include "CachedInput.h" +#include "CachedLayer.h" +#include "CachedNode.h" +#include "FindCanvas.h" +#include "FloatRect.h" +#include "LayerAndroid.h" +#include "ParseCanvas.h" +#include "SkBitmap.h" +#include "SkBounder.h" +#include "SkPixelRef.h" +#include "SkRegion.h" + +#include "CachedRoot.h" + +#if DEBUG_NAV_UI +#include "wtf/text/CString.h" +#endif + +#define DONT_CENTER_IF_ALREADY_VISIBLE + +using std::min; +using std::max; + +#ifdef DUMP_NAV_CACHE_USING_PRINTF + extern android::Mutex gWriteLogMutex; +#endif + +namespace android { + +class CommonCheck : public SkBounder { +public: + enum Type { + kNo_Type, + kDrawBitmap_Type, + kDrawGlyph_Type, + kDrawPaint_Type, + kDrawPath_Type, + kDrawPicture_Type, + kDrawPoints_Type, + kDrawPosText_Type, + kDrawPosTextH_Type, + kDrawRect_Type, + kDrawSprite_Type, + kDrawText_Type, + kDrawTextOnPath_Type, + kPopLayer_Type, + kPushLayer_Type, + kPushSave_Type + }; + + static bool isTextType(Type t) { + return t == kDrawPosTextH_Type || t == kDrawText_Type; + } + + CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) { + setEmpty(); + } + + bool doRect(Type type) { + mType = type; + return doIRect(mUnion); + } + + bool isEmpty() { return mUnion.isEmpty(); } + + bool joinGlyphs(const SkIRect& rect) { + bool isGlyph = mType == kDrawGlyph_Type; + if (isGlyph) + mUnion.join(rect); + return isGlyph; + } + + void setAllOpaque(bool opaque) { mAllOpaque = opaque; } + void setEmpty() { mUnion.setEmpty(); } + void setIsOpaque(bool opaque) { mIsOpaque = opaque; } + void setType(Type type) { mType = type; } + + Type mType; + SkIRect mUnion; + bool mAllOpaque; + bool mIsOpaque; +}; + +#if DEBUG_NAV_UI + static const char* TypeNames[] = { + "kNo_Type", + "kDrawBitmap_Type", + "kDrawGlyph_Type", + "kDrawPaint_Type", + "kDrawPath_Type", + "kDrawPicture_Type", + "kDrawPoints_Type", + "kDrawPosText_Type", + "kDrawPosTextH_Type", + "kDrawRect_Type", + "kDrawSprite_Type", + "kDrawText_Type", + "kDrawTextOnPath_Type", + "kPopLayer_Type", + "kPushLayer_Type", + "kPushSave_Type" + }; +#endif + +#define kMargin 16 +#define kSlop 2 + +class BoundsCanvas : public ParseCanvas { +public: + + BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) { + mTransparentLayer = 0; + setBounder(bounder); + } + + virtual void drawPaint(const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPaint_Type); + INHERITED::drawPaint(paint); + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPoints_Type); + INHERITED::drawPoints(mode, count, pts, paint); + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawRect_Type); + INHERITED::drawRect(rect, paint); + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPath_Type); + INHERITED::drawPath(path, paint); + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& matrix, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawBitmap_Type); + mBounder.setIsOpaque(bitmap.isOpaque()); + INHERITED::commonDrawBitmap(bitmap, rect, matrix, paint); + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint) { + mBounder.setType(CommonCheck::kDrawSprite_Type); + mBounder.setIsOpaque(bitmap.isOpaque() && + (!paint || paint->getAlpha() == 255)); + INHERITED::drawSprite(bitmap, left, top, paint); + } + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawText(text, byteLength, x, y, paint); + mBounder.doRect(CommonCheck::kDrawText_Type); + } + + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawPosText(text, byteLength, pos, paint); + if (!mBounder.isEmpty()) + mBounder.doRect(CommonCheck::kDrawPosText_Type); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); + if (mBounder.mUnion.isEmpty()) { + DBG_NAV_LOGD("empty constY=%g", SkScalarToFloat(constY)); + return; + } + SkPaint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent}, + {xpos[0], constY + metrics.fDescent} }; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapPoints(upDown, 2); + if (upDown[0].fX == upDown[1].fX) { + mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY); + mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY); + } + mBounder.doRect(CommonCheck::kDrawPosTextH_Type); + } + + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint); + mBounder.doRect(CommonCheck::kDrawTextOnPath_Type); + } + + virtual void drawPicture(SkPicture& picture) { + mBounder.setType(CommonCheck::kDrawPicture_Type); + INHERITED::drawPicture(picture); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) { + int depth = INHERITED::saveLayer(bounds, paint, flags); + if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) { + mTransparentLayer = depth; + mBounder.setAllOpaque(false); + } + return depth; + } + + virtual void restore() { + mBounder.setType(CommonCheck::kDrawSprite_Type); // for layer draws + int depth = getSaveCount(); + if (depth == mTransparentLayer) { + mTransparentLayer = 0; + mBounder.setAllOpaque(true); + } + INHERITED::restore(); + } + + int mTransparentLayer; + CommonCheck& mBounder; +private: + typedef ParseCanvas INHERITED; +}; + +/* +LeftCheck examines the text in a picture, within a viewable rectangle, +and returns via left() the position of the left edge of the paragraph. +It first looks at the left edge of the test point, then looks above and below +it for more lines of text to determine the div's left edge. +*/ +class LeftCheck : public CommonCheck { +public: + LeftCheck(int x, int y) : mX(x), mY(y), mHitLeft(INT_MAX), + mMostLeft(INT_MAX) { + mHit.set(x - (HIT_SLOP << 1), y - HIT_SLOP, x, y + HIT_SLOP); + mPartial.setEmpty(); + mBounds.setEmpty(); + mPartialType = kNo_Type; + } + + int left() { + if (isTextType(mType)) + doRect(); // process the final line of text + return mMostLeft != INT_MAX ? mMostLeft : mX >> 1; + } + + // FIXME: this is identical to CenterCheck::onIRect() + // refactor so that LeftCheck and CenterCheck inherit common functions + virtual bool onIRect(const SkIRect& rect) { + bool opaqueBitmap = mType == kDrawBitmap_Type && mIsOpaque; + if (opaqueBitmap && rect.contains(mX, mY)) { + mMostLeft = rect.fLeft; + return false; + } + if (joinGlyphs(rect)) // assembles glyphs into a text string + return false; + if (!isTextType(mType) && !opaqueBitmap) + return false; + /* Text on one line may be broken into several parts. Reassemble + the text into a rectangle before considering it. */ + if (rect.fTop < mPartial.fBottom + && rect.fBottom > mPartial.fTop + && mPartial.fRight + JOIN_SLOP_X >= rect.fLeft + && (mPartialType != kDrawBitmap_Type + || mPartial.height() <= rect.height() + JOIN_SLOP_Y)) { + DBG_NAV_LOGD("LeftCheck join mPartial=(%d, %d, %d, %d)" + " rect=(%d, %d, %d, %d)", + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + mPartial.join(rect); + return false; + } + if (mPartial.isEmpty() == false) { + doRect(); // process the previous line of text +#if DEBUG_NAV_UI + if (mHitLeft == INT_MAX) + DBG_NAV_LOGD("LeftCheck disabled rect=(%d, %d, %d, %d)", + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); +#endif + } + mPartial = rect; + mPartialType = mType; + return false; + } + + void doRect() + { + /* Record the outer bounds of the lines of text that intersect the + touch coordinates, given some slop */ + if (SkIRect::Intersects(mPartial, mHit)) { + if (mHitLeft > mPartial.fLeft) + mHitLeft = mPartial.fLeft; + DBG_NAV_LOGD("LeftCheck mHitLeft=%d", mHitLeft); + } else if (mHitLeft == INT_MAX) + return; // wait for intersect success + /* If text is too far away vertically, don't consider it */ + if (!mBounds.isEmpty() && (mPartial.fTop > mBounds.fBottom + HIT_SLOP + || mPartial.fBottom < mBounds.fTop - HIT_SLOP)) { + DBG_NAV_LOGD("LeftCheck stop mPartial=(%d, %d, %d, %d)" + " mBounds=(%d, %d, %d, %d)", + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, + mBounds.fLeft, mBounds.fTop, mBounds.fRight, mBounds.fBottom); + mHitLeft = INT_MAX; // and disable future comparisons + return; + } + /* If the considered text is completely to the left or right of the + touch coordinates, skip it, turn off further detection */ + if (mPartial.fLeft > mX || mPartial.fRight < mX) { + DBG_NAV_LOGD("LeftCheck stop mX=%d mPartial=(%d, %d, %d, %d)", mX, + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom); + mHitLeft = INT_MAX; + return; + } + /* record the smallest margins on the left and right */ + if (mMostLeft > mPartial.fLeft) { + DBG_NAV_LOGD("LeftCheck new mMostLeft=%d (old=%d)", mPartial.fLeft, + mMostLeft); + mMostLeft = mPartial.fLeft; + } + if (mBounds.isEmpty()) + mBounds = mPartial; + else if (mPartial.fBottom > mBounds.fBottom) { + DBG_NAV_LOGD("LeftCheck new bottom=%d (old=%d)", mPartial.fBottom, + mBounds.fBottom); + mBounds.fBottom = mPartial.fBottom; + } + } + + static const int JOIN_SLOP_X = 30; // horizontal space between text parts + static const int JOIN_SLOP_Y = 5; // vertical space between text lines + static const int HIT_SLOP = 30; // diameter allowing for tap size + /* const */ SkIRect mHit; // sloppy hit rectangle + SkIRect mBounds; // reference bounds + SkIRect mPartial; // accumulated text bounds, per line + const int mX; // touch location + const int mY; + int mHitLeft; // touched text extremes + int mMostLeft; // paragraph extremes + Type mPartialType; +}; + +/* +CenterCheck examines the text in a picture, within a viewable rectangle, +and returns via center() the optimal amount to scroll in x to display the +paragraph of text. + +The caller of CenterCheck has configured (but not allocated) a bitmap +the height and three times the width of the view. The picture is drawn centered +in the bitmap, so text that would be revealed, if the view was scrolled up to +a view-width to the left or right, is considered. +*/ +class CenterCheck : public CommonCheck { +public: + CenterCheck(int x, int y, int width) : mX(x), mY(y), + mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX), + mViewLeft(width), mViewRight(width << 1) { + mHit.set(x - CENTER_SLOP, y - CENTER_SLOP, + x + CENTER_SLOP, y + CENTER_SLOP); + mPartial.setEmpty(); + } + + int center() { + doRect(); // process the final line of text + /* If the touch coordinates aren't near any text, return 0 */ + if (mHitLeft == mHitRight) { + DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft); + return 0; + } + int leftOver = mHitLeft - mViewLeft; + int rightOver = mHitRight - mViewRight; + int center; + /* If the touched text is too large to entirely fit on the screen, + center it. */ + if (leftOver < 0 && rightOver > 0) { + center = (leftOver + rightOver) >> 1; + DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d", + leftOver, rightOver, center); + return center; + } + center = (mMostLeft + mMostRight) >> 1; // the paragraph center + if (leftOver > 0 && rightOver >= 0) { // off to the right + if (center > mMostLeft) // move to center loses left-most text? + center = mMostLeft; + } else if (rightOver < 0 && leftOver <= 0) { // off to the left + if (center < mMostRight) // move to center loses right-most text? + center = mMostRight; + } else { +#ifdef DONT_CENTER_IF_ALREADY_VISIBLE + center = 0; // paragraph is already fully visible +#endif + } + DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d", + leftOver, rightOver, center); + return center; + } + +protected: + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) // assembles glyphs into a text string + return false; + if (!isTextType(mType)) + return false; + /* Text on one line may be broken into several parts. Reassemble + the text into a rectangle before considering it. */ + if (rect.fTop < mPartial.fBottom && rect.fBottom > + mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) { + DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)", + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + mPartial.join(rect); + return false; + } + if (mPartial.isEmpty() == false) + doRect(); // process the previous line of text + mPartial = rect; + return false; + } + + void doRect() + { + /* Record the outer bounds of the lines of text that was 'hit' by the + touch coordinates, given some slop */ + if (SkIRect::Intersects(mPartial, mHit)) { + if (mHitLeft > mPartial.fLeft) + mHitLeft = mPartial.fLeft; + if (mHitRight < mPartial.fRight) + mHitRight = mPartial.fRight; + DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight); + } + /* If the considered text is completely to the left or right of the + touch coordinates, skip it */ + if (mPartial.fLeft > mX || mPartial.fRight < mX) + return; + int leftOver = mPartial.fLeft - mViewLeft; + int rightOver = mPartial.fRight - mViewRight; + /* If leftOver <= 0, the text starts off the screen. + If rightOver >= 0, the text ends off the screen. + */ + if (leftOver <= 0 && rightOver >= 0) // discard wider than screen + return; +#ifdef DONT_CENTER_IF_ALREADY_VISIBLE + if (leftOver > 0 && rightOver < 0) // discard already visible + return; +#endif + /* record the smallest margins on the left and right */ + if (mMostLeft > leftOver) + mMostLeft = leftOver; + if (mMostRight < rightOver) + mMostRight = rightOver; + DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d", + leftOver, rightOver, mMostLeft, mMostRight); + } + + static const int CENTER_SLOP = 10; // space between text parts and lines + /* const */ SkIRect mHit; // sloppy hit rectangle + SkIRect mPartial; // accumulated text bounds, per line + const int mX; // touch location + const int mY; + int mHitLeft; // touched text extremes + int mHitRight; + int mMostLeft; // paragraph extremes + int mMostRight; + const int mViewLeft; // middle third of 3x-wide view + const int mViewRight; +}; + +class ImageCanvas : public ParseCanvas { +public: + ImageCanvas(SkBounder* bounder) : mURI(NULL) { + setBounder(bounder); + } + + const char* getURI() { return mURI; } + +protected: +// Currently webkit's bitmap draws always seem to be cull'd before this entry +// point is called, so we assume that any bitmap that gets here is inside our +// tiny clip (may not be true in the future) + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& , const SkPaint& ) { + SkPixelRef* pixelRef = bitmap.pixelRef(); + if (pixelRef != NULL) { + mURI = pixelRef->getURI(); + } + } + +private: + const char* mURI; +}; + +class ImageCheck : public SkBounder { +public: + virtual bool onIRect(const SkIRect& rect) { + return false; + } +}; + +class JiggleCheck : public CommonCheck { +public: + JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) { + mMaxJiggle = 0; + mMinX = mMinJiggle = abs(delta); + mMaxWidth = width + mMinX; + } + + int jiggle() { + if (mMinJiggle > mMaxJiggle) + return mDelta; + int avg = (mMinJiggle + mMaxJiggle + 1) >> 1; + return mDelta < 0 ? -avg : avg; + } + + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) + return false; + if (mType != kDrawBitmap_Type && !isTextType(mType)) + return false; + int min, max; + if (mDelta < 0) { + min = mMinX - rect.fLeft; + max = mMaxWidth - rect.fRight; + } else { + min = rect.fRight - mMaxX; + max = rect.fLeft; + } + if (min <= 0) + return false; + if (max >= mMinX) + return false; + if (mMinJiggle > min) + mMinJiggle = min; + if (mMaxJiggle < max) + mMaxJiggle = max; + return false; + } + + int mDelta; + int mMaxJiggle; + int mMaxX; + int mMinJiggle; + int mMinX; + int mMaxWidth; +}; + +class RingCheck : public CommonCheck { +public: + RingCheck(const WTF::Vector<WebCore::IntRect>& rings, + const WebCore::IntRect& bitBounds, const WebCore::IntRect& testBounds, + bool singleImage) + : mTestBounds(testBounds) + , mBitBounds(bitBounds) + , mPushPop(false) + , mSingleImage(singleImage) + { + const WebCore::IntRect* r; + for (r = rings.begin(); r != rings.end(); r++) { + SkIRect fatter = {r->x(), r->y(), r->maxX(), r->maxY()}; + fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS); + DBG_NAV_LOGD("RingCheck fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop, + fatter.fRight, fatter.fBottom); + mTextSlop.op(fatter, SkRegion::kUnion_Op); + mTextTest.op(*r, SkRegion::kUnion_Op); + } + int dx = -bitBounds.x(); + int dy = -bitBounds.y(); + DBG_NAV_LOGD("RingCheck translate=(%d,%d)", dx, dy); + mTextSlop.translate(dx, dy); + mTextTest.translate(dx, dy); + mTestBounds.translate(dx, dy); + mEmpty.setEmpty(); + } + + bool hiddenRings(SkRegion* clipped) + { + findBestLayer(); + if (!mBestLayer) { + DBG_NAV_LOG("RingCheck empty"); + clipped->setEmpty(); + return true; + } + const SkRegion* layersEnd = mLayers.end(); + const Type* layerTypes = &mLayerTypes[mBestLayer - mLayers.begin()]; + bool collectGlyphs = true; + bool collectOvers = false; + SkRegion over; + for (const SkRegion* layers = mBestLayer; layers != layersEnd; layers++) { + Type layerType = *layerTypes++; + DBG_NAV_LOGD("RingCheck #%d %s (%d,%d,r=%d,b=%d)", + layers - mLayers.begin(), TypeNames[layerType], + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom); + if (collectGlyphs && (layerType == kDrawGlyph_Type + || ((layerType == kDrawRect_Type && mTextTest.contains(*layers)) + || (layerType == kDrawBitmap_Type && mTextSlop.contains(*layers))))) { + DBG_NAV_LOGD("RingCheck #%d collectOvers", layers - mLayers.begin()); + collectOvers = true; + clipped->op(*layers, SkRegion::kUnion_Op); + continue; + } + collectGlyphs &= layerType != kPushLayer_Type; + if (collectOvers && (layerType == kDrawRect_Type + || layerType == kDrawBitmap_Type + || (!collectGlyphs && layerType == kDrawSprite_Type))) { + DBG_NAV_LOGD("RingCheck #%d over.op", layers - mLayers.begin()); + over.op(*layers, SkRegion::kUnion_Op); + } + } + bool result = !collectOvers || clipped->intersects(over); + const SkIRect t = clipped->getBounds(); + const SkIRect o = over.getBounds(); + clipped->op(over, SkRegion::kDifference_Op); + clipped->translate(mBitBounds.x(), mBitBounds.y()); + const SkIRect c = clipped->getBounds(); + DBG_NAV_LOGD("RingCheck intersects=%s text=(%d,%d,r=%d,b=%d)" + " over=(%d,%d,r=%d,b=%d) clipped=(%d,%d,r=%d,b=%d)", + result ? "true" : "false", + t.fLeft, t.fTop, t.fRight, t.fBottom, + o.fLeft, o.fTop, o.fRight, o.fBottom, + c.fLeft, c.fTop, c.fRight, c.fBottom); + return result; + } + + void push(Type type, const SkIRect& bounds) + { +#if DEBUG_NAV_UI + // this caches the push string and subquently ignores if pushSave + // is immediately followed by popLayer. Push/pop pairs happen + // frequently and just add noise to the log. + static String lastLog; + String currentLog = String("RingCheck append #") + + String::number(mLayers.size()) + + " type=" + TypeNames[type] + " bounds=(" + + String::number(bounds.fLeft) + + "," + String::number(bounds.fTop) + "," + + String::number(bounds.fRight) + "," + + String::number(bounds.fBottom) + ")"; + if (lastLog.length() == 0 || type != kPopLayer_Type) { + if (lastLog.length() != 0) + DBG_NAV_LOGD("%s", lastLog.latin1().data()); + if (type == kPushSave_Type) + lastLog = currentLog; + else + DBG_NAV_LOGD("%s", currentLog.latin1().data()); + } else + lastLog = ""; +#endif + popEmpty(); + mPushPop |= type >= kPopLayer_Type; + if (type == kPopLayer_Type) { + Type last = mLayerTypes.last(); + // remove empty brackets + if (last == kPushLayer_Type || last == kPushSave_Type) { + mLayers.removeLast(); + mLayerTypes.removeLast(); + return; + } + // remove push/pop from push/bitmap/pop + size_t pushIndex = mLayerTypes.size() - 2; + if (last == kDrawBitmap_Type + && mLayerTypes.at(pushIndex) == kPushLayer_Type) { + mLayers.at(pushIndex) = mLayers.last(); + mLayerTypes.at(pushIndex) = kDrawBitmap_Type; + mLayers.removeLast(); + mLayerTypes.removeLast(); + return; + } + // remove non-layer brackets + int stack = 0; + Type* types = mLayerTypes.end(); + while (types != mLayerTypes.begin()) { + Type type = *--types; + if (type == kPopLayer_Type) { + stack++; + continue; + } + if (type != kPushLayer_Type && type != kPushSave_Type) + continue; + if (--stack >= 0) + continue; + if (type == kPushLayer_Type) + break; + int remove = types - mLayerTypes.begin(); + DBG_NAV_LOGD("RingCheck remove=%d mLayers.size=%d" + " mLayerTypes.size=%d", remove, mLayers.size(), + mLayerTypes.size()); + mLayers.remove(remove); + mLayerTypes.remove(remove); + mAppendLikeTypes = false; + return; + } + } + mLayers.append(bounds); + mLayerTypes.append(type); + } + + void startText(const SkPaint& paint) + { + mPaint = &paint; + if (!mLayerTypes.isEmpty() && mLayerTypes.last() == kDrawGlyph_Type + && !mLayers.last().isEmpty()) { + push(kDrawGlyph_Type, mEmpty); + } + } + + bool textOutsideRings() + { + findBestLayer(); + if (!mBestLayer) { + DBG_NAV_LOG("RingCheck empty"); + return false; + } + const SkRegion* layers = mBestLayer; + const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; + // back up to include text drawn before the best layer + SkRegion active = SkRegion(mBitBounds); + active.translate(-mBitBounds.x(), -mBitBounds.y()); + while (layers != mLayers.begin()) { + --layers; + Type layerType = *--layerTypes; + DBG_NAV_LOGD("RingCheck #%d %s" + " mTestBounds=(%d,%d,r=%d,b=%d) layers=(%d,%d,r=%d,b=%d)" + " active=(%d,%d,r=%d,b=%d)", + layers - mLayers.begin(), TypeNames[layerType], + mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, + mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom, + active.getBounds().fLeft, active.getBounds().fTop, + active.getBounds().fRight, active.getBounds().fBottom); + if (layerType == kDrawRect_Type || layerType == kDrawBitmap_Type) { + SkRegion temp = *layers; + temp.op(mTestBounds, SkRegion::kIntersect_Op); + active.op(temp, SkRegion::kDifference_Op); + if (active.isEmpty()) { + DBG_NAV_LOGD("RingCheck #%d empty", layers - mLayers.begin()); + break; + } + } else if (layerType == kDrawGlyph_Type) { + SkRegion temp = *layers; + temp.op(active, SkRegion::kIntersect_Op); + if (!mTestBounds.intersects(temp)) + continue; + if (!mTestBounds.contains(temp)) + return false; + } else + break; + } + layers = mBestLayer; + layerTypes = &mLayerTypes[layers - mLayers.begin()]; + bool foundGlyph = false; + bool collectGlyphs = true; + do { + Type layerType = *layerTypes++; + DBG_NAV_LOGD("RingCheck #%d %s mTestBounds=(%d,%d,r=%d,b=%d)" + " layers=(%d,%d,r=%d,b=%d) collects=%s intersects=%s contains=%s", + layers - mLayers.begin(), TypeNames[layerType], + mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, + mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom, + collectGlyphs ? "true" : "false", + mTestBounds.intersects(*layers) ? "true" : "false", + mTextSlop.contains(*layers) ? "true" : "false"); + if (collectGlyphs && layerType == kDrawGlyph_Type) { + if (!mTestBounds.intersects(*layers)) + continue; + if (!mTextSlop.contains(*layers)) + return false; + foundGlyph = true; + } + collectGlyphs &= layerType != kPushLayer_Type; + } while (++layers != mLayers.end()); + DBG_NAV_LOGD("RingCheck foundGlyph=%s", foundGlyph ? "true" : "false"); + return foundGlyph; + } + +protected: + virtual bool onIRect(const SkIRect& rect) + { + joinGlyphs(rect); + if (mType != kDrawGlyph_Type && mType != kDrawRect_Type + && mType != kDrawSprite_Type && mType != kDrawBitmap_Type) + return false; + if (mLayerTypes.isEmpty() || mLayerTypes.last() != mType + || !mAppendLikeTypes || mPushPop || mSingleImage + // if the last and current were not glyphs, + // and the two bounds have a gap between, don't join them -- push + // an empty between them + || (mType != kDrawGlyph_Type && !joinable(rect))) { + push(mType, mEmpty); + } + DBG_NAV_LOGD("RingCheck join %s (%d,%d,r=%d,b=%d) '%c'", + TypeNames[mType], rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + mCh); + mLayers.last().op(rect, SkRegion::kUnion_Op); + mAppendLikeTypes = true; + mPushPop = false; + return false; + } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + mCh = ' '; + if (mPaint) { + SkUnichar unichar; + SkPaint utfPaint = *mPaint; + utfPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + utfPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar); + mCh = unichar < 0x7f ? unichar : '?'; + } + return onIRect(rect); + } + +private: + int calcOverlap(SkRegion& testRegion) + { + if (testRegion.isEmpty()) + return INT_MAX; + testRegion.op(mTextTest, SkRegion::kXOR_Op); + SkRegion::Iterator iter(testRegion); + int area = 0; + while (!iter.done()) { + const SkIRect& cr = iter.rect(); + area += cr.width() * cr.height(); + iter.next(); + } + DBG_NAV_LOGD("RingCheck area=%d", area); + return area; + } + + void findBestLayer() + { + popEmpty(); + mBestLayer = 0; + const SkRegion* layers = mLayers.begin(); + const SkRegion* layersEnd = mLayers.end(); + if (layers == layersEnd) { + DBG_NAV_LOG("RingCheck empty"); + return; + } + // find text most like focus rings by xoring found with original + int bestArea = INT_MAX; + const SkRegion* testLayer = 0; + SkRegion testRegion; + const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; + for (; layers != mLayers.end(); layers++) { + Type layerType = *layerTypes++; +#if DEBUG_NAV_UI + const SkIRect& gb = layers->getBounds(); + const SkIRect& tb = mTextSlop.getBounds(); + DBG_NAV_LOGD("RingCheck #%d %s mTextSlop=(%d,%d,%d,%d)" + " contains=%s bounds=(%d,%d,%d,%d)", + layers - mLayers.begin(), TypeNames[layerType], + tb.fLeft, tb.fTop, tb.fRight, tb.fBottom, + mTextSlop.contains(*layers) ? "true" : "false", + gb.fLeft, gb.fTop, gb.fRight, gb.fBottom); +#endif + if (((layerType == kDrawGlyph_Type || layerType == kDrawBitmap_Type) + && mTextSlop.contains(*layers)) + || (layerType == kDrawRect_Type + && mTextTest.contains(*layers))) { + if (!testLayer) + testLayer = layers; + testRegion.op(*layers, SkRegion::kUnion_Op); + continue; + } + if (testLayer) { + int area = calcOverlap(testRegion); + if (bestArea > area) { + bestArea = area; + mBestLayer = testLayer; + } + DBG_NAV_LOGD("RingCheck #%d push test=%d best=%d", + layers - mLayers.begin(), testLayer - mLayers.begin(), + mBestLayer ? mBestLayer - mLayers.begin() : -1); + testRegion.setEmpty(); + testLayer = 0; + } + } + if (testLayer && bestArea > calcOverlap(testRegion)) { + DBG_NAV_LOGD("RingCheck last best=%d", testLayer - mLayers.begin()); + mBestLayer = testLayer; + } + } + + bool joinable(const SkIRect& rect) + { + SkRegion region = mLayers.last(); + if (!region.isRect()) + return false; + const SkIRect& bounds1 = region.getBounds(); + int area1 = bounds1.width() * bounds1.height(); + area1 += rect.width() * rect.height(); + region.op(rect, SkRegion::kUnion_Op); + const SkIRect& bounds2 = region.getBounds(); + int area2 = bounds2.width() * bounds2.height(); + return area2 <= area1; + } + + void popEmpty() + { + if (mLayerTypes.size() == 0) + return; + Type last = mLayerTypes.last(); + if (last >= kPopLayer_Type) + return; + const SkRegion& area = mLayers.last(); + if (!area.isEmpty()) + return; + DBG_NAV_LOGD("RingCheck #%d %s", mLayers.size() - 1, TypeNames[last]); + mLayers.removeLast(); + mLayerTypes.removeLast(); + } + + SkRegion mTestBounds; + IntRect mBitBounds; + SkIRect mEmpty; + const SkRegion* mBestLayer; + SkRegion mTextSlop; // outset rects for inclusion test + SkRegion mTextTest; // exact rects for xor area test + Type mLastType; + Vector<SkRegion> mLayers; + Vector<Type> mLayerTypes; + const SkPaint* mPaint; + char mCh; + bool mAppendLikeTypes; + bool mPushPop; + bool mSingleImage; +}; + +class RingCanvas : public BoundsCanvas { +public: + RingCanvas(RingCheck* bounder) + : INHERITED(bounder) + { + } + +protected: + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawText(text, byteLength, x, y, paint); + } + + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawPosText(text, byteLength, pos, paint); + } + + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); + } + + virtual int save(SaveFlags flags) + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPushSave_Type, getTotalClip().getBounds()); + return INHERITED::save(flags); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPushLayer_Type, getTotalClip().getBounds()); + return INHERITED::save(flags); + } + + virtual void restore() + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPopLayer_Type, getTotalClip().getBounds()); + INHERITED::restore(); + } + +private: + typedef BoundsCanvas INHERITED; +}; + +bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction, + WebCore::IntPoint* scrollPtr, bool findClosest) +{ + WebCore::IntRect newOutset; + const CachedNode* newNode = best->mNode; + // see if there's a middle node + // if the middle node is in the visited list, + // or if none was computed and the newNode is in the visited list, + // treat result as NULL + if (newNode != NULL && findClosest) { + if (best->bounds().intersects(mHistory->mPriorBounds) == false && + checkBetween(best, direction)) + newNode = best->mNode; + if (findClosest && maskIfHidden(best)) { + innerMove(document(), best, direction, scrollPtr, false); + return true; + } + newOutset = newNode->cursorRingBounds(best->mFrame); + } + int delta; + bool newNodeInView = scrollDelta(newOutset, direction, &delta); + if (delta && scrollPtr && (newNode == NULL || newNodeInView == false || + (best->mNavOutside && best->mWorkingOutside))) + *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta, + direction & UP_DOWN ? delta : 0); + return false; +} + +void CachedRoot::calcBitBounds(const IntRect& nodeBounds, IntRect* bitBounds) const +{ + IntRect contentBounds = IntRect(0, 0, mPicture->width(), mPicture->height()); + IntRect overBounds = nodeBounds; + overBounds.inflate(kMargin); + IntRect viewableBounds = mScrolledBounds; + viewableBounds.unite(mViewBounds); + *bitBounds = contentBounds; + bitBounds->intersect(overBounds); + if (!bitBounds->intersects(viewableBounds)) + *bitBounds = IntRect(0, 0, 0, 0); + DBG_NAV_LOGD("contentBounds=(%d,%d,r=%d,b=%d) overBounds=(%d,%d,r=%d,b=%d)" + " mScrolledBounds=(%d,%d,r=%d,b=%d) mViewBounds=(%d,%d,r=%d,b=%d)" + " bitBounds=(%d,%d,r=%d,b=%d)", + contentBounds.x(), contentBounds.y(), contentBounds.maxX(), + contentBounds.maxY(), + overBounds.x(), overBounds.y(), overBounds.maxX(), overBounds.maxY(), + mScrolledBounds.x(), mScrolledBounds.y(), mScrolledBounds.maxX(), + mScrolledBounds.maxY(), + mViewBounds.x(), mViewBounds.y(), mViewBounds.maxX(), + mViewBounds.maxY(), + bitBounds->x(), bitBounds->y(), bitBounds->maxX(), + bitBounds->maxY()); +} + + +int CachedRoot::checkForCenter(int x, int y) const +{ + int width = mViewBounds.width(); + SkPicture* picture = pictureAt(&x, &y); + CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(), + width); + BoundsCanvas checker(¢erCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3, + mViewBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(width - mViewBounds.x()), + SkIntToScalar(-mViewBounds.y())); + checker.drawPicture(*picture); + return centerCheck.center(); +} + +void CachedRoot::checkForJiggle(int* xDeltaPtr) const +{ + int xDelta = *xDeltaPtr; + JiggleCheck jiggleCheck(xDelta, mViewBounds.width()); + BoundsCanvas checker(&jiggleCheck); + SkBitmap bitmap; + int absDelta = abs(xDelta); + bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() + + absDelta, mViewBounds.height()); + checker.setBitmapDevice(bitmap); + int x = -mViewBounds.x() - (xDelta < 0 ? xDelta : 0); + int y = -mViewBounds.y(); + SkPicture* picture = pictureAt(&x, &y); + checker.translate(SkIntToScalar(x), SkIntToScalar(y)); + checker.drawPicture(*picture); + *xDeltaPtr = jiggleCheck.jiggle(); +} + +bool CachedRoot::checkRings(SkPicture* picture, const CachedNode* node, + const WebCore::IntRect& testBounds) const +{ + if (!picture) + return false; + const WTF::Vector<WebCore::IntRect>& rings = node->rings(); + const WebCore::IntRect& nodeBounds = node->rawBounds(); + IntRect bitBounds; + calcBitBounds(nodeBounds, &bitBounds); + RingCheck ringCheck(rings, bitBounds, testBounds, node->singleImage()); + RingCanvas checker(&ringCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), + bitBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(-bitBounds.x()), + SkIntToScalar(-bitBounds.y())); + checker.drawPicture(*picture); + bool result = ringCheck.textOutsideRings(); + DBG_NAV_LOGD("bitBounds=(%d,%d,r=%d,b=%d) nodeBounds=(%d,%d,r=%d,b=%d)" + " testBounds=(%d,%d,r=%d,b=%d) success=%s", + bitBounds.x(), bitBounds.y(), bitBounds.maxX(), bitBounds.maxY(), + nodeBounds.x(), nodeBounds.y(), nodeBounds.maxX(), nodeBounds.maxY(), + testBounds.x(), testBounds.y(), testBounds.maxX(), testBounds.maxY(), + result ? "true" : "false"); + return result; +} + +void CachedRoot::draw(FindCanvas& canvas) const +{ + canvas.setLayerId(-1); // overlays change the ID as their pictures draw + canvas.drawPicture(*mPicture); +#if USE(ACCELERATED_COMPOSITING) + if (!mRootLayer) + return; + canvas.drawLayers(mRootLayer); +#endif +} + +const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* x, int* y, bool checkForHidden) const +{ +#if DEBUG_NAV_UI + DBG_NAV_LOGD("rect=(%d,%d,w=%d,h=%d) xy=(%d,%d)", rect.x(), rect.y(), + rect.width(), rect.height(), *x, *y); +#if DUMP_NAV_CACHE + if (mRootLayer) CachedLayer::Debug::printRootLayerAndroid(mRootLayer); +#endif +#endif + int best = INT_MAX; + bool inside = false; + (const_cast<CachedRoot*>(this))->resetClippedOut(); + const CachedFrame* directHitFramePtr; + const CachedNode* directHit = NULL; + const CachedNode* node = findBestAt(rect, &best, &inside, &directHit, + &directHitFramePtr, framePtr, x, y, checkForHidden); + DBG_NAV_LOGD("node=%d (%p) xy=(%d,%d)", node == NULL ? 0 : node->index(), + node == NULL ? NULL : node->nodePointer(), *x, *y); + if (node == NULL) { + node = findBestHitAt(rect, framePtr, x, y); + DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), + node == NULL ? NULL : node->nodePointer()); + } + if (node == NULL) { + *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1), + rect.y() + (rect.height() >> 1)); + } + return node; +} + +WebCore::IntPoint CachedRoot::cursorLocation() const +{ + const WebCore::IntRect& bounds = mHistory->mNavBounds; + return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1), + bounds.y() + (bounds.height() >> 1)); +} + +WebCore::IntPoint CachedRoot::focusLocation() const +{ + return WebCore::IntPoint(mFocusBounds.x() + (mFocusBounds.width() >> 1), + mFocusBounds.y() + (mFocusBounds.height() >> 1)); +} + +// These reset the values because we only want to get the selection the first time. +// After that, the selection is no longer accurate. +int CachedRoot::getAndResetSelectionEnd() +{ + int end = mSelectionEnd; + mSelectionEnd = -1; + return end; +} + +int CachedRoot::getAndResetSelectionStart() +{ + int start = mSelectionStart; + mSelectionStart = -1; + return start; +} + +int CachedRoot::getBlockLeftEdge(int x, int y, float scale) const +{ + DBG_NAV_LOGD("x=%d y=%d scale=%g mViewBounds=(%d,%d,%d,%d)", x, y, scale, + mViewBounds.x(), mViewBounds.y(), mViewBounds.width(), + mViewBounds.height()); + // if (x, y) is in a textArea or textField, return that + const int slop = 1; + WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop, + slop * 2, slop * 2); + const CachedFrame* frame; + int fx, fy; + const CachedNode* node = findAt(rect, &frame, &fx, &fy, true); + if (node && node->wantsKeyEvents()) { + DBG_NAV_LOGD("x=%d (%s)", node->bounds(frame).x(), + node->isTextInput() ? "text" : "plugin"); + return node->bounds(frame).x(); + } + SkPicture* picture = node ? frame->picture(node, &x, &y) : pictureAt(&x, &y); + if (!picture) + return x; + int halfW = (int) (mViewBounds.width() * scale * 0.5f); + int fullW = halfW << 1; + int halfH = (int) (mViewBounds.height() * scale * 0.5f); + int fullH = halfH << 1; + LeftCheck leftCheck(fullW, halfH); + BoundsCanvas checker(&leftCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, fullW, fullH); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(fullW - x), SkIntToScalar(halfH - y)); + checker.drawPicture(*picture); + int result = x + leftCheck.left() - fullW; + DBG_NAV_LOGD("halfW=%d halfH=%d mMostLeft=%d x=%d", + halfW, halfH, leftCheck.mMostLeft, result); + return result; +} + +void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) const +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds; + int x = mouseBounds.x(); + int y = mouseBounds.y(); + int width = mouseBounds.width(); + int height = mouseBounds.height(); + point->setX(x + (width >> 1)); // default to box center + point->setY(y + (height >> 1)); +#if DEBUG_NAV_UI + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + DBG_NAV_LOGD("mHistory->mNavBounds={%d,%d,%d,%d} " + "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}", + navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(), + mouseBounds.x(), mouseBounds.y(), mouseBounds.width(), + mouseBounds.height(), point->x(), point->y()); +#endif +} + +void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history) +{ + CachedFrame::init(this, -1, frame); + reset(); + mHistory = history; + mPicture = NULL; +} + +bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingVertical() >= mViewBounds.x()); + ASSERT(maxWorkingVertical() <= mViewBounds.maxX()); + setupScrolledBounds(); + // (line up) + mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); + int testTop = mScrolledBounds.y(); + int viewBottom = mViewBounds.maxY(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.maxY() > viewBottom && viewBottom < mContents.height()) + return false; + if (navBounds.isEmpty() == false) { + int navTop = navBounds.y(); + int scrollBottom; + if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.maxY())) { + mScrolledBounds.setHeight(scrollBottom - navTop); + mScrolledBounds.setY(navTop); + } + } + setCursorCache(0, mMaxYScroll); + frameDown(test, NULL, bestData); + return true; +} + +bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingHorizontal() >= mViewBounds.y()); + ASSERT(maxWorkingHorizontal() <= mViewBounds.maxY()); + setupScrolledBounds(); + mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll); + mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); + int testRight = mScrolledBounds.maxX(); + int viewLeft = mViewBounds.x(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.x() < viewLeft && viewLeft > mContents.x()) + return false; + if (navBounds.isEmpty() == false) { + int navRight = navBounds.maxX(); + int scrollLeft; + if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x())) + mScrolledBounds.setWidth(navRight - scrollLeft); + } + setCursorCache(-mMaxXScroll, 0); + frameLeft(test, NULL, bestData); + return true; +} + + +void CachedRoot::innerMove(const CachedNode* node, BestData* bestData, + Direction direction, WebCore::IntPoint* scroll, bool firstCall) +{ + bestData->reset(); + bool outOfCursor = mCursorIndex == CURSOR_CLEARED; + DBG_NAV_LOGD("mHistory->didFirstLayout()=%s && mCursorIndex=%d", + mHistory->didFirstLayout() ? "true" : "false", mCursorIndex); + if (mHistory->didFirstLayout() && mCursorIndex < CURSOR_SET) { + mHistory->reset(); + outOfCursor = true; + } + const CachedFrame* cursorFrame; + const CachedNode* cursor = currentCursor(&cursorFrame); + mHistory->setWorking(direction, cursorFrame, cursor, mViewBounds); + bool findClosest = false; + if (mScrollOnly == false) { + switch (direction) { + case LEFT: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.maxX(), + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerLeft(node, bestData); + break; + case RIGHT: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1, + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerRight(node, bestData); + break; + case UP: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), + mViewBounds.maxY(), mViewBounds.width(), 1); + findClosest = innerUp(node, bestData); + break; + case DOWN: + if (outOfCursor) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), + mViewBounds.y() - 1, mViewBounds.width(), 1); + findClosest = innerDown(node, bestData); + break; + case UNINITIALIZED: + default: + ASSERT(0); + } + } + if (firstCall) + mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL + bestData->setMouseBounds(bestData->bounds()); + if (adjustForScroll(bestData, direction, scroll, findClosest)) + return; + if (bestData->mNode != NULL) { + mHistory->addToVisited(bestData->mNode, direction); + mHistory->mNavBounds = bestData->bounds(); + mHistory->mMouseBounds = bestData->mouseBounds(); + } else if (scroll->x() != 0 || scroll->y() != 0) { + WebCore::IntRect newBounds = mHistory->mNavBounds; + int offsetX = scroll->x(); + int offsetY = scroll->y(); + newBounds.move(offsetX, offsetY); + if (mViewBounds.x() > newBounds.x()) + offsetX = mViewBounds.x() - mHistory->mNavBounds.x(); + else if (mViewBounds.maxX() < newBounds.maxX()) + offsetX = mViewBounds.maxX() - mHistory->mNavBounds.maxX(); + if (mViewBounds.y() > newBounds.y()) + offsetY = mViewBounds.y() - mHistory->mNavBounds.y(); + else if (mViewBounds.maxY() < newBounds.maxY()) + offsetY = mViewBounds.maxY() - mHistory->mNavBounds.maxY(); + mHistory->mNavBounds.move(offsetX, offsetY); + } + mHistory->setDidFirstLayout(false); +} + +bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingHorizontal() >= mViewBounds.y()); + ASSERT(maxWorkingHorizontal() <= mViewBounds.maxY()); + setupScrolledBounds(); + // (align) + mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); + int testLeft = mScrolledBounds.x(); + int viewRight = mViewBounds.maxX(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.maxX() > viewRight && viewRight < mContents.width()) + return false; + if (navBounds.isEmpty() == false) { + int navLeft = navBounds.x(); + int scrollRight; + if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.maxX())) { + mScrolledBounds.setWidth(scrollRight - navLeft); + mScrolledBounds.setX(navLeft); + } + } + setCursorCache(mMaxXScroll, 0); + frameRight(test, NULL, bestData); + return true; +} + +bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingVertical() >= mViewBounds.x()); + ASSERT(maxWorkingVertical() <= mViewBounds.maxX()); + setupScrolledBounds(); + mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll); + mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); + int testBottom = mScrolledBounds.maxY(); + int viewTop = mViewBounds.y(); + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + if (navBounds.isEmpty() == false && + navBounds.y() < viewTop && viewTop > mContents.y()) + return false; + if (navBounds.isEmpty() == false) { + int navBottom = navBounds.maxY(); + int scrollTop; + if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y())) + mScrolledBounds.setHeight(navBottom - scrollTop); + } + setCursorCache(0, -mMaxYScroll); + frameUp(test, NULL, bestData); + return true; +} + +WTF::String CachedRoot::imageURI(int x, int y) const +{ + DBG_NAV_LOGD("x/y=(%d,%d)", x, y); + ImageCheck imageCheck; + ImageCanvas checker(&imageCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); + checker.setBitmapDevice(bitmap); + SkPicture* picture = pictureAt(&x, &y); + checker.translate(SkIntToScalar(-x), SkIntToScalar(-y)); + checker.drawPicture(*picture); + DBG_NAV_LOGD("uri=%s", checker.getURI()); + return WTF::String(checker.getURI()); +} + +bool CachedRoot::maskIfHidden(BestData* best) const +{ + const CachedNode* bestNode = best->mNode; + if (bestNode->isUnclipped()) + return false; + const CachedFrame* frame = best->mFrame; + SkPicture* picture = frame->picture(bestNode); + if (picture == NULL) { + DBG_NAV_LOG("missing picture"); + return false; + } + Vector<IntRect> rings; + bestNode->cursorRings(frame, &rings); + const WebCore::IntRect& bounds = bestNode->bounds(frame); + IntRect bitBounds; + calcBitBounds(bounds, &bitBounds); + RingCheck ringCheck(rings, bitBounds, bounds, bestNode->singleImage()); + RingCanvas checker(&ringCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), + bitBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(-bitBounds.x()), + SkIntToScalar(-bitBounds.y())); + checker.drawPicture(*picture); + SkRegion clipRgn; + bool clipped = ringCheck.hiddenRings(&clipRgn); + CachedNode* node = const_cast<CachedNode*>(best->mNode); + DBG_NAV_LOGD("clipped=%s clipRgn.isEmpty=%s", clipped ? "true" : "false", + clipRgn.isEmpty() ? "true" : "false"); + if (clipped && clipRgn.isEmpty()) { + node->setDisabled(true); + IntRect clippedBounds = bounds; + clippedBounds.intersect(bitBounds); + node->setClippedOut(clippedBounds != bounds); + return true; + } + // was it partially occluded by later drawing? + // if partially occluded, modify the bounds so that the mouse click has a better x,y + if (clipped) { + DBG_NAV_LOGD("clipped clipRgn={%d,%d,r=%d,b=%d}", + clipRgn.getBounds().fLeft, clipRgn.getBounds().fTop, + clipRgn.getBounds().fRight, clipRgn.getBounds().fBottom); + best->setMouseBounds(clipRgn.getBounds()); + if (!node->clip(best->mouseBounds())) { + node->setDisabled(true); + node->setClippedOut(true); + return true; + } + } else + node->fixUpCursorRects(frame); + return false; +} + +const CachedNode* CachedRoot::moveCursor(Direction direction, const CachedFrame** framePtr, + WebCore::IntPoint* scroll) +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + CachedRoot* frame = this; + const CachedNode* node = frame->document(); + if (node == NULL) + return NULL; + if (mViewBounds.isEmpty()) + return NULL; + resetClippedOut(); + setData(); + BestData bestData; + innerMove(node, &bestData, direction, scroll, true); + // if node is partially or fully concealed by layer, scroll it into view + if (mRootLayer && bestData.mNode && !bestData.mNode->isInLayer()) { +#if USE(ACCELERATED_COMPOSITING) +#if DUMP_NAV_CACHE + CachedLayer::Debug::printRootLayerAndroid(mRootLayer); +#endif + SkIRect original = bestData.mNode->cursorRingBounds(bestData.mFrame); + DBG_NAV_LOGD("original=(%d,%d,w=%d,h=%d) scroll=(%d,%d)", + original.fLeft, original.fTop, original.width(), original.height(), + scroll->x(), scroll->y()); + original.offset(-scroll->x(), -scroll->y()); + SkRegion rings(original); + SkTDArray<SkRect> region; + mRootLayer->clipArea(®ion); + SkRegion layers; + for (int index = 0; index < region.count(); index++) { + SkIRect enclosing; + region[index].round(&enclosing); + rings.op(enclosing, SkRegion::kDifference_Op); + layers.op(enclosing, SkRegion::kUnion_Op); + } + SkIRect layerBounds(layers.getBounds()); + SkIRect ringBounds(rings.getBounds()); + int scrollX = scroll->x(); + int scrollY = scroll->y(); + if (rings.getBounds() != original) { + int topOverlap = layerBounds.fBottom - original.fTop; + int bottomOverlap = original.fBottom - layerBounds.fTop; + int leftOverlap = layerBounds.fRight - original.fLeft; + int rightOverlap = original.fRight - layerBounds.fLeft; + if (direction & UP_DOWN) { + if (layerBounds.fLeft < original.fLeft && leftOverlap < 0) + scroll->setX(leftOverlap); + if (original.fRight < layerBounds.fRight && rightOverlap > 0 + && -leftOverlap > rightOverlap) + scroll->setX(rightOverlap); + bool topSet = scrollY > topOverlap && (direction == UP + || !scrollY); + if (topSet) + scroll->setY(topOverlap); + if (scrollY < bottomOverlap && (direction == DOWN || (!scrollY + && (!topSet || -topOverlap > bottomOverlap)))) + scroll->setY(bottomOverlap); + } else { + if (layerBounds.fTop < original.fTop && topOverlap < 0) + scroll->setY(topOverlap); + if (original.fBottom < layerBounds.fBottom && bottomOverlap > 0 + && -topOverlap > bottomOverlap) + scroll->setY(bottomOverlap); + bool leftSet = scrollX > leftOverlap && (direction == LEFT + || !scrollX); + if (leftSet) + scroll->setX(leftOverlap); + if (scrollX < rightOverlap && (direction == RIGHT || (!scrollX + && (!leftSet || -leftOverlap > rightOverlap)))) + scroll->setX(rightOverlap); + } + DBG_NAV_LOGD("rings=(%d,%d,w=%d,h=%d) layers=(%d,%d,w=%d,h=%d)" + " scroll=(%d,%d)", + ringBounds.fLeft, ringBounds.fTop, ringBounds.width(), ringBounds.height(), + layerBounds.fLeft, layerBounds.fTop, layerBounds.width(), layerBounds.height(), + scroll->x(), scroll->y()); + } +#endif + } + *framePtr = bestData.mFrame; + return const_cast<CachedNode*>(bestData.mNode); +} + +const CachedNode* CachedRoot::nextTextField(const CachedNode* start, + const CachedFrame** framePtr) const +{ + bool startFound = false; + return CachedFrame::nextTextField(start, framePtr, &startFound); +} + +SkPicture* CachedRoot::pictureAt(int* xPtr, int* yPtr, int* id) const +{ +#if USE(ACCELERATED_COMPOSITING) + if (mRootLayer) { + const LayerAndroid* layer = mRootLayer->find(xPtr, yPtr, mPicture); + if (layer) { + SkPicture* picture = layer->picture(); + DBG_NAV_LOGD("layer %d picture=%p (%d,%d)", layer->uniqueId(), + picture, picture ? picture->width() : 0, + picture ? picture->height() : 0); + if (picture) { + if (id) + *id = layer->uniqueId(); + return picture; + } + } + } +#endif + DBG_NAV_LOGD("root mPicture=%p (%d,%d)", mPicture, mPicture ? + mPicture->width() : 0, mPicture ? mPicture->height() : 0); + if (id) + *id = -1; + return mPicture; +} + +void CachedRoot::reset() +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0); + mMaxXScroll = mMaxYScroll = 0; + mRootLayer = 0; + mSelectionStart = mSelectionEnd = -1; + mScrollOnly = false; +} + +bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta) +{ + switch (direction) { + case LEFT: + *delta = -mMaxXScroll; + return newOutset.x() >= mViewBounds.x(); + case RIGHT: + *delta = mMaxXScroll; + return newOutset.maxX() <= mViewBounds.maxX(); + case UP: + *delta = -mMaxYScroll; + return newOutset.y() >= mViewBounds.y(); + case DOWN: + *delta = mMaxYScroll; + return newOutset.maxY() <= mViewBounds.maxY(); + default: + *delta = 0; + ASSERT(0); + } + return false; +} + +void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node) +{ + mFocusBounds = WebCore::IntRect(0, 0, 0, 0); + if (node == NULL) + return; + node->setIsFocus(true); + mFocusBounds = node->bounds(frame); + frame->setFocusIndex(node - frame->document()); + CachedFrame* parent; + while ((parent = frame->parent()) != NULL) { + parent->setFocusIndex(frame->indexInParent()); + frame = parent; + } +#if DEBUG_NAV_UI + const CachedFrame* focusFrame; + const CachedNode* focus = currentFocus(&focusFrame); + WebCore::IntRect bounds = WebCore::IntRect(0, 0, 0, 0); + if (focus) + bounds = focus->bounds(focusFrame); + DBG_NAV_LOGD("new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif +} + +void CachedRoot::setCursor(CachedFrame* frame, CachedNode* node) +{ +#if DEBUG_NAV_UI + const CachedFrame* cursorFrame; + const CachedNode* cursor = currentCursor(&cursorFrame); + WebCore::IntRect bounds; + if (cursor) + bounds = cursor->bounds(cursorFrame); + DBG_NAV_LOGD("old cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", + cursor ? cursor->index() : 0, + cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif + clearCursor(); + if (node == NULL) + return; + node->setIsCursor(true); + node->show(); + frame->setCursorIndex(node - frame->document()); + CachedFrame* parent; + while ((parent = frame->parent()) != NULL) { + parent->setCursorIndex(frame->indexInParent()); + frame = parent; + } +#if DEBUG_NAV_UI + cursor = currentCursor(&cursorFrame); + bounds = WebCore::IntRect(0, 0, 0, 0); + if (cursor) + bounds = cursor->bounds(cursorFrame); + DBG_NAV_LOGD("new cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", + cursor ? cursor->index() : 0, + cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif +} + +void CachedRoot::setCursorCache(int scrollX, int scrollY) const +{ + mCursor = currentCursor(); + if (mCursor) + mCursorBounds = mCursor->bounds(this); + if (!mRootLayer) + return; + SkRegion baseScrolled(mScrolledBounds); + mBaseUncovered = SkRegion(mScrolledBounds); +#if USE(ACCELERATED_COMPOSITING) +#if DUMP_NAV_CACHE + CachedLayer::Debug::printRootLayerAndroid(mRootLayer); +#endif + SkTDArray<SkRect> region; + mRootLayer->clipArea(®ion); + WebCore::IntSize offset( + copysign(min(max(0, mContents.width() - mScrolledBounds.width()), + abs(scrollX)), scrollX), + copysign(min(max(0, mContents.height() - mScrolledBounds.height()), + abs(scrollY)), scrollY)); + bool hasOffset = offset.width() || offset.height(); + // restrict scrollBounds to that which is not under layer + for (int index = 0; index < region.count(); index++) { + SkIRect less; + region[index].round(&less); + DBG_NAV_LOGD("less=(%d,%d,w=%d,h=%d)", less.fLeft, less.fTop, + less.width(), less.height()); + mBaseUncovered.op(less, SkRegion::kDifference_Op); + if (!hasOffset) + continue; + less.offset(offset.width(), offset.height()); + baseScrolled.op(less, SkRegion::kDifference_Op); + } + if (hasOffset) + mBaseUncovered.op(baseScrolled, SkRegion::kUnion_Op); +#endif +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedRoot* CachedRoot::Debug::base() const { + CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug)); + return nav; +} + +void CachedRoot::Debug::print() const +{ +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gWriteLogMutex.lock(); + ASSERT(gNavCacheLogFile == NULL); + gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); +#endif + CachedRoot* b = base(); + b->CachedFrame::mDebug.print(); + b->mHistory->mDebug.print(b); + DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n", + b->mMaxXScroll, b->mMaxYScroll); + if (b->mRootLayer) + CachedLayer::Debug::printRootLayerAndroid(b->mRootLayer); +#ifdef DUMP_NAV_CACHE_USING_PRINTF + if (gNavCacheLogFile) + fclose(gNavCacheLogFile); + gNavCacheLogFile = NULL; + gWriteLogMutex.unlock(); +#endif +} + +#endif + +} diff --git a/Source/WebKit/android/nav/CachedRoot.h b/Source/WebKit/android/nav/CachedRoot.h new file mode 100644 index 0000000..65c6062 --- /dev/null +++ b/Source/WebKit/android/nav/CachedRoot.h @@ -0,0 +1,142 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedRoot_h +#define CachedRoot_h + +#include "CachedFrame.h" +#include "IntRect.h" +#include "SkPicture.h" +#include "SkRegion.h" +#include "wtf/Vector.h" + +class SkRect; + +namespace WebCore { + class LayerAndroid; +} + +namespace android { + +class CachedHistory; +class CachedNode; +class FindCanvas; + +class CachedRoot : public CachedFrame { +public: + bool adjustForScroll(BestData* , Direction , WebCore::IntPoint* scrollPtr, + bool findClosest); + const SkRegion& baseUncovered() const { return mBaseUncovered; } + void calcBitBounds(const IntRect& , IntRect* ) const; + int checkForCenter(int x, int y) const; + void checkForJiggle(int* ) const; + bool checkRings(SkPicture* , const CachedNode* , + const WebCore::IntRect& testBounds) const; + WebCore::IntPoint cursorLocation() const; + int documentHeight() { return mContents.height(); } + int documentWidth() { return mContents.width(); } + void draw(FindCanvas& ) const; + const CachedNode* findAt(const WebCore::IntRect& , const CachedFrame** , + int* x, int* y, bool checkForHidden) const; + const WebCore::IntRect& focusBounds() const { return mFocusBounds; } + WebCore::IntPoint focusLocation() const; + int getAndResetSelectionEnd(); + int getAndResetSelectionStart(); + int getBlockLeftEdge(int x, int y, float scale) const; + void getSimulatedMousePosition(WebCore::IntPoint* ) const; + void init(WebCore::Frame* , CachedHistory* ); + bool innerDown(const CachedNode* , BestData* ) const; + bool innerLeft(const CachedNode* , BestData* ) const; + void innerMove(const CachedNode* ,BestData* bestData, Direction , + WebCore::IntPoint* scroll, bool firstCall); + bool innerRight(const CachedNode* , BestData* ) const; + bool innerUp(const CachedNode* , BestData* ) const; + WTF::String imageURI(int x, int y) const; + bool maskIfHidden(BestData* ) const; + const CachedNode* moveCursor(Direction , const CachedFrame** , WebCore::IntPoint* scroll); + /** + * Find the next textfield/textarea + * @param start The textfield/textarea to search from. + * @param framePtr If non-zero, returns CachedFrame* containing result. + * @return CachedNode* Next textfield/textarea or null (0) if none. + */ + const CachedNode* nextTextField(const CachedNode* start, + const CachedFrame** framePtr) const; + SkPicture* pictureAt(int* xPtr, int* yPtr, int* id) const; + SkPicture* pictureAt(int* xPtr, int* yPtr) const { + return pictureAt(xPtr, yPtr, 0); } + void reset(); + CachedHistory* rootHistory() const { return mHistory; } + WebCore::LayerAndroid* rootLayer() const { return mRootLayer; } + bool scrollDelta(WebCore::IntRect& cursorRingBounds, Direction , int* delta); + const WebCore::IntRect& scrolledBounds() const { return mScrolledBounds; } + void setCursor(CachedFrame* , CachedNode* ); + void setCursorCache(int scrollX, int scrollY) const; // compute cached state used to find next cursor + void setCachedFocus(CachedFrame* , CachedNode* ); + void setFocusBounds(const WebCore::IntRect& r) { mFocusBounds = r; } + void setTextGeneration(int textGeneration) { mTextGeneration = textGeneration; } + void setMaxScroll(int x, int y) { mMaxXScroll = x; mMaxYScroll = y; } + void setPicture(SkPicture* picture) { mPicture = picture; } + void setRootLayer(WebCore::LayerAndroid* layer) { + mRootLayer = layer; + resetLayers(); + } + void setScrollOnly(bool state) { mScrollOnly = state; } + void setSelection(int start, int end) { mSelectionStart = start; mSelectionEnd = end; } + void setupScrolledBounds() const { mScrolledBounds = mViewBounds; } + void setVisibleRect(const WebCore::IntRect& r) { mViewBounds = r; } + int textGeneration() const { return mTextGeneration; } + int width() const { return mPicture ? mPicture->width() : 0; } +private: + friend class CachedFrame; + CachedHistory* mHistory; + SkPicture* mPicture; + WebCore::LayerAndroid* mRootLayer; + WebCore::IntRect mFocusBounds; // dom text input focus node bounds + mutable WebCore::IntRect mScrolledBounds; // view bounds + amount visible as result of scroll + int mTextGeneration; + int mMaxXScroll; + int mMaxYScroll; + // These two are ONLY used when the tree is rebuilt and the focus is a textfield/area + int mSelectionStart; + int mSelectionEnd; + // these four set up as cache for use by frameDown/Up/Left/Right etc + mutable WebCore::IntRect mCursorBounds; + mutable const CachedNode* mCursor; + mutable SkRegion mBaseUncovered; + bool mScrollOnly; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedRoot* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/Source/WebKit/android/nav/DrawExtra.cpp b/Source/WebKit/android/nav/DrawExtra.cpp deleted file mode 100644 index 2f57dc1..0000000 --- a/Source/WebKit/android/nav/DrawExtra.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2012, The Android Open Source Project - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" - -#include "DrawExtra.h" -#include "GLExtras.h" -#include "LayerAndroid.h" -#include "SkCanvas.h" -#include "SkRegion.h" -#include "WebViewCore.h" - -RegionLayerDrawExtra::RegionLayerDrawExtra() - : m_highlightColor(COLOR_HOLO_LIGHT) -{} - -RegionLayerDrawExtra::~RegionLayerDrawExtra() -{ - HighlightRegionMap::iterator end = m_highlightRegions.end(); - for (HighlightRegionMap::iterator it = m_highlightRegions.begin(); it != end; ++it) { - delete it->second; - it->second = 0; - } -} - -SkRegion* RegionLayerDrawExtra::getHighlightRegionsForLayer(const LayerAndroid* layer) -{ - int layerId = layer ? layer->uniqueId() : 0; - return m_highlightRegions.get(layerId); -} - -void RegionLayerDrawExtra::addHighlightRegion(const LayerAndroid* layer, const Vector<IntRect>& rects, - const IntPoint& additionalOffset) -{ - if (rects.isEmpty()) - return; - int layerId = layer ? layer->uniqueId() : 0; - SkRegion* region = m_highlightRegions.get(layerId); - if (!region) { - region = new SkRegion(); - m_highlightRegions.set(layerId, region); - } - IntPoint offset = additionalOffset; - WebViewCore::layerToAbsoluteOffset(layer, offset); - for (size_t i = 0; i < rects.size(); i++) { - IntRect r = rects.at(i); - r.move(-offset.x(), -offset.y()); - region->op(r.x(), r.y(), r.maxX(), r.maxY(), SkRegion::kUnion_Op); - } -} - -void RegionLayerDrawExtra::draw(SkCanvas* canvas, LayerAndroid* layer) -{ - SkRegion* region = getHighlightRegionsForLayer(layer); - if (!region || region->isEmpty()) - return; - SkRegion::Iterator rgnIter(*region); - SkPaint paint; - paint.setColor(m_highlightColor.rgb()); - while (!rgnIter.done()) { - const SkIRect& rect = rgnIter.rect(); - canvas->drawIRect(rect, paint); - rgnIter.next(); - } -} - -void RegionLayerDrawExtra::drawGL(GLExtras* glExtras, const LayerAndroid* layer) -{ - SkRegion* region = getHighlightRegionsForLayer(layer); - if (!region || region->isEmpty()) - return; - const TransformationMatrix* transform = layer ? layer->drawTransform() : 0; - glExtras->drawRegion(*region, true, false, transform, m_highlightColor); -} diff --git a/Source/WebKit/android/nav/DrawExtra.h b/Source/WebKit/android/nav/DrawExtra.h index cc94476..6716a65 100644 --- a/Source/WebKit/android/nav/DrawExtra.h +++ b/Source/WebKit/android/nav/DrawExtra.h @@ -26,25 +26,11 @@ #ifndef DrawExtra_h #define DrawExtra_h -#include "config.h" - -#include "Color.h" -#include "IntPoint.h" -#include "IntRect.h" -#include "wtf/HashMap.h" -#include "wtf/Vector.h" - -// Color of the ring copied from framework's holo_light -#define COLOR_HOLO_LIGHT 0x6633B5E5 -// Color of the ring copied from framework's holo_dark -#define COLOR_HOLO_DARK 0x660099CC - class SkCanvas; -class SkRegion; namespace WebCore { + class IntRect; class LayerAndroid; - class GLExtras; } using namespace WebCore; @@ -54,27 +40,7 @@ namespace android { class DrawExtra { public: virtual ~DrawExtra() {} - virtual void draw(SkCanvas*, LayerAndroid*) {} - virtual void drawGL(GLExtras*, const LayerAndroid*) {} -}; - -// A helper extra that has a SkRegion per LayerAndroid -class RegionLayerDrawExtra : public DrawExtra { -public: - RegionLayerDrawExtra(); - virtual ~RegionLayerDrawExtra(); - - void addHighlightRegion(const LayerAndroid* layer, const Vector<IntRect>& rects, - const IntPoint& additionalOffset = IntPoint()); - virtual void draw(SkCanvas*, LayerAndroid*); - virtual void drawGL(GLExtras*, const LayerAndroid*); - -private: - SkRegion* getHighlightRegionsForLayer(const LayerAndroid* layer); - - typedef HashMap<int, SkRegion* > HighlightRegionMap; - HighlightRegionMap m_highlightRegions; - Color m_highlightColor; + virtual void draw(SkCanvas* , LayerAndroid* , IntRect* ) = 0; }; } diff --git a/Source/WebKit/android/nav/FindCanvas.cpp b/Source/WebKit/android/nav/FindCanvas.cpp new file mode 100644 index 0000000..ca3cfba --- /dev/null +++ b/Source/WebKit/android/nav/FindCanvas.cpp @@ -0,0 +1,700 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webviewglue" + +#include "config.h" +#include "FindCanvas.h" +#include "LayerAndroid.h" +#include "IntRect.h" +#include "SelectText.h" +#include "SkBlurMaskFilter.h" +#include "SkCornerPathEffect.h" +#include "SkRect.h" +#include "SkUtils.h" + +#include <utils/Log.h> + +#define INTEGER_OUTSET 2 + +namespace android { + +// MatchInfo methods +//////////////////////////////////////////////////////////////////////////////// + +MatchInfo::MatchInfo() { + m_picture = 0; +} + +MatchInfo::~MatchInfo() { + SkSafeUnref(m_picture); +} + +MatchInfo::MatchInfo(const MatchInfo& src) { + m_layerId = src.m_layerId; + m_location = src.m_location; + m_picture = src.m_picture; + SkSafeRef(m_picture); +} + +void MatchInfo::set(const SkRegion& region, SkPicture* pic, int layerId) { + SkSafeUnref(m_picture); + m_layerId = layerId; + m_location = region; + m_picture = pic; + SkASSERT(pic); + pic->ref(); +} + +// GlyphSet methods +//////////////////////////////////////////////////////////////////////////////// + +GlyphSet::GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper, + size_t byteLength) { + SkPaint clonePaint(paint); + clonePaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + mTypeface = paint.getTypeface(); + mCount = clonePaint.textToGlyphs(lower, byteLength, NULL); + if (mCount > MAX_STORAGE_COUNT) { + mLowerGlyphs = new uint16_t[2*mCount]; + } else { + mLowerGlyphs = &mStorage[0]; + } + // Use one array, and have mUpperGlyphs point to a portion of it, + // so that we can reduce the number of new/deletes + mUpperGlyphs = mLowerGlyphs + mCount; + int count2 = clonePaint.textToGlyphs(lower, byteLength, mLowerGlyphs); + SkASSERT(mCount == count2); + count2 = clonePaint.textToGlyphs(upper, byteLength, mUpperGlyphs); + SkASSERT(mCount == count2); +} + +GlyphSet::~GlyphSet() { + // Do not need to delete mTypeface, which is not owned by us. + if (mCount > MAX_STORAGE_COUNT) { + delete[] mLowerGlyphs; + } // Otherwise, we just used local storage space, so no need to delete + // Also do not need to delete mUpperGlyphs, which simply points to a + // part of mLowerGlyphs +} + +GlyphSet& GlyphSet::operator=(GlyphSet& src) { + mTypeface = src.mTypeface; + mCount = src.mCount; + if (mCount > MAX_STORAGE_COUNT) { + mLowerGlyphs = new uint16_t[2*mCount]; + } else { + mLowerGlyphs = &mStorage[0]; + } + memcpy(mLowerGlyphs, src.mLowerGlyphs, 2*mCount*sizeof(uint16_t)); + mUpperGlyphs = mLowerGlyphs + mCount; + return *this; +} + +bool GlyphSet::characterMatches(uint16_t c, int index) { + SkASSERT(index < mCount && index >= 0); + return c == mLowerGlyphs[index] || c == mUpperGlyphs[index]; +} + +// FindCanvas methods +//////////////////////////////////////////////////////////////////////////////// + +FindCanvas::FindCanvas(int width, int height, const UChar* lower, + const UChar* upper, size_t byteLength) + : mLowerText(lower) + , mUpperText(upper) + , mLength(byteLength) + , mNumFound(0) { + // the text has been provided in read order. Reverse as needed so the + // result contains left-to-right characters. + const uint16_t* start = mLowerText; + size_t count = byteLength >> 1; + const uint16_t* end = mLowerText + count; + while (start < end) { + SkUnichar ch = SkUTF16_NextUnichar(&start); + WTF::Unicode::Direction charDirection = WTF::Unicode::direction(ch); + if (WTF::Unicode::RightToLeftArabic == charDirection + || WTF::Unicode::RightToLeft == charDirection) { + mLowerReversed.clear(); + mLowerReversed.append(mLowerText, count); + WebCore::ReverseBidi(mLowerReversed.begin(), count); + mLowerText = mLowerReversed.begin(); + mUpperReversed.clear(); + mUpperReversed.append(mUpperText, count); + WebCore::ReverseBidi(mUpperReversed.begin(), count); + mUpperText = mUpperReversed.begin(); + break; + } + } + + setBounder(&mBounder); + mOutset = -SkIntToScalar(INTEGER_OUTSET); + mMatches = new WTF::Vector<MatchInfo>(); + mWorkingIndex = 0; + mWorkingCanvas = 0; + mWorkingPicture = 0; +} + +FindCanvas::~FindCanvas() { + setBounder(NULL); + /* Just in case getAndClear was not called. */ + delete mMatches; + SkSafeUnref(mWorkingPicture); +} + +// Each version of addMatch returns a rectangle for a match. +// Not all of the parameters are used by each version. +SkRect FindCanvas::addMatchNormal(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y) { + const uint16_t* lineStart = glyphs - index; + /* Use the original paint, since "text" is in glyphs */ + SkScalar before = paint.measureText(lineStart, index * sizeof(uint16_t), 0); + SkRect rect; + rect.fLeft = pos[0] + before; + int countInBytes = count * sizeof(uint16_t); + rect.fRight = paint.measureText(glyphs, countInBytes, 0) + rect.fLeft; + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + SkScalar baseline = y; + rect.fTop = baseline + fontMetrics.fAscent; + rect.fBottom = baseline + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&rect); + // Add the text to our picture. + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawText(glyphs, countInBytes, pos[0] + before, y, paint); + canvas->restoreToCount(saveCount); + return rect; +} + +SkRect FindCanvas::addMatchPos(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar xPos[], SkScalar /* y */) { + SkRect r; + r.setEmpty(); + const SkPoint* temp = reinterpret_cast<const SkPoint*> (xPos); + const SkPoint* points = &temp[index]; + int countInBytes = count * sizeof(uint16_t); + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + // Need to check each character individually, since the heights may be + // different. + for (int j = 0; j < count; j++) { + SkRect bounds; + bounds.fLeft = points[j].fX; + bounds.fRight = bounds.fLeft + + paint.measureText(&glyphs[j], sizeof(uint16_t), 0); + SkScalar baseline = points[j].fY; + bounds.fTop = baseline + fontMetrics.fAscent; + bounds.fBottom = baseline + fontMetrics.fDescent; + /* Accumulate and then add the resulting rect to mMatches */ + r.join(bounds); + } + SkMatrix matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosText(glyphs, countInBytes, points, paint); + canvas->restoreToCount(saveCount); + return r; +} + +SkRect FindCanvas::addMatchPosH(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar position[], SkScalar constY) { + SkRect r; + // We only care about the positions starting at the index of our match + const SkScalar* xPos = &position[index]; + // This assumes that the position array is monotonic increasing + // The left bounds will be the position of the left most character + r.fLeft = xPos[0]; + // The right bounds will be the position of the last character plus its + // width + int lastIndex = count - 1; + r.fRight = paint.measureText(&glyphs[lastIndex], sizeof(uint16_t), 0) + + xPos[lastIndex]; + // Grab font metrics to determine the top and bottom of the bounds + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + r.fTop = constY + fontMetrics.fAscent; + r.fBottom = constY + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosTextH(glyphs, count * sizeof(uint16_t), xPos, constY, paint); + canvas->restoreToCount(saveCount); + return r; +} + +void FindCanvas::drawLayers(LayerAndroid* layer) { +#if USE(ACCELERATED_COMPOSITING) + SkPicture* picture = layer->picture(); + if (picture) { + setLayerId(layer->uniqueId()); + drawPicture(*picture); + } + for (int i = 0; i < layer->countChildren(); i++) + drawLayers(layer->getChild(i)); +#endif +} + +void FindCanvas::drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + findHelper(text, byteLength, paint, &x, y, &FindCanvas::addMatchNormal); +} + +void FindCanvas::drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + // Pass in the first y coordinate for y so that we can check to see whether + // it is lower than the last draw call (to check if we are continuing to + // another line). + findHelper(text, byteLength, paint, (const SkScalar*) pos, pos[0].fY, + &FindCanvas::addMatchPos); +} + +void FindCanvas::drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + findHelper(text, byteLength, paint, xpos, constY, + &FindCanvas::addMatchPosH); +} + +/* The current behavior is to skip substring matches. This means that in the + * string + * batbatbat + * a search for + * batbat + * will return 1 match. If the desired behavior is to return 2 matches, define + * INCLUDE_SUBSTRING_MATCHES to be 1. + */ +#define INCLUDE_SUBSTRING_MATCHES 0 + +// Need a quick way to know a maximum distance between drawText calls to know if +// they are part of the same logical phrase when searching. By crude +// inspection, half the point size seems a good guess at the width of a space +// character. +static inline SkScalar approximateSpaceWidth(const SkPaint& paint) { + return SkScalarHalf(paint.getTextSize()); +} + +void FindCanvas::findHelper(const void* text, size_t byteLength, + const SkPaint& paint, const SkScalar positions[], + SkScalar y, + SkRect (FindCanvas::*addMatch)(int index, + const SkPaint& paint, int count, + const uint16_t* glyphs, + const SkScalar positions[], SkScalar y)) { + SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); + SkASSERT(mMatches); + GlyphSet* glyphSet = getGlyphs(paint); + const int count = glyphSet->getCount(); + int numCharacters = byteLength >> 1; + const uint16_t* chars = (const uint16_t*) text; + // This block will check to see if we are continuing from another line. If + // so, the user needs to have added a space, which we do not draw. + if (mWorkingIndex) { + SkPoint newY; + getTotalMatrix().mapXY(0, y, &newY); + SkIRect workingBounds = mWorkingRegion.getBounds(); + int newYInt = SkScalarRound(newY.fY); + if (workingBounds.fTop > newYInt) { + // The new text is above the working region, so we know it's not + // a continuation. + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } else if (workingBounds.fBottom < newYInt) { + // Now we know that this line is lower than our partial match. + SkPaint clonePaint(paint); + clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + uint16_t space; + clonePaint.textToGlyphs(" ", 1, &space); + if (glyphSet->characterMatches(space, mWorkingIndex)) { + mWorkingIndex++; + if (mWorkingIndex == count) { + // We already know that it is not clipped out because we + // checked for that before saving the working region. + insertMatchInfo(mWorkingRegion); + + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + // We have found a match, so continue on this line from + // scratch. + } + } else { + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } + } + // If neither one is true, then we are likely continuing on the same + // line, but are in a new draw call because the paint has changed. In + // this case, we can continue without adding a space. + } + // j is the position in the search text + // Start off with mWorkingIndex in case we are continuing from a prior call + int j = mWorkingIndex; + // index is the position in the drawn text + int index = 0; + for ( ; index != numCharacters; index++) { + if (glyphSet->characterMatches(chars[index], j)) { + // The jth character in the search text matches the indexth position + // in the drawn text, so increase j. + j++; + if (j != count) { + continue; + } + // The last count characters match, so we found the entire + // search string. + int remaining = count - mWorkingIndex; + int matchIndex = index - remaining + 1; + // Set up a pointer to the matching text in 'chars'. + const uint16_t* glyphs = chars + matchIndex; + SkRect rect = (this->*addMatch)(matchIndex, paint, + remaining, glyphs, positions, y); + // We need an SkIRect for SkRegion operations. + SkIRect iRect; + rect.roundOut(&iRect); + // Want to outset the drawn rectangle by the same amount as + // mOutset + iRect.inset(-INTEGER_OUTSET, -INTEGER_OUTSET); + SkRegion regionToAdd(iRect); + if (!mWorkingRegion.isEmpty()) { + // If this is on the same line as our working region, make + // sure that they are close enough together that they are + // supposed to be part of the same text string. + // The width of two spaces has arbitrarily been chosen. + const SkIRect& workingBounds = mWorkingRegion.getBounds(); + if (workingBounds.fTop <= iRect.fBottom && + workingBounds.fBottom >= iRect.fTop && + SkIntToScalar(iRect.fLeft - workingBounds.fRight) > + approximateSpaceWidth(paint)) { + index = -1; // Will increase to 0 on next run + // In this case, we need to start from the beginning of + // the text being searched and our search term. + j = 0; + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + continue; + } + // Add the mWorkingRegion, which contains rectangles from + // the previous line(s). + regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op); + } + insertMatchInfo(regionToAdd); +#if INCLUDE_SUBSTRING_MATCHES + // Reset index to the location of the match and reset j to the + // beginning, so that on the next iteration of the loop, index + // will advance by 1 and we will compare the next character in + // chars to the first character in the GlyphSet. + index = matchIndex; +#endif + // Whether the clip contained it or not, we need to start over + // with our recording canvas + resetWorkingCanvas(); + } else { + // Index needs to be set to index - j + 1. + // This is a ridiculous case, but imagine the situation where the + // user is looking for the string "jjog" in the drawText call for + // "jjjog". The first two letters match. However, when the index + // is 2, and we discover that 'o' and 'j' do not match, we should go + // back to 1, where we do, in fact, have a match + // FIXME: This does not work if (as in our example) "jj" is in one + // draw call and "jog" is in the next. Doing so would require a + // stack, keeping track of multiple possible working indeces and + // regions. This is likely an uncommon case. + index = index - j; // index will be increased by one on the next + // iteration + } + // We reach here in one of two cases: + // 1) We just completed a match, so any working rectangle/index is no + // longer needed, and we will start over from the beginning + // 2) The glyphs do not match, so we start over at the beginning of + // the search string. + j = 0; + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } + // At this point, we have searched all of the text in the current drawText + // call. + // Keep track of a partial match that may start on this line. + if (j > 0) { // if j is greater than 0, we have a partial match + int relativeCount = j - mWorkingIndex; // Number of characters in this + // part of the match. + int partialIndex = index - relativeCount; // Index that starts our + // partial match. + const uint16_t* partialGlyphs = chars + partialIndex; + SkRect partial = (this->*addMatch)(partialIndex, paint, relativeCount, + partialGlyphs, positions, y); + partial.inset(mOutset, mOutset); + SkIRect dest; + partial.roundOut(&dest); + mWorkingRegion.op(dest, SkRegion::kUnion_Op); + mWorkingIndex = j; + return; + } + // This string doesn't go into the next drawText, so reset our working + // variables + mWorkingRegion.setEmpty(); + mWorkingIndex = 0; +} + +SkCanvas* FindCanvas::getWorkingCanvas() { + if (!mWorkingPicture) { + mWorkingPicture = new SkPicture; + mWorkingCanvas = mWorkingPicture->beginRecording(0,0); + } + return mWorkingCanvas; +} + +GlyphSet* FindCanvas::getGlyphs(const SkPaint& paint) { + SkTypeface* typeface = paint.getTypeface(); + GlyphSet* end = mGlyphSets.end(); + for (GlyphSet* ptr = mGlyphSets.begin();ptr != end; ptr++) { + if (ptr->getTypeface() == typeface) { + return ptr; + } + } + + GlyphSet set(paint, mLowerText, mUpperText, mLength); + *mGlyphSets.append() = set; + return &(mGlyphSets.top()); +} + +void FindCanvas::insertMatchInfo(const SkRegion& region) { + mNumFound++; + mWorkingPicture->endRecording(); + MatchInfo matchInfo; + mMatches->append(matchInfo); + LOGD("%s region=%p pict=%p layer=%d", __FUNCTION__, + ®ion, mWorkingPicture, mLayerId); + mMatches->last().set(region, mWorkingPicture, mLayerId); +} + +void FindCanvas::resetWorkingCanvas() { + mWorkingPicture->unref(); + mWorkingPicture = 0; + // Do not need to reset mWorkingCanvas itself because we only access it via + // getWorkingCanvas. +} + +// This function sets up the paints that are used to draw the matches. +void FindOnPage::setUpFindPaint() { + // Set up the foreground paint + m_findPaint.setAntiAlias(true); + const SkScalar roundiness = SkIntToScalar(2); + SkCornerPathEffect* cornerEffect = new SkCornerPathEffect(roundiness); + m_findPaint.setPathEffect(cornerEffect); + m_findPaint.setARGB(255, 132, 190, 0); + + // Set up the background blur paint. + m_findBlurPaint.setAntiAlias(true); + m_findBlurPaint.setARGB(204, 0, 0, 0); + m_findBlurPaint.setPathEffect(cornerEffect); + cornerEffect->unref(); + SkMaskFilter* blurFilter = SkBlurMaskFilter::Create(SK_Scalar1, + SkBlurMaskFilter::kNormal_BlurStyle); + m_findBlurPaint.setMaskFilter(blurFilter)->unref(); + m_isFindPaintSetUp = true; +} + +IntRect FindOnPage::currentMatchBounds() const { + IntRect noBounds = IntRect(0, 0, 0, 0); + if (!m_matches || !m_matches->size()) + return noBounds; + MatchInfo& info = (*m_matches)[m_findIndex]; + return info.getLocation().getBounds(); +} + +bool FindOnPage::currentMatchIsInLayer() const { + if (!m_matches || !m_matches->size()) + return false; + MatchInfo& info = (*m_matches)[m_findIndex]; + return info.isInLayer(); +} + +int FindOnPage::currentMatchLayerId() const { + return (*m_matches)[m_findIndex].layerId(); +} + +// This function is only used by findNext and setMatches. In it, we store +// upper left corner of the match specified by m_findIndex in +// m_currentMatchLocation. +void FindOnPage::storeCurrentMatchLocation() { + SkASSERT(m_findIndex < m_matches->size()); + const SkIRect& bounds = (*m_matches)[m_findIndex].getLocation().getBounds(); + m_currentMatchLocation.set(bounds.fLeft, bounds.fTop); + m_hasCurrentLocation = true; +} + +// Put a cap on the number of matches to draw. If the current page has more +// matches than this, only draw the focused match. +#define MAX_NUMBER_OF_MATCHES_TO_DRAW 101 + +void FindOnPage::draw(SkCanvas* canvas, LayerAndroid* layer, IntRect* inval) { + if (!m_lastBounds.isEmpty()) { + inval->unite(m_lastBounds); + m_lastBounds.setEmpty(); + } + if (!m_hasCurrentLocation || !m_matches || !m_matches->size()) + return; + int layerId = layer->uniqueId(); + if (m_findIndex >= m_matches->size()) + m_findIndex = 0; + const MatchInfo& matchInfo = (*m_matches)[m_findIndex]; + const SkRegion& currentMatchRegion = matchInfo.getLocation(); + + // Set up the paints used for drawing the matches + if (!m_isFindPaintSetUp) + setUpFindPaint(); + + // Draw the current match + if (matchInfo.layerId() == layerId) { + drawMatch(currentMatchRegion, canvas, true); + // Now draw the picture, so that it shows up on top of the rectangle + int saveCount = canvas->save(); + SkPath matchPath; + currentMatchRegion.getBoundaryPath(&matchPath); + canvas->clipPath(matchPath); + canvas->drawPicture(*matchInfo.getPicture()); + canvas->restoreToCount(saveCount); + const SkMatrix& matrix = canvas->getTotalMatrix(); + const SkRect& localBounds = matchPath.getBounds(); + SkRect globalBounds; + matrix.mapRect(&globalBounds, localBounds); + globalBounds.round(&m_lastBounds); + inval->unite(m_lastBounds); + } + // Draw the rest + unsigned numberOfMatches = m_matches->size(); + if (numberOfMatches > 1 + && numberOfMatches < MAX_NUMBER_OF_MATCHES_TO_DRAW) { + for (unsigned i = 0; i < numberOfMatches; i++) { + // The current match has already been drawn + if (i == m_findIndex) + continue; + if ((*m_matches)[i].layerId() != layerId) + continue; + const SkRegion& region = (*m_matches)[i].getLocation(); + // Do not draw matches which intersect the current one, or if it is + // offscreen + if (currentMatchRegion.intersects(region)) + continue; + SkRect bounds; + bounds.set(region.getBounds()); + if (canvas->quickReject(bounds, SkCanvas::kAA_EdgeType)) + continue; + drawMatch(region, canvas, false); + } + } +} + +// Draw the match specified by region to the canvas. +void FindOnPage::drawMatch(const SkRegion& region, SkCanvas* canvas, + bool focused) +{ + // For the match which has focus, use a filled paint. For the others, use + // a stroked paint. + if (focused) { + m_findPaint.setStyle(SkPaint::kFill_Style); + m_findBlurPaint.setStyle(SkPaint::kFill_Style); + } else { + m_findPaint.setStyle(SkPaint::kStroke_Style); + m_findPaint.setStrokeWidth(SK_Scalar1); + m_findBlurPaint.setStyle(SkPaint::kStroke_Style); + m_findBlurPaint.setStrokeWidth(SkIntToScalar(2)); + } + // Find the path for the current match + SkPath matchPath; + region.getBoundaryPath(&matchPath); + // Offset the path for a blurred shadow + SkPath blurPath; + matchPath.offset(SK_Scalar1, SkIntToScalar(2), &blurPath); + int saveCount = 0; + if (!focused) { + saveCount = canvas->save(); + canvas->clipPath(matchPath, SkRegion::kDifference_Op); + } + // Draw the blurred background + canvas->drawPath(blurPath, m_findBlurPaint); + if (!focused) + canvas->restoreToCount(saveCount); + // Draw the foreground + canvas->drawPath(matchPath, m_findPaint); +} + +void FindOnPage::findNext(bool forward) +{ + if (!m_matches || !m_matches->size() || !m_hasCurrentLocation) + return; + if (forward) { + m_findIndex++; + if (m_findIndex == m_matches->size()) + m_findIndex = 0; + } else { + if (m_findIndex == 0) { + m_findIndex = m_matches->size() - 1; + } else { + m_findIndex--; + } + } + storeCurrentMatchLocation(); +} + +// With this call, WebView takes ownership of matches, and is responsible for +// deleting it. +void FindOnPage::setMatches(WTF::Vector<MatchInfo>* matches) +{ + if (m_matches) + delete m_matches; + m_matches = matches; + if (m_matches->size()) { + if (m_hasCurrentLocation) { + for (unsigned i = 0; i < m_matches->size(); i++) { + const SkIRect& rect = (*m_matches)[i].getLocation().getBounds(); + if (rect.fLeft == m_currentMatchLocation.fX + && rect.fTop == m_currentMatchLocation.fY) { + m_findIndex = i; + return; + } + } + } + // If we did not have a stored location, or if we were unable to restore + // it, store the new one. + m_findIndex = 0; + storeCurrentMatchLocation(); + } else { + m_hasCurrentLocation = false; + } +} + +} diff --git a/Source/WebKit/android/nav/FindCanvas.h b/Source/WebKit/android/nav/FindCanvas.h new file mode 100644 index 0000000..0fa095c --- /dev/null +++ b/Source/WebKit/android/nav/FindCanvas.h @@ -0,0 +1,259 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef Find_Canvas_h +#define Find_Canvas_h + +#include "DrawExtra.h" +#include "IntRect.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkRegion.h" +#include "SkTDArray.h" + +#include <unicode/umachine.h> +#include <wtf/Vector.h> + +namespace android { + +// Stores both region information and an SkPicture of the match, so that the +// region can be drawn, followed by drawing the matching text on top of it. +// This class owns its SkPicture +class MatchInfo { +public: + MatchInfo(); + ~MatchInfo(); + MatchInfo(const MatchInfo& src); + const SkRegion& getLocation() const { return m_location; } + // Return a pointer to our picture, representing the matching text. Does + // not transfer ownership of the picture. + SkPicture* getPicture() const { return m_picture; } + // This will make a copy of the region, and increase the ref count on the + // SkPicture. If this MatchInfo already had one, unref it. + void set(const SkRegion& region, SkPicture* pic, int layerId); + bool isInLayer() const { return m_layerId >= 0; } + int layerId() const { return m_layerId; } +private: + MatchInfo& operator=(MatchInfo& src); + SkRegion m_location; + SkPicture* m_picture; + int m_layerId; +}; + +// A class containing a typeface for reference, the length in glyphs, and +// the upper and lower case representations of the search string. +class GlyphSet { +public: + GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper, + size_t byteLength); + ~GlyphSet(); + GlyphSet& operator=(GlyphSet& src); + + // Return true iff c matches one of our glyph arrays at index + bool characterMatches(uint16_t c, int index); + + int getCount() const { return mCount; } + + const SkTypeface* getTypeface() const { return mTypeface; } + +private: + // Disallow copy constructor + GlyphSet(GlyphSet& src) { } + + // mTypeface is used for comparison only + const SkTypeface* mTypeface; + // mLowerGlyphs points to all of our storage space: the lower set followed + // by the upper set. mUpperGlyphs is purely a convenience pointer to the + // start of the upper case glyphs. + uint16_t* mLowerGlyphs; + uint16_t* mUpperGlyphs; + // mCount is the number of glyphs of the search string. Must be the same + // for both the lower case set and the upper case set. + int mCount; + + // Arbitrarily chose the maximum storage to use in the GlyphSet. This is + // based on the length of the word being searched. If users are always + // searching for 3 letter words (for example), an ideal number would be 3. + // Each time the user searches for a word longer than (in this case, 3) that + // will result in calling new/delete. + enum Storage { + MAX_STORAGE_COUNT = 16 + }; + // In order to eliminate new/deletes, create storage that will be enough + // most of the time + uint16_t mStorage[2*MAX_STORAGE_COUNT]; +}; + +class FindBounder : public SkBounder { +public: + FindBounder() {} + ~FindBounder() {} +protected: + virtual bool onIRect(const SkIRect&) { return false; } +}; + +class FindCanvas : public SkCanvas { +public: + FindCanvas(int width, int height, const UChar* , const UChar*, + size_t byteLength); + + virtual ~FindCanvas(); + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint); + + /* FIXME: This path has not been tested. */ + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint); + + /* Also untested */ + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint); + + /* Not sure what to do here or for drawTextOnPathHV */ + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + } + + void drawLayers(LayerAndroid*); + int found() const { return mNumFound; } + void setLayerId(int layerId) { mLayerId = layerId; } + + // This method detaches our array of matches and passes ownership to + // the caller, who is then responsible for deleting them. + WTF::Vector<MatchInfo>* detachMatches() { + WTF::Vector<MatchInfo>* array = mMatches; + mMatches = NULL; + return array; + } + +private: + // These calls are made by findHelper to store information about each match + // that is found. They return a rectangle which is used to highlight the + // match. They also add to our SkPicture (which can be accessed with + // getDrawnMatches) a draw of each match. This way it can be drawn after + // the rectangle. The rect that is returned is in device coordinates. + SkRect addMatchNormal(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y); + + SkRect addMatchPos(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar xPos[], SkScalar /* y */); + + SkRect addMatchPosH(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar position[], SkScalar constY); + + // Helper for each of our draw calls + void findHelper(const void* text, size_t byteLength, const SkPaint& paint, + const SkScalar xPos[], SkScalar y, + SkRect (FindCanvas::*addMatch)(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y)); + + // If we already have a working canvas, grab it. Otherwise, create a new + // one. + SkCanvas* getWorkingCanvas(); + + // Return the set of glyphs and its count for the text being searched for + // and the parameter paint. If one has already been created and cached + // for this paint, use it. If not, create a new one and cache it. + GlyphSet* getGlyphs(const SkPaint& paint); + + // Store all the accumulated info about a match in our vector. + void insertMatchInfo(const SkRegion& region); + + // Throw away our cumulative information about our working SkCanvas. After + // this call, next call to getWorkingCanvas will create a new one. + void resetWorkingCanvas(); + + // Since we may transfer ownership of this array (see detachRects()), we + // hold a pointer to the array instead of just the array itself. + WTF::Vector<MatchInfo>* mMatches; + const UChar* mLowerText; + const UChar* mUpperText; + Vector<UChar> mLowerReversed; + Vector<UChar> mUpperReversed; + size_t mLength; + FindBounder mBounder; + int mNumFound; + SkScalar mOutset; + SkTDArray<GlyphSet> mGlyphSets; + + SkPicture* mWorkingPicture; + SkCanvas* mWorkingCanvas; + SkRegion mWorkingRegion; + int mWorkingIndex; + int mLayerId; +}; + +class FindOnPage : public DrawExtra { +public: + FindOnPage() { + m_matches = 0; + m_hasCurrentLocation = false; + m_isFindPaintSetUp = false; + m_lastBounds.setEmpty(); + } + virtual ~FindOnPage() { if (m_matches) delete m_matches; } + void clearCurrentLocation() { m_hasCurrentLocation = false; } + IntRect currentMatchBounds() const; + int currentMatchIndex() const { return m_findIndex; } + bool currentMatchIsInLayer() const; + // This requires the current match to be in a layer. See + // currentMatchIsInLayer(). + int currentMatchLayerId() const; + virtual void draw(SkCanvas* , LayerAndroid* , IntRect* ); + void findNext(bool forward); + bool isCurrentLocationValid() { return m_hasCurrentLocation; } + void setMatches(WTF::Vector<MatchInfo>* matches); + WTF::Vector<MatchInfo>* matches() { return m_matches; } +private: + void drawMatch(const SkRegion& region, SkCanvas* canvas, bool focused); + void setUpFindPaint(); + void storeCurrentMatchLocation(); + WTF::Vector<MatchInfo>* m_matches; + // Stores the location of the current match. + SkIPoint m_currentMatchLocation; + // Tells whether the value in m_currentMatchLocation is valid. + bool m_hasCurrentLocation; + // Tells whether we have done the setup to draw the Find matches. + bool m_isFindPaintSetUp; + // Paint used to draw our Find matches. + SkPaint m_findPaint; + // Paint used for the background of our Find matches. + SkPaint m_findBlurPaint; + unsigned m_findIndex; + SkIRect m_lastBounds; +}; + +} + +#endif // Find_Canvas_h diff --git a/Source/WebKit/android/nav/SelectText.cpp b/Source/WebKit/android/nav/SelectText.cpp index 7ce32c3..d20c44a 100644 --- a/Source/WebKit/android/nav/SelectText.cpp +++ b/Source/WebKit/android/nav/SelectText.cpp @@ -25,20 +25,23 @@ #define LOG_TAG "webviewglue" -#include "config.h" - +#include "CachedPrefix.h" #include "BidiResolver.h" #include "BidiRunList.h" -#include "GLExtras.h" +#include "CachedRoot.h" #include "LayerAndroid.h" +#include "ParseCanvas.h" #include "SelectText.h" #include "SkBitmap.h" #include "SkBounder.h" -#include "SkCanvas.h" +#include "SkGradientShader.h" +#include "SkMatrix.h" #include "SkPicture.h" +#include "SkPixelXorXfermode.h" #include "SkPoint.h" #include "SkRect.h" #include "SkRegion.h" +#include "SkUtils.h" #include "TextRun.h" #ifdef DEBUG_NAV_UI @@ -49,7 +52,7 @@ // #define EXTRA_NOISY_LOGGING 1 #define DEBUG_TOUCH_HANDLES 0 #if DEBUG_TOUCH_HANDLES -#define DBG_HANDLE_LOG(format, ...) ALOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#define DBG_HANDLE_LOG(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) #else #define DBG_HANDLE_LOG(...) #endif @@ -144,3 +147,1921 @@ void ReverseBidi(UChar* chars, int len) { } +namespace android { + +#define HYPHEN_MINUS 0x2D // ASCII hyphen +#define SOLIDUS 0x2F // ASCII slash +#define REVERSE_SOLIDUS 0x5C // ASCII backslash +#define HYPHEN 0x2010 // unicode hyphen, first in range of dashes +#define HORZ_BAR 0x2015 // unicode horizontal bar, last in range of dashes +#define TOUCH_SLOP 10 // additional distance from character rect when hit + +class CommonCheck : public SkBounder { +public: + CommonCheck(const SkIRect& area) + : mArea(area) + , mLastUni(0) + { + mLastGlyph.fGlyphID = static_cast<uint16_t>(-1); + mLastCandidate.fGlyphID = static_cast<uint16_t>(-1); + mMatrix.reset(); + reset(); + } + + /* called only while the picture is parsed */ + int base() { + if (mBase == INT_MAX) { + SkPoint result; + mMatrix.mapXY(0, mY, &result); + mBase = SkScalarFloor(result.fY); + } + return mBase; + } + + /* called only while the picture is parsed */ + int bottom() { + if (mBottom == INT_MAX) { + SkPoint result; + SkPaint::FontMetrics metrics; + mPaint.getFontMetrics(&metrics); + mMatrix.mapXY(0, metrics.fDescent + mY, &result); + mBottom = SkScalarCeil(result.fY); + } + return mBottom; + } + +#if DEBUG_NAV_UI + // make current (possibily uncomputed) value visible for debugging + int bottomDebug() const + { + return mBottom; + } +#endif + + bool addNewLine(const SkBounder::GlyphRec& rec) + { + SkFixed lineSpacing = SkFixedAbs(mLastGlyph.fLSB.fY - rec.fLSB.fY); + SkFixed lineHeight = SkIntToFixed(bottom() - top()); + return lineSpacing >= lineHeight + (lineHeight >> 1); // 1.5 + } + + bool addSpace(const SkBounder::GlyphRec& rec) + { + bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY; + if (((mLastUni >= HYPHEN && mLastUni <= HORZ_BAR) + || mLastUni == HYPHEN_MINUS || mLastUni == SOLIDUS + || mLastUni == REVERSE_SOLIDUS) && newBaseLine) + { + return false; + } + return isSpace(rec); + } + + void finishGlyph() + { + mLastGlyph = mLastCandidate; + mLastUni = mLastUniCandidate; + mLastPaint = mLastPaintCandidate; + } + + const SkIRect& getArea() const { + return mArea; + } + + /* called only while the picture is parsed */ + SkUnichar getUniChar(const SkBounder::GlyphRec& rec) + { + SkUnichar unichar; + SkPaint::TextEncoding save = mPaint.getTextEncoding(); + mPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + mPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar); + mPaint.setTextEncoding(save); + return unichar; + } + + bool isSpace(const SkBounder::GlyphRec& rec) + { + if (mLastGlyph.fGlyphID == static_cast<uint16_t>(-1)) + return true; + DBG_NAV_LOGD("mLastGlyph=((%g, %g),(%g, %g), %d)" + " rec=((%g, %g),(%g, %g), %d) mLastUni=0x%04x '%c'", + SkFixedToScalar(mLastGlyph.fLSB.fX), + SkFixedToScalar(mLastGlyph.fLSB.fY), + SkFixedToScalar(mLastGlyph.fRSB.fX), + SkFixedToScalar(mLastGlyph.fRSB.fY), mLastGlyph.fGlyphID, + SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fLSB.fY), + SkFixedToScalar(rec.fRSB.fX), SkFixedToScalar(rec.fRSB.fY), + rec.fGlyphID, + mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?'); + bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY; + if (newBaseLine) + return true; + SkFixed gapOne = mLastGlyph.fLSB.fX - rec.fRSB.fX; + SkFixed gapTwo = rec.fLSB.fX - mLastGlyph.fRSB.fX; + if (gapOne < 0 && gapTwo < 0) + return false; // overlaps + const SkBounder::GlyphRec& first = mLastGlyph.fLSB.fX < rec.fLSB.fX + ? mLastGlyph : rec; + const SkBounder::GlyphRec& second = mLastGlyph.fLSB.fX < rec.fLSB.fX + ? rec : mLastGlyph; + uint16_t firstGlyph = first.fGlyphID; + SkScalar firstWidth = mLastPaint.measureText(&firstGlyph, sizeof(firstGlyph)); + SkFixed ceilWidth = SkIntToFixed(SkScalarCeil(firstWidth)); + SkFixed posNoSpace = first.fLSB.fX + ceilWidth; + SkFixed ceilSpace = SkIntToFixed(SkFixedCeil(minSpaceWidth(mLastPaint))); + SkFixed posWithSpace = posNoSpace + ceilSpace; + SkFixed diffNoSpace = SkFixedAbs(second.fLSB.fX - posNoSpace); + SkFixed diffWithSpace = SkFixedAbs(second.fLSB.fX - posWithSpace); + DBG_NAV_LOGD("second=%g width=%g (%g) noSpace=%g (%g) withSpace=%g (%g)" + " fontSize=%g", + SkFixedToScalar(second.fLSB.fX), + firstWidth, SkFixedToScalar(ceilWidth), + SkFixedToScalar(posNoSpace), SkFixedToScalar(diffNoSpace), + SkFixedToScalar(posWithSpace), SkFixedToScalar(diffWithSpace), + mLastPaint.getTextSize()); + return diffWithSpace <= diffNoSpace; + } + + SkFixed minSpaceWidth(SkPaint& paint) + { + if (mMinSpaceWidth == SK_FixedMax) { + SkPaint::TextEncoding save = paint.getTextEncoding(); + paint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + SkScalar width = paint.measureText(" ", 1); + mMinSpaceWidth = SkScalarToFixed(width * mMatrix.getScaleX()); + paint.setTextEncoding(save); + DBG_NAV_LOGV("width=%g matrix sx/sy=(%g, %g) tx/ty=(%g, %g)" + " mMinSpaceWidth=%g", width, + mMatrix.getScaleX(), mMatrix.getScaleY(), + mMatrix.getTranslateX(), mMatrix.getTranslateY(), + SkFixedToScalar(mMinSpaceWidth)); + } + return mMinSpaceWidth; + } + + void recordGlyph(const SkBounder::GlyphRec& rec) + { + mLastCandidate = rec; + mLastUniCandidate = getUniChar(rec); + mLastPaintCandidate = mPaint; + } + + void reset() + { + mMinSpaceWidth = SK_FixedMax; // mark as uninitialized + mBase = mBottom = mTop = INT_MAX; // mark as uninitialized + } + + void set(CommonCheck& check) + { + mLastGlyph = check.mLastGlyph; + mLastUni = check.mLastUni; + mMatrix = check.mMatrix; + mLastPaint = check.mLastPaint; + reset(); + } + + void setGlyph(CommonCheck& check) + { + mLastGlyph = check.mLastGlyph; + mLastUni = check.mLastUni; + mLastPaint = check.mLastPaint; + } + + void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y, + const void* text) + { + mMatrix = matrix; + mPaint = paint; + mText = static_cast<const uint16_t*>(text); + mY = y; + reset(); + } + + /* called only while the picture is parsed */ + int top() { + if (mTop == INT_MAX) { + SkPoint result; + SkPaint::FontMetrics metrics; + mPaint.getFontMetrics(&metrics); + mMatrix.mapXY(0, metrics.fAscent + mY, &result); + mTop = SkScalarFloor(result.fY); + } + return mTop; + } + +#if DEBUG_NAV_UI + // make current (possibily uncomputed) value visible for debugging + int topDebug() const + { + return mTop; + } +#endif + +protected: + SkIRect mArea; + SkBounder::GlyphRec mLastCandidate; + SkBounder::GlyphRec mLastGlyph; + SkPaint mLastPaint; // available after picture has been parsed + SkPaint mLastPaintCandidate; // associated with candidate glyph + SkUnichar mLastUni; + SkUnichar mLastUniCandidate; + SkMatrix mMatrix; + SkPaint mPaint; // only set up while the picture is parsed + const uint16_t* mText; + SkScalar mY; +private: + int mBase; + int mBottom; + SkFixed mMinSpaceWidth; + int mTop; + friend class EdgeCheck; +}; + +// generate the limit area for the new selection +class LineCheck : public CommonCheck { +public: + LineCheck(int x, int y, const SkIRect& area) + : INHERITED(area) + , mX(x) + , mY(y) + , mInBetween(false) + { + mLast.setEmpty(); + } + + void finish(const SkRegion& selectedRgn) + { + if (!mParagraphs.count() && mLast.isEmpty()) + return; + processLine(); + bool above = false; + bool below = false; + bool selected = false; + SkRegion localRgn(selectedRgn); + localRgn.translate(-mArea.fLeft, -mArea.fTop, &localRgn); + DBG_NAV_LOGD("localRgn=(%d,%d,%d,%d)", + localRgn.getBounds().fLeft, localRgn.getBounds().fTop, + localRgn.getBounds().fRight, localRgn.getBounds().fBottom); + for (int index = 0; index < mParagraphs.count(); index++) { + const SkIRect& rect = mParagraphs[index]; + bool localSelected = localRgn.intersects(rect); + DBG_NAV_LOGD("[%d] rect=(%d,%d,%d,%d)", index, rect.fLeft, rect.fTop, + rect.fRight, rect.fBottom); + if (localSelected) { + DBG_NAV_LOGD("[%d] localSelected=true", index); + *mSelected.append() = rect; + } + if (rect.fRight <= mX || rect.fLeft >= mX) + continue; + if (mY > rect.fBottom) { + below = true; + selected |= localSelected; + DBG_NAV_LOGD("[%d] below=true localSelected=%s", index, + localSelected ? "true" : "false"); + } + if (mY < rect.fTop) { + above = true; + selected |= localSelected; + DBG_NAV_LOGD("[%d] above=true localSelected=%s", index, + localSelected ? "true" : "false"); + } + } + DBG_NAV_LOGD("mX=%d mY=%d above=%s below=%s selected=%s", + mX, mY, above ? "true" : "false", below ? "true" : "false", + selected ? "true" : "false"); + mInBetween = above && below && selected; + } + + bool inBetween() const + { + return mInBetween; + } + + bool inColumn(const SkIRect& test) const + { + for (int index = 0; index < mSelected.count(); index++) { + const SkIRect& rect = mSelected[index]; + if (rect.fRight > test.fLeft && rect.fLeft < test.fRight) + return true; + } + return false; + } + + bool inColumn(int x, int y) const + { + for (int index = 0; index < mSelected.count(); index++) { + const SkIRect& rect = mSelected[index]; + if (rect.contains(x, y)) + return true; + } + return false; + } + + virtual bool onIRect(const SkIRect& rect) + { + SkIRect bounds; + bounds.set(rect.fLeft, top(), rect.fRight, bottom()); + // assume that characters must be consecutive to describe spaces + // (i.e., don't join rects drawn at different times) + if (bounds.fTop != mLast.fTop || bounds.fBottom != mLast.fBottom + || bounds.fLeft > mLast.fRight + minSpaceWidth(mPaint) + || bounds.fLeft < mLast.fLeft) { + processLine(); + mLast = bounds; + } else + mLast.join(bounds); + return false; + } + + void processLine() + { + // assume line spacing of 1.5 + int lineHeight = bottom() - top(); + mLast.inset(0, -lineHeight >> 1); + // collect arrays of rectangles making up glyphs below or above this one + for (int index = 0; index < mParagraphs.count(); index++) { + SkIRect& rect = mParagraphs[index]; + if (SkIRect::Intersects(rect, mLast)) { + rect.join(mLast); + return; + } + } + *mParagraphs.append() = mLast; + } + +protected: + int mX; + int mY; + SkIRect mLast; + SkTDArray<SkIRect> mParagraphs; + SkTDArray<SkIRect> mSelected; + bool mInBetween; +private: + typedef CommonCheck INHERITED; +}; + +class SelectText::FirstCheck : public CommonCheck { +public: + FirstCheck(int x, int y, const SkIRect& area) + : INHERITED(area) + , mLineCheck(0) + , mFocusX(x - area.fLeft) + , mFocusY(y - area.fTop) + , mBestInColumn(false) + , mRecordGlyph(false) + { + reset(); + } + + const SkIRect& adjustedBounds(int* base) + { + *base = mBestBase + mArea.fTop; + mBestBounds.offset(mArea.fLeft, mArea.fTop); + DBG_NAV_LOGD("FirstCheck mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d", + mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, + mBestBounds.fBottom, topDebug(), bottomDebug()); + return mBestBounds; + } + + int focusX() const { return mFocusX; } + int focusY() const { return mFocusY; } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + /* compute distance from rectangle center. + * centerX = (rect.L + rect.R) / 2 + * multiply centerX and comparison x by 2 to retain better precision + */ + SkIRect testBounds = {rect.fLeft, top(), rect.fRight, bottom()}; + // dx and dy are the distances from the tested edge + // The edge distance is paramount if the test point is far away + int dx = std::max(0, std::max(testBounds.fLeft - mFocusX, + mFocusX - testBounds.fRight)); + int dy = std::max(0, std::max(testBounds.fTop - mFocusY, + mFocusY - testBounds.fBottom)); + bool testInColumn = false; + bool inBetween = false; + bool inFocus = false; + if (mLineCheck) { + testInColumn = mLineCheck->inColumn(testBounds); + inBetween = mLineCheck->inBetween(); + inFocus = mLineCheck->inColumn(mFocusX, mFocusY); + } +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) { + SkUnichar ch = getUniChar(rec); + DBG_NAV_LOGD("FC dx/y=%d,%d mDx/y=%d,%d test=%d,%d,%d,%d" + " best=%d,%d,%d,%d bestIn=%s tween=%s testIn=%s focus=%s ch=%c", + dx, dy, mDx, mDy, + testBounds.fLeft, testBounds.fTop, testBounds.fRight, + testBounds.fBottom, mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, + mBestInColumn ? "true" : "false", inBetween ? "true" : "false", + testInColumn ? "true" : "false", inFocus ? "true" : "false", + ch < 0x7f ? ch : '?'); + } +#endif + if ((mBestInColumn || inBetween) && !testInColumn) { +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) DBG_NAV_LOG("FirstCheck reject column"); +#endif + return false; + } + bool ignoreColumn = mBestInColumn == testInColumn || !inFocus; + if (ignoreColumn && dy > 0 && (mDy < dy + || (mDy == dy && dx > 0 && mDx <= dx))) { +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) DBG_NAV_LOG("FirstCheck reject edge"); +#endif + return false; + } + // cx and cy are the distances from the tested center + // The center distance is used when the test point is over the text + int cx = std::abs(((testBounds.fLeft + testBounds.fRight) >> 1) + - mFocusX); + int cy = std::abs(((testBounds.fTop + testBounds.fBottom) >> 1) + - mFocusY); + if (ignoreColumn && dy == 0 && mDy == 0) { + if (mCy < cy) { +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("FirstCheck reject cy=%d mCy=%d", cy, mCy); +#endif + return false; + } + if (mCy == cy) { + if (dx == 0 && mDx == 0) { + if (mCx < cx) { +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("FirstCheck reject cx=%d mCx=%d", cx, mCx); +#endif + return false; + } + } else if (dx > 0 && mDx <= dx) { +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("FirstCheck reject dx=%d mDx=%d", dx, mDx); +#endif + return false; + } + } + } +#ifdef EXTRA_NOISY_LOGGING + if (dy < 10) { + DBG_NAV_LOGD("FirstCheck cx/y=(%d,%d)", cx, cy); + } +#endif + mBestBase = base(); + mBestBounds = testBounds; + mBestInColumn = testInColumn; +#ifndef EXTRA_NOISY_LOGGING + if (dy < 10 && dx < 10) +#endif + { +#if DEBUG_NAV_UI + SkUnichar ch = getUniChar(rec); +#endif + DBG_NAV_LOGD("FirstCheck dx/y=(%d,%d) mFocus=(%d,%d)" + " mBestBounds={%d,%d,r=%d,b=%d} inColumn=%s ch=%c", + dx, dy, mFocusX, mFocusY, + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, + mBestInColumn ? "true" : "false", ch < 0x7f ? ch : '?'); + } + mCx = cx; + mCy = cy; + mDx = dx; + mDy = dy; + if (mRecordGlyph) + recordGlyph(rec); + return false; + } + + void reset() + { + mBestBounds.setEmpty(); + mDx = mDy = mCx = mCy = INT_MAX; + } + + void setLines(const LineCheck* lineCheck) { mLineCheck = lineCheck; } + void setRecordGlyph() { mRecordGlyph = true; } + +protected: + const LineCheck* mLineCheck; + int mBestBase; + SkIRect mBestBounds; + int mCx; + int mCy; + int mDx; + int mDy; + int mFocusX; + int mFocusY; + bool mBestInColumn; + bool mRecordGlyph; +private: + typedef CommonCheck INHERITED; +}; + +class SelectText::EdgeCheck : public SelectText::FirstCheck { +public: + EdgeCheck(int x, int y, const SkIRect& area, CommonCheck& last, bool left) + : INHERITED(x, y, area) + , mLast(area) + , mLeft(left) + { + mLast.set(last); // CommonCheck::set() + setGlyph(last); + } + + bool adjacent() + { + return !mLast.isSpace(mLastGlyph); + } + + const SkIRect& bestBounds(int* base) + { + *base = mBestBase; + return mBestBounds; + } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + int dx = mLeft ? mFocusX - rect.fRight : rect.fLeft - mFocusX; + int dy = ((top() + bottom()) >> 1) - mFocusY; + dx = abs(dx); + dy = abs(dy); + if (mLeft ? mFocusX <= rect.fLeft : mFocusX >= rect.fRight) { + if (dx <= 10 && dy <= 10) { + DBG_NAV_LOGD("EdgeCheck fLeft=%d fRight=%d mFocusX=%d dx=%d dy=%d", + rect.fLeft, rect.fRight, mFocusX, dx, dy); + } + return false; + } + if (mDy > dy || (mDy == dy && mDx > dx)) { + if (rec.fLSB == mLastGlyph.fLSB && rec.fRSB == mLastGlyph.fRSB) { + DBG_NAV_LOGD("dup rec.fLSB.fX=%g rec.fRSB.fX=%g", + SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fRSB.fX)); + return false; + } + recordGlyph(rec); + mDx = dx; + mDy = dy; + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + if (dx <= 10 && dy <= 10) { + DBG_NAV_LOGD("EdgeCheck mBestBounds={%d,%d,r=%d,b=%d} dx/y=(%d, %d)", + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, dx, dy); + } + } + return false; + } + + void shiftStart(SkIRect bounds) + { + DBG_NAV_LOGD("EdgeCheck mFocusX=%d mLeft=%s bounds.fLeft=%d bounds.fRight=%d", + mFocusX, mLeft ? "true" : "false", bounds.fLeft, bounds.fRight); + reset(); + mFocusX = mLeft ? bounds.fLeft : bounds.fRight; + mLast.set(*this); // CommonCheck::set() + } + +protected: + CommonCheck mLast; + bool mLeft; +private: + typedef SelectText::FirstCheck INHERITED; +}; + +class FindFirst : public CommonCheck { +public: + FindFirst(const SkIRect& area) + : INHERITED(area) + { + mBestBounds.set(area.width(), area.height(), area.width(), area.height()); + } + + const SkIRect& bestBounds(int* base) + { + *base = mBestBase; + return mBestBounds; + } + + virtual bool onIRect(const SkIRect& rect) + { + if (mBestBounds.isEmpty()) { + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + } + return false; + } + +protected: + int mBestBase; + SkIRect mBestBounds; +private: + typedef CommonCheck INHERITED; +}; + +class FindLast : public FindFirst { +public: + FindLast(const SkIRect& area) + : INHERITED(area) + { + mBestBounds.setEmpty(); + } + + virtual bool onIRect(const SkIRect& rect) + { + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + return false; + } + +private: + typedef FindFirst INHERITED; +}; + +static bool baseLinesAgree(const SkIRect& rectA, int baseA, + const SkIRect& rectB, int baseB) +{ + return (rectA.fTop < baseB && rectA.fBottom >= baseB) + || (rectB.fTop < baseA && rectB.fBottom >= baseA); +} + +class BuilderCheck : public CommonCheck { +protected: + enum IntersectionType { + NO_INTERSECTION, // debugging printf expects this to equal zero + LAST_INTERSECTION, // debugging printf expects this to equal one + WAIT_FOR_INTERSECTION + }; + + BuilderCheck(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area) + : INHERITED(area) + , mCapture(false) + , mEnd(end) + , mEndBase(endBase) + , mStart(start) + , mStartBase(startBase) + { + mEnd.offset(-area.fLeft, -area.fTop); + mEndBase -= area.fTop; + mEndExtra.setEmpty(); + mLast.setEmpty(); + mLastBase = INT_MAX; + mSelectRect.setEmpty(); + mStart.offset(-area.fLeft, -area.fTop); + mStartBase -= area.fTop; + mStartExtra.setEmpty(); + DBG_NAV_LOGD(" mStart=(%d,%d,r=%d,b=%d) mStartBase=%d" + " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d", + mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase, + mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase); + } + + int checkFlipRect(const SkIRect& full, int fullBase) { + mCollectFull = false; + // is the text to collect between the selection top and bottom? + if (fullBase < mStart.fTop || fullBase > mEnd.fBottom) { + if (VERBOSE_LOGGING && !mLast.isEmpty()) DBG_NAV_LOGD("%s 1" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase); + return mLastIntersects; + } + // is the text to the left of the selection start? + if (baseLinesAgree(mStart, mStartBase, full, fullBase) + && full.fLeft < mStart.fLeft) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 2" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mStart=(%d,%d,r=%d,b=%d) mStartBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase); + mStartExtra.join(full); + return mLastIntersects; + } + // is the text to the right of the selection end? + if (baseLinesAgree(mEnd, mEndBase, full, fullBase) + && full.fRight > mEnd.fRight) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 3" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase); + mEndExtra.join(full); + return mLastIntersects; + } + int spaceGap = SkFixedRound(minSpaceWidth(mPaint) * 3); + // should text to the left of the start be added to the selection bounds? + if (!mStartExtra.isEmpty()) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)" + " mStartExtra=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom); + if (mStartExtra.fRight + spaceGap >= mStart.fLeft) + mSelectRect.join(mStartExtra); + mStartExtra.setEmpty(); + } + // should text to the right of the end be added to the selection bounds? + if (!mEndExtra.isEmpty()) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)" + " mEndExtra=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom); + if (mEndExtra.fLeft - spaceGap <= mEnd.fRight) + mSelectRect.join(mEndExtra); + mEndExtra.setEmpty(); + } + bool sameBaseLine = baseLinesAgree(mLast, mLastBase, full, fullBase); + bool adjacent = (full.fLeft - mLast.fRight) < spaceGap; + // is this the first, or are there more characters on the same line? + if (mLast.isEmpty() || (sameBaseLine && adjacent)) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("WAIT_FOR_INTERSECTION" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mSelectRect=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom); + mLast.join(full); + mLastIntersects = SkIRect::Intersects(mLast, mSelectRect); + return WAIT_FOR_INTERSECTION; + } + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 4" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mSelectRect=(%d,%d,r=%d,b=%d)" + " mStartExtra=(%d,%d,r=%d,b=%d)" + " mEndExtra=(%d,%d,r=%d,b=%d)", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom, + mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom); + // after the caller determines what to do with the last collection, + // start the collection over with full and fullBase. + mCollectFull = true; + return mLastIntersects; + } + + bool resetLast(const SkIRect& full, int fullBase) + { + if (mCollectFull) { + mLast = full; + mLastBase = fullBase; + mLastIntersects = SkIRect::Intersects(mLast, mSelectRect); + } else { + mLast.setEmpty(); + mLastBase = INT_MAX; + mLastIntersects = false; + } + return mCollectFull; + } + + void setFlippedState() + { + mSelectRect = mStart; + mSelectRect.join(mEnd); + DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom); + mLast.setEmpty(); + mLastBase = INT_MAX; + mLastIntersects = NO_INTERSECTION; + } + + bool mCapture; + bool mCollectFull; + SkIRect mEnd; + int mEndBase; + SkIRect mEndExtra; + bool mFlipped; + SkIRect mLast; + int mLastBase; + int mLastIntersects; + SkIRect mSelectRect; + SkIRect mStart; + SkIRect mStartExtra; + int mStartBase; +private: + typedef CommonCheck INHERITED; + +}; + +class MultilineBuilder : public BuilderCheck { +public: + MultilineBuilder(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area, SkRegion* region) + : INHERITED(start, startBase, end, endBase, area) + , mSelectRegion(region) + { + mFlipped = false; + } + + void addLastToRegion() { + if (VERBOSE_LOGGING) DBG_NAV_LOGD(" mLast=(%d,%d,r=%d,b=%d)", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom); + mSelectRegion->op(mLast, SkRegion::kUnion_Op); + } + + void finish() { + if (!mFlipped || !mLastIntersects) + return; + addLastToRegion(); + } + + // return true if capture end was not found after capture begin + bool flipped() { + DBG_NAV_LOGD("flipped=%s", mCapture ? "true" : "false"); + if (!mCapture) + return false; + mFlipped = true; + setFlippedState(); + mSelectRegion->setEmpty(); + return true; + } + + virtual bool onIRect(const SkIRect& rect) { + SkIRect full; + full.set(rect.fLeft, top(), rect.fRight, bottom()); + int fullBase = base(); + if (mFlipped) { + int intersectType = checkFlipRect(full, fullBase); + if (intersectType == LAST_INTERSECTION) + addLastToRegion(); + if (intersectType != WAIT_FOR_INTERSECTION) + resetLast(full, fullBase); + return false; + } + if (full == mStart) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mStart full=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom); + mCapture = true; + } + if (mCapture) { + bool sameLines = baseLinesAgree(mLast, mLastBase, full, fullBase); + if (sameLines) + mLast.join(full); + if (!sameLines || full == mEnd) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("finish mLast=(%d,%d,r=%d,b=%d)", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom); + addLastToRegion(); + mLast = full; + mLastBase = fullBase; + } + } + if (full == mEnd) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mEnd full=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom); + mCapture = false; + if (full == mStart) + addLastToRegion(); + } + return false; + } + +protected: + SkRegion* mSelectRegion; +private: + typedef BuilderCheck INHERITED; +}; + +static inline bool compareBounds(const SkIRect* first, const SkIRect* second) +{ + return first->fTop < second->fTop; +} + +class TextExtractor : public BuilderCheck { +public: + TextExtractor(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area, bool flipped) + : INHERITED(start, startBase, end, endBase, area) + , mSelectStartIndex(-1) + , mSkipFirstSpace(true) // don't start with a space + { + mFlipped = flipped; + if (flipped) + setFlippedState(); + } + + void addCharacter(const SkBounder::GlyphRec& rec) + { + if (mSelectStartIndex < 0) + mSelectStartIndex = mSelectText.count(); + if (!mSkipFirstSpace) { + if (addNewLine(rec)) { + DBG_NAV_LOG("write new line"); + *mSelectText.append() = '\n'; + *mSelectText.append() = '\n'; + } else if (addSpace(rec)) { + DBG_NAV_LOG("write space"); + *mSelectText.append() = ' '; + } + } else + mSkipFirstSpace = false; + recordGlyph(rec); + finishGlyph(); + if (VERBOSE_LOGGING) DBG_NAV_LOGD("glyphID=%d uni=%d '%c'", rec.fGlyphID, + mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?'); + if (mLastUni) { + uint16_t chars[2]; + size_t count = SkUTF16_FromUnichar(mLastUni, chars); + *mSelectText.append() = chars[0]; + if (count == 2) + *mSelectText.append() = chars[1]; + } + } + + void addLast() + { + *mSelectBounds.append() = mLast; + *mSelectStart.append() = mSelectStartIndex; + *mSelectEnd.append() = mSelectText.count(); + } + + /* Text characters are collected before it's been determined that the + characters are part of the selection. The bounds describe valid parts + of the selection, but the bounds are out of order. + + This sorts the characters by sorting the bounds, then copying the + characters that were captured. + */ + void finish() + { + if (mLastIntersects) + addLast(); + Vector<SkIRect*> sortedBounds; + SkTDArray<uint16_t> temp; + int index; + DBG_NAV_LOGD("mSelectBounds.count=%d text=%d", mSelectBounds.count(), + mSelectText.count()); + for (index = 0; index < mSelectBounds.count(); index++) + sortedBounds.append(&mSelectBounds[index]); + std::sort(sortedBounds.begin(), sortedBounds.end(), compareBounds); + int lastEnd = -1; + for (index = 0; index < mSelectBounds.count(); index++) { + int order = sortedBounds[index] - &mSelectBounds[0]; + int start = mSelectStart[order]; + int end = mSelectEnd[order]; + DBG_NAV_LOGD("order=%d start=%d end=%d top=%d", order, start, end, + mSelectBounds[order].fTop); + int count = temp.count(); + if (count > 0 && temp[count - 1] != '\n' && start != lastEnd) { + // always separate paragraphs when original text is out of order + DBG_NAV_LOG("write new line"); + *temp.append() = '\n'; + *temp.append() = '\n'; + } + temp.append(end - start, &mSelectText[start]); + lastEnd = end; + } + mSelectText.swap(temp); + } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + SkIRect full; + full.set(rect.fLeft, top(), rect.fRight, bottom()); + int fullBase = base(); + if (mFlipped) { + int intersectType = checkFlipRect(full, fullBase); + if (WAIT_FOR_INTERSECTION == intersectType) + addCharacter(rec); // may not be copied + else { + if (LAST_INTERSECTION == intersectType) + addLast(); + else + mSkipFirstSpace = true; + mSelectStartIndex = -1; + if (resetLast(full, fullBase)) + addCharacter(rec); // may not be copied + } + return false; + } + if (full == mStart) + mCapture = true; + if (mCapture) + addCharacter(rec); + else + mSkipFirstSpace = true; + if (full == mEnd) + mCapture = false; + return false; + } + + WTF::String text() { + if (mFlipped) + finish(); + // the text has been copied in visual order. Reverse as needed if + // result contains right-to-left characters. + const uint16_t* start = mSelectText.begin(); + const uint16_t* end = mSelectText.end(); + while (start < end) { + SkUnichar ch = SkUTF16_NextUnichar(&start); + WTF::Unicode::Direction charDirection = WTF::Unicode::direction(ch); + if (WTF::Unicode::RightToLeftArabic == charDirection + || WTF::Unicode::RightToLeft == charDirection) { + WebCore::ReverseBidi(mSelectText.begin(), mSelectText.count()); + break; + } + } + return WTF::String(mSelectText.begin(), mSelectText.count()); + } + +protected: + SkIRect mEmpty; + SkTDArray<SkIRect> mSelectBounds; + SkTDArray<int> mSelectEnd; + SkTDArray<int> mSelectStart; + int mSelectStartIndex; + SkTDArray<uint16_t> mSelectText; + bool mSkipFirstSpace; +private: + typedef BuilderCheck INHERITED; +}; + +class TextCanvas : public ParseCanvas { +public: + + TextCanvas(CommonCheck* bounder) + : mBounder(*bounder) { + setBounder(bounder); + SkBitmap bitmap; + const SkIRect& area = bounder->getArea(); + bitmap.setConfig(SkBitmap::kARGB_8888_Config, area.width(), + area.height()); + setBitmapDevice(bitmap); + translate(SkIntToScalar(-area.fLeft), SkIntToScalar(-area.fTop)); +#ifdef DEBUG_NAV_UI + const SkIRect& clip = getTotalClip().getBounds(); + const SkMatrix& matrix = getTotalMatrix(); + DBG_NAV_LOGD("bitmap=(%d,%d) clip=(%d,%d,%d,%d) matrix=(%g,%g)", + bitmap.width(), bitmap.height(), clip.fLeft, clip.fTop, + clip.fRight, clip.fBottom, matrix.getTranslateX(), matrix.getTranslateY()); +#endif + } + + virtual void drawPaint(const SkPaint& paint) { + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) { + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) { + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& matrix, const SkPaint& paint) { + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint = NULL) { + } + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + mBounder.setUp(paint, getTotalMatrix(), y, text); + INHERITED::drawText(text, byteLength, x, y, paint); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + mBounder.setUp(paint, getTotalMatrix(), constY, text); + INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); + } + + virtual void drawVertices(VertexMode vmode, int vertexCount, + const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) { + } + + CommonCheck& mBounder; +private: + typedef ParseCanvas INHERITED; +}; + +static bool buildSelection(const SkPicture& picture, const SkIRect& area, + const SkIRect& selStart, int startBase, + const SkIRect& selEnd, int endBase, SkRegion* region) +{ + DBG_NAV_LOGD("area=(%d, %d, %d, %d) selStart=(%d, %d, %d, %d)" + " selEnd=(%d, %d, %d, %d)", + area.fLeft, area.fTop, area.fRight, area.fBottom, + selStart.fLeft, selStart.fTop, selStart.fRight, selStart.fBottom, + selEnd.fLeft, selEnd.fTop, selEnd.fRight, selEnd.fBottom); + MultilineBuilder builder(selStart, startBase, selEnd, endBase, area, region); + TextCanvas checker(&builder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + bool flipped = builder.flipped(); + if (flipped) { + TextCanvas checker(&builder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + } + builder.finish(); + region->translate(area.fLeft, area.fTop); + return flipped; +} + +static SkIRect findFirst(const SkPicture& picture, int* base) +{ + SkIRect area; + area.set(0, 0, picture.width(), picture.height()); + FindFirst finder(area); + TextCanvas checker(&finder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return finder.bestBounds(base); +} + +static SkIRect findLast(const SkPicture& picture, int* base) +{ + SkIRect area; + area.set(0, 0, picture.width(), picture.height()); + FindLast finder(area); + TextCanvas checker(&finder); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return finder.bestBounds(base); +} + +static WTF::String text(const SkPicture& picture, const SkIRect& area, + const SkIRect& start, int startBase, const SkIRect& end, + int endBase, bool flipped) +{ + TextExtractor extractor(start, startBase, end, endBase, area, flipped); + TextCanvas checker(&extractor); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return extractor.text(); +} + +#define CONTROL_NOTCH 16 +// TODO: Now that java is the one actually drawing these, get the real values +// from the drawable itself +#define CONTROL_HEIGHT 47 +#define CONTROL_WIDTH 26 +#define CONTROL_SLOP 5 +#define STROKE_WIDTH 1.0f +#define STROKE_OUTSET 3.5f +#define STROKE_I_OUTSET 4 // (int) ceil(STROKE_OUTSET) +#define STROKE_COLOR 0x66000000 +#define OUTER_COLOR 0x33000000 +#define INNER_COLOR 0xe6aae300 + +SelectText::SelectText() + : m_controlWidth(CONTROL_WIDTH) + , m_controlHeight(CONTROL_HEIGHT) + , m_controlSlop(CONTROL_SLOP) +{ + m_picture = 0; + reset(); + SkPaint paint; + SkRect oval; + + SkPath startOuterPath; + oval.set(-CONTROL_WIDTH - STROKE_OUTSET, CONTROL_NOTCH - STROKE_OUTSET, + -CONTROL_WIDTH + STROKE_OUTSET, CONTROL_NOTCH + STROKE_OUTSET); + startOuterPath.arcTo(oval, 180, 45, true); + oval.set(-STROKE_OUTSET, -STROKE_OUTSET, STROKE_OUTSET, STROKE_OUTSET); + startOuterPath.arcTo(oval, 180 + 45, 135, false); + oval.set(-STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + startOuterPath.arcTo(oval, 0, 90, false); + oval.set(-CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + -CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + startOuterPath.arcTo(oval, 90, 90, false); + startOuterPath.close(); + SkPath startInnerPath; + startInnerPath.moveTo(-CONTROL_WIDTH, CONTROL_NOTCH); + startInnerPath.lineTo(-CONTROL_WIDTH, CONTROL_HEIGHT); + startInnerPath.lineTo(0, CONTROL_HEIGHT); + startInnerPath.lineTo(0, 0); + startInnerPath.close(); + startOuterPath.addPath(startInnerPath, 0, 0); + + SkCanvas* canvas = m_startControl.beginRecording( + CONTROL_WIDTH + STROKE_OUTSET * 2, + CONTROL_HEIGHT + STROKE_OUTSET * 2); + paint.setAntiAlias(true); + paint.setColor(INNER_COLOR); + paint.setStyle(SkPaint::kFill_Style); + canvas->drawPath(startInnerPath, paint); + paint.setColor(OUTER_COLOR); + canvas->drawPath(startOuterPath, paint); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(STROKE_COLOR); + paint.setStrokeWidth(STROKE_WIDTH); + canvas->drawPath(startInnerPath, paint); + m_startControl.endRecording(); + + SkPath endOuterPath; + oval.set(-STROKE_OUTSET, -STROKE_OUTSET, STROKE_OUTSET, STROKE_OUTSET); + endOuterPath.arcTo(oval, 180, 135, true); + oval.set(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_NOTCH - STROKE_OUTSET, + CONTROL_WIDTH + STROKE_OUTSET, CONTROL_NOTCH + STROKE_OUTSET); + endOuterPath.arcTo(oval, 360 - 45, 45, false); + oval.set(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + endOuterPath.arcTo(oval, 0, 90, false); + oval.set(-STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET, + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET); + endOuterPath.arcTo(oval, 90, 90, false); + startOuterPath.close(); + SkPath endInnerPath; + endInnerPath.moveTo(0, 0); + endInnerPath.lineTo(0, CONTROL_HEIGHT); + endInnerPath.lineTo(CONTROL_WIDTH, CONTROL_HEIGHT); + endInnerPath.lineTo(CONTROL_WIDTH, CONTROL_NOTCH); + endInnerPath.close(); + endOuterPath.addPath(endInnerPath, 0, 0); + + canvas = m_endControl.beginRecording(CONTROL_WIDTH + STROKE_OUTSET * 2, + CONTROL_HEIGHT + STROKE_OUTSET * 2); + paint.setColor(INNER_COLOR); + paint.setStyle(SkPaint::kFill_Style); + canvas->drawPath(endInnerPath, paint); + paint.setColor(OUTER_COLOR); + canvas->drawPath(endOuterPath, paint); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(STROKE_COLOR); + paint.setStrokeWidth(STROKE_WIDTH); + canvas->drawPath(endInnerPath, paint); + m_endControl.endRecording(); +} + +SelectText::~SelectText() +{ + SkSafeUnref(m_picture); +} + +void SelectText::draw(SkCanvas* canvas, LayerAndroid* layer, IntRect* inval) +{ + if (m_layerId != layer->uniqueId()) + return; + // reset m_picture to match m_layerId + SkSafeUnref(m_picture); + m_picture = layer->picture(); + SkSafeRef(m_picture); + DBG_NAV_LOGD("m_extendSelection=%d m_drawPointer=%d layer [%d]", + m_extendSelection, m_drawPointer, layer->uniqueId()); + if (m_extendSelection) + drawSelectionRegion(canvas, inval); + if (m_drawPointer) + drawSelectionPointer(canvas, inval); +} + +static void addInval(IntRect* inval, const SkCanvas* canvas, + const SkRect& bounds) { + const SkMatrix& matrix = canvas->getTotalMatrix(); + SkRect transformed; + matrix.mapRect(&transformed, bounds); + SkIRect iTrans; + transformed.round(&iTrans); + inval->unite(iTrans); +} + +void SelectText::drawSelectionPointer(SkCanvas* canvas, IntRect* inval) +{ + SkPath path; + if (m_extendSelection) + getSelectionCaret(&path); + else + getSelectionArrow(&path); + SkPixelXorXfermode xorMode(SK_ColorWHITE); + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLACK); + if (m_extendSelection) + paint.setXfermode(&xorMode); + else + paint.setStrokeWidth(SK_Scalar1 * 2); + int sc = canvas->save(); + canvas->scale(m_inverseScale, m_inverseScale); + canvas->translate(m_selectX, m_selectY); + canvas->drawPath(path, paint); + if (!m_extendSelection) { + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorWHITE); + canvas->drawPath(path, paint); + } + SkRect bounds = path.getBounds(); + bounds.inset(-SK_Scalar1 * 2, -SK_Scalar1 * 2); // stroke width + addInval(inval, canvas, bounds); + canvas->restoreToCount(sc); +} + +static void addStart(SkRegion* diff, const SkIRect& rect) +{ + SkIRect bounds; + bounds.set(rect.fLeft - CONTROL_WIDTH - STROKE_I_OUTSET, + rect.fBottom - STROKE_I_OUTSET, rect.fLeft + STROKE_I_OUTSET, + rect.fBottom + CONTROL_HEIGHT + STROKE_I_OUTSET); + diff->op(bounds, SkRegion::kUnion_Op); +} + +static void addEnd(SkRegion* diff, const SkIRect& rect) +{ + SkIRect bounds; + bounds.set(rect.fRight - STROKE_I_OUTSET, rect.fBottom - STROKE_I_OUTSET, + rect.fRight + CONTROL_WIDTH + STROKE_I_OUTSET, + rect.fBottom + CONTROL_HEIGHT + STROKE_I_OUTSET); + diff->op(bounds, SkRegion::kUnion_Op); +} + +void SelectText::getSelectionRegion(const IntRect& vis, SkRegion *region, + LayerAndroid* root) +{ + SkIRect ivisBounds = vis; + ivisBounds.join(m_selStart); + ivisBounds.join(m_selEnd); + region->setEmpty(); + buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase, + m_selEnd, m_endBase, region); + if (root && m_layerId) { + Layer* layer = root->findById(m_layerId); + while (layer) { + const SkPoint& pos = layer->getPosition(); + region->translate(pos.fX, pos.fY); + layer = layer->getParent(); + } + } +} + +void SelectText::drawSelectionRegion(SkCanvas* canvas, IntRect* inval) +{ + if (!m_picture) + return; + SkIRect ivisBounds = m_visibleRect; + ivisBounds.join(m_selStart); + ivisBounds.join(m_selEnd); + DBG_NAV_LOGD("m_selStart=(%d,%d,r=%d,b=%d) m_selEnd=(%d,%d,r=%d,b=%d)" + " ivisBounds=(%d,%d,r=%d,b=%d)", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom, + ivisBounds.fLeft, ivisBounds.fTop, ivisBounds.fRight, ivisBounds.fBottom); + if (m_lastSelRegion != m_selRegion) + m_lastSelRegion.set(m_selRegion); + SkRegion diff(m_lastSelRegion); + m_selRegion.setEmpty(); + m_flipped = buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase, + m_selEnd, m_endBase, &m_selRegion); + SkPath path; + m_selRegion.getBoundaryPath(&path); + path.setFillType(SkPath::kEvenOdd_FillType); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SkColorSetARGB(0x80, 0x83, 0xCC, 0x39)); + canvas->drawPath(path, paint); + // experiment to draw touchable controls that resize the selection + float scale = m_controlHeight / (float)CONTROL_HEIGHT; + canvas->save(); + canvas->translate(m_selStart.fLeft, m_selStart.fBottom); + canvas->scale(scale, scale); + canvas->drawPicture(m_startControl); + canvas->restore(); + canvas->save(); + canvas->translate(m_selEnd.fRight, m_selEnd.fBottom); + canvas->scale(scale, scale); + canvas->drawPicture(m_endControl); + canvas->restore(); + +#if DEBUG_TOUCH_HANDLES + SkRect touchHandleRect; + paint.setColor(SkColorSetARGB(0x60, 0xFF, 0x00, 0x00)); + touchHandleRect.set(0, m_selStart.fBottom, m_selStart.fLeft, 0); + touchHandleRect.fBottom = touchHandleRect.fTop + m_controlHeight; + touchHandleRect.fLeft = touchHandleRect.fRight - m_controlWidth; + canvas->drawRect(touchHandleRect, paint); + touchHandleRect.inset(-m_controlSlop, -m_controlSlop); + canvas->drawRect(touchHandleRect, paint); + touchHandleRect.set(m_selEnd.fRight, m_selEnd.fBottom, 0, 0); + touchHandleRect.fBottom = touchHandleRect.fTop + m_controlHeight; + touchHandleRect.fRight = touchHandleRect.fLeft + m_controlWidth; + canvas->drawRect(touchHandleRect, paint); + touchHandleRect.inset(-m_controlSlop, -m_controlSlop); + canvas->drawRect(touchHandleRect, paint); +#endif + + SkIRect a = diff.getBounds(); + SkIRect b = m_selRegion.getBounds(); + diff.op(m_selRegion, SkRegion::kXOR_Op); + SkIRect c = diff.getBounds(); + DBG_NAV_LOGD("old=(%d,%d,r=%d,b=%d) new=(%d,%d,r=%d,b=%d) diff=(%d,%d,r=%d,b=%d)", + a.fLeft, a.fTop, a.fRight, a.fBottom, b.fLeft, b.fTop, b.fRight, b.fBottom, + c.fLeft, c.fTop, c.fRight, c.fBottom); + DBG_NAV_LOGD("lastStart=(%d,%d,r=%d,b=%d) m_lastEnd=(%d,%d,r=%d,b=%d)", + m_lastStart.fLeft, m_lastStart.fTop, m_lastStart.fRight, m_lastStart.fBottom, + m_lastEnd.fLeft, m_lastEnd.fTop, m_lastEnd.fRight, m_lastEnd.fBottom); + if (!m_lastDrawnStart.isEmpty()) + addStart(&diff, m_lastDrawnStart); + if (m_lastStart != m_selStart) { + m_lastDrawnStart = m_lastStart; + m_lastStart = m_selStart; + } + addStart(&diff, m_selStart); + if (!m_lastDrawnEnd.isEmpty()) + addEnd(&diff, m_lastDrawnEnd); + if (m_lastEnd != m_selEnd) { + m_lastDrawnEnd = m_lastEnd; + m_lastEnd = m_selEnd; + } + addEnd(&diff, m_selEnd); + SkIRect iBounds = diff.getBounds(); + DBG_NAV_LOGD("diff=(%d,%d,r=%d,b=%d)", + iBounds.fLeft, iBounds.fTop, iBounds.fRight, iBounds.fBottom); + SkRect bounds; + bounds.set(iBounds); + addInval(inval, canvas, bounds); +} + +void SelectText::extendSelection(const IntRect& vis, int x, int y) +{ + if (!m_picture) + return; + setVisibleRect(vis); + SkIRect clipRect = m_visibleRect; + int base; + DBG_NAV_LOGD("extend x/y=%d,%d m_startOffset=%d,%d", x, y, + m_startOffset.fX, m_startOffset.fY); + x -= m_startOffset.fX; + y -= m_startOffset.fY; + if (m_startSelection) { + if (!clipRect.contains(x, y) + || !clipRect.contains(m_original.fX, m_original.fY)) { + clipRect.set(m_original.fX, m_original.fY, x, y); + clipRect.sort(); + clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height()); + } + FirstCheck center(m_original.fX, m_original.fY, clipRect); + m_selStart = m_selEnd = findClosest(center, *m_picture, &base); + if (m_selStart.isEmpty()) + return; + DBG_NAV_LOGD("selStart clip=(%d,%d,%d,%d) m_original=%d,%d" + " m_selStart=(%d,%d,%d,%d)", clipRect.fLeft, clipRect.fTop, + clipRect.fRight, clipRect.fBottom, m_original.fX, m_original.fY, + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom); + m_startBase = m_endBase = base; + m_startSelection = false; + m_extendSelection = true; + m_original.fX = m_original.fY = 0; + } + DBG_NAV_LOGD("extend x/y=%d,%d m_original=%d,%d", x, y, + m_original.fX, m_original.fY); + x -= m_original.fX; + y -= m_original.fY; + if (!clipRect.contains(x, y) || !clipRect.contains(m_selStart)) { + clipRect.set(m_selStart.fLeft, m_selStart.fTop, x, y); + clipRect.sort(); + clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height()); + } + DBG_NAV_LOGD("extend clip=(%d,%d,%d,%d) x/y=%d,%d wordSel=%s outsideWord=%s", + clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom, x, y, + m_wordSelection ? "true" : "false", m_outsideWord ? "true" : "false"); + FirstCheck extension(x, y, clipRect); + SkIRect found = findClosest(extension, *m_picture, &base); + if (m_wordSelection) { + SkIRect wordBounds = m_wordBounds; + if (!m_outsideWord) + wordBounds.inset(-TOUCH_SLOP, -TOUCH_SLOP); + DBG_NAV_LOGD("x=%d y=%d wordBounds=(%d,%d,r=%d,b=%d)" + " found=(%d,%d,r=%d,b=%d)", x, y, wordBounds.fLeft, wordBounds.fTop, + wordBounds.fRight, wordBounds.fBottom, found.fLeft, found.fTop, + found.fRight, found.fBottom); + if (wordBounds.contains(x, y)) { + DBG_NAV_LOG("wordBounds.contains=true"); + m_outsideWord = false; + return; + } + m_outsideWord = true; + if (found.fBottom <= wordBounds.fTop) + m_hitTopLeft = true; + else if (found.fTop >= wordBounds.fBottom) + m_hitTopLeft = false; + else + m_hitTopLeft = (found.fLeft + found.fRight) + < (wordBounds.fLeft + wordBounds.fRight); + } + DBG_NAV_LOGD("x=%d y=%d m_startSelection=%s %s=(%d, %d, %d, %d)" + " m_extendSelection=%s", + x, y, m_startSelection ? "true" : "false", + m_hitTopLeft ? "m_selStart" : "m_selEnd", + found.fLeft, found.fTop, found.fRight, found.fBottom, + m_extendSelection ? "true" : "false"); + if (m_hitTopLeft) { + m_startBase = base; + m_selStart = found; + } else { + m_endBase = base; + m_selEnd = found; + } + swapAsNeeded(); +} + +SkIRect SelectText::findClosest(FirstCheck& check, const SkPicture& picture, + int* base) +{ + LineCheck lineCheck(check.focusX(), check.focusY(), check.getArea()); + TextCanvas lineChecker(&lineCheck); + lineChecker.drawPicture(const_cast<SkPicture&>(picture)); + lineCheck.finish(m_selRegion); + check.setLines(&lineCheck); + TextCanvas checker(&check); + checker.drawPicture(const_cast<SkPicture&>(picture)); + check.finishGlyph(); + return check.adjustedBounds(base); +} + +SkIRect SelectText::findEdge(const SkPicture& picture, const SkIRect& area, + int x, int y, bool left, int* base) +{ + SkIRect result; + result.setEmpty(); + FirstCheck center(x, y, area); + center.setRecordGlyph(); + int closestBase; + SkIRect closest = findClosest(center, picture, &closestBase); + SkIRect sloppy = closest; + sloppy.inset(-TOUCH_SLOP, -TOUCH_SLOP); + if (!sloppy.contains(x, y)) { + DBG_NAV_LOGD("sloppy=(%d, %d, %d, %d) area=(%d, %d, %d, %d) x/y=%d,%d", + sloppy.fLeft, sloppy.fTop, sloppy.fRight, sloppy.fBottom, + area.fLeft, area.fTop, area.fRight, area.fBottom, x, y); + return result; + } + EdgeCheck edge(x, y, area, center, left); + do { // detect left or right until there's a gap + DBG_NAV_LOGD("edge=%p picture=%p area=%d,%d,%d,%d", + &edge, &picture, area.fLeft, area.fTop, area.fRight, area.fBottom); + TextCanvas checker(&edge); + checker.drawPicture(const_cast<SkPicture&>(picture)); + edge.finishGlyph(); + if (!edge.adjacent()) { + if (result.isEmpty()) { + *base = closestBase; + DBG_NAV_LOGD("closest=%d,%d,%d,%d", closest.fLeft, + closest.fTop, closest.fRight, closest.fBottom); + return closest; + } + DBG_NAV_LOG("adjacent break"); + break; + } + int nextBase; + const SkIRect& next = edge.bestBounds(&nextBase); + if (next.isEmpty()) { + DBG_NAV_LOG("empty"); + break; + } + if (result == next) { + DBG_NAV_LOG("result == next"); + break; + } + *base = nextBase; + result = next; + edge.shiftStart(result); + } while (true); + if (!result.isEmpty()) { + *base += area.fTop; + result.offset(area.fLeft, area.fTop); + } + return result; +} + +SkIRect SelectText::findLeft(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base) +{ + return findEdge(picture, area, x, y, true, base); +} + +SkIRect SelectText::findRight(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base) +{ + return findEdge(picture, area, x, y, false, base); +} + +const String SelectText::getSelection() +{ + if (!m_picture) + return String(); + SkIRect clipRect; + clipRect.set(0, 0, m_picture->width(), m_picture->height()); + String result = text(*m_picture, clipRect, m_selStart, m_startBase, + m_selEnd, m_endBase, m_flipped); + DBG_NAV_LOGD("clip=(%d,%d,%d,%d)" + " m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)", + clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom, + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); + DBG_NAV_LOGD("text=%s", result.latin1().data()); // uses CString + return result; +} + +void SelectText::getSelectionArrow(SkPath* path) +{ + const int arrow[] = { + 0, 14, 3, 11, 5, 15, 9, 15, 7, 11, 11, 11 + }; + for (unsigned index = 0; index < sizeof(arrow)/sizeof(arrow[0]); index += 2) + path->lineTo(arrow[index], arrow[index + 1]); + path->close(); +} + +void SelectText::getSelectionCaret(SkPath* path) +{ + SkScalar height = m_selStart.fBottom - m_selStart.fTop; + SkScalar dist = height / 4; + path->moveTo(0, -height / 2); + path->rLineTo(0, height); + path->rLineTo(-dist, dist); + path->rMoveTo(0, -0.5f); + path->rLineTo(dist * 2, 0); + path->rMoveTo(0, 0.5f); + path->rLineTo(-dist, -dist); +} + +bool SelectText::hitCorner(int cx, int cy, int x, int y) const +{ + SkIRect test; + test.set(cx, cy, cx + m_controlWidth, cy + m_controlHeight); + test.inset(-m_controlSlop, -m_controlSlop); + DBG_HANDLE_LOG("checking if %dx%d,%d-%d contains %dx%d", + cx, cy, m_controlWidth, m_controlHeight, x, y); + return test.contains(x, y); +} + +bool SelectText::hitStartHandle(int x, int y) const +{ + int left = m_selStart.fLeft - m_controlWidth; + return hitCorner(left, m_selStart.fBottom, x, y); +} + +bool SelectText::hitEndHandle(int x, int y) const +{ + int left = m_selEnd.fRight; + return hitCorner(left, m_selEnd.fBottom, x, y); +} + +bool SelectText::hitSelection(int x, int y) const +{ + x -= m_startOffset.fX; + y -= m_startOffset.fY; + if (hitStartHandle(x, y)) + return true; + if (hitEndHandle(x, y)) + return true; + return m_selRegion.contains(x, y); +} + +void SelectText::getSelectionHandles(int* handles, LayerAndroid* root) +{ + handles[0] = m_selStart.fLeft; + handles[1] = m_selStart.fBottom; + handles[2] = m_selEnd.fRight; + handles[3] = m_selEnd.fBottom; + if (root && m_layerId) { + Layer* layer = root->findById(m_layerId); + while (layer) { + const SkPoint& pos = layer->getPosition(); + handles[0] += pos.fX; + handles[2] += pos.fX; + handles[1] += pos.fY; + handles[3] += pos.fY; + layer = layer->getParent(); + } + } +} + +void SelectText::moveSelection(const IntRect& vis, int x, int y) +{ + if (!m_picture) + return; + x -= m_startOffset.fX; + y -= m_startOffset.fY; + setVisibleRect(vis); + SkIRect clipRect = m_visibleRect; + clipRect.join(m_selStart); + clipRect.join(m_selEnd); + FirstCheck center(x, y, clipRect); + int base; + SkIRect found = findClosest(center, *m_picture, &base); + if (m_hitTopLeft || !m_extendSelection) { + m_startBase = base; + m_selStart = found; + } + if (!m_hitTopLeft || !m_extendSelection) { + m_endBase = base; + m_selEnd = found; + } + swapAsNeeded(); + DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)" + " m_selEnd=(%d, %d, %d, %d)", x, y, m_extendSelection ? "true" : "false", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); +} + +void SelectText::reset() +{ + DBG_NAV_LOG("m_extendSelection=false"); + m_selStart.setEmpty(); + m_lastStart.setEmpty(); + m_lastDrawnStart.setEmpty(); + m_selEnd.setEmpty(); + m_lastEnd.setEmpty(); + m_lastDrawnEnd.setEmpty(); + m_extendSelection = false; + m_startSelection = false; + SkSafeUnref(m_picture); + m_picture = 0; + m_layerId = 0; +} + +IntPoint SelectText::selectableText(const CachedRoot* root) +{ + int x = 0; + int y = 0; + SkPicture* picture = root->pictureAt(&x, &y, &m_layerId); + if (!picture) { + DBG_NAV_LOG("picture==0"); + return IntPoint(0, 0); + } + int width = picture->width(); + int height = picture->height(); + IntRect vis(0, 0, width, height); + FirstCheck center(width >> 1, height >> 1, vis); + int base; + const SkIRect& closest = findClosest(center, *picture, &base); + return IntPoint((closest.fLeft + closest.fRight) >> 1, + (closest.fTop + closest.fBottom) >> 1); +} + +void SelectText::selectAll() +{ + if (!m_picture) + return; + m_selStart = findFirst(*m_picture, &m_startBase); + m_selEnd = findLast(*m_picture, &m_endBase); + m_extendSelection = true; +} + +int SelectText::selectionX() const +{ + return (m_hitTopLeft ? m_selStart.fLeft : m_selEnd.fRight) + m_startOffset.fX; +} + +int SelectText::selectionY() const +{ + const SkIRect& rect = m_hitTopLeft ? m_selStart : m_selEnd; + return ((rect.fTop + rect.fBottom) >> 1) + m_startOffset.fY; +} + +void SelectText::setVisibleRect(const IntRect& vis) +{ + DBG_NAV_LOGD("vis=(%d,%d,w=%d,h=%d) offset=(%d,%d)", + vis.x(), vis.y(), vis.width(), vis.height(), m_startOffset.fX, + m_startOffset.fY); + m_visibleRect = vis; + m_visibleRect.offset(-m_startOffset.fX, -m_startOffset.fY); +} + +bool SelectText::startSelection(const CachedRoot* root, const IntRect& vis, + int x, int y) +{ + m_wordSelection = false; + m_startOffset.set(x, y); + DBG_NAV_LOGD("x/y=(%d,%d)", x, y); + SkSafeUnref(m_picture); + m_picture = root->pictureAt(&x, &y, &m_layerId); + DBG_NAV_LOGD("m_picture=%p m_layerId=%d x/y=(%d,%d)", m_picture, m_layerId, + x, y); + if (!m_picture) { + DBG_NAV_LOG("picture==0"); + return false; + } + m_picture->ref(); + m_startOffset.fX -= x; + m_startOffset.fY -= y; + m_original.fX = x; + m_original.fY = y; + setVisibleRect(vis); + if (m_selStart.isEmpty()) { + DBG_NAV_LOGD("empty start picture=(%d,%d) x=%d y=%d", + m_picture->width(), m_picture->height(), x, y); + m_startSelection = true; + return true; + } + m_hitTopLeft = hitStartHandle(x, y); + bool hitBottomRight = hitEndHandle(x, y); + DBG_NAV_LOGD("picture=(%d,%d) left=%d top=%d right=%d bottom=%d x=%d y=%d", + m_picture->width(), m_picture->height(),left, top, right, bottom, x, y); + if (m_hitTopLeft) { + DBG_NAV_LOG("hit top left"); + m_original.fX -= m_selStart.fLeft; + m_original.fY -= (m_selStart.fTop + m_selStart.fBottom) >> 1; + } else if (hitBottomRight) { + DBG_NAV_LOG("hit bottom right"); + m_original.fX -= m_selEnd.fRight; + m_original.fY -= (m_selEnd.fTop + m_selEnd.fBottom) >> 1; + } + return m_hitTopLeft || hitBottomRight; +} + +void SelectText::updateHandleScale(float handleScale) +{ + m_controlHeight = CONTROL_HEIGHT * handleScale; + m_controlWidth = CONTROL_WIDTH * handleScale; + m_controlSlop = CONTROL_SLOP * handleScale; +} + +/* selects the word at (x, y) +* a word is normally delimited by spaces +* a string of digits (even with inside spaces) is a word (for phone numbers) +* FIXME: digit find isn't implemented yet +* returns true if a word was selected +*/ +bool SelectText::wordSelection(const CachedRoot* root, const IntRect& vis, + int x, int y) +{ + IntRect tapArea = IntRect(x - TOUCH_SLOP, y - TOUCH_SLOP, TOUCH_SLOP * 2, + TOUCH_SLOP * 2); + if (!startSelection(root, tapArea, x, y)) + return false; + extendSelection(tapArea, x, y); + if (m_selStart.isEmpty()) + return false; + setDrawPointer(false); + setVisibleRect(vis); + SkIRect ivisBounds = m_visibleRect; + ivisBounds.join(m_selStart); + ivisBounds.join(m_selEnd); + DBG_NAV_LOGD("m_selStart=(%d,%d,r=%d,b=%d) m_selEnd=(%d,%d,r=%d,b=%d)" + " ivisBounds=(%d,%d,r=%d,b=%d)", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom, + ivisBounds.fLeft, ivisBounds.fTop, ivisBounds.fRight, ivisBounds.fBottom); + m_selRegion.setEmpty(); + buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase, + m_selEnd, m_endBase, &m_selRegion); + x = m_selStart.fLeft; + y = (m_selStart.fTop + m_selStart.fBottom) >> 1; + SkIRect clipRect = m_visibleRect; + clipRect.fLeft -= m_visibleRect.width() >> 1; + clipRect.fLeft = std::max(clipRect.fLeft, 0); + int base; + SkIRect left = findLeft(*m_picture, clipRect, x, y, &base); + if (!left.isEmpty()) { + m_startBase = base; + m_selStart = left; + } + x = m_selEnd.fRight; + y = (m_selEnd.fTop + m_selEnd.fBottom) >> 1; + clipRect = m_visibleRect; + clipRect.fRight += m_visibleRect.width() >> 1; + SkIRect right = findRight(*m_picture, clipRect, x, y, &base); + if (!right.isEmpty()) { + m_endBase = base; + m_selEnd = right; + } + DBG_NAV_LOGD("m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); + if (!left.isEmpty() || !right.isEmpty()) { + m_wordBounds = m_selStart; + m_wordBounds.join(m_selEnd); + m_extendSelection = m_wordSelection = true; + m_outsideWord = false; + return true; + } + return false; +} + +void SelectText::swapAsNeeded() +{ + if (m_selStart.fTop >= (m_selEnd.fTop + m_selEnd.fBottom) >> 1 + || (m_selEnd.fTop < (m_selStart.fTop + m_selStart.fBottom) >> 1 + && m_selStart.fRight > m_selEnd.fLeft)) + { + SkTSwap(m_startBase, m_endBase); + SkTSwap(m_selStart, m_selEnd); + m_hitTopLeft ^= true; + DBG_NAV_LOGD("m_hitTopLeft=%s", m_hitTopLeft ? "true" : "false"); + } +} + +} diff --git a/Source/WebKit/android/nav/SelectText.h b/Source/WebKit/android/nav/SelectText.h index aaaf3bb..b454b8e 100644 --- a/Source/WebKit/android/nav/SelectText.h +++ b/Source/WebKit/android/nav/SelectText.h @@ -27,33 +27,90 @@ #define SelectText_h #include "DrawExtra.h" +#include "IntPoint.h" #include "IntRect.h" #include "PlatformString.h" +#include "SkPath.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkRegion.h" namespace android { -class SelectText : public RegionLayerDrawExtra { -public: - enum HandleId { - LeftHandle = 0, - RightHandle = 1, - }; - - IntRect& caretRect(HandleId id) { return m_caretRects[id]; } - void setCaretRect(HandleId id, const IntRect& rect) { m_caretRects[id] = rect; } - IntRect& textRect(HandleId id) { return m_textRects[id]; } - void setTextRect(HandleId id, const IntRect& rect) { m_textRects[id] = rect; } - int caretLayerId(HandleId id) { return m_caretLayerId[id]; } - void setCaretLayerId(HandleId id, int layerId) { m_caretLayerId[id] = layerId; } - - void setText(const String& text) { m_text = text.threadsafeCopy(); } - String& getText() { return m_text; } +class CachedRoot; +class SelectText : public DrawExtra { +public: + SelectText(); + virtual ~SelectText(); + virtual void draw(SkCanvas* , LayerAndroid* , IntRect* ); + void extendSelection(const IntRect& vis, int x, int y); + const String getSelection(); + bool hitSelection(int x, int y) const; + void moveSelection(const IntRect& vis, int x, int y); + void reset(); + IntPoint selectableText(const CachedRoot* ); + void selectAll(); + int selectionX() const; + int selectionY() const; + void setDrawPointer(bool drawPointer) { m_drawPointer = drawPointer; } + void setExtendSelection(bool extend) { m_extendSelection = extend; } + bool startSelection(const CachedRoot* , const IntRect& vis, int x, int y); + bool wordSelection(const CachedRoot* , const IntRect& vis, int x, int y); + void getSelectionRegion(const IntRect& vis, SkRegion *region, LayerAndroid* root); + void updateHandleScale(float handleScale); + void getSelectionHandles(int* handles, LayerAndroid* root); +public: + float m_inverseScale; // inverse scale, x, y used for drawing select path + int m_selectX; + int m_selectY; private: - IntRect m_caretRects[2]; - IntRect m_textRects[2]; - int m_caretLayerId[2]; - String m_text; + int m_controlWidth; + int m_controlHeight; + int m_controlSlop; + class FirstCheck; + class EdgeCheck; + void drawSelectionPointer(SkCanvas* , IntRect* ); + void drawSelectionRegion(SkCanvas* , IntRect* ); + SkIRect findClosest(FirstCheck& , const SkPicture& , int* base); + SkIRect findEdge(const SkPicture& , const SkIRect& area, + int x, int y, bool left, int* base); + SkIRect findLeft(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base); + SkIRect findRight(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base); + static void getSelectionArrow(SkPath* ); + void getSelectionCaret(SkPath* ); + bool hitCorner(int cx, int cy, int x, int y) const; + bool hitStartHandle(int x, int y) const; + bool hitEndHandle(int x, int y) const; + void setVisibleRect(const IntRect& ); + void swapAsNeeded(); + SkIPoint m_original; // computed start of extend selection + SkIPoint m_startOffset; // difference from global to layer + SkIRect m_selStart; + SkIRect m_selEnd; + SkIRect m_lastStart; + SkIRect m_lastEnd; + SkIRect m_lastDrawnStart; + SkIRect m_lastDrawnEnd; + SkIRect m_wordBounds; + int m_startBase; + int m_endBase; + int m_layerId; + SkIRect m_visibleRect; // constrains picture computations to visible area + SkRegion m_lastSelRegion; + SkRegion m_selRegion; // computed from sel start, end + SkPicture m_startControl; + SkPicture m_endControl; + const SkPicture* m_picture; + bool m_drawPointer; + bool m_extendSelection; // false when trackball is moving pointer + bool m_flipped; + bool m_hitTopLeft; + bool m_startSelection; + bool m_wordSelection; + bool m_outsideWord; }; } diff --git a/Source/WebKit/android/nav/WebView.cpp b/Source/WebKit/android/nav/WebView.cpp index a67b5fd..7cb41d9 100644 --- a/Source/WebKit/android/nav/WebView.cpp +++ b/Source/WebKit/android/nav/WebView.cpp @@ -30,16 +30,17 @@ #include "AndroidAnimation.h" #include "AndroidLog.h" #include "BaseLayerAndroid.h" -#include "BaseRenderer.h" +#include "CachedFrame.h" +#include "CachedNode.h" +#include "CachedRoot.h" #include "DrawExtra.h" +#include "FindCanvas.h" #include "Frame.h" -#include "GLWebViewState.h" #include "GraphicsJNI.h" #include "HTMLInputElement.h" #include "IntPoint.h" #include "IntRect.h" #include "LayerAndroid.h" -#include "LayerContent.h" #include "Node.h" #include "utils/Functor.h" #include "private/hwui/DrawGlInfo.h" @@ -52,8 +53,10 @@ #include "SkPicture.h" #include "SkRect.h" #include "SkTime.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif #include "TilesManager.h" -#include "TransferQueue.h" #include "WebCoreJni.h" #include "WebRequestContext.h" #include "WebViewCore.h" @@ -68,7 +71,7 @@ #include <JNIUtility.h> #include <JNIHelp.h> #include <jni.h> -#include <androidfw/KeycodeLabels.h> +#include <ui/KeycodeLabels.h> #include <wtf/text/AtomicString.h> #include <wtf/text/CString.h> @@ -92,7 +95,7 @@ static jfieldID gWebViewField; static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) { jmethodID m = env->GetMethodID(clazz, name, signature); - ALOG_ASSERT(m, "Could not find method %s", name); + LOG_ASSERT(m, "Could not find method %s", name); return m; } @@ -107,81 +110,105 @@ enum FrameCachePermission { AllowNewer }; -#define DRAW_EXTRAS_SIZE 2 enum DrawExtras { // keep this in sync with WebView.java DrawExtrasNone = 0, - DrawExtrasSelection = 1, - DrawExtrasCursorRing = 2 + DrawExtrasFind = 1, + DrawExtrasSelection = 2, + DrawExtrasCursorRing = 3 }; struct JavaGlue { jweak m_obj; + jmethodID m_overrideLoading; jmethodID m_scrollBy; + jmethodID m_sendMoveFocus; + jmethodID m_sendMoveMouse; + jmethodID m_sendMoveMouseIfLatest; + jmethodID m_sendMotionUp; + jmethodID m_domChangedFocus; jmethodID m_getScaledMaxXScroll; jmethodID m_getScaledMaxYScroll; - jmethodID m_updateRectsForGL; + jmethodID m_getVisibleRect; + jmethodID m_rebuildWebTextView; jmethodID m_viewInvalidate; jmethodID m_viewInvalidateRect; jmethodID m_postInvalidateDelayed; jmethodID m_pageSwapCallback; + jmethodID m_inFullScreenMode; jfieldID m_rectLeft; jfieldID m_rectTop; jmethodID m_rectWidth; jmethodID m_rectHeight; - jfieldID m_quadFP1; - jfieldID m_quadFP2; - jfieldID m_quadFP3; - jfieldID m_quadFP4; + jfieldID m_rectFLeft; + jfieldID m_rectFTop; + jmethodID m_rectFWidth; + jmethodID m_rectFHeight; + jmethodID m_getTextHandleScale; AutoJObject object(JNIEnv* env) { return getRealObject(env, m_obj); } } m_javaGlue; WebView(JNIEnv* env, jobject javaWebView, int viewImpl, WTF::String drawableDir, - bool isHighEndGfx) - : m_isHighEndGfx(isHighEndGfx) + bool isHighEndGfx) : + m_ring((WebViewCore*) viewImpl) + , m_isHighEndGfx(isHighEndGfx) { - memset(m_extras, 0, DRAW_EXTRAS_SIZE * sizeof(DrawExtra*)); - jclass clazz = env->FindClass("android/webkit/WebViewClassic"); + jclass clazz = env->FindClass("android/webkit/WebView"); + // m_javaGlue = new JavaGlue; m_javaGlue.m_obj = env->NewWeakGlobalRef(javaWebView); m_javaGlue.m_scrollBy = GetJMethod(env, clazz, "setContentScrollBy", "(IIZ)Z"); + m_javaGlue.m_overrideLoading = GetJMethod(env, clazz, "overrideLoading", "(Ljava/lang/String;)V"); + m_javaGlue.m_sendMoveFocus = GetJMethod(env, clazz, "sendMoveFocus", "(II)V"); + m_javaGlue.m_sendMoveMouse = GetJMethod(env, clazz, "sendMoveMouse", "(IIII)V"); + m_javaGlue.m_sendMoveMouseIfLatest = GetJMethod(env, clazz, "sendMoveMouseIfLatest", "(ZZ)V"); + m_javaGlue.m_sendMotionUp = GetJMethod(env, clazz, "sendMotionUp", "(IIIII)V"); + m_javaGlue.m_domChangedFocus = GetJMethod(env, clazz, "domChangedFocus", "()V"); m_javaGlue.m_getScaledMaxXScroll = GetJMethod(env, clazz, "getScaledMaxXScroll", "()I"); m_javaGlue.m_getScaledMaxYScroll = GetJMethod(env, clazz, "getScaledMaxYScroll", "()I"); - m_javaGlue.m_updateRectsForGL = GetJMethod(env, clazz, "updateRectsForGL", "()V"); + m_javaGlue.m_getVisibleRect = GetJMethod(env, clazz, "sendOurVisibleRect", "()Landroid/graphics/Rect;"); + m_javaGlue.m_rebuildWebTextView = GetJMethod(env, clazz, "rebuildWebTextView", "()V"); m_javaGlue.m_viewInvalidate = GetJMethod(env, clazz, "viewInvalidate", "()V"); m_javaGlue.m_viewInvalidateRect = GetJMethod(env, clazz, "viewInvalidate", "(IIII)V"); m_javaGlue.m_postInvalidateDelayed = GetJMethod(env, clazz, "viewInvalidateDelayed", "(JIIII)V"); m_javaGlue.m_pageSwapCallback = GetJMethod(env, clazz, "pageSwapCallback", "(Z)V"); + m_javaGlue.m_inFullScreenMode = GetJMethod(env, clazz, "inFullScreenMode", "()Z"); + m_javaGlue.m_getTextHandleScale = GetJMethod(env, clazz, "getTextHandleScale", "()F"); env->DeleteLocalRef(clazz); jclass rectClass = env->FindClass("android/graphics/Rect"); - ALOG_ASSERT(rectClass, "Could not find Rect class"); + LOG_ASSERT(rectClass, "Could not find Rect class"); m_javaGlue.m_rectLeft = env->GetFieldID(rectClass, "left", "I"); m_javaGlue.m_rectTop = env->GetFieldID(rectClass, "top", "I"); m_javaGlue.m_rectWidth = GetJMethod(env, rectClass, "width", "()I"); m_javaGlue.m_rectHeight = GetJMethod(env, rectClass, "height", "()I"); env->DeleteLocalRef(rectClass); - jclass quadFClass = env->FindClass("android/webkit/QuadF"); - ALOG_ASSERT(quadFClass, "Could not find QuadF class"); - m_javaGlue.m_quadFP1 = env->GetFieldID(quadFClass, "p1", "Landroid/graphics/PointF;"); - m_javaGlue.m_quadFP2 = env->GetFieldID(quadFClass, "p2", "Landroid/graphics/PointF;"); - m_javaGlue.m_quadFP3 = env->GetFieldID(quadFClass, "p3", "Landroid/graphics/PointF;"); - m_javaGlue.m_quadFP4 = env->GetFieldID(quadFClass, "p4", "Landroid/graphics/PointF;"); - env->DeleteLocalRef(quadFClass); + jclass rectClassF = env->FindClass("android/graphics/RectF"); + LOG_ASSERT(rectClassF, "Could not find RectF class"); + m_javaGlue.m_rectFLeft = env->GetFieldID(rectClassF, "left", "F"); + m_javaGlue.m_rectFTop = env->GetFieldID(rectClassF, "top", "F"); + m_javaGlue.m_rectFWidth = GetJMethod(env, rectClassF, "width", "()F"); + m_javaGlue.m_rectFHeight = GetJMethod(env, rectClassF, "height", "()F"); + env->DeleteLocalRef(rectClassF); env->SetIntField(javaWebView, gWebViewField, (jint)this); m_viewImpl = (WebViewCore*) viewImpl; + m_frameCacheUI = 0; + m_navPictureUI = 0; m_generation = 0; m_heightCanMeasure = false; m_lastDx = 0; m_lastDxTime = 0; + m_ringAnimationEnd = 0; m_baseLayer = 0; m_glDrawFunctor = 0; m_isDrawingPaused = false; + m_buttonSkin = drawableDir.isEmpty() ? 0 : new RenderSkinButton(drawableDir); #if USE(ACCELERATED_COMPOSITING) m_glWebViewState = 0; + m_pageSwapCallbackRegistered = false; #endif } @@ -199,17 +226,11 @@ WebView(JNIEnv* env, jobject javaWebView, int viewImpl, WTF::String drawableDir, // deallocated base layer. stopGL(); #endif + delete m_frameCacheUI; + delete m_navPictureUI; SkSafeUnref(m_baseLayer); delete m_glDrawFunctor; - for (int i = 0; i < DRAW_EXTRAS_SIZE; i++) - delete m_extras[i]; -} - -DrawExtra* getDrawExtra(DrawExtras extras) -{ - if (extras == DrawExtrasNone) - return 0; - return m_extras[extras - 1]; + delete m_buttonSkin; } void stopGL() @@ -224,6 +245,126 @@ WebViewCore* getWebViewCore() const { return m_viewImpl; } +float getTextHandleScale() +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return 0; + float result = env->CallFloatMethod(javaObject.get(), m_javaGlue.m_getTextHandleScale); + checkException(env); + return result; +} + +void updateSelectionHandles() +{ + if (!m_baseLayer) + return; + // Adjust for device density & scale + m_selectText.updateHandleScale(getTextHandleScale()); +} + +// removes the cursor altogether (e.g., when going to a new page) +void clearCursor() +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + DBG_NAV_LOG(""); + m_viewImpl->m_hasCursorBounds = false; + root->clearCursor(); + viewInvalidate(); +} + +// leaves the cursor where it is, but suppresses drawing it +void hideCursor() +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + DBG_NAV_LOG(""); + hideCursor(root); + viewInvalidate(); +} + +void hideCursor(CachedRoot* root) +{ + DBG_NAV_LOG("inner"); + m_viewImpl->m_hasCursorBounds = false; + root->hideCursor(); +} + +#if DUMP_NAV_CACHE +void debugDump() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) + root->mDebug.print(); +} +#endif + +void scrollToCurrentMatch() +{ + if (!m_findOnPage.currentMatchIsInLayer()) { + scrollRectOnScreen(m_findOnPage.currentMatchBounds()); + return; + } + + SkRect matchBounds = m_findOnPage.currentMatchBounds(); + LayerAndroid* rootLayer = getFrameCache(DontAllowNewer)->rootLayer(); + Layer* layerContainingMatch = rootLayer->findById(m_findOnPage.currentMatchLayerId()); + ASSERT(layerContainingMatch); + + // If the match is in a fixed position layer, there's nothing to do. + if (layerContainingMatch->shouldInheritFromRootTransform()) + return; + + // If the match is in a scrollable layer or a descendant of such a layer, + // there may be a range of of scroll configurations that will make the + // current match visible. Our approach is the simplest possible. Starting at + // the layer in which the match is found, we move up the layer tree, + // scrolling any scrollable layers as little as possible to make sure that + // the current match is in view. This approach has the disadvantage that we + // may end up scrolling a larger number of elements than is necessary, which + // may be visually jarring. However, minimising the number of layers + // scrolled would complicate the code significantly. + + bool didScrollLayer = false; + for (Layer* layer = layerContainingMatch; layer; layer = layer->getParent()) { + ASSERT(layer->getParent() || layer == rootLayer); + + if (layer->contentIsScrollable()) { + // Convert the match location to layer's local space and scroll it. + // Repeatedly calling Layer::localToAncestor() is inefficient as + // each call repeats part of the calculation. It would be more + // efficient to maintain the transform here and update it on each + // iteration, but that would mean duplicating logic from + // Layer::localToAncestor() and would complicate things. + SkMatrix transform; + layerContainingMatch->localToAncestor(layer, &transform); + SkRect transformedMatchBounds; + transform.mapRect(&transformedMatchBounds, matchBounds); + SkIRect roundedTransformedMatchBounds; + transformedMatchBounds.roundOut(&roundedTransformedMatchBounds); + // Only ScrollableLayerAndroid returns true for contentIsScrollable(). + didScrollLayer |= static_cast<ScrollableLayerAndroid*>(layer)->scrollRectIntoView(roundedTransformedMatchBounds); + } + } + // Invalidate, as the call below to scroll the main page may be a no-op. + if (didScrollLayer) + viewInvalidate(); + + // Convert matchBounds to the global space so we can scroll the main page. + SkMatrix transform; + layerContainingMatch->localToGlobal(&transform); + SkRect transformedMatchBounds; + transform.mapRect(&transformedMatchBounds, matchBounds); + SkIRect roundedTransformedMatchBounds; + transformedMatchBounds.roundOut(&roundedTransformedMatchBounds); + scrollRectOnScreen(roundedTransformedMatchBounds); +} + void scrollRectOnScreen(const IntRect& rect) { if (rect.isEmpty()) @@ -231,59 +372,147 @@ void scrollRectOnScreen(const IntRect& rect) int dx = 0; int left = rect.x(); int right = rect.maxX(); - if (left < m_visibleContentRect.fLeft) - dx = left - m_visibleContentRect.fLeft; + if (left < m_visibleRect.fLeft) + dx = left - m_visibleRect.fLeft; // Only scroll right if the entire width can fit on screen. - else if (right > m_visibleContentRect.fRight - && right - left < m_visibleContentRect.width()) - dx = right - m_visibleContentRect.fRight; + else if (right > m_visibleRect.fRight + && right - left < m_visibleRect.width()) + dx = right - m_visibleRect.fRight; int dy = 0; int top = rect.y(); int bottom = rect.maxY(); - if (top < m_visibleContentRect.fTop) - dy = top - m_visibleContentRect.fTop; + if (top < m_visibleRect.fTop) + dy = top - m_visibleRect.fTop; // Only scroll down if the entire height can fit on screen - else if (bottom > m_visibleContentRect.fBottom - && bottom - top < m_visibleContentRect.height()) - dy = bottom - m_visibleContentRect.fBottom; + else if (bottom > m_visibleRect.fBottom + && bottom - top < m_visibleRect.height()) + dy = bottom - m_visibleRect.fBottom; if ((dx|dy) == 0 || !scrollBy(dx, dy)) return; viewInvalidate(); } -int drawGL(WebCore::IntRect& invScreenRect, WebCore::IntRect* invalRect, - WebCore::IntRect& screenRect, int titleBarHeight, - WebCore::IntRect& screenClip, float scale, int extras, bool shouldDraw) +void resetCursorRing() { + m_ringAnimationEnd = 0; + m_viewImpl->m_hasCursorBounds = false; +} + +bool drawCursorPreamble(CachedRoot* root) +{ + if (!root) return false; + const CachedFrame* frame; + const CachedNode* node = root->currentCursor(&frame); + if (!node) { + DBG_NAV_LOGV("%s", "!node"); + resetCursorRing(); + return false; + } + m_ring.setIsButton(node); + if (node->isHidden()) { + DBG_NAV_LOG("node->isHidden()"); + m_viewImpl->m_hasCursorBounds = false; + return false; + } #if USE(ACCELERATED_COMPOSITING) - if (!m_baseLayer) - return 0; + if (node->isInLayer() && root->rootLayer()) { + LayerAndroid* layer = root->rootLayer(); + layer->updateFixedLayersPositions(m_visibleRect); + layer->updatePositions(); + } +#endif + setVisibleRect(root); + m_ring.m_root = root; + m_ring.m_frame = frame; + m_ring.m_node = node; + SkMSec time = SkTime::GetMSecs(); + m_ring.m_isPressed = time < m_ringAnimationEnd + && m_ringAnimationEnd != UINT_MAX; + return true; +} - if (m_viewImpl) - m_viewImpl->setPrerenderingEnabled(!m_isDrawingPaused); +void drawCursorPostamble() +{ + if (m_ringAnimationEnd == UINT_MAX) + return; + SkMSec time = SkTime::GetMSecs(); + if (time < m_ringAnimationEnd) { + // views assume that inval bounds coordinates are non-negative + WebCore::IntRect invalBounds(0, 0, INT_MAX, INT_MAX); + invalBounds.intersect(m_ring.m_absBounds); + postInvalidateDelayed(m_ringAnimationEnd - time, invalBounds); + } else { + hideCursor(const_cast<CachedRoot*>(m_ring.m_root)); + } +} + +bool drawGL(WebCore::IntRect& viewRect, WebCore::IntRect* invalRect, + WebCore::IntRect& webViewRect, int titleBarHeight, + WebCore::IntRect& clip, float scale, int extras) +{ +#if USE(ACCELERATED_COMPOSITING) + if (!m_baseLayer || inFullScreenMode()) + return false; if (!m_glWebViewState) { - TilesManager::instance()->setHighEndGfx(m_isHighEndGfx); m_glWebViewState = new GLWebViewState(); - m_glWebViewState->setBaseLayer(m_baseLayer, false, true); + m_glWebViewState->setHighEndGfx(m_isHighEndGfx); + m_glWebViewState->glExtras()->setCursorRingExtra(&m_ring); + m_glWebViewState->glExtras()->setFindOnPageExtra(&m_findOnPage); + if (m_baseLayer->content()) { + SkRegion region; + SkIRect rect; + rect.set(0, 0, m_baseLayer->content()->width(), m_baseLayer->content()->height()); + region.setRect(rect); + m_glWebViewState->setBaseLayer(m_baseLayer, region, false, true); + } } - DrawExtra* extra = getDrawExtra((DrawExtras) extras); + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + if (extras == DrawExtrasCursorRing) + resetCursorRing(); + } + DrawExtra* extra = 0; + switch (extras) { + case DrawExtrasFind: + extra = &m_findOnPage; + break; + case DrawExtrasSelection: + // This will involve a JNI call, but under normal circumstances we will + // not hit this anyway. Only if USE_JAVA_TEXT_SELECTION is disabled + // in WebView.java will we hit this (so really debug only) + updateSelectionHandles(); + extra = &m_selectText; + break; + case DrawExtrasCursorRing: + if (drawCursorPreamble(root) && m_ring.setup()) { + if (m_ring.m_isPressed || m_ringAnimationEnd == UINT_MAX) + extra = &m_ring; + drawCursorPostamble(); + } + break; + default: + ; + } + unsigned int pic = m_glWebViewState->currentPictureCounter(); m_glWebViewState->glExtras()->setDrawExtra(extra); // Make sure we have valid coordinates. We might not have valid coords // if the zoom manager is still initializing. We will be redrawn // once the correct scale is set - if (!m_visibleContentRect.isFinite()) - return 0; + if (!m_visibleRect.isFinite()) + return false; bool treesSwapped = false; bool newTreeHasAnim = false; - int ret = m_glWebViewState->drawGL(invScreenRect, m_visibleContentRect, invalRect, - screenRect, titleBarHeight, screenClip, scale, - &treesSwapped, &newTreeHasAnim, shouldDraw); - if (treesSwapped) { - ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + bool ret = m_glWebViewState->drawGL(viewRect, m_visibleRect, invalRect, + webViewRect, titleBarHeight, clip, scale, + &treesSwapped, &newTreeHasAnim); + if (treesSwapped && (m_pageSwapCallbackRegistered || newTreeHasAnim)) { + m_pageSwapCallbackRegistered = false; + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue.object(env); if (javaObject.get()) { @@ -291,45 +520,237 @@ int drawGL(WebCore::IntRect& invScreenRect, WebCore::IntRect* invalRect, checkException(env); } } - return m_isDrawingPaused ? 0 : ret; + if (ret || m_glWebViewState->currentPictureCounter() != pic) + return !m_isDrawingPaused; #endif - return 0; + return false; } -void draw(SkCanvas* canvas, SkColor bgColor, DrawExtras extras) +PictureSet* draw(SkCanvas* canvas, SkColor bgColor, int extras, bool split) { + PictureSet* ret = 0; if (!m_baseLayer) { canvas->drawColor(bgColor); - return; + return ret; } // draw the content of the base layer first - LayerContent* content = m_baseLayer->content(); + PictureSet* content = m_baseLayer->content(); int sc = canvas->save(SkCanvas::kClip_SaveFlag); - if (content) { - canvas->clipRect(SkRect::MakeLTRB(0, 0, content->width(), content->height()), - SkRegion::kDifference_Op); - } - Color c = m_baseLayer->getBackgroundColor(); - canvas->drawColor(SkColorSetARGBInline(c.alpha(), c.red(), c.green(), c.blue())); + canvas->clipRect(SkRect::MakeLTRB(0, 0, content->width(), + content->height()), SkRegion::kDifference_Op); + canvas->drawColor(bgColor); canvas->restoreToCount(sc); + if (content->draw(canvas)) + ret = split ? new PictureSet(*content) : 0; + + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + if (extras == DrawExtrasCursorRing) + resetCursorRing(); + } + LayerAndroid mainPicture(m_navPictureUI); + DrawExtra* extra = 0; + switch (extras) { + case DrawExtrasFind: + extra = &m_findOnPage; + break; + case DrawExtrasSelection: + // This will involve a JNI call, but under normal circumstances we will + // not hit this anyway. Only if USE_JAVA_TEXT_SELECTION is disabled + // in WebView.java will we hit this (so really debug only) + updateSelectionHandles(); + extra = &m_selectText; + break; + case DrawExtrasCursorRing: + if (drawCursorPreamble(root) && m_ring.setup()) { + extra = &m_ring; + drawCursorPostamble(); + } + break; + default: + ; + } +#if USE(ACCELERATED_COMPOSITING) + LayerAndroid* compositeLayer = compositeRoot(); + if (compositeLayer) { + // call this to be sure we've adjusted for any scrolling or animations + // before we actually draw + compositeLayer->updateFixedLayersPositions(m_visibleRect); + compositeLayer->updatePositions(); + // We have to set the canvas' matrix on the base layer + // (to have fixed layers work as intended) + SkAutoCanvasRestore restore(canvas, true); + m_baseLayer->setMatrix(canvas->getTotalMatrix()); + canvas->resetMatrix(); + m_baseLayer->draw(canvas); + } +#endif + if (extra) { + IntRect dummy; // inval area, unused for now + extra->draw(canvas, &mainPicture, &dummy); + } + return ret; +} - // call this to be sure we've adjusted for any scrolling or animations - // before we actually draw - m_baseLayer->updatePositionsRecursive(m_visibleContentRect); - m_baseLayer->updatePositions(); - // We have to set the canvas' matrix on the base layer - // (to have fixed layers work as intended) - SkAutoCanvasRestore restore(canvas, true); - m_baseLayer->setMatrix(canvas->getTotalMatrix()); - canvas->resetMatrix(); - m_baseLayer->draw(canvas, getDrawExtra(extras)); +bool cursorIsTextInput(FrameCachePermission allowNewer) +{ + CachedRoot* root = getFrameCache(allowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* cursor = root->currentCursor(); + if (!cursor) { + DBG_NAV_LOG("!cursor"); + return false; + } + DBG_NAV_LOGD("%s", cursor->isTextInput() ? "true" : "false"); + return cursor->isTextInput(); +} + +void cursorRingBounds(WebCore::IntRect* bounds) +{ + DBG_NAV_LOGD("%s", ""); + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedFrame* cachedFrame; + const CachedNode* cachedNode = root->currentCursor(&cachedFrame); + if (cachedNode) { + *bounds = cachedNode->cursorRingBounds(cachedFrame); + DBG_NAV_LOGD("bounds={%d,%d,%d,%d}", bounds->x(), bounds->y(), + bounds->width(), bounds->height()); + return; + } + } + *bounds = WebCore::IntRect(0, 0, 0, 0); +} + +void fixCursor() +{ + m_viewImpl->gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_viewImpl->m_hasCursorBounds; + IntRect bounds = m_viewImpl->m_cursorBounds; + m_viewImpl->gCursorBoundsMutex.unlock(); + if (!hasCursorBounds) + return; + int x, y; + const CachedFrame* frame; + const CachedNode* node = m_frameCacheUI->findAt(bounds, &frame, &x, &y, true); + if (!node) + return; + // require that node have approximately the same bounds (+/- 4) and the same + // center (+/- 2) + IntPoint oldCenter = IntPoint(bounds.x() + (bounds.width() >> 1), + bounds.y() + (bounds.height() >> 1)); + IntRect newBounds = node->bounds(frame); + IntPoint newCenter = IntPoint(newBounds.x() + (newBounds.width() >> 1), + newBounds.y() + (newBounds.height() >> 1)); + DBG_NAV_LOGD("oldCenter=(%d,%d) newCenter=(%d,%d)" + " bounds=(%d,%d,w=%d,h=%d) newBounds=(%d,%d,w=%d,h=%d)", + oldCenter.x(), oldCenter.y(), newCenter.x(), newCenter.y(), + bounds.x(), bounds.y(), bounds.width(), bounds.height(), + newBounds.x(), newBounds.y(), newBounds.width(), newBounds.height()); + if (abs(oldCenter.x() - newCenter.x()) > 2) + return; + if (abs(oldCenter.y() - newCenter.y()) > 2) + return; + if (abs(bounds.x() - newBounds.x()) > 4) + return; + if (abs(bounds.y() - newBounds.y()) > 4) + return; + if (abs(bounds.maxX() - newBounds.maxX()) > 4) + return; + if (abs(bounds.maxY() - newBounds.maxY()) > 4) + return; + DBG_NAV_LOGD("node=%p frame=%p x=%d y=%d bounds=(%d,%d,w=%d,h=%d)", + node, frame, x, y, bounds.x(), bounds.y(), bounds.width(), + bounds.height()); + m_frameCacheUI->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(node)); +} + +CachedRoot* getFrameCache(FrameCachePermission allowNewer) +{ + if (!m_viewImpl->m_updatedFrameCache) { + DBG_NAV_LOGV("%s", "!m_viewImpl->m_updatedFrameCache"); + return m_frameCacheUI; + } + if (allowNewer == DontAllowNewer && m_viewImpl->m_lastGeneration < m_generation) { + DBG_NAV_LOGD("allowNewer==DontAllowNewer m_viewImpl->m_lastGeneration=%d" + " < m_generation=%d", m_viewImpl->m_lastGeneration, m_generation); + return m_frameCacheUI; + } + DBG_NAV_LOGD("%s", "m_viewImpl->m_updatedFrameCache == true"); + const CachedFrame* oldCursorFrame; + const CachedNode* oldCursorNode = m_frameCacheUI ? + m_frameCacheUI->currentCursor(&oldCursorFrame) : 0; +#if USE(ACCELERATED_COMPOSITING) + int layerId = -1; + if (oldCursorNode && oldCursorNode->isInLayer()) { + const LayerAndroid* cursorLayer = oldCursorFrame->layer(oldCursorNode) + ->layer(m_frameCacheUI->rootLayer()); + if (cursorLayer) + layerId = cursorLayer->uniqueId(); + } +#endif + // get id from old layer and use to find new layer + bool oldFocusIsTextInput = false; + void* oldFocusNodePointer = 0; + if (m_frameCacheUI) { + const CachedNode* oldFocus = m_frameCacheUI->currentFocus(); + if (oldFocus) { + oldFocusIsTextInput = oldFocus->isTextInput(); + oldFocusNodePointer = oldFocus->nodePointer(); + } + } + m_viewImpl->gFrameCacheMutex.lock(); + delete m_frameCacheUI; + SkSafeUnref(m_navPictureUI); + m_viewImpl->m_updatedFrameCache = false; + m_frameCacheUI = m_viewImpl->m_frameCacheKit; + m_navPictureUI = m_viewImpl->m_navPictureKit; + m_viewImpl->m_frameCacheKit = 0; + m_viewImpl->m_navPictureKit = 0; + m_viewImpl->gFrameCacheMutex.unlock(); + if (m_frameCacheUI) + m_frameCacheUI->setRootLayer(compositeRoot()); +#if USE(ACCELERATED_COMPOSITING) + if (layerId >= 0) { + LayerAndroid* layer = const_cast<LayerAndroid*>( + m_frameCacheUI->rootLayer()); + if (layer) { + layer->updateFixedLayersPositions(m_visibleRect); + layer->updatePositions(); + } + } +#endif + fixCursor(); + if (oldFocusIsTextInput) { + const CachedNode* newFocus = m_frameCacheUI->currentFocus(); + if (newFocus && oldFocusNodePointer != newFocus->nodePointer() + && newFocus->isTextInput() + && newFocus != m_frameCacheUI->currentCursor()) { + // The focus has changed. We may need to update things. + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (javaObject.get()) { + env->CallVoidMethod(javaObject.get(), m_javaGlue.m_domChangedFocus); + checkException(env); + } + } + } + if (oldCursorNode && (!m_frameCacheUI || !m_frameCacheUI->currentCursor())) + viewInvalidate(); // redraw in case cursor ring is still visible + return m_frameCacheUI; } int getScaledMaxXScroll() { - ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue.object(env); if (!javaObject.get()) @@ -341,7 +762,7 @@ int getScaledMaxXScroll() int getScaledMaxYScroll() { - ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue.object(env); if (!javaObject.get()) @@ -351,37 +772,294 @@ int getScaledMaxYScroll() return result; } -// Call through JNI to ask Java side to update the rectangles for GL functor. -// This is called at every draw when it is not in process mode, so we should -// keep this route as efficient as possible. Currently, its average cost on Xoom -// is about 0.1ms - 0.2ms. -// Alternatively, this can be achieved by adding more listener on Java side, but -// that will be more likely causing jank when triggering GC. -void updateRectsForGL() +IntRect getVisibleRect() { + IntRect rect; + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue.object(env); if (!javaObject.get()) - return; - env->CallVoidMethod(javaObject.get(), m_javaGlue.m_updateRectsForGL); + return rect; + jobject jRect = env->CallObjectMethod(javaObject.get(), m_javaGlue.m_getVisibleRect); + checkException(env); + rect.setX(env->GetIntField(jRect, m_javaGlue.m_rectLeft)); + checkException(env); + rect.setY(env->GetIntField(jRect, m_javaGlue.m_rectTop)); + checkException(env); + rect.setWidth(env->CallIntMethod(jRect, m_javaGlue.m_rectWidth)); checkException(env); + rect.setHeight(env->CallIntMethod(jRect, m_javaGlue.m_rectHeight)); + checkException(env); + env->DeleteLocalRef(jRect); + checkException(env); + return rect; +} + +static CachedFrame::Direction KeyToDirection(int32_t keyCode) +{ + switch (keyCode) { + case AKEYCODE_DPAD_RIGHT: + DBG_NAV_LOGD("keyCode=%s", "right"); + return CachedFrame::RIGHT; + case AKEYCODE_DPAD_LEFT: + DBG_NAV_LOGD("keyCode=%s", "left"); + return CachedFrame::LEFT; + case AKEYCODE_DPAD_DOWN: + DBG_NAV_LOGD("keyCode=%s", "down"); + return CachedFrame::DOWN; + case AKEYCODE_DPAD_UP: + DBG_NAV_LOGD("keyCode=%s", "up"); + return CachedFrame::UP; + default: + DBG_NAV_LOGD("bad key %d sent", keyCode); + return CachedFrame::UNINITIALIZED; + } +} + +WTF::String imageURI(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + return root ? root->imageURI(x, y) : WTF::String(); +} + +bool cursorWantsKeyEvents() +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedNode* focus = root->currentCursor(); + if (focus) + return focus->wantsKeyEvents(); + } + return false; +} + + +/* returns true if the key had no effect (neither scrolled nor changed cursor) */ +bool moveCursor(int keyCode, int count, bool ignoreScroll) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return true; + } + + m_viewImpl->m_moveGeneration++; + CachedFrame::Direction direction = KeyToDirection(keyCode); + const CachedFrame* cachedFrame, * oldFrame = 0; + const CachedNode* cursor = root->currentCursor(&oldFrame); + WebCore::IntPoint cursorLocation = root->cursorLocation(); + DBG_NAV_LOGD("old cursor %d (nativeNode=%p) cursorLocation={%d, %d}", + cursor ? cursor->index() : 0, + cursor ? cursor->nodePointer() : 0, cursorLocation.x(), cursorLocation.y()); + WebCore::IntRect visibleRect = setVisibleRect(root); + int xMax = getScaledMaxXScroll(); + int yMax = getScaledMaxYScroll(); + root->setMaxScroll(xMax, yMax); + const CachedNode* cachedNode = 0; + int dx = 0; + int dy = 0; + int counter = count; + while (--counter >= 0) { + WebCore::IntPoint scroll = WebCore::IntPoint(0, 0); + cachedNode = root->moveCursor(direction, &cachedFrame, &scroll); + dx += scroll.x(); + dy += scroll.y(); + } + DBG_NAV_LOGD("new cursor %d (nativeNode=%p) cursorLocation={%d, %d}" + "bounds={%d,%d,w=%d,h=%d}", cachedNode ? cachedNode->index() : 0, + cachedNode ? cachedNode->nodePointer() : 0, + root->cursorLocation().x(), root->cursorLocation().y(), + cachedNode ? cachedNode->bounds(cachedFrame).x() : 0, + cachedNode ? cachedNode->bounds(cachedFrame).y() : 0, + cachedNode ? cachedNode->bounds(cachedFrame).width() : 0, + cachedNode ? cachedNode->bounds(cachedFrame).height() : 0); + // If !m_heightCanMeasure (such as in the browser), we want to scroll no + // matter what + if (!ignoreScroll && (!m_heightCanMeasure || + !cachedNode || + (cursor && cursor->nodePointer() == cachedNode->nodePointer()))) + { + if (count == 1 && dx != 0 && dy == 0 && -m_lastDx == dx && + SkTime::GetMSecs() - m_lastDxTime < 1000) + root->checkForJiggle(&dx); + DBG_NAV_LOGD("scrollBy %d,%d", dx, dy); + if ((dx | dy)) + this->scrollBy(dx, dy); + m_lastDx = dx; + m_lastDxTime = SkTime::GetMSecs(); + } + bool result = false; + if (cachedNode) { + showCursorUntimed(); + m_viewImpl->updateCursorBounds(root, cachedFrame, cachedNode); + root->setCursor(const_cast<CachedFrame*>(cachedFrame), + const_cast<CachedNode*>(cachedNode)); + const CachedNode* focus = root->currentFocus(); + bool clearTextEntry = cachedNode != focus && focus + && cachedNode->nodePointer() != focus->nodePointer() && focus->isTextInput(); + // Stop painting the caret if the old focus was a text input and so is the new cursor. + bool stopPaintingCaret = clearTextEntry && cachedNode->wantsKeyEvents(); + sendMoveMouseIfLatest(clearTextEntry, stopPaintingCaret); + } else { + int docHeight = root->documentHeight(); + int docWidth = root->documentWidth(); + if (visibleRect.maxY() + dy > docHeight) + dy = docHeight - visibleRect.maxY(); + else if (visibleRect.y() + dy < 0) + dy = -visibleRect.y(); + if (visibleRect.maxX() + dx > docWidth) + dx = docWidth - visibleRect.maxX(); + else if (visibleRect.x() < 0) + dx = -visibleRect.x(); + result = direction == CachedFrame::LEFT ? dx >= 0 : + direction == CachedFrame::RIGHT ? dx <= 0 : + direction == CachedFrame::UP ? dy >= 0 : dy <= 0; + } + return result; +} + +void notifyProgressFinished() +{ + DBG_NAV_LOGD("cursorIsTextInput=%d", cursorIsTextInput(DontAllowNewer)); + rebuildWebTextView(); +#if DEBUG_NAV_UI + if (m_frameCacheUI) { + const CachedNode* focus = m_frameCacheUI->currentFocus(); + DBG_NAV_LOGD("focus %d (nativeNode=%p)", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : 0); + } +#endif +} + +const CachedNode* findAt(CachedRoot* root, const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* rxPtr, int* ryPtr) +{ + *rxPtr = 0; + *ryPtr = 0; + *framePtr = 0; + if (!root) + return 0; + setVisibleRect(root); + return root->findAt(rect, framePtr, rxPtr, ryPtr, true); +} + +IntRect setVisibleRect(CachedRoot* root) +{ + IntRect visibleRect = getVisibleRect(); + DBG_NAV_LOGD("getVisibleRect %d,%d,%d,%d", + visibleRect.x(), visibleRect.y(), visibleRect.width(), visibleRect.height()); + root->setVisibleRect(visibleRect); + return visibleRect; +} + +void selectBestAt(const WebCore::IntRect& rect) +{ + const CachedFrame* frame; + int rx, ry; + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + const CachedNode* node = findAt(root, rect, &frame, &rx, &ry); + if (!node) { + DBG_NAV_LOGD("no nodes found root=%p", root); + root->rootHistory()->setMouseBounds(rect); + m_viewImpl->m_hasCursorBounds = false; + root->setCursor(0, 0); + viewInvalidate(); + } else { + DBG_NAV_LOGD("CachedNode:%p (%d)", node, node->index()); + WebCore::IntRect bounds = node->bounds(frame); + root->rootHistory()->setMouseBounds(bounds); + m_viewImpl->updateCursorBounds(root, frame, node); + showCursorTimed(); + root->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(node)); + } + sendMoveMouseIfLatest(false, false); +} + +const CachedNode* m_cacheHitNode; +const CachedFrame* m_cacheHitFrame; + +bool pointInNavCache(int x, int y, int slop) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return false; + IntRect rect = IntRect(x - slop, y - slop, slop * 2, slop * 2); + int rx, ry; + return (m_cacheHitNode = findAt(root, rect, &m_cacheHitFrame, &rx, &ry)); +} + +bool motionUp(int x, int y, int slop) +{ + bool pageScrolled = false; + IntRect rect = IntRect(x - slop, y - slop, slop * 2, slop * 2); + int rx, ry; + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return 0; + const CachedFrame* frame = 0; + const CachedNode* result = findAt(root, rect, &frame, &rx, &ry); + CachedHistory* history = root->rootHistory(); + if (!result) { + DBG_NAV_LOGD("no nodes found root=%p", root); + history->setNavBounds(rect); + m_viewImpl->m_hasCursorBounds = false; + root->hideCursor(); + int dx = root->checkForCenter(x, y); + if (dx) { + scrollBy(dx, 0); + pageScrolled = true; + } + sendMotionUp(frame ? (WebCore::Frame*) frame->framePointer() : 0, + 0, x, y); + viewInvalidate(); + return pageScrolled; + } + DBG_NAV_LOGD("CachedNode:%p (%d) x=%d y=%d rx=%d ry=%d", result, + result->index(), x, y, rx, ry); + WebCore::IntRect navBounds = WebCore::IntRect(rx, ry, 1, 1); + history->setNavBounds(navBounds); + history->setMouseBounds(navBounds); + m_viewImpl->updateCursorBounds(root, frame, result); + root->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(result)); + if (result->isSyntheticLink()) + overrideUrlLoading(result->getExport()); + else { + sendMotionUp( + (WebCore::Frame*) frame->framePointer(), + (WebCore::Node*) result->nodePointer(), rx, ry); + } + if (result->isTextInput() || result->isSelect() + || result->isContentEditable()) { + showCursorUntimed(); + } else + showCursorTimed(); + return pageScrolled; } #if USE(ACCELERATED_COMPOSITING) static const ScrollableLayerAndroid* findScrollableLayer( const LayerAndroid* parent, int x, int y, SkIRect* foundBounds) { - IntRect bounds = enclosingIntRect(parent->fullContentAreaMapped()); - + SkRect bounds; + parent->bounds(&bounds); // Check the parent bounds first; this will clip to within a masking layer's // bounds. if (parent->masksToBounds() && !bounds.contains(x, y)) return 0; - + // Move the hit test local to parent. + x -= bounds.fLeft; + y -= bounds.fTop; int count = parent->countChildren(); while (count--) { const LayerAndroid* child = parent->getChild(count); - const ScrollableLayerAndroid* result = findScrollableLayer(child, x, y, foundBounds); + const ScrollableLayerAndroid* result = findScrollableLayer(child, x, y, + foundBounds); if (result) { + foundBounds->offset(bounds.fLeft, bounds.fTop); if (parent->masksToBounds()) { if (bounds.width() < foundBounds->width()) foundBounds->fRight = foundBounds->fLeft + bounds.width(); @@ -392,7 +1070,7 @@ static const ScrollableLayerAndroid* findScrollableLayer( } } if (parent->contentIsScrollable()) { - foundBounds->set(bounds.x(), bounds.y(), bounds.width(), bounds.height()); + foundBounds->set(0, 0, bounds.width(), bounds.height()); return static_cast<const ScrollableLayerAndroid*>(parent); } return 0; @@ -402,9 +1080,11 @@ static const ScrollableLayerAndroid* findScrollableLayer( int scrollableLayer(int x, int y, SkIRect* layerRect, SkIRect* bounds) { #if USE(ACCELERATED_COMPOSITING) - if (!m_baseLayer) + const LayerAndroid* layerRoot = compositeRoot(); + if (!layerRoot) return 0; - const ScrollableLayerAndroid* result = findScrollableLayer(m_baseLayer, x, y, bounds); + const ScrollableLayerAndroid* result = findScrollableLayer(layerRoot, x, y, + bounds); if (result) { result->getScrollRect(layerRect); return result->uniqueId(); @@ -419,6 +1099,52 @@ void scrollLayer(int layerId, int x, int y) m_glWebViewState->scrollLayer(layerId, x, y); } +int getBlockLeftEdge(int x, int y, float scale) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (root) + return root->getBlockLeftEdge(x, y, scale); + return -1; +} + +void overrideUrlLoading(const WTF::String& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return; + jstring jName = wtfStringToJstring(env, url); + env->CallVoidMethod(javaObject.get(), m_javaGlue.m_overrideLoading, jName); + env->DeleteLocalRef(jName); +} + +void setFindIsUp(bool up) +{ + DBG_NAV_LOGD("up=%d", up); + m_viewImpl->m_findIsUp = up; +} + +void setFindIsEmpty() +{ + DBG_NAV_LOG(""); + m_findOnPage.clearCurrentLocation(); +} + +void showCursorTimed() +{ + DBG_NAV_LOG(""); + m_ringAnimationEnd = SkTime::GetMSecs() + PRESSED_STATE_DURATION; + viewInvalidate(); +} + +void showCursorUntimed() +{ + DBG_NAV_LOG(""); + m_ring.m_isPressed = false; + m_ringAnimationEnd = UINT_MAX; + viewInvalidate(); +} + void setHeightCanMeasure(bool measure) { m_heightCanMeasure = measure; @@ -426,16 +1152,170 @@ void setHeightCanMeasure(bool measure) String getSelection() { - SelectText* select = static_cast<SelectText*>( - getDrawExtra(WebView::DrawExtrasSelection)); - if (select) - return select->getText(); - return String(); + return m_selectText.getSelection(); +} + +void moveSelection(int x, int y) +{ + m_selectText.moveSelection(getVisibleRect(), x, y); +} + +IntPoint selectableText() +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return IntPoint(0, 0); + return m_selectText.selectableText(root); +} + +void selectAll() +{ + m_selectText.selectAll(); +} + +int selectionX() +{ + return m_selectText.selectionX(); +} + +int selectionY() +{ + return m_selectText.selectionY(); +} + +void resetSelection() +{ + m_selectText.reset(); +} + +bool startSelection(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return false; + updateSelectionHandles(); + return m_selectText.startSelection(root, getVisibleRect(), x, y); +} + +bool wordSelection(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return false; + updateSelectionHandles(); + return m_selectText.wordSelection(root, getVisibleRect(), x, y); +} + +bool extendSelection(int x, int y) +{ + m_selectText.extendSelection(getVisibleRect(), x, y); + return true; +} + +bool hitSelection(int x, int y) +{ + updateSelectionHandles(); + return m_selectText.hitSelection(x, y); +} + +void setExtendSelection() +{ + m_selectText.setExtendSelection(true); +} + +void setSelectionPointer(bool set, float scale, int x, int y) +{ + m_selectText.setDrawPointer(set); + if (!set) + return; + m_selectText.m_inverseScale = scale; + m_selectText.m_selectX = x; + m_selectText.m_selectY = y; +} + +void sendMoveFocus(WebCore::Frame* framePtr, WebCore::Node* nodePtr) +{ + DBG_NAV_LOGD("framePtr=%p nodePtr=%p", framePtr, nodePtr); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return; + env->CallVoidMethod(javaObject.get(), m_javaGlue.m_sendMoveFocus, (jint) framePtr, (jint) nodePtr); + checkException(env); +} + +void sendMoveMouse(WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y) +{ + DBG_NAV_LOGD("framePtr=%p nodePtr=%p x=%d y=%d", framePtr, nodePtr, x, y); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return; + env->CallVoidMethod(javaObject.get(), m_javaGlue.m_sendMoveMouse, reinterpret_cast<jint>(framePtr), reinterpret_cast<jint>(nodePtr), x, y); + checkException(env); +} + +void sendMoveMouseIfLatest(bool clearTextEntry, bool stopPaintingCaret) +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return; + env->CallVoidMethod(javaObject.get(), m_javaGlue.m_sendMoveMouseIfLatest, clearTextEntry, stopPaintingCaret); + checkException(env); +} + +void sendMotionUp(WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y) +{ + DBG_NAV_LOGD("m_generation=%d framePtr=%p nodePtr=%p x=%d y=%d", m_generation, framePtr, nodePtr, x, y); + LOG_ASSERT(m_javaGlue.m_obj, "A WebView was not associated with this WebViewNative!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return; + m_viewImpl->m_touchGeneration = ++m_generation; + env->CallVoidMethod(javaObject.get(), m_javaGlue.m_sendMotionUp, m_generation, (jint) framePtr, (jint) nodePtr, x, y); + checkException(env); +} + +void findNext(bool forward) +{ + m_findOnPage.findNext(forward); + scrollToCurrentMatch(); + viewInvalidate(); +} + +// With this call, WebView takes ownership of matches, and is responsible for +// deleting it. +void setMatches(WTF::Vector<MatchInfo>* matches, jboolean sameAsLastSearch) +{ + // If this search is the same as the last one, check against the old + // location to determine whether to scroll. If the same word is found + // in the same place, then do not scroll. + IntRect oldLocation; + bool checkAgainstOldLocation = false; + if (sameAsLastSearch && m_findOnPage.isCurrentLocationValid()) { + oldLocation = m_findOnPage.currentMatchBounds(); + checkAgainstOldLocation = true; + } + + m_findOnPage.setMatches(matches); + + if (!checkAgainstOldLocation || oldLocation != m_findOnPage.currentMatchBounds()) + scrollToCurrentMatch(); + viewInvalidate(); +} + +int currentMatchIndex() +{ + return m_findOnPage.currentMatchIndex(); } bool scrollBy(int dx, int dy) { - ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); AutoJObject javaObject = m_javaGlue.object(env); @@ -454,6 +1334,44 @@ void setIsScrolling(bool isScrolling) #endif } +bool hasCursorNode() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* cursorNode = root->currentCursor(); + DBG_NAV_LOGD("cursorNode=%d (nodePointer=%p)", + cursorNode ? cursorNode->index() : -1, + cursorNode ? cursorNode->nodePointer() : 0); + return cursorNode; +} + +bool hasFocusNode() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* focusNode = root->currentFocus(); + DBG_NAV_LOGD("focusNode=%d (nodePointer=%p)", + focusNode ? focusNode->index() : -1, + focusNode ? focusNode->nodePointer() : 0); + return focusNode; +} + +void rebuildWebTextView() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return; + env->CallVoidMethod(javaObject.get(), m_javaGlue.m_rebuildWebTextView); + checkException(env); +} + void viewInvalidate() { JNIEnv* env = JSC::Bindings::getJNIEnv(); @@ -485,64 +1403,112 @@ void postInvalidateDelayed(int64_t delay, const WebCore::IntRect& bounds) checkException(env); } +bool inFullScreenMode() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject javaObject = m_javaGlue.object(env); + if (!javaObject.get()) + return false; + jboolean result = env->CallBooleanMethod(javaObject.get(), m_javaGlue.m_inFullScreenMode); + checkException(env); + return result; +} + +int moveGeneration() +{ + return m_viewImpl->m_moveGeneration; +} + +LayerAndroid* compositeRoot() const +{ + LOG_ASSERT(!m_baseLayer || m_baseLayer->countChildren() == 1, + "base layer can't have more than one child %s", __FUNCTION__); + if (m_baseLayer && m_baseLayer->countChildren() == 1) + return static_cast<LayerAndroid*>(m_baseLayer->getChild(0)); + else + return 0; +} + #if ENABLE(ANDROID_OVERFLOW_SCROLL) -static void copyScrollPosition(const LayerAndroid* fromRoot, - LayerAndroid* toRoot, int layerId) +static void copyScrollPositionRecursive(const LayerAndroid* from, + LayerAndroid* root) { - if (!fromRoot || !toRoot) - return; - const LayerAndroid* from = fromRoot->findById(layerId); - LayerAndroid* to = toRoot->findById(layerId); - if (!from || !to || !from->contentIsScrollable() || !to->contentIsScrollable()) - return; - // TODO: Support this for iframes. - if (to->isIFrameContent() || from->isIFrameContent()) + if (!from || !root) return; - to->setScrollOffset(from->getScrollOffset()); + for (int i = 0; i < from->countChildren(); i++) { + const LayerAndroid* l = from->getChild(i); + if (l->contentIsScrollable()) { + const SkPoint& pos = l->getPosition(); + LayerAndroid* match = root->findById(l->uniqueId()); + if (match && match->contentIsScrollable()) + match->setPosition(pos.fX, pos.fY); + } + copyScrollPositionRecursive(l, root); + } } #endif -BaseLayerAndroid* getBaseLayer() const { return m_baseLayer; } +void registerPageSwapCallback() +{ + m_pageSwapCallbackRegistered = true; +} -bool setBaseLayer(BaseLayerAndroid* newBaseLayer, bool showVisualIndicator, - bool isPictureAfterFirstLayout, int scrollingLayer) +void setBaseLayer(BaseLayerAndroid* layer, SkRegion& inval, bool showVisualIndicator, + bool isPictureAfterFirstLayout, bool registerPageSwapCallback) { - bool queueFull = false; #if USE(ACCELERATED_COMPOSITING) if (m_glWebViewState) - queueFull = m_glWebViewState->setBaseLayer(newBaseLayer, showVisualIndicator, - isPictureAfterFirstLayout); + m_glWebViewState->setBaseLayer(layer, inval, showVisualIndicator, + isPictureAfterFirstLayout); + m_pageSwapCallbackRegistered |= registerPageSwapCallback; #endif #if ENABLE(ANDROID_OVERFLOW_SCROLL) - copyScrollPosition(m_baseLayer, newBaseLayer, scrollingLayer); + if (layer) { + // TODO: the below tree copies are only necessary in software rendering + LayerAndroid* newCompositeRoot = static_cast<LayerAndroid*>(layer->getChild(0)); + copyScrollPositionRecursive(compositeRoot(), newCompositeRoot); + } #endif SkSafeUnref(m_baseLayer); - m_baseLayer = newBaseLayer; - - return queueFull; + m_baseLayer = layer; + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + root->resetLayers(); + root->setRootLayer(compositeRoot()); } -void copyBaseContentToPicture(SkPicture* picture) +void getTextSelectionRegion(SkRegion *region) { - if (!m_baseLayer || !m_baseLayer->content()) - return; - LayerContent* content = m_baseLayer->content(); - SkCanvas* canvas = picture->beginRecording(content->width(), content->height(), - SkPicture::kUsePathBoundsForClip_RecordingFlag); + m_selectText.getSelectionRegion(getVisibleRect(), region, compositeRoot()); +} - // clear the BaseLayerAndroid's previous matrix (set at each draw) - SkMatrix baseMatrix; - baseMatrix.reset(); - m_baseLayer->setMatrix(baseMatrix); +void getTextSelectionHandles(int* handles) +{ + m_selectText.getSelectionHandles(handles, compositeRoot()); +} - m_baseLayer->draw(canvas, 0); +void replaceBaseContent(PictureSet* set) +{ + if (!m_baseLayer) + return; + m_baseLayer->setContent(*set); + delete set; +} +void copyBaseContentToPicture(SkPicture* picture) +{ + if (!m_baseLayer) + return; + PictureSet* content = m_baseLayer->content(); + m_baseLayer->drawCanvas(picture->beginRecording(content->width(), content->height(), + SkPicture::kUsePathBoundsForClip_RecordingFlag)); picture->endRecording(); } bool hasContent() { - if (!m_baseLayer || !m_baseLayer->content()) + if (!m_baseLayer) return false; return !m_baseLayer->content()->isEmpty(); } @@ -556,197 +1522,37 @@ Functor* getFunctor() { return m_glDrawFunctor; } -void setVisibleContentRect(SkRect& visibleContentRect) { - m_visibleContentRect = visibleContentRect; -} - -void setDrawExtra(DrawExtra *extra, DrawExtras type) -{ - if (type == DrawExtrasNone) - return; - DrawExtra* old = m_extras[type - 1]; - m_extras[type - 1] = extra; - if (old != extra) { - delete old; - } -} - -void setTextSelection(SelectText *selection) { - setDrawExtra(selection, DrawExtrasSelection); -} - -const TransformationMatrix* getLayerTransform(int layerId) { - if (layerId != -1 && m_baseLayer) { - LayerAndroid* layer = m_baseLayer->findById(layerId); - // We need to make sure the drawTransform is up to date as this is - // called before a draw() or drawGL() - if (layer) { - m_baseLayer->updatePositionsRecursive(m_visibleContentRect); - return layer->drawTransform(); - } - } - return 0; -} - -int getHandleLayerId(SelectText::HandleId handleId, SkIPoint& cursorPoint, - FloatQuad& textBounds) { - SelectText* selectText = static_cast<SelectText*>(getDrawExtra(DrawExtrasSelection)); - if (!selectText || !m_baseLayer) - return -1; - int layerId = selectText->caretLayerId(handleId); - IntRect cursorRect = selectText->caretRect(handleId); - IntRect textRect = selectText->textRect(handleId); - // Rects exclude the last pixel on right/bottom. We want only included pixels. - cursorPoint.set(cursorRect.x(), cursorRect.maxY() - 1); - textRect.setHeight(std::max(1, textRect.height() - 1)); - textRect.setWidth(std::max(1, textRect.width() - 1)); - textBounds = FloatQuad(textRect); - - const TransformationMatrix* transform = getLayerTransform(layerId); - if (transform) { - // We're overloading the concept of Rect to be just the two - // points (bottom-left and top-right. - cursorPoint = transform->mapPoint(cursorPoint); - textBounds = transform->mapQuad(textBounds); - } - return layerId; -} - -void mapLayerRect(int layerId, SkIRect& rect) { - const TransformationMatrix* transform = getLayerTransform(layerId); - if (transform) - rect = transform->mapRect(rect); -} - -void floatQuadToQuadF(JNIEnv* env, const FloatQuad& nativeTextQuad, - jobject textQuad) -{ - jobject p1 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP1); - jobject p2 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP2); - jobject p3 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP3); - jobject p4 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP4); - GraphicsJNI::point_to_jpointf(nativeTextQuad.p1(), env, p1); - GraphicsJNI::point_to_jpointf(nativeTextQuad.p2(), env, p2); - GraphicsJNI::point_to_jpointf(nativeTextQuad.p3(), env, p3); - GraphicsJNI::point_to_jpointf(nativeTextQuad.p4(), env, p4); - env->DeleteLocalRef(p1); - env->DeleteLocalRef(p2); - env->DeleteLocalRef(p3); - env->DeleteLocalRef(p4); +BaseLayerAndroid* getBaseLayer() { + return m_baseLayer; } -// This is called when WebView switches rendering modes in a more permanent fashion -// such as when the layer type is set or the view is attached/detached from the window -int setHwAccelerated(bool hwAccelerated) { - if (!m_glWebViewState) - return 0; - LayerAndroid* root = m_baseLayer; - if (root) - return root->setHwAccelerated(hwAccelerated); - return 0; +void setVisibleRect(SkRect& visibleRect) { + m_visibleRect = visibleRect; } -void setDrawingPaused(bool isPaused) -{ - m_isDrawingPaused = isPaused; - if (m_viewImpl) - m_viewImpl->setPrerenderingEnabled(!isPaused); -} - -// Finds the rectangles within world to the left, right, top, and bottom -// of rect and adds them to rects. If no intersection exists, false is returned. -static bool findMaskedRects(const FloatRect& world, - const FloatRect& rect, Vector<FloatRect>& rects) { - if (!world.intersects(rect)) - return false; // nothing to subtract - - // left rectangle - if (rect.x() > world.x()) - rects.append(FloatRect(world.x(), world.y(), - rect.x() - world.x(), world.height())); - // top rectangle - if (rect.y() > world.y()) - rects.append(FloatRect(world.x(), world.y(), - world.width(), rect.y() - world.y())); - // right rectangle - if (rect.maxX() < world.maxX()) - rects.append(FloatRect(rect.maxX(), world.y(), - world.maxX() - rect.maxX(), world.height())); - // bottom rectangle - if (rect.maxY() < world.maxY()) - rects.append(FloatRect(world.x(), rect.maxY(), - world.width(), world.maxY() - rect.maxY())); - return true; -} - -// Returns false if layerId is a fixed position layer, otherwise -// all fixed position layer rectangles are subtracted from those within -// rects. Rects will be modified to contain rectangles that don't include -// the fixed position layer rectangles. -static bool findMaskedRectsForLayer(LayerAndroid* layer, - Vector<FloatRect>& rects, int layerId) -{ - if (layer->isPositionFixed()) { - if (layerId == layer->uniqueId()) - return false; - FloatRect layerRect = layer->fullContentAreaMapped(); - for (int i = rects.size() - 1; i >= 0; i--) - if (findMaskedRects(rects[i], layerRect, rects)) - rects.remove(i); - } - - int childIndex = 0; - while (LayerAndroid* child = layer->getChild(childIndex++)) - if (!findMaskedRectsForLayer(child, rects, layerId)) - return false; - - return true; -} - -// Finds the largest rectangle not masked by any fixed layer. -void findMaxVisibleRect(int movingLayerId, SkIRect& visibleContentRect) -{ - if (!m_baseLayer) - return; - - FloatRect visibleContentFloatRect(visibleContentRect); - m_baseLayer->updatePositionsRecursive(visibleContentFloatRect); - Vector<FloatRect> rects; - rects.append(visibleContentFloatRect); - if (findMaskedRectsForLayer(m_baseLayer, rects, movingLayerId)) { - float maxSize = 0.0; - const FloatRect* largest = 0; - for (int i = 0; i < rects.size(); i++) { - const FloatRect& rect = rects[i]; - float size = rect.width() * rect.height(); - if (size > maxSize) { - maxSize = size; - largest = ▭ - } - } - if (largest) { - SkRect largeRect = *largest; - largeRect.round(&visibleContentRect); - } - } -} - -private: // local state for WebView bool m_isDrawingPaused; +private: // local state for WebView // private to getFrameCache(); other functions operate in a different thread + CachedRoot* m_frameCacheUI; // navigation data ready for use WebViewCore* m_viewImpl; int m_generation; // associate unique ID with sent kit focus to match with ui + SkPicture* m_navPictureUI; + SkMSec m_ringAnimationEnd; // Corresponds to the same-named boolean on the java side. bool m_heightCanMeasure; int m_lastDx; SkMSec m_lastDxTime; - DrawExtra* m_extras[DRAW_EXTRAS_SIZE]; + SelectText m_selectText; + FindOnPage m_findOnPage; + CursorRing m_ring; BaseLayerAndroid* m_baseLayer; Functor* m_glDrawFunctor; #if USE(ACCELERATED_COMPOSITING) GLWebViewState* m_glWebViewState; + bool m_pageSwapCallbackRegistered; #endif - SkRect m_visibleContentRect; + RenderSkinButton* m_buttonSkin; + SkRect m_visibleRect; bool m_isHighEndGfx; }; // end of WebView class @@ -759,54 +1565,45 @@ private: // local state for WebView class GLDrawFunctor : Functor { public: GLDrawFunctor(WebView* _wvInstance, - int (WebView::*_funcPtr)(WebCore::IntRect&, WebCore::IntRect*, - WebCore::IntRect&, int, WebCore::IntRect&, jfloat, jint, bool), - WebCore::IntRect _invScreenRect, float _scale, int _extras) { + bool(WebView::*_funcPtr)(WebCore::IntRect&, WebCore::IntRect*, + WebCore::IntRect&, int, WebCore::IntRect&, + jfloat, jint), + WebCore::IntRect _viewRect, float _scale, int _extras) { wvInstance = _wvInstance; funcPtr = _funcPtr; - invScreenRect = _invScreenRect; + viewRect = _viewRect; scale = _scale; extras = _extras; }; - status_t operator()(int messageId, void* data) { - TRACE_METHOD(); - bool shouldDraw = (messageId == uirenderer::DrawGlInfo::kModeDraw); - if (shouldDraw) - wvInstance->updateRectsForGL(); - - if (invScreenRect.isEmpty()) { + if (viewRect.isEmpty()) { // NOOP operation if viewport is empty return 0; } WebCore::IntRect inval; - int titlebarHeight = screenRect.height() - invScreenRect.height(); + int titlebarHeight = webViewRect.height() - viewRect.height(); uirenderer::DrawGlInfo* info = reinterpret_cast<uirenderer::DrawGlInfo*>(data); - WebCore::IntRect screenClip(info->clipLeft, info->clipTop, - info->clipRight - info->clipLeft, - info->clipBottom - info->clipTop); - - WebCore::IntRect localInvScreenRect = invScreenRect; - if (info->isLayer) { - // When webview is on a layer, we need to use the viewport relative - // to the FBO, rather than the screen(which will use invScreenRect). - localInvScreenRect.setX(screenClip.x()); - localInvScreenRect.setY(info->height - screenClip.y() - screenClip.height()); - } - // Send the necessary info to the shader. - TilesManager::instance()->shader()->setGLDrawInfo(info); - - int returnFlags = (*wvInstance.*funcPtr)(localInvScreenRect, &inval, screenRect, - titlebarHeight, screenClip, scale, extras, shouldDraw); - if ((returnFlags & uirenderer::DrawGlInfo::kStatusDraw) != 0) { + WebCore::IntRect localViewRect = viewRect; + if (info->isLayer) + localViewRect.move(-1 * localViewRect.x(), -1 * localViewRect.y()); + + WebCore::IntRect clip(info->clipLeft, info->clipTop, + info->clipRight - info->clipLeft, + info->clipBottom - info->clipTop); + TilesManager::instance()->shader()->setWebViewMatrix(info->transform, info->isLayer); + + bool retVal = (*wvInstance.*funcPtr)(localViewRect, &inval, webViewRect, + titlebarHeight, clip, scale, extras); + if (retVal) { IntRect finalInval; - if (inval.isEmpty()) - finalInval = screenRect; - else { - finalInval.setX(screenRect.x() + inval.x()); - finalInval.setY(screenRect.y() + titlebarHeight + inval.y()); + if (inval.isEmpty()) { + finalInval = webViewRect; + retVal = true; + } else { + finalInval.setX(webViewRect.x() + inval.x()); + finalInval.setY(webViewRect.y() + titlebarHeight + inval.y()); finalInval.setWidth(inval.width()); finalInval.setHeight(inval.height()); } @@ -815,45 +1612,201 @@ class GLDrawFunctor : Functor { info->dirtyRight = finalInval.maxX(); info->dirtyBottom = finalInval.maxY(); } - // return 1 if invalidation needed, 2 to request non-drawing functor callback, 0 otherwise - ALOGV("returnFlags are %d, shouldDraw %d", returnFlags, shouldDraw); - return returnFlags; - } - void updateScreenRect(WebCore::IntRect& _screenRect) { - screenRect = _screenRect; + // return 1 if invalidation needed, 0 otherwise + return retVal ? 1 : 0; } - void updateInvScreenRect(WebCore::IntRect& _invScreenRect) { - invScreenRect = _invScreenRect; + void updateRect(WebCore::IntRect& _viewRect) { + viewRect = _viewRect; } - void updateScale(float _scale) { - scale = _scale; - } - void updateExtras(jint _extras) { - extras = _extras; + void updateViewRect(WebCore::IntRect& _viewRect) { + webViewRect = _viewRect; } private: WebView* wvInstance; - int (WebView::*funcPtr)(WebCore::IntRect&, WebCore::IntRect*, - WebCore::IntRect&, int, WebCore::IntRect&, float, int, bool); - WebCore::IntRect invScreenRect; - WebCore::IntRect screenRect; + bool (WebView::*funcPtr)(WebCore::IntRect&, WebCore::IntRect*, + WebCore::IntRect&, int, WebCore::IntRect&, float, int); + WebCore::IntRect viewRect; + WebCore::IntRect webViewRect; jfloat scale; jint extras; }; +static jobject createJavaRect(JNIEnv* env, int x, int y, int right, int bottom) +{ + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, x, y, right, bottom); + env->DeleteLocalRef(rectClass); + return rect; +} + /* * Native JNI methods */ +static int nativeCacheHitFramePointer(JNIEnv *env, jobject obj) +{ + return reinterpret_cast<int>(GET_NATIVE_VIEW(env, obj) + ->m_cacheHitFrame->framePointer()); +} + +static jobject nativeCacheHitNodeBounds(JNIEnv *env, jobject obj) +{ + WebCore::IntRect bounds = GET_NATIVE_VIEW(env, obj) + ->m_cacheHitNode->originalAbsoluteBounds(); + return createJavaRect(env, bounds.x(), bounds.y(), + bounds.maxX(), bounds.maxY()); +} + +static int nativeCacheHitNodePointer(JNIEnv *env, jobject obj) +{ + return reinterpret_cast<int>(GET_NATIVE_VIEW(env, obj) + ->m_cacheHitNode->nodePointer()); +} + +static bool nativeCacheHitIsPlugin(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->m_cacheHitNode->isPlugin(); +} + +static void nativeClearCursor(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->clearCursor(); +} static void nativeCreate(JNIEnv *env, jobject obj, int viewImpl, jstring drawableDir, jboolean isHighEndGfx) { WTF::String dir = jstringToWtfString(env, drawableDir); - new WebView(env, obj, viewImpl, dir, isHighEndGfx); + WebView* webview = new WebView(env, obj, viewImpl, dir, isHighEndGfx); // NEED THIS OR SOMETHING LIKE IT! //Release(obj); } +static jint nativeCursorFramePointer(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedFrame* frame = 0; + (void) root->currentCursor(&frame); + return reinterpret_cast<int>(frame ? frame->framePointer() : 0); +} + +static const CachedNode* getCursorNode(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentCursor() : 0; +} + +static const CachedNode* getCursorNode(JNIEnv *env, jobject obj, + const CachedFrame** frame) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentCursor(frame) : 0; +} + +static const CachedNode* getFocusCandidate(JNIEnv *env, jobject obj, + const CachedFrame** frame) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedNode* cursor = root->currentCursor(frame); + if (cursor && cursor->wantsKeyEvents()) + return cursor; + return root->currentFocus(frame); +} + +static bool focusCandidateHasNextTextfield(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return false; + const CachedNode* cursor = root->currentCursor(); + if (!cursor || !cursor->isTextInput()) + cursor = root->currentFocus(); + if (!cursor || !cursor->isTextInput()) return false; + return root->nextTextField(cursor, 0); +} + +static const CachedNode* getFocusNode(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentFocus() : 0; +} + +static const CachedNode* getFocusNode(JNIEnv *env, jobject obj, + const CachedFrame** frame) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->currentFocus(frame) : 0; +} + +static const CachedInput* getInputCandidate(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedFrame* frame; + const CachedNode* cursor = root->currentCursor(&frame); + if (!cursor || !cursor->wantsKeyEvents()) + cursor = root->currentFocus(&frame); + return cursor ? frame->textInput(cursor) : 0; +} + +static jboolean nativePageShouldHandleShiftAndArrows(JNIEnv *env, jobject obj) +{ + const CachedNode* focus = getFocusNode(env, obj); + if (!focus) return false; + // Plugins handle shift and arrows whether or not they have focus. + if (focus->isPlugin()) return true; + const CachedNode* cursor = getCursorNode(env, obj); + // ContentEditable nodes should only receive shift and arrows if they have + // both the cursor and the focus. + return cursor && cursor->nodePointer() == focus->nodePointer() + && cursor->isContentEditable(); +} + +static jobject nativeCursorNodeBounds(JNIEnv *env, jobject obj) +{ + const CachedFrame* frame; + const CachedNode* node = getCursorNode(env, obj, &frame); + WebCore::IntRect bounds = node ? node->bounds(frame) + : WebCore::IntRect(0, 0, 0, 0); + return createJavaRect(env, bounds.x(), bounds.y(), + bounds.maxX(), bounds.maxY()); +} + +static jint nativeCursorNodePointer(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + return reinterpret_cast<int>(node ? node->nodePointer() : 0); +} + +static jobject nativeCursorPosition(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + const CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + WebCore::IntPoint pos = WebCore::IntPoint(0, 0); + if (root) + root->getSimulatedMousePosition(&pos); + jclass pointClass = env->FindClass("android/graphics/Point"); + jmethodID init = env->GetMethodID(pointClass, "<init>", "(II)V"); + jobject point = env->NewObject(pointClass, init, pos.x(), pos.y()); + env->DeleteLocalRef(pointClass); + return point; +} + static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj) { if (obj) { @@ -872,67 +1825,88 @@ static SkRect jrectf_to_rect(JNIEnv* env, jobject obj) return rect; } -static void nativeDraw(JNIEnv *env, jobject obj, jobject canv, +static bool nativeCursorIntersects(JNIEnv *env, jobject obj, jobject visRect) +{ + const CachedFrame* frame; + const CachedNode* node = getCursorNode(env, obj, &frame); + return node ? node->bounds(frame).intersects( + jrect_to_webrect(env, visRect)) : false; +} + +static bool nativeCursorIsAnchor(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + return node ? node->isAnchor() : false; +} + +static bool nativeCursorIsTextInput(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + return node ? node->isTextInput() : false; +} + +static jobject nativeCursorText(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getCursorNode(env, obj); + if (!node) + return 0; + WTF::String value = node->getExport(); + return wtfStringToJstring(env, value); +} + +static void nativeDebugDump(JNIEnv *env, jobject obj) +{ +#if DUMP_NAV_CACHE + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->debugDump(); +#endif +} + +static jint nativeDraw(JNIEnv *env, jobject obj, jobject canv, jobject visible, jint color, - jint extras) { + jint extras, jboolean split) { SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); WebView* webView = GET_NATIVE_VIEW(env, obj); - SkRect visibleContentRect = jrectf_to_rect(env, visible); - webView->setVisibleContentRect(visibleContentRect); - webView->draw(canvas, color, static_cast<WebView::DrawExtras>(extras)); -} - -static jint nativeCreateDrawGLFunction(JNIEnv *env, jobject obj, jint nativeView, - jobject jinvscreenrect, jobject jscreenrect, - jobject jvisiblecontentrect, - jfloat scale, jint extras) { - WebCore::IntRect invScreenRect = jrect_to_webrect(env, jinvscreenrect); - WebView *wvInstance = reinterpret_cast<WebView*>(nativeView); - SkRect visibleContentRect = jrectf_to_rect(env, jvisiblecontentrect); - wvInstance->setVisibleContentRect(visibleContentRect); - - GLDrawFunctor* functor = (GLDrawFunctor*) wvInstance->getFunctor(); - if (!functor) { - functor = new GLDrawFunctor(wvInstance, &android::WebView::drawGL, - invScreenRect, scale, extras); - wvInstance->setFunctor((Functor*) functor); - } else { - functor->updateInvScreenRect(invScreenRect); - functor->updateScale(scale); - functor->updateExtras(extras); - } + SkRect visibleRect = jrectf_to_rect(env, visible); + webView->setVisibleRect(visibleRect); + PictureSet* pictureSet = webView->draw(canvas, color, extras, split); + return reinterpret_cast<jint>(pictureSet); +} - WebCore::IntRect rect = jrect_to_webrect(env, jscreenrect); - functor->updateScreenRect(rect); +static jint nativeGetDrawGLFunction(JNIEnv *env, jobject obj, jint nativeView, + jobject jrect, jobject jviewrect, + jobject jvisiblerect, + jfloat scale, jint extras) { + WebCore::IntRect viewRect = jrect_to_webrect(env, jrect); + WebView *wvInstance = (WebView*) nativeView; + SkRect visibleRect = jrectf_to_rect(env, jvisiblerect); + wvInstance->setVisibleRect(visibleRect); - return (jint)functor; -} + GLDrawFunctor* functor = new GLDrawFunctor(wvInstance, + &android::WebView::drawGL, viewRect, scale, extras); + wvInstance->setFunctor((Functor*) functor); -static jint nativeGetDrawGLFunction(JNIEnv *env, jobject obj, jint nativeView) { - WebView *wvInstance = reinterpret_cast<WebView*>(nativeView); - if (!wvInstance) - return 0; + WebCore::IntRect webViewRect = jrect_to_webrect(env, jviewrect); + functor->updateViewRect(webViewRect); - return (jint) wvInstance->getFunctor(); + return (jint)functor; } -static void nativeUpdateDrawGLFunction(JNIEnv *env, jobject obj, jint nativeView, - jobject jinvscreenrect, jobject jscreenrect, - jobject jvisiblecontentrect, jfloat scale) { - WebView *wvInstance = reinterpret_cast<WebView*>(nativeView); +static void nativeUpdateDrawGLFunction(JNIEnv *env, jobject obj, jobject jrect, + jobject jviewrect, jobject jvisiblerect) { + WebView *wvInstance = GET_NATIVE_VIEW(env, obj); if (wvInstance) { GLDrawFunctor* functor = (GLDrawFunctor*) wvInstance->getFunctor(); if (functor) { - WebCore::IntRect invScreenRect = jrect_to_webrect(env, jinvscreenrect); - functor->updateInvScreenRect(invScreenRect); - - SkRect visibleContentRect = jrectf_to_rect(env, jvisiblecontentrect); - wvInstance->setVisibleContentRect(visibleContentRect); + WebCore::IntRect viewRect = jrect_to_webrect(env, jrect); + functor->updateRect(viewRect); - WebCore::IntRect screenRect = jrect_to_webrect(env, jscreenrect); - functor->updateScreenRect(screenRect); + SkRect visibleRect = jrectf_to_rect(env, jvisiblerect); + wvInstance->setVisibleRect(visibleRect); - functor->updateScale(scale); + WebCore::IntRect webViewRect = jrect_to_webrect(env, jviewrect); + functor->updateViewRect(webViewRect); } } } @@ -941,29 +1915,56 @@ static bool nativeEvaluateLayersAnimations(JNIEnv *env, jobject obj, jint native { // only call in software rendering, initialize and evaluate animations #if USE(ACCELERATED_COMPOSITING) - BaseLayerAndroid* baseLayer = reinterpret_cast<WebView*>(nativeView)->getBaseLayer(); - if (baseLayer) { - baseLayer->initAnimations(); - return baseLayer->evaluateAnimations(); + LayerAndroid* root = ((WebView*)nativeView)->compositeRoot(); + if (root) { + root->initAnimations(); + return root->evaluateAnimations(); } #endif return false; } -static bool nativeSetBaseLayer(JNIEnv *env, jobject obj, jint nativeView, jint layer, - jboolean showVisualIndicator, - jboolean isPictureAfterFirstLayout, - jint scrollingLayer) +static void nativeSetBaseLayer(JNIEnv *env, jobject obj, jint layer, jobject inval, + jboolean showVisualIndicator, + jboolean isPictureAfterFirstLayout, + jboolean registerPageSwapCallback) { BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(layer); - return reinterpret_cast<WebView*>(nativeView)->setBaseLayer(layerImpl, showVisualIndicator, - isPictureAfterFirstLayout, - scrollingLayer); + SkRegion invalRegion; + if (inval) + invalRegion = *GraphicsJNI::getNativeRegion(env, inval); + GET_NATIVE_VIEW(env, obj)->setBaseLayer(layerImpl, invalRegion, showVisualIndicator, + isPictureAfterFirstLayout, + registerPageSwapCallback); +} + +static void nativeGetTextSelectionRegion(JNIEnv *env, jobject obj, jint view, + jobject region) +{ + if (!region) + return; + SkRegion* nregion = GraphicsJNI::getNativeRegion(env, region); + ((WebView*)view)->getTextSelectionRegion(nregion); +} + +static void nativeGetSelectionHandles(JNIEnv *env, jobject obj, jint view, + jintArray arr) +{ + int handles[4]; + ((WebView*)view)->getTextSelectionHandles(handles); + env->SetIntArrayRegion(arr, 0, 4, handles); + checkException(env); +} + +static BaseLayerAndroid* nativeGetBaseLayer(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->getBaseLayer(); } -static BaseLayerAndroid* nativeGetBaseLayer(JNIEnv *env, jobject obj, jint nativeView) +static void nativeReplaceBaseContent(JNIEnv *env, jobject obj, jint content) { - return reinterpret_cast<WebView*>(nativeView)->getBaseLayer(); + PictureSet* set = reinterpret_cast<PictureSet*>(content); + GET_NATIVE_VIEW(env, obj)->replaceBaseContent(set); } static void nativeCopyBaseContentToPicture(JNIEnv *env, jobject obj, jobject pict) @@ -977,40 +1978,549 @@ static bool nativeHasContent(JNIEnv *env, jobject obj) return GET_NATIVE_VIEW(env, obj)->hasContent(); } +static jobject nativeImageURI(JNIEnv *env, jobject obj, jint x, jint y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WTF::String uri = view->imageURI(x, y); + return wtfStringToJstring(env, uri); +} + +static jint nativeFocusCandidateFramePointer(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return 0; + const CachedFrame* frame = 0; + const CachedNode* cursor = root->currentCursor(&frame); + if (!cursor || !cursor->wantsKeyEvents()) + (void) root->currentFocus(&frame); + return reinterpret_cast<int>(frame ? frame->framePointer() : 0); +} + +static bool nativeFocusCandidateIsPassword(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input && input->getType() == CachedInput::PASSWORD; +} + +static bool nativeFocusCandidateIsRtlText(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->isRtlText() : false; +} + +static bool nativeFocusCandidateIsTextInput(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusCandidate(env, obj, 0); + return node ? node->isTextInput() : false; +} + +static jint nativeFocusCandidateMaxLength(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->maxLength() : false; +} + +static jint nativeFocusCandidateIsAutoComplete(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->autoComplete() : false; +} + +static jobject nativeFocusCandidateName(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + if (!input) + return 0; + const WTF::String& name = input->name(); + return wtfStringToJstring(env, name); +} + +static jobject nativeFocusCandidateNodeBounds(JNIEnv *env, jobject obj) +{ + const CachedFrame* frame; + const CachedNode* node = getFocusCandidate(env, obj, &frame); + WebCore::IntRect bounds = node ? node->originalAbsoluteBounds() + : WebCore::IntRect(0, 0, 0, 0); + // Inset the rect by 1 unit, so that the focus candidate's border can still + // be seen behind it. + return createJavaRect(env, bounds.x(), bounds.y(), + bounds.maxX(), bounds.maxY()); +} + +static jobject nativeFocusCandidatePaddingRect(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + if (!input) + return 0; + // Note that the Java Rect is being used to pass four integers, rather than + // being used as an actual rectangle. + return createJavaRect(env, input->paddingLeft(), input->paddingTop(), + input->paddingRight(), input->paddingBottom()); +} + +static jint nativeFocusCandidatePointer(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusCandidate(env, obj, 0); + return reinterpret_cast<int>(node ? node->nodePointer() : 0); +} + +static jint nativeFocusCandidateIsSpellcheck(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->spellcheck() : false; +} + +static jobject nativeFocusCandidateText(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusCandidate(env, obj, 0); + if (!node) + return 0; + WTF::String value = node->getExport(); + return wtfStringToJstring(env, value); +} + +static int nativeFocusCandidateLineHeight(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->lineHeight() : 0; +} + +static jfloat nativeFocusCandidateTextSize(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + return input ? input->textSize() : 0.f; +} + +static int nativeFocusCandidateType(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + if (!input) + return CachedInput::NONE; + + if (input->isTextArea()) + return CachedInput::TEXT_AREA; + + return input->getType(); +} + +static int nativeFocusCandidateLayerId(JNIEnv *env, jobject obj) +{ + const CachedFrame* frame = 0; + const CachedNode* node = getFocusNode(env, obj, &frame); + if (!node || !frame) + return -1; + const CachedLayer* layer = frame->layer(node); + if (!layer) + return -1; + return layer->uniqueId(); +} + +static bool nativeFocusIsPlugin(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusNode(env, obj); + return node ? node->isPlugin() : false; +} + +static jobject nativeFocusNodeBounds(JNIEnv *env, jobject obj) +{ + const CachedFrame* frame; + const CachedNode* node = getFocusNode(env, obj, &frame); + WebCore::IntRect bounds = node ? node->bounds(frame) + : WebCore::IntRect(0, 0, 0, 0); + return createJavaRect(env, bounds.x(), bounds.y(), + bounds.maxX(), bounds.maxY()); +} + +static jint nativeFocusNodePointer(JNIEnv *env, jobject obj) +{ + const CachedNode* node = getFocusNode(env, obj); + return node ? reinterpret_cast<int>(node->nodePointer()) : 0; +} + +static bool nativeCursorWantsKeyEvents(JNIEnv* env, jobject jwebview) { + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->cursorWantsKeyEvents(); +} + +static void nativeHideCursor(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->hideCursor(); +} + +static void nativeInstrumentReport(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounter::reportNow(); +#endif +} + +static void nativeSelectBestAt(JNIEnv *env, jobject obj, jobject jrect) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::IntRect rect = jrect_to_webrect(env, jrect); + view->selectBestAt(rect); +} + +static void nativeSelectAt(JNIEnv *env, jobject obj, jint x, jint y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::IntRect rect = IntRect(x, y , 1, 1); + view->selectBestAt(rect); + if (view->hasCursorNode()) + view->showCursorUntimed(); +} + +static jobject nativeLayerBounds(JNIEnv* env, jobject obj, jint jlayer) +{ + SkRect r; +#if USE(ACCELERATED_COMPOSITING) + LayerAndroid* layer = (LayerAndroid*) jlayer; + r = layer->bounds(); +#else + r.setEmpty(); +#endif + SkIRect irect; + r.round(&irect); + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, irect.fLeft, irect.fTop, + irect.fRight, irect.fBottom); + env->DeleteLocalRef(rectClass); + return rect; +} + +static jobject nativeSubtractLayers(JNIEnv* env, jobject obj, jobject jrect) +{ + SkIRect irect = jrect_to_webrect(env, jrect); +#if USE(ACCELERATED_COMPOSITING) + LayerAndroid* root = GET_NATIVE_VIEW(env, obj)->compositeRoot(); + if (root) { + SkRect rect; + rect.set(irect); + rect = root->subtractLayers(rect); + rect.round(&irect); + } +#endif + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, irect.fLeft, irect.fTop, + irect.fRight, irect.fBottom); + env->DeleteLocalRef(rectClass); + return rect; +} + +static jint nativeTextGeneration(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + return root ? root->textGeneration() : 0; +} + +static bool nativePointInNavCache(JNIEnv *env, jobject obj, + int x, int y, int slop) +{ + return GET_NATIVE_VIEW(env, obj)->pointInNavCache(x, y, slop); +} + +static bool nativeMotionUp(JNIEnv *env, jobject obj, + int x, int y, int slop) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->motionUp(x, y, slop); +} + +static bool nativeHasCursorNode(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->hasCursorNode(); +} + +static bool nativeHasFocusNode(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->hasFocusNode(); +} + +static bool nativeMoveCursor(JNIEnv *env, jobject obj, + int key, int count, bool ignoreScroll) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + DBG_NAV_LOGD("env=%p obj=%p view=%p", env, obj, view); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->moveCursor(key, count, ignoreScroll); +} + +static void nativeSetFindIsUp(JNIEnv *env, jobject obj, jboolean isUp) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->setFindIsUp(isUp); +} + +static void nativeSetFindIsEmpty(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setFindIsEmpty(); +} + +static void nativeShowCursorTimed(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->showCursorTimed(); +} + static void nativeSetHeightCanMeasure(JNIEnv *env, jobject obj, bool measure) { WebView* view = GET_NATIVE_VIEW(env, obj); - ALOG_ASSERT(view, "view not set in nativeSetHeightCanMeasure"); + LOG_ASSERT(view, "view not set in nativeSetHeightCanMeasure"); view->setHeightCanMeasure(measure); } -static void nativeDestroy(JNIEnv *env, jobject obj, jint ptr) +static jobject nativeGetCursorRingBounds(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find Rect class!"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + LOG_ASSERT(init, "Could not find constructor for Rect"); + WebCore::IntRect webRect; + view->cursorRingBounds(&webRect); + jobject rect = env->NewObject(rectClass, init, webRect.x(), + webRect.y(), webRect.maxX(), webRect.maxY()); + env->DeleteLocalRef(rectClass); + return rect; +} + +static int nativeFindAll(JNIEnv *env, jobject obj, jstring findLower, + jstring findUpper, jboolean sameAsLastSearch) +{ + // If one or the other is null, do not search. + if (!(findLower && findUpper)) + return 0; + // Obtain the characters for both the lower case string and the upper case + // string representing the same word. + const jchar* findLowerChars = env->GetStringChars(findLower, 0); + const jchar* findUpperChars = env->GetStringChars(findUpper, 0); + // If one or the other is null, do not search. + if (!(findLowerChars && findUpperChars)) { + if (findLowerChars) + env->ReleaseStringChars(findLower, findLowerChars); + if (findUpperChars) + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindAll"); + CachedRoot* root = view->getFrameCache(WebView::AllowNewer); + if (!root) { + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + int length = env->GetStringLength(findLower); + // If the lengths of the strings do not match, then they are not the same + // word, so do not search. + if (!length || env->GetStringLength(findUpper) != length) { + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + int width = root->documentWidth(); + int height = root->documentHeight(); + // Create a FindCanvas, which allows us to fake draw into it so we can + // figure out where our search string is rendered (and how many times). + FindCanvas canvas(width, height, (const UChar*) findLowerChars, + (const UChar*) findUpperChars, length << 1); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + canvas.setBitmapDevice(bitmap); + root->draw(canvas); + WTF::Vector<MatchInfo>* matches = canvas.detachMatches(); + // With setMatches, the WebView takes ownership of matches + view->setMatches(matches, sameAsLastSearch); + + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return canvas.found(); +} + +static void nativeFindNext(JNIEnv *env, jobject obj, bool forward) { - WebView* view = reinterpret_cast<WebView*>(ptr); - ALOGD("nativeDestroy view: %p", view); - ALOG_ASSERT(view, "view not set in nativeDestroy"); + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindNext"); + view->findNext(forward); +} + +static int nativeFindIndex(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindIndex"); + return view->currentMatchIndex(); +} + +static void nativeUpdateCachedTextfield(JNIEnv *env, jobject obj, jstring updatedText, jint generation) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeUpdateCachedTextfield"); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return; + const CachedNode* cachedFocusNode = root->currentFocus(); + if (!cachedFocusNode || !cachedFocusNode->isTextInput()) + return; + WTF::String webcoreString = jstringToWtfString(env, updatedText); + (const_cast<CachedNode*>(cachedFocusNode))->setExport(webcoreString); + root->setTextGeneration(generation); + checkException(env); +} + +static jint nativeGetBlockLeftEdge(JNIEnv *env, jobject obj, jint x, jint y, + jfloat scale) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + if (!view) + return -1; + return view->getBlockLeftEdge(x, y, scale); +} + +static void nativeDestroy(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOGD("nativeDestroy view: %p", view); + LOG_ASSERT(view, "view not set in nativeDestroy"); delete view; } -static void nativeStopGL(JNIEnv *env, jobject obj, jint ptr) +static void nativeStopGL(JNIEnv *env, jobject obj) { - if (ptr) - reinterpret_cast<WebView*>(ptr)->stopGL(); + GET_NATIVE_VIEW(env, obj)->stopGL(); +} + +static bool nativeMoveCursorToNextTextInput(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return false; + const CachedNode* current = root->currentCursor(); + if (!current || !current->isTextInput()) + current = root->currentFocus(); + if (!current || !current->isTextInput()) + return false; + const CachedFrame* frame; + const CachedNode* next = root->nextTextField(current, &frame); + if (!next) + return false; + const WebCore::IntRect& bounds = next->bounds(frame); + root->rootHistory()->setMouseBounds(bounds); + view->getWebViewCore()->updateCursorBounds(root, frame, next); + view->showCursorUntimed(); + root->setCursor(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(next)); + view->sendMoveFocus(static_cast<WebCore::Frame*>(frame->framePointer()), + static_cast<WebCore::Node*>(next->nodePointer())); + if (!next->isInLayer()) + view->scrollRectOnScreen(bounds); + view->getWebViewCore()->m_moveGeneration++; + return true; +} + +static int nativeMoveGeneration(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) + return 0; + return view->moveGeneration(); +} + +static void nativeMoveSelection(JNIEnv *env, jobject obj, int x, int y) +{ + GET_NATIVE_VIEW(env, obj)->moveSelection(x, y); +} + +static void nativeResetSelection(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->resetSelection(); +} + +static jobject nativeSelectableText(JNIEnv* env, jobject obj) +{ + IntPoint pos = GET_NATIVE_VIEW(env, obj)->selectableText(); + jclass pointClass = env->FindClass("android/graphics/Point"); + jmethodID init = env->GetMethodID(pointClass, "<init>", "(II)V"); + jobject point = env->NewObject(pointClass, init, pos.x(), pos.y()); + env->DeleteLocalRef(pointClass); + return point; +} + +static void nativeSelectAll(JNIEnv* env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->selectAll(); +} + +static void nativeSetExtendSelection(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setExtendSelection(); +} + +static jboolean nativeStartSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->startSelection(x, y); +} + +static jboolean nativeWordSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->wordSelection(x, y); +} + +static void nativeExtendSelection(JNIEnv *env, jobject obj, int x, int y) +{ + GET_NATIVE_VIEW(env, obj)->extendSelection(x, y); } static jobject nativeGetSelection(JNIEnv *env, jobject obj) { WebView* view = GET_NATIVE_VIEW(env, obj); - ALOG_ASSERT(view, "view not set in %s", __FUNCTION__); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); String selection = view->getSelection(); return wtfStringToJstring(env, selection); } -static void nativeDiscardAllTextures(JNIEnv *env, jobject obj) +static jboolean nativeHitSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->hitSelection(x, y); +} + +static jint nativeSelectionX(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->selectionX(); +} + +static jint nativeSelectionY(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->selectionY(); +} + +static void nativeSetSelectionPointer(JNIEnv *env, jobject obj, jint nativeView, + jboolean set, jfloat scale, jint x, jint y) +{ + ((WebView*)nativeView)->setSelectionPointer(set, scale, x, y); +} + +static void nativeRegisterPageSwapCallback(JNIEnv *env, jobject obj) { - //discard all textures for debugging/test purposes, but not gl backing memory - bool allTextures = true, deleteGLTextures = false; - TilesManager::instance()->discardTextures(allTextures, deleteGLTextures); + GET_NATIVE_VIEW(env, obj)->registerPageSwapCallback(); } static void nativeTileProfilingStart(JNIEnv *env, jobject obj) @@ -1071,14 +2581,15 @@ static void dumpToFile(const char text[], void* file) { } #endif -// Return true to view invalidate WebView static bool nativeSetProperty(JNIEnv *env, jobject obj, jstring jkey, jstring jvalue) { WTF::String key = jstringToWtfString(env, jkey); WTF::String value = jstringToWtfString(env, jvalue); if (key == "inverted") { - bool shouldInvert = (value == "true"); - TilesManager::instance()->setInvertedScreen(shouldInvert); + if (value == "true") + TilesManager::instance()->setInvertedScreen(true); + else + TilesManager::instance()->setInvertedScreen(false); return true; } else if (key == "inverted_contrast") { @@ -1089,47 +2600,25 @@ static bool nativeSetProperty(JNIEnv *env, jobject obj, jstring jkey, jstring jv else if (key == "enable_cpu_upload_path") { TilesManager::instance()->transferQueue()->setTextureUploadType( value == "true" ? CpuUpload : GpuUpload); + return true; } else if (key == "use_minimal_memory") { TilesManager::instance()->setUseMinimalMemory(value == "true"); - } - else if (key == "use_double_buffering") { - TilesManager::instance()->setUseDoubleBuffering(value == "true"); - } - else if (key == "tree_updates") { - TilesManager::instance()->clearContentUpdates(); + return true; } return false; } -static jstring nativeGetProperty(JNIEnv *env, jobject obj, jstring jkey) +static jstring nativeGetProperty(JNIEnv *env, jobject obj, jstring key) { - WTF::String key = jstringToWtfString(env, jkey); - if (key == "tree_updates") { - int updates = TilesManager::instance()->getContentUpdates(); - WTF::String wtfUpdates = WTF::String::number(updates); - return wtfStringToJstring(env, wtfUpdates); - } return 0; } static void nativeOnTrimMemory(JNIEnv *env, jobject obj, jint level) { if (TilesManager::hardwareAccelerationEnabled()) { - // When we got TRIM_MEMORY_MODERATE or TRIM_MEMORY_COMPLETE, we should - // make sure the transfer queue is empty and then abandon the Surface - // Texture to avoid ANR b/c framework may destroy the EGL context. - // Refer to WindowManagerImpl.java for conditions we followed. - TilesManager* tilesManager = TilesManager::instance(); - if ((level >= TRIM_MEMORY_MODERATE - && !tilesManager->highEndGfx()) - || level >= TRIM_MEMORY_COMPLETE) { - ALOGD("OnTrimMemory with EGL Context %p", eglGetCurrentContext()); - tilesManager->cleanupGLResources(); - } - - bool freeAllTextures = (level > TRIM_MEMORY_UI_HIDDEN), glTextures = true; - tilesManager->discardTextures(freeAllTextures, glTextures); + bool freeAllTextures = (level > TRIM_MEMORY_UI_HIDDEN); + TilesManager::instance()->deallocateTextures(freeAllTextures); } } @@ -1137,7 +2626,7 @@ static void nativeDumpDisplayTree(JNIEnv* env, jobject jwebview, jstring jurl) { #ifdef ANDROID_DUMP_DISPLAY_TREE WebView* view = GET_NATIVE_VIEW(env, jwebview); - ALOG_ASSERT(view, "view not set in %s", __FUNCTION__); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); if (view && view->getWebViewCore()) { FILE* file = fopen(DISPLAY_TREE_LOG_FILE, "w"); @@ -1154,17 +2643,17 @@ static void nativeDumpDisplayTree(JNIEnv* env, jobject jwebview, jstring jurl) SkDumpCanvas canvas(&dumper); // this will playback the picture into the canvas, which will // spew its contents to the dumper - view->draw(&canvas, 0, WebView::DrawExtrasNone); + view->draw(&canvas, 0, 0, false); // we're done with the file now fwrite("\n", 1, 1, file); fclose(file); } #if USE(ACCELERATED_COMPOSITING) - const LayerAndroid* baseLayer = view->getBaseLayer(); - if (baseLayer) { + const LayerAndroid* rootLayer = view->compositeRoot(); + if (rootLayer) { FILE* file = fopen(LAYERS_TREE_LOG_FILE,"w"); if (file) { - baseLayer->dumpLayers(file, 0); + rootLayer->dumpLayers(file, 0); fclose(file); } } @@ -1173,13 +2662,13 @@ static void nativeDumpDisplayTree(JNIEnv* env, jobject jwebview, jstring jurl) #endif } -static int nativeScrollableLayer(JNIEnv* env, jobject jwebview, jint nativeView, - jint x, jint y, jobject rect, jobject bounds) +static int nativeScrollableLayer(JNIEnv* env, jobject jwebview, jint x, jint y, + jobject rect, jobject bounds) { - WebView* webview = reinterpret_cast<WebView*>(nativeView); - ALOG_ASSERT(webview, "webview not set in %s", __FUNCTION__); + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); SkIRect nativeRect, nativeBounds; - int id = webview->scrollableLayer(x, y, &nativeRect, &nativeBounds); + int id = view->scrollableLayer(x, y, &nativeRect, &nativeBounds); if (rect) GraphicsJNI::irect_to_jrect(nativeRect, env, rect); if (bounds) @@ -1187,18 +2676,18 @@ static int nativeScrollableLayer(JNIEnv* env, jobject jwebview, jint nativeView, return id; } -static bool nativeScrollLayer(JNIEnv* env, jobject obj, - jint nativeView, jint layerId, jint x, jint y) +static bool nativeScrollLayer(JNIEnv* env, jobject obj, jint layerId, jint x, + jint y) { #if ENABLE(ANDROID_OVERFLOW_SCROLL) - WebView* webview = reinterpret_cast<WebView*>(nativeView); - webview->scrollLayer(layerId, x, y); + WebView* view = GET_NATIVE_VIEW(env, obj); + view->scrollLayer(layerId, x, y); //TODO: the below only needed for the SW rendering path - LayerAndroid* baseLayer = webview->getBaseLayer(); - if (!baseLayer) + LayerAndroid* root = view->compositeRoot(); + if (!root) return false; - LayerAndroid* layer = baseLayer->findById(layerId); + LayerAndroid* layer = root->findById(layerId); if (!layer || !layer->contentIsScrollable()) return false; return static_cast<ScrollableLayerAndroid*>(layer)->scrollTo(x, y); @@ -1208,10 +2697,9 @@ static bool nativeScrollLayer(JNIEnv* env, jobject obj, static void nativeSetIsScrolling(JNIEnv* env, jobject jwebview, jboolean isScrolling) { - // TODO: Pass in the native pointer instead WebView* view = GET_NATIVE_VIEW(env, jwebview); - if (view) - view->setIsScrolling(isScrolling); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->setIsScrolling(isScrolling); } static void nativeUseHardwareAccelSkia(JNIEnv*, jobject, jboolean enabled) @@ -1219,9 +2707,9 @@ static void nativeUseHardwareAccelSkia(JNIEnv*, jobject, jboolean enabled) BaseRenderer::setCurrentRendererType(enabled ? BaseRenderer::Ganesh : BaseRenderer::Raster); } -static int nativeGetBackgroundColor(JNIEnv* env, jobject obj, jint nativeView) +static int nativeGetBackgroundColor(JNIEnv* env, jobject obj) { - WebView* view = reinterpret_cast<WebView*>(nativeView); + WebView* view = GET_NATIVE_VIEW(env, obj); BaseLayerAndroid* baseLayer = view->getBaseLayer(); if (baseLayer) { WebCore::Color color = baseLayer->getBackgroundColor(); @@ -1235,93 +2723,179 @@ static int nativeGetBackgroundColor(JNIEnv* env, jobject obj, jint nativeView) static void nativeSetPauseDrawing(JNIEnv *env, jobject obj, jint nativeView, jboolean pause) { - reinterpret_cast<WebView*>(nativeView)->setDrawingPaused(pause); -} - -static void nativeSetTextSelection(JNIEnv *env, jobject obj, jint nativeView, - jint selectionPtr) -{ - SelectText* selection = reinterpret_cast<SelectText*>(selectionPtr); - reinterpret_cast<WebView*>(nativeView)->setTextSelection(selection); -} - -static jint nativeGetHandleLayerId(JNIEnv *env, jobject obj, jint nativeView, - jint handleIndex, jobject cursorPoint, - jobject textQuad) -{ - WebView* webview = reinterpret_cast<WebView*>(nativeView); - SkIPoint nativePoint; - FloatQuad nativeTextQuad; - int layerId = webview->getHandleLayerId((SelectText::HandleId) handleIndex, - nativePoint, nativeTextQuad); - if (cursorPoint) - GraphicsJNI::ipoint_to_jpoint(nativePoint, env, cursorPoint); - if (textQuad) - webview->floatQuadToQuadF(env, nativeTextQuad, textQuad); - return layerId; -} - -static void nativeMapLayerRect(JNIEnv *env, jobject obj, jint nativeView, - jint layerId, jobject rect) -{ - WebView* webview = reinterpret_cast<WebView*>(nativeView); - SkIRect nativeRect; - GraphicsJNI::jrect_to_irect(env, rect, &nativeRect); - webview->mapLayerRect(layerId, nativeRect); - GraphicsJNI::irect_to_jrect(nativeRect, env, rect); -} - -static jint nativeSetHwAccelerated(JNIEnv *env, jobject obj, jint nativeView, - jboolean hwAccelerated) -{ - WebView* webview = reinterpret_cast<WebView*>(nativeView); - return webview->setHwAccelerated(hwAccelerated); -} - -static void nativeFindMaxVisibleRect(JNIEnv *env, jobject obj, jint nativeView, - jint movingLayerId, jobject visibleContentRect) -{ - WebView* webview = reinterpret_cast<WebView*>(nativeView); - SkIRect nativeRect; - GraphicsJNI::jrect_to_irect(env, visibleContentRect, &nativeRect); - webview->findMaxVisibleRect(movingLayerId, nativeRect); - GraphicsJNI::irect_to_jrect(nativeRect, env, visibleContentRect); + ((WebView*)nativeView)->m_isDrawingPaused = pause; } /* * JNI registration */ static JNINativeMethod gJavaWebViewMethods[] = { + { "nativeCacheHitFramePointer", "()I", + (void*) nativeCacheHitFramePointer }, + { "nativeCacheHitIsPlugin", "()Z", + (void*) nativeCacheHitIsPlugin }, + { "nativeCacheHitNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeCacheHitNodeBounds }, + { "nativeCacheHitNodePointer", "()I", + (void*) nativeCacheHitNodePointer }, + { "nativeClearCursor", "()V", + (void*) nativeClearCursor }, { "nativeCreate", "(ILjava/lang/String;Z)V", (void*) nativeCreate }, - { "nativeDestroy", "(I)V", + { "nativeCursorFramePointer", "()I", + (void*) nativeCursorFramePointer }, + { "nativePageShouldHandleShiftAndArrows", "()Z", + (void*) nativePageShouldHandleShiftAndArrows }, + { "nativeCursorNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeCursorNodeBounds }, + { "nativeCursorNodePointer", "()I", + (void*) nativeCursorNodePointer }, + { "nativeCursorIntersects", "(Landroid/graphics/Rect;)Z", + (void*) nativeCursorIntersects }, + { "nativeCursorIsAnchor", "()Z", + (void*) nativeCursorIsAnchor }, + { "nativeCursorIsTextInput", "()Z", + (void*) nativeCursorIsTextInput }, + { "nativeCursorPosition", "()Landroid/graphics/Point;", + (void*) nativeCursorPosition }, + { "nativeCursorText", "()Ljava/lang/String;", + (void*) nativeCursorText }, + { "nativeCursorWantsKeyEvents", "()Z", + (void*)nativeCursorWantsKeyEvents }, + { "nativeDebugDump", "()V", + (void*) nativeDebugDump }, + { "nativeDestroy", "()V", (void*) nativeDestroy }, - { "nativeDraw", "(Landroid/graphics/Canvas;Landroid/graphics/RectF;II)V", + { "nativeDraw", "(Landroid/graphics/Canvas;Landroid/graphics/RectF;IIZ)I", (void*) nativeDraw }, - { "nativeCreateDrawGLFunction", "(ILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/RectF;FI)I", - (void*) nativeCreateDrawGLFunction }, - { "nativeGetDrawGLFunction", "(I)I", + { "nativeGetDrawGLFunction", "(ILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/RectF;FI)I", (void*) nativeGetDrawGLFunction }, - { "nativeUpdateDrawGLFunction", "(ILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/RectF;F)V", + { "nativeUpdateDrawGLFunction", "(Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/RectF;)V", (void*) nativeUpdateDrawGLFunction }, { "nativeDumpDisplayTree", "(Ljava/lang/String;)V", (void*) nativeDumpDisplayTree }, { "nativeEvaluateLayersAnimations", "(I)Z", (void*) nativeEvaluateLayersAnimations }, + { "nativeExtendSelection", "(II)V", + (void*) nativeExtendSelection }, + { "nativeFindAll", "(Ljava/lang/String;Ljava/lang/String;Z)I", + (void*) nativeFindAll }, + { "nativeFindNext", "(Z)V", + (void*) nativeFindNext }, + { "nativeFindIndex", "()I", + (void*) nativeFindIndex}, + { "nativeFocusCandidateFramePointer", "()I", + (void*) nativeFocusCandidateFramePointer }, + { "nativeFocusCandidateHasNextTextfield", "()Z", + (void*) focusCandidateHasNextTextfield }, + { "nativeFocusCandidateIsPassword", "()Z", + (void*) nativeFocusCandidateIsPassword }, + { "nativeFocusCandidateIsRtlText", "()Z", + (void*) nativeFocusCandidateIsRtlText }, + { "nativeFocusCandidateIsTextInput", "()Z", + (void*) nativeFocusCandidateIsTextInput }, + { "nativeFocusCandidateLineHeight", "()I", + (void*) nativeFocusCandidateLineHeight }, + { "nativeFocusCandidateMaxLength", "()I", + (void*) nativeFocusCandidateMaxLength }, + { "nativeFocusCandidateIsAutoComplete", "()Z", + (void*) nativeFocusCandidateIsAutoComplete }, + { "nativeFocusCandidateIsSpellcheck", "()Z", + (void*) nativeFocusCandidateIsSpellcheck }, + { "nativeFocusCandidateName", "()Ljava/lang/String;", + (void*) nativeFocusCandidateName }, + { "nativeFocusCandidateNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeFocusCandidateNodeBounds }, + { "nativeFocusCandidatePaddingRect", "()Landroid/graphics/Rect;", + (void*) nativeFocusCandidatePaddingRect }, + { "nativeFocusCandidatePointer", "()I", + (void*) nativeFocusCandidatePointer }, + { "nativeFocusCandidateText", "()Ljava/lang/String;", + (void*) nativeFocusCandidateText }, + { "nativeFocusCandidateTextSize", "()F", + (void*) nativeFocusCandidateTextSize }, + { "nativeFocusCandidateType", "()I", + (void*) nativeFocusCandidateType }, + { "nativeFocusCandidateLayerId", "()I", + (void*) nativeFocusCandidateLayerId }, + { "nativeFocusIsPlugin", "()Z", + (void*) nativeFocusIsPlugin }, + { "nativeFocusNodeBounds", "()Landroid/graphics/Rect;", + (void*) nativeFocusNodeBounds }, + { "nativeFocusNodePointer", "()I", + (void*) nativeFocusNodePointer }, + { "nativeGetCursorRingBounds", "()Landroid/graphics/Rect;", + (void*) nativeGetCursorRingBounds }, { "nativeGetSelection", "()Ljava/lang/String;", (void*) nativeGetSelection }, + { "nativeHasCursorNode", "()Z", + (void*) nativeHasCursorNode }, + { "nativeHasFocusNode", "()Z", + (void*) nativeHasFocusNode }, + { "nativeHideCursor", "()V", + (void*) nativeHideCursor }, + { "nativeHitSelection", "(II)Z", + (void*) nativeHitSelection }, + { "nativeImageURI", "(II)Ljava/lang/String;", + (void*) nativeImageURI }, + { "nativeInstrumentReport", "()V", + (void*) nativeInstrumentReport }, + { "nativeLayerBounds", "(I)Landroid/graphics/Rect;", + (void*) nativeLayerBounds }, + { "nativeMotionUp", "(III)Z", + (void*) nativeMotionUp }, + { "nativeMoveCursor", "(IIZ)Z", + (void*) nativeMoveCursor }, + { "nativeMoveCursorToNextTextInput", "()Z", + (void*) nativeMoveCursorToNextTextInput }, + { "nativeMoveGeneration", "()I", + (void*) nativeMoveGeneration }, + { "nativeMoveSelection", "(II)V", + (void*) nativeMoveSelection }, + { "nativePointInNavCache", "(III)Z", + (void*) nativePointInNavCache }, + { "nativeResetSelection", "()V", + (void*) nativeResetSelection }, + { "nativeSelectableText", "()Landroid/graphics/Point;", + (void*) nativeSelectableText }, + { "nativeSelectAll", "()V", + (void*) nativeSelectAll }, + { "nativeSelectBestAt", "(Landroid/graphics/Rect;)V", + (void*) nativeSelectBestAt }, + { "nativeSelectAt", "(II)V", + (void*) nativeSelectAt }, + { "nativeSelectionX", "()I", + (void*) nativeSelectionX }, + { "nativeSelectionY", "()I", + (void*) nativeSelectionY }, + { "nativeSetExtendSelection", "()V", + (void*) nativeSetExtendSelection }, + { "nativeSetFindIsEmpty", "()V", + (void*) nativeSetFindIsEmpty }, + { "nativeSetFindIsUp", "(Z)V", + (void*) nativeSetFindIsUp }, { "nativeSetHeightCanMeasure", "(Z)V", (void*) nativeSetHeightCanMeasure }, - { "nativeSetBaseLayer", "(IIZZI)Z", + { "nativeSetBaseLayer", "(ILandroid/graphics/Region;ZZZ)V", (void*) nativeSetBaseLayer }, - { "nativeGetBaseLayer", "(I)I", + { "nativeGetTextSelectionRegion", "(ILandroid/graphics/Region;)V", + (void*) nativeGetTextSelectionRegion }, + { "nativeGetSelectionHandles", "(I[I)V", + (void*) nativeGetSelectionHandles }, + { "nativeGetBaseLayer", "()I", (void*) nativeGetBaseLayer }, + { "nativeReplaceBaseContent", "(I)V", + (void*) nativeReplaceBaseContent }, { "nativeCopyBaseContentToPicture", "(Landroid/graphics/Picture;)V", (void*) nativeCopyBaseContentToPicture }, { "nativeHasContent", "()Z", (void*) nativeHasContent }, - { "nativeDiscardAllTextures", "()V", - (void*) nativeDiscardAllTextures }, + { "nativeSetSelectionPointer", "(IZFII)V", + (void*) nativeSetSelectionPointer }, + { "nativeShowCursorTimed", "()V", + (void*) nativeShowCursorTimed }, + { "nativeRegisterPageSwapCallback", "()V", + (void*) nativeRegisterPageSwapCallback }, { "nativeTileProfilingStart", "()V", (void*) nativeTileProfilingStart }, { "nativeTileProfilingStop", "()F", @@ -1336,17 +2910,29 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeTileProfilingGetInt }, { "nativeTileProfilingGetFloat", "(IILjava/lang/String;)F", (void*) nativeTileProfilingGetFloat }, - { "nativeStopGL", "(I)V", + { "nativeStartSelection", "(II)Z", + (void*) nativeStartSelection }, + { "nativeStopGL", "()V", (void*) nativeStopGL }, - { "nativeScrollableLayer", "(IIILandroid/graphics/Rect;Landroid/graphics/Rect;)I", + { "nativeSubtractLayers", "(Landroid/graphics/Rect;)Landroid/graphics/Rect;", + (void*) nativeSubtractLayers }, + { "nativeTextGeneration", "()I", + (void*) nativeTextGeneration }, + { "nativeUpdateCachedTextfield", "(Ljava/lang/String;I)V", + (void*) nativeUpdateCachedTextfield }, + { "nativeWordSelection", "(II)Z", + (void*) nativeWordSelection }, + { "nativeGetBlockLeftEdge", "(IIF)I", + (void*) nativeGetBlockLeftEdge }, + { "nativeScrollableLayer", "(IILandroid/graphics/Rect;Landroid/graphics/Rect;)I", (void*) nativeScrollableLayer }, - { "nativeScrollLayer", "(IIII)Z", + { "nativeScrollLayer", "(III)Z", (void*) nativeScrollLayer }, { "nativeSetIsScrolling", "(Z)V", (void*) nativeSetIsScrolling }, { "nativeUseHardwareAccelSkia", "(Z)V", (void*) nativeUseHardwareAccelSkia }, - { "nativeGetBackgroundColor", "(I)I", + { "nativeGetBackgroundColor", "()I", (void*) nativeGetBackgroundColor }, { "nativeSetProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*) nativeSetProperty }, @@ -1356,27 +2942,17 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeOnTrimMemory }, { "nativeSetPauseDrawing", "(IZ)V", (void*) nativeSetPauseDrawing }, - { "nativeSetTextSelection", "(II)V", - (void*) nativeSetTextSelection }, - { "nativeGetHandleLayerId", "(IILandroid/graphics/Point;Landroid/webkit/QuadF;)I", - (void*) nativeGetHandleLayerId }, - { "nativeMapLayerRect", "(IILandroid/graphics/Rect;)V", - (void*) nativeMapLayerRect }, - { "nativeSetHwAccelerated", "(IZ)I", - (void*) nativeSetHwAccelerated }, - { "nativeFindMaxVisibleRect", "(IILandroid/graphics/Rect;)V", - (void*) nativeFindMaxVisibleRect }, }; int registerWebView(JNIEnv* env) { - jclass clazz = env->FindClass("android/webkit/WebViewClassic"); - ALOG_ASSERT(clazz, "Unable to find class android/webkit/WebViewClassic"); + jclass clazz = env->FindClass("android/webkit/WebView"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebView"); gWebViewField = env->GetFieldID(clazz, "mNativeClass", "I"); - ALOG_ASSERT(gWebViewField, "Unable to find android/webkit/WebViewClassic.mNativeClass"); + LOG_ASSERT(gWebViewField, "Unable to find android/webkit/WebView.mNativeClass"); env->DeleteLocalRef(clazz); - return jniRegisterNativeMethods(env, "android/webkit/WebViewClassic", gJavaWebViewMethods, NELEM(gJavaWebViewMethods)); + return jniRegisterNativeMethods(env, "android/webkit/WebView", gJavaWebViewMethods, NELEM(gJavaWebViewMethods)); } } // namespace android diff --git a/Source/WebKit/android/plugins/ANPSoundInterface.cpp b/Source/WebKit/android/plugins/ANPSoundInterface.cpp index 9d19443..c238872 100644 --- a/Source/WebKit/android/plugins/ANPSoundInterface.cpp +++ b/Source/WebKit/android/plugins/ANPSoundInterface.cpp @@ -38,7 +38,7 @@ struct ANPAudioTrack { android::AudioTrack* mTrack; }; -static ANPSampleFormat toANPFormat(audio_format_t fm) { +static ANPSampleFormat toANPFormat(int fm) { switch (fm) { case AUDIO_FORMAT_PCM_16_BIT: return kPCM16Bit_ANPSampleFormat; @@ -49,7 +49,7 @@ static ANPSampleFormat toANPFormat(audio_format_t fm) { } } -static audio_format_t fromANPFormat(ANPSampleFormat fm) { +static int fromANPFormat(ANPSampleFormat fm) { switch (fm) { case kPCM16Bit_ANPSampleFormat: return AUDIO_FORMAT_PCM_16_BIT; @@ -71,7 +71,7 @@ static void callbackProc(int event, void* user, void* info) { src = reinterpret_cast<android::AudioTrack::Buffer*>(info); dst.bufferData = src->raw; dst.channelCount = src->channelCount; - dst.format = toANPFormat((audio_format_t) src->format); + dst.format = toANPFormat(src->format); dst.size = src->size; track->mProc(kMoreData_ANPAudioEvent, track->mUser, &dst); // return the updated size field @@ -102,7 +102,7 @@ static ANPAudioTrack* ANPCreateTrack(uint32_t sampleRate, fromANPFormat(format), (channelCount > 1) ? AUDIO_CHANNEL_OUT_STEREO : AUDIO_CHANNEL_OUT_MONO, 0, // frameCount - (audio_output_flags_t) 0, // AUDIO_OUTPUT_FLAG_NONE, + 0, // flags callbackProc, track, 0); diff --git a/Source/WebKit/android/plugins/ANPSurfaceInterface.cpp b/Source/WebKit/android/plugins/ANPSurfaceInterface.cpp index 92dbbcd..4b99b31 100644 --- a/Source/WebKit/android/plugins/ANPSurfaceInterface.cpp +++ b/Source/WebKit/android/plugins/ANPSurfaceInterface.cpp @@ -32,7 +32,7 @@ #include "SkANP.h" #include "android_graphics.h" #include <JNIUtility.h> -#include <gui/Surface.h> +#include <surfaceflinger/Surface.h> #include <ui/Rect.h> #include <ui/Region.h> #include <utils/RefBase.h> @@ -47,7 +47,7 @@ static struct ANPSurfaceInterfaceJavaGlue { jfieldID surfacePointer; } gSurfaceJavaGlue; -static inline sp<android::Surface> getSurface(JNIEnv* env, jobject view) { +static inline sp<Surface> getSurface(JNIEnv* env, jobject view) { if (!env || !view) { return NULL; } @@ -80,7 +80,7 @@ static inline sp<android::Surface> getSurface(JNIEnv* env, jobject view) { env->DeleteLocalRef(holder); env->DeleteLocalRef(surface); - return sp<android::Surface>((android::Surface*) surfacePointer); + return sp<Surface>((Surface*) surfacePointer); } static inline ANPBitmapFormat convertPixelFormat(PixelFormat format) { @@ -96,9 +96,9 @@ static bool anp_lock(JNIEnv* env, jobject surfaceView, ANPBitmap* bitmap, ANPRec return false; } - sp<android::Surface> surface = getSurface(env, surfaceView); + sp<Surface> surface = getSurface(env, surfaceView); - if (!bitmap || !android::Surface::isValid(surface)) { + if (!bitmap || !Surface::isValid(surface)) { return false; } @@ -112,7 +112,7 @@ static bool anp_lock(JNIEnv* env, jobject surfaceView, ANPBitmap* bitmap, ANPRec dirtyRegion.set(Rect(0x3FFF, 0x3FFF)); } - android::Surface::SurfaceInfo info; + Surface::SurfaceInfo info; status_t err = surface->lock(&info, &dirtyRegion); if (err < 0) { return false; @@ -150,9 +150,9 @@ static void anp_unlock(JNIEnv* env, jobject surfaceView) { return; } - sp<android::Surface> surface = getSurface(env, surfaceView); + sp<Surface> surface = getSurface(env, surfaceView); - if (!android::Surface::isValid(surface)) { + if (!Surface::isValid(surface)) { return; } diff --git a/Source/WebKit/android/plugins/PluginWidgetAndroid.cpp b/Source/WebKit/android/plugins/PluginWidgetAndroid.cpp index 09bb24e..fc98837 100644 --- a/Source/WebKit/android/plugins/PluginWidgetAndroid.cpp +++ b/Source/WebKit/android/plugins/PluginWidgetAndroid.cpp @@ -160,8 +160,7 @@ bool PluginWidgetAndroid::setDrawingModel(ANPDrawingModel model) { if (model == kOpenGL_ANPDrawingModel && m_layer == 0) { jobject webview = m_core->getWebViewJavaObject(); - AutoJObject webViewCore = m_core->getJavaObject(); - m_layer = new WebCore::MediaLayer(webview, webViewCore.get()); + m_layer = new WebCore::MediaLayer(webview); } else if (model != kOpenGL_ANPDrawingModel && m_layer != 0) { m_layer->unref(); @@ -597,9 +596,9 @@ void PluginWidgetAndroid::scrollToVisiblePluginRect() { android::WebViewCore* core = android::WebViewCore::getWebViewCore(scrollView); #if DEBUG_VISIBLE_RECTS PLUGIN_LOG("%s call scrollTo (%d,%d) to center (%d,%d)", __FUNCTION__, - scrollDocX, scrollDocY, rectCenterX, rectCenterY); + scrollDocX, scrollDocX, rectCenterX, rectCenterY); #endif - core->scrollTo(scrollDocX, scrollDocY, true); + core->scrollTo(scrollDocX, scrollDocX, true); } void PluginWidgetAndroid::requestFullScreen() { diff --git a/Source/WebKit/android/smoke/MessageThread.cpp b/Source/WebKit/android/smoke/MessageThread.cpp index 97ab18c..48f2222 100644 --- a/Source/WebKit/android/smoke/MessageThread.cpp +++ b/Source/WebKit/android/smoke/MessageThread.cpp @@ -79,7 +79,7 @@ void MessageQueue::post(Message* message) { AutoMutex lock(m_mutex); double when = message->m_when; - ALOG_ASSERT(when > 0, "Message time may not be 0"); + LOG_ASSERT(when > 0, "Message time may not be 0"); list<Message*>::iterator it; for (it = m_messages.begin(); it != m_messages.end(); ++it) { diff --git a/Source/WebKit/android/wds/Command.cpp b/Source/WebKit/android/wds/Command.cpp index 1a365e5..bd8536f 100644 --- a/Source/WebKit/android/wds/Command.cpp +++ b/Source/WebKit/android/wds/Command.cpp @@ -95,7 +95,7 @@ public: virtual ~InternalCommand() { delete m_connection; } void doCommand() const { - ALOGD("Executing command '%s' (%s)", m_name, m_description); + 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); diff --git a/Source/WebKit/android/wds/Connection.cpp b/Source/WebKit/android/wds/Connection.cpp index 52193e5..d7e55ac 100644 --- a/Source/WebKit/android/wds/Connection.cpp +++ b/Source/WebKit/android/wds/Connection.cpp @@ -35,7 +35,7 @@ #if ENABLE(WDS) #define MAX_CONNECTION_QUEUE 5 -#define log_errno(x) ALOGE("%s: %d", x, strerror(errno)) +#define log_errno(x) LOGE("%s: %d", x, strerror(errno)) namespace android { diff --git a/Source/WebKit/android/wds/DebugServer.cpp b/Source/WebKit/android/wds/DebugServer.cpp index 2fde6bd..f33a65b 100644 --- a/Source/WebKit/android/wds/DebugServer.cpp +++ b/Source/WebKit/android/wds/DebugServer.cpp @@ -42,7 +42,7 @@ #if ENABLE(WDS) #define DEFAULT_PORT 9999 -#define log_errno(x) ALOGE("%s: %d", x, strerror(errno)) +#define log_errno(x) LOGE("%s: %d", x, strerror(errno)) namespace android { @@ -70,7 +70,7 @@ DebugServer::DebugServer() { char buf[PROPERTY_VALUE_MAX]; int ret = property_get("webcore.wds.enable", buf, NULL); if (ret != -1 && strcmp(buf, "1") == 0) { - ALOGD("WDS Enabled"); + LOGD("WDS Enabled"); m_threadId = createThread(mainThread, this, "WDS"); } // Initialize the available commands. @@ -78,26 +78,26 @@ DebugServer::DebugServer() { } void DebugServer::start() { - ALOGD("DebugServer thread started"); + LOGD("DebugServer thread started"); ConnectionServer cs; if (!cs.connect(DEFAULT_PORT)) { - ALOGE("Failed to start the server socket connection"); + LOGE("Failed to start the server socket connection"); return; } while (true ) { - ALOGD("Waiting for incoming connections..."); + LOGD("Waiting for incoming connections..."); Connection* conn = cs.accept(); if (!conn) { log_errno("Failed to accept new connections"); return; } - ALOGD("...Connection established"); + LOGD("...Connection established"); Command* c = Command::Find(conn); if (!c) { - ALOGE("Could not find matching command"); + LOGE("Could not find matching command"); delete conn; } else { // Dispatch the command, it will handle cleaning up the connection @@ -106,7 +106,7 @@ void DebugServer::start() { } } - ALOGD("DebugServer thread finished"); + LOGD("DebugServer thread finished"); } } // end namespace WDS diff --git a/Source/WebKit/android/wds/client/AdbConnection.cpp b/Source/WebKit/android/wds/client/AdbConnection.cpp index 7d02ecc..465f9c3 100644 --- a/Source/WebKit/android/wds/client/AdbConnection.cpp +++ b/Source/WebKit/android/wds/client/AdbConnection.cpp @@ -78,7 +78,7 @@ bool AdbConnection::connect() { bool AdbConnection::sendRequest(const char* fmt, ...) const { if (m_fd == -1) { - ALOGE("Connection is closed"); + LOGE("Connection is closed"); return false; } @@ -89,7 +89,7 @@ bool AdbConnection::sendRequest(const char* fmt, ...) const { int res = vsnprintf(buf, MAX_COMMAND_LENGTH, fmt, args); va_end(args); - ALOGV("Sending command: %04X%.*s", res, res, buf); + LOGV("Sending command: %04X%.*s", res, res, buf); // Construct the payload length char payloadLen[PAYLOAD_LENGTH + 1]; @@ -115,7 +115,7 @@ static void printFailureMessage(int fd) { // Grab the payload length char lenStr[PAYLOAD_LENGTH + 1]; int payloadLen = recv(fd, lenStr, sizeof(lenStr) - 1, 0); - ALOG_ASSERT(payloadLen == PAYLOAD_LENGTH, "Incorrect payload size"); + LOG_ASSERT(payloadLen == PAYLOAD_LENGTH, "Incorrect payload size"); lenStr[PAYLOAD_LENGTH] = 0; // Parse the hex payload @@ -130,13 +130,13 @@ static void printFailureMessage(int fd) { log_errno("Failure reading failure message from adb"); return; } else if (res != payloadLen) { - ALOGE("Incorrect payload length %d - expected %d", res, payloadLen); + LOGE("Incorrect payload length %d - expected %d", res, payloadLen); return; } msg[res] = 0; // Tell somebody about it - ALOGE("Received failure from adb: %s", msg); + LOGE("Received failure from adb: %s", msg); // Cleanup delete[] msg; @@ -145,7 +145,7 @@ static void printFailureMessage(int fd) { #define ADB_RESPONSE_LENGTH 4 bool AdbConnection::checkOkayResponse() const { - ALOG_ASSERT(m_fd != -1, "Connection has been closed!"); + LOG_ASSERT(m_fd != -1, "Connection has been closed!"); char buf[ADB_RESPONSE_LENGTH]; int res = recv(m_fd, buf, sizeof(buf), 0); @@ -156,14 +156,14 @@ bool AdbConnection::checkOkayResponse() const { // Check for a response other than OKAY/FAIL if ((res == ADB_RESPONSE_LENGTH) && (strncmp(buf, "OKAY", res) == 0)) { - ALOGV("Command OKAY"); + 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; } - ALOGE("Incorrect response from adb - '%.*s'", res, buf); + LOGE("Incorrect response from adb - '%.*s'", res, buf); return false; } @@ -178,13 +178,13 @@ const DeviceList& AdbConnection::getDeviceList() { clearDevices(); if (m_fd == -1) { - ALOGE("Connection is closed"); + LOGE("Connection is closed"); return m_devices; } // Try to send the device list request if (!sendRequest("host:devices")) { - ALOGE("Failed to get device list from adb"); + LOGE("Failed to get device list from adb"); return m_devices; } @@ -210,7 +210,7 @@ const DeviceList& AdbConnection::getDeviceList() { log_errno("Failure reading the device list"); return m_devices; } else if (res != payloadLen) { - ALOGE("Incorrect payload length %d - expected %d", res, payloadLen); + LOGE("Incorrect payload length %d - expected %d", res, payloadLen); return m_devices; } msg[res] = 0; @@ -224,7 +224,7 @@ const DeviceList& AdbConnection::getDeviceList() { static const char emulator[] = "emulator-"; if (strncmp(serial, emulator, sizeof(emulator) - 1) == 0) t = Device::EMULATOR; - ALOGV("Adding device %s (%s)", serial, state); + LOGV("Adding device %s (%s)", serial, state); m_devices.add(new Device(serial, t, this)); // Reset for the next line diff --git a/Source/WebKit/android/wds/client/ClientUtils.h b/Source/WebKit/android/wds/client/ClientUtils.h index 7c4b9ce..7d0db30 100644 --- a/Source/WebKit/android/wds/client/ClientUtils.h +++ b/Source/WebKit/android/wds/client/ClientUtils.h @@ -37,7 +37,7 @@ #endif // Callers need to include Log.h and errno.h to use this macro -#define log_errno(str) ALOGE("%s: %s", str, strerror(errno)) +#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 diff --git a/Source/WebKit/android/wds/client/main.cpp b/Source/WebKit/android/wds/client/main.cpp index 276affe..1c7d856 100644 --- a/Source/WebKit/android/wds/client/main.cpp +++ b/Source/WebKit/android/wds/client/main.cpp @@ -74,7 +74,7 @@ int main(int argc, char** argv) { Device::DeviceType type = Device::NONE; if (argc <= 1) { - ALOGE("wdsclient takes at least 1 argument"); + LOGE("wdsclient takes at least 1 argument"); return 1; } else { // Parse the options, look for -e or -d to choose a device. @@ -94,7 +94,7 @@ int main(int argc, char** argv) { } } if (optind == argc) { - ALOGE("No command specified"); + LOGE("No command specified"); return 1; } } @@ -109,10 +109,10 @@ int main(int argc, char** argv) { // No device specified and more than one connected, bail if (type == Device::NONE && devices.size() > 1) { - ALOGE("More than one device/emulator, please specify with -e or -d"); + LOGE("More than one device/emulator, please specify with -e or -d"); return 1; } else if (devices.size() == 0) { - ALOGE("No devices connected"); + LOGE("No devices connected"); return 1; } @@ -131,23 +131,23 @@ int main(int argc, char** argv) { } if (!device) { - ALOGE("No device found!"); + LOGE("No device found!"); return 1; } // Forward tcp:9999 if (!device->sendRequest("forward:tcp:" PORT_STR ";tcp:" PORT_STR)) { - ALOGE("Failed to send forwarding request"); + LOGE("Failed to send forwarding request"); return 1; } - ALOGV("Connecting to localhost port " PORT_STR); + 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) { - ALOGE("Commands must be 4 characters '%s'", command); + LOGE("Commands must be 4 characters '%s'", command); return 1; } |