Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
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 }