forked from mirrors/gecko-dev
		
	Bug 1811443 - Support optional background.type "module" for WebExtensions Event Page scripts. r=willdurand,robwu
				
					
				
			Differential Revision: https://phabricator.services.mozilla.com/D169922
This commit is contained in:
		
							parent
							
								
									58c4c9c0ff
								
							
						
					
					
						commit
						94cb12435e
					
				
					 8 changed files with 169 additions and 3 deletions
				
			
		|  | @ -316,5 +316,8 @@ dictionary WebExtensionInit { | |||
|   sequence<DOMString>? backgroundScripts = null; | ||||
|   DOMString? backgroundWorkerScript = null; | ||||
| 
 | ||||
|   // Whether the background scripts should be loaded as ES modules. | ||||
|   boolean backgroundTypeModule = false; | ||||
| 
 | ||||
|   Promise<WebExtensionPolicy?> readyPromise; | ||||
| }; | ||||
|  |  | |||
|  | @ -2760,6 +2760,10 @@ class Extension extends ExtensionData { | |||
|     return this.manifest.background?.scripts; | ||||
|   } | ||||
| 
 | ||||
|   get backgroundTypeModule() { | ||||
|     return this.manifest.background?.type === "module"; | ||||
|   } | ||||
| 
 | ||||
