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