架构概述

简介

Gecko 是 Mozilla 开发的一个 Web 引擎,用于为各种平台上的 Firefox 提供动力。Web 引擎大致包含 JavaScript 引擎、渲染引擎、HTML 解析器、网络堆栈、各种媒体编码器、图形引擎、布局引擎等等。

浏览器本身的一部分代码通常被称为“chrome”代码(流行的 Chrome 浏览器由此得名),而网站代码则通常被称为“content”代码或内容网页。

GeckoView 是一个 Android 库,可用于将 Gecko 嵌入到 Android 应用中。以这种方式嵌入 Gecko 的 Android 应用通常被称为“嵌入器”或简称为“应用”。

GeckoView 为所有当前活跃的 Mozilla Android 浏览器提供动力,例如 Firefox for Android 和 Firefox Focus。

API

以下部分描述了 GeckoView API 中对嵌入器公开的公共部分。

api-diagram

总体原则

GeckoView 是一个有主见的库,它包含一个最小的 UI,并且不对正在使用的应用类型做任何假设。它在 Mozilla 内部的主要使用者是浏览器,因此 GeckoView 的许多功能都针对浏览器,但并没有假设嵌入器实际上是一个浏览器(例如,GeckoView 中没有“标签”的概念)。

GeckoView API 尝试保留尽可能少的数据,并将大部分数据存储委托给应用。此规则的一些显著例外是:权限、扩展和 Cookie。

视图、运行时和会话

view-runtime-session

GeckoView API 中有三个主要类

  • GeckoRuntime 表示应用中正在运行的 Gecko 实例。通常,应用只有一个运行时实例,该实例与应用的生命周期相同。API 中任何不特定于会话(稍后将详细介绍)的对象通常都可以从运行时访问。

  • GeckoSession 表示一个网站的实例。您可以将其视为浏览器中的标签或应用中的 Web 视图。与特定会话相关的任何对象都可以从此对象访问。通常,嵌入器将有许多 GeckoSession 实例,分别表示当前打开的每个标签。在内部,会话表示为一个只有一个标签的“窗口”。

  • GeckoView 是一个 Android View,嵌入器可以使用它在应用中绘制 GeckoSession。通常,只有与 GeckoView 关联的 GeckoSession 才是真正活跃的,即可以接收事件、触发计时器等。

委托

由于 GeckoView 没有 UI 元素并且不存储大量数据,因此它需要一种方法在网站需要需要这些功能的功能时委托行为。

为此,GeckoView 向嵌入器公开 Java 接口,称为委托。委托通常与运行时相关联(当它们不引用特定会话时),或者与会话相关联(当它们是特定于会话时)。

最重要的委托是

  • Autocomplete.StorageDelegate 嵌入器使用它来实现登录、地址和信用卡的自动填充功能。

  • ContentDelegate 它接收来自内容网页的事件,例如“打开新窗口”、“全屏请求”、“此标签崩溃”等。

  • HistoryDelegate 它接收有关新历史记录条目或已修改历史记录条目的事件。GeckoView 本身不存储历史记录,因此应用需要侦听历史记录事件并永久存储它们。

  • NavigationDelegate 通知嵌入器有关导航事件和请求。

  • PermissionDelegate 用于提示用户进行权限操作,例如地理位置、通知等。

  • PromptDelegate 实现内容侧提示,例如 alert()、confirm()、基本 HTTP 身份验证等。

  • MediaSession.Delegate 通知嵌入器有关页面上当前活动的媒体元素,并允许嵌入器暂停、恢复、接收播放状态等。

  • WebExtension.MessageDelegate 嵌入器使用它与内置扩展交换消息。另请参阅 与 Web 内容交互

GeckoDisplay

GeckoView 可以绘制到 SurfaceViewTextureView

  • SufaceView 是大多数应用将使用的默认选项,它提供了一个围绕 GL 表面的基本包装器,GeckoView 可以在此表面上绘制。SurfaceView 不是 Android 正常合成的一部分,这意味着 Android 无法在 SurfaceView 上方(部分)绘制或对其应用转换和动画。

  • TextureView 提供了一个可以转换和设置动画的表面,但它速度较慢,并且需要更多内存,因为它三重缓冲(这对于提供动画是必要的)。

大多数应用将使用 GeckoView 类来绘制网页。 GeckoView 类是 Android View,参与 Android 视图层次结构。

