diff -r 000000000000 -r 6474c204b198 mobile/android/base/GeckoView.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/base/GeckoView.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,577 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.gfx.LayerView; +import org.mozilla.gecko.mozglue.GeckoLoader; +import org.mozilla.gecko.util.Clipboard; +import org.mozilla.gecko.util.HardwareUtils; +import org.mozilla.gecko.util.GeckoEventListener; +import org.mozilla.gecko.util.ThreadUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.Log; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GeckoView extends LayerView + implements GeckoEventListener, ContextGetter { + + private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView"; + private static final String LOGTAG = "GeckoView"; + + private ChromeDelegate mChromeDelegate; + private ContentDelegate mContentDelegate; + + public GeckoView(Context context) { + super(context); + init(context, null, true); + } + + public GeckoView(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GeckoView); + String url = a.getString(R.styleable.GeckoView_url); + boolean doInit = a.getBoolean(R.styleable.GeckoView_doinit, true); + a.recycle(); + init(context, url, doInit); + } + + private void init(Context context, String url, boolean doInit) { + // TODO: Fennec currently takes care of its own initialization, so this + // flag is a hack used in Fennec to prevent GeckoView initialization. + // This should go away once Fennec also uses GeckoView for + // initialization. + if (!doInit) + return; + + // If running outside of a GeckoActivity (eg, from a library project), + // load the native code and disable content providers + boolean isGeckoActivity = false; + try { + isGeckoActivity = context instanceof GeckoActivity; + } catch (NoClassDefFoundError ex) {} + + if (!isGeckoActivity) { + // Set the GeckoInterface if the context is an activity and the GeckoInterface + // has not already been set + if (context instanceof Activity && getGeckoInterface() == null) { + setGeckoInterface(new BaseGeckoInterface(context)); + } + + Clipboard.init(context); + HardwareUtils.init(context); + + // If you want to use GeckoNetworkManager, start it. + + GeckoLoader.loadMozGlue(); + BrowserDB.setEnableContentProviders(false); + } + + if (url != null) { + GeckoThread.setUri(url); + GeckoThread.setAction(Intent.ACTION_VIEW); + GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(url)); + } + GeckoAppShell.setContextGetter(this); + if (context instanceof Activity) { + Tabs tabs = Tabs.getInstance(); + tabs.attachToContext(context); + } + + GeckoAppShell.registerEventListener("Gecko:Ready", this); + GeckoAppShell.registerEventListener("Content:StateChange", this); + GeckoAppShell.registerEventListener("Content:LoadError", this); + GeckoAppShell.registerEventListener("Content:PageShow", this); + GeckoAppShell.registerEventListener("DOMTitleChanged", this); + GeckoAppShell.registerEventListener("Link:Favicon", this); + GeckoAppShell.registerEventListener("Prompt:Show", this); + GeckoAppShell.registerEventListener("Prompt:ShowTop", this); + + ThreadUtils.setUiThread(Thread.currentThread(), new Handler()); + initializeView(GeckoAppShell.getEventDispatcher()); + + if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) { + // This is the first launch, so finish initialization and go. + GeckoProfile profile = GeckoProfile.get(context).forceCreate(); + BrowserDB.initialize(profile.getName()); + + GeckoAppShell.setLayerView(this); + GeckoThread.createAndStart(); + } else if(GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) { + // If Gecko is already running, that means the Activity was + // destroyed, so we need to re-attach Gecko to this GeckoView. + connectToGecko(); + } + } + + /** + * Add a Browser to the GeckoView container. + * @param url The URL resource to load into the new Browser. + */ + public Browser addBrowser(String url) { + Tab tab = Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NEW_TAB); + if (tab != null) { + return new Browser(tab.getId()); + } + return null; + } + + /** + * Remove a Browser from the GeckoView container. + * @param browser The Browser to remove. + */ + public void removeBrowser(Browser browser) { + Tab tab = Tabs.getInstance().getTab(browser.getId()); + if (tab != null) { + Tabs.getInstance().closeTab(tab); + } + } + + /** + * Set the active/visible Browser. + * @param browser The Browser to make selected. + */ + public void setCurrentBrowser(Browser browser) { + Tab tab = Tabs.getInstance().getTab(browser.getId()); + if (tab != null) { + Tabs.getInstance().selectTab(tab.getId()); + } + } + + /** + * Get the active/visible Browser. + * @return The current selected Browser. + */ + public Browser getCurrentBrowser() { + Tab tab = Tabs.getInstance().getSelectedTab(); + if (tab != null) { + return new Browser(tab.getId()); + } + return null; + } + + /** + * Get the list of current Browsers in the GeckoView container. + * @return An unmodifiable List of Browser objects. + */ + public List getBrowsers() { + ArrayList browsers = new ArrayList(); + Iterable tabs = Tabs.getInstance().getTabsInOrder(); + for (Tab tab : tabs) { + browsers.add(new Browser(tab.getId())); + } + return Collections.unmodifiableList(browsers); + } + + /** + * Not part of the public API. Ignore. + */ + public void handleMessage(final String event, final JSONObject message) { + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + try { + if (event.equals("Gecko:Ready")) { + GeckoView.this.handleReady(message); + } else if (event.equals("Content:StateChange")) { + GeckoView.this.handleStateChange(message); + } else if (event.equals("Content:LoadError")) { + GeckoView.this.handleLoadError(message); + } else if (event.equals("Content:PageShow")) { + GeckoView.this.handlePageShow(message); + } else if (event.equals("DOMTitleChanged")) { + GeckoView.this.handleTitleChanged(message); + } else if (event.equals("Link:Favicon")) { + GeckoView.this.handleLinkFavicon(message); + } else if (event.equals("Prompt:Show") || event.equals("Prompt:ShowTop")) { + GeckoView.this.handlePrompt(message); + } + } catch (Exception e) { + Log.w(LOGTAG, "handleMessage threw for " + event, e); + } + } + }); + } + + private void connectToGecko() { + GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning); + Tab selectedTab = Tabs.getInstance().getSelectedTab(); + if (selectedTab != null) + Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED); + geckoConnected(); + GeckoAppShell.setLayerClient(getLayerClientObject()); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null)); + } + + private void handleReady(final JSONObject message) { + connectToGecko(); + + if (mChromeDelegate != null) { + mChromeDelegate.onReady(this); + } + } + + private void handleStateChange(final JSONObject message) throws JSONException { + int state = message.getInt("state"); + if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) { + if ((state & GeckoAppShell.WPL_STATE_START) != 0) { + if (mContentDelegate != null) { + int id = message.getInt("tabID"); + mContentDelegate.onPageStart(this, new Browser(id), message.getString("uri")); + } + } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) { + if (mContentDelegate != null) { + int id = message.getInt("tabID"); + mContentDelegate.onPageStop(this, new Browser(id), message.getBoolean("success")); + } + } + } + } + + private void handleLoadError(final JSONObject message) throws JSONException { + if (mContentDelegate != null) { + int id = message.getInt("tabID"); + mContentDelegate.onPageStop(GeckoView.this, new Browser(id), false); + } + } + + private void handlePageShow(final JSONObject message) throws JSONException { + if (mContentDelegate != null) { + int id = message.getInt("tabID"); + mContentDelegate.onPageShow(GeckoView.this, new Browser(id)); + } + } + + private void handleTitleChanged(final JSONObject message) throws JSONException { + if (mContentDelegate != null) { + int id = message.getInt("tabID"); + mContentDelegate.onReceivedTitle(GeckoView.this, new Browser(id), message.getString("title")); + } + } + + private void handleLinkFavicon(final JSONObject message) throws JSONException { + if (mContentDelegate != null) { + int id = message.getInt("tabID"); + mContentDelegate.onReceivedFavicon(GeckoView.this, new Browser(id), message.getString("href"), message.getInt("size")); + } + } + + private void handlePrompt(final JSONObject message) throws JSONException { + if (mChromeDelegate != null) { + String hint = message.optString("hint"); + if ("alert".equals(hint)) { + String text = message.optString("text"); + mChromeDelegate.onAlert(GeckoView.this, null, text, new PromptResult(message.optString("guid"))); + } else if ("confirm".equals(hint)) { + String text = message.optString("text"); + mChromeDelegate.onConfirm(GeckoView.this, null, text, new PromptResult(message.optString("guid"))); + } else if ("prompt".equals(hint)) { + String text = message.optString("text"); + String defaultValue = message.optString("textbox0"); + mChromeDelegate.onPrompt(GeckoView.this, null, text, defaultValue, new PromptResult(message.optString("guid"))); + } else if ("remotedebug".equals(hint)) { + mChromeDelegate.onDebugRequest(GeckoView.this, new PromptResult(message.optString("guid"))); + } + } + } + + /** + * Set the chrome callback handler. + * This will replace the current handler. + * @param chrome An implementation of GeckoViewChrome. + */ + public void setChromeDelegate(ChromeDelegate chrome) { + mChromeDelegate = chrome; + } + + /** + * Set the content callback handler. + * This will replace the current handler. + * @param content An implementation of ContentDelegate. + */ + public void setContentDelegate(ContentDelegate content) { + mContentDelegate = content; + } + + public static void setGeckoInterface(final BaseGeckoInterface geckoInterface) { + GeckoAppShell.setGeckoInterface(geckoInterface); + } + + public static GeckoAppShell.GeckoInterface getGeckoInterface() { + return GeckoAppShell.getGeckoInterface(); + } + + protected String getSharedPreferencesFile() { + return DEFAULT_SHARED_PREFERENCES_FILE; + } + + public SharedPreferences getSharedPreferences() { + return getContext().getSharedPreferences(getSharedPreferencesFile(), 0); + } + + /** + * Wrapper for a browser in the GeckoView container. Associated with a browser + * element in the Gecko system. + */ + public class Browser { + private final int mId; + private Browser(int Id) { + mId = Id; + } + + /** + * Get the ID of the Browser. This is the same ID used by Gecko for it's underlying + * browser element. + * @return The integer ID of the Browser. + */ + private int getId() { + return mId; + } + + /** + * Load a URL resource into the Browser. + * @param url The URL string. + */ + public void loadUrl(String url) { + JSONObject args = new JSONObject(); + try { + args.put("url", url); + args.put("parentId", -1); + args.put("newTab", false); + args.put("tabID", mId); + } catch (Exception e) { + Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e); + } + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString())); + } + + /** + * Reload the current URL resource into the Browser. The URL is force loaded from the + * network and is not pulled from cache. + */ + public void reload() { + Tab tab = Tabs.getInstance().getTab(mId); + if (tab != null) { + tab.doReload(); + } + } + + /** + * Stop the current loading operation. + */ + public void stop() { + Tab tab = Tabs.getInstance().getTab(mId); + if (tab != null) { + tab.doStop(); + } + } + + /** + * Check to see if the Browser has session history and can go back to a + * previous page. + * @return A boolean flag indicating if previous session exists. + * This method will likely be removed and replaced by a callback in GeckoViewContent + */ + public boolean canGoBack() { + Tab tab = Tabs.getInstance().getTab(mId); + if (tab != null) { + return tab.canDoBack(); + } + return false; + } + + /** + * Move backward in the session history, if that's possible. + */ + public void goBack() { + Tab tab = Tabs.getInstance().getTab(mId); + if (tab != null) { + tab.doBack(); + } + } + + /** + * Check to see if the Browser has session history and can go forward to a + * new page. + * @return A boolean flag indicating if forward session exists. + * This method will likely be removed and replaced by a callback in GeckoViewContent + */ + public boolean canGoForward() { + Tab tab = Tabs.getInstance().getTab(mId); + if (tab != null) { + return tab.canDoForward(); + } + return false; + } + + /** + * Move forward in the session history, if that's possible. + */ + public void goForward() { + Tab tab = Tabs.getInstance().getTab(mId); + if (tab != null) { + tab.doForward(); + } + } + } + + /* Provides a means for the client to indicate whether a JavaScript + * dialog request should proceed. An instance of this class is passed to + * various GeckoViewChrome callback actions. + */ + public class PromptResult { + private final int RESULT_OK = 0; + private final int RESULT_CANCEL = 1; + + private final String mGUID; + + public PromptResult(String guid) { + mGUID = guid; + } + + private JSONObject makeResult(int resultCode) { + JSONObject result = new JSONObject(); + try { + result.put("guid", mGUID); + result.put("button", resultCode); + } catch(JSONException ex) { } + return result; + } + + /** + * Handle a confirmation response from the user. + */ + public void confirm() { + JSONObject result = makeResult(RESULT_OK); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString())); + } + + /** + * Handle a confirmation response from the user. + * @param value String value to return to the browser context. + */ + public void confirmWithValue(String value) { + JSONObject result = makeResult(RESULT_OK); + try { + result.put("textbox0", value); + } catch(JSONException ex) { } + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString())); + } + + /** + * Handle a cancellation response from the user. + */ + public void cancel() { + JSONObject result = makeResult(RESULT_CANCEL); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString())); + } + } + + public interface ChromeDelegate { + /** + * Tell the host application that Gecko is ready to handle requests. + * @param view The GeckoView that initiated the callback. + */ + public void onReady(GeckoView view); + + /** + * Tell the host application to display an alert dialog. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that is loading the content. + * @param message The string to display in the dialog. + * @param result A PromptResult used to send back the result without blocking. + * Defaults to cancel requests. + */ + public void onAlert(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result); + + /** + * Tell the host application to display a confirmation dialog. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that is loading the content. + * @param message The string to display in the dialog. + * @param result A PromptResult used to send back the result without blocking. + * Defaults to cancel requests. + */ + public void onConfirm(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result); + + /** + * Tell the host application to display an input prompt dialog. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that is loading the content. + * @param message The string to display in the dialog. + * @param defaultValue The string to use as default input. + * @param result A PromptResult used to send back the result without blocking. + * Defaults to cancel requests. + */ + public void onPrompt(GeckoView view, GeckoView.Browser browser, String message, String defaultValue, GeckoView.PromptResult result); + + /** + * Tell the host application to display a remote debugging request dialog. + * @param view The GeckoView that initiated the callback. + * @param result A PromptResult used to send back the result without blocking. + * Defaults to cancel requests. + */ + public void onDebugRequest(GeckoView view, GeckoView.PromptResult result); + } + + public interface ContentDelegate { + /** + * A Browser has started loading content from the network. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that is loading the content. + * @param url The resource being loaded. + */ + public void onPageStart(GeckoView view, GeckoView.Browser browser, String url); + + /** + * A Browser has finished loading content from the network. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that was loading the content. + * @param success Whether the page loaded successfully or an error occured. + */ + public void onPageStop(GeckoView view, GeckoView.Browser browser, boolean success); + + /** + * A Browser is displaying content. This page could have been loaded via + * network or from the session history. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that is showing the content. + */ + public void onPageShow(GeckoView view, GeckoView.Browser browser); + + /** + * A page title was discovered in the content or updated after the content + * loaded. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that is showing the content. + * @param title The title sent from the content. + */ + public void onReceivedTitle(GeckoView view, GeckoView.Browser browser, String title); + + /** + * A link element was discovered in the content or updated after the content + * loaded that specifies a favicon. + * @param view The GeckoView that initiated the callback. + * @param browser The Browser that is showing the content. + * @param url The href of the link element specifying the favicon. + * @param size The maximum size specified for the favicon, or -1 for any size. + */ + public void onReceivedFavicon(GeckoView view, GeckoView.Browser browser, String url, int size); + } + +}