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