1. 程式人生 > >Chrome外掛開發,美化網頁上的檔案列表。chrome-extension,background

Chrome外掛開發,美化網頁上的檔案列表。chrome-extension,background

上一篇文章 通過“content-scripts”的方式向頁面注入js和css來美化頁面,但是有一個弊端:一旦配置好需要注入的頁面,如果這個頁面地址以後發生變化,或者要新加一些URL進來,那麼得修改manifest.json這個檔案。試想如果一個Chrome外掛已經打包好,再去改程式碼是不可能的。

現在通過另一種方式來實現相同的功能,同時做到頁面地址動態可配置。以下是本次要處理的頁面(https://jiacrontab.iwannay.cn/download/)

每一行都是一個檔案的基本資訊,包括檔名、日期和檔案大小,檔名格式都是:專案名-版本-作業系統-平臺。現在檔案還少,檢視不是很困難,如果以後檔案多了,那麼找一個檔案是比較困難的。

這裡又不太可能在服務端讀取download目錄下的檔案,然後分類、分頁展示,那隻能在客戶端瀏覽器上想辦法了,Chrome外掛完全可以實現這個功能。

一、小試牛刀

開發Chrome外掛第一步,新建一個manifest.json檔案,並按官方文件的要求配置一些必要引數

{
  "name": "WebFileFilterPro",
  "version": "1.0.0",
  "description": "fast sort your webpage files",
  "icons": {
    "16": "images/16.png",
    "48": "images/48.png",
    
"128": "images/128.png" }, "browser_action": { "default_icon": "images/16.png", "default_title": "WebFileFilterPro" }, "manifest_version": 2 }

name:外掛名稱

version:版本號

description:外掛描述

icons:外掛的圖示,不同尺寸用於不同地方

browser_action:右上角外掛欄的圖示資訊,包括:圖示的圖片路徑、滑鼠劃上去提示的文字

manifest_version:固定為2

注:更多manifest.json的配置參考:

官方文件  至此,專案結構如下所示

一個簡單的沒有任何功能的Chrome外掛就完成了,去Chrome瀏覽器裡安裝下試試:開啟Chrome瀏覽器 - 更多工具 - 擴充套件程式,開啟“開發者模式” - 載入已解壓的擴充套件程式 - 選擇src目錄 - 確定

由圖可見,manifest.json裡配置的外掛名稱、版本號、外掛描述等資訊都體現了

二、加大難度

這個外掛的目的就是向特定頁面注入一些js和css檔案,實現可以分類檢視並且檔案多了還可以翻頁。新建一個配置頁面

 

把需要美化的頁面地址通過配置頁面儲存到Local Storage瀏覽器快取裡面,這樣做的好處是:需要美化的地址隨時可變,同時發現需要美化的頁面直接加進去就可以了,不用修改程式碼。

這個時候大殺器“background”就要出場了,官方描述是這樣的

Extensions are event based programs used to modify or enhance the Chrome browsing experience. Events are browser triggers, such as navigating to a new page, removing a bookmark, or closing a tab. Extensions monitor these events in their background script, then react with specified instructions.

大致意思是,“background”是常駐Chrome瀏覽器外掛後臺執行的,可以捕獲到很多行為:頁面跳轉、移除書籤、關閉一個tab頁等等。這裡不需要那麼強大的功能,只需要獲取使用者開啟的頁面地址,是不是在配置頁面配置的地址即可。

現在改下“manifest.json”檔案,加入新的配置

{
  "name": "WebFileFilterPro",
  "version": "1.0.0",
  "description": "fast sort your webpage files",
  "icons": {
    "16": "images/16.png",
    "48": "images/48.png",
    "128": "images/128.png"
  },
  "browser_action": {
    "default_icon": "images/16.png",
    "default_title": "WebFileFilterPro"
  },
  "options_page": "settings.html",
  "permissions": [
    "tabs",
    "http://*/*",
    "https://*/*"
  ],
  "background": {
    "scripts": [
      "js/background.js"
    ],
    "persistent": false
  },
  "manifest_version": 2
}

options_page:配置頁面地址(右鍵右上角外掛圖示,可以通過“選項”進入配置頁面)

background:後臺配置,包括js檔名和永續性設定

permissions:申請許可權列表

1)tabs:獲取使用者訪問頁面得URL地址,必須得有這個tabs許可權

