1/*
2 * Copyright (C) 2018 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "JSCWeakValue.h"
22
23#include "APICast.h"
24#include "JSCContextPrivate.h"
25#include "JSCInlines.h"
26#include "JSCValuePrivate.h"
27#include "JSWeakValue.h"
28#include "WeakHandleOwner.h"
29#include <wtf/glib/GRefPtr.h>
30#include <wtf/glib/GUniquePtr.h>
31#include <wtf/glib/WTFGType.h>
32
33/**
34 * SECTION: JSCWeakValue
35 * @short_description: JavaScript weak value
36 * @title: JSCWeakValue
37 * @see_also: JSCValue
38 *
39 * JSCWeakValue represents a weak reference to a value in a #JSCContext. It can be used
40 * to keep a reference to a JavaScript value without protecting it from being garbage
41 * collected and without referencing the #JSCContext either.
42 */
43
44enum {
45 PROP_0,
46
47 PROP_VALUE,
48};
49
50enum {
51 CLEARED,
52
53 LAST_SIGNAL
54};
55
56struct _JSCWeakValuePrivate {
57 JSC::Weak<JSC::JSGlobalObject> globalObject;
58 RefPtr<JSC::JSLock> lock;
59 JSC::JSWeakValue weakValueRef;
60};
61
62static guint signals[LAST_SIGNAL] = { 0, };
63
64WEBKIT_DEFINE_TYPE(JSCWeakValue, jsc_weak_value, G_TYPE_OBJECT)
65
66static void jscWeakValueClear(JSCWeakValue* weakValue)
67{
68 JSCWeakValuePrivate* priv = weakValue->priv;
69 priv->globalObject.clear();
70 priv->weakValueRef.clear();
71}
72
73class JSCWeakValueHandleOwner : public JSC::WeakHandleOwner {
74public:
75 void finalize(JSC::Handle<JSC::Unknown>, void* context) override
76 {
77 auto* weakValue = JSC_WEAK_VALUE(context);
78 jscWeakValueClear(weakValue);
79 g_signal_emit(weakValue, signals[CLEARED], 0, nullptr);
80 }
81};
82
83static JSCWeakValueHandleOwner& weakValueHandleOwner()
84{
85 static NeverDestroyed<JSCWeakValueHandleOwner> jscWeakValueHandleOwner;
86 return jscWeakValueHandleOwner;
87}
88
89static void jscWeakValueInitialize(JSCWeakValue* weakValue, JSCValue* value)
90{
91 JSCWeakValuePrivate* priv = weakValue->priv;
92 auto* jsContext = jscContextGetJSContext(jsc_value_get_context(value));
93 JSC::ExecState* exec = toJS(jsContext);
94 JSC::JSGlobalObject* globalObject = exec->lexicalGlobalObject();
95 auto& owner = weakValueHandleOwner();
96 JSC::Weak<JSC::JSGlobalObject> weak(globalObject, &owner, weakValue);
97 priv->globalObject.swap(weak);
98 priv->lock = &exec->vm().apiLock();
99
100 JSC::JSValue jsValue = toJS(exec, jscValueGetJSValue(value));
101 if (jsValue.isObject())
102 priv->weakValueRef.setObject(JSC::jsCast<JSC::JSObject*>(jsValue.asCell()), owner, weakValue);
103 else if (jsValue.isString())
104 priv->weakValueRef.setString(JSC::jsCast<JSC::JSString*>(jsValue.asCell()), owner, weakValue);
105 else
106 priv->weakValueRef.setPrimitive(jsValue);
107}
108
109static void jscWeakValueSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec)
110{
111 switch (propID) {
112 case PROP_VALUE:
113 jscWeakValueInitialize(JSC_WEAK_VALUE(object), JSC_VALUE(g_value_get_object(value)));
114 break;
115 default:
116 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
117 }
118}
119
120static void jscWeakValueDispose(GObject* object)
121{
122 JSCWeakValue* weakValue = JSC_WEAK_VALUE(object);
123 jscWeakValueClear(weakValue);
124
125 G_OBJECT_CLASS(jsc_weak_value_parent_class)->dispose(object);
126}
127
128static void jsc_weak_value_class_init(JSCWeakValueClass* klass)
129{
130 GObjectClass* objClass = G_OBJECT_CLASS(klass);
131 objClass->set_property = jscWeakValueSetProperty;
132 objClass->dispose = jscWeakValueDispose;
133
134 /**
135 * JSCWeakValue:value:
136 *
137 * The #JSCValue referencing the JavaScript value.
138 */
139 g_object_class_install_property(objClass,
140 PROP_VALUE,
141 g_param_spec_object(
142 "value",
143 "JSCValue",
144 "JSC Value",
145 JSC_TYPE_VALUE,
146 static_cast<GParamFlags>(WEBKIT_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
147
148 /**
149 * JSCWeakValue::cleared:
150 * @weak_value: the #JSCWeakValue
151 *
152 * This signal is emitted when the JavaScript value is destroyed.
153 */
154 signals[CLEARED] = g_signal_new(
155 "cleared",
156 G_TYPE_FROM_CLASS(klass),
157 G_SIGNAL_RUN_LAST,
158 0, nullptr, nullptr,
159 g_cclosure_marshal_generic,
160 G_TYPE_NONE, 0,
161 G_TYPE_NONE);
162}
163
164/**
165 * jsc_weak_value_new:
166 * @value: a #JSCValue
167 *
168 * Create a new #JSCWeakValue for the JavaScript value referenced by @value.
169 *
170 * Returns: (transfer full): a new #JSCWeakValue
171 */
172JSCWeakValue* jsc_weak_value_new(JSCValue* value)
173{
174 g_return_val_if_fail(JSC_IS_VALUE(value), nullptr);
175
176 return JSC_WEAK_VALUE(g_object_new(JSC_TYPE_WEAK_VALUE, "value", value, nullptr));
177}
178
179/**
180 * jsc_weak_value_get_value:
181 * @weak_value: a #JSCWeakValue
182 *
183 * Get a #JSCValue referencing the JavaScript value of @weak_value.
184 *
185 * Returns: (transfer full): a new #JSCValue or %NULL if @weak_value was cleared.
186 */
187JSCValue* jsc_weak_value_get_value(JSCWeakValue* weakValue)
188{
189 g_return_val_if_fail(JSC_IS_WEAK_VALUE(weakValue), nullptr);
190
191 JSCWeakValuePrivate* priv = weakValue->priv;
192 WTF::Locker<JSC::JSLock> locker(priv->lock.get());
193 JSC::VM* vm = priv->lock->vm();
194 if (!vm)
195 return nullptr;
196
197 JSC::JSLockHolder apiLocker(vm);
198 if (!priv->globalObject || priv->weakValueRef.isClear())
199 return nullptr;
200
201 JSC::JSValue value;
202 if (priv->weakValueRef.isPrimitive())
203 value = priv->weakValueRef.primitive();
204 else if (priv->weakValueRef.isString())
205 value = priv->weakValueRef.string();
206 else
207 value = priv->weakValueRef.object();
208
209 JSC::ExecState* exec = priv->globalObject->globalExec();
210 GRefPtr<JSCContext> context = jscContextGetOrCreate(toGlobalRef(exec));
211 return jscContextGetOrCreateValue(context.get(), toRef(exec, value)).leakRef();
212}
213