跳转到正文
返回门户

运行环境:background(后台应用) ​

// @match background 是 @match 的保留字:声明后应用不注入任何网页,而是常驻运行在扩展的共享后台沙箱里——适合轮询、定时任务、给终端提供 CLI 命令等不依赖页面的能力。

声明方式executionTarget行为
只有 URL 型 @match(或不写)page现状:注入命中的网页
只有 // @match backgroundbackground只在后台沙箱运行一个常驻实例,不注入任何网页
两者都有both双实例并行:后台实例常驻 + 网页实例按匹配规则注入,互不影响

background ≠ 扩展的 background service worker

后台应用运行在 offscreen 文档内的 sandbox iframe(opaque origin)里,没有任何 chrome.* 扩展 API(不能调 chrome.tabs、chrome.storage 等)。它的能力集与页面应用的 LFRuntime 内置库一致(减去页面 DOM/location 语义),不因换环境获得额外权限。

环境差异 ​

  • 无页面 DOM:document 存在但不可见,querySelector 常返回 null;不要把 DOM 挂载当作交付 UI,LFDOM 的页面能力不可用
  • opaque origin:直接访问 localStorage / IndexedDB 会抛 SecurityError;持久化请用 LFStore(走扩展存储,跨环境共享同一份数据)
  • 生命周期:随浏览器启动常驻,不依赖任何标签页;扩展重启后自动恢复运行
  • @include / @exclude / @exclude-match / @intercept / @run-at 只约束 page 分支,对后台分支无效

内置库可用性 ​

后台沙箱没有自己的 chrome.*,但它拿到一套最小 Port shim:所有出站消息经 offscreen broker 代发、由 background 逐条鉴权,因此内置库的既有通道代码原样工作。换句话说,除了真正需要页面/标签页的能力,其余内置库在后台分支都可用。

能力后台分支说明
LFStore(含 watch)可用与页面分支共享同一命名空间,是 both 应用两个分支之间同步状态的推荐通道
LFModel(含 chatStream)可用流式走 Port,与页面分支一致
LFUser可用登录态与用户信息读取正常
LFHttp可用request / stream / websocket / client.* / server.* 均正常;page.* 需要标签页,返回 TAB_UNAVAILABLE
LFRuntime.cli可用注册的命令出现在 plugincmd list --source background,不依赖任何标签页
LFRuntime.sidebar部分可用getState / getAgentState / input / setModel / close 正常;open() 需要用户手势和调用方窗口,后台没有,会被拒绝
LFRuntime 基础能力可用sleep / registerCleanup / print 等
工作区桥(readFile / writeFile / execTerminalCommand)仅经租约与页面应用相同:应用源码直接调用会编译报错,须在 LFRuntime.cli.register({ workspace: [...] }) 的 handler 里用 ctx.workspace.*,租约只在本次 plugincmd call 期间有效。run --target background 临时脚本可直接调用
LFDOM不可用库能加载,但没有页面 DOM 可读:查询返回空结果而不是抛错;click / fill / waitFor 等依赖可见性的操作会超时(提示 no web page in the background environment),waitForResponse / waitForRequest 以 LF_NETWORK_NO_TAB 拒绝
LFNetwork不可用要把 CDP 附到标签页上,start() 直接抛 LF_NETWORK_NO_TAB
LFRuntime.input不可用只驱动脚本自己所在的标签页,后台没有,直接抛 RUNTIME_INPUT_NO_TAB
LFRuntime.runInMainWorld不可用没有页面可注入,调用会 reject MAIN_WORLD_NO_TAB

websocket 用默认 mode

后台沙箱是 opaque origin,CSP 会拦掉原生 WebSocket,mode: "direct" 连不上。保持默认的 mode: "auto" 即可——它会自动选 background 模式,把 socket 开在扩展后台再把事件回投过来。

设计上的推论:后台分支适合承载数据、轮询、CLI 与对外接口,凡是要碰页面的都放到 page 分支去;both 应用两个分支之间用 LFStore(配合 LFStore.watch)而不是共享内存来传递状态。

