summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testStructuredClone.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jsapi-tests/testStructuredClone.cpp')
-rw-r--r--js/src/jsapi-tests/testStructuredClone.cpp345
1 files changed, 345 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testStructuredClone.cpp b/js/src/jsapi-tests/testStructuredClone.cpp
new file mode 100644
index 0000000000..fe4c9cf68b
--- /dev/null
+++ b/js/src/jsapi-tests/testStructuredClone.cpp
@@ -0,0 +1,345 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/TestingFunctions.h"
+#include "js/ArrayBuffer.h" // JS::{IsArrayBufferObject,GetArrayBufferLengthAndData,NewExternalArrayBuffer}
+#include "js/StructuredClone.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+BEGIN_TEST(testStructuredClone_object) {
+ JS::RootedObject g1(cx, createGlobal());
+ JS::RootedObject g2(cx, createGlobal());
+ CHECK(g1);
+ CHECK(g2);
+
+ JS::RootedValue v1(cx);
+
+ {
+ JSAutoRealm ar(cx, g1);
+ JS::RootedValue prop(cx, JS::Int32Value(1337));
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ v1 = JS::ObjectOrNullValue(obj);
+ CHECK(v1.isObject());
+ CHECK(JS_SetProperty(cx, obj, "prop", prop));
+ }
+
+ {
+ JSAutoRealm ar(cx, g2);
+ JS::RootedValue v2(cx);
+
+ CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
+ CHECK(v2.isObject());
+ JS::RootedObject obj(cx, &v2.toObject());
+
+ JS::RootedValue prop(cx);
+ CHECK(JS_GetProperty(cx, obj, "prop", &prop));
+ CHECK(prop.isInt32());
+ CHECK(&v1.toObject() != obj);
+ CHECK_EQUAL(prop.toInt32(), 1337);
+ }
+
+ return true;
+}
+END_TEST(testStructuredClone_object)
+
+BEGIN_TEST(testStructuredClone_string) {
+ JS::RootedObject g1(cx, createGlobal());
+ JS::RootedObject g2(cx, createGlobal());
+ CHECK(g1);
+ CHECK(g2);
+
+ JS::RootedValue v1(cx);
+
+ {
+ JSAutoRealm ar(cx, g1);
+ JS::RootedValue prop(cx, JS::Int32Value(1337));
+
+ v1 = JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!"));
+ CHECK(v1.isString());
+ CHECK(v1.toString());
+ }
+
+ {
+ JSAutoRealm ar(cx, g2);
+ JS::RootedValue v2(cx);
+
+ CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
+ CHECK(v2.isString());
+ CHECK(v2.toString());
+
+ JS::RootedValue expected(
+ cx, JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!")));
+ CHECK_SAME(v2, expected);
+ }
+
+ return true;
+}
+END_TEST(testStructuredClone_string)
+
+BEGIN_TEST(testStructuredClone_externalArrayBuffer) {
+ ExternalData data("One two three four");
+ JS::RootedObject g1(cx, createGlobal());
+ JS::RootedObject g2(cx, createGlobal());
+ CHECK(g1);
+ CHECK(g2);
+
+ JS::RootedValue v1(cx);
+
+ {
+ JSAutoRealm ar(cx, g1);
+
+ JS::RootedObject obj(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
+ &ExternalData::freeCallback, &data));
+ CHECK(!data.wasFreed());
+
+ v1 = JS::ObjectOrNullValue(obj);
+ CHECK(v1.isObject());
+ }
+
+ {
+ JSAutoRealm ar(cx, g2);
+ JS::RootedValue v2(cx);
+
+ CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
+ CHECK(v2.isObject());
+
+ JS::RootedObject obj(cx, &v2.toObject());
+ CHECK(&v1.toObject() != obj);
+
+ uint32_t len;
+ bool isShared;
+ uint8_t* clonedData;
+ JS::GetArrayBufferLengthAndData(obj, &len, &isShared, &clonedData);
+
+ // The contents of the two array buffers should be equal, but not the
+ // same pointer.
+ CHECK_EQUAL(len, data.len());
+ CHECK(clonedData != data.contents());
+ CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
+ CHECK(!data.wasFreed());
+ }
+
+ // GC the array buffer before data goes out of scope
+ v1.setNull();
+ JS_GC(cx);
+ JS_GC(cx); // Trigger another to wait for background finalization to end
+
+ CHECK(data.wasFreed());
+
+ return true;
+}
+END_TEST(testStructuredClone_externalArrayBuffer)
+
+BEGIN_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess) {
+ CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::SameProcess));
+ CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::DifferentProcess));
+ return true;
+}
+
+bool testStructuredCloneCopy(JS::StructuredCloneScope scope) {
+ ExternalData data("One two three four");
+ JS::RootedObject buffer(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
+ &ExternalData::freeCallback, &data));
+ CHECK(buffer);
+ CHECK(!data.wasFreed());
+
+ JS::RootedValue v1(cx, JS::ObjectValue(*buffer));
+ JS::RootedValue v2(cx);
+ CHECK(clone(scope, v1, &v2));
+ JS::RootedObject bufferOut(cx, v2.toObjectOrNull());
+ CHECK(bufferOut);
+ CHECK(JS::IsArrayBufferObject(bufferOut));
+
+ uint32_t len;
+ bool isShared;
+ uint8_t* clonedData;
+ JS::GetArrayBufferLengthAndData(bufferOut, &len, &isShared, &clonedData);
+
+ // Cloning should copy the data, so the contents of the two array buffers
+ // should be equal, but not the same pointer.
+ CHECK_EQUAL(len, data.len());
+ CHECK(clonedData != data.contents());
+ CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
+ CHECK(!data.wasFreed());
+
+ buffer = nullptr;
+ bufferOut = nullptr;
+ v1.setNull();
+ v2.setNull();
+ JS_GC(cx);
+ JS_GC(cx);
+ CHECK(data.wasFreed());
+
+ return true;
+}
+
+bool clone(JS::StructuredCloneScope scope, JS::HandleValue v1,
+ JS::MutableHandleValue v2) {
+ JSAutoStructuredCloneBuffer clonedBuffer(scope, nullptr, nullptr);
+ CHECK(clonedBuffer.write(cx, v1));
+ CHECK(clonedBuffer.read(cx, v2));
+ return true;
+}
+END_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess)
+
+struct StructuredCloneTestPrincipals final : public JSPrincipals {
+ uint32_t rank;
+
+ explicit StructuredCloneTestPrincipals(uint32_t rank, int32_t rc = 1)
+ : rank(rank) {
+ this->refcount = rc;
+ }
+
+ bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+ return JS_WriteUint32Pair(writer, rank, 0);
+ }
+
+ bool isSystemOrAddonPrincipal() override { return true; }
+
+ static bool read(JSContext* cx, JSStructuredCloneReader* reader,
+ JSPrincipals** outPrincipals) {
+ uint32_t rank;
+ uint32_t unused;
+ if (!JS_ReadUint32Pair(reader, &rank, &unused)) {
+ return false;
+ }
+
+ *outPrincipals = new StructuredCloneTestPrincipals(rank);
+ return !!*outPrincipals;
+ }
+
+ static void destroy(JSPrincipals* p) {
+ auto p1 = static_cast<StructuredCloneTestPrincipals*>(p);
+ delete p1;
+ }
+
+ static uint32_t getRank(JSPrincipals* p) {
+ if (!p) {
+ return 0;
+ }
+ return static_cast<StructuredCloneTestPrincipals*>(p)->rank;
+ }
+
+ static bool subsumes(JSPrincipals* a, JSPrincipals* b) {
+ return getRank(a) > getRank(b);
+ }
+
+ static JSSecurityCallbacks securityCallbacks;
+
+ static StructuredCloneTestPrincipals testPrincipals;
+};
+
+JSSecurityCallbacks StructuredCloneTestPrincipals::securityCallbacks = {
+ nullptr, // contentSecurityPolicyAllows
+ subsumes};
+
+BEGIN_TEST(testStructuredClone_SavedFrame) {
+ JS_SetSecurityCallbacks(cx,
+ &StructuredCloneTestPrincipals::securityCallbacks);
+ JS_InitDestroyPrincipalsCallback(cx, StructuredCloneTestPrincipals::destroy);
+ JS_InitReadPrincipalsCallback(cx, StructuredCloneTestPrincipals::read);
+
+ auto testPrincipals = new StructuredCloneTestPrincipals(42, 0);
+ CHECK(testPrincipals);
+
+ auto DONE = (JSPrincipals*)0xDEADBEEF;
+
+ struct {
+ const char* name;
+ JSPrincipals* principals;
+ } principalsToTest[] = {
+ {"IsSystem", &js::ReconstructedSavedFramePrincipals::IsSystem},
+ {"IsNotSystem", &js::ReconstructedSavedFramePrincipals::IsNotSystem},
+ {"testPrincipals", testPrincipals},
+ {"nullptr principals", nullptr},
+ {"DONE", DONE}};
+
+ const char* FILENAME = "filename.js";
+
+ for (auto* pp = principalsToTest; pp->principals != DONE; pp++) {
+ fprintf(stderr, "Testing with principals '%s'\n", pp->name);
+
+ JS::RealmOptions options;
+ JS::RootedObject g(cx,
+ JS_NewGlobalObject(cx, getGlobalClass(), pp->principals,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(g);
+ JSAutoRealm ar(cx, g);
+
+ CHECK(js::DefineTestingFunctions(cx, g, false, false));
+
+ JS::RootedValue srcVal(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return saveStack(); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()); \n", // 7
+ FILENAME, 1, &srcVal));
+
+ CHECK(srcVal.isObject());
+ JS::RootedObject srcObj(cx, &srcVal.toObject());
+
+ CHECK(srcObj->is<js::SavedFrame>());
+ js::RootedSavedFrame srcFrame(cx, &srcObj->as<js::SavedFrame>());
+
+ CHECK(srcFrame->getPrincipals() == pp->principals);
+
+ JS::RootedValue destVal(cx);
+ CHECK(JS_StructuredClone(cx, srcVal, &destVal, nullptr, nullptr));
+
+ CHECK(destVal.isObject());
+ JS::RootedObject destObj(cx, &destVal.toObject());
+
+ CHECK(destObj->is<js::SavedFrame>());
+ JS::Handle<js::SavedFrame*> destFrame = destObj.as<js::SavedFrame>();
+
+ size_t framesCopied = 0;
+ for (JS::Handle<js::SavedFrame*> f :
+ js::SavedFrame::RootedRange(cx, destFrame)) {
+ framesCopied++;
+
+ CHECK(f != srcFrame);
+
+ if (pp->principals == testPrincipals) {
+ // We shouldn't get a pointer to the same
+ // StructuredCloneTestPrincipals instance since we should have
+ // serialized and then deserialized it into a new instance.
+ CHECK(f->getPrincipals() != pp->principals);
+
+ // But it should certainly have the same rank.
+ CHECK(StructuredCloneTestPrincipals::getRank(f->getPrincipals()) ==
+ StructuredCloneTestPrincipals::getRank(pp->principals));
+ } else {
+ // For our singleton principals, we should always get the same
+ // pointer back.
+ CHECK(js::ReconstructedSavedFramePrincipals::is(pp->principals) ||
+ pp->principals == nullptr);
+ CHECK(f->getPrincipals() == pp->principals);
+ }
+
+ CHECK(EqualStrings(f->getSource(), srcFrame->getSource()));
+ CHECK(f->getLine() == srcFrame->getLine());
+ CHECK(f->getColumn() == srcFrame->getColumn());
+ CHECK(EqualStrings(f->getFunctionDisplayName(),
+ srcFrame->getFunctionDisplayName()));
+
+ srcFrame = srcFrame->getParent();
+ }
+
+ // Four function frames + one global frame.
+ CHECK(framesCopied == 4);
+ }
+
+ return true;
+}
+END_TEST(testStructuredClone_SavedFrame)