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 "content/public/common/url_constants.h"
24 #include "extensions/common/constants.h"
25 #include "extensions/common/error_utils.h"
26 #include "extensions/common/id_util.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/permissions/api_permission_set.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_set.h"
36 #include "grit/chromium_strings.h"
37 #include "net/base/filename_util.h"
38 #include "url/url_util.h"
41 #include "grit/generated_resources.h"
44 namespace extensions
{
46 namespace keys
= manifest_keys
;
47 namespace values
= manifest_values
;
48 namespace errors
= manifest_errors
;
52 const int kModernManifestVersion
= 2;
53 const int kPEMOutputColumns
= 64;
56 const char kKeyBeginHeaderMarker
[] = "-----BEGIN";
57 const char kKeyBeginFooterMarker
[] = "-----END";
58 const char kKeyInfoEndMarker
[] = "KEY-----";
59 const char kPublic
[] = "PUBLIC";
60 const char kPrivate
[] = "PRIVATE";
62 bool ContainsReservedCharacters(const base::FilePath
& path
) {
63 // We should disallow backslash '\\' as file path separator even on Windows,
64 // because the backslash is not regarded as file path separator on Linux/Mac.
65 // Extensions are cross-platform.
66 // Since FilePath uses backslash '\\' as file path separator on Windows, so we
67 // need to check manually.
68 if (path
.value().find('\\') != path
.value().npos
)
70 return !net::IsSafePortableRelativePath(path
);
75 const char Extension::kMimeType
[] = "application/x-chrome-extension";
77 const int Extension::kValidWebExtentSchemes
=
78 URLPattern::SCHEME_HTTP
| URLPattern::SCHEME_HTTPS
;
80 const int Extension::kValidHostPermissionSchemes
= URLPattern::SCHEME_CHROMEUI
|
81 URLPattern::SCHEME_HTTP
|
82 URLPattern::SCHEME_HTTPS
|
83 URLPattern::SCHEME_FILE
|
84 URLPattern::SCHEME_FTP
;
91 scoped_refptr
<Extension
> Extension::Create(const base::FilePath
& path
,
92 Manifest::Location location
,
93 const base::DictionaryValue
& value
,
95 std::string
* utf8_error
) {
96 return Extension::Create(path
,
100 std::string(), // ID is ignored if empty.
104 // TODO(sungguk): Continue removing std::string errors and replacing
105 // with base::string16. See http://crbug.com/71980.
106 scoped_refptr
<Extension
> Extension::Create(const base::FilePath
& path
,
107 Manifest::Location location
,
108 const base::DictionaryValue
& value
,
110 const std::string
& explicit_id
,
111 std::string
* utf8_error
) {
113 base::string16 error
;
114 scoped_ptr
<extensions::Manifest
> manifest(
115 new extensions::Manifest(
116 location
, scoped_ptr
<base::DictionaryValue
>(value
.DeepCopy())));
118 if (!InitExtensionID(manifest
.get(), path
, explicit_id
, flags
, &error
)) {
119 *utf8_error
= base::UTF16ToUTF8(error
);
123 std::vector
<InstallWarning
> install_warnings
;
124 if (!manifest
->ValidateManifest(utf8_error
, &install_warnings
)) {
128 scoped_refptr
<Extension
> extension
= new Extension(path
, manifest
.Pass());
129 extension
->install_warnings_
.swap(install_warnings
);
131 if (!extension
->InitFromValue(flags
, &error
)) {
132 *utf8_error
= base::UTF16ToUTF8(error
);
140 bool Extension::IdIsValid(const std::string
& id
) {
141 // Verify that the id is legal.
142 if (id
.size() != (id_util::kIdSize
* 2))
145 // We only support lowercase IDs, because IDs can be used as URL components
146 // (where GURL will lowercase it).
147 std::string temp
= StringToLowerASCII(id
);
148 for (size_t i
= 0; i
< temp
.size(); i
++)
149 if (temp
[i
] < 'a' || temp
[i
] > 'p')
155 Manifest::Type
Extension::GetType() const {
156 return converted_from_user_script() ?
157 Manifest::TYPE_USER_SCRIPT
: manifest_
->type();
161 GURL
Extension::GetResourceURL(const GURL
& extension_url
,
162 const std::string
& relative_path
) {
163 DCHECK(extension_url
.SchemeIs(extensions::kExtensionScheme
));
164 DCHECK_EQ("/", extension_url
.path());
166 std::string path
= relative_path
;
168 // If the relative path starts with "/", it is "absolute" relative to the
169 // extension base directory, but extension_url is already specified to refer
170 // to that base directory, so strip the leading "/" if present.
171 if (relative_path
.size() > 0 && relative_path
[0] == '/')
172 path
= relative_path
.substr(1);
174 GURL ret_val
= GURL(extension_url
.spec() + path
);
175 DCHECK(StartsWithASCII(ret_val
.spec(), extension_url
.spec(), false));
180 bool Extension::ResourceMatches(const URLPatternSet
& pattern_set
,
181 const std::string
& resource
) const {
182 return pattern_set
.MatchesURL(extension_url_
.Resolve(resource
));
185 ExtensionResource
Extension::GetResource(
186 const std::string
& relative_path
) const {
187 std::string new_path
= relative_path
;
188 // We have some legacy data where resources have leading slashes.
189 // See: http://crbug.com/121164
190 if (!new_path
.empty() && new_path
.at(0) == '/')
191 new_path
.erase(0, 1);
192 base::FilePath relative_file_path
= base::FilePath::FromUTF8Unsafe(new_path
);
193 if (ContainsReservedCharacters(relative_file_path
))
194 return ExtensionResource();
195 ExtensionResource
r(id(), path(), relative_file_path
);
196 if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE
)) {
197 r
.set_follow_symlinks_anywhere();
202 ExtensionResource
Extension::GetResource(
203 const base::FilePath
& relative_file_path
) const {
204 if (ContainsReservedCharacters(relative_file_path
))
205 return ExtensionResource();
206 ExtensionResource
r(id(), path(), relative_file_path
);
207 if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE
)) {
208 r
.set_follow_symlinks_anywhere();
213 // TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a
214 // util class in base:
215 // http://code.google.com/p/chromium/issues/detail?id=13572
217 bool Extension::ParsePEMKeyBytes(const std::string
& input
,
218 std::string
* output
) {
222 if (input
.length() == 0)
225 std::string working
= input
;
226 if (StartsWithASCII(working
, kKeyBeginHeaderMarker
, true)) {
227 working
= base::CollapseWhitespaceASCII(working
, true);
228 size_t header_pos
= working
.find(kKeyInfoEndMarker
,
229 sizeof(kKeyBeginHeaderMarker
) - 1);
230 if (header_pos
== std::string::npos
)
232 size_t start_pos
= header_pos
+ sizeof(kKeyInfoEndMarker
) - 1;
233 size_t end_pos
= working
.rfind(kKeyBeginFooterMarker
);
234 if (end_pos
== std::string::npos
)
236 if (start_pos
>= end_pos
)
239 working
= working
.substr(start_pos
, end_pos
- start_pos
);
240 if (working
.length() == 0)
244 return base::Base64Decode(working
, output
);
248 bool Extension::ProducePEM(const std::string
& input
, std::string
* output
) {
252 base::Base64Encode(input
, output
);
257 bool Extension::FormatPEMForFileOutput(const std::string
& input
,
261 if (input
.length() == 0)
264 output
->append(kKeyBeginHeaderMarker
);
266 output
->append(is_public
? kPublic
: kPrivate
);
268 output
->append(kKeyInfoEndMarker
);
269 output
->append("\n");
270 for (size_t i
= 0; i
< input
.length(); ) {
271 int slice
= std::min
<int>(input
.length() - i
, kPEMOutputColumns
);
272 output
->append(input
.substr(i
, slice
));
273 output
->append("\n");
276 output
->append(kKeyBeginFooterMarker
);
278 output
->append(is_public
? kPublic
: kPrivate
);
280 output
->append(kKeyInfoEndMarker
);
281 output
->append("\n");
287 GURL
Extension::GetBaseURLFromExtensionId(const std::string
& extension_id
) {
288 return GURL(std::string(extensions::kExtensionScheme
) +
289 content::kStandardSchemeSeparator
+ extension_id
+ "/");
292 bool Extension::HasAPIPermission(APIPermission::ID permission
) const {
293 return PermissionsData::HasAPIPermission(this, permission
);
296 bool Extension::HasAPIPermission(const std::string
& permission_name
) const {
297 return PermissionsData::HasAPIPermission(this, permission_name
);
300 scoped_refptr
<const PermissionSet
> Extension::GetActivePermissions() const {
301 return PermissionsData::GetActivePermissions(this);
304 bool Extension::ShowConfigureContextMenus() const {
305 // Don't show context menu for component extensions. We might want to show
306 // options for component extension button but now there is no component
307 // extension with options. All other menu items like uninstall have
308 // no sense for component extensions.
309 return location() != Manifest::COMPONENT
&&
310 location() != Manifest::EXTERNAL_COMPONENT
;
313 bool Extension::OverlapsWithOrigin(const GURL
& origin
) const {
317 if (web_extent().is_empty())
320 // Note: patterns and extents ignore port numbers.
321 URLPattern
origin_only_pattern(kValidWebExtentSchemes
);
322 if (!origin_only_pattern
.SetScheme(origin
.scheme()))
324 origin_only_pattern
.SetHost(origin
.host());
325 origin_only_pattern
.SetPath("/*");
327 URLPatternSet origin_only_pattern_list
;
328 origin_only_pattern_list
.AddPattern(origin_only_pattern
);
330 return web_extent().OverlapsWith(origin_only_pattern_list
);
333 bool Extension::RequiresSortOrdinal() const {
334 return is_app() && (display_in_launcher_
|| display_in_new_tab_page_
);
337 bool Extension::ShouldDisplayInAppLauncher() const {
338 // Only apps should be displayed in the launcher.
339 return is_app() && display_in_launcher_
&& !is_ephemeral();
342 bool Extension::ShouldDisplayInNewTabPage() const {
343 // Only apps should be displayed on the NTP.
344 return is_app() && display_in_new_tab_page_
&& !is_ephemeral();
347 bool Extension::ShouldDisplayInExtensionSettings() const {
348 // Don't show for themes since the settings UI isn't really useful for them.
352 // Don't show component extensions and invisible apps.
353 if (ShouldNotBeVisible())
356 // Always show unpacked extensions and apps.
357 if (Manifest::IsUnpackedLocation(location()))
360 // Unless they are unpacked, never show hosted apps. Note: We intentionally
361 // show packaged apps and platform apps because there are some pieces of
362 // functionality that are only available in chrome://extensions/ but which
363 // are needed for packaged and platform apps. For example, inspecting
364 // background pages. See http://crbug.com/116134.
371 bool Extension::ShouldNotBeVisible() const {
372 // Don't show component extensions because they are only extensions as an
373 // implementation detail of Chrome.
374 if (extensions::Manifest::IsComponentLocation(location()) &&
375 !CommandLine::ForCurrentProcess()->HasSwitch(
376 switches::kShowComponentExtensionOptions
)) {
380 // Always show unpacked extensions and apps.
381 if (Manifest::IsUnpackedLocation(location()))
384 // Don't show apps that aren't visible in either launcher or ntp.
385 if (is_app() && !ShouldDisplayInAppLauncher() && !ShouldDisplayInNewTabPage())
391 Extension::ManifestData
* Extension::GetManifestData(const std::string
& key
)
393 DCHECK(finished_parsing_manifest_
|| thread_checker_
.CalledOnValidThread());
394 ManifestDataMap::const_iterator iter
= manifest_data_
.find(key
);
395 if (iter
!= manifest_data_
.end())
396 return iter
->second
.get();
400 void Extension::SetManifestData(const std::string
& key
,
401 Extension::ManifestData
* data
) {
402 DCHECK(!finished_parsing_manifest_
&& thread_checker_
.CalledOnValidThread());
403 manifest_data_
[key
] = linked_ptr
<ManifestData
>(data
);
406 Manifest::Location
Extension::location() const {
407 return manifest_
->location();
410 const std::string
& Extension::id() const {
411 return manifest_
->extension_id();
414 const std::string
Extension::VersionString() const {
415 return version()->GetString();
418 void Extension::AddInstallWarning(const InstallWarning
& new_warning
) {
419 install_warnings_
.push_back(new_warning
);
422 void Extension::AddInstallWarnings(
423 const std::vector
<InstallWarning
>& new_warnings
) {
424 install_warnings_
.insert(install_warnings_
.end(),
425 new_warnings
.begin(), new_warnings
.end());
428 bool Extension::is_app() const {
429 return manifest_
->is_app();
432 bool Extension::is_platform_app() const {
433 return manifest_
->is_platform_app();
436 bool Extension::is_hosted_app() const {
437 return manifest()->is_hosted_app();
440 bool Extension::is_legacy_packaged_app() const {
441 return manifest()->is_legacy_packaged_app();
444 bool Extension::is_extension() const {
445 return manifest()->is_extension();
448 bool Extension::can_be_incognito_enabled() const {
449 // Only component platform apps are supported in incognito.
450 return !is_platform_app() || location() == Manifest::COMPONENT
;
453 void Extension::AddWebExtentPattern(const URLPattern
& pattern
) {
454 // Bookmark apps are permissionless.
458 extent_
.AddPattern(pattern
);
461 bool Extension::is_theme() const {
462 return manifest()->is_theme();
466 bool Extension::InitExtensionID(extensions::Manifest
* manifest
,
467 const base::FilePath
& path
,
468 const std::string
& explicit_id
,
470 base::string16
* error
) {
471 if (!explicit_id
.empty()) {
472 manifest
->set_extension_id(explicit_id
);
476 if (manifest
->HasKey(keys::kPublicKey
)) {
477 std::string public_key
;
478 std::string public_key_bytes
;
479 if (!manifest
->GetString(keys::kPublicKey
, &public_key
) ||
480 !ParsePEMKeyBytes(public_key
, &public_key_bytes
)) {
481 *error
= base::ASCIIToUTF16(errors::kInvalidKey
);
484 std::string extension_id
= id_util::GenerateId(public_key_bytes
);
485 manifest
->set_extension_id(extension_id
);
489 if (creation_flags
& REQUIRE_KEY
) {
490 *error
= base::ASCIIToUTF16(errors::kInvalidKey
);
493 // If there is a path, we generate the ID from it. This is useful for
494 // development mode, because it keeps the ID stable across restarts and
495 // reloading the extension.
496 std::string extension_id
= id_util::GenerateIdForPath(path
);
497 if (extension_id
.empty()) {
498 NOTREACHED() << "Could not create ID from path.";
501 manifest
->set_extension_id(extension_id
);
506 Extension::Extension(const base::FilePath
& path
,
507 scoped_ptr
<extensions::Manifest
> manifest
)
508 : manifest_version_(0),
509 converted_from_user_script_(false),
510 manifest_(manifest
.release()),
511 finished_parsing_manifest_(false),
512 display_in_launcher_(true),
513 display_in_new_tab_page_(true),
514 wants_file_access_(false),
516 DCHECK(path
.empty() || path
.IsAbsolute());
517 path_
= id_util::MaybeNormalizePath(path
);
520 Extension::~Extension() {
523 bool Extension::InitFromValue(int flags
, base::string16
* error
) {
526 creation_flags_
= flags
;
528 // Important to load manifest version first because many other features
529 // depend on its value.
530 if (!LoadManifestVersion(error
))
533 if (!LoadRequiredFeatures(error
))
536 // We don't need to validate because InitExtensionID already did that.
537 manifest_
->GetString(keys::kPublicKey
, &public_key_
);
539 extension_url_
= Extension::GetBaseURLFromExtensionId(id());
541 // Load App settings. LoadExtent at least has to be done before
542 // ParsePermissions(), because the valid permissions depend on what type of
544 if (is_app() && !LoadAppFeatures(error
))
547 permissions_data_
.reset(new PermissionsData
);
548 if (!permissions_data_
->ParsePermissions(this, error
))
551 if (manifest_
->HasKey(keys::kConvertedFromUserScript
)) {
552 manifest_
->GetBoolean(keys::kConvertedFromUserScript
,
553 &converted_from_user_script_
);
556 if (!LoadSharedFeatures(error
))
559 finished_parsing_manifest_
= true;
561 permissions_data_
->InitializeManifestPermissions(this);
562 permissions_data_
->FinalizePermissions(this);
567 bool Extension::LoadRequiredFeatures(base::string16
* error
) {
568 if (!LoadName(error
) ||
574 bool Extension::LoadName(base::string16
* error
) {
575 base::string16 localized_name
;
576 if (!manifest_
->GetString(keys::kName
, &localized_name
)) {
577 *error
= base::ASCIIToUTF16(errors::kInvalidName
);
580 non_localized_name_
= base::UTF16ToUTF8(localized_name
);
581 base::i18n::AdjustStringForLocaleDirection(&localized_name
);
582 name_
= base::UTF16ToUTF8(localized_name
);
586 bool Extension::LoadVersion(base::string16
* error
) {
587 std::string version_str
;
588 if (!manifest_
->GetString(keys::kVersion
, &version_str
)) {
589 *error
= base::ASCIIToUTF16(errors::kInvalidVersion
);
592 version_
.reset(new Version(version_str
));
593 if (!version_
->IsValid() || version_
->components().size() > 4) {
594 *error
= base::ASCIIToUTF16(errors::kInvalidVersion
);
600 bool Extension::LoadAppFeatures(base::string16
* error
) {
601 if (!LoadExtent(keys::kWebURLs
, &extent_
,
602 errors::kInvalidWebURLs
, errors::kInvalidWebURL
, error
)) {
605 if (manifest_
->HasKey(keys::kDisplayInLauncher
) &&
606 !manifest_
->GetBoolean(keys::kDisplayInLauncher
, &display_in_launcher_
)) {
607 *error
= base::ASCIIToUTF16(errors::kInvalidDisplayInLauncher
);
610 if (manifest_
->HasKey(keys::kDisplayInNewTabPage
)) {
611 if (!manifest_
->GetBoolean(keys::kDisplayInNewTabPage
,
612 &display_in_new_tab_page_
)) {
613 *error
= base::ASCIIToUTF16(errors::kInvalidDisplayInNewTabPage
);
617 // Inherit default from display_in_launcher property.
618 display_in_new_tab_page_
= display_in_launcher_
;
623 bool Extension::LoadExtent(const char* key
,
624 URLPatternSet
* extent
,
625 const char* list_error
,
626 const char* value_error
,
627 base::string16
* error
) {
628 const base::Value
* temp_pattern_value
= NULL
;
629 if (!manifest_
->Get(key
, &temp_pattern_value
))
632 const base::ListValue
* pattern_list
= NULL
;
633 if (!temp_pattern_value
->GetAsList(&pattern_list
)) {
634 *error
= base::ASCIIToUTF16(list_error
);
638 for (size_t i
= 0; i
< pattern_list
->GetSize(); ++i
) {
639 std::string pattern_string
;
640 if (!pattern_list
->GetString(i
, &pattern_string
)) {
641 *error
= ErrorUtils::FormatErrorMessageUTF16(value_error
,
642 base::UintToString(i
),
643 errors::kExpectString
);
647 URLPattern
pattern(kValidWebExtentSchemes
);
648 URLPattern::ParseResult parse_result
= pattern
.Parse(pattern_string
);
649 if (parse_result
== URLPattern::PARSE_ERROR_EMPTY_PATH
) {
650 pattern_string
+= "/";
651 parse_result
= pattern
.Parse(pattern_string
);
654 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
655 *error
= ErrorUtils::FormatErrorMessageUTF16(
657 base::UintToString(i
),
658 URLPattern::GetParseResultString(parse_result
));
662 // Do not allow authors to claim "<all_urls>".
663 if (pattern
.match_all_urls()) {
664 *error
= ErrorUtils::FormatErrorMessageUTF16(
666 base::UintToString(i
),
667 errors::kCannotClaimAllURLsInExtent
);
671 // Do not allow authors to claim "*" for host.
672 if (pattern
.host().empty()) {
673 *error
= ErrorUtils::FormatErrorMessageUTF16(
675 base::UintToString(i
),
676 errors::kCannotClaimAllHostsInExtent
);
680 // We do not allow authors to put wildcards in their paths. Instead, we
681 // imply one at the end.
682 if (pattern
.path().find('*') != std::string::npos
) {
683 *error
= ErrorUtils::FormatErrorMessageUTF16(
685 base::UintToString(i
),
686 errors::kNoWildCardsInPaths
);
689 pattern
.SetPath(pattern
.path() + '*');
691 extent
->AddPattern(pattern
);
697 bool Extension::LoadSharedFeatures(base::string16
* error
) {
698 if (!LoadDescription(error
) ||
699 !ManifestHandler::ParseExtension(this, error
) ||
700 !LoadShortName(error
))
706 bool Extension::LoadDescription(base::string16
* error
) {
707 if (manifest_
->HasKey(keys::kDescription
) &&
708 !manifest_
->GetString(keys::kDescription
, &description_
)) {
709 *error
= base::ASCIIToUTF16(errors::kInvalidDescription
);
715 bool Extension::LoadManifestVersion(base::string16
* error
) {
716 // Get the original value out of the dictionary so that we can validate it
718 if (manifest_
->value()->HasKey(keys::kManifestVersion
)) {
719 int manifest_version
= 1;
720 if (!manifest_
->GetInteger(keys::kManifestVersion
, &manifest_version
) ||
721 manifest_version
< 1) {
722 *error
= base::ASCIIToUTF16(errors::kInvalidManifestVersion
);
727 manifest_version_
= manifest_
->GetManifestVersion();
728 if (manifest_version_
< kModernManifestVersion
&&
729 ((creation_flags_
& REQUIRE_MODERN_MANIFEST_VERSION
&&
730 !CommandLine::ForCurrentProcess()->HasSwitch(
731 switches::kAllowLegacyExtensionManifests
)) ||
732 GetType() == Manifest::TYPE_PLATFORM_APP
)) {
733 *error
= ErrorUtils::FormatErrorMessageUTF16(
734 errors::kInvalidManifestVersionOld
,
735 base::IntToString(kModernManifestVersion
),
736 is_platform_app() ? "apps" : "extensions");
743 bool Extension::LoadShortName(base::string16
* error
) {
744 if (manifest_
->HasKey(keys::kShortName
)) {
745 base::string16 localized_short_name
;
746 if (!manifest_
->GetString(keys::kShortName
, &localized_short_name
) ||
747 localized_short_name
.empty()) {
748 *error
= base::ASCIIToUTF16(errors::kInvalidShortName
);
752 base::i18n::AdjustStringForLocaleDirection(&localized_short_name
);
753 short_name_
= base::UTF16ToUTF8(localized_short_name
);
760 ExtensionInfo::ExtensionInfo(const base::DictionaryValue
* manifest
,
761 const std::string
& id
,
762 const base::FilePath
& path
,
763 Manifest::Location location
)
765 extension_path(path
),
766 extension_location(location
) {
768 extension_manifest
.reset(manifest
->DeepCopy());
771 ExtensionInfo::~ExtensionInfo() {}
773 InstalledExtensionInfo::InstalledExtensionInfo(
774 const Extension
* extension
,
776 const std::string
& old_name
)
777 : extension(extension
),
778 is_update(is_update
),
779 old_name(old_name
) {}
781 UnloadedExtensionInfo::UnloadedExtensionInfo(
782 const Extension
* extension
,
783 UnloadedExtensionInfo::Reason reason
)
785 extension(extension
) {}
787 UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo(
788 const Extension
* extension
,
789 const PermissionSet
* permissions
,
792 extension(extension
),
793 permissions(permissions
) {}
795 } // namespace extensions