当应用不可见时,Android 会回收 GeckoView,释放关联的 SurfaceViewTextureView。这会在 Gecko 端触发一些操作

不使用 GeckoView 的应用(例如,因为它们无法使用 SurfaceView)需要手动管理活动状态,并在会话未在屏幕上绘制时调用 GeckoSession.setActive

线程安全

应用不可避免地必须以重要方式处理 Android UI。大多数 Android UI 工具包都在 UI 线程上运行,并要求使用者在其上执行方法调用。Android UI 线程运行一个事件循环,可用于从其他线程在其上调度任务。

另一方面,Gecko 拥有自己的主线程,其中发生许多前端交互,并且 Gecko 内部许多方法都期望在主线程上调用。

为了不给应用带来不必要的多线程代码负担,GeckoView 将始终桥接两个“主线程”并根据需要重定向方法调用。因此,大多数 GeckoView 委托调用将在 Android UI 线程上发生,并且大多数 API 也期望在 UI 线程上调用。

这有时会导致意想不到的性能问题,如下面的部分将进行说明。

GeckoResult

GeckoView API 中一个普遍使用的工具是 GeckoResult。GeckoResult 是一个类似 Promise 的类,应用和 Gecko 可以使用它以线程安全的方式异步返回值。在内部,GeckoResult 将跟踪其创建所在的线程,并使用线程的 Handler 在同一线程上执行回调。

在 Gecko 中使用时,GeckoResult 可以使用 MozPromise::FromGeckoResult 转换为 MozPromise

页面加载

pageload-diagram

GeckoView 提供了几个入口点,可用于对页面加载的不同阶段做出反应。这些交互可能很复杂且令人惊讶,因此在本节中我们将详细介绍它们。

对于每个页面加载,将发出以下委托调用:onLoadRequestonPageStartonLocationChangeonProgressChangeonSecurityChangeonSessionStateChangeonCanGoBackonCanGoForwardonLoadErroronPageStop

大多数方法调用不言而喻,并为应用提供了一个机会来更新 UI 以响应页面加载状态的变化。下面将介绍更有趣的委托调用。

onPageStart 和 onPageStop

onPageStartonPageStop 保证成对且按顺序出现,并表示页面加载的开始和结束。在开始和停止事件之间,可以执行多个 onLoadRequestonLocationChange 调用,表示重定向。

onLoadRequest

**onLoadRequest**,可能是最重要的一个 API,可以被 App 用于拦截页面加载。App 可以*拒绝*加载,这将阻止页面加载并由 App 内部处理,或者*允许*加载,这将使 Gecko 加载页面。无论页面加载是由 App 本身、Web 内容还是重定向导致,onLoadRequest 都会被调用。

当页面加载源于 Web 内容时,Gecko 必须同步等待 Android UI 线程调度对 onLoadRequest 的调用,并等待 App 响应。这通常需要忽略不计的时间,但当 Android UI 线程繁忙时,例如 App 首次绘制时,延迟可能会很大。这是我们正在积极努力改进的 GeckoView 领域之一。

onLoadError

onLoadError 在页面加载不正确时被调用,例如由于网络错误或 HTTPS 服务器配置错误。App 可以返回一个指向本地 HTML 文件的 URL,Gecko 将在内部使用该文件作为错误页面。

onLocationChange

onLocationChange 在 Gecko 提交导航并且 URL 可以安全地显示在 URL 地址栏时被调用。

onSessionStateChange

onSessionStateChange 在会话状态的任何部分发生变化时被调用,例如表单内容、滚动位置、缩放值等。更改会被批量处理,以避免过于频繁地调用此 API。

App 可以使用 onSessionStateChange 将序列化状态存储到磁盘,以便以后恢复会话。

第三方根证书

Gecko 维护自己的证书颁发机构存储,并且不使用平台的 CA 存储。GeckoView 遵循相同的策略,默认情况下不会读取 Android 的 CA 存储以确定根证书。

但是,GeckoView 提供了一种方法来导入添加到 Android CA 存储中的所有第三方 CA 根证书,方法是将 enterpriseRootsEnabled 运行时设置设置为 true,此功能在 EnterpriseRoots 中实现。

目前还没有任何 API 允许 App 手动指定其他 CA 根证书,不过这可能会随着 Bug 1522162 的修复而改变。

精简版和完整版构建

