Chrome插件开发快速入门

前言

周末学习了下Chrome插件的开发,总体来说入门还是比较容易的,动手配合一些demo就能了解基本的开发过程。这篇是一个学习笔记和总结,希望对大家也有所帮助。

什么是Chrome插件

Chrome插件其实和一个普通web应用一样都是由html+css+js打包组成的,插件可以使用Chrome提供的浏览器API,增强浏扩展览器的功能。
Chrome插件通常是.crx后缀的文件,通过谷歌网上应用商店下载或者在开发者模式中可以直接拖入到浏览器进行安装

用户界面网页(popup)

点击插件图标出来的弹窗其实就是一个html页面,弹窗要显示的内容,和工具栏小图标在manifest.json文件中配置。

弹窗在每次点击插件开始运行,弹窗关闭后结束,可以与background脚本交互。

基础用法

1.创建manifest.json

任何插件都必须要有这个文件,用来描述插件的元数据,插件的配置信息。

一个常用配置项如下:

{
// 必须
"manifest_version": 2,
"name": "插件名称a",
"version": "1.1.2",

// 推荐
"default_locale": "en",
"description": "插件的描述",
"icons": {
 "16": "img/icon.png",   // 扩展程序页面上的图标
 "32": "img/icon.png",   // Windows计算机通常需要此大小。提供此选项可防止尺寸失真缩小48x48选项。
 "48": "img/icon.png",   // 显示在扩展程序管理页面上
 "128": "img/icon.png"   // 在安装和Chrome Webstore中显示
},

// 可选
"background": {
 "page": "background/background.html",
 "scripts": ["background.js"],
 // 推荐
 "persistent": false
},
"browser_action": {
 "default_icon": "img/icon.png", 
 // 特定于工具栏的图标,至少建议使用16x16和32x32尺寸,应为方形,
 // 不然会变形
 "default_title": "悬浮在工具栏插件图标上时的tooltip内容",
 "default_popup": "hello.html"   // 不允许内联JavaScript。
},
"content_scripts": [ {
 "js": [ "inject.js" ],
 "matches": [ "http://*/*", "https://*/*" ],
 "run_at": "document_start"
 } ],
"permissions": [
 "contextMenus",
 "tabs",
 "http://*/*",
 "https://*/*"
],
"web_accessible_resources": [ "dist/*", "dist/**/*" ]
}

上面的配置项看起来内容较多,主要有以下几项:

  • 定义当前插件的名字,描述版本号等信息。
  • "manifest_version":现在应该总是2。
  • background
  • content_scripts
  • permissions:在background或是popup的js里使用一些chrome api,需要授权才能使用,例如要使用chrome.tabs.xxx的api,就要在permissions引入“tabs”

background、content_scripts的使用后面会详细介绍

我们先使用如下基本信息

manifest.json

 {
    "name": "My QrCode",
    "description" : "My QrCode Extension",
    "version": "1.0",
    "manifest_version": 2,
   
    "browser_action": {
        "default_popup": "./popup.html", 
        "default_icon": "./logo.png"
    },
  }

2.新建popup.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
            <h2>hello world!</h2>
</body>
</html>

并找个png的图作为logo

3.打包扩展程序

现在我们就可以看下效果了。

打包扩展程序:

打开chrome://extensions/,打开开发者模式,点击打包扩展程序,选择我们的开发目录

image-20200607172122923.png

注意:第一次打包的时候,会生成一个pem个人密钥文件,以后再次打包的时候就需要密钥文件了。

image-20200607163732018.png

4.加载已解压的扩展程序

image-20200607165136815.png

选择刚刚打包出来的文件即可

现在我们就可以看到效果了:

image-20200607165437236.png

实现一个二维码插件

在上面的基础上,我们实现一个简单的当前页面生成二维码插件:

popup.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="jquery.min.js"></script>
    <script type="text/javascript" src="qrcode.min.js"></script>
</head>
<body>
    <div id="qrcode"></div>
    <script type="text/javascript" src="popup.js"></script>
</body>
</html>

这里我们二维码生成用了这个开源项目:
https://github.com/davidshimjs/qrcodejs

把项目中的jquery.min.js、qrcode.min.js拷过来

注意:

由于popup不允许内联JavaScript,因此需要script标签引入外部脚本

popup.js

let url;

chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
    url = tabs[0].url
    var qrcode = new QRCode(document.getElementById("qrcode"), {
        text: url,
        width: 256,
        height: 256,
        colorDark : "#000000",
        colorLight : "#ffffff",
        correctLevel : QRCode.CorrectLevel.M
    });
});

chrome.tabs.query()这部分用来获取当前页面url

在manifest.json中加入允许:

{
    "name": "My QrCode",
        ...
    "permissions": ["activeTab"]
}

这样,我们一个简单的生成二维码插件就完成了

background

