陈建华的博客
专注web开发
【chrome扩展】Chrome扩展基础(第二章)
2014-11-29 23:11:50   阅读4184次

本章会讲解Chrome扩展的一些基础功能,这些基础的功能在后续的扩展编写中可能会被频繁用到,所以有必要提前进行详细的讲解。本章会配有多个实例,一步步带着读者完成一个个有趣的例子。



2.1操作用户正在浏览的页面


通过Chrome扩展我们可以对用户当前浏览的页面进行操作,实际上就是对用户当前浏览页面的DOM进行操作。通过Manifest中的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中的脚本只是共享页面的DOM1,而并不共享页面内嵌JavaScript的命名空间。也就是说,如果当前页面中的JavaScript有一个全局变量a,content_scripts中注入的脚本也可以有一个全局变量a,两者不会相互干扰。当然你也无法通过content_scripts访问到页面本身内嵌JavaScript的变量和函数。

1 DOM中的自定义属性不会被共享。

下面我们来写一个恶作剧的小扩展,名字就叫做永远点不到的搜索按钮吧 :)

首先创建Manifest文件,内容如下:

{
   "manifest_version": 2,
   "name": "永远点不到的搜索按钮",
   "version": "1.0",
   "description": "让你永远也点击不到Google的搜索按钮",
   "content_scripts": [
       {
           "matches": ["*://www.google.com/"],
           "js": ["js/cannot_touch.js"]
       }
   ]
}

在content_scripts属性中我们定义了一个匹配规则,当URL符合*://www.google.com/规则的时候,就将js/cannot_touch.js注入到页面中。其中*代表任意字符,这样当用户访问http://www.google.com/和https://www.google.com/时就会触发脚本。

右键单击搜索按钮,选择“审查元素”,我们发现Google搜索按钮的id为'gbqfba'。

01YnEx6POjcL.png

通过Chrome浏览器的开发者工具可以看到Google搜索按钮的id

接下来我们开始编写cannot_touch.js。

function btn_move(el, mouseLeft, mouseTop){
   var leftRnd = (Math.random()-0.5)*20;
   var topRnd = (Math.random()-0.5)*20;
   var btnLeft = mouseLeft+(leftRnd>0?100:-100)+leftRnd;
   var btnTop = mouseTop+(topRnd>0?30:-30)+topRnd;
   btnLeft = btnLeft<100?(btnLeft+window.innerWidth-200):(btnLeft>window.innerWidth-100?btnLeft-window.innerWidth+200:btnLeft);
   btnTop =  btnTop<100?( btnTop+window.innerHeight-200):(btnTop>window.innerHeight-100?btnTop-window.innerHeight+200:btnTop);
   el.style.position = 'fixed';
   el.style.left = btnLeft+'px';
   el.style.top = btnTop+'px';
}
function over_btn(e){
   if(!e){
       e = window.event;
   }
   btn_move(this, e.clientX, e.clientY);
}
document.getElementById('gbqfba').onmouseover = over_btn;

由于Manifest将此脚本的位置指定到了js/cannot_touch.js,所以要记得将这个脚本保存到扩展文件夹中的js文件夹下,否则会出现错误。

01YnEyYzvLdk.png

永远点不到的搜索按钮”扩展运行的结果

可以看出,content_scripts很像Userscript,它就是将指定的脚本文件插入到符合规则的特定页面中,从而使插入的脚本可以对页面的DOM进行操作。

这个扩展的源码可以在

https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/cannot_touch下载到。


2.2跨域请求

跨域指的是JavaScript通过XMLHttpRequest请求数据时,调用JavaScript的页面所在的域和被请求页面的域不一致。对于网站来说,浏览器出于安全考虑是不允许跨域。另外,对于域相同,但端口或协议不同时,浏览器也是禁止的。下表给出了进一步的说明:

12112.jpg

但这个规则如果同样限制Chrome扩展应用,就会使其能力大打折扣,所以Google允许Chrome扩展应用不必受限于跨域限制。但出于安全考虑,需要在Manifest的permissions属性中声明需要跨域的权限。

比如,如果我们想设计一款获取维基百科数据并显示在其他网页中的扩展,就要在Manifest中进行如下声明:

{
   ...
   "permissions": [
       "*://*.wikipedia.org/*"
   ]
}

这样Chrome就会允许你的扩展在任意页面请求维基百科上的内容了。

