diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /mobile/android/docs/geckoview | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/docs/geckoview')
36 files changed, 4713 insertions, 0 deletions
diff --git a/mobile/android/docs/geckoview/assets/DisableInstantRun.png b/mobile/android/docs/geckoview/assets/DisableInstantRun.png Binary files differnew file mode 100644 index 0000000000..e666f4c575 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/DisableInstantRun.png diff --git a/mobile/android/docs/geckoview/assets/GeckoViewStructure.png b/mobile/android/docs/geckoview/assets/GeckoViewStructure.png Binary files differnew file mode 100644 index 0000000000..a2ace94c32 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/GeckoViewStructure.png diff --git a/mobile/android/docs/geckoview/assets/LogInBugzilla.png b/mobile/android/docs/geckoview/assets/LogInBugzilla.png Binary files differnew file mode 100644 index 0000000000..ad18c58e30 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/LogInBugzilla.png diff --git a/mobile/android/docs/geckoview/assets/LogInOrRegister.png b/mobile/android/docs/geckoview/assets/LogInOrRegister.png Binary files differnew file mode 100644 index 0000000000..134ad28111 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/LogInOrRegister.png diff --git a/mobile/android/docs/geckoview/assets/LogInPhab.png b/mobile/android/docs/geckoview/assets/LogInPhab.png Binary files differnew file mode 100644 index 0000000000..68c6c5a60e --- /dev/null +++ b/mobile/android/docs/geckoview/assets/LogInPhab.png diff --git a/mobile/android/docs/geckoview/assets/api-diagram.png b/mobile/android/docs/geckoview/assets/api-diagram.png Binary files differnew file mode 100644 index 0000000000..da7d5acbdf --- /dev/null +++ b/mobile/android/docs/geckoview/assets/api-diagram.png diff --git a/mobile/android/docs/geckoview/assets/code-layers.png b/mobile/android/docs/geckoview/assets/code-layers.png Binary files differnew file mode 100644 index 0000000000..0ff9e27d5c --- /dev/null +++ b/mobile/android/docs/geckoview/assets/code-layers.png diff --git a/mobile/android/docs/geckoview/assets/css/geckoview.css b/mobile/android/docs/geckoview/assets/css/geckoview.css new file mode 100644 index 0000000000..e9564a3bc3 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/css/geckoview.css @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* There is some code in just-the-docs that completely breaks styling for code + * blocks, this is an attempt to fix that. */ +code { + font-weight: inherit; + font-size: 80%; +} diff --git a/mobile/android/docs/geckoview/assets/geckoview_icon_1color-black.png b/mobile/android/docs/geckoview/assets/geckoview_icon_1color-black.png Binary files differnew file mode 100644 index 0000000000..ab9e49d25c --- /dev/null +++ b/mobile/android/docs/geckoview/assets/geckoview_icon_1color-black.png diff --git a/mobile/android/docs/geckoview/assets/geckoview_icon_1color-green.png b/mobile/android/docs/geckoview/assets/geckoview_icon_1color-green.png Binary files differnew file mode 100644 index 0000000000..3f6738e6b9 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/geckoview_icon_1color-green.png diff --git a/mobile/android/docs/geckoview/assets/geckoview_icon_fullcolor-green.png b/mobile/android/docs/geckoview/assets/geckoview_icon_fullcolor-green.png Binary files differnew file mode 100644 index 0000000000..162891e993 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/geckoview_icon_fullcolor-green.png diff --git a/mobile/android/docs/geckoview/assets/js/search-data.json b/mobile/android/docs/geckoview/assets/js/search-data.json new file mode 100644 index 0000000000..50a4b9f489 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/js/search-data.json @@ -0,0 +1,12 @@ +--- +--- +{ + {% for page in site.html_pages %}"{{ forloop.index0 }}": { + "id": "{{ forloop.index0 }}", + "title": "{{ page.title | xml_escape }}", + "content": "{{ page.content | markdownify | strip_html | xml_escape | remove: 'Table of contents' | strip_newlines | replace: '\', ' ' }}", + "url": "{{ page.url | absolute_url | xml_escape }}", + "relUrl": "{{ page.url | xml_escape }}" + }{% if forloop.last %}{% else %}, + {% endif %}{% endfor %} +} diff --git a/mobile/android/docs/geckoview/assets/pageload-diagram.png b/mobile/android/docs/geckoview/assets/pageload-diagram.png Binary files differnew file mode 100644 index 0000000000..a1a15ea95a --- /dev/null +++ b/mobile/android/docs/geckoview/assets/pageload-diagram.png diff --git a/mobile/android/docs/geckoview/assets/view-runtime-session.png b/mobile/android/docs/geckoview/assets/view-runtime-session.png Binary files differnew file mode 100644 index 0000000000..17a3245d23 --- /dev/null +++ b/mobile/android/docs/geckoview/assets/view-runtime-session.png diff --git a/mobile/android/docs/geckoview/consumer/automation.rst b/mobile/android/docs/geckoview/consumer/automation.rst new file mode 100644 index 0000000000..2fd596e51b --- /dev/null +++ b/mobile/android/docs/geckoview/consumer/automation.rst @@ -0,0 +1,124 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +Configuring GeckoView for Automation +#################################### +How to set environment variables, Gecko arguments, and Gecko preferences for automation and debugging. + +.. contents:: :local: + +Configuring GeckoView +===================================== + +GeckoView and the underlying Gecko engine have many, many options, switches, and toggles "under the hood". Automation (and to a lesser extent, debugging) can require configuring the Gecko engine to allow (or disallow) specific actions or features. + +Some such actions and features are controlled by the `GeckoRuntimeSettings <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoRuntimeSettings.html>`_ instance you configure in your consuming project. For example, remote debugging web content via the Firefox Developer Tools is configured by `GeckoRuntimeSettings.Builder#remoteDebuggingEnabled <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoRuntimeSettings.Builder.html#remoteDebuggingEnabled(boolean)>`_ + +Not all actions and features have GeckoView API interfaces. Generally, actions and features that do not have GeckoView API interfaces are not intended for broad usage. Configuration for these types of things is controlled by: + +- environment variables in GeckoView's runtime environment +- command line arguments to the Gecko process +- internal Gecko preferences + +Automation-specific configuration is generally in this category. + +Running GeckoView with environment variables +------------------------------------------------ + +After a successful ``./mach build``, ``./mach run --setenv`` can be used to run GeckoView with +the given environment variables. + +For example, to enable extended logging for ``JSComponentLoader``, run ``./mach +run --setenv MOZ_LOG=JSComponentLoader:5``. + +Reading configuration from a file +------------------------------------------------ + +When GeckoView is embedded into a debugabble application (i.e., when your manifest includes ``android:debuggable="true"``), by default GeckoView reads configuration from a file named ``/data/local/tmp/$PACKAGE-geckoview-config.yaml``. For example, if your Android package name is ``com.yourcompany.yourapp``, GeckoView will read configuration from:: + + /data/local/tmp/com.yourcompany.yourapp-geckoview-config.yaml + + +Configuration file format +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The configuration file format is `YAML <https://yaml.org>`_. The following keys are recognized: + +- ``env`` is a map from string environment variable name to string value to set in GeckoView's runtime environment +- ``args`` is a list of string command line arguments to pass to the Gecko process +- ``prefs`` is a map from string Gecko preference name to boolean, string, or integer value to set in the Gecko profile + +.. code-block:: yaml + + # Contents of /data/local/tmp/com.yourcompany.yourapp-geckoview-config.yaml + + env: + MOZ_LOG: nsHttp:5 + + args: + - --marionette + - --profile + - "/path/to/gecko-profile" + + prefs: + foo.bar.boolean: true + foo.bar.string: "string" + foo.bar.int: 500 + + +Verifying configuration from a file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When configuration from a file is read, GeckoView logs to ``adb logcat``, like: :: + + GeckoRuntime I Adding debug configuration from: /data/local/tmp/org.mozilla.geckoview_example-geckoview-config.yaml + GeckoDebugConfig D Adding environment variables from debug config: {MOZ_LOG=nsHttp:5} + GeckoDebugConfig D Adding arguments from debug config: [--marionette] + GeckoDebugConfig D Adding prefs from debug config: {foo.bar.baz=true} + + +When a configuration file is found but cannot be parsed, an error is logged and the file is ignored entirely. When a configuration file is not found, nothing is logged. + +Controlling configuration from a file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, GeckoView provides a secure web rendering engine. Custom configuration can compromise security in many ways: by storing sensitive data in insecure locations on the device, by trusting websites with incorrect security configurations, by not validating HTTP Public Key Pinning configurations; the list goes on. + +**You should only allow such configuration if your end-user opts-in to the configuration!** + +GeckoView will always read configuration from a file if the consuming Android package is set as the current Android "debug app" (see ``set-debug-app`` and ``clear-debug-app`` in the `adb documentation <https://developer.android.com/studio/command-line/adb>`_). An Android package can be set as the "debug app" without regard to the ``android:debuggable`` flag. There can only be one "debug app" set at a time. To disable the "debug app" check, `disable reading configuration from a file entirely <#disabling-reading-configuration-from-a-file-entirely>`_. Setting an Android package as the "debug app" requires privileged shell access to the device (generally via ``adb shell am ...``, which is only possible on devices which have ADB debugging enabled) and therefore it is safe to act on the "debug app" flag. + +To enable reading configuration from a file: :: + + adb shell am set-debug-app --persistent com.yourcompany.yourapp + + +To disable reading configuration from a file: :: + + adb shell am clear-debug-app + +Enabling reading configuration from a file unconditionally +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some applications (for example, web browsers) may want to allow configuration for automation unconditionally, i.e., even when the application is not debuggable, like release builds that have ``android:debuggable="false"``. In such cases, you can use `GeckoRuntimeSettings.Builder#configFilePath`_ to force GeckoView to read configuration from the given file path, like: + +.. code-block:: java + + new GeckoRuntimeSettings.Builder() + .configFilePath("/your/app/specific/location") + .build(); + +Disabling reading configuration from a file entirely +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To force GeckoView to never read configuration from a file, even when the embedding application is debuggable, invoke `GeckoRuntimeSettings.Builder#configFilePath`_ with an empty path, like: + +.. code-block:: java + + new GeckoRuntimeSettings.Builder() + .configFilePath("") + .build(); + +The empty path is recognized and no file I/O is performed. + + +.. _GeckoRuntimeSettings.Builder#configFilePath: https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoRuntimeSettings.Builder.html#configFilePath(java.lang.String) diff --git a/mobile/android/docs/geckoview/consumer/geckoview-quick-start.rst b/mobile/android/docs/geckoview/consumer/geckoview-quick-start.rst new file mode 100644 index 0000000000..7721ff46a9 --- /dev/null +++ b/mobile/android/docs/geckoview/consumer/geckoview-quick-start.rst @@ -0,0 +1,117 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +Getting Started with GeckoView +###################################### + +How to use GeckoView in your Android app. + +*Building a browser? Check out* `Android Components <https://mozilla-mobile.github.io/firefox-android/>`_, *our collection of ready-to-use support libraries!* + +The following article is a brief guide to embedding GeckoView in an app. For a more in depth tutorial on getting started with GeckoView please read the article we have published on `raywenderlich.com <https://www.raywenderlich.com/1381698-android-tutorial-for-geckoview-getting-started>`_. + +.. contents:: :local: + +Configure Gradle +================= + +You need to add or edit four stanzas inside your module's ``build.gradle`` file. + +**1. Set the GeckoView version** + +*Like Firefox, GeckoView has three release channels: Stable, Beta, and Nightly. Browse the* `Maven Repository <https://maven.mozilla.org/?prefix=maven2/org/mozilla/geckoview/>`_ *to see currently available builds.* + +.. code-block:: groovy + + ext { + geckoviewChannel = <channel> + geckoviewVersion = <version> + } + + +**2. Add Mozilla's Maven repository** + +.. code-block:: groovy + + repositories { + maven { + url "https://maven.mozilla.org/maven2/" + } + } + + +**3. Java 11 required support** + +As GeckoView uses some Java 11 APIs, it requires these compatibility flags: + +.. code-block:: groovy + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + +**4. Add GeckoView Implementations** + +.. code-block:: groovy + + dependencies { + // ... + implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}:${geckoviewVersion}" + } + +Add GeckoView to a Layout +========================== + +Inside a layout ``.xml`` file, add the following: + +.. code-block:: xml + + <org.mozilla.geckoview.GeckoView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/geckoview" + android:layout_width="fill_parent" + android:layout_height="fill_parent" /> + +Initialize GeckoView in an Activity +==================================== + +**1. Import the GeckoView classes inside an Activity:** + +.. code-block:: java + + import org.mozilla.geckoview.GeckoRuntime; + import org.mozilla.geckoview.GeckoSession; + import org.mozilla.geckoview.GeckoView; + + +**2. Create a ``static`` member variable to store the ``GeckoRuntime`` instance.** + +.. code-block:: java + + private static GeckoRuntime sRuntime; + +**3. In that activity's** ``onCreate`` **function, add the following:** + +.. code-block:: java + + GeckoView view = findViewById(R.id.geckoview); + GeckoSession session = new GeckoSession(); + + // Workaround for Bug 1758212 + session.setContentDelegate(new GeckoSession.ContentDelegate() {}); + + if (sRuntime == null) { + // GeckoRuntime can only be initialized once per process + sRuntime = GeckoRuntime.create(this); + } + + session.open(sRuntime); + view.setSession(session); + session.loadUri("about:buildconfig"); // Or any other URL... + +You're done! +============== + +Your application should now load and display a webpage inside of GeckoView. + +To learn more about GeckoView's capabilities, review GeckoView's `JavaDoc <https://mozilla.github.io/geckoview/javadoc/mozilla-central/>`_ or the `reference application <https://searchfox.org/mozilla-central/source/mobile/android/geckoview_example>`_. diff --git a/mobile/android/docs/geckoview/consumer/index.rst b/mobile/android/docs/geckoview/consumer/index.rst new file mode 100644 index 0000000000..793ead2386 --- /dev/null +++ b/mobile/android/docs/geckoview/consumer/index.rst @@ -0,0 +1,23 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +=============== +Using GeckoView +=============== + +.. toctree:: + :maxdepth: 1 + :glob: + :hidden: + + * + +- `GeckoView Quick Start Guide <geckoview-quick-start.html>`__: Get + GeckoView up and running inside your application. +- `Interacting with Web Content <web-extensions.html>`__: Writing Web + Extensions, running content scripts and interacting with Javascript + running in a web page. +- `Working with Site Permissions <permissions.html>`__: Handling and + responding to requests from websites for permissions, such as + geolocation, storage, media etc. +- `Configuring GeckoView for Automation <automation.html>`__: Get GeckoView + set up on your automation system. diff --git a/mobile/android/docs/geckoview/consumer/permissions.rst b/mobile/android/docs/geckoview/consumer/permissions.rst new file mode 100644 index 0000000000..5dbab9b6b6 --- /dev/null +++ b/mobile/android/docs/geckoview/consumer/permissions.rst @@ -0,0 +1,287 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +============================= +Working with Site Permissions +============================= + +When a website wants to access certain services on a user’s device, it +will send out a permissions request. This document will explain how to +use GeckoView to receive those requests, and respond to them by granting +or denying those permissions. + +.. contents:: :local: + +The Permission Delegate +----------------------- + +The way an app interacts with site permissions in GeckoView is through +the +`PermissionDelegate <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html>`_. +There are three broad categories of permission that the +``PermissionDelegate`` handles, Android Permissions, Content Permissions +and Media Permissions. All site permissions handled by GeckoView fall +into one of these three categories. + +To get notified about permission requests, you need to implement the +``PermissionDelegate`` interface: + +.. code:: java + + private class ExamplePermissionDelegate implements GeckoSession.PermissionDelegate { + @Override + public void onAndroidPermissionsRequest(final GeckoSession session, + final String[] permissions, + final Callback callback) { } + + @Override + public void onContentPermissionRequest(final GeckoSession session, + final String uri, + final int type, final Callback callback) { } + + @Override + public void onMediaPermissionRequest(final GeckoSession session, + final String uri, + final MediaSource[] video, + final MediaSource[] audio, + final MediaCallback callback) { } + } + +You will then need to register the delegate with your +`GeckoSession <https://mozilla.github.io/geckoview/https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.html>`_ +instance. + +.. code:: java + + public class GeckoViewActivity extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ... + + final ExamplePermissionDelegate permission = new ExamplePermissionDelegate(); + session.setPermissionDelegate(permission); + + ... + } + } + +Android Permissions +~~~~~~~~~~~~~~~~~~~ + +Android permissions are requested whenever a site wants access to a +device’s navigation or input capabilities. + +The user will often need to grant these Android permissions to the app +alongside granting the Content or Media site permissions. + +When you receive an +`onAndroidPermissionsRequest <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#onAndroidPermissionsRequest(org.mozilla.geckoview.GeckoSession,java.lang.String[],org.mozilla.geckoview.GeckoSession.PermissionDelegate.Callback)>`_ +call, you will also receive the ``GeckoSession`` the request was sent +from, an array containing the permissions that are being requested, and +a +`Callback`_ +to respond to the request. It is then up to the app to request those +permissions from the device, which can be done using +`requestPermissions <https://developer.android.com/reference/android/app/Activity#requestPermissions(java.lang.String%5B%5D,%2520int)>`_. + +Possible ``permissions`` values are: +`ACCESS_COARSE_LOCATION <https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_COARSE_LOCATION>`_, +`ACCESS_FINE_LOCATION <https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_FINE_LOCATION>`_, +`CAMERA <https://developer.android.com/reference/android/Manifest.permission.html#CAMERA>`_ +or +`RECORD_AUDIO <https://developer.android.com/reference/android/Manifest.permission.html#RECORD_AUDIO>`_. + +.. code:: java + + private class ExamplePermissionDelegate implements GeckoSession.PermissionDelegate { + private Callback mCallback; + + public void onRequestPermissionsResult(final String[] permissions, + final int[] grantResults) { + if (mCallback == null) { return; } + + final Callback cb = mCallback; + mCallback = null; + for (final int result : grantResults) { + if (result != PackageManager.PERMISSION_GRANTED) { + // At least one permission was not granted. + cb.reject(); + return; + } + } + cb.grant(); + } + + @Override + public void onAndroidPermissionsRequest(final GeckoSession session, + final String[] permissions, + final Callback callback) { + mCallback = callback; + requestPermissions(permissions, androidPermissionRequestCode); + } + } + + public class GeckoViewActivity extends AppCompatActivity { + @Override + public void onRequestPermissionsResult(final int requestCode, + final String[] permissions, + final int[] grantResults) { + if (requestCode == REQUEST_PERMISSIONS || + requestCode == REQUEST_WRITE_EXTERNAL_STORAGE) { + final ExamplePermissionDelegate permission = (ExamplePermissionDelegate) + getCurrentSession().getPermissionDelegate(); + permission.onRequestPermissionsResult(permissions, grantResults); + } else { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + } + +Content Permissions +~~~~~~~~~~~~~~~~~~~ + +Content permissions are requested whenever a site wants access to +content that is stored on the device. The content permissions that can +be requested through GeckoView are: +`Geolocation <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_GEOLOCATION>`_, +`Site Notifications <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_DESKTOP_NOTIFICATION>`_, +`Persistent Storage <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_PERSISTENT_STORAGE>`_, +`XR <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_XR>`_, +`Autoplay Inaudible <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_AUTOPLAY_INAUDIBLE>`_, +`Autoplay Audible <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_AUTOPLAY_AUDIBLE>`_, +and +`DRM Media access <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_MEDIA_KEY_SYSTEM_ACCESS>`_. +Additionally, `tracking protection exceptions <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#PERMISSION_TRACKING>`_ +are treated as a type of content permission. + +When you receive an +`onContentPermissionRequest <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#onContentPermissionRequest(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission)>`_ +call, you will also receive the ``GeckoSession`` the request was sent +from, and all relevant information about the permission being requested +stored in a `ContentPermission <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.ContentPermission.html>`_. +It is then up to the app to present UI to the user asking for the +permissions, and to notify GeckoView of the response via the returned +``GeckoResult``. + +Once a permission has been set in this fashion, GeckoView will persist it +across sessions until it is cleared or modified. When a page is loaded, +the active permissions associated with it (both allowed and denied) will +be reported in `onLocationChange <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.NavigationDelegate.html#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List)>`_ +as a list of ``ContentPermission`` objects; additionally, one may check all stored +content permissions by calling `getAllPermissions <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/StorageController.html#getAllPermissions()>`_ +and the content permissions associated with a given URI by calling +`getPermissions <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/StorageController.html#getPermissions(java.lang.String,java.lang.String)>`_. +In order to modify an existing permission, you will need the associated +``ContentPermission`` (which can be retrieved from any of the above methods); +then, call `setPermission <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/StorageController.html#setPermission(org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission,int)>`_ +with the desired new value, or `VALUE_PROMPT <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.ContentPermission.html#VALUE_PROMPT>`_ +if you wish to unset the permission and let the site request it again in the future. + +Media Permissions +~~~~~~~~~~~~~~~~~ + +Media permissions are requested whenever a site wants access to play or +record media from the device’s camera and microphone. + +When you receive an +`onMediaPermissionRequest <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.html#onMediaPermissionRequest(org.mozilla.geckoview.GeckoSession,java.lang.String,org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource[],org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource[],org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaCallback)>`_ +call, you will also receive the ``GeckoSession`` the request was sent +from, the URI of the site that requested the permission, as a String, +the list of video devices available, if requesting video, the list of +audio devices available, if requesting audio, and a +`MediaCallback <https://searchfox.org/mozilla-central/source/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java#686>`_ +to respond to the request. + +It is up to the app to present UI to the user asking for the +permissions, and to notify GeckoView of the response via the +``MediaCallback``. + +*Please note, media permissions will still be requested if the +associated device permissions have been denied if there are video or +audio sources in that category that can still be accessed when listed. +It is the responsibility of consumers to ensure that media permission +requests are not displayed in this case.* + +.. code:: java + + private class ExamplePermissionDelegate implements GeckoSession.PermissionDelegate { + @Override + public void onMediaPermissionRequest(final GeckoSession session, + final String uri, + final MediaSource[] video, + final MediaSource[] audio, + final MediaCallback callback) { + // Reject permission if Android permission has been previously denied. + if ((audio != null + && ContextCompat.checkSelfPermission(GeckoViewActivity.this, + Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) + || (video != null + && ContextCompat.checkSelfPermission(GeckoViewActivity.this, + Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) { + callback.reject(); + return; + } + + final String host = Uri.parse(uri).getAuthority(); + final String title; + if (audio == null) { + title = getString(R.string.request_video, host); + } else if (video == null) { + title = getString(R.string.request_audio, host); + } else { + title = getString(R.string.request_media, host); + } + + // Get the media device name from the `MediaDevice` + String[] videoNames = normalizeMediaName(video); + String[] audioNames = normalizeMediaName(audio); + + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + + // Create drop down boxes to allow users to select which device to grant permission to + final LinearLayout container = addStandardLayout(builder, title, null); + final Spinner videoSpinner; + if (video != null) { + videoSpinner = addMediaSpinner(builder.getContext(), container, video, videoNames); // create spinner and add to alert UI + } else { + videoSpinner = null; + } + + final Spinner audioSpinner; + if (audio != null) { + audioSpinner = addMediaSpinner(builder.getContext(), container, audio, audioNames); // create spinner and add to alert UI + } else { + audioSpinner = null; + } + + builder.setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + // gather selected media devices and grant access + final MediaSource video = (videoSpinner != null) + ? (MediaSource) videoSpinner.getSelectedItem() : null; + final MediaSource audio = (audioSpinner != null) + ? (MediaSource) audioSpinner.getSelectedItem() : null; + callback.grant(video, audio); + } + }); + + final AlertDialog dialog = builder.create(); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(final DialogInterface dialog) { + callback.reject(); + } + }); + dialog.show(); + } + } + +To see the ``PermissionsDelegate`` in action, you can find the full +example implementation in the `GeckoView example +app <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.MediaCallback.html>`_. + +.. _Callback: https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.Callback.html diff --git a/mobile/android/docs/geckoview/consumer/web-extensions.rst b/mobile/android/docs/geckoview/consumer/web-extensions.rst new file mode 100644 index 0000000000..f4b7b2a179 --- /dev/null +++ b/mobile/android/docs/geckoview/consumer/web-extensions.rst @@ -0,0 +1,403 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +============================ +Interacting with Web content +============================ + +Interacting with Web content and WebExtensions +============================================== + +GeckoView allows embedder applications to register and run +`WebExtensions <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions>`_ +in a GeckoView instance. Extensions are the preferred way to interact +with Web content. + +.. contents:: :local: + +Running extensions in GeckoView +------------------------------- + +Extensions bundled with applications can be provided in a folder in the +``/assets`` section of the APK. Like ordinary extensions, every +extension bundled with GeckoView requires a +`manifest.json <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json>`_ +file. + +To locate files bundled with the APK, GeckoView provides a shorthand +``resource://android/`` that points to the root of the APK. + +E.g. ``resource://android/assets/messaging/`` will point to the +``/assets/messaging/`` folder present in the APK. + +Note: Every installed extension will need an +`id <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings>`_ +and +`version <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version>`_ +specified in the ``manifest.json`` file. + +To install a bundled extension in GeckoView, simply call +`WebExtensionController.installBuiltIn <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtensionController.html#installBuiltIn(java.lang.String)>`_. + +.. code:: java + + runtime.getWebExtensionController() + .installBuiltIn("resource://android/assets/messaging/") + +Note that the lifetime of the extension is not tied with the lifetime of +the +`GeckoRuntime <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoRuntime.html>`_ +instance. The extension persists even when your app is restarted. +Installing at every start up is fine, but it could be slow. To avoid +installing multiple times you can use ``WebExtensionRuntime.ensureBuiltIn``, +which will only install if the extension is not installed yet. + +.. code:: java + + runtime.getWebExtensionController() + .ensureBuiltIn("resource://android/assets/messaging/", "messaging@example.com") + .accept( + extension -> Log.i("MessageDelegate", "Extension installed: " + extension), + e -> Log.e("MessageDelegate", "Error registering WebExtension", e) + ); + +Communicating with Web Content +------------------------------ + +GeckoView allows bidirectional communication with Web pages through +extensions. + +When using GeckoView, `native +messaging <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#Exchanging_messages>`_ +can be used for communicating to and from the browser. + +- `runtime.sendNativeMessage <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendNativeMessage>`_ + for one-off messages. +- `runtime.connectNative <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connectNative>`_ + for connection-based messaging. + +Note: these APIs are only available when the ``geckoViewAddons`` +`permission <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions>`_ +is present in the ``manifest.json`` file of the extension. + +One-off messages +~~~~~~~~~~~~~~~~ + +The easiest way to send messages from a `content +script <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts>`_ +or a `background +script <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#Background_scripts>`_ +is using +`runtime.sendNativeMessage <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendNativeMessage>`_. + +Note: Ordinarily, native extensions would use a `native +manifest <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#App_manifest>`_ +to define what native app identifier to use. For GeckoView this is *not* +needed, the ``nativeApp`` parameter in ``setMessageDelegate`` will be +use to determine what native app string is used. + +Messaging Example +~~~~~~~~~~~~~~~~~ + +To receive messages from the background script, call +`setMessageDelegate <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.html#setMessageDelegate(org.mozilla.geckoview.WebExtension.MessageDelegate,java.lang.String)>`_ +on the +`WebExtension <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.html>`_ +object. + +.. code:: java + + extension.setMessageDelegate(messageDelegate, "browser"); + +`SessionController.setMessageDelegate <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.SessionController.html#setMessageDelegate(org.mozilla.geckoview.WebExtension,org.mozilla.geckoview.WebExtension.MessageDelegate,java.lang.String)>`_ +allows the app to receive messages from content scripts. + +.. code:: java + + session.getWebExtensionController() + .setMessageDelegate(extension, messageDelegate, "browser"); + +Note: the ``"browser"`` parameter in the code above determines what +native app id the extension can use to send native messages. + +Note: extension can only send messages from content scripts if +explicitly authorized by the app by adding +``nativeMessagingFromContent`` in the manifest.json file, e.g. + +.. code:: + + "permissions": [ + "nativeMessaging", + "nativeMessagingFromContent", + "geckoViewAddons" + ] + +Example +~~~~~~~ + +Let’s set up an activity that registers an extension located in the +``/assets/messaging/`` folder of the APK. This activity will set up a +`MessageDelegate <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.MessageDelegate.html>`_ +that will be used to communicate with Web Content. + +You can find the full example here: +`MessagingExample <https://searchfox.org/mozilla-central/source/mobile/android/examples/messaging_example>`_. + +Activity.java +^^^^^^^^^^^^^ + +.. code:: java + + WebExtension.MessageDelegate messageDelegate = new WebExtension.MessageDelegate() { + @Nullable + public GeckoResult<Object> onMessage(final @NonNull String nativeApp, + final @NonNull Object message, + final @NonNull WebExtension.MessageSender sender) { + // The sender object contains information about the session that + // originated this message and can be used to validate that the message + // has been sent from the expected location. + + // Be careful when handling the type of message as it depends on what + // type of object was sent from the WebExtension script. + if (message instanceof JSONObject) { + // Do something with message + } + return null; + } + }; + + // Let's make sure the extension is installed + runtime.getWebExtensionController() + .ensureBuiltIn(EXTENSION_LOCATION, "messaging@example.com").accept( + // Set delegate that will receive messages coming from this extension. + extension -> session.getWebExtensionController() + .setMessageDelegate(extension, messageDelegate, "browser"), + // Something bad happened, let's log an error + e -> Log.e("MessageDelegate", "Error registering extension", e) + ); + + +Now add the ``geckoViewAddons``, ``nativeMessaging`` and +``nativeMessagingFromContent`` permissions to your ``manifest.json`` +file. + +/assets/messaging/manifest.json +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: json + + { + "manifest_version": 2, + "name": "messaging", + "version": "1.0", + "description": "Example messaging web extension.", + "browser_specific_settings": { + "gecko": { + "id": "messaging@example.com" + } + }, + "content_scripts": [ + { + "matches": ["*://*.twitter.com/*"], + "js": ["messaging.js"] + } + ], + "permissions": [ + "nativeMessaging", + "nativeMessagingFromContent", + "geckoViewAddons" + ] + } + +And finally, write a content script that will send a message to the app +when a certain event occurs. For example, you could send a message +whenever a `WPA +manifest <https://developer.mozilla.org/en-US/docs/Web/Manifest>`_ is +found on the page. Note that our ``nativeApp`` identifier used for +``sendNativeMessage`` is the same as the one used in the +``setMessageDelegate`` call in `Activity.java <#activityjava>`_. + +/assets/messaging/messaging.js +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: javascript + + let manifest = document.querySelector("head > link[rel=manifest]"); + if (manifest) { + fetch(manifest.href) + .then(response => response.json()) + .then(json => { + let message = {type: "WPAManifest", manifest: json}; + browser.runtime.sendNativeMessage("browser", message); + }); + } + +You can handle this message in the ``onMessage`` method in the +``messageDelegate`` `above <#activityjava>`_. + +.. code:: java + + @Nullable + public GeckoResult<Object> onMessage(final @NonNull String nativeApp, + final @NonNull Object message, + final @NonNull WebExtension.MessageSender sender) { + if (message instanceof JSONObject) { + JSONObject json = (JSONObject) message; + try { + if (json.has("type") && "WPAManifest".equals(json.getString("type"))) { + JSONObject manifest = json.getJSONObject("manifest"); + Log.d("MessageDelegate", "Found WPA manifest: " + manifest); + } + } catch (JSONException ex) { + Log.e("MessageDelegate", "Invalid manifest", ex); + } + } + return null; + } + +Note that, in the case of content scripts, ``sender.session`` will be a +reference to the ``GeckoSession`` instance from which the message +originated. For background scripts, ``sender.session`` will always be +``null``. + +Also note that the type of ``message`` will depend on what was sent from +the extension. + +The type of ``message`` will be ``JSONObject`` when the extension sends +a javascript object, but could also be a primitive type if the extension +sends one, e.g. for + +.. code:: javascript + + runtime.browser.sendNativeMessage("browser", "Hello World!"); + +the type of ``message`` will be ``java.util.String``. + +Connection-based messaging +-------------------------- + +For more complex scenarios or for when you want to send messages *from* +the app to the extension, +`runtime.connectNative <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connectNative>`_ +is the appropriate API to use. + +``connectNative`` returns a +`runtime.Port <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/Port>`_ +that can be used to send messages to the app. On the app side, +implementing +`MessageDelegate#onConnect <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.MessageDelegate.html#onConnect(org.mozilla.geckoview.WebExtension.Port)>`_ +will allow you to receive a +`Port <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.Port.html>`_ +object that can be used to receive and send messages to the extension. + +The following example can be found +`here <https://searchfox.org/mozilla-central/source/mobile/android/examples/port_messaging_example>`_. + +For this example, the extension side will do the following: + +- open a port on the background script using ``connectNative`` +- listen on the port and log to console every message received +- send a message immediately after opening the port. + +/assets/messaging/background.js +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: javascript + + // Establish connection with app + let port = browser.runtime.connectNative("browser"); + port.onMessage.addListener(response => { + // Let's just echo the message back + port.postMessage(`Received: ${JSON.stringify(response)}`); + }); + port.postMessage("Hello from WebExtension!"); + +On the app side, following the `above <#activityjava>`_ example, +``onConnect`` will be storing the ``Port`` object in a member variable +and then using it when needed. + +.. code:: java + + private WebExtension.Port mPort; + + @Override + protected void onCreate(Bundle savedInstanceState) { + // ... initialize GeckoView + + // This delegate will handle all communications from and to a specific Port + // object + WebExtension.PortDelegate portDelegate = new WebExtension.PortDelegate() { + public WebExtension.Port port = null; + + public void onPortMessage(final @NonNull Object message, + final @NonNull WebExtension.Port port) { + // This method will be called every time a message is sent from the + // extension through this port. For now, let's just log a + // message. + Log.d("PortDelegate", "Received message from WebExtension: " + + message); + } + + public void onDisconnect(final @NonNull WebExtension.Port port) { + // After this method is called, this port is not usable anymore. + if (port == mPort) { + mPort = null; + } + } + }; + + // This delegate will handle requests to open a port coming from the + // extension + WebExtension.MessageDelegate messageDelegate = new WebExtension.MessageDelegate() { + @Nullable + public void onConnect(final @NonNull WebExtension.Port port) { + // Let's store the Port object in a member variable so it can be + // used later to exchange messages with the WebExtension. + mPort = port; + + // Registering the delegate will allow us to receive messages sent + // through this port. + mPort.setDelegate(portDelegate); + } + }; + + runtime.getWebExtensionController() + .ensureBuiltIn("resource://android/assets/messaging/", "messaging@example.com") + .accept( + // Register message delegate for background script + extension -> extension.setMessageDelegate(messageDelegate, "browser"), + e -> Log.e("MessageDelegate", "Error registering WebExtension", e) + ); + + // ... other + } + +For example, let’s send a message to the extension every time the user +long presses on a key on the virtual keyboard, e.g. on the back button. + +.. code:: java + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (mPort == null) { + // No extension registered yet, let's ignore this message + return false; + } + + JSONObject message = new JSONObject(); + try { + message.put("keyCode", keyCode); + message.put("event", KeyEvent.keyCodeToString(event.getKeyCode())); + } catch (JSONException ex) { + throw new RuntimeException(ex); + } + + mPort.postMessage(message); + return true; + } + +This allows bidirectional communication between the app and the +extension. + +.. _GeckoRuntime: https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoRuntime.html +.. _runtime.sendNativeMessage: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendNativeMessage +.. _WebExtension: https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.html diff --git a/mobile/android/docs/geckoview/contributor/apilint.rst b/mobile/android/docs/geckoview/contributor/apilint.rst new file mode 100644 index 0000000000..9d9e315896 --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/apilint.rst @@ -0,0 +1,85 @@ +apilint release process +~~~~~~~~~~~~~~~~~~~~~~~ + +To release a new version of `apilint <https://github.com/mozilla-mobile/gradle-apilint>`_, do the following: + +- Create a commit titled "Branch X.Y" and modify the files ``apilint/build.gradle`` and ``apilint/Config.java`` accordingly. See for example `Branch 0.5 <https://github.com/mozilla-mobile/gradle-apilint/commit/93a79ffddb8587ad018be67a361eb2a6ae777c63>`_. Note that it's not necessary to modify ``apilint/Config.java`` if there aren't any ``apidoc`` changes. + +- Create a git tag with the branch version + +.. code:: bash + + $ git tag X.Y + +- Run tests locally by running + +.. code:: bash + + $ ./gradlew build + + +- Publish new version to local repository + +.. code:: bash + + $ ./gradlew publishToMavenLocal + +- Modify ``mozilla-central`` locally to test ``apilint`` with the new version, add ``mavenLocal()`` to every ``repositories {}`` block inside the root ``build.gradle``, e.g. + + +.. code:: diff + + diff --git a/build.gradle b/build.gradle + index 813ba09aa3d4b..753fdb8d958a6 100644 + --- a/build.gradle + +++ b/build.gradle + @@ -60,6 +60,7 @@ allprojects { + } + + repositories { + + mavenLocal() + gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> + maven { + url repository + @@ -100,6 +101,7 @@ buildDir "${topobjdir}/gradle/build" + + buildscript { + repositories { + + mavenLocal() + gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> + maven { + url repository + @@ -113,7 +115,7 @@ buildscript { + ext.kotlin_version = '1.5.31' + + dependencies { + - classpath 'org.mozilla.apilint:apilint:0.5.2' + + classpath 'org.mozilla.apilint:apilint:0.X.Y' + classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2' + classpath 'org.apache.commons:commons-exec:1.3' + +* Test integration running ``api-lint``, this should always pass with no ``api.txt`` modifications needed (there could be exceptions, but should be intentional). + +.. code:: bash + + $ ./mach lint -l android-api-lint + +- Push the tag to the remote repository (note, the branch commit is `not` pushed to the main branch). + +.. code:: bash + + $ git push -u origin X.Y + +- Wait until github automation finishes successfully. +- (optional, if there are any ``apidoc`` changes) ask the Releng team to publish a new `apidoc` version, the bundle will be present under the github artifacts, e.g. see ``maven.zip`` in `releases/tag/0.5 <https://github.com/mozilla-mobile/gradle-apilint/releases/tag/0.5>`_. See also `Bug 1727585 <https://bugzilla.mozilla.org/show_bug.cgi?id=1727585>`_. + +- Add the ``plugins.gradle.org`` keys to your ``.gradle`` folder, see `publishing_gradle_plugins.html <https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html>`_. + +- Publish plugin by running + +.. code:: bash + + $ ./gradlew apilint:publishPlugins + +- Finally, update ``mozilla-central`` to use the new version, e.g. see `this patch <https://hg.mozilla.org/mozilla-central/rev/0f746422db0e9fc6b70488bdb7114f08973191a0>`_. diff --git a/mobile/android/docs/geckoview/contributor/contributing-to-mc.rst b/mobile/android/docs/geckoview/contributor/contributing-to-mc.rst new file mode 100644 index 0000000000..ee4f5be877 --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/contributing-to-mc.rst @@ -0,0 +1,188 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +================================= +Mozilla Central Contributor Guide +================================= + +Table of contents +================= + +.. contents:: :local: + +Submitting a patch to Firefox using Git. +======================================== + +This guide will take you through submitting and updating a patch to +``mozilla-central`` as a git user. You need to already be `set up to use +git to contribute to mozilla-central <mc-quick-start.html>`_. + +Performing a bug fix +-------------------- + +All of the open bugs for issues in Firefox can be found in +`Bugzilla <https://bugzilla.mozilla.org>`_. If you know the component +that you wish to contribute to you can use Bugzilla to search for issues +in that project. If you are unsure which component you are interested +in, you can search the `Good First +Bugs <https://bugzilla.mozilla.org/buglist.cgi?quicksearch=good-first-bug>`_ +list to find something you want to work on. + +- Once you have your bug, assign it to yourself in Bugzilla. +- Update your local copy of the firefox codebase to match the current + version on the servers to ensure you are working with the most up to + date code. + +.. code:: bash + + git remote update + +- Create a new feature branch tracking either Central or Inbound. + +.. code:: bash + + git checkout -b bugxxxxxxx [inbound|central]/default + +- Work on your bug, checking into git according to your preferred + workflow. *Try to ensure that each individual commit compiles and + passes all of the tests for your component. This will make it easier + to land if you use ``moz-phab`` to submit (details later in this + post).* + +It may be helpful to have Mozilla commit access, at least level 1. There +are three levels of commit access that give increasing levels of access +to the repositories. + +Level 1: Try/User access. You will need this level of access commit to +the try server. + +Level 2: General access. This will give you full commit +access to any mercurial or SVN repository not requiring level 3 access. + +Level 3: Core access. You will need this level to commit directly to any +of the core repositories (Firefox/Thunderbird/Fennec). + +If you wish to apply for commit access, please follow the guide found in +the `Mozilla Commit Access +Policy <https://www.mozilla.org/en-US/about/governance/policies/commit/access-policy/>`_. + +Submitting a patch that touches C/C++ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your patch makes changes to any C or C++ code and your editor does +not have ``clang-format`` support, you should run the clang-format +linter before submitting your patch to ensure that your code is properly +formatted. + +.. code:: bash + + mach clang-format -p path/to/file.cpp + +Note that ``./mach bootstrap`` will offer to set up a commit hook that +will automatically do this for you. + +Submitting to ``try`` with Level 1 commit access. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you only have Level 1 access, you will still need to submit your +patch through phabricator, but you can test it on the try server first. + +- Use ``./mach try fuzzy`` to select jobs to run and push to try. + +Submitting a patch via Phabricator. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To commit anything to the repository, you will need to set up moz-phab +and Phabricator. If you are using ``git-cinnabar`` then you will need to +use git enabled versions of these tools. + +Set up Phabricator +^^^^^^^^^^^^^^^^^^ + +- In a browser, visit Mozilla’s Phabricator instance at + https://phabricator.services.mozilla.com/. + +- Click “Log In” at the top of the page + + .. figure:: ../assets/LogInPhab.png + :alt: Log in to Phabricator + + alt text + +- Click the “Log In or Register” button on the next page. This will + take you to Bugzilla to log in or register a new account. + + .. figure:: ../assets/LogInOrRegister.png + :alt: Log in or register a Phabiricator account + + alt text + +- Sign in with your Bugzilla credentials, or create a new account. + + .. figure:: ../assets/LogInBugzilla.png + :alt: Log in with Bugzilla + + alt text + +- You will be redirected back to Phabricator, where you will have to + create a new Phabricator account. + + .. raw:: html + + <Screenshot Needed> + +- Fill in/amend any fields on the form and click “Register Account”. + + .. raw:: html + + <Screenshot Needed> + +- You now have a Phabricator account and can submit and review patches. + +Installing ``moz-phab`` +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + pip install MozPhab [--user] + +Submitting a patch using ``moz-phab``. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Ensure you are on the branch where you have commits that you want to + submit. + +.. code:: bash + + git checkout your-branch + +- Check the revision numbers for the commits you want to submit + +.. code:: bash + + git log + +- Run ``moz-phab``. Specifying a start commit will submit all commits + from that commit. Specifying an end commit will submit all commits up + to that commit. If no positional arguments are provided, the range is + determined to be starting with the first non-public, non-obsolete + changeset (for Mercurial) and ending with the currently checked-out + changeset. + +.. code:: bash + + moz-phab submit [start_rev] [end_rev] + +- You will receive a Phabricator link for each commit in the set. + +Updating a patch +~~~~~~~~~~~~~~~~ + +- Often you will need to make amendments to a patch after it has been + submitted to address review comments. To do this, add your commits to + the base branch of your fix as normal. + +For ``moz-phab`` run in the same way as the initial submission with the +same arguments, that is, specifying the full original range of commits. +Note that, while inserting and amending commits should work fine, +reordering commits is not yet supported, and deleting commits will leave +the associated revisions open, which should be abandoned manually diff --git a/mobile/android/docs/geckoview/contributor/for-gecko-engineers.rst b/mobile/android/docs/geckoview/contributor/for-gecko-engineers.rst new file mode 100644 index 0000000000..4517d950b4 --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/for-gecko-engineers.rst @@ -0,0 +1,176 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +============================= +GeckoView For Gecko Engineers +============================= + +Table of contents +================= + +.. contents:: :local: + +Introduction +------------ + +Who this guide is for: As the title suggests, the target audience of +this guide is existing Gecko engineers who need to be able to build and +(locally) test GeckoView. If you aren’t already familiar with building +Firefox on a desktop platform, you’ll likely be better served by reading +`our general introduction <geckoview-quick-start.html>`_. This guide may +also be helpful if you find you’ve written a patch that requires +changing GeckoView’s public API, see `Landing a Patch <#landing-a-patch>`_. + +Who this guide is not for: As mentioned above, if you are not already +familiar with building Firefox for desktop, you’d likely be better +served by our general bootstrapping guide. If you are looking to +contribute to front-end development of one of Mozilla’s Android +browsers, you’re likely better off starting with their codebase and +returning here only if actual GeckoView changes are needed. See, for +example, `Fenix’s GitHub <https://github.com/mozilla-mobile/firefox-android/tree/main/fenix>`_. + +What to do if this guide contains bugs or leads you astray: The quickest +way to get a response is to ask generally on #gv on Mozilla Slack; +#mobile on Mozilla IRC may also work for the time being, albeit likely +with slower response times. If you believe the guide needs updating, it +would also be good to file a ticket to request that. + +Configuring the build system +---------------------------- + +First, a quick note: This guide was written on MacOS 10.14; it should +translate quite closely to other supported versions of MacOS and to +Linux. Building GeckoView on Windows is not officially supported at the +moment. To begin with, re-run ``./mach bootstrap``; it will present you +with options for the version of Firefox/GV that you want to build. +Currently, option ``3`` is +``GeckoView/Firefox for Android Artifact Mode`` and ``4`` is +``GeckoView/Firefox for Android``; if you’re here, you want one of +these. The brief and approximately correct breakdown of ``Artifact`` vs +regular builds for GeckoView is that ``Artifact`` builds will not allow +you to work on native code, only on JS or Java. Once you’ve selected +your build type, ``bootstrap`` should do its usual thing and grab +whatever dependencies are necessary. You may need to agree to some +licenses along the way. Once ``bootstrap`` has successfully completed, +it will spit out a recommended ``mozconfig``. + +Mozconfig and Building +---------------------- + +If you’ve followed from the previous section, ``./mach bootstrap`` +printed out a recommended ``mozconfig`` that looks something like this: + +:: + + # Build GeckoView/Firefox for Android: + ac_add_options --enable-project=mobile/android + + # Targeting the following architecture. + # For regular phones, no --target is needed. + # For x86 emulators (and x86 devices, which are uncommon): + # ac_add_options --target=i686 + # For newer phones. + # ac_add_options --target=aarch64 + # For x86_64 emulators (and x86_64 devices, which are even less common): + # ac_add_options --target=x86_64 + +As written, this defaults to building for a 32-bit ARM architecture, +which is probably not what you want. If you intend to work on an actual +device, you almost certainly want a 64-bit ARM build, as it is supported +by virtually all modern ARM phones/tablets and is the only ARM build we +ship on the Google Play Store. To go this route, uncomment the +``ac_add_options --target=aarch64`` line in the ``mozconfig``. On the +other hand, x86-64 emulated devices are widely used by the GeckoView +team and are used extensively on ``try``; if you intend to use an +emulator, uncomment the ``ac_add_options --target=x86_64`` line in the +``mozconfig``. Don’t worry about installing an emulator at the moment, +that will be covered shortly. It’s worth noting here that other +``mozconfig`` options will generally work as you’d expect. Additionally, +if you plan on debugging native code on Android, you should include the +``mozconfig`` changes mentioned `in our native debugging guide <native-debugging.html>`_. Now, using +that ``mozconfig`` with any modifications you’ve made, simply +``./mach build``. If all goes well, you will have successfully built +GeckoView. + +Installing, Running, and Using in Fenix/AC +------------------------------------------ + +An (x86-64) emulator is the most common and developer-friendly way of +contributing to GeckoView in most cases. If you’re going to go this +route, simply run ``./mach android-emulator`` — by default, this will +install and launch an x86-64 Android emulator running the same Android +7.0 image that is used on ``try``. If you need a different emulator +image you can run ``./mach android-emulator --help`` for information on +what Android images are available via ``mach``. You can also install an +emulator image via Android Studio. In cases where an emulator may not +suffice (eg graphics or performance testing), or if you’d simply prefer +not to use an emulator, you can opt to use an actual phone instead. To +do so, you’ll need to enable ``USB Debugging`` on your phone if you +haven’t already. On most modern Android devices, you can do this by +opening ``Settings``, going to ``About phone``, and tapping +``Build number`` seven times. You should get a notification informing +you that you’ve unlocked developer options. Now return to ``Settings``, +go to ``Developer options``, and enable USB debugging. + +GeckoView Example App +~~~~~~~~~~~~~~~~~~~~~ + +Now that you’ve connected a phone or setup an emulator, the simplest way +to test GeckoView is to launch the GeckoView Example app by running +``./mach run`` (or install it with ``./mach install`` and run it +yourself). This is a simplistic GV-based browser that lives in the tree; +in many cases, it is sufficient to test and debug Gecko changes, and is +by far the simplest way of doing so. It supports remote debugging by +default — simply open Remote Debugging on your desktop browser and the +connected device/emulator should show up when the example app is open. +You can also use the example app for native debugging, follow the +`native debugging guide <native-debugging.html>`_. + +GeckoView JUnit Tests +~~~~~~~~~~~~~~~~~~~~~ + +Once you’ve successfully built GV, you can run tests from the GeckoView +JUnit test suite with ``./mach geckoview-junit``. For further examples +(eg running individual tests, repeating tests, etc.), consult the `quick +start guide <geckoview-quick-start.html#running-tests-locally>`_. + +Fenix and other GV-based Apps +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are working on something for which the GeckoView Example app is +not sufficient for some reason, you may need to `use your local build of +GeckoView in one of Mozilla’s GV-based apps like Fenix <geckoview-quick-start.html#include-geckoview-as-a-dependency>`_. + +Debugging +--------- + +Remote Debugging +~~~~~~~~~~~~~~~~ + +To recap a bit of the above, in the GeckoView Example app, remote +debugging is enabled by default, and your device should show up in your +desktop browser’s Remote Debugging window with no special effort. For +Fenix, you can enable remote debugging by opening the three-dot menu and +toggling ``Remote debugging via USB`` under ``Developer tools``; other +Mozilla GV-based browsers have similar options. + +Native Debugging +~~~~~~~~~~~~~~~~ + +To perform native debugging on any GV app will require you to install +Android Studio and follow instructions `here <native-debugging.html>`_. + +Landing a Patch +--------------- + +In most cases, there shouldn’t be anything out of the ordinary to deal +with when landing a patch that affects GeckoView; make sure you include +Android in your ``try`` runs and you should be good. However, if you +need to alter the GeckoView public API in any way — essentially anything +that’s exposed as ``public`` in GeckoView Java files — then you’ll find +that you need to run the API linter and update the change log. To do +this, first run ``./mach lint --linter android-api-lint`` — if you have +indeed changed the public API, this will give you a ``gradle`` command +to run that will give further instructions. GeckoView API changes +require two reviews from GeckoView team members; you can open it up to +the team in general by adding ``#geckoview-reviewers`` as a reviewer on +Phabricator. diff --git a/mobile/android/docs/geckoview/contributor/geckoview-architecture.rst b/mobile/android/docs/geckoview/contributor/geckoview-architecture.rst new file mode 100644 index 0000000000..e4235dd4dd --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/geckoview-architecture.rst @@ -0,0 +1,826 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +===================== +Architecture overview +===================== + +.. contents:: Table of Contents + :depth: 2 + :local: + +Introduction +============ + +*Gecko* is a Web engine developed by Mozilla and used to power Firefox on +various platforms. A Web engine is roughly comprised of a JavaScript engine, a +Rendering engine, HTML parser, a Network stack, various media encoders, a +Graphics engine, a Layout engine and more. + +Code that is part of a browser itself is usually referred to as "chrome" code +(from which the popular Chrome browser takes its name) as opposed to code part +of a Web site, which is usually referred to "content" code or content Web page. + +*GeckoView* is an Android library that can be used to embed Gecko into Android +apps. Android apps that embed Gecko this way are usually referred to by +"embedders" or simply "apps". + +GeckoView powers all currently active Mozilla browsers on Android, like Firefox +for Android and Firefox Focus. + +API +=== + +The following sections describe parts of the GeckoView API that are public and +exposed to embedders. + + |api-diagram| + +Overall tenets +-------------- + +GeckoView is an opinionated library that contains a minimal UI and makes no +assumption about the type of app that is being used by. Its main consumers +inside Mozilla are browsers, so a lot of features of GeckoView are geared +towards browsers, but there is no assumption that the embedder is actually a +browser (e.g. there is no concept of "tab" in GeckoView). + +The GeckoView API tries to retain as little data as possible, delegating most +data storage to apps. Notable exceptions to this rule are: permissions, +extensions and cookies. + +View, Runtime and Session +------------------------- + + |view-runtime-session| + +There are three main classes in the GeckoView API: + +- ``GeckoRuntime`` represents an instance of Gecko running in an app. Normally, + apps have only one instance of the runtime which lives for as long as the app + is alive. Any object in the API that is not specific to a *session* + (more to this later) is usually reachable from the runtime. +- ``GeckoSession`` represents a web site *instance*. You can think of it as a + *tab* in a browser or a Web view in an app. Any object related to the + specific session will be reachable from this object. Normally, embedders + would have many instances of ``GeckoSession`` representing each tab that is + currently open. Internally, a session is represented as a "window" with one + single tab in it. +- ``GeckoView`` is an Android ``View`` that embedders can use to paint a + ``GeckoSession`` in the app. Normally, only ``GeckoSession`` s associated to + a ``GeckoView`` are actually *alive*, i.e. can receive events, fire timers, + etc. + +Delegates +--------- + +Because GeckoView has no UI elements and doesn't store a lot of data, it needs +a way to *delegate* behavior when Web sites need functionality that requires +these features. + +To do that, GeckoView exposes Java interfaces to the embedders, called +Delegates. Delegates are normally associated to either the runtime, when they +don't refer to a specific session, or a session, when they are +session-specific. + +The most important delegates are: + +- ``Autocomplete.StorageDelegate`` Which is used by embedders to implement + autocomplete functionality for logins, addresses and credit cards. +- ``ContentDelegate`` Which receives events from the content Web page like + "open a new window", "on fullscreen request", "this tab crashed" etc. +- ``HistoryDelegate`` Which receives events about new or modified history + entries. GeckoView itself does not store history so the app is required to + listen to history events and store them permanently. +- ``NavigationDelegate`` Informs the embedder about navigation events and + requests. +- ``PermissionDelegate`` Used to prompt the user for permissions like + geolocation, notifications, etc. +- ``PromptDelegate`` Implements content-side prompts like alert(), confirm(), + basic HTTP auth, etc. +- ``MediaSession.Delegate`` Informs the embedder about media elements currently + active on the page and allows the embedder to pause, resume, receive playback + state etc. +- ``WebExtension.MessageDelegate`` Used by the embedder to exchange messages + with built-in extensions. See also `Interacting with Web Content <../consumer/web-extensions.html>`_. + + +.. _GeckoDisplay: + +GeckoDisplay +------------ + +GeckoView can paint to either a ``SurfaceView`` or a ``TextureView``. + +- ``SufaceView`` is what most apps will use and it's the default, it provides a + barebone wrapper around a GL surface where GeckoView can paint on. + SurfaceView is not part of normal Android compositing, which means that + Android is not able to paint (partially) on top of a SurfaceView or apply + transformations and animations to it. +- ``TextureView`` offers a surface which can be transformed and animated but + it's slower and requires more memory because it's `triple-buffered + <https://en.wikipedia.org/wiki/Multiple_buffering#Triple_buffering>`_ + (which is necessary to offer animations). + +Most apps will use the ``GeckoView`` class to paint the web page. The +``GeckoView`` class is an Android ``View`` which takes part in the Android view +hierarchy. + +Android recycles the ``GeckoView`` whenever the app is not visible, releasing +the associated ``SurfaceView`` or ``TextureView``. This triggers a few actions +on the Gecko side: + +- The GL Surface is released, and Gecko is notified in + `SyncPauseCompositor <https://searchfox.org/mozilla-central/rev/ead7da2d9c5400bc7034ff3f06a030531bd7e5b9/widget/android/nsWindow.cpp#1114>`_. +- The ``<browser>`` associated to the ``GeckoSession`` is `set to inactive <https://searchfox.org/mozilla-central/rev/ead7da2d9c5400bc7034ff3f06a030531bd7e5b9/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java#553>`_, + which essentially freezes the JavaScript engine. + +Apps that do not use ``GeckoView``, because e.g. they cannot use +``SurfaceView``, need to manage the active state manually and call +``GeckoSession.setActive`` whenever the session is not being painted on the +screen. + +Thread safety +------------- + +Apps will inevitably have to deal with the Android UI in a significant way. +Most of the Android UI toolkit operates on the UI thread, and requires +consumers to execute method calls on it. The Android UI thread runs an event +loop that can be used to schedule tasks on it from other threads. + +Gecko, on the other hand, has its own main thread where a lot of the front-end +interactions happen, and many methods inside Gecko expect to be called on the +main thread. + +To not overburden the App with unnecessary multi-threaded code, GeckoView will +always bridge the two "main threads" and redirect method calls as appropriate. +Most GeckoView delegate calls will thus happen on the Android UI thread and +most APIs are expected to be called on the UI thread as well. + +This can sometimes create unexpected performance considerations, as illustrated +in later sections. + +GeckoResult +----------- + +An ubiquitous tool in the GeckoView API is ``GeckoResult``. GeckoResult is a +promise-like class that can be used by apps and by Gecko to return values +asynchronously in a thread-safe way. Internally, ``GeckoResult`` will keep +track of what thread it was created on, and will execute callbacks on the same +thread using the thread's ``Handler``. + +When used in Gecko, ``GeckoResult`` can be converted to ``MozPromise`` using +``MozPromise::FromGeckoResult``. + +Page load +--------- + + |pageload-diagram| + +GeckoView offers several entry points that can be used to react to the various +stages of a page load. The interactions can be tricky and surprising so we will +go over them in details in this section. + +For each page load, the following delegate calls will be issued: +``onLoadRequest``, ``onPageStart``, ``onLocationChange``, +``onProgressChange``, ``onSecurityChange``, ``onSessionStateChange``, +``onCanGoBack``, ``onCanGoForward``, ``onLoadError``, ``onPageStop``. + +Most of the method calls are self-explanatory and offer the App a chance to +update the UI in response to a change in the page load state. The more +interesting delegate calls will be described below. + +onPageStart and onPageStop +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``onPageStart`` and ``onPageStop`` are guaranteed to appear in pairs and in +order, and denote the beginning and the end of a page load. In between a start +and stop event, multiple ``onLoadRequest`` and ``onLocationChange`` call can be +executed, denoting redirects. + +onLoadRequest +~~~~~~~~~~~~~ + +``onLoadRequest``, which is perhaps the most important, can be used by the App +to intercept page loads. The App can either *deny* the load, which will stop +the page from loading, and handle it internally, or *allow* the +load, which will load the page in Gecko. ``onLoadRequest`` is called for all +page loads, regardless of whether they were initiated by the app itself, by Web +content, or as a result of a redirect. + +When the page load originates in Web content, Gecko has to synchronously +wait for the Android UI thread to schedule the call to ``onLoadRequest`` and +for the App to respond. This normally takes a negligible amount of time, but +when the Android UI thread is busy, e.g. because the App is being painted for +the first time, the delay can be substantial. This is an area of GeckoView that +we are actively trying to improve. + +onLoadError +~~~~~~~~~~~ + +``onLoadError`` is called whenever the page does not load correctly, e.g. +because of a network error or a misconfigured HTTPS server. The App can return +a URL to a local HTML file that will be used as error page internally by Gecko. + +onLocationChange +~~~~~~~~~~~~~~~~ + +``onLocationChange`` is called whenever Gecko commits to a navigation and the +URL can safely displayed in the URL bar. + +onSessionStateChange +~~~~~~~~~~~~~~~~~~~~ + +``onSessionStateChange`` is called whenever any piece of the session state +changes, e.g. form content, scrolling position, zoom value, etc. Changes are +batched to avoid calling this API too frequently. + +Apps can use ``onSessionStateChange`` to store the serialized state to +disk to support restoring the session at a later time. + +Third-party root certificates +----------------------------- + +Gecko maintains its own Certificate Authority store and does not use the +platform's CA store. GeckoView follows the same policy and will not, by +default, read Android's CA store to determine root certificates. + +However, GeckoView provides a way to import all third-party CA roots added to +the Android CA store by setting the `enterpriseRootsEnabled +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoRuntimeSettings.Builder.html#enterpriseRootsEnabled(boolean)>`_ +runtime setting to ``true``, this feature is implemented in `EnterpriseRoots +<https://searchfox.org/mozilla-central/rev/26a6a38fb515dbab0bb459c40ec4b877477eefef/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EnterpriseRoots.java>`_ + +There is not currently any API for an app to manually specify additional CA +roots, although this might change with `Bug 1522162 +<https://bugzilla.mozilla.org/show_bug.cgi?id=1522162>`_. + +Lite and Omni builds +--------------------- + +A variation of the default GeckoView build, dubbed `Omni` in the codebase, +provides additional libraries that can be helpful when building a browser app. +Currently, the `Glean +<https://docs.telemetry.mozilla.org/concepts/glean/glean.html>`_ library is +included in the ``geckoview-omni`` package. The default build ``geckoview``, +which does not contain such libraries, is similarly dubbed `Lite` in the +codebase. + +The additional libraries in the Omni package are directly built into Gecko's +main ``.so`` file, ``libxul.so``. These libraries are then declared in the +``.module`` package inside the ``maven`` repository, e.g. see the ``.module`` +file for `geckoview-omni +<https://maven.mozilla.org/maven2/org/mozilla/geckoview/geckoview-omni/102.0.20220623063721/geckoview-omni-102.0.20220623063721.module>`_: + +.. code-block:: json + + "capabilities": [ + { + "group": "org.mozilla.geckoview", + "name": "geckoview-omni", + "version": "102.0.20220623063721" + }, + { + "group": "org.mozilla.telemetry", + "name": "glean-native", + "version": "44.1.1" + } + ] + +Notice the ``org.mozilla.telemetry:glean-native`` capability is declared +alongside ``org.mozilla.geckoview``. + +The main Glean library then depends on ``glean-native`` which is either +provided in a standalone package (for apps that do not include GeckoView) or by +the GeckoView capability above. + +In Treeherder, the Lite build is denoted with ``Lite``, while the Omni builds +don't have extra denominations as they are the default build, so e.g. for +``x86_64`` the platorm names would be: + +- ``Android 7.0 x86-64`` for the Omni build +- ``Android 7.0 x86-64 Lite`` for the Lite build + +Extensions +---------- + +Extensions can be installed using ``WebExtensionController::install`` and +``WebExtensionController::installBuiltIn``, which asynchronously returns a +``WebExtension`` object that can be used to set delegates for +extension-specific behavior. + +The ``WebExtension`` object is immutable, and will be replaced every time a +property changes. For instance, to disable an extension, apps can use the +``disable`` method, which will return an updated version of the +``WebExtension`` object. + +Internally, all ``WebExtension`` objects representing one extension share the +same delegates, which are stored in ``WebExtensionController``. + +Given the extensive sprawling amount of data associated to extensions, +extension installation persists across restarts. Existing extensions can be +listed using ``WebExtensionController::list``. + +In addition to ordinary WebExtension APIs, GeckoView allows ``builtIn`` +extensions to communicate to the app via native messaging. Apps can register +themselves as native apps and extensions will be able to communicate to the app +using ``connectNative`` and ``sendNativeMessage``. Further information can be +found `here <../consumer/web-extensions.html>`__. + +Internals +========= + +The following sections describe how Gecko and GeckoView are implemented. These +parts of GeckoView are not normally exposed to embedders. + +Process Model +------------- + +Internally, Gecko uses a multi-process architecture, most of the chrome code +runs in the *main* process, while content code runs in *child* processes also +called *content* processes. There are additional types of specialized processes +like the *socket* process, which runs parts of the networking code, the *gpu* +process which executes GPU commands, the *extension* process which runs most +extension content code, etc. + +We intentionally do not expose our process model to embedders. + +To learn more about the multi-process architecture see `Fission for GeckoView +engineers <https://gist.github.com/agi/c900f3e473ff681158c0c907e34780e4>`_. + +The majority of the GeckoView Java code runs on the main process, with a thin +glue layer on the child processes, mostly contained in ``GeckoThread``. + +Process priority on Android +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On Android, each process is assigned a given priority. When the device is +running low on memory, or when the system wants to conserve resources, e.g. +when the screen has been off for a long period of time, or the battery is low, +Android will sort all processes in reverse priority order and kill, using a +``SIGKILL`` event, enough processes until the given free memory and resource +threshold is reached. + +Processes that are necessary to the function of the device get the highest +priority, followed by apps that are currently visible and focused on the +screen, then apps that are visible (but not on focus), background processes and +so on. + +Processes that do not have a UI associated to it, e.g. background services, +will normally have the lowest priority, and thus will be killed most +frequently. + +To increase the priority of a service, an app can ``bind`` to it. There are +three possible ``bind`` priority values + +- ``BIND_IMPORTANT``: The process will be *as important* as the process binding + to it +- default priority: The process will have lower priority than the process + binding to it, but still higher priority than a background service +- ``BIND_WAIVE_PRIORITY``: The bind will be ignored for priority + considerations. + +It's important to note that the priority of each service is only relative to +the priority of the app binding to it. If the app is not visible, the app +itself and all services attached to it, regardless of binding, will get +background priority (i.e. the lowest possible priority). + +Process management +~~~~~~~~~~~~~~~~~~ + +Each Gecko process corresponds to an Android ``service`` instance, which has to +be declared in GeckoView's ``AndroidManifest.xml``. + +For example, this is the definition of the ``media`` process: + +.. code-block:: + + <service + android:name="org.mozilla.gecko.media.MediaManager" + android:enabled="true" + android:exported="false" + android:isolatedProcess="false" + android:process=":media"> + +Process creation is controlled by Gecko which interfaces to Android using +``GeckoProcessManager``, which translates Gecko's priority to Android's +``bind`` values. + +Because all priorities are waived when the app is in the background, it's not +infrequent that Android kills some of GeckoView's services, while still leaving +the main process alive. + +It is therefore very important that Gecko is able to recover from process +disappearing at any moment at runtime. + +Priority Hint +~~~~~~~~~~~~~ + +Internally, GeckoView ties the lifetime of the ``Surface`` associated to a +``GeckoSession`` and the process priority of the process where the session +lives. + +The underlying assumption is that a session that is not visible doesn't have a +surface associated to it and it's not being used by the user so it shouldn't +receive high priority status. + +The way this is implemented is `by setting +<https://searchfox.org/mozilla-central/rev/5b2d2863bd315f232a3f769f76e0eb16cdca7cb0/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java#114,123>`_ +the ``active`` property on the ``browser`` object to ``false``, which causes +Gecko to de-prioritize the process, assuming that no other windows in the same +process have ``active=true``. See also `GeckoDisplay`_. + +However, there are use cases where just looking at the surface is not enough. +For instance, when the user opens the settings menu, the currently selected tab +becomes invisible, but the user will still expect the browser to retain that +tab state with a higher priority than all the other tabs. Similarly, when the +browser is put in the background, the surface associated to the current tab +gets destroyed, but the current tab is still more important than the other +tabs, but because it doesn't have a surface associated to it, we have no way to +differentiate it from all the other tabs. + +To solve the above problem, we expose an API for consumers to *boost* a session +priority, `setPriorityHint +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.html#setPriorityHint(int)>`_. +The priority hint is taken into consideration when calculating the +priority of a process. Any process that contains either an active session or a +session with the priority hint `is boosted +<https://searchfox.org/mozilla-central/rev/5b2d2863bd315f232a3f769f76e0eb16cdca7cb0/dom/ipc/BrowserParent.cpp#3593>`_ +to the highest priority. + +Shutdown +-------- + +Android does not provide apps with a notification whenever the app is shutting +down. As explained in the section above, apps will simply be killed whenever +the system needs to reclaim resources. This means that Gecko on Android will +never shutdown cleanly, and that shutdown actions will never execute. + +.. _principals: + +Principals +---------- + +In Gecko, a *website* loaded in a session is represented by an abstraction +called `principal +<https://searchfox.org/mozilla-central/rev/5b2d2863bd315f232a3f769f76e0eb16cdca7cb0/caps/nsIPrincipal.idl>`_. +Principals contain information that is used to determine what permissions have +been granted to the website instance, what APIs are available to it, which +container the page is loaded in, is the page in private browsing or not, etc. + +Principals are used throughout the Gecko codebase, GeckoView, however, does not +expose the concept to the API. This is intentional, as exposing it would +potentially expose the app to various security sensitive concepts, which would +violate the "secure" requirement for the GeckoView API. + +The absence of principals from the API is, e.g., why GeckoView does not offer a +way to set permissions given a URL string, as permissions are internally stored +by principal. See also `Setting Permissions`_. + +To learn more about principals see `this talk by Bobby Holley +<https://www.youtube.com/watch?v=28FPetl5Fl4>`_. + +Window model +------------ + +Internally, Gecko has the concept of *window* and *tab*. Given that GeckoView +doesn't have the concept of tab (since it might be used to build something that +is *not* a browser) we hide Gecko tabs from the GeckoView API. + +Each ``GeckoSession`` corresponds to a Gecko ``window`` object with exactly one +``tab`` in it. Because of this you might see ``window`` and ``session`` used +interchangeably in the code. + +Internally, Gecko uses ``window`` s for other things other than +``GeckoSession``, so we have to sometime be careful about knowing which windows +belong to GeckoView and which don't. For example, the background extension page +is implemented as a ``window`` object that doesn't paint to a surface. + +EventDispatcher +--------------- + +The GeckoView codebase is written in C++, JavaScript and Java, it runs across +processes and often deals with asynchronous and garbage-collected code with +complex lifetime dependencies. To make all of this work together, GeckoView +uses a cross-language event-driven architecture. + +The main orchestrator of this event-driven architecture is ``EventDispatcher``. +Each language has an implementation of ``EventDispatcher`` that can be used to +fire events that are reachable from any language. + +Each window (i.e. each session) has its own ``EventDispatcher`` instance, which +is also present on the content process. There is also a global +``EventDispatcher`` that is used to send and receive events that are not +related to a specific session. + +Events can have data associated to it, which is represented as a +``GeckoBundle`` (essentially a ``String``-keyed variant map) on the Java and +C++ side, and a plain object on the JavaScript side. Data is automatically +converted back and forth by ``EventDispatcher``. + +In Java, events are fired in the same thread where the listener was registered, +which allows us to ensure that events are received in a consistent order and +data is kept consistent, so that we by and large don't have to worry about +multi-threaded issues. + +JNI +--- + +GeckoView code uses the Java Native Interface or JNI to communicate between +Java and C++ directly. Our JNI exports are generated from the Java source code +whenever the ``@WrapForJNI`` annotation is present. For non-GeckoView code, the +list of classes for which we generate imports is defined at +``widget/android/bindings``. + +The lifetime of JNI objects depends on their native implementation: + +- If the class implements ``mozilla::SupportsWeakPtr``, the Java object will + store a ``WeakPtr`` to the native object and will not own the lifetime of the + object. +- If the class implements ``AddRef`` and ``Release`` from ``nsISupports``, the + Java object will store a ``RefPtr`` to the native object and will hold a + strong reference until the Java object releases the object using + ``DisposeNative``. +- If neither cases apply, the Java object will store a C++ pointer to the + native object. + +Calling Runtime delegates from native code +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Runtime delegates can be reached directly using the ``GeckoRuntime`` singleton. +A common pattern is to expose a ``@WrapForJNI`` method on ``GeckoRuntime`` that +will call the delegate, that than can be used on the native side. E.g. + +.. code:: java + + @WrapForJNI + private void featureCall() { + ThreadUtils.runOnUiThread(() -> { + if (mFeatureDelegate != null) { + mFeatureDelegate.feature(); + } + }); + } + +And then, on the native side: + +.. code:: cpp + + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + if (runtime != nullptr) { + runtime->FeatureCall(); + } + +Session delegates +~~~~~~~~~~~~~~~~~ + +``GeckoSession`` delegates require a little more care, as there's a copy of a +delegate for each ``window``. Normally, a method on ``android::nsWindow`` is +added which allows Gecko code to call it. A reference to ``nsWindow`` can be +obtained from a ``nsIWidget`` using ``nsWindow::From``: + +.. code:: cpp + + RefPtr<nsWindow> window = nsWindow::From(widget); + window->SessionDelegateFeature(); + +The ``nsWindow`` implementation can then forward the call to +``GeckoViewSupport``, which is the JNI native side of ``GeckoSession.Window``. + +.. code:: cpp + + void nsWindow::SessionDelegateFeature() { + auto acc(mGeckoViewSupport.Access()); + if (!acc) { + return; + } + acc->SessionDelegateFeature(aResponse); + } + +Which can in turn forward the call to the Java side using the JNI stubs. + +.. code:: cpp + + auto GeckoViewSupport::SessionDelegateFeature() { + GeckoSession::Window::LocalRef window(mGeckoViewWindow); + if (!window) { + return; + } + window->SessionDelegateFeature(); + } + +And finally, the Java implementation calls the session delegate. + +.. code:: java + + @WrapForJNI + private void sessionDelegateFeature() { + final GeckoSession session = mOwner.get(); + if (session == null) { + return; + } + ThreadUtils.postToUiThread(() -> { + final FeatureDelegate delegate = session.getFeatureDelegate(); + if (delegate == null) { + return; + } + delegate.feature(); + }); + } + +.. _permissions: + +Permissions +----------- + +There are two separate but related permission concepts in GeckoView: `Content` +permissions and `Android` permissions. See also the related `consumer doc +<../consumer/permissions.html>`_ on permissions. + +Content permissions +~~~~~~~~~~~~~~~~~~~ + +Content permissions are granted to individual web sites (more precisely, +`principals`_) and are managed internally using ``nsIPermissionManager``. +Content permissions are used by Gecko to keep track which website is allowed to +access a group of Web APIs or functionality. The Web has the concept of +permissions, but not all Gecko permissions map to Web-exposed permissions. + +For instance, the ``Notification`` permission, which allows websites to fire +notifications to the user, is exposed to the Web through +`Notification.requestPermission +<https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission>`_, +while the `autoplay` permission, which allows websites to play video and audio +without user interaction, is not exposed to the Web and websites have no way to +set or request this permission. + +GeckoView retains content permission data, which is an explicit violation of +the design principle of not storing data. This is done because storing +permissions is very complex, making a mistake when dealing with permissions +often ends up being a security vulnerability, and because permissions depend on +concepts that are not exposed to the GeckoView API like `principals`_. + +Android permissions +~~~~~~~~~~~~~~~~~~~ + +Consumers of GeckoView are Android apps and therefore they have to receive +permission to use certain features on behalf of websites. + +For instance, when a website requests Geolocation permission for the first +time, the app needs to request the corresponding Geolocation Android permission +in order to receive position data. + +You can read more about Android permissions on `this doc +<https://developer.android.com/guide/topics/permissions/overview>`_. + + +Implementation +~~~~~~~~~~~~~~ + +The main entry point from Gecko is ``nsIContentPermissionPrompt.prompt``, which +is handled in the `Permission module +<https://searchfox.org/mozilla-central/rev/256f84391cf5d4e3a4d66afbbcd744a5bec48956/mobile/android/components/geckoview/GeckoViewPermission.jsm#21>`_ +in the same process where the request is originated. + +The permission module calls the child actor `GeckoViewPermission +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/actors/GeckoViewPermissionChild.jsm#47>`_ +which issues a `GeckoView:ContentPermission +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/actors/GeckoViewPermissionChild.jsm#75>`_ +request to the Java front-end as needed. + +Media permissions are requested using a global observer, and therefore are +handled in a `Process actor +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/actors/GeckoViewPermissionProcessChild.jsm#41>`_, +media permissions requests have enough information to redirect the request to +the corresponding window child actor, with the exception of requests that are +not associated with a window, which are redirected to the `current active +window +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/actors/GeckoViewPermissionProcessParent.jsm#28-35>`_. + +Setting permissions +~~~~~~~~~~~~~~~~~~~ + +Permissions are stored in a map between a `principal <#principals>`_ and a list +of permission (key, value) pairs. To prevent security vulnerabilities, GeckoView +does not provide a way to set permissions given an arbitrary URL and requires +consumers to get hold of the `ContentPermission +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PermissionDelegate.ContentPermission.html>`_ +object. The ContentPermission object is returned in `onLocationChange +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.NavigationDelegate.html#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List)>`_ +upon navigation, making it unlikely to have confusion bugs whereby the +permission is given to the wrong website. + +Internally, some permissions are only present when a certain override is set, +e.g. Tracking Protection override permissions are only present when the page +has been given a TP override. Because the only way to set the value of a +permission is to get hold of the ``ContentPermission`` object, `we manually insert +<https://searchfox.org/mozilla-central/rev/5b2d2863bd315f232a3f769f76e0eb16cdca7cb0/mobile/android/modules/geckoview/GeckoViewNavigation.jsm#605-625>`_ +a `trackingprotection` permission on every page load. + +Autofill Support +---------------- + +GeckoView supports third-party autofill providers through Android's `autofill framework <https://developer.android.com/guide/topics/text/autofill>`_. Internally, this support is referred to as `autofill`. + +Document tree +~~~~~~~~~~~~~ + +The autofill Java front-end is located in the `Autofill class +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autofill.java#37>`_. +GeckoView maintains a virtual tree structure of the current document for each +``GeckoSession``. + +The virtual tree structure is composed of `Node +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autofill.java#593>`_ +objects which are immutable. Data associated to a node, including mutable data +like the current value, is stored in a separate `NodeData +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autofill.java#171>`_ +class. Only HTML nodes that are relevant to autofilling are referenced in the +virtual structure and each node is associated to a root node, e.g. the root +``<form>`` element. All root nodes are children of the autofill `mRoot +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autofill.java#210>`_ +node, hence making the overall structure a tree rather than a collection of +trees. Note that the root node is the only node in the virtual structure that +does not correspond to an actual element on the page. + +Internally, nodes are assigned a unique ``UUID`` string, which is used to match +nodes between the Java front-end and the data stored in GeckoView's chrome +Javascript. The autofill framework itself requires integer IDs for nodes, so we +store a mapping between UUIDs and integer IDs in the associated ``NodeData`` +object. The integer IDs are used only externally, while internally only the +UUIDs are used. The reason why we use a separate ID structure from the autofill +framework is that this allows us to `generate UUIDs +<https://searchfox.org/mozilla-central/rev/7e34cb7a0094a2f325a0c9db720cec0a2f2aca4f/mobile/android/actors/GeckoViewAutoFillChild.jsm#217-220>`_ +directly in the isolated content processes avoiding an IPC roundtrip to the +main process. + +Each ``Node`` object is associated to an ``EventCallback`` object which is +invoked whenever the node is autofilled by the autofill framework. + +Detecting autofillable nodes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +GeckoView scans every web page for password ``<input>`` elements whenever the +``pageshow`` event `fires +<https://searchfox.org/mozilla-central/rev/9dc5ffe42635b602d4ddfc9a4b8ea0befc94975a/mobile/android/actors/GeckoViewAutoFillChild.jsm#74-78>`_. + +It also uses ``DOMFormHasPassword`` and ``DOMInputPasswordAdded`` to detect +whenever a password element is added to the DOM after the ``pageshow`` event. + +Prefs +----- + +`Preferences </modules/libpref/index.html>`_ (or prefs) are used throughout +Gecko to configure the browser, enable custom features, etc. + +GeckoView does not directly expose prefs to Apps. A limited set configuration +options is exposed through ``GeckoRuntimeSettings``. + +``GeckoRuntimeSettings`` can be easily mapped to a Gecko ``pref`` using +``Pref``, e.g. + +.. code:: java + + /* package */ final Pref<Boolean> mPrefExample = + new Pref<Boolean>("example.pref", false); + +The value of the pref can then be read internally using ``mPrefExample.get`` +and written to using ``mPrefExample.commit``. + +Front-end and back-end +---------------------- + + |code-layers| + +Gecko and GeckoView code can be divided in five layers: + +- **Java API** the outermost code layer that is publicly accessible to + GeckoView embedders. +- **Java Front-End** All the Java code that supports the API and talks directly + to the Android APIs and to the JavaScript and C++ front-ends. +- **JavaScript Front-End** The main interface to the Gecko back-end (or Gecko + proper) in GeckoView is JavaScript, we use this layer to call into Gecko and + other utilities provided by Gecko, code lives in ``mobile/android`` +- **C++ Front-End** A smaller part of GeckoView is written in C++ and interacts + with Gecko directly, most of this code is lives in ``widget/android``. +- **C++/Rust Back-End** This is often referred to as "platform", includes all + core parts of Gecko and is usually accessed to in GeckoView from the C++ + front-end or the JavaScript front-end. + +Modules and Actors +------------------ + +GeckoView's JavaScript Front-End is largely divided into units called modules +and actors. For each feature, each window will have an instance of a Module, a +parent-side Actor and (potentially many) content-side Actor instances. For a +detailed description of this see `here <https://gist.github.com/agi/c900f3e473ff681158c0c907e34780e4#actors>`__. + +Testing infrastructure +---------------------- + +For a detailed description of our testing infrastructure see `GeckoView junit +Test Framework <junit.html>`_. + +.. |api-diagram| image:: ../assets/api-diagram.png +.. |view-runtime-session| image:: ../assets/view-runtime-session.png +.. |pageload-diagram| image:: ../assets/pageload-diagram.png +.. |code-layers| image:: ../assets/code-layers.png diff --git a/mobile/android/docs/geckoview/contributor/geckoview-quick-start.rst b/mobile/android/docs/geckoview/contributor/geckoview-quick-start.rst new file mode 100644 index 0000000000..2954ea2b10 --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/geckoview-quick-start.rst @@ -0,0 +1,342 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +================= +Contributor Guide +================= + +Table of contents +================= + +.. contents:: :local: + +GeckoView Contributor Quick Start Guide +======================================= + +This is a guide for developers who want to contribute to the GeckoView +project. If you want to get started using GeckoView in your app then you +should refer to the +`wiki <https://wiki.mozilla.org/Mobile/GeckoView#Get_Started>`_. + +Get set up with Mozilla Central +------------------------------- + +The GeckoView codebase is part of the main Firefox tree and can be found +in ``mozilla-central``. You will need to get set up as a contributor to +Firefox in order to contribute to GeckoView. To get set up with +``mozilla-central``, follow the `Quick Start Guide for Git +Users <mc-quick-start.html>`_, or the `Contributing to the Mozilla code +base <https://firefox-source-docs.mozilla.org/setup/contributing_code.html>`_ +guide and `Firefox Contributors’ Quick Reference +<https://firefox-source-docs.mozilla.org/contributing/contribution_quickref.html>`_ +for Mercurial users. + +Once you have a copy of ``mozilla-central``, you will need to build +GeckoView. + +Bootstrap Gecko +--------------- + +Bootstrap configures everything for GeckoView and Fennec (Firefox for Android) development. + +- Ensure you have ``mozilla-central`` checked out. If this is the first + time you are doing this, it may take some time. + +.. code:: bash + + git checkout central/default + +If you are on Windows, you will need to install the +`Java 1.8 SDK <https://adoptopenjdk.net/?variant=openjdk8>`__. + +If you are on a mac, you will need to have the Xcode build tools +installed. You can do this by either `installing +Xcode <https://developer.apple.com/xcode/>`__ or installing only the +tools from the command line by running ``xcode-select --install`` and +following the on screen instructions. + +You will need to ``bootstrap`` for GeckoView/Firefox for Android. The easiest way is to run the following command: + +.. code:: bash + + ./mach --no-interactive bootstrap --application-choice="GeckoView/Firefox for Android" + +.. note:: + + - The ``--no-interactive`` argument will make ``bootstrap`` run start to finish without requiring any input from you. It will automatically accept any license agreements. + - The ``--application-choice="GeckoView/Firefox for Android"`` argument is needed when using ``--no-interactive`` so that "bootstrapping" is done for the correct application (instead of the default). + + If you want to make all the selections yourself and/or read through the license agreements, you can simply run: + + .. code:: bash + + ./mach bootstrap + + Select ``4. GeckoView/Firefox for Android`` when prompted and respond to any subsequent prompts as they appear. + +Once ``./mach bootstrap`` is complete, it will automatically write +the configuration into a new ``mozconfig`` file. If you already +have a ``mozconfig``, ``mach`` will instead output new configuration +that you should append to your existing file. + +Build from the command line +--------------------------- + +In order to pick up the configuration changes we just made we need to +build from the command line. This will update generated sources, compile +native code, and produce GeckoView AARs and example and test APKs. + +.. code:: bash + + ./mach build + +Build Using Android Studio +-------------------------- + +- Install `Android + Studio <https://developer.android.com/studio/install>`_. +- Choose File->Open from the toolbar +- Navigate to the root of your ``mozilla-central`` source directory and + click “Open” +- Click yes if it asks if you want to use the gradle wrapper. + + - If the gradle sync does not automatically start, select File > + Sync Project with Gradle Files. + +- Wait for the project to index and gradle to sync. Once synced, the + workspace will reconfigure to display the different projects. + + - annotations contains custom Java annotations used inside GeckoView + - app contains geckoview build settings and omnijar. omnijar contains + the parts of Gecko and GeckoView that are not written in Java or Kotlin + - geckoview is the GeckoView project. Here is all the Java files + related to GeckoView + - geckoview_example is an example browser built using GeckoView. + + |alt text 1| + +Now you’re set up and ready to go. + +**Important: at this time, building from Android Studio or directly from +Gradle does not (re-)compile native code, including C++ and Rust.** This +means you will need to run ``mach build`` yourself to pick up changes to +native code. `Bug +1509539 <https://bugzilla.mozilla.org/show_bug.cgi?id=1509539>`_ tracks +making Android Studio and Gradle do this automatically. + +If you want set up code formatting for Kotlin, please reference +`IntelliJ IDEA configuration +<https://pinterest.github.io/ktlint/rules/configuration-intellij-idea/>`_. + +Custom mozconfig with Android Studio +------------------------------------ + +Out of the box, Android Studio will use the default mozconfig file, normally +located at ``mozconfig`` in the root directory of your ``mozilla-central`` +checkout. + +To make Android Studio use a mozconfig in a custom location, you can add the +following to your ``local.properties``: + +:: + + mozilla-central.mozconfig=relative/path/to/mozconfig + +Note that, when running mach from the command line, this value will be ignored, +and the mozconfig from the mach environment will be used instead. + +To override the mozconfig used by mach, you can use the `MOZCONFIG` environment +variable, for example: + +:: + + MOZCONFIG=debug.mozconfig ./mach build + +Performing a bug fix +-------------------- + +One you have got GeckoView building and running, you will want to start +contributing. There is a general guide to `Performing a Bug Fix for Git +Developers <contributing-to-mc.html>`_ for you to follow. To contribute to +GeckoView specifically, you will need the following additional +information. + +Running tests and linter locally +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To ensure that your patch does not break existing functionality in +GeckoView, you can run the junit test suite with the following command + +:: + + ./mach geckoview-junit + +This command also allows you to run individual tests or test classes, +e.g. + +:: + + ./mach geckoview-junit org.mozilla.geckoview.test.NavigationDelegateTest + ./mach geckoview-junit org.mozilla.geckoview.test.NavigationDelegateTest#loadUnknownHost + +If your patch makes a GeckoView JavaScript module, you should run ESLint +as well: + +:: + + ./mach lint -l eslint mobile/android/modules/geckoview/ + +To see information on other options, simply run +``./mach geckoview-junit --help``; of particular note for dealing with +intermittent test failures are ``--repeat N`` and +``--run-until-failure``, both of which do exactly what you’d expect. + +Updating the changelog and API documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the patch that you want to submit changes the public API for +GeckoView, you must ensure that the API documentation is kept up to +date. To check whether your patch has altered the API, run the following +command. + +.. code:: bash + + ./mach lint --linter android-api-lint + +The output of this command will inform you if any changes you have made +break the existing API. Review the changes and follow the instructions +it provides. + +If the linter asks you to update the changelog, please ensure that you +follow the correct format for changelog entries. Under the heading for +the next release version, add a new entry for the changes that you are +making to the API, along with links to any relevant files, and bug +number e.g. + +:: + + - Added [`GeckoRuntimeSettings.Builder#aboutConfigEnabled`][71.12] to control whether or + not `about:config` should be available. + ([bug 1540065]({{bugzilla}}1540065)) + + [71.12]: {{javadoc_uri}}/GeckoRuntimeSettings.Builder.html#aboutConfigEnabled(boolean) + +Submitting to the ``try`` server +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is advisable to run your tests before submitting your patch. You can +do this using Mozilla’s ``try`` server. To submit a GeckoView patch to +``try`` before submitting it for review, type: + +.. code:: bash + + ./mach try auto + +This will automatically select tests to run from our suite. If your patch +passes on ``try`` you can be (fairly) confident that it will land successfully +after review. + +Tagging a reviewer +~~~~~~~~~~~~~~~~~~ + +When submitting a patch to Phabricator, if you know who you want to +review your patch, put their Phabricator handle against the +``reviewers`` field. + +If you don’t know who to tag for a review in the Phabricator submission +message, leave the field blank and, after submission, follow the link to +the patch in Phabricator and scroll to the bottom of the screen until +you see the comment box. + +- Select the ``Add Action`` drop down and pick the ``Change Reviewers`` option. +- In the presented box, add ``geckoview-reviewers``. Selecting this group as the reviewer will notify all the members of the GeckoView team there is a patch to review. +- Click ``Submit`` to submit the reviewer change request. + +Include GeckoView as a dependency +--------------------------------- + +If you want to include a development version of GeckoView as a +dependency inside another app, you must link to a local copy. There are +several ways to achieve this, but the preferred way is to use Gradle’s +*dependency substitution* mechanism, for which there is first-class +support in ``mozilla-central`` and a pattern throughout Mozilla’s +GeckoView-consuming ecosystem. + +The good news is that ``mach build`` produces everything you need, so +that after the configuration below, you should find that the following +commands rebuild your local GeckoView and then consume your local +version in the downstream project. + +.. code:: sh + + cd /path/to/mozilla-central && ./mach build + cd /path/to/project && ./gradlew assembleDebug + +**Be sure that your ``mozconfig`` specifies the correct ``--target`` +argument for your target device.** Many projects use “ABI splitting” to +include only the target device’s native code libraries in APKs deployed +to the device. On x86-64 and aarch64 devices, this can result in +GeckoView failing to find any libraries, because valid x86 and ARM +libraries were not included in a deployed APK. Avoid this by setting +``--target`` to the exact ABI that your device supports. + +Dependency substiting your local GeckoView into a Mozilla project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Most GeckoView-consuming projects produced by Mozilla support dependency +substitution via ``local.properties``. These projects include: + +- `Fenix <https://github.com/mozilla-mobile/firefox-android/tree/main/fenix>`_ +- `reference-browser <https://github.com/mozilla-mobile/reference-browser>`_ +- `android-components <https://github.com/mozilla-mobile/firefox-android/tree/main/android-components>`_ +- `Firefox Reality <https://github.com/MozillaReality/FirefoxReality>`_ + +Simply edit (or create) the file ``local.properties`` in the project +root and include a line like: + +.. code:: properties + + dependencySubstitutions.geckoviewTopsrcdir=/path/to/mozilla-central + +The default object directory – the one that a plain ``mach build`` +discovers – will be used. You can optionally specify a particular object +directory with an additional line like: + +.. code:: properties + + dependencySubstitutions.geckoviewTopobjdir=/path/to/object-directory + +With these lines, the GeckoView-consuming project should use the +GeckoView AAR produced by ``mach build`` in your local +``mozilla-central``. + +**Remember to remove the lines in ``local.properties`` when you want to +return to using the published GeckoView builds!** + +Dependency substituting your local GeckoView into a non-Mozilla project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In projects that don’t have first-class support for dependency +substitution already, you can do the substitution yourself. See the +documentation in +`substitue-local-geckoview.gradle <https://hg.mozilla.org/mozilla-central/file/tip/substitute-local-geckoview.gradle>`_, +but roughly: in each Gradle project that consumes GeckoView, i.e., in +each ``build.gradle`` with a +``dependencies { ... 'org.mozilla.geckoview:geckoview-...' }`` block, +include lines like: + +.. code:: groovy + + ext.topsrcdir = "/path/to/mozilla-central" + ext.topobjdir = "/path/to/object-directory" // Optional. + apply from: "${topsrcdir}/substitute-local-geckoview.gradle" + +**Remember to remove the lines from all ``build.gradle`` files when you +want to return to using the published GeckoView builds!** + +Next Steps +---------- + +- Get started with `Native Debugging <native-debugging.html>`_ + +.. |alt text| image:: ../assets/DisableInstantRun.png +.. |alt text 1| image:: ../assets/GeckoViewStructure.png diff --git a/mobile/android/docs/geckoview/contributor/index.rst b/mobile/android/docs/geckoview/contributor/index.rst new file mode 100644 index 0000000000..f38b1c5677 --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/index.rst @@ -0,0 +1,29 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +========================= +Contributing to GeckoView +========================= + +.. toctree:: + :maxdepth: 1 + :glob: + :hidden: + + * + +- `Contributor Quick Start Guide <geckoview-quick-start.html>`_: + Get GeckoView set up for development. +- `GeckoView for Gecko Engineers <for-gecko-engineers.html>`_: A + quick-start guide for those already familiar with contributing to + Firefox development. +- `Mozilla Central Quick Start Guide <mc-quick-start.html>`_: Get Mozilla + Central set up for development. +- `Mozilla Central Contributor Guide <contributing-to-mc.html>`_: Get + started as a contributor to Mozilla Central. +- `Guide to Native Debugging in Android Studio <native-debugging.html>`_: + Set up Android Studio for debugging native code. +- `Architecture overview <geckoview-architecture.html>`_: An overview of + GeckoView's architecture. +- `Junit Test Framework <junit.html>`_: An overview of GeckoView's custom + Junit code. +- `apilint <apilint.html>`_: GeckoView's linter for the API. diff --git a/mobile/android/docs/geckoview/contributor/junit.rst b/mobile/android/docs/geckoview/contributor/junit.rst new file mode 100644 index 0000000000..bf8cfd4615 --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/junit.rst @@ -0,0 +1,373 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +==================== +Junit Test Framework +==================== + +GeckoView has `a lot +<https://searchfox.org/mozilla-central/rev/36904ac58d2528fc59f640db57cc9429103368d3/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java>`_ +of `custom +<https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support>`_ +code that is used to run junit tests. This document is an overview of what this +code does and how it works. + +.. contents:: Table of Contents + :depth: 2 + :local: + +Introduction +============ + +`GeckoView <https://geckoview.dev>`_ is an Android Library that can be used to +embed Gecko, the Web Engine behind Firefox, in applications. It is the +foundation for Firefox on Android, and it is intended to be used to build Web +Browsers, but can also be used to build other types of apps that need to +display Web content. + +GeckoView itself has no UI elements besides the Web View and uses Java +interfaces called "delegates" to let embedders (i.e. apps that use GeckoView) +implement UI behavior. + +For example, when a Web page's JavaScript code calls ``alert('Hello')`` the +embedder will receive a call to the `onAlertPrompt +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PromptDelegate.html#onAlertPrompt-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.PromptDelegate.AlertPrompt->`_ +method of the `PromptDelegate +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoSession.PromptDelegate.html>`_ +interface with all the information needed to display the prompt. + +As most delegate methods deal with UI elements, GeckoView will execute them on +the UI thread for the embedder's convenience. + +GeckoResult +----------- + +One thing that is important to understand for what follows is `GeckoResult +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/GeckoResult.html>`_. +``GeckoResult`` is a promise-like object that is used throughout the GeckoView +API, it allows embedders to asynchronously respond to delegate calls and +GeckoView to return results asynchronously. This is especially important for +GeckoView as it never provides synchronous access to Gecko as a design +principle. + +For example, when installing a WebExtension in GeckoView, the resulting +`WebExtension +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.html>`_ +object is returned in a ``GeckoResult``, which is completed when the extension +is fully installed: + +.. code:: java + + public GeckoResult<WebExtension> install(...) + +To simplify memory safety, ``GeckoResult`` will always `execute callbacks +<https://searchfox.org/mozilla-central/rev/36904ac58d2528fc59f640db57cc9429103368d3/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoResult.java#740-744>`_ +in the same thread where it was created, turning asynchronous code into +single-threaded javascript-style code. This is currently `implemented +<https://searchfox.org/mozilla-central/rev/36904ac58d2528fc59f640db57cc9429103368d3/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoResult.java#285>`_ +using the Android Looper for the thread, which restricts ``GeckoResult`` to +threads that have a looper, like the Android UI thread. + +Testing overview +---------------- + +Given that GeckoView is effectively a translation layer between Gecko and the +embedder, it's mostly tested through integration tests. The vast majority of +the GeckoView tests are of the form: + +- Load simple test web page +- Interact with the web page through a privileged JavaScript test API +- Verify that the right delegates are called with the right inputs + +and most of the test framework is built around making sure that these +interactions are easy to write and verify. + +Tests in GeckoView can be run using the ``mach`` interface, which is used by +most Gecko tests. E.g. to run the `loadUnknownHost +<https://searchfox.org/mozilla-central/rev/36904ac58d2528fc59f640db57cc9429103368d3/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt#186-196>`_ +test in ``NavigationDelegateTest`` you would type on your terminal: + +.. code:: shell + + ./mach geckoview-junit org.mozilla.geckoview.test.NavigationDelegateTest#loadUnknownHost + +Another way to run GeckoView tests is through the `Android Studio IDE +<https://developer.android.com/studio>`_. By running tests this way, however, +some parts of the test framework are not initialized, and thus some tests +behave differently or fail, as will be explained later. + +Testing envelope +---------------- + +Being a library, GeckoView has a natural, stable, testing envelope, namely the +GeckoView API. The vast majority of GeckoView tests only use +publicly-accessible APIs to verify the behavior of the API. + +Whenever the API is not enough to properly test behavior, the testing framework +offers targeted "privileged" testing APIs. + +Using a restricted, stable testing envelope has proven over the years to be an +effective way of writing consistent tests that don't break upon refactoring. + +Testing Environment +------------------- + +When run through ``mach``, the GeckoView junit tests run in a similar +environment as mochitests (a type of Web regression tests used in Gecko). They +have access to the mochitest web server at `example.com`, and inherit most of +the testing prefs and profile. + +Note the environment will not be the same as mochitests when the test is run +through Android Studio, the prefs will be inherited from the default GeckoView +prefs (i.e. the same prefs that would be enabled in a consumer's build of +GeckoView) and the mochitest web server will not be available. + +Tests account for this using the `isAutomation +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Environment.java#36-38>`_ +check, which essentially checks whether the test is running under ``mach`` or +via Android Studio. + +Unlike most other junit tests in the wild, GeckoView tests run in the UI +thread. This is done so that the GeckoResult objects are created on the right +thread. Without this, every test would most likely include a lot of blocks that +run code in the UI thread, adding significant boilerplate. + +Running tests on the UI thread is achieved by registering a custom ``TestRule`` +called `GeckoSessionTestRule +<https://searchfox.org/mozilla-central/rev/36904ac58d2528fc59f640db57cc9429103368d3/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt#186-196>`_, +which, among other things, `overrides the evaluate +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1307,1312>`_ +method and wraps everything into a ``instrumentation.runOnMainSync`` call. + +Verifying delegates +=================== + +As mentioned earlier, verifying that a delegate call happens is one of the most +common assertions that a GeckoView test makes. To facilitate that, +``GeckoSessionTestRule`` offers several ``delegate*`` utilities like: + +.. code:: java + + sessionRule.delegateUntilTestEnd(...) + sessionRule.delegateDuringNextWait(...) + sessionRule.waitUntilCalled(...) + sessionRule.forCallbacksDuringWait(...) + +These all take an arbitrary delegate object (which may include multiple +delegate implementations) and handle installing and cleaning up the delegate as +needed. + +Another set of facilities that ``GeckoSessionTestRule`` offers allow tests to +synchronously ``wait*`` for events, e.g. + +.. code:: java + + sessionRule.waitForJS(...) + sessionRule.waitForResult(...) + sessionRule.waitForPageStop(...) + +These facilities work together with the ``delegate*`` facilities by marking the +``NextWait`` or the ``DuringWait`` events. + +As an example, a test could load a page using ``session.loadUri``, wait until +the page has finished loading using ``waitForPageStop`` and then verify that +the expected delegate was called using ``forCallbacksDuringWait``. + +Note that the ``DuringWait`` here always refers to the last time a ``wait*`` +method was called and finished executing. + +The next sections will go into how this works and how it's implemented. + +Tracking delegate calls +----------------------- + +One thing you might have noticed in the above section is that +``forCallbacksDuringWait`` moves "backward" in time by replaying the delegates +called that happened while the wait was being executed. +``GeckoSessionTestRule`` achieves this by `injecting a proxy object +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1137>`_ +into every delegate, and `proxying every call +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1091-1092>`_ +to the current delegate according to the ``delegate`` test calls. + +The proxy delegate `is built +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1105-1106>`_ +using the Java reflection's ``Proxy.newProxyInstance`` method and receives `a +callback +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1030-1031>`_ +every time a method on the delegate is being executed. + +``GeckoSessionTestRule`` maintains a list of `"default" delegates +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#743-752>`_ +used in GeckoView, and will `use reflection +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#585>`_ +to match the object passed into the ``delegate*`` calls to the proxy delegates. + +For example, when calling + +.. code:: java + + sessionRule.delegateUntilTestEnd(object : NavigationDelegate, ProgressDelegate {}) + +``GeckoSessionTestRule`` will know to redirect all ``NavigationDelegate`` and +``ProgressDelegate`` calls to the object passed in ``delegateUntilTestEnd``. + +Replaying delegate calls +------------------------ + +Some delegate methods require output data to be passed in by the embedder, and +this requires extra care when going "backward in time" by replaying the +delegate's call. + +For example, whenever a page loads, GeckoView will call +``GeckoResult<AllowOrDeny> onLoadRequest(...)`` to know if the load can +continue or not. When replaying delegates, however, we don't know what the +value of ``onLoadRequest`` will be (or if the test is going to install a +delegate for it, either!). + +What ``GeckoSessionTestRule`` does, instead, is to `return the default value +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1092>`_ +for the delegate method, and ignore the replayed delegate method return value. +This can be a little confusing for test writers, for example this code `will +not` stop the page from loading: + +.. code:: java + + session.loadUri("https://www.mozilla.org") + sessionRule.waitForPageStop() + sessionRule.forCallbacksDuringWait(object : NavigationDelegate { + override fun onLoadRequest(session: GeckoSession, request: LoadRequest) : + GeckoResult<AllowOrDeny>? { + // this value is ignored + return GeckoResult.deny() + } + }) + +as the page has already loaded by the time the ``forCallbacksDuringWait`` call is +executed. + +Tracking Waits +-------------- + +To track when a ``wait`` occurs and to know when to replay delegate calls, +``GeckoSessionTestRule`` `stores +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1075>`_ +the list of delegate calls in a ``List<CallRecord>`` object, where +``CallRecord`` is a class that has enough information to replay a delegate +call. The test rule will track the `start and end index +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1619>`_ +of the last wait's delegate calls and `replay it +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1697-1724>`_ +when ``forCallbacksDuringWait`` is called. + +To wait until a delegate call happens, the test rule will first `examine +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1585>`_ +the already executed delegate calls using the call record list described above. +If none of the calls match, then it will `wait for new calls +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1589>`_ +to happen, using ``UiThreadUtils.waitForCondition``. + +``waitForCondition`` is also used to implement other type of ``wait*`` methods +like ``waitForResult``, which waits until a ``GeckoResult`` is executed. + +``waitForCondition`` runs on the UI thread, and it synchronously waits for an +event to occur. The events it waits for normally execute on the UI thread as +well, so it `injects itself +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/UiThreadUtils.java#145,153>`_ +in the Android event loop, checking for the condition after every event has +executed. If no more events remain in the queue, `it posts a delayed 100ms +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/UiThreadUtils.java#136-141>`_ +task to avoid clogging the event loop. + +Executing Javascript +==================== + +As you might have noticed from an earlier section, the test rule allows tests +to run arbitrary JavaScript code using ``waitForJS``. The GeckoView API, +however, doesn't offer such an API. + +The way ``waitForJS`` and ``evaluateJS`` are implemented will be the focus of +this section. + +How embedders run javascript +---------------------------- + +The only supported way of accessing a web page for embedders is to `write a +built-in WebExtension +<https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/web-extensions.html>`_ +and install it. This was done intentionally to avoid having to rewrite a lot of +the Web-Content-related APIs that the WebExtension API offers. + +GeckoView extends the WebExtension API to allow embedders to communicate to the +extension by `overloading +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/modules/geckoview/GeckoViewWebExtension.jsm#221>`_ +the native messaging API (which is not normally implemented on mobile). +Embedders can register themselves as a `native app +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.MessageDelegate.html>`_ +and the built-in extension will be able to `exchange messages +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.Port.html#postMessage-org.json.JSONObject->`_ +and `open ports +<https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/WebExtension.MessageDelegate.html#onConnect-org.mozilla.geckoview.WebExtension.Port->`_ +with the embedder. + +This is still a controversial topic among smaller embedders, especially solo +developers, and we have discussed internally the possibility to expose a +simpler API to run one-off javascript snippets, similar to what Chromium's +WebView offers, but nothing has been developed so far. + +The test runner extension +------------------------- + +To run arbitrary javascript in GeckoView, the test runner installs a `support +extension +<https://searchfox.org/mozilla-central/source/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support>`_. + +The test framework then `establishes +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1827>`_ +a port for the background script, used to run code in the main process, and a +port for every window, to be able to run javascript on test web pages. + +When ``evaluateJS`` is called, the test framework will send `a message +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1912>`_ +to the extension which then `calls eval +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-support.js#21>`_ +on it and returns the `JSON`-stringified version of the result `back +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1952-1956>`_ +to the test framework. + +The test framework also supports promises with `evaluatePromiseJS +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1888>`_. +It works similarly to ``evaluateJS`` but instead of returning the stringified +value, it `sets +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1879>`_ +the return value of the ``eval`` call into the ``this`` object, keyed by a +randomly-generated UUID. + +.. code:: java + + this[uuid] = eval(...) + +``evaluatePromiseJS`` then returns an ``ExtensionPromise`` Java object which +has a ``getValue`` method on it, which will essentially execute `await +this[uuid] +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#1883-1885>`_ +to get the value from the promise when needed. + +Beyond executing javascript +--------------------------- + +A natural way of breaking the boundaries of the GeckoView API is to run a +so-called "experiment extension". Experiment extensions have access to the full +Gecko front-end, which is written in JavaScript, and don't have limits on what +they can do. Experiment extensions are essentially what old add-ons used to be +in Firefox, very powerful and very dangerous. + +The test runner uses experiments to offer `privileged APIs +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-api.js>`_ +to tests like ``setPref`` or ``getLinkColor`` (which is not normally available +to websites for privacy concerns). + +Each privileged API is exposed as an `ordinary Java API +<https://searchfox.org/mozilla-central/rev/95d8478112eecdd0ee249a941788e03f47df240b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java#2101>`_ +and the test framework doesn't offer a way to run arbitrary chrome code to +discourage developers from relying too much on implementation-dependent +privileged code. diff --git a/mobile/android/docs/geckoview/contributor/mc-quick-start.rst b/mobile/android/docs/geckoview/contributor/mc-quick-start.rst new file mode 100644 index 0000000000..bac9dc3ce5 --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/mc-quick-start.rst @@ -0,0 +1,184 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +=========================== +Mozilla Central Quick Start +=========================== + +Table of contents +================= + +.. contents:: :local: + +Firefox Developer Git Quick Start Guide +======================================= + +Getting setup to as a first time Mozilla contributor is hard. There are +plenty of guides out there to help you get started as a contributor, but +many of the new contributor guides out of date often more current ones +are aimed at more experienced contributors. If you want to review these +guides, you can find several linked to from +:ref:`Working on Firefox <Working on Firefox>`. + +This guide will take you through setting up as a contributor to +``mozilla-central``, the Firefox main repository, as a git user. + +Setup +----- + +The first thing you will need is to install Mercurial as this is the VCS +that ``mozilla-central`` uses. + +.. _mac-0: + +Mac +~~~ + +.. _homebrew-0: + +Homebrew +^^^^^^^^ + +.. code:: bash + + brew install mercurial + +macports +^^^^^^^^ + +.. code:: bash + + sudo port install mercurial + +Linux +~~~~~ + +apt +^^^ + +.. code:: bash + + sudo apt-get install mercurial + +Alternatively you can install `Mercurial directly <https://www.mercurial-scm.org/wiki/Download>`_. + +Check that you have successfully installed Mercurial by running: + +.. code:: bash + + hg --version + +If you are an experienced git user and are unfamiliar with Mercurial, +you may want to install ``git-cinnabar``. Cinnabar is a git remote +helper that allows you to interact with Mercurial repos using git +semantics. + +git-cinnabar +------------ + +There is a Homebrew install option for ``git-cinnabar``, but this did +not work for me, nor did the installer option. Using these tools, when I +tried to clone the Mercurial repo it hung and did not complete. I had to +do a manual install before I could use ``git-cinnabar`` successfully to +download a Mercurial repo. If you would like to try either of these +option, however, here they are: + +.. _mac-1: + +Mac +~~~~~ + +.. _homebrew-1: + +Homebrew +^^^^^^^^ + +.. code:: bash + + brew install git-cinnabar + +All Platforms +~~~~~~~~~~~~~ + +Installer +^^^^^^^^^ + +.. code:: bash + + git cinnabar download + +Manual installation +^^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + git clone https://github.com/glandium/git-cinnabar.git && cd git-cinnabar + make + export PATH="$PATH:/somewhere/git-cinnabar" + echo 'export PATH="$PATH:/somewhere/git-cinnabar"' >> ~/.bash_profile + git cinnabar download + +``git-cinnabar``\ ’s creator, `glandium <https://glandium.org/>`_, has +written a number of posts about setting up for Firefox Development with +git. This `post <https://glandium.org/blog/?page_id=3438>`_ is the one +that has formed the basis for this walkthrough. + +In synopsis: + +- initialize an empty git repository + +.. code:: bash + + git init gecko && cd gecko + +- Configure git: + +.. code:: bash + + git config fetch.prune true + git config push.default upstream + +- Add remotes for your repositories. There are several to choose from, + ``central``, ``inbound``, ``beta``, ``release`` etc. but in reality, + if you plan on using Phabricator, which is Firefox’s preferred patch + submission system, you only need to set up ``central``. It might be + advisable to have access to ``inbound`` however, if you want to work + on a version of Firefox that is queued for release. This guide will + be focused on Phabricator. + +.. code:: bash + + git remote add central hg::https://hg.mozilla.org/mozilla-central -t branches/default/tip + git remote add inbound hg::https://hg.mozilla.org/integration/mozilla-inbound -t branches/default/tip + git remote set-url --push central hg::ssh://hg.mozilla.org/mozilla-central + git remote set-url --push inbound hg::ssh://hg.mozilla.org/integration/mozilla-inbound + +- Expose the branch tip to get quick access with some easy names. + +.. code:: bash + + git config remote.central.fetch +refs/heads/branches/default/tip:refs/remotes/central/default + git config remote.inbound.fetch +refs/heads/branches/default/tip:refs/remotes/inbound/default + +- Setup a remote for the try server. The try server is an easy way to + test a patch without actually checking the patch into the core + repository. Your code will go through the same tests as a + ``mozilla-central`` push, and you’ll be able to download builds if + you wish. + +.. code:: bash + + git remote add try hg::https://hg.mozilla.org/try + git config remote.try.skipDefaultUpdate true + git remote set-url --push try hg::ssh://hg.mozilla.org/try + git config remote.try.push +HEAD:refs/heads/branches/default/tip + +- Now update all the remotes. This performs a ``git fetch`` on all the + remotes. Mozilla Central is a *large* repository. Be prepared for + this to take a very long time. + +.. code:: bash + + git remote update + +All that’s left to do now is pick a bug to fix and `submit a +patch <contributing-to-mc.html>`__. diff --git a/mobile/android/docs/geckoview/contributor/native-debugging.rst b/mobile/android/docs/geckoview/contributor/native-debugging.rst new file mode 100644 index 0000000000..aab67e661c --- /dev/null +++ b/mobile/android/docs/geckoview/contributor/native-debugging.rst @@ -0,0 +1,262 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +===================== +Debugging Native Code +===================== + +Table of contents +================= + +.. contents:: :local: + +Debugging Native Code in Android Studio. +======================================== + +If you want to work on the C++ code that powers GeckoView, you will need +to be able to perform native debugging inside Android Studio. This +article will guide you through how to do that. + +If you need to get set up with GeckoView for the first time, follow the +`Quick Start Guide <geckoview-quick-start.html>`_. + +Perform a debug build of Gecko. +------------------------------- + +1. Edit your ``mozconfig`` file and add the following lines. These will + ensure that the build includes debug checks and symbols. + +.. code:: + + ac_add_options --enable-debug + +2. Ensure that the following lines are commented out in your + ``mozconfig`` if present. ``./mach configure`` will not allow + artifact builds to be enabled when generating a debug build. + +.. code:: + + # ac_add_options --enable-artifact-builds + +3. To be absolutely sure that Android Studio will pick up your debug + symbols, the first time you perform a debug build it is best to + clobber your ``MOZ_OBJDIR``. Subsequent builds should not need this + step. + +.. code:: bash + + ./mach clobber + +4. Build as usual. Because this is a debug build, and because you have + clobbered your ``MOZ_OBJDIR``, this will take a long time. Subsequent + builds will be incremental and take less time, so go make yourself a + cup of your favourite beverage. + +.. code:: bash + + ./mach build + +Set up lldb to find your symbols +-------------------------------- + +Edit your ``~/.lldbinit`` file (or create one if one does not already +exist) and add the following lines. + +The first line tells LLDB to enable inline breakpoints - Android Studio +will need this if you want to use visual breakpoints. + +The remaining lines tell LLDB where to go to find the symbols for +debugging. + +.. code:: bash + + settings set target.inline-breakpoint-strategy always + settings append target.exec-search-paths <PATH>/objdir-android-opt/toolkit/library/build + settings append target.exec-search-paths <PATH>/objdir-android-opt/mozglue/build + settings append target.exec-search-paths <PATH>/objdir-android-opt/security + +Set up Android Studio to perform native debugging. +================================================== + +1. Edit the configuration that you want to debug by clicking + ``Run -> Edit Configurations...`` and selecting the correct + configuration from the options on the left hand side of the resulting + window. +2. Select the ``Debugger`` tab. +3. Select ``Dual`` from the ``Debug type`` select box. Dual will allow + debugging of both native and Java code in the same session. It is + possible to use ``Native``, but it will only allow for debugging + native code, and it’s frequently necessary to break in the Java code + that configures Gecko and child processes in order to attach + debuggers at the correct times. +4. Under ``Symbol Directories``, add a new path pointing to + ``<PATH>/objdir-android-opt/toolkit/library/build``, the same path + that you entered into your ``.lldbinit`` file. +5. Select ``Apply`` and ``OK`` to close the window. + +Debug Native code in Android Studio +=================================== + +1. The first time you are running a debug session for your app, it’s + best to start from a completely clean build. Click + ``Build -> Rebuild Project`` to clean and rebuild. You can also + choose to remove any existing builds from your emulator to be + completely sure, but this may not be necessary. +2. If using Android Studio visual breakpoints, set your breakpoints in + your native code. +3. Run the app in debug mode as usual. +4. When debugging Fennec or geckoview_example, you will almost + immediately hit a breakpoint in ``ElfLoader.cpp``. This is expected. + If you are not using Android Studio visual breakpoints, you can set + your breakpoints here using the lldb console that is available now + this breakpoint has been hit. To set a breakpoint, select the app tab + (if running Dual, there will also be an ``<app> java`` tab) from the + debug window, and then select the ``lldb`` console tab. Type the + following into the console: + +.. code:: + + b <file>.cpp:<line number> + +5. Once your breakpoints have been set, click the continue execution + button to move beyond the ``ElfLoader`` breakpoint and your newly set + native breakpoints should be hit. Debug as usual. + +Attaching debuggers to content and other child processes +-------------------------------------------------------- + +Internally, GeckoView has a multi-process architecture. The main Gecko +process lives in the main Android process, but content rendering and +some other functions live in child processes. This balances load, +ensures certain critical security properties, and allows GeckoView to +recover if content processes become unresponsive or crash. However, it’s +generally delicate to debug child processes because they come and go. + +The general approach is to make the Java code in the child process that +you want to debug wait for a Java debugger at startup, and then to +connect such a Java debugger manually from the Android Studio UI. + +`Bug 1522318 <https://bugzilla.mozilla.org/show_bug.cgi?id=1522318>`__ +added environment variables that makes GeckoView wait for Java debuggers +to attach, making this debug process more developer-friendly. See +`Configuring GeckoView for Automation <../consumer/automation.html>`__ +for instructions on how to set environment variables that configure +GeckoView’s runtime environment. + +Making processes wait for a Java debugger +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``set-debug-app`` command will make Android wait for a debugger before +running an app or service. e.g., to make GeckoViewExample wait, run the +following: + +.. code:: shell + + adb shell am set-debug-app -w --persistent org.mozilla.geckoview_example + +The above command works with child processes too, e.g. to make the GPU +process wait for a debugger, run: + +.. code:: shell + + adb shell am set-debug-app -w --persistent org.mozilla.geckoview_example:gpu + + +Attaching a Java debugger to a waiting child process +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is standard: follow the `Android Studio instructions <https://developer.android.com/studio/debug/index.html#attach-debugger>`_. +You must attach a Java debugger, so you almost certainly want to attach +a ``Dual`` debugger and you definitely can’t attach only a ``Native`` +debugger. + +Determining the correct process to attach to is a little tricky because +the mapping from process ID (pid) to process name is not always clear. +Gecko content child processes are suffixed ``:tab`` at this time. + +If you attach ``Dual`` debuggers to both the main process and a content +child process, you will have four (4!) debug tabs to manage in Android +Studio, which is awkward. Android Studio doesn’t appear to configure +attached debuggers in the same way that it configures debuggers +connecting to launched Run Configurations, so you may need to manually +configure search paths – i.e., you may need to invoke the contents of +your ``lldbinit`` file in the appropriate ``lldb`` console by hand, +using an invocation like +``command source /absolute/path/to/topobjdir/lldbinit``. + +Android Studio also doesn’t appear to support targeting breakpoints from +the UI (say, from clicking in a gutter) to specific debug tabs, so you +may also need to set breakpoints in the appropriate ``lldb`` console by +hand. + +Managing more debug tabs may require different approaches. + +Debug Native Memory Allocations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Android Studio includes a `Native Memory Profiler +<https://developer.android.com/studio/profile/memory-profiler#native-memory-profiler>`_ +which works for physical devices running Android 10 and later. In order to +track allocations correctly Gecko must be built with ``jemalloc`` disabled. +Additionally, the native memory profiler appears to only work with ``aarch64`` +builds. The following must therefore be present in your ``mozconfig`` file: + +.. code:: + + ac_add_options --target=aarch64 + ac_add_options --disable-jemalloc + +The resulting profiles are symbolicated correctly in debug builds, however, you +may prefer to use a release build when profiling. Unfortunately a method to +symbolicate using local symbols from the development machine has not yet been +found, therefore in order for the profile to be symbolicated you must prevent +symbols being stripped during the build process. To do so, add the following to +your ``mozconfig``: + +.. code:: + + ac_add_options STRIP_FLAGS=--strip-debug + +And the following to ``mobile/android/geckoview/build.gradle``, and additionally +to ``mobile/android/geckoview_example/build.gradle`` if profiling GeckoView +Example, or ``app/build.gradle`` if profiling Fenix, for example. + +.. code:: groovy + + android { + packagingOptions { + doNotStrip "**/*.so" + } + } + +Using Android Studio on Windows +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can now use :ref:`artifact builds <Understanding Artifact Builds>` +mode on `MozillaBuild environment <https://wiki.mozilla.org/MozillaBuild>`_ even if you are +not using WSL. If you want to debug GeckoView using Android Studio on +Windows, you have to set an additional environment variable via the +Control Panel to run the gradle script. The ``mach`` command sets these +variables automatically, but Android Studio cannot. + +If you install MozillaBuild tools to ``C:\mozilla-build`` (default +installation path), you have to set the ``MOZILLABUILD`` environment +variable to recognize MozillaBuild installation path. + +To set environment variable on Windows 10, open the ``Control Panel`` +from ``Windows System``, then select ``System and Security`` - +``System`` - ``Advanced system settings`` - +``Environment Variables ...``. + +To set the ``MOZILLABUILD`` variable, click ``New...`` in +``User variables for``, then ``Variable name:`` is ``MOZILLABUILD`` and +``Variable value:`` is ``C:\mozilla-build``. + +You also have to append some tool paths to the ``Path`` environment +variable. + +To append the variables to PATH, double click ``Path`` in +``User Variables for``, then click ``New``. And append the following +variables to ``Path``. + +- ``%MOZILLABUILD%\msys\bin`` +- ``%MOZILLABUILD%\bin`` 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. diff --git a/mobile/android/docs/geckoview/index.rst b/mobile/android/docs/geckoview/index.rst new file mode 100644 index 0000000000..d238ba0a2d --- /dev/null +++ b/mobile/android/docs/geckoview/index.rst @@ -0,0 +1,36 @@ +.. -*- Mode: rst; fill-column: 80; -*- + +GeckoView +========= + +Android offers a built-in WebView, which applications can hook into in order to display web pages within the context of their app. However, Android's WebView is not really intended for building browsers, and hence, many advanced Web APIs are disabled. Furthermore, it is also a moving target: different phones might have different versions of WebView, all of which your app has to support. + +That is where GeckoView comes in. GeckoView is: + +- **Full-featured**: GeckoView is designed to expose the entire power of the Web to applications, and all that through a straightforward API. Think of it as harnessing the full power of Gecko (the engine that powers Firefox), while its API is WebView-like and easy to use. +- **Suited for apps and browsers**: GeckoView is particularly suited for building mobile browsers, but it can be embedded as a web engine component in any kind of app. +- **Self-Contained**: Because GeckoView is a standalone library that you bundle with your application, you can be confident that the code you test is the code that will actually run. +- **Standards Compliant**: Like Firefox, GeckoView offers excellent support for modern Web standards. + +============= +Documentation +============= + +.. toctree:: + :titlesonly: + + consumer/index + contributor/index + design/index + Changelog <https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/doc-files/CHANGELOG> + API Javadoc <https://mozilla.github.io/geckoview/javadoc/mozilla-central/index.html> + +================= +More information +================= + +* Talk to us on `Matrix <https://chat.mozilla.org/#/room/#geckoview:mozilla.org>`_ +* `GeckoView Wiki <https://wiki.mozilla.org/Mobile/GeckoView>`_ +* `GeckoView Source Code <https://searchfox.org/mozilla-central/source/mobile/android/geckoview>`_ +* `Raise a bug on GeckoView code <https://bugzilla.mozilla.org/enter_bug.cgi?product=GeckoView>`_ +* `Raise a documentation bug <https://github.com/mozilla/geckoview/issues>`_ |