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