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