首页 > 技术文章 > 浏览器扩展开发

lihuijuan 2021-01-12 20:50 原文

一、浏览器扩展可以:

1、自定义用户界面:

在工具栏上添加图标、工具提示、徽章和弹出窗口:Browser Actions
在特定页面将图标显示在工具栏上:Page Actions
添加触发扩展操作的键盘快捷键:Commands
添加右键菜单选项:Context Menus
添加关键字功能到地址栏:Omnibox
自定义新标签页,书签页,或历史页面:Chrome url overrides
2、构建扩展工具

二、manifest.json文件配置

{
    // 清单文件的版本,这个必须写,目前写3会报错
    "manifest_version": 2,
    // 插件的名称
    "name": "demo",
    // 插件的版本
    "version": "1.0.0",
    // 插件描述-在扩展程序管理页面显示
    "description": "简单的Chrome扩展demo",
    // 图标-优先级比较低-需要用到图标的地方,如扩展管理页面、权限警告,图标等
    "icons":
    {
        "16": "img/icon.png",
        "48": "img/icon.png",
        "128": "img/icon.png"
    },
    // 会一直常驻的后台JS或后台页面
    //是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,
    //随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面。

  //  background的权限非常高,几乎可以调用所有的Chrome扩展API(除了devtools),
  // 而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置
    "background":
    {
        // 2种指定方式,只能选择1种,如果指定JS,那么会自动生成一个背景页
        "page": "background.html",
        //"scripts": ["js/background.js"],
        // persistent:true, // 默认是true   是否常驻后台
    },
    // 浏览器右上角图标设置,browser_action、page_action必须选一
    "browser_action": 
    {
        "default_icon": "img/icon.png",
        // 图标悬停时的标题,可选
        "default_title": "这是一个示例Chrome插件",
            //点击图标后弹出的菜单
        "default_popup": "popup.html"
    },
    // 当某些特定页面打开才显示的图标-其他页面会灰色显示,调用declarativeContent.ShowPageAction显示
    /*"page_action":
    {
        "default_icon": "img/icon.png",
        "default_title": "我是pageAction",
        "default_popup": "popup.html"
    },*/
    // 需要直接注入页面的JS 
    // Content script脚本是指能够在浏览器已经加载的页面内部运行的javascript脚本。
    // 可以将content script看作是网页的一部分, *是通配符
    // 而不是它所在的扩展的一部分。
    "content_scripts": 
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "<all_urls>" 表示匹配所有地址
            "matches": ["<all_urls>"],
            // 多个JS按顺序注入, 一块注入的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"]
        }
    ],
    // 权限申请-申请之后才能调用相应的api
    "permissions":
    [
        "contextMenus", // 右键菜单
        "tabs", // 标签
        "notifications", // 通知
        "webRequest", // web请求
        "webRequestBlocking",
        "storage", // 插件本地存储
     "decalativeContent",// 
        "cookies", // cookies
        "http://*/*", // 可以通过executeScript或者insertCSS访问的网站
        "https://*/*" // 可以通过executeScript或者insertCSS访问的网站
    ],
    // content-script有一个很大的“缺陷”,通过插件新增的dom元素没法调用content-script中的代码
  // content-scripts和原始页面共享DOM,但是不共享JS,如要访问页面JS(例如某个JS变量),只能通过injected js来实现
