mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-08-19 11:52:31 -04:00
v6.0.0-beta.1
This commit is contained in:
+104
-18
@@ -98,6 +98,10 @@ var tc = {
|
||||
rememberSpeed: getSharedDefault("rememberSpeed", false),
|
||||
forceLastSavedSpeed: getSharedDefault("forceLastSavedSpeed", false),
|
||||
audioBoolean: getSharedDefault("audioBoolean", false),
|
||||
showAmbientLoopControls: getSharedDefault(
|
||||
"showAmbientLoopControls",
|
||||
false
|
||||
),
|
||||
startHidden: getSharedDefault("startHidden", false),
|
||||
hideWithYouTubeControls: getSharedDefault(
|
||||
"hideWithYouTubeControls",
|
||||
@@ -488,6 +492,7 @@ function captureSiteRuleBase() {
|
||||
rememberSpeed: tc.settings.rememberSpeed,
|
||||
forceLastSavedSpeed: tc.settings.forceLastSavedSpeed,
|
||||
audioBoolean: tc.settings.audioBoolean,
|
||||
showAmbientLoopControls: tc.settings.showAmbientLoopControls,
|
||||
controllerOpacity: tc.settings.controllerOpacity,
|
||||
controllerMarginTop: tc.settings.controllerMarginTop,
|
||||
controllerMarginBottom: tc.settings.controllerMarginBottom,
|
||||
@@ -516,6 +521,7 @@ function resetSettingsFromSiteRuleBase() {
|
||||
tc.settings.rememberSpeed = base.rememberSpeed;
|
||||
tc.settings.forceLastSavedSpeed = base.forceLastSavedSpeed;
|
||||
tc.settings.audioBoolean = base.audioBoolean;
|
||||
tc.settings.showAmbientLoopControls = base.showAmbientLoopControls;
|
||||
tc.settings.controllerOpacity = base.controllerOpacity;
|
||||
tc.settings.controllerMarginTop = base.controllerMarginTop;
|
||||
tc.settings.controllerMarginBottom = base.controllerMarginBottom;
|
||||
@@ -1364,8 +1370,70 @@ function isMediaElement(node) {
|
||||
);
|
||||
}
|
||||
|
||||
function hasInteractivePlayerChrome(node) {
|
||||
if (!node || node.nodeName !== "VIDEO") return false;
|
||||
if (node.controls === true) return true;
|
||||
|
||||
if (typeof node.closest === "function") {
|
||||
var knownPlayer = node.closest(
|
||||
".html5-video-player, .video-js, .jwplayer, .plyr, " +
|
||||
"[class~='video-player'], [data-testid*='video-player'], " +
|
||||
"media-player, amp-video"
|
||||
);
|
||||
if (knownPlayer) return true;
|
||||
}
|
||||
|
||||
var ancestor = node.parentElement;
|
||||
var videoRect = node.getBoundingClientRect();
|
||||
var depth = 0;
|
||||
while (ancestor && depth < 4) {
|
||||
var ancestorRect = ancestor.getBoundingClientRect();
|
||||
if (
|
||||
videoRect.width > 0 &&
|
||||
videoRect.height > 0 &&
|
||||
(ancestorRect.width > Math.max(videoRect.width * 1.75, videoRect.width + 160) ||
|
||||
ancestorRect.height > Math.max(videoRect.height * 1.75, videoRect.height + 160))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
if (
|
||||
(videoRect.width <= 0 || videoRect.height <= 0) &&
|
||||
depth > 1
|
||||
) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
var controls = ancestor.querySelectorAll(
|
||||
"button, [role='button'], input[type='range'], [role='slider']"
|
||||
);
|
||||
for (var i = 0; i < Math.min(controls.length, 40); i += 1) {
|
||||
var control = controls[i];
|
||||
var description = [
|
||||
control.getAttribute("aria-label"),
|
||||
control.getAttribute("title"),
|
||||
control.getAttribute("data-title-no-tooltip"),
|
||||
control.className
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
if (
|
||||
/(^|\b)(play|pause|mute|volume|seek|captions?|subtitles?|fullscreen|full screen)(\b|$)/i.test(
|
||||
description
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (_error) {}
|
||||
ancestor = ancestor.parentElement;
|
||||
depth += 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAmbientLoopMedia(node) {
|
||||
return Boolean(
|
||||
var hasAmbientSignature = Boolean(
|
||||
node &&
|
||||
node.nodeName === "VIDEO" &&
|
||||
node.autoplay === true &&
|
||||
@@ -1374,6 +1442,8 @@ function isAmbientLoopMedia(node) {
|
||||
node.playsInline === true &&
|
||||
node.controls !== true
|
||||
);
|
||||
if (!hasAmbientSignature || tc.settings.showAmbientLoopControls) return false;
|
||||
return !hasInteractivePlayerChrome(node);
|
||||
}
|
||||
|
||||
function hasUsableMediaSource(node) {
|
||||
@@ -1424,10 +1494,6 @@ function ensureController(node, parent) {
|
||||
return null;
|
||||
}
|
||||
if (!isMediaElement(node)) return null;
|
||||
if (isAmbientLoopMedia(node)) {
|
||||
if (node.vsc) removeController(node);
|
||||
return null;
|
||||
}
|
||||
if (typeof tc.videoController === "undefined") defineVideoController();
|
||||
|
||||
// href selects site rules; re-run on every new/usable media so all runtime
|
||||
@@ -1437,6 +1503,10 @@ function ensureController(node, parent) {
|
||||
if (node.vsc) removeController(node);
|
||||
return null;
|
||||
}
|
||||
if (isAmbientLoopMedia(node)) {
|
||||
if (node.vsc) removeController(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
node.vsc &&
|
||||
@@ -1913,6 +1983,8 @@ function hydrateRuntimeSettings(rawStorage, options) {
|
||||
tc.settings.rememberSpeed = storage.rememberSpeed === true;
|
||||
tc.settings.forceLastSavedSpeed = storage.forceLastSavedSpeed === true;
|
||||
tc.settings.audioBoolean = storage.audioBoolean === true;
|
||||
tc.settings.showAmbientLoopControls =
|
||||
storage.showAmbientLoopControls === true;
|
||||
tc.settings.enabled = storage.enabled !== false;
|
||||
tc.settings.startHidden = storage.startHidden === true;
|
||||
tc.settings.hideWithControls =
|
||||
@@ -3065,14 +3137,14 @@ function disableDirectFullscreenPopover(videoController) {
|
||||
wrapper.removeAttribute("popover");
|
||||
}
|
||||
|
||||
function enableDirectFullscreenPopover(videoController) {
|
||||
function enableFullscreenPopover(videoController, preferredMount) {
|
||||
if (!videoController || !videoController.video || !videoController.div) {
|
||||
return false;
|
||||
}
|
||||
var wrapper = videoController.div;
|
||||
if (typeof wrapper.showPopover !== "function") return false;
|
||||
|
||||
var normalMount = videoController.normalControllerMount;
|
||||
var normalMount = preferredMount || videoController.normalControllerMount;
|
||||
var normalMountIsConnected = Boolean(
|
||||
normalMount &&
|
||||
(normalMount.isConnected ||
|
||||
@@ -3113,12 +3185,6 @@ function syncControllerFullscreenMount(videoController) {
|
||||
var fullscreenElement = getFullscreenElement(doc);
|
||||
var targetMount = videoController.normalControllerMount;
|
||||
|
||||
if (fullscreenElement === video) {
|
||||
if (enableDirectFullscreenPopover(videoController)) return true;
|
||||
} else {
|
||||
disableDirectFullscreenPopover(videoController);
|
||||
}
|
||||
|
||||
if (
|
||||
fullscreenElement &&
|
||||
(fullscreenElement === video ||
|
||||
@@ -3131,6 +3197,18 @@ function syncControllerFullscreenMount(videoController) {
|
||||
}
|
||||
|
||||
if (!targetMount) return false;
|
||||
|
||||
if (fullscreenElement) {
|
||||
// Fullscreen elements and popovers both participate in the browser's top
|
||||
// layer. Showing Speeder's host after the player enters fullscreen keeps it
|
||||
// above provider-owned surfaces even when the provider clips descendants or
|
||||
// creates a new fullscreen stacking context (notably Firefox + YouTube).
|
||||
// Browsers without the Popover API retain the player-local remount fallback.
|
||||
if (enableFullscreenPopover(videoController, targetMount)) return true;
|
||||
} else {
|
||||
disableDirectFullscreenPopover(videoController);
|
||||
}
|
||||
|
||||
return remountControllerHost(videoController, targetMount);
|
||||
}
|
||||
|
||||
@@ -3573,9 +3651,13 @@ function defineVideoController() {
|
||||
) {
|
||||
wrapper.classList.add("ytp-autohide");
|
||||
|
||||
// Immediately end any temporary "vsc-show" state to hide with YouTube
|
||||
// UNLESS it was forced by a shortcut (vsc-forced-show)
|
||||
if (!wrapper.classList.contains("vsc-forced-show")) {
|
||||
// Preserve an active pointer reveal or shortcut-forced reveal. The
|
||||
// ytp-autohide class remains in place, so CSS hides the host as soon as
|
||||
// vsc-show's bounded timer expires.
|
||||
if (
|
||||
!wrapper.classList.contains("vsc-forced-show") &&
|
||||
!wrapper.classList.contains("vsc-show")
|
||||
) {
|
||||
wrapper.classList.remove("vsc-show");
|
||||
if (wrapper.showTimeOut) {
|
||||
clearTimeout(wrapper.showTimeOut);
|
||||
@@ -3984,6 +4066,7 @@ function applySiteRuleOverrides() {
|
||||
"rememberSpeed",
|
||||
"forceLastSavedSpeed",
|
||||
"audioBoolean",
|
||||
"showAmbientLoopControls",
|
||||
"controllerOpacity",
|
||||
"controllerMarginTop",
|
||||
"controllerMarginBottom",
|
||||
@@ -4033,9 +4116,12 @@ function applySiteRuleOverrides() {
|
||||
}
|
||||
|
||||
function removeIneligibleMediaControllers() {
|
||||
if (tc.settings.audioBoolean) return;
|
||||
tc.mediaElements.slice().forEach(function(media) {
|
||||
if (media && media.nodeName === "AUDIO") {
|
||||
if (
|
||||
media &&
|
||||
((media.nodeName === "AUDIO" && !tc.settings.audioBoolean) ||
|
||||
isAmbientLoopMedia(media))
|
||||
) {
|
||||
removeController(media);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Speeder",
|
||||
"short_name": "Speeder",
|
||||
"version": "5.3.6.1",
|
||||
"version": "6.0.0",
|
||||
"manifest_version": 2,
|
||||
"description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts (New and improved version of \"Video Speed Controller\")",
|
||||
"homepage_url": "https://github.com/SoPat712/speeder",
|
||||
|
||||
@@ -282,6 +282,17 @@
|
||||
</label>
|
||||
<input id="audioBoolean" type="checkbox" />
|
||||
</div>
|
||||
<div class="row row-checkbox">
|
||||
<label for="showAmbientLoopControls"
|
||||
>Show controls on ambient/slideshow videos<br />
|
||||
<em
|
||||
>Also shows Speeder on muted autoplay loops used as animated
|
||||
cards or slideshow artwork. Genuine video players are always
|
||||
controlled regardless of this setting.</em
|
||||
>
|
||||
</label>
|
||||
<input id="showAmbientLoopControls" type="checkbox" />
|
||||
</div>
|
||||
|
||||
<div class="defaults-divider"></div>
|
||||
<h4 class="defaults-sub-heading">Playback</h4>
|
||||
@@ -803,8 +814,8 @@
|
||||
<label class="site-override-lead">
|
||||
<span
|
||||
>Override playback for this site<br /><em
|
||||
>When on, remember speed / force / audio below replace
|
||||
general defaults for matching URLs.</em
|
||||
>When on, remember speed / force / media eligibility
|
||||
below replace general defaults for matching URLs.</em
|
||||
></span
|
||||
>
|
||||
<input type="checkbox" class="override-playback" />
|
||||
@@ -854,6 +865,16 @@
|
||||
>
|
||||
<input type="checkbox" class="site-audioBoolean" />
|
||||
</div>
|
||||
<div class="site-rule-option site-rule-option-checkbox">
|
||||
<label
|
||||
>Show controls on ambient/slideshow videos:<br /><em
|
||||
>Also includes muted autoplay loops used as animated
|
||||
cards or slideshow artwork. Genuine video players are
|
||||
always included.</em
|
||||
></label
|
||||
>
|
||||
<input type="checkbox" class="site-showAmbientLoopControls" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site-rule-override-section">
|
||||
|
||||
@@ -835,6 +835,8 @@ function save_options() {
|
||||
settings.forceLastSavedSpeed =
|
||||
document.getElementById("forceLastSavedSpeed").checked;
|
||||
settings.audioBoolean = document.getElementById("audioBoolean").checked;
|
||||
settings.showAmbientLoopControls =
|
||||
document.getElementById("showAmbientLoopControls").checked;
|
||||
settings.enabled = document.getElementById("enabled").checked;
|
||||
settings.startHidden = document.getElementById("startHidden").checked;
|
||||
settings.shortcutTargetMode =
|
||||
@@ -974,11 +976,15 @@ function save_options() {
|
||||
rule.preferredSpeed = preferredSpeed;
|
||||
}
|
||||
rule.audioBoolean = ruleEl.querySelector(".site-audioBoolean").checked;
|
||||
rule.showAmbientLoopControls = ruleEl.querySelector(
|
||||
".site-showAmbientLoopControls"
|
||||
).checked;
|
||||
} else {
|
||||
delete rule.rememberSpeed;
|
||||
delete rule.forceLastSavedSpeed;
|
||||
delete rule.preferredSpeed;
|
||||
delete rule.audioBoolean;
|
||||
delete rule.showAmbientLoopControls;
|
||||
}
|
||||
|
||||
if (ruleEl.querySelector(".override-opacity").checked) {
|
||||
@@ -1278,7 +1284,8 @@ function createSiteRule(rule) {
|
||||
(rule.rememberSpeed !== undefined ||
|
||||
rule.forceLastSavedSpeed !== undefined ||
|
||||
rule.preferredSpeed !== undefined ||
|
||||
rule.audioBoolean !== undefined)
|
||||
rule.audioBoolean !== undefined ||
|
||||
rule.showAmbientLoopControls !== undefined)
|
||||
);
|
||||
ruleEl.querySelector(".override-playback").checked = hasPlaybackOverride;
|
||||
syncSiteRuleField(ruleEl, rule, "rememberSpeed", true);
|
||||
@@ -1288,6 +1295,7 @@ function createSiteRule(rule) {
|
||||
? String(rule.preferredSpeed)
|
||||
: "";
|
||||
syncSiteRuleField(ruleEl, rule, "audioBoolean", true);
|
||||
syncSiteRuleField(ruleEl, rule, "showAmbientLoopControls", true);
|
||||
applySiteRuleOverrideState(ruleEl, "override-playback", "site-playback-container");
|
||||
|
||||
ruleEl.querySelector(".override-opacity").checked = Boolean(
|
||||
@@ -1780,6 +1788,8 @@ function restore_options(callback) {
|
||||
document.getElementById("forceLastSavedSpeed").checked =
|
||||
storage.forceLastSavedSpeed;
|
||||
document.getElementById("audioBoolean").checked = storage.audioBoolean;
|
||||
document.getElementById("showAmbientLoopControls").checked =
|
||||
storage.showAmbientLoopControls;
|
||||
document.getElementById("enabled").checked = storage.enabled;
|
||||
document.getElementById("startHidden").checked = storage.startHidden;
|
||||
document.getElementById("shortcutTargetMode").value =
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"rememberSpeed",
|
||||
"forceLastSavedSpeed",
|
||||
"audioBoolean",
|
||||
"showAmbientLoopControls",
|
||||
"enabled",
|
||||
"startHidden",
|
||||
"hideWithControls",
|
||||
@@ -58,6 +59,7 @@
|
||||
lastSpeed: 1.0,
|
||||
rememberSpeed: false,
|
||||
audioBoolean: false,
|
||||
showAmbientLoopControls: false,
|
||||
startHidden: false,
|
||||
hideWithYouTubeControls: false,
|
||||
hideWithControls: false,
|
||||
@@ -324,6 +326,7 @@
|
||||
"rememberSpeed",
|
||||
"forceLastSavedSpeed",
|
||||
"audioBoolean",
|
||||
"showAmbientLoopControls",
|
||||
"enableSubtitleNudge",
|
||||
"subtitleNudgeEnabledByDefault",
|
||||
"showPopupControlBar",
|
||||
@@ -865,6 +868,7 @@
|
||||
"rememberSpeed",
|
||||
"forceLastSavedSpeed",
|
||||
"audioBoolean",
|
||||
"showAmbientLoopControls",
|
||||
"enabled",
|
||||
"startHidden",
|
||||
"hideWithControls",
|
||||
|
||||
@@ -25,6 +25,7 @@ describe("import/export flows", () => {
|
||||
const settings = globalThis.vscGetSettingsDefaults();
|
||||
settings.siteRules[1].title = "My Shorts rule";
|
||||
settings.siteRules[1].shortcutTargetMode = "all";
|
||||
settings.siteRules[1].showAmbientLoopControls = true;
|
||||
|
||||
const exported = globalThis.normalizedSettingsForExport(
|
||||
globalThis.vscBuildStoredSettingsDiff(settings)
|
||||
@@ -33,6 +34,7 @@ describe("import/export flows", () => {
|
||||
|
||||
expect(restored.siteRules[1].title).toBe("My Shorts rule");
|
||||
expect(restored.siteRules[1].shortcutTargetMode).toBe("all");
|
||||
expect(restored.siteRules[1].showAmbientLoopControls).toBe(true);
|
||||
expect(exported.siteRules.map((rule) => rule.title)).toEqual([
|
||||
"YouTube videos",
|
||||
"My Shorts rule",
|
||||
|
||||
@@ -309,7 +309,7 @@ describe("inject.js media/controller lifecycle regressions", () => {
|
||||
expect(second.video.playbackRate).toBe(1.2);
|
||||
});
|
||||
|
||||
it("skips GIF-like ambient loops but accepts them if they become real players", async () => {
|
||||
it("skips ambient loops by default and includes them when explicitly enabled", async () => {
|
||||
bootInject();
|
||||
await settleLifecycle();
|
||||
|
||||
@@ -332,12 +332,71 @@ describe("inject.js media/controller lifecycle regressions", () => {
|
||||
expect(window.ensureController(video, mount)).toBeNull();
|
||||
expect(video.vsc).toBeUndefined();
|
||||
|
||||
video.loop = false;
|
||||
video.controls = true;
|
||||
window.tc.settings.showAmbientLoopControls = true;
|
||||
window.tc.siteRuleBase.showAmbientLoopControls = true;
|
||||
expect(window.ensureController(video, mount)).toBeTruthy();
|
||||
expect(video.vsc.div.isConnected).toBe(true);
|
||||
});
|
||||
|
||||
it("always includes a genuine player even when its video has an ambient-loop signature", async () => {
|
||||
bootInject();
|
||||
await settleLifecycle();
|
||||
|
||||
const player = document.createElement("media-player");
|
||||
const video = document.createElement("video");
|
||||
const playButton = document.createElement("button");
|
||||
const rect = makeRect(0, 0, 640, 360);
|
||||
playButton.setAttribute("aria-label", "Play");
|
||||
video.autoplay = true;
|
||||
video.defaultMuted = true;
|
||||
video.muted = true;
|
||||
video.loop = true;
|
||||
video.playsInline = true;
|
||||
video.src = "https://example.org/interactive-loop.mp4";
|
||||
player.append(video, playButton);
|
||||
document.body.appendChild(player);
|
||||
[player, video].forEach((element) => setRect(element, rect));
|
||||
setBoxMetrics(player, rect.width, rect.height);
|
||||
|
||||
expect(window.tc.settings.showAmbientLoopControls).toBe(false);
|
||||
expect(window.ensureController(video, player)).toBeTruthy();
|
||||
expect(video.vsc.div.isConnected).toBe(true);
|
||||
});
|
||||
|
||||
it("applies the ambient-loop setting from a matching site rule", async () => {
|
||||
bootInject({
|
||||
url: "https://news.example.org/story",
|
||||
syncData: {
|
||||
showAmbientLoopControls: false,
|
||||
siteRules: [
|
||||
{
|
||||
pattern: "news.example.org",
|
||||
enabled: true,
|
||||
showAmbientLoopControls: true
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
await settleLifecycle();
|
||||
|
||||
const mount = document.createElement("div");
|
||||
const video = document.createElement("video");
|
||||
const rect = makeRect(0, 0, 480, 270);
|
||||
video.autoplay = true;
|
||||
video.defaultMuted = true;
|
||||
video.muted = true;
|
||||
video.loop = true;
|
||||
video.playsInline = true;
|
||||
video.src = "https://news.example.org/card.mp4";
|
||||
mount.appendChild(video);
|
||||
document.body.appendChild(mount);
|
||||
[mount, video].forEach((element) => setRect(element, rect));
|
||||
setBoxMetrics(mount, rect.width, rect.height);
|
||||
|
||||
expect(window.ensureController(video, mount)).toBeTruthy();
|
||||
expect(window.tc.settings.showAmbientLoopControls).toBe(true);
|
||||
});
|
||||
|
||||
it("moves the host into an ancestor fullscreen subtree and restores it", async () => {
|
||||
bootInject();
|
||||
await settleLifecycle();
|
||||
@@ -384,6 +443,52 @@ describe("inject.js media/controller lifecycle regressions", () => {
|
||||
controller.controllerHostCleanup();
|
||||
});
|
||||
|
||||
it("promotes an ancestor-fullscreen controller into the browser top layer", async () => {
|
||||
bootInject();
|
||||
await settleLifecycle();
|
||||
|
||||
const fullscreenPlayer = document.createElement("div");
|
||||
const video = document.createElement("video");
|
||||
const wrapper = document.createElement("div");
|
||||
const rect = makeRect(0, 0, 1280, 720);
|
||||
|
||||
fullscreenPlayer.append(video, wrapper);
|
||||
document.body.appendChild(fullscreenPlayer);
|
||||
[fullscreenPlayer, video].forEach((element) => {
|
||||
setRect(element, rect);
|
||||
setBoxMetrics(element, rect.width, rect.height);
|
||||
});
|
||||
wrapper.showPopover = vi.fn();
|
||||
wrapper.hidePopover = vi.fn();
|
||||
|
||||
const controller = {
|
||||
video,
|
||||
div: wrapper,
|
||||
normalControllerMount: fullscreenPlayer
|
||||
};
|
||||
window.setupControllerHostTracking(controller, wrapper, fullscreenPlayer);
|
||||
Object.defineProperty(document, "fullscreenElement", {
|
||||
configurable: true,
|
||||
value: fullscreenPlayer
|
||||
});
|
||||
|
||||
expect(window.syncControllerFullscreenMount(controller)).toBe(true);
|
||||
expect(wrapper.parentNode).toBe(fullscreenPlayer);
|
||||
expect(wrapper.showPopover).toHaveBeenCalledOnce();
|
||||
expect(wrapper.getAttribute("popover")).toBe("manual");
|
||||
expect(wrapper.classList.contains("vsc-fullscreen-popover")).toBe(true);
|
||||
|
||||
Object.defineProperty(document, "fullscreenElement", {
|
||||
configurable: true,
|
||||
value: null
|
||||
});
|
||||
window.syncControllerFullscreenMount(controller);
|
||||
expect(wrapper.hidePopover).toHaveBeenCalledOnce();
|
||||
|
||||
wrapper.remove();
|
||||
controller.controllerHostCleanup();
|
||||
});
|
||||
|
||||
it("preserves direct-video requestFullscreen semantics and overlays with a popover", async () => {
|
||||
bootInject();
|
||||
await settleLifecycle();
|
||||
@@ -610,6 +715,14 @@ describe("inject.js media/controller lifecycle regressions", () => {
|
||||
|
||||
controllerElement.dispatchEvent(new Event("pointerleave"));
|
||||
expect(wrapper.classList.contains("ytp-autohide")).toBe(true);
|
||||
|
||||
player.dispatchEvent(new MouseEvent("mousemove", { bubbles: true }));
|
||||
expect(wrapper.classList.contains("vsc-show")).toBe(true);
|
||||
player.classList.remove("ytp-autohide");
|
||||
player.classList.add("ytp-autohide");
|
||||
await settleLifecycle();
|
||||
expect(wrapper.classList.contains("ytp-autohide")).toBe(true);
|
||||
expect(wrapper.classList.contains("vsc-show")).toBe(true);
|
||||
});
|
||||
|
||||
it("suppresses a zero-size host and reveals it when the video becomes visible", async () => {
|
||||
|
||||
@@ -29,6 +29,7 @@ describe("options page", () => {
|
||||
manifestVersion: "5.1.7.0",
|
||||
sync: {
|
||||
rememberSpeed: true,
|
||||
showAmbientLoopControls: true,
|
||||
enabled: false,
|
||||
shortcutTargetMode: "all",
|
||||
subtitleNudgeEnabledByDefault: false,
|
||||
@@ -45,6 +46,7 @@ describe("options page", () => {
|
||||
enabled: true,
|
||||
shortcutTargetMode: "all",
|
||||
preferredSpeed: 2.4,
|
||||
showAmbientLoopControls: false,
|
||||
subtitleNudgeEnabledByDefault: false,
|
||||
showPopupControlBar: false,
|
||||
popupControllerButtons: ["advance", "settings", "advance"]
|
||||
@@ -55,6 +57,7 @@ describe("options page", () => {
|
||||
|
||||
expect(document.getElementById("app-version").textContent).toBe("5.1.7.0");
|
||||
expect(document.getElementById("rememberSpeed").checked).toBe(true);
|
||||
expect(document.getElementById("showAmbientLoopControls").checked).toBe(true);
|
||||
expect(document.getElementById("enabled").checked).toBe(false);
|
||||
expect(document.getElementById("shortcutTargetMode").value).toBe("all");
|
||||
expect(document.getElementById("subtitleNudgeEnabledByDefault").checked).toBe(
|
||||
@@ -72,6 +75,9 @@ describe("options page", () => {
|
||||
expect(document.querySelector(".site-rule .site-preferredSpeed").value).toBe(
|
||||
"2.4"
|
||||
);
|
||||
expect(
|
||||
document.querySelector(".site-rule .site-showAmbientLoopControls").checked
|
||||
).toBe(false);
|
||||
expect(
|
||||
document.querySelector(".site-rule .site-shortcutTargetMode").value
|
||||
).toBe("all");
|
||||
@@ -181,6 +187,7 @@ describe("options page", () => {
|
||||
const chrome = await setupOptions();
|
||||
|
||||
document.getElementById("rememberSpeed").checked = true;
|
||||
document.getElementById("showAmbientLoopControls").checked = true;
|
||||
document.getElementById("hideWithControlsTimer").value = "20";
|
||||
document.getElementById("controllerOpacity").value = "0";
|
||||
document.getElementById("controllerMarginTop").value = "250";
|
||||
@@ -204,6 +211,7 @@ describe("options page", () => {
|
||||
rule.querySelector(".site-pattern").value = "youtube.com";
|
||||
rule.querySelector(".override-playback").checked = true;
|
||||
rule.querySelector(".site-rememberSpeed").checked = true;
|
||||
rule.querySelector(".site-showAmbientLoopControls").checked = false;
|
||||
rule.querySelector(".site-preferredSpeed").value = "2.4";
|
||||
rule.querySelector(".override-shortcut-target").checked = true;
|
||||
rule.querySelector(".site-shortcutTargetMode").value = "all";
|
||||
@@ -231,6 +239,7 @@ describe("options page", () => {
|
||||
);
|
||||
|
||||
expect(savedSettings.rememberSpeed).toBe(true);
|
||||
expect(savedSettings.showAmbientLoopControls).toBe(true);
|
||||
expect(savedSettings.hideWithControlsTimer).toBe(15);
|
||||
expect(savedSettings.controllerOpacity).toBe(0);
|
||||
expect(savedSettings.controllerMarginTop).toBe(200);
|
||||
@@ -246,6 +255,7 @@ describe("options page", () => {
|
||||
title: "My YouTube rule",
|
||||
pattern: "youtube.com",
|
||||
rememberSpeed: true,
|
||||
showAmbientLoopControls: false,
|
||||
shortcutTargetMode: "all",
|
||||
preferredSpeed: 2.4,
|
||||
controllerOpacity: 0,
|
||||
|
||||
@@ -34,10 +34,36 @@ describe("canonical settings storage", () => {
|
||||
})
|
||||
);
|
||||
expect(settings.shortcutTargetMode).toBe("closest");
|
||||
expect(settings.showAmbientLoopControls).toBe(false);
|
||||
expect(settings.subtitleNudgeInterval).toBe(250);
|
||||
expect(settings.siteRules[0].enableSubtitleNudge).toBeUndefined();
|
||||
});
|
||||
|
||||
it("round-trips global and site-specific ambient-loop visibility", () => {
|
||||
const settings = window.vscExpandStoredSettings({
|
||||
showAmbientLoopControls: true,
|
||||
siteRules: [
|
||||
{
|
||||
pattern: "cnn.com",
|
||||
showAmbientLoopControls: false
|
||||
}
|
||||
]
|
||||
});
|
||||
const restored = window.vscExpandStoredSettings(
|
||||
window.vscBuildStoredSettingsDiff(settings)
|
||||
);
|
||||
|
||||
expect(restored.showAmbientLoopControls).toBe(true);
|
||||
expect(restored.siteRules).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
pattern: "cnn.com",
|
||||
showAmbientLoopControls: false
|
||||
})
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("round-trips global and site-specific shortcut targeting", () => {
|
||||
const settings = window.vscExpandStoredSettings({
|
||||
shortcutTargetMode: "all",
|
||||
|
||||
Reference in New Issue
Block a user