mobile/android/base/GeckoView.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import org.mozilla.gecko.db.BrowserDB;
     9 import org.mozilla.gecko.gfx.LayerView;
    10 import org.mozilla.gecko.mozglue.GeckoLoader;
    11 import org.mozilla.gecko.util.Clipboard;
    12 import org.mozilla.gecko.util.HardwareUtils;
    13 import org.mozilla.gecko.util.GeckoEventListener;
    14 import org.mozilla.gecko.util.ThreadUtils;
    16 import org.json.JSONException;
    17 import org.json.JSONObject;
    19 import android.app.Activity;
    20 import android.content.Context;
    21 import android.content.Intent;
    22 import android.content.SharedPreferences;
    23 import android.content.res.TypedArray;
    24 import android.os.Handler;
    25 import android.util.AttributeSet;
    26 import android.util.Log;
    27 import java.util.ArrayList;
    28 import java.util.Collections;
    29 import java.util.List;
    31 public class GeckoView extends LayerView
    32     implements GeckoEventListener, ContextGetter {
    34     private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView";
    35     private static final String LOGTAG = "GeckoView";
    37     private ChromeDelegate mChromeDelegate;
    38     private ContentDelegate mContentDelegate;
    40     public GeckoView(Context context) {
    41         super(context);
    42         init(context, null, true);
    43     }
    45     public GeckoView(Context context, AttributeSet attrs) {
    46         super(context, attrs);
    47         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GeckoView);
    48         String url = a.getString(R.styleable.GeckoView_url);
    49         boolean doInit = a.getBoolean(R.styleable.GeckoView_doinit, true);
    50         a.recycle();
    51         init(context, url, doInit);
    52     }
    54     private void init(Context context, String url, boolean doInit) {
    55         // TODO: Fennec currently takes care of its own initialization, so this
    56         // flag is a hack used in Fennec to prevent GeckoView initialization.
    57         // This should go away once Fennec also uses GeckoView for
    58         // initialization.
    59         if (!doInit)
    60             return;
    62         // If running outside of a GeckoActivity (eg, from a library project),
    63         // load the native code and disable content providers
    64         boolean isGeckoActivity = false;
    65         try {
    66             isGeckoActivity = context instanceof GeckoActivity;
    67         } catch (NoClassDefFoundError ex) {}
    69         if (!isGeckoActivity) {
    70             // Set the GeckoInterface if the context is an activity and the GeckoInterface
    71             // has not already been set
    72             if (context instanceof Activity && getGeckoInterface() == null) {
    73                 setGeckoInterface(new BaseGeckoInterface(context));
    74             }
    76             Clipboard.init(context);
    77             HardwareUtils.init(context);
    79             // If you want to use GeckoNetworkManager, start it.
    81             GeckoLoader.loadMozGlue();
    82             BrowserDB.setEnableContentProviders(false);
    83          }
    85         if (url != null) {
    86             GeckoThread.setUri(url);
    87             GeckoThread.setAction(Intent.ACTION_VIEW);
    88             GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(url));
    89         }
    90         GeckoAppShell.setContextGetter(this);
    91         if (context instanceof Activity) {
    92             Tabs tabs = Tabs.getInstance();
    93             tabs.attachToContext(context);
    94         }
    96         GeckoAppShell.registerEventListener("Gecko:Ready", this);
    97         GeckoAppShell.registerEventListener("Content:StateChange", this);
    98         GeckoAppShell.registerEventListener("Content:LoadError", this);
    99         GeckoAppShell.registerEventListener("Content:PageShow", this);
   100         GeckoAppShell.registerEventListener("DOMTitleChanged", this);
   101         GeckoAppShell.registerEventListener("Link:Favicon", this);
   102         GeckoAppShell.registerEventListener("Prompt:Show", this);
   103         GeckoAppShell.registerEventListener("Prompt:ShowTop", this);
   105         ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
   106         initializeView(GeckoAppShell.getEventDispatcher());
   108         if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
   109             // This is the first launch, so finish initialization and go.
   110             GeckoProfile profile = GeckoProfile.get(context).forceCreate();
   111             BrowserDB.initialize(profile.getName());
   113             GeckoAppShell.setLayerView(this);
   114             GeckoThread.createAndStart();
   115         } else if(GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
   116             // If Gecko is already running, that means the Activity was
   117             // destroyed, so we need to re-attach Gecko to this GeckoView.
   118             connectToGecko();
   119         }
   120     }
   122     /**
   123     * Add a Browser to the GeckoView container.
   124     * @param url The URL resource to load into the new Browser.
   125     */
   126     public Browser addBrowser(String url) {
   127         Tab tab = Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NEW_TAB);
   128         if (tab != null) {
   129             return new Browser(tab.getId());
   130         }
   131         return null;
   132     }
   134     /**
   135     * Remove a Browser from the GeckoView container.
   136     * @param browser The Browser to remove.
   137     */
   138     public void removeBrowser(Browser browser) {
   139         Tab tab = Tabs.getInstance().getTab(browser.getId());
   140         if (tab != null) {
   141             Tabs.getInstance().closeTab(tab);
   142         }
   143     }
   145     /**
   146     * Set the active/visible Browser.
   147     * @param browser The Browser to make selected.
   148     */
   149     public void setCurrentBrowser(Browser browser) {
   150         Tab tab = Tabs.getInstance().getTab(browser.getId());
   151         if (tab != null) {
   152             Tabs.getInstance().selectTab(tab.getId());
   153         }
   154     }
   156     /**
   157     * Get the active/visible Browser.
   158     * @return The current selected Browser.
   159     */
   160     public Browser getCurrentBrowser() {
   161         Tab tab = Tabs.getInstance().getSelectedTab();
   162         if (tab != null) {
   163             return new Browser(tab.getId());
   164         }
   165         return null;
   166     }
   168     /**
   169     * Get the list of current Browsers in the GeckoView container.
   170     * @return An unmodifiable List of Browser objects.
   171     */
   172     public List<Browser> getBrowsers() {
   173         ArrayList<Browser> browsers = new ArrayList<Browser>();
   174         Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
   175         for (Tab tab : tabs) {
   176             browsers.add(new Browser(tab.getId()));
   177         }
   178         return Collections.unmodifiableList(browsers);
   179     }
   181     /**
   182     * Not part of the public API. Ignore.
   183     */
   184     public void handleMessage(final String event, final JSONObject message) {
   185         ThreadUtils.postToUiThread(new Runnable() {
   186             @Override
   187             public void run() {
   188                 try {
   189                     if (event.equals("Gecko:Ready")) {
   190                         GeckoView.this.handleReady(message);
   191                     } else if (event.equals("Content:StateChange")) {
   192                         GeckoView.this.handleStateChange(message);
   193                     } else if (event.equals("Content:LoadError")) {
   194                         GeckoView.this.handleLoadError(message);
   195                     } else if (event.equals("Content:PageShow")) {
   196                         GeckoView.this.handlePageShow(message);
   197                     } else if (event.equals("DOMTitleChanged")) {
   198                         GeckoView.this.handleTitleChanged(message);
   199                     } else if (event.equals("Link:Favicon")) {
   200                         GeckoView.this.handleLinkFavicon(message);
   201                     } else if (event.equals("Prompt:Show") || event.equals("Prompt:ShowTop")) {
   202                         GeckoView.this.handlePrompt(message);
   203                     }
   204                 } catch (Exception e) {
   205                     Log.w(LOGTAG, "handleMessage threw for " + event, e);
   206                 }
   207             }
   208         });
   209     }
   211     private void connectToGecko() {
   212         GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
   213         Tab selectedTab = Tabs.getInstance().getSelectedTab();
   214         if (selectedTab != null)
   215             Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
   216         geckoConnected();
   217         GeckoAppShell.setLayerClient(getLayerClientObject());
   218         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
   219     }
   221     private void handleReady(final JSONObject message) {
   222         connectToGecko();
   224         if (mChromeDelegate != null) {
   225             mChromeDelegate.onReady(this);
   226         }
   227     }
   229     private void handleStateChange(final JSONObject message) throws JSONException {
   230         int state = message.getInt("state");
   231         if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
   232             if ((state & GeckoAppShell.WPL_STATE_START) != 0) {
   233                 if (mContentDelegate != null) {
   234                     int id = message.getInt("tabID");
   235                     mContentDelegate.onPageStart(this, new Browser(id), message.getString("uri"));
   236                 }
   237             } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
   238                 if (mContentDelegate != null) {
   239                     int id = message.getInt("tabID");
   240                     mContentDelegate.onPageStop(this, new Browser(id), message.getBoolean("success"));
   241                 }
   242             }
   243         }
   244     }
   246     private void handleLoadError(final JSONObject message) throws JSONException {
   247         if (mContentDelegate != null) {
   248             int id = message.getInt("tabID");
   249             mContentDelegate.onPageStop(GeckoView.this, new Browser(id), false);
   250         }
   251     }
   253     private void handlePageShow(final JSONObject message) throws JSONException {
   254         if (mContentDelegate != null) {
   255             int id = message.getInt("tabID");
   256             mContentDelegate.onPageShow(GeckoView.this, new Browser(id));
   257         }
   258     }
   260     private void handleTitleChanged(final JSONObject message) throws JSONException {
   261         if (mContentDelegate != null) {
   262             int id = message.getInt("tabID");
   263             mContentDelegate.onReceivedTitle(GeckoView.this, new Browser(id), message.getString("title"));
   264         }
   265     }
   267     private void handleLinkFavicon(final JSONObject message) throws JSONException {
   268         if (mContentDelegate != null) {
   269             int id = message.getInt("tabID");
   270             mContentDelegate.onReceivedFavicon(GeckoView.this, new Browser(id), message.getString("href"), message.getInt("size"));
   271         }
   272     }
   274     private void handlePrompt(final JSONObject message) throws JSONException {
   275         if (mChromeDelegate != null) {
   276             String hint = message.optString("hint");
   277             if ("alert".equals(hint)) {
   278                 String text = message.optString("text");
   279                 mChromeDelegate.onAlert(GeckoView.this, null, text, new PromptResult(message.optString("guid")));
   280             } else if ("confirm".equals(hint)) {
   281                 String text = message.optString("text");
   282                 mChromeDelegate.onConfirm(GeckoView.this, null, text, new PromptResult(message.optString("guid")));
   283             } else if ("prompt".equals(hint)) {
   284                 String text = message.optString("text");
   285                 String defaultValue = message.optString("textbox0");
   286                 mChromeDelegate.onPrompt(GeckoView.this, null, text, defaultValue, new PromptResult(message.optString("guid")));
   287             } else if ("remotedebug".equals(hint)) {
   288                 mChromeDelegate.onDebugRequest(GeckoView.this, new PromptResult(message.optString("guid")));
   289             }
   290         }
   291     }
   293     /**
   294     * Set the chrome callback handler.
   295     * This will replace the current handler.
   296     * @param chrome An implementation of GeckoViewChrome.
   297     */
   298     public void setChromeDelegate(ChromeDelegate chrome) {
   299         mChromeDelegate = chrome;
   300     }
   302     /**
   303     * Set the content callback handler.
   304     * This will replace the current handler.
   305     * @param content An implementation of ContentDelegate.
   306     */
   307     public void setContentDelegate(ContentDelegate content) {
   308         mContentDelegate = content;
   309     }
   311     public static void setGeckoInterface(final BaseGeckoInterface geckoInterface) {
   312         GeckoAppShell.setGeckoInterface(geckoInterface);
   313     }
   315     public static GeckoAppShell.GeckoInterface getGeckoInterface() {
   316         return GeckoAppShell.getGeckoInterface();
   317     }
   319     protected String getSharedPreferencesFile() {
   320         return DEFAULT_SHARED_PREFERENCES_FILE;
   321     }
   323     public SharedPreferences getSharedPreferences() {
   324         return getContext().getSharedPreferences(getSharedPreferencesFile(), 0);
   325     }
   327     /**
   328     * Wrapper for a browser in the GeckoView container. Associated with a browser
   329     * element in the Gecko system.
   330     */
   331     public class Browser {
   332         private final int mId;
   333         private Browser(int Id) {
   334             mId = Id;
   335         }
   337         /**
   338         * Get the ID of the Browser. This is the same ID used by Gecko for it's underlying
   339         * browser element.
   340         * @return The integer ID of the Browser.
   341         */
   342         private int getId() {
   343             return mId;
   344         }
   346         /**
   347         * Load a URL resource into the Browser.
   348         * @param url The URL string.
   349         */
   350         public void loadUrl(String url) {
   351             JSONObject args = new JSONObject();
   352             try {
   353                 args.put("url", url);
   354                 args.put("parentId", -1);
   355                 args.put("newTab", false);
   356                 args.put("tabID", mId);
   357             } catch (Exception e) {
   358                 Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e);
   359             }
   360             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString()));
   361         }
   363         /**
   364         * Reload the current URL resource into the Browser. The URL is force loaded from the
   365         * network and is not pulled from cache.
   366         */
   367         public void reload() {
   368             Tab tab = Tabs.getInstance().getTab(mId);
   369             if (tab != null) {
   370                 tab.doReload();
   371             }
   372         }
   374         /**
   375         * Stop the current loading operation.
   376         */
   377         public void stop() {
   378             Tab tab = Tabs.getInstance().getTab(mId);
   379             if (tab != null) {
   380                 tab.doStop();
   381             }
   382         }
   384         /**
   385         * Check to see if the Browser has session history and can go back to a
   386         * previous page.
   387         * @return A boolean flag indicating if previous session exists.
   388         * This method will likely be removed and replaced by a callback in GeckoViewContent
   389         */
   390         public boolean canGoBack() {
   391             Tab tab = Tabs.getInstance().getTab(mId);
   392             if (tab != null) {
   393                 return tab.canDoBack();
   394             }
   395             return false;
   396         }
   398         /**
   399         * Move backward in the session history, if that's possible.
   400         */
   401         public void goBack() {
   402             Tab tab = Tabs.getInstance().getTab(mId);
   403             if (tab != null) {
   404                 tab.doBack();
   405             }
   406         }
   408         /**
   409         * Check to see if the Browser has session history and can go forward to a
   410         * new page.
   411         * @return A boolean flag indicating if forward session exists.
   412         * This method will likely be removed and replaced by a callback in GeckoViewContent
   413         */
   414         public boolean canGoForward() {
   415             Tab tab = Tabs.getInstance().getTab(mId);
   416             if (tab != null) {
   417                 return tab.canDoForward();
   418             }
   419             return false;
   420         }
   422         /**
   423         * Move forward in the session history, if that's possible.
   424         */
   425         public void goForward() {
   426             Tab tab = Tabs.getInstance().getTab(mId);
   427             if (tab != null) {
   428                 tab.doForward();
   429             }
   430         }
   431     }
   433     /* Provides a means for the client to indicate whether a JavaScript
   434      * dialog request should proceed. An instance of this class is passed to
   435      * various GeckoViewChrome callback actions.
   436      */
   437     public class PromptResult {
   438         private final int RESULT_OK = 0;
   439         private final int RESULT_CANCEL = 1;
   441         private final String mGUID;
   443         public PromptResult(String guid) {
   444             mGUID = guid;
   445         }
   447         private JSONObject makeResult(int resultCode) {
   448             JSONObject result = new JSONObject();
   449             try {
   450                 result.put("guid", mGUID);
   451                 result.put("button", resultCode);
   452             } catch(JSONException ex) { }
   453             return result;
   454         }
   456         /**
   457         * Handle a confirmation response from the user.
   458         */
   459         public void confirm() {
   460             JSONObject result = makeResult(RESULT_OK);
   461             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString()));
   462         }
   464         /**
   465         * Handle a confirmation response from the user.
   466         * @param value String value to return to the browser context.
   467         */
   468         public void confirmWithValue(String value) {
   469             JSONObject result = makeResult(RESULT_OK);
   470             try {
   471                 result.put("textbox0", value);
   472             } catch(JSONException ex) { }
   473             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString()));
   474         }
   476         /**
   477         * Handle a cancellation response from the user.
   478         */
   479         public void cancel() {
   480             JSONObject result = makeResult(RESULT_CANCEL);
   481             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString()));
   482         }
   483     }
   485     public interface ChromeDelegate {
   486         /**
   487         * Tell the host application that Gecko is ready to handle requests.
   488         * @param view The GeckoView that initiated the callback.
   489         */
   490         public void onReady(GeckoView view);
   492         /**
   493         * Tell the host application to display an alert dialog.
   494         * @param view The GeckoView that initiated the callback.
   495         * @param browser The Browser that is loading the content.
   496         * @param message The string to display in the dialog.
   497         * @param result A PromptResult used to send back the result without blocking.
   498         * Defaults to cancel requests.
   499         */
   500         public void onAlert(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result);
   502         /**
   503         * Tell the host application to display a confirmation dialog.
   504         * @param view The GeckoView that initiated the callback.
   505         * @param browser The Browser that is loading the content.
   506         * @param message The string to display in the dialog.
   507         * @param result A PromptResult used to send back the result without blocking.
   508         * Defaults to cancel requests.
   509         */
   510         public void onConfirm(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result);
   512         /**
   513         * Tell the host application to display an input prompt dialog.
   514         * @param view The GeckoView that initiated the callback.
   515         * @param browser The Browser that is loading the content.
   516         * @param message The string to display in the dialog.
   517         * @param defaultValue The string to use as default input.
   518         * @param result A PromptResult used to send back the result without blocking.
   519         * Defaults to cancel requests.
   520         */
   521         public void onPrompt(GeckoView view, GeckoView.Browser browser, String message, String defaultValue, GeckoView.PromptResult result);
   523         /**
   524         * Tell the host application to display a remote debugging request dialog.
   525         * @param view The GeckoView that initiated the callback.
   526         * @param result A PromptResult used to send back the result without blocking.
   527         * Defaults to cancel requests.
   528         */
   529         public void onDebugRequest(GeckoView view, GeckoView.PromptResult result);
   530     }
   532     public interface ContentDelegate {
   533         /**
   534         * A Browser has started loading content from the network.
   535         * @param view The GeckoView that initiated the callback.
   536         * @param browser The Browser that is loading the content.
   537         * @param url The resource being loaded.
   538         */
   539         public void onPageStart(GeckoView view, GeckoView.Browser browser, String url);
   541         /**
   542         * A Browser has finished loading content from the network.
   543         * @param view The GeckoView that initiated the callback.
   544         * @param browser The Browser that was loading the content.
   545         * @param success Whether the page loaded successfully or an error occured.
   546         */
   547         public void onPageStop(GeckoView view, GeckoView.Browser browser, boolean success);
   549         /**
   550         * A Browser is displaying content. This page could have been loaded via
   551         * network or from the session history.
   552         * @param view The GeckoView that initiated the callback.
   553         * @param browser The Browser that is showing the content.
   554         */
   555         public void onPageShow(GeckoView view, GeckoView.Browser browser);
   557         /**
   558         * A page title was discovered in the content or updated after the content
   559         * loaded.
   560         * @param view The GeckoView that initiated the callback.
   561         * @param browser The Browser that is showing the content.
   562         * @param title The title sent from the content.
   563         */
   564         public void onReceivedTitle(GeckoView view, GeckoView.Browser browser, String title);
   566         /**
   567         * A link element was discovered in the content or updated after the content
   568         * loaded that specifies a favicon.
   569         * @param view The GeckoView that initiated the callback.
   570         * @param browser The Browser that is showing the content.
   571         * @param url The href of the link element specifying the favicon.
   572         * @param size The maximum size specified for the favicon, or -1 for any size.
   573         */
   574         public void onReceivedFavicon(GeckoView view, GeckoView.Browser browser, String url, int size);
   575     }
   577 }

mercurial