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