|   get backgroundWorkerScript() { | ||||
|     return this.manifest.background?.service_worker; | ||||
|   } | ||||
|  | @ -2828,6 +2832,7 @@ class Extension extends ExtensionData { | |||
|     return { | ||||
|       backgroundScripts: this.backgroundScripts, | ||||
|       backgroundWorkerScript: this.backgroundWorkerScript, | ||||
|       backgroundTypeModule: this.backgroundTypeModule, | ||||
|       childModules: this.modules && this.modules.child, | ||||
|       dependencies: this.dependencies, | ||||
|       persistentBackground: this.persistentBackground, | ||||
|  |  | |||
|  | @ -142,6 +142,14 @@ ExtensionManager = { | |||
|         ({ backgroundWorkerScript } = getData(extension, "extendedData") || {}); | ||||
|       } | ||||
| 
 | ||||
|       let { backgroundTypeModule } = extension; | ||||
|       if ( | ||||
|         backgroundTypeModule == null && | ||||
|         WebExtensionPolicy.isExtensionProcess | ||||
|       ) { | ||||
|         ({ backgroundTypeModule } = getData(extension, "extendedData") || {}); | ||||
|       } | ||||
| 
 | ||||
|       policy = new WebExtensionPolicy({ | ||||
|         id: extension.id, | ||||
|         mozExtensionHostname: extension.uuid, | ||||
|  | @ -162,6 +170,7 @@ ExtensionManager = { | |||
| 
 | ||||
|         backgroundScripts, | ||||
|         backgroundWorkerScript, | ||||
|         backgroundTypeModule, | ||||
| 
 | ||||
|         contentScripts: extension.contentScripts, | ||||
|       }); | ||||
|  |  | |||
|  | @ -30,6 +30,10 @@ using namespace dom; | |||
| 
 | ||||
| static const char kProto[] = "moz-extension"; | ||||
| 
 | ||||
| static const char kBackgroundScriptTypeDefault[] = "text/javascript"; | ||||
| 
 | ||||
| static const char kBackgroundScriptTypeModule[] = "module"; | ||||
| 
 | ||||
| static const char kBackgroundPageHTMLStart[] = | ||||
|     "<!DOCTYPE html>\n\
 | ||||
| <html>\n\ | ||||
|  | @ -38,7 +42,7 @@ static const char kBackgroundPageHTMLStart[] = | |||
| 
 | ||||
| static const char kBackgroundPageHTMLScript[] = | ||||
|     "\n\
 | ||||
|     <script type=\"text/javascript\" src=\"%s\"></script>"; | ||||
|     <script type=\"%s\" src=\"%s\"></script>"; | ||||
| 
 | ||||
| static const char kBackgroundPageHTMLEnd[] = | ||||
|     "\n\
 | ||||
|  | @ -297,6 +301,8 @@ WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal, | |||
|         aInit.mBackgroundScripts.Value()); | ||||
|   } | ||||
| 
 | ||||
|   mBackgroundTypeModule = aInit.mBackgroundTypeModule; | ||||
| 
 | ||||
|   mContentScripts.SetCapacity(aInit.mContentScripts.Length()); | ||||
|   for (const auto& scriptInit : aInit.mContentScripts) { | ||||
|     // The activeTab permission is only for dynamically injected scripts,
 | ||||
|  | @ -525,11 +531,13 @@ nsCString WebExtensionPolicy::BackgroundPageHTML() const { | |||
| 
 | ||||
|   result.AppendLiteral(kBackgroundPageHTMLStart); | ||||
| 
 | ||||
|   const char* scriptType = mBackgroundTypeModule ? kBackgroundScriptTypeModule | ||||
|                                                  : kBackgroundScriptTypeDefault; | ||||
| 
 | ||||
|   for (auto& script : mBackgroundScripts.Value()) { | ||||
|     nsCString escaped; | ||||
|     nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped); | ||||
| 
 | ||||
|     result.AppendPrintf(kBackgroundPageHTMLScript, escaped.get()); | ||||
|     result.AppendPrintf(kBackgroundPageHTMLScript, scriptType, escaped.get()); | ||||
|   } | ||||
| 
 | ||||
|   result.AppendLiteral(kBackgroundPageHTMLEnd); | ||||
|  |  | |||
|  | @ -368,6 +368,8 @@ class WebExtensionPolicy final : public nsISupports, public nsWrapperCache { | |||
| 
 | ||||
|   dom::Nullable<nsTArray<nsString>> mBackgroundScripts; | ||||
| 
 | ||||
|   bool mBackgroundTypeModule = false; | ||||
| 
 | ||||
|   nsTArray<RefPtr<WebExtensionContentScript>> mContentScripts; | ||||
| 
 | ||||
|   RefPtr<dom::Promise> mReadyPromise; | ||||
|  |  | |||
|  | @ -148,6 +148,11 @@ | |||
|                     "type": "array", | ||||
|                     "items": { "$ref": "ExtensionURL" } | ||||
|                   }, | ||||
|                   "type": { | ||||
|                     "optional": true, | ||||
|                     "type": "string", | ||||
|                     "enum": ["module", "classic"] | ||||
|                   }, | ||||
|                   "persistent": { | ||||
|                     "optional": true, | ||||
|                     "type": "boolean", | ||||
|  |  | |||
|  | @ -0,0 +1,133 @@ | |||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| async function assertBackgroundScriptTypes( | ||||
|   extensionTestWrapper, | ||||
|   expectedScriptTypesMap | ||||
| ) { | ||||
|   const { baseURI } = extensionTestWrapper.extension; | ||||
|   let expectedMapWithResolvedURLs = Object.keys(expectedScriptTypesMap).reduce( | ||||
|     (result, scriptPath) => { | ||||
|       result[baseURI.resolve(scriptPath)] = expectedScriptTypesMap[scriptPath]; | ||||
|       return result; | ||||
|     }, | ||||
|     {} | ||||
|   ); | ||||
|   const page = await ExtensionTestUtils.loadContentPage( | ||||
|     baseURI.resolve("_generated_background_page.html") | ||||
|   ); | ||||
|   const scriptTypesMap = await page.spawn([], () => { | ||||
|     const scripts = Array.from( | ||||
|       this.content.document.querySelectorAll("script") | ||||
|     ); | ||||
|     return scripts.reduce((result, script) => { | ||||
|       result[script.getAttribute("src")] = script.getAttribute("type"); | ||||
|       return result; | ||||
|     }, {}); | ||||
|   }); | ||||
|   await page.close(); | ||||
|   Assert.deepEqual( | ||||
|     scriptTypesMap, | ||||
|     expectedMapWithResolvedURLs, | ||||
|     "Got the expected script type from the generated background page" | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| async function testBackgroundScriptClassic({ manifestTypeClassicSet }) { | ||||
|   const extension = ExtensionTestUtils.loadExtension({ | ||||
|     manifest: { | ||||
|       background: { | ||||
|         scripts: ["anotherScript.js", "main.js"], | ||||
|         type: manifestTypeClassicSet ? "classic" : undefined, | ||||
|       }, | ||||
|     }, | ||||
|     files: { | ||||
|       "main.js": ``, | ||||
|       "anotherScript.js": ``, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   await extension.startup(); | ||||
|   await assertBackgroundScriptTypes(extension, { | ||||
|     "main.js": "text/javascript", | ||||
|     "anotherScript.js": "text/javascript", | ||||
|   }); | ||||
|   await extension.unload(); | ||||
| } | ||||
| 
 | ||||
| add_task(async function test_background_scripts_type_default() { | ||||
|   await testBackgroundScriptClassic({ manifestTypeClassicSet: false }); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_background_scripts_type_classic() { | ||||
|   await testBackgroundScriptClassic({ manifestTypeClassicSet: true }); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_background_scripts_type_module() { | ||||
|   const extension = ExtensionTestUtils.loadExtension({ | ||||
|     manifest: { | ||||
|       background: { | ||||
|         scripts: ["anotherModule.js", "mainModule.js"], | ||||
|         type: "module", | ||||
|       }, | ||||
|     }, | ||||
|     files: { | ||||
|       "mainModule.js": ` | ||||
|         import { initBackground } from "/importedModule.js"; | ||||
|         browser.test.log("mainModule.js - ESM module executing"); | ||||
|         initBackground(); | ||||
|       `,
 | ||||
|       "importedModule.js": ` | ||||
|         export function initBackground() { | ||||
|           browser.test.onMessage.addListener((msg) => { | ||||
|             browser.test.log("importedModule.js - test message received"); | ||||
|             browser.test.sendMessage("esm-module-reply", msg); | ||||
|           }); | ||||
|           browser.test.log("importedModule.js - initBackground executed"); | ||||
|         } | ||||
|         browser.test.log("importedModule.js - ESM module loaded"); | ||||
|       `,
 | ||||
|       "anotherModule.js": ` | ||||
|         browser.test.log("anotherModule.js - ESM module loaded"); | ||||
|       `,
 | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   await extension.startup(); | ||||
|   await extension.sendMessage("test-event-value"); | ||||
|   equal( | ||||
|     await extension.awaitMessage("esm-module-reply"), | ||||
|     "test-event-value", | ||||
|     "Got the expected event from the ESM module loaded from the background script" | ||||
|   ); | ||||
|   await assertBackgroundScriptTypes(extension, { | ||||
|     "mainModule.js": "module", | ||||
|     "anotherModule.js": "module", | ||||
|   }); | ||||
|   await extension.unload(); | ||||
| }); | ||||
| 
 | ||||
| add_task(async function test_background_scripts_type_invalid() { | ||||
|   const extension = ExtensionTestUtils.loadExtension({ | ||||
|     manifest: { | ||||
|       background: { | ||||
|         scripts: ["anotherScript.js", "main.js"], | ||||
|         type: "invalid", | ||||
|       }, | ||||
|     }, | ||||
|     files: { | ||||
|       "main.js": ``, | ||||
|       "anotherScript.js": ``, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   ExtensionTestUtils.failOnSchemaWarnings(false); | ||||
|   await Assert.rejects( | ||||
|     extension.startup(), | ||||
|     /Error processing background: .* \.type must be one of/, | ||||
|     "Expected install to fail" | ||||
|   ); | ||||
|   ExtensionTestUtils.failOnSchemaWarnings(true); | ||||
| }); | ||||
|  | @ -28,6 +28,7 @@ skip-if = os == "android" # Android does not use Places for history. | |||
| [test_ext_background_sub_windows.js] | ||||
| [test_ext_background_teardown.js] | ||||
| [test_ext_background_telemetry.js] | ||||
| [test_ext_background_type_module.js] | ||||
| [test_ext_background_window_properties.js] | ||||
| skip-if = os == "android" | ||||
| [test_ext_browserSettings.js] | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Luca Greco
						Luca Greco