新版本已发布
JxBrowser 7 的支持将于 2025 年 10 月终止,届时将不再提供 Chromium 更新和关键修复。 我们建议您升级至 JxBrowser 8,以享受新功能和改进带来的更多优势。 如果您有任何疑问或在升级过程中需要帮助,欢迎随时 。
目录

JavaScript

本指南描述了如何在加载的网页上访问 JavaScript、执行 JavaScript 代码、注入 Java 对象以从 JavaScript 调用 Java 等操作。

执行 JavaScript 

JxBrowser 允许在加载的网页上访问和执行 JavaScript 代码。

要访问 JavaScript,请确保网页已完全加载,并启用 JavaScript。

要执行 JavaScript 代码,请使用 Frame.executeJavaScript(String)方法。此方法会阻塞当前线程的执行,并等待给定的代码执行完毕。该方法返回一个表示执行结果的 java.lang.Object。如果执行结果为 null 或 undefined,则该方法返回 null。

以下示例执行返回 document 标题的 JavaScript 代码:

Java
Kotlin
String title = frame.executeJavaScript("document.title");
val title = frame.executeJavaScript<String>("document.title")

您可以执行任何 JavaScript 代码:

Java
Kotlin
double number = frame.executeJavaScript("123");
boolean bool = frame.executeJavaScript("true");
String string = frame.executeJavaScript("'你好'");
JsFunction alert = frame.executeJavaScript("window.alert");
JsObject window = frame.executeJavaScript("window");
Element body = frame.executeJavaScript("document.body");
JsPromise promise = frame.executeJavaScript("Promise.resolve('Success')");
JsArray array = frame.executeJavaScript("['苹果', '香蕉']");
JsArrayBuffer arrayBuffer = frame.executeJavaScript("new ArrayBuffer(8)");
JsSet set = frame.executeJavaScript("new Set([1, 2, 3, 4])");
JsMap map = frame.executeJavaScript("new Map([['李白', '32'], ['李清照', '26']])");
val number = frame.executeJavaScript<Double>("123")
val bool = frame.executeJavaScript<Boolean>("true")
val string = frame.executeJavaScript<String>("'你好'")
val alert = frame.executeJavaScript<JsFunction>("window.alert")
val window = frame.executeJavaScript<JsObject>("window")
val body = frame.executeJavaScript<Element>("document.body")
val promise = frame.executeJavaScript<JsPromise>("Promise.resolve('Success')")
val array = frame.executeJavaScript<JsArray>("['苹果', '香蕉']")
val arrayBuffer = frame.executeJavaScript<JsArrayBuffer>("new ArrayBuffer(8)")
val set = frame.executeJavaScript<JsSet>("new Set([1, 2, 3, 4])")
val map = frame.executeJavaScript<JsMap>("new Map([['李白', '32'], ['李清照', '26']])")

如果您不想阻塞当前线程的执行,您可以使用 Frame.executeJavaScript(String javaScript, Consumer<?> callback) 方法。此方法异步执行给定的 JavaScript 代码,并通过提供的 callback 返回执行结果:

Java
Kotlin
frame.executeJavaScript("document.body", (Consumer<Element>) body -> {
    String html = body.innerHtml();
});
frame.executeJavaScript("document.body", Consumer<Element> { body -> 
    val html = body.innerHtml()
})

类型转换 

JavaScript 和 Java 使用不同的原始类型。JxBrowser 实现了从 JavaScript 到 Java 类型的自动转换,反之亦然。

将 JavaScript 转换为 Java 

以下规则用于将 JavaScript 转换为 Java 类型:

  • JavaScript numbers 转换为 java.lang.Double
  • JavaScript string 转换为 java.lang.String
  • JavaScript boolean 转换为 java.lang.Boolean
  • JavaScript null 或者 undefined 转换为 null
  • JavaScript Promise 转换为 JsPromise
  • JavaScript 对象被包装为 JsObject
  • JavaScript 函数被包装为 JsFunction
  • JavaScript DOM 节点对象被包装为 JsObject 和 EventTarget
  • JavaScript ArrayBuffer 被包装为 JsArrayBuffer
  • JavaScript Array 被包装为 JsArray
  • JavaScript Set 被包装为 JsSet
  • JavaScript Map 被包装为 JsMap

