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