2)http/https:向目標頁面裡注入css和js檔案需要的許可權

注:完整許可權api請訪問:permissions官方文件

現在來看下官方說的可以常駐後臺執行的“background.js”到底可以獲取到什麼些東西

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
    if (changeInfo.status == "complete") {
        var url = tab.url;
        console.log("使用者訪問:" + url);

        console.log("快取裡設定的頁面:" + localStorage.url);
    };
}); 

在頁面重新整理完成事件裡面獲取“使用者訪問的頁面地址”和“配置頁面裡面配置的地址”,看看控制檯輸出

 

兩個地址都正確的獲取到了,如果使用者訪問的地址和配置的地址吻合,那麼向這個頁面注入css和js檔案

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
    if (changeInfo.status == "complete") {
        var url = tab.url;
        console.log(url);

        if (localStorage.url != undefined && localStorage.url != '') {
            var urlList = localStorage.url.split("\n");

            if (urlList.indexOf(url) != -1) {
                chrome.tabs.insertCSS(tabId, { file: "css/bootstrap.min.css" });

                chrome.tabs.executeScript(tabId, { file: "js/jquery.min.js" });
                chrome.tabs.executeScript(tabId, { file: "js/bootstrap.min.js" });                
                chrome.tabs.executeScript(tabId, { file: "js/filelist.js" });
            }
        }
    };
}); 

使用“chrome.tabs.insertCSS”和“chrome.tabs.executeScript”這兩個api完成css和js的注入。有了jQuery和Bootstrap的注入,頁面就可以隨意美化了。同時在"filelist.js"裡面獲取頁面原有的內容,然後繫結的Bootstrap外掛上

//console.log("filelist.js");

var fileList = [];
var preList = document.getElementsByTagName("pre");
if (preList.length == 1) {
    var lineList = preList[0].innerHTML.split("\n");
    //console.log(lineList);
    if (lineList.length > 0) {
        var splitLinetext = []; var splitFileName = []; var fileName = '';
        $.each(lineList, function (i, v) {
            //console.log(v);
            if (i == 0) {
                return true;//continue;
            }
            if (v == undefined || v == "") {
                var line = parseInt(i);
                line += 1;
                console.log("line:" + line + " is empty");
                return true;
            }

            splitLinetext = v.split(/\s+/);
            if (splitLinetext.length != 5) {
                console.log(splitLinetext);
                return true;
            }

            fileName = splitLinetext[1].match(/>(\S*)</)[1];
            //console.log(fileName);
            if (fileName == null || fileName == '') {
                console.log("fileName is or empty");
                return true;
            }

            splitFileName = fileName.split('-');
            if (splitFileName.length < 3) {
                console.log(splitFileName);
                return true;
            }

            fileList.push({ PartA: splitFileName[0], PartB: splitFileName[1], FileTime: formatDate(splitLinetext[2] + ' ' + splitLinetext[3]), FileSize: splitLinetext[4], FileName: fileName });
        });
        //console.log(fileList);

        var nodeDoctype = document.implementation.createDocumentType('html', '', '');
        if (document.doctype) {
            document.replaceChild(nodeDoctype, document.doctype);
        } else {
            document.insertBefore(nodeDoctype, document.childNodes[0]);
        }

        $("html").attr("lang", "en");
        $("head").html('<head><meta charset="UTF-8"><link rel="shortcut icon" href="" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>FileList</title></head>');

        $("[rel='shortcut icon']").attr("href", chrome.extension.getURL("images/16.png"));

        $("body").removeAttr("bgcolor").html('<nav class="navbar navbar-default navbar-static-top"><div class="container"><div class="row"><div class="col-sm-2 col-md-2 col-lg-2"></div><div class="col-sm-10 col-md-10 col-lg-10"><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button></div><div id="navbar" class="collapse navbar-collapse"><ul class="nav navbar-nav"></ul></div></div></div></div></nav><div class="container"><div class="row"><div id="nav" class="col-sm-2 col-md-2 col-lg-2"><ul class="nav nav-pills nav-stacked"></ul></div><div class="col-sm-10 col-md-10 col-lg-10"><table id="table" class="table table-bordered"></table><ul class="pager"><span id="pageIndexSpan"></span>&nbsp;/&nbsp;<span id="pageSizeSpan"></span>&nbsp;/&nbsp;<span id="totalCountSpan"></span>&nbsp;&nbsp;<li id="previousPageLi"><a href="javascript:;">Prev</a></li>&nbsp;<li id="nextPageLi"><a href="javascript:;">Next</a></li></ul></div></div></div>');

        var partAs = getDistinctPartA();
        $.each(partAs, function (idx, val) {
            $("#navbar ul").append("<li data-value=\"" + val + "\"><a href='javascript:;'><strong>" + val + "</strong></a></li>");
        });
        if (partAs.length > 0) {
            if (localStorage.partA != undefined && $.inArray(localStorage.partA, partAs) != -1)
                navBarLiClick(localStorage.partA);
            else
                navBarLiClick(partAs[0]);
        }
        else {
            $("body").html('<div class="container"><div class="row"><div class="col-sm-12 col-md-12 col-lg-12"><h3>no data, pls wait and refresh this page :)</h3></div></div></div>');
        }
    }
    else {
        console.log("can't find any line in <pre> tag");
    }
}
else {
    console.log("page's <pre> tag count illegal:" + preList.length);
}

