TestShellActivity.java revision 977f7691e5c852b188ac9131443bff10bc91c635
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.net.http.SslError; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.Message; 32import android.util.Log; 33import android.view.ViewGroup; 34import android.webkit.GeolocationPermissions; 35import android.webkit.HttpAuthHandler; 36import android.webkit.JsPromptResult; 37import android.webkit.JsResult; 38import android.webkit.SslErrorHandler; 39import android.webkit.WebChromeClient; 40import android.webkit.WebSettings; 41import android.webkit.WebStorage; 42import android.webkit.WebView; 43import android.webkit.WebViewClient; 44import android.widget.LinearLayout; 45 46import java.io.BufferedReader; 47import java.io.File; 48import java.io.FileOutputStream; 49import java.io.FileReader; 50import java.io.IOException; 51import java.net.MalformedURLException; 52import java.net.URL; 53import java.util.HashMap; 54import java.util.Map; 55import java.util.Vector; 56 57public class TestShellActivity extends Activity implements LayoutTestController { 58 59 static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP} 60 61 public class AsyncHandler extends Handler { 62 @Override 63 public void handleMessage(Message msg) { 64 if (msg.what == MSG_TIMEOUT) { 65 mTimedOut = true; 66 if(mCallback != null) 67 mCallback.timedOut(mWebView.getUrl()); 68 requestWebKitData(); 69 return; 70 } else if (msg.what == MSG_WEBKIT_DATA) { 71 TestShellActivity.this.dump(mTimedOut, (String)msg.obj); 72 return; 73 } 74 75 super.handleMessage(msg); 76 } 77 } 78 79 public void requestWebKitData() { 80 Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA); 81 82 if (mRequestedWebKitData) 83 throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl()); 84 85 mRequestedWebKitData = true; 86 switch (mDumpDataType) { 87 case DUMP_AS_TEXT: 88 mWebView.documentAsText(callback); 89 break; 90 case EXT_REPR: 91 mWebView.externalRepresentation(callback); 92 break; 93 default: 94 finished(); 95 break; 96 } 97 } 98 99 public void clearCache() { 100 mWebView.freeMemory(); 101 } 102 103 @Override 104 protected void onCreate(Bundle icicle) { 105 super.onCreate(icicle); 106 107 LinearLayout contentView = new LinearLayout(this); 108 contentView.setOrientation(LinearLayout.VERTICAL); 109 setContentView(contentView); 110 111 mWebView = new WebView(this); 112 mEventSender = new WebViewEventSender(mWebView); 113 mCallbackProxy = new CallbackProxy(mEventSender, this); 114 115 mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController"); 116 mWebView.addJavascriptInterface(mCallbackProxy, "eventSender"); 117 setupWebViewForLayoutTests(mWebView, mCallbackProxy); 118 119 contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f)); 120 121 mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); 122 123 // Expose window.gc function to JavaScript. JSC build exposes 124 // this function by default, but V8 requires the flag to turn it on. 125 // WebView::setJsFlags is noop in JSC build. 126 mWebView.setJsFlags("--expose_gc"); 127 128 mHandler = new AsyncHandler(); 129 130 Intent intent = getIntent(); 131 if (intent != null) { 132 executeIntent(intent); 133 } 134 } 135 136 @Override 137 protected void onNewIntent(Intent intent) { 138 super.onNewIntent(intent); 139 executeIntent(intent); 140 } 141 142 private void executeIntent(Intent intent) { 143 resetTestStatus(); 144 if (!Intent.ACTION_VIEW.equals(intent.getAction())) { 145 return; 146 } 147 148 mTestUrl = intent.getStringExtra(TEST_URL); 149 if (mTestUrl == null) { 150 mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST); 151 if(mUiAutoTestPath != null) { 152 beginUiAutoTest(); 153 } 154 return; 155 } 156 157 mResultFile = intent.getStringExtra(RESULT_FILE); 158 mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0); 159 160 Log.v(LOGTAG, " Loading " + mTestUrl); 161 mWebView.loadUrl(mTestUrl); 162 163 if (mTimeoutInMillis > 0) { 164 // Create a timeout timer 165 Message m = mHandler.obtainMessage(MSG_TIMEOUT); 166 mHandler.sendMessageDelayed(m, mTimeoutInMillis); 167 } 168 } 169 170 private void beginUiAutoTest() { 171 try { 172 mTestListReader = new BufferedReader( 173 new FileReader(mUiAutoTestPath)); 174 } catch (IOException ioe) { 175 Log.e(LOGTAG, "Failed to open test list for read.", ioe); 176 finishUiAutoTest(); 177 return; 178 } 179 moveToNextTest(); 180 } 181 182 private void finishUiAutoTest() { 183 try { 184 if(mTestListReader != null) 185 mTestListReader.close(); 186 } catch (IOException ioe) { 187 Log.w(LOGTAG, "Failed to close test list file.", ioe); 188 } 189 ForwardService.getForwardService().stopForwardService(); 190 finished(); 191 } 192 193 private void moveToNextTest() { 194 String url = null; 195 try { 196 url = mTestListReader.readLine(); 197 } catch (IOException ioe) { 198 Log.e(LOGTAG, "Failed to read next test.", ioe); 199 finishUiAutoTest(); 200 return; 201 } 202 if (url == null) { 203 mUiAutoTestPath = null; 204 finishUiAutoTest(); 205 AlertDialog.Builder builder = new AlertDialog.Builder(this); 206 builder.setMessage("All tests finished. Exit?") 207 .setCancelable(false) 208 .setPositiveButton("Yes", new OnClickListener(){ 209 public void onClick(DialogInterface dialog, int which) { 210 TestShellActivity.this.finish(); 211 } 212 }) 213 .setNegativeButton("No", new OnClickListener(){ 214 public void onClick(DialogInterface dialog, int which) { 215 dialog.cancel(); 216 } 217 }); 218 builder.create().show(); 219 return; 220 } 221 Intent intent = new Intent(Intent.ACTION_VIEW); 222 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 223 intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(url)); 224 intent.putExtra(TIMEOUT_IN_MILLIS, 10000); 225 executeIntent(intent); 226 } 227 228 @Override 229 protected void onStop() { 230 super.onStop(); 231 mWebView.stopLoading(); 232 } 233 234 @Override 235 protected void onDestroy() { 236 super.onDestroy(); 237 mWebView.destroy(); 238 mWebView = null; 239 } 240 241 @Override 242 public void onLowMemory() { 243 super.onLowMemory(); 244 Log.e(LOGTAG, "Low memory, clearing caches"); 245 mWebView.freeMemory(); 246 } 247 248 // Dump the page 249 public void dump(boolean timeout, String webkitData) { 250 if (mResultFile == null || mResultFile.length() == 0) { 251 finished(); 252 return; 253 } 254 255 try { 256 File parentDir = new File(mResultFile).getParentFile(); 257 if (!parentDir.exists()) { 258 parentDir.mkdirs(); 259 } 260 261 FileOutputStream os = new FileOutputStream(mResultFile); 262 if (timeout) { 263 Log.w("Layout test: Timeout", mResultFile); 264 os.write(TIMEOUT_STR.getBytes()); 265 os.write('\n'); 266 } 267 if (mDumpTitleChanges) 268 os.write(mTitleChanges.toString().getBytes()); 269 if (mDialogStrings != null) 270 os.write(mDialogStrings.toString().getBytes()); 271 mDialogStrings = null; 272 if (mDatabaseCallbackStrings != null) 273 os.write(mDatabaseCallbackStrings.toString().getBytes()); 274 mDatabaseCallbackStrings = null; 275 if (mConsoleMessages != null) 276 os.write(mConsoleMessages.toString().getBytes()); 277 mConsoleMessages = null; 278 if (webkitData != null) 279 os.write(webkitData.getBytes()); 280 os.flush(); 281 os.close(); 282 } catch (IOException ex) { 283 Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage()); 284 } 285 286 finished(); 287 } 288 289 public void setCallback(TestShellCallback callback) { 290 mCallback = callback; 291 } 292 293 public void finished() { 294 if (mTestPageLoaded) { 295 if (mUiAutoTestPath != null) { 296 //don't really finish here 297 moveToNextTest(); 298 } else { 299 if (mCallback != null) { 300 mCallback.finished(); 301 } 302 } 303 } else { 304 // The test is complete but the page has not completed loading. We 305 // can't continue to the next test until both the test is finished 306 // and the page has stopped loading. 307 mReadyForNextTest = true; 308 } 309 } 310 311 public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) { 312 mDefaultDumpDataType = defaultDumpDataType; 313 } 314 315 // ....................................... 316 // LayoutTestController Functions 317 public void dumpAsText() { 318 mDumpDataType = DumpDataType.DUMP_AS_TEXT; 319 if (mWebView != null) { 320 String url = mWebView.getUrl(); 321 Log.v(LOGTAG, "dumpAsText called: "+url); 322 } 323 } 324 325 public void waitUntilDone() { 326 mWaitUntilDone = true; 327 String url = mWebView.getUrl(); 328 Log.v(LOGTAG, "waitUntilDone called: " + url); 329 } 330 331 public void notifyDone() { 332 String url = mWebView.getUrl(); 333 Log.v(LOGTAG, "notifyDone called: " + url); 334 if (mWaitUntilDone) { 335 mWaitUntilDone = false; 336 mChromeClient.onProgressChanged(mWebView, 100); 337 } 338 } 339 340 public void display() { 341 mWebView.invalidate(); 342 } 343 344 public void clearBackForwardList() { 345 mWebView.clearHistory(); 346 347 } 348 349 public void dumpBackForwardList() { 350 //printf("\n============== Back Forward List ==============\n"); 351 // mWebHistory 352 //printf("===============================================\n"); 353 354 } 355 356 public void dumpChildFrameScrollPositions() { 357 // TODO Auto-generated method stub 358 359 } 360 361 public void dumpEditingCallbacks() { 362 // TODO Auto-generated method stub 363 364 } 365 366 public void dumpSelectionRect() { 367 // TODO Auto-generated method stub 368 369 } 370 371 public void dumpTitleChanges() { 372 if (!mDumpTitleChanges) { 373 mTitleChanges = new StringBuffer(); 374 } 375 mDumpTitleChanges = true; 376 } 377 378 public void keepWebHistory() { 379 if (!mKeepWebHistory) { 380 mWebHistory = new Vector(); 381 } 382 mKeepWebHistory = true; 383 } 384 385 public void queueBackNavigation(int howfar) { 386 // TODO Auto-generated method stub 387 388 } 389 390 public void queueForwardNavigation(int howfar) { 391 // TODO Auto-generated method stub 392 393 } 394 395 public void queueLoad(String Url, String frameTarget) { 396 // TODO Auto-generated method stub 397 398 } 399 400 public void queueReload() { 401 mWebView.reload(); 402 } 403 404 public void queueScript(String scriptToRunInCurrentContext) { 405 mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext); 406 } 407 408 public void repaintSweepHorizontally() { 409 // TODO Auto-generated method stub 410 411 } 412 413 public void setAcceptsEditing(boolean b) { 414 // TODO Auto-generated method stub 415 416 } 417 418 public void setMainFrameIsFirstResponder(boolean b) { 419 // TODO Auto-generated method stub 420 421 } 422 423 public void setWindowIsKey(boolean b) { 424 // This is meant to show/hide the window. The best I can find 425 // is setEnabled() 426 mWebView.setEnabled(b); 427 } 428 429 public void testRepaint() { 430 mWebView.invalidate(); 431 } 432 433 public void dumpDatabaseCallbacks() { 434 Log.v(LOGTAG, "dumpDatabaseCallbacks called."); 435 mDumpDatabaseCallbacks = true; 436 } 437 438 public void setCanOpenWindows() { 439 Log.v(LOGTAG, "setCanOpenWindows called."); 440 mCanOpenWindows = true; 441 } 442 443 /** 444 * Sets the Geolocation permission state to be used for all future requests. 445 */ 446 public void setGeolocationPermission(boolean allow) { 447 mGeolocationPermissionSet = true; 448 mGeolocationPermission = allow; 449 } 450 451 private final WebViewClient mViewClient = new WebViewClient(){ 452 @Override 453 public void onPageFinished(WebView view, String url) { 454 Log.v(LOGTAG, "onPageFinished, url=" + url); 455 mTestPageLoaded = true; 456 super.onPageFinished(view, url); 457 } 458 459 @Override 460 public void onPageStarted(WebView view, String url, Bitmap favicon) { 461 Log.v(LOGTAG, "onPageStarted, url=" + url); 462 mTestPageLoaded = false; 463 super.onPageStarted(view, url, favicon); 464 } 465 466 @Override 467 public void onReceivedError(WebView view, int errorCode, String description, 468 String failingUrl) { 469 Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode 470 + ", desc=" + description + ", url=" + failingUrl); 471 super.onReceivedError(view, errorCode, description, failingUrl); 472 } 473 474 @Override 475 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, 476 String host, String realm) { 477 handler.cancel(); 478 } 479 480 @Override 481 public void onReceivedSslError(WebView view, SslErrorHandler handler, 482 SslError error) { 483 handler.proceed(); 484 } 485 }; 486 487 488 private final WebChromeClient mChromeClient = new WebChromeClient() { 489 @Override 490 public void onProgressChanged(WebView view, int newProgress) { 491 if (newProgress == 100) { 492 493 if (mReadyForNextTest) { 494 // In this case, the test has completed (i.e. called 495 // layoutTestController.notifyDone) before the page finished loading. This 496 // usually happens if the test is not invoked by an onload handler, rather 497 // directly in a script tag. Now that the page has finished loading, it is 498 // safe for DRT to go to the next test. 499 finished(); 500 return; 501 } 502 503 if (!mTimedOut && !mWaitUntilDone && !mRequestedWebKitData) { 504 String url = mWebView.getUrl(); 505 Log.v(LOGTAG, "Finished: "+ url); 506 mHandler.removeMessages(MSG_TIMEOUT); 507 requestWebKitData(); 508 } else { 509 String url = mWebView.getUrl(); 510 if (mTimedOut) { 511 Log.v(LOGTAG, "Timed out before finishing: " + url); 512 } else if (mWaitUntilDone) { 513 Log.v(LOGTAG, "Waiting for notifyDone: " + url); 514 } else if (mRequestedWebKitData) { 515 Log.v(LOGTAG, "Requested webkit data ready: " + url); 516 } 517 } 518 } 519 } 520 521 @Override 522 public void onReceivedTitle(WebView view, String title) { 523 if (title.length() > 30) 524 title = "..."+title.substring(title.length()-30); 525 setTitle(title); 526 if (mDumpTitleChanges) { 527 mTitleChanges.append("TITLE CHANGED: "); 528 mTitleChanges.append(title); 529 mTitleChanges.append("\n"); 530 } 531 } 532 533 @Override 534 public boolean onJsAlert(WebView view, String url, String message, 535 JsResult result) { 536 if (mDialogStrings == null) { 537 mDialogStrings = new StringBuffer(); 538 } 539 mDialogStrings.append("ALERT: "); 540 mDialogStrings.append(message); 541 mDialogStrings.append('\n'); 542 result.confirm(); 543 return true; 544 } 545 546 @Override 547 public boolean onJsConfirm(WebView view, String url, String message, 548 JsResult result) { 549 if (mDialogStrings == null) { 550 mDialogStrings = new StringBuffer(); 551 } 552 mDialogStrings.append("CONFIRM: "); 553 mDialogStrings.append(message); 554 mDialogStrings.append('\n'); 555 result.confirm(); 556 return true; 557 } 558 559 @Override 560 public boolean onJsPrompt(WebView view, String url, String message, 561 String defaultValue, JsPromptResult result) { 562 if (mDialogStrings == null) { 563 mDialogStrings = new StringBuffer(); 564 } 565 mDialogStrings.append("PROMPT: "); 566 mDialogStrings.append(message); 567 mDialogStrings.append(", default text: "); 568 mDialogStrings.append(defaultValue); 569 mDialogStrings.append('\n'); 570 result.confirm(); 571 return true; 572 } 573 574 @Override 575 public boolean onJsTimeout() { 576 Log.v(LOGTAG, "JavaScript timeout"); 577 return false; 578 } 579 580 @Override 581 public void onExceededDatabaseQuota(String url_str, 582 String databaseIdentifier, long currentQuota, 583 long estimatedSize, long totalUsedQuota, 584 WebStorage.QuotaUpdater callback) { 585 if (mDumpDatabaseCallbacks) { 586 if (mDatabaseCallbackStrings == null) { 587 mDatabaseCallbackStrings = new StringBuffer(); 588 } 589 590 String protocol = ""; 591 String host = ""; 592 int port = 0; 593 594 try { 595 URL url = new URL(url_str); 596 protocol = url.getProtocol(); 597 host = url.getHost(); 598 if (url.getPort() > -1) { 599 port = url.getPort(); 600 } 601 } catch (MalformedURLException e) {} 602 603 String databaseCallbackString = 604 "UI DELEGATE DATABASE CALLBACK: " + 605 "exceededDatabaseQuotaForSecurityOrigin:{" + protocol + 606 ", " + host + ", " + port + "} database:" + 607 databaseIdentifier + "\n"; 608 Log.v(LOGTAG, "LOG: "+databaseCallbackString); 609 mDatabaseCallbackStrings.append(databaseCallbackString); 610 } 611 // Give 5MB more quota. 612 callback.updateQuota(currentQuota + 1024 * 1024 * 5); 613 } 614 615 /** 616 * Instructs the client to show a prompt to ask the user to set the 617 * Geolocation permission state for the specified origin. 618 */ 619 @Override 620 public void onGeolocationPermissionsShowPrompt(String origin, 621 GeolocationPermissions.Callback callback) { 622 if (mGeolocationPermissionSet) { 623 callback.invoke(origin, mGeolocationPermission, false); 624 } 625 } 626 627 @Override 628 public void addMessageToConsole(String message, int lineNumber, 629 String sourceID) { 630 if (mConsoleMessages == null) { 631 mConsoleMessages = new StringBuffer(); 632 } 633 String consoleMessage = "CONSOLE MESSAGE: line " 634 + lineNumber +": "+ message +"\n"; 635 mConsoleMessages.append(consoleMessage); 636 Log.v(LOGTAG, "LOG: "+consoleMessage); 637 } 638 639 @Override 640 public boolean onCreateWindow(WebView view, boolean dialog, 641 boolean userGesture, Message resultMsg) { 642 if (!mCanOpenWindows) { 643 return false; 644 } 645 646 // We never display the new window, just create the view and 647 // allow it's content to execute and be recorded by the test 648 // runner. 649 650 HashMap<String, Object> jsIfaces = new HashMap<String, Object>(); 651 jsIfaces.put("layoutTestController", mCallbackProxy); 652 jsIfaces.put("eventSender", mCallbackProxy); 653 WebView newWindowView = new NewWindowWebView(TestShellActivity.this, jsIfaces); 654 setupWebViewForLayoutTests(newWindowView, mCallbackProxy); 655 WebView.WebViewTransport transport = 656 (WebView.WebViewTransport) resultMsg.obj; 657 transport.setWebView(newWindowView); 658 resultMsg.sendToTarget(); 659 return true; 660 } 661 }; 662 663 private static class NewWindowWebView extends WebView { 664 public NewWindowWebView(Context context, Map<String, Object> jsIfaces) { 665 super(context, null, 0, jsIfaces); 666 } 667 } 668 669 private void resetTestStatus() { 670 mWaitUntilDone = false; 671 mDumpDataType = mDefaultDumpDataType; 672 mTimedOut = false; 673 mDumpTitleChanges = false; 674 mRequestedWebKitData = false; 675 mDumpDatabaseCallbacks = false; 676 mCanOpenWindows = false; 677 mEventSender.resetMouse(); 678 mTestPageLoaded = false; 679 mReadyForNextTest = false; 680 } 681 682 private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) { 683 if (webview == null) { 684 return; 685 } 686 687 WebSettings settings = webview.getSettings(); 688 settings.setAppCacheEnabled(true); 689 settings.setAppCachePath(getApplicationContext().getCacheDir().getPath()); 690 settings.setAppCacheMaxSize(Long.MAX_VALUE); 691 settings.setJavaScriptEnabled(true); 692 settings.setJavaScriptCanOpenWindowsAutomatically(true); 693 settings.setSupportMultipleWindows(true); 694 settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); 695 settings.setDatabaseEnabled(true); 696 settings.setDatabasePath(getDir("databases",0).getAbsolutePath()); 697 settings.setDomStorageEnabled(true); 698 settings.setWorkersEnabled(false); 699 700 webview.setWebChromeClient(mChromeClient); 701 webview.setWebViewClient(mViewClient); 702 } 703 704 private WebView mWebView; 705 private WebViewEventSender mEventSender; 706 private AsyncHandler mHandler; 707 private TestShellCallback mCallback; 708 709 private CallbackProxy mCallbackProxy; 710 711 private String mTestUrl; 712 private String mResultFile; 713 private int mTimeoutInMillis; 714 private String mUiAutoTestPath; 715 private BufferedReader mTestListReader; 716 717 // States 718 private boolean mTimedOut; 719 private boolean mRequestedWebKitData; 720 private boolean mFinishedRunning; 721 722 // Layout test controller variables. 723 private DumpDataType mDumpDataType; 724 private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR; 725 private boolean mWaitUntilDone; 726 private boolean mDumpTitleChanges; 727 private StringBuffer mTitleChanges; 728 private StringBuffer mDialogStrings; 729 private boolean mKeepWebHistory; 730 private Vector mWebHistory; 731 private boolean mDumpDatabaseCallbacks; 732 private StringBuffer mDatabaseCallbackStrings; 733 private StringBuffer mConsoleMessages; 734 private boolean mCanOpenWindows; 735 736 private boolean mTestPageLoaded = false; 737 private boolean mReadyForNextTest = false; 738 739 static final String TIMEOUT_STR = "**Test timeout"; 740 741 static final int MSG_TIMEOUT = 0; 742 static final int MSG_WEBKIT_DATA = 1; 743 744 static final String LOGTAG="TestShell"; 745 746 static final String TEST_URL = "TestUrl"; 747 static final String RESULT_FILE = "ResultFile"; 748 static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis"; 749 static final String UI_AUTO_TEST = "UiAutoTest"; 750 751 private boolean mGeolocationPermissionSet; 752 private boolean mGeolocationPermission; 753} 754