• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.database.Cursor;
25 import android.graphics.Bitmap;
26 import android.net.ParseException;
27 import android.net.Uri;
28 import android.net.WebAddress;
29 import android.net.http.SslCertificate;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.provider.OpenableColumns;
33 import android.util.Log;
34 import android.util.TypedValue;
35 import android.view.Surface;
36 import android.view.ViewRoot;
37 import android.view.WindowManager;
38 
39 import junit.framework.Assert;
40 
41 import java.io.InputStream;
42 import java.lang.ref.WeakReference;
43 import java.net.URLEncoder;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.Map;
47 import java.util.Iterator;
48 
49 class BrowserFrame extends Handler {
50 
51     private static final String LOGTAG = "webkit";
52 
53     /**
54      * Cap the number of LoadListeners that will be instantiated, so
55      * we don't blow the GREF count.  Attempting to queue more than
56      * this many requests will prompt an error() callback on the
57      * request's LoadListener
58      */
59     private final static int MAX_OUTSTANDING_REQUESTS = 300;
60 
61     private final CallbackProxy mCallbackProxy;
62     private final WebSettings mSettings;
63     private final Context mContext;
64     private final WebViewDatabase mDatabase;
65     private final WebViewCore mWebViewCore;
66     /* package */ boolean mLoadInitFromJava;
67     private int mLoadType;
68     private boolean mFirstLayoutDone = true;
69     private boolean mCommitted = true;
70     // Flag for blocking messages. This is used during destroy() so
71     // that if the UI thread posts any messages after the message
72     // queue has been cleared,they are ignored.
73     private boolean mBlockMessages = false;
74     private int mOrientation = -1;
75 
76     // Is this frame the main frame?
77     private boolean mIsMainFrame;
78 
79     // Attached Javascript interfaces
80     private Map<String, Object> mJSInterfaceMap;
81 
82     // message ids
83     // a message posted when a frame loading is completed
84     static final int FRAME_COMPLETED = 1001;
85     // orientation change message
86     static final int ORIENTATION_CHANGED = 1002;
87     // a message posted when the user decides the policy
88     static final int POLICY_FUNCTION = 1003;
89 
90     // Note: need to keep these in sync with FrameLoaderTypes.h in native
91     static final int FRAME_LOADTYPE_STANDARD = 0;
92     static final int FRAME_LOADTYPE_BACK = 1;
93     static final int FRAME_LOADTYPE_FORWARD = 2;
94     static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
95     static final int FRAME_LOADTYPE_RELOAD = 4;
96     static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
97     static final int FRAME_LOADTYPE_SAME = 6;
98     static final int FRAME_LOADTYPE_REDIRECT = 7;
99     static final int FRAME_LOADTYPE_REPLACE = 8;
100 
101     // A progress threshold to switch from history Picture to live Picture
102     private static final int TRANSITION_SWITCH_THRESHOLD = 75;
103 
104     // This is a field accessed by native code as well as package classes.
105     /*package*/ int mNativeFrame;
106 
107     // Static instance of a JWebCoreJavaBridge to handle timer and cookie
108     // requests from WebCore.
109     static JWebCoreJavaBridge sJavaBridge;
110 
111     private static class ConfigCallback implements ComponentCallbacks {
112         private final ArrayList<WeakReference<Handler>> mHandlers =
113                 new ArrayList<WeakReference<Handler>>();
114         private final WindowManager mWindowManager;
115 
ConfigCallback(WindowManager wm)116         ConfigCallback(WindowManager wm) {
117             mWindowManager = wm;
118         }
119 
addHandler(Handler h)120         public synchronized void addHandler(Handler h) {
121             // No need to ever remove a Handler. If the BrowserFrame is
122             // destroyed, it will be collected and the WeakReference set to
123             // null. If it happens to still be around during a configuration
124             // change, the message will be ignored.
125             mHandlers.add(new WeakReference<Handler>(h));
126         }
127 
onConfigurationChanged(Configuration newConfig)128         public void onConfigurationChanged(Configuration newConfig) {
129             if (mHandlers.size() == 0) {
130                 return;
131             }
132             int orientation =
133                     mWindowManager.getDefaultDisplay().getOrientation();
134             switch (orientation) {
135                 case Surface.ROTATION_90:
136                     orientation = 90;
137                     break;
138                 case Surface.ROTATION_180:
139                     orientation = 180;
140                     break;
141                 case Surface.ROTATION_270:
142                     orientation = -90;
143                     break;
144                 case Surface.ROTATION_0:
145                     orientation = 0;
146                     break;
147                 default:
148                     break;
149             }
150             synchronized (this) {
151                 // Create a list of handlers to remove. Go ahead and make it
152                 // the same size to avoid resizing.
153                 ArrayList<WeakReference> handlersToRemove =
154                         new ArrayList<WeakReference>(mHandlers.size());
155                 for (WeakReference<Handler> wh : mHandlers) {
156                     Handler h = wh.get();
157                     if (h != null) {
158                         h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
159                                     orientation, 0));
160                     } else {
161                         handlersToRemove.add(wh);
162                     }
163                 }
164                 // Now remove all the null references.
165                 for (WeakReference weak : handlersToRemove) {
166                     mHandlers.remove(weak);
167                 }
168             }
169         }
170 
onLowMemory()171         public void onLowMemory() {}
172     }
173     static ConfigCallback sConfigCallback;
174 
175     /**
176      * Create a new BrowserFrame to be used in an application.
177      * @param context An application context to use when retrieving assets.
178      * @param w A WebViewCore used as the view for this frame.
179      * @param proxy A CallbackProxy for posting messages to the UI thread and
180      *              querying a client for information.
181      * @param settings A WebSettings object that holds all settings.
182      * XXX: Called by WebCore thread.
183      */
BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy, WebSettings settings, Map<String, Object> javascriptInterfaces)184     public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
185             WebSettings settings, Map<String, Object> javascriptInterfaces) {
186 
187         Context appContext = context.getApplicationContext();
188 
189         // Create a global JWebCoreJavaBridge to handle timers and
190         // cookies in the WebCore thread.
191         if (sJavaBridge == null) {
192             sJavaBridge = new JWebCoreJavaBridge();
193             // set WebCore native cache size
194             ActivityManager am = (ActivityManager) context
195                     .getSystemService(Context.ACTIVITY_SERVICE);
196             if (am.getMemoryClass() > 16) {
197                 sJavaBridge.setCacheSize(8 * 1024 * 1024);
198             } else {
199                 sJavaBridge.setCacheSize(4 * 1024 * 1024);
200             }
201             // initialize CacheManager
202             CacheManager.init(appContext);
203             // create CookieSyncManager with current Context
204             CookieSyncManager.createInstance(appContext);
205             // create PluginManager with current Context
206             PluginManager.getInstance(appContext);
207         }
208 
209         if (sConfigCallback == null) {
210             sConfigCallback = new ConfigCallback(
211                     (WindowManager) context.getSystemService(
212                             Context.WINDOW_SERVICE));
213             ViewRoot.addConfigCallback(sConfigCallback);
214         }
215         sConfigCallback.addHandler(this);
216 
217         mJSInterfaceMap = javascriptInterfaces;
218 
219         mSettings = settings;
220         mContext = context;
221         mCallbackProxy = proxy;
222         mDatabase = WebViewDatabase.getInstance(appContext);
223         mWebViewCore = w;
224 
225         AssetManager am = context.getAssets();
226         nativeCreateFrame(w, am, proxy.getBackForwardList());
227 
228         if (DebugFlags.BROWSER_FRAME) {
229             Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
230         }
231     }
232 
233     /**
234      * Load a url from the network or the filesystem into the main frame.
235      * Following the same behaviour as Safari, javascript: URLs are not passed
236      * to the main frame, instead they are evaluated immediately.
237      * @param url The url to load.
238      * @param extraHeaders The extra headers sent with this url. This should not
239      *            include the common headers like "user-agent". If it does, it
240      *            will be replaced by the intrinsic value of the WebView.
241      */
loadUrl(String url, Map<String, String> extraHeaders)242     public void loadUrl(String url, Map<String, String> extraHeaders) {
243         mLoadInitFromJava = true;
244         if (URLUtil.isJavaScriptUrl(url)) {
245             // strip off the scheme and evaluate the string
246             stringByEvaluatingJavaScriptFromString(
247                     url.substring("javascript:".length()));
248         } else {
249             nativeLoadUrl(url, extraHeaders);
250         }
251         mLoadInitFromJava = false;
252     }
253 
254     /**
255      * Load a url with "POST" method from the network into the main frame.
256      * @param url The url to load.
257      * @param data The data for POST request.
258      */
postUrl(String url, byte[] data)259     public void postUrl(String url, byte[] data) {
260         mLoadInitFromJava = true;
261         nativePostUrl(url, data);
262         mLoadInitFromJava = false;
263     }
264 
265     /**
266      * Load the content as if it was loaded by the provided base URL. The
267      * historyUrl is used as the history entry for the load data.
268      *
269      * @param baseUrl Base URL used to resolve relative paths in the content
270      * @param data Content to render in the browser
271      * @param mimeType Mimetype of the data being passed in
272      * @param encoding Character set encoding of the provided data.
273      * @param historyUrl URL to use as the history entry.
274      */
loadData(String baseUrl, String data, String mimeType, String encoding, String historyUrl)275     public void loadData(String baseUrl, String data, String mimeType,
276             String encoding, String historyUrl) {
277         mLoadInitFromJava = true;
278         if (historyUrl == null || historyUrl.length() == 0) {
279             historyUrl = "about:blank";
280         }
281         if (data == null) {
282             data = "";
283         }
284 
285         // Setup defaults for missing values. These defaults where taken from
286         // WebKit's WebFrame.mm
287         if (baseUrl == null || baseUrl.length() == 0) {
288             baseUrl = "about:blank";
289         }
290         if (mimeType == null || mimeType.length() == 0) {
291             mimeType = "text/html";
292         }
293         nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
294         mLoadInitFromJava = false;
295     }
296 
297     /**
298      * Go back or forward the number of steps given.
299      * @param steps A negative or positive number indicating the direction
300      *              and number of steps to move.
301      */
goBackOrForward(int steps)302     public void goBackOrForward(int steps) {
303         mLoadInitFromJava = true;
304         nativeGoBackOrForward(steps);
305         mLoadInitFromJava = false;
306     }
307 
308     /**
309      * native callback
310      * Report an error to an activity.
311      * @param errorCode The HTTP error code.
312      * @param description A String description.
313      * TODO: Report all errors including resource errors but include some kind
314      * of domain identifier. Change errorCode to an enum for a cleaner
315      * interface.
316      */
reportError(final int errorCode, final String description, final String failingUrl)317     private void reportError(final int errorCode, final String description,
318             final String failingUrl) {
319         // As this is called for the main resource and loading will be stopped
320         // after, reset the state variables.
321         resetLoadingStates();
322         mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
323     }
324 
resetLoadingStates()325     private void resetLoadingStates() {
326         mCommitted = true;
327         mFirstLayoutDone = true;
328     }
329 
committed()330     /* package */boolean committed() {
331         return mCommitted;
332     }
333 
firstLayoutDone()334     /* package */boolean firstLayoutDone() {
335         return mFirstLayoutDone;
336     }
337 
loadType()338     /* package */int loadType() {
339         return mLoadType;
340     }
341 
didFirstLayout()342     /* package */void didFirstLayout() {
343         if (!mFirstLayoutDone) {
344             mFirstLayoutDone = true;
345             // ensure {@link WebViewCore#webkitDraw} is called as we were
346             // blocking the update in {@link #loadStarted}
347             mWebViewCore.contentDraw();
348         }
349     }
350 
351     /**
352      * native callback
353      * Indicates the beginning of a new load.
354      * This method will be called once for the main frame.
355      */
loadStarted(String url, Bitmap favicon, int loadType, boolean isMainFrame)356     private void loadStarted(String url, Bitmap favicon, int loadType,
357             boolean isMainFrame) {
358         mIsMainFrame = isMainFrame;
359 
360         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
361             mLoadType = loadType;
362 
363             if (isMainFrame) {
364                 // Call onPageStarted for main frames.
365                 mCallbackProxy.onPageStarted(url, favicon);
366                 // as didFirstLayout() is only called for the main frame, reset
367                 // mFirstLayoutDone only for the main frames
368                 mFirstLayoutDone = false;
369                 mCommitted = false;
370                 // remove pending draw to block update until mFirstLayoutDone is
371                 // set to true in didFirstLayout()
372                 mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
373             }
374 
375             // Note: only saves committed form data in standard load
376             if (loadType == FRAME_LOADTYPE_STANDARD
377                     && mSettings.getSaveFormData()) {
378                 final WebHistoryItem h = mCallbackProxy.getBackForwardList()
379                         .getCurrentItem();
380                 if (h != null) {
381                     String currentUrl = h.getUrl();
382                     if (currentUrl != null) {
383                         mDatabase.setFormData(currentUrl, getFormTextData());
384                     }
385                 }
386             }
387         }
388     }
389 
390     /**
391      * native callback
392      * Indicates the WebKit has committed to the new load
393      */
transitionToCommitted(int loadType, boolean isMainFrame)394     private void transitionToCommitted(int loadType, boolean isMainFrame) {
395         // loadType is not used yet
396         if (isMainFrame) {
397             mCommitted = true;
398             mWebViewCore.getWebView().mViewManager.postResetStateAll();
399         }
400     }
401 
402     /**
403      * native callback
404      * <p>
405      * Indicates the end of a new load.
406      * This method will be called once for the main frame.
407      */
loadFinished(String url, int loadType, boolean isMainFrame)408     private void loadFinished(String url, int loadType, boolean isMainFrame) {
409         // mIsMainFrame and isMainFrame are better be equal!!!
410 
411         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
412             if (isMainFrame) {
413                 resetLoadingStates();
414                 mCallbackProxy.switchOutDrawHistory();
415                 mCallbackProxy.onPageFinished(url);
416             }
417         }
418     }
419 
420     /**
421      * We have received an SSL certificate for the main top-level page.
422      *
423      * !!!Called from the network thread!!!
424      */
certificate(SslCertificate certificate)425     void certificate(SslCertificate certificate) {
426         if (mIsMainFrame) {
427             // we want to make this call even if the certificate is null
428             // (ie, the site is not secure)
429             mCallbackProxy.onReceivedCertificate(certificate);
430         }
431     }
432 
433     /**
434      * Destroy all native components of the BrowserFrame.
435      */
destroy()436     public void destroy() {
437         nativeDestroyFrame();
438         mBlockMessages = true;
439         removeCallbacksAndMessages(null);
440     }
441 
442     /**
443      * Handle messages posted to us.
444      * @param msg The message to handle.
445      */
446     @Override
handleMessage(Message msg)447     public void handleMessage(Message msg) {
448         if (mBlockMessages) {
449             return;
450         }
451         switch (msg.what) {
452             case FRAME_COMPLETED: {
453                 if (mSettings.getSavePassword() && hasPasswordField()) {
454                     WebHistoryItem item = mCallbackProxy.getBackForwardList()
455                             .getCurrentItem();
456                     if (item != null) {
457                         WebAddress uri = new WebAddress(item.getUrl());
458                         String schemePlusHost = uri.mScheme + uri.mHost;
459                         String[] up =
460                                 mDatabase.getUsernamePassword(schemePlusHost);
461                         if (up != null && up[0] != null) {
462                             setUsernamePassword(up[0], up[1]);
463                         }
464                     }
465                 }
466                 WebViewWorker.getHandler().sendEmptyMessage(
467                         WebViewWorker.MSG_TRIM_CACHE);
468                 break;
469             }
470 
471             case POLICY_FUNCTION: {
472                 nativeCallPolicyFunction(msg.arg1, msg.arg2);
473                 break;
474             }
475 
476             case ORIENTATION_CHANGED: {
477                 if (mOrientation != msg.arg1) {
478                     mOrientation = msg.arg1;
479                     nativeOrientationChanged(msg.arg1);
480                 }
481                 break;
482             }
483 
484             default:
485                 break;
486         }
487     }
488 
489     /**
490      * Punch-through for WebCore to set the document
491      * title. Inform the Activity of the new title.
492      * @param title The new title of the document.
493      */
setTitle(String title)494     private void setTitle(String title) {
495         // FIXME: The activity must call getTitle (a native method) to get the
496         // title. We should try and cache the title if we can also keep it in
497         // sync with the document.
498         mCallbackProxy.onReceivedTitle(title);
499     }
500 
501     /**
502      * Retrieves the render tree of this frame and puts it as the object for
503      * the message and sends the message.
504      * @param callback the message to use to send the render tree
505      */
externalRepresentation(Message callback)506     public void externalRepresentation(Message callback) {
507         callback.obj = externalRepresentation();;
508         callback.sendToTarget();
509     }
510 
511     /**
512      * Return the render tree as a string
513      */
externalRepresentation()514     private native String externalRepresentation();
515 
516     /**
517      * Retrieves the visual text of the current frame, puts it as the object for
518      * the message and sends the message.
519      * @param callback the message to use to send the visual text
520      */
documentAsText(Message callback)521     public void documentAsText(Message callback) {
522         callback.obj = documentAsText();;
523         callback.sendToTarget();
524     }
525 
526     /**
527      * Return the text drawn on the screen as a string
528      */
documentAsText()529     private native String documentAsText();
530 
531     /*
532      * This method is called by WebCore to inform the frame that
533      * the Javascript window object has been cleared.
534      * We should re-attach any attached js interfaces.
535      */
windowObjectCleared(int nativeFramePointer)536     private void windowObjectCleared(int nativeFramePointer) {
537         if (mJSInterfaceMap != null) {
538             Iterator iter = mJSInterfaceMap.keySet().iterator();
539             while (iter.hasNext())  {
540                 String interfaceName = (String) iter.next();
541                 nativeAddJavascriptInterface(nativeFramePointer,
542                         mJSInterfaceMap.get(interfaceName), interfaceName);
543             }
544         }
545     }
546 
547     /**
548      * This method is called by WebCore to check whether application
549      * wants to hijack url loading
550      */
handleUrl(String url)551     public boolean handleUrl(String url) {
552         if (mLoadInitFromJava == true) {
553             return false;
554         }
555         if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
556             // if the url is hijacked, reset the state of the BrowserFrame
557             didFirstLayout();
558             return true;
559         } else {
560             return false;
561         }
562     }
563 
addJavascriptInterface(Object obj, String interfaceName)564     public void addJavascriptInterface(Object obj, String interfaceName) {
565         if (mJSInterfaceMap == null) {
566             mJSInterfaceMap = new HashMap<String, Object>();
567         }
568         if (mJSInterfaceMap.containsKey(interfaceName)) {
569             mJSInterfaceMap.remove(interfaceName);
570         }
571         mJSInterfaceMap.put(interfaceName, obj);
572     }
573 
574     /**
575      * Called by JNI.  Given a URI, find the associated file and return its size
576      * @param uri A String representing the URI of the desired file.
577      * @return int The size of the given file.
578      */
getFileSize(String uri)579     private int getFileSize(String uri) {
580         int size = 0;
581         try {
582             InputStream stream = mContext.getContentResolver()
583                             .openInputStream(Uri.parse(uri));
584             size = stream.available();
585             stream.close();
586         } catch (Exception e) {}
587         return size;
588     }
589 
590     /**
591      * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
592      * copy the resource into buffer.
593      * @param uri A String representing the URI of the desired file.
594      * @param buffer The byte array to copy the data into.
595      * @param offset The offet into buffer to place the data.
596      * @param expectedSize The size that the buffer has allocated for this file.
597      * @return int The size of the given file, or zero if it fails.
598      */
getFile(String uri, byte[] buffer, int offset, int expectedSize)599     private int getFile(String uri, byte[] buffer, int offset,
600             int expectedSize) {
601         int size = 0;
602         try {
603             InputStream stream = mContext.getContentResolver()
604                             .openInputStream(Uri.parse(uri));
605             size = stream.available();
606             if (size <= expectedSize && buffer != null
607                     && buffer.length - offset >= size) {
608                 stream.read(buffer, offset, size);
609             } else {
610                 size = 0;
611             }
612             stream.close();
613         } catch (java.io.FileNotFoundException e) {
614             Log.e(LOGTAG, "FileNotFoundException:" + e);
615             size = 0;
616         } catch (java.io.IOException e2) {
617             Log.e(LOGTAG, "IOException: " + e2);
618             size = 0;
619         }
620         return size;
621     }
622 
623     /**
624      * Start loading a resource.
625      * @param loaderHandle The native ResourceLoader that is the target of the
626      *                     data.
627      * @param url The url to load.
628      * @param method The http method.
629      * @param headers The http headers.
630      * @param postData If the method is "POST" postData is sent as the request
631      *                 body. Is null when empty.
632      * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
633      * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
634      * @param mainResource True if the this resource is the main request, not a supporting resource
635      * @param userGesture
636      * @param synchronous True if the load is synchronous.
637      * @return A newly created LoadListener object.
638      */
startLoadingResource(int loaderHandle, String url, String method, HashMap headers, byte[] postData, long postDataIdentifier, int cacheMode, boolean mainResource, boolean userGesture, boolean synchronous, String username, String password)639     private LoadListener startLoadingResource(int loaderHandle,
640                                               String url,
641                                               String method,
642                                               HashMap headers,
643                                               byte[] postData,
644                                               long postDataIdentifier,
645                                               int cacheMode,
646                                               boolean mainResource,
647                                               boolean userGesture,
648                                               boolean synchronous,
649                                               String username,
650                                               String password) {
651         PerfChecker checker = new PerfChecker();
652 
653         if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
654             cacheMode = mSettings.getCacheMode();
655         }
656 
657         if (method.equals("POST")) {
658             // Don't use the cache on POSTs when issuing a normal POST
659             // request.
660             if (cacheMode == WebSettings.LOAD_NORMAL) {
661                 cacheMode = WebSettings.LOAD_NO_CACHE;
662             }
663             if (mSettings.getSavePassword() && hasPasswordField()) {
664                 try {
665                     if (DebugFlags.BROWSER_FRAME) {
666                         Assert.assertNotNull(mCallbackProxy.getBackForwardList()
667                                 .getCurrentItem());
668                     }
669                     WebAddress uri = new WebAddress(mCallbackProxy
670                             .getBackForwardList().getCurrentItem().getUrl());
671                     String schemePlusHost = uri.mScheme + uri.mHost;
672                     String[] ret = getUsernamePassword();
673                     // Has the user entered a username/password pair and is
674                     // there some POST data
675                     if (ret != null && postData != null &&
676                             ret[0].length() > 0 && ret[1].length() > 0) {
677                         // Check to see if the username & password appear in
678                         // the post data (there could be another form on the
679                         // page and that was posted instead.
680                         String postString = new String(postData);
681                         if (postString.contains(URLEncoder.encode(ret[0])) &&
682                                 postString.contains(URLEncoder.encode(ret[1]))) {
683                             String[] saved = mDatabase.getUsernamePassword(
684                                     schemePlusHost);
685                             if (saved != null) {
686                                 // null username implies that user has chosen not to
687                                 // save password
688                                 if (saved[0] != null) {
689                                     // non-null username implies that user has
690                                     // chosen to save password, so update the
691                                     // recorded password
692                                     mDatabase.setUsernamePassword(
693                                             schemePlusHost, ret[0], ret[1]);
694                                 }
695                             } else {
696                                 // CallbackProxy will handle creating the resume
697                                 // message
698                                 mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
699                                         ret[1], null);
700                             }
701                         }
702                     }
703                 } catch (ParseException ex) {
704                     // if it is bad uri, don't save its password
705                 }
706 
707             }
708         }
709 
710         // is this resource the main-frame top-level page?
711         boolean isMainFramePage = mIsMainFrame;
712 
713         if (DebugFlags.BROWSER_FRAME) {
714             Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
715                     + method + ", postData=" + postData + ", isMainFramePage="
716                     + isMainFramePage + ", mainResource=" + mainResource
717                     + ", userGesture=" + userGesture);
718         }
719 
720         // Create a LoadListener
721         LoadListener loadListener = LoadListener.getLoadListener(mContext,
722                 this, url, loaderHandle, synchronous, isMainFramePage,
723                 mainResource, userGesture, postDataIdentifier, username, password);
724 
725         mCallbackProxy.onLoadResource(url);
726 
727         if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
728             // send an error message, so that loadListener can be deleted
729             // after this is returned. This is important as LoadListener's
730             // nativeError will remove the request from its DocLoader's request
731             // list. But the set up is not done until this method is returned.
732             loadListener.error(
733                     android.net.http.EventHandler.ERROR, mContext.getString(
734                             com.android.internal.R.string.httpErrorTooManyRequests));
735             return loadListener;
736         }
737 
738         FrameLoader loader = new FrameLoader(loadListener, mSettings, method);
739         loader.setHeaders(headers);
740         loader.setPostData(postData);
741         // Set the load mode to the mode used for the current page.
742         // If WebKit wants validation, go to network directly.
743         loader.setCacheMode(headers.containsKey("If-Modified-Since")
744                 || headers.containsKey("If-None-Match") ?
745                         WebSettings.LOAD_NO_CACHE : cacheMode);
746         // Set referrer to current URL?
747         if (!loader.executeLoad()) {
748             checker.responseAlert("startLoadingResource fail");
749         }
750         checker.responseAlert("startLoadingResource succeed");
751 
752         return !synchronous ? loadListener : null;
753     }
754 
755     /**
756      * Set the progress for the browser activity.  Called by native code.
757      * Uses a delay so it does not happen too often.
758      * @param newProgress An int between zero and one hundred representing
759      *                    the current progress percentage of loading the page.
760      */
setProgress(int newProgress)761     private void setProgress(int newProgress) {
762         mCallbackProxy.onProgressChanged(newProgress);
763         if (newProgress == 100) {
764             sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
765         }
766         // FIXME: Need to figure out a better way to switch out of the history
767         // drawing mode. Maybe we can somehow compare the history picture with
768         // the current picture, and switch when it contains more content.
769         if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
770             mCallbackProxy.switchOutDrawHistory();
771         }
772     }
773 
774     /**
775      * Send the icon to the activity for display.
776      * @param icon A Bitmap representing a page's favicon.
777      */
didReceiveIcon(Bitmap icon)778     private void didReceiveIcon(Bitmap icon) {
779         mCallbackProxy.onReceivedIcon(icon);
780     }
781 
782     // Called by JNI when an apple-touch-icon attribute was found.
didReceiveTouchIconUrl(String url, boolean precomposed)783     private void didReceiveTouchIconUrl(String url, boolean precomposed) {
784         mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
785     }
786 
787     /**
788      * Request a new window from the client.
789      * @return The BrowserFrame object stored in the new WebView.
790      */
createWindow(boolean dialog, boolean userGesture)791     private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
792         return mCallbackProxy.createWindow(dialog, userGesture);
793     }
794 
795     /**
796      * Try to focus this WebView.
797      */
requestFocus()798     private void requestFocus() {
799         mCallbackProxy.onRequestFocus();
800     }
801 
802     /**
803      * Close this frame and window.
804      */
closeWindow(WebViewCore w)805     private void closeWindow(WebViewCore w) {
806         mCallbackProxy.onCloseWindow(w.getWebView());
807     }
808 
809     // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
810     static final int POLICY_USE = 0;
811     static final int POLICY_IGNORE = 2;
812 
decidePolicyForFormResubmission(int policyFunction)813     private void decidePolicyForFormResubmission(int policyFunction) {
814         Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
815                 POLICY_IGNORE);
816         Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
817                 POLICY_USE);
818         mCallbackProxy.onFormResubmission(dontResend, resend);
819     }
820 
821     /**
822      * Tell the activity to update its global history.
823      */
updateVisitedHistory(String url, boolean isReload)824     private void updateVisitedHistory(String url, boolean isReload) {
825         mCallbackProxy.doUpdateVisitedHistory(url, isReload);
826     }
827 
828     /**
829      * Get the CallbackProxy for sending messages to the UI thread.
830      */
getCallbackProxy()831     /* package */ CallbackProxy getCallbackProxy() {
832         return mCallbackProxy;
833     }
834 
835     /**
836      * Returns the User Agent used by this frame
837      */
getUserAgentString()838     String getUserAgentString() {
839         return mSettings.getUserAgentString();
840     }
841 
842     // These ids need to be in sync with enum rawResId in PlatformBridge.h
843     private static final int NODOMAIN = 1;
844     private static final int LOADERROR = 2;
845     private static final int DRAWABLEDIR = 3;
846     private static final int FILE_UPLOAD_LABEL = 4;
847     private static final int RESET_LABEL = 5;
848     private static final int SUBMIT_LABEL = 6;
849 
getRawResFilename(int id)850     String getRawResFilename(int id) {
851         int resid;
852         switch (id) {
853             case NODOMAIN:
854                 resid = com.android.internal.R.raw.nodomain;
855                 break;
856 
857             case LOADERROR:
858                 resid = com.android.internal.R.raw.loaderror;
859                 break;
860 
861             case DRAWABLEDIR:
862                 // use one known resource to find the drawable directory
863                 resid = com.android.internal.R.drawable.btn_check_off;
864                 break;
865 
866             case FILE_UPLOAD_LABEL:
867                 return mContext.getResources().getString(
868                         com.android.internal.R.string.upload_file);
869 
870             case RESET_LABEL:
871                 return mContext.getResources().getString(
872                         com.android.internal.R.string.reset);
873 
874             case SUBMIT_LABEL:
875                 return mContext.getResources().getString(
876                         com.android.internal.R.string.submit);
877 
878             default:
879                 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
880                 return "";
881         }
882         TypedValue value = new TypedValue();
883         mContext.getResources().getValue(resid, value, true);
884         if (id == DRAWABLEDIR) {
885             String path = value.string.toString();
886             int index = path.lastIndexOf('/');
887             if (index < 0) {
888                 Log.e(LOGTAG, "Can't find drawable directory.");
889                 return "";
890             }
891             return path.substring(0, index + 1);
892         }
893         return value.string.toString();
894     }
895 
density()896     private float density() {
897         return mContext.getResources().getDisplayMetrics().density;
898     }
899 
900     //==========================================================================
901     // native functions
902     //==========================================================================
903 
904     /**
905      * Create a new native frame for a given WebView
906      * @param w     A WebView that the frame draws into.
907      * @param am    AssetManager to use to get assets.
908      * @param list  The native side will add and remove items from this list as
909      *              the native list changes.
910      */
nativeCreateFrame(WebViewCore w, AssetManager am, WebBackForwardList list)911     private native void nativeCreateFrame(WebViewCore w, AssetManager am,
912             WebBackForwardList list);
913 
914     /**
915      * Destroy the native frame.
916      */
nativeDestroyFrame()917     public native void nativeDestroyFrame();
918 
nativeCallPolicyFunction(int policyFunction, int decision)919     private native void nativeCallPolicyFunction(int policyFunction,
920             int decision);
921 
922     /**
923      * Reload the current main frame.
924      */
reload(boolean allowStale)925     public native void reload(boolean allowStale);
926 
927     /**
928      * Go back or forward the number of steps given.
929      * @param steps A negative or positive number indicating the direction
930      *              and number of steps to move.
931      */
nativeGoBackOrForward(int steps)932     private native void nativeGoBackOrForward(int steps);
933 
934     /**
935      * stringByEvaluatingJavaScriptFromString will execute the
936      * JS passed in in the context of this browser frame.
937      * @param script A javascript string to execute
938      *
939      * @return string result of execution or null
940      */
stringByEvaluatingJavaScriptFromString(String script)941     public native String stringByEvaluatingJavaScriptFromString(String script);
942 
943     /**
944      * Add a javascript interface to the main frame.
945      */
nativeAddJavascriptInterface(int nativeFramePointer, Object obj, String interfaceName)946     private native void nativeAddJavascriptInterface(int nativeFramePointer,
947             Object obj, String interfaceName);
948 
949     /**
950      * Enable or disable the native cache.
951      */
952     /* FIXME: The native cache is always on for now until we have a better
953      * solution for our 2 caches. */
setCacheDisabled(boolean disabled)954     private native void setCacheDisabled(boolean disabled);
955 
cacheDisabled()956     public native boolean cacheDisabled();
957 
clearCache()958     public native void clearCache();
959 
960     /**
961      * Returns false if the url is bad.
962      */
nativeLoadUrl(String url, Map<String, String> headers)963     private native void nativeLoadUrl(String url, Map<String, String> headers);
964 
nativePostUrl(String url, byte[] postData)965     private native void nativePostUrl(String url, byte[] postData);
966 
nativeLoadData(String baseUrl, String data, String mimeType, String encoding, String historyUrl)967     private native void nativeLoadData(String baseUrl, String data,
968             String mimeType, String encoding, String historyUrl);
969 
970     /**
971      * Stop loading the current page.
972      */
stopLoading()973     public void stopLoading() {
974         if (mIsMainFrame) {
975             resetLoadingStates();
976         }
977         nativeStopLoading();
978     }
979 
nativeStopLoading()980     private native void nativeStopLoading();
981 
982     /**
983      * Return true if the document has images.
984      */
documentHasImages()985     public native boolean documentHasImages();
986 
987     /**
988      * @return TRUE if there is a password field in the current frame
989      */
hasPasswordField()990     private native boolean hasPasswordField();
991 
992     /**
993      * Get username and password in the current frame. If found, String[0] is
994      * username and String[1] is password. Otherwise return NULL.
995      * @return String[]
996      */
getUsernamePassword()997     private native String[] getUsernamePassword();
998 
999     /**
1000      * Set username and password to the proper fields in the current frame
1001      * @param username
1002      * @param password
1003      */
setUsernamePassword(String username, String password)1004     private native void setUsernamePassword(String username, String password);
1005 
1006     /**
1007      * Get form's "text" type data associated with the current frame.
1008      * @return HashMap If succeed, returns a list of name/value pair. Otherwise
1009      *         returns null.
1010      */
getFormTextData()1011     private native HashMap getFormTextData();
1012 
nativeOrientationChanged(int orientation)1013     private native void nativeOrientationChanged(int orientation);
1014 }
1015