// inject.js与页面中的js没有区别,inject.js与content_scriptcontent_script.js通过h5的postMessge进行通信 // inject.js中一般用来做发送消息,做逻辑转换,content_script中进行业务处理 // 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的 inject-script "web_accessible_resources": ["js/inject.js",'image/test.png'], // 插件主页,这个很重要,不要浪费了这个免费广告位 "homepage_url": "https://www.baidu.com", // 覆盖浏览器默认页面 "chrome_url_overrides": { // 覆盖浏览器默认的新标签页 "newtab": "newtab.html", // 历史页 "history": "history.html", // 书签页 "bookmarks": "bookmarks.html" }, //选项页--一般是让用户对扩展的功能有更多的控制 "options_page": "options.html",// Chrome40以前的插件配置页写法 "options_ui": // // Chrome40以后的插件配置页写法,如果2个都写,新版Chrome只认后面这一个 { "page": "options.html", // 添加一些默认的样式,推荐使用
"chrome_style": true,
     "open_in_tab":false // 默认是false 弹窗的形式打开,不新开页面
}, // 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字 "omnibox": { "keyword" : "go" }, // 默认语言 "default_locale": "zh_CN", // devtools页面入口,注意只能指向一个HTML文件,不能是JS文件 "devtools_page": "devtools.html",   "commands": { // 设置一些快捷键 "_execute_browser_action": { "suggested_key": { "default": "Ctrl+Shift+F", "mac": "MacCtrl+Shift+F" }, "description": "Opens popup.html" } } } Chrome插件的JS主要可以分为这5类:injected script、content-script、popup js、background js和devtools js JS种类 可访问的API DOM访问情况 JS访问情况 直接跨域 injected script 和普通JS无任何差别,不能访问任何扩展API 可以访问 可以访问 不可以 content script 只能访问 extension、runtime等部分API 可以访问 不可以 不可以 popup js 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以 background js 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以 devtools js 只能访问 devtools、extension、runtime等部分API 可以 可以 不可以

三、4种script:

1、background script---背景脚本

可以通过: "background":{ "script": ["background.js"]} 引入,也可以通过 "background":{"page": "background.html"}   再html文件中引入
可以访问巨大部分的api 除了devtools,不能直接访问页面上的dom,是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面,永远看不到它的界面,只能调试它的代码
2、popup.js 
可以通过“browser_action”:{ "default_popup": "popup.html"}  或者"page_action":{"default_popup": "popup.html" }中popup.html中引入

可以访问大部分API,除了devtools系列,不能直接访问页面上的dom
3、content script
通过"content_scripts":[ { "js": [ "content.js" ] }] 引入,可以访问extension、runtime、i18n、storage这四种API
content-scripts和原始页面共享DOM,但是不共享JS,content-scripts可以直接操作页面dom,但是dom不能调用它,因此通过插件增加的dom通过绑定事件的方式调用js

4、inject.js 
通过DOM操作的方式向页面注入,在content-script中通过DOM方式向页面注入inject-script
可以直接操作页面上的dom,包括插件增加上去的dom,与页面上的js无差别,不能访问任何API

window.onload = function () {
  const script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.src = chrome.extension.getURL('js/inject.js');
   script.onload = function() {
        // 放在页面不好看,执行完后移除掉
        this.parentNode.removeChild(this);
    };
  document.body.appendChild(script);
};

 同时需要在manifest中设置"web_accessible_resources": ["js/inject.js"], // 普通页面能够直接访问的插件资源列表
四、script之间的通信的方式:

background.js
  ->popup.js:在bg.js中通过chrome.extension.getViews({type:'popup'}) // 返回数组

  ->content_script.js:

  • 短连接:bg.js中通过chrome.tabs.sendMessage向content_script.js中发送消息,content_script.js中通过chrome.runtime.onMessage.addListener(listener: function)接收消息
  • 长连接:bg.js中通过chrome.tabs.connect向content_script.js中发送消息,content_script.js中通过chrome.runtime.onConnect.addListener(listener: function)接收消息

popup.js
  ->background.js:在popup.js中通过chrome.extension. getBackgroundPage()获取bg对象,可以调用bg对象上的任何方法,bg.document为background页面
  ->content_script.js:

  • 短连接:popup.js中通过chrome.tabs.sendMessage向content_script.js中发送消息,content_script.js中通过chrome.runtime.onMessage.addListener(listener: function)接收消息
  • 长连接:popup.js中通过chrome.tabs.connect向content_script.js中发送消息,content_script.js中通过chrome.runtime.onConnect.addListener(listener: function)接收消息

