Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / extensions / common / extension.cc
blobd8e798e516e2795c50f670fd27a6b26e4a1ed6e9
1 // Copyright (c) 2013 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 "extensions/common/extension.h"
7 #include "base/base64.h"
8 #include "base/basictypes.h"
9 #include "base/command_line.h"
10 #include "base/files/file_path.h"
11 #include "base/i18n/rtl.h"
12 #include "base/logging.h"
13 #include "base/memory/singleton.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string16.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_piece.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/values.h"
22 #include "base/version.h"
23 #include "components/crx_file/id_util.h"
24 #include "content/public/common/url_constants.h"
25 #include "extensions/common/constants.h"
26 #include "extensions/common/error_utils.h"
27 #include "extensions/common/manifest.h"
28 #include "extensions/common/manifest_constants.h"
29 #include "extensions/common/manifest_handler.h"
30 #include "extensions/common/manifest_handlers/permissions_parser.h"
31 #include "extensions/common/permissions/permission_set.h"
32 #include "extensions/common/permissions/permissions_data.h"
33 #include "extensions/common/permissions/permissions_info.h"
34 #include "extensions/common/switches.h"
35 #include "extensions/common/url_pattern.h"
36 #include "net/base/filename_util.h"
37 #include "url/url_util.h"
39 namespace extensions {
41 namespace keys = manifest_keys;
42 namespace values = manifest_values;
43 namespace errors = manifest_errors;
45 namespace {
47 const int kModernManifestVersion = 2;
48 const int kPEMOutputColumns = 64;
50 // KEY MARKERS
51 const char kKeyBeginHeaderMarker[] = "-----BEGIN";
52 const char kKeyBeginFooterMarker[] = "-----END";
53 const char kKeyInfoEndMarker[] = "KEY-----";
54 const char kPublic[] = "PUBLIC";
55 const char kPrivate[] = "PRIVATE";
57 bool ContainsReservedCharacters(const base::FilePath& path) {
58 // We should disallow backslash '\\' as file path separator even on Windows,
59 // because the backslash is not regarded as file path separator on Linux/Mac.
60 // Extensions are cross-platform.
61 // Since FilePath uses backslash '\\' as file path separator on Windows, so we
62 // need to check manually.
63 if (path.value().find('\\') != path.value().npos)
64 return true;
65 return !net::IsSafePortableRelativePath(path);
68 } // namespace
70 const int Extension::kInitFromValueFlagBits = 13;
72 const char Extension::kMimeType[] = "application/x-chrome-extension";
74 const int Extension::kValidWebExtentSchemes =
75 URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS;
77 const int Extension::kValidHostPermissionSchemes = URLPattern::SCHEME_CHROMEUI |
78 URLPattern::SCHEME_HTTP |
79 URLPattern::SCHEME_HTTPS |
80 URLPattern::SCHEME_FILE |
81 URLPattern::SCHEME_FTP;
84 // Extension
87 // static
88 scoped_refptr<Extension> Extension::Create(const base::FilePath& path,
89 Manifest::Location location,
90 const base::DictionaryValue& value,
91 int flags,
92 std::string* utf8_error) {
93 return Extension::Create(path,
94 location,
95 value,
96 flags,
97 std::string(), // ID is ignored if empty.
98 utf8_error);
101 // TODO(sungguk): Continue removing std::string errors and replacing
102 // with base::string16. See http://crbug.com/71980.
103 scoped_refptr<Extension> Extension::Create(const base::FilePath& path,
104 Manifest::Location location,
105 const base::DictionaryValue& value,
106 int flags,
107 const std::string& explicit_id,
108 std::string* utf8_error) {
109 DCHECK(utf8_error);
110 base::string16 error;
111 scoped_ptr<extensions::Manifest> manifest(
112 new extensions::Manifest(
113 location, scoped_ptr<base::DictionaryValue>(value.DeepCopy())));
115 if (!InitExtensionID(manifest.get(), path, explicit_id, flags, &error)) {
116 *utf8_error = base::UTF16ToUTF8(error);
117 return NULL;
120 std::vector<InstallWarning> install_warnings;
121 if (!manifest->ValidateManifest(utf8_error, &install_warnings)) {
122 return NULL;
125 scoped_refptr<Extension> extension = new Extension(path, manifest.Pass());
126 extension->install_warnings_.swap(install_warnings);
128 if (!extension->InitFromValue(flags, &error)) {
129 *utf8_error = base::UTF16ToUTF8(error);
130 return NULL;
133 return extension;
136 Manifest::Type Extension::GetType() const {
137 return converted_from_user_script() ?
138 Manifest::TYPE_USER_SCRIPT : manifest_->type();
141 // static
142 GURL Extension::GetResourceURL(const GURL& extension_url,
143 const std::string& relative_path) {
144 DCHECK(extension_url.SchemeIs(extensions::kExtensionScheme));
145 DCHECK_EQ("/", extension_url.path());
147 std::string path = relative_path;
149 // If the relative path starts with "/", it is "absolute" relative to the
150 // extension base directory, but extension_url is already specified to refer
151 // to that base directory, so strip the leading "/" if present.
152 if (relative_path.size() > 0 && relative_path[0] == '/')
153 path = relative_path.substr(1);
155 GURL ret_val = GURL(extension_url.spec() + path);
156 DCHECK(StartsWithASCII(ret_val.spec(), extension_url.spec(), false));
158 return ret_val;
161 bool Extension::ResourceMatches(const URLPatternSet& pattern_set,
162 const std::string& resource) const {
163 return pattern_set.MatchesURL(extension_url_.Resolve(resource));
166 ExtensionResource Extension::GetResource(
167 const std::string& relative_path) const {
168 std::string new_path = relative_path;
169 // We have some legacy data where resources have leading slashes.
170 // See: http://crbug.com/121164
171 if (!new_path.empty() && new_path.at(0) == '/')
172 new_path.erase(0, 1);
173 base::FilePath relative_file_path = base::FilePath::FromUTF8Unsafe(new_path);
174 if (ContainsReservedCharacters(relative_file_path))
175 return ExtensionResource();
176 ExtensionResource r(id(), path(), relative_file_path);
177 if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) {
178 r.set_follow_symlinks_anywhere();
180 return r;
183 ExtensionResource Extension::GetResource(
184 const base::FilePath& relative_file_path) const {
185 if (ContainsReservedCharacters(relative_file_path))
186 return ExtensionResource();
187 ExtensionResource r(id(), path(), relative_file_path);
188 if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) {
189 r.set_follow_symlinks_anywhere();
191 return r;
194 // TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a
195 // util class in base:
196 // http://code.google.com/p/chromium/issues/detail?id=13572
197 // static
198 bool Extension::ParsePEMKeyBytes(const std::string& input,
199 std::string* output) {
200 DCHECK(output);
201 if (!output)
202 return false;
203 if (input.length() == 0)
204 return false;
206 std::string working = input;
207 if (StartsWithASCII(working, kKeyBeginHeaderMarker, true)) {
208 working = base::CollapseWhitespaceASCII(working, true);
209 size_t header_pos = working.find(kKeyInfoEndMarker,
210 sizeof(kKeyBeginHeaderMarker) - 1);
211 if (header_pos == std::string::npos)
212 return false;
213 size_t start_pos = header_pos + sizeof(kKeyInfoEndMarker) - 1;
214 size_t end_pos = working.rfind(kKeyBeginFooterMarker);
215 if (end_pos == std::string::npos)
216 return false;
217 if (start_pos >= end_pos)
218 return false;
220 working = working.substr(start_pos, end_pos - start_pos);
221 if (working.length() == 0)
222 return false;
225 return base::Base64Decode(working, output);
228 // static
229 bool Extension::ProducePEM(const std::string& input, std::string* output) {
230 DCHECK(output);
231 if (input.empty())
232 return false;
233 base::Base64Encode(input, output);
234 return true;
237 // static
238 bool Extension::FormatPEMForFileOutput(const std::string& input,
239 std::string* output,
240 bool is_public) {
241 DCHECK(output);
242 if (input.length() == 0)
243 return false;
244 *output = "";
245 output->append(kKeyBeginHeaderMarker);
246 output->append(" ");
247 output->append(is_public ? kPublic : kPrivate);
248 output->append(" ");
249 output->append(kKeyInfoEndMarker);
250 output->append("\n");
251 for (size_t i = 0; i < input.length(); ) {
252 int slice = std::min<int>(input.length() - i, kPEMOutputColumns);
253 output->append(input.substr(i, slice));
254 output->append("\n");
255 i += slice;
257 output->append(kKeyBeginFooterMarker);
258 output->append(" ");
259 output->append(is_public ? kPublic : kPrivate);
260 output->append(" ");
261 output->append(kKeyInfoEndMarker);
262 output->append("\n");
264 return true;
267 // static
268 GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) {
269 return GURL(std::string(extensions::kExtensionScheme) +
270 url::kStandardSchemeSeparator + extension_id + "/");
273 bool Extension::ShowConfigureContextMenus() const {
274 // Don't show context menu for component extensions. We might want to show
275 // options for component extension button but now there is no component
276 // extension with options. All other menu items like uninstall have
277 // no sense for component extensions.
278 return location() != Manifest::COMPONENT &&
279 location() != Manifest::EXTERNAL_COMPONENT;
282 bool Extension::OverlapsWithOrigin(const GURL& origin) const {
283 if (url() == origin)
284 return true;
286 if (web_extent().is_empty())
287 return false;
289 // Note: patterns and extents ignore port numbers.
290 URLPattern origin_only_pattern(kValidWebExtentSchemes);
291 if (!origin_only_pattern.SetScheme(origin.scheme()))
292 return false;
293 origin_only_pattern.SetHost(origin.host());
294 origin_only_pattern.SetPath("/*");
296 URLPatternSet origin_only_pattern_list;
297 origin_only_pattern_list.AddPattern(origin_only_pattern);
299 return web_extent().OverlapsWith(origin_only_pattern_list);
302 bool Extension::RequiresSortOrdinal() const {
303 return is_app() && (display_in_launcher_ || display_in_new_tab_page_);
306 bool Extension::ShouldDisplayInAppLauncher() const {
307 // Only apps should be displayed in the launcher.
308 return is_app() && display_in_launcher_;
311 bool Extension::ShouldDisplayInNewTabPage() const {
312 // Only apps should be displayed on the NTP.
313 return is_app() && display_in_new_tab_page_;
316 bool Extension::ShouldDisplayInExtensionSettings() const {
317 // Don't show for themes since the settings UI isn't really useful for them.
318 if (is_theme())
319 return false;
321 // Don't show component extensions and invisible apps.
322 if (ShouldNotBeVisible())
323 return false;
325 // Always show unpacked extensions and apps.
326 if (Manifest::IsUnpackedLocation(location()))
327 return true;
329 // Unless they are unpacked, never show hosted apps. Note: We intentionally
330 // show packaged apps and platform apps because there are some pieces of
331 // functionality that are only available in chrome://extensions/ but which
332 // are needed for packaged and platform apps. For example, inspecting
333 // background pages. See http://crbug.com/116134.
334 if (is_hosted_app())
335 return false;
337 return true;
340 bool Extension::ShouldNotBeVisible() const {
341 // Don't show component extensions because they are only extensions as an
342 // implementation detail of Chrome.
343 if (extensions::Manifest::IsComponentLocation(location()) &&
344 !CommandLine::ForCurrentProcess()->HasSwitch(
345 switches::kShowComponentExtensionOptions)) {
346 return true;
349 // Always show unpacked extensions and apps.
350 if (Manifest::IsUnpackedLocation(location()))
351 return false;
353 // Don't show apps that aren't visible in either launcher or ntp.
354 if (is_app() && !ShouldDisplayInAppLauncher() && !ShouldDisplayInNewTabPage())
355 return true;
357 return false;
360 Extension::ManifestData* Extension::GetManifestData(const std::string& key)
361 const {
362 DCHECK(finished_parsing_manifest_ || thread_checker_.CalledOnValidThread());
363 ManifestDataMap::const_iterator iter = manifest_data_.find(key);
364 if (iter != manifest_data_.end())
365 return iter->second.get();
366 return NULL;
369 void Extension::SetManifestData(const std::string& key,
370 Extension::ManifestData* data) {
371 DCHECK(!finished_parsing_manifest_ && thread_checker_.CalledOnValidThread());
372 manifest_data_[key] = linked_ptr<ManifestData>(data);
375 Manifest::Location Extension::location() const {
376 return manifest_->location();
379 const std::string& Extension::id() const {
380 return manifest_->extension_id();
383 const std::string Extension::VersionString() const {
384 return version()->GetString();
387 void Extension::AddInstallWarning(const InstallWarning& new_warning) {
388 install_warnings_.push_back(new_warning);
391 void Extension::AddInstallWarnings(
392 const std::vector<InstallWarning>& new_warnings) {
393 install_warnings_.insert(install_warnings_.end(),
394 new_warnings.begin(), new_warnings.end());
397 bool Extension::is_app() const {
398 return manifest()->is_app();
401 bool Extension::is_platform_app() const {
402 return manifest()->is_platform_app();
405 bool Extension::is_hosted_app() const {
406 return manifest()->is_hosted_app();
409 bool Extension::is_legacy_packaged_app() const {
410 return manifest()->is_legacy_packaged_app();
413 bool Extension::is_extension() const {
414 return manifest()->is_extension();
417 bool Extension::is_shared_module() const {
418 return manifest()->is_shared_module();
421 bool Extension::is_theme() const {
422 return manifest()->is_theme();
425 bool Extension::can_be_incognito_enabled() const {
426 // Only component platform apps are supported in incognito.
427 return !is_platform_app() || location() == Manifest::COMPONENT;
430 void Extension::AddWebExtentPattern(const URLPattern& pattern) {
431 // Bookmark apps are permissionless.
432 if (from_bookmark())
433 return;
435 extent_.AddPattern(pattern);
438 // static
439 bool Extension::InitExtensionID(extensions::Manifest* manifest,
440 const base::FilePath& path,
441 const std::string& explicit_id,
442 int creation_flags,
443 base::string16* error) {
444 if (!explicit_id.empty()) {
445 manifest->set_extension_id(explicit_id);
446 return true;
449 if (manifest->HasKey(keys::kPublicKey)) {
450 std::string public_key;
451 std::string public_key_bytes;
452 if (!manifest->GetString(keys::kPublicKey, &public_key) ||
453 !ParsePEMKeyBytes(public_key, &public_key_bytes)) {
454 *error = base::ASCIIToUTF16(errors::kInvalidKey);
455 return false;
457 std::string extension_id = crx_file::id_util::GenerateId(public_key_bytes);
458 manifest->set_extension_id(extension_id);
459 return true;
462 if (creation_flags & REQUIRE_KEY) {
463 *error = base::ASCIIToUTF16(errors::kInvalidKey);
464 return false;
465 } else {
466 // If there is a path, we generate the ID from it. This is useful for
467 // development mode, because it keeps the ID stable across restarts and
468 // reloading the extension.
469 std::string extension_id = crx_file::id_util::GenerateIdForPath(path);
470 if (extension_id.empty()) {
471 NOTREACHED() << "Could not create ID from path.";
472 return false;
474 manifest->set_extension_id(extension_id);
475 return true;
479 Extension::Extension(const base::FilePath& path,
480 scoped_ptr<extensions::Manifest> manifest)
481 : manifest_version_(0),
482 converted_from_user_script_(false),
483 manifest_(manifest.release()),
484 finished_parsing_manifest_(false),
485 display_in_launcher_(true),
486 display_in_new_tab_page_(true),
487 wants_file_access_(false),
488 creation_flags_(0) {
489 DCHECK(path.empty() || path.IsAbsolute());
490 path_ = crx_file::id_util::MaybeNormalizePath(path);
493 Extension::~Extension() {
496 bool Extension::InitFromValue(int flags, base::string16* error) {
497 DCHECK(error);
499 creation_flags_ = flags;
501 // Important to load manifest version first because many other features
502 // depend on its value.
503 if (!LoadManifestVersion(error))
504 return false;
506 if (!LoadRequiredFeatures(error))
507 return false;
509 // We don't need to validate because InitExtensionID already did that.
510 manifest_->GetString(keys::kPublicKey, &public_key_);
512 extension_url_ = Extension::GetBaseURLFromExtensionId(id());
514 // Load App settings. LoadExtent at least has to be done before
515 // ParsePermissions(), because the valid permissions depend on what type of
516 // package this is.
517 if (is_app() && !LoadAppFeatures(error))
518 return false;
520 permissions_parser_.reset(new PermissionsParser());
521 if (!permissions_parser_->Parse(this, error))
522 return false;
524 if (manifest_->HasKey(keys::kConvertedFromUserScript)) {
525 manifest_->GetBoolean(keys::kConvertedFromUserScript,
526 &converted_from_user_script_);
529 if (!LoadSharedFeatures(error))
530 return false;
532 permissions_parser_->Finalize(this);
533 permissions_parser_.reset();
535 finished_parsing_manifest_ = true;
537 permissions_data_.reset(new PermissionsData(this));
539 return true;
542 bool Extension::LoadRequiredFeatures(base::string16* error) {
543 if (!LoadName(error) ||
544 !LoadVersion(error))
545 return false;
546 return true;
549 bool Extension::LoadName(base::string16* error) {
550 base::string16 localized_name;
551 if (!manifest_->GetString(keys::kName, &localized_name)) {
552 *error = base::ASCIIToUTF16(errors::kInvalidName);
553 return false;
555 non_localized_name_ = base::UTF16ToUTF8(localized_name);
556 base::i18n::AdjustStringForLocaleDirection(&localized_name);
557 name_ = base::UTF16ToUTF8(localized_name);
558 return true;
561 bool Extension::LoadVersion(base::string16* error) {
562 std::string version_str;
563 if (!manifest_->GetString(keys::kVersion, &version_str)) {
564 *error = base::ASCIIToUTF16(errors::kInvalidVersion);
565 return false;
567 version_.reset(new Version(version_str));
568 if (!version_->IsValid() || version_->components().size() > 4) {
569 *error = base::ASCIIToUTF16(errors::kInvalidVersion);
570 return false;
572 return true;
575 bool Extension::LoadAppFeatures(base::string16* error) {
576 if (!LoadExtent(keys::kWebURLs, &extent_,
577 errors::kInvalidWebURLs, errors::kInvalidWebURL, error)) {
578 return false;
580 if (manifest_->HasKey(keys::kDisplayInLauncher) &&
581 !manifest_->GetBoolean(keys::kDisplayInLauncher, &display_in_launcher_)) {
582 *error = base::ASCIIToUTF16(errors::kInvalidDisplayInLauncher);
583 return false;
585 if (manifest_->HasKey(keys::kDisplayInNewTabPage)) {
586 if (!manifest_->GetBoolean(keys::kDisplayInNewTabPage,
587 &display_in_new_tab_page_)) {
588 *error = base::ASCIIToUTF16(errors::kInvalidDisplayInNewTabPage);
589 return false;
591 } else {
592 // Inherit default from display_in_launcher property.
593 display_in_new_tab_page_ = display_in_launcher_;
595 return true;
598 bool Extension::LoadExtent(const char* key,
599 URLPatternSet* extent,
600 const char* list_error,
601 const char* value_error,
602 base::string16* error) {
603 const base::Value* temp_pattern_value = NULL;
604 if (!manifest_->Get(key, &temp_pattern_value))
605 return true;
607 const base::ListValue* pattern_list = NULL;
608 if (!temp_pattern_value->GetAsList(&pattern_list)) {
609 *error = base::ASCIIToUTF16(list_error);
610 return false;
613 for (size_t i = 0; i < pattern_list->GetSize(); ++i) {
614 std::string pattern_string;
615 if (!pattern_list->GetString(i, &pattern_string)) {
616 *error = ErrorUtils::FormatErrorMessageUTF16(value_error,
617 base::UintToString(i),
618 errors::kExpectString);
619 return false;
622 URLPattern pattern(kValidWebExtentSchemes);
623 URLPattern::ParseResult parse_result = pattern.Parse(pattern_string);
624 if (parse_result == URLPattern::PARSE_ERROR_EMPTY_PATH) {
625 pattern_string += "/";
626 parse_result = pattern.Parse(pattern_string);
629 if (parse_result != URLPattern::PARSE_SUCCESS) {
630 *error = ErrorUtils::FormatErrorMessageUTF16(
631 value_error,
632 base::UintToString(i),
633 URLPattern::GetParseResultString(parse_result));
634 return false;
637 // Do not allow authors to claim "<all_urls>".
638 if (pattern.match_all_urls()) {
639 *error = ErrorUtils::FormatErrorMessageUTF16(
640 value_error,
641 base::UintToString(i),
642 errors::kCannotClaimAllURLsInExtent);
643 return false;
646 // Do not allow authors to claim "*" for host.
647 if (pattern.host().empty()) {
648 *error = ErrorUtils::FormatErrorMessageUTF16(
649 value_error,
650 base::UintToString(i),
651 errors::kCannotClaimAllHostsInExtent);
652 return false;
655 // We do not allow authors to put wildcards in their paths. Instead, we
656 // imply one at the end.
657 if (pattern.path().find('*') != std::string::npos) {
658 *error = ErrorUtils::FormatErrorMessageUTF16(
659 value_error,
660 base::UintToString(i),
661 errors::kNoWildCardsInPaths);
662 return false;
664 pattern.SetPath(pattern.path() + '*');
666 extent->AddPattern(pattern);
669 return true;
672 bool Extension::LoadSharedFeatures(base::string16* error) {
673 if (!LoadDescription(error) ||
674 !ManifestHandler::ParseExtension(this, error) ||
675 !LoadShortName(error))
676 return false;
678 return true;
681 bool Extension::LoadDescription(base::string16* error) {
682 if (manifest_->HasKey(keys::kDescription) &&
683 !manifest_->GetString(keys::kDescription, &description_)) {
684 *error = base::ASCIIToUTF16(errors::kInvalidDescription);
685 return false;
687 return true;
690 bool Extension::LoadManifestVersion(base::string16* error) {
691 // Get the original value out of the dictionary so that we can validate it
692 // more strictly.
693 if (manifest_->value()->HasKey(keys::kManifestVersion)) {
694 int manifest_version = 1;
695 if (!manifest_->GetInteger(keys::kManifestVersion, &manifest_version) ||
696 manifest_version < 1) {
697 *error = base::ASCIIToUTF16(errors::kInvalidManifestVersion);
698 return false;
702 manifest_version_ = manifest_->GetManifestVersion();
703 if (manifest_version_ < kModernManifestVersion &&
704 ((creation_flags_ & REQUIRE_MODERN_MANIFEST_VERSION &&
705 !CommandLine::ForCurrentProcess()->HasSwitch(
706 switches::kAllowLegacyExtensionManifests)) ||
707 GetType() == Manifest::TYPE_PLATFORM_APP)) {
708 *error = ErrorUtils::FormatErrorMessageUTF16(
709 errors::kInvalidManifestVersionOld,
710 base::IntToString(kModernManifestVersion),
711 is_platform_app() ? "apps" : "extensions");
712 return false;
715 return true;
718 bool Extension::LoadShortName(base::string16* error) {
719 if (manifest_->HasKey(keys::kShortName)) {
720 base::string16 localized_short_name;
721 if (!manifest_->GetString(keys::kShortName, &localized_short_name) ||
722 localized_short_name.empty()) {
723 *error = base::ASCIIToUTF16(errors::kInvalidShortName);
724 return false;
727 base::i18n::AdjustStringForLocaleDirection(&localized_short_name);
728 short_name_ = base::UTF16ToUTF8(localized_short_name);
729 } else {
730 short_name_ = name_;
732 return true;
735 ExtensionInfo::ExtensionInfo(const base::DictionaryValue* manifest,
736 const std::string& id,
737 const base::FilePath& path,
738 Manifest::Location location)
739 : extension_id(id),
740 extension_path(path),
741 extension_location(location) {
742 if (manifest)
743 extension_manifest.reset(manifest->DeepCopy());
746 ExtensionInfo::~ExtensionInfo() {}
748 InstalledExtensionInfo::InstalledExtensionInfo(
749 const Extension* extension,
750 bool is_update,
751 bool from_ephemeral,
752 const std::string& old_name)
753 : extension(extension),
754 is_update(is_update),
755 from_ephemeral(from_ephemeral),
756 old_name(old_name) {}
758 UnloadedExtensionInfo::UnloadedExtensionInfo(
759 const Extension* extension,
760 UnloadedExtensionInfo::Reason reason)
761 : reason(reason),
762 extension(extension) {}
764 UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo(
765 const Extension* extension,
766 const PermissionSet* permissions,
767 Reason reason)
768 : reason(reason),
769 extension(extension),
770 permissions(permissions) {}
772 } // namespace extensions