在上面的例子中我们知道 document.title 是一个字符串,所以我们将返回值设置为 java.lang.String。

将 Java 转换为 JavaScript 

以下规则用于将 Java 转换为 JavaScript 类型:

  • java.lang.Double 转换为 JavaScript Number
  • java.lang.String 转换为 JavaScript string
  • java.lang.Boolean 转换为 JavaScript boolean
  • Java null 转换为 JavaScript null
  • JsObject 转换为适当的 JavaScript object
  • JsPromise 转换为 JavaScript Promise
  • EventTarget 转换为适当的 JavaScript DOM 节点对象
  • java.lang.Object 被包装为 JavaScript 代理对象
  • java.util.List<?> 转换为 JavaScript Array 或代理对象
  • JsArray 转换为 JavaScript Array
  • java.util.Set<?> 转换为 JavaScript Set 或代理对象
  • JsSet 转换为 JavaScript Set
  • java.util.Map<?,?> 转换为 JavaScript Map 或代理对象
  • JsMap 转换为 JavaScript Map
  • byte[] 转换为 JavaScript ArrayBuffer
  • JsArrayBuffer 转换为 JavaScript ArrayBuffer

如果将非原始 Java 对象传递给 JavaScript,它将被转换为代理对象。对此对象的方法和属性调用将委托给 Java 对象。出于安全原因,JavaScript 只能访问那些使用 @JsAccessible 注释或 JsAccessibleTypes 类显式标记为可访问的注入 Java 对象的方法和字段。

如果 Java 集合没有使用 @JsAccessible 或通过 JsAccessibleTypes 类来使 JavaScript 可以访问,它们将被转换为 JavaScript 集合。转换后的集合内容是 Java 集合的深拷贝。JavaScript 中对转换后集合的修改不会影响 Java 中的集合。

如果 Java 集合通过 @JsAccessible 注解或通过 JsAccessibleTypes 类被设置为 JavaScript 可访问,它们将被包装成 JavaScript 代理对象。此类代理对象可用于修改 Java 中的集合。

DOM 包装器 

根据自动类型转换的规则,JavaScript DOM 对象会被同时包装为 JsObject 和 EventTarget。这使您能够通过 JxBrowser DOM API 操作 JavaScript DOM 对象。

在以下示例中,我们返回表示 JavaScript DOM 对象的 document。在这种情况下,返回值可以设置为 JsObject 或 Document:

Java
Kotlin
Document document = frame.executeJavaScript("document");
val document = frame.executeJavaScript<Document>("document")
Java
Kotlin
JsObject document = frame.executeJavaScript("document");
val document = frame.executeJavaScript<JsObject>("document")

使用 JsObject 

要从 Java 代码中操作 JavaScript 对象,请使用 JsObject 类。该类允许操作对象的属性并调用其函数。

属性 

要获取 JavaScript 对象的属性名称,包括原型对象的属性,请使用 propertyNames() 方法:

Java
Kotlin
List<String> propertyNames = jsObject.propertyNames();
val propertyNames = jsObject.propertyNames()

要检查 JavaScript 对象是否具有指定属性,请使用 hasProperty(String) 方法:

Java
Kotlin
boolean has = jsObject.hasProperty("<property-name>");
val has = jsObject.hasProperty("<property-name>")

要通过名称获取 JavaScript 对象属性的值,请使用 property(String)。例如:

Java
Kotlin
JsObject document = frame.executeJavaScript("document");
document.property("title").ifPresent(title -> {});
val document = frame.executeJavaScript<JsObject>("document")!!
document.property<String>("title").ifPresent { title -> }

返回值表示 java.lang.Object 它可以被设置为所需的类型。请参阅类型转换。

您可以使用以下方法移除属性:

Java
Kotlin
boolean success = jsObject.removeProperty("<property-name>");
val success = jsObject.removeProperty("<property-name>")

函数 

要调用具有所需名称和参数的函数,请使用 call(String methodName, Object... args) 方法。以下示例演示了如何调用 JavaScript 中的 document.getElementById() 函数:

Java
Kotlin
JsObject element = document.call("getElementById", "elementId");
val element: JsObject = document.call("getElementById", "elementId")

