blob: dae9187b0547e6d6931c2eac56169c0382d7d7de [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Grace Kloba11843132009-10-08 20:48:09 -070019import android.app.ActivityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
21import android.content.res.AssetManager;
Leon Scroggins70ca3c22009-10-02 15:58:55 -040022import android.database.Cursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.graphics.Bitmap;
24import android.net.ParseException;
Leon Scroggins70ca3c22009-10-02 15:58:55 -040025import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.net.WebAddress;
27import android.net.http.SslCertificate;
28import android.os.Handler;
29import android.os.Message;
Leon Scroggins70ca3c22009-10-02 15:58:55 -040030import android.provider.OpenableColumns;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.util.Log;
32import android.util.TypedValue;
Patrick Scottf06364b2009-12-02 08:57:09 -050033import android.view.Surface;
34import android.view.WindowOrientationListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
36import junit.framework.Assert;
37
Leon Scroggins70ca3c22009-10-02 15:58:55 -040038import java.io.InputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import java.net.URLEncoder;
40import java.util.HashMap;
Andrei Popescu4950b2b2009-09-03 13:56:07 +010041import java.util.Map;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import java.util.Iterator;
43
44class BrowserFrame extends Handler {
45
46 private static final String LOGTAG = "webkit";
47
48 /**
49 * Cap the number of LoadListeners that will be instantiated, so
50 * we don't blow the GREF count. Attempting to queue more than
51 * this many requests will prompt an error() callback on the
52 * request's LoadListener
53 */
54 private final static int MAX_OUTSTANDING_REQUESTS = 300;
55
56 private final CallbackProxy mCallbackProxy;
57 private final WebSettings mSettings;
58 private final Context mContext;
59 private final WebViewDatabase mDatabase;
60 private final WebViewCore mWebViewCore;
61 /* package */ boolean mLoadInitFromJava;
62 private int mLoadType;
63 private boolean mFirstLayoutDone = true;
64 private boolean mCommitted = true;
Leon Clarkec0b778e2010-03-15 15:27:02 +000065 // Flag for blocking messages. This is used during destroy() so
66 // that if the UI thread posts any messages after the message
67 // queue has been cleared,they are ignored.
68 private boolean mBlockMessages = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
70 // Is this frame the main frame?
71 private boolean mIsMainFrame;
72
73 // Attached Javascript interfaces
Andrei Popescu4950b2b2009-09-03 13:56:07 +010074 private Map<String, Object> mJSInterfaceMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075
Patrick Scottf06364b2009-12-02 08:57:09 -050076 // Orientation listener
77 private WindowOrientationListener mOrientationListener;
78
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 // message ids
80 // a message posted when a frame loading is completed
81 static final int FRAME_COMPLETED = 1001;
Patrick Scottf06364b2009-12-02 08:57:09 -050082 // orientation change message
83 static final int ORIENTATION_CHANGED = 1002;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 // a message posted when the user decides the policy
85 static final int POLICY_FUNCTION = 1003;
86
87 // Note: need to keep these in sync with FrameLoaderTypes.h in native
88 static final int FRAME_LOADTYPE_STANDARD = 0;
89 static final int FRAME_LOADTYPE_BACK = 1;
90 static final int FRAME_LOADTYPE_FORWARD = 2;
91 static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
92 static final int FRAME_LOADTYPE_RELOAD = 4;
93 static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
94 static final int FRAME_LOADTYPE_SAME = 6;
95 static final int FRAME_LOADTYPE_REDIRECT = 7;
96 static final int FRAME_LOADTYPE_REPLACE = 8;
97
98 // A progress threshold to switch from history Picture to live Picture
99 private static final int TRANSITION_SWITCH_THRESHOLD = 75;
100
101 // This is a field accessed by native code as well as package classes.
102 /*package*/ int mNativeFrame;
103
104 // Static instance of a JWebCoreJavaBridge to handle timer and cookie
105 // requests from WebCore.
106 static JWebCoreJavaBridge sJavaBridge;
107
108 /**
109 * Create a new BrowserFrame to be used in an application.
110 * @param context An application context to use when retrieving assets.
111 * @param w A WebViewCore used as the view for this frame.
112 * @param proxy A CallbackProxy for posting messages to the UI thread and
113 * querying a client for information.
114 * @param settings A WebSettings object that holds all settings.
115 * XXX: Called by WebCore thread.
116 */
117 public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
Andrei Popescu4950b2b2009-09-03 13:56:07 +0100118 WebSettings settings, Map<String, Object> javascriptInterfaces) {
Romain Guy01d0fbf2009-12-01 14:52:19 -0800119
120 Context appContext = context.getApplicationContext();
121
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 // Create a global JWebCoreJavaBridge to handle timers and
123 // cookies in the WebCore thread.
124 if (sJavaBridge == null) {
Romain Guy01d0fbf2009-12-01 14:52:19 -0800125 sJavaBridge = new JWebCoreJavaBridge(appContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 // set WebCore native cache size
Grace Kloba11843132009-10-08 20:48:09 -0700127 ActivityManager am = (ActivityManager) context
128 .getSystemService(Context.ACTIVITY_SERVICE);
129 if (am.getMemoryClass() > 16) {
130 sJavaBridge.setCacheSize(8 * 1024 * 1024);
131 } else {
132 sJavaBridge.setCacheSize(4 * 1024 * 1024);
133 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 // initialize CacheManager
Romain Guy01d0fbf2009-12-01 14:52:19 -0800135 CacheManager.init(appContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 // create CookieSyncManager with current Context
Romain Guy01d0fbf2009-12-01 14:52:19 -0800137 CookieSyncManager.createInstance(appContext);
Grace Kloba658ab7d2009-05-14 14:45:26 -0700138 // create PluginManager with current Context
Romain Guy01d0fbf2009-12-01 14:52:19 -0800139 PluginManager.getInstance(appContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 }
Andrei Popescu4950b2b2009-09-03 13:56:07 +0100141 mJSInterfaceMap = javascriptInterfaces;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142
143 mSettings = settings;
144 mContext = context;
145 mCallbackProxy = proxy;
Romain Guy01d0fbf2009-12-01 14:52:19 -0800146 mDatabase = WebViewDatabase.getInstance(appContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 mWebViewCore = w;
148
Grace Kloba83031582009-09-02 16:21:42 -0700149 AssetManager am = context.getAssets();
150 nativeCreateFrame(w, am, proxy.getBackForwardList());
151
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400152 if (DebugFlags.BROWSER_FRAME) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
154 }
Patrick Scottf06364b2009-12-02 08:57:09 -0500155
156 mOrientationListener = new WindowOrientationListener(context) {
157 @Override
158 public void onOrientationChanged(int orientation) {
159 switch (orientation) {
160 case Surface.ROTATION_90:
161 orientation = 90;
162 break;
163 case Surface.ROTATION_180:
164 orientation = 180;
165 break;
166 case Surface.ROTATION_270:
167 orientation = -90;
168 break;
169 case Surface.ROTATION_0:
170 orientation = 0;
171 break;
172 default:
173 break;
174 }
175 sendMessage(
176 obtainMessage(ORIENTATION_CHANGED, orientation, 0));
177 }
178 };
179 mOrientationListener.enable();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181
182 /**
183 * Load a url from the network or the filesystem into the main frame.
Grace Klobad0d9bc22010-01-26 18:08:28 -0800184 * Following the same behaviour as Safari, javascript: URLs are not passed
185 * to the main frame, instead they are evaluated immediately.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 * @param url The url to load.
Grace Klobad0d9bc22010-01-26 18:08:28 -0800187 * @param extraHeaders The extra headers sent with this url. This should not
188 * include the common headers like "user-agent". If it does, it
189 * will be replaced by the intrinsic value of the WebView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 */
Grace Klobad0d9bc22010-01-26 18:08:28 -0800191 public void loadUrl(String url, Map<String, String> extraHeaders) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 mLoadInitFromJava = true;
193 if (URLUtil.isJavaScriptUrl(url)) {
194 // strip off the scheme and evaluate the string
195 stringByEvaluatingJavaScriptFromString(
196 url.substring("javascript:".length()));
197 } else {
Grace Klobad0d9bc22010-01-26 18:08:28 -0800198 nativeLoadUrl(url, extraHeaders);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 }
200 mLoadInitFromJava = false;
201 }
202
203 /**
Grace Kloba57534302009-05-22 18:55:02 -0700204 * Load a url with "POST" method from the network into the main frame.
205 * @param url The url to load.
206 * @param data The data for POST request.
207 */
208 public void postUrl(String url, byte[] data) {
209 mLoadInitFromJava = true;
210 nativePostUrl(url, data);
211 mLoadInitFromJava = false;
212 }
213
214 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 * Load the content as if it was loaded by the provided base URL. The
216 * failUrl is used as the history entry for the load data. If null or
217 * an empty string is passed for the failUrl, then no history entry is
218 * created.
219 *
220 * @param baseUrl Base URL used to resolve relative paths in the content
221 * @param data Content to render in the browser
222 * @param mimeType Mimetype of the data being passed in
223 * @param encoding Character set encoding of the provided data.
224 * @param failUrl URL to use if the content fails to load or null.
225 */
226 public void loadData(String baseUrl, String data, String mimeType,
227 String encoding, String failUrl) {
228 mLoadInitFromJava = true;
229 if (failUrl == null) {
230 failUrl = "";
231 }
232 if (data == null) {
233 data = "";
234 }
235
236 // Setup defaults for missing values. These defaults where taken from
237 // WebKit's WebFrame.mm
238 if (baseUrl == null || baseUrl.length() == 0) {
239 baseUrl = "about:blank";
240 }
241 if (mimeType == null || mimeType.length() == 0) {
242 mimeType = "text/html";
243 }
244 nativeLoadData(baseUrl, data, mimeType, encoding, failUrl);
245 mLoadInitFromJava = false;
246 }
247
248 /**
249 * Go back or forward the number of steps given.
250 * @param steps A negative or positive number indicating the direction
251 * and number of steps to move.
252 */
253 public void goBackOrForward(int steps) {
254 mLoadInitFromJava = true;
255 nativeGoBackOrForward(steps);
256 mLoadInitFromJava = false;
257 }
258
259 /**
260 * native callback
261 * Report an error to an activity.
262 * @param errorCode The HTTP error code.
263 * @param description A String description.
264 * TODO: Report all errors including resource errors but include some kind
265 * of domain identifier. Change errorCode to an enum for a cleaner
266 * interface.
267 */
268 private void reportError(final int errorCode, final String description,
269 final String failingUrl) {
270 // As this is called for the main resource and loading will be stopped
271 // after, reset the state variables.
The Android Open Source Project4df24232009-03-05 14:34:35 -0800272 resetLoadingStates();
273 mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
274 }
275
276 private void resetLoadingStates() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 mCommitted = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 mFirstLayoutDone = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 }
280
281 /* package */boolean committed() {
282 return mCommitted;
283 }
284
285 /* package */boolean firstLayoutDone() {
286 return mFirstLayoutDone;
287 }
288
289 /* package */int loadType() {
290 return mLoadType;
291 }
292
293 /* package */void didFirstLayout() {
294 if (!mFirstLayoutDone) {
295 mFirstLayoutDone = true;
296 // ensure {@link WebViewCore#webkitDraw} is called as we were
297 // blocking the update in {@link #loadStarted}
298 mWebViewCore.contentDraw();
299 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 }
301
302 /**
303 * native callback
304 * Indicates the beginning of a new load.
305 * This method will be called once for the main frame.
306 */
307 private void loadStarted(String url, Bitmap favicon, int loadType,
308 boolean isMainFrame) {
309 mIsMainFrame = isMainFrame;
310
311 if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
312 mLoadType = loadType;
313
314 if (isMainFrame) {
315 // Call onPageStarted for main frames.
316 mCallbackProxy.onPageStarted(url, favicon);
317 // as didFirstLayout() is only called for the main frame, reset
318 // mFirstLayoutDone only for the main frames
319 mFirstLayoutDone = false;
320 mCommitted = false;
321 // remove pending draw to block update until mFirstLayoutDone is
322 // set to true in didFirstLayout()
323 mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
324 }
325
326 // Note: only saves committed form data in standard load
327 if (loadType == FRAME_LOADTYPE_STANDARD
328 && mSettings.getSaveFormData()) {
329 final WebHistoryItem h = mCallbackProxy.getBackForwardList()
330 .getCurrentItem();
331 if (h != null) {
332 String currentUrl = h.getUrl();
333 if (currentUrl != null) {
334 mDatabase.setFormData(currentUrl, getFormTextData());
335 }
336 }
337 }
338 }
339 }
340
341 /**
342 * native callback
343 * Indicates the WebKit has committed to the new load
344 */
345 private void transitionToCommitted(int loadType, boolean isMainFrame) {
346 // loadType is not used yet
347 if (isMainFrame) {
348 mCommitted = true;
Grace Kloba9a67c822009-12-20 11:33:58 -0800349 mWebViewCore.getWebView().mViewManager.postResetStateAll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 }
351 }
352
353 /**
354 * native callback
355 * <p>
356 * Indicates the end of a new load.
357 * This method will be called once for the main frame.
358 */
359 private void loadFinished(String url, int loadType, boolean isMainFrame) {
360 // mIsMainFrame and isMainFrame are better be equal!!!
361
362 if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
363 if (isMainFrame) {
The Android Open Source Project4df24232009-03-05 14:34:35 -0800364 resetLoadingStates();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 mCallbackProxy.switchOutDrawHistory();
366 mCallbackProxy.onPageFinished(url);
367 }
368 }
369 }
370
371 /**
372 * We have received an SSL certificate for the main top-level page.
373 *
374 * !!!Called from the network thread!!!
375 */
376 void certificate(SslCertificate certificate) {
377 if (mIsMainFrame) {
378 // we want to make this call even if the certificate is null
379 // (ie, the site is not secure)
380 mCallbackProxy.onReceivedCertificate(certificate);
381 }
382 }
383
384 /**
385 * Destroy all native components of the BrowserFrame.
386 */
387 public void destroy() {
Patrick Scottf06364b2009-12-02 08:57:09 -0500388 mOrientationListener.disable();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 nativeDestroyFrame();
Leon Clarkec0b778e2010-03-15 15:27:02 +0000390 mBlockMessages = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 removeCallbacksAndMessages(null);
392 }
393
394 /**
395 * Handle messages posted to us.
396 * @param msg The message to handle.
397 */
398 @Override
399 public void handleMessage(Message msg) {
Leon Clarkec0b778e2010-03-15 15:27:02 +0000400 if (mBlockMessages) {
401 return;
402 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800403 switch (msg.what) {
404 case FRAME_COMPLETED: {
405 if (mSettings.getSavePassword() && hasPasswordField()) {
Patrick Scottaf31c3a2009-08-24 13:46:09 -0400406 WebHistoryItem item = mCallbackProxy.getBackForwardList()
407 .getCurrentItem();
408 if (item != null) {
409 WebAddress uri = new WebAddress(item.getUrl());
410 String schemePlusHost = uri.mScheme + uri.mHost;
411 String[] up =
412 mDatabase.getUsernamePassword(schemePlusHost);
413 if (up != null && up[0] != null) {
414 setUsernamePassword(up[0], up[1]);
415 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416 }
417 }
Grace Kloba2036dba2010-02-15 02:15:37 -0800418 WebViewWorker.getHandler().sendEmptyMessage(
419 WebViewWorker.MSG_TRIM_CACHE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800420 break;
421 }
422
423 case POLICY_FUNCTION: {
424 nativeCallPolicyFunction(msg.arg1, msg.arg2);
425 break;
426 }
427
Patrick Scottf06364b2009-12-02 08:57:09 -0500428 case ORIENTATION_CHANGED: {
429 nativeOrientationChanged(msg.arg1);
430 break;
431 }
432
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 default:
434 break;
435 }
436 }
437
438 /**
439 * Punch-through for WebCore to set the document
440 * title. Inform the Activity of the new title.
441 * @param title The new title of the document.
442 */
443 private void setTitle(String title) {
444 // FIXME: The activity must call getTitle (a native method) to get the
445 // title. We should try and cache the title if we can also keep it in
446 // sync with the document.
447 mCallbackProxy.onReceivedTitle(title);
448 }
449
450 /**
451 * Retrieves the render tree of this frame and puts it as the object for
452 * the message and sends the message.
453 * @param callback the message to use to send the render tree
454 */
455 public void externalRepresentation(Message callback) {
456 callback.obj = externalRepresentation();;
457 callback.sendToTarget();
458 }
459
460 /**
461 * Return the render tree as a string
462 */
463 private native String externalRepresentation();
464
465 /**
466 * Retrieves the visual text of the current frame, puts it as the object for
467 * the message and sends the message.
468 * @param callback the message to use to send the visual text
469 */
470 public void documentAsText(Message callback) {
471 callback.obj = documentAsText();;
472 callback.sendToTarget();
473 }
474
475 /**
476 * Return the text drawn on the screen as a string
477 */
478 private native String documentAsText();
479
480 /*
481 * This method is called by WebCore to inform the frame that
482 * the Javascript window object has been cleared.
483 * We should re-attach any attached js interfaces.
484 */
485 private void windowObjectCleared(int nativeFramePointer) {
486 if (mJSInterfaceMap != null) {
487 Iterator iter = mJSInterfaceMap.keySet().iterator();
488 while (iter.hasNext()) {
489 String interfaceName = (String) iter.next();
490 nativeAddJavascriptInterface(nativeFramePointer,
491 mJSInterfaceMap.get(interfaceName), interfaceName);
492 }
493 }
494 }
495
496 /**
497 * This method is called by WebCore to check whether application
498 * wants to hijack url loading
499 */
500 public boolean handleUrl(String url) {
501 if (mLoadInitFromJava == true) {
502 return false;
503 }
504 if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
505 // if the url is hijacked, reset the state of the BrowserFrame
506 didFirstLayout();
507 return true;
508 } else {
509 return false;
510 }
511 }
512
513 public void addJavascriptInterface(Object obj, String interfaceName) {
514 if (mJSInterfaceMap == null) {
515 mJSInterfaceMap = new HashMap<String, Object>();
516 }
517 if (mJSInterfaceMap.containsKey(interfaceName)) {
518 mJSInterfaceMap.remove(interfaceName);
519 }
520 mJSInterfaceMap.put(interfaceName, obj);
521 }
522
523 /**
Leon Scroggins70ca3c22009-10-02 15:58:55 -0400524 * Called by JNI. Given a URI, find the associated file and return its size
525 * @param uri A String representing the URI of the desired file.
526 * @return int The size of the given file.
527 */
528 private int getFileSize(String uri) {
529 int size = 0;
530 Cursor cursor = mContext.getContentResolver().query(Uri.parse(uri),
531 new String[] { OpenableColumns.SIZE },
532 null,
533 null,
534 null);
535 if (cursor != null) {
536 try {
537 if (cursor.moveToNext()) {
538 size = cursor.getInt(0);
539 }
540 } finally {
541 cursor.close();
542 }
543 }
544 return size;
545 }
546
547 /**
548 * Called by JNI. Given a URI, a buffer, and an offset into the buffer,
549 * copy the resource into buffer.
550 * @param uri A String representing the URI of the desired file.
551 * @param buffer The byte array to copy the data into.
552 * @param offset The offet into buffer to place the data.
Romain Guy01d0fbf2009-12-01 14:52:19 -0800553 * @param expectedSize The size that the buffer has allocated for this file.
Leon Scroggins70ca3c22009-10-02 15:58:55 -0400554 * @return int The size of the given file, or zero if it fails.
555 */
Leon Scrogginsd5ba82a2009-10-13 14:23:56 -0400556 private int getFile(String uri, byte[] buffer, int offset,
557 int expectedSize) {
Leon Scroggins70ca3c22009-10-02 15:58:55 -0400558 int size = 0;
559 try {
560 InputStream stream = mContext.getContentResolver()
561 .openInputStream(Uri.parse(uri));
562 size = stream.available();
Leon Scrogginsd5ba82a2009-10-13 14:23:56 -0400563 if (size <= expectedSize && buffer != null
564 && buffer.length - offset >= size) {
Leon Scroggins70ca3c22009-10-02 15:58:55 -0400565 stream.read(buffer, offset, size);
566 } else {
567 size = 0;
568 }
569 stream.close();
570 } catch (java.io.FileNotFoundException e) {
571 Log.e(LOGTAG, "FileNotFoundException:" + e);
572 size = 0;
573 } catch (java.io.IOException e2) {
574 Log.e(LOGTAG, "IOException: " + e2);
575 size = 0;
576 }
577 return size;
578 }
579
580 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581 * Start loading a resource.
582 * @param loaderHandle The native ResourceLoader that is the target of the
583 * data.
584 * @param url The url to load.
585 * @param method The http method.
586 * @param headers The http headers.
587 * @param postData If the method is "POST" postData is sent as the request
588 * body. Is null when empty.
Brian Carlstromdba8cb72010-03-18 16:56:41 -0700589 * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
590 * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
591 * @param mainResource True if the this resource is the main request, not a supporting resource
592 * @param userGesture
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 * @param synchronous True if the load is synchronous.
594 * @return A newly created LoadListener object.
595 */
596 private LoadListener startLoadingResource(int loaderHandle,
597 String url,
598 String method,
599 HashMap headers,
600 byte[] postData,
Grace Kloba8c92c392009-11-08 19:01:55 -0800601 long postDataIdentifier,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 int cacheMode,
Patrick Scott7c24ed62009-11-30 13:34:40 -0500603 boolean mainResource,
604 boolean userGesture,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 boolean synchronous) {
606 PerfChecker checker = new PerfChecker();
607
608 if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
609 cacheMode = mSettings.getCacheMode();
610 }
611
612 if (method.equals("POST")) {
613 // Don't use the cache on POSTs when issuing a normal POST
614 // request.
615 if (cacheMode == WebSettings.LOAD_NORMAL) {
616 cacheMode = WebSettings.LOAD_NO_CACHE;
617 }
618 if (mSettings.getSavePassword() && hasPasswordField()) {
619 try {
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400620 if (DebugFlags.BROWSER_FRAME) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 Assert.assertNotNull(mCallbackProxy.getBackForwardList()
622 .getCurrentItem());
623 }
624 WebAddress uri = new WebAddress(mCallbackProxy
625 .getBackForwardList().getCurrentItem().getUrl());
626 String schemePlusHost = uri.mScheme + uri.mHost;
627 String[] ret = getUsernamePassword();
628 // Has the user entered a username/password pair and is
629 // there some POST data
630 if (ret != null && postData != null &&
631 ret[0].length() > 0 && ret[1].length() > 0) {
632 // Check to see if the username & password appear in
633 // the post data (there could be another form on the
634 // page and that was posted instead.
635 String postString = new String(postData);
636 if (postString.contains(URLEncoder.encode(ret[0])) &&
637 postString.contains(URLEncoder.encode(ret[1]))) {
638 String[] saved = mDatabase.getUsernamePassword(
639 schemePlusHost);
640 if (saved != null) {
641 // null username implies that user has chosen not to
642 // save password
643 if (saved[0] != null) {
644 // non-null username implies that user has
645 // chosen to save password, so update the
646 // recorded password
647 mDatabase.setUsernamePassword(
648 schemePlusHost, ret[0], ret[1]);
649 }
650 } else {
651 // CallbackProxy will handle creating the resume
652 // message
653 mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
654 ret[1], null);
655 }
656 }
657 }
658 } catch (ParseException ex) {
659 // if it is bad uri, don't save its password
660 }
661
662 }
663 }
664
665 // is this resource the main-frame top-level page?
666 boolean isMainFramePage = mIsMainFrame;
667
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400668 if (DebugFlags.BROWSER_FRAME) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
Patrick Scottfe4fec72009-07-14 15:54:30 -0400670 + method + ", postData=" + postData + ", isMainFramePage="
Patrick Scott7c24ed62009-11-30 13:34:40 -0500671 + isMainFramePage + ", mainResource=" + mainResource
672 + ", userGesture=" + userGesture);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 }
674
675 // Create a LoadListener
Grace Kloba8c92c392009-11-08 19:01:55 -0800676 LoadListener loadListener = LoadListener.getLoadListener(mContext,
677 this, url, loaderHandle, synchronous, isMainFramePage,
Patrick Scott7c24ed62009-11-30 13:34:40 -0500678 mainResource, userGesture, postDataIdentifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679
680 mCallbackProxy.onLoadResource(url);
681
682 if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
Grace Klobafadbbd22009-07-17 20:39:25 -0700683 // send an error message, so that loadListener can be deleted
684 // after this is returned. This is important as LoadListener's
685 // nativeError will remove the request from its DocLoader's request
686 // list. But the set up is not done until this method is returned.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 loadListener.error(
688 android.net.http.EventHandler.ERROR, mContext.getString(
689 com.android.internal.R.string.httpErrorTooManyRequests));
Grace Klobafadbbd22009-07-17 20:39:25 -0700690 return loadListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691 }
692
Patrick Scottfe4fec72009-07-14 15:54:30 -0400693 FrameLoader loader = new FrameLoader(loadListener, mSettings, method);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 loader.setHeaders(headers);
695 loader.setPostData(postData);
The Android Open Source Project4df24232009-03-05 14:34:35 -0800696 // Set the load mode to the mode used for the current page.
697 // If WebKit wants validation, go to network directly.
698 loader.setCacheMode(headers.containsKey("If-Modified-Since")
699 || headers.containsKey("If-None-Match") ?
700 WebSettings.LOAD_NO_CACHE : cacheMode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 // Set referrer to current URL?
702 if (!loader.executeLoad()) {
703 checker.responseAlert("startLoadingResource fail");
704 }
705 checker.responseAlert("startLoadingResource succeed");
706
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800707 return !synchronous ? loadListener : null;
708 }
709
710 /**
711 * Set the progress for the browser activity. Called by native code.
712 * Uses a delay so it does not happen too often.
713 * @param newProgress An int between zero and one hundred representing
714 * the current progress percentage of loading the page.
715 */
716 private void setProgress(int newProgress) {
717 mCallbackProxy.onProgressChanged(newProgress);
718 if (newProgress == 100) {
719 sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
720 }
721 // FIXME: Need to figure out a better way to switch out of the history
722 // drawing mode. Maybe we can somehow compare the history picture with
723 // the current picture, and switch when it contains more content.
724 if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
725 mCallbackProxy.switchOutDrawHistory();
726 }
727 }
728
729 /**
730 * Send the icon to the activity for display.
731 * @param icon A Bitmap representing a page's favicon.
732 */
733 private void didReceiveIcon(Bitmap icon) {
734 mCallbackProxy.onReceivedIcon(icon);
735 }
736
Patrick Scott2ba12622009-08-04 13:20:05 -0400737 // Called by JNI when an apple-touch-icon attribute was found.
Patrick Scottd58ccff2009-09-18 16:29:00 -0400738 private void didReceiveTouchIconUrl(String url, boolean precomposed) {
739 mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
Patrick Scott2ba12622009-08-04 13:20:05 -0400740 }
741
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 /**
743 * Request a new window from the client.
744 * @return The BrowserFrame object stored in the new WebView.
745 */
746 private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
747 WebView w = mCallbackProxy.createWindow(dialog, userGesture);
748 if (w != null) {
749 return w.getWebViewCore().getBrowserFrame();
750 }
751 return null;
752 }
753
754 /**
755 * Try to focus this WebView.
756 */
757 private void requestFocus() {
758 mCallbackProxy.onRequestFocus();
759 }
760
761 /**
762 * Close this frame and window.
763 */
764 private void closeWindow(WebViewCore w) {
765 mCallbackProxy.onCloseWindow(w.getWebView());
766 }
767
768 // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
769 static final int POLICY_USE = 0;
770 static final int POLICY_IGNORE = 2;
771
772 private void decidePolicyForFormResubmission(int policyFunction) {
773 Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
774 POLICY_IGNORE);
775 Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
776 POLICY_USE);
777 mCallbackProxy.onFormResubmission(dontResend, resend);
778 }
779
780 /**
781 * Tell the activity to update its global history.
782 */
783 private void updateVisitedHistory(String url, boolean isReload) {
784 mCallbackProxy.doUpdateVisitedHistory(url, isReload);
785 }
786
787 /**
788 * Get the CallbackProxy for sending messages to the UI thread.
789 */
790 /* package */ CallbackProxy getCallbackProxy() {
791 return mCallbackProxy;
792 }
793
794 /**
795 * Returns the User Agent used by this frame
796 */
797 String getUserAgentString() {
798 return mSettings.getUserAgentString();
799 }
800
Leon Scrogginsaacced62009-12-11 15:01:22 -0500801 // These ids need to be in sync with enum rawResId in PlatformBridge.h
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 private static final int NODOMAIN = 1;
803 private static final int LOADERROR = 2;
Grace Kloba83031582009-09-02 16:21:42 -0700804 private static final int DRAWABLEDIR = 3;
Leon Scrogginsaacced62009-12-11 15:01:22 -0500805 private static final int FILE_UPLOAD_LABEL = 4;
806 private static final int RESET_LABEL = 5;
807 private static final int SUBMIT_LABEL = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808
809 String getRawResFilename(int id) {
810 int resid;
811 switch (id) {
812 case NODOMAIN:
813 resid = com.android.internal.R.raw.nodomain;
814 break;
815
816 case LOADERROR:
817 resid = com.android.internal.R.raw.loaderror;
818 break;
819
Grace Kloba83031582009-09-02 16:21:42 -0700820 case DRAWABLEDIR:
821 // use one known resource to find the drawable directory
822 resid = com.android.internal.R.drawable.btn_check_off;
823 break;
824
Leon Scrogginsaacced62009-12-11 15:01:22 -0500825 case FILE_UPLOAD_LABEL:
826 return mContext.getResources().getString(
827 com.android.internal.R.string.upload_file);
828
829 case RESET_LABEL:
830 return mContext.getResources().getString(
831 com.android.internal.R.string.reset);
832
833 case SUBMIT_LABEL:
834 return mContext.getResources().getString(
835 com.android.internal.R.string.submit);
836
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 default:
838 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
Cary Clark686cf752009-08-11 16:08:52 -0400839 return "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 }
841 TypedValue value = new TypedValue();
842 mContext.getResources().getValue(resid, value, true);
Grace Kloba83031582009-09-02 16:21:42 -0700843 if (id == DRAWABLEDIR) {
844 String path = value.string.toString();
845 int index = path.lastIndexOf('/');
846 if (index < 0) {
847 Log.e(LOGTAG, "Can't find drawable directory.");
848 return "";
849 }
850 return path.substring(0, index + 1);
851 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800852 return value.string.toString();
853 }
854
Grace Kloba408cf852009-09-20 16:34:44 -0700855 private float density() {
856 return mContext.getResources().getDisplayMetrics().density;
857 }
858
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 //==========================================================================
860 // native functions
861 //==========================================================================
862
863 /**
864 * Create a new native frame for a given WebView
865 * @param w A WebView that the frame draws into.
866 * @param am AssetManager to use to get assets.
867 * @param list The native side will add and remove items from this list as
868 * the native list changes.
869 */
870 private native void nativeCreateFrame(WebViewCore w, AssetManager am,
871 WebBackForwardList list);
872
873 /**
874 * Destroy the native frame.
875 */
876 public native void nativeDestroyFrame();
877
878 private native void nativeCallPolicyFunction(int policyFunction,
879 int decision);
880
881 /**
882 * Reload the current main frame.
883 */
884 public native void reload(boolean allowStale);
885
886 /**
887 * Go back or forward the number of steps given.
888 * @param steps A negative or positive number indicating the direction
889 * and number of steps to move.
890 */
891 private native void nativeGoBackOrForward(int steps);
892
893 /**
894 * stringByEvaluatingJavaScriptFromString will execute the
895 * JS passed in in the context of this browser frame.
896 * @param script A javascript string to execute
897 *
898 * @return string result of execution or null
899 */
900 public native String stringByEvaluatingJavaScriptFromString(String script);
901
902 /**
903 * Add a javascript interface to the main frame.
904 */
905 private native void nativeAddJavascriptInterface(int nativeFramePointer,
906 Object obj, String interfaceName);
907
908 /**
909 * Enable or disable the native cache.
910 */
911 /* FIXME: The native cache is always on for now until we have a better
912 * solution for our 2 caches. */
913 private native void setCacheDisabled(boolean disabled);
914
915 public native boolean cacheDisabled();
916
917 public native void clearCache();
918
919 /**
920 * Returns false if the url is bad.
921 */
Grace Klobad0d9bc22010-01-26 18:08:28 -0800922 private native void nativeLoadUrl(String url, Map<String, String> headers);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923
Grace Kloba57534302009-05-22 18:55:02 -0700924 private native void nativePostUrl(String url, byte[] postData);
925
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 private native void nativeLoadData(String baseUrl, String data,
927 String mimeType, String encoding, String failUrl);
928
929 /**
930 * Stop loading the current page.
931 */
The Android Open Source Project4df24232009-03-05 14:34:35 -0800932 public void stopLoading() {
933 if (mIsMainFrame) {
934 resetLoadingStates();
935 }
936 nativeStopLoading();
937 }
938
939 private native void nativeStopLoading();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800940
941 /**
942 * Return true if the document has images.
943 */
944 public native boolean documentHasImages();
945
946 /**
947 * @return TRUE if there is a password field in the current frame
948 */
949 private native boolean hasPasswordField();
950
951 /**
952 * Get username and password in the current frame. If found, String[0] is
953 * username and String[1] is password. Otherwise return NULL.
954 * @return String[]
955 */
956 private native String[] getUsernamePassword();
957
958 /**
959 * Set username and password to the proper fields in the current frame
960 * @param username
961 * @param password
962 */
963 private native void setUsernamePassword(String username, String password);
964
965 /**
966 * Get form's "text" type data associated with the current frame.
967 * @return HashMap If succeed, returns a list of name/value pair. Otherwise
968 * returns null.
969 */
970 private native HashMap getFormTextData();
Patrick Scottf06364b2009-12-02 08:57:09 -0500971
972 private native void nativeOrientationChanged(int orientation);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973}