如何从零开始开发一个 Chrome 插件?

什么是浏览器插件?

简单来说浏览器插件,是浏览器上的一种工具,可以提供一些浏览器没有的功能,帮你做一些有趣的事情。开发者可以根据自己的喜欢,去实现一些功能。插件基于Web技术(html、css、js)构建。

举个栗子????

FeHelper.JSON插件


功能:格式化JSON、编码转化、markdown、代码压缩等功能。

二维码生成器


功能:可以根据当前浏览的网页地址,生成一个二维码。

SwitchyOmega Proxy


功能:你懂的。

Hello World

manifest.json

Chrome 浏览器插件没有严格的文件结构约束,只需要保证文件夹根目录有 manifest.json 文件**,**该文件的内容会概括插件所需的资源、权限等等。

一个段简单的示例:

{"manifest_version": 2, // 必填"name": "my-plugin", // 必填"version": "0.1.0" // 必填
}

manifest_version:代表了manifest文件的版本,浏览器会根据这个值去指定该版本拥有的功能。

name:插件的名称。

version:插件版本。

将manifest.json文件放到一个文件夹内。

chrome://extensions/

在浏览器地址栏输入chrome://extensions/打开“拓展程序”页面。

注意:需要启用右上角的 “开发者模式” 才能加载已解压的插件文件:


加载已解压的插件

启用之后点击加载已解压的拓展程序,选择刚刚我们放入了manifest.json的文件夹,之后你会看到:

新增了一个我们刚刚添加的插件,而且浏览器右上角也会有我们的一个图标:

此时已经加载了一个插件了,但是这个插件除了占用浏览器的一个位置除外,没有任何作用。

如果没有设置插件图标,那么插件的第一个字符会成为插件的默认icon。

让插件看起来更“插件”一点

为了让这个插件更“完善”一点,我们给它加一个icon和描述,并且点击出现一个popup页面,popup 页面一般用来承载临时性的交互,且生命周期很短:单击图标打开popup,焦点离开又立即关闭,可以通过default_popup字段来定义。

{....."description": "这是一段描述",// 插件管理页面的icon"icons": {"84": "./icon/ball.png"},// 浏览器右上角的图标和内容"browser_action": {"default_icon": "./icon/ball.png","default_title": "我的插件","default_popup": "./html/popup.html"}
}

此时我们的目录结构也变成了这样:

给popup.html加上内容:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>my-plugin</title>
</head>
<body><p style="width: 200px;text-align:center;">hello world!!</p>
</body>
</html>

之后,我们点击插件右下角的“刷新”按钮:

你会发现插件有了icon和描述:

并且右上角的icon也变了,点击一下,会弹出我们刚刚编写的popup.html页面:

现在,我们一个“完整”的插件就已经做好了。

manifest.json 配置介绍

background