$("body").on("click", "#navbar ul li", function () {
    navBarLiClick($(this).attr("data-value"));
});

$("body").on("click", "#nav ul li", function () {
    navLiClick($(this).attr("data-parta"), $(this).attr("data-partb"));
});

$("body").on("click", "#table tbody tr", function () {
    $(this).css("background", "#DCDCDC").siblings().css("background", "");
});

$("body").on("click", "#previousPageLi", function () {
    if (localStorage.pageIndex != 1) {
        localStorage.pageIndex = parseInt(localStorage.pageIndex) - 1;
        initFileList(localStorage.partA, localStorage.partB);
    }
});

$("body").on("click", "#nextPageLi", function () {
    var totalPageCount = localStorage.totalCount % localStorage.pageSize == 0 ? localStorage.totalCount / localStorage.pageSize : Math.ceil(localStorage.totalCount / localStorage.pageSize);
    if (localStorage.pageIndex != totalPageCount) {
        localStorage.pageIndex = parseInt(localStorage.pageIndex) + 1;
        initFileList(localStorage.partA, localStorage.partB);
    }
});

function formatDate(dt) {
    var date = new Date(dt);
    var aaaa = date.getFullYear();
    var gg = date.getDate();
    var mm = (date.getMonth() + 1);

    if (gg < 10) gg = "0" + gg;
    if (mm < 10) mm = "0" + mm;

    var cur_day = aaaa + "-" + mm + "-" + gg;
    var hours = date.getHours()
    var minutes = date.getMinutes()
    //var seconds = date.getSeconds();

    if (hours < 10) hours = "0" + hours;
    if (minutes < 10) minutes = "0" + minutes;
    //if (seconds < 10) seconds = "0" + seconds;

    //return cur_day + " " + hours + ":" + minutes + ":" + seconds;
    return cur_day + " " + hours + ":" + minutes;
}

function getDistinctPartA() {
    return JSLINQ(fileList).Distinct(function () { return this.PartA; }).items;
}

function getDistinctPartB(partA) {
    return JSLINQ(fileList).Where(function () { return this.PartA == partA; }).Distinct(function () { return this.PartB; }).items;
}

function getFileListPage(partA, partB) {
    var totalPageCount = localStorage.totalCount % localStorage.pageSize == 0 ? localStorage.totalCount / localStorage.pageSize : Math.ceil(localStorage.totalCount / localStorage.pageSize);

    if (localStorage.pageIndex == 1) {
        $("#previousPageLi").addClass("disabled");
    }
    else {
        $("#previousPageLi").removeClass("disabled");
    }
    if (localStorage.pageIndex == totalPageCount) {
        $("#nextPageLi").addClass("disabled");
    }
    else {
        $("#nextPageLi").removeClass("disabled");
    }

    $("#pageIndexSpan").text(localStorage.pageIndex);
    $("#pageSizeSpan").text(localStorage.pageSize);
    $("#totalCountSpan").text(localStorage.totalCount);

    if (partB == 'all') {
        return JSLINQ(fileList).Reverse().Where(function () { return this.PartA == partA; }).Skip(parseInt(localStorage.pageSize) * (parseInt(localStorage.pageIndex) - 1)).Take(parseInt(localStorage.pageSize)).Select("PartB,FileTime,FileSize,FileName").items;
    }
    else {
        return JSLINQ(fileList).Reverse().Where(function () { return this.PartA == partA && this.PartB == partB; }).Skip(parseInt(localStorage.pageSize) * (parseInt(localStorage.pageIndex) - 1)).Take(parseInt(localStorage.pageSize)).Select("PartB,FileTime,FileSize,FileName").items;
    }
}