我们可以利用如下的代码发起异步请求:

function httpRequest(url, callback){
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.onreadystatechange = function() {
       if (xhr.readyState == 4) {
           callback(xhr.responseText);
       }
   }
   xhr.send();
}

这样每次发起请求时,只要调用httpRequest函数,并传入要请求的URL和接收返回结果的函数就可以了。为什么要使用callback函数接收请求结果,而不直接用return将结果作为函数值返回呢?因为XMLHttpRequest不会阻塞下面代码的运行。

为了更加明确地说清上述问题,让我们来举两个例子。

function count(n){
   var sum = 0;
   for(var i=1; i<=n; i++){
       sum += i;
   }
   return sum;
}
var c = count(5)+1;
console.log(c);

上面这个例子会在控制台显示16,因为count(5)=1+2+3+4+5=15,c=15+1=16。我们再看下面的例子:

function httpRequest(url){
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.onreadystatechange = function() {
       if (xhr.readyState == 4) {
           return xhr.responseText;
       }
   }
   xhr.send();
}
var html = httpRequest('test.txt');
console.log(html);

01YnF7uvbNFI.png

上例运行结果

通过上图可以发现,虽然请求的资源内容为Hello World!,但却并没有正确地显示出来。

对于第一个例子,count函数是一个阻塞函数,在它没有运行完之前它会阻塞下面的代码运行,所以直到count计算结束后才将结果返回后再加1赋给变量c,最后将变量c的值打印出来。而第二个例子中的httpRequest函数不是一个阻塞函数,在它没运行完之前后面的代码就已经开始运行,这样html变量在httpRequest函数没返回值之前就被赋值,所以最终的结果必然就是undefined了。

既然这样,如何将非阻塞函数的最终结果传递下去呢?方法就是使用回调函数。在Chrome扩展应用的API中,大部分函数都是非阻塞函数,所以使用回调函数传递结果的方法以后会经常用到。

让我们来用回调函数的形式重写第二个例子:

function httpRequest(url, callback){
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.onreadystatechange = function() {
       if (xhr.readyState == 4) {
           callback(xhr.responseText);
       }
   }
   xhr.send();
}
var html;
httpRequest('test.txt', function(result){
   html = result;
   console.log(html);
});

01YnF85a9xTF.png

改进后第二个例子的结果

可以看到httpRequest函数运行的结果已经被正确地打印出来了。

下面来实战编写一款显示用户IP的扩展。

{
   "manifest_version": 2,
   "name": "查看我的IP",
   "version": "1.0",
   "description": "查看我的电脑当前的公网IP",
   "icons": {
       "16": "images/icon16.png",
       "48": "images/icon48.png",
       "128": "images/icon128.png"
   },
   "browser_action": {
       "default_icon": {
           "19": "images/icon19.png",
           "38": "images/icon38.png"
       },
       "default_title": "查看我的IP",
       "default_popup": "popup.html"
   },
   "permissions": [
       "http://sneezryworks.sinaapp.com/ip.php"
   ]
}

上面的Manifest定义了这个扩展允许对http://sneezryworks.sinaapp.com/ip.php发起跨域请求,其他的属性在1.2节中都有介绍,在此就不再赘述了。

popup.html的结构也完全可以按照时钟的扩展照抄下来,只是个别元素的id和脚本的路径根据当前扩展的名称稍加更改,同样不再赘述。

<html>
 <head>
  <style>
  * {
   margin: 0;
   padding: 0;
  }
  body {
   width: 400px;
   height: 100px;
  }
  div {
   line-height: 100px;
   font-size: 42px;
   text-align: center;
  }
  </style>
 </head>
 <body>
  <div id="ip_div">正在查询……</div>
  <script src="js/my_ip.js"></script>
 </body>
</html>

下面编写my_ip.js。

function httpRequest(url, callback){
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            callback(xhr.responseText);
        }
    }
    xhr.send();
}
httpRequest('http://sneezryworks.sinaapp.com/ip.php', function(ip){
    document.getElementById('ip_div').innerText = ip;
});

01YnF9BL2PEY.png


查看我的IP”扩展运行结果

