Skip to content
Next Next commit
Fixs JSON.stringify() cannot serialize cyclic structures
  • Loading branch information
LinXunFeng committed Mar 6, 2024
commit 6057b1d418f28908537d4e0807fb314b78cf3e8e
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,51 @@ Future<void> main() async {
await expectLater(
debugMessageReceived.future, completion('debug:Debug message'));
});

testWidgets('can receive console log messages with cyclic object value',
(WidgetTester tester) async {
const String testPage = '''
<!DOCTYPE html>
<html>
<head>
<title>WebResourceError test</title>
<script type="text/javascript">
function onLoad() {
const circularReference = { otherData: 123 };
circularReference.myself = circularReference;
console.log(circularReference);
}
</script>
</head>
<body onload="onLoad();">
</html>
''';

final Completer<String> debugMessageReceived = Completer<String>();
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
);
unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));

await controller
.setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) {
debugMessageReceived
.complete('${consoleMessage.level.name}:${consoleMessage.message}');
});

await controller.loadHtmlString(testPage);

await tester.pumpWidget(Builder(
builder: (BuildContext context) {
return PlatformWebViewWidget(
PlatformWebViewWidgetCreationParams(controller: controller),
).build(context);
},
));

await expectLater(debugMessageReceived.future,
completion('log:{"otherData":123,"myself":"[Circular]"}'));
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,11 +630,34 @@ class WebKitWebViewController extends PlatformWebViewController {
}

Future<void> _injectConsoleOverride() {
// Using the replacer parameter of JSON.stringify() to solve the error
// TypeError: JSON.stringify cannot serialize cyclic structures.
// See https://github.com/flutter/flutter/issues/144535.
//
// The getCircularReplacer() taken from
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value.
const WKUserScript overrideScript = WKUserScript(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment briefly explaining the approach at a high level so readers don't need to reverse-engineer what is being done here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

'''
function getCircularReplacer() {
const ancestors = [];
return function (key, value) {
if (typeof value !== "object" || value === null) {
return value;
}
while (ancestors.length > 0 && ancestors.at(-1) !== this) {
ancestors.pop();
}
if (ancestors.includes(value)) {
return "[Circular]";
}
ancestors.push(value);
return value;
};
}

function log(type, args) {
var message = Object.values(args)
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v, getCircularReplacer()) : v.toString())
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
.join(", ");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1378,9 +1378,26 @@ void main() {
expect(overrideConsoleScript.injectionTime,
WKUserScriptInjectionTime.atDocumentStart);
expect(overrideConsoleScript.source, '''
function getCircularReplacer() {
const ancestors = [];
return function (key, value) {
if (typeof value !== "object" || value === null) {
return value;
}
while (ancestors.length > 0 && ancestors.at(-1) !== this) {
ancestors.pop();
}
if (ancestors.includes(value)) {
return "[Circular]";
}
ancestors.push(value);
return value;
};
}

function log(type, args) {
var message = Object.values(args)
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v, getCircularReplacer()) : v.toString())
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
.join(", ");

Expand Down