Chrome插件探究

1,377 阅读12分钟

作者:小影前端团队—— 祥帅

1.前言

1.1.什么是Chrome插件

首先要明确一个观念,这种扩展程序,实际上不是一个exe、app之类的程序,下载了本地打开运行安装,本质上,它就是一个网页,写的用的都是前端的语言,高档点说是一个程序,通俗来讲, 就是运行在浏览器上的一个网站,网页。

严格来讲,我们正在说的东西应该叫Chrome扩展(Chrome Extension),真正意义上的Chrome插件是更底层的浏览器功能扩展,可能需要对浏览器源码有一定掌握才有能力去开发。

Chrome插件是一个用Web技术开发、用来增强浏览器功能的软件,它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包.

个人猜测crx可能是Chrome Extension这3个字母的简写:

1.png

2.开发与调试

对于一个Chrome插件而言,其项目结构没有严格的要求,只要其目录有一个manifest.json即可, 也不需要专门的IDE,普通的web开发工具即可。

对于开发完成的插件只需要从右上角菜单->更多工具->扩展程序可以进入 插件管理页面,也可以直接在地址栏输入 chrome://extensions 进行访问。

image-7.png 勾选开发者模式即可以文件夹的形式直接加载插件,否则只能安装.crx格式的文件。Chrome要求插件必须从它的Chrome应用商店安装,其它任何网站下载的都无法直接安装,所以,其实我们可以把crx文件解压,然后通过开发者模式直接加载。

开发中,代码有任何改动都必须重新加载插件,只需要在插件管理页按下Ctrl+R即可,以防万一最好还把页面刷新一下。

3.manifest.json里是什么

这是一个Chrome插件最重要也是必不可少的文件,用来配置所有和插件相关的配置,必须放在根目录。其中,manifest_versionnameversion3个是必不可少的,descriptionicons是推荐的。

// 清单文件的版本,这个必须写,而且必须是2:其他会报错(未能成功加载扩展程序)

"manifest_version": 2,

// 插件的名称

"name": "demo",

// 插件的版本

"version": "1.0.0", // 随意

// 插件描述

"description": "简单的Chrome扩展demo",

// 图标(非必填) 在不同的分辨率下,chrome会根据不同情况采用不同大小的图标。

"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": "这是一个示例Chrome插件",

"default_popup": "popup.html"

},

// 当某些特定页面打开才显示的图标

/*"page_action":

{

"default_icon": "img/icon.png",

"default_title": "我是pageAction",

"default_popup": "popup.html"

},*/

// 需要直接注入页面的JS

"content_scripts":

