Chromium的Extension Page其实就是网页,因此它们的加载过程与普通网页相同。常见的Extension Page有Background Page和Popup Page。其中,Background Page在浏览器窗口初始化完成后自动加载,之后运行在后台中。Popup Page在用户点击地址栏右边的按钮时加载,并且显示在弹窗中。本文接下来就分析Extension Page的加载过程。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
Extension Page是由Browser进程发加载在Extension Process中的,如图1所示:
图1 Extension的Background Page和Popup Page的加载示意图
Extension Process实际上就是Render Process。Chromium的Content层向外提供了一个WebContents类,通过调用这个类的静态成员函数Create就可以在一个Extension Process加载一个指定的Extension Page。
Background Page是一个特殊的网页,它的内容是空的,不过包含有一个background.js。这个background.js是在Extension的清单文件中指定的。Popup Page则与普通网页是一样的,它既可以包含有UI元素,也可以包含JavaScript脚本。
接下来,我们就结合源代码,先分析Background Page的加载过程,再分析Popup Page的加载过程。
Chromium的chrome模块会创建一个ChromeNotificationObserver对象,用来监听每一个新打开的浏览器窗口的NOTIFICATION_BROWSER_WINDOW_READY事件。这时候上述ChromeNotificationObserver对象的成员函数OnBrowserWindowReady会被调用,如下所示:
void ChromeNotificationObserver::OnBrowserWindowReady(Browser* browser) { Profile* profile = browser->profile(); ...... extensions::ProcessManager* manager = ExtensionSystem::Get(profile)->process_manager(); ...... manager->OnBrowserWindowReady(); ...... }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/chrome_notification_observer.cc中。
参数browser指向的是一个Browser对象。这个Browser对象描述的就是一个新打开的浏览器窗口,ChromeNotificationObserver类的成员函数OnBrowserWindowReady首先调用它的成员函数profile获得浏览器在启动过程中创建的Profile,然后再根据这个Profile获得一个ProcessManager对象。有了这个ProcessManager对象之后,就可以调用它的成员函数OnBrowserWindowReady,用来通知它有一个新的浏览器窗口打开了。浏览器启动时创建Profile的过程,以及根据Profile创建ProcessManager对象的过程,可以参考前面Chromium扩展(Extension)加载过程分析一文。
ProcessManager类的成员函数OnBrowserWindowReady在调用的过程中,就会为当前加载的Extension创建Background Page,如下所示:
void ProcessManager::OnBrowserWindowReady() { ...... CreateBackgroundHostsForProfileStartup(); }
这个函数定义在文件external/chromium_org/extensions/browser/process_manager.cc中。
ProcessManager类的成员函数OnBrowserWindowReady调用另外一个成员函数CreateBackgroundHostsForProfileStartup为当前加载的Extension创建Background Page,如下所示:
void ProcessManager::CreateBackgroundHostsForProfileStartup() { ...... const ExtensionSet& enabled_extensions = ExtensionRegistry::Get(GetBrowserContext())->enabled_extensions(); for (ExtensionSet::const_iterator extension = enabled_extensions.begin(); extension != enabled_extensions.end(); ++extension) { CreateBackgroundHostForExtensionLoad(this, extension->get()); ...... } ...... }
这个函数定义在文件external/chromium_org/extensions/browser/process_manager.cc中。
在前面Chromium扩展(Extension)加载过程分析一文提到,Chromium的Browser进程在启动的时候,会将那些状态设置为Enabled的Extension保存在一个Extension Registry的Enabled List中。ProcessManager类的成员函数CreateBackgroundHostsForProfileStartup主要就是遍历这个Enabled List中的每一个Extension,并且调用函数CreateBackgroundHostForExtensionLoad检查它们是否指定了Background Page。如果指定了,那么就会进行加载。
函数CreateBackgroundHostForExtensionLoad的实现如下所示:
static void CreateBackgroundHostForExtensionLoad( ProcessManager* manager, const Extension* extension) { DVLOG(1) << "CreateBackgroundHostForExtensionLoad"; if (BackgroundInfo::HasPersistentBackgroundPage(extension)) manager->CreateBackgroundHost(extension, BackgroundInfo::GetBackgroundURL(extension)); }
这个函数定义在文件external/chromium_org/extensions/browser/process_manager.cc中。
函数CreateBackgroundHostForExtensionLoad首先检查参数extension描述的Extension是否指定了类型为persitent的Background Page。如果指定了,那么就会调用参数manager指向的一个ProcessManager对象的成员函数CreateBackgroundHost对它进行加载。对于非persitent的Background Page,它们只会在特定事件发生时,才会被加载。本文主要以类型为persitent的Background Page为例,说明它们的加载过程。非persitent的Background Page的加载过程,也是类似的。
函数CreateBackgroundHostForExtensionLoad在调用ProcessManager类的成员函数CreateBackgroundHost加载一个Background Page之前,首先要获得这个Background Page的URL。这个URL是通过调用BackgroundInfo类的静态成员函数GetBackgroundURL获得的,如下所示:
GURL BackgroundInfo::GetBackgroundURL(const Extension* extension) { const BackgroundInfo& info = GetBackgroundInfo(extension); if (info.background_scripts_.empty()) return info.background_url_; return extension->GetResourceURL(kGeneratedBackgroundPageFilename); }
这个函数定义在文件external/chromium_org/extensions/common/manifest_handlers/background_info.cc中。
Chromium将其平台上的程序分为扩展(Extension)和应用(App)两种。两者具有相同的文件结构,在Chromium中都是通过一个Extension类描述,但是后者比前者具有更严格的权限限制。应用又分为Hosted App(托管应用)和Packaged App(打包应用)两种。Hosted App只提供一个图标和Manifest文件,并且在Manifest文件中声明了它的Popup Page和Background Page等URL。Packaged App则将Popup Page和Background Page文件打包在一起安装在本地。关于Extension、Packaged App和Packaged App的更详细描述,可以参考Chrome应用基础一文。
我们假设参数extension描述的是一个Extension,并且是一个安装在本地的Extension。这时候它在Manifest文件实际上只是为Backgropund Page指定了Background Script,如我们在前面Chromium扩展(Extension)机制简要介绍和学习计划一文所示的Page action example:
{ ...... "background": { "scripts": ["background.js"] }, ...... }
BackgroundInfo类的静态成员函数GetBackgroundURL首先会获得参数extension描述的Extension的Background Page信息。这些信息保存在一个BackgroundInfo对象中。当这个BackgroundInfo对象的成员变量background_scripts_的值不等空时,它的值描述的就是一个Background Page的Background Script,同时也表明参数extension描述的不是一个Hosted App。在这种情况下,我们就不能直接获得Background Page的URL,而是要通过调用参数extension指向的Extension对象的成员函数GetResourceURL获得。
Extension类的成员函数GetResourceURL的实现如下所示:
class Extension : public base::RefCountedThreadSafe<Extension> { public: ...... GURL GetResourceURL(const std::string& relative_path) const { return GetResourceURL(url(), relative_path); } ...... };
这个函数定义在文件external/chromium_org/extensions/common/extension.h中。
从前面的调用过程可以知道,参数relative_path的值为kGeneratedBackgroundPageFilename。Extension类的成员函数GetResourceURL首先调用另外一个成员函数url获得当前正在处理的Extension的URL。这个URL的形式为:chrome-extension://[extension_id]/。其中,[extension_id]为当前正在处理的Extension的ID。
最后,Extension类的成员函数GetResourceURL将参数relative_path的值kGeneratedBackgroundPageFilename添加在前面获得的URL的后面,从而得到当前正在处理的Extension的Background Page的URL。这是通过调用Extension类的静态成员函数GetResourceURL实现的,如下所示:
// static GURL Extension::GetResourceURL(const GURL& extension_url, const std::string& relative_path) { ...... std::string path = relative_path; // If the relative path starts with "/", it is "absolute" relative to the // extension base directory, but extension_url is already specified to refer // to that base directory, so strip the leading "/" if present. if (relative_path.size() > 0 && relative_path[0] == ‘/‘) path = relative_path.substr(1); GURL ret_val = GURL(extension_url.spec() + path); ...... return ret_val; }
这个函数定义在文件external/chromium_org/extensions/common/extension.cc中。
通过上面的分析,我们就可以知道,一个Extension如果指定了Background Page,那么这个Background Page的URL就为chrome-extension://[extension_id]/kGeneratedBackgroundPageFilename。其中,kGeneratedBackgroundPageFilename是一个常量,它的定义如下所示:
const char kGeneratedBackgroundPageFilename[] = "_generated_background_page.html";
这个常量定义在文件external/chromium_org/extensions/common/constants.cc中。
这意味着,一个Extension的Background Page的URL为:chrome-extension://[extension_id]/_generated_background_page.html。Chromium的extension模块会注册一个Chromium Extension Protocol Handler,用来处理chrome-extension协议。也就是说,当我们在浏览器的地址栏输入上述URL时Chromium Extension Protocol Handler会返回[extension_id]/_generated_background_page.html的内容给WebKit处理。这个文件的内容是动态生成的。以前面的Page action example为例,Chromium Extension Protocol Handler为它生成的_generated_background_page.html的内容如下所示:
<!DOCTYPE html> <body> <script src="background.js"></script>
获得了要加载的Background Page的URL之后,回到前面分析的函数CreateBackgroundHostForExtensionLoad中,接下来它就会调用ProcessManager类的成员函数CreateBackgroundHost加载Background Page,如下所示:
bool ProcessManager::CreateBackgroundHost(const Extension* extension, const GURL& url) { ...... ExtensionHost* host = new ExtensionHost(extension, GetSiteInstanceForURL(url), url, VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); host->CreateRenderViewSoon(); ...... return true; }
这个函数定义在文件external/chromium_org/extensions/browser/process_manager.cc中。
ProcessManager类的成员函数CreateBackgroundHost首先是创建一个ExtensionHost对象。这个ExtensionHost对象类似于Chromium加载一个普通URL时在Browser进程中创建的RenderProcessHost对象。关于RenderProcessHost的详细描述,可以参考Chromium多进程架构简要介绍和学习计划这个系列的文章。
ExtensionHost对象在创建的过程中,会创建一个WebContents对象,如下所示:
ExtensionHost::ExtensionHost(const Extension* extension, SiteInstance* site_instance, const GURL& url, ViewType host_type) : ......, initial_url_(url), ...... { ...... host_contents_.reset(WebContents::Create( WebContents::CreateParams(browser_context_, site_instance))), ...... }
这个函数定义在文件external/chromium_org/extensions/browser/extension_host.cc中。
创建出来的WebContents对象保存在ExtensionHost类的成员变量host_contents_中。同时,要加载的Background Page URL将会保存在ExtensionHost类的成员变量initial_url_中。
回到前面分析的ProcessManager类的成员函数CreateBackgroundHost,它创建了一个ExtensionHost对象之后,接下来就会调用这个ExtensionHost对象的成员函数CreateRenderViewSoon加载指定的Background Page,如下所示:
void ExtensionHost::CreateRenderViewSoon() { if ((render_process_host() && render_process_host()->HasConnection())) { // If the process is already started, go ahead and initialize the RenderView // synchronously. The process creation is the real meaty part that we want // to defer. CreateRenderViewNow(); } else { ProcessCreationQueue::GetInstance()->CreateSoon(this); } }
这个函数定义在文件external/chromium_org/extensions/browser/extension_host.cc中。
ExtensionHost类的成员函数CreateRenderViewSoon首先判断用来加载指定的Background Page的Extension Process是否已经创建出来了。如果已经创建,那么就直接调用另外一个成员函数CreateRenderViewNow同步请求在这个Extension Process中加载指定的Background Page。否则的话,则需要通过调用当前进程中的一个ProcessCreationQueue单例对象的成员函数CreateSoon异步请求加载指定的的Background Page。后一种情况之所以要异步请求,是因为这种情况需要先创建一个Extension Process,然后才能加载指定的Background Page,而创建Extension Process是一个相对耗时的操作。
在异步请求情况下,指定的Background Page同样也是通过ExtensionHost类的成员函数CreateRenderViewNow进行加载的。因此,接下来我们继续分析它的实现,如下所示:
void ExtensionHost::CreateRenderViewNow() { LoadInitialURL(); ...... }
这个函数定义在文件external/chromium_org/extensions/browser/extension_host.cc中。
ExtensionHost类的成员函数CreateRenderViewNow调用另外一个成员函数LoadInitialURL加载指定的Background Page,如下所示:
void ExtensionHost::LoadInitialURL() { host_contents_->GetController().LoadURL( initial_url_, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); }
这个函数定义在文件external/chromium_org/extensions/browser/extension_host.cc中。
从前面的分析可以知道,指定要加载的Background Page的URL保存在ExtensionHost类的成员变量initial_url_中。ExtensionHost类的成员函数LoadInitialURL首先通过调用成员变量host_contents_指向的WebContents对象的成员函数GetController获得一个NavigationControllerImpl对象。有了这个NavigationControllerImpl对象之后,就可以调用它的成员函数LoadURL在相应的Extension Process中加载指定的Background Page了。这个过程与加载一个普通的URL是一样的,具体可以参考前面Chromium网页Frame Tree创建过程分析一文。
这样,我们就分析完成了Extension的Background Page的加载过程。从分析的过程可以知道,Extension的Background Page本质上是一个保存在本地的网页,因此它的加载过程与普通的URL并无异。接下来,我们继续分析Extension的Popup Page的加载过程。
从前面Chromium扩展(Extension)机制简要介绍和学习计划一文可以知道,Browser Action和Page Action均可在地址栏右边放置一个按钮,并且在点击该按钮时,显示一个Popup Page。两者加载Popup Page的原理都是一样的,因此接下来我们以Browser Action为例,分析Popup Page的加载过程。
当一个Extension指定了Browser Action时,Chromium将会为其创建一个BrowserActionButton。这个BrowserActionButton描述的就是Extension在地址栏右边的按钮。当用户点击这个按钮的时候,BrowserActionButton类的成员函数ButtonPressed就会被调用,如下所示:
void BrowserActionButton::ButtonPressed(views::Button* sender, const ui::Event& event) { delegate_->OnBrowserActionExecuted(this); }
这个函数定义在文件external/chromium_org/chrome/browser/ui/views/toolbar/browser_action_view.cc中。
BrowserActionButton类的成员变量delegate_指向的是一个BrowserActionsContainer对象。这个BrowserActionsContainer对象描述的是包含Browser Action Button的容器,BrowserActionButton类的成员函数ButtonPressed调用它的成员函数OnBrowserActionExecuted,通知它在一个弹出窗口中加载一个Popup Page。
BrowserActionsContainer类的成员函数OnBrowserActionExecuted的实现如下所示:
void BrowserActionsContainer::OnBrowserActionExecuted( BrowserActionButton* button) { ShowPopup(button, ExtensionPopup::SHOW, true); }
这个函数定义在文件external/chromium_org/chrome/browser/ui/views/toolbar/browser_actions_container.cc中。
BrowserActionsContainer类的成员函数OnBrowserActionExecuted调用另外一个成员函数ShowPopup显示一个Popup Page,如下所示:
bool BrowserActionsContainer::ShowPopup( BrowserActionButton* button, ExtensionPopup::ShowAction show_action, bool should_grant) { const Extension* extension = button->extension(); GURL popup_url; if (model_->ExecuteBrowserAction( extension, browser_, &popup_url, should_grant) != extensions::ExtensionToolbarModel::ACTION_SHOW_POPUP) { return false; } ...... popup_ = ExtensionPopup::ShowPopup(popup_url, browser_, reference_view, views::BubbleBorder::TOP_RIGHT, show_action); ...... return true; }
这个函数定义在文件external/chromium_org/chrome/browser/ui/views/toolbar/browser_actions_container.cc中。
BrowserActionsContainer类的成员函数ShowPopup首先调用成员变量model_指向的一个ExtensionToolbarModel对象的成员函数ExecuteBrowserAction获得要显示的Popup Page的URL,如下所示:
ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction( const Extension* extension, Browser* browser, GURL* popup_url_out, bool should_grant) { ...... ExtensionAction* browser_action = ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension); ...... if (browser_action->HasPopup(tab_id)) { if (popup_url_out) *popup_url_out = browser_action->GetPopupUrl(tab_id); return ACTION_SHOW_POPUP; } ...... return ACTION_NONE; }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_toolbar_model.cc中。
参数extension描述的就是要显示Popup Page的Extension。ExtensionToolbarModel类的成员函数ExecuteBrowserAction首先通过调用ExtensionActionManager类的静态成员函数Get获得与当前使用的Profile关联的一个ExtensionActionManager对象。有了这个ExtensionActionManager对象之后,就可以获得参数extension描述的Extension在清单文件中配置的Browser Action信息。这些信息封装在一个ExtensionAction对象中。
获得了要显示Popup Page的Extension的Browser Action信息之后,就可以检查它是否指定了Popup Page。如果指定了,并且输出参数popup_url_out的值不等于NULL,那么ExtensionToolbarModel类的成员函数ExecuteBrowserAction就会调用前面获得的一个ExtensionAction对象的成员函数GetPopupUrl获得当前要显示的Popup Page的URL。获取到的Popup Page URL将会通过输出参数popup_url_out返回给调用者。
接下来我们继续分析ExtensionAction类的成员函数GetPopupUrl的实现,以便了解Extension的Popup Page URL的结构,如下所示:
GURL ExtensionAction::GetPopupUrl(int tab_id) const { return GetValue(&popup_url_, tab_id); }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_action.cc中。
ExtensionAction类的成员函数GetPopupUrl返回的是成员变量popup_url_的值。这个值是在ExtensionAction类的构造函数中设置的,如下所示:
ExtensionAction::ExtensionAction(const std::string& extension_id, extensions::ActionInfo::Type action_type, const extensions::ActionInfo& manifest_data) : extension_id_(extension_id), action_type_(action_type) { ...... SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url); ...... }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_action.cc中。
参数manifest_data指向的是一个ActionInfo对象。这个ActionInfo对象描述的是Extension在清单文件中配置的Browser Action信息。通过这个ActionInfo对象的成员变量default_popup_url可以获得为Browser Action指定的Popup Page文件名。以前面Chromium扩展(Extension)机制简要介绍和学习计划一文中的Browser action example为例,它为Browser Action指定的Popup Page文件名为“popup.html”,如下所示:
{ ...... "browser_action": { "default_icon": "icon.png", "default_popup": "popup.html" }, ...... }
回到ExtensionAction类的构造函数中,它获得的Popup Page URL将会通过调用成员函数SetPopupUrl保存在成员变量popup_url_中,如下所示:
void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) { ...... SetValue(&popup_url_, tab_id, url); }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_action.cc中。
从前面的分析就可以知道,Extension的Popup Page的URL来自于ExtensionAction类的成员变量popup_url_,而后者的值又来自于ActionInfo类的成员变量default_popup_url。因此,接下来我们继续分析ActionInfo类的成员变量default_popup_url的设置过程,以便了解Extension的Popup Page URL的结构。
ActionInfo类的成员变量default_popup_url是在加载Extension的过程中设置的。一个Extension如果指定了Browser Action,那么Chromium就会调用ActionInfo类的静态成员函数Load解析Browser Action的信息,如下所示:
scoped_ptr<ActionInfo> ActionInfo::Load(const Extension* extension, const base::DictionaryValue* dict, base::string16* error) { scoped_ptr<ActionInfo> result(new ActionInfo()); ...... // Read the action‘s |popup| (optional). const char* popup_key = NULL; if (dict->HasKey(keys::kPageActionDefaultPopup)) popup_key = keys::kPageActionDefaultPopup; ...... if (popup_key) { const base::DictionaryValue* popup = NULL; std::string url_str; if (dict->GetString(popup_key, &url_str)) { // On success, |url_str| is set. Nothing else to do. } ...... if (!url_str.empty()) { // An empty string is treated as having no popup. result->default_popup_url = Extension::GetResourceURL(extension->url(), url_str); ...... } ...... } return result.Pass(); }
这个函数定义在文件external/chromium_org/chrome/common/extensions/api/extension_action/action_info.cc中。
ActionInfo类的静态成员函数Load首先创建一个ActionInfo对象描述当前要解析的Browser Action的信息。
ActionInfo类的静态成员函数Load接下来检查当前要解析的Browser Action是否指定了popup属性。如果指定了,那么它的值就是一个Popup Page的文件名。这个文件名会被提取出来,保存在变量url_str中。
有了Popup Page的文件名之后,我们还需要知道它所属的Extension的URL。这可以通过调用参数extension指向的Extension对象的成员函数url获得。前面我们提到过,一个Extension的URL的形式为:chrome-extension://[extension_id]/。其中,[extension_id]为当前正在处理的Extension的ID。
有了Extension的URL之后,就可以将前面获得的Popup Page文件名附加在它之后,从而得到一个Popup Page的URL。这是通过调用Extension类的静态成员函数GetResourceURL实现的。Extension类的静态成员函数GetResourceURL我们在前面已经分析过,这里不再复述。
最后,ActionInfo类的静态成员函数Load会将获得Popup Page URL保存在前面创建的ActionInfo对象的成员变量default_popup_url中。这样,我们前面分析的ExtensionAction类的构造函数就可以通过这个成员变量获得一个Popup Page的URL,并且将它保存在自己的成员变量popup_url_中了。
这一步执行完成之后,回到前面分析的BrowserActionsContainer类的成员函数ShowPopup中,这时候就它获得了要加载的Popup Page的URL,接下来它又会通过调用ExtensionPopup类的静态成员函数ShowPopup加载该URL,如下所示:
ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url, Browser* browser, views::View* anchor_view, views::BubbleBorder::Arrow arrow, ShowAction show_action) { extensions::ExtensionViewHost* host = extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser); ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow, show_action); ...... return popup; }
这个函数定义在文件external/chromium_org/chrome/browser/ui/views/extensions/extension_popup.cc中。
ExtensionPopup类的静态成员函数ShowPopup首先调用ExtensionViewHostFactory类的静态成员函数CreatePopupHost创建一个ExtensionViewHost对象。有了这个ExtensionViewHost对象之后,就可以将它封装在一个ExtensionPopup对象中。这个ExtensionPopup对象描述的就是当前要显示的Popup Page。
ExtensionViewHostFactory类的静态成员函数CreatePopupHost在创建ExtensionViewHost对象的过程中,就会通过前面提到的WebContents接口加载Popup Page,如下所示:
ExtensionViewHost* ExtensionViewHostFactory::CreatePopupHost(const GURL& url, Browser* browser) { DCHECK(browser); return CreateViewHost( url, browser->profile(), browser, VIEW_TYPE_EXTENSION_POPUP); }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_view_host_factory.cc中。
ExtensionViewHostFactory类的静态成员函数CreatePopupHost通过调用函数CreateViewHost创建一个ExtensionViewHost对象,如下所示:
ExtensionViewHost* CreateViewHost(const GURL& url, Profile* profile, Browser* browser, extensions::ViewType view_type) { ...... const Extension* extension = GetExtensionForUrl(profile, url); ...... return CreateViewHostForExtension( extension, url, profile, browser, view_type); }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_view_host_factory.cc中。
函数CreateViewHost首先通过调用函数GetExtensionForUrl获得一个Extension对象。这个Extension对象描述的就是当前要显示Popup Page的Extension。有了这个Extension对象之后,函数CreateViewHost最后调用另外一个函数CreateViewHostForExtension创建一个ExtensionViewHost对象,如下所示:
ExtensionViewHost* CreateViewHostForExtension(const Extension* extension, const GURL& url, Profile* profile, Browser* browser, ViewType view_type) { ...... ProcessManager* pm = ExtensionSystem::Get(profile)->process_manager(); content::SiteInstance* site_instance = pm->GetSiteInstanceForURL(url); ExtensionViewHost* host = #if defined(OS_MACOSX) new ExtensionViewHostMac(extension, site_instance, url, view_type); #else new ExtensionViewHost(extension, site_instance, url, view_type); #endif host->CreateView(browser); return host; }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_view_host_factory.cc中。
从这里可以看到,在非Mac OS X平台上,函数CreateViewHostForExtension会创建一个ExtensionViewHost对象。这个ExtensionViewHost对象的创建过程,也就是ExtensionViewHost的构造函数的实现,如下所示:
ExtensionViewHost::ExtensionViewHost( const Extension* extension, content::SiteInstance* site_instance, const GURL& url, ViewType host_type) : ExtensionHost(extension, site_instance, url, host_type), ...... { ...... }
这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_view_host.cc中。
ExtensionViewHost类是从ExtensionHost类继承下来的。因此,ExtensionViewHost的构造函数会调用父类ExtensionHost的构造函数,用来执行初始化工作。前面我们在分析Extension的Background Page的加载过程时,已经分析过ExtensionHost类的构造函数了。它将会创建一个WebContents对象。有了这个WebContents对象之后,以后就可以加载这里的参数url描述的一个Popup Page了。
这样,我们就分析完成Extension的Popup Page的加载过程了。从分析的过程就可以看出,Extension的Popup Page与Background Page都具有自己的URL。URL的形式为chrome-extension://[extension_id]/xxx.html。这些URL都是通过Chromium的Content层向外提供的API接口WebContents在一个Extension Process中加载的。
与此同时,普通网页的URL也是通过Chromium的Content层向外提供的API接口WebContents在一个Render Process中加载的。Extension Process和Render Process的概念是一样的,都是用来加载、解析和渲染网页的。从这个角度看,Extension Page的加载、解析和渲染与一般的Web Page并无异。
至此,我们就以Background Page和Popup Page为例,分析了Extension的Page加载过程。我们在前面Chromium扩展(Extension)机制简要介绍和学习计划一文提到,Extension由Page和Content Script组成。在接下来一篇文章中,我们将继续分析Extension的Content Script的加载过程。这样我们就更加完整地理解Extension的实现原理了。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。