| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 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.h" | 5 #include "chrome/common/extensions/extension.h" |
| 6 | 6 |
| 7 #include "base/base64.h" | 7 #include "base/base64.h" |
| 8 #include "base/basictypes.h" | 8 #include "base/basictypes.h" |
| 9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
| 10 #include "base/file_util.h" | 10 #include "base/file_util.h" |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 113 } | 113 } |
| 114 ~ExtensionConfig() { } | 114 ~ExtensionConfig() { } |
| 115 | 115 |
| 116 // A whitelist of extensions that can script anywhere. Do not add to this | 116 // A whitelist of extensions that can script anywhere. Do not add to this |
| 117 // list (except in tests) without consulting the Extensions team first. | 117 // list (except in tests) without consulting the Extensions team first. |
| 118 // Note: Component extensions have this right implicitly and do not need to be | 118 // Note: Component extensions have this right implicitly and do not need to be |
| 119 // added to this list. | 119 // added to this list. |
| 120 Extension::ScriptingWhitelist scripting_whitelist_; | 120 Extension::ScriptingWhitelist scripting_whitelist_; |
| 121 }; | 121 }; |
| 122 | 122 |
| 123 bool ReadLaunchDimension(const extensions::Manifest* manifest, | |
| 124 const char* key, | |
| 125 int* target, | |
| 126 bool is_valid_container, | |
| 127 string16* error) { | |
| 128 const Value* temp = NULL; | |
| 129 if (manifest->Get(key, &temp)) { | |
| 130 if (!is_valid_container) { | |
| 131 *error = ErrorUtils::FormatErrorMessageUTF16( | |
| 132 errors::kInvalidLaunchValueContainer, | |
| 133 key); | |
| 134 return false; | |
| 135 } | |
| 136 if (!temp->GetAsInteger(target) || *target < 0) { | |
| 137 *target = 0; | |
| 138 *error = ErrorUtils::FormatErrorMessageUTF16( | |
| 139 errors::kInvalidLaunchValue, | |
| 140 key); | |
| 141 return false; | |
| 142 } | |
| 143 } | |
| 144 return true; | |
| 145 } | |
| 146 | |
| 147 bool ContainsManifestForbiddenPermission(const APIPermissionSet& apis, | 123 bool ContainsManifestForbiddenPermission(const APIPermissionSet& apis, |
| 148 string16* error) { | 124 string16* error) { |
| 149 CHECK(error); | 125 CHECK(error); |
| 150 for (APIPermissionSet::const_iterator i = apis.begin(); | 126 for (APIPermissionSet::const_iterator i = apis.begin(); |
| 151 i != apis.end(); ++i) { | 127 i != apis.end(); ++i) { |
| 152 if ((*i)->ManifestEntryForbidden()) { | 128 if ((*i)->ManifestEntryForbidden()) { |
| 153 *error = ErrorUtils::FormatErrorMessageUTF16( | 129 *error = ErrorUtils::FormatErrorMessageUTF16( |
| 154 errors::kPermissionNotAllowedInManifest, | 130 errors::kPermissionNotAllowedInManifest, |
| 155 (*i)->info()->name()); | 131 (*i)->info()->name()); |
| 156 return true; | 132 return true; |
| (...skipping 550 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 707 iter != browser_action->default_icon.map().end(); | 683 iter != browser_action->default_icon.map().end(); |
| 708 ++iter) { | 684 ++iter) { |
| 709 image_paths.insert( | 685 image_paths.insert( |
| 710 base::FilePath::FromWStringHack(UTF8ToWide(iter->second))); | 686 base::FilePath::FromWStringHack(UTF8ToWide(iter->second))); |
| 711 } | 687 } |
| 712 } | 688 } |
| 713 | 689 |
| 714 return image_paths; | 690 return image_paths; |
| 715 } | 691 } |
| 716 | 692 |
| 717 GURL Extension::GetFullLaunchURL() const { | |
| 718 return launch_local_path().empty() ? GURL(launch_web_url()) : | |
| 719 url().Resolve(launch_local_path()); | |
| 720 } | |
| 721 | |
| 722 bool Extension::CanExecuteScriptOnPage(const GURL& document_url, | 693 bool Extension::CanExecuteScriptOnPage(const GURL& document_url, |
| 723 const GURL& top_frame_url, | 694 const GURL& top_frame_url, |
| 724 int tab_id, | 695 int tab_id, |
| 725 const UserScript* script, | 696 const UserScript* script, |
| 726 std::string* error) const { | 697 std::string* error) const { |
| 727 base::AutoLock auto_lock(runtime_data_lock_); | 698 base::AutoLock auto_lock(runtime_data_lock_); |
| 728 // The gallery is special-cased as a restricted URL for scripting to prevent | 699 // The gallery is special-cased as a restricted URL for scripting to prevent |
| 729 // access to special JS bindings we expose to the gallery (and avoid things | 700 // access to special JS bindings we expose to the gallery (and avoid things |
| 730 // like extensions removing the "report abuse" link). | 701 // like extensions removing the "report abuse" link). |
| 731 // TODO(erikkay): This seems like the wrong test. Shouldn't we we testing | 702 // TODO(erikkay): This seems like the wrong test. Shouldn't we we testing |
| (...skipping 292 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1024 } | 995 } |
| 1025 | 996 |
| 1026 bool Extension::can_be_incognito_enabled() const { | 997 bool Extension::can_be_incognito_enabled() const { |
| 1027 return !is_platform_app(); | 998 return !is_platform_app(); |
| 1028 } | 999 } |
| 1029 | 1000 |
| 1030 void Extension::AddWebExtentPattern(const URLPattern& pattern) { | 1001 void Extension::AddWebExtentPattern(const URLPattern& pattern) { |
| 1031 extent_.AddPattern(pattern); | 1002 extent_.AddPattern(pattern); |
| 1032 } | 1003 } |
| 1033 | 1004 |
| 1005 void Extension::ClearWebExtentPatterns() { |
| 1006 extent_.ClearPatterns(); |
| 1007 } |
| 1008 |
| 1034 bool Extension::is_theme() const { | 1009 bool Extension::is_theme() const { |
| 1035 return manifest()->is_theme(); | 1010 return manifest()->is_theme(); |
| 1036 } | 1011 } |
| 1037 | 1012 |
| 1038 Extension::RuntimeData::RuntimeData() {} | 1013 Extension::RuntimeData::RuntimeData() {} |
| 1039 Extension::RuntimeData::RuntimeData(const PermissionSet* active) | 1014 Extension::RuntimeData::RuntimeData(const PermissionSet* active) |
| 1040 : active_permissions_(active) {} | 1015 : active_permissions_(active) {} |
| 1041 Extension::RuntimeData::~RuntimeData() {} | 1016 Extension::RuntimeData::~RuntimeData() {} |
| 1042 | 1017 |
| 1043 void Extension::RuntimeData::SetActivePermissions( | 1018 void Extension::RuntimeData::SetActivePermissions( |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1121 // See http://b/4946060 for more details. | 1096 // See http://b/4946060 for more details. |
| 1122 return id == std::string("nckgahadagoaajjgafhacjanaoiihapd"); | 1097 return id == std::string("nckgahadagoaajjgafhacjanaoiihapd"); |
| 1123 } | 1098 } |
| 1124 | 1099 |
| 1125 Extension::Extension(const base::FilePath& path, | 1100 Extension::Extension(const base::FilePath& path, |
| 1126 scoped_ptr<extensions::Manifest> manifest) | 1101 scoped_ptr<extensions::Manifest> manifest) |
| 1127 : manifest_version_(0), | 1102 : manifest_version_(0), |
| 1128 converted_from_user_script_(false), | 1103 converted_from_user_script_(false), |
| 1129 manifest_(manifest.release()), | 1104 manifest_(manifest.release()), |
| 1130 finished_parsing_manifest_(false), | 1105 finished_parsing_manifest_(false), |
| 1131 launch_container_(extension_misc::LAUNCH_TAB), | |
| 1132 launch_width_(0), | |
| 1133 launch_height_(0), | |
| 1134 display_in_launcher_(true), | 1106 display_in_launcher_(true), |
| 1135 display_in_new_tab_page_(true), | 1107 display_in_new_tab_page_(true), |
| 1136 wants_file_access_(false), | 1108 wants_file_access_(false), |
| 1137 creation_flags_(0) { | 1109 creation_flags_(0) { |
| 1138 DCHECK(path.empty() || path.IsAbsolute()); | 1110 DCHECK(path.empty() || path.IsAbsolute()); |
| 1139 path_ = id_util::MaybeNormalizePath(path); | 1111 path_ = id_util::MaybeNormalizePath(path); |
| 1140 } | 1112 } |
| 1141 | 1113 |
| 1142 Extension::~Extension() { | 1114 Extension::~Extension() { |
| 1143 } | 1115 } |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1275 version_.reset(new Version(version_str)); | 1247 version_.reset(new Version(version_str)); |
| 1276 if (!version_->IsValid() || version_->components().size() > 4) { | 1248 if (!version_->IsValid() || version_->components().size() > 4) { |
| 1277 *error = ASCIIToUTF16(errors::kInvalidVersion); | 1249 *error = ASCIIToUTF16(errors::kInvalidVersion); |
| 1278 return false; | 1250 return false; |
| 1279 } | 1251 } |
| 1280 return true; | 1252 return true; |
| 1281 } | 1253 } |
| 1282 | 1254 |
| 1283 bool Extension::LoadAppFeatures(string16* error) { | 1255 bool Extension::LoadAppFeatures(string16* error) { |
| 1284 if (!LoadExtent(keys::kWebURLs, &extent_, | 1256 if (!LoadExtent(keys::kWebURLs, &extent_, |
| 1285 errors::kInvalidWebURLs, errors::kInvalidWebURL, error) || | 1257 errors::kInvalidWebURLs, errors::kInvalidWebURL, error)) { |
| 1286 !LoadLaunchURL(error) || | |
| 1287 !LoadLaunchContainer(error)) { | |
| 1288 return false; | 1258 return false; |
| 1289 } | 1259 } |
| 1290 if (manifest_->HasKey(keys::kDisplayInLauncher) && | 1260 if (manifest_->HasKey(keys::kDisplayInLauncher) && |
| 1291 !manifest_->GetBoolean(keys::kDisplayInLauncher, &display_in_launcher_)) { | 1261 !manifest_->GetBoolean(keys::kDisplayInLauncher, &display_in_launcher_)) { |
| 1292 *error = ASCIIToUTF16(errors::kInvalidDisplayInLauncher); | 1262 *error = ASCIIToUTF16(errors::kInvalidDisplayInLauncher); |
| 1293 return false; | 1263 return false; |
| 1294 } | 1264 } |
| 1295 if (manifest_->HasKey(keys::kDisplayInNewTabPage)) { | 1265 if (manifest_->HasKey(keys::kDisplayInNewTabPage)) { |
| 1296 if (!manifest_->GetBoolean(keys::kDisplayInNewTabPage, | 1266 if (!manifest_->GetBoolean(keys::kDisplayInNewTabPage, |
| 1297 &display_in_new_tab_page_)) { | 1267 &display_in_new_tab_page_)) { |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1372 return false; | 1342 return false; |
| 1373 } | 1343 } |
| 1374 pattern.SetPath(pattern.path() + '*'); | 1344 pattern.SetPath(pattern.path() + '*'); |
| 1375 | 1345 |
| 1376 extent->AddPattern(pattern); | 1346 extent->AddPattern(pattern); |
| 1377 } | 1347 } |
| 1378 | 1348 |
| 1379 return true; | 1349 return true; |
| 1380 } | 1350 } |
| 1381 | 1351 |
| 1382 bool Extension::LoadLaunchContainer(string16* error) { | |
| 1383 const Value* tmp_launcher_container = NULL; | |
| 1384 if (!manifest_->Get(keys::kLaunchContainer, &tmp_launcher_container)) | |
| 1385 return true; | |
| 1386 | |
| 1387 std::string launch_container_string; | |
| 1388 if (!tmp_launcher_container->GetAsString(&launch_container_string)) { | |
| 1389 *error = ASCIIToUTF16(errors::kInvalidLaunchContainer); | |
| 1390 return false; | |
| 1391 } | |
| 1392 | |
| 1393 if (launch_container_string == values::kLaunchContainerPanel) { | |
| 1394 launch_container_ = extension_misc::LAUNCH_PANEL; | |
| 1395 } else if (launch_container_string == values::kLaunchContainerTab) { | |
| 1396 launch_container_ = extension_misc::LAUNCH_TAB; | |
| 1397 } else { | |
| 1398 *error = ASCIIToUTF16(errors::kInvalidLaunchContainer); | |
| 1399 return false; | |
| 1400 } | |
| 1401 | |
| 1402 bool can_specify_initial_size = | |
| 1403 launch_container_ == extension_misc::LAUNCH_PANEL || | |
| 1404 launch_container_ == extension_misc::LAUNCH_WINDOW; | |
| 1405 | |
| 1406 // Validate the container width if present. | |
| 1407 if (!ReadLaunchDimension(manifest_.get(), | |
| 1408 keys::kLaunchWidth, | |
| 1409 &launch_width_, | |
| 1410 can_specify_initial_size, | |
| 1411 error)) { | |
| 1412 return false; | |
| 1413 } | |
| 1414 | |
| 1415 // Validate container height if present. | |
| 1416 if (!ReadLaunchDimension(manifest_.get(), | |
| 1417 keys::kLaunchHeight, | |
| 1418 &launch_height_, | |
| 1419 can_specify_initial_size, | |
| 1420 error)) { | |
| 1421 return false; | |
| 1422 } | |
| 1423 | |
| 1424 return true; | |
| 1425 } | |
| 1426 | |
| 1427 bool Extension::LoadLaunchURL(string16* error) { | |
| 1428 const Value* temp = NULL; | |
| 1429 | |
| 1430 // launch URL can be either local (to chrome-extension:// root) or an absolute | |
| 1431 // web URL. | |
| 1432 if (manifest_->Get(keys::kLaunchLocalPath, &temp)) { | |
| 1433 if (manifest_->Get(keys::kLaunchWebURL, NULL)) { | |
| 1434 *error = ASCIIToUTF16(errors::kLaunchPathAndURLAreExclusive); | |
| 1435 return false; | |
| 1436 } | |
| 1437 | |
| 1438 if (manifest_->Get(keys::kWebURLs, NULL)) { | |
| 1439 *error = ASCIIToUTF16(errors::kLaunchPathAndExtentAreExclusive); | |
| 1440 return false; | |
| 1441 } | |
| 1442 | |
| 1443 std::string launch_path; | |
| 1444 if (!temp->GetAsString(&launch_path)) { | |
| 1445 *error = ErrorUtils::FormatErrorMessageUTF16( | |
| 1446 errors::kInvalidLaunchValue, | |
| 1447 keys::kLaunchLocalPath); | |
| 1448 return false; | |
| 1449 } | |
| 1450 | |
| 1451 // Ensure the launch path is a valid relative URL. | |
| 1452 GURL resolved = url().Resolve(launch_path); | |
| 1453 if (!resolved.is_valid() || resolved.GetOrigin() != url()) { | |
| 1454 *error = ErrorUtils::FormatErrorMessageUTF16( | |
| 1455 errors::kInvalidLaunchValue, | |
| 1456 keys::kLaunchLocalPath); | |
| 1457 return false; | |
| 1458 } | |
| 1459 | |
| 1460 launch_local_path_ = launch_path; | |
| 1461 } else if (manifest_->Get(keys::kLaunchWebURL, &temp)) { | |
| 1462 std::string launch_url; | |
| 1463 if (!temp->GetAsString(&launch_url)) { | |
| 1464 *error = ErrorUtils::FormatErrorMessageUTF16( | |
| 1465 errors::kInvalidLaunchValue, | |
| 1466 keys::kLaunchWebURL); | |
| 1467 return false; | |
| 1468 } | |
| 1469 | |
| 1470 // Ensure the launch URL is a valid absolute URL and web extent scheme. | |
| 1471 GURL url(launch_url); | |
| 1472 URLPattern pattern(kValidWebExtentSchemes); | |
| 1473 if (!url.is_valid() || !pattern.SetScheme(url.scheme())) { | |
| 1474 *error = ErrorUtils::FormatErrorMessageUTF16( | |
| 1475 errors::kInvalidLaunchValue, | |
| 1476 keys::kLaunchWebURL); | |
| 1477 return false; | |
| 1478 } | |
| 1479 | |
| 1480 launch_web_url_ = launch_url; | |
| 1481 } else if (is_legacy_packaged_app() || is_hosted_app()) { | |
| 1482 *error = ASCIIToUTF16(errors::kLaunchURLRequired); | |
| 1483 return false; | |
| 1484 } | |
| 1485 | |
| 1486 // If there is no extent, we default the extent based on the launch URL. | |
| 1487 if (web_extent().is_empty() && !launch_web_url().empty()) { | |
| 1488 GURL launch_url(launch_web_url()); | |
| 1489 URLPattern pattern(kValidWebExtentSchemes); | |
| 1490 if (!pattern.SetScheme("*")) { | |
| 1491 *error = ErrorUtils::FormatErrorMessageUTF16( | |
| 1492 errors::kInvalidLaunchValue, | |
| 1493 keys::kLaunchWebURL); | |
| 1494 return false; | |
| 1495 } | |
| 1496 pattern.SetHost(launch_url.host()); | |
| 1497 pattern.SetPath("/*"); | |
| 1498 extent_.AddPattern(pattern); | |
| 1499 } | |
| 1500 | |
| 1501 // In order for the --apps-gallery-url switch to work with the gallery | |
| 1502 // process isolation, we must insert any provided value into the component | |
| 1503 // app's launch url and web extent. | |
| 1504 if (id() == extension_misc::kWebStoreAppId) { | |
| 1505 std::string gallery_url_str = CommandLine::ForCurrentProcess()-> | |
| 1506 GetSwitchValueASCII(switches::kAppsGalleryURL); | |
| 1507 | |
| 1508 // Empty string means option was not used. | |
| 1509 if (!gallery_url_str.empty()) { | |
| 1510 GURL gallery_url(gallery_url_str); | |
| 1511 OverrideLaunchUrl(gallery_url); | |
| 1512 } | |
| 1513 } else if (id() == extension_misc::kCloudPrintAppId) { | |
| 1514 // In order for the --cloud-print-service switch to work, we must update | |
| 1515 // the launch URL and web extent. | |
| 1516 // TODO(sanjeevr): Ideally we want to use CloudPrintURL here but that is | |
| 1517 // currently under chrome/browser. | |
| 1518 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); | |
| 1519 GURL cloud_print_service_url = GURL(command_line.GetSwitchValueASCII( | |
| 1520 switches::kCloudPrintServiceURL)); | |
| 1521 if (!cloud_print_service_url.is_empty()) { | |
| 1522 std::string path( | |
| 1523 cloud_print_service_url.path() + "/enable_chrome_connector"); | |
| 1524 GURL::Replacements replacements; | |
| 1525 replacements.SetPathStr(path); | |
| 1526 GURL cloud_print_enable_connector_url = | |
| 1527 cloud_print_service_url.ReplaceComponents(replacements); | |
| 1528 OverrideLaunchUrl(cloud_print_enable_connector_url); | |
| 1529 } | |
| 1530 } else if (id() == extension_misc::kChromeAppId) { | |
| 1531 // Override launch url to new tab. | |
| 1532 launch_web_url_ = chrome::kChromeUINewTabURL; | |
| 1533 extent_.ClearPatterns(); | |
| 1534 } | |
| 1535 | |
| 1536 return true; | |
| 1537 } | |
| 1538 | |
| 1539 bool Extension::LoadSharedFeatures(string16* error) { | 1352 bool Extension::LoadSharedFeatures(string16* error) { |
| 1540 if (!LoadDescription(error) || | 1353 if (!LoadDescription(error) || |
| 1541 !ManifestHandler::ParseExtension(this, error) || | 1354 !ManifestHandler::ParseExtension(this, error) || |
| 1542 !LoadNaClModules(error)) | 1355 !LoadNaClModules(error)) |
| 1543 return false; | 1356 return false; |
| 1544 | 1357 |
| 1545 return true; | 1358 return true; |
| 1546 } | 1359 } |
| 1547 | 1360 |
| 1548 bool Extension::LoadDescription(string16* error) { | 1361 bool Extension::LoadDescription(string16* error) { |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1628 | 1441 |
| 1629 if (ActionInfo::GetBrowserActionInfo(this)) | 1442 if (ActionInfo::GetBrowserActionInfo(this)) |
| 1630 ++num_surfaces; | 1443 ++num_surfaces; |
| 1631 | 1444 |
| 1632 if (is_app()) | 1445 if (is_app()) |
| 1633 ++num_surfaces; | 1446 ++num_surfaces; |
| 1634 | 1447 |
| 1635 return num_surfaces > 1; | 1448 return num_surfaces > 1; |
| 1636 } | 1449 } |
| 1637 | 1450 |
| 1638 void Extension::OverrideLaunchUrl(const GURL& override_url) { | |
| 1639 GURL new_url(override_url); | |
| 1640 if (!new_url.is_valid()) { | |
| 1641 DLOG(WARNING) << "Invalid override url given for " << name(); | |
| 1642 } else { | |
| 1643 if (new_url.has_port()) { | |
| 1644 DLOG(WARNING) << "Override URL passed for " << name() | |
| 1645 << " should not contain a port. Removing it."; | |
| 1646 | |
| 1647 GURL::Replacements remove_port; | |
| 1648 remove_port.ClearPort(); | |
| 1649 new_url = new_url.ReplaceComponents(remove_port); | |
| 1650 } | |
| 1651 | |
| 1652 launch_web_url_ = new_url.spec(); | |
| 1653 | |
| 1654 URLPattern pattern(kValidWebExtentSchemes); | |
| 1655 URLPattern::ParseResult result = pattern.Parse(new_url.spec()); | |
| 1656 DCHECK_EQ(result, URLPattern::PARSE_SUCCESS); | |
| 1657 pattern.SetPath(pattern.path() + '*'); | |
| 1658 extent_.AddPattern(pattern); | |
| 1659 } | |
| 1660 } | |
| 1661 | |
| 1662 bool Extension::CanSpecifyExperimentalPermission() const { | 1451 bool Extension::CanSpecifyExperimentalPermission() const { |
| 1663 if (location() == Manifest::COMPONENT) | 1452 if (location() == Manifest::COMPONENT) |
| 1664 return true; | 1453 return true; |
| 1665 | 1454 |
| 1666 if (CommandLine::ForCurrentProcess()->HasSwitch( | 1455 if (CommandLine::ForCurrentProcess()->HasSwitch( |
| 1667 switches::kEnableExperimentalExtensionApis)) { | 1456 switches::kEnableExperimentalExtensionApis)) { |
| 1668 return true; | 1457 return true; |
| 1669 } | 1458 } |
| 1670 | 1459 |
| 1671 // We rely on the webstore to check access to experimental. This way we can | 1460 // We rely on the webstore to check access to experimental. This way we can |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1801 | 1590 |
| 1802 UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo( | 1591 UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo( |
| 1803 const Extension* extension, | 1592 const Extension* extension, |
| 1804 const PermissionSet* permissions, | 1593 const PermissionSet* permissions, |
| 1805 Reason reason) | 1594 Reason reason) |
| 1806 : reason(reason), | 1595 : reason(reason), |
| 1807 extension(extension), | 1596 extension(extension), |
| 1808 permissions(permissions) {} | 1597 permissions(permissions) {} |
| 1809 | 1598 |
| 1810 } // namespace extensions | 1599 } // namespace extensions |
| OLD | NEW |