diff options
39 files changed, 2376 insertions, 112 deletions
@@ -193,6 +193,8 @@ LOCAL_C_INCLUDES := $(LOCAL_C_INCLUDES) \ external/chromium/chrome \ external/skia +LOCAL_CFLAGS += -DWEBKIT_IMPLEMENTATION=1 + # Include WTF source file. d := Source/JavaScriptCore LOCAL_PATH := $(BASE_PATH)/$d @@ -345,18 +347,20 @@ LOCAL_CPPFLAGS := $(WEBKIT_CPPFLAGS) LOCAL_C_INCLUDES := $(WEBKIT_C_INCLUDES) LOCAL_PATH := $(BASE_PATH) LOCAL_SRC_FILES := \ - Source/WebKit/android/jni/WebCoreJniOnLoad.cpp + Source/WebKit/android/jni/WebCoreJniOnLoad.cpp \ + Source/WebKit/chromium/src/android/WebDOMTextContentWalker.cpp \ + Source/WebKit/chromium/src/android/WebHitTestInfo.cpp \ + Source/WebKit/chromium/src/WebRange.cpp \ + Source/WebKit/chromium/src/WebString.cpp ifeq ($(ENABLE_AUTOFILL),true) # AutoFill requires some cpp files from Chromium to link with # libchromium_net. We cannot compile them into libchromium_net # because they have cpp file extensions, not .cc. -LOCAL_CFLAGS += -DWEBKIT_IMPLEMENTATION=1 LOCAL_SRC_FILES += \ Source/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp \ Source/WebKit/chromium/src/WebCString.cpp \ - Source/WebKit/chromium/src/WebRegularExpression.cpp \ - Source/WebKit/chromium/src/WebString.cpp + Source/WebKit/chromium/src/WebRegularExpression.cpp endif # Do this dependency by hand. The reason we have to do this is because the diff --git a/Source/WebCore/Android.mk b/Source/WebCore/Android.mk index bdf6410..d6c899e 100644 --- a/Source/WebCore/Android.mk +++ b/Source/WebCore/Android.mk @@ -124,6 +124,7 @@ LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ dom/DOMImplementation.cpp \ dom/DOMStringList.cpp \ dom/DOMStringMap.cpp \ + dom/DOMTextContentWalker.cpp \ dom/DatasetDOMStringMap.cpp \ dom/DecodedDataDocumentParser.cpp \ dom/DeviceMotionController.cpp \ diff --git a/Source/WebCore/dom/DOMTextContentWalker.cpp b/Source/WebCore/dom/DOMTextContentWalker.cpp new file mode 100644 index 0000000..ccbe1ec --- /dev/null +++ b/Source/WebCore/dom/DOMTextContentWalker.cpp @@ -0,0 +1,90 @@ +/* + * 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 INC. 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 INC. 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" +#include "DOMTextContentWalker.h" + +#if OS(ANDROID) + +#include "Range.h" +#include "TextIterator.h" +#include "VisiblePosition.h" +#include "VisibleSelection.h" +#include "visible_units.h" + +namespace WebCore { + +static PassRefPtr<Range> getRange(const Position& start, const Position& end) +{ + return VisibleSelection(start.parentAnchoredEquivalent(), end.parentAnchoredEquivalent(), DOWNSTREAM).firstRange(); +} + +DOMTextContentWalker::DOMTextContentWalker(const VisiblePosition& position, unsigned maxLength) + : m_hitOffsetInContent(0) +{ + const unsigned halfMaxLength = maxLength / 2; + CharacterIterator forwardChar(makeRange(position, endOfDocument(position)).get(), TextIteratorStopsOnFormControls); + forwardChar.advance(maxLength - halfMaxLength); + + // No forward contents, started inside form control. + RefPtr<Range> range = getRange(position.deepEquivalent(), forwardChar.range()->startPosition()); + if (!range.get() || range->text().length() == 0) + return; + + BackwardsCharacterIterator backwardsChar(makeRange(startOfDocument(position), position).get(), TextIteratorStopsOnFormControls); + backwardsChar.advance(halfMaxLength); + + m_hitOffsetInContent = getRange(backwardsChar.range()->endPosition(), position.deepEquivalent())->text().length(); + m_contentRange = getRange(backwardsChar.range()->endPosition(), forwardChar.range()->startPosition()); +} + +PassRefPtr<Range> DOMTextContentWalker::contentOffsetsToRange(unsigned startInContent, unsigned endInContent) +{ + if (startInContent >= endInContent || endInContent > content().length()) + return 0; + + CharacterIterator iterator(m_contentRange.get()); + iterator.advance(startInContent); + + Position start = iterator.range()->startPosition(); + iterator.advance(endInContent - startInContent); + Position end = iterator.range()->startPosition(); + return getRange(start, end); +} + +String DOMTextContentWalker::content() const +{ + if (m_contentRange) + return m_contentRange->text(); + return String(); +} + +unsigned DOMTextContentWalker::hitOffsetInContent() const +{ + return m_hitOffsetInContent; +} + +} // namespace WebCore + +#endif // OS(ANDROID) diff --git a/Source/WebCore/dom/DOMTextContentWalker.h b/Source/WebCore/dom/DOMTextContentWalker.h new file mode 100644 index 0000000..0d4259b --- /dev/null +++ b/Source/WebCore/dom/DOMTextContentWalker.h @@ -0,0 +1,60 @@ +/* + * 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 INC. 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 INC. 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. + */ + +#ifndef DOMTextContentWalker_h +#define DOMTextContentWalker_h + +#if OS(ANDROID) + +#include "PlatformString.h" + +namespace WebCore { + +class Range; +class VisiblePosition; + +// Explore the DOM tree to find the text contents up to a limit +// around a position in a given text node. +class DOMTextContentWalker { + WTF_MAKE_NONCOPYABLE(DOMTextContentWalker); +public: + DOMTextContentWalker(const VisiblePosition& position, unsigned maxLength); + + String content() const; + unsigned hitOffsetInContent() const; + + // Convert start/end positions in the content text string into a text range. + PassRefPtr<Range> contentOffsetsToRange(unsigned startInContent, unsigned endInContent); + +private: + RefPtr<Range> m_contentRange; + size_t m_hitOffsetInContent; +}; + +} // namespace WebCore + +#endif // OS(ANDROID) + +#endif // DOMTextContentWalker_h + diff --git a/Source/WebCore/editing/TextIterator.cpp b/Source/WebCore/editing/TextIterator.cpp index c3be277..3aa68af 100644 --- a/Source/WebCore/editing/TextIterator.cpp +++ b/Source/WebCore/editing/TextIterator.cpp @@ -239,6 +239,20 @@ static void setUpFullyClippedStack(BitStack& stack, Node* node) ASSERT(stack.size() == 1 + depthCrossingShadowBoundaries(node)); } +#if OS(ANDROID) +static bool checkFormControlElement(Node* startNode) +{ + Node* node = startNode; + while (node) { + if (node->isElementNode() && static_cast<Element*>(node)->isFormControlElement()) + return true; + node = node->parentNode(); + } + return false; +} +#endif + + // -------- TextIterator::TextIterator() @@ -258,6 +272,10 @@ TextIterator::TextIterator() , m_handledFirstLetter(false) , m_ignoresStyleVisibility(false) , m_emitsObjectReplacementCharacters(false) +#if OS(ANDROID) + , m_stopsOnFormControls(false) + , m_shouldStop(false) +#endif { } @@ -277,6 +295,10 @@ TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior) , m_handledFirstLetter(false) , m_ignoresStyleVisibility(behavior & TextIteratorIgnoresStyleVisibility) , m_emitsObjectReplacementCharacters(behavior & TextIteratorEmitsObjectReplacementCharacters) +#if OS(ANDROID) + , m_stopsOnFormControls(behavior & TextIteratorStopsOnFormControls) + , m_shouldStop(false) +#endif { if (!r) return; @@ -334,8 +356,21 @@ TextIterator::~TextIterator() { } +bool TextIterator::atEnd() const +{ +#if OS(ANDROID) + return !m_positionNode || m_shouldStop; +#else + return !m_positionNode; +#endif +} + void TextIterator::advance() { +#if OS(ANDROID) + if (m_shouldStop) + return; +#endif // reset the run information m_positionNode = 0; m_textLength = 0; @@ -368,6 +403,10 @@ void TextIterator::advance() } while (m_node && m_node != m_pastEndNode) { +#if OS(ANDROID) + if (!m_shouldStop && m_stopsOnFormControls && checkFormControlElement(m_node)) + m_shouldStop = true; +#endif // if the range ends at offset 0 of an element, represent the // position, but not the content, of that element e.g. if the // node is a blockflow element, emit a newline that @@ -1034,6 +1073,10 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator() : m_behavior(TextIteratorDefaultBehavior) , m_node(0) , m_positionNode(0) +#if OS(ANDROID) + , m_stopsOnFormControls(false) + , m_shouldStop(false) +#endif { } @@ -1041,8 +1084,16 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r, : m_behavior(behavior) , m_node(0) , m_positionNode(0) +#if OS(ANDROID) + , m_stopsOnFormControls(behavior & TextIteratorStopsOnFormControls) + , m_shouldStop(false) +#endif { +#if OS(ANDROID) + ASSERT(m_behavior == TextIteratorDefaultBehavior || m_behavior == TextIteratorStopsOnFormControls); +#else ASSERT(m_behavior == TextIteratorDefaultBehavior); +#endif if (!r) return; @@ -1091,10 +1142,30 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r, advance(); } +bool SimplifiedBackwardsTextIterator::atEnd() const +{ +#if OS(ANDROID) + return !m_positionNode || m_shouldStop; +#else + return !m_positionNode; +#endif +} + void SimplifiedBackwardsTextIterator::advance() { ASSERT(m_positionNode); +#if OS(ANDROID) + if (m_shouldStop) + return; + + // Prevent changing the iterator position if a form control element was found and advance should stop on it. + if (m_stopsOnFormControls && checkFormControlElement(m_node)) { + m_shouldStop = true; + return; + } +#endif + m_positionNode = 0; m_textLength = 0; diff --git a/Source/WebCore/editing/TextIterator.h b/Source/WebCore/editing/TextIterator.h index 9fe4ceb..c4fc264 100644 --- a/Source/WebCore/editing/TextIterator.h +++ b/Source/WebCore/editing/TextIterator.h @@ -42,7 +42,10 @@ enum TextIteratorBehavior { TextIteratorEntersTextControls = 1 << 1, TextIteratorEmitsTextsWithoutTranscoding = 1 << 2, TextIteratorIgnoresStyleVisibility = 1 << 3, - TextIteratorEmitsObjectReplacementCharacters = 1 << 4 + TextIteratorEmitsObjectReplacementCharacters = 1 << 4, +#if OS(ANDROID) + TextIteratorStopsOnFormControls = 1 << 6 +#endif }; // FIXME: Can't really answer this question correctly without knowing the white-space mode. @@ -88,7 +91,7 @@ public: ~TextIterator(); explicit TextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); - bool atEnd() const { return !m_positionNode; } + bool atEnd() const; void advance(); int length() const { return m_textLength; } @@ -182,6 +185,12 @@ private: bool m_ignoresStyleVisibility; // Used when emitting the special 0xFFFC character is required. bool m_emitsObjectReplacementCharacters; +#if OS(ANDROID) + // Used when the iteration should stop if form controls are reached. + bool m_stopsOnFormControls; + // Used when m_stopsOnFormControls is set to determine if the iterator should keep advancing. + bool m_shouldStop; +#endif }; // Iterates through the DOM range, returning all the text, and 0-length boundaries @@ -192,7 +201,7 @@ public: SimplifiedBackwardsTextIterator(); explicit SimplifiedBackwardsTextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); - bool atEnd() const { return !m_positionNode; } + bool atEnd() const; void advance(); int length() const { return m_textLength; } @@ -240,6 +249,13 @@ private: // Whether m_node has advanced beyond the iteration range (i.e. m_startNode). bool m_havePassedStartNode; + +#if OS(ANDROID) + // Used when the iteration should stop if form controls are reached. + bool m_stopsOnFormControls; + // Used when m_stopsOnFormControls is set to determine if the iterator should keep advancing. + bool m_shouldStop; +#endif }; // Builds on the text iterator, adding a character position so we can walk one diff --git a/Source/WebCore/platform/graphics/GraphicsContext.cpp b/Source/WebCore/platform/graphics/GraphicsContext.cpp index 65cc6df..e032714 100644 --- a/Source/WebCore/platform/graphics/GraphicsContext.cpp +++ b/Source/WebCore/platform/graphics/GraphicsContext.cpp @@ -432,6 +432,7 @@ void GraphicsContext::drawBidiText(const Font& font, const TextRun& run, const F bidiRuns.deleteRuns(); } +#if !PLATFORM(ANDROID) void GraphicsContext::drawHighlightForText(const Font& font, const TextRun& run, const FloatPoint& point, int h, const Color& backgroundColor, ColorSpace colorSpace, int from, int to) { if (paintingDisabled()) @@ -439,6 +440,7 @@ void GraphicsContext::drawHighlightForText(const Font& font, const TextRun& run, fillRect(font.selectionRectForText(run, point, h, from, to), backgroundColor, colorSpace); } +#endif void GraphicsContext::drawImage(Image* image, ColorSpace styleColorSpace, const FloatRect& dest, const FloatRect& src, CompositeOperator op, bool useLowQualityScale) { diff --git a/Source/WebCore/platform/graphics/GraphicsContext.h b/Source/WebCore/platform/graphics/GraphicsContext.h index 2b41c2e..ed43cf0 100644 --- a/Source/WebCore/platform/graphics/GraphicsContext.h +++ b/Source/WebCore/platform/graphics/GraphicsContext.h @@ -382,7 +382,11 @@ namespace WebCore { void drawText(const Font&, const TextRun&, const FloatPoint&, int from = 0, int to = -1); void drawEmphasisMarks(const Font&, const TextRun& , const AtomicString& mark, const FloatPoint&, int from = 0, int to = -1); void drawBidiText(const Font&, const TextRun&, const FloatPoint&); +#if PLATFORM(ANDROID) + void drawHighlightForText(const Font&, const TextRun&, const FloatPoint&, int h, const Color& backgroundColor, ColorSpace, int from = 0, int to = -1, bool isActive = true); +#else void drawHighlightForText(const Font&, const TextRun&, const FloatPoint&, int h, const Color& backgroundColor, ColorSpace, int from = 0, int to = -1); +#endif enum RoundingMode { RoundAllSides, diff --git a/Source/WebCore/platform/graphics/android/BaseLayerAndroid.cpp b/Source/WebCore/platform/graphics/android/BaseLayerAndroid.cpp index 7208380..524f986 100644 --- a/Source/WebCore/platform/graphics/android/BaseLayerAndroid.cpp +++ b/Source/WebCore/platform/graphics/android/BaseLayerAndroid.cpp @@ -247,7 +247,7 @@ bool BaseLayerAndroid::prepareBasePictureInGL(SkRect& viewport, float scale, nextTiledPage->prepare(goingDown, goingLeft, viewportTileBounds, TiledPage::VisibleBounds); // Cancel pending paints for the foreground page - TilesManager::instance()->removePaintOperationsForPage(tiledPage); + TilesManager::instance()->removePaintOperationsForPage(tiledPage, false); } // If we fired a request, let's check if it's ready to use diff --git a/Source/WebCore/platform/graphics/android/GraphicsContextAndroid.cpp b/Source/WebCore/platform/graphics/android/GraphicsContextAndroid.cpp index 9cfed60..0aa1ae6 100644 --- a/Source/WebCore/platform/graphics/android/GraphicsContextAndroid.cpp +++ b/Source/WebCore/platform/graphics/android/GraphicsContextAndroid.cpp @@ -26,6 +26,7 @@ #include "GraphicsContext.h" #include "AffineTransform.h" +#include "Font.h" #include "Gradient.h" #include "NotImplemented.h" #include "Path.h" @@ -1265,6 +1266,25 @@ void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint*, boo // FIXME: IMPLEMENT! } +void GraphicsContext::drawHighlightForText(const Font& font, const TextRun& run, const FloatPoint& point, int h, const Color& backgroundColor, ColorSpace colorSpace, int from, int to, bool isActive) +{ + if (paintingDisabled()) + return; + + IntRect rect = (IntRect)font.selectionRectForText(run, point, h, from, to); + if (isActive) + fillRect(rect, backgroundColor, colorSpace); + else { + int x = rect.x(), y = rect.y(), w = rect.width(), h = rect.height(); + const int t = 3, t2 = t * 2; + + fillRect(IntRect(x, y, w, t), backgroundColor, colorSpace); + fillRect(IntRect(x, y+h-t, w, t), backgroundColor, colorSpace); + fillRect(IntRect(x, y+t, t, h-t2), backgroundColor, colorSpace); + fillRect(IntRect(x+w-t, y+t, t, h-t2), backgroundColor, colorSpace); + } +} + } // namespace WebCore /////////////////////////////////////////////////////////////////////////////// diff --git a/Source/WebCore/platform/graphics/android/TextureOwner.h b/Source/WebCore/platform/graphics/android/TextureOwner.h index 25d234b..5434dbf 100644 --- a/Source/WebCore/platform/graphics/android/TextureOwner.h +++ b/Source/WebCore/platform/graphics/android/TextureOwner.h @@ -26,8 +26,6 @@ #ifndef TextureOwner_h #define TextureOwner_h -#include "SkRefCnt.h" - class SkCanvas; class Layer; @@ -37,7 +35,7 @@ class TiledPage; class BaseTileTexture; class GLWebViewState; -class TextureOwner : public SkRefCnt { +class TextureOwner { public: virtual ~TextureOwner() { } virtual bool removeTexture(BaseTileTexture* texture) = 0; diff --git a/Source/WebCore/platform/graphics/android/TexturesGenerator.cpp b/Source/WebCore/platform/graphics/android/TexturesGenerator.cpp index d27631b..bccb99b 100644 --- a/Source/WebCore/platform/graphics/android/TexturesGenerator.cpp +++ b/Source/WebCore/platform/graphics/android/TexturesGenerator.cpp @@ -65,13 +65,18 @@ void TexturesGenerator::removeOperationsForPage(TiledPage* page) removeOperationsForFilter(new PageFilter(page)); } -void TexturesGenerator::removePaintOperationsForPage(TiledPage* page) +void TexturesGenerator::removePaintOperationsForPage(TiledPage* page, bool waitForRunning) { - removeOperationsForFilter(new PagePaintFilter(page)); + removeOperationsForFilter(new PagePaintFilter(page), waitForRunning); } void TexturesGenerator::removeOperationsForFilter(OperationFilter* filter) { + removeOperationsForFilter(filter, true); +} + +void TexturesGenerator::removeOperationsForFilter(OperationFilter* filter, bool waitForRunning) +{ if (!filter) return; @@ -85,7 +90,34 @@ void TexturesGenerator::removeOperationsForFilter(OperationFilter* filter) i++; } } - delete filter; + + if (waitForRunning && m_currentOperation) { + QueuedOperation* operation = m_currentOperation; + + if (operation && filter->check(operation)) { + m_waitForCompletion = true; + // The reason we are signaling the transferQueue is : + // TransferQueue may be waiting a slot to work on, but now UI + // thread is waiting for Tex Gen thread to finish first before the + // UI thread can free a slot for the transferQueue. + // Therefore, it could be a deadlock. + // The solution is use this as a flag to tell Tex Gen thread that + // UI thread is waiting now, Tex Gen thread should not wait for the + // queue any more. + TilesManager::instance()->transferQueue()->interruptTransferQueue(true); + } + + delete filter; + + // At this point, it means that we are currently executing an operation that + // we want to be removed -- we should wait until it is done, so that + // when we return our caller can be sure that there is no more operations + // in the queue matching the given filter. + while (m_waitForCompletion) + mRequestedOperationsCond.wait(mRequestedOperationsLock); + } else { + delete filter; + } } status_t TexturesGenerator::readyToRun() @@ -160,6 +192,7 @@ bool TexturesGenerator::threadLoop() stop = true; if (m_waitForCompletion) { m_waitForCompletion = false; + TilesManager::instance()->transferQueue()->interruptTransferQueue(false); mRequestedOperationsCond.signal(); } mRequestedOperationsLock.unlock(); diff --git a/Source/WebCore/platform/graphics/android/TexturesGenerator.h b/Source/WebCore/platform/graphics/android/TexturesGenerator.h index c7924cc..2e3b6b4 100644 --- a/Source/WebCore/platform/graphics/android/TexturesGenerator.h +++ b/Source/WebCore/platform/graphics/android/TexturesGenerator.h @@ -42,18 +42,16 @@ class LayerAndroid; class TexturesGenerator : public Thread { public: - TexturesGenerator() - : Thread(false) + TexturesGenerator() : Thread(false) , m_waitForCompletion(false) - , m_currentOperation(0) - {} - + , m_currentOperation(0) { } virtual ~TexturesGenerator() { } virtual status_t readyToRun(); void removeOperationsForPage(TiledPage* page); - void removePaintOperationsForPage(TiledPage* page); + void removePaintOperationsForPage(TiledPage* page, bool waitForRunning); void removeOperationsForFilter(OperationFilter* filter); + void removeOperationsForFilter(OperationFilter* filter, bool waitForRunning); void scheduleOperation(QueuedOperation* operation); diff --git a/Source/WebCore/platform/graphics/android/TiledPage.cpp b/Source/WebCore/platform/graphics/android/TiledPage.cpp index 80d2c3f..8c43fc4 100644 --- a/Source/WebCore/platform/graphics/android/TiledPage.cpp +++ b/Source/WebCore/platform/graphics/android/TiledPage.cpp @@ -59,7 +59,8 @@ namespace WebCore { using namespace android; TiledPage::TiledPage(int id, GLWebViewState* state) - : m_baseTileSize(0) + : m_baseTiles(0) + , m_baseTileSize(0) , m_id(id) , m_scale(1) , m_invScale(1) @@ -69,11 +70,7 @@ TiledPage::TiledPage(int id, GLWebViewState* state) , m_isPrefetchPage(false) , m_willDraw(false) { - int maxTiles = TilesManager::getMaxTextureAllocation() + 1; - m_baseTiles = new BaseTile*[maxTiles]; - for (int i = 0; i < maxTiles; i++) - m_baseTiles[i] = new BaseTile(); - + m_baseTiles = new BaseTile[TilesManager::getMaxTextureAllocation() + 1]; #ifdef DEBUG_COUNT ClassTracker::instance()->increment("TiledPage"); #endif @@ -97,14 +94,13 @@ void TiledPage::updateBaseTileSize() TiledPage::~TiledPage() { TilesManager* tilesManager = TilesManager::instance(); - // remove as many painting operations for this page as possible, to avoid - // unnecessary work. + // In order to delete the page we must ensure that none of its BaseTiles are + // currently painting or scheduled to be painted by the TextureGenerator tilesManager->removeOperationsForPage(this); - - int maxTiles = TilesManager::getMaxTextureAllocation() + 1; - for (int i = 0; i < maxTiles; i++) - SkSafeUnref(m_baseTiles[i]); - delete m_baseTiles; + // Discard the transfer queue after the removal operation to make sure + // no tiles for this page will be left in the transfer queue. + tilesManager->transferQueue()->setPendingDiscardWithLock(); + delete[] m_baseTiles; #ifdef DEBUG_COUNT ClassTracker::instance()->decrement("TiledPage"); #endif @@ -114,9 +110,9 @@ BaseTile* TiledPage::getBaseTile(int x, int y) const { // TODO: replace loop over array with HashMap indexing for (int j = 0; j < m_baseTileSize; j++) { - BaseTile* tile = m_baseTiles[j]; - if (tile->x() == x && tile->y() == y) - return tile; + BaseTile& tile = m_baseTiles[j]; + if (tile.x() == x && tile.y() == y) + return &tile; } return 0; } @@ -124,8 +120,8 @@ BaseTile* TiledPage::getBaseTile(int x, int y) const void TiledPage::discardTextures() { for (int j = 0; j < m_baseTileSize; j++) { - BaseTile* tile = m_baseTiles[j]; - tile->discardTextures(); + BaseTile& tile = m_baseTiles[j]; + tile.discardTextures(); } return; } @@ -165,14 +161,14 @@ void TiledPage::prepareRow(bool goingLeft, int tilesInRow, int firstTileX, int y BaseTile* currentTile = 0; BaseTile* availableTile = 0; for (int j = 0; j < m_baseTileSize; j++) { - BaseTile* tile = m_baseTiles[j]; - if (tile->x() == x && tile->y() == y) { - currentTile = tile; + BaseTile& tile = m_baseTiles[j]; + if (tile.x() == x && tile.y() == y) { + currentTile = &tile; break; } - if (!availableTile || (tile->drawCount() < availableTile->drawCount())) - availableTile = tile; + if (!availableTile || (tile.drawCount() < availableTile->drawCount())) + availableTile = &tile; } if (!currentTile && availableTile) { @@ -220,12 +216,12 @@ bool TiledPage::updateTileDirtiness(const SkIRect& tileBounds) bool visibleTileIsDirty = false; for (int x = 0; x < m_baseTileSize; x++) { - BaseTile* tile = m_baseTiles[x]; + BaseTile& tile = m_baseTiles[x]; // if the tile is in the dirty region then we must invalidate it - if (m_invalRegion.contains(tile->x(), tile->y())) { - tile->markAsDirty(m_latestPictureInval, m_invalTilesRegion); - if (tileBounds.contains(tile->x(), tile->y())) + if (m_invalRegion.contains(tile.x(), tile.y())) { + tile.markAsDirty(m_latestPictureInval, m_invalTilesRegion); + if (tileBounds.contains(tile.x(), tile.y())) visibleTileIsDirty = true; } } @@ -305,9 +301,9 @@ bool TiledPage::hasMissingContent(const SkIRect& tileBounds) { int neededTiles = tileBounds.width() * tileBounds.height(); for (int j = 0; j < m_baseTileSize; j++) { - BaseTile* tile = m_baseTiles[j]; - if (tileBounds.contains(tile->x(), tile->y())) { - if (tile->frontTexture()) + BaseTile& tile = m_baseTiles[j]; + if (tileBounds.contains(tile.x(), tile.y())) { + if (tile.frontTexture()) neededTiles--; } } @@ -319,9 +315,9 @@ bool TiledPage::isReady(const SkIRect& tileBounds) int neededTiles = tileBounds.width() * tileBounds.height(); XLOG("tiled page %p needs %d ready tiles", this, neededTiles); for (int j = 0; j < m_baseTileSize; j++) { - BaseTile* tile = m_baseTiles[j]; - if (tileBounds.contains(tile->x(), tile->y())) { - if (tile->isTileReady()) + BaseTile& tile = m_baseTiles[j]; + if (tileBounds.contains(tile.x(), tile.y())) { + if (tile.isTileReady()) neededTiles--; } } @@ -352,8 +348,8 @@ bool TiledPage::swapBuffersIfReady(const SkIRect& tileBounds, float scale) // swap every tile on page (even if off screen) for (int j = 0; j < m_baseTileSize; j++) { - BaseTile* tile = m_baseTiles[j]; - if (tile->swapTexturesIfNeeded()) + BaseTile& tile = m_baseTiles[j]; + if (tile.swapTexturesIfNeeded()) swaps++; } @@ -377,16 +373,16 @@ void TiledPage::drawGL() const float tileHeight = TilesManager::tileHeight() * m_invScale; for (int j = 0; j < m_baseTileSize; j++) { - BaseTile* tile = m_baseTiles[j]; - bool tileInView = m_tileBounds.contains(tile->x(), tile->y()); + BaseTile& tile = m_baseTiles[j]; + bool tileInView = m_tileBounds.contains(tile.x(), tile.y()); if (tileInView) { SkRect rect; - rect.fLeft = tile->x() * tileWidth; - rect.fTop = tile->y() * tileHeight; + rect.fLeft = tile.x() * tileWidth; + rect.fTop = tile.y() * tileHeight; rect.fRight = rect.fLeft + tileWidth; rect.fBottom = rect.fTop + tileHeight; - tile->draw(m_transparency, rect, m_scale); + tile.draw(m_transparency, rect, m_scale); } TilesManager::instance()->getProfiler()->nextTile(tile, m_invScale, tileInView); diff --git a/Source/WebCore/platform/graphics/android/TiledPage.h b/Source/WebCore/platform/graphics/android/TiledPage.h index d858cc2..791e1f6 100644 --- a/Source/WebCore/platform/graphics/android/TiledPage.h +++ b/Source/WebCore/platform/graphics/android/TiledPage.h @@ -105,10 +105,9 @@ private: BaseTile* getBaseTile(int x, int y) const; - // array of tile ptrs used to compose a page. The tiles are allocated in the + // array of tiles used to compose a page. The tiles are allocated in the // constructor to prevent them from potentially being allocated on the stack - BaseTile** m_baseTiles; - + BaseTile* m_baseTiles; // stores the number of tiles in the m_baseTiles array. This enables us to // quickly iterate over the array without have to check it's size int m_baseTileSize; diff --git a/Source/WebCore/platform/graphics/android/TiledTexture.cpp b/Source/WebCore/platform/graphics/android/TiledTexture.cpp index 4b872a3..1e8b946 100644 --- a/Source/WebCore/platform/graphics/android/TiledTexture.cpp +++ b/Source/WebCore/platform/graphics/android/TiledTexture.cpp @@ -317,8 +317,9 @@ const TransformationMatrix* TiledTexture::transform() void TiledTexture::removeTiles() { - for (unsigned int i = 0; i < m_tiles.size(); i++) - SkSafeUnref(m_tiles[i]); + for (unsigned int i = 0; i < m_tiles.size(); i++) { + delete m_tiles[i]; + } m_tiles.clear(); } diff --git a/Source/WebCore/platform/graphics/android/TilesManager.h b/Source/WebCore/platform/graphics/android/TilesManager.h index d3852d9..b670055 100644 --- a/Source/WebCore/platform/graphics/android/TilesManager.h +++ b/Source/WebCore/platform/graphics/android/TilesManager.h @@ -58,9 +58,9 @@ public: return gInstance != 0; } - void removeOperationsForFilter(OperationFilter* filter) + void removeOperationsForFilter(OperationFilter* filter, bool waitForRunning = false) { - m_pixmapsGenerationThread->removeOperationsForFilter(filter); + m_pixmapsGenerationThread->removeOperationsForFilter(filter, waitForRunning); } void removeOperationsForPage(TiledPage* page) @@ -68,9 +68,9 @@ public: m_pixmapsGenerationThread->removeOperationsForPage(page); } - void removePaintOperationsForPage(TiledPage* page) + void removePaintOperationsForPage(TiledPage* page, bool waitForCompletion) { - m_pixmapsGenerationThread->removePaintOperationsForPage(page); + m_pixmapsGenerationThread->removePaintOperationsForPage(page, waitForCompletion); } void scheduleOperation(QueuedOperation* operation) diff --git a/Source/WebCore/platform/graphics/android/TilesProfiler.cpp b/Source/WebCore/platform/graphics/android/TilesProfiler.cpp index d422904..0271ee3 100644 --- a/Source/WebCore/platform/graphics/android/TilesProfiler.cpp +++ b/Source/WebCore/platform/graphics/android/TilesProfiler.cpp @@ -103,14 +103,14 @@ void TilesProfiler::nextFrame(int left, int top, int right, int bottom, float sc scale, true, (int)(timeDelta * 1000))); } -void TilesProfiler::nextTile(BaseTile* tile, float scale, bool inView) +void TilesProfiler::nextTile(BaseTile& tile, float scale, bool inView) { if (!m_enabled || (m_records.size() > MAX_PROF_FRAMES) || (m_records.size() == 0)) return; - bool isReady = tile->isTileReady(); - int left = tile->x() * TilesManager::tileWidth(); - int top = tile->y() * TilesManager::tileWidth(); + bool isReady = tile.isTileReady(); + int left = tile.x() * TilesManager::tileWidth(); + int top = tile.y() * TilesManager::tileWidth(); int right = left + TilesManager::tileWidth(); int bottom = top + TilesManager::tileWidth(); @@ -122,7 +122,7 @@ void TilesProfiler::nextTile(BaseTile* tile, float scale, bool inView) } m_records.last().append(TileProfileRecord( left, top, right, bottom, - scale, isReady, (int)tile->drawCount())); + scale, isReady, (int)tile.drawCount())); XLOG("adding tile %d %d %d %d, scale %f", left, top, right, bottom, scale); } diff --git a/Source/WebCore/platform/graphics/android/TilesProfiler.h b/Source/WebCore/platform/graphics/android/TilesProfiler.h index 7a3fe59..286d350 100644 --- a/Source/WebCore/platform/graphics/android/TilesProfiler.h +++ b/Source/WebCore/platform/graphics/android/TilesProfiler.h @@ -58,7 +58,7 @@ public: float stop(); void clear(); void nextFrame(int left, int top, int right, int bottom, float scale); - void nextTile(BaseTile* tile, float scale, bool inView); + void nextTile(BaseTile& tile, float scale, bool inView); void nextInval(const IntRect& rect, float scale); int numFrames() { return m_records.size(); diff --git a/Source/WebCore/platform/graphics/android/TransferQueue.cpp b/Source/WebCore/platform/graphics/android/TransferQueue.cpp index 3c2ed1b..2d5be64 100644 --- a/Source/WebCore/platform/graphics/android/TransferQueue.cpp +++ b/Source/WebCore/platform/graphics/android/TransferQueue.cpp @@ -67,6 +67,7 @@ TransferQueue::TransferQueue(bool useMinimalMem) , m_fboID(0) , m_sharedSurfaceTextureId(0) , m_hasGLContext(true) + , m_interruptedByRemovingOp(false) , m_currentDisplay(EGL_NO_DISPLAY) , m_currentUploadType(DEFAULT_UPLOAD_TYPE) { @@ -245,8 +246,17 @@ void TransferQueue::blitTileFromQueue(GLuint fboID, BaseTileTexture* destTex, #endif } +void TransferQueue::interruptTransferQueue(bool interrupt) +{ + m_transferQueueItemLocks.lock(); + m_interruptedByRemovingOp = interrupt; + if (m_interruptedByRemovingOp) + m_transferQueueItemCond.signal(); + m_transferQueueItemLocks.unlock(); +} + // This function must be called inside the m_transferQueueItemLocks, for the -// wait and getHasGLContext(). +// wait, m_interruptedByRemovingOp and getHasGLContext(). // Only called by updateQueueWithBitmap() for now. bool TransferQueue::readyForUpdate() { @@ -254,8 +264,13 @@ bool TransferQueue::readyForUpdate() return false; // Don't use a while loop since when the WebView tear down, the emptyCount // will still be 0, and we bailed out b/c of GL context lost. - if (!m_emptyItemCount) + if (!m_emptyItemCount) { + if (m_interruptedByRemovingOp) + return false; m_transferQueueItemCond.wait(m_transferQueueItemLocks); + if (m_interruptedByRemovingOp) + return false; + } if (!getHasGLContext()) return false; @@ -315,8 +330,6 @@ void TransferQueue::setPendingDiscard() if (m_transferQueue[i].status == pendingBlit) m_transferQueue[i].status = pendingDiscard; - for (unsigned int i = 0 ; i < m_pureColorTileQueue.size(); i++) - SkSafeUnref(m_pureColorTileQueue[i].savedBaseTilePtr); m_pureColorTileQueue.clear(); bool GLContextExisted = getHasGLContext(); @@ -345,7 +358,6 @@ void TransferQueue::updatePureColorTiles() // The queue should be clear instead of setting to different status. XLOG("Warning: Don't expect an emptyItem here."); } - SkSafeUnref(data->savedBaseTilePtr); } m_pureColorTileQueue.clear(); } @@ -386,11 +398,6 @@ void TransferQueue::updateDirtyBaseTiles() if (result != OK) XLOGC("unexpected error: updateTexImage return %d", result); } - - XLOG("removing tile %p from %p, update", - m_transferQueue[index].savedBaseTilePtr, - &m_transferQueue[index]); - SkSafeUnref(m_transferQueue[index].savedBaseTilePtr); m_transferQueue[index].savedBaseTilePtr = 0; m_transferQueue[index].status = emptyItem; if (obsoleteBaseTile) { @@ -511,7 +518,7 @@ bool TransferQueue::tryUpdateQueueWithBitmap(const TileRenderInfo* renderInfo, ANativeWindow_unlockAndPost(m_ANW.get()); } - // b) After update the Surface Texture, now update the transfer queue info. + // b) After update the Surface Texture, now udpate the transfer queue info. addItemInTransferQueue(renderInfo, currentUploadType, &bitmap); XLOG("Bitmap updated x, y %d %d, baseTile %p", @@ -536,19 +543,8 @@ void TransferQueue::addItemCommon(const TileRenderInfo* renderInfo, TextureUploadType type, TileTransferData* data) { - BaseTile* old = data->savedBaseTilePtr; - SkSafeRef(renderInfo->baseTile); - SkSafeUnref(data->savedBaseTilePtr); - data->savedBaseTileTexturePtr = renderInfo->baseTile->backTexture(); data->savedBaseTilePtr = renderInfo->baseTile; - XLOG("adding tile %p, %d by %d, refs %d, removed %p, dataPtr %p", - data->savedBaseTilePtr, - data->savedBaseTilePtr->x(), - data->savedBaseTilePtr->y(), - data->savedBaseTilePtr->getRefCnt(), - old, - data); data->status = pendingBlit; data->uploadType = type; @@ -642,10 +638,6 @@ void TransferQueue::cleanupPendingDiscard() XLOG("transfer queue discarded tile %p, removed texture", tile); } - XLOG("removing tile %p from %p, cleanup", - m_transferQueue[index].savedBaseTilePtr, - &m_transferQueue[index]); - SkSafeUnref(m_transferQueue[index].savedBaseTilePtr); m_transferQueue[index].savedBaseTilePtr = 0; m_transferQueue[index].savedBaseTileTexturePtr = 0; m_transferQueue[index].status = emptyItem; diff --git a/Source/WebCore/platform/graphics/android/TransferQueue.h b/Source/WebCore/platform/graphics/android/TransferQueue.h index 30dd0c6..5dd2e0a 100644 --- a/Source/WebCore/platform/graphics/android/TransferQueue.h +++ b/Source/WebCore/platform/graphics/android/TransferQueue.h @@ -129,6 +129,8 @@ public: // The lock will be done when returning true. bool readyForUpdate(); + void interruptTransferQueue(bool); + void lockQueue() { m_transferQueueItemLocks.lock(); } void unlockQueue() { m_transferQueueItemLocks.unlock(); } @@ -194,6 +196,8 @@ private: int m_emptyItemCount; + bool m_interruptedByRemovingOp; + // We are using wait/signal to handle our own queue sync. // First of all, if we don't have our own lock, then while WebView is // destroyed, the UI thread will wait for the Tex Gen to get out from diff --git a/Source/WebCore/rendering/InlineTextBox.cpp b/Source/WebCore/rendering/InlineTextBox.cpp index e8a9c7f..d5eeeae 100644 --- a/Source/WebCore/rendering/InlineTextBox.cpp +++ b/Source/WebCore/rendering/InlineTextBox.cpp @@ -1070,8 +1070,12 @@ void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& renderer()->theme()->platformInactiveTextSearchHighlightColor(); pt->save(); updateGraphicsContext(pt, color, color, 0, style->colorSpace()); // Don't draw text at all! +#if PLATFORM(ANDROID) + pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos, marker.activeMatch); +#else pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight)); pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style->colorSpace(), sPos, ePos); +#endif pt->restore(); } } diff --git a/Source/WebCore/rendering/RenderBlockLineLayout.cpp b/Source/WebCore/rendering/RenderBlockLineLayout.cpp index df20063..a2469a0 100644 --- a/Source/WebCore/rendering/RenderBlockLineLayout.cpp +++ b/Source/WebCore/rendering/RenderBlockLineLayout.cpp @@ -895,6 +895,10 @@ void RenderBlock::layoutInlineChildren(bool relayoutChildren, int& repaintLogica m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, maxWidth); m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, maxWidth); + // if overflow isn't visible, block elements may get clipped + // due to the limited content width. disable overflow clipping. + setHasOverflowClip(false); + IntRect overflow = layoutOverflowRect(); if (overflow.width() > maxWidth) { overflow.setWidth(maxWidth); diff --git a/Source/WebKit/Android.mk b/Source/WebKit/Android.mk index 8ec48da..34fd14e 100644 --- a/Source/WebKit/Android.mk +++ b/Source/WebKit/Android.mk @@ -50,6 +50,10 @@ LOCAL_SRC_FILES += \ \ android/icu/unicode/ucnv.cpp \ \ + android/content/address_detector.cpp \ + android/content/content_detector.cpp \ + android/content/PhoneEmailDetector.cpp \ + \ android/jni/AndroidHitTestResult.cpp \ android/jni/CacheManager.cpp \ android/jni/CookieManager.cpp \ diff --git a/Source/WebKit/android/content/PhoneEmailDetector.cpp b/Source/WebKit/android/content/PhoneEmailDetector.cpp new file mode 100644 index 0000000..d188c0b --- /dev/null +++ b/Source/WebKit/android/content/PhoneEmailDetector.cpp @@ -0,0 +1,369 @@ +/* + * 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 "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::FindContent(const string16::const_iterator& begin, + const string16::const_iterator& end, + size_t* start_pos, + size_t* end_pos) +{ + FindReset(&m_findState); + m_foundResult = FindPartialNumber(begin, end - begin, &m_findState); + if (m_foundResult == FOUND_COMPLETE) + m_prefix = kTelSchemaPrefix; + else { + FindReset(&m_findState); + 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 new file mode 100644 index 0000000..b61de62 --- /dev/null +++ b/Source/WebKit/android/content/PhoneEmailDetector.h @@ -0,0 +1,75 @@ +/* + * 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; + } + + DISALLOW_COPY_AND_ASSIGN(PhoneEmailDetector); + + FindState m_findState; + FoundState m_foundResult; + const char* m_prefix; +}; diff --git a/Source/WebKit/android/content/address_detector.cpp b/Source/WebKit/android/content/address_detector.cpp new file mode 100644 index 0000000..d5ab6e8 --- /dev/null +++ b/Source/WebKit/android/content/address_detector.cpp @@ -0,0 +1,839 @@ +/* + * 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 "WebString.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 +}; + +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::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()) + return false; + } 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) { + // List of valid zip code ranges. + static const struct { + char low; + char high; + char exception1; + char exception2; + } zip_range[] = { + { 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. + }; + + // Zip numeric value for the first two characters. + DCHECK(word.begin != word.end); + DCHECK(IsAsciiDigit(*word.begin)); + DCHECK(IsAsciiDigit(*(word.begin + 1))); + int zip_prefix = (*word.begin - '0') * 10 + (*(word.begin + 1) - '0'); + + if ((zip_prefix >= zip_range[state_index].low && + zip_prefix <= zip_range[state_index].high) || + zip_prefix == zip_range[state_index].exception1 || + zip_prefix == zip_range[state_index].exception2) { + return true; + } + return false; +} + +bool AddressDetector::IsValidLocationName(const Word& word) { + // Supported location names sorted alphabetically and grouped by first letter. + static const struct LocationNameInfo { + const char* string; + char length; + bool allow_plural; + } location_names[157] = { + { "alley", 5, false }, { "annex", 5, false }, { "arcade", 6, false }, + { "ave", 3, false }, { "ave.", 4, false }, { "avenue", 6, false }, + { "alameda", 7, false }, + { "bayou", 5, false }, { "beach", 5, false }, { "bend", 4, false }, + { "bluff", 5, true }, { "bottom", 6, false }, { "boulevard", 9, false }, + { "branch", 6, false }, { "bridge", 6, false }, { "brook", 5, true }, + { "burg", 4, true }, { "bypass", 6, false }, { "broadway", 8, false }, + { "camino", 6, false }, { "camp", 4, false }, { "canyon", 6, false }, + { "cape", 4, false }, { "causeway", 8, false }, { "center", 6, true }, + { "circle", 6, true }, { "cliff", 5, true }, { "club", 4, false }, + { "common", 6, false }, { "corner", 6, true }, { "course", 6, false }, + { "court", 5, true }, { "cove", 4, true }, { "creek", 5, false }, + { "crescent", 8, false }, { "crest", 5, false }, { "crossing", 8, false }, + { "crossroad", 9, false }, { "curve", 5, false }, { "circulo", 7, false }, + { "dale", 4, false }, { "dam", 3, false }, { "divide", 6, false }, + { "drive", 5, true }, + { "estate", 6, true }, { "expressway", 10, false }, + { "extension", 9, true }, + { "fall", 4, true }, { "ferry", 5, false }, { "field", 5, true }, + { "flat", 4, true }, { "ford", 4, true }, { "forest", 6, false }, + { "forge", 5, true }, { "fork", 4, true }, { "fort", 4, false }, + { "freeway", 7, false }, + { "garden", 6, true }, { "gateway", 7, false }, { "glen", 4, true }, + { "green", 5, true }, { "grove", 5, true }, + { "harbor", 6, true }, { "haven", 5, false }, { "heights", 7, false }, + { "highway", 7, false }, { "hill", 4, true }, { "hollow", 6, false }, + { "inlet", 5, false }, { "island", 6, true }, { "isle", 4, false }, + { "junction", 8, true }, + { "key", 3, true }, { "knoll", 5, true }, + { "lake", 4, true }, { "land", 4, false }, { "landing", 7, false }, + { "lane", 4, false }, { "light", 5, true }, { "loaf", 4, false }, + { "lock", 4, true }, { "lodge", 5, false }, { "loop", 4, false }, + { "mall", 4, false }, { "manor", 5, true }, { "meadow", 6, true }, + { "mews", 4, false }, { "mill", 4, true }, { "mission", 7, false }, + { "motorway", 8, false }, { "mount", 5, false }, { "mountain", 8, true }, + { "neck", 4, false }, + { "orchard", 7, false }, { "oval", 4, false }, { "overpass", 8, false }, + { "park", 4, true }, { "parkway", 7, true }, { "pass", 4, false }, + { "passage", 7, false }, { "path", 4, false }, { "pike", 4, false }, + { "pine", 4, true }, { "plain", 5, true }, { "plaza", 5, false }, + { "point", 5, true }, { "port", 4, true }, { "prairie", 7, false }, + { "privada", 7, false }, + { "radial", 6, false }, { "ramp", 4, false }, { "ranch", 5, false }, + { "rapid", 5, true }, { "rest", 4, false }, { "ridge", 5, true }, + { "river", 5, false }, { "road", 4, true }, { "route", 5, false }, + { "row", 3, false }, { "rue", 3, false }, { "run", 3, false }, + { "shoal", 5, true }, { "shore", 5, true }, { "skyway", 6, false }, + { "spring", 6, true }, { "spur", 4, true }, { "square", 6, true }, + { "station", 7, false }, { "stravenue", 9, false }, { "stream", 6, false }, + { "st", 2, false }, { "st.", 3, false }, { "street", 6, true }, + { "summit", 6, false }, { "speedway", 8, false }, + { "terrace", 7, false }, { "throughway", 10, false }, { "trace", 5, false }, + { "track", 5, false }, { "trafficway", 10, false }, { "trail", 5, false }, + { "tunnel", 6, false }, { "turnpike", 8, false }, + { "underpass", 9, false }, { "union", 5, true }, + { "valley", 6, true }, { "viaduct", 7, false }, { "view", 4, true }, + { "village", 7, true }, { "ville", 5, false }, { "vista", 5, false }, + { "walk", 4, true }, { "wall", 4, false }, { "way", 3, true }, + { "well", 4, true }, + { "xing", 4, false }, { "xrd", 3, false } + }; + + // Accumulative number of location names for each starting letter. + static const int location_names_accumulative[25] = { + 0, 7, 19, 40, 44, + 47, 57, 62, 68, 71, + 72, 74, 83, 92, 93, + 96, 109, 109, 121, 135, + 143, 145, 151, 155, 157 + }; + + DCHECK_EQ( + location_names_accumulative[arraysize(location_names_accumulative) - 1], + static_cast<int>(ARRAYSIZE_UNSAFE(location_names))); + + if (!IsAsciiAlpha(*word.begin)) + return false; + + // No location names start with y, z. + char16 first_letter = base::ToLowerASCII(*word.begin); + if (first_letter > 'x') + return false; + + DCHECK(first_letter >= 'a'); + int index = first_letter - 'a'; + int length = std::distance(word.begin, word.end); + for (int i = location_names_accumulative[index]; + i < location_names_accumulative[index + 1]; ++i) { + if (location_names[i].length != length && + (location_names[i].allow_plural && + location_names[i].length + 1 != length)) { + continue; + } + + if (LowerCaseEqualsASCIIWithPlural(word.begin, word.end, + location_names[i].string, location_names[i].allow_plural)) { + return true; + } + } + + return false; +} diff --git a/Source/WebKit/android/content/address_detector.h b/Source/WebKit/android/content/address_detector.h new file mode 100644 index 0000000..4ba7b47 --- /dev/null +++ b/Source/WebKit/android/content/address_detector.h @@ -0,0 +1,131 @@ +/* + * 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(); + + private: + friend class AddressDetectorTest; + + // 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; + + virtual std::string GetContentText(const WebKit::WebRange& range) OVERRIDE; + virtual GURL GetIntentURL(const std::string& content_text) OVERRIDE; + virtual size_t GetMaximumContentLength() 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 new file mode 100644 index 0000000..b29a457 --- /dev/null +++ b/Source/WebKit/android/content/content_detector.cpp @@ -0,0 +1,86 @@ +/* + * 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" + +using WebKit::WebDOMTextContentWalker; +using WebKit::WebRange; + +ContentDetector::Result ContentDetector::FindTappedContent( + const WebKit::WebHitTestInfo& hit_test) { + 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(); +} diff --git a/Source/WebKit/android/content/content_detector.h b/Source/WebKit/android/content/content_detector.h new file mode 100644 index 0000000..041cbc9 --- /dev/null +++ b/Source/WebKit/android/content/content_detector.h @@ -0,0 +1,103 @@ +/* + * 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; +} + +// 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; + + // 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 index 6f94488..f5dcc48 100644 --- a/Source/WebKit/android/jni/AndroidHitTestResult.cpp +++ b/Source/WebKit/android/jni/AndroidHitTestResult.cpp @@ -28,6 +28,9 @@ #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" @@ -63,6 +66,7 @@ static struct { jfieldID m_TapHighlightColor; jfieldID m_EnclosingParentRects; jfieldID m_HasFocus; + jfieldID m_IntentUrl; } gHitTestGlue; struct field { @@ -89,6 +93,7 @@ static void InitJni(JNIEnv* env) { 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 }, @@ -135,13 +140,29 @@ void AndroidHitTestResult::buildHighlightRects() RenderObject* renderer = node->renderer(); Vector<FloatQuad> quads; renderer->absoluteFocusRingQuads(quads); - for (int i = 0; i < quads.size(); i++) { + 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; + 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); @@ -149,6 +170,13 @@ void setStringField(JNIEnv* env, jobject obj, jfieldID field, const String& str) 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); @@ -176,6 +204,8 @@ jobject AndroidHitTestResult::createJavaObject(JNIEnv* env) 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; @@ -201,8 +231,8 @@ jobject AndroidHitTestResult::createJavaObject(JNIEnv* env) Vector<IntRect> AndroidHitTestResult::enclosingParentRects(Node* node) { - int lastX; int count = 0; + int lastX = 0; Vector<IntRect> rects; while (node && count < 5) { @@ -214,7 +244,7 @@ Vector<IntRect> AndroidHitTestResult::enclosingParentRects(Node* node) node->document()->frame()); IntRect rect = render->absoluteBoundingBoxRect(); rect.move(-frameOffset.x(), -frameOffset.y()); - if (rect.x() != lastX) { + if (count == 0 || rect.x() != lastX) { rects.append(rect); lastX = rect.x(); count++; diff --git a/Source/WebKit/android/jni/AndroidHitTestResult.h b/Source/WebKit/android/jni/AndroidHitTestResult.h index f9709ac..5bbfc6b 100644 --- a/Source/WebKit/android/jni/AndroidHitTestResult.h +++ b/Source/WebKit/android/jni/AndroidHitTestResult.h @@ -26,6 +26,7 @@ #ifndef AndroidHitTestResult_h #define AndroidHitTestResult_h +#include "content/content_detector.h" #include "Element.h" #include "HitTestResult.h" #include "IntRect.h" @@ -48,6 +49,7 @@ public: void setURLElement(WebCore::Element* element); void buildHighlightRects(); + void searchContentDetectors(); jobject createJavaObject(JNIEnv*); @@ -57,6 +59,7 @@ private: WebViewCore* m_webViewCore; WebCore::HitTestResult m_hitTestResult; Vector<WebCore::IntRect> m_highlightRects; + ContentDetector::Result m_searchResult; }; } // namespace android diff --git a/Source/WebKit/android/jni/WebViewCore.cpp b/Source/WebKit/android/jni/WebViewCore.cpp index 1a3f7ca..d10a7e9 100644 --- a/Source/WebKit/android/jni/WebViewCore.cpp +++ b/Source/WebKit/android/jni/WebViewCore.cpp @@ -2014,6 +2014,7 @@ AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool do } } if (!nodeDataList.size()) { + androidHitResult.searchContentDetectors(); return androidHitResult; } // finally select the node with the largest overlap with the fat point @@ -2061,6 +2062,8 @@ AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool do m_scrollOffsetX, m_scrollOffsetY); } } + } else { + androidHitResult.searchContentDetectors(); } return androidHitResult; } diff --git a/Source/WebKit/chromium/public/WebRange.h b/Source/WebKit/chromium/public/WebRange.h index 89fc8f6..3da3d95 100644 --- a/Source/WebKit/chromium/public/WebRange.h +++ b/Source/WebKit/chromium/public/WebRange.h @@ -37,10 +37,10 @@ namespace WebCore { class Range; } namespace WTF { template <typename T> class PassRefPtr; } #endif +namespace WebCore { class Node; } namespace WebKit { -class WebNode; class WebRangePrivate; class WebString; @@ -64,8 +64,8 @@ public: WEBKIT_API int startOffset() const; WEBKIT_API int endOffset() const; - WEBKIT_API WebNode startContainer(int& exceptionCode) const; - WEBKIT_API WebNode endContainer(int& exceptionCode) const; + WEBKIT_API WebCore::Node* startContainer(int& exceptionCode) const; + WEBKIT_API WebCore::Node* endContainer(int& exceptionCode) const; WEBKIT_API WebString toHTMLText() const; WEBKIT_API WebString toPlainText() const; diff --git a/Source/WebKit/chromium/public/android/WebDOMTextContentWalker.h b/Source/WebKit/chromium/public/android/WebDOMTextContentWalker.h new file mode 100644 index 0000000..26ba589 --- /dev/null +++ b/Source/WebKit/chromium/public/android/WebDOMTextContentWalker.h @@ -0,0 +1,69 @@ +/* + * 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 INC. 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 INC. 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. + */ + +#ifndef WebDOMTextContentWalker_h +#define WebDOMTextContentWalker_h + +#include "../WebPrivateOwnPtr.h" +#include "../WebRange.h" +#include "../WebString.h" + +namespace WebCore { +class DOMTextContentWalker; +class Node; +} + +namespace WebKit { + +class WebHitTestInfo; + +class WebDOMTextContentWalker { +public: + WebDOMTextContentWalker(); + ~WebDOMTextContentWalker(); + + // Creates a new text content walker centered in the position described by the hit test. + // The maximum length of the contents retrieved by the walker is defined by maxLength. + WEBKIT_API WebDOMTextContentWalker(const WebHitTestInfo&, size_t maxLength); + + // Creates a new text content walker centered in the selected offset of the given text node. + // The maximum length of the contents retrieved by the walker is defined by maxLength. + WEBKIT_API WebDOMTextContentWalker(WebCore::Node* textNode, size_t offset, size_t maxLength); + + // Text content retrieved by the walker. + WEBKIT_API WebString content() const; + + // Position of the initial text node offset in the content string. + WEBKIT_API size_t hitOffsetInContent() const; + + // Convert start/end positions in the content text string into a WebKit text range. + WEBKIT_API WebRange contentOffsetsToRange(size_t startInContent, size_t endInContent); + +protected: + WebPrivateOwnPtr<WebCore::DOMTextContentWalker> m_private; +}; + +} // namespace WebKit + +#endif diff --git a/Source/WebKit/chromium/public/android/WebHitTestInfo.h b/Source/WebKit/chromium/public/android/WebHitTestInfo.h new file mode 100644 index 0000000..79b354e --- /dev/null +++ b/Source/WebKit/chromium/public/android/WebHitTestInfo.h @@ -0,0 +1,76 @@ +/* +* 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. + */ + +#ifndef WebHitTestInfo_h +#define WebHitTestInfo_h + +#include "../WebPoint.h" +#include "../WebPrivateOwnPtr.h" +#include "../WebURL.h" + +namespace WebCore { +class HitTestResult; +class Node; +} + +namespace WebKit { + +// Properties of a hit test result, i.e. properties of the nodes at a given point +// (the hit point) on the page. Both urls may be populated at the same time, for +// example in the instance of an <img> inside an <a>. +class WebHitTestInfo { +public: + WebHitTestInfo(); + WebHitTestInfo(const WebHitTestInfo&); + ~WebHitTestInfo(); + + // The absolute URL of the link returned by the hit test. + WEBKIT_API WebURL linkURL() const; + + // The absolute URL of the image returned by the hit test. + WEBKIT_API WebURL imageURL() const; + + // The node that got hit. + WEBKIT_API WebCore::Node* node() const; + + // Point coordinates of the hit. + WEBKIT_API WebPoint point() const; + + // True iff the hit was on an editable field or node. + WEBKIT_API bool isContentEditable() const; + +#if WEBKIT_IMPLEMENTATION + WebHitTestInfo(const WebCore::HitTestResult&); + WebHitTestInfo& operator=(const WebCore::HitTestResult&); + operator WebCore::HitTestResult() const; +#endif + +protected: + WebPrivateOwnPtr<WebCore::HitTestResult> m_private; +}; + +} // namespace WebKit + +#endif diff --git a/Source/WebKit/chromium/src/WebRange.cpp b/Source/WebKit/chromium/src/WebRange.cpp index 3dd000d..5ea3990 100644 --- a/Source/WebKit/chromium/src/WebRange.cpp +++ b/Source/WebKit/chromium/src/WebRange.cpp @@ -32,7 +32,6 @@ #include "WebRange.h" #include "Range.h" -#include "WebNode.h" #include "WebString.h" #include <wtf/PassRefPtr.h> @@ -66,14 +65,14 @@ int WebRange::endOffset() const return m_private->endOffset(); } -WebNode WebRange::startContainer(int& exceptionCode) const +Node* WebRange::startContainer(int& exceptionCode) const { - return PassRefPtr<Node>(m_private->startContainer(exceptionCode)); + return m_private->startContainer(exceptionCode); } -WebNode WebRange::endContainer(int& exceptionCode) const +Node* WebRange::endContainer(int& exceptionCode) const { - return PassRefPtr<Node>(m_private->endContainer(exceptionCode)); + return m_private->endContainer(exceptionCode); } WebString WebRange::toHTMLText() const diff --git a/Source/WebKit/chromium/src/android/WebDOMTextContentWalker.cpp b/Source/WebKit/chromium/src/android/WebDOMTextContentWalker.cpp new file mode 100644 index 0000000..80155fb --- /dev/null +++ b/Source/WebKit/chromium/src/android/WebDOMTextContentWalker.cpp @@ -0,0 +1,85 @@ +/* + * 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 INC. 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 INC. 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" +#include "android/WebDOMTextContentWalker.h" + +#include "DOMTextContentWalker.h" +#include "Element.h" +#include "Node.h" +#include "Range.h" +#include "RenderObject.h" +#include "Text.h" +#include "VisiblePosition.h" +#include "android/WebHitTestInfo.h" + +using namespace WebCore; + +namespace WebKit { + +WebDOMTextContentWalker::WebDOMTextContentWalker() +{ +} + +WebDOMTextContentWalker::~WebDOMTextContentWalker() +{ + m_private.reset(0); +} + +WebDOMTextContentWalker::WebDOMTextContentWalker(const WebHitTestInfo& hitTestInfo, size_t maxLength) +{ + Node* node = hitTestInfo.node(); + if (!node) + return; + + Element* element = node->parentElement(); + if (!node->inDocument() && element && element->inDocument()) + node = element; + m_private.reset(new DOMTextContentWalker(node->renderer()->positionForPoint(hitTestInfo.point()), maxLength)); +} + +WebDOMTextContentWalker::WebDOMTextContentWalker(Node* node, size_t offset, size_t maxLength) +{ + if (!node || !node->isTextNode() || offset >= node->nodeValue().length()) + return; + + m_private.reset(new DOMTextContentWalker(VisiblePosition(Position(static_cast<Text*>(node), offset).parentAnchoredEquivalent(), DOWNSTREAM), maxLength)); +} + +WebString WebDOMTextContentWalker::content() const +{ + return m_private->content(); +} + +size_t WebDOMTextContentWalker::hitOffsetInContent() const +{ + return m_private->hitOffsetInContent(); +} + +WebRange WebDOMTextContentWalker::contentOffsetsToRange(size_t startInContent, size_t endInContent) +{ + return m_private->contentOffsetsToRange(startInContent, endInContent); +} + +} // namespace WebKit diff --git a/Source/WebKit/chromium/src/android/WebHitTestInfo.cpp b/Source/WebKit/chromium/src/android/WebHitTestInfo.cpp new file mode 100644 index 0000000..948c6fb --- /dev/null +++ b/Source/WebKit/chromium/src/android/WebHitTestInfo.cpp @@ -0,0 +1,95 @@ +/* +* 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" +#include "android/WebHitTestInfo.h" + +#include "Element.h" +#include "HitTestResult.h" +#include "KURL.h" +#include "Node.h" +#include "RenderObject.h" +#include "VisiblePosition.h" + +using namespace WebCore; + +namespace WebKit { + +WebHitTestInfo::WebHitTestInfo() +{ +} + +WebHitTestInfo::WebHitTestInfo(const WebHitTestInfo& testInfo) + : m_private(new HitTestResult(testInfo)) +{ +} + +WebURL WebHitTestInfo::linkURL() const +{ + return m_private->absoluteLinkURL(); +} + +WebHitTestInfo::~WebHitTestInfo() +{ + m_private.reset(0); +} + +WebURL WebHitTestInfo::imageURL() const +{ + return m_private->absoluteImageURL(); +} + +Node* WebHitTestInfo::node() const +{ + return m_private->innerNode(); +} + +WebPoint WebHitTestInfo::point() const +{ + return WebPoint(m_private->localPoint()); +} + +bool WebHitTestInfo::isContentEditable() const +{ + return m_private->isContentEditable(); +} + +WebHitTestInfo::WebHitTestInfo(const HitTestResult& result) +{ + m_private.reset(new HitTestResult(result)); +} + +WebHitTestInfo& WebHitTestInfo::operator=(const HitTestResult& result) +{ + m_private.reset(new HitTestResult(result)); + return *this; +} + +WebHitTestInfo::operator HitTestResult() const +{ + return *m_private.get(); +} + +} // namespace WebKit |