-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[webview_flutter_web] Add WebWebViewController.runJavaScript #9835
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces the runJavaScript
method for the web implementation of WebView
and refactors loadHtmlString
to better support the baseUrl
parameter. The changes are generally good, but there are a few areas for improvement regarding event listener management and robustness against race conditions. I've identified a potential memory leak in loadHtmlString
and a high-severity issue in _getIFrameDocument
that could lead to an infinite loop. I've provided suggestions to address these issues by using one-time event listeners and replacing polling with a more robust event-based approach.
Future<web.Document> _getIFrameDocument() async { | ||
while (_webWebViewParams.iFrame.contentDocument == null) { | ||
await Future<void>.delayed(_webWebViewParams._iFrameWaitDelay); | ||
} | ||
|
||
return _webWebViewParams.iFrame.contentDocument!; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The polling mechanism in _getIFrameDocument
can lead to an infinite loop if it's called before any content has been loaded (e.g., on a newly created controller). In this scenario, the iframe's src
is empty, contentDocument
remains null
, and the while
loop never terminates.
Instead of polling, it's more robust to use the load
event to wait for the document to be ready. If no src
is set, you can default to loading about:blank
to ensure a document context is always available for methods like runJavaScript
.
Future<web.Document> _getIFrameDocument() async {
// If the document is not yet available, wait for the 'load' event.
if (_webWebViewParams.iFrame.contentDocument == null) {
final Completer<void> completer = Completer<void>();
_webWebViewParams.iFrame.addEventListener(
'load',
(web.Event _) {
completer.complete();
}.toJS,
AddEventListenerOptions(once: true),
);
// If src is not set, the iframe will never load.
if (_webWebViewParams.iFrame.src.isEmpty) {
_webWebViewParams.iFrame.src = 'about:blank';
}
await completer.future;
}
return _webWebViewParams.iFrame.contentDocument!;
}
_webWebViewParams.iFrame.addEventListener( | ||
'load', | ||
() { | ||
_webWebViewParams.iFrame.contentDocument?.write(html.toJS); | ||
loading.complete(); | ||
}.toJS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The load
event listener is added every time loadHtmlString
is called but is never removed. This can lead to a memory leak if this method is called multiple times on the same controller, as multiple listeners will be attached.
To ensure the listener is only called once per loadHtmlString
call, you can use the once
option in addEventListener
. This will automatically remove the listener after it has been invoked.
_webWebViewParams.iFrame.addEventListener(
'load',
(web.Event _) {
_webWebViewParams.iFrame.contentDocument?.write(html.toJS);
loading.complete();
}.toJS,
AddEventListenerOptions(once: true),
);
script.addEventListener( | ||
'load', | ||
() { | ||
body.removeChild(script); | ||
run.complete(); | ||
}.toJS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The load
event listener on the script element is only intended to fire once. It's good practice to make this explicit by using the once: true
option in addEventListener
. This also ensures the listener is removed automatically, preventing any potential for lingering listeners, even though the script element is removed from the DOM.
script.addEventListener(
'load',
(web.Event _) {
body.removeChild(script);
run.complete();
}.toJS,
AddEventListenerOptions(once: true),
);
This PR adds an implementation for
WebWebViewController.runJavaScript
and changes the wayWebWebViewController.loadHtmlString
injects HTML code while also adding support for thebaseUrl
option.The following issues are fixed:
runJavaScript
for Web when the content has the same origin flutter#173899Pre-Review Checklist
[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or I have commented below to indicate which version change exemption this PR falls under1.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style, or I have commented below to indicate which CHANGELOG exemption this PR falls under1.///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assist
bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2 ↩3