谷歌瀏覽器插件開發

谷歌瀏覽器插件開發

簡介

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拓展應用及開發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章