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