blob: 4dff2066ad44b192d99c65042e157d1afa299a99 [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/script/mozjs-45/wrapper_private.h"
#include "base/hash_tables.h"
#include "cobalt/script/mozjs-45/mozjs_global_environment.h"
#include "cobalt/script/mozjs-45/proxy_handler.h"
#include "cobalt/script/mozjs-45/referenced_object_map.h"
#include "third_party/mozjs-45/js/src/jsapi.h"
#include "third_party/mozjs-45/js/src/jsfun.h"
#include "third_party/mozjs-45/js/src/proxy/Proxy.h"
#include "third_party/mozjs-45/js/src/vm/NativeObject.h"
namespace cobalt {
namespace script {
namespace mozjs {
void Tracer::Trace(Wrappable* wrappable) {
// Clearly, a null wrappable could not possibly reference any other
// wrappables.
if (!wrappable) {
return;
}
// Unfortunately, |JSTracer| will only supply us with a |JSRuntime|,
// rather than a |JSContext|. Fortunately, Cobalt will only create one
// global environment per runtime, so we can still safely get back to our
// context, and thus our global environment.
JSContext* context = NULL;
JS_ContextIterator(js_tracer_->runtime(), &context);
DCHECK(context);
MozjsGlobalEnvironment* global_environment =
MozjsGlobalEnvironment::GetFromContext(context);
DCHECK(global_environment);
// Clearly, if we have already visited this wrappable during the current
// tracing session, there is no need to visit it again. We rely on
// |JS_SetGCCallback| in the |MozjsEngine| to properly manage clearing
// |visited_wrappables_| in between GC sessions.
base::hash_set<Wrappable*>* visited_wrappables =
global_environment->visited_wrappables();
DCHECK(visited_wrappables);
if (!visited_wrappables->insert(wrappable).second) {
return;
}
// There are now two cases left to handle. Since we cannot create the
// wrapper while tracing due to internal SpiderMonkey restrictions, we will
// instead directly call |TraceMembers| here if the wrapper does not exist.
// In the case where the wrapper already does exist, we will pass the
// wrapper to |JS_CallObjectTracer|, and rely on SpiderMonkey to begin
// another |WrapperPrivate::Trace| on that wrapper.
WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
if (!wrapper_factory->HasWrapperProxy(wrappable)) {
frontier_.push_back(wrappable);
} else {
JSObject* proxy_object = wrapper_factory->GetWrapperProxy(wrappable);
JSObject* target = js::GetProxyTargetObject(proxy_object);
WrapperPrivate* wrapper_private =
static_cast<WrapperPrivate*>(JS_GetPrivate(target));
DCHECK(wrapper_private->context_ == context);
DCHECK(wrapper_private->wrapper_proxy_);
JS_CallObjectTracer(js_tracer_, &wrapper_private->wrapper_proxy_,
"WrapperPrivate::TraceWrappable");
}
DCHECK(JS_ContextIterator(js_tracer_->runtime(), &context) == NULL);
}
void Tracer::TraceFrom(Wrappable* wrappable) {
DCHECK(frontier_.empty());
frontier_.push_back(wrappable);
while (!frontier_.empty()) {
Wrappable* wrappable = frontier_.back();
frontier_.pop_back();
wrappable->TraceMembers(this);
}
}
// static
void WrapperPrivate::AddPrivateData(JSContext* context,
JS::HandleObject wrapper_proxy,
const scoped_refptr<Wrappable>& wrappable) {
DCHECK(js::IsProxy(wrapper_proxy));
WrapperPrivate* private_data =
new WrapperPrivate(context, wrappable, wrapper_proxy);
JS::RootedObject target_object(context,
js::GetProxyTargetObject(wrapper_proxy));
JS_SetPrivate(target_object, private_data);
DCHECK_EQ(JS_GetPrivate(target_object), private_data);
}
// static
bool WrapperPrivate::HasWrapperPrivate(JSContext* context,
JS::HandleObject object) {
if (js::IsProxy(object)) {
JS::RootedObject target_object(context, js::GetProxyTargetObject(object));
return WrapperPrivate::HasWrapperPrivate(context, target_object);
}
DCHECK(object->isNative());
return object->as<js::NativeObject>().hasPrivate();
}
// static
WrapperPrivate* WrapperPrivate::GetFromWrappable(
const scoped_refptr<Wrappable>& wrappable, JSContext* context,
WrapperFactory* wrapper_factory) {
JS::RootedObject wrapper_proxy(context,
wrapper_factory->GetWrapperProxy(wrappable));
WrapperPrivate* private_data = GetFromProxyObject(context, wrapper_proxy);
DCHECK(private_data);
DCHECK_EQ(private_data->wrappable_, wrappable);
return private_data;
}
// static
WrapperPrivate* WrapperPrivate::GetFromWrapperObject(JS::HandleObject wrapper) {
DCHECK(!js::IsProxy(wrapper));
if (!(JS_GetClass(wrapper)->flags & JSCLASS_HAS_PRIVATE)) {
return NULL;
}
WrapperPrivate* private_data =
static_cast<WrapperPrivate*>(JS_GetPrivate(wrapper));
DCHECK(private_data);
return private_data;
}
// static
WrapperPrivate* WrapperPrivate::GetFromProxyObject(
JSContext* context, JS::HandleObject proxy_object) {
DCHECK(js::IsProxy(proxy_object));
JS::RootedObject target(context, js::GetProxyTargetObject(proxy_object));
return GetFromWrapperObject(target);
}
// static
WrapperPrivate* WrapperPrivate::GetFromObject(JSContext* context,
JS::HandleObject object) {
if (js::IsProxy(object)) {
return GetFromProxyObject(context, object);
} else {
return GetFromWrapperObject(object);
}
}
// static
void WrapperPrivate::Finalizer(JSFreeOp* /* free_op */, JSObject* object) {
WrapperPrivate* wrapper_private =
reinterpret_cast<WrapperPrivate*>(JS_GetPrivate(object));
DCHECK(wrapper_private);
delete wrapper_private;
}
// static
void WrapperPrivate::Trace(JSTracer* trace, JSObject* object) {
WrapperPrivate* wrapper_private =
reinterpret_cast<WrapperPrivate*>(JS_GetPrivate(object));
// Verify that this trace function is called for the object (rather than the
// proxy object).
DCHECK(!js::IsProxy(object));
// The GC could run on this object before we've had a chance to set its
// private data, so we must handle the case where JS_GetPrivate returns NULL.
if (wrapper_private) {
// Verify that WrapperPrivate::wrapper_proxy_'s target object is this
// object.
DCHECK_EQ(object,
js::GetProxyTargetObject(wrapper_private->wrapper_proxy_));
// The wrapper's proxy object will keep the wrapper object alive, but the
// reverse is not true, so we must trace it explicitly.
JS_CallObjectTracer(trace, &wrapper_private->wrapper_proxy_,
"WrapperPrivate::Trace");
MozjsGlobalEnvironment* global_environment =
MozjsGlobalEnvironment::GetFromContext(wrapper_private->context_);
intptr_t key = ReferencedObjectMap::GetKeyForWrappable(
wrapper_private->wrappable_.get());
global_environment->referenced_objects()->TraceReferencedObjects(trace,
key);
Tracer tracer(trace);
tracer.TraceFrom(wrapper_private->wrappable_);
}
}
WrapperPrivate::WrapperPrivate(JSContext* context,
const scoped_refptr<Wrappable>& wrappable,
JS::HandleObject wrapper_proxy)
: context_(context), wrappable_(wrappable), wrapper_proxy_(wrapper_proxy) {
DCHECK(js::IsProxy(wrapper_proxy));
}
WrapperPrivate::~WrapperPrivate() {
wrapper_proxy_ = NULL;
}
} // namespace mozjs
} // namespace script
} // namespace cobalt