aardio 文档

web.view 快速入门指南

1. 简介

web.view 是 aardio 中用于创建基于 WebView2 (Edge/Chromium 内核) 的浏览器控件的库。WebView2 接口简洁、性能强悍、稳定可靠。

更重要的是 WebView2 是 Win10、Win11 等主流桌面操作系统的自带组件,可生成体积较小的独立 EXE 程序。

2. 创建 web.view 控件

首先,我们需要导入必要的库并创建一个窗体:

import win.ui;
import web.view;

// 创建主窗口
var winform = win.form(text="web.view 示例");

// 创建 web.view 控件
var wb = web.view(winform);

winform.show();
win.loopMessage();

要点:

web.view 的第二个构造参数可选用一个表对象指定启动参数,示例:

var wb = web.view(winform,{ 
    extensions = true; //可选字段,是否允许用 `wb.loadExtension()` 加载浏览器扩展。
    language =  "zh-CN"; //可选字段,用于自定义浏览器界面语言与 Accept-Language 请求头 
    userDataDir = //可选字段,用于自定义用户数据目录,不同目录可隔离会话。

    /*
    startArguments 也是可选字段,指定与 Chrome / Edge 浏览器兼容的启动命令行参数。如果 startArguments 是表对象,则参数表中驼峰风格的参数名自动转为连字符格式并添加 -- 前缀。
    */
    startArguments = { 
        proxyServer = "SOCKS5://代理地址";
        userAgent = "Mozilla/5.0 (Linux; Android 9) AppleWebKit/537.36 Chrome/100.0 Mobile"
    };
})

web.view 第三个构造参数以后后续参数指定 startArguments:

var wb = web.view(winform,userDataDir,startArguments,...);

示例:


//指定多个命令行参数时,由 aardio 处理命令行转义并合并为空格分开的单个字符串。
var wb = web.view(winform, ,`--user-agent=Mozilla/5.0`,`--accept-lang=zh-CN`)

//以空格分隔的命令行参数,原始命令行格式自行处理转义
var wb = web.view(winform, ,`--user-agent=Mozilla/5.0 --accept-lang=zh-CN`)

3. 加载网页内容

web.view 控件提供了多种方式加载网页内容:

// 加载 URL,不指定  HTTP referrer 请求头
wb.go("https://example.com");

//跳转网址,当前网址设为 HTTP referrer 请求头(引用页面)
wb.location = "https://example.com"

// 加载 HTML 字符串
wb.html = "<html><body><h1>Hello World</h1></body></html>";

// 加载本地 HTML 文件
wb.go("/web/index.html");

如果 wb.go 的第一个参数指定了应用程序根目录下的相对路径(以单个斜杠或反斜杠开始),并且该路径位于发布后会嵌入 EXE 文件的内嵌资源目录,则我们必须事先引入 aardio 提供的嵌入式 HTTP 服务端,例如:

aardio 将会将资源路径自动转换为通过嵌入 HTTP 服务端访问的网址,如果 wb.go 指定的是 index.html 的路径,则 index.html 的父目录自动被设置为文档根目录( documentBase,在网页中通过 / 表示该目录 ),并且自动支持 SPA 应用。

如果 wb.go 的第二个参数是一个指定前端开发时的调试端口的数值,则在 aardio 开发环境中启动时会自动连接并等待调试端口可用(端口不可用时 web.view 将显示等待页面 ),示例:

wb.go("\web\index.html",37151);

在程序发布后,上面的参数 37151 将被忽略。

4. 在 aardio 中如何调用 JavaScript 。

首先在网页中添加下面的 JavaScript 全局函数:

<script> 
//定义全局函数
window.add = function(a,b){
    return a+ b;  
} 
</script>

在 aardio 中就可以如下调用上面的 JS 函数:

//调用 JS 函数
var result = wb.xcall("add",12,3);

//显示返回值
winform.msgbox(result);

wb.xcall 使用 JSON 在 aardio 与 JS 之间转换函数的调用参数与返回值。第一个参数可指定可获取 JS 函数的 JS 表达式。

wb.xcall 会阻塞等待 JS 函数的返回值,如果不需要返回值可改用异步调用的 wb.invoke 调用 JS 函数。wb.xcall 与 wb.invoke 除了是否等待 JS 返回值的区别,其他用法完全一样。

通过 wb.eval 函数可以直接执行 JS 表达式并获取返回值。wb.eval 使用 JSON 将 JS 返回值转换为 aardio 对象。注意 wb.eval 也是阻塞调用函数。

我们也可以通过 wb.doScript 函数执行 JS 代码,并可选指定一个异步非阻塞的回调函数以获取 JS 返回值,示例:

