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