问题:
我们想在弹窗中写一个计数器,每点击一次加1。但发现Popup 弹窗无法记录数据

实现代码如下:

popup.html

    <input type="text" id="input" value="0">
    <button id="btn">+1</button>

popup.js

var count = 0;
$(function(){
    $('#input').val(count);
    $('#btn').click(function(){
        count = count+1;
        $('#input').val(count);
    });
})

当我们点击 “+1” ,输入框中的数字就会增加,然后关闭弹窗,再点开,发现数字又变成了0,这说明当我们关闭弹窗时,popup.html就被销毁了,我们在popup.js 中用count 存储的全局变量,也被销毁了。

解法:
由于popup在弹窗关闭后就销毁了。如果想要在popup中记录上次关闭后的结果的话,需要引入background

概念

background可以理解为插件运行在浏览器中的一个后台网站/脚本,注意它是与当前浏览页面无关的。
实际上这部分内容的配置情况也会写在manifest里,对应的是background配置项。单独拿出来讲,是彰显它的分量很重,也是一个插件常用的配置。从其中几个配置项项去了解一下什么是background script

manifest.json

   "background": {
    "page": "background/background.html",
    "scripts": ["background.js"],
    // 推荐
    "persistent": false
   },

page

可以理解为这个后台网站的主页,在这个主页中,有引用的脚本,其中一般都会有一个专门来管理插件各种交互以及监听浏览器行为的脚本,一般都起名为background.js。这个主页,不一定要求有。

scripts

这里的脚本其实跟写在page里html引入的脚本目的一样,个人的理解是,page的html在没有的情况下,那么脚本就需要通过这个属性引入了;
如果在存在page的情况下,一般在这里引入的脚本是专门为插件服务的脚本,而那些第三方脚本如jquery还是在page里引用比较好,或许这是一个众人的“潜规则”吧

persistent

所谓的后台脚本,在chrome扩展中又分为两类,分别运行于后台页面(background page)和事件页面(event page)中。两者区别在于,

前者(后台页面)持续运行,生存周期和浏览器相同,即从打开浏览器到关闭浏览器期间,后台脚本一直在运行,一直占据着内存等系统资源,persistent设为true;

后者(事件页面)只在需要活动时活动,在完全不活动的状态持续几秒后,chrome将会终止其运行,从而释放其占据的系统资源,而在再次有事件需要后台脚本来处理时,重新载入它,persistent设为false。

保持后台脚本持久活动的唯一场合是扩展使用chrome.webRequest API来阻止或修改网络请求。webRequest API与非持久性后台页面不兼容。

popup与background的通信

popup与background的交流,常见于popup要获取background里的某些“东西”

通信方式:

popup:

var bg = chrome.extension.getBackgroundPage();
bg.someMethod();    //someMethod()是background中的一个方法

实现一个计数器

对于计数器的例子来说:

manifest.json

    "background" : {
        "scripts": ["background.js"],
        "persistent": false
    },

background.js

var count = 0;

popup.js

var bg = chrome.extension.getBackgroundPage(); 
$(function(){
    $('#input').val(bg.count);
    $('#btn').click(function(){
        bg.count = bg.count+1;
        $('#input').val(bg.count);
    });
})

这样,按钮的点击次数就可以记录下来了

content script

概念

向符合条件的网页插入该js脚本,对网页做一些处理。它具有独立而富有包容性。

  • 独立,指它的工作空间,命名空间,域等是独立的,不会说跟插入到的页面的某些函数和变量发生冲突;

  • 包容性,指插件把自己的一些脚本(content script)插入到符合条件的页面里,作为页面的脚本,因此与插入的页面共享dom的,即用dom操作是针对插入的网页的,在这些脚本里使用的window对象跟插入页面的window是一样的。主要用在消息传递上(使用postMessage和onmessage)

配置也会写在manifest里,对应的是content_scripts配置项。

manifest.json

   "content_scripts": [ {
    "js": [ "inject.js" ],
    "matches": [ "http://*/*", "https://*/*" ],
    "run_at": "document_start"
    } ],

js

要插入到页面里的脚本。例子很常见,例如在一个别人的网页上,你要打开你做的扩展,对别人的网页做一些处理或者获取一些数据等,那怎么跟别人的页面建立起联系呢?就是通过把js里的这些脚本嵌入都别人的网页里。

matches

必需。匹配规则组成的数组,用来匹配页面url的,符合条件的页面将会插入js的脚本。当然,有可以匹配的自然会有不匹配的——exclude_matches。匹配规则:

developer.chrome.com/extensions/…

run_at

js配置项里的脚本何时插入到页面里呢,这个配置项来控制插入时机。有三个选择项:

  • document_start
  • document_end
  • document_idle(默认)

document_ start

style样式加载好,dom渲染完成和脚本执行前

document_end

dom渲染完成后,即DOMContentLoaded后马上执行

document_idle

