LayoutTestsExecutor.java revision 3c90952036a5ff7ddb2946c643f1a0bf1c31d53a
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dumprendertree2;
18
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.net.http.SslError;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Message;
29import android.os.Messenger;
30import android.os.PowerManager;
31import android.os.PowerManager.WakeLock;
32import android.os.Process;
33import android.os.RemoteException;
34import android.util.Log;
35import android.view.Window;
36import android.webkit.ConsoleMessage;
37import android.webkit.GeolocationPermissions;
38import android.webkit.HttpAuthHandler;
39import android.webkit.JsPromptResult;
40import android.webkit.JsResult;
41import android.webkit.SslErrorHandler;
42import android.webkit.WebChromeClient;
43import android.webkit.WebSettings;
44import android.webkit.WebSettingsClassic;
45import android.webkit.WebStorage;
46import android.webkit.WebStorage.QuotaUpdater;
47import android.webkit.WebView;
48import android.webkit.WebViewClassic;
49import android.webkit.WebViewClient;
50
51import java.lang.Thread.UncaughtExceptionHandler;
52import java.util.HashMap;
53import java.util.Iterator;
54import java.util.List;
55import java.util.Map;
56
57/**
58 * This activity executes the test. It contains WebView and logic of LayoutTestController
59 * functions. It runs in a separate process and sends the results of running the test
60 * to ManagerService. The reason why is to handle crashing (test that crashes brings down
61 * whole process with it).
62 */
63public class LayoutTestsExecutor extends Activity {
64
65    private enum CurrentState {
66        IDLE,
67        RENDERING_PAGE,
68        WAITING_FOR_ASYNCHRONOUS_TEST,
69        OBTAINING_RESULT;
70
71        public boolean isRunningState() {
72            return this == CurrentState.RENDERING_PAGE ||
73                    this == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
74        }
75    }
76
77    private static final String LOG_TAG = "LayoutTestsExecutor";
78
79    public static final String EXTRA_TESTS_FILE = "TestsList";
80    public static final String EXTRA_TEST_INDEX = "TestIndex";
81
82    private static final int MSG_ACTUAL_RESULT_OBTAINED = 0;
83    private static final int MSG_TEST_TIMED_OUT = 1;
84
85    private static final int DEFAULT_TIME_OUT_MS = 15 * 1000;
86
87    /** A list of tests that remain to run since last crash */
88    private List<String> mTestsList;
89
90    /**
91     * This is a number of currently running test. It is 0-based and doesn't reset after
92     * the crash. Initial index is passed to LayoutTestsExecuter in the intent that starts
93     * it.
94     */
95    private int mCurrentTestIndex;
96
97    /** The total number of tests to run, doesn't reset after crash */
98    private int mTotalTestCount;
99
100    private WebView mCurrentWebView;
101    private String mCurrentTestRelativePath;
102    private String mCurrentTestUri;
103    private CurrentState mCurrentState = CurrentState.IDLE;
104
105    private boolean mCurrentTestTimedOut;
106    private AbstractResult mCurrentResult;
107    private AdditionalTextOutput mCurrentAdditionalTextOutput;
108
109    private LayoutTestController mLayoutTestController = new LayoutTestController(this);
110    private boolean mCanOpenWindows;
111    private boolean mDumpDatabaseCallbacks;
112    private boolean mIsGeolocationPermissionSet;
113    private boolean mGeolocationPermission;
114    private Map<GeolocationPermissions.Callback, String> mPendingGeolocationPermissionCallbacks;
115
116    private EventSender mEventSender = new EventSender();
117
118    private WakeLock mScreenDimLock;
119
120    /** COMMUNICATION WITH ManagerService */
121
122    private Messenger mManagerServiceMessenger;
123
124    private ServiceConnection mServiceConnection = new ServiceConnection() {
125
126        @Override
127        public void onServiceConnected(ComponentName name, IBinder service) {
128            mManagerServiceMessenger = new Messenger(service);
129            startTests();
130        }
131
132        @Override
133        public void onServiceDisconnected(ComponentName name) {
134            /** TODO */
135        }
136    };
137
138    private final Handler mResultHandler = new Handler() {
139        @Override
140        public void handleMessage(Message msg) {
141            switch (msg.what) {
142                case MSG_ACTUAL_RESULT_OBTAINED:
143                    onActualResultsObtained();
144                    break;
145
146                case MSG_TEST_TIMED_OUT:
147                    onTestTimedOut();
148                    break;
149
150                default:
151                    break;
152            }
153        }
154    };
155
156    /** WEBVIEW CONFIGURATION */
157
158    private WebViewClient mWebViewClient = new WebViewClient() {
159        @Override
160        public void onPageFinished(WebView view, String url) {
161            /** Some tests fire up many page loads, we don't want to detect them */
162            if (!url.equals(mCurrentTestUri)) {
163                return;
164            }
165
166            if (mCurrentState == CurrentState.RENDERING_PAGE) {
167                onTestFinished();
168            }
169        }
170
171         @Override
172         public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
173                 String host, String realm) {
174             if (handler.useHttpAuthUsernamePassword() && view != null) {
175                 String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
176                 if (credentials != null && credentials.length == 2) {
177                     handler.proceed(credentials[0], credentials[1]);
178                     return;
179                 }
180             }
181             handler.cancel();
182         }
183
184         @Override
185         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
186             // We ignore SSL errors. In particular, the certificate used by the LayoutTests server
187             // produces an error as it lacks a CN field.
188             handler.proceed();
189         }
190    };
191
192    private WebChromeClient mWebChromeClient = new WebChromeClient() {
193        @Override
194        public void onExceededDatabaseQuota(String url, String databaseIdentifier,
195                long currentQuota, long estimatedSize, long totalUsedQuota,
196                QuotaUpdater quotaUpdater) {
197            /** TODO: This should be recorded as part of the text result */
198            /** TODO: The quota should also probably be reset somehow for every test? */
199            if (mDumpDatabaseCallbacks) {
200                getCurrentAdditionalTextOutput().appendExceededDbQuotaMessage(url,
201                        databaseIdentifier);
202            }
203            quotaUpdater.updateQuota(currentQuota + 5 * 1024 * 1024);
204        }
205
206        @Override
207        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
208            getCurrentAdditionalTextOutput().appendJsAlert(message);
209            result.confirm();
210            return true;
211        }
212
213        @Override
214        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
215            getCurrentAdditionalTextOutput().appendJsConfirm(message);
216            result.confirm();
217            return true;
218        }
219
220        @Override
221        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
222                JsPromptResult result) {
223            getCurrentAdditionalTextOutput().appendJsPrompt(message, defaultValue);
224            result.confirm();
225            return true;
226        }
227
228        @Override
229        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
230            getCurrentAdditionalTextOutput().appendConsoleMessage(consoleMessage);
231            return true;
232        }
233
234        @Override
235        public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture,
236                Message resultMsg) {
237            WebView.WebViewTransport transport = (WebView.WebViewTransport)resultMsg.obj;
238            /** By default windows cannot be opened, so just send null back. */
239            WebView newWindowWebView = null;
240
241            if (mCanOpenWindows) {
242                /**
243                 * We never display the new window, just create the view and allow it's content to
244                 * execute and be recorded by the executor.
245                 */
246                newWindowWebView = createWebViewWithJavascriptInterfaces();
247                setupWebView(newWindowWebView);
248            }
249
250            transport.setWebView(newWindowWebView);
251            resultMsg.sendToTarget();
252            return true;
253        }
254
255        @Override
256        public void onGeolocationPermissionsShowPrompt(String origin,
257                GeolocationPermissions.Callback callback) {
258            if (mIsGeolocationPermissionSet) {
259                callback.invoke(origin, mGeolocationPermission, false);
260                return;
261            }
262            if (mPendingGeolocationPermissionCallbacks == null) {
263                mPendingGeolocationPermissionCallbacks =
264                        new HashMap<GeolocationPermissions.Callback, String>();
265            }
266            mPendingGeolocationPermissionCallbacks.put(callback, origin);
267        }
268    };
269
270    /** IMPLEMENTATION */
271
272    @Override
273    protected void onCreate(Bundle savedInstanceState) {
274        super.onCreate(savedInstanceState);
275
276        /**
277         * It detects the crash by catching all the uncaught exceptions. However, we
278         * still have to kill the process, because after catching the exception the
279         * activity remains in a strange state, where intents don't revive it.
280         * However, we send the message to the service to speed up the rebooting
281         * (we don't have to wait for time-out to kick in).
282         */
283        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
284            @Override
285            public void uncaughtException(Thread thread, Throwable e) {
286                Log.w(LOG_TAG,
287                        "onTestCrashed(): " + mCurrentTestRelativePath + " thread=" + thread, e);
288
289                try {
290                    Message serviceMsg =
291                            Message.obtain(null, ManagerService.MSG_CURRENT_TEST_CRASHED);
292
293                    mManagerServiceMessenger.send(serviceMsg);
294                } catch (RemoteException e2) {
295                    Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e2);
296                }
297
298                Process.killProcess(Process.myPid());
299            }
300        });
301
302        requestWindowFeature(Window.FEATURE_PROGRESS);
303
304        Intent intent = getIntent();
305        mTestsList = FsUtils.loadTestListFromStorage(intent.getStringExtra(EXTRA_TESTS_FILE));
306        mCurrentTestIndex = intent.getIntExtra(EXTRA_TEST_INDEX, -1);
307        mTotalTestCount = mCurrentTestIndex + mTestsList.size();
308
309        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
310        mScreenDimLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
311                | PowerManager.ON_AFTER_RELEASE, "WakeLock in LayoutTester");
312        mScreenDimLock.acquire();
313
314        bindService(new Intent(this, ManagerService.class), mServiceConnection,
315                Context.BIND_AUTO_CREATE);
316    }
317
318    private void reset() {
319        WebView previousWebView = mCurrentWebView;
320
321        resetLayoutTestController();
322
323        mCurrentTestTimedOut = false;
324        mCurrentResult = null;
325        mCurrentAdditionalTextOutput = null;
326
327        mCurrentWebView = createWebViewWithJavascriptInterfaces();
328        // When we create the first WebView, we need to pause to wait for the WebView thread to spin
329        // and up and for it to register its message handlers.
330        if (previousWebView == null) {
331            try {
332                Thread.currentThread().sleep(1000);
333            } catch (Exception e) {}
334        }
335        setupWebView(mCurrentWebView);
336
337        mEventSender.reset(mCurrentWebView);
338
339        setContentView(mCurrentWebView);
340        if (previousWebView != null) {
341            Log.d(LOG_TAG + "::reset", "previousWebView != null");
342            previousWebView.destroy();
343        }
344    }
345
346    private static class WebViewWithJavascriptInterfaces extends WebView {
347        public WebViewWithJavascriptInterfaces(
348                Context context, Map<String, Object> javascriptInterfaces) {
349            super(context,
350                  null, // attribute set
351                  0, // default style resource ID
352                  javascriptInterfaces,
353                  false); // is private browsing
354        }
355    }
356    private WebView createWebViewWithJavascriptInterfaces() {
357        Map<String, Object> javascriptInterfaces = new HashMap<String, Object>();
358        javascriptInterfaces.put("layoutTestController", mLayoutTestController);
359        javascriptInterfaces.put("eventSender", mEventSender);
360        return new WebViewWithJavascriptInterfaces(this, javascriptInterfaces);
361    }
362
363    private void setupWebView(WebView webView) {
364        webView.setWebViewClient(mWebViewClient);
365        webView.setWebChromeClient(mWebChromeClient);
366
367        /**
368         * Setting a touch interval of -1 effectively disables the optimisation in WebView
369         * that stops repeated touch events flooding WebCore. The Event Sender only sends a
370         * single event rather than a stream of events (like what would generally happen in
371         * a real use of touch events in a WebView)  and so if the WebView drops the event,
372         * the test will fail as the test expects one callback for every touch it synthesizes.
373         */
374        WebViewClassic webViewClassic = WebViewClassic.fromWebView(webView);
375        webViewClassic.setTouchInterval(-1);
376
377        webViewClassic.clearCache(true);
378
379        WebSettingsClassic webViewSettings = webViewClassic.getSettings();
380        webViewSettings.setAppCacheEnabled(true);
381        webViewSettings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
382        // Use of larger values causes unexplained AppCache database corruption.
383        // TODO: Investigate what's really going on here.
384        webViewSettings.setAppCacheMaxSize(100 * 1024 * 1024);
385        webViewSettings.setJavaScriptEnabled(true);
386        webViewSettings.setJavaScriptCanOpenWindowsAutomatically(true);
387        webViewSettings.setSupportMultipleWindows(true);
388        webViewSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
389        webViewSettings.setDatabaseEnabled(true);
390        webViewSettings.setDatabasePath(getDir("databases", 0).getAbsolutePath());
391        webViewSettings.setDomStorageEnabled(true);
392        webViewSettings.setWorkersEnabled(false);
393        webViewSettings.setXSSAuditorEnabled(false);
394        webViewSettings.setPageCacheCapacity(0);
395
396        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
397        WebViewClassic.fromWebView(mCurrentWebView).useMockDeviceOrientation();
398
399        // Must do this after setting the AppCache path.
400        WebStorage.getInstance().deleteAllData();
401    }
402
403    private void startTests() {
404        // This is called when the tests are started and after each crash.
405        // We only send the reset message in the former case.
406        if (mCurrentTestIndex <= 0) {
407            sendResetMessage();
408        }
409        if (mCurrentTestIndex == 0) {
410            sendFirstTestMessage();
411        }
412
413        runNextTest();
414    }
415
416    private void sendResetMessage() {
417        try {
418            Message serviceMsg = Message.obtain(null, ManagerService.MSG_RESET);
419            mManagerServiceMessenger.send(serviceMsg);
420        } catch (RemoteException e) {
421            Log.e(LOG_TAG, "Error sending message to manager service:", e);
422        }
423    }
424
425    private void sendFirstTestMessage() {
426        try {
427            Message serviceMsg = Message.obtain(null, ManagerService.MSG_FIRST_TEST);
428
429            Bundle bundle = new Bundle();
430            bundle.putString("firstTest", mTestsList.get(0));
431            bundle.putInt("index", mCurrentTestIndex);
432
433            serviceMsg.setData(bundle);
434            mManagerServiceMessenger.send(serviceMsg);
435        } catch (RemoteException e) {
436            Log.e(LOG_TAG, "Error sending message to manager service:", e);
437        }
438    }
439
440    private void runNextTest() {
441        assert mCurrentState == CurrentState.IDLE : "mCurrentState = " + mCurrentState.name();
442
443        if (mTestsList.isEmpty()) {
444            onAllTestsFinished();
445            return;
446        }
447
448        mCurrentTestRelativePath = mTestsList.remove(0);
449
450        Log.i(LOG_TAG, "runNextTest(): Start: " + mCurrentTestRelativePath +
451                " (" + mCurrentTestIndex + ")");
452
453        mCurrentTestUri = FileFilter.getUrl(mCurrentTestRelativePath, true).toString();
454
455        reset();
456
457        /** Start time-out countdown and the test */
458        mCurrentState = CurrentState.RENDERING_PAGE;
459        mResultHandler.sendEmptyMessageDelayed(MSG_TEST_TIMED_OUT, DEFAULT_TIME_OUT_MS);
460        mCurrentWebView.loadUrl(mCurrentTestUri);
461    }
462
463    private void onTestTimedOut() {
464        assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
465
466        Log.w(LOG_TAG, "onTestTimedOut(): " + mCurrentTestRelativePath);
467        mCurrentTestTimedOut = true;
468
469        /**
470         * While it is theoretically possible that the test times out because
471         * of webview becoming unresponsive, it is very unlikely. Therefore it's
472         * assumed that obtaining results (that calls various webview methods)
473         * will not itself hang.
474         */
475        obtainActualResultsFromWebView();
476    }
477
478    private void onTestFinished() {
479        assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
480
481        Log.i(LOG_TAG, "onTestFinished(): " + mCurrentTestRelativePath);
482        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
483        obtainActualResultsFromWebView();
484    }
485
486    private void obtainActualResultsFromWebView() {
487        /**
488         * If the result has not been set by the time the test finishes we create
489         * a default type of result.
490         */
491        if (mCurrentResult == null) {
492            /** TODO: Default type should be RenderTreeResult. We don't support it now. */
493            mCurrentResult = new TextResult(mCurrentTestRelativePath);
494        }
495
496        mCurrentState = CurrentState.OBTAINING_RESULT;
497
498        if (mCurrentTestTimedOut) {
499            mCurrentResult.setDidTimeOut();
500        }
501        mCurrentResult.obtainActualResults(mCurrentWebView,
502                mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
503    }
504
505    private void onActualResultsObtained() {
506        assert mCurrentState == CurrentState.OBTAINING_RESULT
507                : "mCurrentState = " + mCurrentState.name();
508
509        Log.i(LOG_TAG, "onActualResultsObtained(): " + mCurrentTestRelativePath);
510        mCurrentState = CurrentState.IDLE;
511
512        reportResultToService();
513        mCurrentTestIndex++;
514        updateProgressBar();
515        runNextTest();
516    }
517
518    private void reportResultToService() {
519        if (mCurrentAdditionalTextOutput != null) {
520            mCurrentResult.setAdditionalTextOutputString(mCurrentAdditionalTextOutput.toString());
521        }
522
523        try {
524            Message serviceMsg =
525                    Message.obtain(null, ManagerService.MSG_PROCESS_ACTUAL_RESULTS);
526
527            Bundle bundle = mCurrentResult.getBundle();
528            bundle.putInt("testIndex", mCurrentTestIndex);
529            if (!mTestsList.isEmpty()) {
530                bundle.putString("nextTest", mTestsList.get(0));
531            }
532
533            serviceMsg.setData(bundle);
534            mManagerServiceMessenger.send(serviceMsg);
535        } catch (RemoteException e) {
536            Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
537        }
538    }
539
540    private void updateProgressBar() {
541        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
542                mCurrentTestIndex * Window.PROGRESS_END / mTotalTestCount);
543        setTitle(mCurrentTestIndex * 100 / mTotalTestCount + "% " +
544                "(" + mCurrentTestIndex + "/" + mTotalTestCount + ")");
545    }
546
547    private void onAllTestsFinished() {
548        mScreenDimLock.release();
549
550        try {
551            Message serviceMsg =
552                    Message.obtain(null, ManagerService.MSG_ALL_TESTS_FINISHED);
553            mManagerServiceMessenger.send(serviceMsg);
554        } catch (RemoteException e) {
555            Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
556        }
557
558        unbindService(mServiceConnection);
559    }
560
561    private AdditionalTextOutput getCurrentAdditionalTextOutput() {
562        if (mCurrentAdditionalTextOutput == null) {
563            mCurrentAdditionalTextOutput = new AdditionalTextOutput();
564        }
565        return mCurrentAdditionalTextOutput;
566    }
567
568    /** LAYOUT TEST CONTROLLER */
569
570    private static final int MSG_WAIT_UNTIL_DONE = 0;
571    private static final int MSG_NOTIFY_DONE = 1;
572    private static final int MSG_DUMP_AS_TEXT = 2;
573    private static final int MSG_DUMP_CHILD_FRAMES_AS_TEXT = 3;
574    private static final int MSG_SET_CAN_OPEN_WINDOWS = 4;
575    private static final int MSG_DUMP_DATABASE_CALLBACKS = 5;
576    private static final int MSG_SET_GEOLOCATION_PERMISSION = 6;
577    private static final int MSG_OVERRIDE_PREFERENCE = 7;
578    private static final int MSG_SET_XSS_AUDITOR_ENABLED = 8;
579
580    /** String constants for use with layoutTestController.overridePreference() */
581    private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED =
582            "WebKitOfflineWebApplicationCacheEnabled";
583    private final String WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY = "WebKitUsesPageCachePreferenceKey";
584
585    Handler mLayoutTestControllerHandler = new Handler() {
586        @Override
587        public void handleMessage(Message msg) {
588            assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
589
590            switch (msg.what) {
591                case MSG_DUMP_AS_TEXT:
592                    if (mCurrentResult == null) {
593                        mCurrentResult = new TextResult(mCurrentTestRelativePath);
594                    }
595                    assert mCurrentResult instanceof TextResult
596                            : "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
597                    break;
598
599                case MSG_DUMP_CHILD_FRAMES_AS_TEXT:
600                    /** If dumpAsText was not called we assume that the result should be text */
601                    if (mCurrentResult == null) {
602                        mCurrentResult = new TextResult(mCurrentTestRelativePath);
603                    }
604
605                    assert mCurrentResult instanceof TextResult
606                            : "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
607
608                    ((TextResult)mCurrentResult).setDumpChildFramesAsText(true);
609                    break;
610
611                case MSG_DUMP_DATABASE_CALLBACKS:
612                    mDumpDatabaseCallbacks = true;
613                    break;
614
615                case MSG_NOTIFY_DONE:
616                    if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) {
617                        onTestFinished();
618                    }
619                    break;
620
621                case MSG_OVERRIDE_PREFERENCE:
622                    /**
623                     * TODO: We should look up the correct WebView for the frame which
624                     * called the layoutTestController method. Currently, we just use the
625                     * WebView for the main frame. EventSender suffers from the same
626                     * problem.
627                     */
628                    String key = msg.getData().getString("key");
629                    boolean value = msg.getData().getBoolean("value");
630                    if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) {
631                        WebViewClassic.fromWebView(mCurrentWebView).getSettings().
632                                setAppCacheEnabled(value);
633                    } else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) {
634                        // Cache the maximum possible number of pages.
635                        WebViewClassic.fromWebView(mCurrentWebView).getSettings().
636                                setPageCacheCapacity(Integer.MAX_VALUE);
637                    } else {
638                        Log.w(LOG_TAG, "LayoutTestController.overridePreference(): " +
639                              "Unsupported preference '" + key + "'");
640                    }
641                    break;
642
643                case MSG_SET_CAN_OPEN_WINDOWS:
644                    mCanOpenWindows = true;
645                    break;
646
647                case MSG_SET_GEOLOCATION_PERMISSION:
648                    mIsGeolocationPermissionSet = true;
649                    mGeolocationPermission = msg.arg1 == 1;
650
651                    if (mPendingGeolocationPermissionCallbacks != null) {
652                        Iterator<GeolocationPermissions.Callback> iter =
653                                mPendingGeolocationPermissionCallbacks.keySet().iterator();
654                        while (iter.hasNext()) {
655                            GeolocationPermissions.Callback callback = iter.next();
656                            String origin = mPendingGeolocationPermissionCallbacks.get(callback);
657                            callback.invoke(origin, mGeolocationPermission, false);
658                        }
659                        mPendingGeolocationPermissionCallbacks = null;
660                    }
661                    break;
662
663                case MSG_SET_XSS_AUDITOR_ENABLED:
664                    WebViewClassic.fromWebView(mCurrentWebView).getSettings().
665                            setXSSAuditorEnabled(msg.arg1 == 1);
666                    break;
667
668                case MSG_WAIT_UNTIL_DONE:
669                    mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
670                    break;
671
672                default:
673                    assert false : "msg.what=" + msg.what;
674                    break;
675            }
676        }
677    };
678
679    private void resetLayoutTestController() {
680        mCanOpenWindows = false;
681        mDumpDatabaseCallbacks = false;
682        mIsGeolocationPermissionSet = false;
683        mPendingGeolocationPermissionCallbacks = null;
684    }
685
686    public void dumpAsText(boolean enablePixelTest) {
687        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpAsText(" + enablePixelTest + ") called");
688        /** TODO: Implement */
689        if (enablePixelTest) {
690            Log.w(LOG_TAG, "enablePixelTest not implemented, switching to false");
691        }
692        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_AS_TEXT);
693    }
694
695    public void dumpChildFramesAsText() {
696        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpChildFramesAsText() called");
697        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_CHILD_FRAMES_AS_TEXT);
698    }
699
700    public void dumpDatabaseCallbacks() {
701        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpDatabaseCallbacks() called");
702        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_DATABASE_CALLBACKS);
703    }
704
705    public void notifyDone() {
706        Log.i(LOG_TAG, mCurrentTestRelativePath + ": notifyDone() called");
707        mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE);
708    }
709
710    public void overridePreference(String key, boolean value) {
711        Log.i(LOG_TAG, mCurrentTestRelativePath + ": overridePreference(" + key + ", " + value +
712        ") called");
713        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_OVERRIDE_PREFERENCE);
714        msg.getData().putString("key", key);
715        msg.getData().putBoolean("value", value);
716        msg.sendToTarget();
717    }
718
719    public void setCanOpenWindows() {
720        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setCanOpenWindows() called");
721        mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS);
722    }
723
724    public void setGeolocationPermission(boolean allow) {
725        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setGeolocationPermission(" + allow +
726                ") called");
727        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_SET_GEOLOCATION_PERMISSION);
728        msg.arg1 = allow ? 1 : 0;
729        msg.sendToTarget();
730    }
731
732    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
733            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
734        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setMockDeviceOrientation(" + canProvideAlpha +
735                ", " + alpha + ", " + canProvideBeta + ", " + beta + ", " + canProvideGamma +
736                ", " + gamma + ")");
737        WebViewClassic.fromWebView(mCurrentWebView).setMockDeviceOrientation(canProvideAlpha,
738                alpha, canProvideBeta, beta, canProvideGamma, gamma);
739    }
740
741    public void setXSSAuditorEnabled(boolean flag) {
742        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setXSSAuditorEnabled(" + flag + ") called");
743        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_SET_XSS_AUDITOR_ENABLED);
744        msg.arg1 = flag ? 1 : 0;
745        msg.sendToTarget();
746    }
747
748    public void waitUntilDone() {
749        Log.i(LOG_TAG, mCurrentTestRelativePath + ": waitUntilDone() called");
750        mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE);
751    }
752
753}
754