Bug 1819287 - Make nsContentUtils::StringifyJSON more flexible; r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D171215
This commit is contained in:
Ms2ger 2023-03-02 15:47:40 +00:00
parent 92601c6d5a
commit 3f2d0f2321
10 changed files with 99 additions and 24 deletions

View file

@ -133,7 +133,8 @@ void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder,
} }
JS::Rooted<JS::Value> convertedValue(cx, JS::ObjectValue(*converted)); JS::Rooted<JS::Value> convertedValue(cx, JS::ObjectValue(*converted));
if (!nsContentUtils::StringifyJSON(cx, convertedValue, aStackString)) { if (!nsContentUtils::StringifyJSON(cx, convertedValue, aStackString,
UndefinedIsNullStringLiteral)) {
JS_ClearPendingException(cx); JS_ClearPendingException(cx);
return; return;
} }

View file

@ -10628,16 +10628,28 @@ static bool JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData) {
/* static */ /* static */
bool nsContentUtils::StringifyJSON(JSContext* aCx, JS::Handle<JS::Value> aValue, bool nsContentUtils::StringifyJSON(JSContext* aCx, JS::Handle<JS::Value> aValue,
nsAString& aOutStr) { nsAString& aOutStr, JSONBehavior aBehavior) {
MOZ_ASSERT(aCx); MOZ_ASSERT(aCx);
aOutStr.Truncate(); switch (aBehavior) {
JS::Rooted<JS::Value> value(aCx, aValue); case UndefinedIsNullStringLiteral: {
nsAutoString serializedValue; aOutStr.Truncate();
NS_ENSURE_TRUE(JS_Stringify(aCx, &value, nullptr, JS::NullHandleValue, JS::Rooted<JS::Value> value(aCx, aValue);
JSONCreator, &serializedValue), nsAutoString serializedValue;
false); NS_ENSURE_TRUE(JS_Stringify(aCx, &value, nullptr, JS::NullHandleValue,
aOutStr = serializedValue; JSONCreator, &serializedValue),
return true; false);
aOutStr = serializedValue;
return true;
}
case UndefinedIsVoidString: {
aOutStr.SetIsVoid(true);
return JS::ToJSON(aCx, aValue, nullptr, JS::NullHandleValue, JSONCreator,
&aOutStr);
}
default:
MOZ_ASSERT_UNREACHABLE("Invalid value for aBehavior");
return false;
}
} }
/* static */ /* static */

View file

@ -243,7 +243,11 @@ struct EventNameMapping {
namespace mozilla { namespace mozilla {
enum class PreventDefaultResult : uint8_t { No, ByContent, ByChrome }; enum class PreventDefaultResult : uint8_t { No, ByContent, ByChrome };
namespace dom {
enum JSONBehavior { UndefinedIsNullStringLiteral, UndefinedIsVoidString };
} }
} // namespace mozilla
class nsContentUtils { class nsContentUtils {
friend class nsAutoScriptBlockerSuppressNodeRemoved; friend class nsAutoScriptBlockerSuppressNodeRemoved;
@ -256,6 +260,7 @@ class nsContentUtils {
using EventMessage = mozilla::EventMessage; using EventMessage = mozilla::EventMessage;
using TimeDuration = mozilla::TimeDuration; using TimeDuration = mozilla::TimeDuration;
using Trusted = mozilla::Trusted; using Trusted = mozilla::Trusted;
using JSONBehavior = mozilla::dom::JSONBehavior;
public: public:
static nsresult Init(); static nsresult Init();
@ -3256,15 +3261,21 @@ class nsContentUtils {
static nsresult AnonymizeURI(nsIURI* aURI, nsCString& aAnonymizedURI); static nsresult AnonymizeURI(nsIURI* aURI, nsCString& aAnonymizedURI);
/** /**
* Serializes a JSON-like JS::Value into a string, returning the string "null" * Serializes a JSON-like JS::Value into a string.
* where JSON.stringify would return undefined. * Cases where JSON.stringify would return undefined are handled according to
* the |aBehavior| argument:
* *
* - If it is |UndefinedIsNullStringLiteral|, the string "null" is returned.
* - If it is |UndefinedIsVoidString|, the void string is returned.
*
* The |UndefinedIsNullStringLiteral| case is likely not desirable, but is
* retained for now for historical reasons.
* Usage: * Usage:
* nsAutoString serializedValue; * nsAutoString serializedValue;
* nsContentUtils::StringifyJSON(cx, value, serializedValue); * nsContentUtils::StringifyJSON(cx, value, serializedValue, behavior);
*/ */
static bool StringifyJSON(JSContext* aCx, JS::Handle<JS::Value> aValue, static bool StringifyJSON(JSContext* aCx, JS::Handle<JS::Value> aValue,
nsAString& aOutStr); nsAString& aOutStr, JSONBehavior aBehavior);
/** /**
* Returns true if the top level ancestor content document of aDocument hasn't * Returns true if the top level ancestor content document of aDocument hasn't

View file

@ -472,7 +472,9 @@ bool nsFrameMessageManager::GetParamsForMessage(JSContext* aCx,
// properly cases when interface is implemented in JS and used // properly cases when interface is implemented in JS and used
// as a dictionary. // as a dictionary.
nsAutoString json; nsAutoString json;
NS_ENSURE_TRUE(nsContentUtils::StringifyJSON(aCx, v, json), false); NS_ENSURE_TRUE(
nsContentUtils::StringifyJSON(aCx, v, json, UndefinedIsNullStringLiteral),
false);
NS_ENSURE_TRUE(!json.IsEmpty(), false); NS_ENSURE_TRUE(!json.IsEmpty(), false);
JS::Rooted<JS::Value> val(aCx, JS::NullValue()); JS::Rooted<JS::Value> val(aCx, JS::NullValue());

View file

@ -14,6 +14,8 @@
#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SimpleGlobalObject.h" #include "mozilla/dom/SimpleGlobalObject.h"
using namespace mozilla::dom;
struct IsURIInListMatch { struct IsURIInListMatch {
nsLiteralCString pattern; nsLiteralCString pattern;
bool firstMatch, secondMatch; bool firstMatch, secondMatch;
@ -64,7 +66,8 @@ TEST(DOM_Base_ContentUtils, IsURIInList)
} }
} }
TEST(DOM_Base_ContentUtils, StringifyJSON_EmptyValue) TEST(DOM_Base_ContentUtils,
StringifyJSON_EmptyValue_UndefinedIsNullStringLiteral)
{ {
JS::Rooted<JSObject*> globalObject( JS::Rooted<JSObject*> globalObject(
mozilla::dom::RootingCx(), mozilla::dom::RootingCx(),
@ -76,11 +79,12 @@ TEST(DOM_Base_ContentUtils, StringifyJSON_EmptyValue)
nsAutoString serializedValue; nsAutoString serializedValue;
ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, JS::UndefinedHandleValue, ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, JS::UndefinedHandleValue,
serializedValue)); serializedValue,
UndefinedIsNullStringLiteral));
ASSERT_TRUE(serializedValue.EqualsLiteral("null")); ASSERT_TRUE(serializedValue.EqualsLiteral("null"));
} }
TEST(DOM_Base_ContentUtils, StringifyJSON_Object) TEST(DOM_Base_ContentUtils, StringifyJSON_Object_UndefinedIsNullStringLiteral)
{ {
JS::Rooted<JSObject*> globalObject( JS::Rooted<JSObject*> globalObject(
mozilla::dom::RootingCx(), mozilla::dom::RootingCx(),
@ -96,7 +100,47 @@ TEST(DOM_Base_ContentUtils, StringifyJSON_Object)
ASSERT_TRUE(JS_DefineProperty(cx, jsObj, "key1", valueStr, JSPROP_ENUMERATE)); ASSERT_TRUE(JS_DefineProperty(cx, jsObj, "key1", valueStr, JSPROP_ENUMERATE));
JS::Rooted<JS::Value> jsValue(cx, JS::ObjectValue(*jsObj)); JS::Rooted<JS::Value> jsValue(cx, JS::ObjectValue(*jsObj));
ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, jsValue, serializedValue)); ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, jsValue, serializedValue,
UndefinedIsNullStringLiteral));
ASSERT_TRUE(serializedValue.EqualsLiteral("{\"key1\":\"Hello World!\"}"));
}
TEST(DOM_Base_ContentUtils, StringifyJSON_EmptyValue_UndefinedIsVoidString)
{
JS::Rooted<JSObject*> globalObject(
mozilla::dom::RootingCx(),
mozilla::dom::SimpleGlobalObject::Create(
mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
mozilla::dom::AutoJSAPI jsAPI;
ASSERT_TRUE(jsAPI.Init(globalObject));
JSContext* cx = jsAPI.cx();
nsAutoString serializedValue;
ASSERT_TRUE(nsContentUtils::StringifyJSON(
cx, JS::UndefinedHandleValue, serializedValue, UndefinedIsVoidString));
ASSERT_TRUE(serializedValue.IsVoid());
}
TEST(DOM_Base_ContentUtils, StringifyJSON_Object_UndefinedIsVoidString)
{
JS::Rooted<JSObject*> globalObject(
mozilla::dom::RootingCx(),
mozilla::dom::SimpleGlobalObject::Create(
mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
mozilla::dom::AutoJSAPI jsAPI;
ASSERT_TRUE(jsAPI.Init(globalObject));
JSContext* cx = jsAPI.cx();
nsAutoString serializedValue;
JS::Rooted<JSObject*> jsObj(cx, JS_NewPlainObject(cx));
JS::Rooted<JSString*> valueStr(cx, JS_NewStringCopyZ(cx, "Hello World!"));
ASSERT_TRUE(JS_DefineProperty(cx, jsObj, "key1", valueStr, JSPROP_ENUMERATE));
JS::Rooted<JS::Value> jsValue(cx, JS::ObjectValue(*jsObj));
ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, jsValue, serializedValue,
UndefinedIsVoidString));
ASSERT_TRUE(serializedValue.EqualsLiteral("{\"key1\":\"Hello World!\"}")); ASSERT_TRUE(serializedValue.EqualsLiteral("{\"key1\":\"Hello World!\"}"));
} }

View file

@ -48,7 +48,8 @@ nsresult GetAsString(const RefPtr<Promise>& aPromise, nsAString& aString) {
} }
case Promise::PromiseState::Resolved: { case Promise::PromiseState::Resolved: {
if (nsContentUtils::StringifyJSON(cx, vp, aString)) { if (nsContentUtils::StringifyJSON(cx, vp, aString,
UndefinedIsNullStringLiteral)) {
return NS_OK; return NS_OK;
} }

View file

@ -142,7 +142,8 @@ void DOMLocalization::SetAttributes(
if (aArgs.WasPassed() && aArgs.Value()) { if (aArgs.WasPassed() && aArgs.Value()) {
nsAutoString data; nsAutoString data;
JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value())); JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value()));
if (!nsContentUtils::StringifyJSON(aCx, val, data)) { if (!nsContentUtils::StringifyJSON(aCx, val, data,
UndefinedIsNullStringLiteral)) {
aRv.NoteJSContextException(aCx); aRv.NoteJSContextException(aCx);
return; return;
} }

View file

@ -22,7 +22,8 @@ nsresult SerializeFromJSObject(JSContext* aCx, JS::Handle<JSObject*> aObject,
nsresult SerializeFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aValue, nsresult SerializeFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aValue,
nsAString& aSerializedValue) { nsAString& aSerializedValue) {
aSerializedValue.Truncate(); aSerializedValue.Truncate();
NS_ENSURE_TRUE(nsContentUtils::StringifyJSON(aCx, aValue, aSerializedValue), NS_ENSURE_TRUE(nsContentUtils::StringifyJSON(aCx, aValue, aSerializedValue,
UndefinedIsNullStringLiteral),
NS_ERROR_XPC_BAD_CONVERT_JS); NS_ERROR_XPC_BAD_CONVERT_JS);
NS_ENSURE_TRUE(!aSerializedValue.IsEmpty(), NS_ERROR_FAILURE); NS_ENSURE_TRUE(!aSerializedValue.IsEmpty(), NS_ERROR_FAILURE);
return NS_OK; return NS_OK;

View file

@ -1128,7 +1128,8 @@ MOZ_CAN_RUN_SCRIPT
static void SetSessionData(JSContext* aCx, Element* aElement, static void SetSessionData(JSContext* aCx, Element* aElement,
JS::MutableHandle<JS::Value> aObject) { JS::MutableHandle<JS::Value> aObject) {
nsAutoString data; nsAutoString data;
if (nsContentUtils::StringifyJSON(aCx, aObject, data)) { if (nsContentUtils::StringifyJSON(aCx, aObject, data,
UndefinedIsNullStringLiteral)) {
SetElementAsString(aElement, data); SetElementAsString(aElement, data);
} else { } else {
JS_ClearPendingException(aCx); JS_ClearPendingException(aCx);

View file

@ -230,7 +230,8 @@ class UntrustedModulesFixture : public TelemetryTestFixture {
serializer.GetObject(&jsval); serializer.GetObject(&jsval);
nsAutoString json; nsAutoString json;
EXPECT_TRUE(nsContentUtils::StringifyJSON(cx.GetJSContext(), jsval, json)); EXPECT_TRUE(nsContentUtils::StringifyJSON(
cx.GetJSContext(), jsval, json, dom::UndefinedIsNullStringLiteral));
JS::Rooted<JSObject*> re( JS::Rooted<JSObject*> re(
cx.GetJSContext(), cx.GetJSContext(),