作为一个开发者,安全问题永远都不应被轻视。在你从外域获取到数据后,不要轻易作为当前页面元素的innerHTML直接插入,更不要用eval函数去执行它,否则很可能将用户置于危险的境地。如果要将请求到的数据写入页面,可以使用innerText,就像我们这个查看IP的扩展那样。如果是JSON格式是数据就使用JSON.parse函数去解析。为了避免请求数据返回的格式错误,结合try-catch一起使用也是不错的选择。

本节中扩展的源码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/what_is_my_ip  下载到。


2.3常驻后台

有时我们希望扩展不仅在用户主动发起时(如开启特定页面或点击扩展图标等)才运行,而是希望扩展自动运行并常驻后台来实现一些特定的功能,比如实时提示未读邮件数量、后台播放音乐等等。

Chrome允许扩展应用在后台常驻一个页面以实现这样的功能。在一些典型的扩展中,UI页面,如popup页面或者options页面,在需要更新一些状态时,会向后台页面请求数据,而当后台页面检测到状态发生改变时,也会通知UI界面刷新。

后台页面与UI页面可以相互通信,这将在后续的章节中做进一步的讲解,本节将主要讲解后台页面是如何工作的。

在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。

由于编写一个只有后台页面的扩展,很难看到扩展运行的结果,所以我决定在本节中破例使用一个尚未讲到但是很简单的扩展功能,动态改变扩展图标,这在后面的例子中会进行说明。

下面我们来编写一款实时监视网站在线状态的扩展。思路很简单,每隔5秒就发起一次连接请求,如果请求成功就代表网站在线,将扩展图标显示为绿色,如果请求失败就代表网站不在线,将扩展图标显示为红色。

下面是这个扩展的Manifest文件,此例中以检测www.google.cn为例,你可以根据自己的意愿更改为其他的网站。

{
    "manifest_version": 2,
    "name": "Google在线状态",
    "version": "1.0",
    "description": "监视Google是否在线",
    "icons": {
        "16": "images/icon16.png",
        "48": "images/icon48.png",
        "128": "images/icon128.png"
    },
    "browser_action": {
        "default_icon": {
            "19": "images/icon19.png",
            "38": "images/icon38.png"
        }
    },
    "background": {
        "scripts": [
            "js/status.js"
        ]
    },
    "permissions": [
        "http://www.google.cn/"
    ]
}

由于这个扩展没有UI,所以我们不必编写HTML文件,下面直接编写status.js。

function httpRequest(url, callback){
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            callback(true);
        }
    }
    xhr.onerror = function(){
        callback(false);
    }
    xhr.send();
}
setInterval(function(){
    httpRequest('http://www.google.cn/', function(status){
        chrome.browserAction.setIcon({path: 'images/'+(status?'online.png':'offline.png')});
    });
},5000);

status.js调用了我们之前没有介绍过的方法,chrome.browserAction.setIcon。Chrome为扩展应用提供了很多类似的方法可以使得扩展应用做更多的事情,并且与浏览器结合得更加紧密。这个方法的作用就是更换扩展在浏览器工具栏中的图标。

本节示例扩展中的httpRequest函数,与上节所讲述跨域请求中所使用的函数非常类似,但请注意本节在httpRequest函数中加入了onerror事件,正是因为加入了这个事件才能捕捉到请求过程中是否发生了错误,从而得知所监视的网站是否在线。将本例载入Chrome后,在联网的情况下可以看到扩展图标为绿色,断开网络连接后扩展图标变为了红色。

01YqUXEU4Fkq.png

本节示例扩展的运行结果

小提示:如果想在用户打开浏览器之前就让扩展运行,可以在Manifest的permissions属性中加入"background",但除非必要,否则尽量不要这么做,因为大部分用户不喜欢这样。

本例中所编写的扩展源码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/website_status  下载得到。

2.4带选项页面的扩展

有一些扩展允许用户进行个性化设置,这样就需要向用户提供一个选项页面。Chrome通过Manifest文件的options_page属性为开发者提供了这样的接口,可以为扩展指定一个选项页面。当用户在扩展图标上点击右键,选择菜单中的“选项”后,就会打开这个页面1。

1 对于没有图标的扩展,可以在chrome://extensions页面中单击“选项”。

01YqUXeAPtqZ.png

指定options_page属性后,扩展图标上的右键菜单会包含“选项”链接

