chrome插件开发
借鉴TODO-List:Chrome插件开发简要指南
文件结构
在应用商店中下载下来的插件基本上都是以.crx
为文件后缀,该文件其实就是一个压缩包(文件夹压缩而来),包括插件所需要的html、css、javascript、图片资源等等文件。
其中,
manifest.json
是整个插件的功能及文件配置清单,非常重要。
static
目录是放置整个插件的静态资源文件的,包括css、js、图片等等资源
template
目录是放置整个插件的功能页面模板的。
_locales
目录是放置整个插件的国际化语言脚本的。
一般来说,清单文件manifest.json
文件是必须的,且必须放在插件开发目录的根目录上。其他的目录都可以自定义。
弹出窗口和后台页面
弹出窗口一般用于插件和用户的交互,而后台页面一般用于插件本身做一些额外的事情。比如有时候,插件需要联网进行数据同步等操作,这种操作用户是无感知的,所有就要求插件需要有一个后台页面来运行这部分的逻辑。
其实后台页面还可以分为持久运行的后端页面和事件页面,这里对这两者就不多做说明了,更多的内容可以参阅具体的文档。
弹出式插件教程TODO-LIST
manifest.json
1 2 3 4 5 6 7 8 9 10 11
| { "name": "todo-plugin", "version": "0.9.0", "manifest_version": 2, "description": "chrome plugin demo", "browser_action": { "default_icon": "icon.png", "default_title": "Todo List", "default_popup": "popup.html" } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <style> body { width: 150px; } #add-new-item { cursor: pointer; color: #CCC; } .hide { display: none; } .show { display: block; } .item { cursor: pointer; margin: 5px 0; } .item input { display: inline-block; width: 12px; height: 12px; } input { width: 120px; } </style> <div id="add-new-item">添加新项</div> <div id="add-new-item-input" class="hide"> <input type="text" id="new-item-title" placeholder="添加新任务"/> </div> <div id="item-list"></div> <script type="text/javascript" src="main.js"></script>
|
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| (function () { var $ = function (id) { return document.getElementById(id); }; var Tasks = { show: function (obj) { obj.className = ''; return this; }, hide: function (obj) { obj.className = 'hide'; return this; }, $addNewItem: $('add-new-item'), $addNewItemInput: $('add-new-item-input'), $itemList: $('item-list'), $newItemTitle: $('new-item-title'), init: function () { Tasks.$addNewItem.addEventListener('click', function () { Tasks.show(Tasks.$addNewItemInput).hide(Tasks.$addNewItem); Tasks.$newItemTitle.focus(); }, true); Tasks.$newItemTitle.addEventListener('keyup', function (ev) { var ev = ev || window.event; if (ev.keyCode == 13) { var task = Tasks.$newItemTitle.value; Tasks.AppendHtml(task); Tasks.$newItemTitle.value = ''; Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem); } ev.preventDefault(); }, true); Tasks.$newItemTitle.addEventListener('blur', function () { Tasks.$newItemTitle.value = ''; Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem); }, true); }, Add: function () { }, Edit: function () { }, Del: function () { }, AppendHtml: function (title) { var oDiv = document.createElement('div'); oDiv.className = 'item item-todo'; var oInput = document.createElement('input'); oInput.type = 'checkbox'; var oTitle = document.createElement('span'); oTitle.innerHTML = title; oDiv.appendChild(oInput); oDiv.appendChild(oTitle); Tasks.$itemList.appendChild(oDiv); oDiv.addEventListener('click', function () { }, true); }, RemoveHtml: function () { } } Tasks.init(); })();
|
天猫秒杀插件 Tmall_Kill
Code Template: https://link.zhihu.com/?target=https%3A//github.com/cehui0303/Tmall_Tickets
代码比较简单,实现原理为,打开浏览器后执行插件,JS脚本将会在指定时间点击“结算”按钮,然后发起订单,从而锁定库存抢货。
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| var timer = null;
function checkElementState(path,callback){ var ele = document.querySelector(path); if(ele){ callback && callback(); }else{ console.log('异步加载元素中....' + path ); setTimeout( function(){checkElementState(path,callback);},200); } }
function checkOut(){ console.log('结算开始....'); var btn = document.getElementById('J_Go'); if(btn){ btn.click(); }else{ console.log('结算按钮没找到'); } }
function checkOutAsync(){ checkElementState('#J_Go',checkOut); }
function submitOrder(){ console.log('提交订单开始....'); checkElementState('.go-btn',function(){ var btn = document.querySelector(".go-btn"); if(btn){ btn.click(); }else{ console.log('提交订单按钮没找到'); } }); }
var dDate = new Date(); if( dDate.getHours() < 10 ){ dDate.setHours(9,59,59.2); }else{ dDate.setHours(19,59,59.2); }
function enterTimeCheckLoop(callback){ var date = new Date(); var diff = Date.parse(dDate) - Date.parse(date) ; console.log(diff); if(diff < -90 ){ console.log('时间过了!'); }else if(diff < 50 ) { callback && callback(); console.log('时间到了!!!'); }else{ setTimeout(function(){ enterTimeCheckLoop(callback);},400); } }
(function main(){ console.log('############################天猫枪杀脚本############################'); var href = window.location.href; if(href.indexOf('cart.tmall.com') > -1 ){ enterTimeCheckLoop( checkOutAsync ); }else if(href.indexOf('buy.tmall.com') > -1 ){ submitOrder(); } })()
|
这种理论上是可行的,只不过肯定会有刷新上的问题,效率不及网络请求,但思路依旧是值得学习的,因此借此机会也是学下Chrome插件的编写。
manifest.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| { "manifest_version": 2, // manifest文件版本号。Chrome18开始必须为2 "name": "Tmall-tickets", // crx扩展名称 "version": "1.0", // 插件版本 "description": "天猫超市枪杀脚本", "browser_action": { // 地址栏右侧图标管理。含图标及弹出页面的设置等 "default_icon": "icon.png" }, "content_scripts": [ // 指定要向Web页面内注入的脚本。可注入多个css与js。 { "matches": ["https://chaoshi.detail.tmall.com/*","https://cart.tmall.com/*","https://buy.tmall.com/*"], "js": ["maotai.js"], "run_at": "document_idle" } ], "permissions": [ "https://www.baidu.com/*", "background", "tabs" ], //权限 "plugins": [{ "path": "extension_plugin.dll", "public": true }], // 扩展。可调用第三方扩展 }
|
content_scripts,其数组元素的字段有:
-
matches:String数组,必须。定义content_scripts对哪些页面生效。其规则符合permissions的模式匹配。
-
css:String数组,可选。定义哪些css文件在web页面DOM创建前注入到web页面中。
-
js:String数组,可选。定义哪些js文件注入到web页面中。其js文件的注入顺序与数组中定义的顺序相同。至于这些js与web页面中所定义js的顺序关系,取决于run_at字段。
-
run_at:String,可选。定义content_scripts的注入时机,从而影响到js与web页面所定义js的顺序关系。取值有:
-
document_start:所有css加载完毕,但DOM尚未创建时。
-
document_end:DOM创建完成,但图片及frame等子资源尚未加载时。
-
document_idle:document_end之后,window.onload之前。
默认是document_idle,也就是content_scripts的js都罗列在web页面的js之后。
-
all_frames:boolean,可选。是否运行在页面所有的frame中。若为false,则只运行在最上层的frame中。默认为false。
-
include_globs:String数组,可选。用于规定页面匹配的白名单。一个URL,必须同时满足:匹配matches,匹配include_globs白名单,不匹配exclude_globs黑名单这三个条件才可以。注意include_globs和exclude_globs中的匹配语法与permissions和matches所用的匹配模式不同。
-
exclude_globs:String数组,可选。用于规定页面匹配的黑名单。同⑥。
permissions:扩展所需要的权限。permissions是一个String数组,每一个权限都使用String来表示。某些权限在安装前会告知用户。
- 模式配匹:用于指定扩展会在哪些URL中生效。例如:
- background:启用扩展后端环境。即在浏览器运行期始终运行,与单个页面无关。可以在这里调用浏览器的API,通常在这里进行扩展主要逻辑的开发。配合manifest.json的background字段使用。
background: 这是一个比较重要的属性,如果你需要运行一些后台脚本,比如监听用户在扩展信息栏按下你的插件图标,或者你要监听用户新建tab页,这个时候你就需要有一个background的页面。background你可以指定一个HTML页面(如我的插件),也可以指定一个JS文件,如:
1 2 3 4 5 6 7 8 9 10 11
| { "name": "My extension", ... "background": { "scripts": ["background.js"] }, ... } // 需要注意两点: // 1、是HTML不能写JS代码,JS代码需要写到JS文件中后引入; // 2、不能使用jquery(没有详细测试,可能是我没用正确);
|
background.js
1 2 3 4 5
|
chrome.browserAction.onClicked.addListener(function (tab) { alert(tab.url); });
|
改进Tmall_Kill
需要修改的功能为:
- 插件中设定时间=>时间框选择时间
- 匹配网站自动运行->弹出页面手动点击运行
- 选中购物车商品后结算->到时间后自动勾选商品结算
- 弹出页+时间框 popup.html
HTML5日期输入框(date)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
<style> body { width: 250px; } #add-new-item { cursor: pointer; color: #CCC; } .hide { display: none; } .show { display: block; } .item { cursor: pointer; margin: 5px 0; } .item input { display: inline-block; width: 12px; height: 12px; } input { width: 200px; } </style> <div id="add-new-item">add new item</div> <div id="add-new-item-input" class="hide"> <input type="text" id="new-item-title" placeholder="add new"/> </div> <label for="meeting">killTime:</label><input id="meeting" type="datetime-local"/> <div id="item-list"></div>
<script type="text/javascript" src="main.js"></script>
|
配套的manifest.json
1 2 3 4 5 6 7 8 9 10 11
| { "name": "todo-plugin", "version": "0.9.0", "manifest_version": 2, "description": "chrome plugin demo", "browser_action": { "default_icon": "icon.png", "default_title": "Todo List", "default_popup": "popup.html" } }
|
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| (function () { var $ = function (id) { return document.getElementById(id); }; var Tasks = { show: function (obj) { obj.className = ''; return this; }, hide: function (obj) { obj.className = 'hide'; return this; }, $addNewItem: $('add-new-item'), $addNewItemInput: $('add-new-item-input'), $itemList: $('item-list'), $newItemTitle: $('new-item-title'), init: function () { Tasks.$addNewItem.addEventListener('click', function () { Tasks.show(Tasks.$addNewItemInput).hide(Tasks.$addNewItem); Tasks.$newItemTitle.focus(); }, true); Tasks.$newItemTitle.addEventListener('keyup', function (ev) { var ev = ev || window.event; if (ev.keyCode == 13) { var task = Tasks.$newItemTitle.value; Tasks.AppendHtml(task); Tasks.$newItemTitle.value = ''; Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem); } ev.preventDefault(); }, true); Tasks.$newItemTitle.addEventListener('blur', function () { Tasks.$newItemTitle.value = ''; Tasks.hide(Tasks.$addNewItemInput).show(Tasks.$addNewItem); }, true); }, Add: function () { }, Edit: function () { }, Del: function () { }, AppendHtml: function (title) { var oDiv = document.createElement('div'); oDiv.className = 'item item-todo'; var oInput = document.createElement('input'); oInput.type = 'checkbox'; var oTitle = document.createElement('span'); oTitle.innerHTML = title; oDiv.appendChild(oInput); oDiv.appendChild(oTitle); Tasks.$itemList.appendChild(oDiv); oDiv.addEventListener('click', function () { }, true); }, RemoveHtml: function () { } } Tasks.init(); })();
|
学习案例: