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

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

时间: 2024-11-14 00:59:50

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

Chromium扩展(Extension)的Content Script加载过程分析

Chromium的Extension由Page和Content Script组成.Page有UI和JS,它们加载在自己的Extension Process中渲染和执行.Content Script只有JS,这些JS是注入在宿主网页中执行的.Content Script可以访问宿主网页的DOM Tree,从而可以增强宿主网页的功能.本文接下来分析Content Script注入到宿主网页执行的过程. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 我们可以在

Chromium扩展(Extension)加载过程分析

Chromium在启动的时候,会根据当前用户的Profile创建一个Extension Service.Extension Service在创建过程中,会加载当前已经安装的所有Extension,并且将它们注册在一个Extension Registry中.以后通过这个Extension Registry,就可以得到当前可用的Extension的信息了.本文接下来就分析Extension的加载过程. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! Chrom

Chromium插件(Plugin)模块(Module)加载过程分析

在Chromium中,每一个Plugin都对应一个Module,称为Plugin Module.一个Plugin Module可创建多个Plugin Instance.每一个Plugin Instance对应于网页中的一个<embed>标签.在为<embed>标签创建Plugin Instance之前,先要加载其对应的Plugin Module.本文接下来分析Plugin Module的加载过程. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注

前端技术-HTML页面的加载

HTML页面的加载 HTML页面的加载实际上是基于http过程+浏览器对数据的解析渲染. http协议的请求过程是基于TCP协议的.http是要基于TCP连接基础上,简单的说,TCP单纯建立连接,不涉及任何我们需要请求的实际数据,简单的传输.http基于TCP建立的连接来收发数据,即实际应用上来的. 一个HTML页面的加载的交互流程大致如下: 0.输入URL1.解析URL2.构造并发送HTTP请求服务器的永久重定向响应(从 http://example.com 到 http://www.exam

关于asp.net中页面事件加载的先后顺序

一.ASP.NET 母版页和内容页中的事件 母版页和内容页都可以包含控件的事件处理程序.对于控件而言,事件是在本地处理的,即内容页中的控件在内容页中引发事件,母版页中的控件在母版页中引发事件.控件事件不会从内容页发送到母版页.同样,也不能在内容页中处理来自母版页控件的事件. 在某些情况下,内容页和母版页中会引发相同的事件.例如,两者都引发 Init 和 Load 事件.引发事件的一般规则是初始化事件从最里面的控件向最外面的控件引发,所有其他事件则从最外面的控件向最里面的控件引发. 请记住,母版页

yii2 如何在页面底部加载css和js

作者:白狼 出处:www.manks.top/article/yii2_load_js_css_in_end 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 正所谓命由己造,相由心生,那么我们的问题又是怎么抛出来的喃? 这个问题也算是个比较久远但是经常会有人要问到的问题.究其原因, 一来实际开发中不可避免,二来也就是刚接触yii的小伙伴喜欢揪着不放.那 为啥了解过这个问题的人就不纠结了喃?外星人:楼主你si不si撒,人家都

通过页面预加载(preload)提升小程序的响应速度

GitHub: https://github.com/WozHuang/mp-extend 主要目标 如果小程序在打开新页面时需要通过网络请求从接口中获取所用的数据,在请求完成之前页面都会因为没有数据而呈现一片空白,解决这个问题常见的解决方案有: 先使用从缓存中取出上一次的数据,等到请求结束后再使用请求得到的数据(这个对于变动不大的数据是完全可行的,但是对于变动大或者以内容为主的功能并不合适) 在加载时显示骨架屏(知乎和饿了么的移动端有的地方是这么做的) 前一个页面预加载下一个页面的数据,达到秒

HTML页面的加载

HTML页面的加载实际上是基于http过程+浏览器对数据的解析渲染. http协议的请求过程是基于TCP协议的.http是要基于TCP连接基础上,简单的说,TCP单纯建立连接,不涉及任何我们需要请求的实际数据,简单的传输.http基于TCP建立的连接来收发数据,即实际应用上来的. 一个HTML页面的加载的交互流程大致如下: 0.输入URL1.解析URL2.构造并发送HTTP请求服务器的永久重定向响应(从 http://example.com 到 http://www.example.com)浏览

document.readyState等属性,判断页面是否加载完

如何在页面加载完成后再去做某事?什么方法可以判断当前页面加载已完成?document.readyState 判断页面是否加载完成?javascript提供了document.readyState=="complete"方法来解决当前页面加载判断的问题. <script type="text/javascript">   function initView(){         if (document.readyState=="complete