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