BrowserFrame.java revision 3001a035439d8134a7d70d796376d1dfbff3cdcd
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.content.Context;
20import android.content.res.AssetManager;
21import android.graphics.Bitmap;
22import android.net.ParseException;
23import android.net.WebAddress;
24import android.net.http.SslCertificate;
25import android.os.Handler;
26import android.os.Message;
27import android.util.Config;
28import android.util.Log;
29import android.util.TypedValue;
30
31import junit.framework.Assert;
32
33import java.net.URLEncoder;
34import java.util.HashMap;
35import java.util.Iterator;
36
37class BrowserFrame extends Handler {
38
39    private static final String LOGTAG = "webkit";
40
41    /**
42     * Cap the number of LoadListeners that will be instantiated, so
43     * we don't blow the GREF count.  Attempting to queue more than
44     * this many requests will prompt an error() callback on the
45     * request's LoadListener
46     */
47    private final static int MAX_OUTSTANDING_REQUESTS = 300;
48
49    private final CallbackProxy mCallbackProxy;
50    private final WebSettings mSettings;
51    private final Context mContext;
52    private final WebViewDatabase mDatabase;
53    private final WebViewCore mWebViewCore;
54    /* package */ boolean mLoadInitFromJava;
55    private int mLoadType;
56    private boolean mFirstLayoutDone = true;
57    private boolean mCommitted = true;
58
59    // Is this frame the main frame?
60    private boolean mIsMainFrame;
61
62    // Attached Javascript interfaces
63    private HashMap mJSInterfaceMap;
64
65    // message ids
66    // a message posted when a frame loading is completed
67    static final int FRAME_COMPLETED = 1001;
68    // a message posted when the user decides the policy
69    static final int POLICY_FUNCTION = 1003;
70
71    // Note: need to keep these in sync with FrameLoaderTypes.h in native
72    static final int FRAME_LOADTYPE_STANDARD = 0;
73    static final int FRAME_LOADTYPE_BACK = 1;
74    static final int FRAME_LOADTYPE_FORWARD = 2;
75    static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
76    static final int FRAME_LOADTYPE_RELOAD = 4;
77    static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
78    static final int FRAME_LOADTYPE_SAME = 6;
79    static final int FRAME_LOADTYPE_REDIRECT = 7;
80    static final int FRAME_LOADTYPE_REPLACE = 8;
81
82    // A progress threshold to switch from history Picture to live Picture
83    private static final int TRANSITION_SWITCH_THRESHOLD = 75;
84
85    // This is a field accessed by native code as well as package classes.
86    /*package*/ int mNativeFrame;
87
88    // Static instance of a JWebCoreJavaBridge to handle timer and cookie
89    // requests from WebCore.
90    static JWebCoreJavaBridge sJavaBridge;
91
92    /**
93     * Create a new BrowserFrame to be used in an application.
94     * @param context An application context to use when retrieving assets.
95     * @param w A WebViewCore used as the view for this frame.
96     * @param proxy A CallbackProxy for posting messages to the UI thread and
97     *              querying a client for information.
98     * @param settings A WebSettings object that holds all settings.
99     * XXX: Called by WebCore thread.
100     */
101    public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
102            WebSettings settings) {
103        // Create a global JWebCoreJavaBridge to handle timers and
104        // cookies in the WebCore thread.
105        if (sJavaBridge == null) {
106            sJavaBridge = new JWebCoreJavaBridge();
107            // set WebCore native cache size
108            sJavaBridge.setCacheSize(4 * 1024 * 1024);
109            // initialize CacheManager
110            CacheManager.init(context);
111            // create CookieSyncManager with current Context
112            CookieSyncManager.createInstance(context);
113        }
114        AssetManager am = context.getAssets();
115        nativeCreateFrame(w, am, proxy.getBackForwardList());
116
117        mSettings = settings;
118        mContext = context;
119        mCallbackProxy = proxy;
120        mDatabase = WebViewDatabase.getInstance(context);
121        mWebViewCore = w;
122
123        if (Config.LOGV) {
124            Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
125        }
126    }
127
128    /**
129     * Load a url from the network or the filesystem into the main frame.
130     * Following the same behaviour as Safari, javascript: URLs are not
131     * passed to the main frame, instead they are evaluated immediately.
132     * @param url The url to load.
133     */
134    public void loadUrl(String url) {
135        mLoadInitFromJava = true;
136        if (URLUtil.isJavaScriptUrl(url)) {
137            // strip off the scheme and evaluate the string
138            stringByEvaluatingJavaScriptFromString(
139                    url.substring("javascript:".length()));
140        } else {
141            nativeLoadUrl(url);
142        }
143        mLoadInitFromJava = false;
144    }
145
146    /**
147     * Load the content as if it was loaded by the provided base URL. The
148     * failUrl is used as the history entry for the load data. If null or
149     * an empty string is passed for the failUrl, then no history entry is
150     * created.
151     *
152     * @param baseUrl Base URL used to resolve relative paths in the content
153     * @param data Content to render in the browser
154     * @param mimeType Mimetype of the data being passed in
155     * @param encoding Character set encoding of the provided data.
156     * @param failUrl URL to use if the content fails to load or null.
157     */
158    public void loadData(String baseUrl, String data, String mimeType,
159            String encoding, String failUrl) {
160        mLoadInitFromJava = true;
161        if (failUrl == null) {
162            failUrl = "";
163        }
164        if (data == null) {
165            data = "";
166        }
167
168        // Setup defaults for missing values. These defaults where taken from
169        // WebKit's WebFrame.mm
170        if (baseUrl == null || baseUrl.length() == 0) {
171            baseUrl = "about:blank";
172        }
173        if (mimeType == null || mimeType.length() == 0) {
174            mimeType = "text/html";
175        }
176        nativeLoadData(baseUrl, data, mimeType, encoding, failUrl);
177        mLoadInitFromJava = false;
178    }
179
180    /**
181     * Go back or forward the number of steps given.
182     * @param steps A negative or positive number indicating the direction
183     *              and number of steps to move.
184     */
185    public void goBackOrForward(int steps) {
186        mLoadInitFromJava = true;
187        nativeGoBackOrForward(steps);
188        mLoadInitFromJava = false;
189    }
190
191    /**
192     * native callback
193     * Report an error to an activity.
194     * @param errorCode The HTTP error code.
195     * @param description A String description.
196     * TODO: Report all errors including resource errors but include some kind
197     * of domain identifier. Change errorCode to an enum for a cleaner
198     * interface.
199     */
200    private void reportError(final int errorCode, final String description,
201            final String failingUrl) {
202        // As this is called for the main resource and loading will be stopped
203        // after, reset the state variables.
204        mCommitted = true;
205        mWebViewCore.mEndScaleZoom = mFirstLayoutDone == false;
206        mFirstLayoutDone = true;
207        mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
208    }
209
210    /* package */boolean committed() {
211        return mCommitted;
212    }
213
214    /* package */boolean firstLayoutDone() {
215        return mFirstLayoutDone;
216    }
217
218    /* package */int loadType() {
219        return mLoadType;
220    }
221
222    /* package */void didFirstLayout() {
223        if (!mFirstLayoutDone) {
224            mFirstLayoutDone = true;
225            // ensure {@link WebViewCore#webkitDraw} is called as we were
226            // blocking the update in {@link #loadStarted}
227            mWebViewCore.contentDraw();
228        }
229        mWebViewCore.mEndScaleZoom = true;
230    }
231
232    /**
233     * native callback
234     * Indicates the beginning of a new load.
235     * This method will be called once for the main frame.
236     */
237    private void loadStarted(String url, Bitmap favicon, int loadType,
238            boolean isMainFrame) {
239        mIsMainFrame = isMainFrame;
240
241        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
242            mLoadType = loadType;
243
244            if (isMainFrame) {
245                // Call onPageStarted for main frames.
246                mCallbackProxy.onPageStarted(url, favicon);
247                // as didFirstLayout() is only called for the main frame, reset
248                // mFirstLayoutDone only for the main frames
249                mFirstLayoutDone = false;
250                mCommitted = false;
251                // remove pending draw to block update until mFirstLayoutDone is
252                // set to true in didFirstLayout()
253                mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
254            }
255
256            // Note: only saves committed form data in standard load
257            if (loadType == FRAME_LOADTYPE_STANDARD
258                    && mSettings.getSaveFormData()) {
259                final WebHistoryItem h = mCallbackProxy.getBackForwardList()
260                        .getCurrentItem();
261                if (h != null) {
262                    String currentUrl = h.getUrl();
263                    if (currentUrl != null) {
264                        mDatabase.setFormData(currentUrl, getFormTextData());
265                    }
266                }
267            }
268        }
269    }
270
271    /**
272     * native callback
273     * Indicates the WebKit has committed to the new load
274     */
275    private void transitionToCommitted(int loadType, boolean isMainFrame) {
276        // loadType is not used yet
277        if (isMainFrame) {
278            mCommitted = true;
279        }
280    }
281
282    /**
283     * native callback
284     * <p>
285     * Indicates the end of a new load.
286     * This method will be called once for the main frame.
287     */
288    private void loadFinished(String url, int loadType, boolean isMainFrame) {
289        // mIsMainFrame and isMainFrame are better be equal!!!
290
291        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
292            if (isMainFrame) {
293                mCallbackProxy.switchOutDrawHistory();
294                mCallbackProxy.onPageFinished(url);
295            }
296        }
297    }
298
299    /**
300     * We have received an SSL certificate for the main top-level page.
301     *
302     * !!!Called from the network thread!!!
303     */
304    void certificate(SslCertificate certificate) {
305        if (mIsMainFrame) {
306            // we want to make this call even if the certificate is null
307            // (ie, the site is not secure)
308            mCallbackProxy.onReceivedCertificate(certificate);
309        }
310    }
311
312    /**
313     * Destroy all native components of the BrowserFrame.
314     */
315    public void destroy() {
316        nativeDestroyFrame();
317        removeCallbacksAndMessages(null);
318    }
319
320    /**
321     * Handle messages posted to us.
322     * @param msg The message to handle.
323     */
324    @Override
325    public void handleMessage(Message msg) {
326        switch (msg.what) {
327            case FRAME_COMPLETED: {
328                if (mSettings.getSavePassword() && hasPasswordField()) {
329                    if (Config.DEBUG) {
330                        Assert.assertNotNull(mCallbackProxy.getBackForwardList()
331                                .getCurrentItem());
332                    }
333                    WebAddress uri = new WebAddress(
334                            mCallbackProxy.getBackForwardList().getCurrentItem()
335                            .getUrl());
336                    String schemePlusHost = uri.mScheme + uri.mHost;
337                    String[] up = mDatabase.getUsernamePassword(schemePlusHost);
338                    if (up != null && up[0] != null) {
339                        setUsernamePassword(up[0], up[1]);
340                    }
341                }
342                CacheManager.trimCacheIfNeeded();
343                break;
344            }
345
346            case POLICY_FUNCTION: {
347                nativeCallPolicyFunction(msg.arg1, msg.arg2);
348                break;
349            }
350
351            default:
352                break;
353        }
354    }
355
356    /**
357     * Punch-through for WebCore to set the document
358     * title. Inform the Activity of the new title.
359     * @param title The new title of the document.
360     */
361    private void setTitle(String title) {
362        // FIXME: The activity must call getTitle (a native method) to get the
363        // title. We should try and cache the title if we can also keep it in
364        // sync with the document.
365        mCallbackProxy.onReceivedTitle(title);
366    }
367
368    /**
369     * Retrieves the render tree of this frame and puts it as the object for
370     * the message and sends the message.
371     * @param callback the message to use to send the render tree
372     */
373    public void externalRepresentation(Message callback) {
374        callback.obj = externalRepresentation();;
375        callback.sendToTarget();
376    }
377
378    /**
379     * Return the render tree as a string
380     */
381    private native String externalRepresentation();
382
383    /**
384     * Retrieves the visual text of the current frame, puts it as the object for
385     * the message and sends the message.
386     * @param callback the message to use to send the visual text
387     */
388    public void documentAsText(Message callback) {
389        callback.obj = documentAsText();;
390        callback.sendToTarget();
391    }
392
393    /**
394     * Return the text drawn on the screen as a string
395     */
396    private native String documentAsText();
397
398    /*
399     * This method is called by WebCore to inform the frame that
400     * the Javascript window object has been cleared.
401     * We should re-attach any attached js interfaces.
402     */
403    private void windowObjectCleared(int nativeFramePointer) {
404        if (mJSInterfaceMap != null) {
405            Iterator iter = mJSInterfaceMap.keySet().iterator();
406            while (iter.hasNext())  {
407                String interfaceName = (String) iter.next();
408                nativeAddJavascriptInterface(nativeFramePointer,
409                        mJSInterfaceMap.get(interfaceName), interfaceName);
410            }
411        }
412    }
413
414    /**
415     * This method is called by WebCore to check whether application
416     * wants to hijack url loading
417     */
418    public boolean handleUrl(String url) {
419        if (mLoadInitFromJava == true) {
420            return false;
421        }
422        if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
423            // if the url is hijacked, reset the state of the BrowserFrame
424            didFirstLayout();
425            return true;
426        } else {
427            return false;
428        }
429    }
430
431    public void addJavascriptInterface(Object obj, String interfaceName) {
432        if (mJSInterfaceMap == null) {
433            mJSInterfaceMap = new HashMap<String, Object>();
434        }
435        if (mJSInterfaceMap.containsKey(interfaceName)) {
436            mJSInterfaceMap.remove(interfaceName);
437        }
438        mJSInterfaceMap.put(interfaceName, obj);
439    }
440
441    /**
442     * Start loading a resource.
443     * @param loaderHandle The native ResourceLoader that is the target of the
444     *                     data.
445     * @param url The url to load.
446     * @param method The http method.
447     * @param headers The http headers.
448     * @param postData If the method is "POST" postData is sent as the request
449     *                 body. Is null when empty.
450     * @param cacheMode The cache mode to use when loading this resource.
451     * @param isHighPriority True if this resource needs to be put at the front
452     *                       of the network queue.
453     * @param synchronous True if the load is synchronous.
454     * @return A newly created LoadListener object.
455     */
456    private LoadListener startLoadingResource(int loaderHandle,
457                                              String url,
458                                              String method,
459                                              HashMap headers,
460                                              byte[] postData,
461                                              int cacheMode,
462                                              boolean isHighPriority,
463                                              boolean synchronous) {
464        PerfChecker checker = new PerfChecker();
465
466        if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
467            cacheMode = mSettings.getCacheMode();
468        }
469
470        if (method.equals("POST")) {
471            // Don't use the cache on POSTs when issuing a normal POST
472            // request.
473            if (cacheMode == WebSettings.LOAD_NORMAL) {
474                cacheMode = WebSettings.LOAD_NO_CACHE;
475            }
476            if (mSettings.getSavePassword() && hasPasswordField()) {
477                try {
478                    if (Config.DEBUG) {
479                        Assert.assertNotNull(mCallbackProxy.getBackForwardList()
480                                .getCurrentItem());
481                    }
482                    WebAddress uri = new WebAddress(mCallbackProxy
483                            .getBackForwardList().getCurrentItem().getUrl());
484                    String schemePlusHost = uri.mScheme + uri.mHost;
485                    String[] ret = getUsernamePassword();
486                    // Has the user entered a username/password pair and is
487                    // there some POST data
488                    if (ret != null && postData != null &&
489                            ret[0].length() > 0 && ret[1].length() > 0) {
490                        // Check to see if the username & password appear in
491                        // the post data (there could be another form on the
492                        // page and that was posted instead.
493                        String postString = new String(postData);
494                        if (postString.contains(URLEncoder.encode(ret[0])) &&
495                                postString.contains(URLEncoder.encode(ret[1]))) {
496                            String[] saved = mDatabase.getUsernamePassword(
497                                    schemePlusHost);
498                            if (saved != null) {
499                                // null username implies that user has chosen not to
500                                // save password
501                                if (saved[0] != null) {
502                                    // non-null username implies that user has
503                                    // chosen to save password, so update the
504                                    // recorded password
505                                    mDatabase.setUsernamePassword(
506                                            schemePlusHost, ret[0], ret[1]);
507                                }
508                            } else {
509                                // CallbackProxy will handle creating the resume
510                                // message
511                                mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
512                                        ret[1], null);
513                            }
514                        }
515                    }
516                } catch (ParseException ex) {
517                    // if it is bad uri, don't save its password
518                }
519
520            }
521        }
522
523        // is this resource the main-frame top-level page?
524        boolean isMainFramePage = mIsMainFrame;
525
526        if (Config.LOGV) {
527            Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
528                    + method + ", postData=" + postData + ", isHighPriority="
529                    + isHighPriority + ", isMainFramePage=" + isMainFramePage);
530        }
531
532        // Create a LoadListener
533        LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url,
534                loaderHandle, synchronous, isMainFramePage);
535
536        mCallbackProxy.onLoadResource(url);
537
538        if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
539            loadListener.error(
540                    android.net.http.EventHandler.ERROR, mContext.getString(
541                            com.android.internal.R.string.httpErrorTooManyRequests));
542            loadListener.notifyError();
543            loadListener.tearDown();
544            return null;
545        }
546
547        // during synchronous load, the WebViewCore thread is blocked, so we
548        // need to endCacheTransaction first so that http thread won't be
549        // blocked in setupFile() when createCacheFile.
550        if (synchronous) {
551            CacheManager.endCacheTransaction();
552        }
553
554        FrameLoader loader = new FrameLoader(loadListener, mSettings,
555                method, isHighPriority);
556        loader.setHeaders(headers);
557        loader.setPostData(postData);
558        loader.setCacheMode(cacheMode); // Set the load mode to the mode used
559                                        // for the current page.
560        // Set referrer to current URL?
561        if (!loader.executeLoad()) {
562            checker.responseAlert("startLoadingResource fail");
563        }
564        checker.responseAlert("startLoadingResource succeed");
565
566        if (synchronous) {
567            CacheManager.startCacheTransaction();
568        }
569
570        return !synchronous ? loadListener : null;
571    }
572
573    /**
574     * Set the progress for the browser activity.  Called by native code.
575     * Uses a delay so it does not happen too often.
576     * @param newProgress An int between zero and one hundred representing
577     *                    the current progress percentage of loading the page.
578     */
579    private void setProgress(int newProgress) {
580        mCallbackProxy.onProgressChanged(newProgress);
581        if (newProgress == 100) {
582            sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
583        }
584        // FIXME: Need to figure out a better way to switch out of the history
585        // drawing mode. Maybe we can somehow compare the history picture with
586        // the current picture, and switch when it contains more content.
587        if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
588            mCallbackProxy.switchOutDrawHistory();
589        }
590    }
591
592    /**
593     * Send the icon to the activity for display.
594     * @param icon A Bitmap representing a page's favicon.
595     */
596    private void didReceiveIcon(Bitmap icon) {
597        mCallbackProxy.onReceivedIcon(icon);
598    }
599
600    /**
601     * Request a new window from the client.
602     * @return The BrowserFrame object stored in the new WebView.
603     */
604    private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
605        WebView w = mCallbackProxy.createWindow(dialog, userGesture);
606        if (w != null) {
607            return w.getWebViewCore().getBrowserFrame();
608        }
609        return null;
610    }
611
612    /**
613     * Try to focus this WebView.
614     */
615    private void requestFocus() {
616        mCallbackProxy.onRequestFocus();
617    }
618
619    /**
620     * Close this frame and window.
621     */
622    private void closeWindow(WebViewCore w) {
623        mCallbackProxy.onCloseWindow(w.getWebView());
624    }
625
626    // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
627    static final int POLICY_USE = 0;
628    static final int POLICY_IGNORE = 2;
629
630    private void decidePolicyForFormResubmission(int policyFunction) {
631        Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
632                POLICY_IGNORE);
633        Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
634                POLICY_USE);
635        mCallbackProxy.onFormResubmission(dontResend, resend);
636    }
637
638    /**
639     * Tell the activity to update its global history.
640     */
641    private void updateVisitedHistory(String url, boolean isReload) {
642        mCallbackProxy.doUpdateVisitedHistory(url, isReload);
643    }
644
645    /**
646     * Get the CallbackProxy for sending messages to the UI thread.
647     */
648    /* package */ CallbackProxy getCallbackProxy() {
649        return mCallbackProxy;
650    }
651
652    /**
653     * Returns the User Agent used by this frame
654     */
655    String getUserAgentString() {
656        return mSettings.getUserAgentString();
657    }
658
659    // these ids need to be in sync with enum RAW_RES_ID in WebFrame
660    private static final int NODOMAIN = 1;
661    private static final int LOADERROR = 2;
662
663    String getRawResFilename(int id) {
664        int resid;
665        switch (id) {
666            case NODOMAIN:
667                resid = com.android.internal.R.raw.nodomain;
668                break;
669
670            case LOADERROR:
671                resid = com.android.internal.R.raw.loaderror;
672                break;
673
674            default:
675                Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
676                return new String();
677        }
678        TypedValue value = new TypedValue();
679        mContext.getResources().getValue(resid, value, true);
680        return value.string.toString();
681    }
682
683    //==========================================================================
684    // native functions
685    //==========================================================================
686
687    /**
688     * Create a new native frame for a given WebView
689     * @param w     A WebView that the frame draws into.
690     * @param am    AssetManager to use to get assets.
691     * @param list  The native side will add and remove items from this list as
692     *              the native list changes.
693     */
694    private native void nativeCreateFrame(WebViewCore w, AssetManager am,
695            WebBackForwardList list);
696
697    /**
698     * Destroy the native frame.
699     */
700    public native void nativeDestroyFrame();
701
702    private native void nativeCallPolicyFunction(int policyFunction,
703            int decision);
704
705    /**
706     * Reload the current main frame.
707     */
708    public native void reload(boolean allowStale);
709
710    /**
711     * Go back or forward the number of steps given.
712     * @param steps A negative or positive number indicating the direction
713     *              and number of steps to move.
714     */
715    private native void nativeGoBackOrForward(int steps);
716
717    /**
718     * stringByEvaluatingJavaScriptFromString will execute the
719     * JS passed in in the context of this browser frame.
720     * @param script A javascript string to execute
721     *
722     * @return string result of execution or null
723     */
724    public native String stringByEvaluatingJavaScriptFromString(String script);
725
726    /**
727     * Add a javascript interface to the main frame.
728     */
729    private native void nativeAddJavascriptInterface(int nativeFramePointer,
730            Object obj, String interfaceName);
731
732    /**
733     * Enable or disable the native cache.
734     */
735    /* FIXME: The native cache is always on for now until we have a better
736     * solution for our 2 caches. */
737    private native void setCacheDisabled(boolean disabled);
738
739    public native boolean cacheDisabled();
740
741    public native void clearCache();
742
743    /**
744     * Returns false if the url is bad.
745     */
746    private native void nativeLoadUrl(String url);
747
748    private native void nativeLoadData(String baseUrl, String data,
749            String mimeType, String encoding, String failUrl);
750
751    /**
752     * Stop loading the current page.
753     */
754    public native void stopLoading();
755
756    /**
757     * Return true if the document has images.
758     */
759    public native boolean documentHasImages();
760
761    /**
762     * @return TRUE if there is a password field in the current frame
763     */
764    private native boolean hasPasswordField();
765
766    /**
767     * Get username and password in the current frame. If found, String[0] is
768     * username and String[1] is password. Otherwise return NULL.
769     * @return String[]
770     */
771    private native String[] getUsernamePassword();
772
773    /**
774     * Set username and password to the proper fields in the current frame
775     * @param username
776     * @param password
777     */
778    private native void setUsernamePassword(String username, String password);
779
780    /**
781     * Get form's "text" type data associated with the current frame.
782     * @return HashMap If succeed, returns a list of name/value pair. Otherwise
783     *         returns null.
784     */
785    private native HashMap getFormTextData();
786}
787