1// Copyright 2012 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
5package org.chromium.android_webview.test;
6
7import org.chromium.android_webview.AwContents;
8
9import java.util.concurrent.CountDownLatch;
10import java.util.concurrent.TimeUnit;
11import java.util.concurrent.TimeoutException;
12
13/**
14 * Base class for WebView find-in-page API tests.
15 */
16public class WebViewFindApisTestBase extends AwTestBase {
17
18    private static final String WOODCHUCK =
19            "How much WOOD would a woodchuck chuck if a woodchuck could chuck wOoD?";
20
21    private FindResultListener mFindResultListener;
22    private AwContents mContents;
23
24    @Override
25    protected void setUp() throws Exception {
26        super.setUp();
27        try {
28            mContents = loadContentsFromStringSync(WOODCHUCK);
29        } catch (Throwable t) {
30            throw new Exception(t);
31        }
32    }
33
34    protected AwContents contents() {
35        return mContents;
36    }
37
38    // Internal interface to intercept find results from AwContentsClient.
39    private interface FindResultListener {
40        public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
41                boolean isDoneCounting);
42    };
43
44    private AwContents loadContentsFromStringSync(final String html) throws Throwable {
45        final TestAwContentsClient contentsClient = new TestAwContentsClient() {
46            @Override
47            public void onFindResultReceived(int activeMatchOrdinal,
48                    int numberOfMatches, boolean isDoneCounting) {
49                if (mFindResultListener == null) return;
50                mFindResultListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
51                        isDoneCounting);
52            }
53        };
54
55        final AwContents contents =
56                createAwTestContainerViewOnMainSync(contentsClient).getAwContents();
57        final String data = "<html><head></head><body>" + html + "</body></html>";
58        loadDataSync(contents, contentsClient.getOnPageFinishedHelper(),
59                data, "text/html", false);
60        return contents;
61    }
62
63    /**
64     * Invokes findAllAsync on the UI thread, blocks until find results are
65     * received, and returns the number of matches.
66     *
67     * @param searchString A string to search for.
68     * @return The number of instances of the string that were found.
69     * @throws Throwable
70     */
71    protected int findAllAsyncOnUiThread(final String searchString)
72            throws Throwable {
73        final IntegerFuture future = new IntegerFuture() {
74            @Override
75            public void run() {
76                mFindResultListener = new FindResultListener() {
77                    @Override
78                    public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
79                            boolean isDoneCounting) {
80                        if (isDoneCounting) set(numberOfMatches);
81                    }
82                };
83                mContents.findAllAsync(searchString);
84            }
85        };
86        runTestOnUiThread(future);
87        return future.get(10, TimeUnit.SECONDS);
88    }
89
90    /**
91     * Invokes findNext on the UI thread, blocks until find results are
92     * received, and returns the ordinal of the highlighted match.
93     *
94     * @param forwards The direction to search as a boolean, with forwards
95     *                 represented as true and backwards as false.
96     * @return The ordinal of the highlighted match.
97     * @throws Throwable
98     */
99    protected int findNextOnUiThread(final boolean forwards)
100            throws Throwable {
101        final IntegerFuture future = new IntegerFuture() {
102            @Override
103            public void run() {
104                mFindResultListener = new FindResultListener() {
105                    @Override
106                    public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
107                            boolean isDoneCounting) {
108                        if (isDoneCounting) set(activeMatchOrdinal);
109                    }
110                };
111                mContents.findNext(forwards);
112            }
113        };
114        runTestOnUiThread(future);
115        return future.get(10, TimeUnit.SECONDS);
116    }
117
118    /**
119     * Invokes clearMatches on the UI thread.
120     *
121     * @throws Throwable
122     */
123    protected void clearMatchesOnUiThread() throws Throwable {
124        runTestOnUiThread(new Runnable() {
125            @Override
126            public void run() {
127                mContents.clearMatches();
128            }
129        });
130    }
131
132    // Similar to java.util.concurrent.Future, but without the ability to cancel.
133    private abstract static class IntegerFuture implements Runnable {
134        private CountDownLatch mLatch = new CountDownLatch(1);
135        private int mValue;
136
137        @Override
138        public abstract void run();
139
140        /**
141         * Gets the value of this Future, blocking for up to the specified
142         * timeout for it become available. Throws a TimeoutException if the
143         * timeout expires.
144         */
145        public int get(long timeout, TimeUnit unit) throws Throwable {
146            if (!mLatch.await(timeout, unit)) {
147                throw new TimeoutException();
148            }
149            return mValue;
150        }
151
152        /**
153         * Sets the value of this Future.
154         */
155        protected void set(int value) {
156            mValue = value;
157            mLatch.countDown();
158        }
159    }
160}
161