| /* 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/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); |
| |
| { |
| JSAutoCompartment ac(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)); |
| } |
| |
| { |
| JSAutoCompartment ac(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); |
| |
| { |
| JSAutoCompartment ac(cx, g1); |
| JS::RootedValue prop(cx, JS::Int32Value(1337)); |
| |
| v1 = JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!")); |
| CHECK(v1.isString()); |
| CHECK(v1.toString()); |
| } |
| |
| { |
| JSAutoCompartment ac(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) |
| |
| 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); |
| } |
| |
| 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(rt, &StructuredCloneTestPrincipals::securityCallbacks); |
| JS_InitDestroyPrincipalsCallback(rt, StructuredCloneTestPrincipals::destroy); |
| JS_InitReadPrincipalsCallback(rt, 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::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), pp->principals, |
| JS::FireOnNewGlobalHook)); |
| CHECK(g); |
| JSAutoCompartment ac(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>()); |
| auto destFrame = &destObj->as<js::SavedFrame>(); |
| |
| size_t framesCopied = 0; |
| for (auto& f : *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) |