首页 / 浏览器扩展开发入门教程 / 右键菜单 menus

浏览器扩展开发入门教程

右键菜单 menus

本教程共 56 篇 · 第 28 篇 · 更新于 2026-08-13 · 约 8 分钟阅读

contextMenus右键菜单上下文类型onClicked菜单层级复选菜单

本节目标:学完能在右键菜单里加自定义项,并且根据点击的网页内容做出不同反应。

28-1 右键菜单 API 概览

contextMenus 让你往浏览器右键菜单塞自己的菜单项。用户右键时就能看到、点它。

它运行在后台服务工作者(Service Worker)里,因为菜单的点击要在全局层面监听。

用这个 API 前,必须在清单里申请 contextMenus 权限。

{
  "manifest_version": 3,
  "permissions": ["contextMenus"]
}
Note

右键菜单不需要 host 权限,它操作的是浏览器菜单本身,不是某个具体网页。

很多“划词翻译”“图片下载”类扩展,都是靠右键菜单把功能送到用户手边。

它和 popup、omnibox 一样,都是“入口型”组件:不抢界面,用户需要时才出现。区别是右键菜单绑定在用户右键的具体内容上,比如一段选中的文字、一张图片、一个链接。

28-2 声明 contextMenus 权限

权限写在 permissions 数组里,值就是字符串 “contextMenus”。

它声明后即可使用,通常不会在安装时制造明显的警告横幅,不需要 optional 那套流程。

只要声明了,后台脚本就能调用 chrome.contextMenus 下的一堆方法。

你也可以在清单里顺手写好菜单的图标,让它在菜单里更显眼,不过图标不是必须的。

Tip

contextMenus 是常见权限,申请时用户不会觉得突兀,但清单里写错拼写了就完全不生效,务必核对拼写。

有一点容易混淆:权限名是 contextMenus(带 s),而 API 命名空间也是 chrome.contextMenus。两者一致,但别和别处的大小写搞混。

28-3 创建菜单项 create

创建菜单靠 chrome.contextMenus.create,传一个配置对象进去。

chrome.contextMenus.create({
  id: "saveLink",
  title: "保存此链接",
  contexts: ["link"]
});

id 是菜单项的唯一标识,点击时靠它来区分是哪个项被点了。

title 就是用户看到的文字。contexts 决定这个项在什么场景下出现。

建议把创建逻辑放在 runtime.onInstalled 里,避免每次后台唤醒都重复建。

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: "saveLink",
    title: "保存此链接",
    contexts: ["link"]
  });
});
Tip

重复 create 同 id 会报错。稳妥做法:先 removeAll 再 create,保证干净。

create 方法是有返回值的(新建项的 id),但在 MV3 里多数情况下你只需要用自己指定的 id 即可,不必依赖返回值。

28-4 上下文类型 contexts

contexts 决定菜单项在哪种右键场景出现,可以传一个数组包含多种。

常用的有:page(网页空白)、selection(选中文字)、link(链接)、image(图片)。

还有 editable(输入框)、frame(框架)、video、audio 等,按场景挑。

chrome.contextMenus.create({
  id: "searchText",
  title: "搜索:%s",
  contexts: ["selection"]
});

注意 title 里的 %s 会被自动替换成用户选中的文字,很适合搜索类功能。

Note

contexts 不填时默认是 ['page'],也就是只在网页空白处右键时出现。想要更多场景,就显式列出来。

如果你只想要在图片和链接上出现,就写成 [“image”, “link”]。场景收得越窄,菜单出现得越“刚好”,用户越不觉得烦。

还有个特殊场景叫 “action”,它表示在工具栏图标的右键菜单里出现,而不是网页右键。这个用得少,但如果你要扩展工具栏图标的右键能力,可以了解一下。

28-5 菜单层级与类型

想做多级菜单,用 parentId 把子项挂到某个父项下面。

chrome.contextMenus.create({ id: "parent", title: "我的工具", contexts: ["page"] });
chrome.contextMenus.create({ id: "child", parentId: "parent", title: "子功能", contexts: ["page"] });

type 字段控制项的形态,默认是 normal(普通点击项)。

还可以是 checkbox(复选)、radio(单选)、separator(分隔线)。

chrome.contextMenus.create({
  id: "toggle",
  title: "自动运行",
  type: "checkbox",
  checked: false,
  contexts: ["page"]
});

checkbox 和 radio 点击后会有 checked 状态,适合做开关式设置。

Note

separator 是纯分隔线,不需要 title 和 id,用来把菜单分组更清晰。

父项必须先于子项创建,否则子项会找不到挂载点。如果你在 onInstalled 里批量创建,注意顺序:先建父,再建子。

radio 类型适合“多选一”的场景,比如选择默认搜索引擎;checkbox 适合“开关”场景,比如是否启用某功能。两者点击后都会在 onClicked 的 info 里带上最新的 checked 值。

28-6 处理点击 onClicked

用户点了你的菜单项,onClicked 就会触发,回调拿到 info 和 tab。

chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === "saveLink") {
    console.log("链接地址:", info.linkUrl);
    console.log("所在标签页:", tab.id);
  }
});

info 里装着你最关心的内容:选中文字、链接、图片地址、页面地址都在。

比如 selectionText 是选中的文字,linkUrl 是右键链接的 href,srcUrl 是图片源。

chrome.contextMenus.onClicked.addListener((info) => {
  if (info.menuItemId === "searchText") {
    const q = info.selectionText;
    console.log("要搜索:", q);
  }
});
Tip

checkbox 或 radio 点击后,info.checked 会是新的勾选状态,直接读它就能更新设置。

如果菜单项有父子层级,点击子项时 info.parentMenuItemId 也会带上,方便你在多层菜单里做更精细的判断。

onClicked 监听必须注册在后台服务工作者里。它和创建菜单一样,都属于“全局行为”,不能放在 popup 或选项页中。

28-7 动态管理菜单

菜单不是建了就定死,你可以随时增删改。

remove(id) 删单个,removeAll() 清空全部,update(id, props) 改属性。

chrome.contextMenus.removeAll(() => {
  chrome.contextMenus.create({ id: "newItem", title: "新项", contexts: ["page"] });
});

这种“先清后建”的写法,在 onInstalled 里特别稳,能避免重复创建报错。

如果你要根据页面状态显示隐藏某项,用 update 改它的 enabled 或 visible 即可。

chrome.contextMenus.update("saveLink", { enabled: false });

update 同样接受回调,方便你在改完之后接着做点事。注意 update 和 remove 都是异步的,别假设它们立刻生效。

Note

removeAll 会清空当前扩展创建的所有菜单项,不会动到其他扩展的菜单,所以放心用。

28-8 注意事项与调试

菜单的创建必须在后台脚本里执行,popup 或选项页里建是不生效的。

菜单在 onInstalled 里创建一次即可,浏览器会持久保留,后台休眠唤醒不影响它。

改了菜单逻辑后,去扩展管理页点“重新加载”,右键才能看到新菜单。

Tip

调试时如果菜单不出现,先看权限声明对没对,再看 create 是不是放在了 onInstalled 里。

真正要防的是“重复创建”:如果你把 create 无条件写在脚本顶层,每次唤醒都会重新执行一遍,同一个 id 会被反复创建而报错。把 create 放进 onInstalled,或做成“先 removeAll 再 create”,才是稳妥做法。

另外,菜单点击的日志在后台服务工作者的控制台里,不在网页控制台。调试时记得打开的是后台的 DevTools,而不是右键页面的检查面板。

28-9 小结

右键菜单的套路可以浓缩成四步:申请权限、在 onInstalled 里 create、用 contexts 限定场景、在 onClicked 里响应。

最容易出错的地方,是“菜单不显示”。八成是因为没把 create 放进 onInstalled,或者权限忘了声明。

Tip

调试菜单时,先确认扩展已重新加载,再右键目标元素(比如一段选中文字),看对应 contexts 是否匹配。

如果你要做可勾选的设置,checkbox 和 radio 比普通项更合适,点击后直接读 info.checked 即可。

菜单不是越多越好。场景收得越窄,用户看到你项的时候越“刚好需要”,体验反而更好。

28-10 常用 info 字段清单

onClicked 的 info 对象里,不同场景能拿到不同字段。提前知道有哪些,写逻辑时才不会抓瞎。

selectionText 是选中的文字;linkUrl 是右键链接的地址;srcUrl 是图片或媒体源的地址。

pageUrl 是当前页面地址,frameUrl 是框架地址,editable 表示右键是否落在输入框里。

Tip

如果某项始终拿不到(比如 linkUrl 是 undefined),多半是 contexts 没包含对应场景,先回去核对 contexts。

menuItemId 是最常用的判断依据。父菜单点下去时,parentMenuItemId 也能帮你区分层级。

checkbox 和 radio 类型的项,点击后 info.checked 会是新的勾选状态,直接读它来更新你的设置即可。

需要提醒:有些字段只在特定 contexts 下才有值。比如 linkUrl 只在右键链接时存在,srcUrl 只在右键图片/媒体时存在。写逻辑时要做“是否存在”的判断,别直接访问不存在的字段。

28-11 常见误区

最常见的问题是“菜单根本不显示”。先排查两件事:权限里有没有声明 contextMenus,create 是不是放在了后台脚本里。

如果这两项都对,再看 contexts 是否匹配你右键的位置。在图片上右键,菜单项却只声明了 [“link”],自然不会出现。

Tip

改了任何菜单逻辑后,记得在扩展管理页点“重新加载”,让新代码生效。

另一个坑是重复 create 同 id。把 create 无条件写在顶层,每次脚本执行都会重复创建而报错;放进 onInstalled 或先 removeAll 再 create,能避免重复报错。

如果你用 parentId 做子菜单,父项必须先于子项创建,否则子项会找不到挂载点而失败。