Key knowledge of Chrome plug-in development
This article first introduces several core knowledge points of the plug-in, and then deeply memorizes it from the perspective of actual project combat.
Introduction
Chrome Extension
) in the upper right corner of Google Chrome is actually a lower-level browser function extension. Chrome
plug-in is a software developed with Web technology to enhance browser functions. It is actually a suffix compressed package composed of HTML, CSS, JS, pictures and other resources.
Core introduction
manifest.json
This is the most important and indispensable file for a Chrome plug-in. It is used to configure all plug-in-related configurations and must be placed in the root directory. Among them, manifest_version
, name
, and version
are indispensable, and description
and icons
are recommended.
The following are some common configuration items, all with Chinese annotations. For complete configuration documents, please click here .
{
// 清单文件的版本,这个必须写,而且必须是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"]
},
// 浏览器右上角图标设置,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"
}
content-scripts
The so-called content-scripts is actually a form of injecting scripts into the page in the Chrome plug-in (although it is called script, it can also include css). With the help of content-scripts
we can easily inject JS and specific pages into the specified page through configuration. CSS (if you need dynamic injection, you can refer to the following), the most common such as: ad blocking, page CSS customization, and so on.
Sample configuration:
{
// 需要直接注入页面的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"
}
],
}
Special attention, if you do not actively specify run_at
as document_start
(default is document_idle
), the following code will not take effect:
document.addEventListener('DOMContentLoaded', function()
{
console.log('我被执行了!');
});
content-scripts
shares DOM with the original page, but does not share JS. If you want to access the page JS (for example, a certain JS variable), you can only use injected js
. content-scripts
cannot access most of chrome.xxx.api
, except for the following 4 types:
- chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
- chrome.i18n
- chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
- chrome.storage
In fact, don't be pessimistic when you see this. These APIs are enough for most of the time. If you have to call other APIs, you can also use communication to make the background call for you (the communication will be described in detail later).
background
The background (let’s translate it this way) is a resident page. Its life cycle is the longest of all types of pages in the plug-in. It opens when the browser is opened, and closes when the browser is closed, so Usually put the global code that needs to be running all the time, running on startup, and putting it in the background.
The background permission is very high, almost all Chrome extension APIs (except devtools) can be called, and it can cross domains without restriction, that is, cross domains can visit any website without requiring the other party to set CORS
.
After testing, it is not only the background, but all the chrome-extension://id/xx.html
opened directly through 0614a8e30ec448 can cross domains without restriction.
In the configuration, background
can specify a webpage page
scripts
, and Chrome will automatically generate a default webpage for this JS:
{
// 会一直常驻的后台JS或后台页面
"background":
{
// 2种指定方式,如果指定JS,那么会自动生成一个背景页
"page": "background.html"
//"scripts": ["js/background.js"]
},
}
It should be noted that although you can chrome-extension://xxx/background.html
, the background page you open is not the same as the one that has been running in the background. In other words, you can open countless background.html
, but they are really in the background. There is only one resident, and you can never see its interface, you can only debug its code.
event-pages
Here is an introduction to event-pages , what is it? In view of the background life cycle is too long, mounting the background for a long time may affect performance, so Google has made another event-pages
. In the configuration file, the only difference between it and background is the addition of a persistent
parameter:
{
"background":
{
"scripts": ["event-page.js"],
"persistent": false
},
}
Its life cycle is: it is loaded when it is needed, and it is closed when it is idle. What does it mean when it is needed? For example, the first installation, plug-in update, content-script to send messages to it, and so on.
In addition to the configuration file changes, there are also some minor changes in the code. Just a brief understanding of this is enough. Under normal circumstances, the background will not consume performance.
popup
popup
is a small window webpage that opens when you click on the browser_action
or page_action
. When the focus leaves the webpage, it will be closed immediately. It is generally used for some temporary interaction.
popup
can contain any HTML content you want, and it will adapt to the size. You can default_popup
field, or you can call the setPopup()
method.
Configuration method:
{
"browser_action":
{
"default_icon": "img/icon.png",
// 图标悬停时的标题,可选
"default_title": "这是一个示例Chrome插件",
"default_popup": "popup.html"
}
}
It is important to note that since you click the icon to open the popup, and the focus is left and then immediately closed, the life cycle of the popup page is generally very short. Do not write code that takes a long time to run in the popup.
In terms of permissions, it is very similar to background. The biggest difference between them is the life cycle. In the popup, you can directly obtain the window object of the background chrome.extension.getBackgroundPage()
injected-script
The injected-script
here is what I took for it, which refers to a type of JS injected into the page through DOM manipulation. Why should this JS be discussed separately? Or why do you need to inject JS in this way?
This is because content-script
has a big "defect", that is, it cannot access the JS in the page. Although it can manipulate the DOM, the DOM cannot call it, that is, it cannot be called in the DOM by binding events in content-script
. Code (including direct writing onclick
and addEventListener
two ways are not good), but "add a button on the page and call the extension API of the plug-in" is a very common requirement, so what should I do? In fact, this is what this section will talk about.
inject-script
code example into the page through DOM in content-script
// 向页面注入JS
function injectCustomJs(jsPath)
{
jsPath = jsPath || 'js/inject.js';
var temp = document.createElement('script');
temp.setAttribute('type', 'text/javascript');
// 获得的地址类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
temp.src = chrome.extension.getURL(jsPath);
temp.onload = function()
{
// 放在页面不好看,执行完后移除掉
this.parentNode.removeChild(this);
};
document.head.appendChild(temp);
}
Do you think that's all right? If you execute it, you will see the following error:
Denying load of chrome-extension://efbllncjkjiijkppagepehoekjojdclc/js/inject.js. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.
It means that if you want to directly access the resources in the plug-in on the web, you must display the declaration. The following is added to the configuration file:
{
// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
"web_accessible_resources": ["js/inject.js"],
}
As for how inject-script
content-script
, I will introduce it in detail in a special message communication chapter later.
homepage_url
8 display forms of Chrome plug-in
browserAction (upper right corner of the browser)
By configuring browser_action
browser_action
can be added to the upper right corner of the browser. A 0614a8e30ec999 can have an icon, a tooltip
, a badge
and a popup
.
The sample configuration is as follows:
"browser_action":
{
"default_icon": "img/icon.png",
"default_title": "这是一个示例Chrome插件",
"default_popup": "popup.html"
}
pageAction (right side of the address bar)
The so-called pageAction
refers to the icon that is displayed only when certain pages are opened. The browserAction
is that one is always displayed, and the other is displayed only in specific situations.
It should be noted that in earlier versions of Chrome, pageAction was placed on the far right of the address bar, left-click to pop up popup, and right-click to pop up related default options menu:
The new version of Chrome has changed this strategy. The pageAction is placed in the upper right corner of the browser just like the ordinary browserAction, but it is gray when it is not lit, and it is colored when it is lit. When it is gray, it does not matter whether the left button or the right button is clicked. Clicking are all pop-up options:
The specific version was changed from which I did not go carefully. Anyway, I knew that it was the former when v50.0, and it was changed to the latter when v58.0.
We can simply regard the adjusted pageAction
browserAction
that can be grayed out.
- chrome.pageAction.show(tabId) display icon;
- chrome.pageAction.hide(tabId) hide icon;
Example (the icon will only be displayed when Baidu is opened):
// 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()]
}
]);
});
});
Effect picture:
Right click menu
The right-click menu of the browser can be customized by developing a Chrome plug-in, mainly through the chrome.contextMenus
API. The right-click menu can appear in different contexts, such as ordinary pages, selected text, pictures, links, etc., if there is the same plug-in If multiple menus are defined, Chrome will automatically combine them into the secondary menu named after the plug-in, as follows:
The simplest right-click menu example:
// manifest.json
{"permissions": ["contextMenus"]}
// background.js
chrome.contextMenus.create({
title: "测试右键菜单",
onclick: function(){alert('您点击了右键菜单!');}
});
override (override a specific page)
Use the override
page to replace some of the Chrome default specific pages and use the pages provided by the extension instead.
Extensions can replace the following pages:
- History: The page visited when you click History on the Tools menu, or directly enter chrome://history from the address bar
- New tab page: the page to visit when creating a new tab, or directly enter chrome://newtab from the address bar
- Bookmarks: browser bookmarks, or directly enter chrome://bookmarks
Notice:
- An extension can only replace one page;
- Cannot replace the new tab page of the incognito window;
- A title must be set on the webpage, otherwise the user may see the URL of the webpage, causing confusion;
Code (note that a plug-in can only replace one default page, the following is just a demonstration):
"chrome_url_overrides":
{
"newtab": "newtab.html",
"history": "history.html",
"bookmarks": "bookmarks.html"
}
devtools (developer tools)
Those who have used Vue should have seen this type of plug-in:
Yes, Chrome allows plugins to be used on the developer tools (devtools), mainly in:
- Customize one or more
Sources
same level asElements
,Console
, 0614a8e30ed2f2; - Customize the sidebar (sidebar), currently you can only customize the sidebar of the
Elements
Every time a developer tools window is opened, an instance of the devtools page will be created, and the F12 window will be closed, and the page will also be closed, so the life cycle of the devtools page and the devtools window are the same. The devtools page can access a unique set of DevTools API
and limited extension APIs. This unique set of DevTools API
can only be accessed by the devtools page, and the background is not authorized. These APIs include:
chrome.devtools.panels
: Panel related;chrome.devtools.inspectedWindow
: Obtain the relevant information of the censored window;chrome.devtools.network
: Get information about network requests;
Most of the extended API can not be directly DevTools
page calls, but it can be as content-script
as a direct call chrome.extension
and chrome.runtime
API, but it also can be like content-script
as using the Message interacts with background pages to communicate.
option (option page)
The so-called options
page is the plug-in settings page. There are 2 entrances, one is the right-click icon with an "option" menu, and the other is on the plug-in management page:
Before Chrome40, the options page was no different from other ordinary pages, and there have been some changes after Chrome40.
We first look at the old version of options :
{
// Chrome40以前的插件配置页写法
"options_page": "options.html",
}
You can use the content on this page as you like. After configuration, you will see a option button entry on the plug-in management page. Clicking in is to open a web page. There is nothing to talk about.
Effect:
Look at the new version of optionsV2 :
{
"options_ui":
{
"page": "options.html",
// 添加一些默认的样式,推荐使用
"chrome_style": true
},
}
Does it look tall?
A few notes:
- For compatibility, it is recommended to write both. If both are written, Chrome 40 will read the new version by default in the future;
- Alert cannot be used in the new version of options;
- It is recommended to use chrome.storage for data storage, because it will be automatically synchronized with the user;
omnibox
omnibox
is a way to provide users with search suggestions. Let's take a look at the gif
picture first to understand what the hell is this thing:
Register a certain keyword to trigger the plug-in’s own search suggestion interface, and then you can play it arbitrarily.
First, the configuration file is as follows:
{
// 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字
"omnibox": { "keyword" : "go" },
}
Then register the monitoring event in 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});
})
}
Desktop notification
Chrome provides a chrome.notifications
API for plug-ins to push desktop notifications. The significant differences and advantages between chrome.notifications
Notification
that comes with HTML5 have not been found yet.
In the background JS, no matter you use chrome.notifications
or Notification
do not need to apply for permission (the HTML5 method requires permission to apply), and you can use it directly.
The simplest notice:
Code:
chrome.notifications.create(null, {
type: 'basic',
iconUrl: 'img/icon.png',
title: '这是标题',
message: '您刚才点击了自定义右键菜单!'
});
5 types of JS comparison
Chrome plug-in JS can be divided into these 5 categories: injected script
, content-script
, popup js
, background js
and devtools js
Permission comparison
JS type | Accessible API | DOM access | JS access situation | Direct cross-domain |
---|---|---|---|---|
injected script | No difference from ordinary JS, no extension API can be accessed | Can visit | Can visit | Can not |
content script | Only some APIs such as extension and runtime can be accessed | Can visit | Can not | Can not |
popup js | Can access most APIs, except for the devtools series | Not directly accessible | Can not | Can |
background js | Can access most APIs, except for the devtools series | Not directly accessible | Can not | Can |
devtools js | Can only access some APIs such as devtools, extension, runtime, etc. | Can | Can | Can not |
Comparison of debugging methods
JS type | Debugging method | illustrate |
---|---|---|
injected script | Just plain F12 | Too lazy to take a screenshot |
content-script | Open the Console and switch as shown | |
popup-js | Right-click on popup page to review elements | |
background | Click on the background page on the plug-in management page | |
devtools-js | No effective method has been found yet | - |
Message communication
Communication homepage: https://developer.chrome.com/extensions/messaging
Earlier we introduced the 5 types of JS that exist in the Chrome plug-in, so how do they communicate with each other? Let's first give an overview of the system, and then classify them in detail. What you need to know is that popup and background can almost be regarded as one kind of thing, because they have the same access API, the same communication mechanism, and they can be cross-domain.
Overview of mutual communication
Note: -
means that it does not exist or is meaningless, or to be verified.
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 |
Communication details
1. popup and background
The popup can directly call the JS method in the background, or directly access the DOM of the background:
// background.js
function test()
{
alert('我是background!');
}
// popup.js
var bg = chrome.extension.getBackgroundPage();
bg.test(); // 访问bg的函数
alert(bg.document.body.innerHTML); // 访问bg的DOM
A small episode, I encountered a situation today and found that popup could not obtain any method of background. After looking for a long time, I discovered that it was because the background js reported an error, and if you did not actively check the background js, you would not see the error message. Hereby remind.
As for background
access popup
as follows (provided that popup
has been opened):
var views = chrome.extension.getViews({type:'popup'});
if(views.length > 0) {
console.log(views[0].location.href);
}
2. Popup or bg actively send messages to content
background.js or popup.js:
function sendMessageToContentScript(message, callback)
{
chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
{
chrome.tabs.sendMessage(tabs[0].id, message, function(response)
{
if(callback) callback(response);
});
});
}
sendMessageToContentScript({cmd:'test', value:'你好,我是popup!'}, function(response)
{
console.log('来自content的回复:'+response);
});
content-script.js
receives:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
// console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
if(request.cmd == 'test') alert(request.value);
sendResponse('我收到了你的消息!');
});
Both parties directly send JSON objects, not JSON strings, so there is no need to parse them, which is very convenient (of course, you can also send strings directly).
Some old codes on the Internet usechrome.extension.onMessage
, and the difference between the two is not fully understood (it looks like an alias), but it is recommended to usechrome.runtime.onMessage
.
3. The content-script initiatively sends messages to the background
content-script.js:
chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主动发消息给后台!'}, function(response) {
console.log('收到来自后台的回复:' + response);
});
background.js or popup.js:
// 监听来自content-script的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
console.log('收到来自content-script的消息:');
console.log(request, sender, sendResponse);
sendResponse('我是后台,我已收到你的消息:' + JSON.stringify(request));
});
Precautions:
- The premise for content_scripts to
popup
is that the popup must be opened! Otherwise, you need to use background as a transit; - If the background and popup listen at the same time, then they can both receive the message at the same time, but only one can sendResponse, one is sent first, then the other one will be invalid after sending;
4. injected script and content-script
content-script
only thing shared between 0614a8e30ee670 and the script in the page ( injected-script
also belongs to the script in the page) is the DOM element of the page. There are two ways to achieve communication between the two:
- Message communication between the two can be achieved through
window.postMessage
andwindow.addEventListener
- Realize through custom DOM events;
The first method (recommended):
injected-script
:
window.postMessage({"test": '你好!'}, '*');
In the content script:
window.addEventListener("message", function(e)
{
console.log(e.data);
}, false);
The second method:
injected-script
:
var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);
function fireCustomEvent(data) {
hiddenDiv = document.getElementById('myCustomEventDiv');
hiddenDiv.innerText = data
hiddenDiv.dispatchEvent(customEvent);
}
fireCustomEvent('你好,我是普通JS!');
content-script.js
:
var hiddenDiv = document.getElementById('myCustomEventDiv');
if(!hiddenDiv) {
hiddenDiv = document.createElement('div');
hiddenDiv.style.display = 'none';
document.body.appendChild(hiddenDiv);
}
hiddenDiv.addEventListener('myCustomEvent', function() {
var eventData = document.getElementById('myCustomEventDiv').innerText;
console.log('收到自定义事件消息:' + eventData);
});
Long connection and short connection
In fact, the above has already been involved, and I will explain it separately here. There are two communication methods in the Chrome plug-in, one is a short connection ( chrome.tabs.sendMessage
and chrome.runtime.sendMessage
), and the other is a long connection ( chrome.tabs.connect
and chrome.runtime.connect
).
A short connection is like squeezing toothpaste. I will send it, and you will reply when you receive it. If the other party does not reply, you can only re-send it, while a long connection like WebSocket
will always establish a connection, and both parties can send messages to each other at any time.
There is already a code example above for short connection, here we only talk about long connection.
popup.js:
getCurrentTabId((tabId) => {
var port = chrome.tabs.connect(tabId, {name: 'test-connect'});
port.postMessage({question: '你是谁啊?'});
port.onMessage.addListener(function(msg) {
alert('收到消息:'+msg.answer);
if(msg.answer && msg.answer.startsWith('我是'))
{
port.postMessage({question: '哦,原来是你啊!'});
}
});
});
content-script.js:
// 监听长连接
chrome.runtime.onConnect.addListener(function(port) {
console.log(port);
if(port.name == 'test-connect') {
port.onMessage.addListener(function(msg) {
console.log('收到长连接消息:', msg);
if(msg.question == '你是谁啊?') port.postMessage({answer: '我是你爸!'});
});
}
});
Other supplements
Dynamically inject or execute JS
Although the page DOM cannot be directly accessed background
and popup
chrome.tabs.executeScript
to access the DOM of the web page (note that this method also cannot directly access the page JS).
Example manifest.json
configuration:
{
"name": "动态JS注入演示",
...
"permissions": [
"tabs", "http://*/*", "https://*/*"
],
...
}
JS:
// 动态执行JS代码
chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});
// 动态执行JS文件
chrome.tabs.executeScript(tabId, {file: 'some-script.js'});
Dynamically inject CSS
Example manifest.json
configuration:
{
"name": "动态CSS注入演示",
...
"permissions": [
"tabs", "http://*/*", "https://*/*"
],
...
}
JS code:
// 动态执行CSS代码,TODO,这里有待验证
chrome.tabs.insertCSS(tabId, {code: 'xxx'});
// 动态执行CSS文件
chrome.tabs.insertCSS(tabId, {file: 'some-style.css'});
Get the current window ID
chrome.windows.getCurrent(function(currentWindow)
{
console.log('当前窗口ID:' + currentWindow.id);
});
Get the ID of the current tab
There are generally 2 methods:
// 获取当前选项卡ID
function getCurrentTabId(callback)
{
chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
{
if(callback) callback(tabs.length ? tabs[0].id: null);
});
}
Another way to get the id of the current tab is similar most of the time, only a few times will be different (for example, when the window is minimized)
// 获取当前选项卡ID
function getCurrentTabId2()
{
chrome.windows.getCurrent(function(currentWindow)
{
chrome.tabs.query({active: true, windowId: currentWindow.id}, function(tabs)
{
if(callback) callback(tabs.length ? tabs[0].id: null);
});
});
}
Local storage
For local storage, it is recommended to use chrome.storage
instead of the ordinary localStorage
. There are several differences. I personally think that the most important two differences are:
chrome.storage
against global plugins, even if youbackground
stored data, incontent-script
can get to;chrome.storage.sync
can be automatically synchronized with the currently logged-in user. The modified settings of this computer will automatically be synchronized to other computers. It is very convenient. If you are not logged in or not connected to the Internet, save it to the local first, and then synchronize to the network after logging in;
Need to declare storage
permissions, there are chrome.storage.sync
and chrome.storage.local
, examples of use are as follows:
// 读取数据,第一个参数是指定要读取的key以及设置默认值
chrome.storage.sync.get({color: 'red', age: 18}, function(items) {
console.log(items.color, items.age);
});
// 保存数据
chrome.storage.sync.set({color: 'blue'}, function() {
console.log('保存成功!');
});
webRequest
beforeRequest
through the webRequest series of APIs. Here is a brief demonstration of the tip of the iceberg through 0614a8e31002f1:
//manifest.json
{
// 权限申请
"permissions":
[
"webRequest", // web请求
"webRequestBlocking", // 阻塞式web请求
"storage", // 插件本地存储
"http://*/*", // 可以通过executeScript或者insertCSS访问的网站
"https://*/*" // 可以通过executeScript或者insertCSS访问的网站
],
}
// background.js
// 是否显示图片
var showImage;
chrome.storage.sync.get({showImage: true}, function(items) {
showImage = items.showImage;
});
// web请求监听,最后一个参数表示阻塞式,需单独声明权限:webRequestBlocking
chrome.webRequest.onBeforeRequest.addListener(details => {
// cancel 表示取消本次请求
if(!showImage && details.type == 'image') return {cancel: true};
// 简单的音视频检测
// 大部分网站视频的type并不是media,且视频做了防下载处理,所以这里仅仅是为了演示效果,无实际意义
if(details.type == 'media') {
chrome.notifications.create(null, {
type: 'basic',
iconUrl: 'img/icon.png',
title: '检测到音视频',
message: '音视频地址:' + details.url,
});
}
}, {urls: ["<all_urls>"]}, ["blocking"]);
API summary
Some commonly used API series:
- chrome.tabs
- chrome.runtime
- chrome.webRequest
- chrome.window
- chrome.storage
- chrome.contextMenus
- chrome.devtools
- chrome.extension
Project actual combat
Use vue cli 4 to develop chrome plug-ins
Create vue cli 4 project
vue create google-extension-demo
Check Babel
, TypeScript
, CSS Pre-processors
, Linter / Formatter
Here, the CSS preprocessor is arbitrary, I chose Sass/SCSS (with dart-sass)
.
In addition, linter / formatter
in TSLint
is no longer maintained. It is recommended to use ESLint
+ Prettier
.
Do not use class-style component syntax
, see vue-cli-plugin-chrome-ext .
Add chrome-ext plugin
vue add chrome-ext
Remember to check ts here:
? Name of the Chrome Extension? todolist
? Description for the Chrome Extension? chrome extension
? Version for the Chrome Extension? 0.0.1
? javascript or typescript?
js
❯ ts
An error occurred:
⠋ Running completion hooks...error: Require statement not part of import statement (@typescript-eslint/no-var-requires) at vue.config.js:1:27:
> 1 | const CopyWebpackPlugin = require("copy-webpack-plugin");
| ^
2 | const path = require("path");
3 |
4 | // Generate pages object
This is because eslint
does not allow the syntax of commonjs
vue.config.js
is a special file, we should put it in the .eslintignore
file and let eslint
ignore it.
Write in .eslintignore
vue.config.js
vue-cli-plugin-chrome-ext
`Creation is not a SPA application. The chrome plug-in has many interfaces, so each interface is compiled separately.
For example, src/popup is an interface, and src/options is also an interface.
In this way, many of the original files are useless, we can delete them, which are src/main.ts
and src/App.vue
.
Hot reload development
Use npm run build-watch
to develop using hot reload.
After running, a dist folder will be generated, and it will be recompiled and loaded every time you change the code.
Import dist into the chrome plugin
Enter chrome://extensions/
, select and load the dist folder to enter.
After loading successfully, remember to go to the management interface in the upper right corner to open the popup interface (new version of chrome).
Now, after typing the code, you can immediately see the rendering result in the chrome popup view.
Use UI components
Although vue is running, it is useless if the component cannot be imported.
Take ant design vue
as an example, let's test it.
npm i --save ant-design-vue
Import in the popup interface
Add the following in src/popup/index.ts
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
Vue.use(Antd);
Vue.config.productionTip = false;
As we said before, each folder under the src folder represents a page, and each page has a corresponding index.ts (equivalent to main.js), index.html, and App.vue.
In other words, if we want to use the UI library, it is not necessary to configure it in a global main.js, we need to import it in the index.ts under the folder of each page.
Here we only imported it under the popup interface. If you need to use it in the options interface, you need to go to src/options/index.ts and import it again.
Write a simple popup interface
Use ant design
of card
components to test, whether you can use.
The main code of the interface should be written in the App/App.vue
directory.
<template>
<div class="main_app">
<a-card title="ghost wang">
coding
</a-card>
</div>
</template>
<script lang="ts">
export default {
name: "app"
};
</script>
<style>
.main_app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
height: 500px;
width: 300px;
padding: 10px;
}
</style>
The effect is like this:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。