默认 GeckoView 构建的一个变体,在代码库中称为“完整版”(Omni),提供了在构建浏览器 App 时可能会有帮助的其他库。目前,Glean 库包含在 geckoview-omni 包中。默认构建 geckoview 不包含此类库,在代码库中称为“精简版”(Lite)。

完整版包中的其他库直接构建到 Gecko 的主 .so 文件 libxul.so 中。然后在 .module 包(位于 maven 存储库中)中声明这些库,例如,请参阅 geckoview-omni.module 文件。

"capabilities": [
  {
    "group": "org.mozilla.geckoview",
    "name": "geckoview-omni",
    "version": "102.0.20220623063721"
  },
  {
    "group": "org.mozilla.telemetry",
    "name": "glean-native",
    "version": "44.1.1"
  }
]

请注意,org.mozilla.telemetry:glean-native 功能与 org.mozilla.geckoview 一起声明。

然后,主要的 Glean 库依赖于 glean-native,它要么在独立包中提供(对于不包含 GeckoView 的 App),要么由上述 GeckoView 功能提供。

在 Treeherder 中,精简版构建用 Lite 表示,而完整版构建没有额外的标识,因为它们是默认构建,例如,对于 x86_64,平台名称将是:

  • Android 7.0 x86-64(完整版构建)

  • Android 7.0 x86-64 Lite(精简版构建)

扩展

可以使用 WebExtensionController::installWebExtensionController::installBuiltIn 安装扩展,它们会异步返回一个 WebExtension 对象,该对象可用于设置扩展特定行为的委托。

WebExtension 对象是不可变的,并且会在每次属性更改时被替换。例如,要禁用扩展,App 可以使用 disable 方法,该方法将返回 WebExtension 对象的更新版本。

在内部,所有表示同一个扩展的 WebExtension 对象共享相同的委托,这些委托存储在 WebExtensionController 中。

鉴于与扩展相关的海量数据,扩展安装会跨重启持久化。可以使用 WebExtensionController::list 列出现有的扩展。

除了普通的 WebExtension API 之外,GeckoView 还允许“内置”扩展通过原生消息传递与 App 通信。App 可以将自身注册为原生 App,并且扩展将能够使用 connectNativesendNativeMessage 与 App 通信。更多信息可以在这里找到 here

内部机制

以下部分描述了 Gecko 和 GeckoView 的实现方式。这些 GeckoView 部分通常不会公开给嵌入器。

进程模型

在内部,Gecko 使用多进程架构,大多数 Chrome 代码运行在“主”进程中,而内容代码运行在“子”进程中,也称为“内容”进程。还有其他类型的专用进程,例如“套接字”进程(运行网络代码的一部分)、“GPU”进程(执行 GPU 命令)、“扩展”进程(运行大多数扩展内容代码)等。

我们有意不对嵌入器公开我们的进程模型。

要详细了解多进程架构,请参阅 Fission for GeckoView engineers

大部分 GeckoView Java 代码运行在主进程上,子进程上有一层薄薄的粘合层,主要包含在 GeckoThread 中。

Android 上的进程优先级

在 Android 上,每个进程都被分配一个特定的优先级。当设备内存不足或系统想要节省资源时(例如,屏幕关闭一段时间或电池电量低),Android 将按反优先级顺序对所有进程进行排序,并使用 SIGKILL 事件杀死足够多的进程,直到达到给定的空闲内存和资源阈值。

对设备功能必不可少的进程获得最高优先级,其次是当前可见且处于屏幕焦点上的 App,然后是可见的 App(但不在焦点上)、后台进程等等。

没有关联 UI 的进程(例如后台服务)通常具有最低优先级,因此将被最频繁地杀死。

要提高服务的优先级,App 可以与其 bind。有三个可能的 bind 优先级值:

  • BIND_IMPORTANT:进程将与绑定到它的进程“一样重要”。

  • 默认优先级:进程的优先级将低于绑定到它的进程,但仍高于后台服务。

  • BIND_WAIVE_PRIORITY:将忽略此绑定以进行优先级考虑。

需要注意的是,每个服务的优先级仅相对于绑定到它的 App 的优先级而言。如果 App 不可见,则 App 本身以及附加到它的所有服务(无论绑定与否)都将获得后台优先级(即最低优先级)。

进程管理

