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