content_script.js

  ->background.js/popup.js:

  • 短连接:content.js中通过chrome.runtime.sendMessage向background.js/popup.js中发送消息,background.js/popup.js中通过chrome.runtime.onMessage.addListener(listener: function)接收消息
  • 长连接:content.js中通过chrome.runtime.connect向background.js/popup.js中发送消息,background.js/popup.js中通过chrome.runtime.onConnect.addListener(listener: function)接收消息

  ->inject.js:window.postMessage

inject.js

  ->content_script.js:在inject.js中通过window.postMessage向content.js中发送消息,在content中通过window.addEventListener("message",callback, false|true)监听消息

 injected-scriptcontent-scriptpopup-jsbackground-js
injected-script - window.postMessage - -
content-script window.postMessage - chrome.runtime.sendMessage chrome.runtime.connect chrome.runtime.sendMessage chrome.runtime.connect
popup-js - chrome.tabs.sendMessage chrome.tabs.connect - chrome.extension. getBackgroundPage()
background-js - chrome.tabs.sendMessage chrome.tabs.connect chrome.extension.getViews -
devtools-js chrome.devtools. inspectedWindow.eval - chrome.runtime.sendMessage chrome.runtime.sendMessage

content_scripts向popup主动发消息的前提是popup必须打开!否则需要利用background作中转;
五、chrome API

1、chrome.extension
它支持在扩展及其内容脚本之间或扩展之间交换消息
方法:

chrome.extension.getBackgroundPage(): Window // 获取后台页对象
chrome.extension.getExtensionTabs(windowId: number): Window[]
chrome.extension.getURL(path: string): string // 将扩展中的相对路径转换为绝对路径
chrome.extension.sendRequest(extensionId?: string, request: any, responseCallback: function) // 向扩展内的其他侦听器发送请求
事件:
chrome.extension.onRequest.addListener(listener: function) // 监听请求

2、chrome.runtime

使用chrome.runtime API来检索后台页面,返回关于清单的详细信息,并监听和响应应用程序或扩展生命周期中的事件。您还可以使用此API将url的相对路径转换为完整的url
chrome.runtime.getURL(path: string): string // 将扩展中的相对路径转换为绝对路径

chrome.runtime.onInstalled.addListener(listener: function) // 当扩展第一次安装时,当扩展更新到一个新版本时,本地调试扩展更新时,当Chrome更新到一个新版本时触发
chrome.runtime.sendMessage(extensionId?: string, message: any, options: object, responseCallback: function)
chrome.runtime.connect(extensionId: string, connectInfo: object): Port // 

chrome.runtime.onMessage.addListener(listener: function)当有通过chrome.tabs.sendMessage或chrome.runtime.sendMessage传递过来的消息时触发,
listener有三个参数request:发送过来的信息, sender:包含关于发送消息或请求的脚本上下文信息的对象, sendResponse:一个用来返回信息的函数,参数是要返回的信息

chrome.runtime.onConnect.addListener(listener: function)

 

3、chrome.tabs,需要在permissions中引入tabs 

chrome.tabs.query(queryInfo: object, callback: function) 获取具有指定属性的所有tab,如果未指定属性,则获取所有tab
chrome.tabs.executeScript(tabId?: number, details: object, callback: function) // 将代码注入到页面,details注入的内容,虽然在background和popup中无法直接访问页面DOM,但是可以通过chrome.tabs.executeScript来执行脚本,从而实现访问web页面的DOM
chrome.tabs.create(createProperties: object, callback: function) // 创建一个新的标签页tab
chrome.tabs.update(tabId?: number, updateProperties: object, callback: function) // 修改标签页的指定属性
chrome.tabs.highlight(highlightInfo: object, callback: function) // 切换标签页
chrome.tabs.sendMessage(tabId: number, message: any, options: object, responseCallback: function) // 向指定标签页的内容脚本发送一条消息,responseCallback为处理返回响应的回调函数,参数为返回内容;