每个 Gecko 进程对应一个 Android service 实例,该实例必须在 GeckoView 的 AndroidManifest.xml 中声明。

例如,这是 media 进程的定义:

<service
        android:name="org.mozilla.gecko.media.MediaManager"
        android:enabled="true"
        android:exported="false"
        android:isolatedProcess="false"
        android:process=":media">

进程创建由 Gecko 控制,Gecko 使用 GeckoProcessManager 与 Android 交互,该管理器将 Gecko 的优先级转换为 Android 的 bind 值。

因为当 App 处于后台时所有优先级都会被放弃,所以 Android 杀死一些 GeckoView 的服务而仍然使主进程保持活动状态的情况并不少见。

因此,Gecko 能够在运行时随时从进程消失中恢复非常重要。

优先级提示

在内部,GeckoView 将与 GeckoSession 关联的 Surface 的生命周期和会话所在的进程的进程优先级绑定在一起。

基本假设是,不可见的会话没有关联的 Surface,并且用户没有使用它,因此它不应获得高优先级状态。

实现方法是 通过设置 browser 对象上的 active 属性为 false,这会导致 Gecko 降低进程的优先级,假设同一进程中的其他窗口都没有 active=true。另请参阅 GeckoDisplay

但是,在某些用例中,仅查看 Surface 不够。例如,当用户打开设置菜单时,当前选定的标签将变为不可见,但用户仍然希望浏览器以比所有其他标签更高的优先级保留该标签状态。同样,当浏览器置于后台时,与当前标签关联的 Surface 将被销毁,但当前标签仍然比其他标签更重要,但由于它没有关联的 Surface,因此我们无法将其与所有其他标签区分开来。

为了解决上述问题,我们为使用者提供了一个 API 来“提升”会话优先级,setPriorityHint。在计算进程的优先级时会考虑优先级提示。包含活动会话或优先级提示会话的任何进程 都会被提升到最高优先级。

关闭

Android 不会在 App 关闭时向 App 提供通知。如上一节所述,只要系统需要回收资源,App 就会被简单地杀死。这意味着 Android 上的 Gecko 永远不会干净地关闭,并且关闭操作永远不会执行。

主体

在 Gecko 中,会话中加载的“网站”由称为 主体 的抽象表示。主体包含用于确定已授予网站实例哪些权限、哪些 API 可用、页面加载到哪个容器中、页面是否处于隐私浏览模式等的信息。

主体在整个 Gecko 代码库(包括 GeckoView)中使用,但是 GeckoView 不会将其概念公开到 API 中。这是有意的,因为公开它可能会将 App 暴露于各种安全敏感的概念,这将违反 GeckoView API 的“安全”要求。

API 中缺少 Principal 是 GeckoView 不提供通过 URL 字符串设置权限的方法的原因,因为权限是在内部由 Principal 存储的。另请参阅 设置权限

要详细了解 Principal,请参阅 Bobby Holley 的这段演讲

窗口模型

在内部,Gecko 有窗口标签页的概念。鉴于 GeckoView 没有标签页的概念(因为它可能被用于构建浏览器的东西),我们从 GeckoView API 中隐藏了 Gecko 标签页。

每个 GeckoSession 对应一个 Gecko window 对象,其中恰好包含一个 tab。因此,您可能会看到代码中 windowsession 交换使用。

在内部,Gecko 使用 window 来执行除了 GeckoSession 之外的其他操作,因此我们有时必须注意哪些窗口属于 GeckoView,哪些不属于。例如,后台扩展页面是作为 window 对象实现的,该对象不会绘制到表面上。

EventDispatcher

GeckoView 代码库是用 C++、JavaScript 和 Java 编写的,它跨进程运行,并且经常处理具有复杂生命周期依赖关系的异步和垃圾回收代码。为了使所有这些都能协同工作,GeckoView 使用了一种跨语言的事件驱动架构。

这种事件驱动架构的主要协调器是 EventDispatcher。每种语言都有一个 EventDispatcher 的实现,可用于触发任何语言都可以访问的事件。

每个窗口(即每个会话)都有自己的 EventDispatcher 实例,该实例也存在于内容进程中。还有一个全局的 EventDispatcher,用于发送和接收与特定会话无关的事件。

事件可以关联数据,这些数据在 Java 和 C++ 端表示为 GeckoBundle(本质上是 String 键控变体映射),在 JavaScript 端表示为普通对象。数据由 EventDispatcher 自动转换。

