代码改变世界

ChromeExtension那些事儿

2017-12-22 00:21  猴子猿  阅读(...)  评论(...编辑  收藏

Chrome Extension是什么呢?

简而言之,就是Chrome扩展,它是基于Chrome浏览器的,我们可以理解它为一个独立运行在Chrome浏览器下的APP,当然核心编程语言就是JavaScript咯,然后结合HTML以及CSS来开发。重点是,这个“APP”功能强大,可以独自运行,亦可以与打开的网页、Chrome控制面板(DevTools)、第三方插件等等进行通信,且,Google允许ChromeExtension不必受限于跨域限制,结合以上种种优点,固,我们可以使用ChromeExtension,结合自身业务,开发出许多提高工作效率的工具。

部署ChromeExtension也很简单,如下:

好了,现在来听听属于ChromeExtension它的故事。

一、概要及manifest.json

ChromeExtension是Chrome提出来的一个概念,其实正如上文所说,核心编程语言就是JavaScript,然后提供一切通信、存储接口,大致就差不多了。

需要注意的是,ChromeExtension都包含一个Manifest文件——manifest.json,这个文件可以告诉Chrome关于这个扩展的相关信息,它是整个扩展的入口,也是Chrome扩展必不可少的部分。且必须包含name、version和manifest_version属性,其他常用的可选属性还有browser_action、page_action、background、permissions、options_page、content_scripts。

所以我们可以保留一份manifest.json模板,当编写新的扩展时直接填入相应的属性值就OK了。

manifest.json模板及相关属性解释如下:

{
    "manifest_version": 2,
    //定义chrome扩展的名称
    "name": "My Extension",
    //定义chrome扩展的版本
    "version": "versionString",
    "default_locale": "en",
    //定义chrome扩展的描述
    "description": "A plain text description",
    //定义了扩展相关图标文件的位置
    "icons": {
        "16": "images/icon16.png",
        "48": "images/icon48.png",
        "128": "images/icon128.png"
    },
    /*
      Browser Actions将扩展图标置于Chrome浏览器工具栏中,地址栏的右侧。如果声明了popup页面,当用户点击图标时,在图标的下侧会打开这个页面。同时图标上面还可以附带badge——一个带有显示有限字符空间(只能显示4字节长度信息)的区域——用以显示一些有用的信息,如未读邮件数等。且Badge目前只能够通过JavaScript设定显示的内容,同时Chrome还提供了更改badge背景的方法。如果不定义badge的背景颜色,默认将使用红色。例如,显示了一个背景颜色为蓝色,内容为“Dog”的badge:
      chrome.browserAction.setBadgeBackgroundColor({color: '#0000FF'});
      chrome.browserAction.setBadgeText({text: 'Dog'});
    */
    "browser_action": {
        //通过setIcon方法可以动态更改扩展的图标,chrome.browserAction.setIcon(details, callback)
        "default_icon": {
            "19": "images/icon19.png",
            "38": "images/icon38.png"
        },
        //定义了当用户鼠标悬停于扩展图标上所显示的文字,chrome.browserAction.setTitle({title: 'This is a new title'})
        "default_title": "Extension Title",
        /*
          定义了当用户单击扩展图标时所显示页面的文件位置, 值得注意的是Chrome不允许将JavaScript代码段直接内嵌入HTML文档,所以我们需要通过外部引入的方式引用JS文件。由于其在关闭后,就相当于用户关闭了相应的标签页,这个页面不会继续运行。当用户再次打开这个页面时,所有的DOM和js空间变量都将被重新创建,所以不要在popup页面的js空间变量中保存数据,而是利用localStorage和chrome.storage将数据保存在用户的硬盘上。
        */
        "default_popup": "popup.html"
    },
    /*
      Page Actions与Browser Actions非常类似,除了Page Actions没有badge外,其他Browser Actions所有的方法Page Actions都有。另外的区别就是,Page Actions并不像Browser Actions那样一直显示图标,而是可以在特定标签特定情况下显示或隐藏,所以它还具有独有的show和hide方法。
        chrome.pageAction.show(integer tabId);
        chrome.pageAction.hide(integer tabId);
        另,tabId为标签(下面会具体讲解)id,可以通过tabs接口获取。
    */
    "page_action": {
        "default_icon": {
            "19": "images/icon19.png",
            "38": "images/icon38.png"
        },
        "default_title": "Extension Title",
        "default_popup": "popup.html"
    },
    /*
      在Manifest中指定background域可以使扩展常驻后台。background可以包含三种属性,分别是scripts、page和persistent。如果指定了scripts属性,则Chrome会在扩展启动时自动创建一个包含所有指定脚本的页面;如果指定了page属性,则Chrome会将指定的HTML文件作为后台页面运行。通常我们只需要使用scripts属性即可,除非在后台页面中需要构建特殊的HTML——但一般情况下后台页面的HTML我们是看不到的。persistent属性定义了常驻后台的方式——当其值为true时,表示扩展将一直在后台运行,无论其是否正在工作;当其值为false时,表示扩展在后台按需运行,这就是Chrome后来提出的Event Page。Event Page可以有效减小扩展对内存的消耗,如非必要,请将persistent设置为false。注意,persistent的默认值为true。
    */
    "background": {
        "scripts": ["background.js"]
    },
    /*
      可以指定将哪些脚本何时注入到哪些页面中,当用户访问这些页面后,相应脚本即可自动运行,从而对页面DOM进行操作。属性值为数组类型,数组的每个元素可以包含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的值共同决定.

    注意:content_scripts中的脚本只是共享页面的DOM(DOM中的自定义属性不会被共享),而并不共享页面内嵌JavaScript的命名空间。也就是说,如果当前页面中的JavaScript有一个全局变量a,content_scripts中注入的脚本也可以有一个全局变量a,两者不会相互干扰。当然你也无法通过content_scripts访问到页面本身内嵌JavaScript的变量和函数。
    */
    "content_scripts": [
        {
            "matches": ["http://www.google.com/*"],
            "css": ["mystyles.css"],
            "js": ["jquery.js", "myscript.js"]
        }
    ],
    /*
      有一些扩展允许用户进行个性化设置,这样就需要向用户提供一个选项页面。Chrome通过Manifest文件的options_page属性为开发者提供了这样的接口,可以为扩展指定一个选项页面。当用户在扩展图标上点击右键,选择菜单中的“选项”后,就会打开这个页面
    */
    "options_page": "options.html",
    /*
      浏览器出于安全考虑是不允许跨域, 但这个规则如果同样限制Chrome扩展应用,就会使其能力大打折扣,所以Google允许Chrome扩展应用不必受限于跨域限制。但出于安全考虑,需要在Manifest的permissions属性中声明需要跨域的权限。
    */
    "permissions": [
        "*://www.google.com/*"
    ],
    // 为notification服务,桌面通知功能
    "web_accessible_resources": [
        "images/*.png"
    ]
}
二、存储

对于网站来说,用户的设置通常保存在Cookies中,或者保存在网站服务器的数据库中。对于JavaScript来说,一些数据可以保存在变量中。
但,如果用户重新启动浏览器,这些数据就会消失。那么如何在扩展中保存用户的设置呢?我们可以使用HTML5新增的localStorage接口。
当然,Chrome为扩展应用提供了存储API,以便将扩展中需要保存的数据写入本地磁盘。Chrome提供的存储API可以说是对localStorage的改进,它与localStorage相比有以下区别:
  1.如果储存区域指定为sync,数据可以自动同步;
  2.content_scripts可以直接读取数据,而不必通过background页面;
  3.在隐身模式下仍然可以读出之前存储的数据;
  4.读写速度更快;
  5.用户数据可以以对象的类型保存。
对于第二点需要说明一下。首先localStorage是基于域名的,而content_scripts是注入到用户当前浏览页面中的,如果content_scripts直接读取localStorage,所读取到的数据是用户当前浏览页面所在域中的。所以通常的解决办法是“content_scripts”通过runtime.sendMessage和“background”通信,由“background”读写扩展所在域(通常是chrome-extension://extension-id/)的localStorage,然后再传递给content_scripts。

使用Chrome存储API必须要在Manifest的permissions中声明"storage",之后才有权限调用。Chrome存储API提供了2种储存区域,分别是sync和local。两种储存区域的区别在于,sync储存的区域会根据用户当前在Chrome上登陆的Google账户自动同步数据,当无可用网络连接可用时,sync区域对数据的读写和local区域对数据的读写行为一致。对于每种储存区域,Chrome又提供了5个方法,
分别是get、getBytesInUse、set、remove和clear,如下:

/*
StorageArea为sync或则local
例, chrome.storage.sync.get(...)
*/
chrome.storage.StorageArea.get(keys, function(result){
  console.log(result);
});
chrome.storage.StorageArea.getBytesInUse(keys, function(bytes){
  console.log(bytes);
});
chrome.storage.StorageArea.set(items, function(){
  //do something
});
chrome.storage.StorageArea.remove(keys, function(){
  //do something
});
chrome.storage.StorageArea.clear(function(){
  //do something
});

Chrome同时还为存储API提供了一个onChanged事件,当存储区的数据发生改变时,这个事件会被触发,如下:

/*
callback会接收到两个参数,第一个为changes,第二个是StorageArea。changes是个对象,键为更改的属性名称,值包含两个属性,分别为oldValue和newValue
*/
chrome.storage.onChanged.addListener(function(changes, areaName){
  console.log('Value in '+areaName+' has been changed:');
  console.log(changes);
});
三、通信

Chrome提供了4个有关ChromeExtension页面间相互通信的接口,分别是runtime.sendMessageruntime.onMessageruntime.connectruntime.onConnect。

且,Chrome提供的大部分API是不支持在"content_scripts"中运行的,但runtime.sendMessageruntime.onMessage可以在"content_scripts"中运行,所以扩展的其他页面也可以同content_scripts相互通信。

/*
extensionId(optional)为所发送消息的目标扩展,如果不指定这个值,则默认为发起此消息的扩展本身;
message(required)为要发送的内容,类型随意,内容随意
options(optional)
callback(optional)用于接收返回结果
*/
chrome.runtime.sendMessage(extensionId, message, options, callback)
/*
callback(required)接收到的参数有三个,分别是message、sender和sendResponse。
其中sender对象包含4个属性,分别是tab、id、url和tlsChannelId,tab是发起消息的标签(下节会详讲)
*/ chrome.runtime.onMessage.addListener(callback)

 例如,popup.html与backgroud可以如下通信:

//popup.html
chrome.runtime.sendMessage('Hello', function(response){
    document.write(response);
});

//background
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
    if(message == 'Hello'){
        sendResponse('Hello from background.');
    }
});

查看popup.html页面会发现有输出“Hello from background.”

四、标签

标签的意思就是在浏览器中打开的一个个页面,如下:

Chrome通过tabs方法提供了管理标签的方法与监听标签行为的事件,大多数方法与事件是无需声明特殊权限的,但有关标签的urltitlefavIconUrl的操作(包括读取),都需要声明tabs权限。

"permissions": [
    "tabs"
]

获取标签信息。Chrome提供了三种获取标签信息的方法,分别是getgetCurrentqueryget方法可以获取到指定id的标签,getCurrent则获取运行的脚本本身所在的标签,query可以获取所有符合指定条件的标签。

以getCurrent为例,代码如下:

chrome.tabs.getCurrent(function(tab){
    console.log(tab);
});

重点是,ChromeExtension也可以与指定的标签通信(标签中注入了"content_scripts"),方法如下:

chrome.tabs.sendMessage(tabId, message, function(response){
    console.log(response);
});
五、拓展阅读

[1]. Chrome Extensions

[2]. Chrome扩展及应用开发