@javascriptcommon/react-native-webview 13.19.0 → 13.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -178,6 +178,7 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
178
178
  public static void enqueueBgScript(String script) {
179
179
  RNCWebView v = EMBED_INSTANCE;
180
180
  if (v != null && script != null) {
181
+ v.noteActivity();
181
182
  v.pendingBgScripts.add(script);
182
183
  v.bgInjectHandler.post(v.drainRunnable); // immediate drain (FIFO)
183
184
  }
@@ -213,10 +214,39 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
213
214
  private static final long KEEP_ALIVE_INTERVAL_MS = 200;
214
215
  private boolean keepAliveRunning = false;
215
216
 
217
+ // The tick only earns its keep while the page has something to do: a track
218
+ // playing (the page reports its position every second), loading, or being
219
+ // driven by the app. Paused, it ran on for as long as the process lived —
220
+ // five evaluations a second, each waking the renderer and the main thread,
221
+ // with the screen off, for nothing. So it stops after this long without any
222
+ // activity (a script from the app, a message from the page) and the next
223
+ // one starts it again, on the same main-thread handler and before that
224
+ // script is drained, so a play or a navigate never lands on a renderer the
225
+ // tick was supposed to be keeping awake.
226
+ private static final long KEEP_ALIVE_IDLE_MS = 60_000;
227
+ private volatile long lastActivityAt = 0;
228
+
229
+ private final Runnable resumeKeepAliveRunnable = new Runnable() {
230
+ @Override
231
+ public void run() { if (contentBlockerEnabled) startKeepAlive(); }
232
+ };
233
+
234
+ /** Something is happening on the embed page: keep (or start) the tick. Any thread. */
235
+ void noteActivity() {
236
+ lastActivityAt = android.os.SystemClock.elapsedRealtime();
237
+ if (contentBlockerEnabled && !keepAliveRunning) {
238
+ bgInjectHandler.postAtFrontOfQueue(resumeKeepAliveRunnable);
239
+ }
240
+ }
241
+
216
242
  private final Runnable keepAliveRunnable = new Runnable() {
217
243
  @Override
218
244
  public void run() {
219
245
  if (!keepAliveRunning) return;
246
+ if (android.os.SystemClock.elapsedRealtime() - lastActivityAt > KEEP_ALIVE_IDLE_MS) {
247
+ stopKeepAlive();
248
+ return;
249
+ }
220
250
  // Throws once the WebView is destroyed; the flag above normally gets
221
251
  // there first, but a destroy racing this tick must not take the app.
222
252
  try { evaluateJavascript("1+1", null); } catch (Throwable ignored) { }
@@ -227,6 +257,7 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
227
257
  private void startKeepAlive() {
228
258
  if (keepAliveRunning) return;
229
259
  keepAliveRunning = true;
260
+ lastActivityAt = android.os.SystemClock.elapsedRealtime();
230
261
  // Logged because there is no way to tell from outside whether the tick
231
262
  // is running: it evaluates a script with no observable effect, which is
232
263
  // the whole point of it. One line at each edge, never per tick.
@@ -457,6 +488,9 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
457
488
  }
458
489
 
459
490
  protected void evaluateJavascriptWithFallback(String script) {
491
+ // The app driving the page (injectJavaScript, the frame-gated channel)
492
+ // is activity too; the keep-alive's own "1+1" does not come through here.
493
+ noteActivity();
460
494
  evaluateJavascript(script, null);
461
495
  }
462
496
 
@@ -486,6 +520,7 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
486
520
  }
487
521
 
488
522
  public void onMessage(String message, String sourceUrl) {
523
+ noteActivity();
489
524
  ThemedReactContext reactContext = getThemedReactContext();
490
525
  RNCWebView mWebView = this;
491
526
 
@@ -576,6 +611,10 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
576
611
  }
577
612
 
578
613
  protected void cleanupCallbacksAndDestroy() {
614
+ // A message from the page racing the teardown must not start the tick
615
+ // again on a destroyed view (noteActivity is gated on this flag).
616
+ contentBlockerEnabled = false;
617
+ bgInjectHandler.removeCallbacks(resumeKeepAliveRunnable);
579
618
  stopKeepAlive();
580
619
  bgInjectHandler.removeCallbacks(drainRunnable);
581
620
  if (EMBED_INSTANCE == this) EMBED_INSTANCE = null;
@@ -315,6 +315,7 @@ RCTAutoInsetsProtocol>
315
315
 
316
316
  - (void)dealloc
317
317
  {
318
+ [self stopKeepWebViewActiveTimer];
318
319
  [[NSNotificationCenter defaultCenter] removeObserver:self];
319
320
  if (@available(iOS 11.0, *)) {
320
321
  [self.webView.configuration.websiteDataStore.httpCookieStore removeObserver:self];
@@ -548,6 +549,35 @@ RCTAutoInsetsProtocol>
548
549
  }
549
550
  }
550
551
 
552
+ // The keep-alive tick belongs to one WKWebView. It used to be created with
553
+ // `target:self` and never invalidated: a scheduled NSTimer retains its target,
554
+ // so destroyWebView (Fabric's prepareForRecycle) left it firing 5 times a
555
+ // second for the rest of the process, kept this view alive with it, and a
556
+ // recycled view that built a new WKWebView started a second one next to it.
557
+ // Every WebView the app ever mounted (the embed engine, each rebuilt
558
+ // extraction page, sign-in and import pages) added a tick that never stopped.
559
+ // Now the timer holds the view weakly, there is at most one per view, and it
560
+ // ends with the WKWebView it keeps alive.
561
+ - (void)startKeepWebViewActiveTimer {
562
+ [self stopKeepWebViewActiveTimer];
563
+ __weak RNCWebViewImpl *weakSelf = self;
564
+ self.keepWebViewActiveTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
565
+ repeats:YES
566
+ block:^(NSTimer *timer) {
567
+ RNCWebViewImpl *strongSelf = weakSelf;
568
+ if (strongSelf == nil) {
569
+ [timer invalidate];
570
+ return;
571
+ }
572
+ [strongSelf _keepWKWebViewActive:timer];
573
+ }];
574
+ }
575
+
576
+ - (void)stopKeepWebViewActiveTimer {
577
+ [self.keepWebViewActiveTimer invalidate];
578
+ self.keepWebViewActiveTimer = nil;
579
+ }
580
+
551
581
  - (void)didMoveToWindow
552
582
  {
553
583
  if (self.window != nil && _webView == nil) {
@@ -612,11 +642,7 @@ RCTAutoInsetsProtocol>
612
642
  [self setKeyboardDisplayRequiresUserAction: _savedKeyboardDisplayRequiresUserAction];
613
643
  [self visitSource];
614
644
 
615
- self.keepWebViewActiveTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
616
- target:self
617
- selector:@selector(_keepWKWebViewActive:)
618
- userInfo:nil
619
- repeats:YES];
645
+ [self startKeepWebViewActiveTimer];
620
646
  }
621
647
 
622
648
  #if !TARGET_OS_OSX
@@ -655,6 +681,7 @@ RCTAutoInsetsProtocol>
655
681
  - (void)removeFromSuperview
656
682
  #endif
657
683
  {
684
+ [self stopKeepWebViewActiveTimer];
658
685
  if (_webView) {
659
686
  [_webView.configuration.userContentController removeScriptMessageHandlerForName:HistoryShimName];
660
687
  [_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHandlerName];
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "Thibault Malbranche <malbranche.thibault@gmail.com>"
11
11
  ],
12
12
  "license": "MIT",
13
- "version": "13.19.0",
13
+ "version": "13.19.1",
14
14
  "homepage": "https://github.com/react-native-webview/react-native-webview#readme",
15
15
  "scripts": {
16
16
  "android": "react-native run-android",