在 Java 中,事件在注册侦听器的同一线程中触发,这使我们能够确保事件以一致的顺序接收,并且数据保持一致,因此我们大体上不必担心多线程问题。

JNI

GeckoView 代码使用 Java 本地接口或 JNI 来直接在 Java 和 C++ 之间进行通信。我们的 JNI 导出是在 Java 源代码中存在 @WrapForJNI 注解时生成的。对于非 GeckoView 代码,我们为其生成导入的类的列表定义在 widget/android/bindings 中。

JNI 对象的生命周期取决于其原生实现。

  • 如果类实现了 mozilla::SupportsWeakPtr,则 Java 对象将存储对原生对象的 WeakPtr,并且不会拥有该对象的生命周期。

  • 如果类实现了 nsISupports 中的 AddRefRelease,则 Java 对象将存储对原生对象的 RefPtr,并将保持强引用,直到 Java 对象使用 DisposeNative 释放该对象。

  • 如果两种情况都不适用,则 Java 对象将存储指向原生对象的 C++ 指针。

从原生代码调用运行时委托

可以使用 GeckoRuntime 单例直接访问运行时委托。一种常见的模式是在 GeckoRuntime 上公开一个 @WrapForJNI 方法,该方法将调用委托,然后可以在原生端使用它。例如:

@WrapForJNI
private void featureCall() {
  ThreadUtils.runOnUiThread(() -> {
    if (mFeatureDelegate != null) {
      mFeatureDelegate.feature();
    }
  });
}

然后,在原生端

java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance();
if (runtime != nullptr) {
  runtime->FeatureCall();
}

会话委托

GeckoSession 委托需要稍微注意一些,因为每个 window 都有一个委托副本。通常,会添加 android::nsWindow 上的方法,允许 Gecko 代码调用它。可以使用 nsWindow::FromnsIWidget 获取对 nsWindow 的引用。

RefPtr<nsWindow> window = nsWindow::From(widget);
window->SessionDelegateFeature();

nsWindow 实现随后可以将调用转发到 GeckoViewSupport,它是 GeckoSession.Window 的 JNI 原生端。

void nsWindow::SessionDelegateFeature() {
  auto acc(mGeckoViewSupport.Access());
  if (!acc) {
    return;
  }
  acc->SessionDelegateFeature(aResponse);
}

然后,它可以使用 JNI 存根将调用转发到 Java 端。

auto GeckoViewSupport::SessionDelegateFeature() {
  GeckoSession::Window::LocalRef window(mGeckoViewWindow);
  if (!window) {
    return;
  }
  window->SessionDelegateFeature();
}

最后,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();
  });
}

权限

GeckoView 中有两个独立但相关的权限概念:内容 权限和 Android 权限。另请参阅有关权限的相关 消费者文档

内容权限

内容权限授予给各个网站(更准确地说是 Principal),并使用 nsIPermissionManager 在内部进行管理。内容权限由 Gecko 用于跟踪允许哪个网站访问一组 Web API 或功能。Web 具有权限的概念,但并非所有 Gecko 权限都映射到 Web 公开的权限。

例如,Notification 权限允许网站向用户触发通知,通过 Notification.requestPermission 公开给 Web,而 autoplay 权限允许网站在没有用户交互的情况下播放视频和音频,则不会公开给 Web,并且网站无法设置或请求此权限。

GeckoView 保留内容权限数据,这明确违反了不存储数据的设计原则。之所以这样做是因为存储权限非常复杂,处理权限时出错通常会导致安全漏洞,并且因为权限取决于未公开给 GeckoView API 的概念,例如 Principal

Android 权限

GeckoView 的使用者是 Android 应用,因此它们必须获得代表网站使用某些功能的权限。

例如,当网站首次请求地理位置权限时,应用需要请求相应的地理位置 Android 权限才能接收位置数据。

您可以在 此文档 中了解更多关于 Android 权限的信息。

实现

来自 Gecko 的主要入口点是 nsIContentPermissionPrompt.prompt,它在请求起源的同一进程中的 权限模块 中进行处理。

权限模块调用子 Actor GeckoViewPermission,该 Actor 根据需要向 Java 前端发出 GeckoView:ContentPermission 请求。

