1 /* 2 * Copyright (C) 2006 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 android.webkit; 18 19 import android.app.ActivityManager; 20 import android.content.ComponentCallbacks; 21 import android.content.Context; 22 import android.content.res.AssetManager; 23 import android.content.res.Configuration; 24 import android.database.Cursor; 25 import android.graphics.Bitmap; 26 import android.net.ParseException; 27 import android.net.Uri; 28 import android.net.WebAddress; 29 import android.net.http.SslCertificate; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.provider.OpenableColumns; 33 import android.util.Log; 34 import android.util.TypedValue; 35 import android.view.Surface; 36 import android.view.ViewRoot; 37 import android.view.WindowManager; 38 39 import junit.framework.Assert; 40 41 import java.io.InputStream; 42 import java.lang.ref.WeakReference; 43 import java.net.URLEncoder; 44 import java.util.ArrayList; 45 import java.util.HashMap; 46 import java.util.Map; 47 import java.util.Iterator; 48 49 class BrowserFrame extends Handler { 50 51 private static final String LOGTAG = "webkit"; 52 53 /** 54 * Cap the number of LoadListeners that will be instantiated, so 55 * we don't blow the GREF count. Attempting to queue more than 56 * this many requests will prompt an error() callback on the 57 * request's LoadListener 58 */ 59 private final static int MAX_OUTSTANDING_REQUESTS = 300; 60 61 private final CallbackProxy mCallbackProxy; 62 private final WebSettings mSettings; 63 private final Context mContext; 64 private final WebViewDatabase mDatabase; 65 private final WebViewCore mWebViewCore; 66 /* package */ boolean mLoadInitFromJava; 67 private int mLoadType; 68 private boolean mFirstLayoutDone = true; 69 private boolean mCommitted = true; 70 // Flag for blocking messages. This is used during destroy() so 71 // that if the UI thread posts any messages after the message 72 // queue has been cleared,they are ignored. 73 private boolean mBlockMessages = false; 74 private int mOrientation = -1; 75 76 // Is this frame the main frame? 77 private boolean mIsMainFrame; 78 79 // Attached Javascript interfaces 80 private Map<String, Object> mJSInterfaceMap; 81 82 // message ids 83 // a message posted when a frame loading is completed 84 static final int FRAME_COMPLETED = 1001; 85 // orientation change message 86 static final int ORIENTATION_CHANGED = 1002; 87 // a message posted when the user decides the policy 88 static final int POLICY_FUNCTION = 1003; 89 90 // Note: need to keep these in sync with FrameLoaderTypes.h in native 91 static final int FRAME_LOADTYPE_STANDARD = 0; 92 static final int FRAME_LOADTYPE_BACK = 1; 93 static final int FRAME_LOADTYPE_FORWARD = 2; 94 static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3; 95 static final int FRAME_LOADTYPE_RELOAD = 4; 96 static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5; 97 static final int FRAME_LOADTYPE_SAME = 6; 98 static final int FRAME_LOADTYPE_REDIRECT = 7; 99 static final int FRAME_LOADTYPE_REPLACE = 8; 100 101 // A progress threshold to switch from history Picture to live Picture 102 private static final int TRANSITION_SWITCH_THRESHOLD = 75; 103 104 // This is a field accessed by native code as well as package classes. 105 /*package*/ int mNativeFrame; 106 107 // Static instance of a JWebCoreJavaBridge to handle timer and cookie 108 // requests from WebCore. 109 static JWebCoreJavaBridge sJavaBridge; 110 111 private static class ConfigCallback implements ComponentCallbacks { 112 private final ArrayList<WeakReference<Handler>> mHandlers = 113 new ArrayList<WeakReference<Handler>>(); 114 private final WindowManager mWindowManager; 115 116 ConfigCallback(WindowManager wm) { 117 mWindowManager = wm; 118 } 119 120 public synchronized void addHandler(Handler h) { 121 // No need to ever remove a Handler. If the BrowserFrame is 122 // destroyed, it will be collected and the WeakReference set to 123 // null. If it happens to still be around during a configuration 124 // change, the message will be ignored. 125 mHandlers.add(new WeakReference<Handler>(h)); 126 } 127 128 public void onConfigurationChanged(Configuration newConfig) { 129 if (mHandlers.size() == 0) { 130 return; 131 } 132 int orientation = 133 mWindowManager.getDefaultDisplay().getOrientation(); 134 switch (orientation) { 135 case Surface.ROTATION_90: 136 orientation = 90; 137 break; 138 case Surface.ROTATION_180: 139 orientation = 180; 140 break; 141 case Surface.ROTATION_270: 142 orientation = -90; 143 break; 144 case Surface.ROTATION_0: 145 orientation = 0; 146 break; 147 default: 148 break; 149 } 150 synchronized (this) { 151 // Create a list of handlers to remove. Go ahead and make it 152 // the same size to avoid resizing. 153 ArrayList<WeakReference> handlersToRemove = 154 new ArrayList<WeakReference>(mHandlers.size()); 155 for (WeakReference<Handler> wh : mHandlers) { 156 Handler h = wh.get(); 157 if (h != null) { 158 h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED, 159 orientation, 0)); 160 } else { 161 handlersToRemove.add(wh); 162 } 163 } 164 // Now remove all the null references. 165 for (WeakReference weak : handlersToRemove) { 166 mHandlers.remove(weak); 167 } 168 } 169 } 170 171 public void onLowMemory() {} 172 } 173 static ConfigCallback sConfigCallback; 174 175 /** 176 * Create a new BrowserFrame to be used in an application. 177 * @param context An application context to use when retrieving assets. 178 * @param w A WebViewCore used as the view for this frame. 179 * @param proxy A CallbackProxy for posting messages to the UI thread and 180 * querying a client for information. 181 * @param settings A WebSettings object that holds all settings. 182 * XXX: Called by WebCore thread. 183 */ 184 public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy, 185 WebSettings settings, Map<String, Object> javascriptInterfaces) { 186 187 Context appContext = context.getApplicationContext(); 188 189 // Create a global JWebCoreJavaBridge to handle timers and 190 // cookies in the WebCore thread. 191 if (sJavaBridge == null) { 192 sJavaBridge = new JWebCoreJavaBridge(); 193 // set WebCore native cache size 194 ActivityManager am = (ActivityManager) context 195 .getSystemService(Context.ACTIVITY_SERVICE); 196 if (am.getMemoryClass() > 16) { 197 sJavaBridge.setCacheSize(8 * 1024 * 1024); 198 } else { 199 sJavaBridge.setCacheSize(4 * 1024 * 1024); 200 } 201 // initialize CacheManager 202 CacheManager.init(appContext); 203 // create CookieSyncManager with current Context 204 CookieSyncManager.createInstance(appContext); 205 // create PluginManager with current Context 206 PluginManager.getInstance(appContext); 207 } 208 209 if (sConfigCallback == null) { 210 sConfigCallback = new ConfigCallback( 211 (WindowManager) context.getSystemService( 212 Context.WINDOW_SERVICE)); 213 ViewRoot.addConfigCallback(sConfigCallback); 214 } 215 sConfigCallback.addHandler(this); 216 217 mJSInterfaceMap = javascriptInterfaces; 218 219 mSettings = settings; 220 mContext = context; 221 mCallbackProxy = proxy; 222 mDatabase = WebViewDatabase.getInstance(appContext); 223 mWebViewCore = w; 224 225 AssetManager am = context.getAssets(); 226 nativeCreateFrame(w, am, proxy.getBackForwardList()); 227 228 if (DebugFlags.BROWSER_FRAME) { 229 Log.v(LOGTAG, "BrowserFrame constructor: this=" + this); 230 } 231 } 232 233 /** 234 * Load a url from the network or the filesystem into the main frame. 235 * Following the same behaviour as Safari, javascript: URLs are not passed 236 * to the main frame, instead they are evaluated immediately. 237 * @param url The url to load. 238 * @param extraHeaders The extra headers sent with this url. This should not 239 * include the common headers like "user-agent". If it does, it 240 * will be replaced by the intrinsic value of the WebView. 241 */ 242 public void loadUrl(String url, Map<String, String> extraHeaders) { 243 mLoadInitFromJava = true; 244 if (URLUtil.isJavaScriptUrl(url)) { 245 // strip off the scheme and evaluate the string 246 stringByEvaluatingJavaScriptFromString( 247 url.substring("javascript:".length())); 248 } else { 249 nativeLoadUrl(url, extraHeaders); 250 } 251 mLoadInitFromJava = false; 252 } 253 254 /** 255 * Load a url with "POST" method from the network into the main frame. 256 * @param url The url to load. 257 * @param data The data for POST request. 258 */ 259 public void postUrl(String url, byte[] data) { 260 mLoadInitFromJava = true; 261 nativePostUrl(url, data); 262 mLoadInitFromJava = false; 263 } 264 265 /** 266 * Load the content as if it was loaded by the provided base URL. The 267 * historyUrl is used as the history entry for the load data. 268 * 269 * @param baseUrl Base URL used to resolve relative paths in the content 270 * @param data Content to render in the browser 271 * @param mimeType Mimetype of the data being passed in 272 * @param encoding Character set encoding of the provided data. 273 * @param historyUrl URL to use as the history entry. 274 */ 275 public void loadData(String baseUrl, String data, String mimeType, 276 String encoding, String historyUrl) { 277 mLoadInitFromJava = true; 278 if (historyUrl == null || historyUrl.length() == 0) { 279 historyUrl = "about:blank"; 280 } 281 if (data == null) { 282 data = ""; 283 } 284 285 // Setup defaults for missing values. These defaults where taken from 286 // WebKit's WebFrame.mm 287 if (baseUrl == null || baseUrl.length() == 0) { 288 baseUrl = "about:blank"; 289 } 290 if (mimeType == null || mimeType.length() == 0) { 291 mimeType = "text/html"; 292 } 293 nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl); 294 mLoadInitFromJava = false; 295 } 296 297 /** 298 * Go back or forward the number of steps given. 299 * @param steps A negative or positive number indicating the direction 300 * and number of steps to move. 301 */ 302 public void goBackOrForward(int steps) { 303 mLoadInitFromJava = true; 304 nativeGoBackOrForward(steps); 305 mLoadInitFromJava = false; 306 } 307 308 /** 309 * native callback 310 * Report an error to an activity. 311 * @param errorCode The HTTP error code. 312 * @param description A String description. 313 * TODO: Report all errors including resource errors but include some kind 314 * of domain identifier. Change errorCode to an enum for a cleaner 315 * interface. 316 */ 317 private void reportError(final int errorCode, final String description, 318 final String failingUrl) { 319 // As this is called for the main resource and loading will be stopped 320 // after, reset the state variables. 321 resetLoadingStates(); 322 mCallbackProxy.onReceivedError(errorCode, description, failingUrl); 323 } 324 325 private void resetLoadingStates() { 326 mCommitted = true; 327 mFirstLayoutDone = true; 328 } 329 330 /* package */boolean committed() { 331 return mCommitted; 332 } 333 334 /* package */boolean firstLayoutDone() { 335 return mFirstLayoutDone; 336 } 337 338 /* package */int loadType() { 339 return mLoadType; 340 } 341 342 /* package */void didFirstLayout() { 343 if (!mFirstLayoutDone) { 344 mFirstLayoutDone = true; 345 // ensure {@link WebViewCore#webkitDraw} is called as we were 346 // blocking the update in {@link #loadStarted} 347 mWebViewCore.contentDraw(); 348 } 349 } 350 351 /** 352 * native callback 353 * Indicates the beginning of a new load. 354 * This method will be called once for the main frame. 355 */ 356 private void loadStarted(String url, Bitmap favicon, int loadType, 357 boolean isMainFrame) { 358 mIsMainFrame = isMainFrame; 359 360 if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) { 361 mLoadType = loadType; 362 363 if (isMainFrame) { 364 // Call onPageStarted for main frames. 365 mCallbackProxy.onPageStarted(url, favicon); 366 // as didFirstLayout() is only called for the main frame, reset 367 // mFirstLayoutDone only for the main frames 368 mFirstLayoutDone = false; 369 mCommitted = false; 370 // remove pending draw to block update until mFirstLayoutDone is 371 // set to true in didFirstLayout() 372 mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW); 373 } 374 375 // Note: only saves committed form data in standard load 376 if (loadType == FRAME_LOADTYPE_STANDARD 377 && mSettings.getSaveFormData()) { 378 final WebHistoryItem h = mCallbackProxy.getBackForwardList() 379 .getCurrentItem(); 380 if (h != null) { 381 String currentUrl = h.getUrl(); 382 if (currentUrl != null) { 383 mDatabase.setFormData(currentUrl, getFormTextData()); 384 } 385 } 386 } 387 } 388 } 389 390 /** 391 * native callback 392 * Indicates the WebKit has committed to the new load 393 */ 394 private void transitionToCommitted(int loadType, boolean isMainFrame) { 395 // loadType is not used yet 396 if (isMainFrame) { 397 mCommitted = true; 398 mWebViewCore.getWebView().mViewManager.postResetStateAll(); 399 } 400 } 401 402 /** 403 * native callback 404 * <p> 405 * Indicates the end of a new load. 406 * This method will be called once for the main frame. 407 */ 408 private void loadFinished(String url, int loadType, boolean isMainFrame) { 409 // mIsMainFrame and isMainFrame are better be equal!!! 410 411 if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) { 412 if (isMainFrame) { 413 resetLoadingStates(); 414 mCallbackProxy.switchOutDrawHistory(); 415 mCallbackProxy.onPageFinished(url); 416 } 417 } 418 } 419 420 /** 421 * We have received an SSL certificate for the main top-level page. 422 * 423 * !!!Called from the network thread!!! 424 */ 425 void certificate(SslCertificate certificate) { 426 if (mIsMainFrame) { 427 // we want to make this call even if the certificate is null 428 // (ie, the site is not secure) 429 mCallbackProxy.onReceivedCertificate(certificate); 430 } 431 } 432 433 /** 434 * Destroy all native components of the BrowserFrame. 435 */ 436 public void destroy() { 437 nativeDestroyFrame(); 438 mBlockMessages = true; 439 removeCallbacksAndMessages(null); 440 } 441 442 /** 443 * Handle messages posted to us. 444 * @param msg The message to handle. 445 */ 446 @Override 447 public void handleMessage(Message msg) { 448 if (mBlockMessages) { 449 return; 450 } 451 switch (msg.what) { 452 case FRAME_COMPLETED: { 453 if (mSettings.getSavePassword() && hasPasswordField()) { 454 WebHistoryItem item = mCallbackProxy.getBackForwardList() 455 .getCurrentItem(); 456 if (item != null) { 457 WebAddress uri = new WebAddress(item.getUrl()); 458 String schemePlusHost = uri.mScheme + uri.mHost; 459 String[] up = 460 mDatabase.getUsernamePassword(schemePlusHost); 461 if (up != null && up[0] != null) { 462 setUsernamePassword(up[0], up[1]); 463 } 464 } 465 } 466 WebViewWorker.getHandler().sendEmptyMessage( 467 WebViewWorker.MSG_TRIM_CACHE); 468 break; 469 } 470 471 case POLICY_FUNCTION: { 472 nativeCallPolicyFunction(msg.arg1, msg.arg2); 473 break; 474 } 475 476 case ORIENTATION_CHANGED: { 477 if (mOrientation != msg.arg1) { 478 mOrientation = msg.arg1; 479 nativeOrientationChanged(msg.arg1); 480 } 481 break; 482 } 483 484 default: 485 break; 486 } 487 } 488 489 /** 490 * Punch-through for WebCore to set the document 491 * title. Inform the Activity of the new title. 492 * @param title The new title of the document. 493 */ 494 private void setTitle(String title) { 495 // FIXME: The activity must call getTitle (a native method) to get the 496 // title. We should try and cache the title if we can also keep it in 497 // sync with the document. 498 mCallbackProxy.onReceivedTitle(title); 499 } 500 501 /** 502 * Retrieves the render tree of this frame and puts it as the object for 503 * the message and sends the message. 504 * @param callback the message to use to send the render tree 505 */ 506 public void externalRepresentation(Message callback) { 507 callback.obj = externalRepresentation();; 508 callback.sendToTarget(); 509 } 510 511 /** 512 * Return the render tree as a string 513 */ 514 private native String externalRepresentation(); 515 516 /** 517 * Retrieves the visual text of the current frame, puts it as the object for 518 * the message and sends the message. 519 * @param callback the message to use to send the visual text 520 */ 521 public void documentAsText(Message callback) { 522 callback.obj = documentAsText();; 523 callback.sendToTarget(); 524 } 525 526 /** 527 * Return the text drawn on the screen as a string 528 */ 529 private native String documentAsText(); 530 531 /* 532 * This method is called by WebCore to inform the frame that 533 * the Javascript window object has been cleared. 534 * We should re-attach any attached js interfaces. 535 */ 536 private void windowObjectCleared(int nativeFramePointer) { 537 if (mJSInterfaceMap != null) { 538 Iterator iter = mJSInterfaceMap.keySet().iterator(); 539 while (iter.hasNext()) { 540 String interfaceName = (String) iter.next(); 541 nativeAddJavascriptInterface(nativeFramePointer, 542 mJSInterfaceMap.get(interfaceName), interfaceName); 543 } 544 } 545 } 546 547 /** 548 * This method is called by WebCore to check whether application 549 * wants to hijack url loading 550 */ 551 public boolean handleUrl(String url) { 552 if (mLoadInitFromJava == true) { 553 return false; 554 } 555 if (mCallbackProxy.shouldOverrideUrlLoading(url)) { 556 // if the url is hijacked, reset the state of the BrowserFrame 557 didFirstLayout(); 558 return true; 559 } else { 560 return false; 561 } 562 } 563 564 public void addJavascriptInterface(Object obj, String interfaceName) { 565 if (mJSInterfaceMap == null) { 566 mJSInterfaceMap = new HashMap<String, Object>(); 567 } 568 if (mJSInterfaceMap.containsKey(interfaceName)) { 569 mJSInterfaceMap.remove(interfaceName); 570 } 571 mJSInterfaceMap.put(interfaceName, obj); 572 } 573 574 /** 575 * Called by JNI. Given a URI, find the associated file and return its size 576 * @param uri A String representing the URI of the desired file. 577 * @return int The size of the given file. 578 */ 579 private int getFileSize(String uri) { 580 int size = 0; 581 try { 582 InputStream stream = mContext.getContentResolver() 583 .openInputStream(Uri.parse(uri)); 584 size = stream.available(); 585 stream.close(); 586 } catch (Exception e) {} 587 return size; 588 } 589 590 /** 591 * Called by JNI. Given a URI, a buffer, and an offset into the buffer, 592 * copy the resource into buffer. 593 * @param uri A String representing the URI of the desired file. 594 * @param buffer The byte array to copy the data into. 595 * @param offset The offet into buffer to place the data. 596 * @param expectedSize The size that the buffer has allocated for this file. 597 * @return int The size of the given file, or zero if it fails. 598 */ 599 private int getFile(String uri, byte[] buffer, int offset, 600 int expectedSize) { 601 int size = 0; 602 try { 603 InputStream stream = mContext.getContentResolver() 604 .openInputStream(Uri.parse(uri)); 605 size = stream.available(); 606 if (size <= expectedSize && buffer != null 607 && buffer.length - offset >= size) { 608 stream.read(buffer, offset, size); 609 } else { 610 size = 0; 611 } 612 stream.close(); 613 } catch (java.io.FileNotFoundException e) { 614 Log.e(LOGTAG, "FileNotFoundException:" + e); 615 size = 0; 616 } catch (java.io.IOException e2) { 617 Log.e(LOGTAG, "IOException: " + e2); 618 size = 0; 619 } 620 return size; 621 } 622 623 /** 624 * Start loading a resource. 625 * @param loaderHandle The native ResourceLoader that is the target of the 626 * data. 627 * @param url The url to load. 628 * @param method The http method. 629 * @param headers The http headers. 630 * @param postData If the method is "POST" postData is sent as the request 631 * body. Is null when empty. 632 * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0. 633 * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode 634 * @param mainResource True if the this resource is the main request, not a supporting resource 635 * @param userGesture 636 * @param synchronous True if the load is synchronous. 637 * @return A newly created LoadListener object. 638 */ 639 private LoadListener startLoadingResource(int loaderHandle, 640 String url, 641 String method, 642 HashMap headers, 643 byte[] postData, 644 long postDataIdentifier, 645 int cacheMode, 646 boolean mainResource, 647 boolean userGesture, 648 boolean synchronous, 649 String username, 650 String password) { 651 PerfChecker checker = new PerfChecker(); 652 653 if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) { 654 cacheMode = mSettings.getCacheMode(); 655 } 656 657 if (method.equals("POST")) { 658 // Don't use the cache on POSTs when issuing a normal POST 659 // request. 660 if (cacheMode == WebSettings.LOAD_NORMAL) { 661 cacheMode = WebSettings.LOAD_NO_CACHE; 662 } 663 if (mSettings.getSavePassword() && hasPasswordField()) { 664 try { 665 if (DebugFlags.BROWSER_FRAME) { 666 Assert.assertNotNull(mCallbackProxy.getBackForwardList() 667 .getCurrentItem()); 668 } 669 WebAddress uri = new WebAddress(mCallbackProxy 670 .getBackForwardList().getCurrentItem().getUrl()); 671 String schemePlusHost = uri.mScheme + uri.mHost; 672 String[] ret = getUsernamePassword(); 673 // Has the user entered a username/password pair and is 674 // there some POST data 675 if (ret != null && postData != null && 676 ret[0].length() > 0 && ret[1].length() > 0) { 677 // Check to see if the username & password appear in 678 // the post data (there could be another form on the 679 // page and that was posted instead. 680 String postString = new String(postData); 681 if (postString.contains(URLEncoder.encode(ret[0])) && 682 postString.contains(URLEncoder.encode(ret[1]))) { 683 String[] saved = mDatabase.getUsernamePassword( 684 schemePlusHost); 685 if (saved != null) { 686 // null username implies that user has chosen not to 687 // save password 688 if (saved[0] != null) { 689 // non-null username implies that user has 690 // chosen to save password, so update the 691 // recorded password 692 mDatabase.setUsernamePassword( 693 schemePlusHost, ret[0], ret[1]); 694 } 695 } else { 696 // CallbackProxy will handle creating the resume 697 // message 698 mCallbackProxy.onSavePassword(schemePlusHost, ret[0], 699 ret[1], null); 700 } 701 } 702 } 703 } catch (ParseException ex) { 704 // if it is bad uri, don't save its password 705 } 706 707 } 708 } 709 710 // is this resource the main-frame top-level page? 711 boolean isMainFramePage = mIsMainFrame; 712 713 if (DebugFlags.BROWSER_FRAME) { 714 Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method=" 715 + method + ", postData=" + postData + ", isMainFramePage=" 716 + isMainFramePage + ", mainResource=" + mainResource 717 + ", userGesture=" + userGesture); 718 } 719 720 // Create a LoadListener 721 LoadListener loadListener = LoadListener.getLoadListener(mContext, 722 this, url, loaderHandle, synchronous, isMainFramePage, 723 mainResource, userGesture, postDataIdentifier, username, password); 724 725 mCallbackProxy.onLoadResource(url); 726 727 if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) { 728 // send an error message, so that loadListener can be deleted 729 // after this is returned. This is important as LoadListener's 730 // nativeError will remove the request from its DocLoader's request 731 // list. But the set up is not done until this method is returned. 732 loadListener.error( 733 android.net.http.EventHandler.ERROR, mContext.getString( 734 com.android.internal.R.string.httpErrorTooManyRequests)); 735 return loadListener; 736 } 737 738 FrameLoader loader = new FrameLoader(loadListener, mSettings, method); 739 loader.setHeaders(headers); 740 loader.setPostData(postData); 741 // Set the load mode to the mode used for the current page. 742 // If WebKit wants validation, go to network directly. 743 loader.setCacheMode(headers.containsKey("If-Modified-Since") 744 || headers.containsKey("If-None-Match") ? 745 WebSettings.LOAD_NO_CACHE : cacheMode); 746 // Set referrer to current URL? 747 if (!loader.executeLoad()) { 748 checker.responseAlert("startLoadingResource fail"); 749 } 750 checker.responseAlert("startLoadingResource succeed"); 751 752 return !synchronous ? loadListener : null; 753 } 754 755 /** 756 * Set the progress for the browser activity. Called by native code. 757 * Uses a delay so it does not happen too often. 758 * @param newProgress An int between zero and one hundred representing 759 * the current progress percentage of loading the page. 760 */ 761 private void setProgress(int newProgress) { 762 mCallbackProxy.onProgressChanged(newProgress); 763 if (newProgress == 100) { 764 sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100); 765 } 766 // FIXME: Need to figure out a better way to switch out of the history 767 // drawing mode. Maybe we can somehow compare the history picture with 768 // the current picture, and switch when it contains more content. 769 if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) { 770 mCallbackProxy.switchOutDrawHistory(); 771 } 772 } 773 774 /** 775 * Send the icon to the activity for display. 776 * @param icon A Bitmap representing a page's favicon. 777 */ 778 private void didReceiveIcon(Bitmap icon) { 779 mCallbackProxy.onReceivedIcon(icon); 780 } 781 782 // Called by JNI when an apple-touch-icon attribute was found. 783 private void didReceiveTouchIconUrl(String url, boolean precomposed) { 784 mCallbackProxy.onReceivedTouchIconUrl(url, precomposed); 785 } 786 787 /** 788 * Request a new window from the client. 789 * @return The BrowserFrame object stored in the new WebView. 790 */ 791 private BrowserFrame createWindow(boolean dialog, boolean userGesture) { 792 return mCallbackProxy.createWindow(dialog, userGesture); 793 } 794 795 /** 796 * Try to focus this WebView. 797 */ 798 private void requestFocus() { 799 mCallbackProxy.onRequestFocus(); 800 } 801 802 /** 803 * Close this frame and window. 804 */ 805 private void closeWindow(WebViewCore w) { 806 mCallbackProxy.onCloseWindow(w.getWebView()); 807 } 808 809 // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore 810 static final int POLICY_USE = 0; 811 static final int POLICY_IGNORE = 2; 812 813 private void decidePolicyForFormResubmission(int policyFunction) { 814 Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction, 815 POLICY_IGNORE); 816 Message resend = obtainMessage(POLICY_FUNCTION, policyFunction, 817 POLICY_USE); 818 mCallbackProxy.onFormResubmission(dontResend, resend); 819 } 820 821 /** 822 * Tell the activity to update its global history. 823 */ 824 private void updateVisitedHistory(String url, boolean isReload) { 825 mCallbackProxy.doUpdateVisitedHistory(url, isReload); 826 } 827 828 /** 829 * Get the CallbackProxy for sending messages to the UI thread. 830 */ 831 /* package */ CallbackProxy getCallbackProxy() { 832 return mCallbackProxy; 833 } 834 835 /** 836 * Returns the User Agent used by this frame 837 */ 838 String getUserAgentString() { 839 return mSettings.getUserAgentString(); 840 } 841 842 // These ids need to be in sync with enum rawResId in PlatformBridge.h 843 private static final int NODOMAIN = 1; 844 private static final int LOADERROR = 2; 845 private static final int DRAWABLEDIR = 3; 846 private static final int FILE_UPLOAD_LABEL = 4; 847 private static final int RESET_LABEL = 5; 848 private static final int SUBMIT_LABEL = 6; 849 850 String getRawResFilename(int id) { 851 int resid; 852 switch (id) { 853 case NODOMAIN: 854 resid = com.android.internal.R.raw.nodomain; 855 break; 856 857 case LOADERROR: 858 resid = com.android.internal.R.raw.loaderror; 859 break; 860 861 case DRAWABLEDIR: 862 // use one known resource to find the drawable directory 863 resid = com.android.internal.R.drawable.btn_check_off; 864 break; 865 866 case FILE_UPLOAD_LABEL: 867 return mContext.getResources().getString( 868 com.android.internal.R.string.upload_file); 869 870 case RESET_LABEL: 871 return mContext.getResources().getString( 872 com.android.internal.R.string.reset); 873 874 case SUBMIT_LABEL: 875 return mContext.getResources().getString( 876 com.android.internal.R.string.submit); 877 878 default: 879 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID"); 880 return ""; 881 } 882 TypedValue value = new TypedValue(); 883 mContext.getResources().getValue(resid, value, true); 884 if (id == DRAWABLEDIR) { 885 String path = value.string.toString(); 886 int index = path.lastIndexOf('/'); 887 if (index < 0) { 888 Log.e(LOGTAG, "Can't find drawable directory."); 889 return ""; 890 } 891 return path.substring(0, index + 1); 892 } 893 return value.string.toString(); 894 } 895 896 private float density() { 897 return mContext.getResources().getDisplayMetrics().density; 898 } 899 900 //========================================================================== 901 // native functions 902 //========================================================================== 903 904 /** 905 * Create a new native frame for a given WebView 906 * @param w A WebView that the frame draws into. 907 * @param am AssetManager to use to get assets. 908 * @param list The native side will add and remove items from this list as 909 * the native list changes. 910 */ 911 private native void nativeCreateFrame(WebViewCore w, AssetManager am, 912 WebBackForwardList list); 913 914 /** 915 * Destroy the native frame. 916 */ 917 public native void nativeDestroyFrame(); 918 919 private native void nativeCallPolicyFunction(int policyFunction, 920 int decision); 921 922 /** 923 * Reload the current main frame. 924 */ 925 public native void reload(boolean allowStale); 926 927 /** 928 * Go back or forward the number of steps given. 929 * @param steps A negative or positive number indicating the direction 930 * and number of steps to move. 931 */ 932 private native void nativeGoBackOrForward(int steps); 933 934 /** 935 * stringByEvaluatingJavaScriptFromString will execute the 936 * JS passed in in the context of this browser frame. 937 * @param script A javascript string to execute 938 * 939 * @return string result of execution or null 940 */ 941 public native String stringByEvaluatingJavaScriptFromString(String script); 942 943 /** 944 * Add a javascript interface to the main frame. 945 */ 946 private native void nativeAddJavascriptInterface(int nativeFramePointer, 947 Object obj, String interfaceName); 948 949 /** 950 * Enable or disable the native cache. 951 */ 952 /* FIXME: The native cache is always on for now until we have a better 953 * solution for our 2 caches. */ 954 private native void setCacheDisabled(boolean disabled); 955 956 public native boolean cacheDisabled(); 957 958 public native void clearCache(); 959 960 /** 961 * Returns false if the url is bad. 962 */ 963 private native void nativeLoadUrl(String url, Map<String, String> headers); 964 965 private native void nativePostUrl(String url, byte[] postData); 966 967 private native void nativeLoadData(String baseUrl, String data, 968 String mimeType, String encoding, String historyUrl); 969 970 /** 971 * Stop loading the current page. 972 */ 973 public void stopLoading() { 974 if (mIsMainFrame) { 975 resetLoadingStates(); 976 } 977 nativeStopLoading(); 978 } 979 980 private native void nativeStopLoading(); 981 982 /** 983 * Return true if the document has images. 984 */ 985 public native boolean documentHasImages(); 986 987 /** 988 * @return TRUE if there is a password field in the current frame 989 */ 990 private native boolean hasPasswordField(); 991 992 /** 993 * Get username and password in the current frame. If found, String[0] is 994 * username and String[1] is password. Otherwise return NULL. 995 * @return String[] 996 */ 997 private native String[] getUsernamePassword(); 998 999 /** 1000 * Set username and password to the proper fields in the current frame 1001 * @param username 1002 * @param password 1003 */ 1004 private native void setUsernamePassword(String username, String password); 1005 1006 /** 1007 * Get form's "text" type data associated with the current frame. 1008 * @return HashMap If succeed, returns a list of name/value pair. Otherwise 1009 * returns null. 1010 */ 1011 private native HashMap getFormTextData(); 1012 1013 private native void nativeOrientationChanged(int orientation); 1014 } 1015