• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.dumprendertree;
18 
19 import com.android.dumprendertree.forwarder.ForwardService;
20 
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.DialogInterface.OnClickListener;
27 import android.graphics.Bitmap;
28 import android.graphics.Canvas;
29 import android.graphics.Bitmap.CompressFormat;
30 import android.graphics.Bitmap.Config;
31 import android.net.http.SslError;
32 import android.os.Bundle;
33 import android.os.Environment;
34 import android.os.Handler;
35 import android.os.Message;
36 import android.util.Log;
37 import android.view.ViewGroup;
38 import android.view.Window;
39 import android.webkit.CookieManager;
40 import android.webkit.ConsoleMessage;
41 import android.webkit.CookieManager;
42 import android.webkit.GeolocationPermissions;
43 import android.webkit.HttpAuthHandler;
44 import android.webkit.JsPromptResult;
45 import android.webkit.JsResult;
46 import android.webkit.SslErrorHandler;
47 import android.webkit.WebChromeClient;
48 import android.webkit.WebSettings;
49 import android.webkit.WebStorage;
50 import android.webkit.WebView;
51 import android.webkit.WebViewClient;
52 import android.widget.LinearLayout;
53 
54 import java.io.BufferedReader;
55 import java.io.File;
56 import java.io.FileOutputStream;
57 import java.io.FileReader;
58 import java.io.IOException;
59 import java.net.MalformedURLException;
60 import java.net.URL;
61 import java.util.HashMap;
62 import java.util.Iterator;
63 import java.util.Map;
64 import java.util.Vector;
65 
66 public 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
handleMessage(Message msg)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 
requestWebKitData()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 
setDumpTimeout(long timeout)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 
clearCache()133     public void clearCache() {
134       mWebView.freeMemory();
135     }
136 
137     @Override
onCreate(Bundle icicle)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
onNewIntent(Intent intent)176     protected void onNewIntent(Intent intent) {
177         super.onNewIntent(intent);
178         executeIntent(intent);
179     }
180 
executeIntent(Intent intent)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 
beginUiAutoTest()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 
finishUiAutoTest()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 
moveToNextTest()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
onStop()284     protected void onStop() {
285         super.onStop();
286         mWebView.stopLoading();
287     }
288 
289     @Override
onDestroy()290     protected void onDestroy() {
291         super.onDestroy();
292         mWebView.destroy();
293         mWebView = null;
294     }
295 
296     @Override
onLowMemory()297     public void onLowMemory() {
298         super.onLowMemory();
299         Log.e(LOGTAG, "Low memory, clearing caches");
300         mWebView.freeMemory();
301     }
302 
303     // Dump the page
dump(boolean timeout, String webkitData)304     public void dump(boolean timeout, String webkitData) {
305         mDumpWebKitData = true;
306         if (mResultFile == null || mResultFile.length() == 0) {
307             finished();
308             return;
309         }
310 
311         try {
312             File parentDir = new File(mResultFile).getParentFile();
313             if (!parentDir.exists()) {
314                 parentDir.mkdirs();
315             }
316 
317             FileOutputStream os = new FileOutputStream(mResultFile);
318             if (timeout) {
319                 Log.w("Layout test: Timeout", mResultFile);
320                 os.write(TIMEOUT_STR.getBytes());
321                 os.write('\n');
322             }
323             if (mDumpTitleChanges)
324                 os.write(mTitleChanges.toString().getBytes());
325             if (mDialogStrings != null)
326                 os.write(mDialogStrings.toString().getBytes());
327             mDialogStrings = null;
328             if (mDatabaseCallbackStrings != null)
329                 os.write(mDatabaseCallbackStrings.toString().getBytes());
330             mDatabaseCallbackStrings = null;
331             if (mConsoleMessages != null)
332                 os.write(mConsoleMessages.toString().getBytes());
333             mConsoleMessages = null;
334             if (webkitData != null)
335                 os.write(webkitData.getBytes());
336             os.flush();
337             os.close();
338         } catch (IOException ex) {
339             Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage());
340         }
341 
342         finished();
343     }
344 
setCallback(TestShellCallback callback)345     public void setCallback(TestShellCallback callback) {
346         mCallback = callback;
347     }
348 
finished()349     public boolean finished() {
350         if (canMoveToNextTest()) {
351             mHandler.removeMessages(MSG_TIMEOUT);
352             if (mUiAutoTestPath != null) {
353                 //don't really finish here
354                 moveToNextTest();
355             } else {
356                 if (mCallback != null) {
357                     mCallback.finished();
358                 }
359             }
360             return true;
361         }
362         return false;
363     }
364 
setDefaultDumpDataType(DumpDataType defaultDumpDataType)365     public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) {
366         mDefaultDumpDataType = defaultDumpDataType;
367     }
368 
369     // .......................................
370     // LayoutTestController Functions
dumpAsText(boolean enablePixelTests)371     public void dumpAsText(boolean enablePixelTests) {
372         // Added after webkit update to r63859. See trac.webkit.org/changeset/63730.
373         if (enablePixelTests) {
374             Log.v(LOGTAG, "dumpAsText(enablePixelTests == true) not implemented on Android!");
375         }
376 
377         mDumpDataType = DumpDataType.DUMP_AS_TEXT;
378         mDumpTopFrameAsText = true;
379         if (mWebView != null) {
380             String url = mWebView.getUrl();
381             Log.v(LOGTAG, "dumpAsText called: "+url);
382         }
383     }
384 
dumpChildFramesAsText()385     public void dumpChildFramesAsText() {
386         mDumpDataType = DumpDataType.DUMP_AS_TEXT;
387         mDumpChildFramesAsText = true;
388         if (mWebView != null) {
389             String url = mWebView.getUrl();
390             Log.v(LOGTAG, "dumpChildFramesAsText called: "+url);
391         }
392     }
393 
waitUntilDone()394     public void waitUntilDone() {
395         mWaitUntilDone = true;
396         String url = mWebView.getUrl();
397         Log.v(LOGTAG, "waitUntilDone called: " + url);
398     }
399 
notifyDone()400     public void notifyDone() {
401         String url = mWebView.getUrl();
402         Log.v(LOGTAG, "notifyDone called: " + url);
403         if (mWaitUntilDone) {
404             mWaitUntilDone = false;
405             if (!mRequestedWebKitData && !mTimedOut && !finished()) {
406                 requestWebKitData();
407             }
408         }
409     }
410 
display()411     public void display() {
412         mWebView.invalidate();
413     }
414 
clearBackForwardList()415     public void clearBackForwardList() {
416         mWebView.clearHistory();
417 
418     }
419 
dumpBackForwardList()420     public void dumpBackForwardList() {
421         //printf("\n============== Back Forward List ==============\n");
422         // mWebHistory
423         //printf("===============================================\n");
424 
425     }
426 
dumpChildFrameScrollPositions()427     public void dumpChildFrameScrollPositions() {
428         // TODO Auto-generated method stub
429 
430     }
431 
dumpEditingCallbacks()432     public void dumpEditingCallbacks() {
433         // TODO Auto-generated method stub
434 
435     }
436 
dumpSelectionRect()437     public void dumpSelectionRect() {
438         // TODO Auto-generated method stub
439 
440     }
441 
dumpTitleChanges()442     public void dumpTitleChanges() {
443         if (!mDumpTitleChanges) {
444             mTitleChanges = new StringBuffer();
445         }
446         mDumpTitleChanges = true;
447     }
448 
keepWebHistory()449     public void keepWebHistory() {
450         if (!mKeepWebHistory) {
451             mWebHistory = new Vector();
452         }
453         mKeepWebHistory = true;
454     }
455 
queueBackNavigation(int howfar)456     public void queueBackNavigation(int howfar) {
457         // TODO Auto-generated method stub
458 
459     }
460 
queueForwardNavigation(int howfar)461     public void queueForwardNavigation(int howfar) {
462         // TODO Auto-generated method stub
463 
464     }
465 
queueLoad(String Url, String frameTarget)466     public void queueLoad(String Url, String frameTarget) {
467         // TODO Auto-generated method stub
468 
469     }
470 
queueReload()471     public void queueReload() {
472         mWebView.reload();
473     }
474 
queueScript(String scriptToRunInCurrentContext)475     public void queueScript(String scriptToRunInCurrentContext) {
476         mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);
477     }
478 
repaintSweepHorizontally()479     public void repaintSweepHorizontally() {
480         // TODO Auto-generated method stub
481 
482     }
483 
setAcceptsEditing(boolean b)484     public void setAcceptsEditing(boolean b) {
485         // TODO Auto-generated method stub
486 
487     }
488 
setMainFrameIsFirstResponder(boolean b)489     public void setMainFrameIsFirstResponder(boolean b) {
490         // TODO Auto-generated method stub
491 
492     }
493 
setWindowIsKey(boolean b)494     public void setWindowIsKey(boolean b) {
495         // This is meant to show/hide the window. The best I can find
496         // is setEnabled()
497         mWebView.setEnabled(b);
498     }
499 
testRepaint()500     public void testRepaint() {
501         mWebView.invalidate();
502     }
503 
dumpDatabaseCallbacks()504     public void dumpDatabaseCallbacks() {
505         Log.v(LOGTAG, "dumpDatabaseCallbacks called.");
506         mDumpDatabaseCallbacks = true;
507     }
508 
setCanOpenWindows()509     public void setCanOpenWindows() {
510         Log.v(LOGTAG, "setCanOpenWindows called.");
511         mCanOpenWindows = true;
512     }
513 
514     /**
515      * Sets the Geolocation permission state to be used for all future requests.
516      */
setGeolocationPermission(boolean allow)517     public void setGeolocationPermission(boolean allow) {
518         mIsGeolocationPermissionSet = true;
519         mGeolocationPermission = allow;
520 
521         if (mPendingGeolocationPermissionCallbacks != null) {
522             Iterator iter = mPendingGeolocationPermissionCallbacks.keySet().iterator();
523             while (iter.hasNext()) {
524                 GeolocationPermissions.Callback callback =
525                         (GeolocationPermissions.Callback) iter.next();
526                 String origin = (String) mPendingGeolocationPermissionCallbacks.get(callback);
527                 callback.invoke(origin, mGeolocationPermission, false);
528             }
529             mPendingGeolocationPermissionCallbacks = null;
530         }
531     }
532 
setMockDeviceOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma)533     public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
534             boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
535         mWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
536                 canProvideGamma, gamma);
537     }
538 
overridePreference(String key, boolean value)539     public void overridePreference(String key, boolean value) {
540         // TODO: We should look up the correct WebView for the frame which
541         // called the layoutTestController method. Currently, we just use the
542         // WebView for the main frame. EventSender suffers from the same
543         // problem.
544         if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) {
545             mWebView.getSettings().setAppCacheEnabled(value);
546         } else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) {
547             // Cache the maximum possible number of pages.
548             mWebView.getSettings().setPageCacheCapacity(Integer.MAX_VALUE);
549         } else {
550             Log.w(LOGTAG, "LayoutTestController.overridePreference(): " +
551                   "Unsupported preference '" + key + "'");
552         }
553     }
554 
setXSSAuditorEnabled(boolean flag)555     public void setXSSAuditorEnabled (boolean flag) {
556         mWebView.getSettings().setXSSAuditorEnabled(flag);
557     }
558 
559     private final WebViewClient mViewClient = new WebViewClient(){
560         @Override
561         public void onPageFinished(WebView view, String url) {
562             Log.v(LOGTAG, "onPageFinished, url=" + url);
563             mPageFinished = true;
564             // get page draw time
565             if (FsUtils.isTestPageUrl(url)) {
566                 if (mGetDrawtime) {
567                     long[] times = new long[DRAW_RUNS];
568                     times = getDrawWebViewTime(mWebView, DRAW_RUNS);
569                     FsUtils.writeDrawTime(DRAW_TIME_LOG, url, times);
570                 }
571                 if (mSaveImagePath != null) {
572                     String name = FsUtils.getLastSegmentInPath(url);
573                     drawPageToFile(mSaveImagePath + "/" + name + ".png", mWebView);
574                 }
575             }
576 
577             // Calling finished() will check if we've met all the conditions for completing
578             // this test and move to the next one if we are ready. Otherwise we ask WebCore to
579             // dump the page.
580             if (finished()) {
581                 return;
582             }
583 
584             if (!mWaitUntilDone && !mRequestedWebKitData && !mTimedOut) {
585                 requestWebKitData();
586             } else {
587                 if (mWaitUntilDone) {
588                     Log.v(LOGTAG, "page finished loading but waiting for notifyDone to be called: " + url);
589                 }
590 
591                 if (mRequestedWebKitData) {
592                     Log.v(LOGTAG, "page finished loading but webkit data has already been requested: " + url);
593                 }
594 
595                 if (mTimedOut) {
596                     Log.v(LOGTAG, "page finished loading but already timed out: " + url);
597                 }
598             }
599 
600             super.onPageFinished(view, url);
601         }
602 
603         @Override
604         public void onPageStarted(WebView view, String url, Bitmap favicon) {
605             Log.v(LOGTAG, "onPageStarted, url=" + url);
606             mPageFinished = false;
607             super.onPageStarted(view, url, favicon);
608         }
609 
610         @Override
611         public void onReceivedError(WebView view, int errorCode, String description,
612                 String failingUrl) {
613             Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode
614                     + ", desc=" + description + ", url=" + failingUrl);
615             super.onReceivedError(view, errorCode, description, failingUrl);
616         }
617 
618         @Override
619         public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
620                 String host, String realm) {
621             if (handler.useHttpAuthUsernamePassword() && view != null) {
622                 String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
623                 if (credentials != null && credentials.length == 2) {
624                     handler.proceed(credentials[0], credentials[1]);
625                     return;
626                 }
627             }
628             handler.cancel();
629         }
630 
631         @Override
632         public void onReceivedSslError(WebView view, SslErrorHandler handler,
633                 SslError error) {
634             handler.proceed();
635         }
636     };
637 
638 
639     private final WebChromeClient mChromeClient = new WebChromeClient() {
640         @Override
641         public void onReceivedTitle(WebView view, String title) {
642             setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount + ": "+ title);
643             if (mDumpTitleChanges) {
644                 mTitleChanges.append("TITLE CHANGED: ");
645                 mTitleChanges.append(title);
646                 mTitleChanges.append("\n");
647             }
648         }
649 
650         @Override
651         public boolean onJsAlert(WebView view, String url, String message,
652                 JsResult result) {
653             if (mDialogStrings == null) {
654                 mDialogStrings = new StringBuffer();
655             }
656             mDialogStrings.append("ALERT: ");
657             mDialogStrings.append(message);
658             mDialogStrings.append('\n');
659             result.confirm();
660             return true;
661         }
662 
663         @Override
664         public boolean onJsConfirm(WebView view, String url, String message,
665                 JsResult result) {
666             if (mDialogStrings == null) {
667                 mDialogStrings = new StringBuffer();
668             }
669             mDialogStrings.append("CONFIRM: ");
670             mDialogStrings.append(message);
671             mDialogStrings.append('\n');
672             result.confirm();
673             return true;
674         }
675 
676         @Override
677         public boolean onJsPrompt(WebView view, String url, String message,
678                 String defaultValue, JsPromptResult result) {
679             if (mDialogStrings == null) {
680                 mDialogStrings = new StringBuffer();
681             }
682             mDialogStrings.append("PROMPT: ");
683             mDialogStrings.append(message);
684             mDialogStrings.append(", default text: ");
685             mDialogStrings.append(defaultValue);
686             mDialogStrings.append('\n');
687             result.confirm();
688             return true;
689         }
690 
691         @Override
692         public boolean onJsTimeout() {
693             Log.v(LOGTAG, "JavaScript timeout");
694             return false;
695         }
696 
697         @Override
698         public void onExceededDatabaseQuota(String url_str,
699                 String databaseIdentifier, long currentQuota,
700                 long estimatedSize, long totalUsedQuota,
701                 WebStorage.QuotaUpdater callback) {
702             if (mDumpDatabaseCallbacks) {
703                 if (mDatabaseCallbackStrings == null) {
704                     mDatabaseCallbackStrings = new StringBuffer();
705                 }
706 
707                 String protocol = "";
708                 String host = "";
709                 int port = 0;
710 
711                 try {
712                     URL url = new URL(url_str);
713                     protocol = url.getProtocol();
714                     host = url.getHost();
715                     if (url.getPort() > -1) {
716                         port = url.getPort();
717                     }
718                 } catch (MalformedURLException e) {}
719 
720                 String databaseCallbackString =
721                         "UI DELEGATE DATABASE CALLBACK: " +
722                         "exceededDatabaseQuotaForSecurityOrigin:{" + protocol +
723                         ", " + host + ", " + port + "} database:" +
724                         databaseIdentifier + "\n";
725                 Log.v(LOGTAG, "LOG: "+databaseCallbackString);
726                 mDatabaseCallbackStrings.append(databaseCallbackString);
727             }
728             // Give 5MB more quota.
729             callback.updateQuota(currentQuota + 1024 * 1024 * 5);
730         }
731 
732         /**
733          * Instructs the client to show a prompt to ask the user to set the
734          * Geolocation permission state for the specified origin.
735          */
736         @Override
737         public void onGeolocationPermissionsShowPrompt(String origin,
738                 GeolocationPermissions.Callback callback) {
739             if (mIsGeolocationPermissionSet) {
740                 callback.invoke(origin, mGeolocationPermission, false);
741                 return;
742             }
743             if (mPendingGeolocationPermissionCallbacks == null) {
744                 mPendingGeolocationPermissionCallbacks =
745                         new HashMap<GeolocationPermissions.Callback, String>();
746             }
747             mPendingGeolocationPermissionCallbacks.put(callback, origin);
748         }
749 
750         @Override
751         public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
752             String msg = "CONSOLE MESSAGE: line " + consoleMessage.lineNumber() + ": "
753                     + consoleMessage.message() + "\n";
754             if (mConsoleMessages == null) {
755                 mConsoleMessages = new StringBuffer();
756             }
757             mConsoleMessages.append(msg);
758             Log.v(LOGTAG, "LOG: " + msg);
759             // the rationale here is that if there's an error of either type, and the test was
760             // waiting for "notifyDone" signal to finish, then there's no point in waiting
761             // anymore because the JS execution is already terminated at this point and a
762             // "notifyDone" will never come out so it's just wasting time till timeout kicks in
763             if ((msg.contains("Uncaught ReferenceError:") || msg.contains("Uncaught TypeError:"))
764                     && mWaitUntilDone && mStopOnRefError) {
765                 Log.w(LOGTAG, "Terminating test case on uncaught ReferenceError or TypeError.");
766                 mHandler.postDelayed(new Runnable() {
767                     public void run() {
768                         notifyDone();
769                     }
770                 }, 500);
771             }
772             return true;
773         }
774 
775         @Override
776         public boolean onCreateWindow(WebView view, boolean dialog,
777                 boolean userGesture, Message resultMsg) {
778             if (!mCanOpenWindows) {
779                 // We can't open windows, so just send null back.
780                 WebView.WebViewTransport transport =
781                         (WebView.WebViewTransport) resultMsg.obj;
782                 transport.setWebView(null);
783                 resultMsg.sendToTarget();
784                 return true;
785             }
786 
787             // We never display the new window, just create the view and
788             // allow it's content to execute and be recorded by the test
789             // runner.
790 
791             HashMap<String, Object> jsIfaces = new HashMap<String, Object>();
792             jsIfaces.put("layoutTestController", mCallbackProxy);
793             jsIfaces.put("eventSender", mCallbackProxy);
794             WebView newWindowView = new NewWindowWebView(TestShellActivity.this, jsIfaces);
795             setupWebViewForLayoutTests(newWindowView, mCallbackProxy);
796             WebView.WebViewTransport transport =
797                     (WebView.WebViewTransport) resultMsg.obj;
798             transport.setWebView(newWindowView);
799             resultMsg.sendToTarget();
800             return true;
801         }
802 
803         @Override
804         public void onCloseWindow(WebView view) {
805             view.destroy();
806         }
807     };
808 
809     private static class NewWindowWebView extends WebView {
NewWindowWebView(Context context, Map<String, Object> jsIfaces)810         public NewWindowWebView(Context context, Map<String, Object> jsIfaces) {
811             super(context, null, 0, jsIfaces, false);
812         }
813     }
814 
resetTestStatus()815     private void resetTestStatus() {
816         mWaitUntilDone = false;
817         mDumpDataType = mDefaultDumpDataType;
818         mDumpTopFrameAsText = false;
819         mDumpChildFramesAsText = false;
820         mTimedOut = false;
821         mDumpTitleChanges = false;
822         mRequestedWebKitData = false;
823         mDumpDatabaseCallbacks = false;
824         mCanOpenWindows = false;
825         mEventSender.resetMouse();
826         mEventSender.clearTouchPoints();
827         mEventSender.clearTouchMetaState();
828         mPageFinished = false;
829         mDumpWebKitData = false;
830         mGetDrawtime = false;
831         mSaveImagePath = null;
832         setDefaultWebSettings(mWebView);
833         mIsGeolocationPermissionSet = false;
834         mPendingGeolocationPermissionCallbacks = null;
835         CookieManager.getInstance().removeAllCookie();
836     }
837 
getDrawWebViewTime(WebView view, int count)838     private long[] getDrawWebViewTime(WebView view, int count) {
839         if (count == 0)
840             return null;
841         long[] ret = new long[count];
842         long start;
843         Canvas canvas = new Canvas();
844         Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888);
845         canvas.setBitmap(bitmap);
846         for (int i = 0; i < count; i++) {
847             start = System.currentTimeMillis();
848             view.draw(canvas);
849             ret[i] = System.currentTimeMillis() - start;
850         }
851         return ret;
852     }
853 
drawPageToFile(String fileName, WebView view)854     private void drawPageToFile(String fileName, WebView view) {
855         Canvas canvas = new Canvas();
856         Bitmap bitmap = Bitmap.createBitmap(view.getContentWidth(), view.getContentHeight(),
857                 Config.ARGB_8888);
858         canvas.setBitmap(bitmap);
859         view.drawPage(canvas);
860         try {
861             FileOutputStream fos = new FileOutputStream(fileName);
862             if(!bitmap.compress(CompressFormat.PNG, 90, fos)) {
863                 Log.w(LOGTAG, "Failed to compress and save image.");
864             }
865         } catch (IOException ioe) {
866             Log.e(LOGTAG, "", ioe);
867         }
868         bitmap.recycle();
869     }
870 
canMoveToNextTest()871     private boolean canMoveToNextTest() {
872         return (mDumpWebKitData && mPageFinished && !mWaitUntilDone) || mTimedOut;
873     }
874 
setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy)875     private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
876         if (webview == null) {
877             return;
878         }
879 
880         setDefaultWebSettings(webview);
881 
882         webview.setWebChromeClient(mChromeClient);
883         webview.setWebViewClient(mViewClient);
884         // Setting a touch interval of -1 effectively disables the optimisation in WebView
885         // that stops repeated touch events flooding WebCore. The Event Sender only sends a
886         // single event rather than a stream of events (like what would generally happen in
887         // a real use of touch events in a WebView)  and so if the WebView drops the event,
888         // the test will fail as the test expects one callback for every touch it synthesizes.
889         webview.setTouchInterval(-1);
890     }
891 
setDefaultWebSettings(WebView webview)892     public void setDefaultWebSettings(WebView webview) {
893         WebSettings settings = webview.getSettings();
894         settings.setAppCacheEnabled(true);
895         settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
896         settings.setAppCacheMaxSize(Long.MAX_VALUE);
897         settings.setJavaScriptEnabled(true);
898         settings.setJavaScriptCanOpenWindowsAutomatically(true);
899         settings.setSupportMultipleWindows(true);
900         settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
901         settings.setDatabaseEnabled(true);
902         settings.setDatabasePath(getDir("databases",0).getAbsolutePath());
903         settings.setDomStorageEnabled(true);
904         settings.setWorkersEnabled(false);
905         settings.setXSSAuditorEnabled(false);
906         settings.setPageCacheCapacity(0);
907         // this enables cpu upload path (as opposed to gpu upload path)
908         // and it's only meant to be a temporary workaround!
909         settings.setProperty("enable_cpu_upload_path", "true");
910     }
911 
912     private WebView mWebView;
913     private WebViewEventSender mEventSender;
914     private AsyncHandler mHandler;
915     private TestShellCallback mCallback;
916 
917     private CallbackProxy mCallbackProxy;
918 
919     private String mTestUrl;
920     private String mResultFile;
921     private int mTimeoutInMillis;
922     private String mUiAutoTestPath;
923     private String mSaveImagePath;
924     private BufferedReader mTestListReader;
925     private boolean mGetDrawtime;
926     private int mTotalTestCount;
927     private int mCurrentTestNumber;
928     private boolean mStopOnRefError;
929 
930     // States
931     private boolean mTimedOut;
932     private boolean mRequestedWebKitData;
933     private boolean mFinishedRunning;
934 
935     // Layout test controller variables.
936     private DumpDataType mDumpDataType;
937     private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR;
938     private boolean mDumpTopFrameAsText;
939     private boolean mDumpChildFramesAsText;
940     private boolean mWaitUntilDone;
941     private boolean mDumpTitleChanges;
942     private StringBuffer mTitleChanges;
943     private StringBuffer mDialogStrings;
944     private boolean mKeepWebHistory;
945     private Vector mWebHistory;
946     private boolean mDumpDatabaseCallbacks;
947     private StringBuffer mDatabaseCallbackStrings;
948     private StringBuffer mConsoleMessages;
949     private boolean mCanOpenWindows;
950 
951     private boolean mPageFinished = false;
952     private boolean mDumpWebKitData = false;
953 
954     static final String TIMEOUT_STR = "**Test timeout";
955     static final long DUMP_TIMEOUT_MS = 100000; // 100s timeout for dumping webview content
956 
957     static final int MSG_TIMEOUT = 0;
958     static final int MSG_WEBKIT_DATA = 1;
959     static final int MSG_DUMP_TIMEOUT = 2;
960 
961     static final String LOGTAG="TestShell";
962 
963     static final String TEST_URL = "TestUrl";
964     static final String RESULT_FILE = "ResultFile";
965     static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis";
966     static final String UI_AUTO_TEST = "UiAutoTest";
967     static final String GET_DRAW_TIME = "GetDrawTime";
968     static final String SAVE_IMAGE = "SaveImage";
969     static final String TOTAL_TEST_COUNT = "TestCount";
970     static final String CURRENT_TEST_NUMBER = "TestNumber";
971     static final String STOP_ON_REF_ERROR = "StopOnReferenceError";
972 
973     static final int DRAW_RUNS = 5;
974     static final String DRAW_TIME_LOG = Environment.getExternalStorageDirectory() +
975         "/android/page_draw_time.txt";
976 
977     private boolean mIsGeolocationPermissionSet;
978     private boolean mGeolocationPermission;
979     private Map mPendingGeolocationPermissionCallbacks;
980 }
981