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