mirror of
https://github.com/flutter/packages.git
synced 2025-07-02 08:34:31 +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:
1
AUTHORS
1
AUTHORS
@ -75,3 +75,4 @@ Amir Panahandeh <amirpanahandeh@gmail.com>
|
|||||||
Daniele Cambi <dancam.dev@gmail.com>
|
Daniele Cambi <dancam.dev@gmail.com>
|
||||||
Michele Benedetti <michelebenx98@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>
|
Antonino Di Natale <gyorgio88@gmail.com>
|
||||||
Nick Bradshaw <nickalasb@gmail.com>
|
Nick Bradshaw <nickalasb@gmail.com>
|
||||||
The Vinh Luong <ltv.luongthevinh@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
|
## 3.13.0
|
||||||
|
|
||||||
* Adds `decidePolicyForNavigationResponse` to internal WKNavigationDelegate to support the
|
* Adds `decidePolicyForNavigationResponse` to internal WKNavigationDelegate to support the
|
||||||
|
@ -1525,6 +1525,64 @@ Future<void> main() async {
|
|||||||
await expectLater(
|
await expectLater(
|
||||||
debugMessageReceived.future, completion('debug:Debug message'));
|
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() {
|
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(
|
const WKUserScript overrideScript = WKUserScript(
|
||||||
'''
|
'''
|
||||||
function log(type, args) {
|
var _flutter_webview_plugin_overrides = _flutter_webview_plugin_overrides || {
|
||||||
var message = Object.values(args)
|
removeCyclicObject: function() {
|
||||||
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
|
const traversalStack = [];
|
||||||
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
|
return function (k, v) {
|
||||||
.join(", ");
|
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 = {
|
var log = {
|
||||||
level: type,
|
level: type,
|
||||||
message: message
|
message: message
|
||||||
};
|
};
|
||||||
|
|
||||||
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let originalLog = console.log;
|
let originalLog = console.log;
|
||||||
let originalInfo = console.info;
|
let originalInfo = console.info;
|
||||||
@ -652,11 +684,11 @@ let originalWarn = console.warn;
|
|||||||
let originalError = console.error;
|
let originalError = console.error;
|
||||||
let originalDebug = console.debug;
|
let originalDebug = console.debug;
|
||||||
|
|
||||||
console.log = function() { log("log", arguments); originalLog.apply(null, arguments) };
|
console.log = function() { _flutter_webview_plugin_overrides.log("log", arguments); originalLog.apply(null, arguments) };
|
||||||
console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) };
|
console.info = function() { _flutter_webview_plugin_overrides.log("info", arguments); originalInfo.apply(null, arguments) };
|
||||||
console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) };
|
console.warn = function() { _flutter_webview_plugin_overrides.log("warning", arguments); originalWarn.apply(null, arguments) };
|
||||||
console.error = function() { log("error", arguments); originalError.apply(null, arguments) };
|
console.error = function() { _flutter_webview_plugin_overrides.log("error", arguments); originalError.apply(null, arguments) };
|
||||||
console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) };
|
console.debug = function() { _flutter_webview_plugin_overrides.log("debug", arguments); originalDebug.apply(null, arguments) };
|
||||||
|
|
||||||
window.addEventListener("error", function(e) {
|
window.addEventListener("error", function(e) {
|
||||||
log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno);
|
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.
|
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
|
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
|
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:
|
environment:
|
||||||
sdk: ^3.2.3
|
sdk: ^3.2.3
|
||||||
|
@ -1378,19 +1378,37 @@ void main() {
|
|||||||
expect(overrideConsoleScript.injectionTime,
|
expect(overrideConsoleScript.injectionTime,
|
||||||
WKUserScriptInjectionTime.atDocumentStart);
|
WKUserScriptInjectionTime.atDocumentStart);
|
||||||
expect(overrideConsoleScript.source, '''
|
expect(overrideConsoleScript.source, '''
|
||||||
function log(type, args) {
|
var _flutter_webview_plugin_overrides = _flutter_webview_plugin_overrides || {
|
||||||
var message = Object.values(args)
|
removeCyclicObject: function() {
|
||||||
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
|
const traversalStack = [];
|
||||||
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
|
return function (k, v) {
|
||||||
.join(", ");
|
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 = {
|
var log = {
|
||||||
level: type,
|
level: type,
|
||||||
message: message
|
message: message
|
||||||
};
|
};
|
||||||
|
|
||||||
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let originalLog = console.log;
|
let originalLog = console.log;
|
||||||
let originalInfo = console.info;
|
let originalInfo = console.info;
|
||||||
@ -1398,11 +1416,11 @@ let originalWarn = console.warn;
|
|||||||
let originalError = console.error;
|
let originalError = console.error;
|
||||||
let originalDebug = console.debug;
|
let originalDebug = console.debug;
|
||||||
|
|
||||||
console.log = function() { log("log", arguments); originalLog.apply(null, arguments) };
|
console.log = function() { _flutter_webview_plugin_overrides.log("log", arguments); originalLog.apply(null, arguments) };
|
||||||
console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) };
|
console.info = function() { _flutter_webview_plugin_overrides.log("info", arguments); originalInfo.apply(null, arguments) };
|
||||||
console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) };
|
console.warn = function() { _flutter_webview_plugin_overrides.log("warning", arguments); originalWarn.apply(null, arguments) };
|
||||||
console.error = function() { log("error", arguments); originalError.apply(null, arguments) };
|
console.error = function() { _flutter_webview_plugin_overrides.log("error", arguments); originalError.apply(null, arguments) };
|
||||||
console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) };
|
console.debug = function() { _flutter_webview_plugin_overrides.log("debug", arguments); originalDebug.apply(null, arguments) };
|
||||||
|
|
||||||
window.addEventListener("error", function(e) {
|
window.addEventListener("error", function(e) {
|
||||||
log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno);
|
log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno);
|
||||||
|
Reference in New Issue
Block a user