这相当于 JavaScript 中的以下代码:

var element = document.getElementById("demo");

如果在函数执行期间发生错误,该方法将抛出 JsException。

关闭 

具有 JsObject 对应项的 V8 对象不会受到 V8 垃圾回收机制的管理。默认情况下,我们将这些对象保留在内存中,直到页面被卸载。

为了优化内存使用,您可以基于每个对象启用垃圾回收:

Java
Kotlin
jsObject.close();
jsObject.close()

关闭 JsObject 会将相应的 Blink 对象标记为可回收对象,但它不会立即释放该对象。在调用 close() 方法后,尝试使用 JsObject 将导致 ObjectClosedException。

JsFunctionCallback 

另一种从 JavaScript 调用 Java 的方法是使用 JsFunctionCallback。

JavaScript-Java 桥接允许您将 JsFunctionCallback 与 JavaScript 属性相关联,该属性将被视为可以在 JavaScript 代码中调用的函数。

例如,您可以使用以下代码注册一个与 JsFunctionCallback 实例相关联的 JavaScript 函数:

Java
Kotlin
JsObject window = frame.executeJavaScript("window");
if (window != null) {
    window.putProperty("sayHello", (JsFunctionCallback) args ->
            "你好," + args[0]);
}
val window = frame.executeJavaScript<JsObject>("window")
window?.putProperty("sayHello", JsFunctionCallback { args -> "你好,${args[0]}" })

现在,在 JavaScript 中,您可以通过以下方式调用此函数:

window.sayHello('李白');

JsFunction 

从 7.7 版本开始,您可以直接从 Java 代码中使用 JavaScript 函数,并将对函数的引用从 JavaScript 传递给 Java。例如:

Java
Kotlin
JsObject window = frame.executeJavaScript("window");
if (window != null) {
    JsFunction alert = frame.executeJavaScript("window.alert");
    if (alert != null) {
        alert.invoke(window, "你好,世界!");
    }
}
val window = frame.executeJavaScript<JsObject>("window")
if (window != null) {
    val alert = frame.executeJavaScript<JsFunction>("window.alert")
    alert?.invoke<Any>(window, "你好,世界!")
}

JsPromise 

从 7.17 版本开始,您可以直接在 Java 代码中使用 JavaScript Promises。例如:

Java
Kotlin
JsPromise promise = frame.executeJavaScript(
        "new Promise(function(resolve, reject) {\n"
                + "    setTimeout(function() {\n"
                + "        resolve('你好 Java!');\n"
                + "    }, 2000);"
                + "})");
promise.then(results -> {
    System.out.println(results[0]);
    return promise;
}).then(results -> {
    System.out.println(results[0]);
    return promise;
}).catchError(errors -> {
    System.out.println(errors[0]);
    return promise;
});
val promise = frame.executeJavaScript<JsPromise>(
    """new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('你好 Java!');
            }, 2000);
        })
    """
)!!
promise.then { results ->
    println(results[0])
    promise
}.then { results ->
    println(results[0])
    promise
}.catchError { errors ->
    println(errors[0])
    promise
}

从 JavaScript 调用 Java 

当您将 java.lang.Object 作为属性值传递,或者在调用 JavaScript 函数时作为参数传递时,Java 对象将自动包装成 JavaScript 对象。

它允许将 Java 对象注入 JavaScript,并从 JavaScript 调用其公共方法和字段。

出于安全原因,只有使用 @JsAccessible 注解的公共非静态方法和字段,或者在标记了 @JsAccessible 注解的类中声明的方法和字段,才能从 JavaScript 中访问。带注释的 protected、private 或 package-private 方法和字段,或者在类中使用此类修饰符声明的方法和字段,在 JavaScript 中是不可访问的。

要将 Java 对象注入 JavaScript,请定义 Java 对象类,并使用 @JsAccessible 标记应可从 JavaScript 中访问的公共方法:

Java
Kotlin
public final class JavaObject {
    @JsAccessible
    public String sayHelloTo(String firstName) {
        return "你好 " + firstName + "!";
    }
}
class JavaObject {
    @JsAccessible
    fun sayHelloTo(firstName: String) = "你好 $firstName!"
}

