1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/json/json_file_value_serializer.h"
6 #include "base/json/json_writer.h"
7 #include "base/strings/string_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/extensions/api/developer_private/extension_info_generator.h"
10 #include "chrome/browser/extensions/api/developer_private/inspectable_views_finder.h"
11 #include "chrome/browser/extensions/error_console/error_console.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/extension_service_test_base.h"
14 #include "chrome/browser/extensions/extension_util.h"
15 #include "chrome/browser/extensions/permissions_updater.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/extensions/api/developer_private.h"
18 #include "chrome/common/pref_names.h"
19 #include "components/crx_file/id_util.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/common/constants.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_builder.h"
24 #include "extensions/common/feature_switch.h"
25 #include "extensions/common/value_builder.h"
26 #include "testing/gtest/include/gtest/gtest.h"
28 namespace extensions
{
30 namespace developer
= api::developer_private
;
34 const char kAllHostsPermission
[] = "*://*/*";
36 scoped_ptr
<base::DictionaryValue
> DeserializeJSONTestData(
37 const base::FilePath
& path
,
39 base::Value
* value
= nullptr;
40 JSONFileValueDeserializer
deserializer(path
);
41 value
= deserializer
.Deserialize(nullptr, error
);
42 return make_scoped_ptr(static_cast<base::DictionaryValue
*>(value
));
47 class ExtensionInfoGeneratorUnitTest
: public ExtensionServiceTestBase
{
49 ExtensionInfoGeneratorUnitTest() {}
50 ~ExtensionInfoGeneratorUnitTest() override
{}
53 void SetUp() override
{
54 ExtensionServiceTestBase::SetUp();
55 InitializeEmptyExtensionService();
58 void OnInfosGenerated(linked_ptr
<developer::ExtensionInfo
>* info_out
,
59 const ExtensionInfoGenerator::ExtensionInfoList
& list
) {
60 EXPECT_EQ(1u, list
.size());
64 quit_closure_
.Reset();
67 scoped_ptr
<developer::ExtensionInfo
> GenerateExtensionInfo(
68 const std::string
& extension_id
) {
69 linked_ptr
<developer::ExtensionInfo
> info
;
70 base::RunLoop run_loop
;
71 quit_closure_
= run_loop
.QuitClosure();
72 scoped_ptr
<ExtensionInfoGenerator
> generator(
73 new ExtensionInfoGenerator(browser_context()));
74 generator
->CreateExtensionInfo(
76 base::Bind(&ExtensionInfoGeneratorUnitTest::OnInfosGenerated
,
77 base::Unretained(this),
78 base::Unretained(&info
)));
80 return make_scoped_ptr(info
.release());
83 const scoped_refptr
<const Extension
> CreateExtension(
84 const std::string
& name
,
85 ListBuilder
& permissions
) {
86 const std::string kId
= crx_file::id_util::GenerateId(name
);
87 scoped_refptr
<const Extension
> extension
=
88 ExtensionBuilder().SetManifest(
91 .Set("description", "an extension")
92 .Set("manifest_version", 2)
93 .Set("version", "1.0.0")
94 .Set("permissions", permissions
))
95 .SetLocation(Manifest::INTERNAL
)
99 ExtensionRegistry::Get(profile())->AddEnabled(extension
);
100 PermissionsUpdater(profile()).InitializePermissions(extension
.get());
104 scoped_ptr
<developer::ExtensionInfo
> CreateExtensionInfoFromPath(
105 const base::FilePath
& extension_path
,
106 Manifest::Location location
) {
109 base::FilePath manifest_path
= extension_path
.Append(kManifestFilename
);
110 scoped_ptr
<base::DictionaryValue
> extension_data
=
111 DeserializeJSONTestData(manifest_path
, &error
);
112 EXPECT_EQ(std::string(), error
);
114 scoped_refptr
<Extension
> extension(Extension::Create(
115 extension_path
, location
, *extension_data
, Extension::REQUIRE_KEY
,
117 CHECK(extension
.get());
118 service()->AddExtension(extension
.get());
119 EXPECT_EQ(std::string(), error
);
121 return GenerateExtensionInfo(extension
->id());
124 void CompareExpectedAndActualOutput(
125 const base::FilePath
& extension_path
,
126 const InspectableViewsFinder::ViewList
& views
,
127 const base::FilePath
& expected_output_path
) {
129 scoped_ptr
<base::DictionaryValue
> expected_output_data(
130 DeserializeJSONTestData(expected_output_path
, &error
));
131 EXPECT_EQ(std::string(), error
);
133 // Produce test output.
134 scoped_ptr
<developer::ExtensionInfo
> info
=
135 CreateExtensionInfoFromPath(extension_path
, Manifest::INVALID_LOCATION
);
137 scoped_ptr
<base::DictionaryValue
> actual_output_data
= info
->ToValue();
138 ASSERT_TRUE(actual_output_data
);
140 // Compare the outputs.
141 // Ignore unknown fields in the actual output data.
142 std::string paths_details
= " - expected (" +
143 expected_output_path
.MaybeAsASCII() + ") vs. actual (" +
144 extension_path
.MaybeAsASCII() + ")";
145 std::string expected_string
;
146 std::string actual_string
;
147 for (base::DictionaryValue::Iterator
field(*expected_output_data
);
148 !field
.IsAtEnd(); field
.Advance()) {
149 const base::Value
& expected_value
= field
.value();
150 base::Value
* actual_value
= nullptr;
151 EXPECT_TRUE(actual_output_data
->Get(field
.key(), &actual_value
)) <<
152 field
.key() + " is missing" + paths_details
;
155 if (!actual_value
->Equals(&expected_value
)) {
156 base::JSONWriter::Write(expected_value
, &expected_string
);
157 base::JSONWriter::Write(*actual_value
, &actual_string
);
158 EXPECT_EQ(expected_string
, actual_string
) <<
159 field
.key() << paths_details
;
165 base::Closure quit_closure_
;
167 DISALLOW_COPY_AND_ASSIGN(ExtensionInfoGeneratorUnitTest
);
170 // Test some of the basic fields.
171 TEST_F(ExtensionInfoGeneratorUnitTest
, BasicInfoTest
) {
172 // Enable error console for testing.
173 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT
);
174 FeatureSwitch::ScopedOverride
error_console_override(
175 FeatureSwitch::error_console(), true);
176 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode
, true);
178 const char kName
[] = "extension name";
179 const char kVersion
[] = "1.0.0.1";
180 std::string id
= crx_file::id_util::GenerateId("alpha");
181 scoped_ptr
<base::DictionaryValue
> manifest
=
182 DictionaryBuilder().Set("name", kName
)
183 .Set("version", kVersion
)
184 .Set("manifest_version", 2)
185 .Set("description", "an extension")
187 ListBuilder().Append("file://*/*")).Build();
188 scoped_ptr
<base::DictionaryValue
> manifest_copy(manifest
->DeepCopy());
189 scoped_refptr
<const Extension
> extension
=
190 ExtensionBuilder().SetManifest(manifest
.Pass())
191 .SetLocation(Manifest::UNPACKED
)
195 service()->AddExtension(extension
.get());
196 ErrorConsole
* error_console
= ErrorConsole::Get(profile());
197 error_console
->ReportError(
198 make_scoped_ptr(new RuntimeError(
201 base::UTF8ToUTF16("source"),
202 base::UTF8ToUTF16("message"),
203 StackTrace(1, StackFrame(1,
205 base::UTF8ToUTF16("source"),
206 base::UTF8ToUTF16("function"))),
211 error_console
->ReportError(
212 make_scoped_ptr(new ManifestError(extension
->id(),
213 base::UTF8ToUTF16("message"),
214 base::UTF8ToUTF16("key"),
216 error_console
->ReportError(
217 make_scoped_ptr(new RuntimeError(
220 base::UTF8ToUTF16("source"),
221 base::UTF8ToUTF16("message"),
222 StackTrace(1, StackFrame(1,
224 base::UTF8ToUTF16("source"),
225 base::UTF8ToUTF16("function"))),
227 logging::LOG_VERBOSE
,
231 // It's not feasible to validate every field here, because that would be
232 // a duplication of the logic in the method itself. Instead, test a handful
233 // of fields for sanity.
234 scoped_ptr
<api::developer_private::ExtensionInfo
> info
=
235 GenerateExtensionInfo(extension
->id());
236 ASSERT_TRUE(info
.get());
237 EXPECT_EQ(kName
, info
->name
);
238 EXPECT_EQ(id
, info
->id
);
239 EXPECT_EQ(kVersion
, info
->version
);
240 EXPECT_EQ(info
->location
, developer::LOCATION_UNPACKED
);
241 ASSERT_TRUE(info
->path
);
242 EXPECT_EQ(data_dir(), base::FilePath::FromUTF8Unsafe(*info
->path
));
243 EXPECT_EQ(api::developer_private::EXTENSION_STATE_ENABLED
, info
->state
);
244 EXPECT_EQ(api::developer_private::EXTENSION_TYPE_EXTENSION
, info
->type
);
245 EXPECT_TRUE(info
->file_access
.is_enabled
);
246 EXPECT_FALSE(info
->file_access
.is_active
);
247 EXPECT_TRUE(info
->incognito_access
.is_enabled
);
248 EXPECT_FALSE(info
->incognito_access
.is_active
);
249 ASSERT_EQ(2u, info
->runtime_errors
.size());
250 const api::developer_private::RuntimeError
& runtime_error
=
251 *info
->runtime_errors
[0];
252 EXPECT_EQ(extension
->id(), runtime_error
.extension_id
);
253 EXPECT_EQ(api::developer_private::ERROR_TYPE_RUNTIME
, runtime_error
.type
);
254 EXPECT_EQ(api::developer_private::ERROR_LEVEL_ERROR
,
255 runtime_error
.severity
);
256 EXPECT_EQ(1u, runtime_error
.stack_trace
.size());
257 ASSERT_EQ(1u, info
->manifest_errors
.size());
258 const api::developer_private::RuntimeError
& runtime_error_verbose
=
259 *info
->runtime_errors
[1];
260 EXPECT_EQ(api::developer_private::ERROR_LEVEL_LOG
,
261 runtime_error_verbose
.severity
);
262 const api::developer_private::ManifestError
& manifest_error
=
263 *info
->manifest_errors
[0];
264 EXPECT_EQ(extension
->id(), manifest_error
.extension_id
);
266 // Test an extension that isn't unpacked.
267 manifest_copy
->SetString("update_url",
268 "https://clients2.google.com/service/update2/crx");
269 id
= crx_file::id_util::GenerateId("beta");
270 extension
= ExtensionBuilder().SetManifest(manifest_copy
.Pass())
271 .SetLocation(Manifest::EXTERNAL_PREF
)
274 service()->AddExtension(extension
.get());
275 info
= GenerateExtensionInfo(extension
->id());
276 EXPECT_EQ(developer::LOCATION_THIRD_PARTY
, info
->location
);
277 EXPECT_FALSE(info
->path
);
280 // Test three generated json outputs.
281 TEST_F(ExtensionInfoGeneratorUnitTest
, GenerateExtensionsJSONData
) {
283 base::FilePath extension_path
=
284 data_dir().AppendASCII("good")
285 .AppendASCII("Extensions")
286 .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
287 .AppendASCII("1.0.0.0");
289 InspectableViewsFinder::ViewList views
;
290 views
.push_back(InspectableViewsFinder::ConstructView(
291 GURL("chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/bar.html"),
292 42, 88, false, VIEW_TYPE_TAB_CONTENTS
));
293 views
.push_back(InspectableViewsFinder::ConstructView(
294 GURL("chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/dog.html"),
295 0, 0, false, VIEW_TYPE_TAB_CONTENTS
));
297 base::FilePath expected_outputs_path
=
298 data_dir().AppendASCII("api_test")
299 .AppendASCII("developer")
300 .AppendASCII("generated_output");
302 CompareExpectedAndActualOutput(
305 expected_outputs_path
.AppendASCII(
306 "behllobkkfkfnphdnhnkndlbkcpglgmj.json"));
308 #if !defined(OS_CHROMEOS)
310 extension_path
= data_dir().AppendASCII("good")
311 .AppendASCII("Extensions")
312 .AppendASCII("hpiknbiabeeppbpihjehijgoemciehgk")
315 // It's OK to have duplicate URLs, so long as the IDs are different.
317 "chrome-extension://hpiknbiabeeppbpihjehijgoemciehgk/bar.html";
318 views
[1]->url
= views
[0]->url
;
320 CompareExpectedAndActualOutput(
323 expected_outputs_path
.AppendASCII(
324 "hpiknbiabeeppbpihjehijgoemciehgk.json"));
328 extension_path
= data_dir().AppendASCII("good")
329 .AppendASCII("Extensions")
330 .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
334 CompareExpectedAndActualOutput(
337 expected_outputs_path
.AppendASCII(
338 "bjafgdebaacbbbecmhlhpofkepfkgcpa.json"));
341 // Test that the all_urls checkbox only shows up for extensions that want all
342 // urls, and only when the switch is on.
343 TEST_F(ExtensionInfoGeneratorUnitTest
, ExtensionInfoRunOnAllUrls
) {
344 // Start with the switch enabled.
345 scoped_ptr
<FeatureSwitch::ScopedOverride
> enable_scripts_switch(
346 new FeatureSwitch::ScopedOverride(
347 FeatureSwitch::scripts_require_action(), true));
348 // Two extensions - one with all urls, one without.
349 scoped_refptr
<const Extension
> all_urls_extension
= CreateExtension(
350 "all_urls", ListBuilder().Append(kAllHostsPermission
).Pass());
351 scoped_refptr
<const Extension
> no_urls_extension
=
352 CreateExtension("no urls", ListBuilder().Pass());
354 scoped_ptr
<developer::ExtensionInfo
> info
=
355 GenerateExtensionInfo(all_urls_extension
->id());
357 // The extension should want all urls, but not currently have it.
358 EXPECT_TRUE(info
->run_on_all_urls
.is_enabled
);
359 EXPECT_FALSE(info
->run_on_all_urls
.is_active
);
361 // Give the extension all urls.
362 util::SetAllowedScriptingOnAllUrls(all_urls_extension
->id(), profile(), true);
364 // Now the extension should both want and have all urls.
365 info
= GenerateExtensionInfo(all_urls_extension
->id());
366 EXPECT_TRUE(info
->run_on_all_urls
.is_enabled
);
367 EXPECT_TRUE(info
->run_on_all_urls
.is_active
);
369 // The other extension should neither want nor have all urls.
370 info
= GenerateExtensionInfo(no_urls_extension
->id());
371 EXPECT_FALSE(info
->run_on_all_urls
.is_enabled
);
372 EXPECT_FALSE(info
->run_on_all_urls
.is_active
);
374 // Revoke the first extension's permissions.
375 util::SetAllowedScriptingOnAllUrls(
376 all_urls_extension
->id(), profile(), false);
378 // Turn off the switch and load another extension (so permissions are
380 enable_scripts_switch
.reset();
382 // Since the extension doesn't have access to all urls (but normally would),
383 // the extension should have the "want" flag even with the switch off.
384 info
= GenerateExtensionInfo(all_urls_extension
->id());
385 EXPECT_TRUE(info
->run_on_all_urls
.is_enabled
);
386 EXPECT_FALSE(info
->run_on_all_urls
.is_active
);
388 // If we grant the extension all urls, then the checkbox should still be
389 // there, since it has an explicitly-set user preference.
390 util::SetAllowedScriptingOnAllUrls(all_urls_extension
->id(), profile(), true);
391 info
= GenerateExtensionInfo(all_urls_extension
->id());
392 EXPECT_TRUE(info
->run_on_all_urls
.is_enabled
);
393 EXPECT_TRUE(info
->run_on_all_urls
.is_active
);
395 // Load another extension with all urls (so permissions get re-init'd).
396 all_urls_extension
= CreateExtension(
397 "all_urls_II", ListBuilder().Append(kAllHostsPermission
).Pass());
399 // Even though the extension has all_urls permission, the checkbox shouldn't
400 // show up without the switch.
401 info
= GenerateExtensionInfo(all_urls_extension
->id());
402 EXPECT_FALSE(info
->run_on_all_urls
.is_enabled
);
403 EXPECT_TRUE(info
->run_on_all_urls
.is_active
);
406 } // namespace extensions