TestShellActivity.java revision 5f37cc4308438bcac45d06d07d77a56911620046
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.graphics.Canvas; 29import android.graphics.Bitmap.CompressFormat; 30import android.graphics.Bitmap.Config; 31import android.net.http.SslError; 32import android.os.Bundle; 33import android.os.Environment; 34import android.os.Handler; 35import android.os.Message; 36import android.util.Log; 37import android.view.ViewGroup; 38import android.view.Window; 39import android.webkit.ConsoleMessage; 40import android.webkit.GeolocationPermissions; 41import android.webkit.HttpAuthHandler; 42import android.webkit.JsPromptResult; 43import android.webkit.JsResult; 44import android.webkit.SslErrorHandler; 45import android.webkit.WebChromeClient; 46import android.webkit.WebSettings; 47import android.webkit.WebStorage; 48import android.webkit.WebView; 49import android.webkit.WebViewClient; 50import android.widget.LinearLayout; 51 52import java.io.BufferedReader; 53import java.io.File; 54import java.io.FileOutputStream; 55import java.io.FileReader; 56import java.io.IOException; 57import java.net.MalformedURLException; 58import java.net.URL; 59import java.util.HashMap; 60import java.util.Iterator; 61import java.util.Map; 62import java.util.Vector; 63 64public class TestShellActivity extends Activity implements LayoutTestController { 65 66 static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP} 67 68 // String constants for use with layoutTestController.overridePreferences 69 private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED = 70 "WebKitOfflineWebApplicationCacheEnabled"; 71 private final String WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY = "WebKitUsesPageCachePreferenceKey"; 72 73 public class AsyncHandler extends Handler { 74 @Override 75 public void handleMessage(Message msg) { 76 if (msg.what == MSG_TIMEOUT) { 77 mTimedOut = true; 78 if (mCallback != null) 79 mCallback.timedOut(mWebView.getUrl()); 80 if (!mRequestedWebKitData) { 81 requestWebKitData(); 82 } else { 83 // if timed out and webkit data has been dumped before 84 // finish directly 85 finished(); 86 } 87 return; 88 } else if (msg.what == MSG_WEBKIT_DATA) { 89 TestShellActivity.this.dump(mTimedOut, (String)msg.obj); 90 return; 91 } 92 93 super.handleMessage(msg); 94 } 95 } 96 97 public void requestWebKitData() { 98 Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA); 99 100 if (mRequestedWebKitData) 101 throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl()); 102 103 mRequestedWebKitData = true; 104 Log.v(LOGTAG, "message sent to WebView to dump text."); 105 switch (mDumpDataType) { 106 case DUMP_AS_TEXT: 107 callback.arg1 = mDumpTopFrameAsText ? 1 : 0; 108 callback.arg2 = mDumpChildFramesAsText ? 1 : 0; 109 mWebView.documentAsText(callback); 110 break; 111 case EXT_REPR: 112 mWebView.externalRepresentation(callback); 113 break; 114 default: 115 finished(); 116 break; 117 } 118 } 119 120 public void clearCache() { 121 mWebView.freeMemory(); 122 } 123 124 @Override 125 protected void onCreate(Bundle icicle) { 126 super.onCreate(icicle); 127 requestWindowFeature(Window.FEATURE_PROGRESS); 128 129 LinearLayout contentView = new LinearLayout(this); 130 contentView.setOrientation(LinearLayout.VERTICAL); 131 setContentView(contentView); 132 133 mWebView = new WebView(this); 134 mEventSender = new WebViewEventSender(mWebView); 135 mCallbackProxy = new CallbackProxy(mEventSender, this); 136 137 mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController"); 138 mWebView.addJavascriptInterface(mCallbackProxy, "eventSender"); 139 setupWebViewForLayoutTests(mWebView, mCallbackProxy); 140 141 contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0.0f)); 142 143 mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); 144 145 // Expose window.gc function to JavaScript. JSC build exposes 146 // this function by default, but V8 requires the flag to turn it on. 147 // WebView::setJsFlags is noop in JSC build. 148 mWebView.setJsFlags("--expose_gc"); 149 150 // Always send multitouch events to Webkit since the layout test 151 // is only for the Webkit not the browser's UI. 152 mWebView.setDeferMultiTouch(true); 153 154 mHandler = new AsyncHandler(); 155 156 Intent intent = getIntent(); 157 if (intent != null) { 158 executeIntent(intent); 159 } 160 161 // This is asynchronous, but it gets processed by WebCore before it starts loading pages. 162 mWebView.useMockDeviceOrientation(); 163 } 164 165 @Override 166 protected void onNewIntent(Intent intent) { 167 super.onNewIntent(intent); 168 executeIntent(intent); 169 } 170 171 private void executeIntent(Intent intent) { 172 resetTestStatus(); 173 if (!Intent.ACTION_VIEW.equals(intent.getAction())) { 174 return; 175 } 176 177 mTotalTestCount = intent.getIntExtra(TOTAL_TEST_COUNT, mTotalTestCount); 178 mCurrentTestNumber = intent.getIntExtra(CURRENT_TEST_NUMBER, mCurrentTestNumber); 179 180 mTestUrl = intent.getStringExtra(TEST_URL); 181 if (mTestUrl == null) { 182 mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST); 183 if(mUiAutoTestPath != null) { 184 beginUiAutoTest(); 185 } 186 return; 187 } 188 189 mResultFile = intent.getStringExtra(RESULT_FILE); 190 mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0); 191 mGetDrawtime = intent.getBooleanExtra(GET_DRAW_TIME, false); 192 mSaveImagePath = intent.getStringExtra(SAVE_IMAGE); 193 mStopOnRefError = intent.getBooleanExtra(STOP_ON_REF_ERROR, false); 194 setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount); 195 float ratio = (float)mCurrentTestNumber / mTotalTestCount; 196 int progress = (int)(ratio * Window.PROGRESS_END); 197 getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress); 198 199 Log.v(LOGTAG, " Loading " + mTestUrl); 200 mWebView.loadUrl(mTestUrl); 201 202 if (mTimeoutInMillis > 0) { 203 // Create a timeout timer 204 Message m = mHandler.obtainMessage(MSG_TIMEOUT); 205 mHandler.sendMessageDelayed(m, mTimeoutInMillis); 206 } 207 } 208 209 private void beginUiAutoTest() { 210 try { 211 mTestListReader = new BufferedReader( 212 new FileReader(mUiAutoTestPath)); 213 } catch (IOException ioe) { 214 Log.e(LOGTAG, "Failed to open test list for read.", ioe); 215 finishUiAutoTest(); 216 return; 217 } 218 moveToNextTest(); 219 } 220 221 private void finishUiAutoTest() { 222 try { 223 if(mTestListReader != null) 224 mTestListReader.close(); 225 } catch (IOException ioe) { 226 Log.w(LOGTAG, "Failed to close test list file.", ioe); 227 } 228 ForwardService.getForwardService().stopForwardService(); 229 finished(); 230 } 231 232 private void moveToNextTest() { 233 String url = null; 234 try { 235 url = mTestListReader.readLine(); 236 } catch (IOException ioe) { 237 Log.e(LOGTAG, "Failed to read next test.", ioe); 238 finishUiAutoTest(); 239 return; 240 } 241 if (url == null) { 242 mUiAutoTestPath = null; 243 finishUiAutoTest(); 244 AlertDialog.Builder builder = new AlertDialog.Builder(this); 245 builder.setMessage("All tests finished. Exit?") 246 .setCancelable(false) 247 .setPositiveButton("Yes", new OnClickListener(){ 248 public void onClick(DialogInterface dialog, int which) { 249 TestShellActivity.this.finish(); 250 } 251 }) 252 .setNegativeButton("No", new OnClickListener(){ 253 public void onClick(DialogInterface dialog, int which) { 254 dialog.cancel(); 255 } 256 }); 257 builder.create().show(); 258 return; 259 } 260 Intent intent = new Intent(Intent.ACTION_VIEW); 261 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 262 intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(url)); 263 intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, ++mCurrentTestNumber); 264 intent.putExtra(TIMEOUT_IN_MILLIS, 10000); 265 executeIntent(intent); 266 } 267 268 @Override 269 protected void onStop() { 270 super.onStop(); 271 mWebView.stopLoading(); 272 } 273 274 @Override 275 protected void onDestroy() { 276 super.onDestroy(); 277 mWebView.destroy(); 278 mWebView = null; 279 } 280 281 @Override 282 public void onLowMemory() { 283 super.onLowMemory(); 284 Log.e(LOGTAG, "Low memory, clearing caches"); 285 mWebView.freeMemory(); 286 } 287 288 // Dump the page 289 public void dump(boolean timeout, String webkitData) { 290 mDumpWebKitData = true; 291 if (mResultFile == null || mResultFile.length() == 0) { 292 finished(); 293 return; 294 } 295 296 try { 297 File parentDir = new File(mResultFile).getParentFile(); 298 if (!parentDir.exists()) { 299 parentDir.mkdirs(); 300 } 301 302 FileOutputStream os = new FileOutputStream(mResultFile); 303 if (timeout) { 304 Log.w("Layout test: Timeout", mResultFile); 305 os.write(TIMEOUT_STR.getBytes()); 306 os.write('\n'); 307 } 308 if (mDumpTitleChanges) 309 os.write(mTitleChanges.toString().getBytes()); 310 if (mDialogStrings != null) 311 os.write(mDialogStrings.toString().getBytes()); 312 mDialogStrings = null; 313 if (mDatabaseCallbackStrings != null) 314 os.write(mDatabaseCallbackStrings.toString().getBytes()); 315 mDatabaseCallbackStrings = null; 316 if (mConsoleMessages != null) 317 os.write(mConsoleMessages.toString().getBytes()); 318 mConsoleMessages = null; 319 if (webkitData != null) 320 os.write(webkitData.getBytes()); 321 os.flush(); 322 os.close(); 323 } catch (IOException ex) { 324 Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage()); 325 } 326 327 finished(); 328 } 329 330 public void setCallback(TestShellCallback callback) { 331 mCallback = callback; 332 } 333 334 public boolean finished() { 335 if (canMoveToNextTest()) { 336 mHandler.removeMessages(MSG_TIMEOUT); 337 if (mUiAutoTestPath != null) { 338 //don't really finish here 339 moveToNextTest(); 340 } else { 341 if (mCallback != null) { 342 mCallback.finished(); 343 } 344 } 345 return true; 346 } 347 return false; 348 } 349 350 public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) { 351 mDefaultDumpDataType = defaultDumpDataType; 352 } 353 354 // ....................................... 355 // LayoutTestController Functions 356 public void dumpAsText(boolean enablePixelTests) { 357 // Added after webkit update to r63859. See trac.webkit.org/changeset/63730. 358 if (enablePixelTests) { 359 Log.v(LOGTAG, "dumpAsText(enablePixelTests == true) not implemented on Android!"); 360 } 361 362 mDumpDataType = DumpDataType.DUMP_AS_TEXT; 363 mDumpTopFrameAsText = true; 364 if (mWebView != null) { 365 String url = mWebView.getUrl(); 366 Log.v(LOGTAG, "dumpAsText called: "+url); 367 } 368 } 369 370 public void dumpChildFramesAsText() { 371 mDumpDataType = DumpDataType.DUMP_AS_TEXT; 372 mDumpChildFramesAsText = true; 373 if (mWebView != null) { 374 String url = mWebView.getUrl(); 375 Log.v(LOGTAG, "dumpChildFramesAsText called: "+url); 376 } 377 } 378 379 public void waitUntilDone() { 380 mWaitUntilDone = true; 381 String url = mWebView.getUrl(); 382 Log.v(LOGTAG, "waitUntilDone called: " + url); 383 } 384 385 public void notifyDone() { 386 String url = mWebView.getUrl(); 387 Log.v(LOGTAG, "notifyDone called: " + url); 388 if (mWaitUntilDone) { 389 mWaitUntilDone = false; 390 if (!mRequestedWebKitData && !mTimedOut && !finished()) { 391 requestWebKitData(); 392 } 393 } 394 } 395 396 public void display() { 397 mWebView.invalidate(); 398 } 399 400 public void clearBackForwardList() { 401 mWebView.clearHistory(); 402 403 } 404 405 public void dumpBackForwardList() { 406 //printf("\n============== Back Forward List ==============\n"); 407 // mWebHistory 408 //printf("===============================================\n"); 409 410 } 411 412 public void dumpChildFrameScrollPositions() { 413 // TODO Auto-generated method stub 414 415 } 416 417 public void dumpEditingCallbacks() { 418 // TODO Auto-generated method stub 419 420 } 421 422 public void dumpSelectionRect() { 423 // TODO Auto-generated method stub 424 425 } 426 427 public void dumpTitleChanges() { 428 if (!mDumpTitleChanges) { 429 mTitleChanges = new StringBuffer(); 430 } 431 mDumpTitleChanges = true; 432 } 433 434 public void keepWebHistory() { 435 if (!mKeepWebHistory) { 436 mWebHistory = new Vector(); 437 } 438 mKeepWebHistory = true; 439 } 440 441 public void queueBackNavigation(int howfar) { 442 // TODO Auto-generated method stub 443 444 } 445 446 public void queueForwardNavigation(int howfar) { 447 // TODO Auto-generated method stub 448 449 } 450 451 public void queueLoad(String Url, String frameTarget) { 452 // TODO Auto-generated method stub 453 454 } 455 456 public void queueReload() { 457 mWebView.reload(); 458 } 459 460 public void queueScript(String scriptToRunInCurrentContext) { 461 mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext); 462 } 463 464 public void repaintSweepHorizontally() { 465 // TODO Auto-generated method stub 466 467 } 468 469 public void setAcceptsEditing(boolean b) { 470 // TODO Auto-generated method stub 471 472 } 473 474 public void setMainFrameIsFirstResponder(boolean b) { 475 // TODO Auto-generated method stub 476 477 } 478 479 public void setWindowIsKey(boolean b) { 480 // This is meant to show/hide the window. The best I can find 481 // is setEnabled() 482 mWebView.setEnabled(b); 483 } 484 485 public void testRepaint() { 486 mWebView.invalidate(); 487 } 488 489 public void dumpDatabaseCallbacks() { 490 Log.v(LOGTAG, "dumpDatabaseCallbacks called."); 491 mDumpDatabaseCallbacks = true; 492 } 493 494 public void setCanOpenWindows() { 495 Log.v(LOGTAG, "setCanOpenWindows called."); 496 mCanOpenWindows = true; 497 } 498 499 /** 500 * Sets the Geolocation permission state to be used for all future requests. 501 */ 502 public void setGeolocationPermission(boolean allow) { 503 mIsGeolocationPermissionSet = true; 504 mGeolocationPermission = allow; 505 506 if (mPendingGeolocationPermissionCallbacks != null) { 507 Iterator iter = mPendingGeolocationPermissionCallbacks.keySet().iterator(); 508 while (iter.hasNext()) { 509 GeolocationPermissions.Callback callback = 510 (GeolocationPermissions.Callback) iter.next(); 511 String origin = (String) mPendingGeolocationPermissionCallbacks.get(callback); 512 callback.invoke(origin, mGeolocationPermission, false); 513 } 514 mPendingGeolocationPermissionCallbacks = null; 515 } 516 } 517 518 public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, 519 boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { 520 mWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta, 521 canProvideGamma, gamma); 522 } 523 524 public void overridePreference(String key, boolean value) { 525 // TODO: We should look up the correct WebView for the frame which 526 // called the layoutTestController method. Currently, we just use the 527 // WebView for the main frame. EventSender suffers from the same 528 // problem. 529 if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) { 530 mWebView.getSettings().setAppCacheEnabled(value); 531 } else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) { 532 // Cache the maximum possible number of pages. 533 mWebView.getSettings().setPageCacheCapacity(Integer.MAX_VALUE); 534 } else { 535 Log.w(LOGTAG, "LayoutTestController.overridePreference(): " + 536 "Unsupported preference '" + key + "'"); 537 } 538 } 539 540 public void setXSSAuditorEnabled (boolean flag) { 541 mWebView.getSettings().setXSSAuditorEnabled(flag); 542 } 543 544 private final WebViewClient mViewClient = new WebViewClient(){ 545 @Override 546 public void onPageFinished(WebView view, String url) { 547 Log.v(LOGTAG, "onPageFinished, url=" + url); 548 mPageFinished = true; 549 // get page draw time 550 if (FsUtils.isTestPageUrl(url)) { 551 if (mGetDrawtime) { 552 long[] times = new long[DRAW_RUNS]; 553 times = getDrawWebViewTime(mWebView, DRAW_RUNS); 554 FsUtils.writeDrawTime(DRAW_TIME_LOG, url, times); 555 } 556 if (mSaveImagePath != null) { 557 String name = FsUtils.getLastSegmentInPath(url); 558 drawPageToFile(mSaveImagePath + "/" + name + ".png", mWebView); 559 } 560 } 561 562 // Calling finished() will check if we've met all the conditions for completing 563 // this test and move to the next one if we are ready. Otherwise we ask WebCore to 564 // dump the page. 565 if (finished()) { 566 return; 567 } 568 569 if (!mWaitUntilDone && !mRequestedWebKitData && !mTimedOut) { 570 requestWebKitData(); 571 } else { 572 if (mWaitUntilDone) { 573 Log.v(LOGTAG, "page finished loading but waiting for notifyDone to be called: " + url); 574 } 575 576 if (mRequestedWebKitData) { 577 Log.v(LOGTAG, "page finished loading but webkit data has already been requested: " + url); 578 } 579 580 if (mTimedOut) { 581 Log.v(LOGTAG, "page finished loading but already timed out: " + url); 582 } 583 } 584 585 super.onPageFinished(view, url); 586 } 587 588 @Override 589 public void onPageStarted(WebView view, String url, Bitmap favicon) { 590 Log.v(LOGTAG, "onPageStarted, url=" + url); 591 mPageFinished = false; 592 super.onPageStarted(view, url, favicon); 593 } 594 595 @Override 596 public void onReceivedError(WebView view, int errorCode, String description, 597 String failingUrl) { 598 Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode 599 + ", desc=" + description + ", url=" + failingUrl); 600 super.onReceivedError(view, errorCode, description, failingUrl); 601 } 602 603 @Override 604 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, 605 String host, String realm) { 606 if (handler.useHttpAuthUsernamePassword() && view != null) { 607 String[] credentials = view.getHttpAuthUsernamePassword(host, realm); 608 if (credentials != null && credentials.length == 2) { 609 handler.proceed(credentials[0], credentials[1]); 610 return; 611 } 612 } 613 handler.cancel(); 614 } 615 616 @Override 617 public void onReceivedSslError(WebView view, SslErrorHandler handler, 618 SslError error) { 619 handler.proceed(); 620 } 621 }; 622 623 624 private final WebChromeClient mChromeClient = new WebChromeClient() { 625 @Override 626 public void onReceivedTitle(WebView view, String title) { 627 setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount + ": "+ title); 628 if (mDumpTitleChanges) { 629 mTitleChanges.append("TITLE CHANGED: "); 630 mTitleChanges.append(title); 631 mTitleChanges.append("\n"); 632 } 633 } 634 635 @Override 636 public boolean onJsAlert(WebView view, String url, String message, 637 JsResult result) { 638 if (mDialogStrings == null) { 639 mDialogStrings = new StringBuffer(); 640 } 641 mDialogStrings.append("ALERT: "); 642 mDialogStrings.append(message); 643 mDialogStrings.append('\n'); 644 result.confirm(); 645 return true; 646 } 647 648 @Override 649 public boolean onJsConfirm(WebView view, String url, String message, 650 JsResult result) { 651 if (mDialogStrings == null) { 652 mDialogStrings = new StringBuffer(); 653 } 654 mDialogStrings.append("CONFIRM: "); 655 mDialogStrings.append(message); 656 mDialogStrings.append('\n'); 657 result.confirm(); 658 return true; 659 } 660 661 @Override 662 public boolean onJsPrompt(WebView view, String url, String message, 663 String defaultValue, JsPromptResult result) { 664 if (mDialogStrings == null) { 665 mDialogStrings = new StringBuffer(); 666 } 667 mDialogStrings.append("PROMPT: "); 668 mDialogStrings.append(message); 669 mDialogStrings.append(", default text: "); 670 mDialogStrings.append(defaultValue); 671 mDialogStrings.append('\n'); 672 result.confirm(); 673 return true; 674 } 675 676 @Override 677 public boolean onJsTimeout() { 678 Log.v(LOGTAG, "JavaScript timeout"); 679 return false; 680 } 681 682 @Override 683 public void onExceededDatabaseQuota(String url_str, 684 String databaseIdentifier, long currentQuota, 685 long estimatedSize, long totalUsedQuota, 686 WebStorage.QuotaUpdater callback) { 687 if (mDumpDatabaseCallbacks) { 688 if (mDatabaseCallbackStrings == null) { 689 mDatabaseCallbackStrings = new StringBuffer(); 690 } 691 692 String protocol = ""; 693 String host = ""; 694 int port = 0; 695 696 try { 697 URL url = new URL(url_str); 698 protocol = url.getProtocol(); 699 host = url.getHost(); 700 if (url.getPort() > -1) { 701 port = url.getPort(); 702 } 703 } catch (MalformedURLException e) {} 704 705 String databaseCallbackString = 706 "UI DELEGATE DATABASE CALLBACK: " + 707 "exceededDatabaseQuotaForSecurityOrigin:{" + protocol + 708 ", " + host + ", " + port + "} database:" + 709 databaseIdentifier + "\n"; 710 Log.v(LOGTAG, "LOG: "+databaseCallbackString); 711 mDatabaseCallbackStrings.append(databaseCallbackString); 712 } 713 // Give 5MB more quota. 714 callback.updateQuota(currentQuota + 1024 * 1024 * 5); 715 } 716 717 /** 718 * Instructs the client to show a prompt to ask the user to set the 719 * Geolocation permission state for the specified origin. 720 */ 721 @Override 722 public void onGeolocationPermissionsShowPrompt(String origin, 723 GeolocationPermissions.Callback callback) { 724 if (mIsGeolocationPermissionSet) { 725 callback.invoke(origin, mGeolocationPermission, false); 726 return; 727 } 728 if (mPendingGeolocationPermissionCallbacks == null) { 729 mPendingGeolocationPermissionCallbacks = 730 new HashMap<GeolocationPermissions.Callback, String>(); 731 } 732 mPendingGeolocationPermissionCallbacks.put(callback, origin); 733 } 734 735 @Override 736 public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 737 String msg = "CONSOLE MESSAGE: line " + consoleMessage.lineNumber() + ": " 738 + consoleMessage.message() + "\n"; 739 if (mConsoleMessages == null) { 740 mConsoleMessages = new StringBuffer(); 741 } 742 mConsoleMessages.append(msg); 743 Log.v(LOGTAG, "LOG: " + msg); 744 // the rationale here is that if there's an error of either type, and the test was 745 // waiting for "notifyDone" signal to finish, then there's no point in waiting 746 // anymore because the JS execution is already terminated at this point and a 747 // "notifyDone" will never come out so it's just wasting time till timeout kicks in 748 if ((msg.contains("Uncaught ReferenceError:") || msg.contains("Uncaught TypeError:")) 749 && mWaitUntilDone && mStopOnRefError) { 750 Log.w(LOGTAG, "Terminating test case on uncaught ReferenceError or TypeError."); 751 mHandler.postDelayed(new Runnable() { 752 public void run() { 753 notifyDone(); 754 } 755 }, 500); 756 } 757 return true; 758 } 759 760 @Override 761 public boolean onCreateWindow(WebView view, boolean dialog, 762 boolean userGesture, Message resultMsg) { 763 if (!mCanOpenWindows) { 764 // We can't open windows, so just send null back. 765 WebView.WebViewTransport transport = 766 (WebView.WebViewTransport) resultMsg.obj; 767 transport.setWebView(null); 768 resultMsg.sendToTarget(); 769 return true; 770 } 771 772 // We never display the new window, just create the view and 773 // allow it's content to execute and be recorded by the test 774 // runner. 775 776 HashMap<String, Object> jsIfaces = new HashMap<String, Object>(); 777 jsIfaces.put("layoutTestController", mCallbackProxy); 778 jsIfaces.put("eventSender", mCallbackProxy); 779 WebView newWindowView = new NewWindowWebView(TestShellActivity.this, jsIfaces); 780 setupWebViewForLayoutTests(newWindowView, mCallbackProxy); 781 WebView.WebViewTransport transport = 782 (WebView.WebViewTransport) resultMsg.obj; 783 transport.setWebView(newWindowView); 784 resultMsg.sendToTarget(); 785 return true; 786 } 787 788 @Override 789 public void onCloseWindow(WebView view) { 790 view.destroy(); 791 } 792 }; 793 794 private static class NewWindowWebView extends WebView { 795 public NewWindowWebView(Context context, Map<String, Object> jsIfaces) { 796 super(context, null, 0, jsIfaces, false); 797 } 798 } 799 800 private void resetTestStatus() { 801 mWaitUntilDone = false; 802 mDumpDataType = mDefaultDumpDataType; 803 mDumpTopFrameAsText = false; 804 mDumpChildFramesAsText = false; 805 mTimedOut = false; 806 mDumpTitleChanges = false; 807 mRequestedWebKitData = false; 808 mDumpDatabaseCallbacks = false; 809 mCanOpenWindows = false; 810 mEventSender.resetMouse(); 811 mEventSender.clearTouchPoints(); 812 mEventSender.clearTouchMetaState(); 813 mPageFinished = false; 814 mDumpWebKitData = false; 815 mGetDrawtime = false; 816 mSaveImagePath = null; 817 setDefaultWebSettings(mWebView); 818 mIsGeolocationPermissionSet = false; 819 mPendingGeolocationPermissionCallbacks = null; 820 } 821 822 private long[] getDrawWebViewTime(WebView view, int count) { 823 if (count == 0) 824 return null; 825 long[] ret = new long[count]; 826 long start; 827 Canvas canvas = new Canvas(); 828 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888); 829 canvas.setBitmap(bitmap); 830 for (int i = 0; i < count; i++) { 831 start = System.currentTimeMillis(); 832 view.draw(canvas); 833 ret[i] = System.currentTimeMillis() - start; 834 } 835 return ret; 836 } 837 838 private void drawPageToFile(String fileName, WebView view) { 839 Canvas canvas = new Canvas(); 840 Bitmap bitmap = Bitmap.createBitmap(view.getContentWidth(), view.getContentHeight(), 841 Config.ARGB_8888); 842 canvas.setBitmap(bitmap); 843 view.drawPage(canvas); 844 try { 845 FileOutputStream fos = new FileOutputStream(fileName); 846 if(!bitmap.compress(CompressFormat.PNG, 90, fos)) { 847 Log.w(LOGTAG, "Failed to compress and save image."); 848 } 849 } catch (IOException ioe) { 850 Log.e(LOGTAG, "", ioe); 851 } 852 bitmap.recycle(); 853 } 854 855 private boolean canMoveToNextTest() { 856 return (mDumpWebKitData && mPageFinished && !mWaitUntilDone) || mTimedOut; 857 } 858 859 private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) { 860 if (webview == null) { 861 return; 862 } 863 864 setDefaultWebSettings(webview); 865 866 webview.setWebChromeClient(mChromeClient); 867 webview.setWebViewClient(mViewClient); 868 // Setting a touch interval of -1 effectively disables the optimisation in WebView 869 // that stops repeated touch events flooding WebCore. The Event Sender only sends a 870 // single event rather than a stream of events (like what would generally happen in 871 // a real use of touch events in a WebView) and so if the WebView drops the event, 872 // the test will fail as the test expects one callback for every touch it synthesizes. 873 webview.setTouchInterval(-1); 874 } 875 876 public void setDefaultWebSettings(WebView webview) { 877 WebSettings settings = webview.getSettings(); 878 settings.setAppCacheEnabled(true); 879 settings.setAppCachePath(getApplicationContext().getCacheDir().getPath()); 880 settings.setAppCacheMaxSize(Long.MAX_VALUE); 881 settings.setJavaScriptEnabled(true); 882 settings.setJavaScriptCanOpenWindowsAutomatically(true); 883 settings.setSupportMultipleWindows(true); 884 settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); 885 settings.setDatabaseEnabled(true); 886 settings.setDatabasePath(getDir("databases",0).getAbsolutePath()); 887 settings.setDomStorageEnabled(true); 888 settings.setWorkersEnabled(false); 889 settings.setXSSAuditorEnabled(false); 890 settings.setPageCacheCapacity(0); 891 } 892 893 private WebView mWebView; 894 private WebViewEventSender mEventSender; 895 private AsyncHandler mHandler; 896 private TestShellCallback mCallback; 897 898 private CallbackProxy mCallbackProxy; 899 900 private String mTestUrl; 901 private String mResultFile; 902 private int mTimeoutInMillis; 903 private String mUiAutoTestPath; 904 private String mSaveImagePath; 905 private BufferedReader mTestListReader; 906 private boolean mGetDrawtime; 907 private int mTotalTestCount; 908 private int mCurrentTestNumber; 909 private boolean mStopOnRefError; 910 911 // States 912 private boolean mTimedOut; 913 private boolean mRequestedWebKitData; 914 private boolean mFinishedRunning; 915 916 // Layout test controller variables. 917 private DumpDataType mDumpDataType; 918 private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR; 919 private boolean mDumpTopFrameAsText; 920 private boolean mDumpChildFramesAsText; 921 private boolean mWaitUntilDone; 922 private boolean mDumpTitleChanges; 923 private StringBuffer mTitleChanges; 924 private StringBuffer mDialogStrings; 925 private boolean mKeepWebHistory; 926 private Vector mWebHistory; 927 private boolean mDumpDatabaseCallbacks; 928 private StringBuffer mDatabaseCallbackStrings; 929 private StringBuffer mConsoleMessages; 930 private boolean mCanOpenWindows; 931 932 private boolean mPageFinished = false; 933 private boolean mDumpWebKitData = false; 934 935 static final String TIMEOUT_STR = "**Test timeout"; 936 937 static final int MSG_TIMEOUT = 0; 938 static final int MSG_WEBKIT_DATA = 1; 939 940 static final String LOGTAG="TestShell"; 941 942 static final String TEST_URL = "TestUrl"; 943 static final String RESULT_FILE = "ResultFile"; 944 static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis"; 945 static final String UI_AUTO_TEST = "UiAutoTest"; 946 static final String GET_DRAW_TIME = "GetDrawTime"; 947 static final String SAVE_IMAGE = "SaveImage"; 948 static final String TOTAL_TEST_COUNT = "TestCount"; 949 static final String CURRENT_TEST_NUMBER = "TestNumber"; 950 static final String STOP_ON_REF_ERROR = "StopOnReferenceError"; 951 952 static final int DRAW_RUNS = 5; 953 static final String DRAW_TIME_LOG = Environment.getExternalStorageDirectory() + 954 "/android/page_draw_time.txt"; 955 956 private boolean mIsGeolocationPermissionSet; 957 private boolean mGeolocationPermission; 958 private Map mPendingGeolocationPermissionCallbacks; 959} 960