Chrome浏览器插件编程入门

18 May 2013

其实在高中毕业那会儿就有想编写浏览器插件的念头,主要是当时玩一个网页游戏很辛苦,想写个外挂练级。后来找到一些外挂,有一个很牛,独立程序,不知道通过什么手段可以获取打开的web页面的数据(解析后),然后应该是通过给浏览器发送消息完成一些任务。当时一直想了解下,不过问了很多人也没人知道具体是怎么实现的。

去年,不知道怎么看到关于chrome插件文章,知道了chrome的插件是纯js编写的(firefox应该也是)。正好前段时间有个朋友问我有没有办法监控twitter,因为nike官网会定时发布一些限量的鞋子,所以。。。

我想了想还是用写浏览器插件最方便,于是开始翻阅资料开始弄这个东西。官方文档在这里:http://developer.chrome.com/extensions/index.html

看完上面的东西呢,大概可以了解到整个chrome插件的结构。主要就是background.htm 和 content.js 两个大部分。前者是执行插件的主要功能,里面可以包含js来完成插件的主要控制逻辑。后者呢是注入到当前页面的一个脚本,用来检测和操作当前页面的dom元素。manifest.json是配置文件,定义了background page,content scripts,哪些页面执行插件,图标等等。

我的插件功能是:监控twitter页面,如果发现新的推文,并且是限量版鞋子的发布,就自动跳转到推文中给的链接,然后自动选择尺码并提交表单。

具体流程是:background.js 不断发送消息给 content script,让content script检测是否有新的推文更新并检测是不是新品发布。如果是的话response给background.js,background.js 会update tab的url属性,进行跳转,当页面加载完成之后,再通知content script进行表单填写和提交。

下面是代码清单: manifest.json:


{
    "name": "NikeStore Twitter Helper",
    "manifest_version": 2,
    "version": "0.1",
    "background": 
    {
        "scripts":
        [
            "background.js"
        ]
    },
    "content_scripts": [
    {
      "matches": ["http://twitter.com/*","https://twitter.com/*","http://store.nike.com/*"],
      "css": ["mystyles.css"],
      "js": ["jquery-1.9.0.js", "NikeStore.js"]
    }],
    "permissions":
    [
        "tabs",
        "http://*/*",
        "https://*/*"
    ],
    "browser_action":
    {
        "name": "Click to change the icon's color",
        "default_title":"title....",
        "default_badge":"badg",
        "default_icon":"image/nike2.png"
    }
}

我这里background 没有定义page属性,之定义了scripts,chrome会自动生成一个默认的“_generated_background_page.html” 注意到content_sripts 下面有matches属性,定义了匹配成功哪些pattern的网站才执行脚本。

background.js:

var isRun = false;
var timer;
 
function RunStop()
{
    isRun = !isRun;
    if(isRun)
    {
        chrome.browserAction.setIcon({"path":"image/nike.png"});
        timer = setInterval(checkAvalible,1000);
    }else
    {
        chrome.browserAction.setIcon({"path":"image/nike2.png"});
        clearInterval(timer);
    }
}
 
function checkAvalible()
{
    console.log("checkAvalible");
    chrome.tabs.getSelected(null,
        function(tab)
        {
            chrome.tabs.sendRequest(tab.id, {type: "checkAvalible"},
            function(response)
            {
                //console.log(response.data);
                var url = response.data;
                chrome.tabs.update(tab.id,{url:url},watchNikeStore)
            });
        }
    );
}
 
function watchNikeStore(tab)
{
    //停止刷新监测
    RunStop();
    //开始监控 nike store 加载完成
    chrome.tabs.onUpdated.addListener(SubmitCart);
    console.log(tab.url);
    console.log("SubmitCarted!!!!");
}
 
function SubmitCart(tabId,changeInfo,tab)
{
    console.log('tab updated:'+tab.url);
    console.log(changeInfo);
    if(changeInfo.status != 'complete' || tab.url.indexOf('http://store.nike.com') == -1)
    {
        return;
    }
    chrome.tabs.sendRequest(tabId, {type: "SubmitCart"});
}
 
chrome.browserAction.onClicked.addListener(RunStop);

注意到这里使用了 chrome.browserAction 来控制插件的按钮。以及使用chrome.tabs.sendRequest来与content_script进行通讯。 因为background的脚本是无法直接操作tab里面的dom元素的,所以需要通过通讯的方式来远程调用 content_script里面的函数进行dom操作。 稍后的content script代码里面将演示如何接受request。这里还有用到了监控tab update事件,通过chrome.tabs.onUpdated.addListener 添加回调,可以知道当前页面的url变化了。chrome.tabs.update可以改变tab的属性,我在这里更新url,即进行了一次页面跳转。

NikeStore.js:

var isRun = false;
 
//处理twitter页面
 
function checkAvalible(request, sender, sendResponse)
{
    //alert($($(".js-tweet-text")[0]).text());
    var statuses = $("#stream-items-id .content");
    for(var i=0; i < statuses.length; ++i)
    {
        var time = $($(statuses[i]).find("._timestamp")[0]).attr("data-time");
        var status = $($(statuses[i]).find(".js-tweet-text")[0]);
        var time2 = new Date(parseInt(time) * 1000);
        //alert(status.text() +'n' + time +'n'+ time2.toLocaleString());
        if(status.text().indexOf('available') == -1)
        {
            continue;
        }
        var links = status.find("a");
        for(var j=0; j < links.length; ++j)
        {
            var link = $(links[j]);
            var url = link.attr('href');
            if(url.indexOf("http://t.co") == -1)
            {
                continue;
            }
            return url;
        }
        break;
    }
    return "";
}
 
//处理nike store 页面
 
function SubmitCart()
{
    $(document).ready(function()
    {
 
        //$('*[value="3211862:4Y"]').trigger("click");
        //$('*[name="skuAndSize"]').attr("value","3211862:4Y");
        $('*[name="skuAndSize"]').val("3211862:4Y");
        $('.selectBox-label').text("(4Y)");
 
        $('.add-to-cart').trigger("click");
 
        alert("clicked!");
    });
    //alert("registed!");
}
 
 
 
//消息转发
function RequestHandle(request, sender, sendResponse)
{
    console.log(sender.tab ?
            "from a content script:" + sender.tab.url :
            "from the extension");
    if (request.type == "checkAvalible")
    {
        sendResponse({data:checkAvalible(request, sender, sendResponse)});
    }
    else if (request.type == "SubmitCart")
    {
        SubmitCart();
        sendResponse({data:"ok"});
    }
    else if (request.type == "setRun")
    {
        isRun = true;
        sendResponse({data:"ok"});
    }
    else if (request.type == "setStop")
    {
        isRun = false;
        sendResponse({data:"ok"});
    }
    else
        sendResponse({data:"error"});
}
 
function main()
{
    console.log("started...");
    chrome.extension.onRequest.addListener(RequestHandle);
}
 
main();

上面的脚本就是content script了,宿在真实的页面中,但是与页面的js在不同的沙盒里面。可以用来操作dom。 chrome.extension.onRequest.addListener 用来接受 background.js 发送来的消息。 sendResponse用来进行回应。

完整代码在:https://github.com/dingyaguang117/NikeStore