谷歌瀏覽器插件開發
簡介
Chrome擴展主要用於對瀏覽器功能的增強,它更強調與瀏覽器相結合。比如Chrome擴展可以在瀏覽器的工具欄和地址欄中顯示圖標,它可以更改用戶當前瀏覽的網頁中的內容,直接操作瀏覽頁面的DOM樹等。這裏用它來採集數據,類似於爬蟲,然後將處理的數據發送到指定接口,導入數據庫。
還有一種Chrome應用,但與瀏覽器內容相對獨立,這裏不介紹。
開發環境
開發瀏覽器插件不需要特別的工具,只需要裝上谷歌瀏覽器, 一個記事本足矣。調試什麼都可以在瀏覽器進行。
目錄結構
這裏寫的是簡單的插件,基本目錄結構如下:
谷歌瀏覽器簡單的插件實際上就是一個拓展的網頁,所以和普通的HTML的結構沒什麼特殊之處,上面的js、images完全可以是你喜歡的名稱,引用時路徑寫對就行,需要注意的是,插件必須包含一個manifest.json文件(必須是這個名字),此文件描述了插件的一些基本信息,安裝插件時會讀取插件相關信息。
插件的html頁面可以引用外部的js文件,但是注意,不能直接在頁面裏面直接寫JavaScript腳本,是不會執行,必須是引用的腳本,切記
manifest.json詳解
manifest.json是插件最重要的配置和描述文件,下面用我的例子來說說
{
"manifest_version": 2,
"name": "採集插件",
"version": "1.0",
"description": "獲取網頁數據,保存直接到OA系統",
"content_scripts": [
{
"matches": ["https://abc.xxxx.com/*"],
"run_at": "document_end",
"js": ["js/getdata.js"]
}
],
"browser_action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png",
"38": "images/icon38.png",
"48": "images/icon48.png",
"64": "images/icon64.png",
"128": "images/icon128.png"
},
"default_title": "狀態",
"default_popup": "popup.html"
},
"options_page": "options.html",
"permissions": [
"storage",
"https://abc.xxxx.com/*",
"http://127.0.0.1:8080/*"
]
}
name定義了擴展的名稱,
version定義了擴展的版本,
description定義了擴展的描述,
icons定義了擴展相關圖標文件的位置,谷歌會根據需要選擇合適大小的圖標
version的值最多可以是由三個圓點分爲四段的版本號,每段只能是數字,每段數字不能大於65535且不能以0開頭(可以是0,但不可以是0123),版本號段左側爲高位,比如1.0.2.0版本比1.0.0.1版本更高。每次更新擴展時,新的版本號必須比之前的版本號高。
browser_action指定擴展的圖標放在Chrome的工具欄中,
browser_action中的default_icon屬性定義了相應圖標文件的位置,
default_title定義了當用戶鼠標懸停於擴展圖標上所顯示的文字,default_popup則定義了當用戶單擊擴展圖標時所顯示頁面的文件位置,
content_scripts屬性可以指定將哪些腳本何時注入到哪些頁面中,當用戶訪問這些頁面後,相應腳本即可自動運行,從而對頁面DOM進行操作。
Manifest的content_scripts屬性值爲數組類型,數組的每個元素可以包含matches、exclude_matches、css、js、run_at、all_frames、include_globs和exclude_globs等屬性。
其中matches屬性定義了哪些頁面會被注入腳本,exclude_matches則定義了哪些頁面不會被注入腳本,css和js對應要注入的樣式表和JavaScript;
run_at定義了何時進行注入,
另外,all_frames可以定義腳本是否會注入到嵌入式框架中,
include_globs和exclude_globs則是全局URL匹配,最終腳本是否會被注入由matches、exclude_matches、include_globs和exclude_globs的值共同決定。
簡單的說,如果URL匹配mathces值的同時也匹配include_globs的值,會被注入;如果URL匹配exclude_matches的值或者匹配exclude_globs的值,則不會被注入。
content_scripts中的腳本只是共享頁面的DOM樹,而並不共享頁面內嵌JavaScript的命名空間。
也就是說,如果當前頁面中的JavaScript有一個全局變量a,content_scripts中注入的腳本也可以有一個全局變量a,兩者不會相互干擾。當然你也無法通過content_scripts訪問到頁面本身內嵌JavaScript的變量和函數。
Manifest的permissions屬性中聲明需要谷歌拓展API的storage權限和跨域的權限。
項目業務代碼等
業務部分
以下是get_data.js代碼,主要業務操作在這裏
timeStart="";
timeEnd="";
timeEnd_collect=""; //採集結束時間,程序到這時間終止
//encode要發送到OA系統的數據
function encodeFormData(data){
if(!data) return '';
var pairs = [];
for(var name in data){
if(!data.hasOwnProperty(name)) continue;
if(typeof data[name] === 'function') continue;
var value = data[name].toString();
name = encodeURIComponent(name.replace('%20','+'));
value = encodeURIComponent(value.replace('%20','+'));
pairs.push(name+'='+value);
}
return pairs.join('&');
}
//獲取,處理頁面數據
function get_data(){
var trs = window.frames["MainIframe"].document.getElementById("table_data_tbody").children;
var datas=[];
for(var i=0;i<trs.length;i++){
var tds =trs[i].childNodes;
var data_tmp= {
STARTTIME:tds[1].innerText,
ENDTIME:tds[2].innerText,
ZUOXI:tds[3].innerText,
ZUOXI_ID:tds[4].innerText,
FROM_NUM:tds[5].innerText,
TO_NUM:tds[6].innerText,
CALL_TYPE:tds[7].innerText,
DURATION:tds[8].innerText,
SATISFACTION:tds[9].innerText,
ACID:tds[10].innerText
};
datas[i]=data_tmp;
}
var s = JSON.stringify(datas);
var data2sent={
data:s
}
httpRequest('http://127.0.0.1:8080/invCloudOA/appuser/calllog2db',encodeFormData(data2sent),function(result){
html = result;
console.log(html);
});
}
//模擬用戶操作,發送請求給acc
function sent_req(){
chrome.storage.local.set({"log":"模擬用戶操作,修改時間參數"});
var timestamp_end = Date.parse(new Date(timeEnd));
window.frames["MainIframe"].document.getElementById("type_duration").click();
window.frames["MainIframe"].document.getElementById("timeStart").value=timeStart;
window.frames["MainIframe"].document.getElementById("timeEnd").value=timeEnd;
window.frames["MainIframe"].document.getElementById("btnOk").click();
chrome.storage.local.set({"log":"發送請求,延時處理返回數據"});
//延時數據處理
setTimeout(get_data,12000);
//設置新的時間
chrome.storage.local.set({"log":"時間參數修改"});
timeStart=timeEnd;
var end_timestamp = Date.parse(new Date(timeEnd));
var new_end_date = new Date();
new_end_date.setTime(end_timestamp+60*60*1000);
timeEnd=new_end_date.format('yyyy-MM-dd hh:mm:ss');
var p = {
timeStart:timeStart,
timeEnd:timeEnd,
}
chrome.storage.local.set(p,function(){});
chrome.storage.local.set({"log":"數據處理完成,準備發給OA"});
setTimeout(sent_req, 10000);
}
//構造請求,發給OA
function httpRequest(url,data, callback){
var xhr = new XMLHttpRequest();
xhr.open('post',url);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
chrome.storage.local.set({"log":"發送數據給OA,OA處理中"});
xhr.send(data);
}
//時間格式化工具
Date.prototype.format = function(format) {
var date = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S+": this.getMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1
? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
}
}
return format;
}
//程序入口
chrome.storage.local.get("isenable", function(obj) {
chrome.storage.local.get("timeStart", function(obj) {
timeStart=obj.timeStart;
});
chrome.storage.local.get("timeEnd", function(obj) {
timeEnd=obj.timeEnd;
});
if(obj.isenable){
setTimeout(sent_req,6000);
chrome.storage.local.set({"log":"插件已經正常開啓!"});
}
});
以上是點擊插件圖標彈出的頁面,預想是用來顯示運行時的日誌的,效果不好,懶得改了。
<html>
<head>
<title>參數設定</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
<div style="width: 300px;height:60px;text-align: center">
<br/>
<p id="log">
插件日誌
</p>
</div>
<script src="js/popup.js"></script>
</body>
</html>
插件的配置部分
配置頁面,沒追求,自己隨便寫一個簡陋的頁面
<html>
<head>
<title>參數設定</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<style>
.big{
font-size: 30px;
}
.biginput{
width: 300px;
height: 50px;
font-size: 30px;
}
</style>
<body>
<div style="width: 80%;height:auto;text-align: center">
<br/>
<span class="big">開始時間:</span><input class="biginput" type="text" id="timeStart" value="2017-11-01 00:00:00"/><br/><br/>
<span class="big">結束時間:</span><input class="biginput" type="text" id="timeEnd" value="2017-11-01 00:30:00"/><br/><br/>
<span class="big">是否啓用:</span><input type="checkbox" id="isenable" /><br/><br/>
<input type="button" class="biginput" id="save" value="保存" /><br/>
</div>
<script src="js/options.js"></script>
</body>
</html>
配置頁面引用的腳本,一看就懂
//加載數據,顯示目前的配置
window.onload=function(){
chrome.storage.local.get("isenable", function(obj) {
document.getElementById('isenable').checked=obj.isenable
});
chrome.storage.local.get("timeStart", function(obj) {
document.getElementById('timeStart').value=obj.timeStart
});
chrome.storage.local.get("timeEnd", function(obj) {
document.getElementById('timeEnd').value=obj.timeEnd
});
}
//保存
document.getElementById('save').onclick = function(){
var timeStart = document.getElementById('timeStart').value;
var timeEnd = document.getElementById('timeEnd').value;
var isenable = document.getElementById('isenable').checked;
var p = {
timeStart:timeStart,
timeEnd:timeEnd,
isenable:isenable
}
chrome.storage.local.set(p,function(){
alert('設置已保存');
}
);
}
運行
打開插件管理
加載已經解壓的應用,選擇對應文件夾
發佈
直接點擊上圖的打包拓展程序,選擇文件夾,密鑰可選,如果是升級,可以選擇首次自動生成的密鑰
生成插件文件和證書
安裝
打開拓展頁面,直接拉過去,確定即可
這裏特別說一下,谷歌插件支持三種方法中的一種來儲存數據:
第一種是使用HTML5的localStorage;
第二種是使用Chrome提供的存儲API;
第三種是使用Web SQL Database。
localStorage就是h5自帶的
Chrome提供的存儲API和localStorage相似,拓展一些功能
如果儲存區域指定爲sync,數據可以自動同步;
content_scripts可以直接讀取數據,而不必通過background頁面;
在隱身模式下仍然可以讀出之前存儲的數據;
讀寫速度更快;
用戶數據可以以對象的類型保存。
localStorage是基於域名的,而content_scripts是注入到用戶當前瀏覽頁面中的,如果content_scripts直接讀取localStorage,所讀取到的數據是用戶當前瀏覽頁面所在域中的。所以通常的解決辦法是content_scripts通過runtime.sendMessage和background通信,由background讀寫擴展所在域(通常是chrome-extension://extension-id/)的localStorage,然後再傳遞給content_scripts。
Chrome提供的存儲API就沒有這些問題,可以跨頁面讀取,666
參考文檔:Chrome拓展應用及開發