TestShellActivity.java revision 3ae8c42152d890ab771053fa6b16b038ee44326d
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.DialogInterface.OnClickListener;
26import android.content.Intent;
27import android.graphics.Bitmap;
28import android.net.http.SslError;
29import android.os.Bundle;
30import android.os.Environment;
31import android.os.Handler;
32import android.os.Message;
33import android.util.Log;
34import android.view.ViewGroup;
35import android.view.Window;
36import android.webkit.ConsoleMessage;
37import android.webkit.CookieManager;
38import android.webkit.GeolocationPermissions;
39import android.webkit.HttpAuthHandler;
40import android.webkit.JsPromptResult;
41import android.webkit.JsResult;
42import android.webkit.SslErrorHandler;
43import android.webkit.WebChromeClient;
44import android.webkit.WebSettings;
45import android.webkit.WebSettingsClassic;
46import android.webkit.WebStorage;
47import android.webkit.WebView;
48import android.webkit.WebViewClassic;
49import android.webkit.WebViewClient;
50import android.widget.LinearLayout;
51
52import java.io.BufferedReader;
53import java.io.File;
54import java.io.FileOutputStream;
55import java.io.FileReader;
56import java.io.IOException;
57import java.net.MalformedURLException;
58import java.net.URL;
59import java.util.HashMap;
60import java.util.Iterator;
61import java.util.Map;
62import java.util.Vector;
63
64public class TestShellActivity extends Activity implements LayoutTestController {
65
66    static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP}
67
68    // String constants for use with layoutTestController.overridePreferences
69    private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED =
70            "WebKitOfflineWebApplicationCacheEnabled";
71    private final String WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY = "WebKitUsesPageCachePreferenceKey";
72
73    public class AsyncHandler extends Handler {
74        @Override
75        public void handleMessage(Message msg) {
76            if (msg.what == MSG_TIMEOUT) {
77                mTimedOut = true;
78                mWebView.stopLoading();
79                if (mCallback != null)
80                    mCallback.timedOut(mWebView.getUrl());
81                if (!mRequestedWebKitData) {
82                    requestWebKitData();
83                } else {
84                    // if timed out and webkit data has been dumped before
85                    // finish directly
86                    finished();
87                }
88                return;
89            } else if (msg.what == MSG_WEBKIT_DATA) {
90                Log.v(LOGTAG, "Received WebView dump data");
91                mHandler.removeMessages(MSG_DUMP_TIMEOUT);
92                TestShellActivity.this.dump(mTimedOut, (String)msg.obj);
93                return;
94            } else if (msg.what == MSG_DUMP_TIMEOUT) {
95                throw new RuntimeException("WebView dump timeout, is it pegged?");
96            }
97            super.handleMessage(msg);
98        }
99    }
100
101    public void requestWebKitData() {
102        setDumpTimeout(DUMP_TIMEOUT_MS);
103        Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA);
104
105        if (mRequestedWebKitData)
106            throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl());
107
108        mRequestedWebKitData = true;
109        Log.v(LOGTAG, "message sent to WebView to dump text.");
110        switch (mDumpDataType) {
111            case DUMP_AS_TEXT:
112                callback.arg1 = mDumpTopFrameAsText ? 1 : 0;
113                callback.arg2 = mDumpChildFramesAsText ? 1 : 0;
114                mWebViewClassic.documentAsText(callback);
115                break;
116            case EXT_REPR:
117                mWebViewClassic.externalRepresentation(callback);
118                break;
119            default:
120                finished();
121                break;
122        }
123    }
124
125    private void setDumpTimeout(long timeout) {
126        Log.v(LOGTAG, "setting dump timeout at " + timeout);
127        Message msg = mHandler.obtainMessage(MSG_DUMP_TIMEOUT);
128        mHandler.sendMessageDelayed(msg, timeout);
129    }
130
131    public void clearCache() {
132      mWebView.freeMemory();
133    }
134
135    @Override
136    protected void onCreate(Bundle icicle) {
137        super.onCreate(icicle);
138        requestWindowFeature(Window.FEATURE_PROGRESS);
139
140        LinearLayout contentView = new LinearLayout(this);
141        contentView.setOrientation(LinearLayout.VERTICAL);
142        setContentView(contentView);
143
144        CookieManager.setAcceptFileSchemeCookies(true);
145        mWebView = new WebView(this);
146        mWebViewClassic = WebViewClassic.fromWebView(mWebView);
147        mEventSender = new WebViewEventSender(mWebView);
148        mCallbackProxy = new CallbackProxy(mEventSender, this);
149
150        mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController");
151        mWebView.addJavascriptInterface(mCallbackProxy, "eventSender");
152        setupWebViewForLayoutTests(mWebView, mCallbackProxy);
153
154        contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0.0f));
155
156        mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
157
158        // Expose window.gc function to JavaScript. JSC build exposes
159        // this function by default, but V8 requires the flag to turn it on.
160        // WebView::setJsFlags is noop in JSC build.
161        mWebViewClassic.setJsFlags("--expose_gc");
162
163        mHandler = new AsyncHandler();
164
165        Intent intent = getIntent();
166        if (intent != null) {
167            executeIntent(intent);
168        }
169
170        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
171        mWebViewClassic.useMockDeviceOrientation();
172    }
173
174    @Override
175    protected void onNewIntent(Intent intent) {
176        super.onNewIntent(intent);
177        executeIntent(intent);
178    }
179
180    private void executeIntent(Intent intent) {
181        resetTestStatus();
182        if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
183            return;
184        }
185
186        mTotalTestCount = intent.getIntExtra(TOTAL_TEST_COUNT, mTotalTestCount);
187        mCurrentTestNumber = intent.getIntExtra(CURRENT_TEST_NUMBER, mCurrentTestNumber);
188
189        mTestUrl = intent.getStringExtra(TEST_URL);
190        if (mTestUrl == null) {
191            mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST);
192            if(mUiAutoTestPath != null) {
193                beginUiAutoTest();
194            }
195            return;
196        }
197
198        mResultFile = intent.getStringExtra(RESULT_FILE);
199        mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0);
200        mStopOnRefError = intent.getBooleanExtra(STOP_ON_REF_ERROR, false);
201        setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount);
202        float ratio = (float)mCurrentTestNumber / mTotalTestCount;
203        int progress = (int)(ratio * Window.PROGRESS_END);
204        getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress);
205
206        Log.v(LOGTAG, "  Loading " + mTestUrl);
207
208        if (mTestUrl.contains("/dumpAsText/")) {
209            dumpAsText(false);
210        }
211
212        mWebView.loadUrl(mTestUrl);
213
214        if (mTimeoutInMillis > 0) {
215            // Create a timeout timer
216            Message m = mHandler.obtainMessage(MSG_TIMEOUT);
217            mHandler.sendMessageDelayed(m, mTimeoutInMillis);
218        }
219    }
220
221    private void beginUiAutoTest() {
222        try {
223            mTestListReader = new BufferedReader(
224                    new FileReader(mUiAutoTestPath));
225        } catch (IOException ioe) {
226            Log.e(LOGTAG, "Failed to open test list for read.", ioe);
227            finishUiAutoTest();
228            return;
229        }
230        moveToNextTest();
231    }
232
233    private void finishUiAutoTest() {
234        try {
235            if(mTestListReader != null)
236                mTestListReader.close();
237        } catch (IOException ioe) {
238            Log.w(LOGTAG, "Failed to close test list file.", ioe);
239        }
240        ForwardService.getForwardService().stopForwardService();
241        finished();
242    }
243
244    private void moveToNextTest() {
245        String url = null;
246        try {
247            url = mTestListReader.readLine();
248        } catch (IOException ioe) {
249            Log.e(LOGTAG, "Failed to read next test.", ioe);
250            finishUiAutoTest();
251            return;
252        }
253        if (url == null) {
254            mUiAutoTestPath = null;
255            finishUiAutoTest();
256            AlertDialog.Builder builder = new AlertDialog.Builder(this);
257            builder.setMessage("All tests finished. Exit?")
258                   .setCancelable(false)
259                   .setPositiveButton("Yes", new OnClickListener(){
260                       public void onClick(DialogInterface dialog, int which) {
261                           TestShellActivity.this.finish();
262                       }
263                   })
264                   .setNegativeButton("No", new OnClickListener(){
265                       public void onClick(DialogInterface dialog, int which) {
266                           dialog.cancel();
267                       }
268                   });
269            builder.create().show();
270            return;
271        }
272        Intent intent = new Intent(Intent.ACTION_VIEW);
273        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
274        intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(url));
275        intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, ++mCurrentTestNumber);
276        intent.putExtra(TIMEOUT_IN_MILLIS, 10000);
277        executeIntent(intent);
278    }
279
280    @Override
281    protected void onStop() {
282        super.onStop();
283        mWebView.stopLoading();
284    }
285
286    @Override
287    protected void onDestroy() {
288        super.onDestroy();
289        mWebView.destroy();
290        mWebView = null;
291        mWebViewClassic = null;
292    }
293
294    @Override
295    public void onLowMemory() {
296        super.onLowMemory();
297        Log.e(LOGTAG, "Low memory, clearing caches");
298        mWebView.freeMemory();
299    }
300
301    // Dump the page
302    public void dump(boolean timeout, String webkitData) {
303        mDumpWebKitData = true;
304        if (mResultFile == null || mResultFile.length() == 0) {
305            finished();
306            return;
307        }
308
309        if (mCallback != null) {
310            mCallback.dumpResult(webkitData);
311        }
312
313        try {
314            File parentDir = new File(mResultFile).getParentFile();
315            if (!parentDir.exists()) {
316                parentDir.mkdirs();
317            }
318
319            FileOutputStream os = new FileOutputStream(mResultFile);
320            if (timeout) {
321                Log.w("Layout test: Timeout", mResultFile);
322                os.write(TIMEOUT_STR.getBytes());
323                os.write('\n');
324            }
325            if (mDumpTitleChanges)
326                os.write(mTitleChanges.toString().getBytes());
327            if (mDialogStrings != null)
328                os.write(mDialogStrings.toString().getBytes());
329            mDialogStrings = null;
330            if (mDatabaseCallbackStrings != null)
331                os.write(mDatabaseCallbackStrings.toString().getBytes());
332            mDatabaseCallbackStrings = null;
333            if (mConsoleMessages != null)
334                os.write(mConsoleMessages.toString().getBytes());
335            mConsoleMessages = null;
336            if (webkitData != null)
337                os.write(webkitData.getBytes());
338            os.flush();
339            os.close();
340        } catch (IOException ex) {
341            Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage());
342        }
343
344        finished();
345    }
346
347    public void setCallback(TestShellCallback callback) {
348        mCallback = callback;
349    }
350
351    public boolean finished() {
352        if (canMoveToNextTest()) {
353            mHandler.removeMessages(MSG_TIMEOUT);
354            if (mUiAutoTestPath != null) {
355                //don't really finish here
356                moveToNextTest();
357            } else {
358                if (mCallback != null) {
359                    mCallback.finished();
360                }
361            }
362            return true;
363        }
364        return false;
365    }
366
367    public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) {
368        mDefaultDumpDataType = defaultDumpDataType;
369    }
370
371    // .......................................
372    // LayoutTestController Functions
373    public void dumpAsText(boolean enablePixelTests) {
374        // Added after webkit update to r63859. See trac.webkit.org/changeset/63730.
375        if (enablePixelTests) {
376            Log.v(LOGTAG, "dumpAsText(enablePixelTests == true) not implemented on Android!");
377        }
378
379        mDumpDataType = DumpDataType.DUMP_AS_TEXT;
380        mDumpTopFrameAsText = true;
381        if (mWebView != null) {
382            String url = mWebView.getUrl();
383            Log.v(LOGTAG, "dumpAsText called: "+url);
384        }
385    }
386
387    public void dumpChildFramesAsText() {
388        mDumpDataType = DumpDataType.DUMP_AS_TEXT;
389        mDumpChildFramesAsText = true;
390        if (mWebView != null) {
391            String url = mWebView.getUrl();
392            Log.v(LOGTAG, "dumpChildFramesAsText called: "+url);
393        }
394    }
395
396    public void waitUntilDone() {
397        mWaitUntilDone = true;
398        String url = mWebView.getUrl();
399        Log.v(LOGTAG, "waitUntilDone called: " + url);
400    }
401
402    public void notifyDone() {
403        String url = mWebView.getUrl();
404        Log.v(LOGTAG, "notifyDone called: " + url);
405        if (mWaitUntilDone) {
406            mWaitUntilDone = false;
407            if (!mRequestedWebKitData && !mTimedOut && !finished()) {
408                requestWebKitData();
409            }
410        }
411    }
412
413    public void display() {
414        mWebView.invalidate();
415    }
416
417    public void clearBackForwardList() {
418        mWebView.clearHistory();
419
420    }
421
422    public void dumpBackForwardList() {
423        //printf("\n============== Back Forward List ==============\n");
424        // mWebHistory
425        //printf("===============================================\n");
426
427    }
428
429    public void dumpChildFrameScrollPositions() {
430        // TODO Auto-generated method stub
431
432    }
433
434    public void dumpEditingCallbacks() {
435        // TODO Auto-generated method stub
436
437    }
438
439    public void dumpSelectionRect() {
440        // TODO Auto-generated method stub
441
442    }
443
444    public void dumpTitleChanges() {
445        if (!mDumpTitleChanges) {
446            mTitleChanges = new StringBuffer();
447        }
448        mDumpTitleChanges = true;
449    }
450
451    public void keepWebHistory() {
452        if (!mKeepWebHistory) {
453            mWebHistory = new Vector();
454        }
455        mKeepWebHistory = true;
456    }
457
458    public void queueBackNavigation(int howfar) {
459        // TODO Auto-generated method stub
460
461    }
462
463    public void queueForwardNavigation(int howfar) {
464        // TODO Auto-generated method stub
465
466    }
467
468    public void queueLoad(String Url, String frameTarget) {
469        // TODO Auto-generated method stub
470
471    }
472
473    public void queueReload() {
474        mWebView.reload();
475    }
476
477    public void queueScript(String scriptToRunInCurrentContext) {
478        mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);
479    }
480
481    public void repaintSweepHorizontally() {
482        // TODO Auto-generated method stub
483
484    }
485
486    public void setAcceptsEditing(boolean b) {
487        // TODO Auto-generated method stub
488
489    }
490
491    public void setMainFrameIsFirstResponder(boolean b) {
492        // TODO Auto-generated method stub
493
494    }
495
496    public void setWindowIsKey(boolean b) {
497        // This is meant to show/hide the window. The best I can find
498        // is setEnabled()
499        mWebView.setEnabled(b);
500    }
501
502    public void testRepaint() {
503        mWebView.invalidate();
504    }
505
506    public void dumpDatabaseCallbacks() {
507        Log.v(LOGTAG, "dumpDatabaseCallbacks called.");
508        mDumpDatabaseCallbacks = true;
509    }
510
511    public void setCanOpenWindows() {
512        Log.v(LOGTAG, "setCanOpenWindows called.");
513        mCanOpenWindows = true;
514    }
515
516    /**
517     * Sets the Geolocation permission state to be used for all future requests.
518     */
519    public void setGeolocationPermission(boolean allow) {
520        mIsGeolocationPermissionSet = true;
521        mGeolocationPermission = allow;
522
523        if (mPendingGeolocationPermissionCallbacks != null) {
524            Iterator iter = mPendingGeolocationPermissionCallbacks.keySet().iterator();
525            while (iter.hasNext()) {
526                GeolocationPermissions.Callback callback =
527                        (GeolocationPermissions.Callback) iter.next();
528                String origin = (String) mPendingGeolocationPermissionCallbacks.get(callback);
529                callback.invoke(origin, mGeolocationPermission, false);
530            }
531            mPendingGeolocationPermissionCallbacks = null;
532        }
533    }
534
535    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
536            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
537        WebViewClassic.fromWebView(mWebView).setMockDeviceOrientation(canProvideAlpha, alpha,
538                canProvideBeta, beta, canProvideGamma, gamma);
539    }
540
541    public void overridePreference(String key, boolean value) {
542        // TODO: We should look up the correct WebView for the frame which
543        // called the layoutTestController method. Currently, we just use the
544        // WebView for the main frame. EventSender suffers from the same
545        // problem.
546        if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) {
547            mWebViewClassic.getSettings().setAppCacheEnabled(value);
548        } else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) {
549            // Cache the maximum possible number of pages.
550            mWebViewClassic.getSettings().setPageCacheCapacity(Integer.MAX_VALUE);
551        } else {
552            Log.w(LOGTAG, "LayoutTestController.overridePreference(): " +
553                  "Unsupported preference '" + key + "'");
554        }
555    }
556
557    public void setXSSAuditorEnabled (boolean flag) {
558        mWebViewClassic.getSettings().setXSSAuditorEnabled(flag);
559    }
560
561    private final WebViewClient mViewClient = new WebViewClient(){
562        @Override
563        public void onPageFinished(WebView view, String url) {
564            Log.v(LOGTAG, "onPageFinished, url=" + url);
565            mPageFinished = true;
566
567            // Calling finished() will check if we've met all the conditions for completing
568            // this test and move to the next one if we are ready. Otherwise we ask WebCore to
569            // dump the page.
570            if (finished()) {
571                return;
572            }
573
574            if (!mWaitUntilDone && !mRequestedWebKitData && !mTimedOut) {
575                requestWebKitData();
576            } else {
577                if (mWaitUntilDone) {
578                    Log.v(LOGTAG, "page finished loading but waiting for notifyDone to be called: " + url);
579                }
580
581                if (mRequestedWebKitData) {
582                    Log.v(LOGTAG, "page finished loading but webkit data has already been requested: " + url);
583                }
584
585                if (mTimedOut) {
586                    Log.v(LOGTAG, "page finished loading but already timed out: " + url);
587                }
588            }
589
590            super.onPageFinished(view, url);
591        }
592
593        @Override
594        public void onPageStarted(WebView view, String url, Bitmap favicon) {
595            Log.v(LOGTAG, "onPageStarted, url=" + url);
596            mPageFinished = false;
597            super.onPageStarted(view, url, favicon);
598        }
599
600        @Override
601        public void onReceivedError(WebView view, int errorCode, String description,
602                String failingUrl) {
603            Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode
604                    + ", desc=" + description + ", url=" + failingUrl);
605            super.onReceivedError(view, errorCode, description, failingUrl);
606        }
607
608        @Override
609        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
610                String host, String realm) {
611            if (handler.useHttpAuthUsernamePassword() && view != null) {
612                String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
613                if (credentials != null && credentials.length == 2) {
614                    handler.proceed(credentials[0], credentials[1]);
615                    return;
616                }
617            }
618            handler.cancel();
619        }
620
621        @Override
622        public void onReceivedSslError(WebView view, SslErrorHandler handler,
623                SslError error) {
624            handler.proceed();
625        }
626    };
627
628
629    private final WebChromeClient mChromeClient = new WebChromeClient() {
630        @Override
631        public void onReceivedTitle(WebView view, String title) {
632            setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount + ": "+ title);
633            if (mDumpTitleChanges) {
634                mTitleChanges.append("TITLE CHANGED: ");
635                mTitleChanges.append(title);
636                mTitleChanges.append("\n");
637            }
638        }
639
640        @Override
641        public boolean onJsAlert(WebView view, String url, String message,
642                JsResult result) {
643            if (mDialogStrings == null) {
644                mDialogStrings = new StringBuffer();
645            }
646            mDialogStrings.append("ALERT: ");
647            mDialogStrings.append(message);
648            mDialogStrings.append('\n');
649            result.confirm();
650            return true;
651        }
652
653        @Override
654        public boolean onJsConfirm(WebView view, String url, String message,
655                JsResult result) {
656            if (mDialogStrings == null) {
657                mDialogStrings = new StringBuffer();
658            }
659            mDialogStrings.append("CONFIRM: ");
660            mDialogStrings.append(message);
661            mDialogStrings.append('\n');
662            result.confirm();
663            return true;
664        }
665
666        @Override
667        public boolean onJsPrompt(WebView view, String url, String message,
668                String defaultValue, JsPromptResult result) {
669            if (mDialogStrings == null) {
670                mDialogStrings = new StringBuffer();
671            }
672            mDialogStrings.append("PROMPT: ");
673            mDialogStrings.append(message);
674            mDialogStrings.append(", default text: ");
675            mDialogStrings.append(defaultValue);
676            mDialogStrings.append('\n');
677            result.confirm();
678            return true;
679        }
680
681        @Override
682        public boolean onJsTimeout() {
683            Log.v(LOGTAG, "JavaScript timeout");
684            return false;
685        }
686
687        @Override
688        public void onExceededDatabaseQuota(String url_str,
689                String databaseIdentifier, long currentQuota,
690                long estimatedSize, long totalUsedQuota,
691                WebStorage.QuotaUpdater callback) {
692            if (mDumpDatabaseCallbacks) {
693                if (mDatabaseCallbackStrings == null) {
694                    mDatabaseCallbackStrings = new StringBuffer();
695                }
696
697                String protocol = "";
698                String host = "";
699                int port = 0;
700
701                try {
702                    URL url = new URL(url_str);
703                    protocol = url.getProtocol();
704                    host = url.getHost();
705                    if (url.getPort() > -1) {
706                        port = url.getPort();
707                    }
708                } catch (MalformedURLException e) {}
709
710                String databaseCallbackString =
711                        "UI DELEGATE DATABASE CALLBACK: " +
712                        "exceededDatabaseQuotaForSecurityOrigin:{" + protocol +
713                        ", " + host + ", " + port + "} database:" +
714                        databaseIdentifier + "\n";
715                Log.v(LOGTAG, "LOG: "+databaseCallbackString);
716                mDatabaseCallbackStrings.append(databaseCallbackString);
717            }
718            // Give 5MB more quota.
719            callback.updateQuota(currentQuota + 1024 * 1024 * 5);
720        }
721
722        /**
723         * Instructs the client to show a prompt to ask the user to set the
724         * Geolocation permission state for the specified origin.
725         */
726        @Override
727        public void onGeolocationPermissionsShowPrompt(String origin,
728                GeolocationPermissions.Callback callback) {
729            if (mIsGeolocationPermissionSet) {
730                callback.invoke(origin, mGeolocationPermission, false);
731                return;
732            }
733            if (mPendingGeolocationPermissionCallbacks == null) {
734                mPendingGeolocationPermissionCallbacks =
735                        new HashMap<GeolocationPermissions.Callback, String>();
736            }
737            mPendingGeolocationPermissionCallbacks.put(callback, origin);
738        }
739
740        @Override
741        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
742            String msg = "CONSOLE MESSAGE: line " + consoleMessage.lineNumber() + ": "
743                    + consoleMessage.message() + "\n";
744            if (mConsoleMessages == null) {
745                mConsoleMessages = new StringBuffer();
746            }
747            mConsoleMessages.append(msg);
748            Log.v(LOGTAG, "LOG: " + msg);
749            // the rationale here is that if there's an error of either type, and the test was
750            // waiting for "notifyDone" signal to finish, then there's no point in waiting
751            // anymore because the JS execution is already terminated at this point and a
752            // "notifyDone" will never come out so it's just wasting time till timeout kicks in
753            if ((msg.contains("Uncaught ReferenceError:") || msg.contains("Uncaught TypeError:"))
754                    && mWaitUntilDone && mStopOnRefError) {
755                Log.w(LOGTAG, "Terminating test case on uncaught ReferenceError or TypeError.");
756                mHandler.postDelayed(new Runnable() {
757                    public void run() {
758                        notifyDone();
759                    }
760                }, 500);
761            }
762            return true;
763        }
764
765        @Override
766        public boolean onCreateWindow(WebView view, boolean dialog,
767                boolean userGesture, Message resultMsg) {
768            if (!mCanOpenWindows) {
769                // We can't open windows, so just send null back.
770                WebView.WebViewTransport transport =
771                        (WebView.WebViewTransport) resultMsg.obj;
772                transport.setWebView(null);
773                resultMsg.sendToTarget();
774                return true;
775            }
776
777            // We never display the new window, just create the view and
778            // allow it's content to execute and be recorded by the test
779            // runner.
780
781            HashMap<String, Object> jsIfaces = new HashMap<String, Object>();
782            jsIfaces.put("layoutTestController", mCallbackProxy);
783            jsIfaces.put("eventSender", mCallbackProxy);
784            WebView newWindowView = new NewWindowWebView(TestShellActivity.this, jsIfaces);
785            setupWebViewForLayoutTests(newWindowView, mCallbackProxy);
786            WebView.WebViewTransport transport =
787                    (WebView.WebViewTransport) resultMsg.obj;
788            transport.setWebView(newWindowView);
789            resultMsg.sendToTarget();
790            return true;
791        }
792
793        @Override
794        public void onCloseWindow(WebView view) {
795            view.destroy();
796        }
797    };
798
799    private static class NewWindowWebView extends WebView {
800        public NewWindowWebView(Context context, Map<String, Object> jsIfaces) {
801            super(context, null, 0, jsIfaces, false);
802        }
803    }
804
805    private void resetTestStatus() {
806        mWaitUntilDone = false;
807        mDumpDataType = mDefaultDumpDataType;
808        mDumpTopFrameAsText = false;
809        mDumpChildFramesAsText = false;
810        mTimedOut = false;
811        mDumpTitleChanges = false;
812        mRequestedWebKitData = false;
813        mDumpDatabaseCallbacks = false;
814        mCanOpenWindows = false;
815        mEventSender.resetMouse();
816        mEventSender.clearTouchPoints();
817        mEventSender.clearTouchMetaState();
818        mPageFinished = false;
819        mDumpWebKitData = false;
820        setDefaultWebSettings(mWebView);
821        mIsGeolocationPermissionSet = false;
822        mPendingGeolocationPermissionCallbacks = null;
823        CookieManager.getInstance().removeAllCookie();
824    }
825
826    private boolean canMoveToNextTest() {
827        return (mDumpWebKitData && mPageFinished && !mWaitUntilDone) || mTimedOut;
828    }
829
830    private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
831        if (webview == null) {
832            return;
833        }
834
835        setDefaultWebSettings(webview);
836
837        webview.setWebChromeClient(mChromeClient);
838        webview.setWebViewClient(mViewClient);
839        // Setting a touch interval of -1 effectively disables the optimisation in WebView
840        // that stops repeated touch events flooding WebCore. The Event Sender only sends a
841        // single event rather than a stream of events (like what would generally happen in
842        // a real use of touch events in a WebView)  and so if the WebView drops the event,
843        // the test will fail as the test expects one callback for every touch it synthesizes.
844        WebViewClassic.fromWebView(webview).setTouchInterval(-1);
845    }
846
847    public void setDefaultWebSettings(WebView webview) {
848        WebSettingsClassic settings = WebViewClassic.fromWebView(webview).getSettings();
849        settings.setAppCacheEnabled(true);
850        settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
851        settings.setAppCacheMaxSize(Long.MAX_VALUE);
852        settings.setJavaScriptEnabled(true);
853        settings.setJavaScriptCanOpenWindowsAutomatically(true);
854        settings.setSupportMultipleWindows(true);
855        settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
856        settings.setDatabaseEnabled(true);
857        settings.setDatabasePath(getDir("databases",0).getAbsolutePath());
858        settings.setDomStorageEnabled(true);
859        settings.setWorkersEnabled(false);
860        settings.setXSSAuditorEnabled(false);
861        settings.setPageCacheCapacity(0);
862        settings.setProperty("use_minimal_memory", "false");
863    }
864
865    private WebViewClassic mWebViewClassic;
866    private WebView mWebView;
867    private WebViewEventSender mEventSender;
868    private AsyncHandler mHandler;
869    private TestShellCallback mCallback;
870
871    private CallbackProxy mCallbackProxy;
872
873    private String mTestUrl;
874    private String mResultFile;
875    private int mTimeoutInMillis;
876    private String mUiAutoTestPath;
877    private BufferedReader mTestListReader;
878    private int mTotalTestCount;
879    private int mCurrentTestNumber;
880    private boolean mStopOnRefError;
881
882    // States
883    private boolean mTimedOut;
884    private boolean mRequestedWebKitData;
885    private boolean mFinishedRunning;
886
887    // Layout test controller variables.
888    private DumpDataType mDumpDataType;
889    private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR;
890    private boolean mDumpTopFrameAsText;
891    private boolean mDumpChildFramesAsText;
892    private boolean mWaitUntilDone;
893    private boolean mDumpTitleChanges;
894    private StringBuffer mTitleChanges;
895    private StringBuffer mDialogStrings;
896    private boolean mKeepWebHistory;
897    private Vector mWebHistory;
898    private boolean mDumpDatabaseCallbacks;
899    private StringBuffer mDatabaseCallbackStrings;
900    private StringBuffer mConsoleMessages;
901    private boolean mCanOpenWindows;
902
903    private boolean mPageFinished = false;
904    private boolean mDumpWebKitData = false;
905
906    static final String TIMEOUT_STR = "**Test timeout";
907    static final long DUMP_TIMEOUT_MS = 100000; // 100s timeout for dumping webview content
908
909    static final int MSG_TIMEOUT = 0;
910    static final int MSG_WEBKIT_DATA = 1;
911    static final int MSG_DUMP_TIMEOUT = 2;
912
913    static final String LOGTAG="TestShell";
914
915    static final String TEST_URL = "TestUrl";
916    static final String RESULT_FILE = "ResultFile";
917    static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis";
918    static final String UI_AUTO_TEST = "UiAutoTest";
919    static final String GET_DRAW_TIME = "GetDrawTime";
920    static final String SAVE_IMAGE = "SaveImage";
921    static final String TOTAL_TEST_COUNT = "TestCount";
922    static final String CURRENT_TEST_NUMBER = "TestNumber";
923    static final String STOP_ON_REF_ERROR = "StopOnReferenceError";
924
925    static final int DRAW_RUNS = 5;
926    static final String DRAW_TIME_LOG = Environment.getExternalStorageDirectory() +
927        "/android/page_draw_time.txt";
928
929    private boolean mIsGeolocationPermissionSet;
930    private boolean mGeolocationPermission;
931    private Map mPendingGeolocationPermissionCallbacks;
932}
933