From d0497d60e811f0e0821044247fe4646a99f8dea9 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 3 Jul 2022 17:53:40 -0400 Subject: [PATCH] Add indicator where current player is for segments in popup --- public/popup.css | 9 + src/background.ts | 20 ++- src/config.ts | 4 +- src/content.ts | 14 +- src/messageTypes.ts | 10 +- src/popup.ts | 395 +++++++++++++++++++++++--------------------- 6 files changed, 261 insertions(+), 191 deletions(-) diff --git a/public/popup.css b/public/popup.css index 686f94ac..b9656c26 100644 --- a/public/popup.css +++ b/public/popup.css @@ -219,6 +219,15 @@ .segmentSummary > div { text-align: left; } + +.segmentActive { + color: #bdfffb; +} + +.segmentPassed { + color: #adadad; +} + /* * Category dot in segment */ diff --git a/src/background.ts b/src/background.ts index 7b0a50f2..52eec433 100644 --- a/src/background.ts +++ b/src/background.ts @@ -14,6 +14,8 @@ const utils = new Utils({ unregisterFirefoxContentScript }); +const popupPort: Record = {}; + // Used only on Firefox, which does not support non persistent background pages. const contentScriptRegistrations = {}; @@ -53,7 +55,7 @@ if (!Config.configSyncListeners.includes(onNavigationApiAvailableChange)) { Config.configSyncListeners.push(onNavigationApiAvailableChange); } -chrome.runtime.onMessage.addListener(function (request, _, callback) { +chrome.runtime.onMessage.addListener(function (request, sender, callback) { switch(request.message) { case "openConfig": chrome.tabs.create({url: chrome.runtime.getURL('options/options.html' + (request.hash ? '#' + request.hash : ''))}); @@ -100,9 +102,25 @@ chrome.runtime.onMessage.addListener(function (request, _, callback) { }); return true; } + case "time": + if (sender.tab) { + popupPort[sender.tab.id].postMessage(request); + } + return false; } }); +chrome.runtime.onConnect.addListener((port) => { + if (port.name === "popup") { + chrome.tabs.query({ + active: true, + currentWindow: true + }, tabs => { + popupPort[tabs[0].id] = port; + }); + } +}); + //add help page on install chrome.runtime.onInstalled.addListener(function () { // This let's the config sync to run fully before checking. diff --git a/src/config.ts b/src/config.ts index 0060a4ec..f74edd5e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -57,7 +57,7 @@ interface SBConfig { categoryPillUpdate: boolean, darkMode: boolean, showCategoryGuidelines: boolean, - chaptersActivated: boolean, + chaptersAvailable: boolean, // Used to cache calculated text color info categoryPillColors: { @@ -175,7 +175,7 @@ const Config: SBObject = { categoryPillUpdate: false, darkMode: true, showCategoryGuidelines: true, - chaptersActivated: true, + chaptersAvailable: true, categoryPillColors: {}, diff --git a/src/content.ts b/src/content.ts index 9213629d..b5d23146 100644 --- a/src/content.ts +++ b/src/content.ts @@ -167,6 +167,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo sendResponse({ found: sponsorDataFound, sponsorTimes: sponsorTimes, + time: video.currentTime, onMobileYouTube }); @@ -208,6 +209,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo sponsorsLookup(false).then(() => sendResponse({ found: sponsorDataFound, sponsorTimes: sponsorTimes, + time: video.currentTime, onMobileYouTube })); @@ -532,7 +534,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?: } lastTimeFromWaitingEvent = null; - previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime); + updateActiveSegment(currentTime); if (video.paused) return; if (videoMuted && !inMuteSegment(currentTime)) { @@ -783,7 +785,7 @@ function setupVideoListeners() { startSponsorSchedule(); } else { - previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, video.currentTime); + updateActiveSegment(video.currentTime); } }); video.addEventListener('ratechange', () => startSponsorSchedule()); @@ -2069,6 +2071,14 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string { return sponsorTimesMessage; } +function updateActiveSegment(currentTime: number): void { + previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime); + chrome.runtime.sendMessage({ + message: "time", + time: currentTime + }); +} + function addPageListeners(): void { const refreshListners = () => { if (!isVisible(video)) { diff --git a/src/messageTypes.ts b/src/messageTypes.ts index eba8c7ba..cdc49011 100644 --- a/src/messageTypes.ts +++ b/src/messageTypes.ts @@ -74,6 +74,7 @@ export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoF export interface IsInfoFoundMessageResponse { found: boolean; sponsorTimes: SponsorTime[]; + time: number; onMobileYouTube: boolean; } @@ -111,4 +112,11 @@ export interface VoteResponse { export interface ImportSegmentsResponse { importedSegments: SponsorTime[]; -} \ No newline at end of file +} + +export interface TimeUpdateMessage { + message: "time"; + time: number; +} + +export type PopupMessage = TimeUpdateMessage; diff --git a/src/popup.ts b/src/popup.ts index 58d7a6c2..205a0280 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -2,7 +2,7 @@ import Config from "./config"; import Utils from "./utils"; import { SponsorTime, SponsorHideType, ActionType, SegmentUUID, SponsorSourceType, StorageChangesObject, CategorySkipOption } from "./types"; -import { Message, MessageResponse, IsInfoFoundMessageResponse, ImportSegmentsResponse } from "./messageTypes"; +import { Message, MessageResponse, IsInfoFoundMessageResponse, ImportSegmentsResponse, PopupMessage } from "./messageTypes"; import { showDonationLink } from "./utils/configUtils"; import { AnimationUtils } from "./utils/animationUtils"; import { GenericUtils } from "./utils/genericUtils"; @@ -81,6 +81,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { Chapters } let segmentTab = SegmentTab.Segments; + let port: chrome.runtime.Port = null; const PageElements: PageElements = {}; @@ -237,6 +238,8 @@ async function runThePopup(messageListener?: MessageListener): Promise { }); } + setupComPort(); + //show proper disable skipping button const disableSkipping = Config.config.disableSkipping; if (disableSkipping != undefined && disableSkipping) { @@ -403,10 +406,13 @@ async function runThePopup(messageListener?: MessageListener): Promise { PageElements.whitelistButton.classList.remove("hidden"); PageElements.loadingIndicator.style.display = "none"; + downloadedTimes = request.sponsorTimes ?? []; if (request.found) { PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound"); - displayDownloadedSponsorTimes(request); + if (request.sponsorTimes) { + displayDownloadedSponsorTimes(request.sponsorTimes, request.time); + } } else { PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404"); } @@ -477,203 +483,208 @@ async function runThePopup(messageListener?: MessageListener): Promise { } //display the video times from the array at the top, in a different section - function displayDownloadedSponsorTimes(request: { found: boolean, sponsorTimes: SponsorTime[] }) { - if (request.sponsorTimes != undefined) { - let currentSegmentTab = segmentTab; - if (!request.sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter)) { - PageElements.issueReporterTabs.classList.add("hidden"); - currentSegmentTab = SegmentTab.Segments; + function displayDownloadedSponsorTimes(sponsorTimes: SponsorTime[], time: number) { + let currentSegmentTab = segmentTab; + if (!sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter)) { + PageElements.issueReporterTabs.classList.add("hidden"); + currentSegmentTab = SegmentTab.Segments; + } else { + PageElements.issueReporterTabs.classList.remove("hidden"); + } + + // Sort list by start time + const downloadedTimes = sponsorTimes + .filter((segment) => { + if (currentSegmentTab === SegmentTab.Segments) { + return segment.actionType !== ActionType.Chapter; + } else if (currentSegmentTab === SegmentTab.Chapters) { + return segment.actionType === ActionType.Chapter + && segment.source !== SponsorSourceType.YouTube; + } else { + return true; + } + }) + .sort((a, b) => a.segment[1] - b.segment[1]) + .sort((a, b) => a.segment[0] - b.segment[0]); + + //add them as buttons to the issue reporting container + const container = document.getElementById("issueReporterTimeButtons"); + while (container.firstChild) { + container.removeChild(container.firstChild); + } + + if (downloadedTimes.length > 0) { + PageElements.issueReporterImportExport.classList.remove("hidden"); + if (utils.getCategorySelection("chapter")?.option === CategorySkipOption.ShowOverlay) { + PageElements.importSegmentsButton.classList.remove("hidden"); + } + } else { + PageElements.issueReporterImportExport.classList.add("hidden"); + } + + const isVip = Config.config.isVip; + for (let i = 0; i < downloadedTimes.length; i++) { + const UUID = downloadedTimes[i].UUID; + const locked = downloadedTimes[i].locked; + const category = downloadedTimes[i].category; + const actionType = downloadedTimes[i].actionType; + + const segmentSummary = document.createElement("summary"); + segmentSummary.classList.add("segmentSummary"); + if (time >= downloadedTimes[i].segment[0]) { + if (time < downloadedTimes[i].segment[1]) { + segmentSummary.classList.add("segmentActive"); + } else { + segmentSummary.classList.add("segmentPassed"); + } + } + + const categoryColorCircle = document.createElement("span"); + categoryColorCircle.id = "sponsorTimesCategoryColorCircle" + UUID; + categoryColorCircle.style.backgroundColor = Config.config.barTypes[category]?.color; + categoryColorCircle.classList.add("dot"); + categoryColorCircle.classList.add("sponsorTimesCategoryColorCircle"); + + let extraInfo = ""; + if (downloadedTimes[i].hidden === SponsorHideType.Downvoted) { + //this one is downvoted + extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")"; + } else if (downloadedTimes[i].hidden === SponsorHideType.MinimumDuration) { + //this one is too short + extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")"; + } else if (downloadedTimes[i].hidden === SponsorHideType.Hidden) { + extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")"; + } + + const name = downloadedTimes[i].description || shortCategoryName(category); + const textNode = document.createTextNode(name + extraInfo); + const segmentTimeFromToNode = document.createElement("div"); + if (downloadedTimes[i].actionType === ActionType.Full) { + segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full"); } else { - PageElements.issueReporterTabs.classList.remove("hidden"); + segmentTimeFromToNode.innerText = GenericUtils.getFormattedTime(downloadedTimes[i].segment[0], true) + + (actionType !== ActionType.Poi + ? " " + chrome.i18n.getMessage("to") + " " + GenericUtils.getFormattedTime(downloadedTimes[i].segment[1], true) + : ""); } - // Sort list by start time - downloadedTimes = request.sponsorTimes - .filter((segment) => { - if (currentSegmentTab === SegmentTab.Segments) { - return segment.actionType !== ActionType.Chapter; - } else if (currentSegmentTab === SegmentTab.Chapters) { - return segment.actionType === ActionType.Chapter - && segment.source !== SponsorSourceType.YouTube; - } else { - return true; - } - }) - .sort((a, b) => a.segment[1] - b.segment[1]) - .sort((a, b) => a.segment[0] - b.segment[0]); + segmentTimeFromToNode.style.margin = "5px"; + + // for inline-styling purposes + const labelContainer = document.createElement("div"); + if (actionType !== ActionType.Chapter) labelContainer.appendChild(categoryColorCircle); - //add them as buttons to the issue reporting container - const container = document.getElementById("issueReporterTimeButtons"); - while (container.firstChild) { - container.removeChild(container.firstChild); + const span = document.createElement('span'); + span.className = "summaryLabel"; + span.appendChild(textNode); + labelContainer.appendChild(span); + + segmentSummary.appendChild(labelContainer); + segmentSummary.appendChild(segmentTimeFromToNode); + + const votingButtons = document.createElement("details"); + votingButtons.classList.add("votingButtons"); + + //thumbs up and down buttons + const voteButtonsContainer = document.createElement("div"); + voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + UUID; + voteButtonsContainer.classList.add("sbVoteButtonsContainer"); + + const upvoteButton = document.createElement("img"); + upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID; + upvoteButton.className = "voteButton"; + upvoteButton.title = chrome.i18n.getMessage("upvote"); + upvoteButton.src = chrome.runtime.getURL("icons/thumbs_up.svg"); + upvoteButton.addEventListener("click", () => vote(1, UUID)); + + const downvoteButton = document.createElement("img"); + downvoteButton.id = "sponsorTimesDownvoteButtonsContainer" + UUID; + downvoteButton.className = "voteButton"; + downvoteButton.title = chrome.i18n.getMessage("downvote"); + downvoteButton.src = locked && isVip ? chrome.runtime.getURL("icons/thumbs_down_locked.svg") : chrome.runtime.getURL("icons/thumbs_down.svg"); + downvoteButton.addEventListener("click", () => vote(0, UUID)); + + const uuidButton = document.createElement("img"); + uuidButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID; + uuidButton.className = "voteButton"; + uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg"); + uuidButton.title = chrome.i18n.getMessage("copySegmentID"); + uuidButton.addEventListener("click", () => { + copyToClipboard(UUID); + const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3); + stopAnimation(); + }); + + const hideButton = document.createElement("img"); + hideButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID; + hideButton.className = "voteButton"; + hideButton.title = chrome.i18n.getMessage("hideSegment"); + if (downloadedTimes[i].hidden === SponsorHideType.Hidden) { + hideButton.src = chrome.runtime.getURL("icons/not_visible.svg"); + } else { + hideButton.src = chrome.runtime.getURL("icons/visible.svg"); } + hideButton.addEventListener("click", () => { + const stopAnimation = AnimationUtils.applyLoadingAnimation(hideButton, 0.4); + stopAnimation(); - if (downloadedTimes.length > 0) { - PageElements.issueReporterImportExport.classList.remove("hidden"); - if (utils.getCategorySelection("chapter")?.option === CategorySkipOption.ShowOverlay) { - PageElements.importSegmentsButton.classList.remove("hidden"); - } - } else { - PageElements.issueReporterImportExport.classList.add("hidden"); - } - - const isVip = Config.config.isVip; - for (let i = 0; i < downloadedTimes.length; i++) { - const UUID = downloadedTimes[i].UUID; - const locked = downloadedTimes[i].locked; - const category = downloadedTimes[i].category; - const actionType = downloadedTimes[i].actionType; - - const segmentSummary = document.createElement("summary"); - segmentSummary.className = "segmentSummary"; - - const categoryColorCircle = document.createElement("span"); - categoryColorCircle.id = "sponsorTimesCategoryColorCircle" + UUID; - categoryColorCircle.style.backgroundColor = Config.config.barTypes[category]?.color; - categoryColorCircle.classList.add("dot"); - categoryColorCircle.classList.add("sponsorTimesCategoryColorCircle"); - - let extraInfo = ""; - if (downloadedTimes[i].hidden === SponsorHideType.Downvoted) { - //this one is downvoted - extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")"; - } else if (downloadedTimes[i].hidden === SponsorHideType.MinimumDuration) { - //this one is too short - extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")"; - } else if (downloadedTimes[i].hidden === SponsorHideType.Hidden) { - extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")"; - } - - const name = downloadedTimes[i].description || shortCategoryName(category); - const textNode = document.createTextNode(name + extraInfo); - const segmentTimeFromToNode = document.createElement("div"); - if (downloadedTimes[i].actionType === ActionType.Full) { - segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full"); - } else { - segmentTimeFromToNode.innerText = GenericUtils.getFormattedTime(downloadedTimes[i].segment[0], true) + - (actionType !== ActionType.Poi - ? " " + chrome.i18n.getMessage("to") + " " + GenericUtils.getFormattedTime(downloadedTimes[i].segment[1], true) - : ""); - } - - segmentTimeFromToNode.style.margin = "5px"; - - // for inline-styling purposes - const labelContainer = document.createElement("div"); - if (actionType !== ActionType.Chapter) labelContainer.appendChild(categoryColorCircle); - - const span = document.createElement('span'); - span.className = "summaryLabel"; - span.appendChild(textNode); - labelContainer.appendChild(span); - - segmentSummary.appendChild(labelContainer); - segmentSummary.appendChild(segmentTimeFromToNode); - - const votingButtons = document.createElement("details"); - votingButtons.classList.add("votingButtons"); - - //thumbs up and down buttons - const voteButtonsContainer = document.createElement("div"); - voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + UUID; - voteButtonsContainer.classList.add("sbVoteButtonsContainer"); - - const upvoteButton = document.createElement("img"); - upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID; - upvoteButton.className = "voteButton"; - upvoteButton.title = chrome.i18n.getMessage("upvote"); - upvoteButton.src = chrome.runtime.getURL("icons/thumbs_up.svg"); - upvoteButton.addEventListener("click", () => vote(1, UUID)); - - const downvoteButton = document.createElement("img"); - downvoteButton.id = "sponsorTimesDownvoteButtonsContainer" + UUID; - downvoteButton.className = "voteButton"; - downvoteButton.title = chrome.i18n.getMessage("downvote"); - downvoteButton.src = locked && isVip ? chrome.runtime.getURL("icons/thumbs_down_locked.svg") : chrome.runtime.getURL("icons/thumbs_down.svg"); - downvoteButton.addEventListener("click", () => vote(0, UUID)); - - const uuidButton = document.createElement("img"); - uuidButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID; - uuidButton.className = "voteButton"; - uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg"); - uuidButton.title = chrome.i18n.getMessage("copySegmentID"); - uuidButton.addEventListener("click", () => { - copyToClipboard(UUID); - const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3); - stopAnimation(); - }); - - const hideButton = document.createElement("img"); - hideButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID; - hideButton.className = "voteButton"; - hideButton.title = chrome.i18n.getMessage("hideSegment"); if (downloadedTimes[i].hidden === SponsorHideType.Hidden) { - hideButton.src = chrome.runtime.getURL("icons/not_visible.svg"); - } else { hideButton.src = chrome.runtime.getURL("icons/visible.svg"); + downloadedTimes[i].hidden = SponsorHideType.Visible; + } else { + hideButton.src = chrome.runtime.getURL("icons/not_visible.svg"); + downloadedTimes[i].hidden = SponsorHideType.Hidden; } - hideButton.addEventListener("click", () => { - const stopAnimation = AnimationUtils.applyLoadingAnimation(hideButton, 0.4); - stopAnimation(); - if (downloadedTimes[i].hidden === SponsorHideType.Hidden) { - hideButton.src = chrome.runtime.getURL("icons/visible.svg"); - downloadedTimes[i].hidden = SponsorHideType.Visible; - } else { - hideButton.src = chrome.runtime.getURL("icons/not_visible.svg"); - downloadedTimes[i].hidden = SponsorHideType.Hidden; - } - - messageHandler.query({ - active: true, - currentWindow: true - }, tabs => { - messageHandler.sendMessage( - tabs[0].id, - { - message: "hideSegment", - type: downloadedTimes[i].hidden, - UUID: UUID - } - ); - }); + messageHandler.query({ + active: true, + currentWindow: true + }, tabs => { + messageHandler.sendMessage( + tabs[0].id, + { + message: "hideSegment", + type: downloadedTimes[i].hidden, + UUID: UUID + } + ); }); + }); - const skipButton = document.createElement("img"); - skipButton.id = "sponsorTimesSkipButtonContainer" + UUID; - skipButton.className = "voteButton"; - skipButton.src = chrome.runtime.getURL("icons/skip.svg"); - skipButton.addEventListener("click", () => skipSegment(actionType, UUID, skipButton)); - container.addEventListener("dblclick", () => skipSegment(actionType, UUID)); + const skipButton = document.createElement("img"); + skipButton.id = "sponsorTimesSkipButtonContainer" + UUID; + skipButton.className = "voteButton"; + skipButton.src = chrome.runtime.getURL("icons/skip.svg"); + skipButton.addEventListener("click", () => skipSegment(actionType, UUID, skipButton)); + container.addEventListener("dblclick", () => skipSegment(actionType, UUID)); - //add thumbs up, thumbs down and uuid copy buttons to the container - voteButtonsContainer.appendChild(upvoteButton); - voteButtonsContainer.appendChild(downvoteButton); - voteButtonsContainer.appendChild(uuidButton); - if (downloadedTimes[i].actionType === ActionType.Skip - && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) { - voteButtonsContainer.appendChild(hideButton); - } - voteButtonsContainer.appendChild(skipButton); - - - // Will contain request status - const voteStatusContainer = document.createElement("div"); - voteStatusContainer.id = "sponsorTimesVoteStatusContainer" + UUID; - voteStatusContainer.classList.add("sponsorTimesVoteStatusContainer"); - voteStatusContainer.style.display = "none"; - - const thanksForVotingText = document.createElement("div"); - thanksForVotingText.id = "sponsorTimesThanksForVotingText" + UUID; - thanksForVotingText.classList.add("sponsorTimesThanksForVotingText"); - voteStatusContainer.appendChild(thanksForVotingText); - - votingButtons.append(segmentSummary); - votingButtons.append(voteButtonsContainer); - votingButtons.append(voteStatusContainer); - - container.appendChild(votingButtons); + //add thumbs up, thumbs down and uuid copy buttons to the container + voteButtonsContainer.appendChild(upvoteButton); + voteButtonsContainer.appendChild(downvoteButton); + voteButtonsContainer.appendChild(uuidButton); + if (downloadedTimes[i].actionType === ActionType.Skip + && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) { + voteButtonsContainer.appendChild(hideButton); } + voteButtonsContainer.appendChild(skipButton); + + + // Will contain request status + const voteStatusContainer = document.createElement("div"); + voteStatusContainer.id = "sponsorTimesVoteStatusContainer" + UUID; + voteStatusContainer.classList.add("sponsorTimesVoteStatusContainer"); + voteStatusContainer.style.display = "none"; + + const thanksForVotingText = document.createElement("div"); + thanksForVotingText.id = "sponsorTimesThanksForVotingText" + UUID; + thanksForVotingText.classList.add("sponsorTimesThanksForVotingText"); + voteStatusContainer.appendChild(thanksForVotingText); + + votingButtons.append(segmentSummary); + votingButtons.append(voteButtonsContainer); + votingButtons.append(voteStatusContainer); + + container.appendChild(votingButtons); } } @@ -1076,6 +1087,20 @@ async function runThePopup(messageListener?: MessageListener): Promise { } } } + + function setupComPort(): void { + port = chrome.runtime.connect({ name: "popup" }); + port.onDisconnect.addListener(() => setupComPort()); + port.onMessage.addListener((msg) => onMessage(msg)); + } + + function onMessage(msg: PopupMessage) { + switch (msg.message) { + case "time": + displayDownloadedSponsorTimes(downloadedTimes, msg.time); + break; + } + } } runThePopup();