Skip to content
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

MWPW-156749: Fix video CLS #2849

Merged
merged 14 commits into from
Sep 17, 2024
34 changes: 7 additions & 27 deletions libs/blocks/adobetv/adobetv.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { createIntersectionObserver } from '../../utils/utils.js';
import { applyHoverPlay, getVideoAttrs } from '../../utils/decorate.js';
import { decorateAnchorVideo } from '../../utils/decorate.js';

const ROOT_MARGIN = 1000;

const loadAdobeTv = (a) => {
export default function init(a) {
a.classList.add('hide-video');
const bgBlocks = ['aside', 'marquee', 'hero-marquee'];
if (a.href.includes('.mp4') && bgBlocks.some((b) => a.closest(`.${b}`))) {
a.classList.add('hide');
const { href, hash, dataset } = a;
const attrs = getVideoAttrs(hash || 'autoplay', dataset);
const video = `<video ${attrs}>
<source src="${href}" type="video/mp4" />
</video>`;
if (!a.parentNode) return;
a.insertAdjacentHTML('afterend', video);
const videoElem = document.body.querySelector(`source[src="${href}"]`)?.parentElement;
applyHoverPlay(videoElem);
a.remove();
decorateAnchorVideo({
src: a.href,
anchorTag: a,
});
} else {
const embed = `<div class="milo-video">
<iframe src="${a.href}" class="adobetv" webkitallowfullscreen mozallowfullscreen allowfullscreen scrolling="no" allow="encrypted-media" title="Adobe Video Publishing Cloud Player" loading="lazy">
Expand All @@ -25,17 +18,4 @@ const loadAdobeTv = (a) => {
a.insertAdjacentHTML('afterend', embed);
a.remove();
}
};

export default function init(a) {
a.classList.add('hide-video');
if (a.textContent.includes('no-lazy')) {
loadAdobeTv(a);
} else {
createIntersectionObserver({
el: a,
options: { rootMargin: `${ROOT_MARGIN}px` },
callback: loadAdobeTv,
});
}
}
36 changes: 18 additions & 18 deletions libs/blocks/figure/figure.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { applyHoverPlay, getVideoAttrs } from '../../utils/decorate.js';
import { applyHoverPlay, decorateAnchorVideo } from '../../utils/decorate.js';
import { createTag } from '../../utils/utils.js';

function buildCaption(pEl) {
const figCaptionEl = document.createElement('figcaption');
Expand All @@ -15,23 +16,22 @@ function htmlToElement(html) {
}

function decorateVideo(clone, figEl) {
let video = clone.querySelector('video');
const videoLink = clone.querySelector('a[href*=".mp4"]');
if (videoLink) {
const { href, hash, dataset } = videoLink;
const attrs = getVideoAttrs(hash, dataset);
const videoElem = `<video ${attrs}>
<source src="${href}" type="video/mp4" />
</video>`;

videoLink.insertAdjacentHTML('afterend', videoElem);
videoLink.remove();
video = clone.querySelector('video');
}
if (video) {
video.removeAttribute('data-mouseevent');
applyHoverPlay(video);
figEl.prepend(video);
const videoTag = clone.querySelector('video');
const anchorTag = clone.querySelector('a[href*=".mp4"]');
if (anchorTag && !anchorTag.hash) anchorTag.hash = '#autoplay';
if (anchorTag) decorateAnchorVideo({ src: anchorTag.href, anchorTag });
if (videoTag) {
videoTag.removeAttribute('data-mouseevent');
if (videoTag.dataset?.videoSource) {
videoTag.appendChild(
createTag('source', {
src: videoTag.dataset?.videoSource,
type: 'video/mp4',
}),
);
}
applyHoverPlay(videoTag);
mokimo marked this conversation as resolved.
Show resolved Hide resolved
figEl.prepend(videoTag);
}
}

Expand Down
43 changes: 13 additions & 30 deletions libs/blocks/video/video.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { createIntersectionObserver, getConfig } from '../../utils/utils.js';
import { applyHoverPlay, getVideoAttrs, applyInViewPortPlay } from '../../utils/decorate.js';
import { getConfig } from '../../utils/utils.js';
import { decorateAnchorVideo } from '../../utils/decorate.js';

const ROOT_MARGIN = 1000;

const loadVideo = (a) => {
const { pathname, hash, dataset } = a;
export default function init(a) {
a.classList.add('hide-video');
if (!a.parentNode) {
a.remove();
return;
}

Check warning on line 9 in libs/blocks/video/video.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/video/video.js#L7-L9

Added lines #L7 - L9 were not covered by tests
mokimo marked this conversation as resolved.
Show resolved Hide resolved
const { pathname } = a;
let videoPath = `.${pathname}`;
if (pathname.match('media_.*.mp4')) {
const { codeRoot } = getConfig();
Expand All @@ -14,28 +17,8 @@
const mediaFilename = pathname.split('/').pop();
videoPath = `${root}${mediaFilename}`;
}

const attrs = getVideoAttrs(hash, dataset);
const video = `<video ${attrs}>
<source src="${videoPath}" type="video/mp4" />
</video>`;
if (!a.parentNode) return;
a.insertAdjacentHTML('afterend', video);
const videoElem = document.body.querySelector(`source[src="${videoPath}"]`)?.parentElement;
applyHoverPlay(videoElem);
applyInViewPortPlay(videoElem);
a.remove();
};

export default function init(a) {
a.classList.add('hide-video');
if (a.textContent.includes('no-lazy')) {
loadVideo(a);
} else {
createIntersectionObserver({
el: a,
options: { rootMargin: `${ROOT_MARGIN}px` },
callback: loadVideo,
});
}
decorateAnchorVideo({
src: videoPath,
anchorTag: a,
});
}
29 changes: 23 additions & 6 deletions libs/utils/decorate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createTag } from './utils.js';
import { createTag, createIntersectionObserver } from './utils.js';

export function decorateButtons(el, size) {
const buttons = el.querySelectorAll('em a, strong a, p > a strong');
Expand Down Expand Up @@ -112,8 +112,6 @@ export async function decorateBlockBg(block, node, { useHandleFocalpoint = false
const allVP = [['mobile-only'], ['tablet-only'], ['desktop-only']];
const viewports = childCount === 2 ? binaryVP : allVP;
[...node.children].forEach((child, i) => {
const videoLink = child.querySelector('a[href*=".mp4"]');
if (videoLink && !videoLink.hash) videoLink.hash = 'autoplay';
if (childCount > 1) child.classList.add(...viewports[i]);
const pic = child.querySelector('picture');
if (useHandleFocalpoint && pic
Expand Down Expand Up @@ -199,7 +197,7 @@ export function getImgSrc(pic) {
return source?.srcset ? `poster='${source.srcset}'` : '';
}

export function getVideoAttrs(hash, dataset) {
function getVideoAttrs(hash, dataset) {
const isAutoplay = hash?.includes('autoplay');
const isAutoplayOnce = hash?.includes('autoplay1');
const playOnHover = hash?.includes('hoverplay');
Expand Down Expand Up @@ -257,7 +255,7 @@ export function handleObjectFit(bgRow) {
});
}

export function getVideoIntersectionObserver() {
function getVideoIntersectionObserver() {
if (!window?.videoIntersectionObs) {
window.videoIntersectionObs = new window.IntersectionObserver((entries) => {
entries.forEach((entry) => {
Expand All @@ -278,7 +276,7 @@ export function getVideoIntersectionObserver() {
return window.videoIntersectionObs;
}

export function applyInViewPortPlay(video) {
function applyInViewPortPlay(video) {
if (!video) return;
if (video.hasAttribute('data-play-viewport')) {
const observer = getVideoIntersectionObserver();
Expand Down Expand Up @@ -308,3 +306,22 @@ export function decorateMultiViewport(el) {
}
return foreground;
}

export function decorateAnchorVideo({ src, anchorTag }) {
if (!src.length || !(anchorTag instanceof HTMLElement)) return;
if (anchorTag.closest('.marquee, .aside, .hero-marquee') && !anchorTag.hash) anchorTag.hash = '#autoplay';
const { dataset, parentElement } = anchorTag;
const video = `<video ${getVideoAttrs(anchorTag.hash, dataset)} data-video-source=${src}></video>`;
anchorTag.insertAdjacentHTML('afterend', video);
const videoEl = parentElement.querySelector('video');
createIntersectionObserver({
el: parentElement,
options: { rootMargin: '1000px' },
callback: () => {
videoEl?.appendChild(createTag('source', { src, type: 'video/mp4' }));
},
});
applyHoverPlay(videoEl);
applyInViewPortPlay(videoEl);
anchorTag.remove();
}
11 changes: 0 additions & 11 deletions test/blocks/adobetv/adobetv.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@ document.body.innerHTML = await readFile({ path: './mocks/body.html' });
const { default: init } = await import('../../../libs/blocks/adobetv/adobetv.js');

describe('adobetv autoblock', () => {
it('decorates no-lazy video', async () => {
const block = document.querySelector('.video.no-lazy');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = await waitForElement('.video.no-lazy iframe');
expect(video).to.exist;
});

it('creates video block', async () => {
const wrapper = document.body.querySelector('.adobe-tv');
const a = wrapper.querySelector(':scope > a');
Expand Down
2 changes: 1 addition & 1 deletion test/blocks/adobetv/mocks/body.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<main>
<div class="video no-lazy">
<div class="video">
<a href="https://video.tv.adobe.com/v/3410934t1">
https://video.tv.adobe.com/v/3410934t1
</a>
Expand Down
5 changes: 3 additions & 2 deletions test/blocks/figure/figure.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ describe('init', () => {
init(blockEl);

const figures = blockEl.querySelectorAll('.figure');
expect(figures[0].querySelector('a > picture')).to.be.exist;
expect(figures[1].querySelector('a > video')).to.be.exist;
expect(figures[0].querySelector('a > picture')).to.exist;
expect(figures[1].querySelector('a > video')).to.exist;
expect(figures[1].querySelector('a > video > source')).to.exist;
});

it('should not add any classes to the block element when no pictures are present', () => {
Expand Down
16 changes: 2 additions & 14 deletions test/blocks/figure/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,7 @@
<div>
<p>
<video>
<source type="image/webp"
srcset=""
media="(min-width: 400px)">
<source type="image/webp"
srcset="">
<source type="image/jpeg"
srcset=""
media="(min-width: 400px)">
<img loading="lazy" alt="" type="image/jpeg"
src=""
width="2000" height="876">

</video>
</p>
<p><em>caption</em></p>
Expand Down Expand Up @@ -114,9 +104,7 @@
<div>
<div>
<p>
<video width="320" height="240" controls>
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
<video width="320" height="240" controls data-video-source="movie.mp4">
</video>
</p>
<p><a href="https://blog.adobe.com/en/publish/2022/12/07/kah-milah-ledgester-breathes-life-into-award-winning-art-with-adobe-creative-cloud">https://blog.adobe.com/en/publish/2022/12/07/kah-milah-ledgester-breathes-life-into-award-winning-art-with-adobe-creative-cloud</a></p>
Expand Down
21 changes: 17 additions & 4 deletions test/blocks/marquee/marquee.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { readFile } from '@web/test-runner-commands';
import { expect } from '@esm-bundle/chai';
import sinon from 'sinon';
import { waitForElement } from '../../helpers/waitfor.js';
import { setConfig } from '../../../libs/utils/utils.js';
import { waitFor, waitForElement } from '../../helpers/waitfor.js';
import { setConfig, loadStyle } from '../../../libs/utils/utils.js';
import { loadMnemonicList } from '../../../libs/blocks/marquee/marquee.js';

const locales = { '': { ietf: 'en-US', tk: 'hah7vzn.css' } };
Expand All @@ -16,6 +16,15 @@ const video = await readFile({ path: './mocks/video.html' });
const multipleIcons = await readFile({ path: './mocks/multiple-icons.html' });

describe('marquee', () => {
before(async () => {
await new Promise((resolve) => {
loadStyle('../../../../libs/styles/styles.css', resolve);
});
await new Promise((resolve) => {
loadStyle('../../../../libs/blocks/marquee/marquee.css', resolve);
});
});

const marquees = document.querySelectorAll('.marquee');
marquees.forEach((marquee) => {
init(marquee);
Expand Down Expand Up @@ -69,7 +78,9 @@ describe('marquee', () => {
init(marquee);
videoBLock(document.querySelector('#single-background a[href*=".mp4"]'));
const videoEl = await waitForElement('#single-background .background video');
expect(videoEl).to.exist;
const intersectionObserverAddsSource = () => videoEl.querySelector('source');
await waitFor(intersectionObserverAddsSource);
expect(videoEl.querySelector('source')).to.exist;
document.getElementById('single-background').remove();
});

Expand All @@ -78,7 +89,9 @@ describe('marquee', () => {
init(marquee);
document.querySelectorAll('#multiple-background a[href*=".mp4"]').forEach((videoLink) => videoBLock(videoLink));
await waitForElement('#multiple-background .background video');
expect(marquee.querySelectorAll('.background video').length).to.equal(1);
const intersectionObserverAddsSource = () => document.querySelector('.background video source');
await waitFor(intersectionObserverAddsSource);
expect(marquee.querySelectorAll('.background video source').length).to.equal(1);
document.getElementById('multiple-background').remove();
});

Expand Down
2 changes: 1 addition & 1 deletion test/blocks/video/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
}
</style>
<main>
<div class="video no-lazy">
<div class="video">
<a href="https://main--blog--adobecom.hlx.page/media_17927691d22fe4e1bd058e94762a224fdc57ebb7b.mp4">
https://main--blog--adobecom.hlx.page/media_17927691d22fe4e1bd058e94762a224fdc57ebb7b.mp4
</a>
Expand Down
Loading
Loading