Extension controls not appearing?
- This extension only works with HTML5 audio and video. If the
- controls never appear, you may be looking at Flash content instead.
- Right-click the player to check: if the menu mentions Flash, that
- is the issue. Most sites will fall back to HTML5 when Flash is not
- available, so disabling Flash in the browser can help.
+ Speeder works with HTML5 video and, when enabled above, HTML5 audio.
+ Check that the current site is enabled, then use the popup’s
+ “Rescan page for videos” action after a player loads
+ dynamically. Browser-internal pages and players that do not expose
+ HTML5 media to extensions cannot be controlled.
diff --git a/extension/options/options.js b/extension/options/options.js
index 1ef11f6..9c7d5c7 100644
--- a/extension/options/options.js
+++ b/extension/options/options.js
@@ -672,6 +672,20 @@ function appendSelectOptions(select, options) {
});
}
+function labelShortcutRow(row) {
+ if (!row) return;
+ var action = row.dataset.action;
+ var name = actionLabels[action] || action || "Shortcut";
+ var keyInput = row.querySelector(".customKey");
+ var valueInput = row.querySelector(".customValue");
+ var removeButton = row.querySelector(".removeParent");
+ if (keyInput) keyInput.setAttribute("aria-label", name + " key");
+ if (valueInput) valueInput.setAttribute("aria-label", name + " value");
+ if (removeButton) {
+ removeButton.setAttribute("aria-label", "Remove " + name + " shortcut");
+ }
+}
+
function add_shortcut(action, value) {
if (!action) return;
@@ -711,6 +725,7 @@ function add_shortcut(action, value) {
div.appendChild(keyInput);
div.appendChild(valueInput);
div.appendChild(removeButton);
+ labelShortcutRow(div);
var customsElement = document.querySelector(".shortcuts-grid");
customsElement.appendChild(div);
@@ -793,9 +808,8 @@ function validate() {
if (pattern.startsWith("/")) {
try {
var lastSlash = pattern.lastIndexOf("/");
- if (lastSlash > 0) {
- new RegExp(pattern.substring(1, lastSlash), pattern.substring(lastSlash + 1));
- }
+ if (lastSlash === 0) throw new Error("Missing closing slash");
+ new RegExp(pattern.substring(1, lastSlash), pattern.substring(lastSlash + 1));
} catch (err) {
status.textContent =
"Error: Invalid site rule regex: " + pattern + ". Unable to save";
@@ -1145,15 +1159,7 @@ function addSiteRuleShortcut(rowsEl, action, binding, value, force) {
var actionLabel = document.createElement("div");
actionLabel.className = "shortcut-label";
- var actionLabelText = actionLabels[action] || action;
- if (action === "toggleSubtitleNudge") {
- var ruleEl = rowsEl.closest(".site-rule");
- var pattern = ruleEl ? ruleEl.querySelector(".site-pattern").value : "";
- if (!pattern.toLowerCase().includes("youtube.com")) {
- actionLabelText += " (only for YouTube embeds)";
- }
- }
- actionLabel.textContent = actionLabelText;
+ actionLabel.textContent = actionLabels[action] || action;
var keyInput = document.createElement("input");
keyInput.className = "customKey";
@@ -1201,10 +1207,40 @@ function addSiteRuleShortcut(rowsEl, action, binding, value, force) {
div.appendChild(valueInput);
div.appendChild(forceLabel);
div.appendChild(removeButton);
+ labelShortcutRow(div);
rowsEl.appendChild(div);
}
+var siteRuleControlId = 0;
+
+function associateSiteRuleLabels(ruleEl) {
+ ruleEl.querySelectorAll(".site-rule-option").forEach(function(option) {
+ var label = Array.from(option.children).find(function(child) {
+ return child.tagName === "LABEL";
+ });
+ var controls = option.querySelectorAll("input, select, textarea");
+ if (!label || controls.length !== 1 || label.contains(controls[0])) return;
+ controls[0].id = "site-rule-control-" + ++siteRuleControlId;
+ label.htmlFor = controls[0].id;
+ });
+
+ ruleEl.querySelectorAll(".margin-pad-cell").forEach(function(cell) {
+ var input = cell.querySelector("input");
+ var miniLabel = cell.querySelector(".margin-pad-mini");
+ if (input && miniLabel) {
+ input.setAttribute(
+ "aria-label",
+ "Controller margin " +
+ (miniLabel.textContent === "T" ? "top" : "bottom")
+ );
+ }
+ });
+
+ var removeRule = ruleEl.querySelector(".remove-site-rule");
+ if (removeRule) removeRule.setAttribute("aria-label", "Remove site rule");
+}
+
function createSiteRule(rule) {
var template = document.getElementById("siteRuleTemplate");
var clone = template.content.cloneNode(true);
@@ -1377,6 +1413,7 @@ function createSiteRule(rule) {
}
applySiteRuleOverrideState(ruleEl, "override-shortcuts", "site-shortcuts-container");
refreshSiteRuleAddShortcutSelector(ruleEl);
+ associateSiteRuleLabels(ruleEl);
document.getElementById("siteRulesContainer").appendChild(ruleEl);
}
@@ -1385,7 +1422,8 @@ function createControlBarBlock(buttonId) {
var def = controllerButtonDefs[buttonId];
if (!def) return null;
- var block = document.createElement("div");
+ var block = document.createElement("button");
+ block.type = "button";
block.className = "cb-block";
block.dataset.buttonId = buttonId;
block.draggable = true;
@@ -1408,6 +1446,25 @@ function createControlBarBlock(buttonId) {
return block;
}
+function updateControlBarBlockLabels(editor) {
+ if (!editor) return;
+ editor.querySelectorAll(".cb-dropzone").forEach(function(zone) {
+ var state = zone.classList.contains("cb-active-zone")
+ ? "active"
+ : "available";
+ zone.querySelectorAll(".cb-block").forEach(function(block) {
+ var def = controllerButtonDefs[block.dataset.buttonId];
+ block.setAttribute(
+ "aria-label",
+ (def ? def.name : block.dataset.buttonId) +
+ ", " +
+ state +
+ ". Press Enter to move; use arrow keys to reorder."
+ );
+ });
+ });
+}
+
function populateControlBarZones(activeZone, availableZone, activeIds, allowButtonId) {
vscClearElement(activeZone);
vscClearElement(availableZone);
@@ -1430,6 +1487,8 @@ function populateControlBarZones(activeZone, availableZone, activeIds, allowButt
if (block) availableZone.appendChild(block);
}
});
+
+ updateControlBarBlockLabels(activeZone.closest(".cb-editor"));
}
function readControlBarOrder(activeZone) {
@@ -1473,11 +1532,11 @@ function updatePopupEditorDisabledState() {
var checkbox = document.getElementById("popupMatchHoverControls");
var wrap = document.getElementById("popupCbEditorWrap");
if (!checkbox || !wrap) return;
- if (checkbox.checked) {
- wrap.classList.add("cb-editor-disabled");
- } else {
- wrap.classList.remove("cb-editor-disabled");
- }
+ wrap.classList.toggle("cb-editor-disabled", checkbox.checked);
+ wrap.querySelectorAll(".cb-block").forEach(function(block) {
+ block.disabled = checkbox.checked;
+ block.draggable = !checkbox.checked;
+ });
}
function getDragAfterElement(container, x, y) {
@@ -1500,6 +1559,8 @@ function getDragAfterElement(container, x, y) {
}
function initControlBarEditor() {
+ if (document.vscControlBarEditorInitialized) return;
+ document.vscControlBarEditorInitialized = true;
var draggedBlock = null;
function clearControlBarDropTargets(activeZone) {
@@ -1512,7 +1573,7 @@ function initControlBarEditor() {
document.addEventListener("dragstart", function (e) {
var block = e.target.closest(".cb-block");
- if (!block) return;
+ if (!block || block.disabled) return;
draggedBlock = block;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", block.dataset.buttonId);
@@ -1557,11 +1618,49 @@ function initControlBarEditor() {
var zone = e.target.closest(".cb-dropzone");
if (zone) {
e.preventDefault();
+ updateControlBarBlockLabels(zone.closest(".cb-editor"));
scheduleAutoSave();
}
clearControlBarDropTargets(null);
});
+
+ document.addEventListener("click", function(e) {
+ var block = e.target.closest ? e.target.closest(".cb-block") : null;
+ if (!block || block.disabled) return;
+ var editor = block.closest(".cb-editor");
+ var currentZone = block.closest(".cb-dropzone");
+ if (!editor || !currentZone) return;
+ var otherZone = editor.querySelector(
+ currentZone.classList.contains("cb-active-zone")
+ ? ".cb-available-zone"
+ : ".cb-active-zone"
+ );
+ if (!otherZone) return;
+ otherZone.appendChild(block);
+ updateControlBarBlockLabels(editor);
+ block.focus();
+ scheduleAutoSave();
+ });
+
+ document.addEventListener("keydown", function(e) {
+ var block = e.target.closest ? e.target.closest(".cb-block") : null;
+ if (!block || block.disabled) return;
+ var previous = e.key === "ArrowLeft" || e.key === "ArrowUp";
+ var next = e.key === "ArrowRight" || e.key === "ArrowDown";
+ if (!previous && !next) return;
+ var sibling = previous ? block.previousElementSibling : block.nextElementSibling;
+ if (!sibling) return;
+ e.preventDefault();
+ if (previous) {
+ block.parentNode.insertBefore(block, sibling);
+ } else {
+ block.parentNode.insertBefore(block, sibling.nextElementSibling);
+ }
+ updateControlBarBlockLabels(block.closest(".cb-editor"));
+ block.focus();
+ scheduleAutoSave();
+ });
}
var lucidePickerSelectedSlug = null;
@@ -1633,6 +1732,10 @@ function initLucideButtonIconsUI() {
b.dataset.slug = slug;
b.title = slug;
b.setAttribute("aria-label", slug);
+ b.setAttribute(
+ "aria-pressed",
+ slug === lucidePickerSelectedSlug ? "true" : "false"
+ );
if (slug === lucidePickerSelectedSlug) {
b.classList.add("lucide-picked");
}
@@ -1654,6 +1757,10 @@ function initLucideButtonIconsUI() {
resultsEl.querySelectorAll("button"),
function (x) {
x.classList.toggle("lucide-picked", x.dataset.slug === slug);
+ x.setAttribute(
+ "aria-pressed",
+ x.dataset.slug === slug ? "true" : "false"
+ );
}
);
fetchLucideSvg(slug)
@@ -1719,7 +1826,7 @@ function initLucideButtonIconsUI() {
slug +
" for " +
action +
- ". Reload pages for the hover bar."
+ ". Open pages update automatically."
);
});
})
@@ -1902,6 +2009,13 @@ function restore_options(callback) {
}
function restore_defaults() {
+ if (
+ !window.confirm(
+ "Restore all defaults? This removes saved preferences, remembered speeds, and custom icons."
+ )
+ ) {
+ return;
+ }
var status = document.getElementById("status");
var restoreButton = document.getElementById("restore");
setOptionsSyncSettingsLoaded(false);
@@ -1981,6 +2095,8 @@ document.addEventListener("DOMContentLoaded", function () {
versionElement.textContent = manifest.version;
}
+ document.querySelectorAll("#customs .shortcut-row").forEach(labelShortcutRow);
+
restore_options();
initControlBarEditor();
diff --git a/extension/popup/popup.css b/extension/popup/popup.css
index be45ffe..f26e021 100644
--- a/extension/popup/popup.css
+++ b/extension/popup/popup.css
@@ -4,8 +4,9 @@
--border: #e2e5e9;
--border-strong: #d4d9e0;
--text: #17191c;
- --muted: #626b76;
- --accent: #111827;
+ --muted: #626b76;
+ --accent: #111827;
+ --focus-ring: #2563eb;
}
* {
@@ -83,8 +84,8 @@ button:active {
background: #f1f3f5;
}
-button:focus-visible {
- outline: 2px solid rgba(17, 24, 39, 0.14);
+button:focus-visible {
+ outline: 2px solid var(--focus-ring);
outline-offset: 2px;
}
@@ -250,8 +251,8 @@ button:focus-visible {
background: #f1f3f5;
}
-.donate-icon-btn:focus-visible {
- outline: 2px solid rgba(17, 24, 39, 0.14);
+.donate-icon-btn:focus-visible {
+ outline: 2px solid var(--focus-ring);
outline-offset: 2px;
position: relative;
z-index: 1;
@@ -295,8 +296,9 @@ button:focus-visible {
--border: #2b3138;
--border-strong: #3a414a;
--text: #f2f4f6;
- --muted: #a0a8b2;
- --accent: #f2f4f6;
+ --muted: #a0a8b2;
+ --accent: #f2f4f6;
+ --focus-ring: #93c5fd;
}
body {
diff --git a/extension/popup/popup.html b/extension/popup/popup.html
index 8f3dfc9..f27e515 100644
--- a/extension/popup/popup.html
+++ b/extension/popup/popup.html
@@ -32,7 +32,12 @@