{..."background": {// 提供一个页面给background"page": "./html/background.html"// 或者若干个js文件,后台会默认生成一个空白的html"scripts": ["./js/background.js"]}
}

background配置项,为插件的后台常驻页面,生命周期随着浏览器的生命周期一样,浏览器一启动,后台页面就会开始运行,直到浏览器被关闭;或者在插件管理页面,将该插件禁用了,后台页面也会停止运行。

另外,background拥有的权限比较高,几乎可以调用所有的Chrome扩展API(除了devtools),同时拥有直接跨域的能力。

page:指定一个网页为后台页面。

scripts:指定若干个js文件,后台会自动生成一个html,并按顺序调用这些js文件。

注意:pagescripts 选项只能二选一,不然会报错。

配置好之后,属性插件,会出现一个背景页选项:

我使用的是一个background.js文件:

function _back() {console.log('background.js')
}
console.log('running...')

点进去看看里面装的什么玩意:

没错,是一个普通的后台页面,如果background.js和其他页面有通信,则可以在这里进行查看请求或者调试代码。

如果使用page选项,打开也是这个样子。

另外:由于background是一直在后台运行的,为了优化性能,可以增加一个配置:

{..."background": {..."persistent": false}
}

这样,插件就会在被需要时加载,在空闲时被关闭。比如安装、更新插件的时候,或者有其他页面与background通信的时候才会被加载。

content-scripts

content-scripts能够在合适的时机(页面载入前、载入后、空闲时)注入脚本,允许内容脚本更改其JavaScript环境,而不与页面或其他内容脚本发生冲突。

例如,原页面有个按钮,并且给按钮添加了一个点击事件:

<html><button id="mybutton">click me</button><script>var greeting = "hello, ";var button = document.getElementById("mybutton");button.person_name = "Bob";button.addEventListener("click", function() {alert(greeting + button.person_name + ".");}, false);</script></html>

在content-scripts中,加入以下代码:

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener("click", function() {
alert(greeting + button.person_name + ".");
}, false);

当页面运行之后,脚本内容也会在插件定义的时间运行,当页面点击按钮时,会出现两次弹窗。

content-scripts配置:

{..."content_scripts": [{// 在匹配的URL中运行,<all_urls>表示所有的URL都会运行。"matches": ["<all_urls>"],// 注入的js,会按顺序运行。"js": ["./js/content.js"],// css引入需谨慎,因为可能会影响全局的样式,同样也能接收多个css文件,会按顺序插入到页面中"css": ["./css/style.css"],// 代码注入的时机,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle"run_at": "document_start"},{"matches": ["https://www.baidu.com/"],"js": ["./js/other.js"],"run_at": "document_start"}],...
}

content.js代码如下:

console.log('hello, from content.js');

other.js代码如下:

console.log('hello, from other.js...')

更新插件,当在 https://bytedance.feishu.cn/drive/home/运行时:

因为【 https://bytedance.feishu.cn/drive/home/】只匹配到了<all_urls>的规则,所以之后运行content.js

当在https://www.baidu.com/运行时:

同时命中了2个规则,所以content.js和other.js都会运行,顺序也是正确的。

content-scripts 和原始页面共享DOM,但是不共享JS,如要访问页面JS(例如某个JS变量),只能通过inject-scripts来实现。content-scripts能够访问的Chrome API的权限也比较低,只能访问以下四个API:

  • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)

  • chrome.i18n

  • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)

  • chrome.storage

Inject-scripts

inject-scripts 是通过DOM操作插入的JS代码,通常在content-scripts只能操作DOM,但是却无法访问页面的JS,借助content-scripts可以操作DOM的能力,往页面中插入JS文件,给页面提供调用插件API的能力,以及和background通信的能力。

在插入之前,需配置一下web可访问的资源,同时content-scripts的调用时机换成"document_end"或者"document_idle",不然会无法获取DOM,导致插入失败。在manifest.json中添加以下内容:

{..."content_scripts": [{"matches": ["<all_urls>"],"js": ["./js/content.js"],"run_at": "document_end"},...],"web_accessible_resources": ["js/inject.js"],...
}

inject.js的内容如下:

function mockApi () {console.log('this is from inject.js')
}

content.js增加以下代码:

(function () {let path = 'js/inject.js';let script = document.createElement('script');script.setAttribute('type', 'text/javascript');// 注意,路径需用Chrome API 生成,这个方法可以获得插件的资源的真实路径。// 类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.jsscript.src = chrome.extension.getURL(path);script.onload = function () {// 在执行完代码之后移除script标签this.parentNode.removeChild(this);}document.body.appendChild(script);
})();

更新插件后,页面就可以访问inject.js的方法:

permissions

插件后台有的操作需要配置相应的权限,例如本地存储、网络请求、通知等等,示例:

{..."permissions": ["contextMenus", // 右键菜单"tabs", // 标签"notifications", // 通知"webRequest", // web请求"webRequestBlocking","storage" // 插件本地存储],...
}

完整的manifest配置

官方文档:https://developer.chrome.com/extensions/manifest

通信

popup和background通信

popup可以通过 chrome.extension.getBackgroundPage() API 直接获取到background的上下文,从而调用background的方法来通信:

// popup.js
var backend = chrome.extension.getBackgroundPage();
backend.test(); // 访问bbackground的函数

background可以通过chrome.extension.getViews({type:'popup'}) 获取到popup的上下文,前提是popup页面是打开的状态下。

let views = chrome.extension.getViews({type:'popup'});
let popup = null
if(views.length > 0) {popup = views[0];// 直接访问popup的函数popup.test();
}

这里需要注意一点:

在popup页面,你如果想编写js,请将js编写在一个文件里面,然后引入进来,不然会报错,这是因为Chrome的安全政策规定的:https://developer.chrome.com/extensions/contentSecurityPolicy

popup错误示范:

<!DOCTYPE html>
<html lang="en">
<head>...
</head>
<body><p style="width: 200px;text-align:center;">hello world!!</p><script>// 不能直接在里面写</script>
</body>
</html>

正确姿势:

<!DOCTYPE html>
<html lang="en">
<head>...
</head>
<body><p style="width: 200px;text-align:center;">hello world!!</p><script src="../js/popup.js"></script>
</body>
</html>

content-scripts和background通信

content-scripts可以通过 chrome.runtime.sendMessage(message) 给background发送消息:

chrome.runtime.sendMessage('message content', (res) => {console.log('from background:', res)
});

background通过chrome.runtime.onMessage.addListener()监听content-scripts发送的消息:

chrome.runtime.onMessage.addListener(function(message, sender, callback) {console.log(mesasge); // meesage contentcallback && callback('yes this from background')
});

background主动给content-scripts发消息,首先得查找要给哪个tab发消息,使用chrome.tabs.query 这个方法查找到tab,再使用chrome.tabs.sendMessage 方法给tab发消息:

// {active: true, currentWindow: true} 表示查找当前屏幕下的active状态的tab;
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {chrome.tabs.sendMessage(tabs[0].id, 'message content', (res) => {console.log('from content:', res)});
});

content-scripts通过chrome.runtime.onMessage.addListener 去监听事件:

chrome.runtime.onMessage.addListener(function (message, sender, callback) {console.log(message, sender)callback && callback('yes this from content')
});

注意:

1.消息内容可以直接发送JSON格式的对象。

2.popup和content的通信方式与上面一样。

3.如果popup和background都监听了从content发来的消息,两者都能收到监听消息,但是callback只会触发一次,被谁触发取决与谁先发送。

inject-scripts和content-scripts

inject-scripts和content-scripts通信有两种方法:

1.window.postMessage发送,window.addEventListener接收

2.还有一种是自定义的DOM事件;

但是很少情况会是content-scripts去调inject-scripts,因为,可以,但是没必要....content-scripts完全可以自己处理一些API的事件监听,况且inject-scripts也只是content-scripts生成并插入到DOM里面的,所以在content-scripts眼里,inject-scripts就是个弟弟...

但是,很多用户触发的事件,需要通过inject-scripts告诉content-scripts,content-scripts再给background通信并且去做一些事情,然后再发消息告诉inject-scripts,从这个角度看:content-scripts就是一个inject-scripts的工具人!

(扯平了,完美。)

inject-scripts给content-scripts发消息:

window.postMessage({"test": '你好!工具人!'}, '*');

content-scripts接收消息:

window.addEventListener("message", function(message) {console.log('来了老弟!', message.data);
}, false);

同样的,content-scripts给inject-scripts发消息是一样的。

练练手:HTTP Header 插件

实现一个HTTP Header 插件,可以实现动态添加header,并且给网络请求自动加上header,header参数可以配置。

示例图:

Background 功能设计

background复制存储、操作headers,对所有浏览器请求做一层拦截,并加上启用的headers。

注意:因为涉及到网络请求,所以需在manifest.json中添加权限:

{..."permissions": ["storage", // 本地存储"webRequest", // 网络请求"webRequestBlocking", // 网络请求 阻塞式"<all_urls>" // 匹配的URL]...
}

Background 功能伪代码:

// headers数据结构, 附带默认值;(可以改为本地存储)。
const headers = [{key: 'Content-Type',value: 'application/x-www-form-urlencoded',enable: false,},{key: 'Test-Header',value: '按F进入坦克',enable: true,},
];// 获取、新增、删除、启用禁用
function getHeaders () {return headers;
}
function addHeader (header) {headers.push(header);
}
function deleteHeader (index) {headers.splice(index, 1);
}function toggleHeader(index) {headers[index].enable = !headers[index].enable;
}
...// 请求拦截器
// On install 在被安装的时候去初始化
chrome.runtime.onInstalled.addListener(function(){// 添加事件  chrome.webRequest.onBeforeSendHeaders.addListener(requestHandle, {urls: ["<all_urls>"],// 拦截所有URL的请求},["blocking", "requestHeaders"]); // 阻塞式console.log('load');
});// 添加header
function requestHandle(request) {let requestHeaders = request.requestHeaders;// 添加headersheaders.forEach(item => {if (item.enable) {requestHeaders.push({name: item.key,value: item.value,});}});return {requestHeaders};
}

chrome.webRequest的生命周期:

详细参考:https://developer.chrome.com/extensions/webRequest

popup 页面设计

popup页面提供增加、删除、启用禁用功能接口,并且在每次打开popup页面的时候去background获取最新的header数据,展示在前台。

popup.js 功能伪代码:

// popup页面被打开时,去后台获取最新header
window.onload = function () {let backend = chrome.extension.getBackgroundPage();// 调用background方法,获得headerslet headers = backend.getHeaders();// 渲染headercreateElement(headers);
}// 增加按钮
function addHeader() {let backend = chrome.extension.getBackgroundPage();let key = document.querySelector('.key');let value =  document.querySelector('.value');let header = {key: key.value,value: value.value,enable: true}// 调用background方法,新增headersbackend.addHeader(header);createElement(header);
}
// 启用禁用、删除功能
function toggleHeader(index) {let backend = chrome.extension.getBackgroundPage();backend.toggleHeader(index);
}function delHeader(index) {let backend = chrome.extension.getBackgroundPage();backend.deleteHeader(index);
}

效果

打开popup,添加一个header:

随便打开一个网页,打开控制台查看RequestHeaders:

总结

  • 很多权限、功能需要在manifest.json配置。

  • content-scripts、popup、background、inject-scripts拥有的权限不一样,通信方式也不一样,理解各个脚本的特点,组合使用。

  • 开发调试可在后台背景页查看信息,popup、inject-scripts、content-scripts可直接审查元素调试。

Chrome 插件还有很多功能这里没有详细介绍,例如devtools。感兴趣的同学可以查阅下面的参考文档。

参考文档

官方文档:https://developer.chrome.com/extensions

参考博客:https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html


今天组建了一个江西人的前端交流群,如果你也是江西人可以加我微信ruochuan12 拉你进群。


你好,我是若川,江西人~(点击蓝字了解我)历时一年只写了一个学习源码整体架

构系列 有哪些必看的JS库:jQuery、underscore、lodash、sentry、vuex、axios、koa、redux

  1. 关注若川视野,回复"pdf" 领取优质前端书籍pdf,回复"1",可加群长期交流学习

  2. 我的博客地址:https://lxchuan12.gitee.io 欢迎收藏

  3. 觉得文章不错,可以 分享、点赞、在看 呀^_^另外欢迎留言交流~

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

轉Excel的一種簡單方法

写了这么久的程序﹐越来越喜欢那种简单的解决方法﹐这段时间在做一个报表系统﹐其中有需要转Excel﹐而且要求兼容openoffice﹐遂利用asp语法,asp.net的控件封装特性以及excel 2003的xml试算清格式做了一个看起来比较"清爽"的excel转档方案。一.开始原理很简单﹐excel…

总结:自学前端的高效学习路线

提到前端&#xff0c;大多数人都会想到薪资高。也正因为如此&#xff0c;很多人想要从事前端开发这个岗位&#xff0c;也由此衍生出来一个问题&#xff1a;为什么前端工程师供不应求&#xff0c;但还是有很多学前端的人找不到工作&#xff1f;其实行业不是缺前端工程师&#xf…

vmware创建虚拟机不识别网卡

今天在给虚拟机添加网卡的时候&#xff0c;出现了虚拟机不识别新加的网卡&#xff0c;很纳闷&#xff0c;连的一样的端口组&#xff0c;为什么新加的网卡识别不了呢 然后查看pci设备&#xff0c;发现网卡的驱动为 AMD 79C970 PCnet32- LANCE 然后都vc上查看&#xff0c;果真驱动…

周末包邮送书和小红包中奖名单公布

大家好&#xff0c;我是若川。周末送福利&#xff0c;给大家送红包、包邮送新书&#xff01;这篇文章中&#xff0c;准备了3本自选前端新书&#xff0c;10个2元小红包&#xff0c;在看抽10人每人5元红包&#xff0c;2月28日晚8点开奖。现将名单公布如下&#xff1a;在看抽奖&am…

python treeview底部加个按钮_Python爬取京东商品信息(GUI版本)

前言本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。作者&#xff1a;DYblog转载&#xff1a;https://www.cnblogs.com/dy8888/p/13257918.htmlPS&#xff1a;如有需要Python学习资料的小伙伴可以加点击…

jmeter学习指南之聚合报告

jmeter视频地址&#xff1a;https://edu.51cto.com/course/14305.html 上一篇文章中我们讲了Jmeter结果分析最常用的一个Listener查看结果树&#xff0c;今天接着讲另一个最常用的listener--聚合报告Aggregate Report。我们先来看看聚合报告中的主要名称的含意&#xff1a;Labe…

2021 整理的最全学习资源,送给每一个努力着的人

时间来到了 2021 年&#xff0c;新的一年有新的期待&#xff0c;而我亦有新的祝福如果说在过去的一年&#xff0c;经历太多&#xff0c;心酸、迷茫、焦虑、幸福、喜悦那么在 2021 年&#xff0c;希望你可以去过一种遇见自己的生活&#xff0c;恬淡、热情&#xff0c;喜欢自己而…

Chrome OS 设备或将允许用户自行选择 Linux 发行版

百度智能云 云生态狂欢季 热门云产品1折起>>> 谷歌去年宣布在 Chrome OS 上支持运行 Linux 应用&#xff0c;前不久又有消息称其将为运行这些 Linux 应用提供 GPU 加速支持&#xff0c;而现在&#xff0c;Chrome OS 似乎将在 Linux 的方向上更进一步&#xff0c;让 …

博文视点 OpenParty第11期:世界黑客大会那些事

博文视点 OpenParty第11期&#xff1a;世界黑客大会那些事 亲爱的读者朋友&#xff1a; 您好&#xff01; 2009年&#xff0c;博文视点Open Party共举办8场&#xff0c;累计到场2000人次&#xff0c;影响力辐射近5000人次&#xff0c;真正实现了博文视点Open Party的初…

我从 Vuejs 中学到了什么——框架设计学问

框架设计远没有大家想的那么简单&#xff0c;并不是说只把功能开发完成&#xff0c;能用就算完事儿了&#xff0c;这里面还是有很多学问的。比如说&#xff0c;我们的框架应该给用户提供哪些构建产物&#xff1f;产物的模块格式如何&#xff1f;当用户没有以预期的方式使用框架…

CSS制作的32种图形效果[梯形|三角|椭圆|平行四边形|菱形|四分之一圆|旗帜]

转载链接&#xff1a;http://www.w3cplus.com/css/css-simple-shapes-cheat-sheet 前面在《纯CSS制作的图形效果》一文中介绍了十六种CSS画各种不同图形的方法。今天花了点时间将这方面的制作成一份清单&#xff0c;方便大家急用时有地方可查。别的不多说了&#xff0c;直接看代…

yaml加配置文件后起不来_YAML配置文件管理资源

YAML是配置文件的格式&#xff0c;YAML文件中是由一些易读的字段和指令组成的。K8S使用YAML配置文件需要注意如下事项。定义配置时&#xff0c;指定最新稳定版API(当前最新稳定版是v1版本)。最新版本的API可以通过kubectl api-versions命令进行查看&#xff0c;命令如下所示。前…

html5/css3响应式布局介绍

转载链接&#xff1a;http://www.51xuediannao.com/htmlcss/htmlcssjq/694.html html5/css3响应式布局介绍 html5/css3响应式布局介绍及设计流程&#xff0c;利用css3的media query媒体查询功能。移动终端一般都是对css3支持比较好的高级浏览器不需要考虑响应式布局的媒体查询…

MobX 上手指南,写 Vue 的感觉?

之前用 Redux 比较多&#xff0c;一直听说 Mobx 能让你体验到在 React 里面写 Vue 的感觉&#xff0c;今天打算尝试下 Mobx 是不是真的有写 Vue 的感觉。题外话在介绍 MobX 的用法之前&#xff0c;先说点题外话&#xff0c;我们可以看一下 MobX 的中文简介。在 MobX 的中文网站…

ansible中yaml语法应用

4、yaml语法应用 ansible的playbook编写是yaml语言编写&#xff0c;掌握yaml语法是编写playbook的必要条件&#xff0c;格式要求和Python相似&#xff0c;具体教程参考如下yaml语言教程 附上一个yaml文件转js格式文件链接在线免费yaml内容转json格式 4.1、 ansible中的yaml语法…

(送书和红包)快人一步,掌握前端函数式编程

大家好&#xff0c;我是若川。上周末送出了3本新书和若干红包&#xff0c;抽奖名单已公布。本周又争取到了4本《前端函数式编程》书籍包邮送给大家&#xff0c;抽奖规则见文末&#xff0c;与以往不同的是除了关键词、留言、在看抽奖外&#xff0c;还有最早关注奖&#xff0c;欢…

JavaScript操作表格进行拖拽排序

<js-dodo-table- Drag and Drop JQuery plugin>以上插件可对table进行排序&#xff0c;拖动&#xff01; 例如有下面一个样子的id为table的表格&#xff1a; <table id"table"cellspacing"0"cellpadding"2"><tr id"1"…

ping -a 获取不到主机名_网络测试命令——PING

命令功能&#xff1a;Linux系统和windows系统的ping命令是常用的网络命令&#xff0c;它通常用来测试与目标主机的连通性&#xff0c;这样我们就可以根据它ping输出的信息来确定目标主机是否可访问(但这不是绝对的)。有些服务器为了防止通过ping探测到&#xff0c;通过防火墙设…

写在2021: 值得关注/学习的前端框架和工具库

前言最近在知乎看到了这么个问题&#xff1a;学完Vue还有必要学习React和Node吗&#xff1f;[1]&#xff0c; 有很奇妙的感觉&#xff0c;因为我在最开始入门前端时&#xff0c;也是以Vue入的门&#xff0c;在“学完”Vue之后&#xff0c; 我也有了这个疑问&#xff0c;但当时的…

万物互联之~RPC专栏

3.RPC引入 上篇回顾&#xff1a;万物互联之~深入篇 Code&#xff1a;https://github.com/lotapp/BaseCode/tree/master/python/6.net/6.rpc/ 其他专栏最新篇&#xff1a;协程加强之~兼容答疑篇 | 聊聊数据库~SQL环境篇 3.1.概念 RPC(Remote Procedure Call)&#xff1a;分布式系…