BrowserFrame.java revision bf52c0ea10482ad761e4fbc8ce07e9517b8541f6
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.ViewRootImpl; 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 ViewRootImpl.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 url = WebTextView.urlForAutoCompleteData(h.getUrl()); 425 if (url != null) { 426 mDatabase.setFormData(url, 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 void certificate(SslCertificate certificate) { 476 if (mIsMainFrame) { 477 // we want to make this call even if the certificate is null 478 // (ie, the site is not secure) 479 mCallbackProxy.onReceivedCertificate(certificate); 480 } 481 } 482 483 /** 484 * Destroy all native components of the BrowserFrame. 485 */ 486 public void destroy() { 487 nativeDestroyFrame(); 488 mBlockMessages = true; 489 removeCallbacksAndMessages(null); 490 } 491 492 /** 493 * Handle messages posted to us. 494 * @param msg The message to handle. 495 */ 496 @Override 497 public void handleMessage(Message msg) { 498 if (mBlockMessages) { 499 return; 500 } 501 switch (msg.what) { 502 case FRAME_COMPLETED: { 503 if (mSettings.getSavePassword() && hasPasswordField()) { 504 WebHistoryItem item = mCallbackProxy.getBackForwardList() 505 .getCurrentItem(); 506 if (item != null) { 507 WebAddress uri = new WebAddress(item.getUrl()); 508 String schemePlusHost = uri.getScheme() + uri.getHost(); 509 String[] up = 510 mDatabase.getUsernamePassword(schemePlusHost); 511 if (up != null && up[0] != null) { 512 setUsernamePassword(up[0], up[1]); 513 } 514 } 515 } 516 if (!JniUtil.useChromiumHttpStack()) { 517 WebViewWorker.getHandler().sendEmptyMessage( 518 WebViewWorker.MSG_TRIM_CACHE); 519 } 520 break; 521 } 522 523 case POLICY_FUNCTION: { 524 nativeCallPolicyFunction(msg.arg1, msg.arg2); 525 break; 526 } 527 528 case ORIENTATION_CHANGED: { 529 if (mOrientation != msg.arg1) { 530 mOrientation = msg.arg1; 531 nativeOrientationChanged(msg.arg1); 532 } 533 break; 534 } 535 536 default: 537 break; 538 } 539 } 540 541 /** 542 * Punch-through for WebCore to set the document 543 * title. Inform the Activity of the new title. 544 * @param title The new title of the document. 545 */ 546 private void setTitle(String title) { 547 // FIXME: The activity must call getTitle (a native method) to get the 548 // title. We should try and cache the title if we can also keep it in 549 // sync with the document. 550 mCallbackProxy.onReceivedTitle(title); 551 } 552 553 /** 554 * Retrieves the render tree of this frame and puts it as the object for 555 * the message and sends the message. 556 * @param callback the message to use to send the render tree 557 */ 558 public void externalRepresentation(Message callback) { 559 callback.obj = externalRepresentation();; 560 callback.sendToTarget(); 561 } 562 563 /** 564 * Return the render tree as a string 565 */ 566 private native String externalRepresentation(); 567 568 /** 569 * Retrieves the visual text of the frames, puts it as the object for 570 * the message and sends the message. 571 * @param callback the message to use to send the visual text 572 */ 573 public void documentAsText(Message callback) { 574 StringBuilder text = new StringBuilder(); 575 if (callback.arg1 != 0) { 576 // Dump top frame as text. 577 text.append(documentAsText()); 578 } 579 if (callback.arg2 != 0) { 580 // Dump child frames as text. 581 text.append(childFramesAsText()); 582 } 583 callback.obj = text.toString(); 584 callback.sendToTarget(); 585 } 586 587 /** 588 * Return the text drawn on the screen as a string 589 */ 590 private native String documentAsText(); 591 592 /** 593 * Return the text drawn on the child frames as a string 594 */ 595 private native String childFramesAsText(); 596 597 /* 598 * This method is called by WebCore to inform the frame that 599 * the Javascript window object has been cleared. 600 * We should re-attach any attached js interfaces. 601 */ 602 private void windowObjectCleared(int nativeFramePointer) { 603 Iterator<String> iter = mJavaScriptObjects.keySet().iterator(); 604 while (iter.hasNext()) { 605 String interfaceName = iter.next(); 606 Object object = mJavaScriptObjects.get(interfaceName); 607 if (object != null) { 608 nativeAddJavascriptInterface(nativeFramePointer, 609 mJavaScriptObjects.get(interfaceName), interfaceName); 610 } 611 } 612 mRemovedJavaScriptObjects.clear(); 613 614 stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE); 615 } 616 617 /** 618 * This method is called by WebCore to check whether application 619 * wants to hijack url loading 620 */ 621 public boolean handleUrl(String url) { 622 if (mLoadInitFromJava == true) { 623 return false; 624 } 625 if (mCallbackProxy.shouldOverrideUrlLoading(url)) { 626 // if the url is hijacked, reset the state of the BrowserFrame 627 didFirstLayout(); 628 return true; 629 } else { 630 return false; 631 } 632 } 633 634 public void addJavascriptInterface(Object obj, String interfaceName) { 635 assert obj != null; 636 removeJavascriptInterface(interfaceName); 637 638 mJavaScriptObjects.put(interfaceName, obj); 639 } 640 641 public void removeJavascriptInterface(String interfaceName) { 642 // We keep a reference to the removed object because the native side holds only a weak 643 // reference and we need to allow the object to continue to be used until the page has been 644 // navigated. 645 if (mJavaScriptObjects.containsKey(interfaceName)) { 646 mRemovedJavaScriptObjects.add(mJavaScriptObjects.remove(interfaceName)); 647 } 648 } 649 650 /** 651 * Called by JNI. Given a URI, find the associated file and return its size 652 * @param uri A String representing the URI of the desired file. 653 * @return int The size of the given file. 654 */ 655 private int getFileSize(String uri) { 656 int size = 0; 657 try { 658 InputStream stream = mContext.getContentResolver() 659 .openInputStream(Uri.parse(uri)); 660 size = stream.available(); 661 stream.close(); 662 } catch (Exception e) {} 663 return size; 664 } 665 666 /** 667 * Called by JNI. Given a URI, a buffer, and an offset into the buffer, 668 * copy the resource into buffer. 669 * @param uri A String representing the URI of the desired file. 670 * @param buffer The byte array to copy the data into. 671 * @param offset The offet into buffer to place the data. 672 * @param expectedSize The size that the buffer has allocated for this file. 673 * @return int The size of the given file, or zero if it fails. 674 */ 675 private int getFile(String uri, byte[] buffer, int offset, 676 int expectedSize) { 677 int size = 0; 678 try { 679 InputStream stream = mContext.getContentResolver() 680 .openInputStream(Uri.parse(uri)); 681 size = stream.available(); 682 if (size <= expectedSize && buffer != null 683 && buffer.length - offset >= size) { 684 stream.read(buffer, offset, size); 685 } else { 686 size = 0; 687 } 688 stream.close(); 689 } catch (java.io.FileNotFoundException e) { 690 Log.e(LOGTAG, "FileNotFoundException:" + e); 691 size = 0; 692 } catch (java.io.IOException e2) { 693 Log.e(LOGTAG, "IOException: " + e2); 694 size = 0; 695 } 696 return size; 697 } 698 699 /** 700 * Get the InputStream for an Android resource 701 * There are three different kinds of android resources: 702 * - file:///android_res 703 * - file:///android_asset 704 * - content:// 705 * @param url The url to load. 706 * @return An InputStream to the android resource 707 */ 708 private InputStream inputStreamForAndroidResource(String url) { 709 // This list needs to be kept in sync with the list in 710 // external/webkit/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp 711 final String ANDROID_ASSET = "file:///android_asset/"; 712 final String ANDROID_RESOURCE = "file:///android_res/"; 713 final String ANDROID_CONTENT = "content:"; 714 715 // file:///android_res 716 if (url.startsWith(ANDROID_RESOURCE)) { 717 url = url.replaceFirst(ANDROID_RESOURCE, ""); 718 if (url == null || url.length() == 0) { 719 Log.e(LOGTAG, "url has length 0 " + url); 720 return null; 721 } 722 int slash = url.indexOf('/'); 723 int dot = url.indexOf('.', slash); 724 if (slash == -1 || dot == -1) { 725 Log.e(LOGTAG, "Incorrect res path: " + url); 726 return null; 727 } 728 String subClassName = url.substring(0, slash); 729 String fieldName = url.substring(slash + 1, dot); 730 String errorMsg = null; 731 try { 732 final Class<?> d = mContext.getApplicationContext() 733 .getClassLoader().loadClass( 734 mContext.getPackageName() + ".R$" 735 + subClassName); 736 final java.lang.reflect.Field field = d.getField(fieldName); 737 final int id = field.getInt(null); 738 TypedValue value = new TypedValue(); 739 mContext.getResources().getValue(id, value, true); 740 if (value.type == TypedValue.TYPE_STRING) { 741 return mContext.getAssets().openNonAsset( 742 value.assetCookie, value.string.toString(), 743 AssetManager.ACCESS_STREAMING); 744 } else { 745 // Old stack only supports TYPE_STRING for res files 746 Log.e(LOGTAG, "not of type string: " + url); 747 return null; 748 } 749 } catch (Exception e) { 750 Log.e(LOGTAG, "Exception: " + url); 751 return null; 752 } 753 754 // file:///android_asset 755 } else if (url.startsWith(ANDROID_ASSET)) { 756 url = url.replaceFirst(ANDROID_ASSET, ""); 757 try { 758 AssetManager assets = mContext.getAssets(); 759 return assets.open(url, AssetManager.ACCESS_STREAMING); 760 } catch (IOException e) { 761 return null; 762 } 763 764 // content:// 765 } else if (mSettings.getAllowContentAccess() && 766 url.startsWith(ANDROID_CONTENT)) { 767 try { 768 // Strip off mimetype, for compatibility with ContentLoader.java 769 // If we don't do this, we can fail to load Gmail attachments, 770 // because the URL being loaded doesn't exactly match the URL we 771 // have permission to read. 772 int mimeIndex = url.lastIndexOf('?'); 773 if (mimeIndex != -1) { 774 url = url.substring(0, mimeIndex); 775 } 776 Uri uri = Uri.parse(url); 777 return mContext.getContentResolver().openInputStream(uri); 778 } catch (Exception e) { 779 Log.e(LOGTAG, "Exception: " + url); 780 return null; 781 } 782 } else { 783 return null; 784 } 785 } 786 787 /** 788 * Start loading a resource. 789 * @param loaderHandle The native ResourceLoader that is the target of the 790 * data. 791 * @param url The url to load. 792 * @param method The http method. 793 * @param headers The http headers. 794 * @param postData If the method is "POST" postData is sent as the request 795 * body. Is null when empty. 796 * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0. 797 * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode 798 * @param mainResource True if the this resource is the main request, not a supporting resource 799 * @param userGesture 800 * @param synchronous True if the load is synchronous. 801 * @return A newly created LoadListener object. 802 */ 803 private LoadListener startLoadingResource(int loaderHandle, 804 String url, 805 String method, 806 HashMap headers, 807 byte[] postData, 808 long postDataIdentifier, 809 int cacheMode, 810 boolean mainResource, 811 boolean userGesture, 812 boolean synchronous, 813 String username, 814 String password) { 815 PerfChecker checker = new PerfChecker(); 816 817 if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) { 818 cacheMode = mSettings.getCacheMode(); 819 } 820 821 if (method.equals("POST")) { 822 // Don't use the cache on POSTs when issuing a normal POST 823 // request. 824 if (cacheMode == WebSettings.LOAD_NORMAL) { 825 cacheMode = WebSettings.LOAD_NO_CACHE; 826 } 827 String[] ret = getUsernamePassword(); 828 if (ret != null) { 829 String domUsername = ret[0]; 830 String domPassword = ret[1]; 831 maybeSavePassword(postData, domUsername, domPassword); 832 } 833 } 834 835 // is this resource the main-frame top-level page? 836 boolean isMainFramePage = mIsMainFrame; 837 838 if (DebugFlags.BROWSER_FRAME) { 839 Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method=" 840 + method + ", postData=" + postData + ", isMainFramePage=" 841 + isMainFramePage + ", mainResource=" + mainResource 842 + ", userGesture=" + userGesture); 843 } 844 845 // Create a LoadListener 846 LoadListener loadListener = LoadListener.getLoadListener(mContext, 847 this, url, loaderHandle, synchronous, isMainFramePage, 848 mainResource, userGesture, postDataIdentifier, username, password); 849 850 if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) { 851 // send an error message, so that loadListener can be deleted 852 // after this is returned. This is important as LoadListener's 853 // nativeError will remove the request from its DocLoader's request 854 // list. But the set up is not done until this method is returned. 855 loadListener.error( 856 android.net.http.EventHandler.ERROR, mContext.getString( 857 com.android.internal.R.string.httpErrorTooManyRequests)); 858 return loadListener; 859 } 860 861 // Note that we are intentionally skipping 862 // inputStreamForAndroidResource. This is so that FrameLoader will use 863 // the various StreamLoader classes to handle assets. 864 FrameLoader loader = new FrameLoader(loadListener, mSettings, method, 865 mCallbackProxy.shouldInterceptRequest(url)); 866 loader.setHeaders(headers); 867 loader.setPostData(postData); 868 // Set the load mode to the mode used for the current page. 869 // If WebKit wants validation, go to network directly. 870 loader.setCacheMode(headers.containsKey("If-Modified-Since") 871 || headers.containsKey("If-None-Match") ? 872 WebSettings.LOAD_NO_CACHE : cacheMode); 873 // Set referrer to current URL? 874 if (!loader.executeLoad()) { 875 checker.responseAlert("startLoadingResource fail"); 876 } 877 checker.responseAlert("startLoadingResource succeed"); 878 879 return !synchronous ? loadListener : null; 880 } 881 882 /** 883 * If this looks like a POST request (form submission) containing a username 884 * and password, give the user the option of saving them. Will either do 885 * nothing, or block until the UI interaction is complete. 886 * 887 * Called by startLoadingResource when using the Apache HTTP stack. 888 * Called directly by WebKit when using the Chrome HTTP stack. 889 * 890 * @param postData The data about to be sent as the body of a POST request. 891 * @param username The username entered by the user (sniffed from the DOM). 892 * @param password The password entered by the user (sniffed from the DOM). 893 */ 894 private void maybeSavePassword( 895 byte[] postData, String username, String password) { 896 if (postData == null 897 || username == null || username.isEmpty() 898 || password == null || password.isEmpty()) { 899 return; // No password to save. 900 } 901 902 if (!mSettings.getSavePassword()) { 903 return; // User doesn't want to save passwords. 904 } 905 906 try { 907 if (DebugFlags.BROWSER_FRAME) { 908 Assert.assertNotNull(mCallbackProxy.getBackForwardList() 909 .getCurrentItem()); 910 } 911 WebAddress uri = new WebAddress(mCallbackProxy 912 .getBackForwardList().getCurrentItem().getUrl()); 913 String schemePlusHost = uri.getScheme() + uri.getHost(); 914 // Check to see if the username & password appear in 915 // the post data (there could be another form on the 916 // page and that was posted instead. 917 String postString = new String(postData); 918 if (postString.contains(URLEncoder.encode(username)) && 919 postString.contains(URLEncoder.encode(password))) { 920 String[] saved = mDatabase.getUsernamePassword( 921 schemePlusHost); 922 if (saved != null) { 923 // null username implies that user has chosen not to 924 // save password 925 if (saved[0] != null) { 926 // non-null username implies that user has 927 // chosen to save password, so update the 928 // recorded password 929 mDatabase.setUsernamePassword( 930 schemePlusHost, username, password); 931 } 932 } else { 933 // CallbackProxy will handle creating the resume 934 // message 935 mCallbackProxy.onSavePassword(schemePlusHost, username, 936 password, null); 937 } 938 } 939 } catch (ParseException ex) { 940 // if it is bad uri, don't save its password 941 } 942 } 943 944 // Called by jni from the chrome network stack. 945 private WebResourceResponse shouldInterceptRequest(String url) { 946 InputStream androidResource = inputStreamForAndroidResource(url); 947 if (androidResource != null) { 948 return new WebResourceResponse(null, null, androidResource); 949 } 950 WebResourceResponse response = mCallbackProxy.shouldInterceptRequest(url); 951 if (response == null && "browser:incognito".equals(url)) { 952 try { 953 Resources res = mContext.getResources(); 954 InputStream ins = res.openRawResource( 955 com.android.internal.R.raw.incognito_mode_start_page); 956 response = new WebResourceResponse("text/html", "utf8", ins); 957 } catch (NotFoundException ex) { 958 // This shouldn't happen, but try and gracefully handle it jic 959 Log.w(LOGTAG, "Failed opening raw.incognito_mode_start_page", ex); 960 } 961 } 962 return response; 963 } 964 965 /** 966 * Set the progress for the browser activity. Called by native code. 967 * Uses a delay so it does not happen too often. 968 * @param newProgress An int between zero and one hundred representing 969 * the current progress percentage of loading the page. 970 */ 971 private void setProgress(int newProgress) { 972 mCallbackProxy.onProgressChanged(newProgress); 973 if (newProgress == 100) { 974 sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100); 975 } 976 // FIXME: Need to figure out a better way to switch out of the history 977 // drawing mode. Maybe we can somehow compare the history picture with 978 // the current picture, and switch when it contains more content. 979 if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) { 980 mCallbackProxy.switchOutDrawHistory(); 981 } 982 } 983 984 /** 985 * Send the icon to the activity for display. 986 * @param icon A Bitmap representing a page's favicon. 987 */ 988 private void didReceiveIcon(Bitmap icon) { 989 mCallbackProxy.onReceivedIcon(icon); 990 } 991 992 // Called by JNI when an apple-touch-icon attribute was found. 993 private void didReceiveTouchIconUrl(String url, boolean precomposed) { 994 mCallbackProxy.onReceivedTouchIconUrl(url, precomposed); 995 } 996 997 /** 998 * Request a new window from the client. 999 * @return The BrowserFrame object stored in the new WebView. 1000 */ 1001 private BrowserFrame createWindow(boolean dialog, boolean userGesture) { 1002 return mCallbackProxy.createWindow(dialog, userGesture); 1003 } 1004 1005 /** 1006 * Try to focus this WebView. 1007 */ 1008 private void requestFocus() { 1009 mCallbackProxy.onRequestFocus(); 1010 } 1011 1012 /** 1013 * Close this frame and window. 1014 */ 1015 private void closeWindow(WebViewCore w) { 1016 mCallbackProxy.onCloseWindow(w.getWebView()); 1017 } 1018 1019 // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore 1020 static final int POLICY_USE = 0; 1021 static final int POLICY_IGNORE = 2; 1022 1023 private void decidePolicyForFormResubmission(int policyFunction) { 1024 Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction, 1025 POLICY_IGNORE); 1026 Message resend = obtainMessage(POLICY_FUNCTION, policyFunction, 1027 POLICY_USE); 1028 mCallbackProxy.onFormResubmission(dontResend, resend); 1029 } 1030 1031 /** 1032 * Tell the activity to update its global history. 1033 */ 1034 private void updateVisitedHistory(String url, boolean isReload) { 1035 mCallbackProxy.doUpdateVisitedHistory(url, isReload); 1036 } 1037 1038 /** 1039 * Get the CallbackProxy for sending messages to the UI thread. 1040 */ 1041 /* package */ CallbackProxy getCallbackProxy() { 1042 return mCallbackProxy; 1043 } 1044 1045 /** 1046 * Returns the User Agent used by this frame 1047 */ 1048 String getUserAgentString() { 1049 return mSettings.getUserAgentString(); 1050 } 1051 1052 // These ids need to be in sync with enum rawResId in PlatformBridge.h 1053 private static final int NODOMAIN = 1; 1054 private static final int LOADERROR = 2; 1055 /* package */ static final int DRAWABLEDIR = 3; 1056 private static final int FILE_UPLOAD_LABEL = 4; 1057 private static final int RESET_LABEL = 5; 1058 private static final int SUBMIT_LABEL = 6; 1059 private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7; 1060 1061 private String getRawResFilename(int id) { 1062 return getRawResFilename(id, mContext); 1063 } 1064 /* package */ static String getRawResFilename(int id, Context context) { 1065 int resid; 1066 switch (id) { 1067 case NODOMAIN: 1068 resid = com.android.internal.R.raw.nodomain; 1069 break; 1070 1071 case LOADERROR: 1072 resid = com.android.internal.R.raw.loaderror; 1073 break; 1074 1075 case DRAWABLEDIR: 1076 // use one known resource to find the drawable directory 1077 resid = com.android.internal.R.drawable.btn_check_off; 1078 break; 1079 1080 case FILE_UPLOAD_LABEL: 1081 return context.getResources().getString( 1082 com.android.internal.R.string.upload_file); 1083 1084 case RESET_LABEL: 1085 return context.getResources().getString( 1086 com.android.internal.R.string.reset); 1087 1088 case SUBMIT_LABEL: 1089 return context.getResources().getString( 1090 com.android.internal.R.string.submit); 1091 1092 case FILE_UPLOAD_NO_FILE_CHOSEN: 1093 return context.getResources().getString( 1094 com.android.internal.R.string.no_file_chosen); 1095 1096 default: 1097 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID"); 1098 return ""; 1099 } 1100 TypedValue value = new TypedValue(); 1101 context.getResources().getValue(resid, value, true); 1102 if (id == DRAWABLEDIR) { 1103 String path = value.string.toString(); 1104 int index = path.lastIndexOf('/'); 1105 if (index < 0) { 1106 Log.e(LOGTAG, "Can't find drawable directory."); 1107 return ""; 1108 } 1109 return path.substring(0, index + 1); 1110 } 1111 return value.string.toString(); 1112 } 1113 1114 private float density() { 1115 return mContext.getResources().getDisplayMetrics().density; 1116 } 1117 1118 /** 1119 * Called by JNI when the native HTTP stack gets an authentication request. 1120 * 1121 * We delegate the request to CallbackProxy, and route its response to 1122 * {@link #nativeAuthenticationProceed(int, String, String)} or 1123 * {@link #nativeAuthenticationCancel(int)}. 1124 * 1125 * We don't care what thread the callback is invoked on. All threading is 1126 * handled on the C++ side, because the WebKit thread may be blocked on a 1127 * synchronous call and unable to pump our MessageQueue. 1128 */ 1129 private void didReceiveAuthenticationChallenge( 1130 final int handle, String host, String realm, final boolean useCachedCredentials, 1131 final boolean suppressDialog) { 1132 1133 HttpAuthHandler handler = new HttpAuthHandler() { 1134 1135 @Override 1136 public boolean useHttpAuthUsernamePassword() { 1137 return useCachedCredentials; 1138 } 1139 1140 @Override 1141 public void proceed(String username, String password) { 1142 nativeAuthenticationProceed(handle, username, password); 1143 } 1144 1145 @Override 1146 public void cancel() { 1147 nativeAuthenticationCancel(handle); 1148 } 1149 1150 @Override 1151 public boolean suppressDialog() { 1152 return suppressDialog; 1153 } 1154 }; 1155 mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm); 1156 } 1157 1158 /** 1159 * Called by JNI when the Chromium HTTP stack gets an invalid certificate chain. 1160 * 1161 * We delegate the request to CallbackProxy, and route its response to 1162 * {@link #nativeSslCertErrorProceed(int)} or 1163 * {@link #nativeSslCertErrorCancel(int, int)}. 1164 */ 1165 private void reportSslCertError(final int handle, final int certError, byte certDER[], 1166 String url) { 1167 final SslError sslError; 1168 try { 1169 X509Certificate cert = new X509CertImpl(certDER); 1170 SslCertificate sslCert = new SslCertificate(cert); 1171 sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, url); 1172 } catch (IOException e) { 1173 // Can't get the certificate, not much to do. 1174 Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling"); 1175 nativeSslCertErrorCancel(handle, certError); 1176 return; 1177 } 1178 1179 if (SslCertLookupTable.getInstance().isAllowed(sslError)) { 1180 nativeSslCertErrorProceed(handle); 1181 return; 1182 } 1183 1184 SslErrorHandler handler = new SslErrorHandler() { 1185 @Override 1186 public void proceed() { 1187 SslCertLookupTable.getInstance().setIsAllowed(sslError); 1188 nativeSslCertErrorProceed(handle); 1189 } 1190 @Override 1191 public void cancel() { 1192 nativeSslCertErrorCancel(handle, certError); 1193 } 1194 }; 1195 mCallbackProxy.onReceivedSslError(handler, sslError); 1196 } 1197 1198 /** 1199 * Called by JNI when the native HTTPS stack gets a client 1200 * certificate request. 1201 * 1202 * We delegate the request to CallbackProxy, and route its response to 1203 * {@link #nativeSslClientCert(int, X509Certificate)}. 1204 */ 1205 private void requestClientCert(int handle, byte[] host_and_port_bytes) { 1206 String host_and_port = new String(host_and_port_bytes, Charsets.UTF_8); 1207 SslClientCertLookupTable table = SslClientCertLookupTable.getInstance(); 1208 if (table.IsAllowed(host_and_port)) { 1209 // previously allowed 1210 nativeSslClientCert(handle, 1211 table.PrivateKey(host_and_port), 1212 table.CertificateChain(host_and_port)); 1213 } else if (table.IsDenied(host_and_port)) { 1214 // previously denied 1215 nativeSslClientCert(handle, null, null); 1216 } else { 1217 // previously ignored or new 1218 mCallbackProxy.onReceivedClientCertRequest( 1219 new ClientCertRequestHandler(this, handle, host_and_port, table), 1220 host_and_port); 1221 } 1222 } 1223 1224 /** 1225 * Called by JNI when the native HTTP stack needs to download a file. 1226 * 1227 * We delegate the request to CallbackProxy, which owns the current app's 1228 * DownloadListener. 1229 */ 1230 private void downloadStart(String url, String userAgent, 1231 String contentDisposition, String mimeType, long contentLength) { 1232 // This will only work if the url ends with the filename 1233 if (mimeType.isEmpty()) { 1234 try { 1235 String extension = url.substring(url.lastIndexOf('.') + 1); 1236 mimeType = libcore.net.MimeUtils.guessMimeTypeFromExtension(extension); 1237 // MimeUtils might return null, not sure if downloadmanager is happy with that 1238 if (mimeType == null) 1239 mimeType = ""; 1240 } catch(IndexOutOfBoundsException exception) { 1241 // mimeType string end with a '.', not much to do 1242 } 1243 } 1244 mimeType = MimeTypeMap.getSingleton().remapGenericMimeType( 1245 mimeType, url, contentDisposition); 1246 1247 if (CertTool.getCertType(mimeType) != null) { 1248 mKeyStoreHandler = new KeyStoreHandler(mimeType); 1249 } else { 1250 mCallbackProxy.onDownloadStart(url, userAgent, 1251 contentDisposition, mimeType, contentLength); 1252 } 1253 } 1254 1255 /** 1256 * Called by JNI for Chrome HTTP stack when the Java side needs to access the data. 1257 */ 1258 private void didReceiveData(byte data[], int size) { 1259 if (mKeyStoreHandler != null) mKeyStoreHandler.didReceiveData(data, size); 1260 } 1261 1262 private void didFinishLoading() { 1263 if (mKeyStoreHandler != null) { 1264 mKeyStoreHandler.installCert(mContext); 1265 mKeyStoreHandler = null; 1266 } 1267 } 1268 1269 /** 1270 * Called by JNI when we load a page over SSL. 1271 */ 1272 private void setCertificate(byte cert_der[]) { 1273 try { 1274 X509Certificate cert = new X509CertImpl(cert_der); 1275 mCallbackProxy.onReceivedCertificate(new SslCertificate(cert)); 1276 } catch (IOException e) { 1277 // Can't get the certificate, not much to do. 1278 Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling"); 1279 return; 1280 } 1281 } 1282 1283 /*package*/ SearchBox getSearchBox() { 1284 return mSearchBox; 1285 } 1286 1287 /** 1288 * Called by JNI when processing the X-Auto-Login header. 1289 */ 1290 private void autoLogin(String realm, String account, String args) { 1291 mCallbackProxy.onReceivedLoginRequest(realm, account, args); 1292 } 1293 1294 //========================================================================== 1295 // native functions 1296 //========================================================================== 1297 1298 /** 1299 * Create a new native frame for a given WebView 1300 * @param w A WebView that the frame draws into. 1301 * @param am AssetManager to use to get assets. 1302 * @param list The native side will add and remove items from this list as 1303 * the native list changes. 1304 */ 1305 private native void nativeCreateFrame(WebViewCore w, AssetManager am, 1306 WebBackForwardList list); 1307 1308 /** 1309 * Destroy the native frame. 1310 */ 1311 public native void nativeDestroyFrame(); 1312 1313 private native void nativeCallPolicyFunction(int policyFunction, 1314 int decision); 1315 1316 /** 1317 * Reload the current main frame. 1318 */ 1319 public native void reload(boolean allowStale); 1320 1321 /** 1322 * Go back or forward the number of steps given. 1323 * @param steps A negative or positive number indicating the direction 1324 * and number of steps to move. 1325 */ 1326 private native void nativeGoBackOrForward(int steps); 1327 1328 /** 1329 * stringByEvaluatingJavaScriptFromString will execute the 1330 * JS passed in in the context of this browser frame. 1331 * @param script A javascript string to execute 1332 * 1333 * @return string result of execution or null 1334 */ 1335 public native String stringByEvaluatingJavaScriptFromString(String script); 1336 1337 /** 1338 * Add a javascript interface to the main frame. 1339 */ 1340 private native void nativeAddJavascriptInterface(int nativeFramePointer, 1341 Object obj, String interfaceName); 1342 1343 /** 1344 * Enable or disable the native cache. 1345 */ 1346 /* FIXME: The native cache is always on for now until we have a better 1347 * solution for our 2 caches. */ 1348 private native void setCacheDisabled(boolean disabled); 1349 1350 public native boolean cacheDisabled(); 1351 1352 public native void clearCache(); 1353 1354 /** 1355 * Returns false if the url is bad. 1356 */ 1357 private native void nativeLoadUrl(String url, Map<String, String> headers); 1358 1359 private native void nativePostUrl(String url, byte[] postData); 1360 1361 private native void nativeLoadData(String baseUrl, String data, 1362 String mimeType, String encoding, String historyUrl); 1363 1364 /** 1365 * Stop loading the current page. 1366 */ 1367 public void stopLoading() { 1368 if (mIsMainFrame) { 1369 resetLoadingStates(); 1370 } 1371 nativeStopLoading(); 1372 } 1373 1374 private native void nativeStopLoading(); 1375 1376 /** 1377 * Return true if the document has images. 1378 */ 1379 public native boolean documentHasImages(); 1380 1381 /** 1382 * @return TRUE if there is a password field in the current frame 1383 */ 1384 private native boolean hasPasswordField(); 1385 1386 /** 1387 * Get username and password in the current frame. If found, String[0] is 1388 * username and String[1] is password. Otherwise return NULL. 1389 * @return String[] 1390 */ 1391 private native String[] getUsernamePassword(); 1392 1393 /** 1394 * Set username and password to the proper fields in the current frame 1395 * @param username 1396 * @param password 1397 */ 1398 private native void setUsernamePassword(String username, String password); 1399 1400 private native String nativeSaveWebArchive(String basename, boolean autoname); 1401 1402 private native void nativeOrientationChanged(int orientation); 1403 1404 private native void nativeAuthenticationProceed(int handle, String username, String password); 1405 private native void nativeAuthenticationCancel(int handle); 1406 1407 private native void nativeSslCertErrorProceed(int handle); 1408 private native void nativeSslCertErrorCancel(int handle, int certError); 1409 1410 native void nativeSslClientCert(int handle, 1411 byte[] pkcs8EncodedPrivateKey, 1412 byte[][] asn1DerEncodedCertificateChain); 1413} 1414