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