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