Chromium扩展(Extension)的页面(Page)加载过程分析

2504次阅读  |  发布于5年以前

Chromium的Extension Page其实就是网页,因此它们的加载过程与普通网页相同。常见的Extension Page有Background Page和Popup Page。其中,Background Page在浏览器窗口初始化完成后自动加载,之后运行在后台中。Popup Page在用户点击地址栏右边的按钮时加载,并且显示在弹窗中。本文接下来就分析Extension Page的加载过程。

Extension Page是加载在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、Hosted 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类的成员变量popupurl,而后者的值又来自于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

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8