1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/GeckoView.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,577 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import org.mozilla.gecko.db.BrowserDB; 1.12 +import org.mozilla.gecko.gfx.LayerView; 1.13 +import org.mozilla.gecko.mozglue.GeckoLoader; 1.14 +import org.mozilla.gecko.util.Clipboard; 1.15 +import org.mozilla.gecko.util.HardwareUtils; 1.16 +import org.mozilla.gecko.util.GeckoEventListener; 1.17 +import org.mozilla.gecko.util.ThreadUtils; 1.18 + 1.19 +import org.json.JSONException; 1.20 +import org.json.JSONObject; 1.21 + 1.22 +import android.app.Activity; 1.23 +import android.content.Context; 1.24 +import android.content.Intent; 1.25 +import android.content.SharedPreferences; 1.26 +import android.content.res.TypedArray; 1.27 +import android.os.Handler; 1.28 +import android.util.AttributeSet; 1.29 +import android.util.Log; 1.30 +import java.util.ArrayList; 1.31 +import java.util.Collections; 1.32 +import java.util.List; 1.33 + 1.34 +public class GeckoView extends LayerView 1.35 + implements GeckoEventListener, ContextGetter { 1.36 + 1.37 + private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView"; 1.38 + private static final String LOGTAG = "GeckoView"; 1.39 + 1.40 + private ChromeDelegate mChromeDelegate; 1.41 + private ContentDelegate mContentDelegate; 1.42 + 1.43 + public GeckoView(Context context) { 1.44 + super(context); 1.45 + init(context, null, true); 1.46 + } 1.47 + 1.48 + public GeckoView(Context context, AttributeSet attrs) { 1.49 + super(context, attrs); 1.50 + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GeckoView); 1.51 + String url = a.getString(R.styleable.GeckoView_url); 1.52 + boolean doInit = a.getBoolean(R.styleable.GeckoView_doinit, true); 1.53 + a.recycle(); 1.54 + init(context, url, doInit); 1.55 + } 1.56 + 1.57 + private void init(Context context, String url, boolean doInit) { 1.58 + // TODO: Fennec currently takes care of its own initialization, so this 1.59 + // flag is a hack used in Fennec to prevent GeckoView initialization. 1.60 + // This should go away once Fennec also uses GeckoView for 1.61 + // initialization. 1.62 + if (!doInit) 1.63 + return; 1.64 + 1.65 + // If running outside of a GeckoActivity (eg, from a library project), 1.66 + // load the native code and disable content providers 1.67 + boolean isGeckoActivity = false; 1.68 + try { 1.69 + isGeckoActivity = context instanceof GeckoActivity; 1.70 + } catch (NoClassDefFoundError ex) {} 1.71 + 1.72 + if (!isGeckoActivity) { 1.73 + // Set the GeckoInterface if the context is an activity and the GeckoInterface 1.74 + // has not already been set 1.75 + if (context instanceof Activity && getGeckoInterface() == null) { 1.76 + setGeckoInterface(new BaseGeckoInterface(context)); 1.77 + } 1.78 + 1.79 + Clipboard.init(context); 1.80 + HardwareUtils.init(context); 1.81 + 1.82 + // If you want to use GeckoNetworkManager, start it. 1.83 + 1.84 + GeckoLoader.loadMozGlue(); 1.85 + BrowserDB.setEnableContentProviders(false); 1.86 + } 1.87 + 1.88 + if (url != null) { 1.89 + GeckoThread.setUri(url); 1.90 + GeckoThread.setAction(Intent.ACTION_VIEW); 1.91 + GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(url)); 1.92 + } 1.93 + GeckoAppShell.setContextGetter(this); 1.94 + if (context instanceof Activity) { 1.95 + Tabs tabs = Tabs.getInstance(); 1.96 + tabs.attachToContext(context); 1.97 + } 1.98 + 1.99 + GeckoAppShell.registerEventListener("Gecko:Ready", this); 1.100 + GeckoAppShell.registerEventListener("Content:StateChange", this); 1.101 + GeckoAppShell.registerEventListener("Content:LoadError", this); 1.102 + GeckoAppShell.registerEventListener("Content:PageShow", this); 1.103 + GeckoAppShell.registerEventListener("DOMTitleChanged", this); 1.104 + GeckoAppShell.registerEventListener("Link:Favicon", this); 1.105 + GeckoAppShell.registerEventListener("Prompt:Show", this); 1.106 + GeckoAppShell.registerEventListener("Prompt:ShowTop", this); 1.107 + 1.108 + ThreadUtils.setUiThread(Thread.currentThread(), new Handler()); 1.109 + initializeView(GeckoAppShell.getEventDispatcher()); 1.110 + 1.111 + if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) { 1.112 + // This is the first launch, so finish initialization and go. 1.113 + GeckoProfile profile = GeckoProfile.get(context).forceCreate(); 1.114 + BrowserDB.initialize(profile.getName()); 1.115 + 1.116 + GeckoAppShell.setLayerView(this); 1.117 + GeckoThread.createAndStart(); 1.118 + } else if(GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) { 1.119 + // If Gecko is already running, that means the Activity was 1.120 + // destroyed, so we need to re-attach Gecko to this GeckoView. 1.121 + connectToGecko(); 1.122 + } 1.123 + } 1.124 + 1.125 + /** 1.126 + * Add a Browser to the GeckoView container. 1.127 + * @param url The URL resource to load into the new Browser. 1.128 + */ 1.129 + public Browser addBrowser(String url) { 1.130 + Tab tab = Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NEW_TAB); 1.131 + if (tab != null) { 1.132 + return new Browser(tab.getId()); 1.133 + } 1.134 + return null; 1.135 + } 1.136 + 1.137 + /** 1.138 + * Remove a Browser from the GeckoView container. 1.139 + * @param browser The Browser to remove. 1.140 + */ 1.141 + public void removeBrowser(Browser browser) { 1.142 + Tab tab = Tabs.getInstance().getTab(browser.getId()); 1.143 + if (tab != null) { 1.144 + Tabs.getInstance().closeTab(tab); 1.145 + } 1.146 + } 1.147 + 1.148 + /** 1.149 + * Set the active/visible Browser. 1.150 + * @param browser The Browser to make selected. 1.151 + */ 1.152 + public void setCurrentBrowser(Browser browser) { 1.153 + Tab tab = Tabs.getInstance().getTab(browser.getId()); 1.154 + if (tab != null) { 1.155 + Tabs.getInstance().selectTab(tab.getId()); 1.156 + } 1.157 + } 1.158 + 1.159 + /** 1.160 + * Get the active/visible Browser. 1.161 + * @return The current selected Browser. 1.162 + */ 1.163 + public Browser getCurrentBrowser() { 1.164 + Tab tab = Tabs.getInstance().getSelectedTab(); 1.165 + if (tab != null) { 1.166 + return new Browser(tab.getId()); 1.167 + } 1.168 + return null; 1.169 + } 1.170 + 1.171 + /** 1.172 + * Get the list of current Browsers in the GeckoView container. 1.173 + * @return An unmodifiable List of Browser objects. 1.174 + */ 1.175 + public List<Browser> getBrowsers() { 1.176 + ArrayList<Browser> browsers = new ArrayList<Browser>(); 1.177 + Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder(); 1.178 + for (Tab tab : tabs) { 1.179 + browsers.add(new Browser(tab.getId())); 1.180 + } 1.181 + return Collections.unmodifiableList(browsers); 1.182 + } 1.183 + 1.184 + /** 1.185 + * Not part of the public API. Ignore. 1.186 + */ 1.187 + public void handleMessage(final String event, final JSONObject message) { 1.188 + ThreadUtils.postToUiThread(new Runnable() { 1.189 + @Override 1.190 + public void run() { 1.191 + try { 1.192 + if (event.equals("Gecko:Ready")) { 1.193 + GeckoView.this.handleReady(message); 1.194 + } else if (event.equals("Content:StateChange")) { 1.195 + GeckoView.this.handleStateChange(message); 1.196 + } else if (event.equals("Content:LoadError")) { 1.197 + GeckoView.this.handleLoadError(message); 1.198 + } else if (event.equals("Content:PageShow")) { 1.199 + GeckoView.this.handlePageShow(message); 1.200 + } else if (event.equals("DOMTitleChanged")) { 1.201 + GeckoView.this.handleTitleChanged(message); 1.202 + } else if (event.equals("Link:Favicon")) { 1.203 + GeckoView.this.handleLinkFavicon(message); 1.204 + } else if (event.equals("Prompt:Show") || event.equals("Prompt:ShowTop")) { 1.205 + GeckoView.this.handlePrompt(message); 1.206 + } 1.207 + } catch (Exception e) { 1.208 + Log.w(LOGTAG, "handleMessage threw for " + event, e); 1.209 + } 1.210 + } 1.211 + }); 1.212 + } 1.213 + 1.214 + private void connectToGecko() { 1.215 + GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning); 1.216 + Tab selectedTab = Tabs.getInstance().getSelectedTab(); 1.217 + if (selectedTab != null) 1.218 + Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED); 1.219 + geckoConnected(); 1.220 + GeckoAppShell.setLayerClient(getLayerClientObject()); 1.221 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null)); 1.222 + } 1.223 + 1.224 + private void handleReady(final JSONObject message) { 1.225 + connectToGecko(); 1.226 + 1.227 + if (mChromeDelegate != null) { 1.228 + mChromeDelegate.onReady(this); 1.229 + } 1.230 + } 1.231 + 1.232 + private void handleStateChange(final JSONObject message) throws JSONException { 1.233 + int state = message.getInt("state"); 1.234 + if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) { 1.235 + if ((state & GeckoAppShell.WPL_STATE_START) != 0) { 1.236 + if (mContentDelegate != null) { 1.237 + int id = message.getInt("tabID"); 1.238 + mContentDelegate.onPageStart(this, new Browser(id), message.getString("uri")); 1.239 + } 1.240 + } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) { 1.241 + if (mContentDelegate != null) { 1.242 + int id = message.getInt("tabID"); 1.243 + mContentDelegate.onPageStop(this, new Browser(id), message.getBoolean("success")); 1.244 + } 1.245 + } 1.246 + } 1.247 + } 1.248 + 1.249 + private void handleLoadError(final JSONObject message) throws JSONException { 1.250 + if (mContentDelegate != null) { 1.251 + int id = message.getInt("tabID"); 1.252 + mContentDelegate.onPageStop(GeckoView.this, new Browser(id), false); 1.253 + } 1.254 + } 1.255 + 1.256 + private void handlePageShow(final JSONObject message) throws JSONException { 1.257 + if (mContentDelegate != null) { 1.258 + int id = message.getInt("tabID"); 1.259 + mContentDelegate.onPageShow(GeckoView.this, new Browser(id)); 1.260 + } 1.261 + } 1.262 + 1.263 + private void handleTitleChanged(final JSONObject message) throws JSONException { 1.264 + if (mContentDelegate != null) { 1.265 + int id = message.getInt("tabID"); 1.266 + mContentDelegate.onReceivedTitle(GeckoView.this, new Browser(id), message.getString("title")); 1.267 + } 1.268 + } 1.269 + 1.270 + private void handleLinkFavicon(final JSONObject message) throws JSONException { 1.271 + if (mContentDelegate != null) { 1.272 + int id = message.getInt("tabID"); 1.273 + mContentDelegate.onReceivedFavicon(GeckoView.this, new Browser(id), message.getString("href"), message.getInt("size")); 1.274 + } 1.275 + } 1.276 + 1.277 + private void handlePrompt(final JSONObject message) throws JSONException { 1.278 + if (mChromeDelegate != null) { 1.279 + String hint = message.optString("hint"); 1.280 + if ("alert".equals(hint)) { 1.281 + String text = message.optString("text"); 1.282 + mChromeDelegate.onAlert(GeckoView.this, null, text, new PromptResult(message.optString("guid"))); 1.283 + } else if ("confirm".equals(hint)) { 1.284 + String text = message.optString("text"); 1.285 + mChromeDelegate.onConfirm(GeckoView.this, null, text, new PromptResult(message.optString("guid"))); 1.286 + } else if ("prompt".equals(hint)) { 1.287 + String text = message.optString("text"); 1.288 + String defaultValue = message.optString("textbox0"); 1.289 + mChromeDelegate.onPrompt(GeckoView.this, null, text, defaultValue, new PromptResult(message.optString("guid"))); 1.290 + } else if ("remotedebug".equals(hint)) { 1.291 + mChromeDelegate.onDebugRequest(GeckoView.this, new PromptResult(message.optString("guid"))); 1.292 + } 1.293 + } 1.294 + } 1.295 + 1.296 + /** 1.297 + * Set the chrome callback handler. 1.298 + * This will replace the current handler. 1.299 + * @param chrome An implementation of GeckoViewChrome. 1.300 + */ 1.301 + public void setChromeDelegate(ChromeDelegate chrome) { 1.302 + mChromeDelegate = chrome; 1.303 + } 1.304 + 1.305 + /** 1.306 + * Set the content callback handler. 1.307 + * This will replace the current handler. 1.308 + * @param content An implementation of ContentDelegate. 1.309 + */ 1.310 + public void setContentDelegate(ContentDelegate content) { 1.311 + mContentDelegate = content; 1.312 + } 1.313 + 1.314 + public static void setGeckoInterface(final BaseGeckoInterface geckoInterface) { 1.315 + GeckoAppShell.setGeckoInterface(geckoInterface); 1.316 + } 1.317 + 1.318 + public static GeckoAppShell.GeckoInterface getGeckoInterface() { 1.319 + return GeckoAppShell.getGeckoInterface(); 1.320 + } 1.321 + 1.322 + protected String getSharedPreferencesFile() { 1.323 + return DEFAULT_SHARED_PREFERENCES_FILE; 1.324 + } 1.325 + 1.326 + public SharedPreferences getSharedPreferences() { 1.327 + return getContext().getSharedPreferences(getSharedPreferencesFile(), 0); 1.328 + } 1.329 + 1.330 + /** 1.331 + * Wrapper for a browser in the GeckoView container. Associated with a browser 1.332 + * element in the Gecko system. 1.333 + */ 1.334 + public class Browser { 1.335 + private final int mId; 1.336 + private Browser(int Id) { 1.337 + mId = Id; 1.338 + } 1.339 + 1.340 + /** 1.341 + * Get the ID of the Browser. This is the same ID used by Gecko for it's underlying 1.342 + * browser element. 1.343 + * @return The integer ID of the Browser. 1.344 + */ 1.345 + private int getId() { 1.346 + return mId; 1.347 + } 1.348 + 1.349 + /** 1.350 + * Load a URL resource into the Browser. 1.351 + * @param url The URL string. 1.352 + */ 1.353 + public void loadUrl(String url) { 1.354 + JSONObject args = new JSONObject(); 1.355 + try { 1.356 + args.put("url", url); 1.357 + args.put("parentId", -1); 1.358 + args.put("newTab", false); 1.359 + args.put("tabID", mId); 1.360 + } catch (Exception e) { 1.361 + Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e); 1.362 + } 1.363 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString())); 1.364 + } 1.365 + 1.366 + /** 1.367 + * Reload the current URL resource into the Browser. The URL is force loaded from the 1.368 + * network and is not pulled from cache. 1.369 + */ 1.370 + public void reload() { 1.371 + Tab tab = Tabs.getInstance().getTab(mId); 1.372 + if (tab != null) { 1.373 + tab.doReload(); 1.374 + } 1.375 + } 1.376 + 1.377 + /** 1.378 + * Stop the current loading operation. 1.379 + */ 1.380 + public void stop() { 1.381 + Tab tab = Tabs.getInstance().getTab(mId); 1.382 + if (tab != null) { 1.383 + tab.doStop(); 1.384 + } 1.385 + } 1.386 + 1.387 + /** 1.388 + * Check to see if the Browser has session history and can go back to a 1.389 + * previous page. 1.390 + * @return A boolean flag indicating if previous session exists. 1.391 + * This method will likely be removed and replaced by a callback in GeckoViewContent 1.392 + */ 1.393 + public boolean canGoBack() { 1.394 + Tab tab = Tabs.getInstance().getTab(mId); 1.395 + if (tab != null) { 1.396 + return tab.canDoBack(); 1.397 + } 1.398 + return false; 1.399 + } 1.400 + 1.401 + /** 1.402 + * Move backward in the session history, if that's possible. 1.403 + */ 1.404 + public void goBack() { 1.405 + Tab tab = Tabs.getInstance().getTab(mId); 1.406 + if (tab != null) { 1.407 + tab.doBack(); 1.408 + } 1.409 + } 1.410 + 1.411 + /** 1.412 + * Check to see if the Browser has session history and can go forward to a 1.413 + * new page. 1.414 + * @return A boolean flag indicating if forward session exists. 1.415 + * This method will likely be removed and replaced by a callback in GeckoViewContent 1.416 + */ 1.417 + public boolean canGoForward() { 1.418 + Tab tab = Tabs.getInstance().getTab(mId); 1.419 + if (tab != null) { 1.420 + return tab.canDoForward(); 1.421 + } 1.422 + return false; 1.423 + } 1.424 + 1.425 + /** 1.426 + * Move forward in the session history, if that's possible. 1.427 + */ 1.428 + public void goForward() { 1.429 + Tab tab = Tabs.getInstance().getTab(mId); 1.430 + if (tab != null) { 1.431 + tab.doForward(); 1.432 + } 1.433 + } 1.434 + } 1.435 + 1.436 + /* Provides a means for the client to indicate whether a JavaScript 1.437 + * dialog request should proceed. An instance of this class is passed to 1.438 + * various GeckoViewChrome callback actions. 1.439 + */ 1.440 + public class PromptResult { 1.441 + private final int RESULT_OK = 0; 1.442 + private final int RESULT_CANCEL = 1; 1.443 + 1.444 + private final String mGUID; 1.445 + 1.446 + public PromptResult(String guid) { 1.447 + mGUID = guid; 1.448 + } 1.449 + 1.450 + private JSONObject makeResult(int resultCode) { 1.451 + JSONObject result = new JSONObject(); 1.452 + try { 1.453 + result.put("guid", mGUID); 1.454 + result.put("button", resultCode); 1.455 + } catch(JSONException ex) { } 1.456 + return result; 1.457 + } 1.458 + 1.459 + /** 1.460 + * Handle a confirmation response from the user. 1.461 + */ 1.462 + public void confirm() { 1.463 + JSONObject result = makeResult(RESULT_OK); 1.464 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString())); 1.465 + } 1.466 + 1.467 + /** 1.468 + * Handle a confirmation response from the user. 1.469 + * @param value String value to return to the browser context. 1.470 + */ 1.471 + public void confirmWithValue(String value) { 1.472 + JSONObject result = makeResult(RESULT_OK); 1.473 + try { 1.474 + result.put("textbox0", value); 1.475 + } catch(JSONException ex) { } 1.476 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString())); 1.477 + } 1.478 + 1.479 + /** 1.480 + * Handle a cancellation response from the user. 1.481 + */ 1.482 + public void cancel() { 1.483 + JSONObject result = makeResult(RESULT_CANCEL); 1.484 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", result.toString())); 1.485 + } 1.486 + } 1.487 + 1.488 + public interface ChromeDelegate { 1.489 + /** 1.490 + * Tell the host application that Gecko is ready to handle requests. 1.491 + * @param view The GeckoView that initiated the callback. 1.492 + */ 1.493 + public void onReady(GeckoView view); 1.494 + 1.495 + /** 1.496 + * Tell the host application to display an alert dialog. 1.497 + * @param view The GeckoView that initiated the callback. 1.498 + * @param browser The Browser that is loading the content. 1.499 + * @param message The string to display in the dialog. 1.500 + * @param result A PromptResult used to send back the result without blocking. 1.501 + * Defaults to cancel requests. 1.502 + */ 1.503 + public void onAlert(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result); 1.504 + 1.505 + /** 1.506 + * Tell the host application to display a confirmation dialog. 1.507 + * @param view The GeckoView that initiated the callback. 1.508 + * @param browser The Browser that is loading the content. 1.509 + * @param message The string to display in the dialog. 1.510 + * @param result A PromptResult used to send back the result without blocking. 1.511 + * Defaults to cancel requests. 1.512 + */ 1.513 + public void onConfirm(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result); 1.514 + 1.515 + /** 1.516 + * Tell the host application to display an input prompt dialog. 1.517 + * @param view The GeckoView that initiated the callback. 1.518 + * @param browser The Browser that is loading the content. 1.519 + * @param message The string to display in the dialog. 1.520 + * @param defaultValue The string to use as default input. 1.521 + * @param result A PromptResult used to send back the result without blocking. 1.522 + * Defaults to cancel requests. 1.523 + */ 1.524 + public void onPrompt(GeckoView view, GeckoView.Browser browser, String message, String defaultValue, GeckoView.PromptResult result); 1.525 + 1.526 + /** 1.527 + * Tell the host application to display a remote debugging request dialog. 1.528 + * @param view The GeckoView that initiated the callback. 1.529 + * @param result A PromptResult used to send back the result without blocking. 1.530 + * Defaults to cancel requests. 1.531 + */ 1.532 + public void onDebugRequest(GeckoView view, GeckoView.PromptResult result); 1.533 + } 1.534 + 1.535 + public interface ContentDelegate { 1.536 + /** 1.537 + * A Browser has started loading content from the network. 1.538 + * @param view The GeckoView that initiated the callback. 1.539 + * @param browser The Browser that is loading the content. 1.540 + * @param url The resource being loaded. 1.541 + */ 1.542 + public void onPageStart(GeckoView view, GeckoView.Browser browser, String url); 1.543 + 1.544 + /** 1.545 + * A Browser has finished loading content from the network. 1.546 + * @param view The GeckoView that initiated the callback. 1.547 + * @param browser The Browser that was loading the content. 1.548 + * @param success Whether the page loaded successfully or an error occured. 1.549 + */ 1.550 + public void onPageStop(GeckoView view, GeckoView.Browser browser, boolean success); 1.551 + 1.552 + /** 1.553 + * A Browser is displaying content. This page could have been loaded via 1.554 + * network or from the session history. 1.555 + * @param view The GeckoView that initiated the callback. 1.556 + * @param browser The Browser that is showing the content. 1.557 + */ 1.558 + public void onPageShow(GeckoView view, GeckoView.Browser browser); 1.559 + 1.560 + /** 1.561 + * A page title was discovered in the content or updated after the content 1.562 + * loaded. 1.563 + * @param view The GeckoView that initiated the callback. 1.564 + * @param browser The Browser that is showing the content. 1.565 + * @param title The title sent from the content. 1.566 + */ 1.567 + public void onReceivedTitle(GeckoView view, GeckoView.Browser browser, String title); 1.568 + 1.569 + /** 1.570 + * A link element was discovered in the content or updated after the content 1.571 + * loaded that specifies a favicon. 1.572 + * @param view The GeckoView that initiated the callback. 1.573 + * @param browser The Browser that is showing the content. 1.574 + * @param url The href of the link element specifying the favicon. 1.575 + * @param size The maximum size specified for the favicon, or -1 for any size. 1.576 + */ 1.577 + public void onReceivedFavicon(GeckoView view, GeckoView.Browser browser, String url, int size); 1.578 + } 1.579 + 1.580 +}