mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-10-08 03:03:14 -04:00
feat(popup): pause Speeder per tab
This commit is contained in:
10 files changed
+279
-24
No files matched your search
@@ -9,6 +9,15 @@ function setToolbarIcon(enabled) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pausedTabIds = new Set();
|
||||||
|
|
||||||
|
function getMessageTabId(request, sender) {
|
||||||
|
if (request && Number.isInteger(request.tabId)) return request.tabId;
|
||||||
|
return sender && sender.tab && Number.isInteger(sender.tab.id)
|
||||||
|
? sender.tab.id
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
chrome.storage.sync.get(["enabled"], function(storage) {
|
chrome.storage.sync.get(["enabled"], function(storage) {
|
||||||
if (!chrome.runtime.lastError) setToolbarIcon(storage.enabled !== false);
|
if (!chrome.runtime.lastError) setToolbarIcon(storage.enabled !== false);
|
||||||
});
|
});
|
||||||
@@ -19,8 +28,40 @@ chrome.storage.onChanged.addListener(function(changes, areaName) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(function (request) {
|
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
|
||||||
if (request.action === "openOptions") {
|
if (request.action === "openOptions") {
|
||||||
chrome.tabs.create({ url: chrome.runtime.getURL("options/options.html") });
|
chrome.tabs.create({ url: chrome.runtime.getURL("options/options.html") });
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (request.action === "get_tab_pause_state") {
|
||||||
|
var queriedTabId = getMessageTabId(request, sender);
|
||||||
|
sendResponse({
|
||||||
|
paused: queriedTabId !== null && pausedTabIds.has(queriedTabId)
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (request.action === "set_tab_paused") {
|
||||||
|
var tabId = getMessageTabId(request, sender);
|
||||||
|
if (tabId === null) {
|
||||||
|
sendResponse({ paused: false });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var paused = request.paused === true;
|
||||||
|
if (paused) pausedTabIds.add(tabId);
|
||||||
|
else pausedTabIds.delete(tabId);
|
||||||
|
chrome.tabs.sendMessage(
|
||||||
|
tabId,
|
||||||
|
{ action: "set_tab_paused", paused: paused },
|
||||||
|
function () {
|
||||||
|
void chrome.runtime.lastError;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
sendResponse({ paused: paused });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.tabs.onRemoved.addListener(function(tabId) {
|
||||||
|
pausedTabIds.delete(tabId);
|
||||||
});
|
});
|
||||||
+42
-14
@@ -141,6 +141,7 @@ function getDiagnosticsSnapshot(media) {
|
|||||||
},
|
},
|
||||||
effectiveSettings: {
|
effectiveSettings: {
|
||||||
enabled: tc.settings.enabled !== false,
|
enabled: tc.settings.enabled !== false,
|
||||||
|
tabPaused: tc.tabPaused === true,
|
||||||
startHidden: tc.settings.startHidden === true,
|
startHidden: tc.settings.startHidden === true,
|
||||||
hideWithControls: tc.settings.hideWithControls === true,
|
hideWithControls: tc.settings.hideWithControls === true,
|
||||||
rememberSpeed: tc.settings.rememberSpeed === true,
|
rememberSpeed: tc.settings.rememberSpeed === true,
|
||||||
@@ -225,6 +226,7 @@ var tc = {
|
|||||||
speedAccessTimes: {},
|
speedAccessTimes: {},
|
||||||
persistedLastSpeed: 1.0,
|
persistedLastSpeed: 1.0,
|
||||||
activeSiteRule: null,
|
activeSiteRule: null,
|
||||||
|
tabPaused: false,
|
||||||
siteRuleBase: null,
|
siteRuleBase: null,
|
||||||
runtimeSettingsHydrated: false,
|
runtimeSettingsHydrated: false,
|
||||||
pendingMediaCandidates: [],
|
pendingMediaCandidates: [],
|
||||||
@@ -237,6 +239,29 @@ var tc = {
|
|||||||
: String(Date.now()) + "-" + Math.random().toString(36).slice(2)
|
: String(Date.now()) + "-" + Math.random().toString(36).slice(2)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isSpeederActiveForCurrentPage() {
|
||||||
|
return (
|
||||||
|
tc.tabPaused !== true &&
|
||||||
|
siteRuleUtils.isSpeederActiveForSite(
|
||||||
|
tc.settings.enabled,
|
||||||
|
tc.activeSiteRule
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTabPausedState(paused) {
|
||||||
|
tc.tabPaused = paused === true;
|
||||||
|
if (!tc.runtimeSettingsHydrated) return;
|
||||||
|
if (tc.tabPaused) {
|
||||||
|
clearAllSpeedRestoreEnforcement();
|
||||||
|
tc.mediaElements.slice().forEach(function(media) {
|
||||||
|
removeController(media);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initializeWhenReady(document, true);
|
||||||
|
}
|
||||||
|
|
||||||
var MIN_SPEED = Number(keyBindingUtils.MIN_SPEED) || 0.1;
|
var MIN_SPEED = Number(keyBindingUtils.MIN_SPEED) || 0.1;
|
||||||
var MAX_SPEED = Number(keyBindingUtils.MAX_SPEED) || 16;
|
var MAX_SPEED = Number(keyBindingUtils.MAX_SPEED) || 16;
|
||||||
var YT_NATIVE_MIN = 0.25;
|
var YT_NATIVE_MIN = 0.25;
|
||||||
@@ -1590,7 +1615,7 @@ function ensureController(node, parent) {
|
|||||||
// href selects site rules; re-run on every new/usable media so all runtime
|
// href selects site rules; re-run on every new/usable media so all runtime
|
||||||
// paths agree on activation and effective settings.
|
// paths agree on activation and effective settings.
|
||||||
applySiteRuleOverrides();
|
applySiteRuleOverrides();
|
||||||
if (!siteRuleUtils.isSpeederActiveForSite(tc.settings.enabled, tc.activeSiteRule)) {
|
if (!isSpeederActiveForCurrentPage()) {
|
||||||
if (node.vsc) removeController(node);
|
if (node.vsc) removeController(node);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -2398,12 +2423,24 @@ function loadInitialRuntimeSettings(attempt) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hydrateRuntimeSettings(rawStorage || {});
|
hydrateRuntimeSettings(rawStorage || {});
|
||||||
|
if (chrome.runtime && typeof chrome.runtime.sendMessage === "function") {
|
||||||
|
chrome.runtime.sendMessage({ action: "get_tab_pause_state" }, function(response) {
|
||||||
|
if (!chrome.runtime.lastError && response) {
|
||||||
|
applyTabPausedState(response.paused === true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// patchAttachShadow() is now called at top-level before this callback
|
// patchAttachShadow() is now called at top-level before this callback
|
||||||
// Add a listener for messages from the popup.
|
// Add a listener for messages from the popup.
|
||||||
// We use a global flag to ensure the listener is only attached once.
|
// We use a global flag to ensure the listener is only attached once.
|
||||||
if (!window.vscMessageListener) {
|
if (!window.vscMessageListener) {
|
||||||
chrome.runtime.onMessage.addListener(
|
chrome.runtime.onMessage.addListener(
|
||||||
function(request, sender, sendResponse) {
|
function(request, sender, sendResponse) {
|
||||||
|
if (request.action === "set_tab_paused") {
|
||||||
|
applyTabPausedState(request.paused === true);
|
||||||
|
sendResponse({ paused: tc.tabPaused });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (request.action === "rescan_page") {
|
if (request.action === "rescan_page") {
|
||||||
log("Re-scan command received from popup.", 4);
|
log("Re-scan command received from popup.", 4);
|
||||||
initializeWhenReady(document, true);
|
initializeWhenReady(document, true);
|
||||||
@@ -2462,10 +2499,7 @@ function loadInitialRuntimeSettings(attempt) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!siteRuleUtils.isSpeederActiveForSite(
|
!isSpeederActiveForCurrentPage()
|
||||||
tc.settings.enabled,
|
|
||||||
tc.activeSiteRule
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -4235,7 +4269,7 @@ function refreshAllControllerGeometry() {
|
|||||||
/** Re-match site rules for current URL and refresh controller position/opacity on every video. */
|
/** Re-match site rules for current URL and refresh controller position/opacity on every video. */
|
||||||
function reapplySiteRulesAndControllerGeometry() {
|
function reapplySiteRulesAndControllerGeometry() {
|
||||||
applySiteRuleOverrides();
|
applySiteRuleOverrides();
|
||||||
if (!siteRuleUtils.isSpeederActiveForSite(tc.settings.enabled, tc.activeSiteRule)) {
|
if (!isSpeederActiveForCurrentPage()) {
|
||||||
tc.mediaElements.slice().forEach(function(video) {
|
tc.mediaElements.slice().forEach(function(video) {
|
||||||
removeController(video);
|
removeController(video);
|
||||||
});
|
});
|
||||||
@@ -4493,10 +4527,7 @@ function attachKeydownListeners(doc) {
|
|||||||
if (isEditableShortcutTarget(event)) return;
|
if (isEditableShortcutTarget(event)) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!siteRuleUtils.isSpeederActiveForSite(
|
!isSpeederActiveForCurrentPage()
|
||||||
tc.settings.enabled,
|
|
||||||
tc.activeSiteRule
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4721,10 +4752,7 @@ function initializeNow(doc, forceReinit = false) {
|
|||||||
attachNavigationListeners();
|
attachNavigationListeners();
|
||||||
if (typeof tc.videoController === "undefined") defineVideoController();
|
if (typeof tc.videoController === "undefined") defineVideoController();
|
||||||
applySiteRuleOverrides();
|
applySiteRuleOverrides();
|
||||||
var isActive = siteRuleUtils.isSpeederActiveForSite(
|
var isActive = isSpeederActiveForCurrentPage();
|
||||||
tc.settings.enabled,
|
|
||||||
tc.activeSiteRule
|
|
||||||
);
|
|
||||||
|
|
||||||
// Keep observing while inactive so dynamically-created media/shadow roots
|
// Keep observing while inactive so dynamically-created media/shadow roots
|
||||||
// are available to the next forced SPA rescan, but remove stale controls.
|
// are available to the next forced SPA rescan, but remove stale controls.
|
||||||
|
|||||||
@@ -118,6 +118,12 @@ button:disabled {
|
|||||||
border-color: #9fbd98;
|
border-color: #9fbd98;
|
||||||
color: #285d21;
|
color: #285d21;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pauseTab[aria-pressed="true"] {
|
||||||
|
background: #fff3d6;
|
||||||
|
border-color: #d8b66b;
|
||||||
|
color: #704f0d;
|
||||||
|
}
|
||||||
|
|
||||||
.popup-divider {
|
.popup-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|||||||
@@ -24,6 +24,12 @@
|
|||||||
type="button"
|
type="button"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
>Force last saved speed</button>
|
>Force last saved speed</button>
|
||||||
|
<button
|
||||||
|
id="pauseTab"
|
||||||
|
class="popup-compact-action"
|
||||||
|
type="button"
|
||||||
|
aria-pressed="false"
|
||||||
|
>Pause on this tab</button>
|
||||||
<div class="popup-divider"></div>
|
<div class="popup-divider"></div>
|
||||||
<div id="popupControlBar" class="popup-control-bar">
|
<div id="popupControlBar" class="popup-control-bar">
|
||||||
<span id="popupSpeed" class="popup-speed">1.00</span>
|
<span id="popupSpeed" class="popup-speed">1.00</span>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
if (window.vscPopupInitialized) return;
|
||||||
|
window.vscPopupInitialized = true;
|
||||||
var speederShared =
|
var speederShared =
|
||||||
typeof SpeederShared === "object" && SpeederShared ? SpeederShared : {};
|
typeof SpeederShared === "object" && SpeederShared ? SpeederShared : {};
|
||||||
var siteRuleUtils = speederShared.siteRules || {};
|
var siteRuleUtils = speederShared.siteRules || {};
|
||||||
@@ -31,6 +33,27 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
var shortcutTargetMode = "closest";
|
var shortcutTargetMode = "closest";
|
||||||
var diagnosticContext = null;
|
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 getSafePageDetails(url) {
|
function getSafePageDetails(url) {
|
||||||
try {
|
try {
|
||||||
var parsed = new URL(url);
|
var parsed = new URL(url);
|
||||||
@@ -93,6 +116,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
.sort()
|
.sort()
|
||||||
: []
|
: []
|
||||||
},
|
},
|
||||||
|
tabPaused: diagnosticContext.tabPaused === true,
|
||||||
frame: frame && frame.diagnostics ? frame.diagnostics : null
|
frame: frame && frame.diagnostics ? frame.diagnostics : null
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
@@ -195,16 +219,23 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getActiveTabContext(callback) {
|
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) {
|
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
|
||||||
var activeTab = tabs && tabs[0] ? tabs[0] : null;
|
var activeTab = tabs && tabs[0] ? tabs[0] : null;
|
||||||
if (!activeTab || !activeTab.id) {
|
if (!activeTab || !activeTab.id) {
|
||||||
if (callback) callback({ tab: null, url: "" });
|
finish({ tab: null, url: "" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tabUrl = typeof activeTab.url === "string" ? activeTab.url : "";
|
var tabUrl = typeof activeTab.url === "string" ? activeTab.url : "";
|
||||||
if (tabUrl.length > 0) {
|
if (tabUrl.length > 0) {
|
||||||
if (callback) callback({ tab: activeTab, url: tabUrl });
|
finish({ tab: activeTab, url: tabUrl });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,13 +244,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
{ action: "get_page_context" },
|
{ action: "get_page_context" },
|
||||||
function (response) {
|
function (response) {
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
if (callback) callback({ tab: activeTab, url: "" });
|
finish({ tab: activeTab, url: "" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pageUrl =
|
var pageUrl =
|
||||||
response && typeof response.url === "string" ? response.url : "";
|
response && typeof response.url === "string" ? response.url : "";
|
||||||
if (callback) callback({ tab: activeTab, url: pageUrl });
|
finish({ tab: activeTab, url: pageUrl });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -422,6 +453,40 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 () {
|
document.querySelector("#donate").addEventListener("click", function () {
|
||||||
this.classList.add("hide");
|
this.classList.add("hide");
|
||||||
document.querySelector("#donateOptions").classList.remove("hide");
|
document.querySelector("#donateOptions").classList.remove("hide");
|
||||||
@@ -534,6 +599,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
getActiveTabContext(function (context) {
|
getActiveTabContext(function (context) {
|
||||||
if (currentRenderToken !== renderToken) return;
|
if (currentRenderToken !== renderToken) return;
|
||||||
|
|
||||||
|
var tabPaused = context && context.tabPaused === true;
|
||||||
|
updateTabPauseButton(tabPaused);
|
||||||
|
|
||||||
var url = context && context.url ? context.url : "";
|
var url = context && context.url ? context.url : "";
|
||||||
var siteRule = matchSiteRule(url, storage.siteRules);
|
var siteRule = matchSiteRule(url, storage.siteRules);
|
||||||
var siteDisabled = isSiteRuleDisabled(siteRule);
|
var siteDisabled = isSiteRuleDisabled(siteRule);
|
||||||
@@ -554,16 +622,21 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
? siteRule.forceLastSavedSpeed === true
|
? siteRule.forceLastSavedSpeed === true
|
||||||
: storage.forceLastSavedSpeed === true;
|
: storage.forceLastSavedSpeed === true;
|
||||||
diagnosticContext = {
|
diagnosticContext = {
|
||||||
|
tab: context && context.tab,
|
||||||
url: url,
|
url: url,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
siteRule: siteRule,
|
siteRule: siteRule,
|
||||||
frame: null
|
frame: null,
|
||||||
|
tabPaused: tabPaused,
|
||||||
|
siteAvailable: siteAvailable,
|
||||||
|
showBar: showBar
|
||||||
};
|
};
|
||||||
document.querySelector("#addSiteRule").disabled =
|
document.querySelector("#addSiteRule").disabled =
|
||||||
!getCurrentSiteRuleDetails(url);
|
!getCurrentSiteRuleDetails(url);
|
||||||
|
|
||||||
if (siteRule && siteRule.showPopupControlBar !== undefined) {
|
if (siteRule && siteRule.showPopupControlBar !== undefined) {
|
||||||
showBar = siteRule.showPopupControlBar;
|
showBar = siteRule.showPopupControlBar;
|
||||||
|
diagnosticContext.showBar = showBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleEnabledUI(storage.enabled !== false);
|
toggleEnabledUI(storage.enabled !== false);
|
||||||
@@ -572,7 +645,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
resolvePopupButtons(storage, siteRule),
|
resolvePopupButtons(storage, siteRule),
|
||||||
customIconsMap
|
customIconsMap
|
||||||
);
|
);
|
||||||
setControlBarVisible(siteAvailable && showBar);
|
setControlBarVisible(!tabPaused && siteAvailable && showBar);
|
||||||
|
|
||||||
|
if (tabPaused) {
|
||||||
|
setForceButtonLoading(true);
|
||||||
|
setStatusMessage("Speeder is paused for this tab.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (siteDisabled) {
|
if (siteDisabled) {
|
||||||
setForceButtonLoading(false);
|
setForceButtonLoading(false);
|
||||||
|
|||||||
@@ -33,4 +33,34 @@ describe("background toolbar state", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps tab pauses in memory and clears closed tabs", () => {
|
||||||
|
loadHtmlString("<!doctype html><html><body></body></html>");
|
||||||
|
const chrome = createChromeMock();
|
||||||
|
global.chrome = chrome;
|
||||||
|
window.chrome = chrome;
|
||||||
|
|
||||||
|
evaluateScript("extension/background/background.js");
|
||||||
|
const listener = chrome.runtime.onMessage.listeners[0];
|
||||||
|
const respond = vi.fn();
|
||||||
|
|
||||||
|
listener(
|
||||||
|
{ action: "set_tab_paused", tabId: 42, paused: true },
|
||||||
|
{},
|
||||||
|
respond
|
||||||
|
);
|
||||||
|
expect(respond).toHaveBeenLastCalledWith({ paused: true });
|
||||||
|
expect(chrome.tabs.sendMessage).toHaveBeenCalledWith(
|
||||||
|
42,
|
||||||
|
{ action: "set_tab_paused", paused: true },
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
listener({ action: "get_tab_pause_state" }, { tab: { id: 42 } }, respond);
|
||||||
|
expect(respond).toHaveBeenLastCalledWith({ paused: true });
|
||||||
|
|
||||||
|
chrome.tabs.onRemoved.emit(42);
|
||||||
|
listener({ action: "get_tab_pause_state", tabId: 42 }, {}, respond);
|
||||||
|
expect(respond).toHaveBeenLastCalledWith({ paused: false });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -173,7 +173,13 @@ export function createChromeMock(options = {}) {
|
|||||||
getManifest: vi.fn(() => ({
|
getManifest: vi.fn(() => ({
|
||||||
version: options.manifestVersion || "9.9.9"
|
version: options.manifestVersion || "9.9.9"
|
||||||
})),
|
})),
|
||||||
getURL: vi.fn((url) => "moz-extension://speeder/" + url)
|
getURL: vi.fn((url) => "moz-extension://speeder/" + url),
|
||||||
|
sendMessage: vi.fn((message, callback) => {
|
||||||
|
if (options.runtimeSendMessageImpl) {
|
||||||
|
return options.runtimeSendMessageImpl(message, callback);
|
||||||
|
}
|
||||||
|
if (callback) callback({ paused: false });
|
||||||
|
})
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
sync: syncArea,
|
sync: syncArea,
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ function createChromeMock(options) {
|
|||||||
const storageOnChanged = createChromeEvent();
|
const storageOnChanged = createChromeEvent();
|
||||||
const tabsOnActivated = createChromeEvent();
|
const tabsOnActivated = createChromeEvent();
|
||||||
const tabsOnUpdated = createChromeEvent();
|
const tabsOnUpdated = createChromeEvent();
|
||||||
|
const tabsOnRemoved = createChromeEvent();
|
||||||
const runtimeOnMessage = createChromeEvent();
|
const runtimeOnMessage = createChromeEvent();
|
||||||
|
|
||||||
const chrome = {
|
const chrome = {
|
||||||
@@ -252,6 +253,12 @@ function createChromeMock(options) {
|
|||||||
lastError: null,
|
lastError: null,
|
||||||
getManifest: vi.fn(() => clone(config.manifest) || { version: "0.0.0-test" }),
|
getManifest: vi.fn(() => clone(config.manifest) || { version: "0.0.0-test" }),
|
||||||
getURL: vi.fn((relPath) => `moz-extension://${relPath}`),
|
getURL: vi.fn((relPath) => `moz-extension://${relPath}`),
|
||||||
|
sendMessage: vi.fn((message, callback) => {
|
||||||
|
if (config.runtimeSendMessageImpl) {
|
||||||
|
return config.runtimeSendMessageImpl(message, callback);
|
||||||
|
}
|
||||||
|
if (callback) callback({ paused: false });
|
||||||
|
}),
|
||||||
onMessage: runtimeOnMessage
|
onMessage: runtimeOnMessage
|
||||||
},
|
},
|
||||||
browserAction: {
|
browserAction: {
|
||||||
@@ -272,7 +279,8 @@ function createChromeMock(options) {
|
|||||||
}),
|
}),
|
||||||
create: vi.fn(),
|
create: vi.fn(),
|
||||||
onActivated: tabsOnActivated,
|
onActivated: tabsOnActivated,
|
||||||
onUpdated: tabsOnUpdated
|
onUpdated: tabsOnUpdated,
|
||||||
|
onRemoved: tabsOnRemoved
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
onChanged: storageOnChanged,
|
onChanged: storageOnChanged,
|
||||||
|
|||||||
@@ -329,6 +329,31 @@ describe("inject.js media/controller lifecycle regressions", () => {
|
|||||||
expect(video.playbackRate).toBe(initialSpeed);
|
expect(video.playbackRate).toBe(initialSpeed);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("removes and restores controls when this tab is paused", async () => {
|
||||||
|
const chrome = bootInject();
|
||||||
|
await settleLifecycle();
|
||||||
|
const { video } = createControlledVideo();
|
||||||
|
const listener = chrome.runtime.onMessage.listeners[0];
|
||||||
|
const playbackRate = video.playbackRate;
|
||||||
|
|
||||||
|
listener(
|
||||||
|
{ action: "set_tab_paused", paused: true },
|
||||||
|
{},
|
||||||
|
vi.fn()
|
||||||
|
);
|
||||||
|
expect(window.tc.tabPaused).toBe(true);
|
||||||
|
expect(video.vsc).toBeUndefined();
|
||||||
|
expect(video.playbackRate).toBe(playbackRate);
|
||||||
|
|
||||||
|
listener(
|
||||||
|
{ action: "set_tab_paused", paused: false },
|
||||||
|
{},
|
||||||
|
vi.fn()
|
||||||
|
);
|
||||||
|
expect(window.tc.tabPaused).toBe(false);
|
||||||
|
expect(video.vsc).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it("drops a stale hover-preview shortcut target after SPA navigation", async () => {
|
it("drops a stale hover-preview shortcut target after SPA navigation", async () => {
|
||||||
bootInject({
|
bootInject({
|
||||||
url: "https://www.youtube.com/",
|
url: "https://www.youtube.com/",
|
||||||
|
|||||||
+27
-1
@@ -18,6 +18,7 @@ function bootPopup(options) {
|
|||||||
manifest: { version: "9.9.9-test" },
|
manifest: { version: "9.9.9-test" },
|
||||||
syncData: config.syncData,
|
syncData: config.syncData,
|
||||||
localData: config.localData,
|
localData: config.localData,
|
||||||
|
runtimeSendMessageImpl: config.runtimeSendMessageImpl,
|
||||||
tabsQueryResult: [
|
tabsQueryResult: [
|
||||||
config.activeTab || { id: 99, active: true, url: "https://example.com/" }
|
config.activeTab || { id: 99, active: true, url: "https://example.com/" }
|
||||||
]
|
]
|
||||||
@@ -107,7 +108,7 @@ describe("popup.js", () => {
|
|||||||
executeScriptImpl: (tabId, details, callback) => {
|
executeScriptImpl: (tabId, details, callback) => {
|
||||||
speedQueryCount += 1;
|
speedQueryCount += 1;
|
||||||
callback(
|
callback(
|
||||||
speedQueryCount <= 2
|
speedQueryCount === 1
|
||||||
? [
|
? [
|
||||||
{ speed: 1.25, preferred: false },
|
{ speed: 1.25, preferred: false },
|
||||||
{ speed: 1.5, frameToken: "playing-frame", preferred: true }
|
{ speed: 1.5, frameToken: "playing-frame", preferred: true }
|
||||||
@@ -243,6 +244,31 @@ describe("popup.js", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("pauses only the active tab for the browser session", async () => {
|
||||||
|
let paused = false;
|
||||||
|
const chrome = bootPopup({
|
||||||
|
runtimeSendMessageImpl: (message, callback) => {
|
||||||
|
if (message.action === "set_tab_paused") paused = message.paused === true;
|
||||||
|
callback({ paused });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
document.querySelector("#pauseTab").click();
|
||||||
|
|
||||||
|
expect(chrome.runtime.sendMessage).toHaveBeenCalledWith(
|
||||||
|
{ action: "set_tab_paused", tabId: 99, paused: true },
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
expect(document.querySelector("#pauseTab").textContent).toBe(
|
||||||
|
"Resume on this tab"
|
||||||
|
);
|
||||||
|
expect(document.querySelector("#popupControlBar").style.display).toBe(
|
||||||
|
"none"
|
||||||
|
);
|
||||||
|
expect(document.querySelector("#status").textContent).toContain("paused");
|
||||||
|
});
|
||||||
|
|
||||||
it("toggles enablement and closes after a successful refresh", async () => {
|
it("toggles enablement and closes after a successful refresh", async () => {
|
||||||
const chrome = bootPopup({
|
const chrome = bootPopup({
|
||||||
syncData: {
|
syncData: {
|
||||||
|
|||||||
Reference in new issue
Block a user