diff options
Diffstat (limited to 'mobile/android/docs/geckoview/design')
7 files changed, 1236 insertions, 0 deletions
diff --git a/mobile/android/docs/geckoview/design/breaking-changes.rst b/mobile/android/docs/geckoview/design/breaking-changes.rst new file mode 100644 index 0000000000..8838a225fa --- /dev/null +++ b/mobile/android/docs/geckoview/design/breaking-changes.rst @@ -0,0 +1,232 @@ +Breaking changes in GeckoView +============================= + +Agi sferro <agi@sferro.dev> + +Abstract +-------- + +This document describes the reasoning behind the GeckoView deprecation policy, +where we are today and where we want to be in the future. + +Background +---------- + +The following sections illustrate how breaking changes are expensive and +frustrating as a consumer of GeckoView, as a Gecko engineer and as an external +consumer, how they take away time from the Fenix team and reduce the average +testing time on Nightly up to 30%. And finally, how breaking changes negate the +very advantages that brought us to the current modularized architecture. + +Introduction +------------ + +GeckoView is a library that provides consumers access to Gecko and is the main +way through which Gecko is consumed on Mozilla’s Android products. + +GeckoView provides Nightly, Beta and Release channels which update with the +same cadence as Firefox Desktop does. + +Firefox for Android (code name Fenix) is developed on a standalone repository +on GitHub and uses GeckoView through Android Components (AC for short), an +Android library also developed on its own standalone repository. + +Fenix also provides Nightly, Beta and Release updates that mirror GeckoView and +Firefox Desktop’s. + +Testing days +------------ + +All Firefox Gecko-based products release a new major version every 4 weeks. +Which means that, on average, a commit that lands on a random day during the +release cycle gets 2 weeks of testing time on the Nightly user base. + +We try to increase the average testing time on Nightly by having a few “soft” +code-freeze days before each Merge day where engineers are not supposed to push +risky changes, but there’s no enforcement and it’s left to each engineer to +decide whether their change is risky or not. + +Each day where the Nightly build is delayed, every change contained in the +current Nightly cycle gets 7% (1 out of 14 days) on average less testing that +it normally would during a build. That is assuming that a problem gets +immediately reported and the report is immediately referred to the right +Engineering team. + +Assuming a 4 days report delay, each day where the Nightly build is delayed, +due to reasons such as breaking changes, reduces the average testing time by +10%. + +Nightly update +-------------- + +Fenix Nightly consumes GeckoView indirectly through Android Components. Each +day, an automated script makes a change in Fenix’s codebase to update AC’s +version. This change is then submitted to Fenix’s CI and, if all tests pass, is +merged to the codebase automatically. + +A new Fenix Nightly build is then generated and automatically published to +Google’s Play Store, from where it gets distributed to all Nightly users on +Android. + +Android Components has a similar automated process which publishes new versions +every day, picking up the new GeckoView nightly build. + +The update process fails from time to time. The cause of the failure largely +falls in one of the following three buckets. + +- An intermittent test failure +- A bug introduced in the latest AC or GeckoView update which causes a test to + fail +- A backward incompatible change has been made in AC or GeckoView that breaks + the build. + +The current mitigation for 1 is to disable or fix tests that fail +intermittently, similarly to what happens in mozilla-central. + +2 and 3 are problems unique to Fenix and AC (as compared to Firefox Desktop) +and are a direct consequence of the multi-package infrastructure of Fenix. + +Build breakages +--------------- + +When the automated Nightly update fails, an engineer on the Fenix team needs to +manually intervene to unblock the build. + +The need for a manual intervention automatically adds a day of Nightly build +delay when the failure occurs outside of business hours, and 2 or 3 days of +delay when the failure happens on a Friday night. + +Therefore, even assuming that a build breakage takes no time to fix, the +average testing time is reduced by 7-30% for each build breakage that occurs. + +In the case where the breakage takes a few days or more to fix, the average +testing time can be reduced to as much as half of what it would be on a +breakage-free Nightly cycle. + +Build breakages put undue burden on the Fenix team, who has to jump on the +breakage and has to drop their current work to avoid losing additional testing +days. + +Reducing breakages +------------------ + +Breakages caused by upstream teams like GeckoView can be divided into 2 groups: + +- Behavior changes that cause test failures downstream +- Breaking changes in the API that cause the build to fail. + +To reduce breakages from group 1, the GeckoView team maintains an extensive set +of integration tests that operate solely on the GeckoView API, and therefore +rarely break because of refactoring. + +For group 2, the GeckoView team instituted a deprecation policy which requires +each backward-incompatible change to keep the old code for 3 releases, allowing +downstream consumers, like Fenix, time to migrate asynchronously to the new +code without breaking the build. + +Functional testing and prototyping +---------------------------------- + +GeckoView offers a test browser app called GeckoViewExample (or GVE) that is +developed in-tree and thus always available to test local changes. + +GVE is the main testing vehicle for Gecko and GeckoView engineers that want to +develop new code, however, there frequently are issues or new features that +cannot be tested on GVE and need to be tested directly on Fenix. + +To test new code in Fenix, the build system offers an easy way to swap +locally-build GeckoView in Fenix. + +The process of testing new Gecko code in Fenix needs to be straightforward, as +it’s often used by platform engineers that are unfamiliar with Android and +Fenix itself, and are not likely to retain knowledge from running code on +Android and would likely need help to do so from the GeckoView or Fenix team. + +Side-effects of build breakages +------------------------------- + +When a breakage lands in mozilla-central and until the breakage is fixed in the +Fenix codebase, a locally built GeckoView is not compatible with the +most-recent tip of Fenix. + +This can be confusing to an engineer that is unfamiliar to Fenix, and can cause +frustration and time lost trying to figure out why upstream code, without +modifications, fails to compile. + +Beyond confusion, an incompatibility on the GeckoView/Fenix combined history +negates the primary advantage of building Fenix in a separate package: +decoupling Gecko from the Android front-end. + +Building older versions from source is also harder, as the set of version +couples (GeckoView, Fenix) that are compatible with each other is not +explicitly documented anywhere. + +External consumers +------------------ + +For apps interested in building a browser for Android, GeckoView provides the +unique combination of being a modern Web engine with a relatively stable API. + +For comparison, alternatives to GeckoView include: + +- WebView, Android’s way of embedding web pages on Android apps. WebView has + has several drawbacks for browser developers, including: + + - having a limited API for building browsers, as it does not expose modern + Web features or browser-specific APIs like bookmarks, passwords, etc; + - not allowing developers to control the underlying Chromium version. WebView + users will get whatever version of WebView is installed on the device. + - On the other hand, using WebView has the advantage of providing a smaller + download package, as the bulk of the engine is already installed on the + device. + +- Fork Chromium, which has the drawback of either having to rewrite the entire + browser front-end or locally patching the Chrome front-end, which involves + frequent changes and updates to be on top of. Using Chromium has the advantage + of providing the most stable, performant and compatible Web Engine on the + market. + +If the cost of updating GeckoView becomes high enough because of frequent API +changes, the advantage of using GeckoView is negated. + +Prior Art +--------- + +Many public libraries offer a deprecation policy similar or better than +GeckoView. For example, Android APIs need to be deprecated for a few releases +before being considered for removal, and completely removed only in exceptional +cases. Google products’ deprecated APIs are supported for a year before being +removed. Ebay requires deprecating an API before removal. + +Status quo +---------- + +Making backward-incompatible changes to the GeckoView API is currently heavily +discouraged and requires approval by the GeckoView team. + +We do, however, have breaking changes from time to time. The last breaking +change was in June 2021, a refactor of the permission API which we didn’t think +was worth executing in a backward compatible way. Before that, the last +breaking change was in September 2020. + +Tracking breaking changes +------------------------- + +Internally, GeckoView tracks the API using apilint. Each change that touches +the API requires an additional GeckoView peer to review the patch and a +description of the change in the changelog. + +Apilint also tracks deprecated APIs and enforces their removal, so that old, +deprecated APIs don’t linger in the codebase for longer than necessary. + +The future +---------- + +The ideal end state for GeckoView would be to not have any more backward +incompatible changes. Our experience is that supporting the old APIs for a +limited time is a small overhead in our development and that the benefits from +having a backward compatible API greatly outweigh the cost. + +We cannot, however, predict all future needs of GeckoView and Firefox as a +whole, so we cannot exclude the possibility of having new breaking changes +going forward. diff --git a/mobile/android/docs/geckoview/design/index.rst b/mobile/android/docs/geckoview/design/index.rst new file mode 100644 index 0000000000..f0e2a2dc84 --- /dev/null +++ b/mobile/android/docs/geckoview/design/index.rst @@ -0,0 +1,19 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +=========== +Design docs +=========== + +.. toctree:: + :maxdepth: 1 + :glob: + :hidden: + + * + +- `Breaking changes <breaking-changes.html>`_ +- `Login Storage <login-storage-api.html>`_ +- `Extension Managing <managing-extensions.html>`_ +- `Priority Hint <priority-hint.html>`_ +- `Save to PDF <save-to-pdf.html>`_ +- `Sharing rust libraries across the Firefox stack <sharing-rust-libraries.html>`_ diff --git a/mobile/android/docs/geckoview/design/login-storage-api.rst b/mobile/android/docs/geckoview/design/login-storage-api.rst new file mode 100644 index 0000000000..3dc4ceac62 --- /dev/null +++ b/mobile/android/docs/geckoview/design/login-storage-api.rst @@ -0,0 +1,207 @@ +GeckoView Login Storage API +=========================== + +Eugen Sawin <esawin@mozilla.com> + +December 20th, 2019 + +Motivation +---------- + +The current GV Autofill API provides all the essential callbacks and meta +information for the implementation of autofill/login app support. It also +manages the fallback to the Android ``AutofillManager``, which delegates +requests to the system-wide autofill service set by the user. + +However, the current GV Autofill API does not leverage the complete range of +Gecko heuristics that handle many autofill/login scenarios. + +The GV Login Storage API is meant to bridge that gap and provide an +intermediate solution for Fenix to enable feature-rich autofill/login support +without duplicating Gecko mechanics. As a storage-level API, it would also +enable easy integration with the existing Firefox Sync AC. + +API Proposal A (deprecated) +--------------------------- + +Unified Login Storage API: session delegate + +.. code:: java + + class LoginStorage { + class Login { + String guid; + // @Fenix: currently called `hostname` in AsyncLoginsStorage. + String origin; + // @Fenix: currently called `formSubmitURL` in AsyncLoginsStorage + String formActionOrigin; + String httpRealm; + String username; + String password; + } + + class Hint { + // @Fenix: Automatically save the login and indicate this to the + // user. + int GENERATED; + // @Fenix: Don’t prompt to save but allow the user to open UI to + // save if they really want. + int PRIVATE_MODE; + // The data looks like it may be some other data (e.g. CC) entered + // in a password field. + // @Fenix: Don’t prompt to save but allow the user to open UI to + // save if they want (e.g. in case the CC number is actually the + // username for a credit card account) + int LOW_CONFIDENCE; + // TBD + } + + interface Delegate { + // Notify that the given login has been used for login. + // @Fenix: call AsyncLoginsStorage.touch(login.guid). + void onLoginUsed(Login login); + + // Request logins for the given domain. + // @Fenix: return AsyncLoginsStorage.getByHostname(domain). + GeckoResult<Login[]> onLoginRequest(String domain); + + // Request to save or update the given login. + // The hint should help determining the appropriate user prompting + // behavior. + // @Fenix: Use the API from application-services/issues/1983 to + // determine whether to show a Save or Update button on the + // doorhanger, taking into account un/pw edits in the doorhanger. + // When the user confirms the save/update, + void onLoginSave(Login login, int hint); + + // TBD (next API iteration): handle autocomplete selection. + // GeckoResult<Login> onLoginSelect(Login[] logins); + } + } + +Extension of ``GeckoSession`` + +.. code:: java + + // Extending existing session class. + class GeckoSession { + // Set the login storage delegate for this session. + void setLoginStorageDelegate(LoginStorage.Delegate delegate); + + LoginStorage.Delegate getLoginStorageDelegate(); + } + +API Proposal B +-------------- + +Split Login Storage API: runtime storage delegate / session prompts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Split storing and prompting. Fetching and saving of logins is handled by the +runtime delegate, prompting for saving and (in future) autocompletion is +handled by the prompt delegate. + +.. code:: java + + class LoginStorage { + class Login { + String guid; + // @Fenix: currently called `hostname` in AsyncLoginsStorage. + String origin; + // @Fenix: currently called `formSubmitURL` in AsyncLoginsStorage + String formActionOrigin; + String httpRealm; + String username; + String password; + } + + interface Delegate { + // v2 + // Notify that the given login has been used for login. + // @Fenix: call AsyncLoginsStorage.touch(login.guid). + void onLoginUsed(Login login); + + // Request logins for the given domain. + // @Fenix: return AsyncLoginsStorage.getByHostname(domain). + GeckoResult<Login[]> onLoginFetch(String domain); + + // Request to save or update the given login. + void onLoginSave(Login login); + } + } + +Extension of ``GeckoRuntime`` + +.. code:: java + + // Extending existing runtime class. + class GeckoRuntime { + // Set the login storage delegate for this runtime. + void setLoginStorageDelegate(LoginStorage.Delegate delegate); + } + +Extension of ``GeckoSession.PromptDelegate`` + +.. code:: java + + // Extending existing prompt delegate. + class GeckoSession { + interface PromptDelegate { + class LoginStoragePrompt extends BasePrompt { + class Type { + int SAVE; + // TBD: autocomplete selection. + // int SELECT; + } + + class Hint { + // v2 + // @Fenix: Automatically save the login and indicate this + // to the user. + int GENERATED; + // @Fenix: Don’t prompt to save but allow the user to open + // UI to save if they really want. + int PRIVATE_MODE; + // The data looks like it may be some other data (e.g. CC) + // entered in a password field + // @Fenix: Don’t prompt to save but allow the user to open + // UI to save if they want (e.g. in case the CC number is + // actually the username for a credit card account) + int LOW_CONFIDENCE; + // TBD + } + + // Type + int type; + + // Hint + // The hint should help determining the appropriate user + // prompting behavior. + // @Fenix: Use the API from application-services/issues/1983 to + // determine whether to show a Save or Update button on the + // doorhanger, taking into account un/pw edits in the + // doorhanger. When the user confirms the save/update. + int hint; + + // For SAVE, it will hold the login to be stored or updated. + // For SELECT, it will hold the logins for the autocomplete + // selection. + Login[] logins; + + // Confirm SAVE prompt: the login would include a user’s edits + // to what will be saved. + // v2 + // Confirm SELECT (autocomplete) prompt by providing the + // selected login. + PromptResponse confirm(Login login); + + // Dismiss request. + PromptResponse dismiss(); + } + + GeckoResult<PromptResponse> onLoginStoragePrompt( + GeckoSession session, + LoginStoragePrompt prompt + ); + } + } diff --git a/mobile/android/docs/geckoview/design/managing-extensions.rst b/mobile/android/docs/geckoview/design/managing-extensions.rst new file mode 100644 index 0000000000..8592854ecd --- /dev/null +++ b/mobile/android/docs/geckoview/design/managing-extensions.rst @@ -0,0 +1,229 @@ +GeckoView Extension Managing API +================================ + +Agi Sferro <agi@sferro.dev> + +November 19th, 2019 + +Introduction +------------ + +This document describes the API for installing, uninstalling and updating +Extensions with GeckoView. + +Installing an extension provides the extension the ability to run at startup +time, especially useful for e.g. extensions that intercept network requests, +like an ad-blocker or a proxy extension. It also provides additional security +from third-party extensions like signature checking and prompting the user for +permissions. + +For this version of the API we will assume that the extension store is backed +by ``addons.mozilla.org``, and so are the signatures. Running a third-party +extension store is something we might consider in the future but explicitly not +in scope for this document. + +API +--- + +The embedder will be able to install, uninstall, enable, disable and update +extensions using the similarly-named APIs. + +Installing +^^^^^^^^^^ + +Gecko will download the extension pointed by the URI provided in install, parse +the manifest and signature and provide an ``onInstallPrompt`` callback with the +list of permissions requested by the extension and some information about the +extension. + +The embedder will be able to install bundled first-party extensions using +``installBuiltIn``. This method will only accept URIs that start with +``resource://`` and will give additional privileges like being able to use app +messaging and not needing a signature. + +Each permission will have a machine readable name that the embedder will use to +produce user-facing internationalized strings. E.g. “bookmarks” gives access to +bookmarks, “sessions” gives access to recently closed sessions. The full list +of permissions that are currently shown to the UI in Firefox Desktop is +available at: ``chrome/browser/browser.properties`` + +Updating +^^^^^^^^ + +To update an extension, the embedder will be able to call update which will +check if any update is available (using the update_url provided by the +extension, or addons.mozilla.org if no update_url has been provided). The +embedder will receive a GeckoResult that will provide the updated extension +object. This result can also be used to know when the update process is +complete, e.g. the embedder could use it to display a persistent notification +to the user to avoid having the app be killed while updates are in process. + +If the updated extension needs additional permissions, ``GeckoView`` will call +``onUpdatePrompt``. + +Until this callback is resolved (i.e. the embedder’s returned ``GeckoResult`` +is completed), the old addon will be running, only when the prompt is resolved +and the update is applied the new version of the addon starts running and the +``GeckoResult`` returned from update is resolved. + +This callback will provide both the current ``WebExtension`` object and the +updated WebExtension object so that the embedder can show appropriate +information to the user, e.g. the app might decide to remember whether the user +denied the request for a certain version and only prompt the user once the +version string changes. + +As a side effect of updating, Gecko will check its internal blocklist and might +disable extensions that are incompatible with the current version of Gecko or +deemed unsafe. The resulting ``WebExtension`` object will reflect that by +having isEnabled set to false. The embedder will be able to inspect the reason +why the extension was disabled using ``metaData.blockedReason``. + +Gecko will not update any extension or blocklist state without the embedder’s +input. + +Enabling and Disabling +^^^^^^^^^^^^^^^^^^^^^^ + +Embedders will be able to enable and disabling extension using the homonymous +APIs. Calling enable on an extension might not actually enable it if the +extension has been added to the Gecko blocklist. Embedders can check the value +of ``metaData.blockedReason`` to display to the user whether the extension can +actually be enabled or not. The returned WebExtension object will reflect the +updated enablement state in isEnabled. + +Listing +^^^^^^^ + +The embedder is expected to keep a collection of all available extensions using +the result of install and update. To retrieve the extensions that are already +installed the embedder will be able to use ``listInstalled`` which will +asynchronously retrieve the full list of extensions. We recommend calling +``listInstalled`` every time the user is presented with the extension manager +UI to ensure all information is up to date. + +Outline +^^^^^^^ + +.. code:: java + + public class WebExtensionController { + // Start the process of installing an extension, + // the embedder will either get the installed extension + // or an error + GeckoResult<WebExtension> install(String uri); + + // Install a built-in WebExtension with privileged + // permissions, uri must be resource:// + // Privileged WebExtensions have access to experiments + // (i.e. they can run chrome code), don’t need signatures + // and have access to native messaging to the app + GeckoResult<WebExtension> installBuiltIn(String uri) + + GeckoResult<Void> uninstall(WebExtension extension); + + GeckoResult<WebExtension> enable(WebExtension extension); + + GeckoResult<WebExtension> disable(WebExtension extension); + + GeckoResult<List<WebExtension>> listInstalled(); + + // Checks for updates. This method returns a GeckoResult that is + // resolved either with the updated WebExtension object or null + // if the extension does not have pending updates. + GeckoResult<WebExtension> update(WebExtension extension); + + public interface PromptDelegate { + GeckoResult<AllowOrDeny> onInstallPrompt(WebExtension extension); + + GeckoResult<AllowOrDeny> onUpdatePrompt( + WebExtension currentlyInstalled, + WebExtension updatedExtension, + List<String> newPermissions); + + // Called when the extension calls browser.permission.request + GeckoResult<AllowOrDeny> onOptionalPrompt( + WebExtension extension, + List<String> optionalPermissions); + } + + void setPromptDelegate(PromptDelegate promptDelegate); + } + +As part of this document, we will add a ``MetaData`` field to WebExtension +which will contain all the information known about the extension. Note: we will +rename ``ActionIcon`` to Icon to represent its generic use as the +``WebExtension`` icon class. + +.. code:: java + + public class WebExtension { + // Renamed from ActionIcon + static class Icon {} + + final MetaData metadata; + final boolean isBuiltIn; + + final boolean isEnabled; + + public static class SignedStateFlags { + final static int UNKNOWN; + final static int PRELIMINARY; + final static int SIGNED; + final static int SYSTEM; + final static int PRIVILEGED; + } + + // See nsIBlocklistService.idl + public static class BlockedReason { + final static int NOT_BLOCKED; + final static int SOFTBLOCKED; + final static int BLOCKED; + final static int OUTDATED; + final static int VULNERABLE_UPDATE_AVAILABLE; + final static int VULNERABLE_NO_UPDATE; + } + + public class MetaData { + final Icon icon; + final String[] permissions; + final String[] origins; + final String name; + final String description; + final String version; + final String creatorName; + final String creatorUrl; + final String homepageUrl; + final String optionsPageUrl; + final boolean openOptionsPageInTab; + final boolean isRecommended; + final @BlockedReason int blockedReason; + final @SignedState int signedState; + // more if needed + } + } + +Implementation Details +^^^^^^^^^^^^^^^^^^^^^^ + +We will use ``AddonManager`` as a backend for ``WebExtensionController`` and +delegate the prompt to the app using ``PromptDelegate``. We will also merge +``WebExtensionController`` and ``WebExtensionEventDispatcher`` for ease of +implementation. + +Existing APIs +^^^^^^^^^^^^^ + +Some APIs today return a ``WebExtension`` object that might have not been +fetched yet by ``listInstalled``. In these cases, GeckoView will return a stub +``WebExtension`` object in which the metadata field will be null to avoid +waiting for a addon list call. To ensure that the metadata field is populated, +the embedder will need to call ``listInstalled`` at least once during the app +startup. + +Deprecation Path +^^^^^^^^^^^^^^^^ + +The existing ``registerWebExtension`` and ``unregisterWebExtension`` APIs will +be deprecated by ``installBuiltIn`` and ``uninstall``. We will remove the above +APIs 6 releases after the implementation of ``installBuiltIn`` lands and mark +it as deprecated in the API. diff --git a/mobile/android/docs/geckoview/design/priority-hint.rst b/mobile/android/docs/geckoview/design/priority-hint.rst new file mode 100644 index 0000000000..4a915bb7ed --- /dev/null +++ b/mobile/android/docs/geckoview/design/priority-hint.rst @@ -0,0 +1,68 @@ +GeckoView Priority Hint API +=========================== + +Cathy Lu <calu@mozilla.com>, `Bug 1764998 <https://bugzilla.mozilla.org/show_bug.cgi?id=1764998>`_ + +May 2nd, 2022 + +Summary +------- + +This document describes the API for setting a process to high priority by +applying a high priority hint. Instead of deducing the priority based on the +extension’s active priority, this will add an API to set it explicitly. + +Motivation +---------- + +This API will allow Glean metrics to be measured in order to compare +performance and stability metrics for process prioritization on vs off. +Previously, prioritization depended on whether or not a ``GeckoSession`` had a +surface associated with it, which lowered the priority of background tabs and +needed to be reloaded more often. + +Goals +----- + +Apps can set ``priorityHint`` on a ``GeckoSession``. + +Existing Work +------------- + +In `bug 1753700 <https://bugzilla.mozilla.org/show_bug.cgi?id=1753700>`_, we +added an API in dom/ipc to allow ``GeckoViewWebExtension`` to set a specific +``remoteTab``’s boolean ``priorityHint``. This allows tabs that do not have a +surface but are active according to web extension to have high priority. + +Implementation +-------------- + +In ``GeckoSession``, add an API ``setPriorityHint`` that takes an integer as a +parameter. The priority int can be ``PRIORITY_DEFAULT`` or ``PRIORITY_HIGH``. +Specified and active tabs would be ``PRIORITY_HIGH``. The default would be +``PRIORITY_DEFAULT``. The API will dispatch an event +``GeckoView:SetPriorityHint``. + +.. code:: java + + public void setPriorityHint(final @Priority int priorityHint) + +Listeners in ``GeckoViewContent.jsm`` will set +``this.browser.frameLoader.remoteTab.priorityHint`` to the boolean passed in. + +.. code:: java + + case "GeckoView:setPriorityHint": + if (this.browser.isRemoteBrowser) { + let remoteTab = this.browser.frameLoader?.remoteTab; + if (remoteTab) { + remoteTab.renderLayers.priorityHint = val; + } + } + break; + +Additional Complexities +----------------------- + +Apps that use this API will need to manually use the API to set the +priorityHint when the tab goes to foreground or background. diff --git a/mobile/android/docs/geckoview/design/save-to-pdf.rst b/mobile/android/docs/geckoview/design/save-to-pdf.rst new file mode 100644 index 0000000000..fc4edc6310 --- /dev/null +++ b/mobile/android/docs/geckoview/design/save-to-pdf.rst @@ -0,0 +1,202 @@ +GeckoView Save to PDF +===================== + +Olivia Hall <ohall@mozilla.com>, Jonathan Almeida <jon@mozilla.com> + +Why +--- + +- The Save to PDF feature was originally available in Fennec and users would + like to see the return of this feature. There are a lot of user requests for + Save to PDF in Fenix. +- We would have more parity with Desktop, and be able to share the same + underlying implementation with them. +- Product is currently evaluating the addition of pdf.js as well; having Save + to PDF would be an added bonus. + +Goals +----- + +- Save the current page to a text-based PDF document. +- Embedders should also be able to call into GeckoView to provide a PDF copy of + the selected GeckoSession. +- Enable the ability to iterate on PDF customizations. + +Non-Goals +--------- + +- We do not want to implement a PDF “preview” of the document prior to the + download. This has open questions: does Product want this, should this be + implemented by the embedder, etc. +- The generated PDF should not match the theme (e.g., light or dark mode) of + the currently displayed page - the PDF will always appear as themeless or as + a plain document. +- No customizable settings. The current API design will not include + customization settings that the embedder can control. This can be worked on + in a follow-up feature request. Our current API design however, would enable + for these particular iterations. + +What +---- + +This work will add a method to ``GeckoSession`` called ``savePdf`` for +embedders to use, which will communicate with a new ``GeckoViewPdf.jsm`` to +create the PDF file. When the document is available, the +``GeckoViewPdfController`` will notify the +``ContentDelegate.onExternalResponse`` with the downloadable document. + +- ``GeckoViewPdf.jsm`` - JavaScript implementation that converts the content to + a PDF and saves the file, also responds to messaging from + ``GeckoViewPdfController``. +- ``GeckoViewPdfController.java`` - The Controller coordinates between the Java + and JS through response messaging and notifies the content delegate when the + PDF is available for use. + +API +--- + +GeckoSession.java +^^^^^^^^^^^^^^^^^ + +.. code:: java + + public class GeckoSession { + public GeckoSession(final @Nullable GeckoSessionSettings settings) { + mPdfController = new PdfController(this); + } + + @UiThread + public void saveAsPdf(PdfSettings settings) { + mPdfController.savePdf(null); + } + } + + +GeckoViewPdf.jsm +^^^^^^^^^^^^^^^^ +.. code:: java + + this.registerListener([ + "GeckoView:SavePdf", + ]); + + async onEvent(aEvent, aData, aCallback) { + debug`onEvent: event=${aEvent}, data=${aData}`; + + switch (aEvent) { + case "GeckoView:SavePdf": + this.saveToPDF(); + Break; + } + } + } + + async saveToPDF() { + // Reference: https://searchfox.org/mozilla-central/source/remote/cdp/domains/parent/Page.jsm#519 + } + + +GeckoViewPdfController.java +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code:: java + + class PdfController { + private static final String LOGTAG = "PdfController"; + private final GeckoSession mSession; + + PdfController(final GeckoSession session) { + mSession = session; + } + + private PdfDelegate mDelegate; + private BundleEventListener mEventListener; + + /* package */ + PdfController() { + mEventListener = new EventListener(); + EventDispatcher.getInstance() + .registerUiThreadListener(mEventListener,"GeckoView:PdfSaved"); + } + + @UiThread + public void setDelegate(final @Nullable PdfDelegate delegate) { + ThreadUtils.assertOnUiThread(); + mDelegate = delegate; + } + + @UiThread + @Nullable + public PdfDelegate getDelegate() { + ThreadUtils.assertOnUiThread(); + return mDelegate; + } + + @UiThread + public void savePdf() { + ThreadUtils.assertOnUiThread(); + mEventDispatcher.dispatch("GeckoView:SavePdf", null); + } + + + private class EventListener implements BundleEventListener { + + @Override + public void handleMessage( + final String event, + final GeckoBundle message, + final EventCallback callback + ) { + if (mDelegate == null) { + callback.sendError("Not allowed"); + return; + } + + switch (event) { + case "GeckoView:PdfSaved": { + final ContentDelegate delegate = mSession.getContentDelegate(); + + if (message.containsKey("pdfPath")) { + InputStream inputStream; /* construct InputStream from local file path */ + WebResponse response = WebResponse.Builder() + .body(inputStream) + // Add other attributes as well. + .build(); + + if (delegate != null) { + delegate.onExternalResponse(mSession, response); + } else { + throw Exception("Needs ContentDelegate for this to work.") + } + } + + break; + } + } + } + } + } + +geckoview.js +^^^^^^^^^^^^ +.. code:: java + + { + name: "GeckoViewPdf", + onInit: { + resource: "resource://gre/modules/GeckoViewPdf.jsm", + } + } + + +Testing +------- + +- Tests for the jsm and java code will be covered by mochitests and junit. +- Make assertions to check that the text and images are in the finished PDF; + the PDF is a non-zero file size. + +Risks +----- + +The API and the code that this work would be using are pretty new, currently +pref'd off in Nightly and could contain implementation bugs. diff --git a/mobile/android/docs/geckoview/design/sharing-rust-libraries.rst b/mobile/android/docs/geckoview/design/sharing-rust-libraries.rst new file mode 100644 index 0000000000..1aa6657b1f --- /dev/null +++ b/mobile/android/docs/geckoview/design/sharing-rust-libraries.rst @@ -0,0 +1,279 @@ +Sharing rust libraries across the Firefox (for Android) stack +============================================================= + +`Agi Sferro <agi@sferro.dev>` + +March 20th, 2021 + +The problem +----------- + +We don’t have a good story for integrating a rust library so that it’s +available to use in Gecko, GeckoView, AC and Fenix and also in a way that rust +can call rust directly avoiding a C FFI layer. + +Goals +----- + +- Being able to integrate a rust library that can be called from Gecko, + GeckoView, AC, Fenix, including having singleton-like instances that are + shared across the stack, per-process. +- The rust library should be able to call and be called by other rust libraries + or rust code in Gecko directly (i.e. without a C FFI layer) +- A build-time assurance that all components in the stack compile against the + same version of the rust library +- Painless, quick and automated updates. Should be able to produce chemspill + updates for the rust library in under 24h with little manual intervention + (besides security checks / code review / QA). +- Support for non-Gecko consumers of the rust library is essential. I.e. + providing a version of Gecko that does not include any of the libraries +- (optional?) Provide an easy way to create bundles of rust libraries depending + on consumers needs. + +Proposal +-------- + +1. Rename libmegazord.so to librustcomponents.so to clarify what the purpose of + this artifact is. +2. Every rust library that wants to be called or wants to call rust code + directly will be included in libxul.so (which contains most of Gecko native + code), and vendored in mozilla-central. This includes, among others, Glean and + Nimbus. +3. libxul.so will expose the necessary FFI symbols for the Kotlin wrappers + needed by the libraries vendored in mozilla-central in step (2). +4. At every nightly / beta / release build of Gecko, we will generate an (or + possibly many) additional librustcomponents.so artifacts that will be published + as an AAR in maven.mozilla.org. This will also publish all the vendored + libraries in mozilla-central to maven, which will have a dependency on the + librustcomponents.so produced as part of this step. Doing this will ensure that + both libxul.so and librustcomponents.so contain the exact same code and can be + swapped freely in the dependency graph. +5. Provide a new GeckoView build with artifactId geckoview-omni which will + depend on all the rust libraries. The existing geckoview will not have such + dependency and will be kept for third-party users of GeckoView. +6. GeckoView will depend on the Kotlin wrappers of all the libraries that + depend on librustcomponents.so built in step (4) in the .pom file. For example + + .. code:: xml + + <dependency> + <groupId>org.mozilla.telemetry</groupId> + <artifactId>glean</artifactId> + <version>33.1.2</version> + <scope>compile</scope> + </dependency> + + It will also exclude the org.mozilla.telemetry.glean dependency to + librustcomponents.so, as the native code is now included in libxul.so as part + of step (2). Presumably Glean will discover where its native code lives by + either trying librustcomponents.so or libxul.so (or some other better methods, + suggestions welcome). + +7. Android Components and Fenix will remove their explicit dependency on Glean, + Nimbus and all other libraries provided by GeckoView, and instead consume the + one provided by GeckoView (this step is optional, note that any version + conflict would cause a build error). + + +The good +-------- + +- We get automated integration with AC for free. When an update for a library + is pushed to mozilla-central, a new nightly build for GeckoView will be + produced which is already consumed by AC automatically (and indirectly into + Fenix). +- Publishing infrastructure to maven is already figured out, and we can reuse + the existing process for GeckoView to publish all the dependencies. +- If a consumer (say AC) uses a mismatched version for a dependency, a + compile-time error will be thrown. +- All consumers of the rust libraries packaged this way are on the same version + (provided they stay up to date with releases) +- Non-Mozilla consumers get first-class visibility into what is packaged into + GeckoView, and can independently discover Glean, Nimbus, etc, since we define + our dependencies in the pom file. +- Gecko Desktop and Gecko Mobile consumer Glean and other libraries in the same + way, removing unnecessary deviation. + +Worth Noting +------------ + +- Uplifts to beta / release versions of Fenix will involve more checks as they + impact Gecko too. + +The Bad +------- + +- Libraries need to be vendored in mozilla-central. Dependencies will follow + the Gecko train which might not be right for them, as some dependencies don’t + really have a nightly vs stable version. - This could change in the future, as + the integration gets deeper and updates to the library become more frequent / + at every commit. +- Locally testing a change in a rust library involves rebuilding all of Gecko. + This is a side effect of statically linking rust libraries to Gecko. +- All rust libraries that are both used by Android and Gecko will need to be + updated together, and we cannot have separate versions on Desktop/Mobile. + Although this can be mitigated by providing flexible dependency on the library + side (e.g. nimbus doesn’t need to depend on a specific version of - Glean and + can accept whatever is in Gecko) +- Code that doesn’t natively live in mozilla-central has double the work to get + into a product - first a release process is needed from the native repo, then + a phabricator process for the vendoring. + +Alternatives Considered +----------------------- + +Telemetry delegate +^^^^^^^^^^^^^^^^^^ + +GeckoView provides a Java Telemetry delegate interface that Glean can implement +on the AC layer to provide Glean functionality to consumers. Glean would offer +a rust wrapper to the Java delegate API to transparently call either the +delegate (when built for mobile) or the Glean instance directly (when built for +Desktop). + +Drawbacks +""""""""" + +- This involves a lot of work on the Glean side to build and maintain the + delegate +- A large section of the Glean API is embedded in the GeckoView API without a + direct dependency +- We don’t expect the telemetry delegate to have other implementations other + than Glean itself, despite the apparent generic nature of the telemetry + delegate +- Glean and GeckoView engineers need to coordinate for every API update, as an + update to the Glean API likely triggers an update to the GV API. +- Gecko Desktop and Gecko Mobile use Glean a meaningfully different way +- Doesn’t solve the dependency problem: even though in theory this would allow + Gecko to work with multiple Glean versions, in practice the GV Telemetry + delegate is going to track Glean so closely that it will inevitably require + pretty specific Glean versions to work. + +Advantages +"""""""""" + +- Explicit code dependency, an uninformed observer can understand how telemetry + is extracted from GeckoView by just looking at the API +- No hard Glean version requirement, AC can be (in theory) built with a + different Glean version than Gecko and things would still work + +Why we decided against +"""""""""""""""""""""" + +The amount of ongoing maintenance work involved on the Glean side far outweighs +the small advantages, namely to not tie AC to a specific Glean version. +Significantly complicates the stack. + +Dynamic Discovery +^^^^^^^^^^^^^^^^^ + +Gecko discovers when it’s being loaded as part of Fenix (or some other +Gecko-powered browser) by calling dlsym on the Glean library. When the +discovery is successful, and the Glean version matches, Gecko will directly use +the Glean provided by Fenix. + +Drawbacks +""""""""" + +- Non standard, non-Mozilla apps will not expect this to work the way it does +- “Magic”: there’s no way to know that the dyscovery is happening (or what + version of Glean is provided with Gecko) unless you know it’s there. +- The standard failure mode is at runtime, as there’s no built-in way to check + that the version provided by Gecko is the same as the one provided by Fenix + at build time. +- Doesn’t solve the synchronization problem: Gecko and Fenix will have to be on + the same Glean version for this to work. +- Gecko Mobile deviates meaningfully from Desktop in the way it uses Glean for + no intrinsic reason + +Advantages +"""""""""" + +- This system is transparent to Consuming apps, e.g. Nimbus can use Glean as + is, with no significant modifications needed. + +Why we decided against +"""""""""""""""""""""" + +- This alternative does not provide substantial benefits over the proposal + outlined in this doc and has significant drawbacks like the runtime failure + case and the non-standard linking process. + +Hybrid Dynamic Discovery +^^^^^^^^^^^^^^^^^^^^^^^^ + +This is a variation of the Dynamic Discovery where Gecko and GeckoView include +Glean directly and consumers get Glean from Gecko dynamically (i.e. they dlsym +libxul.so). + +Drawbacks +""""""""" + +- Glean still needs to build a wrapper for libraries not included in Gecko + (like Nimbus) that want to call Glean directly. + +Advantages +"""""""""" + +- The dependency to Glean is explicit and clear from an uninformed observer + point of view. +- Smaller scope, only Glean would need to be moved to mozilla-central + +Why we decided against +"""""""""""""""""""""" + +Not enough advantages over the proposal, significant ongoing maintenance work +required from the Glean side. + +Open Questions +-------------- + +- How does iOS consume megazord today? Do they have a maven-like dependency + system we can use to publish the iOS megazord? +- How do we deal with licenses in about:license? Application-services has a + build step that extracts rust dependencies and puts them in the pom file +- What would be the process for coordinating a-c breaking changes? +- Would the desire to vendor apply even if this were not Rust code? + +Common Questions +---------------- + +- **How do we make sure GV/AC/Gecko consume the same version of the native + libraries?** The pom dependency in GeckoView ensures that any GeckoView + consumers depend on the same version of a given library, this includes AC and + Fenix. +- **What happens to non-Gecko consumers of megazord?** This plan is transparent + to a non-Gecko consumer of megazord, as they will still consume the native + libraries through the megazord dependency in Glean/Nimbus/etc. With the added + benefit that, if the consumer stays up to date with the megazord dependency, + they will use the same version that Gecko uses. +- **What’s the process to publish an update to the megazord?** When a team + wants to publish an update to the megazord it will need to commit the update + to mozilla-central. A new build will be generated in the next nightly cycle, + producing an updated version of the megazord. My understanding is that current + megazord releases are stable (and don’t have beta/nightly cycles) so for + external consumers, consuming the nightly build could be adequate, and provide + the fastest turnaround on updates. For Gecko consumers the turnaround will be + the same to Firefox Desktop (i.e. roughly 6-8 weeks from commit to release + build). +- **How do we handle security uplifts?** If you have a security release one + rust library you would need to request uplift to beta/release branches + (depending on impact) like all other Gecko changes. The process in itself can + be expedited and have a fast turnaround when needed (below 24h). We have been + using this process for all Gecko changes so I would not expect particular + problems with it. +- **What about OOP cases? E.g. GeckoView as a service?** We briefly discussed + this in the email chain, there are ways we could make that work (e.g. + providing a IPC shim). The details are fuzzy but since we don’t have any + immediate need for such support knowing that it’s doable with a reasonable + amount of work is enough for now. +- **Vendoring in mozilla-central seems excessive.** I agree. This is an + unfortunate requirement stemming from a few assumptions (which could be + challenged! We are choosing not to): + + - Gecko wants to vendor whatever it consumes for rust + - We want rust to call rust directly (without a C FFI layer) + - We want adding new libraries to be a painless experience + + Because of the above, vendoring in mozilla-central seems to be the best if not + the only way to achieve our goals. |