[

{

//"matches": ["http://*/*", "https://*/*"],

// "<all_urls>" 表示匹配所有地址

"matches": ["<all_urls>"],

// 多个JS按顺序注入

"js": ["js/jquery-1.8.3.js", "js/content-script.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":

[

"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",

// 覆盖浏览器默认页面

"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"

完整的配置文档请戳这里

4.核心介绍

4.1.content-scripts

所谓content-scripts,其实就是Chrome插件中向页面注入脚本的一种形式(虽然名为script,其实还可以包括css的),借助content-scripts我们可以实现通过配置的方式轻松向指定页面注入JS和CSS(如果需要动态注入,可以参考下文),最常见的比如:广告屏蔽、页面CSS定制,等等。

{

// 需要直接注入页面的JS

"content_scripts":

[

{

//"matches": ["http://*/*", "https://*/*"],

// "<all_urls>" 表示匹配所有地址

"matches": ["<all_urls>"],

// 多个JS按顺序注入

"js": ["js/jquery-1.8.3.js", "js/content-script.js"],

// JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式

"css": ["css/custom.css"],

// 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle

"run_at": "document_start"

}

],

}

4.2.background

后台(姑且这么翻译吧),是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面。

background的权限非常高,几乎可以调用所有的Chrome扩展API(除了devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS

配置中,background可以通过page指定一张网页,也可以通过scripts直接指定一个JS,Chrome会自动为这个JS生成一个默认的网页:

{

// 会一直常驻的后台JS或后台页面

"background":

{

// 2种指定方式,如果指定JS,那么会自动生成一个背景页

"page": "background.html"

//"scripts": ["js/background.js"]

},

}

需要特别说明的是,虽然你可以通过chrome-extension://xxx/background.html

直接打开后台页,但是你打开的后台页和真正一直在后台运行的那个页面不是同一个,换句话说,你可以打开无数个background.html,但是真正在后台常驻的只有一个,而且这个你永远看不到它的界面,只能调试它的代码。

比如,我们在background.js中对某个站点发送ajax请求,当站点有信息更新时,background.js也会更新工具栏右侧插件图标的状态,进而通知到用户!

4.3.popup

popup是点击图标时打开的一个小窗口网页,焦点离开网页就立即关闭,一般用来做一些临时性的交互。

2.png

popup可以包含任意你想要的HTML内容,并且会自适应大小。可以通过default_popup字段来指定popup页面,也可以调用setPopup()方法。

{

"browser_action":

{

"default_icon": "img/icon.png",

// 图标悬停时的标题,可选

"default_title": "这是一个示例Chrome插件",

"default_popup": "popup.html"

}

}

4.4 pageAction(地址栏右侧)

所谓pageAction,指的是只有当某些特定页面打开才显示的图标,它和browserAction最大的区别是一个始终都显示,一个只在特定情况才显示。

通俗的举个例子,一些扩展任何页面可用,就都会显示在工具栏上为可用状态,一些扩展只适用于某些页面,如大家很熟悉的vue tools调试器,在检测到页面用的是vue时,就会在工具栏显示出来并可用(非灰色)。

需要特别说明的是早些版本的Chrome是将pageAction放在地址栏的最右边,左键单击弹出popup,右键单击则弹出相关默认的选项菜单:

而新版的Chrome更改了这一策略,pageAction和普通的browserAction一样也是放在浏览器右上角,只不过没有点亮时是灰色的,点亮了才是彩色的,灰色时无论左键还是右键单击都是弹出选项:

  • chrome.pageAction.show(tabId) 显示图标;
  • chrome.pageAction.hide(tabId) 隐藏图标;
// manifest.json

{

"page_action":

{

"default_icon": "img/icon.png",

"default_title": "我是pageAction",

"default_popup": "popup.html"

},

"permissions": ["declarativeContent"]

}

// background.js

chrome.runtime.onInstalled.addListener(function(){

chrome.declarativeContent.onPageChanged.removeRules(undefined, function(){

chrome.declarativeContent.onPageChanged.addRules([

{

conditions: [

// 只有打开百度才显示pageAction

new chrome.declarativeContent.PageStateMatcher({pageUrl: {urlContains: 'baidu.com'}})

],

actions: [new chrome.declarativeContent.ShowPageAction()]

}

]);

});

});

4.5.override(覆盖特定页面)

使用override页可以将Chrome默认的一些特定页面替换掉,改为使用扩展提供的页面。

扩展可以替代如下页面:

  • 历史记录:从工具菜单上点击历史记录时访问的页面,或者从地址栏直接输入 chrome://history
  • 新标签页:当创建新标签的时候访问的页面,或者从地址栏直接输入 chrome://newtab
  • 书签:浏览器的书签,或者直接输入 chrome://bookmarks

注意:

  • 一个扩展只能替代一个页面;
  • 不能替代隐身窗口的新标签页;
  • 网页必须设置title,否则用户可能会看到网页的URL,造成困扰;
"chrome_url_overrides":

{

"newtab": "newtab.html", // 新标签页

"history": "history.html", // 历史记录页

"bookmarks": "bookmarks.html" // 书签页

}

5.devtools(开发者工具)

3.png

是的,Chrome允许插件在开发者工具(devtools)上动手脚,主要表现在:

  • 自定义一个和多个和ElementsConsoleSources等同级别的面板;
  • 自定义侧边栏(sidebar),目前只能自定义Elements面板的侧边栏;

其实每打开一个开发者工具窗口,都会创建devtools页面的实例,F12窗口关闭,页面也随着关闭,所以devtools页面的生命周期和devtools窗口是一致的。devtools页面可以访问一组特有的DevTools API以及有限的扩展API,这组特有的DevTools API只有devtools页面才可以访问,background都无权访问,这些API包括:

  • chrome.devtools.panels:面板相关;
  • chrome.devtools.inspectedWindow:获取被审查窗口的有关信息;
  • chrome.devtools.network:获取有关网络请求的信息;

大部分扩展API都无法直接被DevTools页面调用,但它可以像content-script一样直接调用chrome.extensionchrome.runtimeAPI,同时它也可以像content-script一样使用Message交互的方式与background页面进行通信。

尝试创建一个devtools扩展:

首先,要针对开发者工具开发插件,需要在清单文件声明如下:

{

// 只能指向一个HTML文件,不能是JS文件

"devtools_page": "devtools.html"

}

这个devtools.html里面一般什么都没有,就引入一个js:

<!DOCTYPE html>

<html>

<head></head>

<body>

<script type="text/javascript" src="js/devtools.js"></script>

</body>

</html>

再来看看这个js,感觉是不是有点多此一举。

// 创建自定义面板,同一个插件可以创建多个自定义面板

// 几个参数依次为:panel标题、图标(其实设置了也没地方显示)、要加载的页面、加载成功后的回调

chrome.devtools.panels.create('MyPanel', 'img/icon.png', 'mypanel.html', function(panel)

{

console.log('自定义面板创建成功!'); // 注意这个log一般看不到

});

// 创建自定义侧边栏

chrome.devtools.panels.elements.createSidebarPane("Images", function(sidebar)

{

// sidebar.setPage('../sidebar.html'); // 指定加载某个页面

sidebar.setExpression('document.querySelectorAll("img")', 'All Images');

// 通过表达式来指定

//sidebar.setObject({aaa: 111, bbb: 'Hello World!'}); // 直接设置显示某个对象

});

6. 其他补充

虽然在backgroundpopup无法直接访问页面dom元素,但是可以通过chrome.tabs.executeScript 来执行脚本。

示例

manifest.json配置:

{

"name": "动态JS注入演示",

"permissions": [

"tabs", "http://*/*", "https://*/*"

],

}
// 动态执行JS代码

chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});

// 动态执行JS文件

chrome.tabs.executeScript(tabId, {file: 'example.js'});

6.1 . 查看已安装插件路径

在地址栏输入chrome://version 找到chrome安装路径:

4.png

然后打开Extensions文件夹

5.png

每一个插件被放在以插件ID为名的文件夹里面,想要学习某个插件的某个功能是如何实现的,看人家的源码是最好的方法了。

6.png

总结

  1. Chrome插件本身有机制控制,不会无限制的开放很多权限给你。
  2. 页面的DOM元素时可以操作的,Chrome插件是和宿主页面之间是共享DOM对象,但是不共享Javascript上下文容器,也就是说在插件中你不能也无权执行宿主页面中的JS脚本。这个和跨域限制同理。
  3. 既然可以操作DOM元素,那么可以通过插入
  4. 基于第3点,不要随意的使用别人提供的插件,尽量从官方市场下载经过安全审核的。除非你确认这个插件是可信的或仅限于内部使用的。
  5. 除了操作dom和js,再补充点吧,cookie也是能够被插件所操作的(有很多cookie工具插件),而且估计最大的危险就是cookie被操作了。

相关资料参考

推荐查看官方文档,虽然是英文,但是全且新,国内的中文资料都比较旧:

招贤纳士

小影前端团队,是一个年轻、活动且富有创造力的团队,隶属于小影科技开发中心,Base 在风景如画的杭州。团队在日常的业务对接之外,还在互动技术、图像渲染、跨端技术、工程化平台、性能体验、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,并一直保持好奇,持续探索前端技术。 如果你想大胆自信的表达你的想法;如果你想有个机会实现自己的想法;如果你希望落地的想法有一个团队来支撑;如果你愿意跟一群积极向上的小伙伴干一些持续迭代自己,持续提升技术能力的事;如果你相信相信的力量;那就加入我们吧!快戳链接>>>quvideo.jobs.feishu.cn/s/eV8jfyf