从零开始编写一个Chrome插件
编写一个chrome
插件是一个很简单的事只要会javascript
就可以轻松完成这个事情,但是你要完成一个十分完美的插件还是比较困难的,使用插件我们可以做很多有趣的事情,比如我们可以做一个抢火票的插件,自动提醒,自动买票等等。本篇教程将教大家怎么完成一个chrome插件。
开始工作
插件功能
提取页面所有样式表里面的背景图片显示到插件中
Manifest
manifest.json是插件的配置文件整个插件最重要的文件,配置权限、content脚本、后台脚本、popup、插件ICON都是这个文件。官方文档
{
"manifest_version": 2, //扩展使用的Manifest 版本, 1是chrome版本低于17才使用.目前主流都是用2
"name": "ImageSource", //扩展名称
"version": "1.0", //扩展版本
"description": "抓取当前页面样式文件中的所有图片源地址", //扩展描述
//设置扩展将在地址右侧显示扩展图标,弹出窗,提示等
//@see https://developer.chrome.com/extensions/browserAction
//当你的扩展是针对某些页面操作的话,你应该使用page_action
/**
"browser_action": {
"default_icon": { // 设置ICON,不同大小,浏览会选择显示那种ICON
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
"default_title": "Google Mail", // 设置默认的标题
"default_popup": "popup.html" // 设置默认的弹出层页面
},
*/
"page_action": {
"default_icon": "images/icon-32.png", // 设置ICON,不同大小,浏览会选择显示那种ICON
"default_title": "图片源", // 设置默认的标题
"default_popup": "popup.html" // 设置默认的弹出层页面
},
"icons": {
"48": "images/icon-48.png",
"128": "images/icon-128.png"
},
//运行于后台的实例, 后台实例是一直在运行中,打开浏览器只执行一次实例
//官方推荐使用事件页面代替背景页面
//@see
"background": {
//"page": "background.html",
"scripts": [
"js/background.js"
],
"persistent": false
},
//页面脚本注入
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/content.js"]
}
],
//扩展权限
//@see https://developer.chrome.com/extensions/declare_permissions 权限列表文档
"permissions": [
"background",
"tabs",
"activeTab",
"http://*/*",
"https://*/*"
],
"author": "骑驴找蚂蚁", //开发者
"homepage_url": "http://loocode.com" //项目主页
}
上面是插件的manifest.json完整内容.
pageAction
在这个插件中我们使用的是page_action
而不是browser_action
, 使用page_action
需要开发者自己控制插件的使用状态, 默认情况是下是非使用状态(开启chrome.pageAction.show(tabId)
)与browser_action
相反。官方认为在某些条件下使用才能使用插件应该选择page_action
, 比如我是针对某个网站(github
, google
),或者针对网页内容特定值(json
, rss
)。
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var url = tab.url;
var status = changeInfo.status; //loading and complete two state
if (url !== undefined && status === 'complete' && url.indexOf("https://github.com") !== -1) {
chrome.pageAction.show(tabId);
}
});
比如监听tabs页的更新事件,当打开的页面是github网站的话就把插件更新为活动状态.这里需要申请tabs权限,
permissions
属性里面填写tabs即可, 而browser_action不需要这么做。
default_popup
这个属性是设置点击插件弹出的页面层. 也是上面那张事例的显示页面。
<!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>Document</title>
<script type="text/javascript" src="js/popup.js"></script>
</head>
<body>
</body>
</html>
Note:
在html
中是不可以内嵌内联脚本的,所以必须文件形式引入`
var query = {
active: true, currentWindow: true
};
var tab = null;
var bg = chrome.extension.getBackgroundPage();
function ajax(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var content = xhr.responseText,
reg = /url\("?(.*?\.(png|gif|jpeg|jpg|svg))"?\)/gi,
match = null, images = [];
while((match = reg.exec(content)) !== null) {
images.push(match[1]);
}
if (images.length > 0) {
var p = document.createElement("p");
p.innerHTML = url,
offset = url.lastIndexOf("/") + 1,
newUrl = url.substring(0, offset),
point = newUrl.lastIndexOf(".") + 1,
domain = null;
for (var j = 1; j <= 6; j++) {
if (url.substr(point+j, 1) === "/") {
domain = url.substring(0, point+j);
break;
}
}
document.body.appendChild(p);
for (var i in images) {
var img = document.createElement("img");
if (images[i].substring(0,1) === '/') {
img.setAttribute("src", domain + images[i]);
} else {
img.setAttribute("src", newUrl + images[i]);
}
document.body.appendChild(img);
}
}
}
};
xhr.send();
}
/**
* 查找当前窗口活动的tab,并发送消息给backgorund.js取得页面的sheet表,
* 再依次请求CSS文件获取内容读出图片地址,显示在popup.html。
* @param {*} tabs
*/
function callback(tabs) {
tab = tabs[0]; // there will be only one in this array
chrome.runtime.sendMessage({
type: 'popup',
url: tab.url
}, function(response) {
var sheet = response.sheet;
for(var key in sheet) {
ajax(sheet[key]);
}
});
}
chrome.tabs.query(query, callback);
content_scripts
当前一个tab页打开时,chrome就会执行插件注入的脚本("js": ["js/content.js"]
), 会根据"matches": ["http://*/*", "https://*/*"]
匹配规则。
注入的脚本和页面中的脚本运行环境是相互独立的互不干扰。注入的脚本可以使用DOM API
来控制页面的内容和操作。
/**
* 绑定页面载入后将sheets信息发送给background
*/
window.onload = function(e) {
var content = document.body.innerHTML;
var sheet = document.styleSheets;
var href = [];
for (var key in sheet) {
if (sheet[key].href != null) {
href.push(sheet[key].href);
}
}
chrome.runtime.sendMessage({
type: "content",
sheet: href,
url: location.href
});
}
Note:
建立插件之间的通信或者background、popup、content_script之间通信都是通过sendMessage来完成。必须有绑定chrome.runtime.onMessage.addListener
的地方,这里我们绑定在background脚本中。
background
插件中的后台脚本在chrome
打开就会执行,只要有权限几乎所有API都可以使用。
var url = {},
/**
* 根据url生成对应的hash
*/
hashCode = function(url) {
var hash = 0;
for (var i = 0; i < url.length; i++) {
var character = url.charCodeAt(i);
hash = ((hash<<5)-hash)+character;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
/**
* @see https://developer.chrome.com/extensions/pageAction
*/
/**
chrome.pageAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
var hash = hashCode(tab.url);
});
*/
/**
* 删除对应的网页数据
* @see https://developer.chrome.com/extensions/tabs#on-removed
*/
chrome.tabs.onRemoved.addListener(function(tab) {
var hash = hashCode(tab.url);
if (url.hasOwnProperty(code) === false) {
delete url[hash]
}
});
/**
chrome.webRequest.onBeforeSendHeaders.addListener(function(request) {
console.log(request);
});
*/
/**
* 监听标签页更新事件
* @see https://developer.chrome.com/extensions/tabs#on-updated
*/
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var url = tab.url;
var status = changeInfo.status; //loading and complete two state
if (url !== undefined && status === 'complete' && url.substring(0, 4) === 'http') {
chrome.pageAction.show(tabId);
} else {
//chrome.pageAction.hide(tabId);
}
});
/**
* 监听消息事件
* @see https://developer.chrome.com/extensions/runtime#event-onMessage
*/
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
var code = hashCode(message.url);
console.log(message.url);
console.log(code);
if (message.type === 'content') {
if (url.hasOwnProperty(code) === false) {
url[code] = message.sheet;
}
} else if (message.type === 'popup') {
sendResponse({sheet: url[code]});
}
console.log(url);
});
Note:
注意updated的状态,是有两种loading和complete。还是消息是通过消息内容来区别消息类型的
插件流程
现在整个插件的功能完全实现了,不过还有多很不足之处没有解决。background活动问题,展现形式都不太好,不过大伙可根据现有的代码去修改。
源码地址
https://github.com/TianLiangZhou/loocode-example/tree/master/chrome-plugin-image-source
下载地址
http://backend.loocode.com/upload/images/20171103/chrome-plugin-image-source.zip