对于网站来说,用户的设置通常保存在Cookies中,或者保存在网站服务器的数据库中。对于JavaScript来说,一些数据可以保存在变量中,但如果用户重新启动浏览器,这些数据就会消失。那么如何在扩展中保存用户的设置呢?我们可以使用HTML5新增的localStorage接口。除了localStorage接口以外,还可以使用其他的储存方法。后面将专门拿出一节来讲解数据存储,本节中我们先使用最简单的localStorage方法储存数据。

localStorage是HTML5新增的方法,它允许JavaScript在用户计算机硬盘上永久储存数据(除非用户主动删除)。但localStorage也有一些限制,首先是localStorage和Cookies类似,都有域的限制,运行在不同域的JavaScript无法调用其他域localStorage的数据;其次是单个域在localStorage中存储数据的大小通常有限制(虽然W3C没有给出限制),对于Chrome这个限制是5MB2;最后localStorage只能储存字符串型的数据,无法保存数组和对象,但可以通过join、toString和JSON.stringify等方法先转换成字符串再储存。

2 通过声明unlimitedStorage权限,Chrome扩展和应用可以突破这一限制。

下面我们将编写一个天气预报的扩展,这个扩展将提供一个选项页面供用户填写所关注的城市。

有很多网站提供天气预报的API,比如OpenWeatherMap的API。可以通过http://openweathermap.org/API了解更多相关内容。

{
    "manifest_version": 2,
    "name": "天气预报",
    "version": "1.0",
    "description": "查看未来两周的天气情况",
    "icons": {
        "16": "images/icon16.png",
        "48": "images/icon48.png",
        "128": "images/icon128.png"
    },
    "browser_action": {
        "default_icon": {
            "19": "images/icon19.png",
            "38": "images/icon38.png"
        },
        "default_title": "天气预报",
        "default_popup": "popup.html"
    },
    "options_page": "options.html",
    "permissions": [
        "http://api.openweathermap.org/data/2.5/forecast?q=*"
    ]
}

上面是这个扩展的Manifest文件,options.html为设定选项的页面。下面开始编写options.html文件。

<html>
    <head>
        <title>设定城市</title>
    </head>
    <body>
        <input type="text" id="city" />
        <input type="button" id="save" value="保存" />
        <script src="js/options.js"></script>
    </body>
</html>

这个页面提供了一个id为city的文本框和一个id为save的按钮。由于Chrome不允许将JavaScript内嵌在HTML文件中,所以我们单独编写一个options.js脚本文件,并在HTML文件中引用它。下面来编写options.js文件。

var city = localStorage.city || 'beijing';
document.getElementById('city').value = city;
document.getElementById('save').onclick = function(){
    localStorage.city = document.getElementById('city').value;
    alert('保存成功。');
}

从options.js的代码中可以看到,localStorage的读取和写入方法很简单,和JavaScript中的变量读写方法类似。localStorage除了使用localStorage.namespace的方法引用和写入数据外,还可以使用localStorage['namespace']的形式。请注意第二种方法namespace要用引号包围,单引号和双引号都可以。如果想彻底删除一个数据,可以使用localStorage.removeItem('namespace')方法。

为了显示天气预报的结果,我们为扩展指定了一个popup页面,popup.html。下面来编写这个UI页面。

<html>
<head>
<style>
* {
    margin: 0;
    padding: 0;
}
body {
    width: 520px;
    height: 270px;
}
table {
    font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
    font-size: 12px;
    width: 480px;
    text-align: left;
    border-collapse: collapse;
    border: 1px solid #69c;
    margin: 20px;
    cursor: default;
}
table th {
    font-weight: normal;
    font-size: 14px;
    color: #039;
    border-bottom: 1px dashed #69c;
    padding: 12px 17px;
    white-space: nowrap;
}
table td {
    color: #669;
    padding: 7px 17px;
    white-space: nowrap;
}
table tbody tr:hover td {
    color: #339;
    background: #d0dafd;
}
</style>
</head>
<body>
<div id="weather"></div>
<script src="js/weather.js"></script>
</body>
</html>

其中id为weather的div元素将用于显示天气预报的结果。下面来编写weather.js文件。

