原文地址: https://segmentfault.com/a/11...
转载请在文首注明来源
TL;DRcss
本文历时较久,2020年写了一半,写到迷茫,索性搁置,2021年翻出来继续写的;通过了半年多的时间再看,会有一些不同的理解,存在部分配图风格不一致.可是我会确保内容的正确和表达的一致.
2020年的某一天我把以前写的QQ空间批量删除说说&留言的脚本写了个chrome插件版
,名字也改为了QQ空间小助手.
由于也是第一次写插件,因此记录这个过程,分享开发过程和期间遇到的问题,并作一些关键点的梳理.
因此,这并非一篇大而全的chrome extension开发教程,这是一篇以QQ空间助手为实战来切入插件开发可是侧重讲chrome extension开发的文章.html
QQ空间小助手功能很简单,核心功能(其实也就这点功能)是批量删除qq空间的说说和留言.
实现上也很简单,只须要两个步骤.前端
这里面的困难一个在于找到获取&删除 留言和说说 所须要的参数(这个咱们不展开介绍),
另一个困难是这些功能怎么在chrome extension
中实现.git
根据上面这些需求,我理解大概操做流程是这样的,github
点击icon
,或者点击icon
弹出来的页面而后就开始获取列表,而后遍历列表开始删说说.web
里面大概须要用到,组件,组件间通讯这些内容.chrome
可是由于是没有接触过这块,因此得搞清楚都须要有什么知识储备,那就先从这里开始介绍.json
总体上,插件开发这个东西没那么难,须要的技术也比较简单.基本上就是前端那些知识:vim
外加一些软技能segmentfault
chrome devtools
的使用和基本的调试能力基本上作过项目的前端都能hold住.
若是你在开发以前有些不肯定的问题的话须要提早了解的话,也能够看这篇文档高频问题Q&A.
介绍完了知识储备,咱们就得看文档了.
接触新的技术,文档确定是不可少的,可是网上各类教程 + 文档,鱼龙混杂,咱们到底应该看教程仍是看文档.
我我的理解是,无论教程仍是文档,均可以看,可是要优先看官方的文档和教程,看一手的资料,由于我看文档的过程当中发现,非官方的东西(非一手文档)信息传达不完整,甚至有纰漏.因此尽着官方的来,除非有很好的非官方教程或者其余的缘由.
也有一份非官方文档不错,可是不推荐直接看,建议结合官方文档做为对照来看,由于我在开发的时候发现这份文档有些内容和官方最新的文档不一致(好比对于browser action
和page action
的定义).
另外,在实际的阅读过程当中,还会有些地方看文档看不明白的,这时候有针对的搜一些博客/教程就能够.好比此次组件通讯的地方有困惑,就找了些不错的文档看.
还有一些别的文章,都整理下来能够参考.
看完文档咱们大体会获得一些信息.下面就来说讲.
在这一部分你只须要了解一个插件会由哪些部分组成就能够了,具体功能后面介绍.
正常状况下咱们看到的浏览器和插件大概长这样
当咱们鼠标点击插件icon以后就变成了这样
从上面的图上来看,主要由
icon
(任务栏的图标) 和 点击以后的弹窗两个大件组成,部分插件还会有一个专门的用于配置的页面(这里没用到,就不介绍了).
另外,在UI以外,还会background
和content_script
存在.
再加上这两个重要的概念以后整个结构就是这样的
下面看看文件结构.
文件结构比较能直观的反应组件的组成和功能.从文件结构来看。通常一个插件的文件结构长这样的(之因此说通常是由于不是每一个插件都能用到所有的功能).
qzone_helper_extension │ background.js │ manifest.json │ popup.html │ popup.js │ qq_icon.png └─ content_script.js
其中各个文件的功能以下:
manifest.json
是咱们最早须要了解的部分,这个文件的功能相似于package.json
文件,这里定义插件的配置信息,这些信息小到插件名称,图标,版本号等描述信息,大到你须要申请的权限和各类引入的资源,入口文件等重要配置。因此这里应该是咱们最早须要了解的部分。qq_icon.png
是最不值得咱们理解的部分,这就是个图标文件而已。popup.html
是比较直观的一个文件,这个就是你点击插件图标以后弹出来的界面,那个界面是html写的,一样的,对应的popup.js
用来处理popup
页面的交互和popup
模块和别的模块之间的通讯。background.js
和content_script.js
是核心.其中,background.js
能够理解为项目的app.js
文件,而content_script.js
是和网页是一伙的,能够简单的理解为是咱们在网页那边的代理,帮咱们作些网页相关的操做.加上这些结构和文件之间的对应关系是这样的
到这里,你大概了解的一个插件的基本组成结构.了解完结构,咱们分来看看各部分的具体状况.
通常来讲,Icon
是咱们插件的功能入口,也能够显示一些徽标.
在chrome插件中,这种icon按钮是分两种的:
他们的区别与使用场景以下
类型 | 支持功能 | 应用场景 |
---|---|---|
page action | 不能使用徽章(就是提醒你有多少未读消息的红点和数字) | 非应用在全部页面的状况,好比我此次开发的QQ空间小助手就只是应用在user.qzone.qq.com的一个插件,使用的就是page action |
browser action | 拥有完整的功能(tooltip / popup / badges 对于这些功能再也不赘述) | 对全部页面均可以使用的插件,好比adblock / vimium这种 |
你可能有点困惑,我了解了这个区别了,可是有什么用吗?有用,在定义manifest.json
的时候会用到.page action
对应的定义字段是page_action
,browser action
对应的定义字段是browser_action
.
pupop
这个页面主要承载简单的配置和信息展现功能.
这就是个普通的html
文件,里面写你的css
和js
逻辑.
须要注意的是,这里的js
只能操做pupop
里面的DOM
.
回到咱们的需求,咱们能够在
popup
页面放置一些用于触发操做的按钮.好比删除说说按钮
这个文件很重要,可是一上来就放这个文件,让人有点摸不着头脑,因此,放在这里.
可是这个文件不必每一个字段都清楚啥意思,搞清楚你用到的就好了.
{ // Require "manifest_version": 2, // 不一样的manifest版本会有不一样的功能 "name": "My Extension", "version": "versionString", // Recommended "default_locale": "en", "description": "A plain text description", "icons": {...}, // icon文件路径 // Pick one (or none) "browser_action": {...}, "page_action": {...}, // Optional "action": ..., "author": ..., "automation": ..., "background": { // background对应的配置 // Recommended "persistent": false, // Optional "service_worker": }, "chrome_settings_overrides": {...}, "chrome_ui_overrides": { "bookmarks_ui": { "remove_bookmark_shortcut": true, "remove_button": true } }, "chrome_url_overrides": {...}, "commands": {...}, "content_capabilities": ..., "content_scripts": [{...}], // content_script对应的配置 "content_security_policy": "policyString", "converted_from_user_script": ..., "current_locale": ..., "declarative_net_request": ..., "devtools_page": "devtools.html", "event_rules": [{...}], "externally_connectable": { "matches": ["*://*.example.com/*"] }, "file_browser_handlers": [...], "file_system_provider_capabilities": { "configurable": true, "multiple_mounts": true, "source": "network" }, "homepage_url": "http://path/to/homepage", "import": [{"id": "aaaa"}], "incognito": "spanning, split, or not_allowed", "input_components": ..., "key": "publicKey", "minimum_chrome_version": "versionString", "nacl_modules": [...], "oauth2": ..., "offline_enabled": true, "omnibox": { "keyword": "aString" }, "optional_permissions": ["tabs"], "options_page": "options.html", "options_ui": { "chrome_style": true, "page": "options.html" }, "permissions": ["tabs"], // 须要申请的权限 "platforms": ..., "replacement_web_app": ..., "requirements": {...}, "sandbox": [...], "short_name": "Short Name", "signature": ..., "spellcheck": ..., "storage": { "managed_schema": "schema.json" }, "system_indicator": ..., "tts_engine": {...}, "update_url": "http://path/to/updateInfo.xml", "version_name": "aString", "web_accessible_resources": [...] }
这基本能够理解为是一个常驻后台的js
文件,在里面能够处理一些事件监听.好比监听页面初始化的事件(chrome.runtime.onInstalled
),监听通讯事件(chrome.runtime.onMessage
).
另外,从background
发出去的请求能够跨域.
在V3的实现中,background引入了 service worker的概念.
在chrome的设置-更多工具-任务管理器里面能够看到咱们的background
任务进程.
content_script
文件是和浏览器打开的页面一块加载的,能够操做打开页面的DOM.
例如,点击插件的图标,而后页面改变颜色.这种状况是不能在background.js
中直接改变页面颜色的,而是须要经过事件发送消息到content_script.js
中,经过content_script
来操做页面DOM
.
了解完这些咱们大概知道了,咱们获取发请求的哪些参数包括发请求均可以在
content_script
中实现.由于只有之类能够操做页面的DOM.
上面介绍完了各个组成部分,下面介绍这些部分之间的通讯.chrome extension
的通讯是经过事件来进行的,通讯的内容是有效的JSON
对象.共有三种通讯方式:
Simple one-time requests
(就像短链接)Long-lived connections
(就像长链接)Cross-extension messaging
(多个插件间通讯)咱们这里只展开此次用到的Simple one-time requests
.
插件内的通讯分为这几种:
content script => background
content script => popup
background => content script
background => popup
popup => content script
popup => background
content script => background
/content script => popup
/background => popup
/popup => background
这么发送事件
chrome.runtime.sendMessage({greeting: "hello"}, function(response) { console.log(response.farewell); });
content script
发送的事件popup
和background
均可以收到,因此再发送的时候须要加上发给谁的标识,而后收到消息的时候处理popup => background
的通讯其实也能够经过chrome.extension.getBackgroundPage()
来获取到background的全部方法,直接调用
background => content script
/popup => content script
这么发送事件
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) { console.log(response.farewell); }); });
- 由于可能打开了多个同一个url的tab,因此须要区分tab.
- 经过chrome.tabs.sendMessage发送的事件只有指定页面的content能够收到
而对于事件的监听处理是同样的
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { console.log(sender.tab ? "from a content script:" + sender.tab.url : "from the extension"); if (request.greeting == "hello") sendResponse({farewell: "goodbye"}); } );
整个通讯用一张图表示就是
加上通讯的部分,咱们大概清楚了,咱们在popup
中触发操做,而后popup
发送事件给content_script
,content_script
开始获取请求参数,请求数据列表,而后遍历数据作删除操做.整个过程大概就这样,没用到background
.
![]()
在地址栏打开chrome://extensions/
,而后开启右上角的开发者模式
而后点击加载已解压的拓展程序
,在打开的文件选择框中选择你的目录就算安装你开发的插件了.
background / popup / content script
的控制台都是独立的,能够经过下面的方式打开
background
的控制台chrome://extensions/
页面,点击对应的插件背景页三个字便可打开background
的控制台popup
的控制台icon
在弹出的popup页面
上右键,而后点击检查,就能够打开popup
的控制台.须要注意的是popup
页面一旦关闭,控制台也会随之关闭.content script
的控制台content script
的控制台其实就是tab页的控制台,可是须要切换一下插件的调试和普通前端开发的debug方式是同样的.
更多详情能够参考文档:Debugging extensions
咱们的插件开发完成以后能够选择打包成crx
文件发布到chrome 网上应用店,上传到chrome是须要注册开发者的,注册帐号须要5$,注册以后能够发布多个chrome插件.
更多详情能够参考文档 建立和发布自定义 Chrome 应用和扩展程序
打包功能在加载已解压的拓展程序
按钮的旁边,而后按照提示走就能够了
也能够直接打成压缩包放到网上让别人下载使用,以前能够本地安装crx
文件,如今不支持了,都是经过加载已解压的拓展程序
在本地使用.
开发的过程当中发现有的时候chrome.tabs.query
获得的结果为[]
,后来发现这是chrome的一个bug,在2015年就存在了,一直没有修复.解决方法以下:
var activeTabId; chrome.tabs.onActivated.addListener(function(activeInfo) { activeTabId = activeInfo.tabId; }); // https://bugs.chromium.org/p/chromium/issues/detail?id=462939 function getActiveTab(callback) { chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { let tab = tabs[0]; if (tab) { callback(tab.id); } else { chrome.tabs.get(activeTabId, function (tab) { if (tab) { callback(tab.id); } else { console.log('No active tab identified.'); } }); } }); }
需求是在某些页面插件的icon
才亮起来,点击icon
才会展现popup页面
.
这个功能的实现思路比较多,这里讲两种
一种是经过监听conect事件,在事件的处理方法中setIcon
和初始化对应的popup
页面.
Vimium是这么作的,能够看这里.
还有一些利用chrome
的onPageChanged
事件的规则来实现,这种处理只有在符合规则的时候才展现popup
页面.
chrome.runtime.onInstalled.addListener(function() { chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { chrome.declarativeContent.onPageChanged.addRules([{ conditions: [ new chrome.declarativeContent.PageStateMatcher({ pageUrl: { hostContains: 'qzone.qq.com' } }) ], actions: [new chrome.declarativeContent.ShowPageAction()] }]); }); });
这种状况要留意插件管理页面你的插件那里有没有报错提示,像这样
这里的报错必须点进去清除了,才能继续向下进行,否则就会出现操做没有响应的状况.