Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions js&css/web-accessible/www.youtube.com/shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,52 @@ ImprovedTube.shortcutActivateCaptions = function () {
};
/*------Chapters------*/
ImprovedTube.shortcutChapters = function () {
const available = document.querySelector('[target-id*=chapters][visibility*=HIDDEN]') || document.querySelector('[target-id*=chapters]').clientHeight;
const fullscreenElement =
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.querySelector('.html5-video-player.ytp-fullscreen');

if (fullscreenElement) {
const fullscreenChapters = fullscreenElement.querySelector(
'.ytp-chapter-title'
);

if (fullscreenChapters) {
fullscreenChapters.click();
return;
}
}

const chaptersPanel = document.querySelector('[target-id*=chapters]');

if (!chaptersPanel) {
return;
}

const modernChapters = document.querySelector(
'[modern-chapters] #navigation-button button[aria-label]'
);

const visibilityButton = document.querySelector(
'[target-id*=chapters][visibility*=EXPANDED] #visibility-button button[aria-label]'
);

const available =
document.querySelector('[target-id*=chapters][visibility*=HIDDEN]') ||
chaptersPanel.clientHeight;

if (available) {
const modernChapters = document.querySelector('[modern-chapters] #navigation-button button[aria-label]');
modernChapters ? modernChapters.click() : document.querySelector('[target-id*=chapters]')?.removeAttribute('visibility');
if (modernChapters) {
modernChapters.click();
} else {
chaptersPanel.removeAttribute('visibility');
}
} else if (visibilityButton) {
visibilityButton.click();
} else {
const visibilityButton = document.querySelector('[target-id*=chapters][visibility*=EXPANDED] #visibility-button button[aria-label]');
visibilityButton ? visibilityButton.click() : document.querySelector('*[target-id*=chapters] #visibility-button button')?.click();
}
if (!modernChapters && visibilityButton) {
console.error('shortcutChapters: Cant fint proper Enable button, falling back to unreliable bruteforce method');
chaptersPanel
.querySelector('#visibility-button button')
?.click();
}
};
/*------Transcript------*/
Expand Down
80 changes: 80 additions & 0 deletions tests/unit/shortcut-chapters-fullscreen.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const fs = require('fs');
const path = require('path');
const vm = require('vm');

const SHORTCUTS_SRC = path.join(
__dirname,
'../../js&css/web-accessible/www.youtube.com/shortcuts.js'
);

function loadShortcuts(documentMock) {
const improvedTube = {};

const sandbox = {
ImprovedTube: improvedTube,
document: documentMock,
window: {},
console,
setTimeout,
clearTimeout
};

vm.createContext(sandbox);

vm.runInContext(
fs.readFileSync(SHORTCUTS_SRC, 'utf8'),
sandbox
);

return improvedTube;
}

describe('Issue #4283: chapters shortcut in fullscreen', () => {
test('clicks the fullscreen chapters control when available', () => {
const chaptersButton = {
click: jest.fn()
};

const fullscreenElement = {
querySelector: jest.fn((selector) => {
if (selector === '.ytp-chapter-title') {
return chaptersButton;
}

return null;
})
};

const documentMock = {
fullscreenElement,
webkitFullscreenElement: null,

querySelector: jest.fn(() => null)
};

const improvedTube = loadShortcuts(documentMock);

improvedTube.shortcutChapters();

expect(fullscreenElement.querySelector)
.toHaveBeenCalledWith('.ytp-chapter-title');

expect(chaptersButton.click)
.toHaveBeenCalledTimes(1);
});

test('does not throw when chapters UI is unavailable', () => {
const documentMock = {
fullscreenElement: null,
webkitFullscreenElement: null,

querySelector: jest.fn(() => null)
};

const improvedTube = loadShortcuts(documentMock);

expect(() => {
improvedTube.shortcutChapters();
}).not.toThrow();
});
});