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