在加载的网页上执行 JavaScript 之前,将一个 Java 对象的实例注入到 JavaScript 中:

Java
Kotlin
browser.set(InjectJsCallback.class, params -> {
    JsObject window = params.frame().executeJavaScript("window");
    window.putProperty("java", new JavaObject());
    return InjectJsCallback.Response.proceed();
});
browser.set(InjectJsCallback::class.java, InjectJsCallback { params ->
    val window = params.frame().executeJavaScript<JsObject>("window")
    window?.putProperty("java", JavaObject())
    InjectJsCallback.Response.proceed()
})

现在您可以从 JavaScript 引用该对象并调用其方法:

window.java.sayHelloTo("李白");

注释规则 

@JsAccessible 注解允许将注入的 Java 对象的方法和字段暴露给 JavaScript。

您只能使公共类型、方法和字段可访问。支持的完整情况列表如下:

  • 顶级类或接口
  • 嵌套的静态类或接口
  • 类或接口的非静态方法
  • 类的非静态字段

该注释不能应用于非公共类型、方法和字段。非公共类型的公共方法和字段被认为是非公共的。当您注释一个类型时,其所有公共方法和字段都会变得对 JavaScript 可访问。当您注释一个未注释类型的方法或字段时,只有注释的成员会对 JavaScript 可访问。

当可访问的方法在子类中被重写时,它仍然保持可访问。这意味着您可以使一个接口可访问,并将其任何实现传递给 JavaScript:接口中声明的所有方法都将从 JavaScript 可访问。在实现类中声明的其他方法和字段将保持不可访问,除非您显式地用此注释标记它们或整个类型。

另一种使类型可从 JavaScript 访问的方法是使用 JsAccessibleTypes。当您想要一个核心 Java 类型之一(例如 java.util.List)可访问,或第三方库中的类型可访问而无法使用此注释访问时,这特别有用。

例子:

公共顶级类的注释方法和字段是可访问的:

Java
Kotlin
public final class TopClass {
    @JsAccessible
    public Object accessibleField;
    @JsAccessible
    public void accessibleMethod() {}
}
class TopClass {
    @JsAccessible
    var accessibleField: Any? = null
    @JsAccessible
    fun accessibleMethod() {}
}

公共静态嵌套类的注释方法和字段是可访问的:

Java
Kotlin
public final class TopClass {
   public static class NestedClass {
       @JsAccessible
       public Object accessibleField;
       @JsAccessible
       public void accessibleMethod() {}
   }
}
class TopClass {
    class NestedClass {
        @JsAccessible
        var accessibleField: Any? = null
        @JsAccessible
        fun accessibleMethod() {}
    }
}

带注释类的未注释方法和字段是可访问的:

Java
Kotlin
@JsAccessible
public final class TopClass {
    public Object accessibleField;
    public void accessibleMethod() {}
}
@JsAccessible
class TopClass {
    var accessibleField: Any? = null
    fun accessibleMethod() {}
}

带注释的基类的方法和字段可以从继承者访问:

Java
Kotlin
public final class TopClass {
   @JsAccessible
   public static class BaseNestedClass {
       public Object accessibleFieldFromInheritor;
       public void accessibleMethodFromInheritor() {}
   }
   public static class NestedClass extends BaseNestedClass {
       public Object inaccessibleField;
       public void inaccessibleMethod() {}
   }
}
class TopClass {
    @JsAccessible
    open class BaseNestedClass {
        var accessibleFieldFromInheritor: Any? = null
        fun accessibleMethodFromInheritor() {}
    }
    class NestedClass : BaseNestedClass() {
        var inaccessibleField: Any? = null
        fun inaccessibleMethod() {}
    }
}

如果继承的方法和字段或它们所声明的类没有被注释,则不可访问:

Java
Kotlin
public final class TopClass {
   public static class BaseNestedClass {
       public Object inaccessibleField;
       public void inaccessibleMethod() {}
   }
   @JsAccessible
   public static class NestedClass extends BaseNestedClass {
       public Object accessibleField;
       public void accessibleMethod() {}
   }
}
class TopClass {
    open class BaseNestedClass {
        var inaccessibleField: Any? = null
        fun inaccessibleMethod() {}
    }
    @JsAccessible
    class NestedClass : BaseNestedClass() {
        var accessibleField: Any? = null
        fun accessibleMethod() {}
    }
}