chrome.runtime.onMessage.addListener监听
chrome.tabs.connect(tabId: number, connectInfo: object)runtime.Port // 返回一个可用于通信的对象,

port.onMessage.addListener/port.postMessage

有;用chrome.runtime.onConnect监听


4、chrome.storage,需要在permissions中引入storage,用于存储、检索和跟踪用户数据的更改
chrome.storage.sync可以跟随当前登录用户自动同步,这台电脑修改的设置会自动同步到其它电脑,很方便,如果没有登录或者未联网则先保存到本地,等登录了再同步至网络;

chrome.storage.onChanged.addListener(listener: function)  // 每当存储中发生任何变化时,该事件就会触发。

chrome.storage.sync.set({key: value}, function() {
console.log('Value is set to ' + value);
});同步设置一个存储

chrome.storage.sync.get(['key'], function(result) {
console.log('Value currently is ' + result.key);
});读取存储

5、chrome.declarativeContent,需要在permissions中引入declarativeContent
允许根据web页面的URL及其内容匹配的CSS选择器来显示扩展页面,而不需要获得主机许可或注入内容脚本。使用activeTab权限,以便在用户单击页面操作后能够与页面交互

var rule1 = {
  conditions: [
    new chrome.declarativeContent.PageStateMatcher({
      pageUrl: { hostEquals: 'www.google.com', schemes: ['https'] },
      css: ["input[type='password']"]
    }),
  new chrome.declarativeContent.PageStateMatcher({
css: ["video"]
})
], actions: [ new chrome.declarativeContent.ShowPageAction() ] };

 

所有的condition和action都是通过一个构造函数创建的

chrome.declarativeContent.onPageChanged.addRules([rule1]); // 添加规则

 

6、chrome.contextMenus,需要在permissions中引入contextMenus
chrome.contextMenus.create(createProperties: object, callback: function): enum // 添加右键菜单选项

 7、chrome.devtools:扩展开发者工具

 devtools.panels :创建面板/面板交互
chrome.devtools.inspectedWindow:获取被审查窗口的有关信息;
chrome.devtools.network:获取有关网络请求的信息;

chrome.devtools.panels.create(title: string, iconPath: string, pagePath: string, callback: function) // 新建一个开发者工具panel
chrome.devtools.panels.elements.createSidebarPane(title: string, callback: function) => {...}) // 给elements添加一个侧边栏,目前只能自定义Elements面板的侧边栏

 devtools.network:获取有关网络请求的信息
devtools.inspectedWindow:访问被检查的窗口的有关信息
8、chrome.notifications:通知消息
chrome.notifications.create(notificationId?: string, options: NotificationOptions, callback: function) // 新建一个桌面通知
9、chrome.windows:与浏览器窗口进行交互,可以创建、修改和重新排列窗口,permission中需要加入tabs
chrome.windows.create(createData: object, callback: function) // 创建一个新的浏览器窗口
chrome.windows.getCurrent(queryOptions?: QueryOptions, callback: function) // 获取当前窗口
chrome.windows.update(windowId: number, updateInfo: object, callback: function) // 更改窗口属性
chrome.windows.remove(windowId: number, callback: function) // 关闭窗口与其中的所有的标签页
10、chrome.webRequest:用于观察和分析请求,并拦截、阻止或修改正在运行的请求

chrome.webRequest.onBeforeRequest.addListener(listener: function) // 请求即将发生时触发
chrome.webRequest.onBeforeSendHeaders.addListener(listener: function) // 当请求即将发生且请求头已准备好时触发
chrome.webRequest.onSendHeaders.addListener(listener: function) // 在请求将被发送到服务器之前触发
chrome.webRequest.onHeadersReceived.addListener(listener: function) // 收到响应头时触发
chrome.webRequest.onCompleted.addListener(listener: function) // 当请求被成功处理时触发

推荐阅读