wb.doScript("window.location.href",function(result){
    winform.msgbox(result,"result 为 JS 返回的值")
})

注意: 所有阻塞调用 JS 的函数都 不会卡界面,等待的同时会继续处理窗口消息。

5. 使用 wb.external 导出 aardio 对象或函数到 JavaScript 。

web.view 可以通过 external 导出网页可以直接访问的 aardio 对象,示例:

// 导出 aardio 对象到 JavaScript
wb.external = {
    sayHello = function(name){
        winform.msgbox("Hello, " + name);
    }
}

// 网页中调用 aardio 函数
wb.html = /**
<!doctype html>
<html>
<body>
    <button onclick="aardio.sayHello('World')">Click Me</button>
</body>
</html>
**/

要点:

6. 使用 wb.export 导出 JavaScript 全局对象。

在 aardio 中可以用 wb.export 导出 aardio 对象为 JavaScript 全局对象。

要点:

示例:

wb.export({
    alert = function(msg){
        winform.msgbox(msg) 

        //要避免重入,例如:不能在 alert 回调中再次调用 alert 函数
        //wb.invoke("alet('test')");
    };
    nativeAdd = function(a,b){ 
        return a + b; 
    }
})

上面的参数是一个表,表中每个元素在等号前面的键名为导出到 JavaScript 的全局变量名称,等号后面的值为导出到 JavaScript 的全局对象。

wb.export 导出的函数使用 JSON 协议在 aardio 与 JavaScript 之间自动转换参数和返回值,JS 函数通常无需特别处理就默认支持 JSON 兼容的参数。

注意:

7. 网页调试

在网页上右键选择"检查"或按 F12 键可以打开开发者工具 (DevTools),在控制台可以查看 JS 输出的信息与错误信息。

8. 等待网页节点

wb.waitEle 可在当前网页内等待指定的 HTML 节点。

用法:

wb.waitEle(selector,callback,timeout)

要特别注意,wb.waitEle 仅在单个页在有效,如果打开其他的网页会导致 wb.waitEle 退出等待。

wb.waitEle2 则可以跨网页等待,用法:

wb.waitEle2(selector,timeout)

selector,timeout 参数的用法与 wb.waitEle 相同。

wb.waitEle2 没有回调参数,只能同步等待。好处是 wb.waitEle2 跨网页有效。如果在等待过程中可能需要打开不同的网页,则应使用支持跨网页的 wb.waitEle2 函数等待节点。

9. 完整示例

//创建 winform 窗口
import win.ui; 
var winform = win.form(text="窗口标题")

//创建 WebView2 浏览器控件
import web.view;
var wb = web.view(winform);

// 导出 aardio 函数
wb.external = {
    add: function(a, b) {
        return a + b;
    }   
} 

// 指定网页 HTML 代码
wb.html = /******
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
</head><body> 
<div id="result"></div>

<script> 
(async ()=>{

    //调用 aardio 导出的 wb.external.add 函数。
    var num = await aardio.add(1, 2)

    //显示结果
    document.getElementById('result').innerText = num;
})()
</script>
******/;

//在当前页面等待指定节点,改用 wb.waitEle2 则支持跨网页等待
wb.waitEle("#result","this.style.color='red'");

//显示窗口
winform.show();

//启动界面消息循环
win.loopMessage();

10. 使用 wb.preloadScript 添加网页默认加载执行的 JavaScript。

wb.preloadScript 函数添加的 JavaScript 在网页初始化时执行,保证在 window.onload 事件前执行,每次添加都会增加 JavaScript 脚本而不会覆盖之前添加的脚本。

下面的示例使用 wb.preloadScript 实现禁止在网页按 F5,Ctrl + R 刷新:

import win.ui;
/*DSG{{*/
var winform = win.form(text="WebView2 浏览器控件 - 禁止按 F5,Ctrl + R 刷新")
/*}}*/

import web.view;
var wb = web.view(winform);

var initScript = /****

//禁止页面刷新
document.onkeydown = function (e) {
    if (e.key == "F5" || (e.ctrlKey && e.key == "r") ) {
        e.preventDefault(); 
    }
} 

//禁止滚轮缩放
document.addEventListener('wheel', function(e) {
    if(e.ctrlKey) {
        e.preventDefault();
    }
}, { passive: false });

****/

//添加网页默认加载执行的 JavaScript
wb.preloadScript(initScript)

//打开网页
wb.go("https://www.example.com")

winform.show();
win.loopMessage();

10. CDP(Chrome DevTools Protocol) 接口

调用 CDP 命令的参数格式:

var result = wb.cdp(method,params,callback) 

