




已阅读5页,还剩18页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Chromium扩展(Extension)的Content Script加载过程分析Chromium的Extension由Page和Content Script组成。Page有UI和JS,它们加载在自己的Extension Process中渲染和执行。Content Script只有JS,这些JS是注入在宿主网页中执行的。Content Script可以访问宿主网页的DOM Tree,从而可以增强宿主网页的功能。本文接下来分析Content Script注入到宿主网页执行的过程。plain view plain copy 在CODE上查看代码片派生到我的代码片 . content_scripts: matches: /, js: content.js, run_at: document_start, all_frames: true 这个清单文件仅对URL为“/”的网页感兴趣。当这个网页在Chromium中加载的时候,Chromium就会往里面注入脚本content.js。注入过程如图1所示:首先,在前面文章中提到,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类的构造函数说起,如下所示:cpp view plain copy 在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(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,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片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,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片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,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void UserScriptMaster:ScriptReloader:StartLoad( const UserScriptList& user_scripts, const ExtensionsInfo& extensions_info) / Add a reference to ourselves to keep ourselves alive while were 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 Script的过程中被销毁。 从前面的调用过程可以知道,参数user_scripts描述的是一个std:vector。这个std:vector保存在当前已经加载的Extension定义的Content Script。另外一个参数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中的Content Script,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片/ This method will be called on the file thread. void UserScriptMaster:ScriptReloader:RunLoad( const UserScriptList& user_scripts) LoadUserScripts(const_cast(&user_scripts); / Scripts now contains list of up-to-date scripts. Load the content in the / shared memory and let the master know its ready. We need to post the task / back even if no scripts ware found to balance the AddRef/Release calls. 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定义的Content Script,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void UserScriptMaster:ScriptReloader:LoadUserScripts( UserScriptList* user_scripts) for (size_t i = 0; i size(); +i) UserScript& script = user_scripts-at(i); scoped_ptr localization_messages( GetLocalizationMessages(script.extension_id(); for (size_t k = 0; k script.js_scripts().size(); +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_t k = 0; k extension_root(), script_file-relative_path(), ExtensionResource:SYMLINKS_MUST_RESOLVE_WITHIN_ROOT); if (path.empty() . else if (!base:ReadFileToString(path, &content) LOG(WARNING) Failed to load user script file: set_content(content.substr(strlen(base:kUtf8ByteOrderMark); else script_file-set_content(content); return true; 这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。 函数LoadScriptContent首先调用ExtensionResource类的静态成员函数GetFilePath获得要读取的Content Script的文件路径,然后再调用函数base:ReadFileToString读取该文件的内容。这样就可以获得要读取的Content Script的内容,这些内容最终又会保存在参数script_file描述的一个UserScript:File对象的内部。 这一步执行完成后,Chromium就获得了当前已经加载的Extension所定义的Content Script的内容。这些内容保存在每一个Content Script对应的UserScript:File对象中。回到前面分析的ScriptReloader类的成员函数RunLoad中,接下来它调用函数Serialize将前面读取的Content Script的内容保存在一块共享内存中,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片/ Pickle user scripts and return pointer to the shared memory. static scoped_ptr Serialize(const UserScriptList& scripts) Pickle pickle; pickle.WriteUInt64(scripts.size(); for (size_t i = 0; i scripts.size(); i+) const UserScript& script = scriptsi; / TODO(aa): This can be replaced by sending content script metadata to / renderers along with other extension data in ExtensionMsg_Loaded. / See /70516. script.Pickle(&pickle); / Write scripts as data so that we can read it out in the slave without / allocating a new string. for (size_t j = 0; j script.js_scripts().size(); j+) base:StringPiece contents = script.js_scripts()j.GetContent(); pickle.WriteData(contents.data(), contents.length(); for (size_t j = 0; j script.css_scripts().size(); j+) base:StringPiece contents = script.css_scripts()j.GetContent(); pickle.WriteData(contents.data(), contents.length(); / Create the shared memory object. base:SharedMemory shared_memory; base:SharedMemoryCreateOptions options; options.size = pickle.size(); options.share_read_only = true; if (!shared_memory.Create(options) return scoped_ptr(); if (!shared_memory.Map(pickle.size() return scoped_ptr(); / Copy the pickle to shared memory. memcpy(shared_memory.memory(), pickle.data(), pickle.size(); base:SharedMemoryHandle readonly_handle; if (!shared_memory.ShareReadOnlyToProcess(base:GetCurrentProcessHandle(), &readonly_handle) return scoped_ptr(); return make_scoped_ptr(new base:SharedMemory(readonly_handle, /*read_only=*/true); 这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。 函数Serialize依次遍历保存在参数scripts描述的一个std:vector中的每一个UserScript对象,并且将这些UserScript对象包含的Content Script写入到本地变量pickle描述一个Pickle对象中。从前面Chromium的IPC消息发送、接收和分发机制分析一文可以知道,Pickle类是Chromium定义的一种IPC消息格式,它将数据按照一定的格式打包在一块内存中。 函数Serialize将Content Script写入到本地变量pickle描述的Pickle对象中去之后,接下来又会创建一块共享内存。这块共享内存通过本地变量shared_memory描述的一个SharedMemory对象描述。有了这块共享内存之后,函数Serialize就会将Content Script的内容从本地变量pickle描述的Pickle对象中拷贝到它里面去。 函数Serialize最后获得已经写入了Content Script的共享内存的只读版本,并且将这个只读版本封装在另外一个SharedMemory对象中返回给调用者,以便调用者以后可以将它传递给宿主网页所在的Render进程进行只读访问。 这一步执行完成后,Chromium就获得了一块只读的共享内存,这块共享内存保存了当前已经加载的Extension所定义的Content Script的内容。回到前面分析的ScriptReloader类的成员函数RunLoad中,接下来它又会向成员变量master_thread_id_描述的线程的消息队列发送一个Task。这个Task绑定了ScriptReloader类的成员函数NotifyMaster,用来通知其内部引用的一个UserScriptMaster对象,当前已经加载的Extension所定义的Content Script的内容已经读取完毕。 ScriptReloader类的成员函数NotifyMaster的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void UserScriptMaster:ScriptReloader:NotifyMaster( scoped_ptr memory) / The master could go away if (master_) master_-NewScriptsAvailable(memory.Pass(); / Drop our self-reference. / Balances StartLoad(). Release(); 这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。 ScriptReloader类内部引用的UserScriptMaster对象保存在成员变量master_中。ScriptReloader类的成员函数NotifyMaster首先调用这个UserScriptMaster对象的成员函数NewScriptsAvailable,通知它已经加载的Extension所定义的Content Script的内容已经读取完毕。 通知完成后,ScriptReloader类的成员函数NotifyMaster还会调用成员函数Release减少当前正在处理的ScriptReloader对象的引用计数,用来平衡在读取Content Script之前,对该ScriptReloader对象的引用计数的增加。 接下来我们继续分析UserScriptMaster类的成员函数NewScriptsAvailable的实现,以便了解它是如何处理当前已经加载的Extension的Content Script的内容的,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void UserScriptMaster:NewScriptsAvailable( scoped_ptr handle) if (pending_load_) / While we were loading, there were further changes. Dont bother / notifying about these scripts and instead just immediately reload. pending_load_ = false; StartLoad(); else . / Weve got scripts ready to go. shared_memory_ = handle.Pass(); for (content:RenderProcessHost:iterator i( content:RenderProcessHost:AllHostsIterator(); !i.IsAtEnd(); i.Advance() SendUpdate(i.GetCurrentValue(), shared_memory_.get(), changed_extensions_); . 这个函数定义在文件external/chromium_org/chrome/browser/extensions/user_script_master.cc中。 UserScriptMaster类的成员函数NewScriptsAvailable首先判断成员变量pending_load_的值是否等于true。如果等于true,那么就说明前面在读取Content Script的过程中,又有新的Extension被加载。这时候UserScriptMaster类的成员函数会调用前面分析过的成员函数StartLoad对Content Script进行重新读取。 我们假设这时候UserScriptMaster类的成员变量pending_load_的值等于false。在这种情况下,UserScriptMaster类的成员函数NewScriptsAvailable首先将参数handle描述的共享内存保存在成员变量shared_memory_中,接下来又会将这块共享内存传递给当前已经启动的Render进程,这是通过调用成员函数SendUpdate实现的。 我们假设当前还没有Render进程启动起来。前面分析UserScriptMaster类的构造函数的实现时提到,UserScriptMaster类会监控Render进程启动事件,也就是content:NOTIFICATION_RENDERER_PROCESS_CREATED事件。以后每当有一个Render进程启动完成,UserScriptMaster类的成员函数Observe就会被调用。UserScriptMaster类的成员函数Observe在调用的过程中,就会将前面已经加载的Extension的Content Script发送给新启动的Render进程,以便后者可以将Content Script注入到它后续加载的网页中去。 接下来我们就从Render进程启动完成后开始,分析UserScriptMaster类将Content Script传递给Render进程的过程。从前面Chromium的Render进程启动过程分析一文可以知道,Render进程是由Browser进程启动的。Browser进程又是通过ChildProcessLauncher:Context类启动Render进程的。当Render进程启动完成后,ChildProcessLauncher:Context类的静态成员函数OnChildProcessStarted就会被调用,它的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class ChildProcessLauncher:Context : public base:RefCountedThreadSafe public: . static void OnChildProcessStarted( / |this_object| is NOT thread safe. Only use it to post a t
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 劳动关系考试题及答案
- 篮球机构考试题及答案
- 矿灯房考试题及答案
- 科技创意考试题及答案
- 酒楼消防考试题及答案
- 铸管制芯工上岗考核试卷及答案
- 地勘掘进工设备调试考核试卷及答案
- 酒精原料粉碎工新员工考核试卷及答案
- 烧结原料工上岗考核试卷及答案
- 锚链热处理工基础考核试卷及答案
- 人教版九年级上册数学教师用书
- GB/T 42381.8-2023数据质量第8部分:信息和数据质量:概念和测量
- 中国传统故事英文九色鹿二篇
- 突发事件处理记录表(标准范本)
- 房产归属协议书范本
- 350吨履带吊地基承载力验算
- 影视艺术导论教材课件汇总完整版ppt全套课件最全教学教程整本书电子教案全书教案课件合集
- TSG-R0005-2022《移动式压力容器安全技术监察规程》(2022版)
- 2020 ACLS-PC-SA课前自我测试试题及答案
- 第1章 税务会计与纳税筹划概述
- GB∕T 41181-2021 坐姿椅
评论
0/150
提交评论