Chrome 扩展crx开发
Chrome扩展提供的入口
- 左键 crx,popup
- 右键 crx,homelink + option
- 右键上下文菜单
Chrome扩展的文件结构
Chrome扩展就是一个文件夹下包括一堆符合规范的文件。首先是清单文件manifest.json
,指定了该扩展的整体布局和结构。实例:
{
// 清单文件的版本,这个必须写,而且必须是2
"manifest_version": 2,
// 插件的名称
"name": "leocrx_demo",
// 插件的版本
"version": "0.1",
// 插件描述
"description": "简单的Chrome扩展demo",
// 图标,一般偷懒全部用一个尺寸的也没问题
"icons":
{
"16": "img/icon.png",
"48": "img/icon.png",
"128": "img/icon.png"
},
// 会一直常驻的后台JS或后台页面
"background":
{
// 2种指定方式,如果指定JS,那么会自动生成一个背景页
"page": "background.html"
//"scripts": ["js/background.js"]
},
// 浏览器右上角图标设置,browser_action、page_action、app必须三选一
"browser_action":
{
"default_icon": "img/icon.png",
// 图标悬停时的标题,可选
"default_title": "this is browser_action.default_title",
"default_popup": "popup.html",
"badge":"这是badge"
},
// 当某些特定页面打开才显示的图标
/*"page_action":
{
"default_icon": "img/icon.png",
"default_title": "我是pageAction",
"default_popup": "page_action_popup.html"
},*/
// 需要直接注入页面的JS
"content_scripts":
[
{
//"matches": ["http://*/*", "https://*/*"],
// "<all_urls>" 表示匹配所有地址
"matches": ["<all_urls>"],
// 多个JS按顺序注入
"js": ["js/hello_content.js"],
// JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式
//"css": ["css/custom.css"],
// 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
"run_at": "document_start"
}//,
// 这里仅仅是为了演示content-script可以配置多个规则
//{
// "matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
// "js": ["js/show-image-content-size.js"]
//}
],
// 权限申请
"permissions":
[
"nativeMessaging",
"contextMenus", // 右键菜单
"tabs", // 标签
"notifications", // 通知
"webRequest", // web请求
"webRequestBlocking",
"storage", // 插件本地存储
"http://*/*", // 可以通过executeScript或者insertCSS访问的网站
"https://*/*" // 可以通过executeScript或者insertCSS访问的网站
],
// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
"web_accessible_resources": ["js/inject.js"],
// 插件主页,这个很重要,不要浪费了这个免费广告位
"homepage_url": "https://www.baidu.com?homepage_url",
// 覆盖浏览器默认页面
//"chrome_url_overrides":
//{
// // 覆盖浏览器默认的新标签页
// "newtab": "newtab.html"
//},
// Chrome40以前的插件配置页写法
"options_page": "options.html",
// Chrome40以后的插件配置页写法,如果2个都写,新版Chrome只认后面这一个
"options_ui":
{
"page": "options.html",
// 添加一些默认的样式,推荐使用
"chrome_style": true
},
// 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字
"omnibox": { "keyword" : "go" },
// 默认语言
//"default_locale": "zh_CN",
// devtools页面入口,注意只能指向一个HTML文件,不能是JS文件
"devtools_page": "devtools.html"
}
content js
manifest.json中的content script可以配置run_at为document_start表示DOM加载之前执行,或者document_end表示页面DOM加载结束后执行,或者document_idle表示页面空闲时加载。content script可以理解为向DOM注入js或者css的入口准备。
popup js
popup js是指在popup页面中加载执行的js,逻辑上这个可以看做chrome://crx-id/
这个server context下面的一个页面,这个页面chrome://crx-id/popup.html
提供独立的操作空间。
background js
在manifest.json中配置background,提供了chrome://crx-id/
下面的另一个页面chrome://crx-id/background.html
,这个页面是不显示的,只是在后台运行,这是和popup.html页面的唯一区别。我理解chrome提供这两种页面给开发者选择,是因为有的开发者可能不不需要为crx最终客户提供可见的popup页面。从功能上面讲,有了popup.html就可以实现所有的功能。
devtool js
这个主要是修改chrome调试部分的功能。访问chrome api。
下面是总结的表格。
JS种类 | 可访问的API | DOM访问情况 | JS访问情况 | 直接跨域 | 常见用法 |
---|---|---|---|---|---|
injected script | 和普通JS无任何差别,不能访问任何扩展API | 可以访问 | 可以访问 | 不可以 | 作为DOM的一部分,操作DOM |
content script | 只能访问 extension、runtime等部分API | 可以访问 | 不可以 | 不可以 | 作为inject js的入口操作 |
popup js | 可访问绝大部分API,除了devtools系列 | 不可直接访问 | 不可以 | 可以 | 操作popup DOM和chrome的API,扩展本身的数据管理 |
background js | 可访问绝大部分API,除了devtools系列 | 不可直接访问 | 不可以 | 可以 | 操作bg DOM和chrome API,管理CRX本身的数据 |
devtools js | 只能访问 devtools、extension、runtime等部分API | 可以 | 可以 | 不可以 | devtools部分的修改 |
调试方法打开popup.html或者background.html所代表的页面,右键审查元素
通信
background和popup通信
前文已经说过,这两个页面是一脉相承的,所以chrome为他们之间的代码层面的访问调用提供了简单的方式。
// popup.js
var bg = chrome.extension.getBackgroundPage();
bg.test(); // 访问bg的函数
alert(bg.document.body.innerHTML); // 访问bg的DOM
//bg.js
var views = chrome.extension.getViews({type:'popup'});
if(views.length > 0) {
console.log(views[0].location.href);
}
bg和content之间通信
他们的js中收消息都是通过
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse))
发送的方式略有区别
//content js
chrome.runtime.sendMessage(msgjson, function(response) {
});
//bg js
chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
{
chrome.tabs.sendMessage(tabs[0].id, message, function(response)
{
if(callback) callback(response);
});
});
content js和inject js, 其他
还有其他相关略去,参考https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html#%E6%89%93%E5%8C%85%E4%B8%8E%E5%8F%91%E5%B8%83
chrome扩展自身数据存储
通过HTML5的localStorage完成,chrome也有特殊的api,chrome.storage
API总结
- chrome.tabs
- chrome.runtime
- chrome.webRequest
- chrome.window
- chrome.storage
- chrome.contextMenu
- chrome.devtools
- chrome.extension
不支持内联js
在popup.html和backgrou.html中不支持嵌入的js代码,甚至定义标签的onclick也不可以,只能在js中显式绑定。
Chrome扩展与本地进程通信
chrome扩展crx提供的能力是:
本地程序向chrome注册可以被那些crx调用和通信,注册的方式是通过配置清单文件customizenamed.json:
{
"allowed_origins" : [ "chrome-extension://fakmdcpkljckjjmemeninkejdibeiobe/" ],
"description" : "Leo Test Native fire Native Message Host",
"name" : "com.leo.test",
//"path" : "C:\\ProgramFiles\\Python27\\python.exe",
//"path" : "C:\\Users\\leo\\source\\repos\\ConsoleApp1\\ConsoleApp1\\bin\\Debug\\ConsoleApp1.exe",
"path" : "C:\\D\\dist\\leonative.exe",
//必须是stdio
"type" : "stdio"
}
其中的kv含义显而易见,主要是定义了path指定的可执行文件可以被那些crx调用和通信。
chrome如何找到这个json文件并load? chrome约定了两种实现
- windows平台,在注册表中添加key,HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Google\Chrome\NativeMessagingHosts\
com.leo.test
,value为json的路径 - POSIX平台,把
json
文件放置到特定路径/etc/opt/chrome/native-messaging-hosts/
下,文件名为com.leo.test.json
这样chrome通过load文件customizenamed.json,可以找到需要启动的本地可执行程序。
与本地进程通信的协议
本地程序需要是专用的程序。
因为chrome启动本地程序的时候,传入的启动args是固定的:
[arg 0] chrome-extension://fakmdcpkljckjjmemeninkejdibeiobe/
[arg 1] --parent-window=1057900
真正的启动命令可能是yourexecutable chrome-extension://fakmdcpkljckjjmemeninkejdibeiobe/ --parent-window=1057900
启动后,chrome(实际上应该是crx对应的进程)向本地进程yourexecutable
的stdin写入消息,从其stdout读取消息,完成crx和本地进程之间的通信。
本地进程通信消息协议
crx与本地通信的消息协议参见 https://developer.chrome.com/apps/nativeMessaging#native-messaging-host-protocol
要点是,消息的头部插入4byte标记消息字节数,意味着一条消息不可能超过4GB,而从native进程发送到crx的消息则规定不超过1MB。
本地进程通过args知道自己是本crx启动的,开始从stdin读取4byte,转为int,读取后续消息。
//bg.js
//connect to native host and get the communicatetion port
function connectToNativeHost()
{
var msg = 'hello msg'
var nativeHostName = "com.leo.test";
console.log(nativeHostName);
port = chrome.runtime.connectNative(nativeHostName);
port.onMessage.addListener(onNativeMessage);
port.onDisconnect.addListener(onDisconnected);
port.postMessage(msg);
}
可执行文件
customizenamed.json中指定的可执行文件不可以是.py脚本源文件,而必须是一个exe文件,因为实际上执行.py文件的cmd是 python xx.p。而chrome应该是通过win32的CreateProcess启动本地进程,构造cmd作为参数传递给该API,如果是一个.py源文件,不符合win32可执行文件格式。
workaround方案:通过pyinstaller把py程序打包成一个win32的可执行exe文件。
由于linux上ld对脚本第一行#!
的直接支持,可能在Linux上面是可以指定.py文件的。
实验验证:
- Linux可以通过
#!
识别脚本这是一个可执行文件,这是系统层ld-*.so加载链接器提供的支持。
使用场景
本地App需要提供crx支持,那么安装App的时候需要部署com.leo.test.json文件(Linux平台)或者添加注册表Key-Value指定json文件路径(Windows平台)。
客户安装crx即可按预定方案调用App。
如果crx需要本地App支持,稍微复杂,需要倒逼客户安装App,这种方式也可以,但是基本不是很现实的场景。