[Extension test] Add ability to load event_page extension as SW extension.
Add a flag to RunExtensionTest so that an existing event page based
test extension can be loaded as SW based extension. This can be
used to run extension tests with Service Worker based extensions.
This CL also adds a simple event page based tabs API test
at tabs/lazy_background_on_created/ and demonstrates that
the extension can be loaded as SW extension and the corresponding test
ExtensionApiTabTest.LazyBackgroundTabsOnCreated passes with the flag in
ServiceWorkerBasedBackgroundTest.TabsOnCreated.
Doc: https://docs.google.com/document/d/1PvXZ7VGRGdmd1s99SFByn9NCGj0GAiBhhPmfjj35ZeI/edit#
Bug: 967899
Change-Id: Ie979986e4c9f5bcac644152d8527aeb1c8c365c2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1636988
Commit-Queue: Istiaque Ahmed <[email protected]>
Reviewed-by: Devlin <[email protected]>
Cr-Commit-Position: refs/heads/master@{#666484}
diff --git a/chrome/browser/extensions/extension_apitest.cc b/chrome/browser/extensions/extension_apitest.cc
index 9d75c92..f8981cc 100644
--- a/chrome/browser/extensions/extension_apitest.cc
+++ b/chrome/browser/extensions/extension_apitest.cc
@@ -261,6 +261,11 @@
}
if (flags & kFlagLoadForLoginScreen)
browser_test_flags |= ExtensionBrowserTest::kFlagLoadForLoginScreen;
+ if (flags & kFlagRunAsServiceWorkerBasedExtension) {
+ browser_test_flags |=
+ ExtensionBrowserTest::kFlagRunAsServiceWorkerBasedExtension;
+ }
+
extension = LoadExtensionWithFlags(extension_path, browser_test_flags);
}
if (!extension) {
diff --git a/chrome/browser/extensions/extension_apitest.h b/chrome/browser/extensions/extension_apitest.h
index 90d4d8c..a72b420 100644
--- a/chrome/browser/extensions/extension_apitest.h
+++ b/chrome/browser/extensions/extension_apitest.h
@@ -66,6 +66,10 @@
// usually provided for force-installed extension on the login screen. This
// also sets the location to EXTERNAL_POLICY.
kFlagLoadForLoginScreen = 1 << 8,
+
+ // Load the event page extension as a Service Worker based background
+ // extension.
+ kFlagRunAsServiceWorkerBasedExtension = 1 << 9,
};
ExtensionApiTest();
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 1189ed4..5a63e5a 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -16,6 +16,7 @@
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/run_loop.h"
+#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
@@ -67,8 +68,12 @@
#include "extensions/browser/notification_types.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/browser/uninstall_reason.h"
+#include "extensions/common/constants.h"
#include "extensions/common/extension_set.h"
+#include "extensions/common/file_test_util.h"
+#include "extensions/common/file_util.h"
#include "extensions/common/switches.h"
+#include "extensions/common/value_builder.h"
#include "net/url_request/url_request_file_job.h"
#if defined(OS_CHROMEOS)
@@ -237,7 +242,12 @@
const Extension* ExtensionBrowserTest::LoadExtensionWithFlags(
const base::FilePath& path, int flags) {
- return LoadExtensionWithInstallParam(path, flags, std::string());
+ base::FilePath extension_path = path;
+ if (flags & kFlagRunAsServiceWorkerBasedExtension) {
+ if (!CreateServiceWorkerBasedExtension(path, &extension_path))
+ return nullptr;
+ }
+ return LoadExtensionWithInstallParam(extension_path, flags, std::string());
}
const Extension* ExtensionBrowserTest::LoadExtensionWithInstallParam(
@@ -262,6 +272,123 @@
return extension.get();
}
+bool ExtensionBrowserTest::CreateServiceWorkerBasedExtension(
+ const base::FilePath& path,
+ base::FilePath* out_path) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+
+ // This dir will contain all files for the Service Worker based extension.
+ base::FilePath temp_extension_container;
+ if (!base::CreateTemporaryDirInDir(temp_dir_.GetPath(),
+ base::FilePath::StringType(),
+ &temp_extension_container)) {
+ ADD_FAILURE() << "Could not create temporary dir for test under "
+ << temp_dir_.GetPath();
+ return false;
+ }
+
+ // Copy all files from test dir to temp dir.
+ if (!base::CopyDirectory(path, temp_extension_container,
+ true /* recursive */)) {
+ ADD_FAILURE() << path.value() << " could not be copied to "
+ << temp_extension_container.value();
+ return false;
+ }
+
+ const base::FilePath extension_root =
+ temp_extension_container.Append(path.BaseName());
+
+ std::string error;
+ std::unique_ptr<base::DictionaryValue> manifest_dict =
+ file_util::LoadManifest(extension_root, &error);
+ if (!manifest_dict) {
+ ADD_FAILURE() << path.value() << " could not load manifest: " << error;
+ return false;
+ }
+
+ // Retrieve the value of the "background" key and verify that it is
+ // non-persistent and specifies JS files.
+ // Persistent background pages or background pages that specify HTML files
+ // are not supported.
+ base::Value* background_dict =
+ manifest_dict->FindKeyOfType("background", base::Value::Type::DICTIONARY);
+ if (!background_dict) {
+ ADD_FAILURE() << path.value()
+ << " 'background' key not found in manifest.json";
+ return false;
+ }
+ {
+ base::Value* background_persistent = background_dict->FindKeyOfType(
+ "persistent", base::Value::Type::BOOLEAN);
+ if (!background_persistent || background_persistent->GetBool()) {
+ ADD_FAILURE() << path.value()
+ << ": Only event pages can be loaded as SW extension.";
+ return false;
+ }
+ }
+ base::Value* background_scripts_list =
+ background_dict->FindKeyOfType("scripts", base::Value::Type::LIST);
+ if (!background_scripts_list) {
+ ADD_FAILURE() << path.value()
+ << ": Only event pages with JS script(s) can be loaded "
+ "as SW extension.";
+ return false;
+ }
+
+ // Number of JS scripts must be > 1.
+ base::Value::ListStorage& scripts_list = background_scripts_list->GetList();
+ if (scripts_list.size() < 1) {
+ ADD_FAILURE() << path.value()
+ << ": Only event pages with JS script(s) can be loaded "
+ " as SW extension.";
+ return false;
+ }
+
+ // Generate combined script as Service Worker script using importScripts().
+ constexpr const char kGeneratedSWFileName[] = "generated_service_worker__.js";
+
+ std::vector<std::string> script_filenames;
+ for (const base::Value& script : scripts_list)
+ script_filenames.push_back(base::StrCat({"'", script.GetString(), "'"}));
+
+ base::FilePath combined_script_filepath =
+ extension_root.AppendASCII(kGeneratedSWFileName);
+ // Collision with generated script filename.
+ if (base::PathExists(combined_script_filepath)) {
+ ADD_FAILURE() << combined_script_filepath.value()
+ << " already exists, make sure " << path.value()
+ << " does not contained file named " << kGeneratedSWFileName;
+ return false;
+ }
+ std::string generated_sw_script_content = base::StringPrintf(
+ "importScripts(%s);", base::JoinString(script_filenames, ",").c_str());
+ if (!file_test_util::WriteFile(combined_script_filepath,
+ generated_sw_script_content)) {
+ ADD_FAILURE() << "Could not write combined Service Worker script to: "
+ << combined_script_filepath.value();
+ return false;
+ }
+
+ // Remove the existing background specification and replace it with a service
+ // worker.
+ background_dict->RemoveKey("persistent");
+ background_dict->RemoveKey("scripts");
+ background_dict->SetStringPath("service_worker", kGeneratedSWFileName);
+
+ // Write out manifest.json.
+ DictionaryBuilder manifest_builder(*manifest_dict);
+ std::string manifest_contents = manifest_builder.ToJSON();
+ base::FilePath manifest_path = extension_root.Append(kManifestFilename);
+ if (!file_test_util::WriteFile(manifest_path, manifest_contents)) {
+ ADD_FAILURE() << "Could not write manifest file to "
+ << manifest_path.value();
+ return false;
+ }
+
+ *out_path = extension_root;
+ return true;
+}
+
const Extension* ExtensionBrowserTest::LoadExtensionAsComponentWithManifest(
const base::FilePath& path,
const base::FilePath::CharType* manifest_relative_path) {
diff --git a/chrome/browser/extensions/extension_browsertest.h b/chrome/browser/extensions/extension_browsertest.h
index 714adf1..0ab486d 100644
--- a/chrome/browser/extensions/extension_browsertest.h
+++ b/chrome/browser/extensions/extension_browsertest.h
@@ -66,6 +66,9 @@
// Pass the FOR_LOGIN_SCREEN flag when loading the extension. This flag is
// usually provided for force-installed extension on the login screen.
kFlagLoadForLoginScreen = 1 << 4,
+
+ // Load the provided extension as Service Worker based extension.
+ kFlagRunAsServiceWorkerBasedExtension = 1 << 5,
};
ExtensionBrowserTest();
@@ -121,6 +124,16 @@
int flags,
const std::string& install_param);
+ // Converts an extension from |path| to a Service Worker based extension and
+ // returns true on success.
+ // If successful, |out_path| contains path of the converted extension.
+ //
+ // NOTE: The conversion works only for extensions with background.scripts and
+ // background.persistent = false; persistent background pages and
+ // background.page are not supported.
+ bool CreateServiceWorkerBasedExtension(const base::FilePath& path,
+ base::FilePath* out_path);
+
// Loads unpacked extension from |path| with manifest |manifest_relative_path|
// and imitates that it is a component extension.
// |manifest_relative_path| is relative to |path|.
diff --git a/chrome/browser/extensions/extension_tabs_apitest.cc b/chrome/browser/extensions/extension_tabs_apitest.cc
index b0eaaa2..452e67b 100644
--- a/chrome/browser/extensions/extension_tabs_apitest.cc
+++ b/chrome/browser/extensions/extension_tabs_apitest.cc
@@ -221,6 +221,10 @@
ASSERT_TRUE(RunExtensionTest("tabs/on_created")) << message_;
}
+IN_PROC_BROWSER_TEST_F(ExtensionApiTabTest, LazyBackgroundTabsOnCreated) {
+ ASSERT_TRUE(RunExtensionTest("tabs/lazy_background_on_created")) << message_;
+}
+
IN_PROC_BROWSER_TEST_F(ExtensionApiTabTest, TabsOnUpdated) {
ASSERT_TRUE(RunExtensionTest("tabs/on_updated")) << message_;
}
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index b92e752..4b47ee65 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -1794,6 +1794,12 @@
EXPECT_TRUE(moved_tab_listener.WaitUntilSatisfied());
}
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, TabsOnCreated) {
+ ASSERT_TRUE(RunExtensionTestWithFlags("tabs/lazy_background_on_created",
+ kFlagRunAsServiceWorkerBasedExtension))
+ << message_;
+}
+
// Tests that console messages logged by extension service workers, both via
// the typical console.* methods and via our custom bindings console, are
// passed through the normal ServiceWorker console messaging and are
diff --git a/chrome/test/data/extensions/api_test/tabs/lazy_background_on_created/manifest.json b/chrome/test/data/extensions/api_test/tabs/lazy_background_on_created/manifest.json
new file mode 100644
index 0000000..b3510f9d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tabs/lazy_background_on_created/manifest.json
@@ -0,0 +1,10 @@
+{
+ "name": "tabs/lazy_background_on_created",
+ "version": "0.1",
+ "manifest_version": 2,
+ "description": "Tests chrome.tabs.onCreated from event page.",
+ "background": {
+ "scripts": ["test.js"],
+ "persistent": false
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/tabs/lazy_background_on_created/test.js b/chrome/test/data/extensions/api_test/tabs/lazy_background_on_created/test.js
new file mode 100644
index 0000000..b8c4683
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tabs/lazy_background_on_created/test.js
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.runTests([
+ function testCreateWithActiveTrue() {
+ chrome.test.listenOnce(chrome.tabs.onCreated,
+ function(tab) {
+ chrome.test.assertEq(tab.active, true);
+ });
+ chrome.tabs.create({url: 'chrome://newtab/', active: true});
+ },
+ function testCreateWithActiveFalse() {
+ chrome.test.listenOnce(chrome.tabs.onCreated,
+ function(tab) {
+ chrome.test.assertEq(tab.active, false);
+ });
+ chrome.tabs.create({url: 'chrome://newtab/', active: false});
+ }
+]);