Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. 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
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #ifndef INCLUDED_SCRIPTINTERFACE_OBJECT
19 : #define INCLUDED_SCRIPTINTERFACE_OBJECT
20 :
21 : #include "ScriptConversions.h"
22 : #include "ScriptRequest.h"
23 : #include "ScriptTypes.h"
24 :
25 : #include "ps/CLogger.h"
26 :
27 : /**
28 : * Wraps SM APIs for manipulating JS objects.
29 : */
30 :
31 : namespace Script
32 : {
33 : /**
34 : * Get the named property on the given object.
35 : */
36 : template<typename PropType>
37 203 : inline bool GetProperty(const ScriptRequest& rq, JS::HandleValue obj, PropType name, JS::MutableHandleValue out)
38 : {
39 203 : if (!obj.isObject())
40 0 : return false;
41 406 : JS::RootedObject object(rq.cx, &obj.toObject());
42 : if constexpr (std::is_same_v<int, PropType>)
43 : {
44 12 : JS::RootedId id(rq.cx, INT_TO_JSID(name));
45 6 : return JS_GetPropertyById(rq.cx, object, id, out);
46 : }
47 : else if constexpr (std::is_same_v<const char*, PropType>)
48 197 : return JS_GetProperty(rq.cx, object, name, out);
49 : else
50 : return JS_GetUCProperty(rq.cx, object, name, wcslen(name), out);
51 : }
52 :
53 : template<typename T, typename PropType>
54 82 : inline bool GetProperty(const ScriptRequest& rq, JS::HandleValue obj, PropType name, T& out)
55 : {
56 164 : JS::RootedValue val(rq.cx);
57 82 : if (!GetProperty<PropType>(rq, obj, name, &val))
58 0 : return false;
59 82 : return FromJSVal(rq, val, out);
60 : }
61 : inline bool GetProperty(const ScriptRequest& rq, JS::HandleValue obj, const char* name, JS::MutableHandleObject out)
62 : {
63 : JS::RootedValue val(rq.cx, JS::ObjectValue(*out.get()));
64 : if (!GetProperty(rq, obj, name, &val))
65 : return false;
66 : out.set(val.toObjectOrNull());
67 : return true;
68 : }
69 :
70 : template<typename T>
71 0 : inline bool GetPropertyInt(const ScriptRequest& rq, JS::HandleValue obj, int name, T& out)
72 : {
73 0 : return GetProperty(rq, obj, name, out);
74 : }
75 6 : inline bool GetPropertyInt(const ScriptRequest& rq, JS::HandleValue obj, int name, JS::MutableHandleValue out)
76 : {
77 6 : return GetProperty(rq, obj, name, out);
78 : }
79 : /**
80 : * Check the named property has been defined on the given object.
81 : */
82 150 : inline bool HasProperty(const ScriptRequest& rq, JS::HandleValue obj, const char* name)
83 : {
84 150 : if (!obj.isObject())
85 0 : return false;
86 300 : JS::RootedObject object(rq.cx, &obj.toObject());
87 :
88 : bool found;
89 150 : if (!JS_HasProperty(rq.cx, object, name, &found))
90 0 : return false;
91 150 : return found;
92 : }
93 :
94 : /**
95 : * Set the named property on the given object.
96 : */
97 : template<typename PropType>
98 99 : inline bool SetProperty(const ScriptRequest& rq, JS::HandleValue obj, PropType name, JS::HandleValue value, bool constant = false, bool enumerable = true)
99 : {
100 99 : uint attrs = 0;
101 99 : if (constant)
102 98 : attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
103 99 : if (enumerable)
104 1 : attrs |= JSPROP_ENUMERATE;
105 :
106 99 : if (!obj.isObject())
107 0 : return false;
108 198 : JS::RootedObject object(rq.cx, &obj.toObject());
109 : if constexpr (std::is_same_v<int, PropType>)
110 : {
111 2 : JS::RootedId id(rq.cx, INT_TO_JSID(name));
112 1 : return JS_DefinePropertyById(rq.cx, object, id, value, attrs);
113 : }
114 : else if constexpr (std::is_same_v<const char*, PropType>)
115 98 : return JS_DefineProperty(rq.cx, object, name, value, attrs);
116 : else
117 : return JS_DefineUCProperty(rq.cx, object, name, value, attrs);
118 : }
119 :
120 : template<typename T, typename PropType>
121 99 : inline bool SetProperty(const ScriptRequest& rq, JS::HandleValue obj, PropType name, const T& value, bool constant = false, bool enumerable = true)
122 : {
123 198 : JS::RootedValue val(rq.cx);
124 99 : Script::ToJSVal(rq, &val, value);
125 198 : return SetProperty<PropType>(rq, obj, name, val, constant, enumerable);
126 : }
127 :
128 : template<typename T>
129 1 : inline bool SetPropertyInt(const ScriptRequest& rq, JS::HandleValue obj, int name, const T& value, bool constant = false, bool enumerable = true)
130 : {
131 1 : return SetProperty<T, int>(rq, obj, name, value, constant, enumerable);
132 : }
133 :
134 : template<typename T>
135 65 : inline bool GetObjectClassName(const ScriptRequest& rq, JS::HandleObject obj, T& name)
136 : {
137 130 : JS::RootedValue constructor(rq.cx, JS::ObjectOrNullValue(JS_GetConstructor(rq.cx, obj)));
138 130 : return constructor.isObject() && Script::HasProperty(rq, constructor, "name") && Script::GetProperty(rq, constructor, "name", name);
139 : }
140 :
141 : /**
142 : * Get the name of the object's class. Note that inheritance may lead to unexpected results.
143 : */
144 : template<typename T>
145 1 : inline bool GetObjectClassName(const ScriptRequest& rq, JS::HandleValue val, T& name)
146 : {
147 2 : JS::RootedObject obj(rq.cx, val.toObjectOrNull());
148 1 : if (!obj)
149 0 : return false;
150 1 : return GetObjectClassName(rq, obj, name);
151 : }
152 :
153 294 : inline bool FreezeObject(const ScriptRequest& rq, JS::HandleValue objVal, bool deep)
154 : {
155 294 : if (!objVal.isObject())
156 0 : return false;
157 :
158 588 : JS::RootedObject obj(rq.cx, &objVal.toObject());
159 :
160 294 : if (deep)
161 294 : return JS_DeepFreezeObject(rq.cx, obj);
162 : else
163 0 : return JS_FreezeObject(rq.cx, obj);
164 : }
165 :
166 : /**
167 : * Returns all properties of the object, both own properties and inherited.
168 : * This is essentially equivalent to calling Object.getOwnPropertyNames()
169 : * and recursing up the prototype chain.
170 : * NB: this does not return properties with symbol or numeric keys, as that would
171 : * require a variant in the vector, and it's not useful for now.
172 : * @param enumerableOnly - only return enumerable properties.
173 : */
174 79 : inline bool EnumeratePropertyNames(const ScriptRequest& rq, JS::HandleValue objVal, bool enumerableOnly, std::vector<std::string>& out)
175 : {
176 79 : if (!objVal.isObjectOrNull())
177 : {
178 0 : LOGERROR("EnumeratePropertyNames expected object type!");
179 0 : return false;
180 : }
181 :
182 158 : JS::RootedObject obj(rq.cx, &objVal.toObject());
183 158 : JS::RootedIdVector props(rq.cx);
184 : // This recurses up the prototype chain on its own.
185 79 : if (!js::GetPropertyKeys(rq.cx, obj, enumerableOnly? 0 : JSITER_HIDDEN, &props))
186 0 : return false;
187 :
188 79 : out.reserve(out.size() + props.length());
189 1150 : for (size_t i = 0; i < props.length(); ++i)
190 : {
191 2142 : JS::RootedId id(rq.cx, props[i]);
192 2142 : JS::RootedValue val(rq.cx);
193 1071 : if (!JS_IdToValue(rq.cx, id, &val))
194 0 : return false;
195 :
196 : // Ignore integer properties for now.
197 : // TODO: is this actually a thing in ECMAScript 6?
198 1071 : if (!val.isString())
199 0 : continue;
200 :
201 2142 : std::string propName;
202 1071 : if (!FromJSVal(rq, val, propName))
203 0 : return false;
204 :
205 1071 : out.emplace_back(std::move(propName));
206 : }
207 :
208 79 : return true;
209 : }
210 :
211 : /**
212 : * Create a plain object (i.e. {}). If it fails, returns undefined.
213 : */
214 3 : inline JS::Value CreateObject(const ScriptRequest& rq)
215 : {
216 6 : JS::RootedObject obj(rq.cx, JS_NewPlainObject(rq.cx));
217 3 : if (!obj)
218 0 : return JS::UndefinedValue();
219 3 : return JS::ObjectValue(*obj.get());
220 : }
221 :
222 3 : inline bool CreateObject(const ScriptRequest& rq, JS::MutableHandleValue objectValue)
223 : {
224 3 : objectValue.set(CreateObject(rq));
225 3 : return !objectValue.isNullOrUndefined();
226 : }
227 :
228 : /**
229 : * Sets the given value to a new plain JS::Object, converts the arguments to JS::Values and sets them as properties.
230 : * This is static so that callers like ToJSVal can use it with the JSContext directly instead of having to obtain the instance using GetScriptInterfaceAndCBData.
231 : * Can throw an exception.
232 : */
233 : template<typename T, typename... Args>
234 0 : inline bool CreateObject(const ScriptRequest& rq, JS::MutableHandleValue objectValue, const char* propertyName, const T& propertyValue, Args const&... args)
235 : {
236 0 : JS::RootedValue val(rq.cx);
237 0 : ToJSVal(rq, &val, propertyValue);
238 0 : return CreateObject(rq, objectValue, args...) && SetProperty(rq, objectValue, propertyName, val, false, true);
239 : }
240 :
241 : /**
242 : * Sets the given value to a new JS object or Null Value in case of out-of-memory.
243 : */
244 1 : inline bool CreateArray(const ScriptRequest& rq, JS::MutableHandleValue objectValue, size_t length = 0)
245 : {
246 1 : objectValue.setObjectOrNull(JS::NewArrayObject(rq.cx, length));
247 1 : return !objectValue.isNullOrUndefined();
248 : }
249 :
250 : } // namespace Script
251 :
252 : #endif // INCLUDED_SCRIPTINTERFACE_Object
|