mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 07:08:10 +08:00
[webview_flutter_wkwebview] Fixes JSON.stringify() cannot serialize cyclic structures (#6274)
Using the `replacer` parameter of `JSON.stringify()` to remove cyclic object to resolve the following error. ``` TypeError: JSON.stringify cannot serialize cyclic structures. ``` ~~Related solution: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value~~ Fixes https://github.com/flutter/flutter/issues/144535
This commit is contained in:
3
AUTHORS
3
AUTHORS
@ -74,4 +74,5 @@ Twin Sun, LLC <google-contrib@twinsunsolutions.com>
|
||||
Amir Panahandeh <amirpanahandeh@gmail.com>
|
||||
Daniele Cambi <dancam.dev@gmail.com>
|
||||
Michele Benedetti <michelebenx98@gmail.com>
|
||||
Taskulu LDA <contributions@taskulu.com>
|
||||
Taskulu LDA <contributions@taskulu.com>
|
||||
LinXunFeng <linxunfeng@yeah.net>
|
||||
|
@ -68,3 +68,4 @@ Maurits van Beusekom <maurits@baseflow.com>
|
||||
Antonino Di Natale <gyorgio88@gmail.com>
|
||||
Nick Bradshaw <nickalasb@gmail.com>
|
||||
The Vinh Luong <ltv.luongthevinh@gmail.com>
|
||||
LinXunFeng <linxunfeng@yeah.net>
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 3.13.1
|
||||
|
||||
* Fixes `JSON.stringify()` cannot serialize cyclic structures.
|
||||
|
||||
## 3.13.0
|
||||
|
||||
* Adds `decidePolicyForNavigationResponse` to internal WKNavigationDelegate to support the
|
||||
|
@ -1525,6 +1525,64 @@ 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 obj1 = {
|
||||
name: "obj1",
|
||||
};
|
||||
const obj2 = {
|
||||
name: "obj2",
|
||||
obj1: obj1,
|
||||
};
|
||||
const obj = {
|
||||
obj1: obj1,
|
||||
obj2: obj2,
|
||||
};
|
||||
obj.self = obj;
|
||||
console.log(obj);
|
||||
}
|
||||
</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:{"obj1":{"name":"obj1"},"obj2":{"name":"obj2","obj1":{"name":"obj1"}}}'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -630,21 +630,53 @@ class WebKitWebViewController extends PlatformWebViewController {
|
||||
}
|
||||
|
||||
Future<void> _injectConsoleOverride() {
|
||||
// Within overrideScript, a series of console output methods such as
|
||||
// console.log will be rewritten to pass the output content to the Flutter
|
||||
// end.
|
||||
//
|
||||
// These output contents will first be serialized through JSON.stringify(),
|
||||
// but if the output content contains cyclic objects, it will encounter the
|
||||
// following error.
|
||||
// TypeError: JSON.stringify cannot serialize cyclic structures.
|
||||
// See https://github.com/flutter/flutter/issues/144535.
|
||||
//
|
||||
// Considering this is just looking at the logs printed via console.log,
|
||||
// the cyclic object is not important, so remove it.
|
||||
// Therefore, the replacer parameter of JSON.stringify() is used and the
|
||||
// removeCyclicObject method is passed in to solve the error.
|
||||
const WKUserScript overrideScript = WKUserScript(
|
||||
'''
|
||||
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 => v.substring(0, 3000)) // Limit msg to 3000 chars
|
||||
.join(", ");
|
||||
var _flutter_webview_plugin_overrides = _flutter_webview_plugin_overrides || {
|
||||
removeCyclicObject: function() {
|
||||
const traversalStack = [];
|
||||
return function (k, v) {
|
||||
if (typeof v !== "object" || v === null) { return v; }
|
||||
const currentParentObj = this;
|
||||
while (
|
||||
traversalStack.length > 0 &&
|
||||
traversalStack[traversalStack.length - 1] !== currentParentObj
|
||||
) {
|
||||
traversalStack.pop();
|
||||
}
|
||||
if (traversalStack.includes(v)) { return; }
|
||||
traversalStack.push(v);
|
||||
return v;
|
||||
};
|
||||
},
|
||||
log: function (type, args) {
|
||||
var message = Object.values(args)
|
||||
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v, _flutter_webview_plugin_overrides.removeCyclicObject()) : v.toString())
|
||||
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
|
||||
.join(", ");
|
||||
|
||||
var log = {
|
||||
level: type,
|
||||
message: message
|
||||
};
|
||||
var log = {
|
||||
level: type,
|
||||
message: message
|
||||
};
|
||||
|
||||
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
||||
}
|
||||
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
||||
}
|
||||
};
|
||||
|
||||
let originalLog = console.log;
|
||||
let originalInfo = console.info;
|
||||
@ -652,11 +684,11 @@ let originalWarn = console.warn;
|
||||
let originalError = console.error;
|
||||
let originalDebug = console.debug;
|
||||
|
||||
console.log = function() { log("log", arguments); originalLog.apply(null, arguments) };
|
||||
console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) };
|
||||
console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) };
|
||||
console.error = function() { log("error", arguments); originalError.apply(null, arguments) };
|
||||
console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) };
|
||||
console.log = function() { _flutter_webview_plugin_overrides.log("log", arguments); originalLog.apply(null, arguments) };
|
||||
console.info = function() { _flutter_webview_plugin_overrides.log("info", arguments); originalInfo.apply(null, arguments) };
|
||||
console.warn = function() { _flutter_webview_plugin_overrides.log("warning", arguments); originalWarn.apply(null, arguments) };
|
||||
console.error = function() { _flutter_webview_plugin_overrides.log("error", arguments); originalError.apply(null, arguments) };
|
||||
console.debug = function() { _flutter_webview_plugin_overrides.log("debug", arguments); originalDebug.apply(null, arguments) };
|
||||
|
||||
window.addEventListener("error", function(e) {
|
||||
log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno);
|
||||
|
@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
|
||||
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
|
||||
version: 3.13.0
|
||||
version: 3.13.1
|
||||
|
||||
environment:
|
||||
sdk: ^3.2.3
|
||||
|
@ -1378,19 +1378,37 @@ void main() {
|
||||
expect(overrideConsoleScript.injectionTime,
|
||||
WKUserScriptInjectionTime.atDocumentStart);
|
||||
expect(overrideConsoleScript.source, '''
|
||||
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 => v.substring(0, 3000)) // Limit msg to 3000 chars
|
||||
.join(", ");
|
||||
var _flutter_webview_plugin_overrides = _flutter_webview_plugin_overrides || {
|
||||
removeCyclicObject: function() {
|
||||
const traversalStack = [];
|
||||
return function (k, v) {
|
||||
if (typeof v !== "object" || v === null) { return v; }
|
||||
const currentParentObj = this;
|
||||
while (
|
||||
traversalStack.length > 0 &&
|
||||
traversalStack[traversalStack.length - 1] !== currentParentObj
|
||||
) {
|
||||
traversalStack.pop();
|
||||
}
|
||||
if (traversalStack.includes(v)) { return; }
|
||||
traversalStack.push(v);
|
||||
return v;
|
||||
};
|
||||
},
|
||||
log: function (type, args) {
|
||||
var message = Object.values(args)
|
||||
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v, _flutter_webview_plugin_overrides.removeCyclicObject()) : v.toString())
|
||||
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
|
||||
.join(", ");
|
||||
|
||||
var log = {
|
||||
level: type,
|
||||
message: message
|
||||
};
|
||||
var log = {
|
||||
level: type,
|
||||
message: message
|
||||
};
|
||||
|
||||
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
||||
}
|
||||
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
||||
}
|
||||
};
|
||||
|
||||
let originalLog = console.log;
|
||||
let originalInfo = console.info;
|
||||
@ -1398,11 +1416,11 @@ let originalWarn = console.warn;
|
||||
let originalError = console.error;
|
||||
let originalDebug = console.debug;
|
||||
|
||||
console.log = function() { log("log", arguments); originalLog.apply(null, arguments) };
|
||||
console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) };
|
||||
console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) };
|
||||
console.error = function() { log("error", arguments); originalError.apply(null, arguments) };
|
||||
console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) };
|
||||
console.log = function() { _flutter_webview_plugin_overrides.log("log", arguments); originalLog.apply(null, arguments) };
|
||||
console.info = function() { _flutter_webview_plugin_overrides.log("info", arguments); originalInfo.apply(null, arguments) };
|
||||
console.warn = function() { _flutter_webview_plugin_overrides.log("warning", arguments); originalWarn.apply(null, arguments) };
|
||||
console.error = function() { _flutter_webview_plugin_overrides.log("error", arguments); originalError.apply(null, arguments) };
|
||||
console.debug = function() { _flutter_webview_plugin_overrides.log("debug", arguments); originalDebug.apply(null, arguments) };
|
||||
|
||||
window.addEventListener("error", function(e) {
|
||||
log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno);
|
||||
|
Reference in New Issue
Block a user