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