function getFileListTotalCount(partA, partB) {
    if (partB == 'all') {
        return JSLINQ(fileList).Count(function () { return this.PartA == partA; });
    }
    else {
        return JSLINQ(fileList).Count(function () { return this.PartA == partA && this.PartB == partB; });
    }
}

function navBarLiClick(partA) {
    $("#navbar ul li[data-value=\"" + partA + "\"]").addClass("active").siblings().removeClass("active");

    $("#nav ul").empty();
    $("#nav ul").append("<li data-parta=\"" + partA + "\" data-partb=\"all\"><a href='javascript:;'>all</a></li>");

    var partB = '';
    var list = getDistinctPartB(partA);
    //list.sort();
    list.reverse();
    $.each(list, function (i, v) {
        if (localStorage.partB != undefined && localStorage.partA != undefined && localStorage.partA == partA && localStorage.partB == v) {
            partB = localStorage.partB;
            $("#nav ul").append("<li class='active' data-parta=\"" + partA + "\" data-partb=\"" + v + "\"><a href='javascript:;'>" + v + "</a></li>");
        }
        else {
            $("#nav ul").append("<li data-parta=\"" + partA + "\" data-partb=\"" + v + "\"><a href='javascript:;'>" + v + "</a></li>");
        }
    });
    if (partB == '') {
        partB = 'all';
        $("#nav ul li:first").addClass("active");
    }
    initFileList(partA, partB);
}

function navLiClick(partA, partB) {
    $("#nav ul li[data-partb=\"" + partB + "\"]").addClass("active").siblings().removeClass("active");
    initFileList(partA, partB);
}

function initFileList(partA, partB) {
    if (localStorage.partA != partA || localStorage.partB != partB) {
        localStorage.pageIndex = 1;
        localStorage.pageSize = 10;
    }

    if (localStorage.partA != partA) {
        localStorage.partA = partA;
    }
    if (localStorage.partB != partB) {
        localStorage.partB = partB;
    }

    var totalCount = getFileListTotalCount(partA, partB);
    if (localStorage.totalCount != totalCount) {
        localStorage.totalCount = totalCount;
    }

    var fileList = getFileListPage(partA, partB);

    $("#table").empty();
    // $("#table").append("<thead><tr><th>FileName</th><th>Time</th><th>FileSize</th><th>Operate</th></tr></thead><tbody>");
    if (fileList.length > 0) {
        $.each(fileList, function (i, v) {
            $("#table").append("<tr><td>" + v.FileName + "</td><td>" + v.FileTime + "</td><td>" + v.FileSize + "</td><td><a href='" + v.FileName + "' target='_blank'>Link</a></td></tr>");
        });
    }
    else {
        $("#table").append("<tr><td colspan='4'><center>no data</center></td></tr>");
    }
    $("#table").append("</tbody>");
}
View Code

獲取原頁面裡面的所有檔案資訊,專案名去重放在頂部,用Bootstrap的navBar外掛展示,版本號由上到下依次放在左側的nav外掛上,右側則用Bootstrap的table展示檔案詳細資訊。效果如下

至此,分類、翻頁展示功能完美實現,以後就算檔案再多也可以快速找到了。

三、總結

通過“background”的方式注入,優點是頁面隨時可以配置,很方便,缺點是使用者訪問的每個頁面都需要“在background.js”裡面挨個過濾,感覺效率不行。個人還是比較喜歡“content_scripts”這種防止注入。

四、其他

本文僅拋磚引玉,Chrome外掛可以做的事情還有很多,更多使用場景大家自己去發揮。原始碼地址  Chrome商店  開發文件