Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(709)

Side by Side Diff: chrome/common/extensions/extension_file_util_unittest.cc

Issue 236213002: Move most of extension_file_util.cc into extensions/common/file_util.cc (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: zip (file-util) Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/common/extensions/extension_file_util.h" 5 #include "chrome/common/extensions/extension_file_util.h"
6 6
7 #include "base/file_util.h" 7 #include <set>
8 #include "base/files/scoped_temp_dir.h" 8
9 #include "base/json/json_string_value_serializer.h"
10 #include "base/path_service.h" 9 #include "base/path_service.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/common/chrome_paths.h" 10 #include "chrome/common/chrome_paths.h"
14 #include "extensions/common/constants.h"
15 #include "extensions/common/extension.h" 11 #include "extensions/common/extension.h"
12 #include "extensions/common/file_util.h"
16 #include "extensions/common/manifest.h" 13 #include "extensions/common/manifest.h"
17 #include "extensions/common/manifest_constants.h"
18 #include "grit/generated_resources.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h" 14 #include "testing/gtest/include/gtest/gtest.h"
21 #include "ui/base/l10n/l10n_util.h"
22 15
23 using extensions::Extension; 16 namespace extensions {
24 using extensions::Manifest;
25 17
26 namespace keys = extensions::manifest_keys; 18 typedef testing::Test ExtensionFileUtilTest;
27 19
28 class ExtensionFileUtilTest : public testing::Test { 20 // Test that a browser action extension returns a path to an icon.
29 }; 21 TEST_F(ExtensionFileUtilTest, GetBrowserImagePaths) {
James Cook 2014/04/12 00:03:22 I added this trivial unit test mostly to give this
30
31 TEST_F(ExtensionFileUtilTest, InstallUninstallGarbageCollect) {
32 base::ScopedTempDir temp;
33 ASSERT_TRUE(temp.CreateUniqueTempDir());
34
35 // Create a source extension.
36 std::string extension_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
37 std::string version("1.0");
38 base::FilePath src = temp.path().AppendASCII(extension_id);
39 ASSERT_TRUE(base::CreateDirectory(src));
40
41 // Create a extensions tree.
42 base::FilePath all_extensions = temp.path().AppendASCII("extensions");
43 ASSERT_TRUE(base::CreateDirectory(all_extensions));
44
45 // Install in empty directory. Should create parent directories as needed.
46 base::FilePath version_1 = extension_file_util::InstallExtension(
47 src,
48 extension_id,
49 version,
50 all_extensions);
51 ASSERT_EQ(version_1.value(),
52 all_extensions.AppendASCII(extension_id).AppendASCII("1.0_0")
53 .value());
54 ASSERT_TRUE(base::DirectoryExists(version_1));
55
56 // Should have moved the source.
57 ASSERT_FALSE(base::DirectoryExists(src));
58
59 // Install again. Should create a new one with different name.
60 ASSERT_TRUE(base::CreateDirectory(src));
61 base::FilePath version_2 = extension_file_util::InstallExtension(
62 src,
63 extension_id,
64 version,
65 all_extensions);
66 ASSERT_EQ(version_2.value(),
67 all_extensions.AppendASCII(extension_id).AppendASCII("1.0_1")
68 .value());
69 ASSERT_TRUE(base::DirectoryExists(version_2));
70
71 // Should have moved the source.
72 ASSERT_FALSE(base::DirectoryExists(src));
73
74 // Install yet again. Should create a new one with a different name.
75 ASSERT_TRUE(base::CreateDirectory(src));
76 base::FilePath version_3 = extension_file_util::InstallExtension(
77 src,
78 extension_id,
79 version,
80 all_extensions);
81 ASSERT_EQ(version_3.value(),
82 all_extensions.AppendASCII(extension_id).AppendASCII("1.0_2")
83 .value());
84 ASSERT_TRUE(base::DirectoryExists(version_3));
85
86 // Uninstall. Should remove entire extension subtree.
87 extension_file_util::UninstallExtension(all_extensions, extension_id);
88 ASSERT_FALSE(base::DirectoryExists(version_1.DirName()));
89 ASSERT_FALSE(base::DirectoryExists(version_2.DirName()));
90 ASSERT_FALSE(base::DirectoryExists(version_3.DirName()));
91 ASSERT_TRUE(base::DirectoryExists(all_extensions));
92 }
93
94 TEST_F(ExtensionFileUtilTest, LoadExtensionWithValidLocales) {
95 base::FilePath install_dir; 22 base::FilePath install_dir;
96 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir)); 23 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
97 install_dir = install_dir.AppendASCII("extensions") 24 install_dir = install_dir.AppendASCII("extensions")
98 .AppendASCII("good") 25 .AppendASCII("api_test")
99 .AppendASCII("Extensions") 26 .AppendASCII("browser_action")
100 .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj") 27 .AppendASCII("basics");
101 .AppendASCII("1.0.0.0");
102 28
103 std::string error; 29 std::string error;
104 scoped_refptr<Extension> extension(extension_file_util::LoadExtension( 30 scoped_refptr<Extension> extension(file_util::LoadExtension(
105 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error)); 31 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
106 ASSERT_TRUE(extension.get() != NULL); 32 ASSERT_TRUE(extension.get());
107 EXPECT_EQ("The first extension that I made.", extension->description()); 33
34 // The extension contains one icon.
35 std::set<base::FilePath> paths =
36 extension_file_util::GetBrowserImagePaths(extension.get());
37 ASSERT_EQ(1u, paths.size());
38 EXPECT_EQ("icon.png", paths.begin()->BaseName().AsUTF8Unsafe());
108 } 39 }
109 40
110 TEST_F(ExtensionFileUtilTest, LoadExtensionWithoutLocalesFolder) { 41 } // namespace extensions
111 base::FilePath install_dir;
112 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
113 install_dir = install_dir.AppendASCII("extensions")
114 .AppendASCII("good")
115 .AppendASCII("Extensions")
116 .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
117 .AppendASCII("1.0");
118
119 std::string error;
120 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
121 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
122 ASSERT_FALSE(extension.get() == NULL);
123 EXPECT_TRUE(error.empty());
124 }
125
126 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesNoUnderscores) {
127 base::ScopedTempDir temp;
128 ASSERT_TRUE(temp.CreateUniqueTempDir());
129
130 base::FilePath src_path = temp.path().AppendASCII("some_dir");
131 ASSERT_TRUE(base::CreateDirectory(src_path));
132
133 std::string data = "{ \"name\": { \"message\": \"foobar\" } }";
134 ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("some_file.txt"),
135 data.c_str(), data.length()));
136 std::string error;
137 EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
138 &error));
139 }
140
141 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesOnlyReserved) {
142 base::ScopedTempDir temp;
143 ASSERT_TRUE(temp.CreateUniqueTempDir());
144
145 const base::FilePath::CharType* folders[] =
146 { extensions::kLocaleFolder, extensions::kPlatformSpecificFolder };
147
148 for (size_t i = 0; i < arraysize(folders); i++) {
149 base::FilePath src_path = temp.path().Append(folders[i]);
150 ASSERT_TRUE(base::CreateDirectory(src_path));
151 }
152
153 std::string error;
154 EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
155 &error));
156 }
157
158 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesReservedAndIllegal) {
159 base::ScopedTempDir temp;
160 ASSERT_TRUE(temp.CreateUniqueTempDir());
161
162 base::FilePath src_path = temp.path().Append(extensions::kLocaleFolder);
163 ASSERT_TRUE(base::CreateDirectory(src_path));
164
165 src_path = temp.path().AppendASCII("_some_dir");
166 ASSERT_TRUE(base::CreateDirectory(src_path));
167
168 std::string error;
169 EXPECT_FALSE(extension_file_util::CheckForIllegalFilenames(temp.path(),
170 &error));
171 }
172
173 TEST_F(ExtensionFileUtilTest,
174 LoadExtensionGivesHelpfullErrorOnMissingManifest) {
175 base::FilePath install_dir;
176 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
177 install_dir = install_dir.AppendASCII("extensions")
178 .AppendASCII("bad")
179 .AppendASCII("Extensions")
180 .AppendASCII("dddddddddddddddddddddddddddddddd")
181 .AppendASCII("1.0");
182
183 std::string error;
184 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
185 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
186 ASSERT_TRUE(extension.get() == NULL);
187 ASSERT_FALSE(error.empty());
188 ASSERT_STREQ("Manifest file is missing or unreadable.", error.c_str());
189 }
190
191 TEST_F(ExtensionFileUtilTest, LoadExtensionGivesHelpfullErrorOnBadManifest) {
192 base::FilePath install_dir;
193 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
194 install_dir = install_dir.AppendASCII("extensions")
195 .AppendASCII("bad")
196 .AppendASCII("Extensions")
197 .AppendASCII("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
198 .AppendASCII("1.0");
199
200 std::string error;
201 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
202 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
203 ASSERT_TRUE(extension.get() == NULL);
204 ASSERT_FALSE(error.empty());
205 ASSERT_STREQ("Manifest is not valid JSON. "
206 "Line: 2, column: 16, Syntax error.", error.c_str());
207 }
208
209 TEST_F(ExtensionFileUtilTest, FailLoadingNonUTF8Scripts) {
210 base::FilePath install_dir;
211 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
212 install_dir = install_dir.AppendASCII("extensions")
213 .AppendASCII("bad")
214 .AppendASCII("bad_encoding");
215
216 std::string error;
217 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
218 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
219 ASSERT_TRUE(extension.get() == NULL);
220 ASSERT_STREQ("Could not load file 'bad_encoding.js' for content script. "
221 "It isn't UTF-8 encoded.",
222 error.c_str());
223 }
224
225 static scoped_refptr<Extension> LoadExtensionManifest(
226 base::DictionaryValue* manifest,
227 const base::FilePath& manifest_dir,
228 Manifest::Location location,
229 int extra_flags,
230 std::string* error) {
231 scoped_refptr<Extension> extension = Extension::Create(
232 manifest_dir, location, *manifest, extra_flags, error);
233 return extension;
234 }
235
236 static scoped_refptr<Extension> LoadExtensionManifest(
237 const std::string& manifest_value,
238 const base::FilePath& manifest_dir,
239 Manifest::Location location,
240 int extra_flags,
241 std::string* error) {
242 JSONStringValueSerializer serializer(manifest_value);
243 scoped_ptr<base::Value> result(serializer.Deserialize(NULL, error));
244 if (!result.get())
245 return NULL;
246 CHECK_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
247 return LoadExtensionManifest(
248 static_cast<base::DictionaryValue*>(result.get()),
249 manifest_dir,
250 location,
251 extra_flags,
252 error);
253 }
254
255 TEST_F(ExtensionFileUtilTest, ValidateThemeUTF8) {
256 base::ScopedTempDir temp;
257 ASSERT_TRUE(temp.CreateUniqueTempDir());
258
259 // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them.
260 std::string non_ascii_file = "\xC3\xA0\xC3\xA8\xC3\xB2.png";
261 base::FilePath non_ascii_path =
262 temp.path().Append(base::FilePath::FromUTF8Unsafe(non_ascii_file));
263 base::WriteFile(non_ascii_path, "", 0);
264
265 std::string kManifest =
266 base::StringPrintf(
267 "{ \"name\": \"Test\", \"version\": \"1.0\", "
268 " \"theme\": { \"images\": { \"theme_frame\": \"%s\" } }"
269 "}", non_ascii_file.c_str());
270 std::string error;
271 scoped_refptr<Extension> extension = LoadExtensionManifest(
272 kManifest, temp.path(), Manifest::UNPACKED, 0, &error);
273 ASSERT_TRUE(extension.get()) << error;
274
275 std::vector<extensions::InstallWarning> warnings;
276 EXPECT_TRUE(extension_file_util::ValidateExtension(
277 extension.get(), &error, &warnings)) << error;
278 EXPECT_EQ(0U, warnings.size());
279 }
280
281 TEST_F(ExtensionFileUtilTest, BackgroundScriptsMustExist) {
282 base::ScopedTempDir temp;
283 ASSERT_TRUE(temp.CreateUniqueTempDir());
284
285 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
286 value->SetString("name", "test");
287 value->SetString("version", "1");
288 value->SetInteger("manifest_version", 1);
289
290 base::ListValue* scripts = new base::ListValue();
291 scripts->Append(new base::StringValue("foo.js"));
292 value->Set("background.scripts", scripts);
293
294 std::string error;
295 std::vector<extensions::InstallWarning> warnings;
296 scoped_refptr<Extension> extension = LoadExtensionManifest(
297 value.get(), temp.path(), Manifest::UNPACKED, 0, &error);
298 ASSERT_TRUE(extension.get()) << error;
299
300 EXPECT_FALSE(extension_file_util::ValidateExtension(
301 extension.get(), &error, &warnings));
302 EXPECT_EQ(l10n_util::GetStringFUTF8(
303 IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
304 base::ASCIIToUTF16("foo.js")),
305 error);
306 EXPECT_EQ(0U, warnings.size());
307
308 scripts->Clear();
309 scripts->Append(new base::StringValue("http://google.com/foo.js"));
310
311 extension = LoadExtensionManifest(value.get(), temp.path(),
312 Manifest::UNPACKED, 0, &error);
313 ASSERT_TRUE(extension.get()) << error;
314
315 warnings.clear();
316 EXPECT_FALSE(extension_file_util::ValidateExtension(
317 extension.get(), &error, &warnings));
318 EXPECT_EQ(l10n_util::GetStringFUTF8(
319 IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
320 base::ASCIIToUTF16("http://google.com/foo.js")),
321 error);
322 EXPECT_EQ(0U, warnings.size());
323 }
324
325 // Private key, generated by Chrome specifically for this test, and
326 // never used elsewhere.
327 const char private_key[] =
328 "-----BEGIN PRIVATE KEY-----\n"
329 "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKt02SR0FYaYy6fpW\n"
330 "MAA+kU1BgK3d+OmmWfdr+JATIjhRkyeSF4lTd/71JQsyKqPzYkQPi3EeROWM+goTv\n"
331 "EhJqq07q63BolpsFmlV+S4ny+sBA2B4aWwRYXlBWikdrQSA0mJMzvEHc6nKzBgXik\n"
332 "QSVbyyBNAsxlDB9WaCxRVOpK3AgMBAAECgYBGvSPlrVtAOAQ2V8j9FqorKZA8SLPX\n"
333 "IeJC/yzU3RB2nPMjI17aMOvrUHxJUhzMeh4jwabVvSzzDtKFozPGupW3xaI8sQdi2\n"
334 "WWMTQIk/Q9HHDWoQ9qA6SwX2qWCc5SyjCKqVp78ye+000kqTJYjBsDgXeAlzKcx2B\n"
335 "4GAAeWonDdkQJBANNb8wrqNWFn7DqyQTfELzcRTRnqQ/r1pdeJo6obzbnwGnlqe3t\n"
336 "KhLjtJNIGrQg5iC0OVLWFuvPJs0t3z62A1ckCQQDPq2JZuwTwu5Pl4DJ0r9O1FdqN\n"
337 "JgqPZyMptokCDQ3khLLGakIu+TqB9YtrzI69rJMSG2Egb+6McaDX+dh3XmR/AkB9t\n"
338 "xJf6qDnmA2td/tMtTc0NOk8Qdg/fD8xbZ/YfYMnVoYYs9pQoilBaWRePDRNURMLYZ\n"
339 "vHAI0Llmw7tj7jv17pAkEAz44uXRpjRKtllUIvi5pUENAHwDz+HvdpGH68jpU3hmb\n"
340 "uOwrmnQYxaMReFV68Z2w9DcLZn07f7/R9Wn72z89CxwJAFsDoNaDes4h48bX7plct\n"
341 "s9ACjmTwcCigZjN2K7AGv7ntCLF3DnV5dK0dTHNaAdD3SbY3jl29Rk2CwiURSX6Ee\n"
342 "g==\n"
343 "-----END PRIVATE KEY-----\n";
344
345 TEST_F(ExtensionFileUtilTest, FindPrivateKeyFiles) {
346 base::ScopedTempDir temp;
347 ASSERT_TRUE(temp.CreateUniqueTempDir());
348
349 base::FilePath src_path = temp.path().AppendASCII("some_dir");
350 ASSERT_TRUE(base::CreateDirectory(src_path));
351
352 ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("a_key.pem"),
353 private_key, arraysize(private_key)));
354 ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("second_key.pem"),
355 private_key, arraysize(private_key)));
356 // Shouldn't find a key with a different extension.
357 ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("key.diff_ext"),
358 private_key, arraysize(private_key)));
359 // Shouldn't find a key that isn't parsable.
360 ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("unparsable_key.pem"),
361 private_key, arraysize(private_key) - 30));
362 std::vector<base::FilePath> private_keys =
363 extension_file_util::FindPrivateKeyFiles(temp.path());
364 EXPECT_EQ(2U, private_keys.size());
365 EXPECT_THAT(private_keys,
366 testing::Contains(src_path.AppendASCII("a_key.pem")));
367 EXPECT_THAT(private_keys,
368 testing::Contains(src_path.AppendASCII("second_key.pem")));
369 }
370
371 TEST_F(ExtensionFileUtilTest, WarnOnPrivateKey) {
372 base::ScopedTempDir temp;
373 ASSERT_TRUE(temp.CreateUniqueTempDir());
374
375 base::FilePath ext_path = temp.path().AppendASCII("ext_root");
376 ASSERT_TRUE(base::CreateDirectory(ext_path));
377
378 const char manifest[] =
379 "{\n"
380 " \"name\": \"Test Extension\",\n"
381 " \"version\": \"1.0\",\n"
382 " \"manifest_version\": 2,\n"
383 " \"description\": \"The first extension that I made.\"\n"
384 "}\n";
385 ASSERT_TRUE(base::WriteFile(ext_path.AppendASCII("manifest.json"),
386 manifest, strlen(manifest)));
387 ASSERT_TRUE(base::WriteFile(ext_path.AppendASCII("a_key.pem"),
388 private_key, strlen(private_key)));
389
390 std::string error;
391 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
392 ext_path, "the_id", Manifest::EXTERNAL_PREF,
393 Extension::NO_FLAGS, &error));
394 ASSERT_TRUE(extension.get()) << error;
395 ASSERT_EQ(1u, extension->install_warnings().size());
396 EXPECT_THAT(
397 extension->install_warnings(),
398 testing::ElementsAre(
399 testing::Field(
400 &extensions::InstallWarning::message,
401 testing::ContainsRegex(
402 "extension includes the key file.*ext_root.a_key.pem"))));
403
404 // Turn the warning into an error with ERROR_ON_PRIVATE_KEY.
405 extension = extension_file_util::LoadExtension(
406 ext_path, "the_id", Manifest::EXTERNAL_PREF,
407 Extension::ERROR_ON_PRIVATE_KEY, &error);
408 EXPECT_FALSE(extension.get());
409 EXPECT_THAT(error,
410 testing::ContainsRegex(
411 "extension includes the key file.*ext_root.a_key.pem"));
412 }
413
414 TEST_F(ExtensionFileUtilTest, CheckZeroLengthImageFile) {
415 base::FilePath install_dir;
416 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
417
418 // Try to install an extension with a zero-length icon file.
419 base::FilePath ext_dir = install_dir.AppendASCII("extensions")
420 .AppendASCII("bad")
421 .AppendASCII("Extensions")
422 .AppendASCII("ffffffffffffffffffffffffffffffff");
423
424 std::string error;
425 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
426 ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
427 EXPECT_TRUE(extension.get() == NULL);
428 EXPECT_STREQ("Could not load extension icon 'icon.png'.", error.c_str());
429
430 // Try to install an extension with a zero-length browser action icon file.
431 ext_dir = install_dir.AppendASCII("extensions")
432 .AppendASCII("bad")
433 .AppendASCII("Extensions")
434 .AppendASCII("gggggggggggggggggggggggggggggggg");
435
436 scoped_refptr<Extension> extension2(extension_file_util::LoadExtension(
437 ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
438 EXPECT_TRUE(extension2.get() == NULL);
439 EXPECT_STREQ("Could not load icon 'icon.png' for browser action.",
440 error.c_str());
441
442 // Try to install an extension with a zero-length page action icon file.
443 ext_dir = install_dir.AppendASCII("extensions")
444 .AppendASCII("bad")
445 .AppendASCII("Extensions")
446 .AppendASCII("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
447
448 scoped_refptr<Extension> extension3(extension_file_util::LoadExtension(
449 ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
450 EXPECT_TRUE(extension3.get() == NULL);
451 EXPECT_STREQ("Could not load icon 'icon.png' for page action.",
452 error.c_str());
453 }
454
455 // TODO(aa): More tests as motivation allows. Maybe steal some from
456 // ExtensionService? Many of them could probably be tested here without the
457 // MessageLoop shenanigans.
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698