Chromium扩展Extension的Content Script加载过程分析.docx
- 文档编号:17654485
- 上传时间:2023-07-27
- 格式:DOCX
- 页数:37
- 大小:73.31KB
Chromium扩展Extension的Content Script加载过程分析.docx
《Chromium扩展Extension的Content Script加载过程分析.docx》由会员分享,可在线阅读,更多相关《Chromium扩展Extension的Content Script加载过程分析.docx(37页珍藏版)》请在冰点文库上搜索。
Chromium扩展Extension的ContentScript加载过程分析
Chromium扩展(Extension)的ContentScript加载过程分析
Chromium的Extension由Page和ContentScript组成。
Page有UI和JS,它们加载在自己的ExtensionProcess中渲染和执行。
ContentScript只有JS,这些JS是注入在宿主网页中执行的。
ContentScript可以访问宿主网页的DOMTree,从而可以增强宿主网页的功能。
本文接下来分析ContentScript注入到宿主网页执行的过程。
[plain]viewplaincopy在CODE上查看代码片派生到我的代码片
{
......
"content_scripts":
[
{
"matches":
["
"js":
["content.js"],
"run_at":
"document_start",
"all_frames":
true
}
]
}
这个清单文件仅对URL为“
首先,在前面文章中提到,Browser进程在加载Extension之前,会创建一个UserScriptMaster对象。
此后每当加载一个Extension,这个UserScriptMaster对象的成员函数OnExtensionLoaded都会被调用,用来收集当前正在加载的Extension的ContentScript。
此后,每当Browser进程启动一个Render进程时,代表该Render进程的一个RenderProcessHostImpl对象的成员函数OnProcessLaunched都会被调用,用来通知Browser进程新的Render进程已经启动起来的。
这时候这个RenderProcessHostImpl对象会到上述UserScriptMaster对象中获取当前收集到的所有ContentScript。
这些ContentScript接下来会通过一个类型为ExtensionMsg_UpdateUserScript的IPC消息传递给新启动的Render进程。
新启动的Render进程通过一个Dispatcher对象接收这个IPC消息,并且会将它传递过来的ContentScript保存在一个UserScriptSlave对象中。
接下来,每当Render进程加载一个网页时,都会在三个时机检查是否需要在该网页中注入ContentScript。
从前面Chromium扩展(Extension)机制简要介绍和学习计划一文可以知道,这三个时机分别为document_start、document_end和document_idle,分别表示网页的Document对象开始创建、结束创建以及空闲时。
接下来我们以document_start这个时机为例,说明ContentScript注入到宿主网页的过程。
网页的Document对象是在WebKit中创建的。
WebKit为网页创建了Document对象之后,会调用Content层的一个RenderFrameImpl对象的成员函数didCreateDocumentElement,用来通知后者,它描述的网页的Document对象已经创建好了。
这时候这个RenderFrameImpl对象将会调用前面提到的UserScriptSlave对象的成员函数InjectScripts,用来通知后者,现在可以将ContentScript注入当前正在加载的网页中去执行。
前面提到的UserScriptSlave对象会调用另外一个WebLocalFrameImpl对象的成员函数executeScriptInIsolatedWorld,用来注入符合条件的ContentScript到当前正在加载的网页中去,并且在JS引擎的一个IsolatedWorld中执行。
ContentScript在IsolatedWorld中执行,意味着它不可以访问在宿主网页中定义的JavaScript,包括不能调用在宿主网页中定义的JavaScript函数,以及访问宿主网页中定义的变量。
以上就是ContentScript注入到宿主网页中执行的大概流程。
接下来我们结合源代码进行详细的分析,以便对这个注入流程有更深刻的认识。
我们首先分析UserScriptMaster类收集ContentScript的过程。
这要从UserScriptMaster类的构造函数说起,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
UserScriptMaster:
:
UserScriptMaster(Profile*profile)
:
......,
extension_registry_observer_(this){
extension_registry_observer_.Add(ExtensionRegistry:
:
Get(profile_));
registrar_.Add(this,chrome:
:
NOTIFICATION_EXTENSIONS_READY,
content:
:
Source
registrar_.Add(this,content:
:
NOTIFICATION_RENDERER_PROCESS_CREATED,
content:
:
NotificationService:
:
AllBrowserContextsAndSources());
}
这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。
UserScriptMaster类的成员变量extension_registry_observer_描述的是一个ExtensionRegistryObserver对象。
这个ExtensionRegistryObserver对象接下来会注册到与参数profile描述的一个Profile对象关联的一个ExtensionRegistry对象中去,也就是注入到与当前使用的Profile关联的一个ExtensionRegistry对象中去。
这个ExtensionRegistry对象的创建过程可以参考前面Chromium扩展(Extension)加载过程分析一文。
与此同时,UserScriptMaster类的构造函数还会通过成员变量registrar_描述的一个NotificationRegistrar对象监控chrome:
:
NOTIFICATION_EXTENSIONS_READY和content:
:
NOTIFICATION_RENDERER_PROCESS_CREATED事件。
其中,事件chrome:
:
NOTIFICATION_EXTENSIONS_READY用来通知Chromium的ExtensionService已经启动了,而事件content:
:
NOTIFICATION_RENDERER_PROCESS_CREATED用来通知有一个新的Render进程启动起来。
如前面所述,当新的Render进程启动起来的时候,UserScriptMaster类会将当前加载的Extension定义的ContentScript传递给它处理。
这个过程我们在后面会进行详细分析。
从前面Chromium扩展(Extension)加载过程分析一文还可以知道,接下来加载的每一个Extension,都会保存在上述ExtensionRegistry对象内部的一个EnabledList中,并且都会调用注册在上述ExtensionRegistry对象中的每一个ExtensionRegistryObserver对象的成员函数OnExtensionLoaded,通知它们有一个新的Extension被加载。
当UserScriptMaster类的成员变量extension_registry_observer_描述的ExtensionRegistryObserver对象的成员函数OnExtensionLoaded被调用时,它又会调用UserScriptMaster类的成员函数OnExtensionLoaded,以便UserScriptMaster类可以收集新加载的Extension定义的ContentScript,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidUserScriptMaster:
:
OnExtensionLoaded(
content:
:
BrowserContext*browser_context,
constExtension*extension){
//Addanycontentscriptsinsidetheextension.
extensions_info_[extension->id()]=
ExtensionSet:
:
ExtensionPathAndDefaultLocale(
extension->path(),LocaleInfo:
:
GetDefaultLocale(extension));
......
constUserScriptList&scripts=
ContentScriptsInfo:
:
GetContentScripts(extension);
for(UserScriptList:
:
const_iteratoriter=scripts.begin();
iter!
=scripts.end();
++iter){
user_scripts_.push_back(*iter);
.....
}
if(extensions_service_ready_){
changed_extensions_.insert(extension->id());
if(script_reloader_.get()){
pending_load_=true;
}else{
StartLoad();
}
}
}
这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。
参数extension描述的就是当前正在加载的Extension。
UserScriptMaster类的成员函数OnExtensionLoaded首先会调用ExtensionSet类的静态成员函数ExtensionPathAndDefaultLocale将该Extension的Path和Locale信息封装在一个ExtensionPathAndDefaultLocale对象中,并且以该Extension的ID为键值,将上述ExtensionPathAndDefaultLocale对象保存在成员变量extensions_info_描述的一个std:
:
map中。
UserScriptMaster类的成员函数OnExtensionLoaded接下来将当前正在加载的Extension定义的所有ContentScript保存在成员变量user_scripts_描述的一个std:
:
vector中。
UserScriptMaster类有一个类型为bool的成员变量extensions_service_ready_。
当它的值等于true的时候,表示Chromium的ExtensionService已经启动起来了。
这时候extensions_service_ready_就会将当前正在加载的Extension的ID插入到UserScriptMaster类的成员变量changed_extensions_描述的一个std:
:
set中去,表示有一个新的Extension需要处理。
这里说的处理,就是将新加载的Extension定义的ContentScript的内容读取出来,并且保存在一个共享内存中。
将Extension定义的ContentScript的内容读取出来,并且保存在一个共享内存中,是通过UserScriptMaster类的成员变量script_reloader_指向的一个ScriptReloader对象实现的。
如果这个ScriptReloader已经创建出来,那么就表示它现在正在读取ContentScript的过程中。
这时候UserScriptMaster类的成员变量pending_load_的值会被设置为true,表示当前需要读取的ContentScript发生了变化,因此需要重新进行读取。
如果UserScriptMaster类的成员变量script_reloader_指向的ScriptReloader对象还没有创建出来,那么UserScriptMaster类的成员函数OnExtensionLoaded就会调用另外一个成员函数StartLoad创建该ScriptReloader对象,并且通过该ScriptReloader对象读取当前已经加载的Extension定义的ContentScript,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidUserScriptMaster:
:
StartLoad(){
if(!
script_reloader_.get())
script_reloader_=newScriptReloader(this);
script_reloader_->StartLoad(user_scripts_,extensions_info_);
}
这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。
从这里可以看到,如果UserScriptMaster类的成员变量script_reloader_指向的ScriptReloader对象还没有创建出来,那么UserScriptMaster类的成员函数StartLoad就会创建,并且在创建之后,调用它的成员函数StartLoad读取当前已经加载的Extension定义的ContentScript,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidUserScriptMaster:
:
ScriptReloader:
:
StartLoad(
constUserScriptList&user_scripts,
constExtensionsInfo&extensions_info){
//Addareferencetoourselvestokeepourselvesalivewhilewe'rerunning.
//BalancedbyNotifyMaster().
AddRef();
......
this->extensions_info_=extensions_info;
BrowserThread:
:
PostTask(
BrowserThread:
:
FILE,FROM_HERE,
base:
:
Bind(
&UserScriptMaster:
:
ScriptReloader:
:
RunLoad,this,user_scripts));
}
这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。
ScriptReloader类的成员函数StartLoad首先调用成员函数AddRef增加当前正在处理的ScriptReloader对象的引用计数,避免它在读取ContentScript的过程中被销毁。
从前面的调用过程可以知道,参数user_scripts描述的是一个std:
:
vector。
这个std:
:
vector保存在当前已经加载的Extension定义的ContentScript。
另外一个参数extension_info指向的是一个std:
:
map。
这个std:
:
map描述了当前加载的所有Extension。
ScriptReloader类的成员函数StartLoad将参数extension_info指向的std:
:
map保存在自己的成员变量extension_info_之后,就向Browser进程中专门用来执行文件读写操作的BrowserThread:
:
FILE线程的消息队列发送一个Task。
这个Task绑定了ScriptReloader类的成员函数RunLoad。
这意味着ScriptReloader类的成员函数RunLoad接下来会在BrowserThread:
:
FILE线程被调用,用来读取保存在参数user_scripts描述的std:
:
vector中的ContentScript,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
//Thismethodwillbecalledonthefilethread.
voidUserScriptMaster:
:
ScriptReloader:
:
RunLoad(
constUserScriptList&user_scripts){
LoadUserScripts(const_cast
//Scriptsnowcontainslistofup-to-datescripts.Loadthecontentinthe
//sharedmemoryandletthemasterknowit'sready.Weneedtopostthetask
//backevenifnoscriptswarefoundtobalancetheAddRef/Releasecalls.
BrowserThread:
:
PostTask(master_thread_id_,
FROM_HERE,
base:
:
Bind(&ScriptReloader:
:
NotifyMaster,
this,
base:
:
Passed(Serialize(user_scripts))));
}
这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。
ScriptReloader类的成员函数RunLoad首先调用成员函数LoadUserScripts读取当前已经加载的Extension定义的ContentScript,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidUserScriptMaster:
:
ScriptReloader:
:
LoadUserScripts(
UserScriptList*user_scripts){
for(size_ti=0;i
UserScript&script=user_scripts->at(i);
scoped_ptr
GetLocalizationMessages(script.extension_id()));
for(size_tk=0;k UserScript: : File&script_file=script.js_scripts()[k]; if(script_file.GetContent().empty()) LoadScriptContent( script.extension_id(),&script_file,NULL,verifier_.get()); } for(size_tk=0;k UserScript: : File&script_file=script.css_scripts()[k]; if(script_file.GetContent().empty()) LoadScriptContent(script.extension_id(), &script_file, localization_messages.get(), verifier_.get()); } } } 这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。 参数user_srcipts描述的std: : vector里面保存的是一系列的UserScript对象。 每一个UserScript对象里面包含若干个ContentScript文件。 每一个ContentScript文件都是通过一个UserScript: : File对象描述。 注意,这些ContentScript有可能是JavaScript,也有可能是CSSScript。 这意味着Extension不仅可以注入JavaScript到宿主网页中,还可以注入CSSScript。 ScriptReloader类的成员函数LoadUserScripts依次调用函数LoadScriptContent读取这些ContentScript文件的内容,如下所示: [cpp]viewplaincopy在CODE上查看代码片派生到我的代码片 staticboolLoadScriptContent(conststd: : string&extension_id, UserScript: : File*script_file, constSubstitutionMap*localization_messages, ContentVerifier*verifier){ std: : stringcontent; constbase: : FilePath&path=ExtensionResource: : GetFilePath( script_file->extension_root(),script_file->relative_path(), ExtensionResource: : SYMLINKS_MUST_RESOLVE_WITH
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Chromium扩展Extension的Content Script加载过程分析 Chromium 扩展 Extension Content Script 加载 过程 分析