Skip to content

fix(flutter): Guard script completion#3912

Merged
buenaflor merged 5 commits into
mainfrom
fix/web-script-double-completion
Jul 21, 2026
Merged

fix(flutter): Guard script completion#3912
buenaflor merged 5 commits into
mainfrom
fix/web-script-double-completion

Conversation

@buenaflor

@buenaflor buenaflor commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

📜 Description

The Flutter web script loader now settles its load future at most once and cancels both DOM event subscriptions afterward. Browser regression tests cover both load-then-error and error-then-load delivery.

💡 Motivation and Context

Browsers can deliver more than one terminal event for the same script element. The second event previously completed the same Completer again, surfacing StateError: Bad state: Future already completed as an SDK-originated fatal event. Keeping the subscriptions active after settlement also retained their callbacks and captured state for the lifetime of the script element.

Closes #3906

💚 How did you test it?

Temporarily restored the original implementation and ran the browser suite. Both regression tests failed with Bad state: Future already completed; with the fix restored, all browser tests passed.

📝 Checklist

  • I reviewed submitted code
  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPii is enabled
  • I updated the docs if needed
  • All tests passing
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec
  • No breaking changes

Ignore repeated load and error events after the web script loader.

Prevent SDK-originated StateError events when browsers redeliver
terminal script events.

Fixes #3906
Co-Authored-By: Codex <[email protected]>
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


Features

  • (replay) Record segment names on Android by buenaflor in #3897
  • (tracing) Emit segment name source by buenaflor in #3904

Fixes

Flutter

  • Guard script completion by buenaflor in #3912
  • Add app start screen attribute by buenaflor in #3893
  • Prevent StateError when delayed frames list is empty by muhammadkamel in #3876

Other

  • (metrics) Add missing metric byte outcomes by buenaflor in #3905

Dependencies

Deps

  • chore(deps): update Native SDK to v0.15.4 by github-actions in #3910
  • chore(deps): update Android SDK to v8.49.0 by github-actions in #3895
  • chore(deps): update Native SDK to v0.15.3 by github-actions in #3862
  • chore(deps): update Cocoa SDK to v8.58.4 by github-actions in #3864
  • chore(deps): update Android SDK to v8.48.0 by github-actions in #3873

Internal Changes

  • (dart) Accept beta wasm function name by sentry-junior in #3898

🤖 This preview updates automatically when you update the PR.

@buenaflor
buenaflor marked this pull request as ready for review July 21, 2026 09:05
@buenaflor
buenaflor requested a review from denrase as a code owner July 21, 2026 09:05
Copilot AI review requested due to automatic review settings July 21, 2026 09:05
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.90%. Comparing base (a28f929) to head (ab59bbb).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3912      +/-   ##
==========================================
+ Coverage   87.56%   91.90%   +4.33%     
==========================================
  Files         338      105     -233     
  Lines       12345     3828    -8517     
==========================================
- Hits        10810     3518    -7292     
+ Misses       1535      310    -1225     
Flag Coverage Δ
sentry ?
sentry_dio ?
sentry_drift ?
sentry_file ?
sentry_firebase_remote_config 100.00% <ø> (ø)
sentry_flutter 91.52% <ø> (ø)
sentry_grpc ?
sentry_hive ?
sentry_isar ?
sentry_link ?
sentry_logging ?
sentry_sqflite ?
sentry_supabase 97.27% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a Flutter Web regression where a <script> element can emit more than one terminal event (load/error), previously causing Completer double-completion and surfacing StateError: Bad state: Future already completed as an SDK-originated fatal event.

Changes:

  • Guard Completer completion in the web script DOM loader so subsequent terminal events are ignored.
  • Add browser regression tests covering load → error and error → load event delivery.
  • Add package:web/web.dart import to support DOM event dispatching in tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/flutter/lib/src/web/script_loader/web_script_dom_api.dart Adds isCompleted guards around onLoad/onError handlers to prevent double completion.
packages/flutter/test/web/sentry_script_loader_test.dart Adds Chrome/browser tests ensuring second terminal event dispatch does not throw and future settlement is stable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Keep package:web usage in the existing browser test utility.

Use the same DOM abstraction as neighboring script loader tests.

Co-Authored-By: Codex <[email protected]>
Comment thread packages/flutter/lib/src/web/script_loader/web_script_dom_api.dart Outdated
buenaflor and others added 2 commits July 21, 2026 11:15
Name the browser helper after the event it dispatches without implying script ordering.

