Selaa lähdekoodia

fix history tracking for YouTube queue auto-advance

Content script listeners were bound to a single <video> element grabbed
once at injection time, so YouTube's client-side queue-advance (no page
reload) left them attached to a detached, stale element and no play/
pause/ended events reached background.js for subsequent queue videos.

Switch to document-level capture-phase listeners scoped to
#movie_player (so homepage/search hover-preview players and Shorts'
own player aren't picked up), and normalize stored URLs down to just
the `v` param so playlist/history entries don't carry queue/list state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brandon Wong 3 viikkoa sitten
vanhempi
commit
2fff0ac7ef

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 47 - 0
2026-07-26-claude-fix-queue-history-tracking.md


+ 1 - 1
CLAUDE.md

@@ -11,7 +11,7 @@ Firefox browser extension that manages video playlists and tracks playback histo
 - `popup/` — Extension popup UI (Alpine.js, single-page, multiple views)
 - `background.js` — Service worker: context menus, storage init, autoplay chaining, history tracking
 - `content_scripts/content.js` — Injected into YouTube pages; forwards `play`/`pause`/`ended` events to background
-- `shared/playlist-utils.js` — Shared helpers used by both popup and background (video ID extraction, deduplication, search)
+- `shared/playlist-utils.js` — Shared helpers used by the popup, background, and content script (video ID extraction, URL normalization, deduplication, search)
 
 ## Alpine.js CSP Compliance (Critical)
 

+ 21 - 17
content_scripts/content.js

@@ -4,7 +4,7 @@
   function msgPlayEvt(type, target) {
     const payload = {
       type,
-      url: window.location.href,
+      url: PlaylistUtils.cleanVideoUrl(window.location.href),
       timestamp: target.currentTime,
       duration: target.duration,
       title: document.title.replace(/ - YouTube$/, ""),
@@ -12,22 +12,26 @@
     return browser.runtime.sendMessage(payload);
   }
 
-  const vid = document.querySelector("video");
-
-  if (vid) {
-    vid.addEventListener("play", function (evt) {
-      msgPlayEvt("play", evt.target);
-    });
-    vid.addEventListener("playing", function (evt) {
-      msgPlayEvt("playing", evt.target);
-    });
-    vid.addEventListener("pause", function (evt) {
-      msgPlayEvt("pause", evt.target);
-    });
-    vid.addEventListener("ended", function (evt) {
-      msgPlayEvt("ended", evt.target);
-    });
-  }
+  // YouTube is a SPA: advancing to the next video in a queue swaps in a new
+  // <video> element without a full page load, so a listener attached to one
+  // specific element (grabbed once at injection time) stops receiving events.
+  // Listening on `document` in the capture phase catches every video, past or
+  // future, while `#movie_player` scopes this to the standard watch-page
+  // player only (excludes home/search hover-preview thumbnails, Shorts, etc).
+  ["play", "playing", "pause", "ended"].forEach(function (type) {
+    document.addEventListener(
+      type,
+      function (evt) {
+        if (
+          evt.target.tagName === "VIDEO" &&
+          evt.target.closest("#movie_player")
+        ) {
+          msgPlayEvt(type, evt.target);
+        }
+      },
+      true,
+    );
+  });
 
   function playIt(tryAgain) {
     const video = document.querySelector("video");

+ 1 - 1
manifest.json

@@ -23,7 +23,7 @@
   "content_scripts": [
     {
       "matches": ["https://*.youtube.com/*"],
-      "js": ["content_scripts/content.js"]
+      "js": ["shared/playlist-utils.js", "content_scripts/content.js"]
     }
   ]
 }

+ 20 - 2
shared/playlist-utils.js

@@ -16,6 +16,19 @@ const PlaylistUtils = {
     }
   },
 
+  /**
+   * Normalize a YouTube watch URL down to just the `v` (video ID) param,
+   * stripping queue/playlist params like `list`, `index`, and `pp` so that
+   * navigating to the stored URL later doesn't drop the user back into
+   * whatever queue or playlist they originally watched it from.
+   * @param {string} url - YouTube URL
+   * @returns {string} - Clean watch URL, or the original url if no video ID is found
+   */
+  cleanVideoUrl(url) {
+    const videoId = this.extractVideoId(url);
+    return videoId ? `https://www.youtube.com/watch?v=${videoId}` : url;
+  },
+
   /**
    * Find a video in all playlists by URL
    * @param {Object} playlists - All playlists object
@@ -69,7 +82,12 @@ const PlaylistUtils = {
     const { playlists: currentPlaylists } =
       await browser.storage.local.get("playlists");
 
-    const alreadyExists = this.findPlaylist(currentPlaylists, video.url);
+    const cleanedVideo = { ...video, url: this.cleanVideoUrl(video.url) };
+
+    const alreadyExists = this.findPlaylist(
+      currentPlaylists,
+      cleanedVideo.url,
+    );
 
     if (alreadyExists) {
       console.log("Video already exists in playlist:", alreadyExists);
@@ -77,7 +95,7 @@ const PlaylistUtils = {
     }
 
     // Add video to the specified playlist
-    const updatedPlaylist = [...currentPlaylists[playlistName], video];
+    const updatedPlaylist = [...currentPlaylists[playlistName], cleanedVideo];
     const updatedPlaylists = {
       ...currentPlaylists,
       [playlistName]: updatedPlaylist,