分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
Chromium的Extension由Page和Content Script组成。Page有UI和JS,它们加载在自己的Extension Process中渲染和执行。Content Script只有JS,这些JS是注入在宿主网页中执行的。Content Script可以访问宿主网页的DOM Tree,从而可以增强宿主网页的功能。本文接下来分析Content Script注入到宿主网页执行的过程。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!
我们可以在Extension的清单文件中指定Content Script对哪些网页有兴趣,如前面Chromium扩展(Extension)机制简要介绍和学习计划一文的Page action example所示:
{ ...... "content_scripts": [ { "matches": ["https://fast.com/"], "js": ["content.js"], "run_at": "document_start", "all_frames": true } ]}
这个清单文件仅对URL为“https://fast.com/”的网页感兴趣。当这个网页在Chromium中加载的时候,Chromium就会往里面注入脚本content.js。注入过程如图1所示:

图1 Content Script注入到宿主网页执行的过程
首先,在前面Chromium扩展(Extension)加载过程分析一文提到,Browser进程在加载Extension之前,会创建一个UserScriptMaster对象。此后每当加载一个Extension,这个UserScriptMaster对象的成员函数OnExtensionLoaded都会被调用,用来收集当前正在加载的Extension的Content Script。
此后,每当Browser进程启动一个Render进程时,代表该Render进程的一个RenderProcessHostImpl对象的成员函数OnProcessLaunched都会被调用,用来通知Browser进程新的Render进程已经启动起来的。这时候这个RenderProcessHostImpl对象会到上述UserScriptMaster对象中获取当前收集到的所有Content Script。这些Content Script接下来会通过一个类型为ExtensionMsg_UpdateUserScript的IPC消息传递给新启动的Render进程。新启动的Render进程通过一个Dispatcher对象接收这个IPC消息,并且会将它传递过来的Content Script保存在一个UserScriptSlave对象中。
接下来,每当Render进程加载一个网页时,都会在三个时机检查是否需要在该网页中注入Content Script。从前面Chromium扩展(Extension)机制简要介绍和学习计划一文可以知道,这三个时机分别为document_start、document_end和document_idle,分别表示网页的Document对象开始创建、结束创建以及空闲时。接下来我们以document_start这个时机为例,说明Content Script注入到宿主网页的过程。
网页的Document对象是在WebKit中创建的。WebKit为网页创建了Document对象之后,会调用Content层的一个RenderFrameImpl对象的成员函数didCreateDocumentElement,用来通知后者,它描述的网页的Document对象已经创建好了。这时候这个RenderFrameImpl对象将会调用前面提到的UserScriptSlave对象的成员函数InjectScripts,用来通知后者,现在可以将Content Script注入当前正在加载的网页中去执行。前面提到的UserScriptSlave对象会调用另外一个WebLocalFrameImpl对象的成员函数executeScriptInIsolatedWorld,用来注入符合条件的Content Script到当前正在加载的网页中去,并且在JS引擎的一个Isolated World中执行。Content Script在Isolated World中执行,意味着它不可以访问在宿主网页中定义的JavaScript,包括不能调用在宿主网页中定义的JavaScript函数,以及访问宿主网页中定义的变量。
以上就是Content Script注入到宿主网页中执行的大概流程。接下来我们结合源代码进行详细的分析,以便对这个注入流程有更深刻的认识。
我们首先分析UserScriptMaster类收集Content Script的过程。这要从UserScriptMaster类的构造函数说起,如下所示:
UserScriptMaster::UserScriptMaster(Profile* profile) : ......, extension_registry_observer_(this) { extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, content::Source<profile>(profile_)); 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对象关联的一个Extension Registry对象中去,也就是注入到与当前使用的Profile关联的一个Extension Registry对象中去。这个Extension Registry对象的创建过程可以参考前面Chromium扩展(Extension)加载过程分析一文。
与此同时,UserScriptMaster类的构造函数还会通过成员变量registrar_描述的一个NotificationRegistrar对象监控chrome::NOTIFICATION_EXTENSIONS_READY和content::NOTIFICATION_RENDERER_PROCESS_CREATED事件。其中,事件chrome::NOTIFICATION_EXTENSIONS_READY用来通知Chromium的Extension Service已经启动了,而事件content::NOTIFICATION_RENDERER_PROCESS_CREATED用来通知有一个新的Render进程启动起来。如前面所述,当新的Render进程启动起来的时候,UserScriptMaster类会将当前加载的Extension定义的Content Script传递给它处理。这个过程我们在后面会进行详细分析。
从前面Chromium扩展(Extension)加载过程分析一文还可以知道,接下来加载的每一个Extension,都会保存在上述Extension Registry对象内部的一个Enabled List中,并且都会调用注册在上述Extension Registry对象中的每一个ExtensionRegistryObserver对象的成员函数OnExtensionLoaded,通知它们有一个新的Extension被加载。
当UserScriptMaster类的成员变量extension_registry_observer_描述的ExtensionRegistryObserver对象的成员函数OnExtensionLoaded被调用时,它又会调用UserScriptMaster类的成员函数OnExtensionLoaded,以便UserScriptMaster类可以收集新加载的Extension定义的Content Script,如下所示:
void UserScriptMaster::OnExtensionLoaded( content::BrowserContext* browser_context, const Extension* extension) { // Add any content scripts inside the extension. extensions_info_[extension->id()] = ExtensionSet::ExtensionPathAndDefaultLocale( extension->path(), LocaleInfo::GetDefaultLocale(extension)); ...... const UserScriptList& scripts = ContentScriptsInfo::GetContentScripts(extension); for (UserScriptList::const_iterator iter = 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定义的所有Content Script保存在成员变量user_scripts_描述的一个std::vector中。
UserScriptMaster类有一个类型为bool的成员变量extensions_service_ready_。当它的值等于true的时候,表示Chromium的Extension Service已经启动起来了。这时候extensions_service_ready_就会将当前正在加载的Extension的ID插入到UserScriptMaster类的成员变量changed_extensions_描述的一个std::set中去,表示有一个新的Extension需要处理。这里说的处理,就是将新加载的Extension定义的Content Script的内容读取出来,并且保存在一个共享内存中。
将Extension定义的Content Script的内容读取出来,并且保存在一个共享内存中,是通过UserScriptMaster类的成员变量script_reloader_指向的一个ScriptReloader对象实现的。如果这个ScriptReloader已经创建出来,那么就表示它现在正在读取Content Script的过程中。这时候UserScriptMaster类的成员变量pending_load_的值会被设置为true,表示当前需要读取的Content Script发生了变化,因此需要重新进行读取。
如果UserScriptMaster类的成员变量script_reloader_指向的ScriptReloader对象还没有创建出来,那么UserScriptMaster类的成员函数OnExtensionLoaded就会调用另外一个成员函数StartLoad创建该ScriptReloader对象,并且通过该ScriptReloader对象读取当前已经加载的Extension定义的Content Script,如下所示:
void UserScriptMaster::StartLoad() { if (!script_reloader_.get()) script_reloader_ = new ScriptReloader(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定义的Content Script,如下所示:
void UserScriptMaster::ScriptReloader::StartLoad( const UserScriptList& user_scripts, const ExtensionsInfo& extensions_info) { // Add a reference to ourselves to keep ourselves alive while we're running. // Balanced by NotifyMaster(). 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对象的引用计数,避免它在读取Content Sc

本文详细分析了Chromium扩展(Extension)中Content Script的加载过程,包括UserScriptMaster如何收集Content Script,何时将Content Script注入到宿主网页,以及在不同时机(document_start、document_end、document_idle)的注入细节。Content Script在Isolated World中执行,不能访问宿主网页的JavaScript,但可以操作DOM Tree,实现增强网页功能。
最低0.47元/天 解锁文章
495

被折叠的 条评论
为什么被折叠?



