使用Tabhelper解耦WebContents

WebContents是Chromium里面非常核心的一个对象,它本身代表着一个tab页面。通过WebContents我们几乎可以涉及到所有重要的浏览器对象概念。WebContents如此重要,我们做Chromium开发的时候势必会跟它有很多交互。但是我们又不能对WebContents代码的本身做太多的修改。因为随着业务的发展,会导致WebContents的逻辑越来越多,最后代码膨胀,难以维护。所以需要设计一套机制来对WebContents的逻辑进行解耦。

实际上Chromium已经设计好了一套很好的Tabhelper机制来解耦WebContents。我们可以做到完全不用改的WebContents代码而扩展WebContents的逻辑。介绍Tabhelper机制之前我先介绍一下SupportsUserData机制,下面是简化的代码例子:

class SupportsUserData {
public:
  class Data {
  public:
    virtual ~Data() {}
  };

  SupportsUserData() {}
  SupportsUserData(const SupportsUserData &) = delete;
  void operator=(const SupportsUserData &) = delete;

  Data *GetUserData(const void *key) const {
    DataMap::const_iterator found = user_data_.find(key);
    if (found != user_data_.end())
      return found->second.get();
    return nullptr;
  }
  void SetUserData(const void *key, Data *data) {
    user_data_[key] = std::unique_ptr<Data>(data);
  }
  void RemoveUserData(const void *key) { user_data_.erase(key); }

protected:
  virtual ~SupportsUserData() {

    DataMap local_user_data;
    user_data_.swap(local_user_data);
  }

private:
  using DataMap = std::map<const void *, std::unique_ptr<Data>>;
  DataMap user_data_;
};

class Host : public SupportsUserData {
public:
  Host() {}
  ~Host() {}
};

class NameData : public SupportsUserData::Data {
public:
  NameData(const std::string &name) { name_ = name; }
  ~NameData() {}

  static NameData *Get(Host *host) {
    SupportsUserData::Data *data = host->GetUserData(&key_);
    return (data == nullptr) ? nullptr : static_cast<NameData *>(data);
  }

  static void *GetKey() { return static_cast<void *>(&key_); }

  void PrintData() { cout << "name: " << name_ << endl; }

private:
  std::string name_;
  static int key_;
};

int NameData::key_ = 0;

class AgeData : public SupportsUserData::Data {
public:
  AgeData(int age) { age_ = age; }
  ~AgeData() {}

  static AgeData *Get(Host *host) {
    SupportsUserData::Data *data = host->GetUserData(&key_);
    return (data == nullptr) ? nullptr : static_cast<AgeData *>(data);
  }

  static void *GetKey() { return static_cast<void *>(&key_); }

  void PrintData() { cout << "age: " << age_ << endl; }

private:
  int age_;
  static int key_;
};

int AgeData::key_ = 0;

int main() {
  Host h;

  NameData *name = new NameData("hello");
  h.SetUserData(NameData::GetKey(), name);
  NameData *n = NameData::Get(&h);
  n->PrintData();

  AgeData *age = new AgeData(25);
  h.SetUserData(AgeData::GetKey(), age);
  AgeData *a = AgeData::Get(&h);
  a->PrintData();

  return 0;
}

SupportsUserData机制就是不通过增加类的成员变量,把一种类型的实例绑定到另一个类的实例上。以上例子是我们把NameData、AgeData这种类型的实例动态的绑定到Host类的实例上。这样做的好处一是我们不用修改Host类的代码可以动态扩展Host类的功能,具有扩展性,二是相比类变量会绑定到类的所有实例上,而SupportsUserData机制可以让我们有选择的绑到到类的某些实例上,更具有伸缩性。

Tabhelper就是基于SupportsUserData实现的。因为WebContents继承自base::SupportsUserData,所以它支持别的类的实例动态的绑定到一个WebContents实例上。通常一个Tabhelper继承自WebContentsObserver和WebContentsUserData,而WebContentsUserData继承自base::SupportsUserData::Data。通过继承自WebContentsObserver,它可以获得WebContents各种状态和事件。

当一个WebContents在CreateTargetContents里面被创建的时候,会在TabHelpers::AttachTabHelpers里面绑定上很多功能的TabHelpers。我们以页面上的查找框功能FindTabHelper为例子,FindTabHelper就是在这个时候被绑定到一个WebContents上面,并且TabHelper的生命周期归对应的WebContents管理。

除了这种在WebContents创建的时候就绑定上TabHelper,其实可以在WebContents的生命周期内通过CreateForWebContents随时绑定上一个TabHelper。只要有WebContents对象,我们随时可以通过FromWebContents获取到对应的TabHelper。

参考:https://chromium.googlesource.com/chromium/src/+/lkcr/docs/tab_helpers.md