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