chrome扩展必须编写清单文件manifest.json和内容脚本JS,清单文件名字必须是mainifest.json,内容格式为JSON,需要指定扩展页面和内容脚本。
内容脚本在Chrome扩展编写中扮演着非常重要的角色,其内容就是JavaScript代码。内容脚本虽然物理位置是在扩展目录中,但其内容代码是根据匹配规则被注入到对应的原始网页中,因此可以简单的把内容脚本理解为原始网页的组成部分。内容脚本和原始网页共享相同的DOM数据,因此内容脚本可以对页面DOM进行任意增、删、改操作,这是内容脚本最主要的功能。
内容脚本执行的时机由run_at字段指明。可以取三个值:
document_start:文档开始,相当于document对象刚刚创建,此时DOM树还没有构建完成。这个值是内容脚本挂接文档级事件的好时机。
document_end:文档结束,此时DOM树已经构建,主文档加载完毕,但图片等子资源数据可能未加载完成。
document_idle:空闲,可理解为在window.onload事件之后执行。
我们知道chrome浏览器是多进程架构,对于每个扩展也会运行在单独的进程中,但是如果扩展仅仅涉及内容脚本(扩展的功能完全是由内容脚本实现)的话,chrome不会为此扩展启动单独的进程。因为内容脚本被注入到匹配页面中,其执行环境为匹配网页的Render进程。这是内容脚本执行的进程环境。
另外虽然内容脚本被注入到页面中,它和页面中本身的脚本的执行环境也是分割的,即内容脚本运行在孤立的V8上下文环境中。因此内容脚本不能访问页面脚本中的变量和函数。它们唯一共享的是DOM。这是可以理解的,试想如果内容脚本和页面本身的脚本运行在相同的脚本上下文中,变量、函数岂不是可能导致冲突?
本地页面:
<script>
functionsendChatMsg()
{
varmsgNode = document.getElementById("chat_msg");
if(msgNode.value== "")
{
return;
}
chrome.windows.getCurrent(function(wnd){
chrome.tabs.getAllInWindow(wnd.id, function(tabs){
for(var i=0; i < tabs.length; i++)
{
if(tabs[i].url =="http://www.google.com.hk/")
{
chrome.tabs.sendRequest(tabs[i].id,{msg: msgNode.value});
msgNode.value="";
}
}
});
});
contentNode =document.getElementById("chat_content");
contentNode.value = contentNode.value + "我说:" +msgNode.value + "\r\n";
contentNode.scrollTop = contentNode.scrollHeight;
}
chrome.extension.onRequest.addListener(
function(data){
contentNode= document.getElementById("chat_content");
contentNode.value= contentNode.value + "google说:" + data.msg +"\r\n";
contentNode.scrollTop= contentNode.scrollHeight;
}
);
</script>
内容脚本:
var sendChatMsg = function()
{
var msgNode =document.getElementById("chat_msg");
if(msgNode.value =="")
{
return;
}
chrome.extension.sendRequest({msg:msgNode.value});
contentNode =document.getElementById("chat_content");
contentNode.value =contentNode.value + "i say:" + msgNode.value + "\r\n";
contentNode.scrollTop= contentNode.scrollHeight;
msgNode.value="";
};
chatdiv = document.createElement("div");
chatdiv.id = "chat_div";
chatdiv.style.zIndex = "100";
chatdiv.style.position = "absolute";
chatdiv.style.left = "560px";
chatdiv.style.top = "280px";
chatdiv.style.width = "auto";
chatdiv.style.height = "auto";
chatdiv.style.border = "1px solid";
chatdiv.style.overflow = "auto";
chatdiv.style.backgroundColor = "#F7F4EC";
chatdiv.innerHTML = "<div>" +
"<textareaid=\"chat_content\" ROWS=\"8\"COLS=\"40\"></textarea>" +
"<div><inputid='chat_msg' size=\"35\" type=\"text\"/><inputid='sendBtn' type=\"button\"value=\"send\"/></div></div>";
document.body.appendChild(chatdiv);
document.getElementById("sendBtn").addEventListener('click',sendChatMsg,false);
chrome.extension.onRequest.addListener(
function(data){
contentNode= document.getElementById("chat_content");
contentNode.value= contentNode.value + "extension say:" + data.msg + "\r\n";
contentNode.scrollTop= contentNode.scrollHeight;
}
);
html页面(不属于扩展)需要创建一个自定义事件,当它向DOM中的一个特定元素写入事件数据后就会激活并派发这个自定义事件。 Content script在这个特定元素上监听这个自定义事件,从这个元素中获取数据,并向扩展进程post一个消息。通过这种方式, 页面建立了与扩展的通信链接。 这个方法也适用于反向的通信,如果传参数则需要创建CustomEvent事件来进行通信。
远程页面:
var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);
function fireCustomEvent(data) {
hiddenDiv =document.getElementById('myCustomEventDiv');
hiddenDiv.innerText = data;
hiddenDiv.dispatchEvent(customEvent);
}
内容脚本:
var port = chrome.extension.connect();
document.getElementById('myCustomEventDiv').addEventListener('myCustomEvent',function() {
var eventData =document.getElementById('myCustomEventDiv').innerText;
port.postMessage({message:"myCustomEvent", values: eventData});
});
port.onMessage.addListener(function(msg) {
alert(msg.answer);
});
扩展background.html:
<script>
chrome.extension.onConnect.addListener(function(port) {
port.onMessage.addListener(function(msg) {
alert(msg.message);
alert(msg.values);
port.postMessage({answer:"helloworld"});
});
});
</script>
在NPAPI编程的接口中你会发现有NP_打头的,有NPP_打头的,有NPN_打头的。NP是npapi的插件库提供给浏览器的最上层的接口;NPP即NP Plugin是插件本身提供给浏览器调用的接口,主要被用来填充NPPluginFuncs的结构体;NPN即NP Netscape,是浏览器提供给插件使用的接口,这些接口一般都在NPNetscapeFuncs结构体中。
入口函数NP_GetEntryPoints、NP_Initialize、 NP_Shutdown()需要在def中导出。浏览器启动时会调用先调用NP_GetEntryPoints获得NPPluginFuncs结构体对象,然后构造NPNetscapeFuncs结构体并调用NP_Initialize给插件相应结构体赋值,浏览器加载页面时创建Plugin实例并调用其NP_New(),浏览器通过NP_GetValue()来得到插件的信息,浏览器通过NPP_SetWindow传入插件窗口的信息,关闭页面时调用NP_Destroy()并释放Plugin实例,关闭浏览器时调用NP_Shutdown()。
在工程中需要实现函数NS_PluginInitialize、NS_PluginShutdown、NS_NewPluginInstance和NS_DestroyPluginInstance。在NP_Initialize中调用函数fillNetscapeFunctionTable()根据参数给NPNetscapeFuncs对象赋值,并调用NS_PluginInitialize;在NPP_New中调用NS_NewPluginInstance生成新的实例对象(插件类CPlugin);在NPP_Destroy中调用插件类的shut()函数并调用NS_DestroyPluginInstance()来释放资源;在NP_Shutdown中调用NS_PluginShutdown。在NP_GetEntryPoints中调用fillPluginFunctionTable来填充NPPluginFuncs对象。
插件类CPlugin需要继承自nsPluginInstanceBase,并实现三个纯虚函数:init(),shut()和isInitialized()。还可以重载nsPluginInstanceBase的虚函数。一下是NPPluginFuncs成员名、成员值与插件类的调用关系(默认调用基类nsPluginInstanceBase的函数):
Member |
NPP_Xxx |
CPlugin |
newp |
NPP_New |
|
destroy |
NPP_Destroy |
shut |
setwindow |
NPP_SetWindow |
init/ isInitialized/SetWindow |
newstream |
NPP_NewStream |
NewStream |
destroystream |
NPP_DestroyStream |
DestroyStream |
asfile |
NPP_StreamAsFile |
StreamAsFile |
writeready |
NPP_WriteReady |
WriteReady |
write |
NPP_Write |
Write |
|
NPP_Print |
|
event |
NPP_HandleEvent |
HandleEvent |
urlnotify |
NPP_URLNotify |
URLNotify |
getvalue |
NPP_GetValue |
GetValue |
setvalue |
NPP_SetValue |
SetValue |
其中浏览器执行脚本document.getElementById()时会调用函数NPP_GetValue,并传入NPPVpluginScriptableNPObject(作为variable参数)来查询插件是否支持Scriptable功能(即和脚本语言交互的功能),我们可以利用NPN_CreateObject方法来创建一个NPObject对象,并且作为value返回给浏览器。这样浏览器就可以通过这个NPObject对象和我们的插件建立连接。当页面上Javascript调用了我们插件对象的某个方法时,浏览器会调用该NPObject对象的HasProperty来询问是否是一个属性,不是则调用HasMethod方法来查询是否支持这个方法,如果支持则会调用NPObject对象的Invoke方法,传入方法名、参数等信息。这样我们就可以让网页上的脚本语言来执行我们编写的函数了。在Windows上,我们编写的函数就如同编写普通的应用程序一样,可以使用很多Windows API来完成许多复杂的工作。
打开浏览器,在地址栏输入“about:plugins”如果在plugin列表中有本例的npdemo.dll及说明我们的plugin示例已经成功完成。如下图所示:
在页面中测试该插件:pedemo.html
<HTML>
<HEAD>
</HEAD>
<BODY>
<embed type="application/demo-plugin">
</BODY>
</HTML>
在firefox中测试顺利通过,但是chrome中出现如下截图的问题,需要将MIME类型改为application/x-my-extension。