PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
← All changes | modules/likes/queuehandler.js +96 -102 13.6.216.3-a.1 View file →
@@ -23,9 +23,9 @@
23 23 function JetpackLikesPostMessage( message, target ) {
24 24 if ( typeof message === 'string' ) {
25 25 try {
26 26 message = JSON.parse( message );
27 - } catch ( e ) {
27 + } catch {
28 28 return;
29 29 }
30 30 }
31 31
@@ -37,10 +37,10 @@
37 37 data: message,
38 38 } ),
39 39 '*'
40 40 );
41 - } catch ( e ) {
42 - return;
41 + } catch {
42 + // Ignore error
43 43 }
44 44 }
45 45 }
46 46
@@ -93,9 +93,9 @@
93 93 let message = event && event.data;
94 94 if ( typeof message === 'string' ) {
95 95 try {
96 96 message = JSON.parse( message );
97 - } catch ( err ) {
97 + } catch {
98 98 return;
99 99 }
100 100 }
101 101
@@ -130,12 +130,12 @@
130 130 JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] );
131 131 }
132 132
133 133 stylesData.textStyles = {
134 - color: sdTextColorStyles[ 'color' ],
134 + color: sdTextColorStyles.color,
135 135 fontFamily: sdTextColorStyles[ 'font-family' ],
136 136 fontSize: sdTextColorStyles[ 'font-size' ],
137 - direction: sdTextColorStyles[ 'direction' ],
137 + direction: sdTextColorStyles.direction,
138 138 fontWeight: sdTextColorStyles[ 'font-weight' ],
139 139 fontStyle: sdTextColorStyles[ 'font-style' ],
140 140 textDecoration: sdTextColorStyles[ 'text-decoration' ],
141 141 };
@@ -140,9 +140,9 @@
140 140 textDecoration: sdTextColorStyles[ 'text-decoration' ],
141 141 };
142 142
143 143 stylesData.linkStyles = {
144 - color: sdLinkColorStyles[ 'color' ],
144 + color: sdLinkColorStyles.color,
145 145 fontFamily: sdLinkColorStyles[ 'font-family' ],
146 146 fontSize: sdLinkColorStyles[ 'font-size' ],
147 147 textDecoration: sdLinkColorStyles[ 'text-decoration' ],
148 148 fontWeight: sdLinkColorStyles[ 'font-weight' ],
@@ -155,23 +155,17 @@
155 155 } );
156 156
157 157 break;
158 158
159 - case 'showLikeWidget': {
160 - const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` );
161 - if ( placeholder ) {
162 - placeholder.style.display = 'none';
163 - }
159 + // We're keeping this for planned future follow ups.
160 + // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
161 + case 'showLikeWidget':
164 162 break;
165 - }
166 163
167 - case 'showCommentLikeWidget': {
168 - const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` );
169 - if ( placeholder ) {
170 - placeholder.style.display = 'none';
171 - }
164 + // We're keeping this for planned future follow ups.
165 + // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
166 + case 'showCommentLikeWidget':
172 167 break;
173 - }
174 168
175 169 case 'killCommentLikes':
176 170 // If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders.
177 171 document
@@ -196,24 +190,16 @@
196 190 if ( ! container ) {
197 191 break;
198 192 }
199 193
200 - const newLayout = container.classList.contains( 'wpl-new-layout' );
201 -
202 194 const list = container.querySelector( 'ul' );
203 195
204 196 container.style.display = 'none';
205 197 list.innerHTML = '';
206 198
207 - if ( newLayout ) {
208 - container
209 - .querySelectorAll( '.likes-text span' )
210 - .forEach( item => ( item.textContent = data.totalLikesLabel ) );
211 - } else {
212 - container
213 - .querySelectorAll( '.likes-text span' )
214 - .forEach( item => ( item.textContent = data.total ) );
215 - }
199 + container
200 + .querySelectorAll( '.likes-text span' )
201 + .forEach( item => ( item.textContent = data.totalLikesLabel ) );
216 202
217 203 ( data.likers || [] ).forEach( async ( liker, index ) => {
218 204 if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) {
219 205 // We only display gravatars with http or https schema
@@ -222,35 +208,22 @@
222 208
223 209 const element = document.createElement( 'li' );
224 210 list.append( element );
225 211
226 - if ( newLayout ) {
227 - element.innerHTML = `
228 - <a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker">
229 - <img src="${ encodeURI( liker.avatar_URL ) }"
212 + const profileLink = encodeURI( liker.profile_URL );
213 + const avatarLink = encodeURI( liker.avatar_URL );
214 + element.innerHTML = `<a href="${ profileLink }" rel="nofollow" target="_parent" class="wpl-liker">
215 + <img src="${ avatarLink }"
230 216 alt=""
231 217 style="width: 28px; height: 28px;" />
232 218 <span></span>
233 - </a>
234 - `;
235 - } else {
236 - element.innerHTML = `
237 - <a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker">
238 - <img src="${ encodeURI( liker.avatar_URL ) }"
239 - alt=""
240 - style="width: 30px; height: 30px; padding-right: 3px;" />
241 - </a>
242 - `;
243 - }
219 + </a>`;
244 220
245 221 // Add some extra attributes through native methods, to ensure strings are sanitized.
246 222 element.classList.add( liker.css_class );
247 223 element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name );
224 + element.querySelector( 'span' ).innerText = liker.name;
248 225
249 - if ( newLayout ) {
250 - element.querySelector( 'span' ).innerText = liker.name;
251 - }
252 -
253 226 if ( index === data.likers.length - 1 ) {
254 227 element.addEventListener( 'keydown', e => {
255 228 if ( e.key === 'Tab' && ! e.shiftKey ) {
256 229 e.preventDefault();
@@ -276,64 +249,36 @@
276 249 top: rect.top + win.pageYOffset,
277 250 left: rect.left + win.pageXOffset,
278 251 };
279 252
280 - let containerLeft = 0;
281 - if ( newLayout ) {
282 - container.style.top = offset.top + data.position.top - 1 + 'px';
253 + let containerLeft;
254 + container.style.top = offset.top + data.position.top - 1 + 'px';
283 255
284 - if ( isRtl ) {
285 - const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0;
286 - // 24px is the width of the avatar + 4px is the padding between avatars
287 - containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4;
288 - container.style.transform = 'translateX(-100%)';
289 - } else {
290 - containerLeft = offset.left + data.position.left;
291 - }
292 - container.style.left = containerLeft + 'px';
256 + if ( isRtl ) {
257 + const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0;
258 + // 24px is the width of the avatar + 4px is the padding between avatars
259 + containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4;
260 + container.style.transform = 'translateX(-100%)';
293 261 } else {
294 - container.style.left = offset.left + data.position.left - 10 + 'px';
295 - container.style.top = offset.top + data.position.top - 33 + 'px';
262 + containerLeft = offset.left + data.position.left;
296 263 }
264 + container.style.left = containerLeft + 'px';
297 265
298 - // Container width - padding
299 - const initContainerWidth = data.width - 20;
300 - const rowLength = Math.floor( initContainerWidth / 37 );
301 - // # of rows + (avatar + avatar padding) + text above + container padding
302 - let height = Math.ceil( data.likers.length / rowLength ) * 37 + 17 + 22;
303 - if ( height > 204 ) {
304 - height = 204;
305 - }
266 + // If the popup overflows viewport width, we should show it on the next line.
267 + // Push it offscreen to calculated rendered width.
268 + container.style.left = '-9999px';
269 + container.style.display = 'block';
306 270
307 - if ( ! newLayout ) {
308 - // Avatars + padding
309 - const containerWidth = rowLength * 37 + 13;
310 - container.style.height = height + 'px';
311 - container.style.width = containerWidth + 'px';
312 -
313 - const listWidth = rowLength * 37;
314 - list.style.width = listWidth + 'px';
271 + // If the popup exceeds the viewport width,
272 + // flip the position of the popup.
273 + const containerWidth = container.offsetWidth;
274 + const containerRight = containerLeft + containerWidth;
275 + if ( containerRight > win.innerWidth ) {
276 + containerLeft = rect.right - containerWidth;
315 277 }
316 278
317 - // If the popup is overflows viewport width, we should show it on the next line
318 - if ( newLayout ) {
319 - // Push it offscreen to calculated rendered width
320 - container.style.left = '-9999px';
321 - container.style.display = 'block';
322 -
323 - // If the popup exceeds the viewport width,
324 - // flip the position of the popup.
325 - const containerWidth = container.offsetWidth;
326 - const containerRight = containerLeft + containerWidth;
327 - if ( containerRight > win.innerWidth ) {
328 - containerLeft = rect.right - containerWidth;
329 - }
330 -
331 - // Set the container left
332 - container.style.left = containerLeft + 'px';
333 - } else {
334 - container.style.display = 'block';
335 - }
279 + // Set the container left
280 + container.style.left = containerLeft + 'px';
336 281 container.setAttribute( 'aria-hidden', 'false' );
337 282 };
338 283
339 284 positionPopup();
@@ -388,8 +333,14 @@
388 333 function JetpackLikesWidgetQueueHandler() {
389 334 var wrapperID;
390 335
391 336 if ( ! jetpackLikesMasterReady ) {
337 + // The master iframe emits `masterReady` a single time, when it finishes loading. On
338 + // script-heavy pages it can finish loading — and emit — before this script attaches its
339 + // message listener above, so that one event is missed and the queue never starts. Ping the
340 + // master iframe while we wait so it re-emits `masterReady` once both it and our listener
341 + // are ready.
342 + JetpackLikesPostMessage( { event: 'queryMasterReady' }, window.frames[ 'likes-master' ] );
392 343 setTimeout( JetpackLikesWidgetQueueHandler, 500 );
393 344 return;
394 345 }
395 346
@@ -395,8 +346,10 @@
395 346
396 347 // Restore widgets to initial unloaded state when they are scrolled out of view.
397 348 jetpackUnloadScrolledOutWidgets();
398 349
350 + jetpackObserveUnloadedWidgets();
351 +
399 352 var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView();
400 353
401 354 if ( unloadedWidgetsInView.length > 0 ) {
402 355 // Grab any unloaded widgets for a batch request
@@ -460,13 +413,26 @@
460 413 wrapper.classList.remove( 'jetpack-likes-widget-unloaded' );
461 414 wrapper.classList.add( 'jetpack-likes-widget-loading' );
462 415
463 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 +
464 424 JetpackLikesPostMessage(
465 425 { event: 'loadLikeWidget', name: e.target.name, width: e.target.width },
466 426 window.frames[ 'likes-master' ]
467 427 );
468 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 +
469 435 wrapper.classList.remove( 'jetpack-likes-widget-loading' );
470 436 wrapper.classList.add( 'jetpack-likes-widget-loaded' );
471 437 } );
472 438 }
@@ -500,12 +466,15 @@
500 466 widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' );
501 467 widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' );
502 468 widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' );
503 469
504 - // Bring back the loading placeholder into view.
505 - widgetWrapper
506 - .querySelectorAll( '.comment-likes-widget-placeholder' )
507 - .forEach( item => ( item.style.display = 'block' ) );
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 + }
508 477
509 478 // Remove it from the list of loaded widgets.
510 479 jetpackCommentLikesLoadedWidgets.splice( i, 1 );
511 480
@@ -523,8 +492,33 @@
523 492 };
524 493 };
525 494
526 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 +}
527 521
528 522 // Load initial batch of widgets, prior to any scrolling events.
529 523 JetpackLikesWidgetQueueHandler();
530 524