媒体权限请求使用全局观察器,因此在 进程 Actor 中处理,媒体权限请求有足够的信息将请求重定向到相应的窗口子 Actor,除了与窗口无关的请求,这些请求将重定向到 当前活动窗口

设置权限

权限存储在 Principal 和权限(键值对)列表之间的映射中。为了防止安全漏洞,GeckoView 不提供通过任意 URL 设置权限的方法,并要求使用者获取 ContentPermission 对象。ContentPermission 对象在导航时在 onLocationChange 中返回,从而不太可能出现将权限授予错误网站的混淆错误。

在内部,某些权限仅在设置了特定覆盖时才存在,例如,跟踪保护覆盖权限仅在页面已获得 TP 覆盖时才存在。因为设置权限值的唯一方法是获取 ContentPermission 对象,所以 我们手动插入每个页面加载的 trackingprotection 权限。

自动填充支持

GeckoView 通过 Android 的 自动填充框架 支持第三方自动填充提供程序。在内部,此支持称为 autofill

文档树

自动填充 Java 前端位于 Autofill 类 中。GeckoView 为每个 GeckoSession 维持当前文档的虚拟树结构。

虚拟树结构由 Node 对象组成,这些对象是不可变的。与节点关联的数据(包括可变数据,例如当前值)存储在单独的 NodeData 类中。虚拟结构中仅引用与自动填充相关的 HTML 节点,并且每个节点都与根节点关联,例如根 <form> 元素。所有根节点都是自动填充 mRoot 节点的子节点,因此使整体结构成为树而不是树的集合。请注意,根节点是虚拟结构中唯一不对应页面上实际元素的节点。

在内部,节点被分配一个唯一的 UUID 字符串,该字符串用于在 Java 前端和 GeckoView 的 Chrome JavaScript 中存储的数据之间匹配节点。自动填充框架本身需要节点的整数 ID,因此我们在关联的 NodeData 对象中存储 UUID 和整数 ID 之间的映射。整数 ID 仅在外部使用,而在内部仅使用 UUID。我们使用与自动填充框架不同的 ID 结构的原因是,这使我们能够 直接在隔离的内容进程中生成 UUID,避免到主进程的 IPC 往返。

每个 Node 对象都与一个 EventCallback 对象关联,该对象在自动填充框架自动填充节点时被调用。

检测可自动填充的节点

每当 pageshow 事件 触发 时,GeckoView 会扫描每个网页上的密码 <input> 元素。

它还使用 DOMFormHasPasswordDOMInputPasswordAdded 来检测何时在 pageshow 事件之后将密码元素添加到 DOM 中。

偏好设置

偏好设置(或 prefs)在整个 Gecko 中用于配置浏览器、启用自定义功能等。

GeckoView 不会直接向应用公开偏好设置。一组有限的配置选项通过 GeckoRuntimeSettings 公开。

GeckoRuntimeSettings 可以使用 Pref 轻松映射到 Gecko pref,例如:

/* package */ final Pref<Boolean> mPrefExample =
   new Pref<Boolean>("example.pref", false);

然后可以使用 mPrefExample.get 读取偏好设置的值,并使用 mPrefExample.commit 将其写入。

前端和后端

code-layers

Gecko 和 GeckoView 代码可以分为五个层级

  • Java API 最外层的代码层,对 GeckoView 嵌入器公开访问。

  • Java 前端 支持 API 并直接与 Android API 以及 JavaScript 和 C++ 前端交互的所有 Java 代码。

  • JavaScript 前端 GeckoView 中 Gecko 后端(或 Gecko 本身)的主要接口是 JavaScript,我们使用此层调用 Gecko 和 Gecko 提供的其他实用程序,代码位于 mobile/android 中。

  • C++ 前端 GeckoView 的一小部分是用 C++ 编写的,并直接与 Gecko 交互,大部分代码位于 widget/android 中。

  • C++/Rust 后端 这通常称为“平台”,包括 Gecko 的所有核心部分,通常从 GeckoView 中的 C++ 前端或 JavaScript 前端访问。

模块和 Actor

GeckoView 的 JavaScript 前端主要分为称为模块和 Actor 的单元。对于每个功能,每个窗口将有一个模块实例、一个父端 Actor 和(可能很多)内容端 Actor 实例。有关此的详细说明,请参见 此处

测试基础设施

有关我们的测试基础设施的详细说明,请参见 GeckoView junit 测试框架