OpenClaw Canvas 执行:JavaScript 注入实战

目录

    • 摘要
    • [1. 引言 - JavaScript 执行的价值](#1. 引言 - JavaScript 执行的价值)
      • [1.1 JS 执行应用场景](#1.1 JS 执行应用场景)
      • [1.2 eval 功能概览](#1.2 eval 功能概览)
      • [1.3 执行方式](#1.3 执行方式)
    • [2. 基本执行操作](#2. 基本执行操作)
      • [2.1 执行简单脚本](#2.1 执行简单脚本)
      • [2.2 获取页面信息](#2.2 获取页面信息)
      • [2.3 执行多行脚本](#2.3 执行多行脚本)
    • [3. 数据获取](#3. 数据获取)
      • [3.1 获取元素内容](#3.1 获取元素内容)
      • [3.2 获取表单数据](#3.2 获取表单数据)
      • [3.3 获取所有输入值](#3.3 获取所有输入值)
      • [3.4 获取计算样式](#3.4 获取计算样式)
    • [4. DOM 操作](#4. DOM 操作)
      • [4.1 修改元素内容](#4.1 修改元素内容)
      • [4.2 修改元素HTML](#4.2 修改元素HTML)
      • [4.3 添加元素](#4.3 添加元素)
      • [4.4 删除元素](#4.4 删除元素)
      • [4.5 修改属性](#4.5 修改属性)
    • [5. 样式控制](#5. 样式控制)
      • [5.1 修改内联样式](#5.1 修改内联样式)
      • [5.2 添加/删除类](#5.2 添加/删除类)
      • [5.3 主题切换](#5.3 主题切换)
    • [6. 事件处理](#6. 事件处理)
      • [6.1 触发点击](#6.1 触发点击)
      • [6.2 触发表单提交](#6.2 触发表单提交)
      • [6.3 触发自定义事件](#6.3 触发自定义事件)
    • [7. 实战案例一:动态数据更新](#7. 实战案例一:动态数据更新)
      • [7.1 场景描述](#7.1 场景描述)
      • [7.2 实现代码](#7.2 实现代码)
      • [7.3 更新图表](#7.3 更新图表)
    • [8. 实战案例二:表单自动填充](#8. 实战案例二:表单自动填充)
      • [8.1 场景描述](#8.1 场景描述)
      • [8.2 实现代码](#8.2 实现代码)
    • [9. 实战案例三:内容提取](#9. 实战案例三:内容提取)
      • [9.1 场景描述](#9.1 场景描述)
      • [9.2 实现代码](#9.2 实现代码)
    • [10. 高级技巧](#10. 高级技巧)
      • [10.1 异步操作](#10.1 异步操作)
      • [10.2 等待元素](#10.2 等待元素)
      • [10.3 批量操作](#10.3 批量操作)
    • [11. 安全注意事项](#11. 安全注意事项)
      • [11.1 安全风险](#11.1 安全风险)
      • [11.2 安全实践](#11.2 安全实践)
    • [12. 总结](#12. 总结)
      • [12.1 核心要点](#12.1 核心要点)
      • [12.2 最佳实践](#12.2 最佳实践)
      • [12.3 下一步](#12.3 下一步)
    • 参考资料

摘要

本文深入探讨 OpenClaw Canvas 的 JavaScript 执行功能。从基本执行、数据获取、DOM操作到高级应用,全面解析如何通过 eval 注入 JavaScript 实现灵活的界面控制。通过实际案例演示数据提取、动态更新、事件处理等场景,帮助开发者掌握 Canvas 的高级控制能力。⚡


1. 引言 - JavaScript 执行的价值

1.1 JS 执行应用场景

场景 说明 示例
数据获取 获取页面数据 读取表单值
DOM操作 修改页面元素 更新内容
事件触发 触发页面事件 点击按钮
动态更新 实时更新内容 刷新数据
样式控制 动态修改样式 切换主题

1.2 eval 功能概览

#mermaid-svg-36LXF38sEDLZeeCI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-36LXF38sEDLZeeCI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-36LXF38sEDLZeeCI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-36LXF38sEDLZeeCI .error-icon{fill:#552222;}#mermaid-svg-36LXF38sEDLZeeCI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-36LXF38sEDLZeeCI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-36LXF38sEDLZeeCI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-36LXF38sEDLZeeCI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-36LXF38sEDLZeeCI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-36LXF38sEDLZeeCI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-36LXF38sEDLZeeCI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-36LXF38sEDLZeeCI .marker.cross{stroke:#333333;}#mermaid-svg-36LXF38sEDLZeeCI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-36LXF38sEDLZeeCI p{margin:0;}#mermaid-svg-36LXF38sEDLZeeCI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster-label text{fill:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster-label span{color:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster-label span p{background-color:transparent;}#mermaid-svg-36LXF38sEDLZeeCI .label text,#mermaid-svg-36LXF38sEDLZeeCI span{fill:#333;color:#333;}#mermaid-svg-36LXF38sEDLZeeCI .node rect,#mermaid-svg-36LXF38sEDLZeeCI .node circle,#mermaid-svg-36LXF38sEDLZeeCI .node ellipse,#mermaid-svg-36LXF38sEDLZeeCI .node polygon,#mermaid-svg-36LXF38sEDLZeeCI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .rough-node .label text,#mermaid-svg-36LXF38sEDLZeeCI .node .label text,#mermaid-svg-36LXF38sEDLZeeCI .image-shape .label,#mermaid-svg-36LXF38sEDLZeeCI .icon-shape .label{text-anchor:middle;}#mermaid-svg-36LXF38sEDLZeeCI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .rough-node .label,#mermaid-svg-36LXF38sEDLZeeCI .node .label,#mermaid-svg-36LXF38sEDLZeeCI .image-shape .label,#mermaid-svg-36LXF38sEDLZeeCI .icon-shape .label{text-align:center;}#mermaid-svg-36LXF38sEDLZeeCI .node.clickable{cursor:pointer;}#mermaid-svg-36LXF38sEDLZeeCI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-36LXF38sEDLZeeCI .arrowheadPath{fill:#333333;}#mermaid-svg-36LXF38sEDLZeeCI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-36LXF38sEDLZeeCI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-36LXF38sEDLZeeCI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-36LXF38sEDLZeeCI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-36LXF38sEDLZeeCI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-36LXF38sEDLZeeCI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-36LXF38sEDLZeeCI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .cluster text{fill:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster span{color:#333;}#mermaid-svg-36LXF38sEDLZeeCI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-36LXF38sEDLZeeCI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-36LXF38sEDLZeeCI rect.text{fill:none;stroke-width:0;}#mermaid-svg-36LXF38sEDLZeeCI .icon-shape,#mermaid-svg-36LXF38sEDLZeeCI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-36LXF38sEDLZeeCI .icon-shape p,#mermaid-svg-36LXF38sEDLZeeCI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-36LXF38sEDLZeeCI .icon-shape .label rect,#mermaid-svg-36LXF38sEDLZeeCI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-36LXF38sEDLZeeCI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-36LXF38sEDLZeeCI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-36LXF38sEDLZeeCI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} eval 执行
执行类型
数据读取
DOM修改
事件触发
样式控制
返回结果

1.3 执行方式

方式 说明 返回值
同步执行 立即执行并返回
异步执行 异步执行 Promise

2. 基本执行操作

2.1 执行简单脚本

python 复制代码
result = canvas(
    action="eval",
    javaScript="1 + 1"
)
# result = 2

2.2 获取页面信息

python 复制代码
# 获取标题
title = canvas(
    action="eval",
    javaScript="document.title"
)

# 获取URL
url = canvas(
    action="eval",
    javaScript="window.location.href"
)

# 获取HTML
html = canvas(
    action="eval",
    javaScript="document.documentElement.outerHTML"
)

2.3 执行多行脚本

python 复制代码
result = canvas(
    action="eval",
    javaScript="""
    const title = document.title;
    const url = window.location.href;
    JSON.stringify({ title, url });
    """
)

3. 数据获取

3.1 获取元素内容

python 复制代码
def get_element_text(selector):
    """获取元素文本"""
    return canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').textContent
        """
    )

# 使用示例
text = get_element_text("#main-title")

3.2 获取表单数据

python 复制代码
def get_form_data(form_selector):
    """获取表单数据"""
    return canvas(
        action="eval",
        javaScript=f"""
        const form = document.querySelector('{form_selector}');
        const formData = new FormData(form);
        const data = {{}};
        for (let [key, value] of formData.entries()) {{
            data[key] = value;
        }}
        JSON.stringify(data);
        """
    )

3.3 获取所有输入值

python 复制代码
def get_all_inputs():
    """获取所有输入框的值"""
    return canvas(
        action="eval",
        javaScript="""
        const inputs = document.querySelectorAll('input, textarea, select');
        const data = {};
        inputs.forEach(input => {
            const name = input.name || input.id;
            if (name) {
                data[name] = input.value;
            }
        });
        JSON.stringify(data);
        """
    )

3.4 获取计算样式

python 复制代码
def get_computed_style(selector, property):
    """获取计算样式"""
    return canvas(
        action="eval",
        javaScript=f"""
        const element = document.querySelector('{selector}');
        window.getComputedStyle(element).getPropertyValue('{property}');
        """
    )

4. DOM 操作

4.1 修改元素内容

python 复制代码
def set_element_text(selector, text):
    """设置元素文本"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').textContent = '{text}';
        """
    )

4.2 修改元素HTML

python 复制代码
def set_element_html(selector, html):
    """设置元素HTML"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').innerHTML = `{html}`;
        """
    )

4.3 添加元素

python 复制代码
def append_element(parent_selector, html):
    """添加子元素"""
    canvas(
        action="eval",
        javaScript=f"""
        const parent = document.querySelector('{parent_selector}');
        const div = document.createElement('div');
        div.innerHTML = `{html}`;
        parent.appendChild(div.firstElementChild);
        """
    )

4.4 删除元素

python 复制代码
def remove_element(selector):
    """删除元素"""
    canvas(
        action="eval",
        javaScript=f"""
        const element = document.querySelector('{selector}');
        if (element) element.remove();
        """
    )

4.5 修改属性

python 复制代码
def set_attribute(selector, attr, value):
    """设置属性"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').setAttribute('{attr}', '{value}');
        """
    )

5. 样式控制

5.1 修改内联样式

python 复制代码
def set_style(selector, property, value):
    """设置样式"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').style.{property} = '{value}';
        """
    )

# 使用示例
set_style('#header', 'backgroundColor', '#667eea')
set_style('#content', 'display', 'none')

5.2 添加/删除类

python 复制代码
def add_class(selector, class_name):
    """添加类"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').classList.add('{class_name}');
        """
    )

def remove_class(selector, class_name):
    """删除类"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').classList.remove('{class_name}');
        """
    )

def toggle_class(selector, class_name):
    """切换类"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').classList.toggle('{class_name}');
        """
    )

5.3 主题切换

python 复制代码
def toggle_theme():
    """切换主题"""
    canvas(
        action="eval",
        javaScript="""
        document.body.classList.toggle('dark-theme');
        """
    )

6. 事件处理

6.1 触发点击

python 复制代码
def trigger_click(selector):
    """触发点击事件"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{selector}').click();
        """
    )

6.2 触发表单提交

python 复制代码
def submit_form(form_selector):
    """提交表单"""
    canvas(
        action="eval",
        javaScript=f"""
        document.querySelector('{form_selector}').submit();
        """
    )

6.3 触发自定义事件

python 复制代码
def trigger_custom_event(selector, event_name, detail=None):
    """触发自定义事件"""
    detail_json = json.dumps(detail) if detail else '{}'
    canvas(
        action="eval",
        javaScript=f"""
        const event = new CustomEvent('{event_name}', {{ detail: {detail_json} }});
        document.querySelector('{selector}').dispatchEvent(event);
        """
    )

7. 实战案例一:动态数据更新

7.1 场景描述

实时更新 Canvas 中的数据展示。

7.2 实现代码

python 复制代码
def update_dashboard(data):
    """更新仪表盘数据"""
    # 构建更新脚本
    updates = []
    for key, value in data.items():
        updates.append(f"document.getElementById('{key}').textContent = '{value}';")
    
    canvas(
        action="eval",
        javaScript="\n".join(updates)
    )

# 使用示例
update_dashboard({
    "total-users": "1,234",
    "active-users": "567",
    "requests": "12,345",
    "success-rate": "99.5%"
})

7.3 更新图表

python 复制代码
def update_chart(chart_id, new_data):
    """更新图表数据"""
    canvas(
        action="eval",
        javaScript=f"""
        const chart = Chart.getChart('{chart_id}');
        if (chart) {{
            chart.data.datasets[0].data = {json.dumps(new_data)};
            chart.update();
        }}
        """
    )

8. 实战案例二:表单自动填充

8.1 场景描述

自动填充表单数据。

8.2 实现代码

python 复制代码
def fill_form(data):
    """填充表单"""
    fill_scripts = []
    
    for field, value in data.items():
        script = f"""
        const field = document.querySelector('[name="{field}"], #{field}');
        if (field) {{
            if (field.type === 'checkbox' || field.type === 'radio') {{
                field.checked = {str(value).lower()};
            }} else if (field.tagName === 'SELECT') {{
                field.value = '{value}';
            }} else {{
                field.value = '{value}';
            }}
        }}
        """
        fill_scripts.append(script)
    
    canvas(
        action="eval",
        javaScript="\n".join(fill_scripts)
    )

# 使用示例
fill_form({
    "username": "admin",
    "email": "admin@example.com",
    "role": "admin",
    "active": True
})

9. 实战案例三:内容提取

9.1 场景描述

从 Canvas 中提取结构化数据。

9.2 实现代码

python 复制代码
def extract_table_data(table_selector):
    """提取表格数据"""
    return canvas(
        action="eval",
        javaScript=f"""
        const table = document.querySelector('{table_selector}');
        const rows = table.querySelectorAll('tr');
        const data = [];
        
        rows.forEach(row => {{
            const cells = row.querySelectorAll('td, th');
            const rowData = [];
            cells.forEach(cell => rowData.push(cell.textContent.trim()));
            data.push(rowData);
        }});
        
        JSON.stringify(data);
        """
    )

def extract_list_data(list_selector):
    """提取列表数据"""
    return canvas(
        action="eval",
        javaScript=f"""
        const items = document.querySelectorAll('{list_selector} > li');
        const data = [];
        
        items.forEach(item => {{
            data.push(item.textContent.trim());
        }});
        
        JSON.stringify(data);
        """
    )

10. 高级技巧

10.1 异步操作

python 复制代码
def async_operation():
    """异步操作"""
    return canvas(
        action="eval",
        javaScript="""
        (async () => {
            const response = await fetch('/api/data');
            const data = await response.json();
            return JSON.stringify(data);
        })()
        """
    )

10.2 等待元素

python 复制代码
def wait_for_element(selector, timeout=5000):
    """等待元素出现"""
    return canvas(
        action="eval",
        javaScript=f"""
        (async () => {{
            const start = Date.now();
            while (Date.now() - start < {timeout}) {{
                const element = document.querySelector('{selector}');
                if (element) return true;
                await new Promise(r => setTimeout(r, 100));
            }}
            return false;
        }})()
        """
    )

10.3 批量操作

python 复制代码
def batch_dom_operations(operations):
    """批量DOM操作"""
    scripts = []
    
    for op in operations:
        if op['type'] == 'setText':
            scripts.append(f"document.querySelector('{op['selector']}').textContent = '{op['value']}';")
        elif op['type'] == 'setHtml':
            scripts.append(f"document.querySelector('{op['selector']}').innerHTML = `{op['value']}`;")
        elif op['type'] == 'click':
            scripts.append(f"document.querySelector('{op['selector']}').click();")
    
    canvas(
        action="eval",
        javaScript="\n".join(scripts)
    )

11. 安全注意事项

11.1 安全风险

风险 说明 防护措施
XSS攻击 注入恶意脚本 转义用户输入
数据泄露 读取敏感数据 权限控制
无限循环 死循环脚本 超时限制
DOM破坏 破坏页面结构 操作验证

11.2 安全实践

python 复制代码
def safe_eval(script, user_input):
    """安全的执行"""
    # 转义用户输入
    escaped = user_input.replace("'", "\\'").replace('"', '\\"')
    
    # 使用参数化脚本
    safe_script = script.replace('{input}', escaped)
    
    return canvas(action="eval", javaScript=safe_script)

12. 总结

12.1 核心要点

要点 说明
数据获取 读取页面内容和数据
DOM操作 修改元素和结构
样式控制 动态修改样式
事件触发 触发页面事件

12.2 最佳实践

  • 转义用户输入
  • 使用参数化脚本
  • 添加错误处理
  • 限制执行时间

12.3 下一步

  • 第54篇:OpenClaw Canvas 截图:页面捕获与保存
  • 第55篇:OpenClaw Canvas A2UI:交互式界面开发

参考资料


相关推荐
雨落在了我的手上1 小时前
初识java(十五):字符串-String类
java·开发语言
zzx2006__1 小时前
JavaScript最终考核
开发语言·前端·javascript
努力努力再努力wz1 小时前
【Qt入门系列】:QLabel控件详解:从文本显示到图片展示,再到内容布局与伙伴机制
android·开发语言·数据结构·数据库·c++·qt·mysql
甄心爱学习2 小时前
【项目实训(个人10)】
开发语言·前端·javascript
触底反弹2 小时前
dom操作这篇文章就够了
javascript·面试
无糖可可果2 小时前
从"查字典"到"写 Prompt":奇妙学习之旅
javascript
云宝大王2 小时前
JavaScript 异步编程:从回调到探索 Promise的秘密
前端·javascript
右耳朵猫AI2 小时前
Java & JVM技术周刊 2026年第20周
java·开发语言·jvm
daols882 小时前
vxe-table 进阶:同时使用 formatter 与 cell-render 实现格式化与样式定制
前端·javascript·vue.js·vxe-table