Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >关于Browser use控制浏览器,核心代码之DOM树的构建以及DOM元素渲染

关于Browser use控制浏览器,核心代码之DOM树的构建以及DOM元素渲染

作者头像
用户11288949
发布于 2025-06-12 00:58:07
发布于 2025-06-12 00:58:07
9300
代码可运行
举报
文章被收录于专栏:学习学习
运行总次数:0
代码可运行

📚️1.前言

本期底层源码来自github开源项目,项目地址:

https://github.com/browser-use/browser-use

大家可以自己去看看,实验实验,具体步骤小编放置到上一期中咯,小编对于javaScript不是很精通,只是大致讲解一下每个代码的具体的作用

📚️2.DOM元素渲染

具体实现的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function highlightElement()

如下图所示:

这里包含了具体的文件路径,找不到可以在这里看看;

2.1元素高亮前提工作

代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function highlightElement(element, index, parentIframe = null) {
    pushTiming('highlighting');
    
    if (!element) return index;

    // Store overlays and the single label for updating
    const overlays = [];
    let label = null;
    let labelWidth = 20;
    let labelHeight = 16;
    let cleanupFn = null;

传递参数(元素,元素坐标,:可选参数,默认值 null,表示元素所在的 iframe(当前代码未使用)

接下来元素是否存在?存在就继续往下走,反之return

接下来就是覆盖层的高亮标签设置;

2.2高亮容器设置

代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
  // Create or get highlight container
  //这一段就是设置高亮容器,放置我们的高亮效果
  let container = document.getElementById(HIGHLIGHT_CONTAINER_ID);
  if (!container) {
    container = document.createElement("div");
    container.id = HIGHLIGHT_CONTAINER_ID;
    container.style.position = "fixed";
    container.style.pointerEvents = "none";
    container.style.top = "0";
    container.style.left = "0";
    container.style.width = "100%";
    container.style.height = "100%";
    container.style.zIndex = "2147483640";
    container.style.backgroundColor = 'transparent';
    document.body.appendChild(container);
  }

设置我们的容器,来创建高亮的效果

第一步:通过id获取已经存在的高亮容器 第二步:如果容器为空,那么就会进行创建新的容器 第三步:设置具体的内容,创建div元素作为容器 第四步:设置容器id 第五步:设置容器的样式,并添加容器到我们的页面中

2.3设置颜色

代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//根据位置的不同返回我们元素框对应的颜色
const colors = [
  "#FF0000",
  "#00FF00",
  "#0000FF",
  "#FFA500",
  "#800080",
  "#008080",
  "#FF69B4",
  "#4B0082",
  "#FF4500",
  "#2E8B57",
  "#DC143C",
  "#4682B4",
];
const colorIndex = index % colors.length;
const baseColor = colors[colorIndex];
const backgroundColor = baseColor + "1A"; // 10% opacity version of the color

设置颜色,获取对应颜色的下标位置,进行颜色的选取

2.4设置偏移量

代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Get iframe offset if necessary
let iframeOffset = { x: 0, y: 0 };
if (parentIframe) {
  const iframeRect = parentIframe.getBoundingClientRect(); // Keep getBoundingClientRect for iframe offset
  iframeOffset.x = iframeRect.left;
  iframeOffset.y = iframeRect.top;
}

设置偏移量

当元素位于 iframe 内部时,它的坐标是​ ​相对于 iframe 的左上角​​计算的。但是:

  1. 我们的高亮容器是直接放在主文档中的
  2. 高亮位置需要基于​​整个页面的坐标系

因此需要将 iframe 内部的坐标转换为全局坐标

2.5创建高亮覆盖层

代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 overlay.style.position = "fixed";
  overlay.style.border = `2px solid ${baseColor}`; // 使用基础颜色作为边框
  overlay.style.backgroundColor = backgroundColor; // 设置半透明背景色
  overlay.style.pointerEvents = "none"; // 禁止鼠标事件穿透
  overlay.style.boxSizing = "border-box"; // 确保边框不增加额外尺寸

负责为元素的每个可见矩形区域创建高亮覆盖层

紧接着设置高亮层的位置以及尺寸

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
overlay.style.top = `${top}px`;
overlay.style.left = `${left}px`;
overlay.style.width = `${rect.width}px`;
overlay.style.height = `${rect.height}px`;

然后,接下来就是添加到我们的文档片段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 // 6. 将高亮层添加到文档片段
  fragment.appendChild(overlay);

然后针对创建的矩形,创建序号标签:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
label = document.createElement("div");
label.className = "playwright-highlight-label";
label.style.position = "fixed";
label.style.background = baseColor;
label.style.color = "white";
label.style.padding = "1px 4px";
label.style.borderRadius = "4px";
label.style.fontSize = `${Math.min(12, Math.max(8, firstRect.height / 2))}px`;
label.textContent = index;
2.6总结

具体的思路就是:设置高亮覆盖层,高亮元素的标签,然后搞定高亮容器,设置高亮效果,如果是在iframe中,需要修改偏移量

创建高亮覆盖层,负责为元素的每个可见矩形区域创建高亮覆盖层(同样的样式,位置尺寸)

📚️3.智能交互检测

3.1初始工作

主要的核心逻辑:

部分逻辑主要在 isInteractiveElement() 函数中实现,并辅以 isElementDistinctInteraction() 和 isHeuristicallyInteractive() 进行更精细的判断。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Cache the tagName and style lookups
const tagName = element.tagName.toLowerCase();
const style = getCachedComputedStyle(element);

缓存标签名忽略大小写,以及缓存计算样式

  • 首次调用时计算并存储样式到 WeakMap。
  • 后续调用直接返回缓存结果,避免重复计算

目的:缓存后,同一元素的样式只需计算一次,提升性能

3.2定义可交互与不可交互
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const interactiveCursors = new Set([
  'pointer',    // 链接/可点击元素
  'move',       // 可移动元素
  'text',       // 文本选择
  'grab',       // 可拖拽元素
  'grabbing',   // 正在拖拽中
  'cell',       // 表格单元格选择
  'copy',       // 复制操作
  'alias',      // 创建别名
  'all-scroll', // 可滚动内容
  'col-resize', // 列宽调整
  'context-menu', // 上下文菜单可用
  'crosshair',  // 精确选择(十字光标)
  'e-resize',   // 向东调整(右)
  'ew-resize',  // 东西双向调整(水平)
  'help',       // 帮助可用
  'n-resize',   // 向北调整(上)
  'ne-resize',  // 东北向调整(右上)
  'nesw-resize', // 东北-西南双向调整(对角)
  'ns-resize',  // 南北双向调整(垂直)
  'nw-resize',  // 西北向调整(左上)
  'nwse-resize', // 西北-东南双向调整(对角)
  'row-resize', // 行高调整
  's-resize',   // 向南调整(下)
  'se-resize',  // 东南向调整(右下)
  'sw-resize',  // 西南向调整(左下)
  'vertical-text', // 垂直文本选择
  'w-resize',   // 向西调整(左)
  'zoom-in',    // 放大
  'zoom-out'    // 缩小
]);

判断是否可以进行交互的操作;

主要检查元素的style.cursor是否属于上述的数组中,是那么说明可交互;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 定义非交互式光标
const nonInteractiveCursors = new Set([
  'not-allowed', // 操作禁止
  'no-drop',     // 禁止拖放
  'wait',        // 处理中(如加载)
  'progress',    // 操作进行中
  'initial',     // 初始值(默认状态)
  'inherit'      // 继承父元素值
  // 注释说明:
  // 以下光标未包含在内,但可能是非交互的:
  // 'none',     // 无光标
  // 'default',  // 默认箭头
  // 'auto',     // 浏览器自动决定
]);

style.cursor 是DOM元素的CSS属性,用于​​控制鼠标悬停时的光标样式​​。它直接对应CSS的 cursor 属性

cursor:光标样式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function doesElementHaveInteractivePointer(element) {
  if (element.tagName.toLowerCase() === "html") return false;

  if (interactiveCursors.has(style.cursor)) return true;

  return false;
}

这个函数用于​​通过光标样式(cursor)快速判断元素是否具有交互性​​,是 isInteractiveElement 的辅助函数

标签是文档根节点,本身无交互意义,直接跳过检测

并且判断元素的光标样式是否属于上述可交互式光标样式集合

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let isInteractiveCursor = doesElementHaveInteractivePointer(element);

是否是可以交互的,是那么就是true

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const interactiveElements = new Set([
  "a",          // 链接(超链接)
  "button",     // 按钮
  "input",      // 所有输入类型(文本、复选框、单选框等)
  "select",     // 下拉菜单
  "textarea",   // 多行文本输入框
  "details",    // 可折叠/展开的详情块
  "summary",    // 详情块的点击标题部分
  "label",      // 表单标签(通常可点击)
  "option",     // 下拉菜单选项
  "optgroup",   // 下拉菜单选项分组
  "fieldset",   // 表单字段分组(通常包含图例)
  "legend",     // 字段分组的标题
]);

所有​​原生支持交互​​的HTML元素标签名(小写)。这些元素默认具有交互行为(如点击、输入、展开等)

并且这里包保存使用的Set进行存储,可以保证唯一性,并且这里的查找的时间复杂度为1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const explicitDisableTags = new Set([
  'disabled',    // 标准禁用属性(禁用按钮/输入框等)
  // 'aria-disabled',      // ARIA禁用状态(已注释,未启用)
  'readonly',    // 只读属性(禁止输入但允许聚焦)
  // 'aria-readonly',     // ARIA只读状态(已注释)
  // 其他被注释掉的属性:
  // 'aria-hidden',       // 对无障碍隐藏
  // 'hidden',           // HTML全局隐藏属性
  // 'inert',            // 惰性属性(禁止交互)
  // 'tabindex="-1"',    // 从Tab键顺序移除
]);

显示禁用属性集合

定义所有​​显式禁用交互​​的HTML属性。如果元素具有这些属性,即使它是交互式元素(如 ),也应视为​​不可交互​​。避免误判

3.3判断元素是否可交互(核心)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (interactiveElements.has(tagName)) {
  // Check for non-interactive cursor
  if (nonInteractiveCursors.has(style.cursor)) {
    return false;
  }

是否在主判断方法中是否是可交互元素,进入后交给

nonInteractiveCursors再次进行判断是否是可交互

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (const disableTag of explicitDisableTags) {  // 遍历所有禁用属性
  if (
    element.hasAttribute(disableTag) ||          // 属性存在(如 disabled)
    element.getAttribute(disableTag) === 'true' || // 属性值为'true'(如 aria-disabled="true")
    element.getAttribute(disableTag) === ''      // 属性值为空(如 disabled="")
  ) {
    return false; // 命中任意条件则判定为不可交互
  }
}

检查元素是否被显式禁用​​,如果满足禁用条件,则判定该元素​​不可交互​​。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 检查元素的 disabled 属性,DOM属性
if (element.disabled) {
  return false; // 如果禁用,返回不可交互
}

// 检查表单元素的只读属性
if (element.readOnly) {
  return false; // 如果只读,返回不可交互
}

// 检查 inert 属性(HTML5新增的惰性属性)
if (element.inert) {
  return false; // 如果惰性,返回不可交互
}

检查元素的禁用状态​​,通过直接访问DOM元素的属性来判断其是否被禁用或只读

重重判断后,就是一个可交互的元素

3.4检查ARIA角色
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 获取元素的 role 和 aria-role 属性值
const role = element.getAttribute("role");
const ariaRole = element.getAttribute("aria-role");

// 定义交互式ARIA角色的集合
const interactiveRoles = new Set([
  'button',           // 按钮
  'menuitemradio',    // 单选菜单项
  'menuitemcheckbox', // 复选菜单项
  'radio',            // 单选按钮
  'checkbox',         // 复选框
  'tab',              // 标签页
  'switch',           // 切换开关
  'slider',           // 滑块
  'spinbutton',       // 数字调节按钮
  'combobox',         // 组合框(下拉+输入)
  'searchbox',        // 搜索框
  'textbox',          // 文本框
  'option',           // 下拉选项
  'scrollbar'         // 滚动条
]);

// 检查角色是否在交互式集合中
if (interactiveRoles.has(role) || interactiveRoles.has(ariaRole)) {
  return true; // 判定为可交互元素
}

传统检测只能识别原生交互元素

无法识别自定义组件的交互性(ARIA角色作用自定义组件,定义缺失,组件状态)

3.5检查是否可编辑
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Check for contenteditable attribute
if (element.getAttribute("contenteditable") === "true" || element.isContentEditable) {
  return true;
}

作用,检查元素是否可以进行编辑操作(获取JS属性)

例如我们的富文本编辑,评论输入框都被识别为可以交互的元素

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Added enhancement to capture dropdown interactive elements
if (element.classList && (
  element.classList.contains("button") ||
  element.classList.contains('dropdown-toggle') ||
  element.getAttribute('data-index') ||
  element.getAttribute('data-toggle') === 'dropdown' ||
  element.getAttribute('aria-haspopup') === 'true'
)) {
  return true;
}

条件

说明

class="button"

按钮样式类

class="dropdown-toggle"

下拉菜单触发器

data-index

列表项索引

data-toggle="dropdown"

下拉菜单标识

aria-haspopup="true"

有弹出菜单

3.6监听事件
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
  if (typeof getEventListeners === 'function') {
    const listeners = getEventListeners(element);
    const mouseEvents = ['click', 'mousedown', 'mouseup', 'dblclick'];
    for (const eventType of mouseEvents) {
      if (listeners[eventType] && listeners[eventType].length > 0) {
        return true;
      }
    }
  }
} catch (e) {
  // 回退方案:检查内联事件属性
  const commonMouseAttrs = ['onclick', 'onmousedown', 'onmouseup', 'ondblclick'];
  for (const attr of commonMouseAttrs) {
    if (element.hasAttribute(attr) || typeof element[attr] === 'function') {
      return true;
    }
  }
}

使用Chrome API getEventListeners()

  • 检查是否存在以下事件的监听器:
    • click(点击)
    • mousedown(鼠标按下)
    • mouseup(鼠标释放)
    • dblclick(双击)

当 getEventListeners 不可用或出错时(如非Chrome环境:判断是否是具有点击...的属性,或者这个属性是否是一个方法

作用:覆盖通过JavaScript动态添加的交互功能

以及跨浏览器的兼容方案

📚️4.总结

第一初始化开始工作:设置缓存计算方式,以及缓存名,然后设置可交互与不可交互集合(可交互光标样式,不可交互光标样式,原生支持交互,以及显示禁用和交互​​

核心:是可交互式光标样式直接返回true,

是原生支持交互 -》是不是不可交互光标样式 -》是不是显示禁用的元素 - 》 通过直接访问DOM元素的属性来判断其是否被禁用或只读;一个不通过返回false,相反都满足就是true

核心判断路程图:

整体的思路:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
低代码时代的开发加速器
“写代码”这件事变得不再是前端工程师的专属任务。尤其是在低代码平台的推动下,越来越多非技术背景的业务人员也能直接参与产品界面搭建。而其中最具代表性的生产力工具,非
不惑
2025/06/27
980
低代码时代的开发加速器
Hugo博客目录放在侧边 | PaperMod主题
官方当前版本的PaperMod目录是放在顶部的,之后的版本也不知道会不会放在侧边,我个人觉得目录放在顶部不方便展示阅读进度,如果文章很长,还得翻到顶部,极其不方便。
素履coder
2022/02/17
6140
Hugo博客目录放在侧边 | PaperMod主题
实战中学习浏览器工作原理 — 排版与渲染
上一周我们完成了 CSS 的规则计算,其实就是计算了每个元素匹配中了那些 CSS 规则,并且把这些规则挂载到元素的 ComputedStyle 上面。
三钻
2020/10/29
9090
实战中学习浏览器工作原理 — 排版与渲染
浏览器渲染原理
腾讯IVWEB团队
2017/05/05
2.8K0
一些DevTools的小技巧-让你不止会console.log()
在开发过程中,你可能会经常用到控制台命令console.log(),但是,其实除了这个命令外,还有一些其他的命令和技巧可供我们使用,让我们看看它们究竟是什么,会不会为你的调试能力带来一点新的启发。
葡萄城控件
2021/03/30
1.3K0
一些DevTools的小技巧-让你不止会console.log()
分享100 个鲜为人知的 CSS 技巧
金三银四找工作的旺季来了,在过去的一段时间里,我花了很多时间将之前的一些基础知识做了整理,希望这些内容能够帮助你在面试的时候,稍微顺利一些。因此,我将整理好的这 100 个 CSS 知识技巧分享给你,希望你会觉得太对你有用。
前端达人
2024/03/11
7730
分享100 个鲜为人知的 CSS 技巧
Safari 18.0 WebKit 新特性介绍
干扰控制功能允许你在浏览网页时隐藏干扰项,例如登录横幅、Cookie 偏好弹窗、新闻通讯注册覆盖层等。该功能适用于 iOS 18、iPadOS 18 和 macOS Sequoia 上的 Safari。
ACK
2024/09/26
6680
Safari 18.0 WebKit 新特性介绍
VisualDrag低代码拖拽模板
因此得研究实现一个拖拽生成低代码平台,通过查询了各种资料,找到了以下比较合适的开源的低代码平台:
不愿意做鱼的小鲸鱼
2022/11/22
1.8K0
VisualDrag低代码拖拽模板
浏览器渲染流程
页面的设计与实现之后,前端工程师就需要关注性能优化了。其中浏览器渲染机制是前端性能优化的关键,弄浏览器在背后做了什么,才能在明白如何优化。
河马嘴不大
2022/12/24
5370
浏览器渲染流程
vuejs之结合使用vue+element-ui搭建后台管理页面
进入到该项目目录,输入:npm install --save element-ui
西西嘛呦
2020/08/26
1.2K0
从零开始学 Web 之 移动Web(九)微金所案例
相关源代码已放置github:https://github.com/Daotin/Web/blob/master/Code/src/11/wjs.zip
Daotin
2018/08/31
1.6K0
从零开始学 Web 之 移动Web(九)微金所案例
ZeroClipboard实现多个浏览器兼容的复制文本到剪贴板的功能
ZeroClipboard实现多个浏览器兼容的复制文本到剪贴板的功能 本人在项目中使用的js版本。为了方便大家下载。直接粘贴代码给大家看。版本是1.2.0 /*! * ZeroClipboard * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. * Copyright (c
小帅丶
2018/03/12
1.5K0
三种插件开发模式,带你玩废tinymce
前言 TinyMCE是一款开源、易用、UI时新、所见即所得的富文本编辑器。是富文本领域中的佼佼者。整体设计和模式,都是非常不错的。其提供的API 极其丰富和强大,简单点 就是专业牛👍,可供广大开发者用户,方便快捷的自行进行扩展或根据实际业务需求进行二次开发。下面我将分享3种方式,带你玩废 tinymce,适合接触过 tinymce 的 小伙伴,如果没有接触过 就随便看看,收藏也不吃亏,保不齐,后面用得上,知道可以这样玩就好了😎 利用tinymce官方提供的 UI 组件扩展 tinymce 官方提供还算多的
Fivecc
2022/11/21
5.5K1
三种插件开发模式,带你玩废tinymce
分享15个高级前端开发小技巧
我们将提供真实世界的示例,并将它们与旧的基于 JavaScript 的方法进行比较,展示现代 Web 技术的力量。
前端达人
2024/03/25
6190
分享15个高级前端开发小技巧
CSS 入门指南:轻松掌握网页布局与样式设计的艺术
CSS(Cascading Style Sheets,层叠样式表)是一种用于为HTML元素添加样式的语言。CSS决定了页面上元素的外观:颜色、字体、布局等。
方才编程_公众号同名
2024/09/24
6450
CSS 入门指南:轻松掌握网页布局与样式设计的艺术
BootStrap应用开发学习入门
[TOC] 0x00 前言简介 什么是BootStrap? 答:Bootstrap是Twitter 的 Mark Otto 和 Jacob Thornton 开发的推出的一个用于前端开发的开源工具包产
全栈工程师修炼指南
2020/10/23
18.6K0
BootStrap应用开发学习入门
让我们来构建一个浏览器引擎吧
前端有一个经典的面试题:在浏览器地址栏输入URL到最终呈现出页面,中间发生了什么?
五月君
2021/04/22
1.4K0
浏览器页面渲染全解析过程优化及实战指南详解
将HTML、CSS和JavaScript转换为屏幕上的像素,实现用户可交互的视觉界面。
小焱
2025/05/22
2600
浏览器页面渲染全解析过程优化及实战指南详解
Carousel 走马灯开发
有一阵子,我一直在思考一个问题:如何在有限的页面空间内,优雅地展示大量相同结构的信息,比如产品广告、用户评价、合作伙伴 logo 或活动推广图。这类信息往往数量不少、视觉要素集中,但在一个主页面中要找到足够空间“塞进去”并保持整体美感,其实非常棘手。于是我把注意力转向了一个经典但又容易被忽略的组件——Carousel,也叫轮播图、走马灯。
繁依Fanyi
2025/05/10
1550
浏览器渲染之回流重绘
回流和重绘是前端开发的高频词汇之一,你可以在各种面经,性能优化相关文章可以看到,但是很多都是草草带过。本文带你从浏览器渲染流程中了解回流与重绘的原理。
政采云前端团队
2021/09/30
1.8K0
浏览器渲染之回流重绘
相关推荐
低代码时代的开发加速器
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验