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