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