manifest.json:
文件可以告诉Chrome关于这个扩展的相关信息,它是整个扩展的入口,也是Chrome扩展必不可少的部分
注:
Google的官方文档中对于扩展和应用给出了两个不同的Manifest介绍界面,这是因为有些属性只能由扩展使用,而有些属性只能由应用使用。
如果这两者同时出现在同一个Manifest文件中,就会使Chrome困惑,不知是按照扩展对待这个程序还是按照应用来对待这个程序。
但无论是扩展还是应用,它们的Manifest又有很多共有的属性。
manifest.json模板:
Chrome扩展的Manifest必须包含name
、version
和manifest_version
属性,目前来说manifest_version
属性值只能为数字2,对于应用来说,还必须包含app
属性。
browser_action
、page_action
、background
、permissions
、options_page
、content_scripts
,
所以我们可以保留一份manifest.json模板,当编写新的扩展时直接填入相应的属性值。
可以应对大部分的扩展的模板:
{
"app": {
"background": {
"scripts": ["background.js"]
}
},
"manifest_version": 2,
"name": "My Extension",
"version": "versionString",
"default_locale": "en",
"description": "A plain text 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": "Extension Title",
"default_popup": "popup.html"
},
"page_action": {
"default_icon": {
"19": "images/icon19.png",
"38": "images/icon38.png"
},
"default_title": "Extension Title",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["http://www.google.com/*"],
"css": ["mystyles.css"],
"js": ["jquery.js", "myscript.js"]
}
],
"options_page": "options.html",
"permissions": [
"*://www.google.com/*"
],
"web_accessible_resources": [
"images/*.png"
]
}
在官方文档中可以找到完整的Manifest属性列表,扩展在
https://developer.chrome.com/extensions/manifest
操作用户正在浏览的页面:
实际上就是对用户当前浏览页面的DOM进行操作。通过Manifest中的content_scripts
属性可以指定将哪些脚本何时注入到哪些页面中,
当用户访问这些页面后,相应脚本即可自动运行,从而对页面DOM进行操作。
Manifest的content_scripts
属性值为数组类型,数组的每个元素可以包含:
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的变量和函数。
跨域请求:
跨域指的是JavaScript通过XMLHttpRequest
请求数据时,调用JavaScript的页面所在的域和被请求页面的域不一致。
对于网站来说,浏览器出于安全考虑是不允许跨域。另外,对于域相同,但端口或协议不同时,浏览器也是禁止的。下表给出了进一步的说明:
URL | 说明 | 是否允许请求 |
---|---|---|
http://a.example.com/ http://a.example.com/a.txt | 同域下 | 允许 |
http://a.example.com/ http://a.example.com/b/a.txt | 同域下不同目录 | 允许 |
http://a.example.com/ http://a.example.com:8080/a.txt | 同域下不同端口 | 不允许 |
http://a.example.com/ https://a.example.com/a.txt | 同域下不同协议 | 不允许 |
http://a.example.com/ http://b.example.com/a.txt | 不同域下 | 不允许 |
http://a.example.com/ http://a.foo.com/a.txt | 不同域下 | 不允许 |
解决:
但这个规则如果同样限制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,
因为:<code>count</code>函数是一个阻塞函数,在它没有运行完之前它会阻塞下面的代码运行,
所以直到<code>count</code>计算结束后才将结果返回后再加<code>1</code>赋给变量<code>c</code>,最后将变量<code>c</code>的值打印出来
unction 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);
虽然请求的资源内容为<code>Hello World!</code>,但却并没有正确地显示出来
原因:
<code>httpRequest</code>函数不是一个阻塞函数,在它没运行完之前后面的代码就已经开始运行,
这样<code>html</code>变量在<code>httpRequest</code>函数没返回值之前就被赋值,所以最终的结果必然就是<code>undefined</code>了。
既然这样,如何将非阻塞函数的最终结果传递下去呢?方法就是使用回调函数。在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); <code>httpRequest</code>函数运行的结果已经被正确地打印出来了。
});
实战编写一款显示用户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发起跨域请求
]
}
popup.html:
<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>//js文件夹下有my_ip.js文件
</body>
</html>
my_ip.js:注 此 js获取 IP实践后发现不行
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;
});
结果:
源码地址
作为一个开发者,安全问题永远都不应被轻视。在你从外域获取到数据后,不要轻易作为当前页面元素的innerHTML
直接插入,更不要用eval
函数去执行它,否则很可能将用户置于危险的境地。如果要将请求到的数据写入页面,可以使用innerText
,就像我们这个查看IP的扩展那样。如果是JSON格式是数据就使用JSON.parse
函数去解析。为了避免请求数据返回的格式错误,结合try-catch
一起使用也是不错的选择。