TestShellActivity.java revision dad347c8b83aeb49eafae68774b7bfb59c956977
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 android.app.Activity;
20import android.app.AlertDialog;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.content.DialogInterface.OnClickListener;
24import android.graphics.Bitmap;
25import android.net.http.SslError;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.Message;
29import android.util.Log;
30import android.view.ViewGroup;
31import android.webkit.GeolocationPermissions;
32import android.webkit.HttpAuthHandler;
33import android.webkit.JsPromptResult;
34import android.webkit.JsResult;
35import android.webkit.SslErrorHandler;
36import android.webkit.WebChromeClient;
37import android.webkit.WebSettings;
38import android.webkit.WebStorage;
39import android.webkit.WebView;
40import android.webkit.WebViewClient;
41import android.widget.LinearLayout;
42
43import java.io.BufferedReader;
44import java.io.File;
45import java.io.FileOutputStream;
46import java.io.FileReader;
47import java.io.IOException;
48import java.net.MalformedURLException;
49import java.net.URL;
50import java.util.Vector;
51
52public class TestShellActivity extends Activity implements LayoutTestController {
53
54    static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP}
55
56    public class AsyncHandler extends Handler {
57        @Override
58        public void handleMessage(Message msg) {
59            if (msg.what == MSG_TIMEOUT) {
60                mTimedOut = true;
61                if(mCallback != null)
62                    mCallback.timedOut(mWebView.getUrl());
63                requestWebKitData();
64                return;
65            } else if (msg.what == MSG_WEBKIT_DATA) {
66                TestShellActivity.this.dump(mTimedOut, (String)msg.obj);
67                return;
68            }
69
70            super.handleMessage(msg);
71        }
72    }
73
74    public void requestWebKitData() {
75        Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA);
76
77        if (mRequestedWebKitData)
78            throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl());
79
80        mRequestedWebKitData = true;
81        switch (mDumpDataType) {
82            case DUMP_AS_TEXT:
83                mWebView.documentAsText(callback);
84                break;
85            case EXT_REPR:
86                mWebView.externalRepresentation(callback);
87                break;
88            default:
89                finished();
90                break;
91        }
92    }
93
94    public void clearCache() {
95      mWebView.freeMemory();
96    }
97
98    @Override
99    protected void onCreate(Bundle icicle) {
100        super.onCreate(icicle);
101
102        LinearLayout contentView = new LinearLayout(this);
103        contentView.setOrientation(LinearLayout.VERTICAL);
104        setContentView(contentView);
105
106        mWebView = new WebView(this);
107        mEventSender = new WebViewEventSender(mWebView);
108        mCallbackProxy = new CallbackProxy(mEventSender, this);
109
110        setupWebViewForLayoutTests(mWebView, mCallbackProxy);
111
112        contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f));
113
114        mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
115
116        // Expose window.gc function to JavaScript. JSC build exposes
117        // this function by default, but V8 requires the flag to turn it on.
118        // WebView::setJsFlags is noop in JSC build.
119        mWebView.setJsFlags("--expose_gc");
120
121        mHandler = new AsyncHandler();
122
123        Intent intent = getIntent();
124        if (intent != null) {
125            executeIntent(intent);
126        }
127    }
128
129    @Override
130    protected void onNewIntent(Intent intent) {
131        super.onNewIntent(intent);
132        executeIntent(intent);
133    }
134
135    private void executeIntent(Intent intent) {
136        resetTestStatus();
137        if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
138            return;
139        }
140
141        mTestUrl = intent.getStringExtra(TEST_URL);
142        if (mTestUrl == null) {
143            mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST);
144            if(mUiAutoTestPath != null) {
145                beginUiAutoTest();
146            }
147            return;
148        }
149
150        mResultFile = intent.getStringExtra(RESULT_FILE);
151        mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0);
152
153        Log.v(LOGTAG, "  Loading " + mTestUrl);
154        mWebView.loadUrl(mTestUrl);
155
156        if (mTimeoutInMillis > 0) {
157            // Create a timeout timer
158            Message m = mHandler.obtainMessage(MSG_TIMEOUT);
159            mHandler.sendMessageDelayed(m, mTimeoutInMillis);
160        }
161    }
162
163    private void beginUiAutoTest() {
164        try {
165            mTestListReader = new BufferedReader(
166                    new FileReader(mUiAutoTestPath));
167        } catch (IOException ioe) {
168            Log.e(LOGTAG, "Failed to open test list for read.", ioe);
169            finishUiAutoTest();
170            return;
171        }
172        moveToNextTest();
173    }
174
175    private void finishUiAutoTest() {
176        try {
177            if(mTestListReader != null)
178                mTestListReader.close();
179        } catch (IOException ioe) {
180            Log.w(LOGTAG, "Failed to close test list file.", ioe);
181        }
182        finished();
183    }
184
185    private void moveToNextTest() {
186        String url = null;
187        try {
188            url = mTestListReader.readLine();
189        } catch (IOException ioe) {
190            Log.e(LOGTAG, "Failed to read next test.", ioe);
191            finishUiAutoTest();
192            return;
193        }
194        if (url == null) {
195            mUiAutoTestPath = null;
196            finishUiAutoTest();
197            AlertDialog.Builder builder = new AlertDialog.Builder(this);
198            builder.setMessage("All tests finished. Exit?")
199                   .setCancelable(false)
200                   .setPositiveButton("Yes", new OnClickListener(){
201                       public void onClick(DialogInterface dialog, int which) {
202                           TestShellActivity.this.finish();
203                       }
204                   })
205                   .setNegativeButton("No", new OnClickListener(){
206                       public void onClick(DialogInterface dialog, int which) {
207                           dialog.cancel();
208                       }
209                   });
210            builder.create().show();
211            return;
212        }
213        url = "file://" + url;
214        Intent intent = new Intent(Intent.ACTION_VIEW);
215        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
216        intent.putExtra(TestShellActivity.TEST_URL, url);
217        intent.putExtra(TIMEOUT_IN_MILLIS, 10000);
218        executeIntent(intent);
219    }
220
221    @Override
222    protected void onStop() {
223        super.onStop();
224        mWebView.stopLoading();
225    }
226
227    @Override
228    protected void onDestroy() {
229        super.onDestroy();
230        mWebView.destroy();
231        mWebView = null;
232    }
233
234    @Override
235    public void onLowMemory() {
236        super.onLowMemory();
237        Log.e(LOGTAG, "Low memory, clearing caches");
238        mWebView.freeMemory();
239    }
240
241    // Dump the page
242    public void dump(boolean timeout, String webkitData) {
243        if (mResultFile == null || mResultFile.length() == 0) {
244            finished();
245            return;
246        }
247
248        try {
249            File parentDir = new File(mResultFile).getParentFile();
250            if (!parentDir.exists()) {
251                parentDir.mkdirs();
252            }
253
254            FileOutputStream os = new FileOutputStream(mResultFile);
255            if (timeout) {
256                Log.w("Layout test: Timeout", mResultFile);
257                os.write(TIMEOUT_STR.getBytes());
258                os.write('\n');
259            }
260            if (mDumpTitleChanges)
261                os.write(mTitleChanges.toString().getBytes());
262            if (mDialogStrings != null)
263                os.write(mDialogStrings.toString().getBytes());
264            mDialogStrings = null;
265            if (mDatabaseCallbackStrings != null)
266                os.write(mDatabaseCallbackStrings.toString().getBytes());
267            mDatabaseCallbackStrings = null;
268            if (mConsoleMessages != null)
269                os.write(mConsoleMessages.toString().getBytes());
270            mConsoleMessages = null;
271            if (webkitData != null)
272                os.write(webkitData.getBytes());
273            os.flush();
274            os.close();
275        } catch (IOException ex) {
276            Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage());
277        }
278
279        finished();
280    }
281
282    public void setCallback(TestShellCallback callback) {
283        mCallback = callback;
284    }
285
286    public void finished() {
287        if (mUiAutoTestPath != null) {
288            //don't really finish here
289            moveToNextTest();
290        } else {
291            if (mCallback != null) {
292                mCallback.finished();
293            }
294        }
295    }
296
297    public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) {
298        mDefaultDumpDataType = defaultDumpDataType;
299    }
300
301    // .......................................
302    // LayoutTestController Functions
303    public void dumpAsText() {
304        mDumpDataType = DumpDataType.DUMP_AS_TEXT;
305        if (mWebView != null) {
306            String url = mWebView.getUrl();
307            Log.v(LOGTAG, "dumpAsText called: "+url);
308        }
309    }
310
311    public void waitUntilDone() {
312        mWaitUntilDone = true;
313        String url = mWebView.getUrl();
314        Log.v(LOGTAG, "waitUntilDone called: " + url);
315    }
316
317    public void notifyDone() {
318        String url = mWebView.getUrl();
319        Log.v(LOGTAG, "notifyDone called: " + url);
320        if (mWaitUntilDone) {
321            mWaitUntilDone = false;
322            mChromeClient.onProgressChanged(mWebView, 100);
323        }
324    }
325
326    public void display() {
327        mWebView.invalidate();
328    }
329
330    public void clearBackForwardList() {
331        mWebView.clearHistory();
332
333    }
334
335    public void dumpBackForwardList() {
336        //printf("\n============== Back Forward List ==============\n");
337        // mWebHistory
338        //printf("===============================================\n");
339
340    }
341
342    public void dumpChildFrameScrollPositions() {
343        // TODO Auto-generated method stub
344
345    }
346
347    public void dumpEditingCallbacks() {
348        // TODO Auto-generated method stub
349
350    }
351
352    public void dumpSelectionRect() {
353        // TODO Auto-generated method stub
354
355    }
356
357    public void dumpTitleChanges() {
358        if (!mDumpTitleChanges) {
359            mTitleChanges = new StringBuffer();
360        }
361        mDumpTitleChanges = true;
362    }
363
364    public void keepWebHistory() {
365        if (!mKeepWebHistory) {
366            mWebHistory = new Vector();
367        }
368        mKeepWebHistory = true;
369    }
370
371    public void queueBackNavigation(int howfar) {
372        // TODO Auto-generated method stub
373
374    }
375
376    public void queueForwardNavigation(int howfar) {
377        // TODO Auto-generated method stub
378
379    }
380
381    public void queueLoad(String Url, String frameTarget) {
382        // TODO Auto-generated method stub
383
384    }
385
386    public void queueReload() {
387        mWebView.reload();
388    }
389
390    public void queueScript(String scriptToRunInCurrentContext) {
391        mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);
392    }
393
394    public void repaintSweepHorizontally() {
395        // TODO Auto-generated method stub
396
397    }
398
399    public void setAcceptsEditing(boolean b) {
400        // TODO Auto-generated method stub
401
402    }
403
404    public void setMainFrameIsFirstResponder(boolean b) {
405        // TODO Auto-generated method stub
406
407    }
408
409    public void setWindowIsKey(boolean b) {
410        // This is meant to show/hide the window. The best I can find
411        // is setEnabled()
412        mWebView.setEnabled(b);
413    }
414
415    public void testRepaint() {
416        mWebView.invalidate();
417    }
418
419    public void dumpDatabaseCallbacks() {
420        Log.v(LOGTAG, "dumpDatabaseCallbacks called.");
421        mDumpDatabaseCallbacks = true;
422    }
423
424    public void setCanOpenWindows() {
425        Log.v(LOGTAG, "setCanOpenWindows called.");
426        mCanOpenWindows = true;
427    }
428
429    /**
430     * Sets the Geolocation permission state to be used for all future requests.
431     */
432    public void setGeolocationPermission(boolean allow) {
433        mGeolocationPermissionSet = true;
434        mGeolocationPermission = allow;
435    }
436
437    private final WebViewClient mViewClient = new WebViewClient(){
438        @Override
439        public void onPageFinished(WebView view, String url) {
440            Log.v(LOGTAG, "onPageFinished, url=" + url);
441            super.onPageFinished(view, url);
442        }
443
444        @Override
445        public void onPageStarted(WebView view, String url, Bitmap favicon) {
446            Log.v(LOGTAG, "onPageStarted, url=" + url);
447            super.onPageStarted(view, url, favicon);
448        }
449
450        @Override
451        public void onReceivedError(WebView view, int errorCode, String description,
452                String failingUrl) {
453            Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode
454                    + ", desc=" + description + ", url=" + failingUrl);
455            super.onReceivedError(view, errorCode, description, failingUrl);
456        }
457
458        @Override
459        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
460                String host, String realm) {
461            handler.cancel();
462        }
463
464        @Override
465        public void onReceivedSslError(WebView view, SslErrorHandler handler,
466                SslError error) {
467            handler.proceed();
468        }
469    };
470
471
472    private final WebChromeClient mChromeClient = new WebChromeClient() {
473        @Override
474        public void onProgressChanged(WebView view, int newProgress) {
475            if (newProgress == 100) {
476                if (!mTimedOut && !mWaitUntilDone && !mRequestedWebKitData) {
477                    String url = mWebView.getUrl();
478                    Log.v(LOGTAG, "Finished: "+ url);
479                    mHandler.removeMessages(MSG_TIMEOUT);
480                    requestWebKitData();
481                } else {
482                    String url = mWebView.getUrl();
483                    if (mTimedOut) {
484                        Log.v(LOGTAG, "Timed out before finishing: " + url);
485                    } else if (mWaitUntilDone) {
486                        Log.v(LOGTAG, "Waiting for notifyDone: " + url);
487                    } else if (mRequestedWebKitData) {
488                        Log.v(LOGTAG, "Requested webkit data ready: " + url);
489                    }
490                }
491            }
492        }
493
494        @Override
495        public void onReceivedTitle(WebView view, String title) {
496            if (title.length() > 30)
497                title = "..."+title.substring(title.length()-30);
498            setTitle(title);
499            if (mDumpTitleChanges) {
500                mTitleChanges.append("TITLE CHANGED: ");
501                mTitleChanges.append(title);
502                mTitleChanges.append("\n");
503            }
504        }
505
506        @Override
507        public boolean onJsAlert(WebView view, String url, String message,
508                JsResult result) {
509            if (mDialogStrings == null) {
510                mDialogStrings = new StringBuffer();
511            }
512            mDialogStrings.append("ALERT: ");
513            mDialogStrings.append(message);
514            mDialogStrings.append('\n');
515            result.confirm();
516            return true;
517        }
518
519        @Override
520        public boolean onJsConfirm(WebView view, String url, String message,
521                JsResult result) {
522            if (mDialogStrings == null) {
523                mDialogStrings = new StringBuffer();
524            }
525            mDialogStrings.append("CONFIRM: ");
526            mDialogStrings.append(message);
527            mDialogStrings.append('\n');
528            result.confirm();
529            return true;
530        }
531
532        @Override
533        public boolean onJsPrompt(WebView view, String url, String message,
534                String defaultValue, JsPromptResult result) {
535            if (mDialogStrings == null) {
536                mDialogStrings = new StringBuffer();
537            }
538            mDialogStrings.append("PROMPT: ");
539            mDialogStrings.append(message);
540            mDialogStrings.append(", default text: ");
541            mDialogStrings.append(defaultValue);
542            mDialogStrings.append('\n');
543            result.confirm();
544            return true;
545        }
546
547        @Override
548        public boolean onJsTimeout() {
549            Log.v(LOGTAG, "JavaScript timeout");
550            return false;
551        }
552
553        @Override
554        public void onExceededDatabaseQuota(String url_str,
555                String databaseIdentifier, long currentQuota, long totalUsedQuota,
556                WebStorage.QuotaUpdater callback) {
557            if (mDumpDatabaseCallbacks) {
558                if (mDatabaseCallbackStrings == null) {
559                    mDatabaseCallbackStrings = new StringBuffer();
560                }
561
562                String protocol = "";
563                String host = "";
564                int port = 0;
565
566                try {
567                    URL url = new URL(url_str);
568                    protocol = url.getProtocol();
569                    host = url.getHost();
570                    if (url.getPort() > -1) {
571                        port = url.getPort();
572                    }
573                } catch (MalformedURLException e) {}
574
575                String databaseCallbackString =
576                        "UI DELEGATE DATABASE CALLBACK: " +
577                        "exceededDatabaseQuotaForSecurityOrigin:{" + protocol +
578                        ", " + host + ", " + port + "} database:" +
579                        databaseIdentifier + "\n";
580                Log.v(LOGTAG, "LOG: "+databaseCallbackString);
581                mDatabaseCallbackStrings.append(databaseCallbackString);
582            }
583            // Give 5MB more quota.
584            callback.updateQuota(currentQuota + 1024 * 1024 * 5);
585        }
586
587        /**
588         * Instructs the client to show a prompt to ask the user to set the
589         * Geolocation permission state for the specified origin.
590         */
591        @Override
592        public void onGeolocationPermissionsShowPrompt(String origin,
593                GeolocationPermissions.Callback callback) {
594            if (mGeolocationPermissionSet) {
595                callback.invoke(origin, mGeolocationPermission, false);
596            }
597        }
598
599        @Override
600        public void addMessageToConsole(String message, int lineNumber,
601                String sourceID) {
602            if (mConsoleMessages == null) {
603                mConsoleMessages = new StringBuffer();
604            }
605            String consoleMessage = "CONSOLE MESSAGE: line "
606                    + lineNumber +": "+ message +"\n";
607            mConsoleMessages.append(consoleMessage);
608            Log.v(LOGTAG, "LOG: "+consoleMessage);
609        }
610
611        @Override
612        public boolean onCreateWindow(WebView view, boolean dialog,
613                boolean userGesture, Message resultMsg) {
614            if (!mCanOpenWindows) {
615                return false;
616            }
617
618            // We never display the new window, just create the view and
619            // allow it's content to execute and be recorded by the test
620            // runner.
621
622            WebView newWindowView = new WebView(TestShellActivity.this);
623            setupWebViewForLayoutTests(newWindowView, mCallbackProxy);
624            WebView.WebViewTransport transport =
625                    (WebView.WebViewTransport) resultMsg.obj;
626            transport.setWebView(newWindowView);
627            resultMsg.sendToTarget();
628            return true;
629        }
630    };
631
632    private void resetTestStatus() {
633        mWaitUntilDone = false;
634        mDumpDataType = mDefaultDumpDataType;
635        mTimedOut = false;
636        mDumpTitleChanges = false;
637        mRequestedWebKitData = false;
638        mDumpDatabaseCallbacks = false;
639        mCanOpenWindows = false;
640        mEventSender.resetMouse();
641    }
642
643    private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
644        if (webview == null) {
645            return;
646        }
647
648        WebSettings settings = webview.getSettings();
649        settings.setAppCacheEnabled(true);
650        settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
651        settings.setAppCacheMaxSize(Long.MAX_VALUE);
652        settings.setJavaScriptEnabled(true);
653        settings.setJavaScriptCanOpenWindowsAutomatically(true);
654        settings.setSupportMultipleWindows(true);
655        settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
656        settings.setDatabaseEnabled(true);
657        settings.setDatabasePath(getDir("databases",0).getAbsolutePath());
658        settings.setDomStorageEnabled(true);
659        settings.setWorkersEnabled(false);
660
661        webview.addJavascriptInterface(callbackProxy, "layoutTestController");
662        webview.addJavascriptInterface(callbackProxy, "eventSender");
663
664        webview.setWebChromeClient(mChromeClient);
665        webview.setWebViewClient(mViewClient);
666    }
667
668    private WebView mWebView;
669    private WebViewEventSender mEventSender;
670    private AsyncHandler mHandler;
671    private TestShellCallback mCallback;
672
673    private CallbackProxy mCallbackProxy;
674
675    private String mTestUrl;
676    private String mResultFile;
677    private int mTimeoutInMillis;
678    private String mUiAutoTestPath;
679    private BufferedReader mTestListReader;
680
681    // States
682    private boolean mTimedOut;
683    private boolean mRequestedWebKitData;
684    private boolean mFinishedRunning;
685
686    // Layout test controller variables.
687    private DumpDataType mDumpDataType;
688    private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR;
689    private boolean mWaitUntilDone;
690    private boolean mDumpTitleChanges;
691    private StringBuffer mTitleChanges;
692    private StringBuffer mDialogStrings;
693    private boolean mKeepWebHistory;
694    private Vector mWebHistory;
695    private boolean mDumpDatabaseCallbacks;
696    private StringBuffer mDatabaseCallbackStrings;
697    private StringBuffer mConsoleMessages;
698    private boolean mCanOpenWindows;
699
700    static final String TIMEOUT_STR = "**Test timeout";
701
702    static final int MSG_TIMEOUT = 0;
703    static final int MSG_WEBKIT_DATA = 1;
704
705    static final String LOGTAG="TestShell";
706
707    static final String TEST_URL = "TestUrl";
708    static final String RESULT_FILE = "ResultFile";
709    static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis";
710    static final String UI_AUTO_TEST = "UiAutoTest";
711
712    private boolean mGeolocationPermissionSet;
713    private boolean mGeolocationPermission;
714}
715