在DOMContentLoaded 和 window load之间,具体是什么时刻,要视页面的复杂程度和加载时间,并针对页面加载速度进行了优化。

popup与content script通信

popup与content script通信 和 background与contentscript通信用法是一致的

发送消息:

chrome.runtime.sendMessege(
    message,
    function(response) {…}
)

第一个参数message为发送的消息(基础数据类型),回调函数里的第一个参数为background接收消息后返回的消息(如有)

接受消息:

chrome.runtime.onMessege.addListener(
    function(request, sender, sendResponse) {…}
)

监听发来的消息,request表示发来的消息,sendResponse是一个函数,用于对发来的消息进行回应,如 sendResponse('我已收到你的消息:'+JSON.stringify(request));

这里需要注意的是,默认情况下sendResponse函数的执行是同步的,如果在这个监听消息的处理函数的同步执行流程里没有发现sendResponse,则默认返回undefined,假设我们是要经过一个异步处理之后才调用sendResponse,已经为时已晚了。因此,我们可能需要异步执行sendResponse,这时我们在这个监听函数里的添加return true就能实现了。

实现一个搜索请求

实现一个向百度搜索框发送关键词,并提交搜索请求。

这个功能其实没什么用,主要是为了了解content_scripts和popup通信的流程

manifest.json

    "content_scripts": [
        {
            "matches": ["https://www.baidu.com/*"],
            "js": ["jquery.min.js","search-in-baidu.js"]
        }
    ],

上述配置表示当页面 url 地址匹配到 “https://www.baidu.com/*” 模式时才向页面中注入jquery.min.js, search-in-baidu.js 两个js 文件

一个插件里content-script有多个(一个页面一个),那么怎么向特定的content-script发送消息?

首先我们需要知道要向哪个content scripts发送消息,一般一个页面一份content scripts,而一个页面对应一个浏览器tab,每个tab都有自己的tabId,因此首先要获取要发送消息的tab对应的tabId。

/**
 * 获取当前选项卡id
 * @param callback - 获取到id后要执行的回调函数
 */
function getCurrentTabId(callback) {
    chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
        if (callback) {
            callback(tabs.length ? tabs[0].id: null);
        }
    });
}

当知道了tabId后,就使用该api进行发送消息

chrome.tabs.sendMessage(tabId, message, function(response) {...});

其中message为发送的消息,回调函数的response为content scripts接收到消息后的回传消息

在我们这个需求中:

popup.js

$(function(){
    var state = $('#state');
    $('#send').click(function () {//给对象绑定事件
        chrome.tabs.query({active:true, currentWindow:true}, function (tab) {//获取当前tab
            //向tab发送请求
            chrome.tabs.sendMessage(tab[0].id, { 
                action: "send",
                keyword: $('#keyword').val()
            }, function (response) {
                console.log(response);
                state.html(response.state)
            });
        });
    });
    $('#submit').click(function () {
        chrome.tabs.query({active:true, currentWindow:true}, function (tab) {
            chrome.tabs.sendMessage(tab[0].id, {  
               action: "submit"   
            }, function (response) {
                state.html(response.state)
            });
        });
    })
})

search-in-baidu.js

var kw = $('#kw');
var form = $('#form');
chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
        if (request.action == "send") {
            kw.val(request.keyword)
            sendResponse({state:'关键词填写成功!'});
        }
        if (request.action == "submit") {
            form.submit();
            sendResponse({state:'提交成功!'});
        }
    }
);

实现效果:

www.baidu.com中,使用插件,输入想要搜索的关键词,点击发送->点击提交,就查询出来了。

image-20200607230601203.png

image-20200607230642078.png
image-20200607230655872.png

开发调试

image-20200607231951536.png
  • 开发过程中,如果有报错,直接点击错误就能看到具体报错

  • 修改代码后插件没有自动更新的话也可以手动点击刷新按钮

  • popup调试:

    右键插件图标,审查弹出内容

image-20200607232711406.png
  • backgroud script调试:

    点击背景页,就可以看到backgroud script进行调试了,而且,还能在控制台调用chrome api。请求也可以在这里看到。

  • content script调试:
    平常我们打开F12选择到source选项的时候,一般都会显示在"page"下,选择content script,里边的就是各个扩展的内容脚本了。

image-20200607232212373.png

总结

chrome扩展开发入门还是比较容易的,主要有一下几个基本概念

  • manifest.json
  • popup
  • background
  • content script

了解后在实际开发中查阅相关资料即可

参考

更详细的popup、background、content script的通信方法可以看这篇:

一篇文章教你顺利入门和开发chrome扩展程序(插件)

其他:

谷歌(Chrome)浏览器插件开发教程

Chrome 扩展开发教程(2) ——Background的用法

Chrome 扩展开发教程(3)——content_scripts用法

官网教程:https://developer.chrome.com/extensions/getstarted

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350