|
| 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 "Weak.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 | + |
| 44 | +enum { |
| 45 | + PROP_0, |
| 46 | + |
| 47 | + PROP_VALUE, |
| 48 | +}; |
| 49 | + |
| 50 | +enum { |
| 51 | + CLEARED, |
| 52 | + |
| 53 | + LAST_SIGNAL |
| 54 | +}; |
| 55 | + |
| 56 | +// FIXME: move this to its own file to be shared by GLib and Objc APIs. |
| 57 | +class WeakValueRef { |
| 58 | +public: |
| 59 | + ~WeakValueRef() |
| 60 | + { |
| 61 | + clear(); |
| 62 | + } |
| 63 | + |
| 64 | + void clear() |
| 65 | + { |
| 66 | + switch (m_tag) { |
| 67 | + case WeakTypeTag::NotSet: |
| 68 | + return; |
| 69 | + case WeakTypeTag::Primitive: |
| 70 | + u.m_primitive = JSC::JSValue(); |
| 71 | + return; |
| 72 | + case WeakTypeTag::Object: |
| 73 | + u.m_object.clear(); |
| 74 | + return; |
| 75 | + case WeakTypeTag::String: |
| 76 | + u.m_string.clear(); |
| 77 | + return; |
| 78 | + } |
| 79 | + RELEASE_ASSERT_NOT_REACHED(); |
| 80 | + } |
| 81 | + |
| 82 | + bool isClear() const |
| 83 | + { |
| 84 | + switch (m_tag) { |
| 85 | + case WeakTypeTag::NotSet: |
| 86 | + return true; |
| 87 | + case WeakTypeTag::Primitive: |
| 88 | + return !u.m_primitive; |
| 89 | + case WeakTypeTag::Object: |
| 90 | + return !u.m_object; |
| 91 | + case WeakTypeTag::String: |
| 92 | + return !u.m_string; |
| 93 | + } |
| 94 | + RELEASE_ASSERT_NOT_REACHED(); |
| 95 | + } |
| 96 | + |
| 97 | + bool isSet() const { return m_tag != WeakTypeTag::NotSet; } |
| 98 | + bool isPrimitive() const { return m_tag == WeakTypeTag::Primitive; } |
| 99 | + bool isObject() const { return m_tag == WeakTypeTag::Object; } |
| 100 | + bool isString() const { return m_tag == WeakTypeTag::String; } |
| 101 | + |
| 102 | + void setPrimitive(JSC::JSValue primitive) |
| 103 | + { |
| 104 | + ASSERT(!isSet()); |
| 105 | + ASSERT(!u.m_primitive); |
| 106 | + ASSERT(primitive.isPrimitive()); |
| 107 | + m_tag = WeakTypeTag::Primitive; |
| 108 | + u.m_primitive = primitive; |
| 109 | + } |
| 110 | + |
| 111 | + void setObject(JSC::JSObject* object, JSC::WeakHandleOwner& owner, void* context) |
| 112 | + { |
| 113 | + ASSERT(!isSet()); |
| 114 | + ASSERT(!u.m_object); |
| 115 | + m_tag = WeakTypeTag::Object; |
| 116 | + JSC::Weak<JSC::JSObject> weak(object, &owner, context); |
| 117 | + u.m_object.swap(weak); |
| 118 | + } |
| 119 | + |
| 120 | + void setString(JSC::JSString* string, JSC::WeakHandleOwner& owner, void* context) |
| 121 | + { |
| 122 | + ASSERT(!isSet()); |
| 123 | + ASSERT(!u.m_object); |
| 124 | + m_tag = WeakTypeTag::String; |
| 125 | + JSC::Weak<JSC::JSString> weak(string, &owner, context); |
| 126 | + u.m_string.swap(weak); |
| 127 | + } |
| 128 | + |
| 129 | + JSC::JSObject* object() const |
| 130 | + { |
| 131 | + ASSERT(isObject()); |
| 132 | + return u.m_object.get(); |
| 133 | + } |
| 134 | + |
| 135 | + JSC::JSValue primitive() const |
| 136 | + { |
| 137 | + ASSERT(isPrimitive()); |
| 138 | + return u.m_primitive; |
| 139 | + } |
| 140 | + |
| 141 | + JSC::JSString* string() const |
| 142 | + { |
| 143 | + ASSERT(isString()); |
| 144 | + return u.m_string.get(); |
| 145 | + } |
| 146 | + |
| 147 | +private: |
| 148 | + enum class WeakTypeTag { NotSet, Primitive, Object, String }; |
| 149 | + |
| 150 | + WeakTypeTag m_tag { WeakTypeTag::NotSet }; |
| 151 | + |
| 152 | + union WeakValueUnion { |
| 153 | + public: |
| 154 | + WeakValueUnion() |
| 155 | + : m_primitive(JSC::JSValue()) |
| 156 | + { |
| 157 | + } |
| 158 | + |
| 159 | + ~WeakValueUnion() |
| 160 | + { |
| 161 | + ASSERT(!m_primitive); |
| 162 | + } |
| 163 | + |
| 164 | + JSC::JSValue m_primitive; |
| 165 | + JSC::Weak<JSC::JSObject> m_object; |
| 166 | + JSC::Weak<JSC::JSString> m_string; |
| 167 | + } u; |
| 168 | +}; |
| 169 | + |
| 170 | +struct _JSCWeakValuePrivate { |
| 171 | + JSC::Weak<JSC::JSGlobalObject> globalObject; |
| 172 | + RefPtr<JSC::JSLock> lock; |
| 173 | + WeakValueRef weakValueRef; |
| 174 | +}; |
| 175 | + |
| 176 | +static guint signals[LAST_SIGNAL] = { 0, }; |
| 177 | + |
| 178 | +WEBKIT_DEFINE_TYPE(JSCWeakValue, jsc_weak_value, G_TYPE_OBJECT) |
| 179 | + |
| 180 | +static void jscWeakValueClear(JSCWeakValue* weakValue) |
| 181 | +{ |
| 182 | + JSCWeakValuePrivate* priv = weakValue->priv; |
| 183 | + priv->globalObject.clear(); |
| 184 | + priv->weakValueRef.clear(); |
| 185 | +} |
| 186 | + |
| 187 | +class JSCWeakValueHandleOwner : public JSC::WeakHandleOwner { |
| 188 | +public: |
| 189 | + void finalize(JSC::Handle<JSC::Unknown>, void* context) override |
| 190 | + { |
| 191 | + auto* weakValue = JSC_WEAK_VALUE(context); |
| 192 | + jscWeakValueClear(weakValue); |
| 193 | + g_signal_emit(weakValue, signals[CLEARED], 0, nullptr); |
| 194 | + } |
| 195 | +}; |
| 196 | + |
| 197 | +static JSCWeakValueHandleOwner& weakValueHandleOwner() |
| 198 | +{ |
| 199 | + static NeverDestroyed<JSCWeakValueHandleOwner> jscWeakValueHandleOwner; |
| 200 | + return jscWeakValueHandleOwner; |
| 201 | +} |
| 202 | + |
| 203 | +static void jscWeakValueInitialize(JSCWeakValue* weakValue, JSCValue* value) |
| 204 | +{ |
| 205 | + JSCWeakValuePrivate* priv = weakValue->priv; |
| 206 | + auto* jsContext = jscContextGetJSContext(jsc_value_get_context(value)); |
| 207 | + JSC::ExecState* exec = toJS(jsContext); |
| 208 | + JSC::JSGlobalObject* globalObject = exec->lexicalGlobalObject(); |
| 209 | + auto& owner = weakValueHandleOwner(); |
| 210 | + JSC::Weak<JSC::JSGlobalObject> weak(globalObject, &owner, weakValue); |
| 211 | + priv->globalObject.swap(weak); |
| 212 | + priv->lock = &exec->vm().apiLock(); |
| 213 | + |
| 214 | + JSC::JSValue jsValue = toJS(exec, jscValueGetJSValue(value)); |
| 215 | + if (jsValue.isObject()) |
| 216 | + priv->weakValueRef.setObject(JSC::jsCast<JSC::JSObject*>(jsValue.asCell()), owner, weakValue); |
| 217 | + else if (jsValue.isString()) |
| 218 | + priv->weakValueRef.setString(JSC::jsCast<JSC::JSString*>(jsValue.asCell()), owner, weakValue); |
| 219 | + else |
| 220 | + priv->weakValueRef.setPrimitive(jsValue); |
| 221 | +} |
| 222 | + |
| 223 | +static void jscWeakValueSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec) |
| 224 | +{ |
| 225 | + switch (propID) { |
| 226 | + case PROP_VALUE: |
| 227 | + jscWeakValueInitialize(JSC_WEAK_VALUE(object), JSC_VALUE(g_value_get_object(value))); |
| 228 | + break; |
| 229 | + default: |
| 230 | + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +static void jscWeakValueDispose(GObject* object) |
| 235 | +{ |
| 236 | + JSCWeakValue* weakValue = JSC_WEAK_VALUE(object); |
| 237 | + jscWeakValueClear(weakValue); |
| 238 | + |
| 239 | + G_OBJECT_CLASS(jsc_weak_value_parent_class)->dispose(object); |
| 240 | +} |
| 241 | + |
| 242 | +static void jsc_weak_value_class_init(JSCWeakValueClass* klass) |
| 243 | +{ |
| 244 | + GObjectClass* objClass = G_OBJECT_CLASS(klass); |
| 245 | + objClass->set_property = jscWeakValueSetProperty; |
| 246 | + objClass->dispose = jscWeakValueDispose; |
| 247 | + |
| 248 | + /** |
| 249 | + * JSCWeakValue:value: |
| 250 | + * |
| 251 | + * The #JSCValue referencing the JavaScript value. |
| 252 | + */ |
| 253 | + g_object_class_install_property(objClass, |
| 254 | + PROP_VALUE, |
| 255 | + g_param_spec_object( |
| 256 | + "value", |
| 257 | + "JSCValue", |
| 258 | + "JSC Value", |
| 259 | + JSC_TYPE_VALUE, |
| 260 | + static_cast<GParamFlags>(WEBKIT_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY))); |
| 261 | + |
| 262 | + /** |
| 263 | + * JSCWeakValue::cleared: |
| 264 | + * @weak_value: the #JSCWeakValue |
| 265 | + * |
| 266 | + * This signal is emitted when the JavaScript value is destroyed. |
| 267 | + */ |
| 268 | + signals[CLEARED] = g_signal_new( |
| 269 | + "cleared", |
| 270 | + G_TYPE_FROM_CLASS(klass), |
| 271 | + G_SIGNAL_RUN_LAST, |
| 272 | + 0, nullptr, nullptr, |
| 273 | + g_cclosure_marshal_generic, |
| 274 | + G_TYPE_NONE, 0, |
| 275 | + G_TYPE_NONE); |
| 276 | +} |
| 277 | + |
| 278 | +/** |
| 279 | + * jsc_weak_value_new: |
| 280 | + * @value: a #JSCValue |
| 281 | + * |
| 282 | + * Create a new #JSCWeakValue for the JavaScript value referenced by @value. |
| 283 | + * |
| 284 | + * Returns: (transfer full): a new #JSCWeakValue |
| 285 | + */ |
| 286 | +JSCWeakValue* jsc_weak_value_new(JSCValue* value) |
| 287 | +{ |
| 288 | + g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| 289 | + |
| 290 | + return JSC_WEAK_VALUE(g_object_new(JSC_TYPE_WEAK_VALUE, "value", value, nullptr)); |
| 291 | +} |
| 292 | + |
| 293 | +/** |
| 294 | + * jsc_weak_value_get_value: |
| 295 | + * @weak_value: a #JSCWeakValue |
| 296 | + * |
| 297 | + * Get a #JSCValue referencing the JavaScript value of @weak_value. |
| 298 | + * |
| 299 | + * Returns: (transfer full): a new #JSCValue or %NULL if @weak_value was cleared. |
| 300 | + */ |
| 301 | +JSCValue* jsc_weak_value_get_value(JSCWeakValue* weakValue) |
| 302 | +{ |
| 303 | + g_return_val_if_fail(JSC_IS_WEAK_VALUE(weakValue), nullptr); |
| 304 | + |
| 305 | + JSCWeakValuePrivate* priv = weakValue->priv; |
| 306 | + WTF::Locker<JSC::JSLock> locker(priv->lock.get()); |
| 307 | + JSC::VM* vm = priv->lock->vm(); |
| 308 | + if (!vm) |
| 309 | + return nullptr; |
| 310 | + |
| 311 | + JSC::JSLockHolder apiLocker(vm); |
| 312 | + if (!priv->globalObject || priv->weakValueRef.isClear()) |
| 313 | + return nullptr; |
| 314 | + |
| 315 | + JSC::JSValue value; |
| 316 | + if (priv->weakValueRef.isPrimitive()) |
| 317 | + value = priv->weakValueRef.primitive(); |
| 318 | + else if (priv->weakValueRef.isString()) |
| 319 | + value = priv->weakValueRef.string(); |
| 320 | + else |
| 321 | + value = priv->weakValueRef.object(); |
| 322 | + |
| 323 | + JSC::ExecState* exec = priv->globalObject->globalExec(); |
| 324 | + GRefPtr<JSCContext> context = jscContextGetOrCreate(toGlobalRef(exec)); |
| 325 | + return jscContextGetOrCreateValue(context.get(), toRef(exec, value)).leakRef(); |
| 326 | +} |
0 commit comments