| @@ -346,8 +346,10 @@ | ||
| 346 | 346 | |
| 347 | 347 | // Restore widgets to initial unloaded state when they are scrolled out of view. |
| 348 | 348 | jetpackUnloadScrolledOutWidgets(); |
| 349 | 349 | |
| 350 | + jetpackObserveUnloadedWidgets(); | |
| 351 | + | |
| 350 | 352 | var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView(); |
| 351 | 353 | |
| 352 | 354 | if ( unloadedWidgetsInView.length > 0 ) { |
| 353 | 355 | // Grab any unloaded widgets for a batch request |
| @@ -411,13 +413,26 @@ | ||
| 411 | 413 | wrapper.classList.remove( 'jetpack-likes-widget-unloaded' ); |
| 412 | 414 | wrapper.classList.add( 'jetpack-likes-widget-loading' ); |
| 413 | 415 | |
| 414 | 416 | wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => { |
| 417 | + // A widget scrolled out of view mid-load has its iframe dropped, and a reloaded one | |
| 418 | + // replaces it, either of which leaves this closure holding a detached iframe. Acting on | |
| 419 | + // it would hide a placeholder that is now the only thing the widget has left to show. | |
| 420 | + if ( ! wrapper.contains( e.target ) ) { | |
| 421 | + return; | |
| 422 | + } | |
| 423 | + | |
| 415 | 424 | JetpackLikesPostMessage( |
| 416 | 425 | { event: 'loadLikeWidget', name: e.target.name, width: e.target.width }, |
| 417 | 426 | window.frames[ 'likes-master' ] |
| 418 | 427 | ); |
| 419 | 428 | |
| 429 | + // The stylesheet is otherwise the only thing that hides the placeholder, so one that never | |
| 430 | + // applies leaves "Loading…" sitting over a widget that loaded fine. | |
| 431 | + if ( placeholder ) { | |
| 432 | + placeholder.style.display = 'none'; | |
| 433 | + } | |
| 434 | + | |
| 420 | 435 | wrapper.classList.remove( 'jetpack-likes-widget-loading' ); |
| 421 | 436 | wrapper.classList.add( 'jetpack-likes-widget-loaded' ); |
| 422 | 437 | } ); |
| 423 | 438 | } |
| @@ -451,8 +466,16 @@ | ||
| 451 | 466 | widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' ); |
| 452 | 467 | widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' ); |
| 453 | 468 | widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' ); |
| 454 | 469 | |
| 470 | + // An empty string removes the inline declaration rather than setting one, handing the | |
| 471 | + // placeholder back to the stylesheet. The `display: none` set on load would otherwise | |
| 472 | + // outrank it, leaving an unloaded widget with neither an iframe nor a placeholder. | |
| 473 | + const placeholder = widgetWrapper.querySelector( '.likes-widget-placeholder' ); | |
| 474 | + if ( placeholder ) { | |
| 475 | + placeholder.style.display = ''; | |
| 476 | + } | |
| 477 | + | |
| 455 | 478 | // Remove it from the list of loaded widgets. |
| 456 | 479 | jetpackCommentLikesLoadedWidgets.splice( i, 1 ); |
| 457 | 480 | |
| 458 | 481 | // Remove comment like widget iFrame. |
| @@ -469,8 +492,33 @@ | ||
| 469 | 492 | }; |
| 470 | 493 | }; |
| 471 | 494 | |
| 472 | 495 | var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler ); |
| 496 | + | |
| 497 | +// Scrolling is not the only thing that brings a widget into range. A stylesheet that lands late | |
| 498 | +// reflows the page without firing a scroll event, and the queue would never look at that widget | |
| 499 | +// again, leaving it on "Loading…" until the reader happens to scroll. | |
| 500 | +var jetpackLikesWidgetObserver = | |
| 501 | + typeof IntersectionObserver === 'function' | |
| 502 | + ? new IntersectionObserver( jetpackOnScrollStopped, { | |
| 503 | + rootMargin: `${ jetpackLikesLookAhead }px`, | |
| 504 | + // jetpackIsScrolledIntoView() wants the widget fully inside the band, so ask to be | |
| 505 | + // told when it gets there - crossing into partial overlap alone would not load it. | |
| 506 | + threshold: [ 0, 1 ], | |
| 507 | + } ) | |
| 508 | + : null; | |
| 509 | + | |
| 510 | +// Observing an element twice is a no-op, so every queue pass can call this to pick up widgets | |
| 511 | +// added after load. | |
| 512 | +function jetpackObserveUnloadedWidgets() { | |
| 513 | + if ( ! jetpackLikesWidgetObserver ) { | |
| 514 | + return; | |
| 515 | + } | |
| 516 | + | |
| 517 | + document | |
| 518 | + .querySelectorAll( 'div.jetpack-likes-widget-unloaded' ) | |
| 519 | + .forEach( widget => jetpackLikesWidgetObserver.observe( widget ) ); | |
| 520 | +} | |
| 473 | 521 | |
| 474 | 522 | // Load initial batch of widgets, prior to any scrolling events. |
| 475 | 523 | JetpackLikesWidgetQueueHandler(); |
| 476 | 524 | |