mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-10-08 03:03:14 -04:00
feat(popup): copy privacy-safe diagnostics
This commit is contained in:
6 files changed
+206
No files matched your search
@@ -12,6 +12,10 @@
|
|||||||
typeof tc === "object" && typeof tc.frameToken === "string"
|
typeof tc === "object" && typeof tc.frameToken === "string"
|
||||||
? tc.frameToken
|
? tc.frameToken
|
||||||
: null,
|
: null,
|
||||||
|
diagnostics:
|
||||||
|
typeof getDiagnosticsSnapshot === "function"
|
||||||
|
? getDiagnosticsSnapshot(v)
|
||||||
|
: null,
|
||||||
preferred: !v.paused,
|
preferred: !v.paused,
|
||||||
forceLastSavedSpeed: Boolean(
|
forceLastSavedSpeed: Boolean(
|
||||||
typeof tc === "object" && tc.settings && tc.settings.forceLastSavedSpeed
|
typeof tc === "object" && tc.settings && tc.settings.forceLastSavedSpeed
|
||||||
|
|||||||
@@ -90,6 +90,80 @@ function getPrimaryVideoElement(mediaElements) {
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDiagnosticsSnapshot(media) {
|
||||||
|
var primary = media || getPrimaryVideoElement();
|
||||||
|
if (!primary) return null;
|
||||||
|
|
||||||
|
var controller = primary.vsc || null;
|
||||||
|
var wrapper = controller && controller.div;
|
||||||
|
var fullscreenElement = getFullscreenElement(primary.ownerDocument);
|
||||||
|
var rect = null;
|
||||||
|
try {
|
||||||
|
rect = primary.getBoundingClientRect();
|
||||||
|
} catch (_error) {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mediaCount: tc.mediaElements.filter(function(item) {
|
||||||
|
return item && item.isConnected;
|
||||||
|
}).length,
|
||||||
|
mediaType: String(primary.nodeName || "media").toLowerCase(),
|
||||||
|
playbackRate: Number(primary.playbackRate),
|
||||||
|
paused: primary.paused === true,
|
||||||
|
ended: primary.ended === true,
|
||||||
|
readyState: Number(primary.readyState) || 0,
|
||||||
|
muted: primary.muted === true,
|
||||||
|
volume: Number(primary.volume),
|
||||||
|
dimensions: rect
|
||||||
|
? [Math.round(Number(rect.width) || 0), Math.round(Number(rect.height) || 0)]
|
||||||
|
: [0, 0],
|
||||||
|
fullscreen: {
|
||||||
|
active: Boolean(fullscreenElement),
|
||||||
|
element: fullscreenElement
|
||||||
|
? String(fullscreenElement.nodeName || "element").toLowerCase()
|
||||||
|
: null,
|
||||||
|
ownsMedia: Boolean(
|
||||||
|
fullscreenElement &&
|
||||||
|
(fullscreenElement === primary ||
|
||||||
|
isComposedDescendant(primary, fullscreenElement))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
controller: {
|
||||||
|
present: Boolean(controller && wrapper),
|
||||||
|
connected: Boolean(wrapper && wrapper.isConnected),
|
||||||
|
hidden: Boolean(wrapper && wrapper.classList.contains("vsc-hidden")),
|
||||||
|
geometryHidden: Boolean(
|
||||||
|
wrapper && wrapper.classList.contains("vsc-geometry-hidden")
|
||||||
|
),
|
||||||
|
fullscreenPopover: Boolean(
|
||||||
|
wrapper && wrapper.classList.contains("vsc-fullscreen-popover")
|
||||||
|
),
|
||||||
|
location: controller ? controller.controllerLocation : null
|
||||||
|
},
|
||||||
|
effectiveSettings: {
|
||||||
|
enabled: tc.settings.enabled !== false,
|
||||||
|
startHidden: tc.settings.startHidden === true,
|
||||||
|
hideWithControls: tc.settings.hideWithControls === true,
|
||||||
|
rememberSpeed: tc.settings.rememberSpeed === true,
|
||||||
|
forceLastSavedSpeed: tc.settings.forceLastSavedSpeed === true,
|
||||||
|
shortcutTargetMode: tc.settings.shortcutTargetMode
|
||||||
|
},
|
||||||
|
siteRule: {
|
||||||
|
matched: Boolean(tc.activeSiteRule),
|
||||||
|
disabled: Boolean(
|
||||||
|
tc.activeSiteRule &&
|
||||||
|
siteRuleUtils.isSiteRuleDisabled(tc.activeSiteRule)
|
||||||
|
),
|
||||||
|
overrideKeys: tc.activeSiteRule
|
||||||
|
? Object.keys(tc.activeSiteRule)
|
||||||
|
.filter(function(key) {
|
||||||
|
return key !== "pattern" && key !== "title";
|
||||||
|
})
|
||||||
|
.sort()
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var tc = {
|
var tc = {
|
||||||
settings: {
|
settings: {
|
||||||
lastSpeed: getSharedDefault("lastSpeed", 1.0),
|
lastSpeed: getSharedDefault("lastSpeed", 1.0),
|
||||||
@@ -2344,6 +2418,7 @@ function loadInitialRuntimeSettings(attempt) {
|
|||||||
sendResponse({
|
sendResponse({
|
||||||
speed: videoGs.playbackRate,
|
speed: videoGs.playbackRate,
|
||||||
frameToken: tc.frameToken,
|
frameToken: tc.frameToken,
|
||||||
|
diagnostics: getDiagnosticsSnapshot(videoGs),
|
||||||
forceLastSavedSpeed: tc.settings.forceLastSavedSpeed === true,
|
forceLastSavedSpeed: tc.settings.forceLastSavedSpeed === true,
|
||||||
forceLastSavedSpeedControlledBySiteRule: Boolean(
|
forceLastSavedSpeedControlledBySiteRule: Boolean(
|
||||||
tc.activeSiteRule &&
|
tc.activeSiteRule &&
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<div class="popup-links">
|
<div class="popup-links">
|
||||||
<button id="config">Settings</button>
|
<button id="config">Settings</button>
|
||||||
<div class="popup-secondary">
|
<div class="popup-secondary">
|
||||||
|
<button id="copyDiagnostics" class="secondary">Copy diagnostics</button>
|
||||||
<button id="feedback" class="secondary">Feedback</button>
|
<button id="feedback" class="secondary">Feedback</button>
|
||||||
<button id="about" class="secondary">About</button>
|
<button id="about" class="secondary">About</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,6 +29,58 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
var forceLastSavedSpeedControlledBySiteRule = null;
|
var forceLastSavedSpeedControlledBySiteRule = null;
|
||||||
var selectedFrameToken = null;
|
var selectedFrameToken = null;
|
||||||
var shortcutTargetMode = "closest";
|
var shortcutTargetMode = "closest";
|
||||||
|
var diagnosticContext = null;
|
||||||
|
|
||||||
|
function getSafePageDetails(url) {
|
||||||
|
try {
|
||||||
|
var parsed = new URL(url);
|
||||||
|
return {
|
||||||
|
protocol: parsed.protocol,
|
||||||
|
hostname: parsed.hostname || null
|
||||||
|
};
|
||||||
|
} catch (_error) {
|
||||||
|
return { protocol: null, hostname: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDiagnosticReport() {
|
||||||
|
if (!diagnosticContext) return null;
|
||||||
|
var storage = diagnosticContext.storage;
|
||||||
|
var siteRule = diagnosticContext.siteRule;
|
||||||
|
var frame = diagnosticContext.frame;
|
||||||
|
return JSON.stringify(
|
||||||
|
{
|
||||||
|
speederVersion: chrome.runtime.getManifest().version,
|
||||||
|
browser: navigator.userAgent,
|
||||||
|
platform: navigator.platform || null,
|
||||||
|
page: getSafePageDetails(diagnosticContext.url),
|
||||||
|
globalSettings: {
|
||||||
|
enabled: storage.enabled !== false,
|
||||||
|
rememberSpeed: storage.rememberSpeed === true,
|
||||||
|
forceLastSavedSpeed: storage.forceLastSavedSpeed === true,
|
||||||
|
audioEnabled: storage.audioBoolean === true,
|
||||||
|
startHidden: storage.startHidden === true,
|
||||||
|
hideWithControls: storage.hideWithControls === true,
|
||||||
|
controllerLocation: storage.controllerLocation,
|
||||||
|
shortcutTargetMode: storage.shortcutTargetMode
|
||||||
|
},
|
||||||
|
matchedSiteRule: {
|
||||||
|
matched: Boolean(siteRule),
|
||||||
|
disabled: isSiteRuleDisabled(siteRule),
|
||||||
|
overrideKeys: siteRule
|
||||||
|
? Object.keys(siteRule)
|
||||||
|
.filter(function(key) {
|
||||||
|
return key !== "pattern" && key !== "title";
|
||||||
|
})
|
||||||
|
.sort()
|
||||||
|
: []
|
||||||
|
},
|
||||||
|
frame: frame && frame.diagnostics ? frame.diagnostics : null
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function persistExpandedSettings(rawStorage, settings, callback) {
|
function persistExpandedSettings(rawStorage, settings, callback) {
|
||||||
var mutation = vscBuildManagedStorageMutation(rawStorage, settings);
|
var mutation = vscBuildManagedStorageMutation(rawStorage, settings);
|
||||||
@@ -288,6 +340,26 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
window.open("https://github.com/SoPat712/Speeder/issues");
|
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("#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");
|
||||||
@@ -419,6 +491,12 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
forceLastSavedSpeedControlledBySiteRule
|
forceLastSavedSpeedControlledBySiteRule
|
||||||
? siteRule.forceLastSavedSpeed === true
|
? siteRule.forceLastSavedSpeed === true
|
||||||
: storage.forceLastSavedSpeed === true;
|
: storage.forceLastSavedSpeed === true;
|
||||||
|
diagnosticContext = {
|
||||||
|
url: url,
|
||||||
|
storage: storage,
|
||||||
|
siteRule: siteRule,
|
||||||
|
frame: null
|
||||||
|
};
|
||||||
|
|
||||||
if (siteRule && siteRule.showPopupControlBar !== undefined) {
|
if (siteRule && siteRule.showPopupControlBar !== undefined) {
|
||||||
showBar = siteRule.showPopupControlBar;
|
showBar = siteRule.showPopupControlBar;
|
||||||
@@ -443,6 +521,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (siteAvailable) {
|
if (siteAvailable) {
|
||||||
querySpeed(function(frameContext) {
|
querySpeed(function(frameContext) {
|
||||||
if (currentRenderToken !== renderToken) return;
|
if (currentRenderToken !== renderToken) return;
|
||||||
|
diagnosticContext.frame = frameContext || null;
|
||||||
if (
|
if (
|
||||||
frameContext &&
|
frameContext &&
|
||||||
typeof frameContext.forceLastSavedSpeed === "boolean"
|
typeof frameContext.forceLastSavedSpeed === "boolean"
|
||||||
|
|||||||
@@ -71,6 +71,9 @@
|
|||||||
if (typeof result.frameToken === "string") {
|
if (typeof result.frameToken === "string") {
|
||||||
normalized.frameToken = result.frameToken;
|
normalized.frameToken = result.frameToken;
|
||||||
}
|
}
|
||||||
|
if (result.diagnostics && typeof result.diagnostics === "object") {
|
||||||
|
normalized.diagnostics = result.diagnostics;
|
||||||
|
}
|
||||||
if (typeof result.forceLastSavedSpeed === "boolean") {
|
if (typeof result.forceLastSavedSpeed === "boolean") {
|
||||||
normalized.forceLastSavedSpeed = result.forceLastSavedSpeed;
|
normalized.forceLastSavedSpeed = result.forceLastSavedSpeed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,50 @@ describe("popup.js", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("copies redacted diagnostics for the active media frame", async () => {
|
||||||
|
bootPopup({
|
||||||
|
activeTab: {
|
||||||
|
id: 12,
|
||||||
|
active: true,
|
||||||
|
url: "https://video.example/watch?private_token=secret"
|
||||||
|
},
|
||||||
|
executeScriptImpl: (tabId, details, callback) => {
|
||||||
|
callback([
|
||||||
|
{
|
||||||
|
speed: 1.5,
|
||||||
|
preferred: true,
|
||||||
|
diagnostics: {
|
||||||
|
mediaType: "video",
|
||||||
|
fullscreen: { active: true, element: "div", ownsMedia: true },
|
||||||
|
controller: { present: true, hidden: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const writeText = vi.fn(() => Promise.resolve());
|
||||||
|
Object.defineProperty(navigator, "clipboard", {
|
||||||
|
configurable: true,
|
||||||
|
value: { writeText }
|
||||||
|
});
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
document.querySelector("#copyDiagnostics").click();
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
expect(writeText).toHaveBeenCalled();
|
||||||
|
const reportText = writeText.mock.calls.at(-1)[0];
|
||||||
|
const report = JSON.parse(reportText);
|
||||||
|
expect(report.page).toEqual({
|
||||||
|
protocol: "https:",
|
||||||
|
hostname: "video.example"
|
||||||
|
});
|
||||||
|
expect(report.frame.mediaType).toBe("video");
|
||||||
|
expect(reportText).not.toContain("private_token");
|
||||||
|
expect(reportText).not.toContain("secret");
|
||||||
|
expect(document.querySelector("#status").textContent).toContain("copied");
|
||||||
|
});
|
||||||
|
|
||||||
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