前段时间我们在实现CanTK-Runtime时,也曾在V8基础上模拟过浏览器的LocaleStorage功能,其实现非常简单:每个domain的数据使用的单独文件存储,因为同一时间只有一个游戏运行,所以文件操作只是放到了后台线程执行。但是Chrome里的实现就非常复杂了,它主要包括四部分:
- 0.根据IDL文件产生出来的代码,称为Binding代码(gen/blink/bindings/modules/v8/V8Storage.cpp)。这些代码是JS与C++之间的桥梁,JS调用C++时传递过来的参数是一个数组,它负责把参数拆解出来然后调用实际的函数,执行完成后把参数返回给JS。
static void setItemMethod(const v8::FunctionCallbackInfo<v8::Value>& info)
{
ExceptionState exceptionState(ExceptionState::ExecutionContext, "setItem", "Storage", info.Holder(), info.GetIsolate());
if (UNLIKELY(info.Length() < 2)) {
setMinimumArityTypeError(exceptionState, 2, info.Length());
exceptionState.throwIfNeeded();
return;
}
Storage* impl = V8Storage::toImpl(info.Holder());
V8StringResource<> key;
V8StringResource<> data;
{
key = info[0];
if (!key.prepare())
return;
data = info[1];
if (!data.prepare())
return;
}
impl->setItem(key, data, exceptionState);
if (exceptionState.hadException()) {
exceptionState.throwIfNeeded();
return;
}
}
- 1.与浏览器JS对接的模块(WebKit/Source/modules/storage)。Binding代码会调用到这里来,这里主要做些参数以及安全方面的检查,然后调用WebStorageArea的接口。
void StorageArea::setItem(const String& key, const String& value, ExceptionState& exceptionState, LocalFrame* frame)
{
if (!canAccessStorage(frame)) {
exceptionState.throwSecurityError("access is denied for this document.");
return;
}
WebStorageArea::Result result = WebStorageArea::ResultOK;
m_storageArea->setItem(key, value, frame->document()->url(), result);
if (result != WebStorageArea::ResultOK)
exceptionState.throwDOMException(QuotaExceededError, "Setting the value of '" + key + "' exceeded the quota.");
}
- 2.客户端代理(content/renderer/dom_storage/webstoragearea_impl.cc)。上面需要的WebStorageArea接口的实现是WebStorageAreaImpl在chrome里实现的(在WebKit之外)。让我有些惊讶的是,这些代码居然是放在renderer目录下的。后来想了一下,Storage与renderer没关系,但是这些代码是在render进程程执行的。每个标签都有一个Render进程,多个标签可能是同一个domain,也就是会存取同一个Storage,出于性能和共享考虑,所以把真正执行文件系统(数据库)的操作放在服务进程里了,这个模块是客户端的代理。
void WebStorageAreaImpl::setItem(
const WebString& key, const WebString& value, const WebURL& page_url,
WebStorageArea::Result& result) {
if (!cached_area_->SetItem(connection_id_, key, value, page_url))
result = ResultBlockedByQuota;
else
result = ResultOK;
}
bool DOMStorageCachedArea::SetItem(int connection_id,
const base::string16& key,
const base::string16& value,
const GURL& page_url) {
// A quick check to reject obviously overbudget items to avoid
// the priming the cache.
if (key.length() + value.length() > kPerStorageAreaQuota)
return false;
PrimeIfNeeded(connection_id);
base::NullableString16 unused;
if (!map_->SetItem(key, value, &unused))
return false;
// Ignore mutations to 'key' until OnSetItemComplete.
ignore_key_mutations_[key]++;
proxy_->SetItem(
connection_id, key, value, page_url,
base::Bind(&DOMStorageCachedArea::OnSetItemComplete,
weak_factory_.GetWeakPtr(), key));
return true;
}
- 3.服务器及数据库(content/browser/dom_storage/dom_storage_area.cc)。这个是在浏览器的主进程里执行的,数据库使用的Sqlite。
bool DOMStorageArea::SetItem(const base::string16& key,
const base::string16& value,
base::NullableString16* old_value) {
if (is_shutdown_)
return false;
InitialImportIfNeeded();
if (!map_->HasOneRef())
map_ = map_->DeepCopy();
bool success = map_->SetItem(key, value, old_value);
if (success && backing_ &&
(old_value->is_null() || old_value->string() != value)) {
CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
commit_batch->changed_values[key] = base::NullableString16(value, false);
}
return success;
}