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