Chrome扩展中脚本的运行机制和通信方式
Posted asdfasdfasdad
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Chrome扩展中脚本的运行机制和通信方式相关的知识,希望对你有一定的参考价值。
injected.js
生存周期
这种脚本,和原网页自带的脚本,就完全是一路货了。有多种方式来在扩展程序中向正在浏览的页面注入这样的脚本,我只说一种最常用也是最被推荐的:先把脚本保存在js文件里(比如GmailAssist中的tableInited.js),然后在匹配当前页面的content script中(如GmailAssist中的content.js)用类似下面这样的代码来把tableInited.js注入浏览中的页面:
可用API范围
只有网页通用的API是可用的,而chrome为扩展提供的API(chrome.*),这种完全注入到用户浏览的页面中的脚本都不能访问。
作用范围/运行环境
完全和网页原有的脚本文件一样,我称它为“不属于扩展程序的脚本”。
可以访问网页原有js的变量空间。
injected script:
if(email_data)
window.postMessage("usrik": JSON.stringify(userik) , '*');//userik就从GLOBALS中取得
content script:
window.addEventListener("message", function(event)
if(event.data.usrik)
usrik = event.data.usrik
console.log(usrik);
, false);
何时使用
我的建议是,仅当你需要获取被浏览页面中原有js中的变量时,才把你的脚本inject到用户浏览的页面中,然后通过接下来例子里这种方式,把它传到content script中。当然了,有一些单纯地操纵DOM元素而不需要它们再返回什么数据的脚本,也可以直接inject到页面里。
例子
获取ik的值。我在GmailAssist构建初期,尝试过一个gmail的非官方的库,当时为了获取邮箱用户的唯一标识(即ik,它是在gmail原有js的变量空间中的全局变量即GLOBALS中的),就不得不通过向页面中注入injected script来获取到GLOBALS。获取到之后要传给扩展程序的其他部分,则要通过event listener来完成
content-script.js
生存周期
和injected script相似,它也是被注入到用户当前浏览的页面中的。但区别在于,它不是真正完全融入网页上下文的,而是运行在一个单独的被隔离的环境中。它的生存周期也就是跟浏览的网页一样,最迟到网页加载完全完成时,content script就开始跑了,直到用户当前浏览的网页被关闭。每次刷新时将重新载入。
可用API范围
网页通用的API,跨域xhr请求,以及chrome为扩展程序提供的API中的一部分,具体有:(开头都是chrome.)
* extension(getURL、inIncognitoContext、lastError、onRequest、sendRequest);
* i18n;
* runtime(connect、getManifest、getURL、id、onConnect、onMessage、sendMessage);
* storage`
作用范围/运行环境
它是注入用户浏览的网页中的,但又不像injected script那样彻底,而是单独运行在一个隔离环境里。
它可以访问一部分chrome给扩展程序提供的API,但也只有一部分。
它不运行在网页的真正上下文中,因而只能访问和操纵页面DOM,但访问不到页面里js的变量空间(当然也访问不到页面里js定义的函数们)。
它不可以访问background和popup页面中的脚本(我称后面这两类为“完全属于扩展程序的脚本”,接下来介绍)的变量和函数,但可以通过和background的通信来和扩展程序的其他部分实现数据交流。(这句和上句,这两种不可以,都是好理解的,只要你记住content script是运行在专门为它们准备的隔离环境里的即可。)因此我称content script为“半属于扩展程序的脚本”。
需要注意到,manifest中声明的形式,content_scripts字段的值是一个数组。也就是说一个扩展程序可以向一个页面中插入多个content script,而每个content script可以有多个 javascript 和 CSS 文件
。那么有个问题,同一个页面内注入的多个content script可能来自同一个扩展程序,也可能来自不同的扩展程序,那么这些content script可以互相访问对方的变量空间吗?都不可以。
何时使用
需要操纵页面DOM时,需要与具体页面匹配时,需要接受injected js传出来的数据时,以及每次刷新网页都需要重新载入的脚本,就可以作为content script来写。
例子
向gmail服务器发xhr请求数据、操纵gmail页面的DOM,把返回的数据显示出来。
popup
生存周期
在用户点击扩展程序图标时(无论是page action还是browser action),都可以设置弹出一个popup页面。而这个页面中自然是可以有运行的脚本的(比如就叫popup.js)。它会在每次popup页面弹出时重新载入。
可用API范围
这类脚本和下一类(background),我都称为“完全属于扩展程序的脚本”。它们不仅可以访问普通网页API、可以发起跨域xhr请求,而且可以访问chrome为扩展程序专门提供的API(即chrome.*)中的全部。
作用范围/运行环境
“完全属于扩展程序的脚本”之间是可以互相访问的,但popup页面中的脚本,会在每次用户呼出popup页面时重新载入。
何时使用
仅针对popup页面内起作用的、比较小的(这样每次重新载入不会太久)脚本,用这一类来实现,比较合适。当然,这种脚本可以向外部请求数据,也可以访问本地存储API(chrome.storage),那么是可以通过这类脚本来写的。
例子
授权按钮(加载很快,而且只在每次用户点击图标时加载即可,获取的token通过localStorage保存在本地,功能完成后即可把该脚本占据的资源腾出来)。
background.js
生存周期
这类脚本是运行在浏览器后台的,注意它是与当前浏览页面无关的。
所谓的后台脚本,在chrome扩展中又分为两类,分别运行于后台页面(background page)和事件页面(event page)中。两者区别在于,前者(后台页面)持续运行,生存周期和浏览器相同,即从打开浏览器到关闭浏览器期间,后台脚本一直在运行,一直占据着内存等系统资源;而后者(事件页面)只在需要活动时活动,在完全不活动的状态持续几秒后,chrome将会终止其运行,从而释放其占据的系统资源,而在再次有事件需要后台脚本来处理时,重新载入它。这两类咋区分呢?通过你在manifest中的声明:
manifest.json
"background":
"scripts": ["background.js"],
"persistent": false
正如上一节说过的,这里persistent的值默认是true,此时这个js就是运行在后台页面的(持续的);若这个值为false,那就是事件页面(非持续的)了。
可用API范围
和popup那种一样。
作用范围/运行环境
作为“完全属于扩展程序的脚本”,它可以和其他的“完全属于扩展程序的脚本”之间通信;也是运行在浏览器的环境内的,而与当前浏览的页面无关。
何时使用
需要持续运行在后台的,肯定就选这种了,而且要把persistent置为true。需要在后台处理些事件啊之类的,包括要用到content script无法访问的扩展程序专用API们时,也应该用这种,不过只要你不是需要它必须持续运行的,就把它设置成事件页面,从而提高性能。
例子
下载(因为1.能调用chrome给扩展程序提供的下载API的,只有“完全属于扩展程序的脚本”。2.“完全属于扩展程序的脚本”中,只有background这种可以持续运行在后台,或者被别的事件从后台激活,而另一类——popup页面中的脚本则是每次弹出popup页面时都被重新加载一次的,也只有用户点了扩展图标时才会弹出popup页面,若用popup无疑是增加了用户的操作复杂度)。
这几类脚本之间的通信
我们现在知道,chrome扩展中的通信可以有这么几类:content script和“完全属于扩展程序的脚本”之间的通信、injected script和content script之间的通信、“完全属于扩展程序的脚本”们互相之间的通信、扩展程序的脚本和外部服务器之间的通信。
先提一个概念,“异步”。异步和同步相对,同步就可以理解为“串行”,即完成一件事,才能做下一件;而异步就是,这件事的命令发出去就可以做其他事了,等这件事完成,可以通过“回调函数(callback)”来返回执行结果。曾经看到过一个很形象的例子,异步就相当于你去永和豆浆吃早饭,你把点的东西在柜台告诉服务员,然后你领个号就可以去做你自己的事了,等你的饭做好了,服务员会叫你的号(回调函数,callback),然后你去拿饭就可以吃了。类似的情景里,同步是啥呢,就是你点了餐,然后就只能在柜台那等你的餐做好,然后把饭给你你再走(相当于给你一个返回值,return)。
chrome扩展内部的信息交换都是异步的。当然xhr也可以设置成异步的,也可以设置成同步的。
content script和“完全属于扩展程序的脚本”之间的通信
两者可以互相发送消息,接收端的处理方式是一样的:(由于content script可以调用chrome.runtime中和信息传递相关的几个API,而“完全属于扩展程序的脚本”可以访问到全部chrome.*的API,自然可以调用chrome.runtime.onMessage通信API)
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse)
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse(farewell: "goodbye");
);
从content script向“完全属于扩展程序的脚本”发消息:
chrome.runtime.sendMessage(greeting: "hello", function(response)
console.log(response.farewell);
);
从“完全属于扩展程序的脚本”向content script发消息:
chrome.tabs.query(active: true, currentWindow: true, function(tabs)
chrome.tabs.sendMessage(tabs[0].id, greeting: "hello", function(response)
console.log(response.farewell);
);
);
和上面的区别是,这种消息发送的方向,需要你指明你信息要发到哪个页面(用户浏览的页面)中,也就是哪个tab中(chrome中每个页面都在一个tab,即标签中打开嘛)的content script;但显然上面那种就不需要,而且在接收端也不需要。
injected script和content script之间的通信:
通过window.addEventListener和 window.postMessage来实现,代码可以参考上面获取user_ik的那个。
这种通信方式的原理是基于content script和页面内的脚本(injected script自然也属于页面内的脚本)之间唯一共享的东西就是页面的DOM元素,window就是页面元素,因而可以被用来传递消息。
“完全属于扩展程序的脚本”们之间通信
首先,他们之间函数是可以直接互相访问的;其次,它们可以互相访问对方的DOM元素;因而他们之间事实上不存在什么复杂的通信,大家都是一家人。
最后补充几点,
- 与扩展程序相关的信息的传送形式都是JSON字符串(可以简单理解为就是把JSON对象加上引号变成字符串),但你并不需要额外弄个JSON的库到你的程序中,chrome自动集成了这些东西;
- 除了与外部的xhr交流,内部的这些信息传送不需要你手动转成字符串或者手动从字符串转成对象(JSON.stringify或JSON.parse),这两种过程是自动的,你可以直接认为你传送的就是JSON对象,而不是字符串;但在和外部进行XHR交流时,你需要把发出去的信息手动stringify一下,把收到的信息parse一下,才可以正常使用;
- 从安全性的角度出发,不要让你的程序接收到外来的信息时,用eval等方法来处理它们,这可能导致恶意脚本的执行,而应该使用JSON.parse这种不会引起脚本执行的方法来处理收到的信息(这一点如果不好理解,可以不去深究,只要记住,用xhr请求来的信息,统统用JSON.parse来解析即可);
- 可以认为还有一种通信方式,在除了injected script外的剩下几种脚本之间,都可以通过本地保存的数据进行通信(即通过localStorage或者chrome.storage,这二者的概念,下一节中我会介绍)。
转载地址:http://www.cnblogs.com/ligerleng/p/gmail_assist_2.html
以上是关于Chrome扩展中脚本的运行机制和通信方式的主要内容,如果未能解决你的问题,请参考以下文章
Chrome扩展开发之三——Chrome扩展中的数据本地存储和下载