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