From 1347936765eb03f157419cd7fc7a41efcc3fc69c Mon Sep 17 00:00:00 2001 From: Brandon T Date: Mon, 9 Aug 2021 14:14:31 -0400 Subject: [PATCH] Adding ability to detect media element's information based on tagId. --- Client/Frontend/Browser/PlaylistHelper.swift | 20 +++++ .../Frontend/Browser/UserScriptManager.swift | 4 + .../UserContent/UserScripts/Playlist.js | 74 +++++++++++++++++-- Data/models/PlaylistInfo.swift | 7 +- 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/Client/Frontend/Browser/PlaylistHelper.swift b/Client/Frontend/Browser/PlaylistHelper.swift index 8a529faf28b..c317a154093 100644 --- a/Client/Frontend/Browser/PlaylistHelper.swift +++ b/Client/Frontend/Browser/PlaylistHelper.swift @@ -198,3 +198,23 @@ extension PlaylistHelper: UIGestureRecognizerDelegate { return false } } + +extension PlaylistHelper { + static func getCurrentTime(webView: WKWebView, nodeTag: String, completion: @escaping (Double) -> Void) { + let token = UserScriptManager.securityTokenString + let javascript = String(format: "window.mediaCurrentTimeFromTag_%@('%s')", token, nodeTag) + + // swiftlint:disable:next safe_javascript + webView.evaluateJavaScript(javascript, completionHandler: { value, error in + if let error = error { + log.error("Error Retrieving Playlist Page Media Current Time: \(error)") + } + + if let value = value as? Double { + completion(value) + } else { + completion(0.0) + } + }) + } +} diff --git a/Client/Frontend/Browser/UserScriptManager.swift b/Client/Frontend/Browser/UserScriptManager.swift index f7b1830d9a1..30230994f2c 100644 --- a/Client/Frontend/Browser/UserScriptManager.swift +++ b/Client/Frontend/Browser/UserScriptManager.swift @@ -281,12 +281,16 @@ class UserScriptManager { let replacements = [ "$": "Playlist_\(token)", "$": "\(token)", + "$": "tagNode_\(token)", + "$": "tagUUID_\(token)", + "$": "mediaCurrentTimeFromTag_\(token)", "$": "playlistHelper_sendMessage_\(token)", "$": "playlistHelper_\(messageHandlerTokenString)", "$": "notify_\(token)", "$": "onLongPressActivated_\(token)", "$": "setupLongPress_\(token)", "$": "setupDetector_\(token)", + "$": "setupTagNode_\(token)", "$": "notifyNodeSource_\(token)", "$": "notifyNode_\(token)", "$": "observeNode_\(token)", diff --git a/Client/Frontend/UserContent/UserScripts/Playlist.js b/Client/Frontend/UserContent/UserScripts/Playlist.js index 97069c0f2f8..e9ba98d09ce 100644 --- a/Client/Frontend/UserContent/UserScripts/Playlist.js +++ b/Client/Frontend/UserContent/UserScripts/Playlist.js @@ -39,6 +39,21 @@ window.__firefox__.includeOnce("$", function() { return value; } + function uuid_v4() { + // JS cryptographically secure UUIDv4. + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); + } + + function $(node) { + if (node) { + if (!node.$) { + node.$ = uuid_v4(); + } + } + } + function $(message) { if (window.webkit.messageHandlers.$) { window.webkit.messageHandlers.$.postMessage(message); @@ -53,6 +68,7 @@ window.__firefox__.includeOnce("$", function() { } if (target.src && target.src !== "") { + $(target); $({ "securitytoken": "$", "name": name, @@ -62,12 +78,14 @@ window.__firefox__.includeOnce("$", function() { "mimeType": type, "duration": clamp_duration(target.duration), "detected": false, + "tagId": target.$ }); } else { target.querySelectorAll('source').forEach(function(node) { if (node.src && node.src !== "") { if (node.closest('video') === target) { + $(target); $({ "securitytoken": "$", "name": name, @@ -77,10 +95,12 @@ window.__firefox__.includeOnce("$", function() { "mimeType": type, "duration": clamp_duration(target.duration), "detected": false, + "tagId": target.$ }); } if (node.closest('audio') === target) { + $(target); $({ "securitytoken": "$", "name": name, @@ -90,6 +110,7 @@ window.__firefox__.includeOnce("$", function() { "mimeType": type, "duration": clamp_duration(target.duration), "detected": false, + "tagId": target.$ }); } } @@ -100,7 +121,9 @@ window.__firefox__.includeOnce("$", function() { function $() { Object.defineProperty(window, '$', { - value: + enumerable: false, + configurable: false, + value: function(localX, localY) { function execute(page, offsetX, offsetY) { var target = page.document.elementFromPoint(localX - offsetX, localY - offsetY); @@ -151,10 +174,12 @@ window.__firefox__.includeOnce("$", function() { // Elements found if (targetVideo) { + $(targetVideo); $(targetVideo, 'video'); } if (targetAudio) { + $(targetAudio); $(targetAudio, 'audio'); } } @@ -204,6 +229,7 @@ window.__firefox__.includeOnce("$", function() { } if (src !== "") { + $(node); $({ "securitytoken": "$", "name": name, @@ -212,13 +238,15 @@ window.__firefox__.includeOnce("$", function() { "pageTitle": document.title, "mimeType": mimeType, "duration": clamp_duration(node.duration), - "detected": true + "detected": true, + "tagId": node.$ }); } else { var target = node; document.querySelectorAll('source').forEach(function(node) { if (node.src !== "") { if (node.closest('video') === target) { + $(target); $({ "securitytoken": "$", "name": name, @@ -227,11 +255,13 @@ window.__firefox__.includeOnce("$", function() { "pageTitle": document.title, "mimeType": mimeType, "duration": clamp_duration(target.duration), - "detected": true + "detected": true, + "tagId": target.$ }); } if (node.closest('audio') === target) { + $(target); $({ "securitytoken": "$", "name": name, @@ -240,7 +270,8 @@ window.__firefox__.includeOnce("$", function() { "pageTitle": document.title, "mimeType": mimeType, "duration": clamp_duration(target.duration), - "detected": true + "detected": true, + "tagId": target.$ }); } } @@ -269,7 +300,14 @@ window.__firefox__.includeOnce("$", function() { } function $() { - Object.defineProperty(HTMLVideoElement.prototype, 'src', { + Object.defineProperty(HTMLMediaElement.prototype, '$', { + enumerable: false, + configurable: false, + writable: true, + value: null + }); + + Object.defineProperty(HTMLMediaElement.prototype, 'src', { enumerable: true, configurable: false, get: function(){ @@ -317,9 +355,35 @@ window.__firefox__.includeOnce("$", function() { $(); } + function $() { + Object.defineProperty(window, '$', { + enumerable: false, + configurable: false, + value: + function(tag) { + for (element of document.querySelectorAll('video')) { + if (element.$ == tag) { + return clamp_duration(element.currentTime); + } + } + + for (element of document.querySelectorAll('audio')) { + if (element.$ == tag) { + return clamp_duration(element.currentTime); + } + } + + return 0.0; + } + }); + + + } + // MARK: ----------------------------- $(); $(); + $(); }); diff --git a/Data/models/PlaylistInfo.swift b/Data/models/PlaylistInfo.swift index c459c9b88f5..0b67553c757 100644 --- a/Data/models/PlaylistInfo.swift +++ b/Data/models/PlaylistInfo.swift @@ -18,6 +18,7 @@ public struct PlaylistInfo: Codable { public let duration: TimeInterval public let detected: Bool public let dateAdded: Date + public let tagId: String public init(item: PlaylistItem) { self.name = item.name ?? "" @@ -28,9 +29,10 @@ public struct PlaylistInfo: Codable { self.duration = item.duration self.dateAdded = item.dateAdded ?? Date() self.detected = false + self.tagId = "" } - public init(name: String, src: String, pageSrc: String, pageTitle: String, mimeType: String, duration: TimeInterval, detected: Bool, dateAdded: Date) { + public init(name: String, src: String, pageSrc: String, pageTitle: String, mimeType: String, duration: TimeInterval, detected: Bool, dateAdded: Date, tagId: String) { self.name = name self.src = src self.pageSrc = pageSrc @@ -39,6 +41,7 @@ public struct PlaylistInfo: Codable { self.duration = duration self.detected = detected self.dateAdded = dateAdded + self.tagId = tagId } public init(from decoder: Decoder) throws { @@ -50,6 +53,7 @@ public struct PlaylistInfo: Codable { self.mimeType = try container.decodeIfPresent(String.self, forKey: .mimeType) ?? "" self.duration = try container.decodeIfPresent(TimeInterval.self, forKey: .duration) ?? 0.0 self.detected = try container.decodeIfPresent(Bool.self, forKey: .detected) ?? false + self.tagId = try container.decodeIfPresent(String.self, forKey: .tagId) ?? "" self.dateAdded = Date() } @@ -76,6 +80,7 @@ public struct PlaylistInfo: Codable { case mimeType case duration case detected + case tagId case dateAdded } }