Co-Authored-By: Codex <[email protected]>
Cancel both DOM event subscriptions after the script load future settles.

Release their callbacks and captured state.

Co-Authored-By: Codex <[email protected]>
@github-actions

Copy link
Copy Markdown
Contributor

Android Performance metrics 🚀

  Plain With Sentry Diff
Startup time 357.22 ms 346.12 ms -11.10 ms
Size 14.55 MiB 15.87 MiB 1.31 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
9993fa9 389.44 ms 390.85 ms 1.41 ms
c741aa5 360.67 ms 367.86 ms 7.19 ms
cdf371b 367.64 ms 377.02 ms 9.38 ms
ebccb5b 363.79 ms 371.10 ms 7.31 ms
6287dc4 390.30 ms 399.12 ms 8.82 ms
51520fc 351.89 ms 349.79 ms -2.10 ms
3fe9e80 362.67 ms 376.19 ms 13.52 ms
d8f332d 365.31 ms 349.98 ms -15.33 ms
819c1e7 449.80 ms 442.98 ms -6.82 ms
29e8ebe 389.91 ms 395.76 ms 5.84 ms

App size

Revision Plain With Sentry Diff
9993fa9 14.55 MiB 15.87 MiB 1.31 MiB
c741aa5 14.55 MiB 15.87 MiB 1.31 MiB
cdf371b 13.93 MiB 15.18 MiB 1.25 MiB
ebccb5b 13.93 MiB 15.18 MiB 1.25 MiB
6287dc4 13.93 MiB 15.18 MiB 1.25 MiB
51520fc 13.93 MiB 15.18 MiB 1.25 MiB
3fe9e80 14.55 MiB 15.87 MiB 1.31 MiB
d8f332d 14.55 MiB 15.87 MiB 1.31 MiB
819c1e7 13.93 MiB 15.00 MiB 1.06 MiB
29e8ebe 13.93 MiB 15.06 MiB 1.13 MiB

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

iOS Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1264.85 ms 1262.38 ms -2.47 ms
Size 5.83 MiB 6.28 MiB 460.78 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
af96ef2 1260.79 ms 1259.61 ms -1.17 ms
b6c8720 1252.65 ms 1266.61 ms 13.96 ms
af4c7c1 1246.85 ms 1255.35 ms 8.50 ms
8af916c 1246.66 ms 1240.79 ms -5.87 ms
d0aa4b6 1268.23 ms 1268.39 ms 0.15 ms
91629ec 1257.91 ms 1255.93 ms -1.98 ms
c002f00 1252.47 ms 1258.78 ms 6.31 ms
a34fde0 1255.35 ms 1254.31 ms -1.03 ms
3135a81 1258.11 ms 1247.48 ms -10.63 ms
d5a1225 1252.46 ms 1254.66 ms 2.20 ms

App size

Revision Plain With Sentry Diff
af96ef2 5.53 MiB 6.02 MiB 501.31 KiB
b6c8720 7.86 MiB 9.44 MiB 1.58 MiB
af4c7c1 5.83 MiB 6.28 MiB 460.73 KiB
8af916c 5.73 MiB 6.18 MiB 463.84 KiB
d0aa4b6 5.53 MiB 6.02 MiB 502.04 KiB
91629ec 5.83 MiB 6.28 MiB 459.63 KiB
c002f00 5.65 MiB 6.09 MiB 448.38 KiB
a34fde0 5.66 MiB 6.10 MiB 451.57 KiB
3135a81 5.73 MiB 6.17 MiB 453.76 KiB
d5a1225 5.73 MiB 6.17 MiB 453.73 KiB

@buenaflor
buenaflor enabled auto-merge (squash) July 21, 2026 09:49
@buenaflor
buenaflor merged commit f07030a into main Jul 21, 2026
67 of 69 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ab59bbb. Configure here.

completer.completeError('Failed to load $src');
cancelSubscriptions();
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Subscriptions leak on TT failure

Low Severity

Flagging per the Dart resource-disposal guidance in the review rules: load/error subscriptions are created before the Trusted Types try/catch, but cancelSubscriptions is not called when TrustedTypesException is thrown. The listener closures capture those subscriptions, so the unattached HTMLScriptElement can stay retained after policy creation fails.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot (Root)

Reviewed by Cursor Bugbot for commit ab59bbb. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web: loadScript double-completes its Completer — 'Bad state: Future already completed' reported as fatal

3 participants