Home | History | Annotate | Download | only in tests
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "config.h"
      6 
      7 #include "web/TextFinder.h"
      8 
      9 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
     10 #include "core/dom/Document.h"
     11 #include "core/dom/NodeList.h"
     12 #include "core/dom/Range.h"
     13 #include "core/dom/shadow/ShadowRoot.h"
     14 #include "core/html/HTMLElement.h"
     15 #include "public/platform/Platform.h"
     16 #include "public/web/WebDocument.h"
     17 #include "web/FindInPageCoordinates.h"
     18 #include "web/WebLocalFrameImpl.h"
     19 #include "web/tests/FrameTestHelpers.h"
     20 #include "wtf/OwnPtr.h"
     21 #include <gtest/gtest.h>
     22 
     23 using namespace blink;
     24 
     25 namespace {
     26 
     27 class TextFinderTest : public ::testing::Test {
     28 protected:
     29     virtual void SetUp() OVERRIDE;
     30 
     31     Document& document() const;
     32     TextFinder& textFinder() const;
     33 
     34     static WebFloatRect findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset);
     35 
     36 private:
     37     FrameTestHelpers::WebViewHelper m_webViewHelper;
     38     RefPtrWillBePersistent<Document> m_document;
     39     TextFinder* m_textFinder;
     40 };
     41 
     42 void TextFinderTest::SetUp()
     43 {
     44     m_webViewHelper.initialize();
     45     WebLocalFrameImpl& frameImpl = *m_webViewHelper.webViewImpl()->mainFrameImpl();
     46     frameImpl.viewImpl()->resize(WebSize(640, 480));
     47     m_document = PassRefPtrWillBeRawPtr<Document>(frameImpl.document());
     48     m_textFinder = &frameImpl.ensureTextFinder();
     49 }
     50 
     51 Document& TextFinderTest::document() const
     52 {
     53     return *m_document;
     54 }
     55 
     56 TextFinder& TextFinderTest::textFinder() const
     57 {
     58     return *m_textFinder;
     59 }
     60 
     61 WebFloatRect TextFinderTest::findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset)
     62 {
     63     RefPtrWillBeRawPtr<Range> range = Range::create(startContainer->document(), startContainer, startOffset, endContainer, endOffset);
     64     return WebFloatRect(findInPageRectFromRange(range.get()));
     65 }
     66 
     67 TEST_F(TextFinderTest, FindTextSimple)
     68 {
     69     document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
     70     Node* textNode = document().body()->firstChild();
     71 
     72     int identifier = 0;
     73     WebString searchText(String("FindMe"));
     74     WebFindOptions findOptions; // Default.
     75     bool wrapWithinFrame = true;
     76     WebRect* selectionRect = 0;
     77 
     78     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
     79     Range* activeMatch = textFinder().activeMatch();
     80     ASSERT_TRUE(activeMatch);
     81     EXPECT_EQ(textNode, activeMatch->startContainer());
     82     EXPECT_EQ(4, activeMatch->startOffset());
     83     EXPECT_EQ(textNode, activeMatch->endContainer());
     84     EXPECT_EQ(10, activeMatch->endOffset());
     85 
     86     findOptions.findNext = true;
     87     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
     88     activeMatch = textFinder().activeMatch();
     89     ASSERT_TRUE(activeMatch);
     90     EXPECT_EQ(textNode, activeMatch->startContainer());
     91     EXPECT_EQ(14, activeMatch->startOffset());
     92     EXPECT_EQ(textNode, activeMatch->endContainer());
     93     EXPECT_EQ(20, activeMatch->endOffset());
     94 
     95     // Should wrap to the first match.
     96     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
     97     activeMatch = textFinder().activeMatch();
     98     ASSERT_TRUE(activeMatch);
     99     EXPECT_EQ(textNode, activeMatch->startContainer());
    100     EXPECT_EQ(4, activeMatch->startOffset());
    101     EXPECT_EQ(textNode, activeMatch->endContainer());
    102     EXPECT_EQ(10, activeMatch->endOffset());
    103 
    104     // Search in the reverse order.
    105     identifier = 1;
    106     findOptions = WebFindOptions();
    107     findOptions.forward = false;
    108 
    109     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    110     activeMatch = textFinder().activeMatch();
    111     ASSERT_TRUE(activeMatch);
    112     EXPECT_EQ(textNode, activeMatch->startContainer());
    113     EXPECT_EQ(14, activeMatch->startOffset());
    114     EXPECT_EQ(textNode, activeMatch->endContainer());
    115     EXPECT_EQ(20, activeMatch->endOffset());
    116 
    117     findOptions.findNext = true;
    118     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    119     activeMatch = textFinder().activeMatch();
    120     ASSERT_TRUE(activeMatch);
    121     EXPECT_EQ(textNode, activeMatch->startContainer());
    122     EXPECT_EQ(4, activeMatch->startOffset());
    123     EXPECT_EQ(textNode, activeMatch->endContainer());
    124     EXPECT_EQ(10, activeMatch->endOffset());
    125 
    126     // Wrap to the first match (last occurence in the document).
    127     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    128     activeMatch = textFinder().activeMatch();
    129     ASSERT_TRUE(activeMatch);
    130     EXPECT_EQ(textNode, activeMatch->startContainer());
    131     EXPECT_EQ(14, activeMatch->startOffset());
    132     EXPECT_EQ(textNode, activeMatch->endContainer());
    133     EXPECT_EQ(20, activeMatch->endOffset());
    134 }
    135 
    136 TEST_F(TextFinderTest, FindTextNotFound)
    137 {
    138     document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
    139 
    140     int identifier = 0;
    141     WebString searchText(String("Boo"));
    142     WebFindOptions findOptions; // Default.
    143     bool wrapWithinFrame = true;
    144     WebRect* selectionRect = 0;
    145 
    146     EXPECT_FALSE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    147     EXPECT_FALSE(textFinder().activeMatch());
    148 }
    149 
    150 TEST_F(TextFinderTest, FindTextInShadowDOM)
    151 {
    152     document().body()->setInnerHTML("<b>FOO</b><i>foo</i>", ASSERT_NO_EXCEPTION);
    153     RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = document().body()->createShadowRoot(ASSERT_NO_EXCEPTION);
    154     shadowRoot->setInnerHTML("<content select=\"i\"></content><u>Foo</u><content></content>", ASSERT_NO_EXCEPTION);
    155     Node* textInBElement = document().body()->firstChild()->firstChild();
    156     Node* textInIElement = document().body()->lastChild()->firstChild();
    157     Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild();
    158 
    159     int identifier = 0;
    160     WebString searchText(String("foo"));
    161     WebFindOptions findOptions; // Default.
    162     bool wrapWithinFrame = true;
    163     WebRect* selectionRect = 0;
    164 
    165     // TextIterator currently returns the matches in the document order, instead of the visual order. It visits
    166     // the shadow roots first, so in this case the matches will be returned in the order of <u> -> <b> -> <i>.
    167     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    168     Range* activeMatch = textFinder().activeMatch();
    169     ASSERT_TRUE(activeMatch);
    170     EXPECT_EQ(textInUElement, activeMatch->startContainer());
    171     EXPECT_EQ(0, activeMatch->startOffset());
    172     EXPECT_EQ(textInUElement, activeMatch->endContainer());
    173     EXPECT_EQ(3, activeMatch->endOffset());
    174 
    175     findOptions.findNext = true;
    176     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    177     activeMatch = textFinder().activeMatch();
    178     ASSERT_TRUE(activeMatch);
    179     EXPECT_EQ(textInBElement, activeMatch->startContainer());
    180     EXPECT_EQ(0, activeMatch->startOffset());
    181     EXPECT_EQ(textInBElement, activeMatch->endContainer());
    182     EXPECT_EQ(3, activeMatch->endOffset());
    183 
    184     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    185     activeMatch = textFinder().activeMatch();
    186     ASSERT_TRUE(activeMatch);
    187     EXPECT_EQ(textInIElement, activeMatch->startContainer());
    188     EXPECT_EQ(0, activeMatch->startOffset());
    189     EXPECT_EQ(textInIElement, activeMatch->endContainer());
    190     EXPECT_EQ(3, activeMatch->endOffset());
    191 
    192     // Should wrap to the first match.
    193     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    194     activeMatch = textFinder().activeMatch();
    195     ASSERT_TRUE(activeMatch);
    196     EXPECT_EQ(textInUElement, activeMatch->startContainer());
    197     EXPECT_EQ(0, activeMatch->startOffset());
    198     EXPECT_EQ(textInUElement, activeMatch->endContainer());
    199     EXPECT_EQ(3, activeMatch->endOffset());
    200 
    201     // Fresh search in the reverse order.
    202     identifier = 1;
    203     findOptions = WebFindOptions();
    204     findOptions.forward = false;
    205 
    206     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    207     activeMatch = textFinder().activeMatch();
    208     ASSERT_TRUE(activeMatch);
    209     EXPECT_EQ(textInIElement, activeMatch->startContainer());
    210     EXPECT_EQ(0, activeMatch->startOffset());
    211     EXPECT_EQ(textInIElement, activeMatch->endContainer());
    212     EXPECT_EQ(3, activeMatch->endOffset());
    213 
    214     findOptions.findNext = true;
    215     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    216     activeMatch = textFinder().activeMatch();
    217     ASSERT_TRUE(activeMatch);
    218     EXPECT_EQ(textInBElement, activeMatch->startContainer());
    219     EXPECT_EQ(0, activeMatch->startOffset());
    220     EXPECT_EQ(textInBElement, activeMatch->endContainer());
    221     EXPECT_EQ(3, activeMatch->endOffset());
    222 
    223     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    224     activeMatch = textFinder().activeMatch();
    225     ASSERT_TRUE(activeMatch);
    226     EXPECT_EQ(textInUElement, activeMatch->startContainer());
    227     EXPECT_EQ(0, activeMatch->startOffset());
    228     EXPECT_EQ(textInUElement, activeMatch->endContainer());
    229     EXPECT_EQ(3, activeMatch->endOffset());
    230 
    231     // And wrap.
    232     ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
    233     activeMatch = textFinder().activeMatch();
    234     ASSERT_TRUE(activeMatch);
    235     EXPECT_EQ(textInIElement, activeMatch->startContainer());
    236     EXPECT_EQ(0, activeMatch->startOffset());
    237     EXPECT_EQ(textInIElement, activeMatch->endContainer());
    238     EXPECT_EQ(3, activeMatch->endOffset());
    239 }
    240 
    241 TEST_F(TextFinderTest, ScopeTextMatchesSimple)
    242 {
    243     document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
    244     Node* textNode = document().body()->firstChild();
    245 
    246     int identifier = 0;
    247     WebString searchText(String("FindMe"));
    248     WebFindOptions findOptions; // Default.
    249 
    250     textFinder().resetMatchCount();
    251     textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
    252     while (textFinder().scopingInProgress())
    253         FrameTestHelpers::runPendingTasks();
    254 
    255     EXPECT_EQ(2, textFinder().totalMatchCount());
    256     WebVector<WebFloatRect> matchRects;
    257     textFinder().findMatchRects(matchRects);
    258     ASSERT_EQ(2u, matchRects.size());
    259     EXPECT_EQ(findInPageRect(textNode, 4, textNode, 10), matchRects[0]);
    260     EXPECT_EQ(findInPageRect(textNode, 14, textNode, 20), matchRects[1]);
    261 }
    262 
    263 TEST_F(TextFinderTest, ScopeTextMatchesWithShadowDOM)
    264 {
    265     document().body()->setInnerHTML("<b>FOO</b><i>foo</i>", ASSERT_NO_EXCEPTION);
    266     RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = document().body()->createShadowRoot(ASSERT_NO_EXCEPTION);
    267     shadowRoot->setInnerHTML("<content select=\"i\"></content><u>Foo</u><content></content>", ASSERT_NO_EXCEPTION);
    268     Node* textInBElement = document().body()->firstChild()->firstChild();
    269     Node* textInIElement = document().body()->lastChild()->firstChild();
    270     Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild();
    271 
    272     int identifier = 0;
    273     WebString searchText(String("fOO"));
    274     WebFindOptions findOptions; // Default.
    275 
    276     textFinder().resetMatchCount();
    277     textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
    278     while (textFinder().scopingInProgress())
    279         FrameTestHelpers::runPendingTasks();
    280 
    281     // TextIterator currently returns the matches in the document order, instead of the visual order. It visits
    282     // the shadow roots first, so in this case the matches will be returned in the order of <u> -> <b> -> <i>.
    283     EXPECT_EQ(3, textFinder().totalMatchCount());
    284     WebVector<WebFloatRect> matchRects;
    285     textFinder().findMatchRects(matchRects);
    286     ASSERT_EQ(3u, matchRects.size());
    287     EXPECT_EQ(findInPageRect(textInUElement, 0, textInUElement, 3), matchRects[0]);
    288     EXPECT_EQ(findInPageRect(textInBElement, 0, textInBElement, 3), matchRects[1]);
    289     EXPECT_EQ(findInPageRect(textInIElement, 0, textInIElement, 3), matchRects[2]);
    290 }
    291 
    292 TEST_F(TextFinderTest, ScopeRepeatPatternTextMatches)
    293 {
    294     document().body()->setInnerHTML("ab ab ab ab ab", ASSERT_NO_EXCEPTION);
    295     Node* textNode = document().body()->firstChild();
    296 
    297     int identifier = 0;
    298     WebString searchText(String("ab ab"));
    299     WebFindOptions findOptions; // Default.
    300 
    301     textFinder().resetMatchCount();
    302     textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
    303     while (textFinder().scopingInProgress())
    304         FrameTestHelpers::runPendingTasks();
    305 
    306     EXPECT_EQ(2, textFinder().totalMatchCount());
    307     WebVector<WebFloatRect> matchRects;
    308     textFinder().findMatchRects(matchRects);
    309     ASSERT_EQ(2u, matchRects.size());
    310     EXPECT_EQ(findInPageRect(textNode, 0, textNode, 5), matchRects[0]);
    311     EXPECT_EQ(findInPageRect(textNode, 6, textNode, 11), matchRects[1]);
    312 }
    313 
    314 TEST_F(TextFinderTest, OverlappingMatches)
    315 {
    316     document().body()->setInnerHTML("aababaa", ASSERT_NO_EXCEPTION);
    317     Node* textNode = document().body()->firstChild();
    318 
    319     int identifier = 0;
    320     WebString searchText(String("aba"));
    321     WebFindOptions findOptions; // Default.
    322 
    323     textFinder().resetMatchCount();
    324     textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
    325     while (textFinder().scopingInProgress())
    326         FrameTestHelpers::runPendingTasks();
    327 
    328     // We shouldn't find overlapped matches.
    329     EXPECT_EQ(1, textFinder().totalMatchCount());
    330     WebVector<WebFloatRect> matchRects;
    331     textFinder().findMatchRects(matchRects);
    332     ASSERT_EQ(1u, matchRects.size());
    333     EXPECT_EQ(findInPageRect(textNode, 1, textNode, 4), matchRects[0]);
    334 }
    335 
    336 TEST_F(TextFinderTest, SequentialMatches)
    337 {
    338     document().body()->setInnerHTML("ababab", ASSERT_NO_EXCEPTION);
    339     Node* textNode = document().body()->firstChild();
    340 
    341     int identifier = 0;
    342     WebString searchText(String("ab"));
    343     WebFindOptions findOptions; // Default.
    344 
    345     textFinder().resetMatchCount();
    346     textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
    347     while (textFinder().scopingInProgress())
    348         FrameTestHelpers::runPendingTasks();
    349 
    350     EXPECT_EQ(3, textFinder().totalMatchCount());
    351     WebVector<WebFloatRect> matchRects;
    352     textFinder().findMatchRects(matchRects);
    353     ASSERT_EQ(3u, matchRects.size());
    354     EXPECT_EQ(findInPageRect(textNode, 0, textNode, 2), matchRects[0]);
    355     EXPECT_EQ(findInPageRect(textNode, 2, textNode, 4), matchRects[1]);
    356     EXPECT_EQ(findInPageRect(textNode, 4, textNode, 6), matchRects[2]);
    357 }
    358 
    359 class TextFinderFakeTimerTest : public TextFinderTest {
    360 protected:
    361     virtual void SetUp() OVERRIDE;
    362     virtual void TearDown() OVERRIDE;
    363 
    364     // A simple platform that mocks out the clock.
    365     class TimeProxyPlatform : public Platform {
    366     public:
    367         TimeProxyPlatform()
    368             : m_timeCounter(0.)
    369             , m_fallbackPlatform(0)
    370         { }
    371 
    372         void install()
    373         {
    374             // Check that the proxy wasn't installed yet.
    375             ASSERT_NE(Platform::current(), this);
    376             m_fallbackPlatform = Platform::current();
    377             m_timeCounter = m_fallbackPlatform->currentTime();
    378             Platform::initialize(this);
    379             ASSERT_EQ(Platform::current(), this);
    380         }
    381 
    382         void remove()
    383         {
    384             // Check that the proxy was installed.
    385             ASSERT_EQ(Platform::current(), this);
    386             Platform::initialize(m_fallbackPlatform);
    387             ASSERT_EQ(Platform::current(), m_fallbackPlatform);
    388             m_fallbackPlatform = 0;
    389         }
    390 
    391     private:
    392         Platform& ensureFallback()
    393         {
    394             ASSERT(m_fallbackPlatform);
    395             return *m_fallbackPlatform;
    396         }
    397 
    398         // From blink::Platform:
    399         virtual double currentTime() OVERRIDE
    400         {
    401             return ++m_timeCounter;
    402         }
    403 
    404         // These blink::Platform methods must be overriden to make a usable object.
    405         virtual void cryptographicallyRandomValues(unsigned char* buffer, size_t length) OVERRIDE
    406         {
    407             ensureFallback().cryptographicallyRandomValues(buffer, length);
    408         }
    409 
    410         virtual const unsigned char* getTraceCategoryEnabledFlag(const char* categoryName) OVERRIDE
    411         {
    412             return ensureFallback().getTraceCategoryEnabledFlag(categoryName);
    413         }
    414 
    415         // These two methods allow timers to work correctly.
    416         virtual double monotonicallyIncreasingTime() OVERRIDE
    417         {
    418             return ensureFallback().monotonicallyIncreasingTime();
    419         }
    420 
    421         virtual void setSharedTimerFireInterval(double interval) OVERRIDE
    422         {
    423             ensureFallback().setSharedTimerFireInterval(interval);
    424         }
    425 
    426         virtual WebThread* currentThread() OVERRIDE { return ensureFallback().currentThread(); }
    427         virtual WebUnitTestSupport* unitTestSupport() OVERRIDE { return ensureFallback().unitTestSupport(); }
    428         virtual WebString defaultLocale() OVERRIDE { return ensureFallback().defaultLocale(); }
    429         virtual WebCompositorSupport* compositorSupport() OVERRIDE { return ensureFallback().compositorSupport(); }
    430 
    431         double m_timeCounter;
    432         Platform* m_fallbackPlatform;
    433     };
    434 
    435     TimeProxyPlatform m_proxyTimePlatform;
    436 };
    437 
    438 void TextFinderFakeTimerTest::SetUp()
    439 {
    440     TextFinderTest::SetUp();
    441     m_proxyTimePlatform.install();
    442 }
    443 
    444 void TextFinderFakeTimerTest::TearDown()
    445 {
    446     m_proxyTimePlatform.remove();
    447     TextFinderTest::TearDown();
    448 }
    449 
    450 TEST_F(TextFinderFakeTimerTest, ScopeWithTimeouts)
    451 {
    452     // Make a long string.
    453     String text(Vector<UChar>(100));
    454     text.fill('a');
    455     String searchPattern("abc");
    456     // Make 4 substrings "abc" in text.
    457     text.insert(searchPattern, 1);
    458     text.insert(searchPattern, 10);
    459     text.insert(searchPattern, 50);
    460     text.insert(searchPattern, 90);
    461 
    462     document().body()->setInnerHTML(text, ASSERT_NO_EXCEPTION);
    463 
    464     int identifier = 0;
    465     WebFindOptions findOptions; // Default.
    466 
    467     textFinder().resetMatchCount();
    468 
    469     // There will be only one iteration before timeout, because increment
    470     // of the TimeProxyPlatform timer is greater than timeout threshold.
    471     textFinder().scopeStringMatches(identifier, searchPattern, findOptions, true);
    472     while (textFinder().scopingInProgress())
    473         FrameTestHelpers::runPendingTasks();
    474 
    475     EXPECT_EQ(4, textFinder().totalMatchCount());
    476 }
    477 
    478 } // namespace
    479