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