chrome利用NPAPI开发扩展

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插件开发

在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

print

NPP_Print

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。

你可能感兴趣的:(chrome利用NPAPI开发扩展)