判断当前执行环境 ​

both 应用的后台分支与页面分支执行同一份代码,用 LFRuntime.executionEnvironment 在入口分流即可——它在生成产物时就烘焙成常量,不做运行时探测,因此可以安全地写在最顶层。不要靠嗅探 chrome.* 是否缺失、document.querySelector 是否恒返回 null、或捕获 localStorage 的 SecurityError 来推断环境(这些都是内部实现细节)。

取值含义
page注入到网页:已启用/已上架的 page 分支、liveDebug、run --tab。有页面 DOM 与 location
background共享 offscreen 沙箱:后台应用分支、run --target background。可用 LFRuntime.isBackground 快捷判断
editor-sandbox不带 --tab 的 run:跑在编辑器页的隐藏 iframe,没有任何目标网页

两个分支之间共享状态请走 LFStore + LFStore.watch(后台写、页面订阅渲染)。注意 LFRuntime.liveDebug 是「开发期 / 发布态」的区分,与执行环境无关,两者不要混用。

示例:both 应用的双分支

js
// ==UserScript==
// @name Rate Panel
// @description 后台轮询 + 页面展示,同一份代码两个分支
// @match background
// @match https://example.com/*
// @require store
// @require http
// ==/UserScript==

// executionEnvironment 在生成产物时就已烘焙成常量,可直接在入口分流
if (LFRuntime.isBackground) {
  // 后台分支:只做取数与写入,没有 DOM 交付面
  const pull = async () => {
    const data = await LFHttp.json("https://open.er-api.com/v6/latest/USD");
    await LFStore.set("usd-cny", { rate: data.rates.CNY, at: Date.now() });
  };
  pull();
  LFRuntime.interval(pull, 10 * 60 * 1000);
} else if (LFRuntime.executionEnvironment === "page") {
  // 页面分支:只渲染,数据经 LFStore.watch 与后台分支保持同步
  LFRuntime.mountHot("rate-panel", (hot) => {
    const el = LFRuntime.trackNode(document.createElement("div"));
    document.body.appendChild(el);
    const render = (value) => {
      el.textContent = value ? `1 USD = ${value.rate} CNY` : "等待后台取数…";
    };
    const unwatch = LFStore.watch("usd-cny", ({ value }) => render(value), { immediate: true });
    hot.onDispose(unwatch);
  });
}

开发与测试 ​

  • run --target background <入口> 在与正式后台应用完全相同的沙箱环境里执行测试脚本(与 --tab/--focus/--record/--world user-script 互斥)
  • liveDebug on 对 background / both 应用同样可用:后台分支热更新按「整实例替换」执行;both 应用没有匹配网页时后台分支照常运行,页面分支进入 waiting-for-target
  • plugincmd 默认优先调用 background 实例注册的命令,可用 --source background|page 显式指定
  • app list 的 TARGET 列与应用管理页的「后台应用 / 后台+网页」标签展示运行环境。网页对话模式的「本页应用」弹层中,后台应用与「页面分支未命中当前页」的 both 应用恒显示在「后台运行」分组(both 应用带徽标:命中本页时标「后台+本页」,未命中时标「后台+网页」),随时可在此启停

示例:常驻价格监控 ​

main.js

js
// ==UserScript==
// @name Price Watcher
// @description 常驻后台轮询价格,无需打开任何页面
// @match background
// @require store
// @require http
// ==/UserScript==

async function checkOnce() {
  const data = await LFHttp.json("https://open.er-api.com/v6/latest/USD");
  await LFStore.set("usd-cny", { rate: data.rates.CNY, at: Date.now() });
}

checkOnce();
LFRuntime.interval(checkOnce, 10 * 60 * 1000);

LFRuntime.cli.register({
  name: "rate",
  brief: "查看最新缓存汇率",
  handler: async () => {
    const cached = await LFStore.get("usd-cny", null);
    return cached ? `1 USD = ${cached.rate} CNY` : "尚未获取到汇率";
  },
});