Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(259)

Side by Side Diff: android_webview/javatests/src/org/chromium/android_webview/test/AwTestBase.java

Issue 2933623002: Create AwJUnit4ClassRunner AwActivityTestRule and convert AwContentsTest (Closed)
Patch Set: rebase + fix errors Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.android_webview.test; 5 package org.chromium.android_webview.test;
6 6
7 import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout; 7 import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
8 8
9 import android.app.Instrumentation; 9 import android.app.Instrumentation;
10 import android.content.Context; 10 import android.content.Context;
11 import android.os.Build; 11 import android.os.Build;
12 import android.util.AndroidRuntimeException;
13 import android.util.Log;
14 import android.view.ViewGroup; 12 import android.view.ViewGroup;
15 13
16 import org.chromium.android_webview.AwBrowserContext; 14 import org.chromium.android_webview.AwBrowserContext;
17 import org.chromium.android_webview.AwBrowserProcess;
18 import org.chromium.android_webview.AwContents; 15 import org.chromium.android_webview.AwContents;
19 import org.chromium.android_webview.AwContents.DependencyFactory; 16 import org.chromium.android_webview.AwContents.DependencyFactory;
20 import org.chromium.android_webview.AwContents.InternalAccessDelegate; 17 import org.chromium.android_webview.AwContents.InternalAccessDelegate;
21 import org.chromium.android_webview.AwContents.NativeDrawGLFunctorFactory; 18 import org.chromium.android_webview.AwContents.NativeDrawGLFunctorFactory;
22 import org.chromium.android_webview.AwContentsClient; 19 import org.chromium.android_webview.AwContentsClient;
23 import org.chromium.android_webview.AwSettings; 20 import org.chromium.android_webview.AwSettings;
24 import org.chromium.android_webview.AwSwitches; 21 import org.chromium.android_webview.AwSwitches;
25 import org.chromium.android_webview.test.util.GraphicsTestUtils; 22 import org.chromium.android_webview.test.AwTestCommon.AwTestCommonCallback;
26 import org.chromium.android_webview.test.util.JSUtils; 23 import org.chromium.base.Log;
27 import org.chromium.base.ThreadUtils;
28 import org.chromium.base.test.BaseActivityInstrumentationTestCase; 24 import org.chromium.base.test.BaseActivityInstrumentationTestCase;
29 import org.chromium.base.test.util.CallbackHelper; 25 import org.chromium.base.test.util.CallbackHelper;
30 import org.chromium.base.test.util.InMemorySharedPreferences; 26 import org.chromium.base.test.util.InMemorySharedPreferences;
31 import org.chromium.base.test.util.MinAndroidSdkLevel; 27 import org.chromium.base.test.util.MinAndroidSdkLevel;
32 import org.chromium.base.test.util.parameter.CommandLineParameter; 28 import org.chromium.base.test.util.parameter.CommandLineParameter;
33 import org.chromium.content.browser.test.util.Criteria;
34 import org.chromium.content.browser.test.util.CriteriaHelper;
35 import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnPage FinishedHelper;
36 import org.chromium.content_public.browser.LoadUrlParams;
37 import org.chromium.net.test.util.TestWebServer; 29 import org.chromium.net.test.util.TestWebServer;
38 30
39 import java.lang.annotation.Annotation; 31 import java.lang.annotation.Annotation;
40 import java.lang.reflect.AnnotatedElement; 32 import java.lang.reflect.AnnotatedElement;
41 import java.lang.reflect.Method; 33 import java.lang.reflect.Method;
42 import java.util.Map; 34 import java.util.Map;
43 import java.util.concurrent.Callable; 35 import java.util.concurrent.Callable;
44 import java.util.concurrent.FutureTask;
45 import java.util.concurrent.TimeUnit;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48 36
49 /** 37 /**
50 * A base class for android_webview tests. WebView only runs on KitKat and later , 38 * A base class for android_webview tests. WebView only runs on KitKat and
51 * so make sure no one attempts to run the tests on earlier OS releases. 39 * later, so make sure no one attempts to run the tests on earlier OS releases.
52 * 40 * By default, all tests run both in single-process mode, and with sandboxed
53 * By default, all tests run both in single-process mode, and with sandboxed ren derer. 41 * renderer. If a test doesn't yet work with sandboxed renderer, an entire
54 * If a test doesn't yet work with sandboxed renderer, an entire class, or an in dividual test 42 * class, or an individual test method can be marked for single-process testing
55 * method can be marked for single-process testing only by adding the following annotation: 43 * only by adding the following annotation:
56 * 44 *
57 * @SkipCommandLineParameterization 45 * @SkipCommandLineParameterization
boliu 2017/07/20 21:22:47 fix this comment
the real yoland 2017/07/27 00:29:47 Done
58 */ 46 */
59 @MinAndroidSdkLevel(Build.VERSION_CODES.KITKAT) 47 @MinAndroidSdkLevel(Build.VERSION_CODES.KITKAT)
60 @CommandLineParameter({"", AwSwitches.WEBVIEW_SANDBOXED_RENDERER}) 48 @CommandLineParameter({"", AwSwitches.WEBVIEW_SANDBOXED_RENDERER})
61 public class AwTestBase extends BaseActivityInstrumentationTestCase<AwTestRunner Activity> { 49 public class AwTestBase extends BaseActivityInstrumentationTestCase<AwTestRunner Activity>
50 implements AwTestCommonCallback {
51 private static final String TAG = "AwTestBase";
52
62 public static final long WAIT_TIMEOUT_MS = scaleTimeout(15000); 53 public static final long WAIT_TIMEOUT_MS = scaleTimeout(15000);
54
63 public static final int CHECK_INTERVAL = 100; 55 public static final int CHECK_INTERVAL = 100;
64 private static final String TAG = "AwTestBase";
65 private static final Pattern MAYBE_QUOTED_STRING = Pattern.compile("^(\"?)(. *)\\1$");
66 56
67 // The browser context needs to be a process-wide singleton. 57 private final AwTestCommon mTestCommon;
68 private AwBrowserContext mBrowserContext;
69 58
70 public AwTestBase() { 59 public AwTestBase() {
71 super(AwTestRunnerActivity.class); 60 super(AwTestRunnerActivity.class);
61 mTestCommon = new AwTestCommon(this);
72 } 62 }
73 63
74 @Override 64 @Override
75 protected void setUp() throws Exception { 65 protected void setUp() throws Exception {
76 if (needsAwBrowserContextCreated()) { 66 mTestCommon.setUp();
77 createAwBrowserContext();
78 }
79
80 super.setUp();
81 if (needsBrowserProcessStarted()) {
82 startBrowserProcess();
83 }
84 } 67 }
85 68
86 protected void createAwBrowserContext() { 69 protected void createAwBrowserContext() {
87 if (mBrowserContext != null) { 70 mTestCommon.createAwBrowserContext();
88 throw new AndroidRuntimeException("There should only be one browser context.");
89 }
90 getActivity(); // The Activity must be launched in order to load native code
91 final InMemorySharedPreferences prefs = new InMemorySharedPreferences();
92 final Context appContext = getInstrumentation().getTargetContext().getAp plicationContext();
93 getInstrumentation().runOnMainSync(new Runnable() {
94 @Override
95 public void run() {
96 mBrowserContext = createAwBrowserContextOnUiThread(prefs, appCon text);
97 }
98 });
99 } 71 }
100 72
101 protected AwBrowserContext createAwBrowserContextOnUiThread( 73 protected AwBrowserContext createAwBrowserContextOnUiThread(
102 InMemorySharedPreferences prefs, Context appContext) { 74 InMemorySharedPreferences prefs, Context appContext) {
103 return new AwBrowserContext(prefs, appContext); 75 return mTestCommon.createAwBrowserContextOnUiThread(prefs, appContext);
104 } 76 }
105 77
106 protected void startBrowserProcess() throws Exception { 78 protected void startBrowserProcess() throws Exception {
107 // The Activity must be launched in order for proper webview statics to be setup. 79 mTestCommon.startBrowserProcess();
108 getActivity();
109 getInstrumentation().runOnMainSync(new Runnable() {
110 @Override
111 public void run() {
112 AwBrowserProcess.start();
113 }
114 });
115 } 80 }
116 81
117 /** 82 /**
118 * Override this to return false if the test doesn't want to create an AwBro wserContext 83 * Override this to return false if the test doesn't want to create an
119 * automatically. 84 * AwBrowserContext automatically.
120 */ 85 */
121 protected boolean needsAwBrowserContextCreated() { 86 protected boolean needsAwBrowserContextCreated() {
122 return true; 87 return mTestCommon.needsAwBrowserContextCreated();
123 } 88 }
124 89
125 /** 90 /**
126 * Override this to return false if the test doesn't want the browser startu p sequence to 91 * Override this to return false if the test doesn't want the browser
127 * be run automatically. 92 * startup sequence to be run automatically.
128 * @return Whether the instrumentation test requires the browser process to already be started. 93 *
94 * @return Whether the instrumentation test requires the browser process to
95 * already be started.
129 */ 96 */
130 protected boolean needsBrowserProcessStarted() { 97 protected boolean needsBrowserProcessStarted() {
131 return true; 98 return mTestCommon.needsBrowserProcessStarted();
132 } 99 }
133 100
134 /** 101 /**
135 * Runs a {@link Callable} on the main thread, blocking until it is 102 * Runs a {@link Callable} on the main thread, blocking until it is
136 * complete, and returns the result. Calls 103 * complete, and returns the result. Calls
137 * {@link Instrumentation#waitForIdleSync()} first to help avoid certain 104 * {@link Instrumentation#waitForIdleSync()} first to help avoid certain
138 * race conditions. 105 * race conditions.
139 * 106 *
140 * @param <R> Type of result to return 107 * @param <R> Type of result to return
141 */ 108 */
142 public <R> R runTestOnUiThreadAndGetResult(Callable<R> callable) 109 public <R> R runTestOnUiThreadAndGetResult(Callable<R> callable)
143 throws Exception { 110 throws Exception {
144 FutureTask<R> task = new FutureTask<R>(callable); 111 return mTestCommon.runTestOnUiThreadAndGetResult(callable);
145 getInstrumentation().runOnMainSync(task);
146 return task.get();
147 } 112 }
148 113
149 public void enableJavaScriptOnUiThread(final AwContents awContents) { 114 public void enableJavaScriptOnUiThread(final AwContents awContents) {
150 getInstrumentation().runOnMainSync(new Runnable() { 115 mTestCommon.enableJavaScriptOnUiThread(awContents);
151 @Override
152 public void run() {
153 awContents.getSettings().setJavaScriptEnabled(true);
154 }
155 });
156 } 116 }
157 117
158 public void setNetworkAvailableOnUiThread(final AwContents awContents, 118 public void setNetworkAvailableOnUiThread(final AwContents awContents,
159 final boolean networkUp) { 119 final boolean networkUp) {
160 getInstrumentation().runOnMainSync(new Runnable() { 120 mTestCommon.setNetworkAvailableOnUiThread(awContents, networkUp);
161 @Override
162 public void run() {
163 awContents.setNetworkAvailable(networkUp);
164 }
165 });
166 } 121 }
167 122
168 /** 123 /**
169 * Loads url on the UI thread and blocks until onPageFinished is called. 124 * Loads url on the UI thread and blocks until onPageFinished is called.
170 */ 125 */
171 public void loadUrlSync(final AwContents awContents, 126 public void loadUrlSync(final AwContents awContents,
172 CallbackHelper onPageFinishedHelper, 127 CallbackHelper onPageFinishedHelper,
173 final String url) throws Exception { 128 final String url) throws Exception {
174 loadUrlSync(awContents, onPageFinishedHelper, url, null); 129 mTestCommon.loadUrlSync(awContents, onPageFinishedHelper, url);
175 } 130 }
176 131
177 public void loadUrlSync(final AwContents awContents, 132 public void loadUrlSync(final AwContents awContents,
178 CallbackHelper onPageFinishedHelper, 133 CallbackHelper onPageFinishedHelper,
179 final String url, 134 final String url,
180 final Map<String, String> extraHeaders) throws Exception { 135 final Map<String, String> extraHeaders) throws Exception {
181 int currentCallCount = onPageFinishedHelper.getCallCount(); 136 mTestCommon.loadUrlSync(awContents, onPageFinishedHelper, url, extraHead ers);
182 loadUrlAsync(awContents, url, extraHeaders);
183 onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_M S,
184 TimeUnit.MILLISECONDS);
185 } 137 }
186 138
187 public void loadUrlSyncAndExpectError(final AwContents awContents, 139 public void loadUrlSyncAndExpectError(final AwContents awContents,
188 CallbackHelper onPageFinishedHelper, 140 CallbackHelper onPageFinishedHelper,
189 CallbackHelper onReceivedErrorHelper, 141 CallbackHelper onReceivedErrorHelper,
190 final String url) throws Exception { 142 final String url) throws Exception {
191 int onErrorCallCount = onReceivedErrorHelper.getCallCount(); 143 mTestCommon.loadUrlSyncAndExpectError(
192 int onFinishedCallCount = onPageFinishedHelper.getCallCount(); 144 awContents, onPageFinishedHelper, onReceivedErrorHelper, url);
193 loadUrlAsync(awContents, url);
194 onReceivedErrorHelper.waitForCallback(onErrorCallCount, 1, WAIT_TIMEOUT_ MS,
195 TimeUnit.MILLISECONDS);
196 onPageFinishedHelper.waitForCallback(onFinishedCallCount, 1, WAIT_TIMEOU T_MS,
197 TimeUnit.MILLISECONDS);
198 } 145 }
199 146
200 /** 147 /**
201 * Loads url on the UI thread but does not block. 148 * Loads url on the UI thread but does not block.
202 */ 149 */
203 public void loadUrlAsync(final AwContents awContents, 150 public void loadUrlAsync(final AwContents awContents,
204 final String url) throws Exception { 151 final String url) throws Exception {
205 loadUrlAsync(awContents, url, null); 152 mTestCommon.loadUrlAsync(awContents, url);
206 } 153 }
207 154
208 public void loadUrlAsync(final AwContents awContents, 155 public void loadUrlAsync(final AwContents awContents,
209 final String url, 156 final String url,
210 final Map<String, String> extraHeaders) { 157 final Map<String, String> extraHeaders) {
211 getInstrumentation().runOnMainSync(new Runnable() { 158 mTestCommon.loadUrlAsync(awContents, url, extraHeaders);
212 @Override
213 public void run() {
214 awContents.loadUrl(url, extraHeaders);
215 }
216 });
217 } 159 }
218 160
219 /** 161 /**
220 * Posts url on the UI thread and blocks until onPageFinished is called. 162 * Posts url on the UI thread and blocks until onPageFinished is called.
221 */ 163 */
222 public void postUrlSync(final AwContents awContents, 164 public void postUrlSync(final AwContents awContents,
223 CallbackHelper onPageFinishedHelper, final String url, 165 CallbackHelper onPageFinishedHelper, final String url,
224 byte[] postData) throws Exception { 166 byte[] postData) throws Exception {
225 int currentCallCount = onPageFinishedHelper.getCallCount(); 167 mTestCommon.postUrlSync(awContents, onPageFinishedHelper, url, postData) ;
226 postUrlAsync(awContents, url, postData);
227 onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_M S,
228 TimeUnit.MILLISECONDS);
229 } 168 }
230 169
231 /** 170 /**
232 * Loads url on the UI thread but does not block. 171 * Loads url on the UI thread but does not block.
233 */ 172 */
234 public void postUrlAsync(final AwContents awContents, 173 public void postUrlAsync(final AwContents awContents,
235 final String url, byte[] postData) throws Exception { 174 final String url, byte[] postData) throws Exception {
236 class PostUrl implements Runnable { 175 mTestCommon.postUrlAsync(awContents, url, postData);
237 byte[] mPostData;
238 public PostUrl(byte[] postData) {
239 mPostData = postData;
240 }
241 @Override
242 public void run() {
243 awContents.postUrl(url, mPostData);
244 }
245 }
246 getInstrumentation().runOnMainSync(new PostUrl(postData));
247 } 176 }
248 177
249 /** 178 /**
250 * Loads data on the UI thread and blocks until onPageFinished is called. 179 * Loads data on the UI thread and blocks until onPageFinished is called.
251 */ 180 */
252 public void loadDataSync(final AwContents awContents, 181 public void loadDataSync(final AwContents awContents,
253 CallbackHelper onPageFinishedHelper, 182 CallbackHelper onPageFinishedHelper,
254 final String data, final String mimeType, 183 final String data, final String mimeType,
255 final boolean isBase64Encoded) throws Exception { 184 final boolean isBase64Encoded) throws Exception {
256 int currentCallCount = onPageFinishedHelper.getCallCount(); 185 mTestCommon.loadDataSync(awContents, onPageFinishedHelper, data, mimeTyp e, isBase64Encoded);
257 loadDataAsync(awContents, data, mimeType, isBase64Encoded);
258 onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_M S,
259 TimeUnit.MILLISECONDS);
260 } 186 }
261 187
262 public void loadDataSyncWithCharset(final AwContents awContents, 188 public void loadDataSyncWithCharset(final AwContents awContents,
263 CallbackHelper onPageFinishedHelper, 189 CallbackHelper onPageFinishedHelper,
264 final String data, final String mimeType, 190 final String data, final String mimeType,
265 final boolean isBase64Encoded, final String charset) 191 final boolean isBase64Encoded, final String charset)
266 throws Exception { 192 throws Exception {
267 int currentCallCount = onPageFinishedHelper.getCallCount(); 193 mTestCommon.loadDataSyncWithCharset(
268 getInstrumentation().runOnMainSync(new Runnable() { 194 awContents, onPageFinishedHelper, data, mimeType, isBase64Encode d, charset);
269 @Override
270 public void run() {
271 awContents.loadUrl(LoadUrlParams.createLoadDataParams(
272 data, mimeType, isBase64Encoded, charset));
273 }
274 });
275 onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_M S,
276 TimeUnit.MILLISECONDS);
277 } 195 }
278 196
279 /** 197 /**
280 * Loads data on the UI thread but does not block. 198 * Loads data on the UI thread but does not block.
281 */ 199 */
282 public void loadDataAsync(final AwContents awContents, final String data, 200 public void loadDataAsync(final AwContents awContents, final String data,
283 final String mimeType, final boolean isBase64Encoded) 201 final String mimeType, final boolean isBase64Encoded)
284 throws Exception { 202 throws Exception {
285 getInstrumentation().runOnMainSync(new Runnable() { 203 mTestCommon.loadDataAsync(awContents, data, mimeType, isBase64Encoded);
286 @Override
287 public void run() {
288 awContents.loadData(data, mimeType, isBase64Encoded ? "base64" : null);
289 }
290 });
291 } 204 }
292 205
293 public void loadDataWithBaseUrlSync(final AwContents awContents, 206 public void loadDataWithBaseUrlSync(final AwContents awContents,
294 CallbackHelper onPageFinishedHelper, final String data, final String mimeType, 207 CallbackHelper onPageFinishedHelper, final String data, final String mimeType,
295 final boolean isBase64Encoded, final String baseUrl, 208 final boolean isBase64Encoded, final String baseUrl,
296 final String historyUrl) throws Throwable { 209 final String historyUrl) throws Throwable {
297 int currentCallCount = onPageFinishedHelper.getCallCount(); 210 mTestCommon.loadDataWithBaseUrlSync(awContents, onPageFinishedHelper, da ta, mimeType,
298 loadDataWithBaseUrlAsync(awContents, data, mimeType, isBase64Encoded, ba seUrl, historyUrl); 211 isBase64Encoded, baseUrl, historyUrl);
299 onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_M S,
300 TimeUnit.MILLISECONDS);
301 } 212 }
302 213
303 public void loadDataWithBaseUrlAsync(final AwContents awContents, 214 public void loadDataWithBaseUrlAsync(final AwContents awContents,
304 final String data, final String mimeType, final boolean isBase64Enco ded, 215 final String data, final String mimeType, final boolean isBase64Enco ded,
305 final String baseUrl, final String historyUrl) throws Throwable { 216 final String baseUrl, final String historyUrl) throws Throwable {
306 runTestOnUiThread(new Runnable() { 217 mTestCommon.loadDataWithBaseUrlAsync(
307 @Override 218 awContents, data, mimeType, isBase64Encoded, baseUrl, historyUrl );
308 public void run() {
309 awContents.loadDataWithBaseURL(
310 baseUrl, data, mimeType, isBase64Encoded ? "base64" : nu ll, historyUrl);
311 }
312 });
313 } 219 }
314 220
315 /** 221 /**
316 * Reloads the current page synchronously. 222 * Reloads the current page synchronously.
317 */ 223 */
318 public void reloadSync(final AwContents awContents, 224 public void reloadSync(final AwContents awContents,
319 CallbackHelper onPageFinishedHelper) throws Exception { 225 CallbackHelper onPageFinishedHelper) throws Exception {
320 int currentCallCount = onPageFinishedHelper.getCallCount(); 226 mTestCommon.reloadSync(awContents, onPageFinishedHelper);
321 getInstrumentation().runOnMainSync(new Runnable() {
322 @Override
323 public void run() {
324 awContents.getNavigationController().reload(true);
325 }
326 });
327 onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_M S,
328 TimeUnit.MILLISECONDS);
329 } 227 }
330 228
331 /** 229 /**
332 * Stops loading on the UI thread. 230 * Stops loading on the UI thread.
333 */ 231 */
334 public void stopLoading(final AwContents awContents) { 232 public void stopLoading(final AwContents awContents) {
335 getInstrumentation().runOnMainSync(new Runnable() { 233 mTestCommon.stopLoading(awContents);
336 @Override
337 public void run() {
338 awContents.stopLoading();
339 }
340 });
341 } 234 }
342 235
343 public void waitForVisualStateCallback(final AwContents awContents) throws E xception { 236 public void waitForVisualStateCallback(final AwContents awContents) throws E xception {
344 final CallbackHelper ch = new CallbackHelper(); 237 mTestCommon.waitForVisualStateCallback(awContents);
345 final int chCount = ch.getCallCount();
346 getInstrumentation().runOnMainSync(new Runnable() {
347 @Override
348 public void run() {
349 final long requestId = 666;
350 awContents.insertVisualStateCallback(requestId,
351 new AwContents.VisualStateCallback() {
352 @Override
353 public void onComplete(long id) {
354 assertEquals(requestId, id);
355 ch.notifyCalled();
356 }
357 });
358 }
359 });
360 ch.waitForCallback(chCount);
361 } 238 }
362 239
363 public void insertVisualStateCallbackOnUIThread(final AwContents awContents, 240 public void insertVisualStateCallbackOnUIThread(final AwContents awContents,
364 final long requestId, final AwContents.VisualStateCallback callback) { 241 final long requestId, final AwContents.VisualStateCallback callback) {
365 getInstrumentation().runOnMainSync(new Runnable() { 242 mTestCommon.insertVisualStateCallbackOnUIThread(awContents, requestId, c allback);
366 @Override
367 public void run() {
368 awContents.insertVisualStateCallback(requestId, callback);
369 }
370 });
371 } 243 }
372 244
373 // Waits for the pixel at the center of AwContents to color up into expected Color. 245 // Waits for the pixel at the center of AwContents to color up into
374 // Note that this is a stricter condition that waiting for a visual state ca llback, 246 // expectedColor.
375 // as visual state callback only indicates that *something* has appeared in WebView. 247 // Note that this is a stricter condition that waiting for a visual state
248 // callback,
249 // as visual state callback only indicates that *something* has appeared in
250 // WebView.
376 public void waitForPixelColorAtCenterOfView(final AwContents awContents, 251 public void waitForPixelColorAtCenterOfView(final AwContents awContents,
377 final AwTestContainerView testContainerView, final int expectedColor ) throws Exception { 252 final AwTestContainerView testContainerView, final int expectedColor ) throws Exception {
378 pollUiThread(new Callable<Boolean>() { 253 mTestCommon.waitForPixelColorAtCenterOfView(awContents, testContainerVie w, expectedColor);
379 @Override
380 public Boolean call() throws Exception {
381 return GraphicsTestUtils.getPixelColorAtCenterOfView(awContents, testContainerView)
382 == expectedColor;
383 }
384 });
385 }
386
387 /**
388 * Checks the current test has |clazz| annotation. Note this swallows NoSuch MethodException
389 * and returns false in that case.
390 */
391 private boolean testMethodHasAnnotation(Class<? extends Annotation> clazz) {
392 String testName = getName();
393 Method method = null;
394 try {
395 method = getClass().getMethod(testName);
396 } catch (NoSuchMethodException e) {
397 Log.w(TAG, "Test method name not found.", e);
398 return false;
399 }
400
401 // Cast to AnnotatedElement to work around a compilation failure.
402 // Method.isAnnotationPresent() was removed in Java 8 (which is used by the Android N SDK),
403 // so compilation with Java 7 fails. See crbug.com/608792.
404 return ((AnnotatedElement) method).isAnnotationPresent(clazz);
405 }
406
407 /**
408 * Factory class used in creation of test AwContents instances.
409 *
410 * Test cases can provide subclass instances to the createAwTest* methods in order to create an
411 * AwContents instance with injected test dependencies.
412 */
413 public static class TestDependencyFactory extends AwContents.DependencyFacto ry {
414 public AwTestContainerView createAwTestContainerView(AwTestRunnerActivit y activity,
415 boolean allowHardwareAcceleration) {
416 return new AwTestContainerView(activity, allowHardwareAcceleration);
417 }
418 public AwSettings createAwSettings(Context context, boolean supportsLega cyQuirks) {
419 return new AwSettings(context, false /* isAccessFromFileURLsGrantedB yDefault */,
420 supportsLegacyQuirks, false /* allowEmptyDocumentPersistence */,
421 true /* allowGeolocationOnInsecureOrigins */,
422 false /* doNotUpdateSelectionOnMutatingSelectionRange */);
423 }
424
425 public AwContents createAwContents(AwBrowserContext browserContext, View Group containerView,
426 Context context, InternalAccessDelegate internalAccessAdapter,
427 NativeDrawGLFunctorFactory nativeDrawGLFunctorFactory,
428 AwContentsClient contentsClient, AwSettings settings,
429 DependencyFactory dependencyFactory) {
430 return new AwContents(browserContext, containerView, context, intern alAccessAdapter,
431 nativeDrawGLFunctorFactory, contentsClient, settings, depend encyFactory);
432 }
433 } 254 }
434 255
435 protected TestDependencyFactory createTestDependencyFactory() { 256 protected TestDependencyFactory createTestDependencyFactory() {
436 return new TestDependencyFactory(); 257 return mTestCommon.createTestDependencyFactory();
437 } 258 }
438 259
439 public AwTestContainerView createAwTestContainerView( 260 public AwTestContainerView createAwTestContainerView(
440 final AwContentsClient awContentsClient) { 261 final AwContentsClient awContentsClient) {
441 return createAwTestContainerView(awContentsClient, false, null); 262 return mTestCommon.createAwTestContainerView(awContentsClient);
442 } 263 }
443 264
444 public AwTestContainerView createAwTestContainerView(final AwContentsClient awContentsClient, 265 public AwTestContainerView createAwTestContainerView(final AwContentsClient awContentsClient,
445 boolean supportsLegacyQuirks, final TestDependencyFactory testDepend encyFactory) { 266 boolean supportsLegacyQuirks, final TestDependencyFactory testDepend encyFactory) {
446 AwTestContainerView testContainerView = createDetachedAwTestContainerVie w( 267 return mTestCommon.createAwTestContainerView(
447 awContentsClient, supportsLegacyQuirks, testDependencyFactory); 268 awContentsClient, supportsLegacyQuirks, testDependencyFactory);
448 getActivity().addView(testContainerView);
449 testContainerView.requestFocus();
450 return testContainerView;
451 } 269 }
452 270
453 public AwBrowserContext getAwBrowserContext() { 271 public AwBrowserContext getAwBrowserContext() {
454 return mBrowserContext; 272 return mTestCommon.getAwBrowserContext();
455 } 273 }
456 274
457 public AwTestContainerView createDetachedAwTestContainerView( 275 public AwTestContainerView createDetachedAwTestContainerView(
458 final AwContentsClient awContentsClient) { 276 final AwContentsClient awContentsClient) {
459 return createDetachedAwTestContainerView(awContentsClient, false, null); 277 return mTestCommon.createDetachedAwTestContainerView(awContentsClient);
460 } 278 }
461 279
462 public AwTestContainerView createDetachedAwTestContainerView( 280 public AwTestContainerView createDetachedAwTestContainerView(
463 final AwContentsClient awContentsClient, boolean supportsLegacyQuirk s, 281 final AwContentsClient awContentsClient, boolean supportsLegacyQuirk s,
464 TestDependencyFactory testDependencyFactory) { 282 TestDependencyFactory testDependencyFactory) {
465 if (testDependencyFactory == null) { 283 return mTestCommon.createDetachedAwTestContainerView(
466 testDependencyFactory = createTestDependencyFactory(); 284 awContentsClient, supportsLegacyQuirks, testDependencyFactory);
467 }
468 boolean allowHardwareAcceleration = isHardwareAcceleratedTest();
469 final AwTestContainerView testContainerView =
470 testDependencyFactory.createAwTestContainerView(
471 getActivity(), allowHardwareAcceleration);
472
473 AwSettings awSettings =
474 testDependencyFactory.createAwSettings(getActivity(), supportsLe gacyQuirks);
475 AwContents awContents = testDependencyFactory.createAwContents(mBrowserC ontext,
476 testContainerView, testContainerView.getContext(),
477 testContainerView.getInternalAccessDelegate(),
478 testContainerView.getNativeDrawGLFunctorFactory(), awContentsCli ent, awSettings,
479 testDependencyFactory);
480 testContainerView.initialize(awContents);
481 return testContainerView;
482 } 285 }
483 286
484 protected boolean isHardwareAcceleratedTest() { 287 protected boolean isHardwareAcceleratedTest() {
485 return !testMethodHasAnnotation(DisableHardwareAccelerationForTest.class ); 288 return mTestCommon.isHardwareAcceleratedTest();
486 } 289 }
487 290
488 public AwTestContainerView createAwTestContainerViewOnMainSync( 291 public AwTestContainerView createAwTestContainerViewOnMainSync(
489 final AwContentsClient client) throws Exception { 292 final AwContentsClient client) throws Exception {
490 return createAwTestContainerViewOnMainSync(client, false, null); 293 return mTestCommon.createAwTestContainerViewOnMainSync(client);
491 } 294 }
492 295
493 public AwTestContainerView createAwTestContainerViewOnMainSync( 296 public AwTestContainerView createAwTestContainerViewOnMainSync(
494 final AwContentsClient client, final boolean supportsLegacyQuirks) { 297 final AwContentsClient client, final boolean supportsLegacyQuirks) {
495 return createAwTestContainerViewOnMainSync(client, supportsLegacyQuirks, null); 298 return mTestCommon.createAwTestContainerViewOnMainSync(client, supportsL egacyQuirks);
496 } 299 }
497 300
498 public AwTestContainerView createAwTestContainerViewOnMainSync(final AwConte ntsClient client, 301 public AwTestContainerView createAwTestContainerViewOnMainSync(final AwConte ntsClient client,
499 final boolean supportsLegacyQuirks, final TestDependencyFactory test DependencyFactory) { 302 final boolean supportsLegacyQuirks, final TestDependencyFactory test DependencyFactory) {
500 return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<AwTestC ontainerView>() { 303 return mTestCommon.createAwTestContainerViewOnMainSync(
501 @Override 304 client, supportsLegacyQuirks, testDependencyFactory);
502 public AwTestContainerView call() {
503 return createAwTestContainerView(
504 client, supportsLegacyQuirks, testDependencyFactory);
505 }
506 });
507 } 305 }
508 306
509 public void destroyAwContentsOnMainSync(final AwContents awContents) { 307 public void destroyAwContentsOnMainSync(final AwContents awContents) {
510 if (awContents == null) return; 308 mTestCommon.destroyAwContentsOnMainSync(awContents);
511 getInstrumentation().runOnMainSync(new Runnable() {
512 @Override
513 public void run() {
514 awContents.destroy();
515 }
516 });
517 } 309 }
518 310
519 public String getTitleOnUiThread(final AwContents awContents) throws Excepti on { 311 public String getTitleOnUiThread(final AwContents awContents) throws Excepti on {
520 return runTestOnUiThreadAndGetResult(new Callable<String>() { 312 return mTestCommon.getTitleOnUiThread(awContents);
521 @Override
522 public String call() throws Exception {
523 return awContents.getTitle();
524 }
525 });
526 } 313 }
527 314
528 public AwSettings getAwSettingsOnUiThread( 315 public AwSettings getAwSettingsOnUiThread(
529 final AwContents awContents) throws Exception { 316 final AwContents awContents) throws Exception {
530 return runTestOnUiThreadAndGetResult(new Callable<AwSettings>() { 317 return mTestCommon.getAwSettingsOnUiThread(awContents);
531 @Override
532 public AwSettings call() throws Exception {
533 return awContents.getSettings();
534 }
535 });
536 } 318 }
537 319
538 /** 320 /**
539 * Verify double quotes in both sides of the raw string. Strip the double qu otes and 321 * Verify double quotes in both sides of the raw string. Strip the double
540 * returns rest of the string. 322 * quotes and returns rest of the string.
541 */ 323 */
542 protected String maybeStripDoubleQuotes(String raw) { 324 protected String maybeStripDoubleQuotes(String raw) {
543 assertNotNull(raw); 325 return mTestCommon.maybeStripDoubleQuotes(raw);
544 Matcher m = MAYBE_QUOTED_STRING.matcher(raw);
545 assertTrue(m.matches());
546 return m.group(2);
547 } 326 }
548 327
549 /** 328 /**
550 * Executes the given snippet of JavaScript code within the given ContentVie w. Returns the 329 * Executes the given snippet of JavaScript code within the given
551 * result of its execution in JSON format. 330 * ContentView. Returns the result of its execution in JSON format.
552 */ 331 */
553 public String executeJavaScriptAndWaitForResult(final AwContents awContents, 332 public String executeJavaScriptAndWaitForResult(final AwContents awContents,
554 TestAwContentsClient viewClient, final String code) throws Exception { 333 TestAwContentsClient viewClient, final String code) throws Exception {
555 return JSUtils.executeJavaScriptAndWaitForResult(this, awContents, 334 return mTestCommon.executeJavaScriptAndWaitForResult(awContents, viewCli ent, code);
556 viewClient.getOnEvaluateJavaScriptResultHelper(),
557 code);
558 } 335 }
559 336
560 /** 337 /**
561 * Executes JavaScript code within the given ContentView to get the text con tent in 338 * Executes JavaScript code within the given ContentView to get the text con tent in
562 * document body. Returns the result string without double quotes. 339 * document body. Returns the result string without double quotes.
563 */ 340 */
564 protected String getJavaScriptResultBodyTextContent( 341 protected String getJavaScriptResultBodyTextContent(
565 final AwContents awContents, final TestAwContentsClient viewClient) throws Exception { 342 final AwContents awContents, final TestAwContentsClient viewClient) throws Exception {
566 String raw = executeJavaScriptAndWaitForResult( 343 return mTestCommon.getJavaScriptResultBodyTextContent(awContents, viewCl ient);
567 awContents, viewClient, "document.body.textContent");
568 return maybeStripDoubleQuotes(raw);
569 } 344 }
570 345
571 /** 346 /**
572 * Wrapper around CriteriaHelper.pollInstrumentationThread. This uses AwTest Base-specifc 347 * Wrapper around CriteriaHelper.pollInstrumentationThread. This uses AwTest Base-specifc
573 * timeouts and treats timeouts and exceptions as test failures automaticall y. 348 * timeouts and treats timeouts and exceptions as test failures automaticall y.
574 */ 349 */
575 public static void pollInstrumentationThread(final Callable<Boolean> callabl e) 350 public static void pollInstrumentationThread(final Callable<Boolean> callabl e)
576 throws Exception { 351 throws Exception {
577 CriteriaHelper.pollInstrumentationThread(new Criteria() { 352 AwTestCommon.pollInstrumentationThread(callable);
578 @Override
579 public boolean isSatisfied() {
580 try {
581 return callable.call();
582 } catch (Throwable e) {
583 Log.e(TAG, "Exception while polling.", e);
584 return false;
585 }
586 }
587 }, WAIT_TIMEOUT_MS, CHECK_INTERVAL);
588 } 353 }
589 354
590 /** 355 /**
591 * Wrapper around {@link AwTestBase#poll()} but runs the callable on the UI thread. 356 * Wrapper around {@link AwTestBase#poll()} but runs the callable on the UI
357 * thread.
592 */ 358 */
593 public void pollUiThread(final Callable<Boolean> callable) throws Exception { 359 public void pollUiThread(final Callable<Boolean> callable) throws Exception {
594 pollInstrumentationThread(new Callable<Boolean>() { 360 mTestCommon.pollUiThread(callable);
595 @Override
596 public Boolean call() throws Exception {
597 return runTestOnUiThreadAndGetResult(callable);
598 }
599 });
600 } 361 }
601 362
602 /** 363 /**
603 * Clears the resource cache. Note that the cache is per-application, so thi s will clear the 364 * Clears the resource cache. Note that the cache is per-application, so
604 * cache for all WebViews used. 365 * this will clear the cache for all WebViews used.
605 */ 366 */
606 public void clearCacheOnUiThread( 367 public void clearCacheOnUiThread(
607 final AwContents awContents, 368 final AwContents awContents,
608 final boolean includeDiskFiles) throws Exception { 369 final boolean includeDiskFiles) throws Exception {
609 getInstrumentation().runOnMainSync(new Runnable() { 370 mTestCommon.clearCacheOnUiThread(awContents, includeDiskFiles);
610 @Override
611 public void run() {
612 awContents.clearCache(includeDiskFiles);
613 }
614 });
615 } 371 }
616 372
617 /** 373 /**
618 * Returns pure page scale. 374 * Returns pure page scale.
619 */ 375 */
620 public float getScaleOnUiThread(final AwContents awContents) throws Exceptio n { 376 public float getScaleOnUiThread(final AwContents awContents) throws Exceptio n {
621 return runTestOnUiThreadAndGetResult(new Callable<Float>() { 377 return mTestCommon.getScaleOnUiThread(awContents);
622 @Override
623 public Float call() throws Exception {
624 return awContents.getPageScaleFactor();
625 }
626 });
627 } 378 }
628 379
629 /** 380 /**
630 * Returns page scale multiplied by the screen density. 381 * Returns page scale multiplied by the screen density.
631 */ 382 */
632 public float getPixelScaleOnUiThread(final AwContents awContents) throws Exc eption { 383 public float getPixelScaleOnUiThread(final AwContents awContents) throws Exc eption {
633 return runTestOnUiThreadAndGetResult(new Callable<Float>() { 384 return mTestCommon.getPixelScaleOnUiThread(awContents);
634 @Override
635 public Float call() throws Exception {
636 return awContents.getScale();
637 }
638 });
639 } 385 }
640 386
641 /** 387 /**
642 * Returns whether a user can zoom the page in. 388 * Returns whether a user can zoom the page in.
643 */ 389 */
644 public boolean canZoomInOnUiThread(final AwContents awContents) throws Excep tion { 390 public boolean canZoomInOnUiThread(final AwContents awContents) throws Excep tion {
645 return runTestOnUiThreadAndGetResult(new Callable<Boolean>() { 391 return mTestCommon.canZoomInOnUiThread(awContents);
646 @Override
647 public Boolean call() throws Exception {
648 return awContents.canZoomIn();
649 }
650 });
651 } 392 }
652 393
653 /** 394 /**
654 * Returns whether a user can zoom the page out. 395 * Returns whether a user can zoom the page out.
655 */ 396 */
656 public boolean canZoomOutOnUiThread(final AwContents awContents) throws Exce ption { 397 public boolean canZoomOutOnUiThread(final AwContents awContents) throws Exce ption {
657 return runTestOnUiThreadAndGetResult(new Callable<Boolean>() { 398 return mTestCommon.canZoomOutOnUiThread(awContents);
658 @Override
659 public Boolean call() throws Exception {
660 return awContents.canZoomOut();
661 }
662 });
663 } 399 }
664 400
665 public void killRenderProcessOnUiThreadAsync(final AwContents awContents) th rows Exception { 401 public void killRenderProcessOnUiThreadAsync(final AwContents awContents) th rows Exception {
666 getInstrumentation().runOnMainSync(new Runnable() { 402 mTestCommon.killRenderProcessOnUiThreadAsync(awContents);
667 @Override
668 public void run() {
669 awContents.killRenderProcess();
670 }
671 });
672 } 403 }
673 404
674 /** 405 /**
675 * Loads the main html then triggers the popup window. 406 * Loads the main html then triggers the popup window.
676 */ 407 */
677 public void triggerPopup(final AwContents parentAwContents, 408 public void triggerPopup(final AwContents parentAwContents,
678 TestAwContentsClient parentAwContentsClient, TestWebServer testWebSe rver, 409 TestAwContentsClient parentAwContentsClient, TestWebServer testWebSe rver,
679 String mainHtml, String popupHtml, String popupPath, String triggerS cript) 410 String mainHtml, String popupHtml, String popupPath, String triggerS cript)
680 throws Exception { 411 throws Exception {
681 enableJavaScriptOnUiThread(parentAwContents); 412 mTestCommon.triggerPopup(parentAwContents, parentAwContentsClient, testW ebServer, mainHtml,
682 getInstrumentation().runOnMainSync(new Runnable() { 413 popupHtml, popupPath, triggerScript);
683 @Override
684 public void run() {
685 parentAwContents.getSettings().setSupportMultipleWindows(true);
686 parentAwContents.getSettings().setJavaScriptCanOpenWindowsAutoma tically(true);
687 }
688 });
689
690 final String parentUrl = testWebServer.setResponse("/popupParent.html", mainHtml, null);
691 if (popupHtml != null) {
692 testWebServer.setResponse(popupPath, popupHtml, null);
693 } else {
694 testWebServer.setResponseWithNoContentStatus(popupPath);
695 }
696
697 parentAwContentsClient.getOnCreateWindowHelper().setReturnValue(true);
698 loadUrlSync(parentAwContents, parentAwContentsClient.getOnPageFinishedHe lper(), parentUrl);
699
700 TestAwContentsClient.OnCreateWindowHelper onCreateWindowHelper =
701 parentAwContentsClient.getOnCreateWindowHelper();
702 int currentCallCount = onCreateWindowHelper.getCallCount();
703 parentAwContents.evaluateJavaScriptForTests(triggerScript, null);
704 onCreateWindowHelper.waitForCallback(
705 currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
706 } 414 }
707 415
708 /** 416 /**
417 * Supplies the popup window with AwContents then waits for the popup window
418 * to finish loading.
419 */
420 public PopupInfo connectPendingPopup(final AwContents parentAwContents) thro ws Exception {
421 return mTestCommon.connectPendingPopup(parentAwContents);
422 }
423
424 /**
425 * Factory class used in creation of test AwContents instances. Test cases
426 * can provide subclass instances to the createAwTest* methods in order to
427 * create an AwContents instance with injected test dependencies.
428 */
429 public static class TestDependencyFactory extends AwContents.DependencyFacto ry {
430 public AwTestContainerView createAwTestContainerView(
431 AwTestRunnerActivity activity, boolean allowHardwareAcceleration ) {
432 return new AwTestContainerView(activity, allowHardwareAcceleration);
433 }
434
435 public AwSettings createAwSettings(Context context, boolean supportsLega cyQuirks) {
436 return new AwSettings(context, false /* isAccessFromFileURLsGrantedB yDefault */,
437 supportsLegacyQuirks, false /* allowEmptyDocumentPersistence */,
438 true /* allowGeolocationOnInsecureOrigins */,
439 false /* doNotUpdateSelectionOnMutatingSelectionRange */);
440 }
441
442 public AwContents createAwContents(AwBrowserContext browserContext, View Group containerView,
443 Context context, InternalAccessDelegate internalAccessAdapter,
444 NativeDrawGLFunctorFactory nativeDrawGLFunctorFactory,
445 AwContentsClient contentsClient, AwSettings settings,
446 DependencyFactory dependencyFactory) {
447 return new AwContents(browserContext, containerView, context, intern alAccessAdapter,
448 nativeDrawGLFunctorFactory, contentsClient, settings, depend encyFactory);
449 }
450 }
451
452 /**
709 * POD object for holding references to helper objects of a popup window. 453 * POD object for holding references to helper objects of a popup window.
710 */ 454 */
711 public static class PopupInfo { 455 public static class PopupInfo {
712 public final TestAwContentsClient popupContentsClient; 456 public final TestAwContentsClient popupContentsClient;
713 public final AwTestContainerView popupContainerView; 457 public final AwTestContainerView popupContainerView;
714 public final AwContents popupContents; 458 public final AwContents popupContents;
459
715 public PopupInfo(TestAwContentsClient popupContentsClient, 460 public PopupInfo(TestAwContentsClient popupContentsClient,
716 AwTestContainerView popupContainerView, AwContents popupContents ) { 461 AwTestContainerView popupContainerView, AwContents popupContents ) {
717 this.popupContentsClient = popupContentsClient; 462 this.popupContentsClient = popupContentsClient;
718 this.popupContainerView = popupContainerView; 463 this.popupContainerView = popupContainerView;
719 this.popupContents = popupContents; 464 this.popupContents = popupContents;
720 } 465 }
721 } 466 }
722 467
723 /** 468 /**
724 * Supplies the popup window with AwContents then waits for the popup window to finish loading. 469 * Checks the current test has |clazz| annotation. Note this swallows
470 * NoSuchMethodException and returns false in that case.
725 */ 471 */
726 public PopupInfo connectPendingPopup(final AwContents parentAwContents) thro ws Exception { 472 @Override
727 TestAwContentsClient popupContentsClient; 473 public boolean testMethodHasAnnotation(Class<? extends Annotation> clazz) {
728 AwTestContainerView popupContainerView; 474 String testName = getName();
729 final AwContents popupContents; 475 Method method = null;
730 popupContentsClient = new TestAwContentsClient(); 476 try {
731 popupContainerView = createAwTestContainerViewOnMainSync(popupContentsCl ient); 477 method = getClass().getMethod(testName);
732 popupContents = popupContainerView.getAwContents(); 478 } catch (NoSuchMethodException e) {
733 enableJavaScriptOnUiThread(popupContents); 479 Log.w(TAG, "Test method name not found.", e);
480 return false;
481 }
734 482
735 getInstrumentation().runOnMainSync(new Runnable() { 483 // Cast to AnnotatedElement to work around a compilation failure.
736 @Override 484 // Method.isAnnotationPresent() was removed in Java 8 (which is used by
737 public void run() { 485 // the Android N SDK),
738 parentAwContents.supplyContentsForPopup(popupContents); 486 // so compilation with Java 7 fails. See crbug.com/608792.
739 } 487 return ((AnnotatedElement) method).isAnnotationPresent(clazz);
740 }); 488 }
741 489
742 OnPageFinishedHelper onPageFinishedHelper = popupContentsClient.getOnPag eFinishedHelper(); 490 @Override
743 int finishCallCount = onPageFinishedHelper.getCallCount(); 491 public void runOnUiThread(Runnable runnable) {
744 TestAwContentsClient.OnReceivedTitleHelper onReceivedTitleHelper = 492 getInstrumentation().runOnMainSync(runnable);
745 popupContentsClient.getOnReceivedTitleHelper();
746 int titleCallCount = onReceivedTitleHelper.getCallCount();
747
748 onPageFinishedHelper.waitForCallback(finishCallCount, 1, WAIT_TIMEOUT_MS ,
749 TimeUnit.MILLISECONDS);
750 onReceivedTitleHelper.waitForCallback(titleCallCount, 1, WAIT_TIMEOUT_MS ,
751 TimeUnit.MILLISECONDS);
752
753 return new PopupInfo(popupContentsClient, popupContainerView, popupConte nts);
754 } 493 }
755 } 494 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698