1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "builtin/TestingFunctions.h"
6 #include "js/ArrayBuffer.h" // JS::{IsArrayBufferObject,GetArrayBufferLengthAndData,NewExternalArrayBuffer}
7 #include "js/GlobalObject.h" // JS_NewGlobalObject
8 #include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty
9 #include "js/StructuredClone.h"
11 #include "jsapi-tests/tests.h"
16 // Skip test, since it will abort with an assert in buf->Init(7).
18 BEGIN_TEST(testStructuredClone_invalidLength
) {
19 auto buf
= js::MakeUnique
<JSStructuredCloneData
>(
20 JS::StructuredCloneScope::DifferentProcess
);
23 RootedValue
clone(cx
);
24 JS::CloneDataPolicy policy
;
25 CHECK(!JS_ReadStructuredClone(cx
, *buf
, JS_STRUCTURED_CLONE_VERSION
,
26 JS::StructuredCloneScope::DifferentProcess
,
27 &clone
, policy
, nullptr, nullptr));
30 END_TEST(testStructuredClone_invalidLength
)
33 BEGIN_TEST(testStructuredClone_object
) {
34 JS::RootedObject
g1(cx
, createGlobal());
35 JS::RootedObject
g2(cx
, createGlobal());
39 JS::RootedValue
v1(cx
);
42 JSAutoRealm
ar(cx
, g1
);
43 JS::RootedValue
prop(cx
, JS::Int32Value(1337));
45 JS::RootedObject
obj(cx
, JS_NewPlainObject(cx
));
46 v1
= JS::ObjectOrNullValue(obj
);
48 CHECK(JS_SetProperty(cx
, obj
, "prop", prop
));
52 JSAutoRealm
ar(cx
, g2
);
53 JS::RootedValue
v2(cx
);
55 CHECK(JS_StructuredClone(cx
, v1
, &v2
, nullptr, nullptr));
57 JS::RootedObject
obj(cx
, &v2
.toObject());
59 JS::RootedValue
prop(cx
);
60 CHECK(JS_GetProperty(cx
, obj
, "prop", &prop
));
61 CHECK(prop
.isInt32());
62 CHECK(&v1
.toObject() != obj
);
63 CHECK_EQUAL(prop
.toInt32(), 1337);
68 END_TEST(testStructuredClone_object
)
70 BEGIN_TEST(testStructuredClone_string
) {
71 JS::RootedObject
g1(cx
, createGlobal());
72 JS::RootedObject
g2(cx
, createGlobal());
76 JS::RootedValue
v1(cx
);
79 JSAutoRealm
ar(cx
, g1
);
80 JS::RootedValue
prop(cx
, JS::Int32Value(1337));
82 v1
= JS::StringValue(JS_NewStringCopyZ(cx
, "Hello World!"));
88 JSAutoRealm
ar(cx
, g2
);
89 JS::RootedValue
v2(cx
);
91 CHECK(JS_StructuredClone(cx
, v1
, &v2
, nullptr, nullptr));
95 JS::RootedValue
expected(
96 cx
, JS::StringValue(JS_NewStringCopyZ(cx
, "Hello World!")));
97 CHECK_SAME(v2
, expected
);
102 END_TEST(testStructuredClone_string
)
104 BEGIN_TEST(testStructuredClone_externalArrayBuffer
) {
105 ExternalData
data("One two three four");
106 auto dataPointer
= data
.pointer();
107 JS::RootedObject
g1(cx
, createGlobal());
108 JS::RootedObject
g2(cx
, createGlobal());
112 JS::RootedValue
v1(cx
);
115 JSAutoRealm
ar(cx
, g1
);
117 JS::RootedObject
obj(
118 cx
, JS::NewExternalArrayBuffer(cx
, data
.len(), std::move(dataPointer
)));
119 CHECK(!data
.wasFreed());
121 v1
= JS::ObjectOrNullValue(obj
);
122 CHECK(v1
.isObject());
126 JSAutoRealm
ar(cx
, g2
);
127 JS::RootedValue
v2(cx
);
129 CHECK(JS_StructuredClone(cx
, v1
, &v2
, nullptr, nullptr));
130 CHECK(v2
.isObject());
132 JS::RootedObject
obj(cx
, &v2
.toObject());
133 CHECK(&v1
.toObject() != obj
);
138 JS::GetArrayBufferLengthAndData(obj
, &len
, &isShared
, &clonedData
);
140 // The contents of the two array buffers should be equal, but not the
142 CHECK_EQUAL(len
, data
.len());
143 CHECK(clonedData
!= data
.contents());
144 CHECK(strcmp(reinterpret_cast<char*>(clonedData
), data
.asString()) == 0);
145 CHECK(!data
.wasFreed());
148 // GC the array buffer before data goes out of scope
151 JS_GC(cx
); // Trigger another to wait for background finalization to end
153 CHECK(data
.wasFreed());
157 END_TEST(testStructuredClone_externalArrayBuffer
)
159 BEGIN_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess
) {
160 CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::SameProcess
));
161 CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::DifferentProcess
));
165 bool testStructuredCloneCopy(JS::StructuredCloneScope scope
) {
166 ExternalData
data("One two three four");
167 auto dataPointer
= data
.pointer();
168 JS::RootedObject
buffer(
169 cx
, JS::NewExternalArrayBuffer(cx
, data
.len(), std::move(dataPointer
)));
171 CHECK(!data
.wasFreed());
173 JS::RootedValue
v1(cx
, JS::ObjectValue(*buffer
));
174 JS::RootedValue
v2(cx
);
175 CHECK(clone(scope
, v1
, &v2
));
176 JS::RootedObject
bufferOut(cx
, v2
.toObjectOrNull());
178 CHECK(JS::IsArrayBufferObject(bufferOut
));
183 JS::GetArrayBufferLengthAndData(bufferOut
, &len
, &isShared
, &clonedData
);
185 // Cloning should copy the data, so the contents of the two array buffers
186 // should be equal, but not the same pointer.
187 CHECK_EQUAL(len
, data
.len());
188 CHECK(clonedData
!= data
.contents());
189 CHECK(strcmp(reinterpret_cast<char*>(clonedData
), data
.asString()) == 0);
190 CHECK(!data
.wasFreed());
198 CHECK(data
.wasFreed());
203 bool clone(JS::StructuredCloneScope scope
, JS::HandleValue v1
,
204 JS::MutableHandleValue v2
) {
205 JSAutoStructuredCloneBuffer
clonedBuffer(scope
, nullptr, nullptr);
206 CHECK(clonedBuffer
.write(cx
, v1
));
207 CHECK(clonedBuffer
.read(cx
, v2
));
210 END_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess
)
212 struct StructuredCloneTestPrincipals final
: public JSPrincipals
{
215 explicit StructuredCloneTestPrincipals(uint32_t rank
, int32_t rc
= 1)
220 bool write(JSContext
* cx
, JSStructuredCloneWriter
* writer
) override
{
221 return JS_WriteUint32Pair(writer
, rank
, 0);
224 bool isSystemOrAddonPrincipal() override
{ return true; }
226 static bool read(JSContext
* cx
, JSStructuredCloneReader
* reader
,
227 JSPrincipals
** outPrincipals
) {
230 if (!JS_ReadUint32Pair(reader
, &rank
, &unused
)) {
234 *outPrincipals
= new StructuredCloneTestPrincipals(rank
);
235 return !!*outPrincipals
;
238 static void destroy(JSPrincipals
* p
) {
239 auto p1
= static_cast<StructuredCloneTestPrincipals
*>(p
);
243 static uint32_t getRank(JSPrincipals
* p
) {
247 return static_cast<StructuredCloneTestPrincipals
*>(p
)->rank
;
250 static bool subsumes(JSPrincipals
* a
, JSPrincipals
* b
) {
251 return getRank(a
) > getRank(b
);
254 static JSSecurityCallbacks securityCallbacks
;
256 static StructuredCloneTestPrincipals testPrincipals
;
259 JSSecurityCallbacks
StructuredCloneTestPrincipals::securityCallbacks
= {
260 nullptr, // contentSecurityPolicyAllows
263 BEGIN_TEST(testStructuredClone_SavedFrame
) {
264 JS_SetSecurityCallbacks(cx
,
265 &StructuredCloneTestPrincipals::securityCallbacks
);
266 JS_InitDestroyPrincipalsCallback(cx
, StructuredCloneTestPrincipals::destroy
);
267 JS_InitReadPrincipalsCallback(cx
, StructuredCloneTestPrincipals::read
);
269 auto testPrincipals
= new StructuredCloneTestPrincipals(42, 0);
270 CHECK(testPrincipals
);
272 auto DONE
= (JSPrincipals
*)0xDEADBEEF;
276 JSPrincipals
* principals
;
277 } principalsToTest
[] = {
278 {"IsSystem", &js::ReconstructedSavedFramePrincipals::IsSystem
},
279 {"IsNotSystem", &js::ReconstructedSavedFramePrincipals::IsNotSystem
},
280 {"testPrincipals", testPrincipals
},
281 {"nullptr principals", nullptr},
284 const char* FILENAME
= "filename.js";
286 for (auto* pp
= principalsToTest
; pp
->principals
!= DONE
; pp
++) {
287 fprintf(stderr
, "Testing with principals '%s'\n", pp
->name
);
289 JS::RealmOptions options
;
290 JS::RootedObject
g(cx
,
291 JS_NewGlobalObject(cx
, getGlobalClass(), pp
->principals
,
292 JS::FireOnNewGlobalHook
, options
));
294 JSAutoRealm
ar(cx
, g
);
296 CHECK(js::DefineTestingFunctions(cx
, g
, false, false));
298 JS::RootedValue
srcVal(cx
);
300 evaluate("(function one() { \n" // 1
301 " return (function two() { \n" // 2
302 " return (function three() { \n" // 3
303 " return saveStack(); \n" // 4
307 FILENAME
, 1, &srcVal
));
309 CHECK(srcVal
.isObject());
310 JS::RootedObject
srcObj(cx
, &srcVal
.toObject());
312 CHECK(srcObj
->is
<js::SavedFrame
>());
313 JS::Rooted
<js::SavedFrame
*> srcFrame(cx
, &srcObj
->as
<js::SavedFrame
>());
315 CHECK(srcFrame
->getPrincipals() == pp
->principals
);
317 JS::RootedValue
destVal(cx
);
318 CHECK(JS_StructuredClone(cx
, srcVal
, &destVal
, nullptr, nullptr));
320 CHECK(destVal
.isObject());
321 JS::RootedObject
destObj(cx
, &destVal
.toObject());
323 CHECK(destObj
->is
<js::SavedFrame
>());
324 JS::Handle
<js::SavedFrame
*> destFrame
= destObj
.as
<js::SavedFrame
>();
326 size_t framesCopied
= 0;
327 for (JS::Handle
<js::SavedFrame
*> f
:
328 js::SavedFrame::RootedRange(cx
, destFrame
)) {
331 CHECK(f
!= srcFrame
);
333 if (pp
->principals
== testPrincipals
) {
334 // We shouldn't get a pointer to the same
335 // StructuredCloneTestPrincipals instance since we should have
336 // serialized and then deserialized it into a new instance.
337 CHECK(f
->getPrincipals() != pp
->principals
);
339 // But it should certainly have the same rank.
340 CHECK(StructuredCloneTestPrincipals::getRank(f
->getPrincipals()) ==
341 StructuredCloneTestPrincipals::getRank(pp
->principals
));
343 // For our singleton principals, we should always get the same
345 CHECK(js::ReconstructedSavedFramePrincipals::is(pp
->principals
) ||
346 pp
->principals
== nullptr);
347 CHECK(f
->getPrincipals() == pp
->principals
);
350 CHECK(EqualStrings(f
->getSource(), srcFrame
->getSource()));
351 CHECK(f
->getLine() == srcFrame
->getLine());
352 CHECK(f
->getColumn() == srcFrame
->getColumn());
353 CHECK(EqualStrings(f
->getFunctionDisplayName(),
354 srcFrame
->getFunctionDisplayName()));
356 srcFrame
= srcFrame
->getParent();
359 // Four function frames + one global frame.
360 CHECK(framesCopied
== 4);
365 END_TEST(testStructuredClone_SavedFrame
)