订阅 CDP 事件的参数格式:

wb.cdpSubscribe(event,callback)

调用 wb.cdpSubscribe 函数总是会先取消之前订阅的同名事件。

下面是一个自动关闭弹框的例子:

import win.ui;
/*DSG{{*/
var winform = win.form(text="CDP 事件 - 自动关闭网页上弹出信息框")
/*}}*/

import web.view; 
var wb = web.view(winform);
winform.show();

//调用 cdp 命令。 
wb.cdp("Page.enable");

//订阅 CDP 事件
wb.cdpSubscribe("Page.javascriptDialogOpening",function(dlg){

    //为避免阻塞导致某些网页出现异常,应返回异步执行的函数关闭弹框。
    return function(){

        //自动关闭弹框,CDP 参数为 {accept=true}
        wb.cdp("Page.handleJavaScriptDialog",{accept=true});

        //调用 JS 函数打印 dlg 参数:dlg.message 是对话框文本,dlg.type 是对话框类型,dlg.url 是对话框所在页面网址 。
        wb.xcall("(v)=>document.write( JSON.stringify(v) )",dlg); 
    } 
})

wb.html = /**
<script type="text/javascript">alert("测试弹框")</script>
**/
win.loopMessage();

使用 wb.cdpQuery 获取页面节点,参数格式:

var ele = wb.cdpQuery(selector,parent,callback)

如果不指定 callback 参数则成功返回表对象,wb.cdpQuery 失败返回 null,错误代码。如果指定 callback 参数则不会等待查询节果而是异步回调 callback 函数,wb.cdpQuery 成功返回 true,失败返回 null,错误代码。

wb.cdpWaitQuery 函数的用法与 wb.cdpQuery 类似,但会一直等待直到找到节点或者超时:

var ele = wb.wb.cdpWaitQuery(selector,parent,timeout)

wb.cdpWaitQuery 与 wb.waitEle2 相同的是可以跨多页页面等待 CSS 选择器指定的节点出现。

示例:

import win.ui;
/*DSG{{*/
var winform = win.form(text="web.view - 调用 CDP 命令修改文件输入框路径")
/*}}*/

import web.view;
var wb = web.view(winform);

wb.html = `<input type="file">`

//获取控件
var fileInput = wb.cdpWaitQuery(`input[type="file"]`);

//设置文件路径
fileInput.files = { io._exepath }; 

//设置文件路径,fileInput 参数指定了 nodeId 与 files 这两个字段的值
wb.cdp("DOM.setFileInputFiles",fileInput) 

winform.show();
win.loopMessage();

11. 多线程调用 web.view 对象

请参考: 多线程入门

web.view 对象可通过线程参数传入工作线程, 跨线程调用将回发到界面线程执行。

import win.ui;
/*DSG{{*/
var winform = win.form(text="多线程界面回调") 
/*}}*/

import web.view;
var wb = web.view(winform);

//在网页 JS 脚本中通过全局变量名 aardio 调用 wb.external
wb.external = {
    ping = function(domain){

        //创建工作线程
        thread.invoke( 
            function(wb,domain){

                import process.popen; 

                //创建进程管道
                var prcs = process.popen("ping "+ domain);  

                for stdout in prcs.each()  {
                    wb.invoke("document.body.insertAdjacentText",'beforeend',stdout); 
                } 

            },wb,domain //线程启动参数
        )  
    } 
}

wb.html = /**
<body style="white-space: pre;">
<button 
    onclick="javascript:aardio.ping('www.example.com')"
    >开始干活了</button>
**/

winform.show();
win.loopMessage();

12. 弹窗事件 wb.onNewWindow

wb.onNewWindow 可用于拦截网页弹窗,示例:

import win.ui;
/*DSG{{*/
var winform = win.form(text="拦截网页弹窗";right=818;bottom=507)
/*}}*/

import web.view; 
var wb = web.view(winform);

//弹出新窗口触发
wb.onNewWindow = function(url){

    //耗时操作应返回异步自动执行的函数(提前结束 onNewWindow)
    return function(){ 

        //如果打开的是 file: 前缀网址,例如拖放文件到网页上。
            var filePath = inet.url.getFilePath(url)

            if(filePath){
                winform.msgbox(filePath,"本地文件");    
            }
            else {
                //用 wb.location 跳转才会指定 HTTP referrer 请求头
                wb.location = url;
            } 
        }
}

wb.html = /**
<html><head>
<base target="_blank" />
</head>

<a href="http://www.aardio.com">aardio.com</a>
<button onclick="window.open('http://www.aardio.com')" >aardio.com</button>
**/

winform.show();
win.loopMessage();

Markdown 格式