1、谷歌扩展介绍

  1. API文档官网:https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/
  2. 主要使用技术栈为,html、css、js
  3. chrome扩展可以使用javascript APi还有chrome Api
  4. 参考了这位大佬的文章和自己实际使用问题,【干货】Chrome插件(扩展)开发全攻略-好记的博客
  5. 主要构成
    1. manifest.json,扩展的清单是唯一必须具有特定文件名的必需文件,清单记录了重要的元数据、定义资源、声明权限并标识要在后台和页面上运行的文件。
    2. service,扩展服务工作线程处理并侦听浏览器事件。事件有多种类型,例如导航到新页面、删除书签或关闭选项卡。它可以使用所有Chrome API,但不能直接与网页内容交互;
    3. Content内容脚本在网页上下文中执行 Javascript。他们还可以读取和修改所注入页面的DOM 。内容脚本只能使用Chrome API的一部分,但可以通过与扩展服务工作线程交换消息来间接访问其余部分。
    4. pages,扩展可以包括各种 HTML 文件,例如弹出窗口选项页面其他 HTML 页面。所有这些页面都可以访问Chrome API
  1. Chrome扩展是一个用Web技术开发、用来增强浏览器功能的软件,它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包.
  2. 提供了
  • 书签控制;
  • 下载控制;
  • 窗口控制;
  • 标签控制;
  • 网络请求控制,各类事件监听;
  • 自定义原生菜单;
  • 完善的通信机制;
  • 等等

2、各种文件前置介绍

manifest.json文件

