TestShellActivity.java revision e8e335a5002d4edb671a110fd7b13c1153659920
1/*
2 * Copyright (C) 2007 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.dumprendertree;
18
19import com.android.dumprendertree.forwarder.ForwardService;
20
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.DialogInterface.OnClickListener;
27import android.graphics.Bitmap;
28import android.graphics.Canvas;
29import android.graphics.Bitmap.CompressFormat;
30import android.graphics.Bitmap.Config;
31import android.net.http.SslError;
32import android.os.Bundle;
33import android.os.Environment;
34import android.os.Handler;
35import android.os.Message;
36import android.util.Log;
37import android.view.ViewGroup;
38import android.view.Window;
39import android.webkit.CookieManager;
40import android.webkit.ConsoleMessage;
41import android.webkit.CookieManager;
42import android.webkit.GeolocationPermissions;
43import android.webkit.HttpAuthHandler;
44import android.webkit.JsPromptResult;
45import android.webkit.JsResult;
46import android.webkit.SslErrorHandler;
47import android.webkit.WebChromeClient;
48import android.webkit.WebSettings;
49import android.webkit.WebStorage;
50import android.webkit.WebView;
51import android.webkit.WebViewClient;
52import android.widget.LinearLayout;
53
54import java.io.BufferedReader;
55import java.io.File;
56import java.io.FileOutputStream;
57import java.io.FileReader;
58import java.io.IOException;
59import java.net.MalformedURLException;
60import java.net.URL;
61import java.util.HashMap;
62import java.util.Iterator;
63import java.util.Map;
64import java.util.Vector;
65
66public class TestShellActivity extends Activity implements LayoutTestController {
67
68    static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP}
69
70    // String constants for use with layoutTestController.overridePreferences
71    private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED =
72            "WebKitOfflineWebApplicationCacheEnabled";
73    private final String WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY = "WebKitUsesPageCachePreferenceKey";
74
75    public class AsyncHandler extends Handler {
76        @Override
77        public void handleMessage(Message msg) {
78            if (msg.what == MSG_TIMEOUT) {
79                mTimedOut = true;
80                mWebView.stopLoading();
81                if (mCallback != null)
82                    mCallback.timedOut(mWebView.getUrl());
83                if (!mRequestedWebKitData) {
84                    requestWebKitData();
85                } else {
86                    // if timed out and webkit data has been dumped before
87                    // finish directly
88                    finished();
89                }
90                return;
91            } else if (msg.what == MSG_WEBKIT_DATA) {
92                Log.v(LOGTAG, "Received WebView dump data");
93                mHandler.removeMessages(MSG_DUMP_TIMEOUT);
94                TestShellActivity.this.dump(mTimedOut, (String)msg.obj);
95                return;
96            } else if (msg.what == MSG_DUMP_TIMEOUT) {
97                throw new RuntimeException("WebView dump timeout, is it pegged?");
98            }
99            super.handleMessage(msg);
100        }
101    }
102
103    public void requestWebKitData() {
104        Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA);
105
106        if (mRequestedWebKitData)
107            throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl());
108
109        mRequestedWebKitData = true;
110        Log.v(LOGTAG, "message sent to WebView to dump text.");
111        switch (mDumpDataType) {
112            case DUMP_AS_TEXT:
113                callback.arg1 = mDumpTopFrameAsText ? 1 : 0;
114                callback.arg2 = mDumpChildFramesAsText ? 1 : 0;
115                setDumpTimeout(DUMP_TIMEOUT_MS);
116                mWebView.documentAsText(callback);
117                break;
118            case EXT_REPR:
119                mWebView.externalRepresentation(callback);
120                setDumpTimeout(DUMP_TIMEOUT_MS);
121                break;
122            default:
123                finished();
124                break;
125        }
126    }
127
128    private void setDumpTimeout(long timeout) {
129        Log.v(LOGTAG, "setting dump timeout at " + timeout);
130        Message msg = mHandler.obtainMessage(MSG_DUMP_TIMEOUT);
131        mHandler.sendMessageDelayed(msg, timeout);
132    }
133
134    public void clearCache() {
135      mWebView.freeMemory();
136    }
137
138    @Override
139    protected void onCreate(Bundle icicle) {
140        super.onCreate(icicle);
141        requestWindowFeature(Window.FEATURE_PROGRESS);
142
143        LinearLayout contentView = new LinearLayout(this);
144        contentView.setOrientation(LinearLayout.VERTICAL);
145        setContentView(contentView);
146
147        CookieManager.setAcceptFileSchemeCookies(true);
148        mWebView = new WebView(this);
149        mEventSender = new WebViewEventSender(mWebView);
150        mCallbackProxy = new CallbackProxy(mEventSender, this);
151
152        mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController");
153        mWebView.addJavascriptInterface(mCallbackProxy, "eventSender");
154        setupWebViewForLayoutTests(mWebView, mCallbackProxy);
155
156        contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0.0f));
157
158        mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
159
160        // Expose window.gc function to JavaScript. JSC build exposes
161        // this function by default, but V8 requires the flag to turn it on.
162        // WebView::setJsFlags is noop in JSC build.
163        mWebView.setJsFlags("--expose_gc");
164
165        mHandler = new AsyncHandler();
166
167        Intent intent = getIntent();
168        if (intent != null) {
169            executeIntent(intent);
170        }
171
172        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
173        mWebView.useMockDeviceOrientation();
174    }
175
176    @Override
177    protected void onNewIntent(Intent intent) {
178        super.onNewIntent(intent);
179        executeIntent(intent);
180    }
181
182    private void executeIntent(Intent intent) {
183        resetTestStatus();
184        if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
185            return;
186        }
187
188        mTotalTestCount = intent.getIntExtra(TOTAL_TEST_COUNT, mTotalTestCount);
189        mCurrentTestNumber = intent.getIntExtra(CURRENT_TEST_NUMBER, mCurrentTestNumber);
190
191        mTestUrl = intent.getStringExtra(TEST_URL);
192        if (mTestUrl == null) {
193            mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST);
194            if(mUiAutoTestPath != null) {
195                beginUiAutoTest();
196            }
197            return;
198        }
199
200        mResultFile = intent.getStringExtra(RESULT_FILE);
201        mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0);
202        mGetDrawtime = intent.getBooleanExtra(GET_DRAW_TIME, false);
203        mSaveImagePath = intent.getStringExtra(SAVE_IMAGE);
204        mStopOnRefError = intent.getBooleanExtra(STOP_ON_REF_ERROR, false);
205        setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount);
206        float ratio = (float)mCurrentTestNumber / mTotalTestCount;
207        int progress = (int)(ratio * Window.PROGRESS_END);
208        getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress);
209
210        Log.v(LOGTAG, "  Loading " + mTestUrl);
211        mWebView.loadUrl(mTestUrl);
212
213        if (mTimeoutInMillis > 0) {
214            // Create a timeout timer
215            Message m = mHandler.obtainMessage(MSG_TIMEOUT);
216            mHandler.sendMessageDelayed(m, mTimeoutInMillis);
217        }
218    }
219
220    private void beginUiAutoTest() {
221        try {
222            mTestListReader = new BufferedReader(
223                    new FileReader(mUiAutoTestPath));
224        } catch (IOException ioe) {
225            Log.e(LOGTAG, "Failed to open test list for read.", ioe);
226            finishUiAutoTest();
227            return;
228        }
229        moveToNextTest();
230    }
231
232    private void finishUiAutoTest() {
233        try {
234            if(mTestListReader != null)
235                mTestListReader.close();
236        } catch (IOException ioe) {
237            Log.w(LOGTAG, "Failed to close test list file.", ioe);
238        }
239        ForwardService.getForwardService().stopForwardService();
240        finished();
241    }
242
243    private void moveToNextTest() {
244        String url = null;
245        try {
246            url = mTestListReader.readLine();
247        } catch (IOException ioe) {
248            Log.e(LOGTAG, "Failed to read next test.", ioe);
249            finishUiAutoTest();
250            return;
251        }
252        if (url == null) {
253            mUiAutoTestPath = null;
254            finishUiAutoTest();
255            AlertDialog.Builder builder = new AlertDialog.Builder(this);
256            builder.setMessage("All tests finished. Exit?")
257                   .setCancelable(false)
258                   .setPositiveButton("Yes", new OnClickListener(){
259                       public void onClick(DialogInterface dialog, int which) {
260                           TestShellActivity.this.finish();
261                       }
262                   })
263                   .setNegativeButton("No", new OnClickListener(){
264                       public void onClick(DialogInterface dialog, int which) {
265                           dialog.cancel();
266                       }
267                   });
268            builder.create().show();
269            return;
270        }
271        Intent intent = new Intent(Intent.ACTION_VIEW);
272        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
273        intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(url));
274        intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, ++mCurrentTestNumber);
275        intent.putExtra(TIMEOUT_IN_MILLIS, 10000);
276        executeIntent(intent);
277    }
278
279    @Override
280    protected void onStop() {
281        super.onStop();
282        mWebView.stopLoading();
283    }
284
285    @Override
286    protected void onDestroy() {
287        super.onDestroy();
288        mWebView.destroy();
289        mWebView = null;
290    }
291
292    @Override
293    public void onLowMemory() {
294        super.onLowMemory();
295        Log.e(LOGTAG, "Low memory, clearing caches");
296        mWebView.freeMemory();
297    }
298
299    // Dump the page
300    public void dump(boolean timeout, String webkitData) {
301        mDumpWebKitData = true;
302        if (mResultFile == null || mResultFile.length() == 0) {
303            finished();
304            return;
305        }
306
307        try {
308            File parentDir = new File(mResultFile).getParentFile();
309            if (!parentDir.exists()) {
310                parentDir.mkdirs();
311            }
312
313            FileOutputStream os = new FileOutputStream(mResultFile);
314            if (timeout) {
315                Log.w("Layout test: Timeout", mResultFile);
316                os.write(TIMEOUT_STR.getBytes());
317                os.write('\n');
318            }
319            if (mDumpTitleChanges)
320                os.write(mTitleChanges.toString().getBytes());
321            if (mDialogStrings != null)
322                os.write(mDialogStrings.toString().getBytes());
323            mDialogStrings = null;
324            if (mDatabaseCallbackStrings != null)
325                os.write(mDatabaseCallbackStrings.toString().getBytes());
326            mDatabaseCallbackStrings = null;
327            if (mConsoleMessages != null)
328                os.write(mConsoleMessages.toString().getBytes());
329            mConsoleMessages = null;
330            if (webkitData != null)
331                os.write(webkitData.getBytes());
332            os.flush();
333            os.close();
334        } catch (IOException ex) {
335            Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage());
336        }
337
338        finished();
339    }
340
341    public void setCallback(TestShellCallback callback) {
342        mCallback = callback;
343    }
344
345    public boolean finished() {
346        if (canMoveToNextTest()) {
347            mHandler.removeMessages(MSG_TIMEOUT);
348            if (mUiAutoTestPath != null) {
349                //don't really finish here
350                moveToNextTest();
351            } else {
352                if (mCallback != null) {
353                    mCallback.finished();
354                }
355            }
356            return true;
357        }
358        return false;
359    }
360
361    public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) {
362        mDefaultDumpDataType = defaultDumpDataType;
363    }
364
365    // .......................................
366    // LayoutTestController Functions
367    public void dumpAsText(boolean enablePixelTests) {
368        // Added after webkit update to r63859. See trac.webkit.org/changeset/63730.
369        if (enablePixelTests) {
370            Log.v(LOGTAG, "dumpAsText(enablePixelTests == true) not implemented on Android!");
371        }
372
373        mDumpDataType = DumpDataType.DUMP_AS_TEXT;
374        mDumpTopFrameAsText = true;
375        if (mWebView != null) {
376            String url = mWebView.getUrl();
377            Log.v(LOGTAG, "dumpAsText called: "+url);
378        }
379    }
380
381    public void dumpChildFramesAsText() {
382        mDumpDataType = DumpDataType.DUMP_AS_TEXT;
383        mDumpChildFramesAsText = true;
384        if (mWebView != null) {
385            String url = mWebView.getUrl();
386            Log.v(LOGTAG, "dumpChildFramesAsText called: "+url);
387        }
388    }
389
390    public void waitUntilDone() {
391        mWaitUntilDone = true;
392        String url = mWebView.getUrl();
393        Log.v(LOGTAG, "waitUntilDone called: " + url);
394    }
395
396    public void notifyDone() {
397        String url = mWebView.getUrl();
398        Log.v(LOGTAG, "notifyDone called: " + url);
399        if (mWaitUntilDone) {
400            mWaitUntilDone = false;
401            if (!mRequestedWebKitData && !mTimedOut && !finished()) {
402                requestWebKitData();
403            }
404        }
405    }
406
407    public void display() {
408        mWebView.invalidate();
409    }
410
411    public void clearBackForwardList() {
412        mWebView.clearHistory();
413
414    }
415
416    public void dumpBackForwardList() {
417        //printf("\n============== Back Forward List ==============\n");
418        // mWebHistory
419        //printf("===============================================\n");
420
421    }
422
423    public void dumpChildFrameScrollPositions() {
424        // TODO Auto-generated method stub
425
426    }
427
428    public void dumpEditingCallbacks() {
429        // TODO Auto-generated method stub
430
431    }
432
433    public void dumpSelectionRect() {
434        // TODO Auto-generated method stub
435
436    }
437
438    public void dumpTitleChanges() {
439        if (!mDumpTitleChanges) {
440            mTitleChanges = new StringBuffer();
441        }
442        mDumpTitleChanges = true;
443    }
444
445    public void keepWebHistory() {
446        if (!mKeepWebHistory) {
447            mWebHistory = new Vector();
448        }
449        mKeepWebHistory = true;
450    }
451
452    public void queueBackNavigation(int howfar) {
453        // TODO Auto-generated method stub
454
455    }
456
457    public void queueForwardNavigation(int howfar) {
458        // TODO Auto-generated method stub
459
460    }
461
462    public void queueLoad(String Url, String frameTarget) {
463        // TODO Auto-generated method stub
464
465    }
466
467    public void queueReload() {
468        mWebView.reload();
469    }
470
471    public void queueScript(String scriptToRunInCurrentContext) {
472        mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);
473    }
474
475    public void repaintSweepHorizontally() {
476        // TODO Auto-generated method stub
477
478    }
479
480    public void setAcceptsEditing(boolean b) {
481        // TODO Auto-generated method stub
482
483    }
484
485    public void setMainFrameIsFirstResponder(boolean b) {
486        // TODO Auto-generated method stub
487
488    }
489
490    public void setWindowIsKey(boolean b) {
491        // This is meant to show/hide the window. The best I can find
492        // is setEnabled()
493        mWebView.setEnabled(b);
494    }
495
496    public void testRepaint() {
497        mWebView.invalidate();
498    }
499
500    public void dumpDatabaseCallbacks() {
501        Log.v(LOGTAG, "dumpDatabaseCallbacks called.");
502        mDumpDatabaseCallbacks = true;
503    }
504
505    public void setCanOpenWindows() {
506        Log.v(LOGTAG, "setCanOpenWindows called.");
507        mCanOpenWindows = true;
508    }
509
510    /**
511     * Sets the Geolocation permission state to be used for all future requests.
512     */
513    public void setGeolocationPermission(boolean allow) {
514        mIsGeolocationPermissionSet = true;
515        mGeolocationPermission = allow;
516
517        if (mPendingGeolocationPermissionCallbacks != null) {
518            Iterator iter = mPendingGeolocationPermissionCallbacks.keySet().iterator();
519            while (iter.hasNext()) {
520                GeolocationPermissions.Callback callback =
521                        (GeolocationPermissions.Callback) iter.next();
522                String origin = (String) mPendingGeolocationPermissionCallbacks.get(callback);
523                callback.invoke(origin, mGeolocationPermission, false);
524            }
525            mPendingGeolocationPermissionCallbacks = null;
526        }
527    }
528
529    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
530            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
531        mWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
532                canProvideGamma, gamma);
533    }
534
535    public void overridePreference(String key, boolean value) {
536        // TODO: We should look up the correct WebView for the frame which
537        // called the layoutTestController method. Currently, we just use the
538        // WebView for the main frame. EventSender suffers from the same
539        // problem.
540        if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) {
541            mWebView.getSettings().setAppCacheEnabled(value);
542        } else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) {
543            // Cache the maximum possible number of pages.
544            mWebView.getSettings().setPageCacheCapacity(Integer.MAX_VALUE);
545        } else {
546            Log.w(LOGTAG, "LayoutTestController.overridePreference(): " +
547                  "Unsupported preference '" + key + "'");
548        }
549    }
550
551    public void setXSSAuditorEnabled (boolean flag) {
552        mWebView.getSettings().setXSSAuditorEnabled(flag);
553    }
554
555    private final WebViewClient mViewClient = new WebViewClient(){
556        @Override
557        public void onPageFinished(WebView view, String url) {
558            Log.v(LOGTAG, "onPageFinished, url=" + url);
559            mPageFinished = true;
560            // get page draw time
561            if (FsUtils.isTestPageUrl(url)) {
562                if (mGetDrawtime) {
563                    long[] times = new long[DRAW_RUNS];
564                    times = getDrawWebViewTime(mWebView, DRAW_RUNS);
565                    FsUtils.writeDrawTime(DRAW_TIME_LOG, url, times);
566                }
567                if (mSaveImagePath != null) {
568                    String name = FsUtils.getLastSegmentInPath(url);
569                    drawPageToFile(mSaveImagePath + "/" + name + ".png", mWebView);
570                }
571            }
572
573            // Calling finished() will check if we've met all the conditions for completing
574            // this test and move to the next one if we are ready. Otherwise we ask WebCore to
575            // dump the page.
576            if (finished()) {
577                return;
578            }
579
580            if (!mWaitUntilDone && !mRequestedWebKitData && !mTimedOut) {
581                requestWebKitData();
582            } else {
583                if (mWaitUntilDone) {
584                    Log.v(LOGTAG, "page finished loading but waiting for notifyDone to be called: " + url);
585                }
586
587                if (mRequestedWebKitData) {
588                    Log.v(LOGTAG, "page finished loading but webkit data has already been requested: " + url);
589                }
590
591                if (mTimedOut) {
592                    Log.v(LOGTAG, "page finished loading but already timed out: " + url);
593                }
594            }
595
596            super.onPageFinished(view, url);
597        }
598
599        @Override
600        public void onPageStarted(WebView view, String url, Bitmap favicon) {
601            Log.v(LOGTAG, "onPageStarted, url=" + url);
602            mPageFinished = false;
603            super.onPageStarted(view, url, favicon);
604        }
605
606        @Override
607        public void onReceivedError(WebView view, int errorCode, String description,
608                String failingUrl) {
609            Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode
610                    + ", desc=" + description + ", url=" + failingUrl);
611            super.onReceivedError(view, errorCode, description, failingUrl);
612        }
613
614        @Override
615        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
616                String host, String realm) {
617            if (handler.useHttpAuthUsernamePassword() && view != null) {
618                String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
619                if (credentials != null && credentials.length == 2) {
620                    handler.proceed(credentials[0], credentials[1]);
621                    return;
622                }
623            }
624            handler.cancel();
625        }
626
627        @Override
628        public void onReceivedSslError(WebView view, SslErrorHandler handler,
629                SslError error) {
630            handler.proceed();
631        }
632    };
633
634
635    private final WebChromeClient mChromeClient = new WebChromeClient() {
636        @Override
637        public void onReceivedTitle(WebView view, String title) {
638            setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount + ": "+ title);
639            if (mDumpTitleChanges) {
640                mTitleChanges.append("TITLE CHANGED: ");
641                mTitleChanges.append(title);
642                mTitleChanges.append("\n");
643            }
644        }
645
646        @Override
647        public boolean onJsAlert(WebView view, String url, String message,
648                JsResult result) {
649            if (mDialogStrings == null) {
650                mDialogStrings = new StringBuffer();
651            }
652            mDialogStrings.append("ALERT: ");
653            mDialogStrings.append(message);
654            mDialogStrings.append('\n');
655            result.confirm();
656            return true;
657        }
658
659        @Override
660        public boolean onJsConfirm(WebView view, String url, String message,
661                JsResult result) {
662            if (mDialogStrings == null) {
663                mDialogStrings = new StringBuffer();
664            }
665            mDialogStrings.append("CONFIRM: ");
666            mDialogStrings.append(message);
667            mDialogStrings.append('\n');
668            result.confirm();
669            return true;
670        }
671
672        @Override
673        public boolean onJsPrompt(WebView view, String url, String message,
674                String defaultValue, JsPromptResult result) {
675            if (mDialogStrings == null) {
676                mDialogStrings = new StringBuffer();
677            }
678            mDialogStrings.append("PROMPT: ");
679            mDialogStrings.append(message);
680            mDialogStrings.append(", default text: ");
681            mDialogStrings.append(defaultValue);
682            mDialogStrings.append('\n');
683            result.confirm();
684            return true;
685        }
686
687        @Override
688        public boolean onJsTimeout() {
689            Log.v(LOGTAG, "JavaScript timeout");
690            return false;
691        }
692
693        @Override
694        public void onExceededDatabaseQuota(String url_str,
695                String databaseIdentifier, long currentQuota,
696                long estimatedSize, long totalUsedQuota,
697                WebStorage.QuotaUpdater callback) {
698            if (mDumpDatabaseCallbacks) {
699                if (mDatabaseCallbackStrings == null) {
700                    mDatabaseCallbackStrings = new StringBuffer();
701                }
702
703                String protocol = "";
704                String host = "";
705                int port = 0;
706
707                try {
708                    URL url = new URL(url_str);
709                    protocol = url.getProtocol();
710                    host = url.getHost();
711                    if (url.getPort() > -1) {
712                        port = url.getPort();
713                    }
714                } catch (MalformedURLException e) {}
715
716                String databaseCallbackString =
717                        "UI DELEGATE DATABASE CALLBACK: " +
718                        "exceededDatabaseQuotaForSecurityOrigin:{" + protocol +
719                        ", " + host + ", " + port + "} database:" +
720                        databaseIdentifier + "\n";
721                Log.v(LOGTAG, "LOG: "+databaseCallbackString);
722                mDatabaseCallbackStrings.append(databaseCallbackString);
723            }
724            // Give 5MB more quota.
725            callback.updateQuota(currentQuota + 1024 * 1024 * 5);
726        }
727
728        /**
729         * Instructs the client to show a prompt to ask the user to set the
730         * Geolocation permission state for the specified origin.
731         */
732        @Override
733        public void onGeolocationPermissionsShowPrompt(String origin,
734                GeolocationPermissions.Callback callback) {
735            if (mIsGeolocationPermissionSet) {
736                callback.invoke(origin, mGeolocationPermission, false);
737                return;
738            }
739            if (mPendingGeolocationPermissionCallbacks == null) {
740                mPendingGeolocationPermissionCallbacks =
741                        new HashMap<GeolocationPermissions.Callback, String>();
742            }
743            mPendingGeolocationPermissionCallbacks.put(callback, origin);
744        }
745
746        @Override
747        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
748            String msg = "CONSOLE MESSAGE: line " + consoleMessage.lineNumber() + ": "
749                    + consoleMessage.message() + "\n";
750            if (mConsoleMessages == null) {
751                mConsoleMessages = new StringBuffer();
752            }
753            mConsoleMessages.append(msg);
754            Log.v(LOGTAG, "LOG: " + msg);
755            // the rationale here is that if there's an error of either type, and the test was
756            // waiting for "notifyDone" signal to finish, then there's no point in waiting
757            // anymore because the JS execution is already terminated at this point and a
758            // "notifyDone" will never come out so it's just wasting time till timeout kicks in
759            if ((msg.contains("Uncaught ReferenceError:") || msg.contains("Uncaught TypeError:"))
760                    && mWaitUntilDone && mStopOnRefError) {
761                Log.w(LOGTAG, "Terminating test case on uncaught ReferenceError or TypeError.");
762                mHandler.postDelayed(new Runnable() {
763                    public void run() {
764                        notifyDone();
765                    }
766                }, 500);
767            }
768            return true;
769        }
770
771        @Override
772        public boolean onCreateWindow(WebView view, boolean dialog,
773                boolean userGesture, Message resultMsg) {
774            if (!mCanOpenWindows) {
775                // We can't open windows, so just send null back.
776                WebView.WebViewTransport transport =
777                        (WebView.WebViewTransport) resultMsg.obj;
778                transport.setWebView(null);
779                resultMsg.sendToTarget();
780                return true;
781            }
782
783            // We never display the new window, just create the view and
784            // allow it's content to execute and be recorded by the test
785            // runner.
786
787            HashMap<String, Object> jsIfaces = new HashMap<String, Object>();
788            jsIfaces.put("layoutTestController", mCallbackProxy);
789            jsIfaces.put("eventSender", mCallbackProxy);
790            WebView newWindowView = new NewWindowWebView(TestShellActivity.this, jsIfaces);
791            setupWebViewForLayoutTests(newWindowView, mCallbackProxy);
792            WebView.WebViewTransport transport =
793                    (WebView.WebViewTransport) resultMsg.obj;
794            transport.setWebView(newWindowView);
795            resultMsg.sendToTarget();
796            return true;
797        }
798
799        @Override
800        public void onCloseWindow(WebView view) {
801            view.destroy();
802        }
803    };
804
805    private static class NewWindowWebView extends WebView {
806        public NewWindowWebView(Context context, Map<String, Object> jsIfaces) {
807            super(context, null, 0, jsIfaces, false);
808        }
809    }
810
811    private void resetTestStatus() {
812        mWaitUntilDone = false;
813        mDumpDataType = mDefaultDumpDataType;
814        mDumpTopFrameAsText = false;
815        mDumpChildFramesAsText = false;
816        mTimedOut = false;
817        mDumpTitleChanges = false;
818        mRequestedWebKitData = false;
819        mDumpDatabaseCallbacks = false;
820        mCanOpenWindows = false;
821        mEventSender.resetMouse();
822        mEventSender.clearTouchPoints();
823        mEventSender.clearTouchMetaState();
824        mPageFinished = false;
825        mDumpWebKitData = false;
826        mGetDrawtime = false;
827        mSaveImagePath = null;
828        setDefaultWebSettings(mWebView);
829        mIsGeolocationPermissionSet = false;
830        mPendingGeolocationPermissionCallbacks = null;
831        CookieManager.getInstance().removeAllCookie();
832    }
833
834    private long[] getDrawWebViewTime(WebView view, int count) {
835        if (count == 0)
836            return null;
837        long[] ret = new long[count];
838        long start;
839        Canvas canvas = new Canvas();
840        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888);
841        canvas.setBitmap(bitmap);
842        for (int i = 0; i < count; i++) {
843            start = System.currentTimeMillis();
844            view.draw(canvas);
845            ret[i] = System.currentTimeMillis() - start;
846        }
847        return ret;
848    }
849
850    private void drawPageToFile(String fileName, WebView view) {
851        Canvas canvas = new Canvas();
852        Bitmap bitmap = Bitmap.createBitmap(view.getContentWidth(), view.getContentHeight(),
853                Config.ARGB_8888);
854        canvas.setBitmap(bitmap);
855        view.drawPage(canvas);
856        try {
857            FileOutputStream fos = new FileOutputStream(fileName);
858            if(!bitmap.compress(CompressFormat.PNG, 90, fos)) {
859                Log.w(LOGTAG, "Failed to compress and save image.");
860            }
861        } catch (IOException ioe) {
862            Log.e(LOGTAG, "", ioe);
863        }
864        bitmap.recycle();
865    }
866
867    private boolean canMoveToNextTest() {
868        return (mDumpWebKitData && mPageFinished && !mWaitUntilDone) || mTimedOut;
869    }
870
871    private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
872        if (webview == null) {
873            return;
874        }
875
876        setDefaultWebSettings(webview);
877
878        webview.setWebChromeClient(mChromeClient);
879        webview.setWebViewClient(mViewClient);
880        // Setting a touch interval of -1 effectively disables the optimisation in WebView
881        // that stops repeated touch events flooding WebCore. The Event Sender only sends a
882        // single event rather than a stream of events (like what would generally happen in
883        // a real use of touch events in a WebView)  and so if the WebView drops the event,
884        // the test will fail as the test expects one callback for every touch it synthesizes.
885        webview.setTouchInterval(-1);
886    }
887
888    public void setDefaultWebSettings(WebView webview) {
889        WebSettings settings = webview.getSettings();
890        settings.setAppCacheEnabled(true);
891        settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
892        settings.setAppCacheMaxSize(Long.MAX_VALUE);
893        settings.setJavaScriptEnabled(true);
894        settings.setJavaScriptCanOpenWindowsAutomatically(true);
895        settings.setSupportMultipleWindows(true);
896        settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
897        settings.setDatabaseEnabled(true);
898        settings.setDatabasePath(getDir("databases",0).getAbsolutePath());
899        settings.setDomStorageEnabled(true);
900        settings.setWorkersEnabled(false);
901        settings.setXSSAuditorEnabled(false);
902        settings.setPageCacheCapacity(0);
903    }
904
905    private WebView mWebView;
906    private WebViewEventSender mEventSender;
907    private AsyncHandler mHandler;
908    private TestShellCallback mCallback;
909
910    private CallbackProxy mCallbackProxy;
911
912    private String mTestUrl;
913    private String mResultFile;
914    private int mTimeoutInMillis;
915    private String mUiAutoTestPath;
916    private String mSaveImagePath;
917    private BufferedReader mTestListReader;
918    private boolean mGetDrawtime;
919    private int mTotalTestCount;
920    private int mCurrentTestNumber;
921    private boolean mStopOnRefError;
922
923    // States
924    private boolean mTimedOut;
925    private boolean mRequestedWebKitData;
926    private boolean mFinishedRunning;
927
928    // Layout test controller variables.
929    private DumpDataType mDumpDataType;
930    private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR;
931    private boolean mDumpTopFrameAsText;
932    private boolean mDumpChildFramesAsText;
933    private boolean mWaitUntilDone;
934    private boolean mDumpTitleChanges;
935    private StringBuffer mTitleChanges;
936    private StringBuffer mDialogStrings;
937    private boolean mKeepWebHistory;
938    private Vector mWebHistory;
939    private boolean mDumpDatabaseCallbacks;
940    private StringBuffer mDatabaseCallbackStrings;
941    private StringBuffer mConsoleMessages;
942    private boolean mCanOpenWindows;
943
944    private boolean mPageFinished = false;
945    private boolean mDumpWebKitData = false;
946
947    static final String TIMEOUT_STR = "**Test timeout";
948    static final long DUMP_TIMEOUT_MS = 100000; // 100s timeout for dumping webview content
949
950    static final int MSG_TIMEOUT = 0;
951    static final int MSG_WEBKIT_DATA = 1;
952    static final int MSG_DUMP_TIMEOUT = 2;
953
954    static final String LOGTAG="TestShell";
955
956    static final String TEST_URL = "TestUrl";
957    static final String RESULT_FILE = "ResultFile";
958    static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis";
959    static final String UI_AUTO_TEST = "UiAutoTest";
960    static final String GET_DRAW_TIME = "GetDrawTime";
961    static final String SAVE_IMAGE = "SaveImage";
962    static final String TOTAL_TEST_COUNT = "TestCount";
963    static final String CURRENT_TEST_NUMBER = "TestNumber";
964    static final String STOP_ON_REF_ERROR = "StopOnReferenceError";
965
966    static final int DRAW_RUNS = 5;
967    static final String DRAW_TIME_LOG = Environment.getExternalStorageDirectory() +
968        "/android/page_draw_time.txt";
969
970    private boolean mIsGeolocationPermissionSet;
971    private boolean mGeolocationPermission;
972    private Map mPendingGeolocationPermissionCallbacks;
973}
974