function httpRequest(url, callback){
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            callback(xhr.responseText);
        }
    }
    xhr.send();
}
function showWeather(result){
    result = JSON.parse(result);
    var list = result.list;
    var table = '<table><tr><th>日期</th><th>天气</th><th>最低温度</th><th>最高温度</th></tr>';
    for(var i in list){
        var d = new Date(list[i].dt*1000);
        table += '<tr>';
        table += '<td>'+d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate()+'</td>';
        table += '<td>'+list[i].weather[0].description+'</td>';
        table += '<td>'+Math.round(list[i].temp.min-273.15)+' °C</td>';
        table += '<td>'+Math.round(list[i].temp.max-273.15)+' °C</td>';
        table += '</tr>';
    }
    table += '</table>';
    document.getElementById('weather').innerHTML = table;
}
var city = localStorage.city;
city = city?city:'beijing';
var url = 'http://api.openweathermap.org/data/2.5/forecast/daily?q='+city+',china&lang=zh_cn';
httpRequest(url, showWeather);

小提示:无论是options.js还是weather.js中都有如下语句:

var city = localStorage.city;
city = city?city:'beijing';

也就是说,当选项没有值时,应设定一个默认值,以避免程序出错。此处如果用户未设置城市,扩展将显示北京的天气预报。

01YqUcw9EHoV.png

weather扩展的选项页面,点击保存按钮后会提示保存成功

01YqUcwcqLX6.png

weather扩展的运行界面

本节示例扩展的源代码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/weather  下载得到。

2.5扩展页面间的通信

有时需要让扩展中的多个页面之间,或者不同扩展的多个页面之间相互传输数据,以获得彼此的状态。比如音乐播放器扩展,当用户鼠标点击popup页面中的音乐列表时,popup页面应该将用户这个指令告知后台页面,之后后台页面开始播放相应的音乐。

Chrome提供了4个有关扩展页面间相互通信的接口,分别是runtime.sendMessage、runtime.onMessage、runtime.connect和runtime.onConnect。做为一部入门级教程,此节将只讲解runtime.sendMessage和runtime.onMessage接口,runtime.connect和runtime.onConnect做为更高级的接口请读者依据自己的兴趣自行学习,你可以在http://developer.chrome.com/extensions/extension得到有关这两个接口的完整官方文档。

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

runtime.sendMessage完整的方法为:

chrome.runtime.sendMessage(extensionId, message, options, callback)

其中extensionId为所发送消息的目标扩展,如果不指定这个值,则默认为发起此消息的扩展本身;message为要发送的内容,类型随意,内容随意,比如可以是'Hello',也可以是{action: 'play'}、2013和['Jim', 'Tom', 'Kate']等等;options为对象类型,包含一个值为布尔型的includeTlsChannelId属性,此属性的值决定扩展发起此消息时是否要将TLS通道ID发送给监听此消息的外部扩展1,有关TLS的相关内容可以参考http://www.google.com/intl/zh-CN/chrome/browser/privacy/whitepaper.html#tls,这是有关加强用户连接安全性的技术,如果这个参数你捉摸不透,不必理睬它,options是一个可选参数;callback是回调函数,用于接收返回结果,同样是一个可选参数。

1 此属性仅在扩展和网页间通信时才会用到。

runtime.onMessage完整的方法为:

chrome.runtime.onMessage.addListener(callback)

此处的callback为必选参数,为回调函数。callback接收到的参数有三个,分别是message、sender和sendResponse,即消息内容、消息发送者相关信息和相应函数。其中sender对象包含4个属性,分别是tab、id、url和tlsChannelId,tab是发起消息的标签,有关标签的内容可以参看4.5节的内容。

为了进一步说明,下面举一个例子。

在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.”。

01YqUeVhL8GM.png


扩展内部通信Demo的运行画面

上面这个小例子的源代码可以从https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/runtime.sendMessage_runtime.onMessage_demo  下载到。


2.6储存数据

一个程序免不了要储存数据,对于Chrome扩展也是这样。通常Chrome扩展使用以下三种方法中的一种来储存数据:第一种是使用HTML5的localStorage,这种方法在上一节的内容中已经涉及;第二种是使用Chrome提供的存储API;第三种是使用Web SQL Database。

对于一般的扩展,“设置”这种简单的数据可以优先选择第一种,因为这种方法使用简单,可以看成是特殊的JavaScript变量;对于结构稍微复杂一些的数据可以优先选择第二种,这种方法可以保存任意类型的数据,但需要异步调用Chrome的API,结果需要使用回调函数接收,不如第一种操作简单;第三种目前使用的不算太多,因为需要使用SQL语句对数据库进行读写操作,较前两者更加复杂,但是对于数据量庞大的应用来说是个不错的选择。开发者应根据实际的情况选择上述三种方法中的一种或几种来存储扩展中的数据。

