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