1 /* Copyright (C) 2017 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 #include "precompiled.h"
19 
20 #include "ScriptInterface.h"
21 #include "ScriptRuntime.h"
22 #include "ScriptStats.h"
23 
24 #include "lib/debug.h"
25 #include "lib/utf8.h"
26 #include "ps/CLogger.h"
27 #include "ps/Filesystem.h"
28 #include "ps/Profile.h"
29 #include "ps/utf16string.h"
30 
31 #include <cassert>
32 #include <map>
33 
34 #define BOOST_MULTI_INDEX_DISABLE_SERIALIZATION
35 #include <boost/preprocessor/punctuation/comma_if.hpp>
36 #include <boost/preprocessor/repetition/repeat.hpp>
37 #include <boost/random/linear_congruential.hpp>
38 #include <boost/flyweight.hpp>
39 #include <boost/flyweight/key_value.hpp>
40 #include <boost/flyweight/no_locking.hpp>
41 #include <boost/flyweight/no_tracking.hpp>
42 
43 #include "valgrind.h"
44 
45 #include "scriptinterface/ScriptExtraHeaders.h"
46 
47 /**
48  * @file
49  * Abstractions of various SpiderMonkey features.
50  * Engine code should be using functions of these interfaces rather than
51  * directly accessing the underlying JS api.
52  */
53 
54 
55 struct ScriptInterface_impl
56 {
57 	ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& runtime);
58 	~ScriptInterface_impl();
59 	void Register(const char* name, JSNative fptr, uint nargs) const;
60 
61 	// Take care to keep this declaration before heap rooted members. Destructors of heap rooted
62 	// members have to be called before the runtime destructor.
63 	shared_ptr<ScriptRuntime> m_runtime;
64 
65 	JSContext* m_cx;
66 	JS::PersistentRootedObject m_glob; // global scope object
67 	JSCompartment* m_comp;
68 	boost::rand48* m_rng;
69 	JS::PersistentRootedObject m_nativeScope; // native function scope object
70 
71 	typedef std::map<ScriptInterface::CACHED_VAL, JS::PersistentRootedValue> ScriptValCache;
72 	ScriptValCache m_ScriptValCache;
73 };
74 
75 namespace
76 {
77 
78 JSClass global_class = {
79 	"global", JSCLASS_GLOBAL_FLAGS,
80 	nullptr, nullptr,
81 	nullptr, nullptr,
82 	nullptr, nullptr, nullptr,
83 	nullptr, nullptr, nullptr, nullptr,
84 	JS_GlobalObjectTraceHook
85 };
86 
ErrorReporter(JSContext * cx,const char * message,JSErrorReport * report)87 void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
88 {
89 
90 	std::stringstream msg;
91 	bool isWarning = JSREPORT_IS_WARNING(report->flags);
92 	msg << (isWarning ? "JavaScript warning: " : "JavaScript error: ");
93 	if (report->filename)
94 	{
95 		msg << report->filename;
96 		msg << " line " << report->lineno << "\n";
97 	}
98 
99 	msg << message;
100 
101 	// If there is an exception, then print its stack trace
102 	JS::RootedValue excn(cx);
103 	if (JS_GetPendingException(cx, &excn) && excn.isObject())
104 	{
105 		JS::RootedObject excnObj(cx, &excn.toObject());
106 		// TODO: this violates the docs ("The error reporter callback must not reenter the JSAPI.")
107 
108 		// Hide the exception from EvaluateScript
109 		JSExceptionState* excnState = JS_SaveExceptionState(cx);
110 		JS_ClearPendingException(cx);
111 
112 		JS::RootedValue rval(cx);
113 		const char dumpStack[] = "this.stack.trimRight().replace(/^/mg, '  ')"; // indent each line
114 		JS::CompileOptions opts(cx);
115 		if (JS::Evaluate(cx, excnObj, opts.setFileAndLine("(eval)", 1), dumpStack, ARRAY_SIZE(dumpStack)-1, &rval))
116 		{
117 			std::string stackTrace;
118 			if (ScriptInterface::FromJSVal(cx, rval, stackTrace))
119 				msg << "\n" << stackTrace;
120 
121 			JS_RestoreExceptionState(cx, excnState);
122 		}
123 		else
124 		{
125 			// Error got replaced by new exception from EvaluateScript
126 			JS_DropExceptionState(cx, excnState);
127 		}
128 	}
129 
130 	if (isWarning)
131 		LOGWARNING("%s", msg.str().c_str());
132 	else
133 		LOGERROR("%s", msg.str().c_str());
134 
135 	// When running under Valgrind, print more information in the error message
136 //	VALGRIND_PRINTF_BACKTRACE("->");
137 }
138 
139 // Functions in the global namespace:
140 
print(JSContext * cx,uint argc,JS::Value * vp)141 bool print(JSContext* cx, uint argc, JS::Value* vp)
142 {
143 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
144 	for (uint i = 0; i < args.length(); ++i)
145 	{
146 		std::wstring str;
147 		if (!ScriptInterface::FromJSVal(cx, args[i], str))
148 			return false;
149 		debug_printf("%s", utf8_from_wstring(str).c_str());
150 	}
151 	fflush(stdout);
152 	args.rval().setUndefined();
153 	return true;
154 }
155 
logmsg(JSContext * cx,uint argc,JS::Value * vp)156 bool logmsg(JSContext* cx, uint argc, JS::Value* vp)
157 {
158 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
159 	if (args.length() < 1)
160 	{
161 		args.rval().setUndefined();
162 		return true;
163 	}
164 
165 	std::wstring str;
166 	if (!ScriptInterface::FromJSVal(cx, args[0], str))
167 		return false;
168 	LOGMESSAGE("%s", utf8_from_wstring(str));
169 	args.rval().setUndefined();
170 	return true;
171 }
172 
warn(JSContext * cx,uint argc,JS::Value * vp)173 bool warn(JSContext* cx, uint argc, JS::Value* vp)
174 {
175 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
176 	if (args.length() < 1)
177 	{
178 		args.rval().setUndefined();
179 		return true;
180 	}
181 
182 	std::wstring str;
183 	if (!ScriptInterface::FromJSVal(cx, args[0], str))
184 		return false;
185 	LOGWARNING("%s", utf8_from_wstring(str));
186 	args.rval().setUndefined();
187 	return true;
188 }
189 
error(JSContext * cx,uint argc,JS::Value * vp)190 bool error(JSContext* cx, uint argc, JS::Value* vp)
191 {
192 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
193 	if (args.length() < 1)
194 	{
195 		args.rval().setUndefined();
196 		return true;
197 	}
198 
199 	std::wstring str;
200 	if (!ScriptInterface::FromJSVal(cx, args[0], str))
201 		return false;
202 	LOGERROR("%s", utf8_from_wstring(str));
203 	args.rval().setUndefined();
204 	return true;
205 }
206 
deepcopy(JSContext * cx,uint argc,JS::Value * vp)207 bool deepcopy(JSContext* cx, uint argc, JS::Value* vp)
208 {
209 	JSAutoRequest rq(cx);
210 
211 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
212 	if (args.length() < 1)
213 	{
214 		args.rval().setUndefined();
215 		return true;
216 	}
217 
218 	JS::RootedValue ret(cx);
219 	if (!JS_StructuredClone(cx, args[0], &ret, NULL, NULL))
220 		return false;
221 
222 	args.rval().set(ret);
223 	return true;
224 }
225 
deepfreeze(JSContext * cx,uint argc,JS::Value * vp)226 bool deepfreeze(JSContext* cx, uint argc, JS::Value* vp)
227 {
228 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
229 
230 	if (args.length() != 1 || !args.get(0).isObject())
231 	{
232 		JS_ReportError(cx, "deepfreeze requires exactly one object as an argument.");
233 		return false;
234 	}
235 
236 	ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->FreezeObject(args.get(0), true);
237 	args.rval().set(args.get(0));
238 	return true;
239 }
240 
ProfileStart(JSContext * cx,uint argc,JS::Value * vp)241 bool ProfileStart(JSContext* cx, uint argc, JS::Value* vp)
242 {
243 	const char* name = "(ProfileStart)";
244 
245 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
246 	if (args.length() >= 1)
247 	{
248 		std::string str;
249 		if (!ScriptInterface::FromJSVal(cx, args[0], str))
250 			return false;
251 
252 		typedef boost::flyweight<
253 			std::string,
254 			boost::flyweights::no_tracking,
255 			boost::flyweights::no_locking
256 		> StringFlyweight;
257 
258 		name = StringFlyweight(str).get().c_str();
259 	}
260 
261 	if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
262 		g_Profiler.StartScript(name);
263 
264 	g_Profiler2.RecordRegionEnter(name);
265 
266 	args.rval().setUndefined();
267 	return true;
268 }
269 
ProfileStop(JSContext * UNUSED (cx),uint UNUSED (argc),JS::Value * vp)270 bool ProfileStop(JSContext* UNUSED(cx), uint UNUSED(argc), JS::Value* vp)
271 {
272 	JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
273 	if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
274 		g_Profiler.Stop();
275 
276 	g_Profiler2.RecordRegionLeave();
277 
278 	rec.rval().setUndefined();
279 	return true;
280 }
281 
ProfileAttribute(JSContext * cx,uint argc,JS::Value * vp)282 bool ProfileAttribute(JSContext* cx, uint argc, JS::Value* vp)
283 {
284 	const char* name = "(ProfileAttribute)";
285 
286 	JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
287 	if (args.length() >= 1)
288 	{
289 		std::string str;
290 		if (!ScriptInterface::FromJSVal(cx, args[0], str))
291 			return false;
292 
293 		typedef boost::flyweight<
294 			std::string,
295 			boost::flyweights::no_tracking,
296 			boost::flyweights::no_locking
297 		> StringFlyweight;
298 
299 		name = StringFlyweight(str).get().c_str();
300 	}
301 
302 	g_Profiler2.RecordAttribute("%s", name);
303 
304 	args.rval().setUndefined();
305 	return true;
306 }
307 
308 // Math override functions:
309 
310 // boost::uniform_real is apparently buggy in Boost pre-1.47 - for integer generators
311 // it returns [min,max], not [min,max). The bug was fixed in 1.47.
312 // We need consistent behaviour, so manually implement the correct version:
generate_uniform_real(boost::rand48 & rng,double min,double max)313 static double generate_uniform_real(boost::rand48& rng, double min, double max)
314 {
315 	while (true)
316 	{
317 		double n = (double)(rng() - rng.min());
318 		double d = (double)(rng.max() - rng.min()) + 1.0;
319 		ENSURE(d > 0 && n >= 0 && n <= d);
320 		double r = n / d * (max - min) + min;
321 		if (r < max)
322 			return r;
323 	}
324 }
325 
Math_random(JSContext * cx,uint UNUSED (argc),JS::Value * vp)326 bool Math_random(JSContext* cx, uint UNUSED(argc), JS::Value* vp)
327 {
328 	JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
329 	double r;
330 	if (!ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->MathRandom(r))
331 		return false;
332 
333 	rec.rval().setNumber(r);
334 	return true;
335 }
336 
337 } // anonymous namespace
338 
MathRandom(double & nbr)339 bool ScriptInterface::MathRandom(double& nbr)
340 {
341 	if (m->m_rng == NULL)
342 		return false;
343 	nbr = generate_uniform_real(*(m->m_rng), 0.0, 1.0);
344 	return true;
345 }
346 
ScriptInterface_impl(const char * nativeScopeName,const shared_ptr<ScriptRuntime> & runtime)347 ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& runtime) :
348 	m_runtime(runtime), m_glob(runtime->m_rt), m_nativeScope(runtime->m_rt)
349 {
350 	bool ok;
351 
352 	m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE);
353 	ENSURE(m_cx);
354 
355 	JS_SetOffthreadIonCompilationEnabled(m_runtime->m_rt, true);
356 
357 	// For GC debugging:
358 	// JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ);
359 
360 	JS_SetContextPrivate(m_cx, NULL);
361 
362 	JS_SetErrorReporter(m_runtime->m_rt, ErrorReporter);
363 
364 	JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_ION_ENABLE, 1);
365 	JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1);
366 
367 	JS::RuntimeOptionsRef(m_cx).setExtraWarnings(1)
368 		.setWerror(0)
369 		.setVarObjFix(1)
370 		.setStrictMode(1);
371 
372 	JS::CompartmentOptions opt;
373 	opt.setVersion(JSVERSION_LATEST);
374 	// Keep JIT code during non-shrinking GCs. This brings a quite big performance improvement.
375 	opt.setPreserveJitCode(true);
376 
377 	JSAutoRequest rq(m_cx);
378 	JS::RootedObject globalRootedVal(m_cx, JS_NewGlobalObject(m_cx, &global_class, NULL, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt));
379 	m_comp = JS_EnterCompartment(m_cx, globalRootedVal);
380 	ok = JS_InitStandardClasses(m_cx, globalRootedVal);
381 	ENSURE(ok);
382 	m_glob = globalRootedVal.get();
383 
384 	JS_DefineProperty(m_cx, m_glob, "global", globalRootedVal, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
385 
386 	m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
387 
388 	JS_DefineFunction(m_cx, globalRootedVal, "print", ::print,        0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
389 	JS_DefineFunction(m_cx, globalRootedVal, "log",   ::logmsg,       1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
390 	JS_DefineFunction(m_cx, globalRootedVal, "warn",  ::warn,         1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
391 	JS_DefineFunction(m_cx, globalRootedVal, "error", ::error,        1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
392 	JS_DefineFunction(m_cx, globalRootedVal, "clone", ::deepcopy,        1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
393 	JS_DefineFunction(m_cx, globalRootedVal, "deepfreeze", ::deepfreeze, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
394 
395 	Register("ProfileStart", ::ProfileStart, 1);
396 	Register("ProfileStop", ::ProfileStop, 0);
397 	Register("ProfileAttribute", ::ProfileAttribute, 1);
398 
399 	runtime->RegisterContext(m_cx);
400 }
401 
~ScriptInterface_impl()402 ScriptInterface_impl::~ScriptInterface_impl()
403 {
404 	m_runtime->UnRegisterContext(m_cx);
405 	{
406 		JSAutoRequest rq(m_cx);
407 		JS_LeaveCompartment(m_cx, m_comp);
408 	}
409 	JS_DestroyContext(m_cx);
410 }
411 
Register(const char * name,JSNative fptr,uint nargs) const412 void ScriptInterface_impl::Register(const char* name, JSNative fptr, uint nargs) const
413 {
414 	JSAutoRequest rq(m_cx);
415 	JS::RootedObject nativeScope(m_cx, m_nativeScope);
416 	JS::RootedFunction func(m_cx, JS_DefineFunction(m_cx, nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT));
417 }
418 
ScriptInterface(const char * nativeScopeName,const char * debugName,const shared_ptr<ScriptRuntime> & runtime)419 ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr<ScriptRuntime>& runtime) :
420 	m(new ScriptInterface_impl(nativeScopeName, runtime))
421 {
422 	// Profiler stats table isn't thread-safe, so only enable this on the main thread
423 	if (ThreadUtil::IsMainThread())
424 	{
425 		if (g_ScriptStatsTable)
426 			g_ScriptStatsTable->Add(this, debugName);
427 	}
428 
429 	m_CxPrivate.pScriptInterface = this;
430 	JS_SetContextPrivate(m->m_cx, (void*)&m_CxPrivate);
431 }
432 
~ScriptInterface()433 ScriptInterface::~ScriptInterface()
434 {
435 	if (ThreadUtil::IsMainThread())
436 	{
437 		if (g_ScriptStatsTable)
438 			g_ScriptStatsTable->Remove(this);
439 	}
440 }
441 
SetCallbackData(void * pCBData)442 void ScriptInterface::SetCallbackData(void* pCBData)
443 {
444 	m_CxPrivate.pCBData = pCBData;
445 }
446 
GetScriptInterfaceAndCBData(JSContext * cx)447 ScriptInterface::CxPrivate* ScriptInterface::GetScriptInterfaceAndCBData(JSContext* cx)
448 {
449 	CxPrivate* pCxPrivate = (CxPrivate*)JS_GetContextPrivate(cx);
450 	return pCxPrivate;
451 }
452 
GetCachedValue(CACHED_VAL valueIdentifier) const453 JS::Value ScriptInterface::GetCachedValue(CACHED_VAL valueIdentifier) const
454 {
455 	std::map<ScriptInterface::CACHED_VAL, JS::PersistentRootedValue>::const_iterator it = m->m_ScriptValCache.find(valueIdentifier);
456 	ENSURE(it != m->m_ScriptValCache.end());
457 	return it->second.get();
458 }
459 
460 
LoadGlobalScripts()461 bool ScriptInterface::LoadGlobalScripts()
462 {
463 	// Ignore this failure in tests
464 	if (!g_VFS)
465 		return false;
466 
467 	// Load and execute *.js in the global scripts directory
468 	VfsPaths pathnames;
469 	vfs::GetPathnames(g_VFS, L"globalscripts/", L"*.js", pathnames);
470 	for (const VfsPath& path : pathnames)
471 		if (!LoadGlobalScriptFile(path))
472 		{
473 			LOGERROR("LoadGlobalScripts: Failed to load script %s", path.string8());
474 			return false;
475 		}
476 
477 	JSAutoRequest rq(m->m_cx);
478 	JS::RootedValue proto(m->m_cx);
479 	JS::RootedObject global(m->m_cx, m->m_glob);
480 	if (JS_GetProperty(m->m_cx, global, "Vector2Dprototype", &proto))
481 		m->m_ScriptValCache[CACHE_VECTOR2DPROTO].init(GetJSRuntime(), proto);
482 	if (JS_GetProperty(m->m_cx, global, "Vector3Dprototype", &proto))
483 		m->m_ScriptValCache[CACHE_VECTOR3DPROTO].init(GetJSRuntime(), proto);
484 	return true;
485 }
486 
ReplaceNondeterministicRNG(boost::rand48 & rng)487 bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng)
488 {
489 	JSAutoRequest rq(m->m_cx);
490 	JS::RootedValue math(m->m_cx);
491 	JS::RootedObject global(m->m_cx, m->m_glob);
492 	if (JS_GetProperty(m->m_cx, global, "Math", &math) && math.isObject())
493 	{
494 		JS::RootedObject mathObj(m->m_cx, &math.toObject());
495 		JS::RootedFunction random(m->m_cx, JS_DefineFunction(m->m_cx, mathObj, "random", Math_random, 0,
496 			JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT));
497 		if (random)
498 		{
499 			m->m_rng = &rng;
500 			return true;
501 		}
502 	}
503 
504 	LOGERROR("ReplaceNondeterministicRNG: failed to replace Math.random");
505 	return false;
506 }
507 
Register(const char * name,JSNative fptr,size_t nargs) const508 void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) const
509 {
510 	m->Register(name, fptr, (uint)nargs);
511 }
512 
GetContext() const513 JSContext* ScriptInterface::GetContext() const
514 {
515 	return m->m_cx;
516 }
517 
GetJSRuntime() const518 JSRuntime* ScriptInterface::GetJSRuntime() const
519 {
520 	return m->m_runtime->m_rt;
521 }
522 
GetRuntime() const523 shared_ptr<ScriptRuntime> ScriptInterface::GetRuntime() const
524 {
525 	return m->m_runtime;
526 }
527 
CallConstructor(JS::HandleValue ctor,JS::HandleValueArray argv,JS::MutableHandleValue out) const528 void ScriptInterface::CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const
529 {
530 	JSAutoRequest rq(m->m_cx);
531 	if (!ctor.isObject())
532 	{
533 		LOGERROR("CallConstructor: ctor is not an object");
534 		out.setNull();
535 		return;
536 	}
537 
538 	JS::RootedObject ctorObj(m->m_cx, &ctor.toObject());
539 	out.setObjectOrNull(JS_New(m->m_cx, ctorObj, argv));
540 }
541 
DefineCustomObjectType(JSClass * clasp,JSNative constructor,uint minArgs,JSPropertySpec * ps,JSFunctionSpec * fs,JSPropertySpec * static_ps,JSFunctionSpec * static_fs)542 void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs)
543 {
544 	JSAutoRequest rq(m->m_cx);
545 	std::string typeName = clasp->name;
546 
547 	if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end())
548 	{
549 		// This type already exists
550 		throw PSERROR_Scripting_DefineType_AlreadyExists();
551 	}
552 
553 	JS::RootedObject global(m->m_cx, m->m_glob);
554 	JS::RootedObject obj(m->m_cx, JS_InitClass(m->m_cx, global, JS::NullPtr(),
555 									clasp,
556 									constructor, minArgs,				// Constructor, min args
557 									ps, fs,								// Properties, methods
558 									static_ps, static_fs));				// Constructor properties, methods
559 
560 	if (obj == NULL)
561 		throw PSERROR_Scripting_DefineType_CreationFailed();
562 
563 	CustomType& type = m_CustomObjectTypes[typeName];
564 
565 	type.m_Prototype.init(m->m_cx, obj);
566 	type.m_Class = clasp;
567 	type.m_Constructor = constructor;
568 }
569 
CreateCustomObject(const std::string & typeName) const570 JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const
571 {
572 	std::map<std::string, CustomType>::const_iterator it = m_CustomObjectTypes.find(typeName);
573 
574 	if (it == m_CustomObjectTypes.end())
575 		throw PSERROR_Scripting_TypeDoesNotExist();
576 
577 	JS::RootedObject prototype(m->m_cx, it->second.m_Prototype.get());
578 	return JS_NewObjectWithGivenProto(m->m_cx, it->second.m_Class, prototype);
579 }
580 
CallFunction_(JS::HandleValue val,const char * name,JS::HandleValueArray argv,JS::MutableHandleValue ret) const581 bool ScriptInterface::CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const
582 {
583 	JSAutoRequest rq(m->m_cx);
584 	JS::RootedObject obj(m->m_cx);
585 	if (!JS_ValueToObject(m->m_cx, val, &obj) || !obj)
586 		return false;
587 
588 	// Check that the named function actually exists, to avoid ugly JS error reports
589 	// when calling an undefined value
590 	bool found;
591 	if (!JS_HasProperty(m->m_cx, obj, name, &found) || !found)
592 		return false;
593 
594 	bool ok = JS_CallFunctionName(m->m_cx, obj, name, argv, ret);
595 
596 	return ok;
597 }
598 
GetGlobalObject() const599 JS::Value ScriptInterface::GetGlobalObject() const
600 {
601 	JSAutoRequest rq(m->m_cx);
602 	return JS::ObjectValue(*JS::CurrentGlobalOrNull(m->m_cx));
603 }
604 
SetGlobal_(const char * name,JS::HandleValue value,bool replace)605 bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace)
606 {
607 	JSAutoRequest rq(m->m_cx);
608 	JS::RootedObject global(m->m_cx, m->m_glob);
609 	if (!replace)
610 	{
611 		bool found;
612 		if (!JS_HasProperty(m->m_cx, global, name, &found))
613 			return false;
614 		if (found)
615 		{
616 			JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name);
617 			return false;
618 		}
619 	}
620 
621 	bool ok = JS_DefineProperty(m->m_cx, global, name, value, JSPROP_ENUMERATE | JSPROP_READONLY
622  			| JSPROP_PERMANENT);
623 	return ok;
624 }
625 
SetProperty_(JS::HandleValue obj,const char * name,JS::HandleValue value,bool constant,bool enumerate) const626 bool ScriptInterface::SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) const
627 {
628 	JSAutoRequest rq(m->m_cx);
629 	uint attrs = 0;
630 	if (constant)
631 		attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
632 	if (enumerate)
633 		attrs |= JSPROP_ENUMERATE;
634 
635 	if (!obj.isObject())
636 		return false;
637 	JS::RootedObject object(m->m_cx, &obj.toObject());
638 
639 	if (!JS_DefineProperty(m->m_cx, object, name, value, attrs))
640 		return false;
641 	return true;
642 }
643 
SetProperty_(JS::HandleValue obj,const wchar_t * name,JS::HandleValue value,bool constant,bool enumerate) const644 bool ScriptInterface::SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) const
645 {
646 	JSAutoRequest rq(m->m_cx);
647 	uint attrs = 0;
648 	if (constant)
649 		attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
650 	if (enumerate)
651 		attrs |= JSPROP_ENUMERATE;
652 
653 	if (!obj.isObject())
654 		return false;
655 	JS::RootedObject object(m->m_cx, &obj.toObject());
656 
657 	utf16string name16(name, name + wcslen(name));
658 	if (!JS_DefineUCProperty(m->m_cx, object, reinterpret_cast<const char16_t*>(name16.c_str()), name16.length(), value, attrs))
659 		return false;
660 	return true;
661 }
662 
SetPropertyInt_(JS::HandleValue obj,int name,JS::HandleValue value,bool constant,bool enumerate) const663 bool ScriptInterface::SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool constant, bool enumerate) const
664 {
665 	JSAutoRequest rq(m->m_cx);
666 	uint attrs = 0;
667 	if (constant)
668 		attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
669 	if (enumerate)
670 		attrs |= JSPROP_ENUMERATE;
671 
672 	if (!obj.isObject())
673 		return false;
674 	JS::RootedObject object(m->m_cx, &obj.toObject());
675 
676 	JS::RootedId id(m->m_cx, INT_TO_JSID(name));
677 	if (!JS_DefinePropertyById(m->m_cx, object, id, value, attrs))
678 		return false;
679 	return true;
680 }
681 
GetProperty(JS::HandleValue obj,const char * name,JS::MutableHandleValue out) const682 bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const
683 {
684 	return GetProperty_(obj, name, out);
685 }
686 
GetProperty(JS::HandleValue obj,const char * name,JS::MutableHandleObject out) const687 bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleObject out) const
688 {
689 	JSContext* cx = GetContext();
690 	JSAutoRequest rq(cx);
691 	JS::RootedValue val(cx);
692 	if (!GetProperty_(obj, name, &val))
693 		return false;
694 	if (!val.isObject())
695 	{
696 		LOGERROR("GetProperty failed: trying to get an object, but the property is not an object!");
697 		return false;
698 	}
699 
700 	out.set(&val.toObject());
701 	return true;
702 }
703 
GetPropertyInt(JS::HandleValue obj,int name,JS::MutableHandleValue out) const704 bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, JS::MutableHandleValue out) const
705 {
706 	return GetPropertyInt_(obj, name, out);
707 }
708 
GetProperty_(JS::HandleValue obj,const char * name,JS::MutableHandleValue out) const709 bool ScriptInterface::GetProperty_(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const
710 {
711 	JSAutoRequest rq(m->m_cx);
712 	if (!obj.isObject())
713 		return false;
714 	JS::RootedObject object(m->m_cx, &obj.toObject());
715 
716 	if (!JS_GetProperty(m->m_cx, object, name, out))
717 		return false;
718 	return true;
719 }
720 
GetPropertyInt_(JS::HandleValue obj,int name,JS::MutableHandleValue out) const721 bool ScriptInterface::GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue out) const
722 {
723 	JSAutoRequest rq(m->m_cx);
724 	JS::RootedId nameId(m->m_cx, INT_TO_JSID(name));
725 	if (!obj.isObject())
726 		return false;
727 	JS::RootedObject object(m->m_cx, &obj.toObject());
728 
729 	if (!JS_GetPropertyById(m->m_cx, object, nameId, out))
730 		return false;
731 	return true;
732 }
733 
HasProperty(JS::HandleValue obj,const char * name) const734 bool ScriptInterface::HasProperty(JS::HandleValue obj, const char* name) const
735 {
736 	// TODO: proper errorhandling
737 	JSAutoRequest rq(m->m_cx);
738 	if (!obj.isObject())
739 		return false;
740 	JS::RootedObject object(m->m_cx, &obj.toObject());
741 
742 	bool found;
743 	if (!JS_HasProperty(m->m_cx, object, name, &found))
744 		return false;
745 	return found;
746 }
747 
EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal,const char * prefix,std::vector<std::string> & out) const748 bool ScriptInterface::EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal, const char* prefix, std::vector<std::string>& out) const
749 {
750 	JSAutoRequest rq(m->m_cx);
751 
752 	if (!objVal.isObjectOrNull())
753 	{
754 		LOGERROR("EnumeratePropertyNamesWithPrefix expected object type!");
755 		return false;
756 	}
757 
758 	if (objVal.isNull())
759 		return true; // reached the end of the prototype chain
760 
761 	JS::RootedObject obj(m->m_cx, &objVal.toObject());
762 	JS::AutoIdArray props(m->m_cx, JS_Enumerate(m->m_cx, obj));
763 	if (!props)
764 		return false;
765 
766 	for (size_t i = 0; i < props.length(); ++i)
767 	{
768 		JS::RootedId id(m->m_cx, props[i]);
769 		JS::RootedValue val(m->m_cx);
770 		if (!JS_IdToValue(m->m_cx, id, &val))
771 			return false;
772 
773 		if (!val.isString())
774 			continue; // ignore integer properties
775 
776 		JS::RootedString name(m->m_cx, val.toString());
777 		size_t len = strlen(prefix)+1;
778 		std::vector<char> buf(len);
779 		size_t prefixLen = strlen(prefix) * sizeof(char);
780 		JS_EncodeStringToBuffer(m->m_cx, name, &buf[0], prefixLen);
781 		buf[len-1]= '\0';
782 		if (0 == strcmp(&buf[0], prefix))
783 		{
784 			if (JS_StringHasLatin1Chars(name))
785 			{
786 				size_t length;
787 				JS::AutoCheckCannotGC nogc;
788 				const JS::Latin1Char* chars = JS_GetLatin1StringCharsAndLength(m->m_cx, nogc, name, &length);
789 				if (chars)
790 					out.push_back(std::string(chars, chars+length));
791 			}
792 			else
793 			{
794 				size_t length;
795 				JS::AutoCheckCannotGC nogc;
796 				const char16_t* chars = JS_GetTwoByteStringCharsAndLength(m->m_cx, nogc, name, &length);
797 				if (chars)
798 					out.push_back(std::string(chars, chars+length));
799 			}
800 		}
801 	}
802 
803 	// Recurse up the prototype chain
804 	JS::RootedObject prototype(m->m_cx);
805 	if (JS_GetPrototype(m->m_cx, obj, &prototype))
806 	{
807 		JS::RootedValue prototypeVal(m->m_cx, JS::ObjectOrNullValue(prototype));
808 		if (!EnumeratePropertyNamesWithPrefix(prototypeVal, prefix, out))
809 			return false;
810 	}
811 
812 	return true;
813 }
814 
SetPrototype(JS::HandleValue objVal,JS::HandleValue protoVal)815 bool ScriptInterface::SetPrototype(JS::HandleValue objVal, JS::HandleValue protoVal)
816 {
817 	JSAutoRequest rq(m->m_cx);
818 	if (!objVal.isObject() || !protoVal.isObject())
819 		return false;
820 	JS::RootedObject obj(m->m_cx, &objVal.toObject());
821 	JS::RootedObject proto(m->m_cx, &protoVal.toObject());
822 	return JS_SetPrototype(m->m_cx, obj, proto);
823 }
824 
FreezeObject(JS::HandleValue objVal,bool deep) const825 bool ScriptInterface::FreezeObject(JS::HandleValue objVal, bool deep) const
826 {
827 	JSAutoRequest rq(m->m_cx);
828 	if (!objVal.isObject())
829 		return false;
830 
831 	JS::RootedObject obj(m->m_cx, &objVal.toObject());
832 
833 	if (deep)
834 		return JS_DeepFreezeObject(m->m_cx, obj);
835 	else
836 		return JS_FreezeObject(m->m_cx, obj);
837 }
838 
LoadScript(const VfsPath & filename,const std::string & code) const839 bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) const
840 {
841 	JSAutoRequest rq(m->m_cx);
842 	JS::RootedObject global(m->m_cx, m->m_glob);
843 	utf16string codeUtf16(code.begin(), code.end());
844 	uint lineNo = 1;
845 	// CompileOptions does not copy the contents of the filename string pointer.
846 	// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
847 	std::string filenameStr = filename.string8();
848 
849 	JS::CompileOptions options(m->m_cx);
850 	options.setFileAndLine(filenameStr.c_str(), lineNo);
851 	options.setCompileAndGo(true);
852 
853 	JS::RootedFunction func(m->m_cx);
854 	JS::AutoObjectVector emptyScopeChain(m->m_cx);
855 	if (!JS::CompileFunction(m->m_cx, emptyScopeChain, options, NULL, 0, NULL,
856 	                         reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &func))
857 		return false;
858 
859 	JS::RootedValue rval(m->m_cx);
860 	return JS_CallFunction(m->m_cx, JS::NullPtr(), func, JS::HandleValueArray::empty(), &rval);
861 }
862 
CreateRuntime(shared_ptr<ScriptRuntime> parentRuntime,int runtimeSize,int heapGrowthBytesGCTrigger)863 shared_ptr<ScriptRuntime> ScriptInterface::CreateRuntime(shared_ptr<ScriptRuntime> parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger)
864 {
865 	return shared_ptr<ScriptRuntime>(new ScriptRuntime(parentRuntime, runtimeSize, heapGrowthBytesGCTrigger));
866 }
867 
LoadGlobalScript(const VfsPath & filename,const std::wstring & code) const868 bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const
869 {
870 	JSAutoRequest rq(m->m_cx);
871 	JS::RootedObject global(m->m_cx, m->m_glob);
872 	utf16string codeUtf16(code.begin(), code.end());
873 	uint lineNo = 1;
874 	// CompileOptions does not copy the contents of the filename string pointer.
875 	// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
876 	std::string filenameStr = filename.string8();
877 
878 	JS::RootedValue rval(m->m_cx);
879 	JS::CompileOptions opts(m->m_cx);
880 	opts.setFileAndLine(filenameStr.c_str(), lineNo);
881 	return JS::Evaluate(m->m_cx, global, opts,
882 			reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval);
883 }
884 
LoadGlobalScriptFile(const VfsPath & path) const885 bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) const
886 {
887 	JSAutoRequest rq(m->m_cx);
888 	JS::RootedObject global(m->m_cx, m->m_glob);
889 	if (!VfsFileExists(path))
890 	{
891 		LOGERROR("File '%s' does not exist", path.string8());
892 		return false;
893 	}
894 
895 	CVFSFile file;
896 
897 	PSRETURN ret = file.Load(g_VFS, path);
898 
899 	if (ret != PSRETURN_OK)
900 	{
901 		LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret));
902 		return false;
903 	}
904 
905 	std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8
906 
907 	utf16string codeUtf16(code.begin(), code.end());
908 	uint lineNo = 1;
909 	// CompileOptions does not copy the contents of the filename string pointer.
910 	// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
911 	std::string filenameStr = path.string8();
912 
913 	JS::RootedValue rval(m->m_cx);
914 	JS::CompileOptions opts(m->m_cx);
915 	opts.setFileAndLine(filenameStr.c_str(), lineNo);
916 	return JS::Evaluate(m->m_cx, global, opts,
917 			reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval);
918 }
919 
Eval(const char * code) const920 bool ScriptInterface::Eval(const char* code) const
921 {
922 	JSAutoRequest rq(m->m_cx);
923 	JS::RootedValue rval(m->m_cx);
924 	return Eval_(code, &rval);
925 }
926 
Eval_(const char * code,JS::MutableHandleValue rval) const927 bool ScriptInterface::Eval_(const char* code, JS::MutableHandleValue rval) const
928 {
929 	JSAutoRequest rq(m->m_cx);
930 	JS::RootedObject global(m->m_cx, m->m_glob);
931 	utf16string codeUtf16(code, code+strlen(code));
932 
933 	JS::CompileOptions opts(m->m_cx);
934 	opts.setFileAndLine("(eval)", 1);
935 	return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval);
936 }
937 
Eval_(const wchar_t * code,JS::MutableHandleValue rval) const938 bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) const
939 {
940 	JSAutoRequest rq(m->m_cx);
941 	JS::RootedObject global(m->m_cx, m->m_glob);
942 	utf16string codeUtf16(code, code+wcslen(code));
943 
944 	JS::CompileOptions opts(m->m_cx);
945 	opts.setFileAndLine("(eval)", 1);
946 	return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval);
947 }
948 
ParseJSON(const std::string & string_utf8,JS::MutableHandleValue out) const949 bool ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) const
950 {
951 	JSAutoRequest rq(m->m_cx);
952 	std::wstring attrsW = wstring_from_utf8(string_utf8);
953  	utf16string string(attrsW.begin(), attrsW.end());
954 	if (JS_ParseJSON(m->m_cx, reinterpret_cast<const char16_t*>(string.c_str()), (u32)string.size(), out))
955 		return true;
956 
957 	LOGERROR("JS_ParseJSON failed!");
958 	if (!JS_IsExceptionPending(m->m_cx))
959 		return false;
960 
961 	JS::RootedValue exc(m->m_cx);
962 	if (!JS_GetPendingException(m->m_cx, &exc))
963 		return false;
964 
965 	JS_ClearPendingException(m->m_cx);
966 	// We expect an object of type SyntaxError
967 	if (!exc.isObject())
968 		return false;
969 
970 	JS::RootedValue rval(m->m_cx);
971 	JS::RootedObject excObj(m->m_cx, &exc.toObject());
972 	if (!JS_CallFunctionName(m->m_cx, excObj, "toString", JS::HandleValueArray::empty(), &rval))
973 		return false;
974 
975 	std::wstring error;
976 	ScriptInterface::FromJSVal(m->m_cx, rval, error);
977 	LOGERROR("%s", utf8_from_wstring(error));
978 	return false;
979 }
980 
ReadJSONFile(const VfsPath & path,JS::MutableHandleValue out) const981 void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) const
982 {
983 	if (!VfsFileExists(path))
984 	{
985 		LOGERROR("File '%s' does not exist", path.string8());
986 		return;
987 	}
988 
989 	CVFSFile file;
990 
991 	PSRETURN ret = file.Load(g_VFS, path);
992 
993 	if (ret != PSRETURN_OK)
994 	{
995 		LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret));
996 		return;
997 	}
998 
999 	std::string content(file.DecodeUTF8()); // assume it's UTF-8
1000 
1001 	if (!ParseJSON(content, out))
1002 		LOGERROR("Failed to parse '%s'", path.string8());
1003 }
1004 
1005 struct Stringifier
1006 {
callbackStringifier1007 	static bool callback(const char16_t* buf, u32 len, void* data)
1008 	{
1009 		utf16string str(buf, buf+len);
1010 		std::wstring strw(str.begin(), str.end());
1011 
1012 		Status err; // ignore Unicode errors
1013 		static_cast<Stringifier*>(data)->stream << utf8_from_wstring(strw, &err);
1014 		return true;
1015 	}
1016 
1017 	std::stringstream stream;
1018 };
1019 
1020 // TODO: It's not quite clear why JS_Stringify needs JS::MutableHandleValue. |obj| should not get modified.
1021 // It probably has historical reasons and could be changed by SpiderMonkey in the future.
StringifyJSON(JS::MutableHandleValue obj,bool indent) const1022 std::string ScriptInterface::StringifyJSON(JS::MutableHandleValue obj, bool indent) const
1023 {
1024 	JSAutoRequest rq(m->m_cx);
1025 	Stringifier str;
1026 	JS::RootedValue indentVal(m->m_cx, indent ? JS::Int32Value(2) : JS::UndefinedValue());
1027 	if (!JS_Stringify(m->m_cx, obj, JS::NullPtr(), indentVal, &Stringifier::callback, &str))
1028 	{
1029 		JS_ClearPendingException(m->m_cx);
1030 		LOGERROR("StringifyJSON failed");
1031 		return std::string();
1032 	}
1033 
1034 	return str.stream.str();
1035 }
1036 
1037 
ToString(JS::MutableHandleValue obj,bool pretty) const1038 std::string ScriptInterface::ToString(JS::MutableHandleValue obj, bool pretty) const
1039 {
1040 	JSAutoRequest rq(m->m_cx);
1041 
1042 	if (obj.isUndefined())
1043 		return "(void 0)";
1044 
1045 	// Try to stringify as JSON if possible
1046 	// (TODO: this is maybe a bad idea since it'll drop 'undefined' values silently)
1047 	if (pretty)
1048 	{
1049 		Stringifier str;
1050 		JS::RootedValue indentVal(m->m_cx, JS::Int32Value(2));
1051 
1052 		// Temporary disable the error reporter, so we don't print complaints about cyclic values
1053 		JSErrorReporter er = JS_SetErrorReporter(m->m_runtime->m_rt, NULL);
1054 
1055 		bool ok = JS_Stringify(m->m_cx, obj, JS::NullPtr(), indentVal, &Stringifier::callback, &str);
1056 
1057 		// Restore error reporter
1058 		JS_SetErrorReporter(m->m_runtime->m_rt, er);
1059 
1060 		if (ok)
1061 			return str.stream.str();
1062 
1063 		// Clear the exception set when Stringify failed
1064 		JS_ClearPendingException(m->m_cx);
1065 	}
1066 
1067 	// Caller didn't want pretty output, or JSON conversion failed (e.g. due to cycles),
1068 	// so fall back to obj.toSource()
1069 
1070 	std::wstring source = L"(error)";
1071 	CallFunction(obj, "toSource", source);
1072 	return utf8_from_wstring(source);
1073 }
1074 
ReportError(const char * msg) const1075 void ScriptInterface::ReportError(const char* msg) const
1076 {
1077 	JSAutoRequest rq(m->m_cx);
1078 	// JS_ReportError by itself doesn't seem to set a JS-style exception, and so
1079 	// script callers will be unable to catch anything. So use JS_SetPendingException
1080 	// to make sure there really is a script-level exception. But just set it to undefined
1081 	// because there's not much value yet in throwing a real exception object.
1082 	JS_SetPendingException(m->m_cx, JS::UndefinedHandleValue);
1083 	// And report the actual error
1084 	JS_ReportError(m->m_cx, "%s", msg);
1085 
1086 	// TODO: Why doesn't JS_ReportPendingException(m->m_cx); work?
1087 }
1088 
IsExceptionPending(JSContext * cx)1089 bool ScriptInterface::IsExceptionPending(JSContext* cx)
1090 {
1091 	JSAutoRequest rq(cx);
1092 	return JS_IsExceptionPending(cx) ? true : false;
1093 }
1094 
GetClass(JS::HandleObject obj)1095 const JSClass* ScriptInterface::GetClass(JS::HandleObject obj)
1096 {
1097 	return JS_GetClass(obj);
1098 }
1099 
GetPrivate(JS::HandleObject obj)1100 void* ScriptInterface::GetPrivate(JS::HandleObject obj)
1101 {
1102 	// TODO: use JS_GetInstancePrivate
1103 	return JS_GetPrivate(obj);
1104 }
1105 
CloneValueFromOtherContext(const ScriptInterface & otherContext,JS::HandleValue val) const1106 JS::Value ScriptInterface::CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const
1107 {
1108 	PROFILE("CloneValueFromOtherContext");
1109 	JSAutoRequest rq(m->m_cx);
1110 	JS::RootedValue out(m->m_cx);
1111 	shared_ptr<StructuredClone> structuredClone = otherContext.WriteStructuredClone(val);
1112 	ReadStructuredClone(structuredClone, &out);
1113 	return out.get();
1114 }
1115 
StructuredClone()1116 ScriptInterface::StructuredClone::StructuredClone() :
1117 	m_Data(NULL), m_Size(0)
1118 {
1119 }
1120 
~StructuredClone()1121 ScriptInterface::StructuredClone::~StructuredClone()
1122 {
1123 	if (m_Data)
1124 		JS_ClearStructuredClone(m_Data, m_Size, NULL, NULL);
1125 }
1126 
WriteStructuredClone(JS::HandleValue v) const1127 shared_ptr<ScriptInterface::StructuredClone> ScriptInterface::WriteStructuredClone(JS::HandleValue v) const
1128 {
1129 	JSAutoRequest rq(m->m_cx);
1130 	u64* data = NULL;
1131 	size_t nbytes = 0;
1132 	if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes, NULL, NULL, JS::UndefinedHandleValue))
1133 	{
1134 		debug_warn(L"Writing a structured clone with JS_WriteStructuredClone failed!");
1135 		return shared_ptr<StructuredClone>();
1136 	}
1137 
1138 	shared_ptr<StructuredClone> ret(new StructuredClone);
1139 	ret->m_Data = data;
1140 	ret->m_Size = nbytes;
1141 	return ret;
1142 }
1143 
ReadStructuredClone(const shared_ptr<ScriptInterface::StructuredClone> & ptr,JS::MutableHandleValue ret) const1144 void ScriptInterface::ReadStructuredClone(const shared_ptr<ScriptInterface::StructuredClone>& ptr, JS::MutableHandleValue ret) const
1145 {
1146 	JSAutoRequest rq(m->m_cx);
1147 	JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, JS_STRUCTURED_CLONE_VERSION, ret, NULL, NULL);
1148 }
1149