WCPoint 源码深度解析:WebKit 与 JavaFX 之间的"坐标原子"
WCPoint 是 com.sun.webkit.graphics 包中一个极其轻量级的数据传输对象(DTO) 。它比我们之前分析过的 WCRectangle 还要简单,但它的存在却揭示了 JavaFX WebView 底层渲染管道中一个重要的设计原则:"可变与不可变的分离"。
它的核心使命:在 WebKit(C++)与 JavaFX(Java)之间传递单个二维坐标点的数据,用于描述路径锚点、变换原点、鼠标点击位置、渐变起点/终点等。
一、类声明与设计意图
java
public final class WCPoint {
final float x;
final float y;
// ...
}
final修饰:不可继承,表明它是一个纯粹的值对象(Value Object)。- 字段是
final的 :这是它和WCRectangle最本质的区别!WCRectangle的x, y, w, h是包可见且可变的 (setBounds会修改它们),而WCPoint的x, y是 不可变的(Immutable)。 - 包可见性(无
private) :允许同一包内的其他类(如WCGraphicsContext、WCTransform)直接访问x和y字段,省去 getter 调用的开销。同时,final保证了外部即使直接读取字段,也无法修改它们。
二、为什么 WCPoint 是不可变的,而 WCRectangle 是可变的?
这是一个极其精妙的性能与安全权衡设计:
| 类 | 可变性 | 设计理由 |
|---|---|---|
WCRectangle |
可变(Mutable) | 在渲染循环中,矩形区域(如脏区、裁剪区)被频繁修改和复用 。例如,在滚动过程中,同一个 WCRectangle 对象需要反复调用 translate 来更新坐标。如果每次都创建新对象,会瞬间压垮 GC(垃圾回收器)。因此,允许修改内部状态(setBounds、translate)是为了极致的内存性能。 |
WCPoint |
不可变(Immutable) | 点数据通常是作为方法参数 (如 drawLine(x1, y1, x2, y2))或返回值 (如 getMinX)传递。它极少需要原地修改。使用 final 字段确保了它在多线程环境(JNI 回调可能来自渲染线程)下的绝对线程安全,且作为 HashMap 的键时哈希值不会改变。 |
一句话总结:矩形是"工具",需要反复移动和变形(可变);点是"坐标",只需要精确地读出,然后丢弃(不可变)。
三、构造器与 Getter 方法
java
public WCPoint(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() { return x; }
public float getY() { return y; }
public int getIntX() { return (int)x; }
public int getIntY() { return (int)y; }
- 全参构造器:唯一构造方式,确保了对象创建后状态不可变。
getIntX/getIntY:与WCRectangle一样,提供整数版本。这在调用 JavaFX 的Node坐标(如setLayoutX)或进行像素级碰撞检测时非常方便,避免了调用方反复强转。
四、与之前分析过的类的区别与联系
1. 与 WCRectangle 的对比(最接近的"亲戚")
| 维度 | WCPoint |
WCRectangle |
|---|---|---|
| 数据量 | 2 个 float(x, y) |
4 个 float(x, y, w, h) |
| 可变性 | 不可变 (final 字段) |
可变 (字段非 final,有 setFrame 和 translate) |
| 用途 | 表示点、向量、偏移量 | 表示区域、边界、视口 |
| 线程安全 | 天然线程安全 | 需外部同步(通常仅在 JNI 回调线程使用) |
| 内存分配 | 在 JNI 回调中大量创建,但轻量且快速回收 | 对象复用,修改内部状态避免频繁分配 |
2. 与 Ref 的区别(资源 vs 数据)
Ref是资源句柄 ,有id和引用计数,需要WCGraphicsManager管理生命周期。WCPoint是纯数据 ,无生命周期管理,不涉及refMap,用完即抛。
3. 与 WCGraphicsContext 的协作
在 WCGraphicsContext 中,多个方法直接使用 WCPoint 作为参数:
java
public abstract WCGradient createLinearGradient(WCPoint p1, WCPoint p2);
public abstract void drawPattern(..., WCPoint phase, ...);
这里 WCPoint 承载了渐变起止点和平铺相位等几何信息,是绘制指令中"位置"语义的标准载体。
五、在 JNI 管道中的角色(与 C++ 的协作)
在 C++ 的 WebKit 层,存在一个完全对应的结构体(通常是 WebCore::FloatPoint)。当 C++ 层需要传递一个点坐标给 Java 时,JNI 层会直接创建一个 WCPoint 对象(或将两个 float 分别传递,由 GraphicsDecoder 组装)。
例如,fwkGetScreenRect 可能返回 WCRectangle,而某些 JNI 回调会直接返回 WCPoint(如获取鼠标在文档中的偏移量)。
这种"一对一"的映射保证了数据传输的低失真。 C++ 的 FloatPoint 对应 Java 的 WCPoint,C++ 的 FloatRect 对应 Java 的 WCRectangle。
六、为什么不直接使用 javafx.geometry.Point2D?
这个问题与 WCRectangle 为什么不使用 Rectangle2D 的原因完全一致:
- 精度匹配 :WebKit 使用
float,而javafx.geometry.Point2D使用double。使用float避免了跨语言边界的精度转换开销。 - 性能优先 :
Point2D是不可变的(使用double),且位于公共 API 包中,内部分配和缓存机制较复杂。WCPoint是专门为高频 JNI 回调设计的最简 DTO。 - 依赖隔离 :
com.sun.webkit包避免对公共 API 包的过度依赖,保持内部实现的自洽性。
七、总结
WCPoint 是 WebView 渲染管道中最低级、最简洁的"几何原子":
- 它是不可变的,代表"此刻在这里"的快照,适合作为参数在栈上快速传递。
- 它是
WCRectangle的"对立面":矩形是可变的工具(便于复用),点是不可变的信息(便于安全传递)。 - 它与
Ref无关,不涉及任何资源生命周期管理,纯粹的数学载体。 - 它贯穿整个图形管道 ,从 C++ 布局引擎的
FloatPoint,到 JNI 回调,再到WCGraphicsContext的渐变创建、模式平铺,无处不在。
一句话人话总结:
WCPoint 是 WebKit 发给 Java 的一条"坐标纸片"。它简单到无法再简单------就两个小数。正因为简单,它可以在程序里飞来飞去(跨线程、跨语言边界),而不需要担心被修改或产生内存负担。它和 WCRectangle 的区别,就像"图钉"(点)和"纸盒"(矩形)的区别------图钉用来标记位置,纸盒用来框住区域,各司其职。
WebPageClient 接口源码深度解析:WebKit 与 JavaFX WebView 之间的"外交大使"
WebPageClient<T> 是 com.sun.webkit 包中的一个核心接口 ,它在 JavaFX WebView 的底层架构中扮演着 "桥梁" 的角色。如果说 WCGraphicsManager 是图形资源的"总调度中心",WCRenderQueue 是绘制命令的"快递员",那么 WebPageClient 就是"外交官" ------它负责让 WebKit 引擎(通过 WebPage)能够与 JavaFX 的宿主容器(WebView)进行通信,处理那些与图形绘制无关 但与宿主环境紧密相关的事务,如光标样式、焦点、工具提示、屏幕信息、坐标转换、控制台消息等。
它不是 一个可见的类,而是一个契约。它定义了 WebKit 需要从宿主环境中获取哪些服务,以及如何向宿主环境发送通知。
一、接口声明与设计意图
java
public interface WebPageClient<T> {
// 方法定义...
}
- 泛型参数
T:代表宿主容器的类型。在 JavaFX 中,这个T就是WebView(即WebPageClientImpl实现了WebPageClient<WebView>)。 - 位置 :位于
com.sun.webkit包(内部 API),与WebPage类紧密耦合。 - 设计模式 :"依赖倒置原则(Dependency Inversion)" 的完美体现。
WebPage(高层模块)不直接依赖具体的WebView(低层模块),而是依赖WebPageClient这个抽象接口。这使得WebKit核心代码可以独立于 JavaFX 的 UI 控件,便于测试和扩展。
二、方法逐一详解(宿主服务的"菜单")
这个接口定义了 WebKit 可以使用的所有"宿主服务"。我们按功能分组来剖析:
1. 用户交互反馈(光标、焦点、提示)
setCursor(long cursorID)
- 作用 :通知宿主(
WebView)改变鼠标光标样式。 - 背景 :当 CSS 的
cursor属性变化,或悬停在不同元素上时,WebKit 会计算出对应的光标 ID(如IDC_HAND、IDC_TEXT)。 - 调用链 :
WebKit→WCFrameView.setCursor(long)→pageClient.setCursor(cursorID)→WebView设置Cursor。
setFocus(boolean focus)
- 作用:请求宿主获取或释放键盘焦点。
- 背景 :当 HTML 中的
input元素通过focus()方法请求焦点,或用户点击页面空白区域时。 - 调用链 :
WCFrameView.requestFocus()→pageClient.setFocus(true)→WebView.requestFocus()。
transferFocus(boolean forward)
- 作用:将焦点转移给宿主中的下一个/上一个可聚焦元素(Tab 键切换)。
- 背景:当用户在页面内按 Tab 键,且当前焦点在最后一个可聚焦元素时,需要将焦点移出 WebView。
- 实现 :通常调用
WebView的父容器的traverse方法。
setTooltip(String tooltip)
- 作用 :显示或更新 HTML 元素的
title属性或tooltip提示。 - 背景 :当鼠标悬停在元素上时,WebKit 会解析
title属性并调用此方法。 - 实现 :JavaFX 的
Tooltip机制。
2. 宿主环境信息(屏幕参数)
WCRectangle getScreenBounds(boolean available)
- 作用:获取屏幕的尺寸(全屏或可用区域,即排除任务栏等)。
- 背景 :WebKit 需要知道屏幕尺寸来计算
window.screen对象、CSS 媒体查询(@media)等。 - 返回值 :
WCRectangle(几何数据),表示屏幕区域的左、上、宽、高。
int getScreenDepth()
- 作用:获取屏幕颜色深度(每像素位数,如 24 位或 32 位)。
- 背景:影响图像解码和颜色处理策略。
3. 坐标转换(坐标系之间的"翻译")
WCPoint screenToWindow(WCPoint ptScreen)
- 作用:将屏幕坐标系(相对于显示器左上角)转换为窗口坐标系(相对于 WebView 控件的左上角)。
- 用途:处理鼠标点击、触摸事件的坐标映射,或弹出菜单的定位。
WCPoint windowToScreen(WCPoint ptWindow)
- 作用:反向转换,从窗口坐标系到屏幕坐标系。
- 用途 :当 WebKit 需要显示一个原生弹出框(如
<select>下拉菜单)时,需要知道其在屏幕上的绝对位置。
4. 容器引用
T getContainer()
- 作用 :返回宿主容器对象本身。在 JavaFX 中,返回
WebView实例。 - 用途 :某些底层操作需要直接访问
WebView的字段或方法(如获取Skin、触发布局)。
5. 页面后退缓冲(Back Buffer)
WCPageBackBuffer createBackBuffer()
- 作用:创建一个离屏缓冲区,用于在页面导航时保存当前页面的快照(实现"滑动后退"动画效果)。
- 背景:当用户按下后退按钮时,WebKit 可能需要显示上一个页面的缩略图。
boolean isBackBufferSupported()
- 作用 :询问宿主是否支持页面后退缓冲。如果支持,WebKit 会调用
createBackBuffer并合理使用;否则,放弃该优化。
6. 开发者工具支持
void addMessageToConsole(String message, int lineNumber, String sourceId)
- 作用 :将 WebKit 控制台(
console.log、console.error等)的消息转发给宿主,以便显示在 JavaFX 应用程序的日志或调试面板中。 - 背景:帮助开发者调试 Web 页面。
void didClearWindowObject(long context, long windowObject)
- 作用 :当 JavaScript 的
window对象被清除(如页面刷新或导航)后,通知宿主,以便重新注入 Java 对象(通过JSObject)。 - 背景 :允许 Java 代码向 Web 页面暴露 Java 方法(
window.java对象)。
三、与之前分析过的类的深度协作关系
1. 与 WCWidget / WCFrameView 的协作
在 WCWidget 中,我们看到了受保护的钩子方法:
java
protected void setCursor(long cursorID) {}
protected void requestFocus() {}
在 WCFrameView 中,这些方法被重写:
java
@Override protected void setCursor(long cursorID) {
WebPageClient pageClient = getPage().getPageClient();
if (pageClient != null) {
pageClient.setCursor(cursorID);
}
}
协作链条:
WebKit (C++) → JNI → fwkSetCursor() → WCFrameView.setCursor() → WebPageClient.setCursor()
WCFrameView持有WebPage的引用,通过getPage().getPageClient()获得WebPageClient实例。- 它将具体的行为委托给了
WebPageClient,从而将"如何响应"与"由谁响应"解耦。
2. 与 WebPage 的协作
WebPage 类(在 com.sun.webkit 包中)是 WebPageClient 的"持有者":
java
public class WebPage {
private WebPageClient pageClient;
public WebPageClient getPageClient() { return pageClient; }
// ...
}
WebPage在构造时接收一个WebPageClient实例(通常由WebView通过WebPageClientImpl传入)。- 所有
WCWidget子类通过getPage().getPageClient()访问该客户端。
3. 与 WebView 的协作(通过 WebPageClientImpl)
WebPageClientImpl 是 WebPageClient<WebView> 的具体实现,位于 com.sun.javafx.webkit 包(JDK 未包含,属于 JavaFX 内部实现)。它持有 WebView 的引用(通常通过 Accessor 模式间接持有),并实现了所有接口方法:
java
public final class WebPageClientImpl implements WebPageClient<WebView> {
private final Accessor accessor; // 间接持有 WebView
@Override public void setCursor(long cursorID) {
WebView view = accessor.getView();
if (view != null) {
// 将 cursorID 映射为 JavaFX Cursor
view.setCursor(cursorManager.getCursor(cursorID));
}
}
// ...
}
协作流程:
WebView创建时,在内部初始化WebPage并传入一个WebPageClientImpl实例。WebPage保存该实例。- 当
WCFrameView需要改变光标时,调用pageClient.setCursor(...),最终调用WebView.setCursor(...),更新场景图的光标样式。
四、WebPageClientImpl 为何不在 JDK 中?
WebPageClientImpl 位于 com.sun.javafx.webkit 包,而 com.sun.webkit 包是 JDK 内部包。这种设计是为了隔离实现细节:
com.sun.webkit:包含与 WebKit 核心引擎(C++)交互的抽象层(如WebPage、WCWidget、WCGraphicsManager),这部分代码对 JDK 可见。com.sun.javafx.webkit:包含 JavaFX 具体平台的集成代码,如WebView的皮肤、事件处理、WebPageClientImpl等。这部分代码不属于 JDK,而是 JavaFX 的一部分(通常随 JavaFX 运行时一起提供)。
这种分层保证了 JDK 中的 WebKit 抽象层不与具体的 UI 控件耦合,提高了模块化程度。如果你在使用 JavaFX,这些类会在运行时存在;如果你只使用纯 JDK(不引入 JavaFX),这些类自然不会被加载。
五、总结:WebPageClient 在全局架构中的角色
| 维度 | 角色 |
|---|---|
| 本质 | 定义了 WebPage 与宿主容器之间的通信协议 |
| 实现者 | WebPageClientImpl(在 JavaFX 运行时中),连接 WebPage 与 WebView |
| 调用方 | WCWidget、WCFrameView 以及其他需要宿主服务的内部类 |
| 方法覆盖 | 覆盖了光标、焦点、工具提示、屏幕信息、坐标转换、控制台、JS 注入等 |
| 设计模式 | 依赖倒置 、策略模式(宿主的策略通过此接口注入) |
一句话人话总结:
WebPageClient 是 WebKit 引擎向 JavaFX 宿主"求助"的官方渠道。当引擎想改变光标、请求焦点或显示工具提示时,它不会直接操作 WebView(因为引擎压根不知道 WebView 是什么),而是通过这个接口说"我需要服务 X",由接口的实现者(WebPageClientImpl)去调用 WebView 的对应方法。这让引擎变成了一个"只提要求、不问执行"的甲方,保证了核心逻辑(WebKit)和 UI 框架(JavaFX)的松耦合。
Accessor 源码深度解析:JavaFX WebView 模块间的"秘密通道"
Accessor 是 com.sun.javafx.webkit 包中的一个抽象类 。它在 JavaFX WebView 的架构中扮演着一个极其特殊的角色------它是公开的 WebEngine / WebView API 与内部 WebPage / WebKit 底层实现之间的"后门(Backdoor)"或"访问器(Accessor)"。
如果 WebPageClient 是 WebKit 向宿主(WebView)"求助"的官方电话线 ,那么 Accessor 就是宿主(WebView)向 WebKit 核心(WebPage)主动"窥探"的万能钥匙**。** 它打破了模块间的强依赖隔离,让 JavaFX 的公共 API 层能够触碰到内部实现的核心数据。
一、类声明与核心设计意图
java
public abstract class Accessor {
// ...
}
- 位置 :位于
com.sun.javafx.webkit包(JavaFX 内部集成层),而非com.sun.webkit(JDK 内部 WebKit 抽象层)。 - 抽象类 :由具体的实现类(通常是
WebView内部持有的Accessor实例)提供实现。 - 核心目的 :提供一种安全且受控 的方式,让
WebEngine和WebView的公共 API 方法能够获取或操作私有的WebPage对象、管理子节点、监听视图变化,而无需将WebPage类暴露给公共 API(javafx.scene.web包)。
二、静态部分(PageAccessor 与 getPageFor):模块边界的"破壁人"
java
public static interface PageAccessor {
public WebPage getPage(WebEngine w);
}
private static PageAccessor pageAccessor;
public static void setPageAccessor(PageAccessor instance) {
Accessor.pageAccessor = instance;
}
public static WebPage getPageFor(WebEngine w) {
return pageAccessor.getPage(w);
}
这是 Accessor 最精妙的设计------一个静态的"服务注册器"。
- 问题背景 :JavaFX 的公共 API 包(
javafx.scene.web)包含了WebEngine和WebView,但它们不能直接依赖 内部包(com.sun.webkit)中的WebPage类,否则会破坏模块化封装。 - 解决方案 :
Accessor提供了一个静态的"钩子"。- 在 JavaFX 运行时启动时(通常是
WebEngine初始化时),内部代码会调用Accessor.setPageAccessor(instance),向Accessor注册一个PageAccessor实例。 - 这个
PageAccessor实例持有从WebEngine到其内部WebPage的映射关系。 - 当公共 API 代码需要获取
WebPage时(例如在WebView的皮肤或事件处理中),它调用Accessor.getPageFor(webEngine),通过注册好的访问器获取WebPage引用。
- 在 JavaFX 运行时启动时(通常是
这种设计完美地绕过了 Java 的编译时依赖检查,实现了"依赖倒置"中的"接口隔离"。
三、实例方法(Accessor 的具体能力)
java
public abstract WebEngine getEngine();
public abstract WebView getView();
public abstract WebPage getPage();
public abstract void addChild(Node child);
public abstract void removeChild(Node child);
public abstract void addViewListener(InvalidationListener l);
1. 核心对象获取
getEngine() / getView() / getPage()
- 返回
Accessor所关联的核心组件。通常,一个Accessor实例就代表着一个WebView及其内部的WebEngine和WebPage。 - 用途 :允许其他内部组件(如
WebPageClientImpl、GraphicsDecoder、事件处理器)同时获得这三个对象的引用,方便跨层调用。
2. 子节点管理(addChild / removeChild)
这是 WebView 能够"弹出"原生控件(如 <select> 下拉框或 <input type="file"> 对话框)的关键机制。
- 背景 :WebKit 在渲染页面时,有时需要弹出非网页内容的 UI 组件(例如 HTML 的
<select>下拉菜单在 Windows 上可能显示为原生 ComboBox)。 - 实现 :WebKit 内部通过
WCWidget或WebPageClient通知 JavaFX 层创建对应的Node(如Popup或ComboBox),然后调用Accessor.addChild(child)将这个Node直接添加到WebView的children列表中。 - 区别 :这与
WebView通过getChildren().add()添加普通节点不同,Accessor的addChild内部会确保该节点在 WebView 的布局和事件体系中具有特殊优先级(如悬浮于网页之上)。
3. 视图监听(addViewListener)
java
public abstract void addViewListener(InvalidationListener l);
- 作用 :注册一个
InvalidationListener,用于监听WebView的"视图状态"变化。 - 背景 :当
WebView的尺寸、布局或可见性发生改变时,需要通知底层的WebPage重新计算布局或滚动条。addViewListener允许WebPage或WebEngine的某些内部组件(通过Accessor)监听这些变化,而无需持有WebView的直接引用,从而避免循环引用。
四、与之前分析过的类的联系与区别
| 类 | 角色 | 与 Accessor 的关系 |
|---|---|---|
WebPageClient |
定义"服务提供者"的接口(引擎 → 宿主) | Accessor 是"服务消费者"的工具(宿主 → 引擎)。WebPageClient 是 WebPage 调用的,而 Accessor 是 WebView 用来访问 WebPage 的。 |
WebPage |
Java 层 WebKit 核心对象 | Accessor 的 getPage() 方法直接返回 WebPage 引用,使得 WebView 可以调用 WebPage 的 setBounds、paint 等方法。 |
WebView |
JavaFX 场景图中的节点 | Accessor 由 WebView 内部提供实现,用于暴露其内部的 WebEngine、WebPage 以及对子节点的管理能力。 |
WebEngine |
非可视的浏览器引擎 | Accessor 静态部分(getPageFor)允许公共 API 通过 WebEngine 获取其私有的 WebPage。 |
WCWidget |
JNI 回调的"指令分发器" | WCWidget 通过 WebPageClient 向上通信;而 Accessor 则用于向下(从 UI 层到逻辑层)获取数据或执行操作,两者方向互补。 |
关键区别
- 通信方向 :
WebPageClient:自下而上(WebKit 引擎 → JavaFX 宿主)。例如,引擎说"我想改变光标"。Accessor:自上而下(JavaFX 宿主 → WebKit 引擎)。例如,宿主说"我要获取 WebPage 来调整滚动位置"。
- 可见性 :
WebPageClient是接口,由宿主实现,暴露给引擎。Accessor是抽象类,由宿主实现,暴露给框架内部的其它组件(如WebPage的某些方法需要通过Accessor回调到宿主)。
五、Accessor 在全局架构中的角色
它是 JavaFX WebView 模块化设计的"润滑剂":
- 打破模块边界 :利用
PageAccessor静态注册机制,使得公共 API(javafx.scene.web)可以安全地获取内部对象(WebPage),而不产生编译期循环依赖。 - 提供统一的门面 :将
WebView、WebEngine、WebPage、子节点管理、视图监听等分散的功能聚合到一个统一的访问点,便于内部实现时快速获取所需资源。 - 作为"可插拔"的桥梁 :
Accessor的实现可以被替换(在测试或特定环境中),方便 Mock 或模拟 WebView 的行为。
一句话人话总结:
Accessor 是 JavaFX WebView 世界里的 "万能通行证" 。当外交官(WebPageClient)解决不了的问题(比如宿主需要主动拽出引擎内部的数据),就需要这张通行证来敲开 WebPage 的大门。它用静态注册的方式巧妙地避开了 Java 的包隔离限制,让住在"公共区"(javafx 包)的 WebView 能指挥住在"内部区"(com.sun 包)的 WebPage 去干活,而两边都不需要知道对方的具体门牌号。