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