remoteSettings.js (3987B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* global ExtensionAPI, ExtensionCommon */ 8 9 const lazy = {}; 10 11 ChromeUtils.defineESModuleGetters(lazy, { 12 RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", 13 ServiceRequest: "resource://gre/modules/ServiceRequest.sys.mjs", 14 }); 15 16 this.remoteSettings = class RemoteSettingsAPI extends ExtensionAPI { 17 static COLLECTION = "webcompat-interventions"; 18 19 getAPI(context) { 20 const EventManager = ExtensionCommon.EventManager; 21 const { uuid } = context.extension; 22 const missingFilesCache = {}; 23 24 async function missingFiles(obj, basePath) { 25 if (!obj) { 26 return false; 27 } 28 for (let path of Array.isArray(obj) ? obj : [obj]) { 29 path = path.includes("/") ? path : `${basePath}/${path}`; 30 if (path in missingFilesCache) { 31 if (missingFilesCache[path]) { 32 return true; 33 } 34 continue; 35 } 36 const url = `moz-extension://${uuid}/${path}`; 37 missingFilesCache[path] = await new Promise(done => { 38 const req = new lazy.ServiceRequest(); 39 req.responseType = "text"; 40 req.onload = () => done(false); 41 req.onerror = () => done(true); 42 req.open("GET", url); 43 req.send(); 44 }); 45 if (missingFilesCache[path]) { 46 return true; 47 } 48 } 49 return false; 50 } 51 52 async function missingFilesInIntervention(desc) { 53 for (let intervention of desc.interventions) { 54 const { content_scripts } = intervention; 55 if ( 56 (await missingFiles(content_scripts?.js, "injections/js")) || 57 (await missingFiles(content_scripts?.css, "injections/css")) 58 ) { 59 return true; 60 } 61 } 62 return false; 63 } 64 65 async function missingFilesInShim(desc) { 66 if (desc.file) { 67 if (await missingFiles([desc.file], "shims")) { 68 return true; 69 } 70 } 71 if (desc.logos) { 72 if (await missingFiles(desc.logos, "shims")) { 73 return true; 74 } 75 } 76 const { contentScripts } = desc; 77 if (contentScripts) { 78 for (let { css, js } of contentScripts) { 79 if (await missingFiles(css, "shims")) { 80 return true; 81 } 82 if (await missingFiles(js, "shims")) { 83 return true; 84 } 85 } 86 } 87 return false; 88 } 89 90 async function markMissingFiles(update) { 91 if (update?.interventions) { 92 for (let desc of Object.values(update.interventions)) { 93 desc.isMissingFiles = await missingFilesInIntervention(desc); 94 } 95 } 96 if (update?.shims) { 97 for (let desc of Object.values(update.shims)) { 98 desc.isMissingFiles = await missingFilesInShim(desc); 99 } 100 } 101 return update; 102 } 103 104 return { 105 remoteSettings: { 106 onRemoteSettingsUpdate: new EventManager({ 107 context, 108 name: "remoteSettings.onRemoteSettingsUpdate", 109 register: fire => { 110 const callback = ({ data: { current } }) => { 111 return markMissingFiles(current[0]).then(update => { 112 fire.async(update).catch(() => {}); // ignore Message Manager disconnects 113 }); 114 }; 115 lazy 116 .RemoteSettings(RemoteSettingsAPI.COLLECTION) 117 .on("sync", callback); 118 return () => { 119 lazy 120 .RemoteSettings(RemoteSettingsAPI.COLLECTION) 121 .off("sync", callback); 122 }; 123 }, 124 }).api(), 125 async get() { 126 const current = await lazy 127 .RemoteSettings(RemoteSettingsAPI.COLLECTION) 128 .get(); 129 return await markMissingFiles(current[0]); 130 }, 131 }, 132 }; 133 } 134 };