Changeset View
Changeset View
Standalone View
Standalone View
extension/content-script.js
Show All 12 Lines | 1 | /* | |||
---|---|---|---|---|---|
13 | GNU General Public License for more details. | 13 | GNU General Public License for more details. | ||
14 | 14 | | |||
15 | You should have received a copy of the GNU General Public License | 15 | You should have received a copy of the GNU General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | 16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | */ | 17 | */ | ||
18 | 18 | | |||
19 | var callbacks = {}; | 19 | var callbacks = {}; | ||
20 | 20 | | |||
21 | // from https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript | ||||
22 | function generateGuid() { | ||||
fvogt: `generateGuid`, otherwise it looks like a getter | |||||
23 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | ||||
24 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); | ||||
ognarb: var -> const | |||||
25 | return v.toString(16); | ||||
26 | }); | ||||
27 | } | ||||
28 | | ||||
21 | function addCallback(subsystem, action, callback) | 29 | function addCallback(subsystem, action, callback) | ||
22 | { | 30 | { | ||
23 | if (!callbacks[subsystem]) { | 31 | if (!callbacks[subsystem]) { | ||
24 | callbacks[subsystem] = {}; | 32 | callbacks[subsystem] = {}; | ||
25 | } | 33 | } | ||
26 | callbacks[subsystem][action] = callback; | 34 | callbacks[subsystem][action] = callback; | ||
27 | } | 35 | } | ||
28 | 36 | | |||
Show All 28 Lines | 64 | if (items.breezeScrollBars.enabled) { | |||
57 | loadBreezeScrollBars(); | 65 | loadBreezeScrollBars(); | ||
58 | } | 66 | } | ||
59 | if (items.mpris.enabled) { | 67 | if (items.mpris.enabled) { | ||
60 | loadMpris(); | 68 | loadMpris(); | ||
61 | if (items.mprisMediaSessions.enabled) { | 69 | if (items.mprisMediaSessions.enabled) { | ||
62 | loadMediaSessionsShim(); | 70 | loadMediaSessionsShim(); | ||
63 | } | 71 | } | ||
64 | } | 72 | } | ||
73 | if (items.purpose.enabled) { | ||||
74 | loadPurpose(); | ||||
75 | } | ||||
65 | }); | 76 | }); | ||
66 | 77 | | |||
67 | // BREEZE SCROLL BARS | 78 | // BREEZE SCROLL BARS | ||
68 | // ------------------------------------------------------------------------ | 79 | // ------------------------------------------------------------------------ | ||
69 | // | 80 | // | ||
70 | function loadBreezeScrollBars() { | 81 | function loadBreezeScrollBars() { | ||
71 | if (IS_FIREFOX) { | 82 | if (IS_FIREFOX) { | ||
72 | return; | 83 | return; | ||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Line(s) | 132 | } | |||
124 | document.head.appendChild(styleTag); | 135 | document.head.appendChild(styleTag); | ||
125 | } | 136 | } | ||
126 | 137 | | |||
127 | 138 | | |||
128 | // MPRIS | 139 | // MPRIS | ||
129 | // ------------------------------------------------------------------------ | 140 | // ------------------------------------------------------------------------ | ||
130 | // | 141 | // | ||
131 | 142 | | |||
132 | // we give our transfer div a "random id" for privacy | 143 | var mediaSessionsTransferDivId = generateGuid(); | ||
ognarb: var -> let | |||||
133 | // from https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript | | |||
134 | var mediaSessionsTransferDivId ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | | |||
135 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); | | |||
136 | return v.toString(16); | | |||
137 | }); | | |||
138 | 144 | | |||
139 | // also give the function a "random" name as we have to have it in global scope to be able | 145 | // also give the function a "random" name as we have to have it in global scope to be able | ||
140 | // to invoke callbacks from outside, UUID might start with a number, so prepend something | 146 | // to invoke callbacks from outside, UUID might start with a number, so prepend something | ||
141 | var mediaSessionsClassName = "f" + mediaSessionsTransferDivId.replace(/-/g, ""); | 147 | var mediaSessionsClassName = "f" + mediaSessionsTransferDivId.replace(/-/g, ""); | ||
142 | 148 | | |||
143 | var activePlayer; | 149 | var activePlayer; | ||
144 | // When a player has no duration yet, we'll wait for it becoming known | 150 | // When a player has no duration yet, we'll wait for it becoming known | ||
145 | // to determine whether to ignore it (short sound) or make it active | 151 | // to determine whether to ignore it (short sound) or make it active | ||
▲ Show 20 Lines • Show All 657 Lines • ▼ Show 20 Line(s) | 785 | window.Audio = function () { | |||
803 | 809 | | |||
804 | return createdAudio; | 810 | return createdAudio; | ||
805 | }; | 811 | }; | ||
806 | } | 812 | } | ||
807 | `); | 813 | `); | ||
808 | 814 | | |||
809 | } | 815 | } | ||
810 | } | 816 | } | ||
817 | | ||||
818 | // PURPOSE / WEB SHARE API | ||||
819 | // ------------------------------------------------------------------------ | ||||
820 | // | ||||
821 | var purposeTransferDivId = generateGuid(); | ||||
ognarb: var -> let | |||||
822 | var purposeTransferClassName = "p" + purposeTransferDivId.replace(/-/g, ""); | ||||
823 | | ||||
824 | var purposeLoaded = false; | ||||
825 | function loadPurpose() { | ||||
826 | if (purposeLoaded) { | ||||
827 | return; | ||||
828 | } | ||||
829 | | ||||
830 | purposeLoaded = true; | ||||
831 | | ||||
832 | // navigator.share must only be defined in secure (https) context | ||||
fvogt: Where is that documented? | |||||
https://w3c.github.io/web-share/#security-and-privacy-considerations
broulik: https://w3c.github.io/web-share/#security-and-privacy-considerations
> 4. Security and privacy… | |||||
833 | if (!window.isSecureContext) { | ||||
834 | return; | ||||
835 | } | ||||
836 | | ||||
837 | window.addEventListener("message", (e) => { | ||||
838 | let data = e.data || {}; | ||||
839 | let payload = data.payload; | ||||
840 | if (data.subsystem !== "purpose" || data.action !== "share" || typeof payload !== "object") { | ||||
841 | return; | ||||
842 | } | ||||
843 | | ||||
844 | sendMessage("purpose", "share", payload).then((response) => { | ||||
845 | executeScript(` | ||||
846 | function() { | ||||
847 | ${purposeTransferClassName}.pendingResolve(); | ||||
848 | } | ||||
849 | `); | ||||
850 | }, (err) => { | ||||
851 | // Deliberately not giving any more details about why it got rejected | ||||
852 | executeScript(` | ||||
853 | function() { | ||||
854 | ${purposeTransferClassName}.pendingReject(new DOMException("Share request aborted", "AbortError")); | ||||
855 | } | ||||
856 | `); | ||||
857 | }).finally(() => { | ||||
858 | executeScript(` | ||||
859 | function() { | ||||
860 | ${purposeTransferClassName}.reset(); | ||||
861 | } | ||||
862 | `); | ||||
863 | });; | ||||
864 | }); | ||||
865 | | ||||
866 | executeScript(` | ||||
867 | function() { | ||||
868 | ${purposeTransferClassName} = function() {}; | ||||
869 | let transfer = ${purposeTransferClassName}; | ||||
870 | transfer.reset = () => { | ||||
871 | transfer.pendingResolve = null; | ||||
872 | transfer.pendingReject = null; | ||||
873 | }; | ||||
874 | transfer.reset(); | ||||
875 | | ||||
876 | if (!navigator.canShare) { | ||||
877 | navigator.canShare = (data) => { | ||||
878 | if (!data) { | ||||
879 | return false; | ||||
880 | } | ||||
881 | | ||||
882 | if (data.title === undefined && data.text === undefined && data.url === undefined) { | ||||
883 | return false; | ||||
884 | } | ||||
885 | | ||||
886 | if (data.url) { | ||||
887 | // check if URL is valid | ||||
888 | try { | ||||
889 | new URL(data.url, document.location.href); | ||||
890 | } catch (e) { | ||||
891 | return false; | ||||
892 | } | ||||
893 | } | ||||
894 | | ||||
895 | return true; | ||||
896 | } | ||||
897 | } | ||||
898 | | ||||
899 | if (!navigator.share) { | ||||
900 | navigator.share = (data) => { | ||||
901 | return new Promise((resolve, reject) => { | ||||
902 | if (!navigator.canShare(data)) { | ||||
903 | return reject(new TypeError()); | ||||
904 | } | ||||
905 | | ||||
906 | if (data.url) { | ||||
907 | // validity already checked in canShare, hence no catch | ||||
908 | data.url = new URL(data.url, document.location.href).toString(); | ||||
909 | } | ||||
910 | | ||||
911 | if (!window.event || !window.event.isTrusted) { | ||||
912 | return reject(new DOMException("navigator.share can only be called in response to user interaction", "NotAllowedError")); | ||||
913 | } | ||||
914 | | ||||
915 | if (transfer.pendingResolve || transfer.pendingReject) { | ||||
916 | return reject(new DOMException("A share is already in progress", "AbortError")); | ||||
917 | } | ||||
918 | | ||||
919 | transfer.pendingResolve = resolve; | ||||
920 | transfer.pendingReject = reject; | ||||
921 | | ||||
922 | window.postMessage({ | ||||
923 | subsystem: "purpose", | ||||
924 | action: "share", | ||||
925 | payload: data | ||||
926 | }); | ||||
927 | }); | ||||
928 | }; | ||||
929 | } | ||||
930 | } | ||||
931 | `); | ||||
932 | } |
generateGuid, otherwise it looks like a getter