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