WebViewCore.java revision 3141e0a62abe57e93e5d716895a2a57cc052bb50
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 android.webkit; 18 19import android.content.Context; 20import android.content.Intent; 21import android.content.pm.PackageManager.NameNotFoundException; 22import android.database.Cursor; 23import android.graphics.Canvas; 24import android.graphics.DrawFilter; 25import android.graphics.Paint; 26import android.graphics.PaintFlagsDrawFilter; 27import android.graphics.Picture; 28import android.graphics.Point; 29import android.graphics.Rect; 30import android.graphics.Region; 31import android.net.Uri; 32import android.os.Handler; 33import android.os.Looper; 34import android.os.Message; 35import android.os.Process; 36import android.provider.Browser; 37import android.provider.OpenableColumns; 38import android.util.Log; 39import android.util.SparseBooleanArray; 40import android.view.KeyEvent; 41import android.view.SurfaceHolder; 42import android.view.SurfaceView; 43import android.view.View; 44 45import java.util.ArrayList; 46import java.util.Collection; 47import java.util.Map; 48import java.util.Set; 49 50import junit.framework.Assert; 51 52final class WebViewCore { 53 54 private static final String LOGTAG = "webcore"; 55 56 static { 57 // Load libwebcore during static initialization. This happens in the 58 // zygote process so it will be shared read-only across all app 59 // processes. 60 System.loadLibrary("webcore"); 61 } 62 63 /* 64 * WebViewCore always executes in the same thread as the native webkit. 65 */ 66 67 // The WebView that corresponds to this WebViewCore. 68 private WebView mWebView; 69 // Proxy for handling callbacks from native code 70 private final CallbackProxy mCallbackProxy; 71 // Settings object for maintaining all settings 72 private final WebSettings mSettings; 73 // Context for initializing the BrowserFrame with the proper assets. 74 private final Context mContext; 75 // The pointer to a native view object. 76 private int mNativeClass; 77 // The BrowserFrame is an interface to the native Frame component. 78 private BrowserFrame mBrowserFrame; 79 // Custom JS interfaces to add during the initialization. 80 private Map<String, Object> mJavascriptInterfaces; 81 /* 82 * range is from 200 to 10,000. 0 is a special value means device-width. -1 83 * means undefined. 84 */ 85 private int mViewportWidth = -1; 86 87 /* 88 * range is from 200 to 10,000. 0 is a special value means device-height. -1 89 * means undefined. 90 */ 91 private int mViewportHeight = -1; 92 93 /* 94 * scale in percent, range is from 1 to 1000. 0 means undefined. 95 */ 96 private int mViewportInitialScale = 0; 97 98 /* 99 * scale in percent, range is from 1 to 1000. 0 means undefined. 100 */ 101 private int mViewportMinimumScale = 0; 102 103 /* 104 * scale in percent, range is from 1 to 1000. 0 means undefined. 105 */ 106 private int mViewportMaximumScale = 0; 107 108 private boolean mViewportUserScalable = true; 109 110 /* 111 * range is from 70 to 400. 112 * 0 is a special value means device-dpi. The default scale factor will be 113 * always 100. 114 * -1 means undefined. The default scale factor will be 115 * WebView.DEFAULT_SCALE_PERCENT. 116 */ 117 private int mViewportDensityDpi = -1; 118 119 private int mRestoredScale = 0; 120 private int mRestoredScreenWidthScale = 0; 121 private int mRestoredX = 0; 122 private int mRestoredY = 0; 123 124 private int mWebkitScrollX = 0; 125 private int mWebkitScrollY = 0; 126 127 // The thread name used to identify the WebCore thread and for use in 128 // debugging other classes that require operation within the WebCore thread. 129 /* package */ static final String THREAD_NAME = "WebViewCoreThread"; 130 131 public WebViewCore(Context context, WebView w, CallbackProxy proxy, 132 Map<String, Object> javascriptInterfaces) { 133 // No need to assign this in the WebCore thread. 134 mCallbackProxy = proxy; 135 mWebView = w; 136 mJavascriptInterfaces = javascriptInterfaces; 137 // This context object is used to initialize the WebViewCore during 138 // subwindow creation. 139 mContext = context; 140 141 // We need to wait for the initial thread creation before sending 142 // a message to the WebCore thread. 143 // XXX: This is the only time the UI thread will wait for the WebCore 144 // thread! 145 synchronized (WebViewCore.class) { 146 if (sWebCoreHandler == null) { 147 // Create a global thread and start it. 148 Thread t = new Thread(new WebCoreThread()); 149 t.setName(THREAD_NAME); 150 t.start(); 151 try { 152 WebViewCore.class.wait(); 153 } catch (InterruptedException e) { 154 Log.e(LOGTAG, "Caught exception while waiting for thread " + 155 "creation."); 156 Log.e(LOGTAG, Log.getStackTraceString(e)); 157 } 158 } 159 } 160 // Create an EventHub to handle messages before and after the thread is 161 // ready. 162 mEventHub = new EventHub(); 163 // Create a WebSettings object for maintaining all settings 164 mSettings = new WebSettings(mContext, mWebView); 165 // The WebIconDatabase needs to be initialized within the UI thread so 166 // just request the instance here. 167 WebIconDatabase.getInstance(); 168 // Create the WebStorage singleton and the UI handler 169 WebStorage.getInstance().createUIHandler(); 170 // Create the UI handler for GeolocationPermissions 171 GeolocationPermissions.getInstance().createUIHandler(); 172 // Send a message to initialize the WebViewCore. 173 Message init = sWebCoreHandler.obtainMessage( 174 WebCoreThread.INITIALIZE, this); 175 sWebCoreHandler.sendMessage(init); 176 } 177 178 /* Initialize private data within the WebCore thread. 179 */ 180 private void initialize() { 181 /* Initialize our private BrowserFrame class to handle all 182 * frame-related functions. We need to create a new view which 183 * in turn creates a C level FrameView and attaches it to the frame. 184 */ 185 mBrowserFrame = new BrowserFrame(mContext, this, mCallbackProxy, 186 mSettings, mJavascriptInterfaces); 187 mJavascriptInterfaces = null; 188 // Sync the native settings and also create the WebCore thread handler. 189 mSettings.syncSettingsAndCreateHandler(mBrowserFrame); 190 // Create the handler and transfer messages for the IconDatabase 191 WebIconDatabase.getInstance().createHandler(); 192 // Create the handler for WebStorage 193 WebStorage.getInstance().createHandler(); 194 // Create the handler for GeolocationPermissions. 195 GeolocationPermissions.getInstance().createHandler(); 196 // The transferMessages call will transfer all pending messages to the 197 // WebCore thread handler. 198 mEventHub.transferMessages(); 199 200 // Send a message back to WebView to tell it that we have set up the 201 // WebCore thread. 202 if (mWebView != null) { 203 Message.obtain(mWebView.mPrivateHandler, 204 WebView.WEBCORE_INITIALIZED_MSG_ID, 205 mNativeClass, 0).sendToTarget(); 206 } 207 208 } 209 210 /* Handle the initialization of WebViewCore during subwindow creation. This 211 * method is called from the WebCore thread but it is called before the 212 * INITIALIZE message can be handled. 213 */ 214 /* package */ void initializeSubwindow() { 215 // Go ahead and initialize the core components. 216 initialize(); 217 // Remove the INITIALIZE method so we don't try to initialize twice. 218 sWebCoreHandler.removeMessages(WebCoreThread.INITIALIZE, this); 219 } 220 221 /* Get the BrowserFrame component. This is used for subwindow creation and 222 * is called only from BrowserFrame in the WebCore thread. */ 223 /* package */ BrowserFrame getBrowserFrame() { 224 return mBrowserFrame; 225 } 226 227 //------------------------------------------------------------------------- 228 // Common methods 229 //------------------------------------------------------------------------- 230 231 /** 232 * Causes all timers to pause. This applies to all WebViews in the current 233 * app process. 234 */ 235 public static void pauseTimers() { 236 if (BrowserFrame.sJavaBridge == null) { 237 throw new IllegalStateException( 238 "No WebView has been created in this process!"); 239 } 240 BrowserFrame.sJavaBridge.pause(); 241 } 242 243 /** 244 * Resume all timers. This applies to all WebViews in the current process. 245 */ 246 public static void resumeTimers() { 247 if (BrowserFrame.sJavaBridge == null) { 248 throw new IllegalStateException( 249 "No WebView has been created in this process!"); 250 } 251 BrowserFrame.sJavaBridge.resume(); 252 } 253 254 public WebSettings getSettings() { 255 return mSettings; 256 } 257 258 /** 259 * Add an error message to the client's console. 260 * @param message The message to add 261 * @param lineNumber the line on which the error occurred 262 * @param sourceID the filename of the source that caused the error. 263 * @param msgLevel the log level of this message. This is a value casted to int 264 * from WebCore::MessageLevel in WebCore/page/Console.h. 265 */ 266 protected void addMessageToConsole(String message, int lineNumber, String sourceID, 267 int msgLevel) { 268 mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID, msgLevel); 269 } 270 271 /** 272 * Invoke a javascript alert. 273 * @param message The message displayed in the alert. 274 */ 275 protected void jsAlert(String url, String message) { 276 mCallbackProxy.onJsAlert(url, message); 277 } 278 279 280 /** 281 * Called by JNI. Open a file chooser to upload a file. 282 * @return String version of the URI plus the name of the file. 283 * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call 284 * into Java to get the filename. 285 */ 286 private String openFileChooser() { 287 Uri uri = mCallbackProxy.openFileChooser(); 288 if (uri == null) return ""; 289 // Find out the name, and append it to the URI. 290 // Webkit will treat the name as the filename, and 291 // the URI as the path. The URI will be used 292 // in BrowserFrame to get the actual data. 293 Cursor cursor = mContext.getContentResolver().query( 294 uri, 295 new String[] { OpenableColumns.DISPLAY_NAME }, 296 null, 297 null, 298 null); 299 String name = ""; 300 if (cursor != null) { 301 try { 302 if (cursor.moveToNext()) { 303 name = cursor.getString(0); 304 } 305 } finally { 306 cursor.close(); 307 } 308 } 309 return uri.toString() + "/" + name; 310 } 311 312 /** 313 * Notify the browser that the origin has exceeded it's database quota. 314 * @param url The URL that caused the overflow. 315 * @param databaseIdentifier The identifier of the database. 316 * @param currentQuota The current quota for the origin. 317 * @param estimatedSize The estimated size of the database. 318 */ 319 protected void exceededDatabaseQuota(String url, 320 String databaseIdentifier, 321 long currentQuota, 322 long estimatedSize) { 323 // Inform the callback proxy of the quota overflow. Send an object 324 // that encapsulates a call to the nativeSetDatabaseQuota method to 325 // awaken the sleeping webcore thread when a decision from the 326 // client to allow or deny quota is available. 327 mCallbackProxy.onExceededDatabaseQuota(url, databaseIdentifier, 328 currentQuota, estimatedSize, getUsedQuota(), 329 new WebStorage.QuotaUpdater() { 330 public void updateQuota(long quota) { 331 nativeSetNewStorageLimit(quota); 332 } 333 }); 334 } 335 336 /** 337 * Notify the browser that the appcache has exceeded its max size. 338 * @param spaceNeeded is the amount of disk space that would be needed 339 * in order for the last appcache operation to succeed. 340 */ 341 protected void reachedMaxAppCacheSize(long spaceNeeded) { 342 mCallbackProxy.onReachedMaxAppCacheSize(spaceNeeded, getUsedQuota(), 343 new WebStorage.QuotaUpdater() { 344 public void updateQuota(long quota) { 345 nativeSetNewStorageLimit(quota); 346 } 347 }); 348 } 349 350 protected void populateVisitedLinks() { 351 ValueCallback callback = new ValueCallback<String[]>() { 352 public void onReceiveValue(String[] value) { 353 sendMessage(EventHub.POPULATE_VISITED_LINKS, (Object)value); 354 } 355 }; 356 mCallbackProxy.getVisitedHistory(callback); 357 } 358 359 /** 360 * Shows a prompt to ask the user to set the Geolocation permission state 361 * for the given origin. 362 * @param origin The origin for which Geolocation permissions are 363 * requested. 364 */ 365 protected void geolocationPermissionsShowPrompt(String origin) { 366 mCallbackProxy.onGeolocationPermissionsShowPrompt(origin, 367 new GeolocationPermissions.Callback() { 368 public void invoke(String origin, boolean allow, boolean remember) { 369 GeolocationPermissionsData data = new GeolocationPermissionsData(); 370 data.mOrigin = origin; 371 data.mAllow = allow; 372 data.mRemember = remember; 373 // Marshall to WebCore thread. 374 sendMessage(EventHub.GEOLOCATION_PERMISSIONS_PROVIDE, data); 375 } 376 }); 377 } 378 379 /** 380 * Hides the Geolocation permissions prompt. 381 */ 382 protected void geolocationPermissionsHidePrompt() { 383 mCallbackProxy.onGeolocationPermissionsHidePrompt(); 384 } 385 386 /** 387 * Invoke a javascript confirm dialog. 388 * @param message The message displayed in the dialog. 389 * @return True if the user confirmed or false if the user cancelled. 390 */ 391 protected boolean jsConfirm(String url, String message) { 392 return mCallbackProxy.onJsConfirm(url, message); 393 } 394 395 /** 396 * Invoke a javascript prompt dialog. 397 * @param message The message to be displayed in the dialog. 398 * @param defaultValue The default value in the prompt input. 399 * @return The input from the user or null to indicate the user cancelled 400 * the dialog. 401 */ 402 protected String jsPrompt(String url, String message, String defaultValue) { 403 return mCallbackProxy.onJsPrompt(url, message, defaultValue); 404 } 405 406 /** 407 * Invoke a javascript before unload dialog. 408 * @param url The url that is requesting the dialog. 409 * @param message The message displayed in the dialog. 410 * @return True if the user confirmed or false if the user cancelled. False 411 * will cancel the navigation. 412 */ 413 protected boolean jsUnload(String url, String message) { 414 return mCallbackProxy.onJsBeforeUnload(url, message); 415 } 416 417 /** 418 * 419 * Callback to notify that a JavaScript execution timeout has occured. 420 * @return True if the JavaScript execution should be interrupted. False 421 * will continue the execution. 422 */ 423 protected boolean jsInterrupt() { 424 return mCallbackProxy.onJsTimeout(); 425 } 426 427 //------------------------------------------------------------------------- 428 // JNI methods 429 //------------------------------------------------------------------------- 430 431 static native String nativeFindAddress(String addr, boolean caseInsensitive); 432 433 /** 434 * Empty the picture set. 435 */ 436 private native void nativeClearContent(); 437 438 /** 439 * Create a flat picture from the set of pictures. 440 */ 441 private native void nativeCopyContentToPicture(Picture picture); 442 443 /** 444 * Draw the picture set with a background color. Returns true 445 * if some individual picture took too long to draw and can be 446 * split into parts. Called from the UI thread. 447 */ 448 private native boolean nativeDrawContent(Canvas canvas, int color); 449 450 /** 451 * check to see if picture is blank and in progress 452 */ 453 private native boolean nativePictureReady(); 454 455 /** 456 * Redraw a portion of the picture set. The Point wh returns the 457 * width and height of the overall picture. 458 */ 459 private native boolean nativeRecordContent(Region invalRegion, Point wh); 460 461 private native boolean nativeFocusBoundsChanged(); 462 463 /** 464 * Splits slow parts of the picture set. Called from the webkit 465 * thread after nativeDrawContent returns true. 466 */ 467 private native void nativeSplitContent(); 468 469 private native boolean nativeKey(int keyCode, int unichar, 470 int repeatCount, boolean isShift, boolean isAlt, boolean isSym, 471 boolean isDown); 472 473 private native void nativeClick(int framePtr, int nodePtr); 474 475 private native void nativeSendListBoxChoices(boolean[] choices, int size); 476 477 private native void nativeSendListBoxChoice(int choice); 478 479 /* Tell webkit what its width and height are, for the purposes 480 of layout/line-breaking. These coordinates are in document space, 481 which is the same as View coords unless we have zoomed the document 482 (see nativeSetZoom). 483 screenWidth is used by layout to wrap column around. If viewport uses 484 fixed size, screenWidth can be different from width with zooming. 485 should this be called nativeSetViewPortSize? 486 */ 487 private native void nativeSetSize(int width, int height, int screenWidth, 488 float scale, int realScreenWidth, int screenHeight, int anchorX, 489 int anchorY, boolean ignoreHeight); 490 491 private native int nativeGetContentMinPrefWidth(); 492 493 // Start: functions that deal with text editing 494 private native void nativeReplaceTextfieldText( 495 int oldStart, int oldEnd, String replace, int newStart, int newEnd, 496 int textGeneration); 497 498 private native void passToJs(int gen, 499 String currentText, int keyCode, int keyValue, boolean down, 500 boolean cap, boolean fn, boolean sym); 501 502 private native void nativeSetFocusControllerActive(boolean active); 503 504 private native void nativeSaveDocumentState(int frame); 505 506 private native void nativeMoveFocus(int framePtr, int nodePointer); 507 private native void nativeMoveMouse(int framePtr, int x, int y); 508 509 private native void nativeMoveMouseIfLatest(int moveGeneration, 510 int framePtr, int x, int y); 511 512 private native String nativeRetrieveHref(int framePtr, int nodePtr); 513 private native String nativeRetrieveAnchorText(int framePtr, int nodePtr); 514 515 private native void nativeTouchUp(int touchGeneration, 516 int framePtr, int nodePtr, int x, int y); 517 518 private native int nativeHandleTouchEvent(int action, int x, int y, long time); 519 520 private native void nativeUpdateFrameCache(); 521 522 private native void nativeSetBackgroundColor(int color); 523 524 private native void nativeDumpDomTree(boolean useFile); 525 526 private native void nativeDumpRenderTree(boolean useFile); 527 528 private native void nativeDumpNavTree(); 529 530 private native void nativeDumpV8Counters(); 531 532 private native void nativeSetJsFlags(String flags); 533 534 /** 535 * Delete text from start to end in the focused textfield. If there is no 536 * focus, or if start == end, silently fail. If start and end are out of 537 * order, swap them. 538 * @param start Beginning of selection to delete. 539 * @param end End of selection to delete. 540 * @param textGeneration Text generation number when delete was pressed. 541 */ 542 private native void nativeDeleteSelection(int start, int end, 543 int textGeneration); 544 545 /** 546 * Set the selection to (start, end) in the focused textfield. If start and 547 * end are out of order, swap them. 548 * @param start Beginning of selection. 549 * @param end End of selection. 550 */ 551 private native void nativeSetSelection(int start, int end); 552 553 // Register a scheme to be treated as local scheme so that it can access 554 // local asset files for resources 555 private native void nativeRegisterURLSchemeAsLocal(String scheme); 556 557 /* 558 * Inform webcore that the user has decided whether to allow or deny new 559 * quota for the current origin or more space for the app cache, and that 560 * the main thread should wake up now. 561 * @param limit Is the new quota for an origin or new app cache max size. 562 */ 563 private native void nativeSetNewStorageLimit(long limit); 564 565 /** 566 * Provide WebCore with a Geolocation permission state for the specified 567 * origin. 568 * @param origin The origin for which Geolocation permissions are provided. 569 * @param allow Whether Geolocation permissions are allowed. 570 * @param remember Whether this decision should be remembered beyond the 571 * life of the current page. 572 */ 573 private native void nativeGeolocationPermissionsProvide(String origin, boolean allow, boolean remember); 574 575 /** 576 * Provide WebCore with the previously visted links from the history database 577 */ 578 private native void nativeProvideVisitedHistory(String[] history); 579 580 // EventHub for processing messages 581 private final EventHub mEventHub; 582 // WebCore thread handler 583 private static Handler sWebCoreHandler; 584 // Class for providing Handler creation inside the WebCore thread. 585 private static class WebCoreThread implements Runnable { 586 // Message id for initializing a new WebViewCore. 587 private static final int INITIALIZE = 0; 588 private static final int REDUCE_PRIORITY = 1; 589 private static final int RESUME_PRIORITY = 2; 590 private static final int CACHE_TICKER = 3; 591 private static final int BLOCK_CACHE_TICKER = 4; 592 private static final int RESUME_CACHE_TICKER = 5; 593 594 private static final int CACHE_TICKER_INTERVAL = 60 * 1000; // 1 minute 595 596 private static boolean mCacheTickersBlocked = true; 597 598 public void run() { 599 Looper.prepare(); 600 Assert.assertNull(sWebCoreHandler); 601 synchronized (WebViewCore.class) { 602 sWebCoreHandler = new Handler() { 603 @Override 604 public void handleMessage(Message msg) { 605 switch (msg.what) { 606 case INITIALIZE: 607 WebViewCore core = (WebViewCore) msg.obj; 608 core.initialize(); 609 break; 610 611 case REDUCE_PRIORITY: 612 // 3 is an adjustable number. 613 Process.setThreadPriority( 614 Process.THREAD_PRIORITY_DEFAULT + 3 * 615 Process.THREAD_PRIORITY_LESS_FAVORABLE); 616 break; 617 618 case RESUME_PRIORITY: 619 Process.setThreadPriority( 620 Process.THREAD_PRIORITY_DEFAULT); 621 break; 622 623 case CACHE_TICKER: 624 if (!mCacheTickersBlocked) { 625 CacheManager.endCacheTransaction(); 626 CacheManager.startCacheTransaction(); 627 sendMessageDelayed( 628 obtainMessage(CACHE_TICKER), 629 CACHE_TICKER_INTERVAL); 630 } 631 break; 632 633 case BLOCK_CACHE_TICKER: 634 if (CacheManager.endCacheTransaction()) { 635 mCacheTickersBlocked = true; 636 } 637 break; 638 639 case RESUME_CACHE_TICKER: 640 if (CacheManager.startCacheTransaction()) { 641 mCacheTickersBlocked = false; 642 } 643 break; 644 } 645 } 646 }; 647 WebViewCore.class.notify(); 648 } 649 Looper.loop(); 650 } 651 } 652 653 static class BaseUrlData { 654 String mBaseUrl; 655 String mData; 656 String mMimeType; 657 String mEncoding; 658 String mFailUrl; 659 } 660 661 static class CursorData { 662 CursorData() {} 663 CursorData(int frame, int node, int x, int y) { 664 mFrame = frame; 665 mNode = node; 666 mX = x; 667 mY = y; 668 } 669 int mMoveGeneration; 670 int mFrame; 671 int mNode; 672 int mX; 673 int mY; 674 } 675 676 static class JSInterfaceData { 677 Object mObject; 678 String mInterfaceName; 679 } 680 681 static class JSKeyData { 682 String mCurrentText; 683 KeyEvent mEvent; 684 } 685 686 static class MotionUpData { 687 int mFrame; 688 int mNode; 689 Rect mBounds; 690 int mX; 691 int mY; 692 } 693 694 static class GetUrlData { 695 String mUrl; 696 Map<String, String> mExtraHeaders; 697 } 698 699 static class PostUrlData { 700 String mUrl; 701 byte[] mPostData; 702 } 703 704 static class ReplaceTextData { 705 String mReplace; 706 int mNewStart; 707 int mNewEnd; 708 int mTextGeneration; 709 } 710 711 static class TextSelectionData { 712 public TextSelectionData(int start, int end) { 713 mStart = start; 714 mEnd = end; 715 } 716 int mStart; 717 int mEnd; 718 } 719 720 static class TouchUpData { 721 int mMoveGeneration; 722 int mFrame; 723 int mNode; 724 int mX; 725 int mY; 726 } 727 728 // mAction of TouchEventData can be MotionEvent.getAction() which uses the 729 // last two bytes or one of the following values 730 static final int ACTION_LONGPRESS = 0x100; 731 static final int ACTION_DOUBLETAP = 0x200; 732 733 static class TouchEventData { 734 int mAction; 735 int mX; 736 int mY; 737 long mEventTime; 738 } 739 740 static class GeolocationPermissionsData { 741 String mOrigin; 742 boolean mAllow; 743 boolean mRemember; 744 } 745 746 static class PluginFullScreenData { 747 View mView; 748 int mNpp; 749 int mDocX; 750 int mDocY; 751 int mDocWidth; 752 int mDocHeight; 753 } 754 755 static final String[] HandlerDebugString = { 756 "REQUEST_LABEL", // 97 757 "UPDATE_FRAME_CACHE_IF_LOADING", // = 98 758 "SCROLL_TEXT_INPUT", // = 99 759 "LOAD_URL", // = 100; 760 "STOP_LOADING", // = 101; 761 "RELOAD", // = 102; 762 "KEY_DOWN", // = 103; 763 "KEY_UP", // = 104; 764 "VIEW_SIZE_CHANGED", // = 105; 765 "GO_BACK_FORWARD", // = 106; 766 "SET_SCROLL_OFFSET", // = 107; 767 "RESTORE_STATE", // = 108; 768 "PAUSE_TIMERS", // = 109; 769 "RESUME_TIMERS", // = 110; 770 "CLEAR_CACHE", // = 111; 771 "CLEAR_HISTORY", // = 112; 772 "SET_SELECTION", // = 113; 773 "REPLACE_TEXT", // = 114; 774 "PASS_TO_JS", // = 115; 775 "SET_GLOBAL_BOUNDS", // = 116; 776 "UPDATE_CACHE_AND_TEXT_ENTRY", // = 117; 777 "CLICK", // = 118; 778 "SET_NETWORK_STATE", // = 119; 779 "DOC_HAS_IMAGES", // = 120; 780 "121", // = 121; 781 "DELETE_SELECTION", // = 122; 782 "LISTBOX_CHOICES", // = 123; 783 "SINGLE_LISTBOX_CHOICE", // = 124; 784 "MESSAGE_RELAY", // = 125; 785 "SET_BACKGROUND_COLOR", // = 126; 786 "SET_MOVE_FOCUS", // = 127 787 "SAVE_DOCUMENT_STATE", // = 128; 788 "129", // = 129; 789 "WEBKIT_DRAW", // = 130; 790 "SYNC_SCROLL", // = 131; 791 "POST_URL", // = 132; 792 "SPLIT_PICTURE_SET", // = 133; 793 "CLEAR_CONTENT", // = 134; 794 "SET_MOVE_MOUSE", // = 135; 795 "SET_MOVE_MOUSE_IF_LATEST", // = 136; 796 "REQUEST_CURSOR_HREF", // = 137; 797 "ADD_JS_INTERFACE", // = 138; 798 "LOAD_DATA", // = 139; 799 "TOUCH_UP", // = 140; 800 "TOUCH_EVENT", // = 141; 801 "SET_ACTIVE", // = 142; 802 "ON_PAUSE", // = 143 803 "ON_RESUME", // = 144 804 "FREE_MEMORY", // = 145 805 "VALID_NODE_BOUNDS", // = 146 806 }; 807 808 class EventHub { 809 // Message Ids 810 static final int REQUEST_LABEL = 97; 811 static final int UPDATE_FRAME_CACHE_IF_LOADING = 98; 812 static final int SCROLL_TEXT_INPUT = 99; 813 static final int LOAD_URL = 100; 814 static final int STOP_LOADING = 101; 815 static final int RELOAD = 102; 816 static final int KEY_DOWN = 103; 817 static final int KEY_UP = 104; 818 static final int VIEW_SIZE_CHANGED = 105; 819 static final int GO_BACK_FORWARD = 106; 820 static final int SET_SCROLL_OFFSET = 107; 821 static final int RESTORE_STATE = 108; 822 static final int PAUSE_TIMERS = 109; 823 static final int RESUME_TIMERS = 110; 824 static final int CLEAR_CACHE = 111; 825 static final int CLEAR_HISTORY = 112; 826 static final int SET_SELECTION = 113; 827 static final int REPLACE_TEXT = 114; 828 static final int PASS_TO_JS = 115; 829 static final int SET_GLOBAL_BOUNDS = 116; 830 static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117; 831 static final int CLICK = 118; 832 static final int SET_NETWORK_STATE = 119; 833 static final int DOC_HAS_IMAGES = 120; 834 static final int DELETE_SELECTION = 122; 835 static final int LISTBOX_CHOICES = 123; 836 static final int SINGLE_LISTBOX_CHOICE = 124; 837 static final int MESSAGE_RELAY = 125; 838 static final int SET_BACKGROUND_COLOR = 126; 839 static final int SET_MOVE_FOCUS = 127; 840 static final int SAVE_DOCUMENT_STATE = 128; 841 842 static final int WEBKIT_DRAW = 130; 843 static final int SYNC_SCROLL = 131; 844 static final int POST_URL = 132; 845 static final int SPLIT_PICTURE_SET = 133; 846 static final int CLEAR_CONTENT = 134; 847 848 // UI nav messages 849 static final int SET_MOVE_MOUSE = 135; 850 static final int SET_MOVE_MOUSE_IF_LATEST = 136; 851 static final int REQUEST_CURSOR_HREF = 137; 852 static final int ADD_JS_INTERFACE = 138; 853 static final int LOAD_DATA = 139; 854 855 // motion 856 static final int TOUCH_UP = 140; 857 // message used to pass UI touch events to WebCore 858 static final int TOUCH_EVENT = 141; 859 860 // Used to tell the focus controller not to draw the blinking cursor, 861 // based on whether the WebView has focus and whether the WebView's 862 // cursor matches the webpage's focus. 863 static final int SET_ACTIVE = 142; 864 865 // lifecycle activities for just this DOM (unlike pauseTimers, which 866 // is global) 867 static final int ON_PAUSE = 143; 868 static final int ON_RESUME = 144; 869 static final int FREE_MEMORY = 145; 870 static final int VALID_NODE_BOUNDS = 146; 871 872 // Network-based messaging 873 static final int CLEAR_SSL_PREF_TABLE = 150; 874 875 // Test harness messages 876 static final int REQUEST_EXT_REPRESENTATION = 160; 877 static final int REQUEST_DOC_AS_TEXT = 161; 878 879 // debugging 880 static final int DUMP_DOMTREE = 170; 881 static final int DUMP_RENDERTREE = 171; 882 static final int DUMP_NAVTREE = 172; 883 static final int DUMP_V8COUNTERS = 173; 884 885 static final int SET_JS_FLAGS = 174; 886 // Geolocation 887 static final int GEOLOCATION_PERMISSIONS_PROVIDE = 180; 888 889 static final int POPULATE_VISITED_LINKS = 181; 890 891 static final int HIDE_FULLSCREEN = 182; 892 893 static final int SET_NETWORK_TYPE = 183; 894 895 // private message ids 896 private static final int DESTROY = 200; 897 898 // Private handler for WebCore messages. 899 private Handler mHandler; 900 // Message queue for containing messages before the WebCore thread is 901 // ready. 902 private ArrayList<Message> mMessages = new ArrayList<Message>(); 903 // Flag for blocking messages. This is used during DESTROY to avoid 904 // posting more messages to the EventHub or to WebView's event handler. 905 private boolean mBlockMessages; 906 907 private int mTid; 908 private int mSavedPriority; 909 910 /** 911 * Prevent other classes from creating an EventHub. 912 */ 913 private EventHub() {} 914 915 /** 916 * Transfer all messages to the newly created webcore thread handler. 917 */ 918 private void transferMessages() { 919 mTid = Process.myTid(); 920 mSavedPriority = Process.getThreadPriority(mTid); 921 922 mHandler = new Handler() { 923 @Override 924 public void handleMessage(Message msg) { 925 if (DebugFlags.WEB_VIEW_CORE) { 926 Log.v(LOGTAG, (msg.what < REQUEST_LABEL 927 || msg.what 928 > VALID_NODE_BOUNDS ? Integer.toString(msg.what) 929 : HandlerDebugString[msg.what 930 - REQUEST_LABEL]) 931 + " arg1=" + msg.arg1 + " arg2=" + msg.arg2 932 + " obj=" + msg.obj); 933 } 934 switch (msg.what) { 935 case WEBKIT_DRAW: 936 webkitDraw(); 937 break; 938 939 case DESTROY: 940 // Time to take down the world. Cancel all pending 941 // loads and destroy the native view and frame. 942 synchronized (WebViewCore.this) { 943 mBrowserFrame.destroy(); 944 mBrowserFrame = null; 945 mSettings.onDestroyed(); 946 mNativeClass = 0; 947 mWebView = null; 948 } 949 break; 950 951 case REQUEST_LABEL: 952 if (mWebView != null) { 953 int nodePointer = msg.arg2; 954 String label = nativeRequestLabel(msg.arg1, 955 nodePointer); 956 if (label != null && label.length() > 0) { 957 Message.obtain(mWebView.mPrivateHandler, 958 WebView.RETURN_LABEL, nodePointer, 959 0, label).sendToTarget(); 960 } 961 } 962 break; 963 964 case UPDATE_FRAME_CACHE_IF_LOADING: 965 nativeUpdateFrameCacheIfLoading(); 966 break; 967 968 case SCROLL_TEXT_INPUT: 969 nativeScrollFocusedTextInput( 970 ((Float) msg.obj).floatValue(), msg.arg1); 971 break; 972 973 case LOAD_URL: { 974 GetUrlData param = (GetUrlData) msg.obj; 975 loadUrl(param.mUrl, param.mExtraHeaders); 976 break; 977 } 978 979 case POST_URL: { 980 PostUrlData param = (PostUrlData) msg.obj; 981 mBrowserFrame.postUrl(param.mUrl, param.mPostData); 982 break; 983 } 984 case LOAD_DATA: 985 BaseUrlData loadParams = (BaseUrlData) msg.obj; 986 String baseUrl = loadParams.mBaseUrl; 987 if (baseUrl != null) { 988 int i = baseUrl.indexOf(':'); 989 if (i > 0) { 990 /* 991 * In 1.0, {@link 992 * WebView#loadDataWithBaseURL} can access 993 * local asset files as long as the data is 994 * valid. In the new WebKit, the restriction 995 * is tightened. To be compatible with 1.0, 996 * we automatically add the scheme of the 997 * baseUrl for local access as long as it is 998 * not http(s)/ftp(s)/about/javascript 999 */ 1000 String scheme = baseUrl.substring(0, i); 1001 if (!scheme.startsWith("http") && 1002 !scheme.startsWith("ftp") && 1003 !scheme.startsWith("about") && 1004 !scheme.startsWith("javascript")) { 1005 nativeRegisterURLSchemeAsLocal(scheme); 1006 } 1007 } 1008 } 1009 mBrowserFrame.loadData(baseUrl, 1010 loadParams.mData, 1011 loadParams.mMimeType, 1012 loadParams.mEncoding, 1013 loadParams.mFailUrl); 1014 break; 1015 1016 case STOP_LOADING: 1017 // If the WebCore has committed the load, but not 1018 // finished the first layout yet, we need to set 1019 // first layout done to trigger the interpreted side sync 1020 // up with native side 1021 if (mBrowserFrame.committed() 1022 && !mBrowserFrame.firstLayoutDone()) { 1023 mBrowserFrame.didFirstLayout(); 1024 } 1025 // Do this after syncing up the layout state. 1026 stopLoading(); 1027 break; 1028 1029 case RELOAD: 1030 mBrowserFrame.reload(false); 1031 break; 1032 1033 case KEY_DOWN: 1034 key((KeyEvent) msg.obj, true); 1035 break; 1036 1037 case KEY_UP: 1038 key((KeyEvent) msg.obj, false); 1039 break; 1040 1041 case CLICK: 1042 nativeClick(msg.arg1, msg.arg2); 1043 break; 1044 1045 case VIEW_SIZE_CHANGED: { 1046 WebView.ViewSizeData data = 1047 (WebView.ViewSizeData) msg.obj; 1048 viewSizeChanged(data.mWidth, data.mHeight, 1049 data.mTextWrapWidth, data.mScale, 1050 data.mAnchorX, data.mAnchorY, 1051 data.mIgnoreHeight); 1052 break; 1053 } 1054 case SET_SCROLL_OFFSET: 1055 // note: these are in document coordinates 1056 // (inv-zoom) 1057 Point pt = (Point) msg.obj; 1058 nativeSetScrollOffset(msg.arg1, pt.x, pt.y); 1059 break; 1060 1061 case SET_GLOBAL_BOUNDS: 1062 Rect r = (Rect) msg.obj; 1063 nativeSetGlobalBounds(r.left, r.top, r.width(), 1064 r.height()); 1065 break; 1066 1067 case GO_BACK_FORWARD: 1068 // If it is a standard load and the load is not 1069 // committed yet, we interpret BACK as RELOAD 1070 if (!mBrowserFrame.committed() && msg.arg1 == -1 && 1071 (mBrowserFrame.loadType() == 1072 BrowserFrame.FRAME_LOADTYPE_STANDARD)) { 1073 mBrowserFrame.reload(true); 1074 } else { 1075 mBrowserFrame.goBackOrForward(msg.arg1); 1076 } 1077 break; 1078 1079 case RESTORE_STATE: 1080 stopLoading(); 1081 restoreState(msg.arg1); 1082 break; 1083 1084 case PAUSE_TIMERS: 1085 mSavedPriority = Process.getThreadPriority(mTid); 1086 Process.setThreadPriority(mTid, 1087 Process.THREAD_PRIORITY_BACKGROUND); 1088 pauseTimers(); 1089 if (CacheManager.disableTransaction()) { 1090 WebCoreThread.mCacheTickersBlocked = true; 1091 sWebCoreHandler.removeMessages( 1092 WebCoreThread.CACHE_TICKER); 1093 } 1094 break; 1095 1096 case RESUME_TIMERS: 1097 Process.setThreadPriority(mTid, mSavedPriority); 1098 resumeTimers(); 1099 if (CacheManager.enableTransaction()) { 1100 WebCoreThread.mCacheTickersBlocked = false; 1101 sWebCoreHandler.sendMessageDelayed( 1102 sWebCoreHandler.obtainMessage( 1103 WebCoreThread.CACHE_TICKER), 1104 WebCoreThread.CACHE_TICKER_INTERVAL); 1105 } 1106 break; 1107 1108 case ON_PAUSE: 1109 nativePause(); 1110 break; 1111 1112 case ON_RESUME: 1113 nativeResume(); 1114 break; 1115 1116 case FREE_MEMORY: 1117 clearCache(false); 1118 nativeFreeMemory(); 1119 break; 1120 1121 case SET_NETWORK_STATE: 1122 if (BrowserFrame.sJavaBridge == null) { 1123 throw new IllegalStateException("No WebView " + 1124 "has been created in this process!"); 1125 } 1126 BrowserFrame.sJavaBridge 1127 .setNetworkOnLine(msg.arg1 == 1); 1128 break; 1129 1130 case SET_NETWORK_TYPE: 1131 if (BrowserFrame.sJavaBridge == null) { 1132 throw new IllegalStateException("No WebView " + 1133 "has been created in this process!"); 1134 } 1135 Map<String, String> map = (Map<String, String>) msg.obj; 1136 BrowserFrame.sJavaBridge 1137 .setNetworkType(map.get("type"), map.get("subtype")); 1138 break; 1139 1140 case CLEAR_CACHE: 1141 clearCache(msg.arg1 == 1); 1142 break; 1143 1144 case CLEAR_HISTORY: 1145 mCallbackProxy.getBackForwardList(). 1146 close(mBrowserFrame.mNativeFrame); 1147 break; 1148 1149 case REPLACE_TEXT: 1150 ReplaceTextData rep = (ReplaceTextData) msg.obj; 1151 nativeReplaceTextfieldText(msg.arg1, msg.arg2, 1152 rep.mReplace, rep.mNewStart, rep.mNewEnd, 1153 rep.mTextGeneration); 1154 break; 1155 1156 case PASS_TO_JS: { 1157 JSKeyData jsData = (JSKeyData) msg.obj; 1158 KeyEvent evt = jsData.mEvent; 1159 int keyCode = evt.getKeyCode(); 1160 int keyValue = evt.getUnicodeChar(); 1161 int generation = msg.arg1; 1162 passToJs(generation, 1163 jsData.mCurrentText, 1164 keyCode, 1165 keyValue, 1166 evt.isDown(), 1167 evt.isShiftPressed(), evt.isAltPressed(), 1168 evt.isSymPressed()); 1169 break; 1170 } 1171 1172 case SAVE_DOCUMENT_STATE: { 1173 CursorData cDat = (CursorData) msg.obj; 1174 nativeSaveDocumentState(cDat.mFrame); 1175 break; 1176 } 1177 1178 case CLEAR_SSL_PREF_TABLE: 1179 Network.getInstance(mContext) 1180 .clearUserSslPrefTable(); 1181 break; 1182 1183 case TOUCH_UP: 1184 TouchUpData touchUpData = (TouchUpData) msg.obj; 1185 nativeTouchUp(touchUpData.mMoveGeneration, 1186 touchUpData.mFrame, touchUpData.mNode, 1187 touchUpData.mX, touchUpData.mY); 1188 break; 1189 1190 case TOUCH_EVENT: { 1191 TouchEventData ted = (TouchEventData) msg.obj; 1192 Message.obtain( 1193 mWebView.mPrivateHandler, 1194 WebView.PREVENT_TOUCH_ID, ted.mAction, 1195 nativeHandleTouchEvent(ted.mAction, ted.mX, 1196 ted.mY, ted.mEventTime)).sendToTarget(); 1197 break; 1198 } 1199 1200 case SET_ACTIVE: 1201 nativeSetFocusControllerActive(msg.arg1 == 1); 1202 break; 1203 1204 case ADD_JS_INTERFACE: 1205 JSInterfaceData jsData = (JSInterfaceData) msg.obj; 1206 mBrowserFrame.addJavascriptInterface(jsData.mObject, 1207 jsData.mInterfaceName); 1208 break; 1209 1210 case REQUEST_EXT_REPRESENTATION: 1211 mBrowserFrame.externalRepresentation( 1212 (Message) msg.obj); 1213 break; 1214 1215 case REQUEST_DOC_AS_TEXT: 1216 mBrowserFrame.documentAsText((Message) msg.obj); 1217 break; 1218 1219 case SET_MOVE_FOCUS: 1220 CursorData focusData = (CursorData) msg.obj; 1221 nativeMoveFocus(focusData.mFrame, focusData.mNode); 1222 break; 1223 1224 case SET_MOVE_MOUSE: 1225 CursorData cursorData = (CursorData) msg.obj; 1226 nativeMoveMouse(cursorData.mFrame, 1227 cursorData.mX, cursorData.mY); 1228 break; 1229 1230 case SET_MOVE_MOUSE_IF_LATEST: 1231 CursorData cData = (CursorData) msg.obj; 1232 nativeMoveMouseIfLatest(cData.mMoveGeneration, 1233 cData.mFrame, 1234 cData.mX, cData.mY); 1235 break; 1236 1237 case REQUEST_CURSOR_HREF: { 1238 Message hrefMsg = (Message) msg.obj; 1239 hrefMsg.getData().putString("url", 1240 nativeRetrieveHref(msg.arg1, msg.arg2)); 1241 hrefMsg.getData().putString("title", 1242 nativeRetrieveAnchorText(msg.arg1, msg.arg2)); 1243 hrefMsg.sendToTarget(); 1244 break; 1245 } 1246 1247 case UPDATE_CACHE_AND_TEXT_ENTRY: 1248 nativeUpdateFrameCache(); 1249 // FIXME: this should provide a minimal rectangle 1250 if (mWebView != null) { 1251 mWebView.postInvalidate(); 1252 } 1253 sendUpdateTextEntry(); 1254 break; 1255 1256 case DOC_HAS_IMAGES: 1257 Message imageResult = (Message) msg.obj; 1258 imageResult.arg1 = 1259 mBrowserFrame.documentHasImages() ? 1 : 0; 1260 imageResult.sendToTarget(); 1261 break; 1262 1263 case DELETE_SELECTION: 1264 TextSelectionData deleteSelectionData 1265 = (TextSelectionData) msg.obj; 1266 nativeDeleteSelection(deleteSelectionData.mStart, 1267 deleteSelectionData.mEnd, msg.arg1); 1268 break; 1269 1270 case SET_SELECTION: 1271 nativeSetSelection(msg.arg1, msg.arg2); 1272 break; 1273 1274 case LISTBOX_CHOICES: 1275 SparseBooleanArray choices = (SparseBooleanArray) 1276 msg.obj; 1277 int choicesSize = msg.arg1; 1278 boolean[] choicesArray = new boolean[choicesSize]; 1279 for (int c = 0; c < choicesSize; c++) { 1280 choicesArray[c] = choices.get(c); 1281 } 1282 nativeSendListBoxChoices(choicesArray, 1283 choicesSize); 1284 break; 1285 1286 case SINGLE_LISTBOX_CHOICE: 1287 nativeSendListBoxChoice(msg.arg1); 1288 break; 1289 1290 case SET_BACKGROUND_COLOR: 1291 nativeSetBackgroundColor(msg.arg1); 1292 break; 1293 1294 case DUMP_DOMTREE: 1295 nativeDumpDomTree(msg.arg1 == 1); 1296 break; 1297 1298 case DUMP_RENDERTREE: 1299 nativeDumpRenderTree(msg.arg1 == 1); 1300 break; 1301 1302 case DUMP_NAVTREE: 1303 nativeDumpNavTree(); 1304 break; 1305 1306 case DUMP_V8COUNTERS: 1307 nativeDumpV8Counters(); 1308 break; 1309 1310 case SET_JS_FLAGS: 1311 nativeSetJsFlags((String)msg.obj); 1312 break; 1313 1314 case GEOLOCATION_PERMISSIONS_PROVIDE: 1315 GeolocationPermissionsData data = 1316 (GeolocationPermissionsData) msg.obj; 1317 nativeGeolocationPermissionsProvide(data.mOrigin, 1318 data.mAllow, data.mRemember); 1319 break; 1320 1321 case SYNC_SCROLL: 1322 mWebkitScrollX = msg.arg1; 1323 mWebkitScrollY = msg.arg2; 1324 break; 1325 1326 case SPLIT_PICTURE_SET: 1327 nativeSplitContent(); 1328 mSplitPictureIsScheduled = false; 1329 break; 1330 1331 case CLEAR_CONTENT: 1332 // Clear the view so that onDraw() will draw nothing 1333 // but white background 1334 // (See public method WebView.clearView) 1335 nativeClearContent(); 1336 break; 1337 1338 case MESSAGE_RELAY: 1339 if (msg.obj instanceof Message) { 1340 ((Message) msg.obj).sendToTarget(); 1341 } 1342 break; 1343 1344 case POPULATE_VISITED_LINKS: 1345 nativeProvideVisitedHistory((String[])msg.obj); 1346 break; 1347 1348 case VALID_NODE_BOUNDS: { 1349 MotionUpData motionUpData = (MotionUpData) msg.obj; 1350 if (!nativeValidNodeAndBounds( 1351 motionUpData.mFrame, motionUpData.mNode, 1352 motionUpData.mBounds)) { 1353 nativeUpdateFrameCache(); 1354 } 1355 Message message = mWebView.mPrivateHandler 1356 .obtainMessage(WebView.DO_MOTION_UP, 1357 motionUpData.mX, motionUpData.mY); 1358 mWebView.mPrivateHandler.sendMessageAtFrontOfQueue( 1359 message); 1360 break; 1361 } 1362 1363 case HIDE_FULLSCREEN: 1364 nativeFullScreenPluginHidden(msg.arg1); 1365 break; 1366 } 1367 } 1368 }; 1369 // Take all queued messages and resend them to the new handler. 1370 synchronized (this) { 1371 int size = mMessages.size(); 1372 for (int i = 0; i < size; i++) { 1373 mHandler.sendMessage(mMessages.get(i)); 1374 } 1375 mMessages = null; 1376 } 1377 } 1378 1379 /** 1380 * Send a message internally to the queue or to the handler 1381 */ 1382 private synchronized void sendMessage(Message msg) { 1383 if (mBlockMessages) { 1384 return; 1385 } 1386 if (mMessages != null) { 1387 mMessages.add(msg); 1388 } else { 1389 mHandler.sendMessage(msg); 1390 } 1391 } 1392 1393 private synchronized void removeMessages(int what) { 1394 if (mBlockMessages) { 1395 return; 1396 } 1397 if (what == EventHub.WEBKIT_DRAW) { 1398 mDrawIsScheduled = false; 1399 } 1400 if (mMessages != null) { 1401 Log.w(LOGTAG, "Not supported in this case."); 1402 } else { 1403 mHandler.removeMessages(what); 1404 } 1405 } 1406 1407 private synchronized boolean hasMessages(int what) { 1408 if (mBlockMessages) { 1409 return false; 1410 } 1411 if (mMessages != null) { 1412 Log.w(LOGTAG, "hasMessages() is not supported in this case."); 1413 return false; 1414 } else { 1415 return mHandler.hasMessages(what); 1416 } 1417 } 1418 1419 private synchronized void sendMessageDelayed(Message msg, long delay) { 1420 if (mBlockMessages) { 1421 return; 1422 } 1423 mHandler.sendMessageDelayed(msg, delay); 1424 } 1425 1426 /** 1427 * Send a message internally to the front of the queue. 1428 */ 1429 private synchronized void sendMessageAtFrontOfQueue(Message msg) { 1430 if (mBlockMessages) { 1431 return; 1432 } 1433 if (mMessages != null) { 1434 mMessages.add(0, msg); 1435 } else { 1436 mHandler.sendMessageAtFrontOfQueue(msg); 1437 } 1438 } 1439 1440 /** 1441 * Remove all the messages. 1442 */ 1443 private synchronized void removeMessages() { 1444 // reset mDrawIsScheduled flag as WEBKIT_DRAW may be removed 1445 mDrawIsScheduled = false; 1446 mSplitPictureIsScheduled = false; 1447 if (mMessages != null) { 1448 mMessages.clear(); 1449 } else { 1450 mHandler.removeCallbacksAndMessages(null); 1451 } 1452 } 1453 1454 /** 1455 * Block sending messages to the EventHub. 1456 */ 1457 private synchronized void blockMessages() { 1458 mBlockMessages = true; 1459 } 1460 } 1461 1462 //------------------------------------------------------------------------- 1463 // Methods called by host activity (in the same thread) 1464 //------------------------------------------------------------------------- 1465 1466 void stopLoading() { 1467 if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "CORE stopLoading"); 1468 if (mBrowserFrame != null) { 1469 mBrowserFrame.stopLoading(); 1470 } 1471 } 1472 1473 //------------------------------------------------------------------------- 1474 // Methods called by WebView 1475 // If it refers to local variable, it needs synchronized(). 1476 // If it needs WebCore, it has to send message. 1477 //------------------------------------------------------------------------- 1478 1479 void sendMessage(Message msg) { 1480 mEventHub.sendMessage(msg); 1481 } 1482 1483 void sendMessage(int what) { 1484 mEventHub.sendMessage(Message.obtain(null, what)); 1485 } 1486 1487 void sendMessage(int what, Object obj) { 1488 mEventHub.sendMessage(Message.obtain(null, what, obj)); 1489 } 1490 1491 void sendMessage(int what, int arg1) { 1492 // just ignore the second argument (make it 0) 1493 mEventHub.sendMessage(Message.obtain(null, what, arg1, 0)); 1494 } 1495 1496 void sendMessage(int what, int arg1, int arg2) { 1497 mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2)); 1498 } 1499 1500 void sendMessage(int what, int arg1, Object obj) { 1501 // just ignore the second argument (make it 0) 1502 mEventHub.sendMessage(Message.obtain(null, what, arg1, 0, obj)); 1503 } 1504 1505 void sendMessage(int what, int arg1, int arg2, Object obj) { 1506 mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2, obj)); 1507 } 1508 1509 void sendMessageAtFrontOfQueue(int what, Object obj) { 1510 mEventHub.sendMessageAtFrontOfQueue(Message.obtain( 1511 null, what, obj)); 1512 } 1513 1514 void sendMessageDelayed(int what, Object obj, long delay) { 1515 mEventHub.sendMessageDelayed(Message.obtain(null, what, obj), delay); 1516 } 1517 1518 void removeMessages(int what) { 1519 mEventHub.removeMessages(what); 1520 } 1521 1522 void removeMessages() { 1523 mEventHub.removeMessages(); 1524 } 1525 1526 /** 1527 * Removes pending messages and trigger a DESTROY message to send to 1528 * WebCore. 1529 * Called from UI thread. 1530 */ 1531 void destroy() { 1532 // We don't want anyone to post a message between removing pending 1533 // messages and sending the destroy message. 1534 synchronized (mEventHub) { 1535 // RESUME_TIMERS and PAUSE_TIMERS are per process base. They need to 1536 // be preserved even the WebView is destroyed. 1537 // Note: we should not have more than one RESUME_TIMERS/PAUSE_TIMERS 1538 boolean hasResume = mEventHub.hasMessages(EventHub.RESUME_TIMERS); 1539 boolean hasPause = mEventHub.hasMessages(EventHub.PAUSE_TIMERS); 1540 mEventHub.removeMessages(); 1541 mEventHub.sendMessageAtFrontOfQueue( 1542 Message.obtain(null, EventHub.DESTROY)); 1543 if (hasPause) { 1544 mEventHub.sendMessageAtFrontOfQueue( 1545 Message.obtain(null, EventHub.PAUSE_TIMERS)); 1546 } 1547 if (hasResume) { 1548 mEventHub.sendMessageAtFrontOfQueue( 1549 Message.obtain(null, EventHub.RESUME_TIMERS)); 1550 } 1551 mEventHub.blockMessages(); 1552 } 1553 } 1554 1555 //------------------------------------------------------------------------- 1556 // WebViewCore private methods 1557 //------------------------------------------------------------------------- 1558 1559 private void clearCache(boolean includeDiskFiles) { 1560 mBrowserFrame.clearCache(); 1561 if (includeDiskFiles) { 1562 CacheManager.removeAllCacheFiles(); 1563 } 1564 } 1565 1566 private void loadUrl(String url, Map<String, String> extraHeaders) { 1567 if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, " CORE loadUrl " + url); 1568 mBrowserFrame.loadUrl(url, extraHeaders); 1569 } 1570 1571 private void key(KeyEvent evt, boolean isDown) { 1572 if (DebugFlags.WEB_VIEW_CORE) { 1573 Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " 1574 + evt); 1575 } 1576 int keyCode = evt.getKeyCode(); 1577 if (!nativeKey(keyCode, evt.getUnicodeChar(), 1578 evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), 1579 evt.isSymPressed(), 1580 isDown) && keyCode != KeyEvent.KEYCODE_ENTER) { 1581 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 1582 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 1583 if (DebugFlags.WEB_VIEW_CORE) { 1584 Log.v(LOGTAG, "key: arrow unused by plugin: " + keyCode); 1585 } 1586 if (mWebView != null && evt.isDown()) { 1587 Message.obtain(mWebView.mPrivateHandler, 1588 WebView.MOVE_OUT_OF_PLUGIN, keyCode).sendToTarget(); 1589 } 1590 return; 1591 } 1592 // bubble up the event handling 1593 // but do not bubble up the ENTER key, which would open the search 1594 // bar without any text. 1595 mCallbackProxy.onUnhandledKeyEvent(evt); 1596 } 1597 } 1598 1599 // These values are used to avoid requesting a layout based on old values 1600 private int mCurrentViewWidth = 0; 1601 private int mCurrentViewHeight = 0; 1602 private float mCurrentViewScale = 1.0f; 1603 1604 // notify webkit that our virtual view size changed size (after inv-zoom) 1605 private void viewSizeChanged(int w, int h, int textwrapWidth, float scale, 1606 int anchorX, int anchorY, boolean ignoreHeight) { 1607 if (DebugFlags.WEB_VIEW_CORE) { 1608 Log.v(LOGTAG, "viewSizeChanged w=" + w + "; h=" + h 1609 + "; textwrapWidth=" + textwrapWidth + "; scale=" + scale); 1610 } 1611 if (w == 0) { 1612 Log.w(LOGTAG, "skip viewSizeChanged as w is 0"); 1613 return; 1614 } 1615 int width = w; 1616 if (mSettings.getUseWideViewPort()) { 1617 if (mViewportWidth == -1) { 1618 if (mSettings.getLayoutAlgorithm() == 1619 WebSettings.LayoutAlgorithm.NORMAL) { 1620 width = WebView.DEFAULT_VIEWPORT_WIDTH; 1621 } else { 1622 /* 1623 * if a page's minimum preferred width is wider than the 1624 * given "w", use it instead to get better layout result. If 1625 * we start a page with MAX_ZOOM_WIDTH, "w" will be always 1626 * wider. If we start a page with screen width, due to the 1627 * delay between {@link #didFirstLayout} and 1628 * {@link #viewSizeChanged}, 1629 * {@link #nativeGetContentMinPrefWidth} will return a more 1630 * accurate value than initial 0 to result a better layout. 1631 * In the worse case, the native width will be adjusted when 1632 * next zoom or screen orientation change happens. 1633 */ 1634 width = Math.min(WebView.sMaxViewportWidth, Math.max(w, 1635 Math.max(WebView.DEFAULT_VIEWPORT_WIDTH, 1636 nativeGetContentMinPrefWidth()))); 1637 } 1638 } else if (mViewportWidth > 0) { 1639 width = Math.max(w, mViewportWidth); 1640 } else { 1641 width = textwrapWidth; 1642 } 1643 } 1644 nativeSetSize(width, width == w ? h : Math.round((float) width * h / w), 1645 textwrapWidth, scale, w, h, anchorX, anchorY, ignoreHeight); 1646 // Remember the current width and height 1647 boolean needInvalidate = (mCurrentViewWidth == 0); 1648 mCurrentViewWidth = w; 1649 mCurrentViewHeight = h; 1650 mCurrentViewScale = scale; 1651 if (needInvalidate) { 1652 // ensure {@link #webkitDraw} is called as we were blocking in 1653 // {@link #contentDraw} when mCurrentViewWidth is 0 1654 if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "viewSizeChanged"); 1655 contentDraw(); 1656 } 1657 mEventHub.sendMessage(Message.obtain(null, 1658 EventHub.UPDATE_CACHE_AND_TEXT_ENTRY)); 1659 } 1660 1661 private void sendUpdateTextEntry() { 1662 if (mWebView != null) { 1663 Message.obtain(mWebView.mPrivateHandler, 1664 WebView.UPDATE_TEXT_ENTRY_MSG_ID).sendToTarget(); 1665 } 1666 } 1667 1668 // Utility method for exceededDatabaseQuota and reachedMaxAppCacheSize 1669 // callbacks. Computes the sum of database quota for all origins. 1670 private long getUsedQuota() { 1671 WebStorage webStorage = WebStorage.getInstance(); 1672 Collection<WebStorage.Origin> origins = webStorage.getOriginsSync(); 1673 1674 if (origins == null) { 1675 return 0; 1676 } 1677 long usedQuota = 0; 1678 for (WebStorage.Origin website : origins) { 1679 usedQuota += website.getQuota(); 1680 } 1681 return usedQuota; 1682 } 1683 1684 // Used to avoid posting more than one draw message. 1685 private boolean mDrawIsScheduled; 1686 1687 // Used to avoid posting more than one split picture message. 1688 private boolean mSplitPictureIsScheduled; 1689 1690 // Used to suspend drawing. 1691 private boolean mDrawIsPaused; 1692 1693 // mRestoreState is set in didFirstLayout(), and reset in the next 1694 // webkitDraw after passing it to the UI thread. 1695 private RestoreState mRestoreState = null; 1696 1697 static class RestoreState { 1698 float mMinScale; 1699 float mMaxScale; 1700 float mViewScale; 1701 float mTextWrapScale; 1702 float mDefaultScale; 1703 int mScrollX; 1704 int mScrollY; 1705 boolean mMobileSite; 1706 } 1707 1708 static class DrawData { 1709 DrawData() { 1710 mInvalRegion = new Region(); 1711 mWidthHeight = new Point(); 1712 } 1713 Region mInvalRegion; 1714 Point mViewPoint; 1715 Point mWidthHeight; 1716 int mMinPrefWidth; 1717 RestoreState mRestoreState; // only non-null if it is for the first 1718 // picture set after the first layout 1719 boolean mFocusSizeChanged; 1720 } 1721 1722 private void webkitDraw() { 1723 mDrawIsScheduled = false; 1724 DrawData draw = new DrawData(); 1725 if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start"); 1726 if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight) 1727 == false) { 1728 if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort"); 1729 return; 1730 } 1731 if (mWebView != null) { 1732 // Send the native view size that was used during the most recent 1733 // layout. 1734 draw.mFocusSizeChanged = nativeFocusBoundsChanged(); 1735 draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight); 1736 if (mSettings.getUseWideViewPort()) { 1737 draw.mMinPrefWidth = Math.max( 1738 mViewportWidth == -1 ? WebView.DEFAULT_VIEWPORT_WIDTH 1739 : (mViewportWidth == 0 ? mCurrentViewWidth 1740 : mViewportWidth), 1741 nativeGetContentMinPrefWidth()); 1742 } 1743 if (mRestoreState != null) { 1744 draw.mRestoreState = mRestoreState; 1745 mRestoreState = null; 1746 } 1747 if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID"); 1748 Message.obtain(mWebView.mPrivateHandler, 1749 WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget(); 1750 if (mWebkitScrollX != 0 || mWebkitScrollY != 0) { 1751 // as we have the new picture, try to sync the scroll position 1752 Message.obtain(mWebView.mPrivateHandler, 1753 WebView.SYNC_SCROLL_TO_MSG_ID, mWebkitScrollX, 1754 mWebkitScrollY).sendToTarget(); 1755 mWebkitScrollX = mWebkitScrollY = 0; 1756 } 1757 } 1758 } 1759 1760 /////////////////////////////////////////////////////////////////////////// 1761 // These are called from the UI thread, not our thread 1762 1763 static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG | 1764 Paint.DITHER_FLAG | 1765 Paint.SUBPIXEL_TEXT_FLAG; 1766 static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG | 1767 Paint.DITHER_FLAG; 1768 1769 final DrawFilter mZoomFilter = 1770 new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG); 1771 final DrawFilter mScrollFilter = null; 1772 // If we need to trade more speed for less quality on slower devices 1773 // use this: new PaintFlagsDrawFilter(SCROLL_BITS, 0); 1774 1775 /* package */ void drawContentPicture(Canvas canvas, int color, 1776 boolean animatingZoom, 1777 boolean animatingScroll) { 1778 DrawFilter df = null; 1779 if (animatingZoom) { 1780 df = mZoomFilter; 1781 } else if (animatingScroll) { 1782 df = mScrollFilter; 1783 } 1784 canvas.setDrawFilter(df); 1785 boolean tookTooLong = nativeDrawContent(canvas, color); 1786 canvas.setDrawFilter(null); 1787 if (tookTooLong && mSplitPictureIsScheduled == false) { 1788 mSplitPictureIsScheduled = true; 1789 sendMessage(EventHub.SPLIT_PICTURE_SET); 1790 } 1791 } 1792 1793 /* package */ synchronized boolean pictureReady() { 1794 return 0 != mNativeClass ? nativePictureReady() : false; 1795 } 1796 1797 /*package*/ synchronized Picture copyContentPicture() { 1798 Picture result = new Picture(); 1799 if (0 != mNativeClass) { 1800 nativeCopyContentToPicture(result); 1801 } 1802 return result; 1803 } 1804 1805 static void reducePriority() { 1806 // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages 1807 sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY); 1808 sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY); 1809 sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler 1810 .obtainMessage(WebCoreThread.REDUCE_PRIORITY)); 1811 } 1812 1813 static void resumePriority() { 1814 // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages 1815 sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY); 1816 sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY); 1817 sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler 1818 .obtainMessage(WebCoreThread.RESUME_PRIORITY)); 1819 } 1820 1821 static void startCacheTransaction() { 1822 sWebCoreHandler.sendMessage(sWebCoreHandler 1823 .obtainMessage(WebCoreThread.RESUME_CACHE_TICKER)); 1824 } 1825 1826 static void endCacheTransaction() { 1827 sWebCoreHandler.sendMessage(sWebCoreHandler 1828 .obtainMessage(WebCoreThread.BLOCK_CACHE_TICKER)); 1829 } 1830 1831 static void pauseUpdatePicture(WebViewCore core) { 1832 // Note: there is one possible failure mode. If pauseUpdatePicture() is 1833 // called from UI thread while WEBKIT_DRAW is just pulled out of the 1834 // queue in WebCore thread to be executed. Then update won't be blocked. 1835 if (core != null) { 1836 synchronized (core) { 1837 core.mDrawIsPaused = true; 1838 if (core.mDrawIsScheduled) { 1839 core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW); 1840 } 1841 } 1842 } 1843 1844 } 1845 1846 static void resumeUpdatePicture(WebViewCore core) { 1847 if (core != null) { 1848 synchronized (core) { 1849 core.mDrawIsPaused = false; 1850 if (core.mDrawIsScheduled) { 1851 core.mDrawIsScheduled = false; 1852 core.contentDraw(); 1853 } 1854 } 1855 } 1856 } 1857 1858 ////////////////////////////////////////////////////////////////////////// 1859 1860 private void restoreState(int index) { 1861 WebBackForwardList list = mCallbackProxy.getBackForwardList(); 1862 int size = list.getSize(); 1863 for (int i = 0; i < size; i++) { 1864 list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame); 1865 } 1866 mBrowserFrame.mLoadInitFromJava = true; 1867 list.restoreIndex(mBrowserFrame.mNativeFrame, index); 1868 mBrowserFrame.mLoadInitFromJava = false; 1869 } 1870 1871 //------------------------------------------------------------------------- 1872 // Implement abstract methods in WebViewCore, native WebKit callback part 1873 //------------------------------------------------------------------------- 1874 1875 // called from JNI or WebView thread 1876 /* package */ void contentDraw() { 1877 // don't update the Picture until we have an initial width and finish 1878 // the first layout 1879 if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) { 1880 return; 1881 } 1882 // only fire an event if this is our first request 1883 synchronized (this) { 1884 if (mDrawIsScheduled) return; 1885 mDrawIsScheduled = true; 1886 if (mDrawIsPaused) return; 1887 mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW)); 1888 } 1889 } 1890 1891 // called by JNI 1892 private void contentScrollBy(int dx, int dy, boolean animate) { 1893 if (!mBrowserFrame.firstLayoutDone()) { 1894 // Will this happen? If yes, we need to do something here. 1895 return; 1896 } 1897 if (mWebView != null) { 1898 Message msg = Message.obtain(mWebView.mPrivateHandler, 1899 WebView.SCROLL_BY_MSG_ID, dx, dy, new Boolean(animate)); 1900 if (mDrawIsScheduled) { 1901 mEventHub.sendMessage(Message.obtain(null, 1902 EventHub.MESSAGE_RELAY, msg)); 1903 } else { 1904 msg.sendToTarget(); 1905 } 1906 } 1907 } 1908 1909 // called by JNI 1910 private void contentScrollTo(int x, int y) { 1911 if (!mBrowserFrame.firstLayoutDone()) { 1912 /* 1913 * WebKit restore state will be called before didFirstLayout(), 1914 * remember the position as it has to be applied after restoring 1915 * zoom factor which is controlled by screenWidth. 1916 */ 1917 mRestoredX = x; 1918 mRestoredY = y; 1919 return; 1920 } 1921 if (mWebView != null) { 1922 Message msg = Message.obtain(mWebView.mPrivateHandler, 1923 WebView.SCROLL_TO_MSG_ID, x, y); 1924 if (mDrawIsScheduled) { 1925 mEventHub.sendMessage(Message.obtain(null, 1926 EventHub.MESSAGE_RELAY, msg)); 1927 } else { 1928 msg.sendToTarget(); 1929 } 1930 } 1931 } 1932 1933 // called by JNI 1934 private void contentSpawnScrollTo(int x, int y) { 1935 if (!mBrowserFrame.firstLayoutDone()) { 1936 /* 1937 * WebKit restore state will be called before didFirstLayout(), 1938 * remember the position as it has to be applied after restoring 1939 * zoom factor which is controlled by screenWidth. 1940 */ 1941 mRestoredX = x; 1942 mRestoredY = y; 1943 return; 1944 } 1945 if (mWebView != null) { 1946 Message msg = Message.obtain(mWebView.mPrivateHandler, 1947 WebView.SPAWN_SCROLL_TO_MSG_ID, x, y); 1948 if (mDrawIsScheduled) { 1949 mEventHub.sendMessage(Message.obtain(null, 1950 EventHub.MESSAGE_RELAY, msg)); 1951 } else { 1952 msg.sendToTarget(); 1953 } 1954 } 1955 } 1956 1957 // called by JNI 1958 private void sendNotifyProgressFinished() { 1959 sendUpdateTextEntry(); 1960 // as CacheManager can behave based on database transaction, we need to 1961 // call tick() to trigger endTransaction 1962 sWebCoreHandler.removeMessages(WebCoreThread.CACHE_TICKER); 1963 sWebCoreHandler.sendMessage(sWebCoreHandler 1964 .obtainMessage(WebCoreThread.CACHE_TICKER)); 1965 contentDraw(); 1966 } 1967 1968 /* Called by JNI. The coordinates are in doc coordinates, so they need to 1969 be scaled before they can be used by the view system, which happens 1970 in WebView since it (and its thread) know the current scale factor. 1971 */ 1972 private void sendViewInvalidate(int left, int top, int right, int bottom) { 1973 if (mWebView != null) { 1974 Message.obtain(mWebView.mPrivateHandler, 1975 WebView.INVAL_RECT_MSG_ID, 1976 new Rect(left, top, right, bottom)).sendToTarget(); 1977 } 1978 } 1979 1980 private static boolean mRepaintScheduled = false; 1981 1982 /* 1983 * Called by the WebView thread 1984 */ 1985 /* package */ void signalRepaintDone() { 1986 mRepaintScheduled = false; 1987 } 1988 1989 // called by JNI 1990 private void sendImmediateRepaint() { 1991 if (mWebView != null && !mRepaintScheduled) { 1992 mRepaintScheduled = true; 1993 Message.obtain(mWebView.mPrivateHandler, 1994 WebView.IMMEDIATE_REPAINT_MSG_ID).sendToTarget(); 1995 } 1996 } 1997 1998 // called by JNI 1999 private void setRootLayer(int layer) { 2000 if (mWebView != null) { 2001 Message.obtain(mWebView.mPrivateHandler, 2002 WebView.SET_ROOT_LAYER_MSG_ID, 2003 layer, 0).sendToTarget(); 2004 } 2005 } 2006 2007 /* package */ WebView getWebView() { 2008 return mWebView; 2009 } 2010 2011 private native void setViewportSettingsFromNative(); 2012 2013 // called by JNI 2014 private void didFirstLayout(boolean standardLoad) { 2015 if (DebugFlags.WEB_VIEW_CORE) { 2016 Log.v(LOGTAG, "didFirstLayout standardLoad =" + standardLoad); 2017 } 2018 2019 mBrowserFrame.didFirstLayout(); 2020 2021 if (mWebView == null) return; 2022 2023 boolean updateRestoreState = standardLoad || mRestoredScale > 0; 2024 setupViewport(updateRestoreState); 2025 // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will 2026 // be called after the WebView restore the state. If updateRestoreState 2027 // is false, start to draw now as it is ready. 2028 if (!updateRestoreState) { 2029 mWebView.mViewManager.postReadyToDrawAll(); 2030 } 2031 2032 // reset the scroll position, the restored offset and scales 2033 mWebkitScrollX = mWebkitScrollY = mRestoredX = mRestoredY 2034 = mRestoredScale = mRestoredScreenWidthScale = 0; 2035 } 2036 2037 // called by JNI 2038 private void updateViewport() { 2039 // if updateViewport is called before first layout, wait until first 2040 // layout to update the viewport. In the rare case, this is called after 2041 // first layout, force an update as we have just parsed the viewport 2042 // meta tag. 2043 if (mBrowserFrame.firstLayoutDone()) { 2044 setupViewport(true); 2045 } 2046 } 2047 2048 private void setupViewport(boolean updateRestoreState) { 2049 // set the viewport settings from WebKit 2050 setViewportSettingsFromNative(); 2051 2052 // adjust the default scale to match the densityDpi 2053 float adjust = 1.0f; 2054 if (mViewportDensityDpi == -1) { 2055 if (WebView.DEFAULT_SCALE_PERCENT != 100) { 2056 adjust = WebView.DEFAULT_SCALE_PERCENT / 100.0f; 2057 } 2058 } else if (mViewportDensityDpi > 0) { 2059 adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi 2060 / mViewportDensityDpi; 2061 } 2062 int defaultScale = (int) (adjust * 100); 2063 2064 if (mViewportInitialScale > 0) { 2065 mViewportInitialScale *= adjust; 2066 } 2067 if (mViewportMinimumScale > 0) { 2068 mViewportMinimumScale *= adjust; 2069 } 2070 if (mViewportMaximumScale > 0) { 2071 mViewportMaximumScale *= adjust; 2072 } 2073 2074 // infer the values if they are not defined. 2075 if (mViewportWidth == 0) { 2076 if (mViewportInitialScale == 0) { 2077 mViewportInitialScale = defaultScale; 2078 } 2079 } 2080 if (mViewportUserScalable == false) { 2081 mViewportInitialScale = defaultScale; 2082 mViewportMinimumScale = defaultScale; 2083 mViewportMaximumScale = defaultScale; 2084 } 2085 if (mViewportMinimumScale > mViewportInitialScale 2086 && mViewportInitialScale != 0) { 2087 mViewportMinimumScale = mViewportInitialScale; 2088 } 2089 if (mViewportMaximumScale > 0 2090 && mViewportMaximumScale < mViewportInitialScale) { 2091 mViewportMaximumScale = mViewportInitialScale; 2092 } 2093 if (mViewportWidth < 0 && mViewportInitialScale == defaultScale) { 2094 mViewportWidth = 0; 2095 } 2096 2097 // if mViewportWidth is 0, it means device-width, always update. 2098 if (mViewportWidth != 0 && !updateRestoreState) { 2099 RestoreState restoreState = new RestoreState(); 2100 restoreState.mMinScale = mViewportMinimumScale / 100.0f; 2101 restoreState.mMaxScale = mViewportMaximumScale / 100.0f; 2102 restoreState.mDefaultScale = adjust; 2103 // as mViewportWidth is not 0, it is not mobile site. 2104 restoreState.mMobileSite = false; 2105 // for non-mobile site, we don't need minPrefWidth, set it as 0 2106 restoreState.mScrollX = 0; 2107 Message.obtain(mWebView.mPrivateHandler, 2108 WebView.UPDATE_ZOOM_RANGE, restoreState).sendToTarget(); 2109 return; 2110 } 2111 2112 // now notify webview 2113 // webViewWidth refers to the width in the view system 2114 int webViewWidth; 2115 // viewportWidth refers to the width in the document system 2116 int viewportWidth = mCurrentViewWidth; 2117 if (viewportWidth == 0) { 2118 // this may happen when WebView just starts. This is not perfect as 2119 // we call WebView method from WebCore thread. But not perfect 2120 // reference is better than no reference. 2121 webViewWidth = mWebView.getViewWidth(); 2122 viewportWidth = (int) (webViewWidth / adjust); 2123 if (viewportWidth == 0) { 2124 Log.w(LOGTAG, "Can't get the viewWidth after the first layout"); 2125 } 2126 } else { 2127 webViewWidth = Math.round(viewportWidth * mCurrentViewScale); 2128 } 2129 mRestoreState = new RestoreState(); 2130 mRestoreState.mMinScale = mViewportMinimumScale / 100.0f; 2131 mRestoreState.mMaxScale = mViewportMaximumScale / 100.0f; 2132 mRestoreState.mDefaultScale = adjust; 2133 mRestoreState.mScrollX = mRestoredX; 2134 mRestoreState.mScrollY = mRestoredY; 2135 mRestoreState.mMobileSite = (0 == mViewportWidth); 2136 if (mRestoredScale > 0) { 2137 mRestoreState.mViewScale = mRestoredScale / 100.0f; 2138 if (mRestoredScreenWidthScale > 0) { 2139 mRestoreState.mTextWrapScale = 2140 mRestoredScreenWidthScale / 100.0f; 2141 } else { 2142 mRestoreState.mTextWrapScale = mRestoreState.mViewScale; 2143 } 2144 } else { 2145 if (mViewportInitialScale > 0) { 2146 mRestoreState.mViewScale = mRestoreState.mTextWrapScale = 2147 mViewportInitialScale / 100.0f; 2148 } else if (mViewportWidth > 0 && mViewportWidth < webViewWidth) { 2149 mRestoreState.mViewScale = mRestoreState.mTextWrapScale = 2150 (float) webViewWidth / mViewportWidth; 2151 } else { 2152 mRestoreState.mTextWrapScale = adjust; 2153 // 0 will trigger WebView to turn on zoom overview mode 2154 mRestoreState.mViewScale = 0; 2155 } 2156 } 2157 2158 if (mWebView.mHeightCanMeasure) { 2159 // Trick to ensure that the Picture has the exact height for the 2160 // content by forcing to layout with 0 height after the page is 2161 // ready, which is indicated by didFirstLayout. This is essential to 2162 // get rid of the white space in the GMail which uses WebView for 2163 // message view. 2164 mWebView.mLastHeightSent = 0; 2165 // Send a negative scale to indicate that WebCore should reuse 2166 // the current scale 2167 WebView.ViewSizeData data = new WebView.ViewSizeData(); 2168 data.mWidth = mWebView.mLastWidthSent; 2169 data.mHeight = 0; 2170 // if mHeightCanMeasure is true, getUseWideViewPort() can't be 2171 // true. It is safe to use mWidth for mTextWrapWidth. 2172 data.mTextWrapWidth = data.mWidth; 2173 data.mScale = -1.0f; 2174 data.mIgnoreHeight = false; 2175 data.mAnchorX = data.mAnchorY = 0; 2176 // send VIEW_SIZE_CHANGED to the front of the queue so that we can 2177 // avoid pushing the wrong picture to the WebView side. If there is 2178 // a VIEW_SIZE_CHANGED in the queue, probably from WebView side, 2179 // ignore it as we have a new size. If we leave VIEW_SIZE_CHANGED 2180 // in the queue, as mLastHeightSent has been updated here, we may 2181 // miss the requestLayout in WebView side after the new picture. 2182 mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED); 2183 mEventHub.sendMessageAtFrontOfQueue(Message.obtain(null, 2184 EventHub.VIEW_SIZE_CHANGED, data)); 2185 } else if (mSettings.getUseWideViewPort()) { 2186 if (viewportWidth == 0) { 2187 // Trick to ensure VIEW_SIZE_CHANGED will be sent from WebView 2188 // to WebViewCore 2189 mWebView.mLastWidthSent = 0; 2190 } else { 2191 WebView.ViewSizeData data = new WebView.ViewSizeData(); 2192 // mViewScale as 0 means it is in zoom overview mode. So we don't 2193 // know the exact scale. If mRestoredScale is non-zero, use it; 2194 // otherwise just use mTextWrapScale as the initial scale. 2195 data.mScale = mRestoreState.mViewScale == 0 2196 ? (mRestoredScale > 0 ? mRestoredScale / 100.0f 2197 : mRestoreState.mTextWrapScale) 2198 : mRestoreState.mViewScale; 2199 if (DebugFlags.WEB_VIEW_CORE) { 2200 Log.v(LOGTAG, "setupViewport" 2201 + " mRestoredScale=" + mRestoredScale 2202 + " mViewScale=" + mRestoreState.mViewScale 2203 + " mTextWrapScale=" + mRestoreState.mTextWrapScale 2204 ); 2205 } 2206 data.mWidth = Math.round(webViewWidth / data.mScale); 2207 data.mHeight = mCurrentViewHeight * data.mWidth / viewportWidth; 2208 data.mTextWrapWidth = Math.round(webViewWidth 2209 / mRestoreState.mTextWrapScale); 2210 data.mIgnoreHeight = false; 2211 data.mAnchorX = data.mAnchorY = 0; 2212 // send VIEW_SIZE_CHANGED to the front of the queue so that we 2213 // can avoid pushing the wrong picture to the WebView side. 2214 mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED); 2215 mEventHub.sendMessageAtFrontOfQueue(Message.obtain(null, 2216 EventHub.VIEW_SIZE_CHANGED, data)); 2217 } 2218 } 2219 } 2220 2221 // called by JNI 2222 private void restoreScale(int scale) { 2223 if (mBrowserFrame.firstLayoutDone() == false) { 2224 mRestoredScale = scale; 2225 } 2226 } 2227 2228 // called by JNI 2229 private void restoreScreenWidthScale(int scale) { 2230 if (!mSettings.getUseWideViewPort()) { 2231 return; 2232 } 2233 2234 if (mBrowserFrame.firstLayoutDone() == false) { 2235 mRestoredScreenWidthScale = scale; 2236 } 2237 } 2238 2239 // called by JNI 2240 private void needTouchEvents(boolean need) { 2241 if (mWebView != null) { 2242 Message.obtain(mWebView.mPrivateHandler, 2243 WebView.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0) 2244 .sendToTarget(); 2245 } 2246 } 2247 2248 // called by JNI 2249 private void updateTextfield(int ptr, boolean changeToPassword, 2250 String text, int textGeneration) { 2251 if (mWebView != null) { 2252 Message msg = Message.obtain(mWebView.mPrivateHandler, 2253 WebView.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr, 2254 textGeneration, text); 2255 msg.getData().putBoolean("password", changeToPassword); 2256 msg.sendToTarget(); 2257 } 2258 } 2259 2260 // called by JNI 2261 private void updateTextSelection(int pointer, int start, int end, 2262 int textGeneration) { 2263 if (mWebView != null) { 2264 Message.obtain(mWebView.mPrivateHandler, 2265 WebView.UPDATE_TEXT_SELECTION_MSG_ID, pointer, textGeneration, 2266 new TextSelectionData(start, end)).sendToTarget(); 2267 } 2268 } 2269 2270 // called by JNI 2271 private void clearTextEntry() { 2272 if (mWebView == null) return; 2273 Message.obtain(mWebView.mPrivateHandler, 2274 WebView.CLEAR_TEXT_ENTRY).sendToTarget(); 2275 } 2276 2277 // called by JNI 2278 private void sendFindAgain() { 2279 if (mWebView == null) return; 2280 Message.obtain(mWebView.mPrivateHandler, 2281 WebView.FIND_AGAIN).sendToTarget(); 2282 } 2283 2284 private native void nativeUpdateFrameCacheIfLoading(); 2285 private native String nativeRequestLabel(int framePtr, int nodePtr); 2286 /** 2287 * Scroll the focused textfield to (xPercent, y) in document space 2288 */ 2289 private native void nativeScrollFocusedTextInput(float xPercent, int y); 2290 2291 // these must be in document space (i.e. not scaled/zoomed). 2292 private native void nativeSetScrollOffset(int gen, int dx, int dy); 2293 2294 private native void nativeSetGlobalBounds(int x, int y, int w, int h); 2295 2296 // called by JNI 2297 private void requestListBox(String[] array, int[] enabledArray, 2298 int[] selectedArray) { 2299 if (mWebView != null) { 2300 mWebView.requestListBox(array, enabledArray, selectedArray); 2301 } 2302 } 2303 2304 // called by JNI 2305 private void requestListBox(String[] array, int[] enabledArray, 2306 int selection) { 2307 if (mWebView != null) { 2308 mWebView.requestListBox(array, enabledArray, selection); 2309 } 2310 2311 } 2312 2313 // called by JNI 2314 private void requestKeyboard(boolean showKeyboard, boolean isTextView) { 2315 if (mWebView != null) { 2316 Message.obtain(mWebView.mPrivateHandler, 2317 WebView.REQUEST_KEYBOARD, showKeyboard ? 1 : 0, 2318 isTextView ? 1 : 0) 2319 .sendToTarget(); 2320 } 2321 } 2322 2323 // called by JNI 2324 private Context getContext() { 2325 return mContext; 2326 } 2327 2328 // called by JNI 2329 private Class<?> getPluginClass(String libName, String clsName) { 2330 2331 if (mWebView == null) { 2332 return null; 2333 } 2334 2335 PluginManager pluginManager = PluginManager.getInstance(null); 2336 2337 String pkgName = pluginManager.getPluginsAPKName(libName); 2338 if (pkgName == null) { 2339 Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK"); 2340 return null; 2341 } 2342 2343 try { 2344 return pluginManager.getPluginClass(pkgName, clsName); 2345 } catch (NameNotFoundException e) { 2346 Log.e(LOGTAG, "Unable to find plugin classloader for the apk (" + pkgName + ")"); 2347 } catch (ClassNotFoundException e) { 2348 Log.e(LOGTAG, "Unable to find plugin class (" + clsName + 2349 ") in the apk (" + pkgName + ")"); 2350 } 2351 2352 return null; 2353 } 2354 2355 // called by JNI. PluginWidget function to launch a full-screen view using a 2356 // View object provided by the plugin class. 2357 private void showFullScreenPlugin(ViewManager.ChildView childView, 2358 final int npp, int x, int y, int width, int height) { 2359 2360 if (mWebView == null) { 2361 return; 2362 } 2363 2364 PluginFullScreenData data = new PluginFullScreenData(); 2365 data.mView = childView.mView; 2366 data.mNpp = npp; 2367 data.mDocX = x; 2368 data.mDocY = y; 2369 data.mDocWidth = width; 2370 data.mDocHeight = height; 2371 mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN, data) 2372 .sendToTarget(); 2373 } 2374 2375 // called by JNI 2376 private void hideFullScreenPlugin() { 2377 if (mWebView == null) { 2378 return; 2379 } 2380 mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN) 2381 .sendToTarget(); 2382 } 2383 2384 // called by JNI 2385 private void updateFullScreenPlugin(int x, int y, int width, int height) { 2386 if (mWebView == null) { 2387 return; 2388 } 2389 2390 PluginFullScreenData data = new PluginFullScreenData(); 2391 data.mDocX = x; 2392 data.mDocY = y; 2393 data.mDocWidth = width; 2394 data.mDocHeight = height; 2395 // null mView and mNpp to indicate it is an update 2396 mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN, data) 2397 .sendToTarget(); 2398 } 2399 2400 // called by JNI. PluginWidget functions for creating an embedded View for 2401 // the surface drawing model. 2402 private ViewManager.ChildView addSurface(View pluginView, int x, int y, 2403 int width, int height) { 2404 if (mWebView == null) { 2405 return null; 2406 } 2407 2408 if (pluginView == null) { 2409 Log.e(LOGTAG, "Attempted to add an empty plugin view to the view hierarchy"); 2410 return null; 2411 } 2412 2413 // ensures the view system knows the view can redraw itself 2414 pluginView.setWillNotDraw(false); 2415 2416 ViewManager.ChildView view = mWebView.mViewManager.createView(); 2417 view.mView = pluginView; 2418 view.attachView(x, y, width, height); 2419 return view; 2420 } 2421 2422 private void updateSurface(ViewManager.ChildView childView, int x, int y, 2423 int width, int height) { 2424 childView.attachView(x, y, width, height); 2425 } 2426 2427 private void destroySurface(ViewManager.ChildView childView) { 2428 childView.removeView(); 2429 } 2430 2431 // called by JNI 2432 static class ShowRectData { 2433 int mLeft; 2434 int mTop; 2435 int mWidth; 2436 int mHeight; 2437 int mContentWidth; 2438 int mContentHeight; 2439 float mXPercentInDoc; 2440 float mXPercentInView; 2441 float mYPercentInDoc; 2442 float mYPercentInView; 2443 } 2444 2445 private void showRect(int left, int top, int width, int height, 2446 int contentWidth, int contentHeight, float xPercentInDoc, 2447 float xPercentInView, float yPercentInDoc, float yPercentInView) { 2448 if (mWebView != null) { 2449 ShowRectData data = new ShowRectData(); 2450 data.mLeft = left; 2451 data.mTop = top; 2452 data.mWidth = width; 2453 data.mHeight = height; 2454 data.mContentWidth = contentWidth; 2455 data.mContentHeight = contentHeight; 2456 data.mXPercentInDoc = xPercentInDoc; 2457 data.mXPercentInView = xPercentInView; 2458 data.mYPercentInDoc = yPercentInDoc; 2459 data.mYPercentInView = yPercentInView; 2460 Message.obtain(mWebView.mPrivateHandler, WebView.SHOW_RECT_MSG_ID, 2461 data).sendToTarget(); 2462 } 2463 } 2464 2465 private native void nativePause(); 2466 private native void nativeResume(); 2467 private native void nativeFreeMemory(); 2468 private native void nativeFullScreenPluginHidden(int npp); 2469 private native boolean nativeValidNodeAndBounds(int frame, int node, 2470 Rect bounds); 2471 2472} 2473