1 | // Copyright (c) 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 | |
5 | package org.chromium.android_webview.test; |
6 | |
7 | import android.app.Instrumentation; |
8 | import android.content.Context; |
9 | import android.test.ActivityInstrumentationTestCase2; |
10 | |
11 | import org.chromium.android_webview.AwBrowserContext; |
12 | import org.chromium.android_webview.AwBrowserProcess; |
13 | import org.chromium.android_webview.AwContents; |
14 | import org.chromium.android_webview.AwContentsClient; |
15 | import org.chromium.android_webview.AwLayoutSizer; |
16 | import org.chromium.android_webview.AwSettings; |
17 | import org.chromium.android_webview.test.util.JSUtils; |
18 | import org.chromium.base.test.util.InMemorySharedPreferences; |
19 | import org.chromium.content.browser.ContentSettings; |
20 | import org.chromium.content.browser.LoadUrlParams; |
21 | import org.chromium.content.browser.test.util.CallbackHelper; |
22 | import org.chromium.content.browser.test.util.Criteria; |
23 | import org.chromium.content.browser.test.util.CriteriaHelper; |
24 | |
25 | import java.util.concurrent.Callable; |
26 | import java.util.concurrent.FutureTask; |
27 | import java.util.concurrent.TimeUnit; |
28 | import java.util.concurrent.atomic.AtomicReference; |
29 | |
30 | /** |
31 | * A base class for android_webview tests. |
32 | */ |
33 | public class AwTestBase |
34 | extends ActivityInstrumentationTestCase2<AwTestRunnerActivity> { |
35 | protected static final int WAIT_TIMEOUT_SECONDS = 15; |
36 | protected static final int CHECK_INTERVAL = 100; |
37 | |
38 | public AwTestBase() { |
39 | super(AwTestRunnerActivity.class); |
40 | } |
41 | |
42 | @Override |
43 | protected void setUp() throws Exception { |
44 | super.setUp(); |
45 | final Context context = getActivity(); |
46 | getInstrumentation().runOnMainSync(new Runnable() { |
47 | @Override |
48 | public void run() { |
49 | AwBrowserProcess.start(context); |
50 | } |
51 | }); |
52 | } |
53 | |
54 | /** |
55 | * Runs a {@link Callable} on the main thread, blocking until it is |
56 | * complete, and returns the result. Calls |
57 | * {@link Instrumentation#waitForIdleSync()} first to help avoid certain |
58 | * race conditions. |
59 | * |
60 | * @param <R> Type of result to return |
61 | */ |
62 | public <R> R runTestOnUiThreadAndGetResult(Callable<R> callable) |
63 | throws Exception { |
64 | FutureTask<R> task = new FutureTask<R>(callable); |
65 | getInstrumentation().waitForIdleSync(); |
66 | getInstrumentation().runOnMainSync(task); |
67 | return task.get(); |
68 | } |
69 | |
70 | protected void enableJavaScriptOnUiThread(final AwContents awContents) { |
71 | getInstrumentation().runOnMainSync(new Runnable() { |
72 | @Override |
73 | public void run() { |
74 | awContents.getSettings().setJavaScriptEnabled(true); |
75 | } |
76 | }); |
77 | } |
78 | |
79 | /** |
80 | * Loads url on the UI thread and blocks until onPageFinished is called. |
81 | */ |
82 | protected void loadUrlSync(final AwContents awContents, |
83 | CallbackHelper onPageFinishedHelper, |
84 | final String url) throws Exception { |
85 | int currentCallCount = onPageFinishedHelper.getCallCount(); |
86 | loadUrlAsync(awContents, url); |
87 | onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_SECONDS, |
88 | TimeUnit.SECONDS); |
89 | } |
90 | |
91 | protected void loadUrlSyncAndExpectError(final AwContents awContents, |
92 | CallbackHelper onPageFinishedHelper, |
93 | CallbackHelper onReceivedErrorHelper, |
94 | final String url) throws Exception { |
95 | int onErrorCallCount = onReceivedErrorHelper.getCallCount(); |
96 | int onFinishedCallCount = onPageFinishedHelper.getCallCount(); |
97 | loadUrlAsync(awContents, url); |
98 | onReceivedErrorHelper.waitForCallback(onErrorCallCount, 1, WAIT_TIMEOUT_SECONDS, |
99 | TimeUnit.SECONDS); |
100 | onPageFinishedHelper.waitForCallback(onFinishedCallCount, 1, WAIT_TIMEOUT_SECONDS, |
101 | TimeUnit.SECONDS); |
102 | } |
103 | |
104 | /** |
105 | * Loads url on the UI thread but does not block. |
106 | */ |
107 | protected void loadUrlAsync(final AwContents awContents, |
108 | final String url) throws Exception { |
109 | getInstrumentation().runOnMainSync(new Runnable() { |
110 | @Override |
111 | public void run() { |
112 | awContents.loadUrl(new LoadUrlParams(url)); |
113 | } |
114 | }); |
115 | } |
116 | |
117 | /** |
118 | * Posts url on the UI thread and blocks until onPageFinished is called. |
119 | */ |
120 | protected void postUrlSync(final AwContents awContents, |
121 | CallbackHelper onPageFinishedHelper, final String url, |
122 | byte[] postData) throws Exception { |
123 | int currentCallCount = onPageFinishedHelper.getCallCount(); |
124 | postUrlAsync(awContents, url, postData); |
125 | onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_SECONDS, |
126 | TimeUnit.SECONDS); |
127 | } |
128 | |
129 | /** |
130 | * Loads url on the UI thread but does not block. |
131 | */ |
132 | protected void postUrlAsync(final AwContents awContents, |
133 | final String url, byte[] postData) throws Exception { |
134 | class PostUrl implements Runnable { |
135 | byte[] mPostData; |
136 | public PostUrl(byte[] postData) { |
137 | mPostData = postData; |
138 | } |
139 | @Override |
140 | public void run() { |
141 | awContents.loadUrl(LoadUrlParams.createLoadHttpPostParams(url, |
142 | mPostData)); |
143 | } |
144 | } |
145 | getInstrumentation().runOnMainSync(new PostUrl(postData)); |
146 | } |
147 | |
148 | /** |
149 | * Loads data on the UI thread and blocks until onPageFinished is called. |
150 | */ |
151 | protected void loadDataSync(final AwContents awContents, |
152 | CallbackHelper onPageFinishedHelper, |
153 | final String data, final String mimeType, |
154 | final boolean isBase64Encoded) throws Exception { |
155 | int currentCallCount = onPageFinishedHelper.getCallCount(); |
156 | loadDataAsync(awContents, data, mimeType, isBase64Encoded); |
157 | onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_SECONDS, |
158 | TimeUnit.SECONDS); |
159 | } |
160 | |
161 | protected void loadDataSyncWithCharset(final AwContents awContents, |
162 | CallbackHelper onPageFinishedHelper, |
163 | final String data, final String mimeType, |
164 | final boolean isBase64Encoded, final String charset) |
165 | throws Exception { |
166 | int currentCallCount = onPageFinishedHelper.getCallCount(); |
167 | getInstrumentation().runOnMainSync(new Runnable() { |
168 | @Override |
169 | public void run() { |
170 | awContents.loadUrl(LoadUrlParams.createLoadDataParams( |
171 | data, mimeType, isBase64Encoded, charset)); |
172 | } |
173 | }); |
174 | onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_SECONDS, |
175 | TimeUnit.SECONDS); |
176 | } |
177 | |
178 | /** |
179 | * Loads data on the UI thread but does not block. |
180 | */ |
181 | protected void loadDataAsync(final AwContents awContents, final String data, |
182 | final String mimeType, final boolean isBase64Encoded) |
183 | throws Exception { |
184 | getInstrumentation().runOnMainSync(new Runnable() { |
185 | @Override |
186 | public void run() { |
187 | awContents.loadUrl(LoadUrlParams.createLoadDataParams( |
188 | data, mimeType, isBase64Encoded)); |
189 | } |
190 | }); |
191 | } |
192 | |
193 | /** |
194 | * Factory class used in creation of test AwContents instances. |
195 | * |
196 | * Test cases can provide subclass instances to the createAwTest* methods in order to create an |
197 | * AwContents instance with injected test dependencies. |
198 | */ |
199 | public static class TestDependencyFactory { |
200 | public AwLayoutSizer createLayoutSizer() { |
201 | return new AwLayoutSizer(); |
202 | } |
203 | public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) { |
204 | return new AwTestContainerView(activity); |
205 | } |
206 | } |
207 | |
208 | protected TestDependencyFactory createTestDependencyFactory() { |
209 | return new TestDependencyFactory(); |
210 | } |
211 | |
212 | protected AwTestContainerView createAwTestContainerView( |
213 | final AwContentsClient awContentsClient) { |
214 | AwTestContainerView testContainerView = createDetachedAwTestContainerView(awContentsClient); |
215 | getActivity().addView(testContainerView); |
216 | testContainerView.requestFocus(); |
217 | return testContainerView; |
218 | } |
219 | |
220 | // The browser context needs to be a process-wide singleton. |
221 | private AwBrowserContext mBrowserContext = |
222 | new AwBrowserContext(new InMemorySharedPreferences()); |
223 | |
224 | protected AwTestContainerView createDetachedAwTestContainerView( |
225 | final AwContentsClient awContentsClient) { |
226 | final TestDependencyFactory testDependencyFactory = createTestDependencyFactory(); |
227 | final AwTestContainerView testContainerView = |
228 | testDependencyFactory.createAwTestContainerView(getActivity()); |
229 | // TODO(mnaganov): Should also have tests for the "pure Chromium" mode. |
230 | // See http://crbug.com/278106 |
231 | testContainerView.initialize(new AwContents( |
232 | mBrowserContext, testContainerView, testContainerView.getInternalAccessDelegate(), |
233 | awContentsClient, false, testDependencyFactory.createLayoutSizer(), true)); |
234 | return testContainerView; |
235 | } |
236 | |
237 | protected AwTestContainerView createAwTestContainerViewOnMainSync( |
238 | final AwContentsClient client) throws Exception { |
239 | final AtomicReference<AwTestContainerView> testContainerView = |
240 | new AtomicReference<AwTestContainerView>(); |
241 | getInstrumentation().runOnMainSync(new Runnable() { |
242 | @Override |
243 | public void run() { |
244 | testContainerView.set(createAwTestContainerView(client)); |
245 | } |
246 | }); |
247 | return testContainerView.get(); |
248 | } |
249 | |
250 | protected void destroyAwContentsOnMainSync(final AwContents awContents) { |
251 | if (awContents == null) return; |
252 | getInstrumentation().runOnMainSync(new Runnable() { |
253 | @Override |
254 | public void run() { |
255 | awContents.destroy(); |
256 | } |
257 | }); |
258 | } |
259 | |
260 | protected String getTitleOnUiThread(final AwContents awContents) throws Exception { |
261 | return runTestOnUiThreadAndGetResult(new Callable<String>() { |
262 | @Override |
263 | public String call() throws Exception { |
264 | return awContents.getContentViewCore().getTitle(); |
265 | } |
266 | }); |
267 | } |
268 | |
269 | protected ContentSettings getContentSettingsOnUiThread( |
270 | final AwContents awContents) throws Exception { |
271 | return runTestOnUiThreadAndGetResult(new Callable<ContentSettings>() { |
272 | @Override |
273 | public ContentSettings call() throws Exception { |
274 | return awContents.getContentViewCore().getContentSettings(); |
275 | } |
276 | }); |
277 | } |
278 | |
279 | protected AwSettings getAwSettingsOnUiThread( |
280 | final AwContents awContents) throws Exception { |
281 | return runTestOnUiThreadAndGetResult(new Callable<AwSettings>() { |
282 | @Override |
283 | public AwSettings call() throws Exception { |
284 | return awContents.getSettings(); |
285 | } |
286 | }); |
287 | } |
288 | |
289 | /** |
290 | * Executes the given snippet of JavaScript code within the given ContentView. Returns the |
291 | * result of its execution in JSON format. |
292 | */ |
293 | protected String executeJavaScriptAndWaitForResult(final AwContents awContents, |
294 | TestAwContentsClient viewClient, final String code) throws Exception { |
295 | return JSUtils.executeJavaScriptAndWaitForResult(this, awContents, |
296 | viewClient.getOnEvaluateJavaScriptResultHelper(), |
297 | code); |
298 | } |
299 | |
300 | /** |
301 | * Similar to CriteriaHelper.pollForCriteria but runs the callable on the UI thread. |
302 | * Note that exceptions are treated as failure. |
303 | */ |
304 | protected boolean pollOnUiThread(final Callable<Boolean> callable) throws Exception { |
305 | return CriteriaHelper.pollForCriteria(new Criteria() { |
306 | @Override |
307 | public boolean isSatisfied() { |
308 | try { |
309 | return runTestOnUiThreadAndGetResult(callable); |
310 | } catch (Throwable e) { |
311 | return false; |
312 | } |
313 | } |
314 | }); |
315 | } |
316 | |
317 | /** |
318 | * Clears the resource cache. Note that the cache is per-application, so this will clear the |
319 | * cache for all WebViews used. |
320 | */ |
321 | protected void clearCacheOnUiThread( |
322 | final AwContents awContents, |
323 | final boolean includeDiskFiles) throws Exception { |
324 | getInstrumentation().runOnMainSync(new Runnable() { |
325 | @Override |
326 | public void run() { |
327 | awContents.clearCache(includeDiskFiles); |
328 | } |
329 | }); |
330 | } |
331 | } |