由于上节已经讲解了localStorage的使用方法,下面将详细讲解后两种储存数据的方法。

Chrome存储API

Chrome为扩展应用提供了存储API,以便将扩展中需要保存的数据写入本地磁盘。Chrome提供的存储API可以说是对localStorage的改进,它与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必须要在Manifest的permissions中声明"storage",之后才有权限调用。Chrome存储API提供了2种储存区域,分别是sync和local。两种储存区域的区别在于,sync储存的区域会根据用户当前在Chrome上登陆的Google账户自动同步数据,当无可用网络连接可用时,sync区域对数据的读写和local区域对数据的读写行为一致。

对于每种储存区域,Chrome又提供了5个方法,分别是get、getBytesInUse、set、remove和clear。

get方法即为读取数据,完整的方法为:

chrome.storage.StorageArea.get(keys, function(result){
    console.log(result);
});

keys可以是字符串、包含多个字符串的数组或对象。如果keys是字符串,则和localStorage的用法类似;如果是数组,则相当于一次读取了多个数据;如果keys是对象,则会先读取以这个对象属性名为键值的数据,如果这个数据不存在则返回keys对象的属性值(比如keys为{'name':'Billy'},如果name这个值存在,就返回name原有的值,如果不存在就返回Billy)。如果keys为一个空数组([])或空对象({}),则返回一个空列表,如果keys为null,则返回所有存储的数据。

getBytesInUse方法为获取一个数据或多个数据所占用的总空间,返回结果的单位是字节,完整方法为:

chrome.storage.StorageArea.getBytesInUse(keys, function(bytes){
    console.log(bytes);
});

此处的keys只能为null、字符串或包含多个字符串的数组。

set方法为写入数据,完整方法为:

chrome.storage.StorageArea.set(items, function(){
    //do something
});

items为对象类型,形式为键/值对。items的属性值如果是字符型、数字型和数组型,则储存的格式不会改变,但如果是对象型和函数型的,会被储存为“{}”,如果是日期型和正则型的,会被储存为它们的字符串形式。

remove方法为删除数据,完整方法为:

chrome.storage.StorageArea.remove(keys, function(){
    //do something
});

其中keys可以是字符串,也可以是包含多个字符串的数组。

clear方法为删除所有数据,完整方法为:


chrome.storage.StorageArea.clear(function(){
    //do something
});

请注意,上述五种完整方法中,StorageArea必须指定为local或sync中的一个。

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

onChanged的完整方法为:

chrome.storage.onChanged.addListener(function(changes, areaName){
    console.log('Value in '+areaName+' has been changed:');
    console.log(changes);
});

callback会接收到两个参数,第一个为changes,第二个是StorageArea。changes是词典对象,键为更改的属性名称,值包含两个属性,分别为oldValue和newValue;StorageArea为local或sync。

Web SQL Database

Web SQL Database的三个核心方法为openDatabase、transaction和executeSql。openDatabase方法的作用是与数据库建立连接,transaction方法的作用是执行查询,executeSql方法的作用是执行SQL语句。

下面举一个简单的例子:

db = openDatabase("db_name", "0.1", "This is a test db.", 1024*1024);
if(!db){
    alert('数据库连接失败。');
}
else {
    db.transaction( function(tx) {
        tx.executeSql(
            "SELECT COUNT(*) FROM db_name",
            [],
            function(tx, result){
                console.log(result);
            },
            function(tx, error){
                alert('查询失败:'+error.message);
            }
        );
    }
}

更多关于Web SQL Database的资料可以参考http://www.w3.org/TR/webdatabase/  。由于原生的Web SQL Database并不算好用,也有一些开源的二次封装的库来简化Web SQL Database的使用,如https://github.com/KenCorbettJr/html5sql  。

以上几种数据的存储方式都不会对数据加密,如果储存的是敏感的数据,应该先进行加密处理。比如不要将用户密码的明码直接储存,而应先进行MD5加密。




-----------------------------------------------------
转载请注明来源此处
原地址:#

-----网友评论----
暂无评论
-----发表评论----
微网聚博客乐园 ©2014 blog.mn886.net 鲁ICP备14012923号   网站导航