Files
speeder/extension/popup/popup.js
T

731 lines
23 KiB
JavaScript

document.addEventListener("DOMContentLoaded", function () {
if (window.vscPopupInitialized) return;
window.vscPopupInitialized = true;
var speederShared =
typeof SpeederShared === "object" && SpeederShared ? SpeederShared : {};
var siteRuleUtils = speederShared.siteRules || {};
var popupControlUtils = speederShared.popupControls || {};
/* `label` is only used if shared/ui-icons.js has no path for this action (fallback). */
var controllerButtonDefs = {
rewind: { label: "", className: "rw" },
slower: { label: "", className: "" },
faster: { label: "", className: "" },
advance: { label: "", className: "rw" },
display: { label: "", className: "hideButton" },
reset: { label: "\u21BB", className: "" },
fast: { label: "", className: "" },
nudge: { label: "", className: "" },
pause: { label: "", className: "" },
muted: { label: "", className: "" },
louder: { label: "", className: "" },
softer: { label: "", className: "" },
mark: { label: "", className: "" },
jump: { label: "", className: "" },
settings: { label: "", className: "" }
};
var defaultButtons = ["rewind", "slower", "faster", "advance", "display"];
var popupExcludedButtonIds = new Set(["settings"]);
var renderToken = 0;
var forceLastSavedSpeedControlledBySiteRule = null;
var selectedFrameToken = null;
var shortcutTargetMode = "closest";
var diagnosticContext = null;
function updateTabPauseButton(paused) {
var button = document.querySelector("#pauseTab");
button.setAttribute("aria-pressed", paused ? "true" : "false");
button.textContent = paused ? "Resume on this tab" : "Pause on this tab";
}
function getTabPauseState(tab, callback) {
if (!tab || !Number.isInteger(tab.id)) {
callback(false);
return;
}
chrome.runtime.sendMessage(
{ action: "get_tab_pause_state", tabId: tab.id },
function(response) {
callback(
!chrome.runtime.lastError && response && response.paused === true
);
}
);
}
function getCurrentSiteRuleDetails(url) {
try {
var parsed = new URL(url);
if (
(parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
!parsed.host
) {
return null;
}
return {
title: parsed.host,
pattern: parsed.protocol + "//" + parsed.host
};
} catch (_error) {
return null;
}
}
function buildDiagnosticReport() {
if (!diagnosticContext) return null;
return popupControlUtils.buildDiagnosticReport({
speederVersion: chrome.runtime.getManifest().version,
browser: navigator.userAgent,
platform: navigator.platform || null,
url: diagnosticContext.url,
storage: diagnosticContext.storage,
siteRule: diagnosticContext.siteRule,
siteRuleDisabled: isSiteRuleDisabled(diagnosticContext.siteRule),
tabPaused: diagnosticContext.tabPaused,
frame: diagnosticContext.frame
});
}
function persistExpandedSettings(rawStorage, settings, callback) {
var mutation = vscBuildManagedStorageMutation(rawStorage, settings);
function removeStaleKeys() {
if (!mutation.remove.length) {
callback(null);
return;
}
chrome.storage.sync.remove(mutation.remove, function () {
callback(chrome.runtime.lastError || null);
});
}
if (!Object.keys(mutation.set).length) {
removeStaleKeys();
return;
}
chrome.storage.sync.set(mutation.set, function () {
if (chrome.runtime.lastError) {
callback(chrome.runtime.lastError);
return;
}
removeStaleKeys();
});
}
function updateStoredSettings(update, callback) {
chrome.storage.sync.get(null, function (rawStorage) {
if (chrome.runtime.lastError) {
callback(chrome.runtime.lastError);
return;
}
var settings = vscExpandStoredSettings(rawStorage || {});
update(settings);
persistExpandedSettings(rawStorage || {}, settings, callback);
});
}
function updateForceButton(enabled) {
var button = document.getElementById("forceLastSavedSpeed");
if (!button) return;
button.setAttribute("aria-pressed", enabled ? "true" : "false");
button.title = enabled
? "Stop forcing the saved speed"
: "Keep this page at the last speed saved by Speeder";
}
function setForceButtonLoading(loading) {
var button = document.getElementById("forceLastSavedSpeed");
if (!button) return;
button.disabled = loading === true;
button.setAttribute("aria-busy", loading ? "true" : "false");
}
function matchSiteRule(url, siteRules) {
return siteRuleUtils.matchSiteRule(url, siteRules);
}
function isSiteRuleDisabled(rule) {
return siteRuleUtils.isSiteRuleDisabled(rule);
}
function resolvePopupButtons(storage, siteRule) {
return popupControlUtils.resolvePopupButtons(storage, siteRule, {
controllerButtonDefs: controllerButtonDefs,
defaultButtons: defaultButtons,
excludedIds: popupExcludedButtonIds
});
}
function setControlBarVisible(visible) {
var bar = document.getElementById("popupControlBar");
var dividers = document.querySelectorAll(".popup-divider");
if (bar) bar.style.display = visible ? "" : "none";
dividers.forEach(function (d) { d.style.display = visible ? "" : "none"; });
}
function sendToActiveTab(message, callback) {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
if (tabs[0] && tabs[0].id) {
chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
if (chrome.runtime.lastError) {
if (callback) callback(null);
} else {
if (callback) callback(response);
}
});
} else {
if (callback) callback(null);
}
});
}
function getActiveTabContext(callback) {
function finish(context) {
getTabPauseState(context.tab, function(paused) {
context.tabPaused = paused;
if (callback) callback(context);
});
}
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
var activeTab = tabs && tabs[0] ? tabs[0] : null;
if (!activeTab || !activeTab.id) {
finish({ tab: null, url: "" });
return;
}
var tabUrl = typeof activeTab.url === "string" ? activeTab.url : "";
if (tabUrl.length > 0) {
finish({ tab: activeTab, url: tabUrl });
return;
}
chrome.tabs.sendMessage(
activeTab.id,
{ action: "get_page_context" },
function (response) {
if (chrome.runtime.lastError) {
finish({ tab: activeTab, url: "" });
return;
}
var pageUrl =
response && typeof response.url === "string" ? response.url : "";
finish({ tab: activeTab, url: pageUrl });
}
);
});
}
function updateSpeedDisplay(speed) {
var el = document.getElementById("popupSpeed");
if (el) el.textContent = (speed != null ? Number(speed) : 1).toFixed(2);
}
function applySpeedAndResetFromResponse(response) {
if (response && response.speed != null) {
updateSpeedDisplay(response.speed);
}
selectedFrameToken =
response && typeof response.frameToken === "string"
? response.frameToken
: null;
}
function pickBestFrameSpeedResult(results) {
return popupControlUtils.pickBestFrameSpeedResult(results);
}
function querySpeed(callback) {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
if (!tabs[0] || tabs[0].id == null) {
if (callback) callback(null);
return;
}
var tabId = tabs[0].id;
chrome.tabs.executeScript(
tabId,
{ allFrames: true, file: "content/frame-speed-snapshot.js" },
function (results) {
if (chrome.runtime.lastError) {
sendToActiveTab({ action: "get_speed" }, function (response) {
applySpeedAndResetFromResponse(response || { speed: 1 });
if (callback) callback(response || null);
});
return;
}
var best = pickBestFrameSpeedResult(results);
if (best) {
applySpeedAndResetFromResponse(best);
if (callback) callback(best);
} else {
sendToActiveTab({ action: "get_speed" }, function (response) {
applySpeedAndResetFromResponse(response || { speed: 1 });
if (callback) callback(response || null);
});
}
}
);
});
}
function buildControlBar(buttons, customIconsMap) {
var bar = document.getElementById("popupControlBar");
if (!bar) return;
var existing = bar.querySelectorAll("button");
existing.forEach(function (btn) { btn.remove(); });
var customMap = customIconsMap || {};
buttons.forEach(function (btnId) {
var def = controllerButtonDefs[btnId];
if (!def) return;
var btn = document.createElement("button");
btn.dataset.action = btnId;
var customEntry = customMap[btnId];
if (customEntry && customEntry.svg) {
var customSpan = vscCreateSvgWrap(document, customEntry.svg, "vsc-btn-icon");
if (customSpan) {
btn.appendChild(customSpan);
} else {
btn.textContent = def.label || "?";
}
} else if (typeof vscIconSvgString === "function") {
var svgStr = vscIconSvgString(btnId, 16);
if (svgStr) {
var iconSpan = vscCreateSvgWrap(document, svgStr, "vsc-btn-icon");
if (iconSpan) {
btn.appendChild(iconSpan);
} else {
btn.textContent = def.label || "?";
}
} else {
btn.textContent = def.label || "?";
}
} else {
btn.textContent = def.label || "?";
}
if (def.className) btn.className = def.className;
btn.title = btnId.charAt(0).toUpperCase() + btnId.slice(1);
btn.setAttribute("aria-label", btn.title);
btn.addEventListener("click", function () {
if (btnId === "settings") {
window.open(chrome.runtime.getURL("options/options.html"));
return;
}
var message = { action: "run_action", actionName: btnId };
if (shortcutTargetMode !== "all" && selectedFrameToken) {
message.targetFrameToken = selectedFrameToken;
}
sendToActiveTab(
message,
function () {
querySpeed();
}
);
});
bar.appendChild(btn);
});
}
var manifest = chrome.runtime.getManifest();
var versionElement = document.querySelector("#app-version");
if (versionElement) {
versionElement.textContent = manifest.version;
}
document.querySelector("#config").addEventListener("click", function () {
window.open(chrome.runtime.getURL("options/options.html"));
});
document.querySelector("#about").addEventListener("click", function () {
window.open("https://github.com/SoPat712/Speeder");
});
document.querySelector("#feedback").addEventListener("click", function () {
window.open("https://github.com/SoPat712/Speeder/issues");
});
document.querySelector("#copyDiagnostics").addEventListener("click", function () {
var report = buildDiagnosticReport();
if (!report) {
setStatusMessage("Diagnostics are still loading.");
return;
}
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
setStatusMessage("Clipboard access is unavailable.");
return;
}
navigator.clipboard.writeText(report).then(
function () {
setStatusMessage("Diagnostics copied. Review before sharing.");
},
function () {
setStatusMessage("Could not copy diagnostics.");
}
);
});
document.querySelector("#addSiteRule").addEventListener("click", function () {
var details = diagnosticContext
? getCurrentSiteRuleDetails(diagnosticContext.url)
: null;
if (!details) {
setStatusMessage("A site rule cannot be created for this page.");
return;
}
var added = false;
updateStoredSettings(
function (settings) {
var rules = Array.isArray(settings.siteRules) ? settings.siteRules : [];
if (
rules.some(function(rule) {
return rule && rule.pattern === details.pattern;
})
) {
return;
}
settings.siteRules = rules.concat([
{
title: details.title,
pattern: details.pattern,
enabled: true
}
]);
added = true;
},
function (error) {
if (error) {
setStatusMessage("Could not add this site: " + error.message);
return;
}
setStatusMessage(
added
? "Site rule added. Opening settings..."
: "Site rule already exists. Opening settings..."
);
window.open(chrome.runtime.getURL("options/options.html"));
}
);
});
document.querySelector("#pauseTab").addEventListener("click", function () {
var tab = diagnosticContext && diagnosticContext.tab;
if (!tab || !Number.isInteger(tab.id)) {
setStatusMessage("This page cannot be paused.");
return;
}
var button = this;
var paused = button.getAttribute("aria-pressed") !== "true";
button.disabled = true;
chrome.runtime.sendMessage(
{ action: "set_tab_paused", tabId: tab.id, paused: paused },
function(response) {
button.disabled = false;
if (chrome.runtime.lastError || !response) {
setStatusMessage("Could not update this tab.");
return;
}
diagnosticContext.tabPaused = response.paused === true;
updateTabPauseButton(diagnosticContext.tabPaused);
setControlBarVisible(
!diagnosticContext.tabPaused &&
diagnosticContext.siteAvailable &&
diagnosticContext.showBar
);
setForceButtonLoading(diagnosticContext.tabPaused);
setStatusMessage(
diagnosticContext.tabPaused
? "Speeder is paused for this tab."
: "Speeder resumed for this tab."
);
}
);
});
document.querySelector("#donate").addEventListener("click", function () {
this.classList.add("hide");
document.querySelector("#donateOptions").classList.remove("hide");
});
document.querySelector("#enable").addEventListener("click", function () {
toggleEnabled(true, settingsSavedReloadMessage);
});
document.querySelector("#disable").addEventListener("click", function () {
toggleEnabled(false, settingsSavedReloadMessage);
});
document.querySelector("#refresh").addEventListener("click", function () {
setStatusMessage("Rescanning page...");
sendToActiveTab({ action: "rescan_page" }, function (response) {
if (!response) {
setStatusMessage("Cannot run on this page.");
} else if (response.status === "complete") {
setStatusMessage("Scan complete. Closing...");
setTimeout(function () { window.close(); }, 500);
} else {
setStatusMessage("Scan failed. Please reload the page.");
}
});
});
var forceLastSavedSpeedButton = document.querySelector(
"#forceLastSavedSpeed"
);
if (!forceLastSavedSpeedButton.dataset.listenerAttached) {
forceLastSavedSpeedButton.dataset.listenerAttached = "true";
forceLastSavedSpeedButton.addEventListener("click", function () {
if (forceLastSavedSpeedControlledBySiteRule === null) {
setStatusMessage("Loading page settings...");
return;
}
if (forceLastSavedSpeedControlledBySiteRule) {
setStatusMessage("Force setting is controlled by this site rule.");
return;
}
var button = this;
var enabled = button.getAttribute("aria-pressed") !== "true";
updateStoredSettings(
function (settings) {
settings.forceLastSavedSpeed = enabled;
},
function (error) {
if (error) {
setStatusMessage("Could not update speed forcing: " + error.message);
return;
}
sendToActiveTab(
{
action: "set_force_last_saved_speed",
enabled: enabled
},
function (response) {
var effectiveEnabled =
response && typeof response.enabled === "boolean"
? response.enabled
: enabled;
updateForceButton(effectiveEnabled);
if (effectiveEnabled !== enabled) {
setStatusMessage("Force setting is controlled by this site rule.");
if (response && response.speed != null) {
updateSpeedDisplay(response.speed);
}
return;
}
if (response && response.speed != null) {
updateSpeedDisplay(response.speed);
setStatusMessage(
effectiveEnabled
? "Saved speed is now forced."
: "Speed forcing is off."
);
} else {
setStatusMessage(
effectiveEnabled
? "Force enabled. No video found on this page."
: "Speed forcing is off."
);
}
}
);
}
);
});
}
function renderForActiveTab() {
var currentRenderToken = ++renderToken;
forceLastSavedSpeedControlledBySiteRule = null;
selectedFrameToken = null;
setForceButtonLoading(true);
chrome.storage.local.get(["customButtonIcons"], function (loc) {
if (currentRenderToken !== renderToken) return;
var customIconsMap =
loc && loc.customButtonIcons && typeof loc.customButtonIcons === "object"
? loc.customButtonIcons
: {};
chrome.storage.sync.get(null, function (rawStorage) {
if (currentRenderToken !== renderToken) return;
var storage = vscExpandStoredSettings(rawStorage || {});
getActiveTabContext(function (context) {
if (currentRenderToken !== renderToken) return;
var tabPaused = context && context.tabPaused === true;
updateTabPauseButton(tabPaused);
var url = context && context.url ? context.url : "";
var siteRule = matchSiteRule(url, storage.siteRules);
var siteDisabled = isSiteRuleDisabled(siteRule);
var siteAvailable = siteRuleUtils.isSpeederActiveForSite(
storage.enabled,
siteRule
);
shortcutTargetMode =
siteRule && siteRule.shortcutTargetMode !== undefined
? siteRule.shortcutTargetMode
: storage.shortcutTargetMode;
var showBar = storage.showPopupControlBar !== false;
forceLastSavedSpeedControlledBySiteRule = Boolean(
siteRule && siteRule.forceLastSavedSpeed !== undefined
);
var effectiveForceLastSavedSpeed =
forceLastSavedSpeedControlledBySiteRule
? siteRule.forceLastSavedSpeed === true
: storage.forceLastSavedSpeed === true;
diagnosticContext = {
tab: context && context.tab,
url: url,
storage: storage,
siteRule: siteRule,
frame: null,
tabPaused: tabPaused,
siteAvailable: siteAvailable,
showBar: showBar
};
document.querySelector("#addSiteRule").disabled =
!getCurrentSiteRuleDetails(url);
if (siteRule && siteRule.showPopupControlBar !== undefined) {
showBar = siteRule.showPopupControlBar;
diagnosticContext.showBar = showBar;
}
toggleEnabledUI(storage.enabled !== false);
updateForceButton(effectiveForceLastSavedSpeed);
buildControlBar(
resolvePopupButtons(storage, siteRule),
customIconsMap
);
setControlBarVisible(!tabPaused && siteAvailable && showBar);
if (tabPaused) {
setForceButtonLoading(true);
setStatusMessage("Speeder is paused for this tab.");
return;
}
if (siteDisabled) {
setForceButtonLoading(false);
setStatusMessage("Speeder is disabled for this site.");
updateSpeedDisplay(1);
return;
}
clearStatusMessage();
if (siteAvailable) {
querySpeed(function(frameContext) {
if (currentRenderToken !== renderToken) return;
diagnosticContext.frame = frameContext || null;
if (
frameContext &&
typeof frameContext.forceLastSavedSpeed === "boolean"
) {
effectiveForceLastSavedSpeed =
frameContext.forceLastSavedSpeed;
}
if (
frameContext &&
typeof frameContext.forceLastSavedSpeedControlledBySiteRule ===
"boolean"
) {
forceLastSavedSpeedControlledBySiteRule =
frameContext.forceLastSavedSpeedControlledBySiteRule;
}
updateForceButton(effectiveForceLastSavedSpeed);
setForceButtonLoading(false);
});
} else {
updateSpeedDisplay(1);
setForceButtonLoading(false);
}
});
});
});
}
chrome.tabs.onActivated.addListener(function () {
renderForActiveTab();
});
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (!tab || tab.active !== true) return;
if (changeInfo.url !== undefined || changeInfo.status === "complete") {
renderForActiveTab();
}
});
chrome.storage.onChanged.addListener(function (changes, areaName) {
if (areaName === "local" && changes.customButtonIcons) {
renderForActiveTab();
return;
}
if (areaName !== "sync") return;
if (
changes.enabled ||
changes.forceLastSavedSpeed ||
changes.showPopupControlBar ||
changes.controllerButtons ||
changes.popupMatchHoverControls ||
changes.popupControllerButtons ||
changes.siteRules ||
changes.siteRulesMeta ||
changes.siteRulesFormat
) {
renderForActiveTab();
}
});
renderForActiveTab();
function toggleEnabled(enabled, callback) {
updateStoredSettings(
function (settings) {
settings.enabled = enabled;
},
function (error) {
if (error) {
setStatusMessage("Could not update Speeder: " + error.message);
return;
}
toggleEnabledUI(enabled);
if (callback) callback(enabled);
}
);
}
function toggleEnabledUI(enabled) {
document.querySelector("#enable").classList.toggle("hide", enabled);
document.querySelector("#disable").classList.toggle("hide", !enabled);
}
function settingsSavedReloadMessage(enabled) {
setStatusMessage(
`${enabled ? "Enabled" : "Disabled"}. Open pages update automatically.`
);
}
function setStatusMessage(str) {
const status_element = document.querySelector("#status");
status_element.classList.toggle("hide", false);
status_element.textContent = str;
}
function clearStatusMessage() {
const status_element = document.querySelector("#status");
status_element.classList.toggle("hide", true);
status_element.textContent = "";
}
});