覆盖类的方法是可访问的:

Java
Kotlin
public final class TopClass {
   public static class BaseNestedClass {
       @JsAccessible
       public void method() {}
   }
   public static class NestedClass extends BaseNestedClass {
       @Override
       public void method() {} // 可访问
   }
}
class TopClass {
    open class BaseNestedClass {
        @JsAccessible
        open fun method() {
        }
    }
    class NestedClass : BaseNestedClass() {
        override fun method() {} // 可访问
    }
}

已实现接口的方法是可访问的:

Java
Kotlin
public static class TopClass {
   public interface NestedInterface {
       @JsAccessible
       void method();
   }
   public static class AccessibleImplementor implements NestedInterface {
       @Override
       public void method() { } // 可访问
   }
}
class TopClass {
    interface NestedInterface {
        @JsAccessible
        fun method()
    }
    class AccessibleImplementor : NestedInterface {
        override fun method() {} // 可访问
    }
}

如果可访问的 Java 方法的签名包含原始数字参数,则将检查从 JavaScript 传递的数字是否有可能转换为 Java 参数类型。如果可以在无损情况下执行转换,并且没有找到其他合适的重载方法,则调用该方法。

如果存在多个可以接受传递参数的方法,JavaScript 将会抛出异常,指示请求的方法调用存在歧义,无法执行。

如果找不到与请求的名称对应的方法或字段,JavaScript 将会抛出异常,指示请求的成员不存在。

如果 JavaScript 请求的方法和字段具有相同名称,则 JavaScript 将会抛出异常,指示请求的成员不明确,无法访问。

自动类型转换 

当从 JavaScript 调用注入的 Java 对象的公共方法时,JavaScript-Java 桥接提供自动类型转换功能。

该库会在可能的情况下,自动将给定的 JavaScript Number 转换为所需的 Java 类型。如果我们检测到给定的数字不能无损地转换为 Java 的某个类型,例如 byte,那么该库会抛出一个异常并通知 JavaScript 没有合适的 Java 方法。如果给定的值可以在无损情况下进行转换,那么库会进行转换并调用相应的 Java 方法。

例如,如果您将以下 Java 对象注入到 JavaScript 中:

Java
Kotlin
public final class JavaObject {
    @JsAccessible
    public int method(int intValue) {
        return intValue;
    }
}
class JavaObject {
    @JsAccessible
    fun method(intValue: Int) = intValue
}

然后您就可以从 JavaScript 调用它,并传递一个可以无损转换为 Integer 的 JavaScript Number 值:

window.javaObject.method(123);

但是,如果您传递的 Double 值无法在无损情况下转换为 Integer,则会出现错误:

window.javaObject.method(3.14); // <- error

从库中调用 

注入的 Java 对象是一种特殊的对象。它们的行为与常规 JavaScript 对象不同,并且不打算直接传递给 JavaScript 库。

在 JavaScript 库中使用它们之前,我们建议使用 Proxy 进行封装。在这个例子中,我们创建了一个代理对象,它实现了对 JS 可访问成员的读取访问:

const proxy = new Proxy({__java: myJavaObject}, {
    get(target, prop, receiver) {
        for (let javaMemberName in target.__java) {
            if (prop === javaMemberName) {
                return target.__java[prop]
            }
        }
        return Reflect.get(...arguments);
    },
    ...
});

控制台消息 

JxBrowser 允许接收通过 console.log() JavaScript 函数发送到控制台的所有输出消息。您可以监听以下级别的消息:

  • DEBUG
  • LOG
  • WARNING
  • ERROR

要在控制台收到消息时获得通知,请使用 ConsoleMessageReceived 事件。例如:

Java
Kotlin
browser.on(ConsoleMessageReceived.class, event -> {
    ConsoleMessage consoleMessage = event.consoleMessage();
    ConsoleMessageLevel level = consoleMessage.level();
    String message = consoleMessage.message();
});
browser.on(ConsoleMessageReceived::class.java) { event ->
    val consoleMessage = event.consoleMessage()
    val level = consoleMessage.level()
    val message = consoleMessage.message()
}