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