matches的匹配模式

  1. 常用的特殊例子
    1. <all_urls>:它匹配以允许的方案开头的任何 URL,包括有效模式下列出的任何模式。这是一个广泛的主机权限,意味着 Chrome 网上应用店审核可能需要更长时间
    2. file:///:允许您的扩展在本地文件上运行。它没有主机,需要用户手动[授予访问权限][permissions-allow-access]。
  1. 匹配模式是一个以允许的方案开头、以 分隔://、后跟主机和路径的 URL。它可以包含通配符 ( *) 字符。大多数匹配模式由三部分组成:<scheme>://<host>/<path>
    1. scheme:必须包含有效的方案:'http'、'https'、'file'、'ftp'或'urn'。通配符*仅匹配http或https。
    2. host:主机名 ( www.example.com)、在*主机名之前匹配子域 ( *.example.com) 或只是一个通配符*。
    3. path:必须以 开头/并且存在。如果单独存在,则将其视为/*。例如:/*、/foo*、 或/foo/bar。每个 ' *' 匹配 0 个或多个字符。

content-scripts

  1. 所谓content-scripts,其实就是Chrome插件中向页面注入脚本的一种形式(虽然名为script,其实还可以包括css的),借助content-scripts我们可以实现通过配置的方式轻松向指定页面注入JS和CSS(如果需要动态注入,可以参考下文),最常见的比如:广告屏蔽、页面CSS定制,等等。 简单来说就是页面用到的js就在这里面引入。
  2. content-scripts和原始页面共享DOM,但是不共享JS,如要访问页面JS(例如某个JS变量),只能通过injected js来实现。content-scripts不能访问绝大部分chrome.xxx.api,除了下面这4种:
    • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
    • chrome.i18n
    • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
    • chrome.storage

background

  1. 后台(姑且这么翻译吧),是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面。
  2. background的权限非常高,几乎可以调用所有的Chrome扩展API(除了devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS。

event-pages

  1. 这里顺带介绍一下event-pages,它是一个什么东西呢?鉴于background生命周期太长,长时间挂载后台可能会影响性能,所以Google又弄一个event-pages,在配置文件上,它与background的唯一区别就是多了一个persistent参数:
  2. 它的生命周期是:在被需要时加载,在空闲时被关闭,什么叫被需要时呢?比如第一次安装、插件更新、有content-script向它发送消息,等等。
  3. 除了配置文件的变化,代码上也有一些细微变化,个人这个简单了解一下就行了,一般情况下background也不会很消耗性能的。

popup

  1. popup是点击browser_action或者page_action图标时打开的一个小窗口网页,焦点离开网页就立即关闭,一般用来做一些临时性的交互。
  2. popup可以包含任意你想要的HTML内容,并且会自适应大小。可以通过default_popup字段来指定popup页面,也可以调用setPopup()方法。
  3. 需要特别注意的是,由于单击图标打开popup,焦点离开又立即关闭,所以popup页面的生命周期一般很短,需要长时间运行的代码千万不要写在popup里面。
  4. chrome.extension.getBackgroundPage()获取background的window对象。
  5. 最大为800 * 600,设置过大宽高会出现滚动条

injected-script

  1. 这里的injected-script是我给它取的,指的是通过DOM操作的方式向页面注入的一种JS。为什么要把这种JS单独拿出来讨论呢?又或者说为什么需要通过这种方式注入JS呢?
  2. 这是因为content-script有一个很大的“缺陷”,也就是无法访问页面中的JS,虽然它可以操作DOM,但是DOM却不能调用它,也就是无法在DOM中通过绑定事件的方式调用content-script中的代码(包括直接写onclick和addEventListener2种方式都不行),但是,“在页面上添加一个按钮并调用插件的扩展API”是一个很常见的需求,那该怎么办呢?其实这就是本小节要讲的。

homepage_url

  1. 开发者或者插件主页设置,一般会在如下2个地方显示:

omnibox

  1. omnibox是向用户提供搜索建议的一种方式。先来看个gif图以便了解一下这东西到底是个什么鬼:
  2. 注册某个关键字以触发插件自己的搜索建议界面,然后可以任意发挥了。
// manifest.json
{
	// 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字
	"omnibox": { "keyword" : "go" },
}
// background.js
// omnibox 演示
chrome.omnibox.onInputChanged.addListener((text, suggest) => {
	console.log('inputChanged: ' + text);
	if(!text) return;
	if(text == '美女') {
		suggest([
			{content: '中国' + text, description: '你要找“中国美女”吗?'},
			{content: '日本' + text, description: '你要找“日本美女”吗?'},
			{content: '泰国' + text, description: '你要找“泰国美女或人妖”吗?'},
			{content: '韩国' + text, description: '你要找“韩国美女”吗?'}
		]);
	}
	else if(text == '微博') {
		suggest([
			{content: '新浪' + text, description: '新浪' + text},
			{content: '腾讯' + text, description: '腾讯' + text},
			{content: '搜狐' + text, description: '搜索' + text},
		]);
	}
	else {
		suggest([
			{content: '百度搜索 ' + text, description: '百度搜索 ' + text},
			{content: '谷歌搜索 ' + text, description: '谷歌搜索 ' + text},
		]);
	}
});

// 当用户接收关键字建议时触发
chrome.omnibox.onInputEntered.addListener((text) => {
	console.log('inputEntered: ' + text);
	if(!text) return;
	var href = '';
	if(text.endsWith('美女')) href = 'http://image.baidu.com/search/index?tn=baiduimage&ie=utf-8&word=' + text;
	else if(text.startsWith('百度搜索')) href = 'https://www.baidu.com/s?ie=UTF-8&wd=' + text.replace('百度搜索 ', '');
	else if(text.startsWith('谷歌搜索')) href = 'https://www.google.com.tw/search?q=' + text.replace('谷歌搜索 ', '');
	else href = 'https://www.baidu.com/s?ie=UTF-8&wd=' + text;
	openUrlCurrentTab(href);
});
// 获取当前选项卡ID
function getCurrentTabId(callback)
{
	chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
	{
		if(callback) callback(tabs.length ? tabs[0].id: null);
	});
}

// 当前标签打开某个链接
function openUrlCurrentTab(url)
{
	getCurrentTabId(tabId => {
		chrome.tabs.update(tabId, {url: url});
	})
}


 

3、Chrome的8种展示形式

1. browserAction(浏览器右上角)

    1. 通过配置browser_action可以在浏览器的右上角增加一个图标,一个browser_action可以拥有一个图标,一个tooltip,一个badge和一个popup。

2. 图标:

    1. browser_action图标推荐使用宽高都为19像素的图片,更大的图标会被缩小,格式随意,一般推荐png,可以通过manifest中default_icon字段配置,也可以调用setIcon()方法。

3. tooltip

    1. 修改browser_action的manifest中default_title字段,或者调用setTitle()方法。

4. badge

    1. 所谓badge就是在图标上显示一些文本,可以用来更新一些小的扩展状态提示信息。因为badge空间有限,所以只支持4个以下的字符(英文4个,中文2个)。badge无法通过配置文件来指定,必须通过代码实现,设置badge文字和颜色可以分别使用setBadgeText()和setBadgeBackgroundColor()。
    2. chrome.browserAction.setBadgeText({text: 'new'});
      chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 255]});
    3. 上面api为2版本,3版本下最下方常用API

5. pageAction(地址栏右侧)

    1. 所谓pageAction,指的是只有当某些特定页面打开才显示的图标,它和browserAction最大的区别是一个始终都显示,一个只在特定情况才显示。
    2. chrome.pageAction.show(tabId) 显示图标; chrome.pageAction.hide(tabId) 隐藏图标;
    3. 而新版的Chrome更改了这一策略,pageAction和普通的browserAction一样也是放在浏览器右上角,只不过没有点亮时是灰色的,点亮了才是彩色的,灰色时无论左键还是右键单击都是弹出选项:
// 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()]
			}
		]);
	});
});

6. 右键菜单

  1. 新版菜单需要注意的是添加id,和点击事件要用chromeApi,并且要知道id。
  2. 通过开发Chrome插件可以自定义浏览器的右键菜单,主要是通过chrome.contextMenusAPI实现,右键菜单可以出现在不同的上下文,比如普通页面、选中的文字、图片、链接,等等,如果有同一个插件里面定义了多个菜单,Chrome会自动组合放到以插件名字命名的二级菜单里,如下:
  3. 完整API:这里只是简单列举一些常用的,完整API参见https://developer.chrome.com/extensions/contextMenus
  4. 效果图:

mainfest.json
{"permissions": ["contextMenus"]}

background.js
// chrome.contextMenus.create({
//     title: "原神!", //菜单的名称
//     id: '10',//一级菜单的id
//     contexts: ['page'], // page表示页面右键就会有这个菜单,如果想要当选中文字时才会出现此右键菜单,用:selection
// });

// chrome.contextMenus.create({
//     title: '百度', //菜单的名称
//     id: '1101',//二级菜单的id
//     parentId: '10',//表示父菜单是“右键快捷菜单”
//     contexts: ['page'],
// });


// chrome.contextMenus.create({
//     title: 'CSDN', //菜单的名称
//     id: '1102',
//     parentId: '10',//表示父菜单是“右键快捷菜单”
//     contexts: ['page'],
// });


// chrome.contextMenus.create({
//     title: '百度新闻',
//     id: '1103',
//     parentId: '10',//表示父菜单是“右键快捷菜单”
//     contexts: ['page'],
// });

chrome.contextMenus.create({
    id: '10',//一级菜单的id
	title: "测试右键菜单",
    contexts: ['page']
});

chrome.contextMenus.onClicked.addListener(function(info, tab) {
    if (info.menuItemId == "10") {
      // 处理点击事件的代码
      console.log('我莱纳!!!!!!!!');
    }
});
{"permissions": ["contextMenus", "tabs"]}

// background.js
chrome.contextMenus.create({
	title: '使用度娘搜索:%s', // %s表示选中的文字
	contexts: ['selection'], // 只有当选中文字时才会出现此右键菜单
	onclick: function(params)
	{
		// 注意不能使用location.href,因为location是属于background的window对象
		chrome.tabs.create({url: 'https://www.baidu.com/s?ie=utf-8&wd=' + encodeURI(params.selectionText)});
	}
});
chrome.contextMenus.create({
	type: 'normal', // 类型,可选:["normal", "checkbox", "radio", "separator"],默认 normal
	title: '菜单的名字', // 显示的文字,除非为“separator”类型否则此参数必需,如果类型为“selection”,可以使用%s显示选定的文本
	contexts: ['page'], // 上下文环境,可选:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"],默认page
	onclick: function(){}, // 单击时触发的方法
	parentId: 1, // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单
	documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些页面显示此右键菜单
});
// 删除某一个菜单项
chrome.contextMenus.remove(menuItemId);
// 删除所有自定义右键菜单
chrome.contextMenus.removeAll();
// 更新某一个菜单项
chrome.contextMenus.update(menuItemId, updateProperties);
manifest.json 3
{
    "manifest_version": 3,
    "name": "原神,启动!",
    "version": "1.0.1",
    "action": {
        "default_icon": {},
        "default_title": "我是原神玩家",
        "default_popup": "html/popup.html"
    },
    "content_scripts": [
		{
			"matches": ["<all_urls>"],
			"js": ["js/background.js", "js/content.js", "js/popup.js"],
			"run_at": "document_start"
		}
    ],
    "permissions": [
        "contextMenus"
    ],
    "background": {
        "service_worker": "js/background.js"
    }
}

background.js
// 创建
chrome.contextMenus.create({
    "id": "myContextMenu",
    "title": "My Context Menu",
    "contexts": ["page", "selection"],
    type: 'normal'
}); 
// 他的点击事件
chrome.contextMenus.onClicked.addListener(function(info, tab) {
    if (info.menuItemId == "myContextMenu") {
      console.log("Clicked on My Context Menu.");
      // 执行其他操作
    }
  });

7. 桌面菜单

  1. Chrome提供了一个chrome.notificationsAPI以便插件推送桌面通知,暂未找到chrome.notifications和HTML5自带的Notification的显著区别及优势。
// 清单
{
  "permissions": [
    "notifications"
  ],
}

// background.js
chrome.notifications.create('unique_notification_id', {
  type: 'basic',
  iconUrl: 'icon.png', // 通知图标的 URL
  title: '通知标题',
  message: '通知内容'
}, function(notificationId) {
  // 通知创建后的回调函数
});

chrome.notifications.onClicked.addListener(function(notificationId) {
  // 在这里处理通知被点击时的操作
});

chrome.notifications.clear('unique_notification_id', function(wasCleared) {
  // 在通知被清除后执行的回调函数
});

3、工作使用问题

4、知识

4.1、关于packground.js或者别的前端包的版本

  1. 规则
    1. 整数必须介于 0 和 65535 之间(包含 0 和 65535)。
    2. 非零整数不能以 0 开头。例如,032 无效,因为它以零开头。
    3. 它们不能全为零。例如,0和0.0.0.0无效,而0.1.0.0有效
  1. 实例
    1. "version": "1"
    2. "version": "1.0"
    3. "version": "2.10.2"
    4. "version": "3.1.2.4567"

4.2、content.js/background.js/popup.js他们三个之间的关系

  1. content-scripts和原始页面共享DOM,但是不共享JS,如要访问页面JS(例如某个JS变量),只能通过injected js来实现。
  2. content-scripts不能访问绝大部分chrome.xxx.api,除了下面这4种:
    1. chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
    2. chrome.i18n
    3. chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
    4. chrome.storage

4.3、权限对比

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.4、通信方式


injected-script

content-script

popup-js

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

4.4.1、详细介绍

  1. popup和background
    1. popup可以直接调用background中的JS方法,也可以直接访问background的DOM:
  1. popup或者bg向content主动发送消息
  1. content.js向background发送信息
    1. content_scripts向popup主动发消息的前提是popup必须打开!否则需要利用background作中转
    2. 如果background和popup同时监听,那么它们都可以同时收到消息,但是只有一个可以sendResponse,一个先发送了,那么另外一个再发送就无效;

4.5、调试方法

JS类型

调试方式

图片说明

injected script

直接普通的F12即可

懒得截图

content-script

打开Console,如图切换

popup-js

popup页面右键审查元素

background

插件管理页点击背景页即可

devtools-js

暂未找到有效方法

-

4.6、长连接和短连接

  1. 其实上面已经涉及到了,这里再单独说明一下。Chrome插件中有2种通信方式,一个是短连接(chrome.tabs.sendMessage和chrome.runtime.sendMessage),一个是长连接(chrome.tabs.connect和chrome.runtime.connect)。短连接的话就是挤牙膏一样,我发送一下,你收到了再回复一下,如果对方不回复,你只能重新发,而长连接类似WebSocket会一直建立连接,双方可以随时互发消息。
// manifest.json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0",
  "background": {
    "service_worker": "background.js"
  },
  "permissions": [
    "activeTab"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "images/icon16.png",
      "48": "images/icon48.png",
      "128": "images/icon128.png"
    }
  }
}

// background.js
chrome.runtime.onConnect.addListener(function(port) {
  console.assert(port.name === "myPortName");
  console.log("Connected to port: " + port.name);

  // 在这里可以处理与其他扩展页面或内容脚本的通信
  port.onMessage.addListener(function(msg) {
    console.log("Received message from connected script: " + msg);
  });

  // 可以通过端口发送消息到连接的脚本
  port.postMessage("Hello from background!");
});

// 在扩展页面或内容脚本中
const port = chrome.runtime.connect({ name: "myPortName" });

// 发送消息到后台脚本
port.postMessage("Hello from the extension!");

// 处理从后台脚本接��到的消息
port.onMessage.addListener(function(msg) {
  console.log("Received message from background script: " + msg);
});

4.7、国际化

5、常用API

  1. 清单属性介绍
{
	// 清单文件的版本,这个必须写,而且必须是2
	"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":
	{
		// 2种指定方式,如果指定JS,那么会自动生成一个背景页
		"page": "background.html"
		//"scripts": ["js/background.js"]
	},
  // 打开扩展插件时展示的页面
  "action": {
    "default_popup": "page/background.html"
  }
	// 浏览器右上角图标设置,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"
}
{
  	"manifest_version": 3,  // 一个整数,指定当前包的版本目前为3后续可能变为4

    "name": "My Extension", // 扩展的名称

    "version": "1.0.1", // 当前清单的版本(自研版本)

    "action": {
        "default_icon": {              // 图标是工具栏按钮中使用的主要图像,因为设备不同所以鼓励多重尺寸图标
            "16": "images/icon16.png",   // optional
            "24": "images/icon24.png",   // optional
            "32": "images/icon32.png"    // optional
        },
        "default_title": "Click Me",   // 用户鼠标悬停时出现的工具提示或者标题还可以使用action.setTitle()设置
        "default_popup": "popup.html",  // 用户开启扩展时出现的扩展界面
    },

    "default_locale": "en", // 定义支持多个区域设置的扩展默认语言

    "description": "A plain text description", // 描述扩展的纯文本字符串不超过132个字符

    "icons": { 
        "16": "icon16.png", // 代表扩展或主题的一个或多个图标。您应该始终提供 128x128 的图标
        "32": "icon32.png",
        "48": "icon48.png",
        "128": "icon128.png"
    },

     "permissions": [ // 扩展的权限,涉及到你能不能使用的api,详情见官方文档
        "activeTab",
        "contextMenus",
        "storage"
     ],

  		"background": { // 绑定后台需要执行的js文件
        "service_worker": "service-worker.js",
        "type": "module" || "classic"
      },
    	// "module":表示后台页是一个模块化的JavaScript文件,可以使用ES6模块、import语法等
      // "classic":表示后台页是一个传统的JavaScript文件,不能使用ES6模块、import语法等。使用传统的后台页可以兼容更多的浏览器,但可能会使扩展的代码变得混乱。


     "content_scripts": [ // 该键指定每次打开与特定URL 模式"content_scripts"匹配的页面时要使用的静态加载的 JavaScript 或 CSS 文件。
    		{
    			//"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"]
    		}
    ],

    "storage": { // 与local和sync存储区域不同,managed存储区域要求其结构声明为JSON Schema,并由 Chrome 严格验证。
        "managed_schema": "schema.json"
    }, 

    "short_name": "Short Name", // 定义扩展名称的简短版本的可选清单键

    "author": {
        "email": "user@example.com" // 一个可选的清单键,它采用带有“电子邮件”键的对象,为坐着的电子邮件地址
    },

    "key":"asdsadasdasdasd", // 当开发过程中加载扩展或主题时,此值维护其唯一 ID

    "omnibox": { "keyword" : "aaron" }, // 多功能框 API 允许您使用 Google Chrome 的地址栏(也称为多功能框)注册关键字。
    
		"chrome_settings_overrides": { // 可选,用于自定义浏览器的一些设置和行为,简单来说就是让不让你修改浏览器
        "search_provider": {
        "name": "My Search Engine",
        "keyword": "mysearch",
        "search_url": "https://example.com/search?q={searchTerms}"
        }
    },

    "chrome_url_overrides" : { // 可选,扩展程序可以使用 HTML 覆盖页面来替换 Google Chrome 通常提供的页面。
        "PAGE_TO_OVERRIDE": "myPage.html"
    },

    "commands": { // 可选,命令 API 允许扩展开发人员定义特定命令,并将它们绑定到默认组合键。
        "run-foo": {
        "suggested_key": {
            "default": "Ctrl+Shift+Y",
            "mac": "Command+Shift+Y"
        },
        "description": "Run \"foo\" on the current page."
        },
        "_execute_action": {
        "suggested_key": {
            "windows": "Ctrl+Shift+Y",
            "mac": "Command+Shift+Y",
            "chromeos": "Ctrl+Shift+U",
            "linux": "Ctrl+Shift+J"
        }
        }
    },

    "content_security_policy": { // 可选,如果用户未在清单中定义内容安全策略,则默认属性将用于扩展页面和沙盒扩展页面。
        "extension_pages": "script-src 'self'; object-src 'self';",
        "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
    },

    "cross_origin_embedder_policy": { // 可选,清单键允许扩展为发送到扩展源的请求指定,是一个用于控制浏览器跨源嵌入策略的概念。这个策略��要用于保护用户隐私和安全,确保网站能够控制其内部内容如何被其他网站嵌入和访问。Cross-Origin-Embedder-Policycross_origin_embedder_policy (COEP) 响应标头的值。
      "value": "require-corp"
    },

    "cross_origin_opener_policy": { //  一般用不上,是一个用于控制网页间跨域访问的安全策略。
      "value": "same-origin"
    },

    "declarative_net_request" : { // 是 Chrome 浏览器的一项功能,用于帮助用户更好地管理和控制网页上的网络请求和广告。这个功能在扩展程序中广泛使用,以便用户可以自定义网页上的请求规则和过滤器,以提高隐私、安全性和性能。
        "rule_resources" : [{
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules_1.json"
        }, {
        "id": "ruleset_2",
        "enabled": false,
        "path": "rules_2.json"
        }]
    },

    // devtools页面入口,注意只能指向一个HTML文件,不能是JS文件
	"devtools_page": "devtools.html",

    "export": { // 导入资源
        // Optional list of extension IDs explicitly allowed to
        // import this Shared Module's resources.  If no allowlist
        // is given, all extensions are allowed to import it.
        "allowlist": [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
        ]
    },

    "import": [ // 可选,导出资源可供扩展内部使用
        {"id": "cccccccccccccccccccccccccccccccc"},
        {"id": "dddddddddddddddddddddddddddddddd"
        "minimum_version": "0.5" // optional
        },
    ],

     "externally_connectable": { // 可选,属性声明哪些扩展和网页可以通过runtime.connect和runtime.sendMessage连接到您的扩展。
        "ids": [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
        ...
        ],
        // If this field is not specified, no web pages can connect.
        "matches": [
        "https://*.google.com/*",
        "*://*.chromium.org/*",
        ...
        ],
        "accepts_tls_channel_id": false
    },

    "homepage_url": "https://example.com,", // 可选清单键,包含有效主页 URL 的字符串。开发人员可以选择将扩展程序的主页设置为他们的个人或公司网站。如果该参数未定义,则默认主页将是扩展程序管理页面 (chrome://extensions) 上列出的扩展程序的 Chrome Web Store 页面。
    
		"minimum_chrome_version": "107.0.5304.87", // Can also be abbreviated to "107", "107.0", or "107.0.5304", // 可选哪些chrome版本可安装扩展
}

  1. 修改扩展展示图标
    1. 划上提示文本
      1. chrome.browserAction.setTitle({ title: "My Extension Title" });
    1. 文本背景颜色
      1. chrome.browserAction.setBadgeBackgroundColor({ color: "#ff0000" });
    1. 文本内容
      1. chrome.browserAction.setBadgeText({ text: "42" });
    1. 显示图标
      1. chrome.pageAction.show(tabId)
    1. 隐藏图标
      1. chrome.pageAction.hide(tabId)
  1. 本地存储
    1. Chrome 存储 API 提供了 2 种储存区域,分别是 sync 和 local。两种储存区域的区别在于,sync 储存的区域会根据用户当前在 Chrome 上登陆的 Google 账户自动同步数据,当无可用网络连接可用时,sync 区域对数据的读写和 local 区域对数据的读写行为一致。
// 当前窗口
chrome.windows.getCurrent(function(window) {
  var windowId = window.id;
  console.log("当前窗口ID是:" + windowId);
});

// 标签页ID,{active: true, currentWindow: true}参数确保查询的是当前窗口中活动的标签
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    var currentTabId = tabs[0].id;
    console.log("当前标签ID是:" + currentTabId);
});
chrome.contextMenus.create({
	id: '123213', // 必填
  type: 'normal', // 必填,类型,可选:["normal", "checkbox", "radio", "separator"],默认 normal
	title: '菜单的名字', // 显示的文字,除非为“separator”类型否则此参数必需,如果类型为“selection”,可以使用%s显示选定的文本
	contexts: ['page'], // 上下文环境,可选:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"],默认page
	onclick: function(){}, // 单击时触发的方法
	parentId: 1, // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单
	documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些页面显示此右键菜单
  "targetUrlPatterns": ["http://example.com/*"], // 菜单项针对的URL模式,可选属性。可以是一个或多个URL模式,
  "enabled": true, // 菜单项是否可用,可选属性。默认值为true。
  "checked": false, // 菜单项是否选中,可选属性。只有当菜单项类型为"checkbox"或"radio"时才有效。
});
// 删除某一个菜单项
chrome.contextMenus.remove(menuItemId);
// 删除所有自定义右键菜单
chrome.contextMenus.removeAll();
// 更新某一个菜单项
chrome.contextMenus.update(menuItemId, updateProperties);
// 在 sync 区域存储和读取数据:
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);
});

// local 区域:
chrome.storage.local.set({key: value}, function() {
    console.log('Value is set to ' + value);
});
 
chrome.storage.local.get(['key'], function(result) {
    console.log('Value currently is ' + result.key);
});

// remove() 方法为删除数据,完整方法为:
// sync 区域
chrome.storage.sync.remove(keys, function(){
    //do something
});
 
// local 区域
chrome.storage.local.remove(keys, function(){
    //do something
});

// clear() 方法为删除所有数据,完整方法为:
// sync 区域
chrome.storage.sync.clear(function(){
    //do something
});
 
// local 区域
chrome.storage.local.clear(function(){
    //do something
});

// 监听本地存储的变化
chrome.storage.onChanged.addListener(function(changes, namespace) {
  for (let key in changes) {
    const storageChange = changes[key];
    console.log(`存储键 "${key}" 发生变化,新值为: ${storageChange.newValue}`);
  }
});
// 获取
const url = 'https://www.baidu.com';
chrome.cookies.getAll({url}, cookies => {
	console.log(cookies);
});

// 删除
const url = 'https://www.baidu.com';
const cookieName = 'userName';
chrome.cookies.remove({url, name: cookieName}, details => {});
// 获取活动标签页信息
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
  var currentTab = tabs[0];
  console.log(currentTab);
});

// 打开新标签页
chrome.tabs.create({ url: 'https://www.example.com' });

// 更新标签页信息
chrome.tabs.update(tabId, { url: 'https://www.newurl.com' });

// 移除标签页
chrome.tabs.remove(tabId);

// 监听标签页事件
chrome.tabs.onCreated.addListener(function(tab) {
  console.log('标签页被创建:', tab);
});

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
  console.log('标签页被更新:', tab);
});

chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
  console.log('标签页被移除:', tabId);
});
// 监听网络请求
chrome.webRequest.onBeforeRequest.addListener(
  function(details) {
    console.log("请求被发出:", details);
  },
  { urls: ["<all_urls>"] }
);

// 阻止网络请求
chrome.webRequest.onBeforeRequest.addListener(
  function(details) {
    return { cancel: true };
  },
  { urls: ["*://example.com/*"] },
  ["blocking"]
);

// 修改请求头内容
chrome.webRequest.onBeforeSendHeaders.addListener(
  function(details) {
    for (var i = 0; i < details.requestHeaders.length; i++) {
      if (details.requestHeaders[i].name === "User-Agent") {
        details.requestHeaders[i].value = "My Custom User Agent";
        break;
      }
    }
    return { requestHeaders: details.requestHeaders };
  },
  { urls: ["<all_urls>"] },
  ["blocking", "requestHeaders"]
);
// 创建新窗口
chrome.windows.create({
  url: 'https://www.example.com',
  type: 'popup',
  width: 800,
  height: 600
});

// 获取当前窗口
chrome.windows.getCurrent({}, function(windowInfo) {
  console.log('当前窗口信息:', windowInfo);
});

// 获取所有窗口
chrome.windows.getAll({}, function(windowList) {
  console.log('所有窗口信息:', windowList);
});

// 关闭窗口
chrome.windows.remove(windowId, function() {
  console.log('窗口已关闭');
});

// 切换标签页
chrome.tabs.update(tabId, { active: true });
// 创建右键菜单
chrome.contextMenus.create({
  title: "示例菜单项",
  contexts: ["all"], // 上下文,例如 "all" 表示在所有页面上都显示菜单项
  onclick: function(info, tab) {
    // 当用户点击菜单项时执行的操作
    console.log("右键菜单项被点击了!");
    console.log("点击的页面信息:", info);
    console.log("当前标签页信息:", tab);
    // ��这里执行您的自定义逻辑
  }
});

// 更新右键菜单
// 获取要更新的菜单项的标识符
var menuItemId = "myMenuItemId"; // 假设您的菜单项有一个标识符

// 更新菜单项的标题
chrome.contextMenus.update(menuItemId, {
  title: "新的菜单项标题"
});

// 启用或禁用菜单项
chrome.contextMenus.update(menuItemId, {
  enabled: true // 或者 false 来启用或禁用菜单项
});

// 删除右键菜单
var menuItemId = "myMenuItemId"; // 要删除的菜单项的标识符
chrome.contextMenus.remove(menuItemId);
// 设置浏览器动作按钮的图标
chrome.action.setIcon({ path: 'icon.png' });

// 设置浏览器动作按钮的标题
chrome.action.setTitle({ title: 'My Extension' });

// 设置浏览器动作按钮上的文本内容
chrome.action.setBadgeText({ text: 'Hello' });

// 设置浏览器动作按钮上文本的背景颜色
chrome.action.setBadgeBackgroundColor({ color: [255, 0, 0, 255] });

// 监听浏览器动作按钮的点击事件
chrome.action.onClicked.addListener(function(tab) {
  console.log('Button clicked in tab:', tab);
});

6、遇到问题

6.1、只存在一个窗口,关闭时出现chrome.storage.local.get无法存值

  1. 他就是存不了这种方法行不通,如果存在多个窗口可以存,我猜测多个窗口扩展并不会真的停止运行,但是窗口全部关闭时会从而导致了无法存值。
  2. 例如我的场景:页面关闭时停止直播,我监听窗口关闭API然后local.get存储一个值,然后监听窗口开启API里面添加停止直播的方法,完成窗口关闭停止直播。
    1. 但是很明显就是存不上,所以转而在开启直播时直接存一个开启直播窗口的id,然后重新打开窗口使用获取全部窗口API,循环判断是否存在,不存在那就说明关闭了,从而暂停直播

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