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