DeveloperPrivate API cleanup: remove now-unnecessary IsExtensionSupervised call
[chromium-blink-merge.git] / chrome / browser / extensions / api / developer_private / developer_private_api.cc
blob946ec7af46d5458e8bdfe2234b64aa3173bfe940
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
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/developer_private/developer_private_api.h"
7 #include "base/base64.h"
8 #include "base/bind.h"
9 #include "base/files/file_util.h"
10 #include "base/lazy_instance.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/devtools/devtools_window.h"
15 #include "chrome/browser/extensions/api/developer_private/developer_private_mangle.h"
16 #include "chrome/browser/extensions/api/developer_private/entry_picker.h"
17 #include "chrome/browser/extensions/api/developer_private/extension_info_generator.h"
18 #include "chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.h"
19 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
20 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
21 #include "chrome/browser/extensions/devtools_util.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/extensions/extension_ui_util.h"
24 #include "chrome/browser/extensions/extension_util.h"
25 #include "chrome/browser/extensions/shared_module_service.h"
26 #include "chrome/browser/extensions/unpacked_installer.h"
27 #include "chrome/browser/extensions/updater/extension_updater.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/ui/browser_finder.h"
30 #include "chrome/browser/ui/tabs/tab_strip_model.h"
31 #include "chrome/common/extensions/api/developer_private.h"
32 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
33 #include "chrome/common/url_constants.h"
34 #include "chrome/grit/generated_resources.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/render_process_host.h"
38 #include "content/public/browser/render_view_host.h"
39 #include "content/public/browser/site_instance.h"
40 #include "content/public/browser/storage_partition.h"
41 #include "content/public/browser/web_contents.h"
42 #include "extensions/browser/app_window/app_window.h"
43 #include "extensions/browser/app_window/app_window_registry.h"
44 #include "extensions/browser/error_map.h"
45 #include "extensions/browser/extension_error.h"
46 #include "extensions/browser/extension_prefs.h"
47 #include "extensions/browser/extension_registry.h"
48 #include "extensions/browser/extension_system.h"
49 #include "extensions/browser/file_highlighter.h"
50 #include "extensions/browser/management_policy.h"
51 #include "extensions/browser/notification_types.h"
52 #include "extensions/browser/warning_service.h"
53 #include "extensions/common/constants.h"
54 #include "extensions/common/extension_resource.h"
55 #include "extensions/common/extension_set.h"
56 #include "extensions/common/feature_switch.h"
57 #include "extensions/common/install_warning.h"
58 #include "extensions/common/manifest.h"
59 #include "extensions/common/manifest_handlers/icons_handler.h"
60 #include "extensions/common/permissions/permissions_data.h"
61 #include "extensions/grit/extensions_browser_resources.h"
62 #include "storage/browser/fileapi/external_mount_points.h"
63 #include "storage/browser/fileapi/file_system_context.h"
64 #include "storage/browser/fileapi/file_system_operation.h"
65 #include "storage/browser/fileapi/file_system_operation_runner.h"
66 #include "storage/browser/fileapi/isolated_context.h"
67 #include "ui/base/l10n/l10n_util.h"
68 #include "ui/base/resource/resource_bundle.h"
70 namespace extensions {
72 namespace developer_private = api::developer_private;
74 namespace {
76 const char kNoSuchExtensionError[] = "No such extension.";
77 const char kCannotModifyPolicyExtensionError[] =
78 "Cannot modify the extension by policy.";
79 const char kRequiresUserGestureError[] =
80 "This action requires a user gesture.";
81 const char kCouldNotShowSelectFileDialogError[] =
82 "Could not show a file chooser.";
83 const char kFileSelectionCanceled[] =
84 "File selection was canceled.";
85 const char kNoSuchRendererError[] = "No such renderer.";
86 const char kInvalidPathError[] = "Invalid path.";
87 const char kManifestKeyIsRequiredError[] =
88 "The 'manifestKey' argument is required for manifest files.";
89 const char kCouldNotFindWebContentsError[] =
90 "Could not find a valid web contents.";
92 const char kUnpackedAppsFolder[] = "apps_target";
93 const char kManifestFile[] = "manifest.json";
95 ExtensionService* GetExtensionService(content::BrowserContext* context) {
96 return ExtensionSystem::Get(context)->extension_service();
99 ExtensionUpdater* GetExtensionUpdater(Profile* profile) {
100 return GetExtensionService(profile)->updater();
103 GURL GetImageURLFromData(const std::string& contents) {
104 std::string contents_base64;
105 base::Base64Encode(contents, &contents_base64);
107 // TODO(dvh): make use of content::kDataScheme. Filed as crbug/297301.
108 const char kDataURLPrefix[] = "data:;base64,";
109 return GURL(kDataURLPrefix + contents_base64);
112 GURL GetDefaultImageURL(developer_private::ItemType type) {
113 int icon_resource_id;
114 switch (type) {
115 case developer::ITEM_TYPE_LEGACY_PACKAGED_APP:
116 case developer::ITEM_TYPE_HOSTED_APP:
117 case developer::ITEM_TYPE_PACKAGED_APP:
118 icon_resource_id = IDR_APP_DEFAULT_ICON;
119 break;
120 default:
121 icon_resource_id = IDR_EXTENSION_DEFAULT_ICON;
122 break;
125 return GetImageURLFromData(
126 ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
127 icon_resource_id, ui::SCALE_FACTOR_100P).as_string());
130 // TODO(dvh): This code should be refactored and moved to
131 // extensions::ImageLoader. Also a resize should be performed to avoid
132 // potential huge URLs: crbug/297298.
133 GURL ToDataURL(const base::FilePath& path, developer_private::ItemType type) {
134 std::string contents;
135 if (path.empty() || !base::ReadFileToString(path, &contents))
136 return GetDefaultImageURL(type);
138 return GetImageURLFromData(contents);
141 std::string GetExtensionID(const content::RenderViewHost* render_view_host) {
142 if (!render_view_host->GetSiteInstance())
143 return std::string();
145 return render_view_host->GetSiteInstance()->GetSiteURL().host();
148 void BroadcastItemStateChanged(content::BrowserContext* browser_context,
149 developer::EventType event_type,
150 const std::string& item_id) {
151 developer::EventData event_data;
152 event_data.event_type = event_type;
153 event_data.item_id = item_id;
155 scoped_ptr<base::ListValue> args(new base::ListValue());
156 args->Append(event_data.ToValue().release());
157 scoped_ptr<Event> event(new Event(
158 developer_private::OnItemStateChanged::kEventName, args.Pass()));
159 EventRouter::Get(browser_context)->BroadcastEvent(event.Pass());
162 std::string ReadFileToString(const base::FilePath& path) {
163 std::string data;
164 ignore_result(base::ReadFileToString(path, &data));
165 return data;
168 bool UserCanModifyExtensionConfiguration(
169 const Extension* extension,
170 content::BrowserContext* browser_context,
171 std::string* error) {
172 ManagementPolicy* management_policy =
173 ExtensionSystem::Get(browser_context)->management_policy();
174 if (!management_policy->UserMayModifySettings(extension, nullptr)) {
175 LOG(ERROR) << "Attempt to change settings of an extension that is "
176 << "non-usermanagable was made. Extension id : "
177 << extension->id();
178 *error = kCannotModifyPolicyExtensionError;
179 return false;
182 return true;
185 } // namespace
187 namespace ChoosePath = api::developer_private::ChoosePath;
188 namespace GetItemsInfo = api::developer_private::GetItemsInfo;
189 namespace PackDirectory = api::developer_private::PackDirectory;
190 namespace Reload = api::developer_private::Reload;
192 static base::LazyInstance<BrowserContextKeyedAPIFactory<DeveloperPrivateAPI> >
193 g_factory = LAZY_INSTANCE_INITIALIZER;
195 // static
196 BrowserContextKeyedAPIFactory<DeveloperPrivateAPI>*
197 DeveloperPrivateAPI::GetFactoryInstance() {
198 return g_factory.Pointer();
201 // static
202 DeveloperPrivateAPI* DeveloperPrivateAPI::Get(
203 content::BrowserContext* context) {
204 return GetFactoryInstance()->Get(context);
207 DeveloperPrivateAPI::DeveloperPrivateAPI(content::BrowserContext* context)
208 : profile_(Profile::FromBrowserContext(context)) {
209 RegisterNotifications();
212 DeveloperPrivateEventRouter::DeveloperPrivateEventRouter(Profile* profile)
213 : extension_registry_observer_(this), profile_(profile) {
214 registrar_.Add(this,
215 extensions::NOTIFICATION_EXTENSION_VIEW_REGISTERED,
216 content::Source<Profile>(profile_));
217 registrar_.Add(this,
218 extensions::NOTIFICATION_EXTENSION_VIEW_UNREGISTERED,
219 content::Source<Profile>(profile_));
221 // TODO(limasdf): Use scoped_observer instead.
222 ErrorConsole::Get(profile)->AddObserver(this);
224 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
227 DeveloperPrivateEventRouter::~DeveloperPrivateEventRouter() {
228 ErrorConsole::Get(profile_)->RemoveObserver(this);
231 void DeveloperPrivateEventRouter::AddExtensionId(
232 const std::string& extension_id) {
233 extension_ids_.insert(extension_id);
236 void DeveloperPrivateEventRouter::RemoveExtensionId(
237 const std::string& extension_id) {
238 extension_ids_.erase(extension_id);
241 void DeveloperPrivateEventRouter::Observe(
242 int type,
243 const content::NotificationSource& source,
244 const content::NotificationDetails& details) {
245 Profile* profile = content::Source<Profile>(source).ptr();
246 CHECK(profile);
247 CHECK(profile_->IsSameProfile(profile));
248 developer::EventData event_data;
250 switch (type) {
251 case extensions::NOTIFICATION_EXTENSION_VIEW_UNREGISTERED: {
252 event_data.event_type = developer::EVENT_TYPE_VIEW_UNREGISTERED;
253 event_data.item_id = GetExtensionID(
254 content::Details<const content::RenderViewHost>(details).ptr());
255 break;
257 case extensions::NOTIFICATION_EXTENSION_VIEW_REGISTERED: {
258 event_data.event_type = developer::EVENT_TYPE_VIEW_REGISTERED;
259 event_data.item_id = GetExtensionID(
260 content::Details<const content::RenderViewHost>(details).ptr());
261 break;
263 default:
264 NOTREACHED();
265 return;
268 BroadcastItemStateChanged(profile, event_data.event_type, event_data.item_id);
271 void DeveloperPrivateEventRouter::OnExtensionLoaded(
272 content::BrowserContext* browser_context,
273 const Extension* extension) {
274 DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context)));
275 BroadcastItemStateChanged(
276 browser_context, developer::EVENT_TYPE_LOADED, extension->id());
279 void DeveloperPrivateEventRouter::OnExtensionUnloaded(
280 content::BrowserContext* browser_context,
281 const Extension* extension,
282 UnloadedExtensionInfo::Reason reason) {
283 DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context)));
284 BroadcastItemStateChanged(
285 browser_context, developer::EVENT_TYPE_UNLOADED, extension->id());
288 void DeveloperPrivateEventRouter::OnExtensionWillBeInstalled(
289 content::BrowserContext* browser_context,
290 const Extension* extension,
291 bool is_update,
292 bool from_ephemeral,
293 const std::string& old_name) {
294 DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context)));
295 BroadcastItemStateChanged(
296 browser_context, developer::EVENT_TYPE_INSTALLED, extension->id());
299 void DeveloperPrivateEventRouter::OnExtensionUninstalled(
300 content::BrowserContext* browser_context,
301 const Extension* extension,
302 extensions::UninstallReason reason) {
303 DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context)));
304 BroadcastItemStateChanged(
305 browser_context, developer::EVENT_TYPE_UNINSTALLED, extension->id());
308 void DeveloperPrivateEventRouter::OnErrorAdded(const ExtensionError* error) {
309 // We don't want to handle errors thrown by extensions subscribed to these
310 // events (currently only the Apps Developer Tool), because doing so risks
311 // entering a loop.
312 if (extension_ids_.find(error->extension_id()) != extension_ids_.end())
313 return;
315 BroadcastItemStateChanged(
316 profile_, developer::EVENT_TYPE_ERROR_ADDED, error->extension_id());
319 void DeveloperPrivateAPI::SetLastUnpackedDirectory(const base::FilePath& path) {
320 last_unpacked_directory_ = path;
323 void DeveloperPrivateAPI::RegisterNotifications() {
324 EventRouter::Get(profile_)->RegisterObserver(
325 this, developer_private::OnItemStateChanged::kEventName);
328 DeveloperPrivateAPI::~DeveloperPrivateAPI() {}
330 void DeveloperPrivateAPI::Shutdown() {}
332 void DeveloperPrivateAPI::OnListenerAdded(
333 const EventListenerInfo& details) {
334 if (!developer_private_event_router_) {
335 developer_private_event_router_.reset(
336 new DeveloperPrivateEventRouter(profile_));
339 developer_private_event_router_->AddExtensionId(details.extension_id);
342 void DeveloperPrivateAPI::OnListenerRemoved(
343 const EventListenerInfo& details) {
344 if (!EventRouter::Get(profile_)->HasEventListener(
345 developer_private::OnItemStateChanged::kEventName)) {
346 developer_private_event_router_.reset(NULL);
347 } else {
348 developer_private_event_router_->RemoveExtensionId(details.extension_id);
352 namespace api {
354 DeveloperPrivateAPIFunction::~DeveloperPrivateAPIFunction() {
357 const Extension* DeveloperPrivateAPIFunction::GetExtensionById(
358 const std::string& id) {
359 return ExtensionRegistry::Get(browser_context())->GetExtensionById(
360 id, ExtensionRegistry::EVERYTHING);
363 bool DeveloperPrivateAutoUpdateFunction::RunSync() {
364 ExtensionUpdater* updater = GetExtensionUpdater(GetProfile());
365 if (updater)
366 updater->CheckNow(ExtensionUpdater::CheckParams());
367 SetResult(new base::FundamentalValue(true));
368 return true;
371 DeveloperPrivateAutoUpdateFunction::~DeveloperPrivateAutoUpdateFunction() {}
373 DeveloperPrivateGetExtensionsInfoFunction::
374 ~DeveloperPrivateGetExtensionsInfoFunction() {
377 ExtensionFunction::ResponseAction
378 DeveloperPrivateGetExtensionsInfoFunction::Run() {
379 scoped_ptr<developer::GetExtensionsInfo::Params> params(
380 developer::GetExtensionsInfo::Params::Create(*args_));
381 EXTENSION_FUNCTION_VALIDATE(params);
383 bool include_disabled = true;
384 bool include_terminated = true;
385 if (params->options) {
386 if (params->options->include_disabled)
387 include_disabled = *params->options->include_disabled;
388 if (params->options->include_terminated)
389 include_terminated = *params->options->include_terminated;
392 std::vector<linked_ptr<developer::ExtensionInfo>> list =
393 ExtensionInfoGenerator(browser_context()).
394 CreateExtensionsInfo(include_disabled, include_terminated);
396 return RespondNow(ArgumentList(
397 developer::GetExtensionsInfo::Results::Create(list)));
400 DeveloperPrivateGetExtensionInfoFunction::
401 ~DeveloperPrivateGetExtensionInfoFunction() {
404 ExtensionFunction::ResponseAction
405 DeveloperPrivateGetExtensionInfoFunction::Run() {
406 scoped_ptr<developer::GetExtensionInfo::Params> params(
407 developer::GetExtensionInfo::Params::Create(*args_));
408 EXTENSION_FUNCTION_VALIDATE(params);
410 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
411 developer::ExtensionState state = developer::EXTENSION_STATE_ENABLED;
412 const Extension* extension =
413 registry->enabled_extensions().GetByID(params->id);
414 if (!extension &&
415 (extension = registry->disabled_extensions().GetByID(params->id)) !=
416 nullptr) {
417 state = developer::EXTENSION_STATE_DISABLED;
418 } else if (!extension &&
419 (extension =
420 registry->terminated_extensions().GetByID(params->id)) !=
421 nullptr) {
422 state = developer::EXTENSION_STATE_TERMINATED;
425 if (!extension)
426 return RespondNow(Error(kNoSuchExtensionError));
428 return RespondNow(OneArgument(ExtensionInfoGenerator(browser_context()).
429 CreateExtensionInfo(*extension, state)->ToValue().release()));
432 DeveloperPrivateGetItemsInfoFunction::DeveloperPrivateGetItemsInfoFunction() {}
433 DeveloperPrivateGetItemsInfoFunction::~DeveloperPrivateGetItemsInfoFunction() {}
435 ExtensionFunction::ResponseAction DeveloperPrivateGetItemsInfoFunction::Run() {
436 scoped_ptr<developer::GetItemsInfo::Params> params(
437 developer::GetItemsInfo::Params::Create(*args_));
438 EXTENSION_FUNCTION_VALIDATE(params);
440 ExtensionSet items;
441 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
442 items.InsertAll(registry->enabled_extensions());
444 if (params->include_disabled)
445 items.InsertAll(registry->disabled_extensions());
446 if (params->include_terminated)
447 items.InsertAll(registry->terminated_extensions());
449 std::map<std::string, ExtensionResource> resource_map;
450 for (const scoped_refptr<const Extension>& item : items) {
451 // Don't show component extensions and invisible apps.
452 if (ui_util::ShouldDisplayInExtensionSettings(item.get(),
453 browser_context())) {
454 resource_map[item->id()] =
455 IconsInfo::GetIconResource(item.get(),
456 extension_misc::EXTENSION_ICON_MEDIUM,
457 ExtensionIconSet::MATCH_BIGGER);
461 std::vector<linked_ptr<developer::ExtensionInfo>> list =
462 ExtensionInfoGenerator(browser_context()).
463 CreateExtensionsInfo(params->include_disabled,
464 params->include_terminated);
466 for (const linked_ptr<developer::ExtensionInfo>& info : list)
467 item_list_.push_back(developer_private_mangle::MangleExtensionInfo(*info));
469 content::BrowserThread::PostTask(
470 content::BrowserThread::FILE,
471 FROM_HERE,
472 base::Bind(&DeveloperPrivateGetItemsInfoFunction::GetIconsOnFileThread,
473 this,
474 resource_map));
476 return RespondLater();
479 void DeveloperPrivateGetItemsInfoFunction::GetIconsOnFileThread(
480 const std::map<std::string, ExtensionResource> resource_map) {
481 for (const linked_ptr<developer::ItemInfo>& item : item_list_) {
482 auto resource = resource_map.find(item->id);
483 if (resource != resource_map.end()) {
484 item->icon_url = ToDataURL(resource->second.GetFilePath(),
485 item->type).spec();
489 content::BrowserThread::PostTask(
490 content::BrowserThread::UI,
491 FROM_HERE,
492 base::Bind(&DeveloperPrivateGetItemsInfoFunction::Finish, this));
495 void DeveloperPrivateGetItemsInfoFunction::Finish() {
496 Respond(ArgumentList(developer::GetItemsInfo::Results::Create(item_list_)));
499 DeveloperPrivateUpdateExtensionConfigurationFunction::
500 ~DeveloperPrivateUpdateExtensionConfigurationFunction() {}
502 ExtensionFunction::ResponseAction
503 DeveloperPrivateUpdateExtensionConfigurationFunction::Run() {
504 scoped_ptr<developer::UpdateExtensionConfiguration::Params> params(
505 developer::UpdateExtensionConfiguration::Params::Create(*args_));
506 EXTENSION_FUNCTION_VALIDATE(params);
508 const developer::ExtensionConfigurationUpdate& update = params->update;
510 const Extension* extension = GetExtensionById(update.extension_id);
511 if (!extension)
512 return RespondNow(Error(kNoSuchExtensionError));
513 if (!user_gesture())
514 return RespondNow(Error(kRequiresUserGestureError));
516 if (update.file_access) {
517 std::string error;
518 if (!UserCanModifyExtensionConfiguration(extension,
519 browser_context(),
520 &error)) {
521 return RespondNow(Error(error));
523 util::SetAllowFileAccess(
524 extension->id(), browser_context(), *update.file_access);
526 if (update.incognito_access) {
527 util::SetIsIncognitoEnabled(
528 extension->id(), browser_context(), *update.incognito_access);
530 if (update.error_collection) {
531 ErrorConsole::Get(browser_context())->SetReportingAllForExtension(
532 extension->id(), *update.error_collection);
534 if (update.run_on_all_urls) {
535 util::SetAllowedScriptingOnAllUrls(
536 extension->id(), browser_context(), *update.run_on_all_urls);
538 if (update.show_action_button) {
539 ExtensionActionAPI::SetBrowserActionVisibility(
540 ExtensionPrefs::Get(browser_context()),
541 extension->id(),
542 *update.show_action_button);
545 return RespondNow(NoArguments());
548 DeveloperPrivateReloadFunction::~DeveloperPrivateReloadFunction() {}
550 ExtensionFunction::ResponseAction DeveloperPrivateReloadFunction::Run() {
551 scoped_ptr<Reload::Params> params(Reload::Params::Create(*args_));
552 EXTENSION_FUNCTION_VALIDATE(params.get());
554 const Extension* extension = GetExtensionById(params->extension_id);
555 if (!extension)
556 return RespondNow(Error(kNoSuchExtensionError));
558 bool fail_quietly = params->options &&
559 params->options->fail_quietly &&
560 *params->options->fail_quietly;
562 ExtensionService* service = GetExtensionService(browser_context());
563 if (fail_quietly)
564 service->ReloadExtensionWithQuietFailure(params->extension_id);
565 else
566 service->ReloadExtension(params->extension_id);
568 // TODO(devlin): We shouldn't return until the extension has finished trying
569 // to reload (and then we could also return the error).
570 return RespondNow(NoArguments());
573 DeveloperPrivateShowPermissionsDialogFunction::
574 DeveloperPrivateShowPermissionsDialogFunction() {}
576 DeveloperPrivateShowPermissionsDialogFunction::
577 ~DeveloperPrivateShowPermissionsDialogFunction() {}
579 ExtensionFunction::ResponseAction
580 DeveloperPrivateShowPermissionsDialogFunction::Run() {
581 scoped_ptr<developer::ShowPermissionsDialog::Params> params(
582 developer::ShowPermissionsDialog::Params::Create(*args_));
583 EXTENSION_FUNCTION_VALIDATE(params);
585 const Extension* target_extension = GetExtensionById(params->extension_id);
586 if (!target_extension)
587 return RespondNow(Error(kNoSuchExtensionError));
589 content::WebContents* web_contents = GetSenderWebContents();
590 if (!web_contents)
591 return RespondNow(Error(kCouldNotFindWebContentsError));
593 ShowPermissionsDialogHelper::Show(
594 browser_context(),
595 web_contents,
596 target_extension,
597 source_context_type() == Feature::WEBUI_CONTEXT,
598 base::Bind(&DeveloperPrivateShowPermissionsDialogFunction::Finish, this));
599 return RespondLater();
602 void DeveloperPrivateShowPermissionsDialogFunction::Finish() {
603 Respond(NoArguments());
606 DeveloperPrivateLoadUnpackedFunction::DeveloperPrivateLoadUnpackedFunction()
607 : fail_quietly_(false) {
610 ExtensionFunction::ResponseAction DeveloperPrivateLoadUnpackedFunction::Run() {
611 scoped_ptr<developer_private::LoadUnpacked::Params> params(
612 developer_private::LoadUnpacked::Params::Create(*args_));
613 EXTENSION_FUNCTION_VALIDATE(params);
615 if (!ShowPicker(
616 ui::SelectFileDialog::SELECT_FOLDER,
617 l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY),
618 ui::SelectFileDialog::FileTypeInfo(),
619 0 /* file_type_index */)) {
620 return RespondNow(Error(kCouldNotShowSelectFileDialogError));
623 fail_quietly_ = params->options &&
624 params->options->fail_quietly &&
625 *params->options->fail_quietly;
627 AddRef(); // Balanced in FileSelected / FileSelectionCanceled.
628 return RespondLater();
631 void DeveloperPrivateLoadUnpackedFunction::FileSelected(
632 const base::FilePath& path) {
633 scoped_refptr<UnpackedInstaller> installer(
634 UnpackedInstaller::Create(GetExtensionService(browser_context())));
635 installer->set_be_noisy_on_failure(!fail_quietly_);
636 installer->set_completion_callback(
637 base::Bind(&DeveloperPrivateLoadUnpackedFunction::OnLoadComplete, this));
638 installer->Load(path);
640 DeveloperPrivateAPI::Get(browser_context())->SetLastUnpackedDirectory(path);
642 Release(); // Balanced in Run().
645 void DeveloperPrivateLoadUnpackedFunction::FileSelectionCanceled() {
646 // This isn't really an error, but we should keep it like this for
647 // backward compatability.
648 Respond(Error(kFileSelectionCanceled));
649 Release(); // Balanced in Run().
652 void DeveloperPrivateLoadUnpackedFunction::OnLoadComplete(
653 const Extension* extension,
654 const base::FilePath& file_path,
655 const std::string& error) {
656 Respond(extension ? NoArguments() : Error(error));
659 bool DeveloperPrivateChooseEntryFunction::ShowPicker(
660 ui::SelectFileDialog::Type picker_type,
661 const base::string16& select_title,
662 const ui::SelectFileDialog::FileTypeInfo& info,
663 int file_type_index) {
664 content::WebContents* web_contents = GetSenderWebContents();
665 if (!web_contents)
666 return false;
668 // The entry picker will hold a reference to this function instance,
669 // and subsequent sending of the function response) until the user has
670 // selected a file or cancelled the picker. At that point, the picker will
671 // delete itself.
672 new EntryPicker(this,
673 web_contents,
674 picker_type,
675 DeveloperPrivateAPI::Get(browser_context())->
676 GetLastUnpackedDirectory(),
677 select_title,
678 info,
679 file_type_index);
680 return true;
683 DeveloperPrivateChooseEntryFunction::~DeveloperPrivateChooseEntryFunction() {}
685 void DeveloperPrivatePackDirectoryFunction::OnPackSuccess(
686 const base::FilePath& crx_file,
687 const base::FilePath& pem_file) {
688 developer::PackDirectoryResponse response;
689 response.message = base::UTF16ToUTF8(
690 PackExtensionJob::StandardSuccessMessage(crx_file, pem_file));
691 response.status = developer::PACK_STATUS_SUCCESS;
692 Respond(OneArgument(response.ToValue().release()));
693 Release(); // Balanced in Run().
696 void DeveloperPrivatePackDirectoryFunction::OnPackFailure(
697 const std::string& error,
698 ExtensionCreator::ErrorType error_type) {
699 developer::PackDirectoryResponse response;
700 response.message = error;
701 if (error_type == ExtensionCreator::kCRXExists) {
702 response.item_path = item_path_str_;
703 response.pem_path = key_path_str_;
704 response.override_flags = ExtensionCreator::kOverwriteCRX;
705 response.status = developer::PACK_STATUS_WARNING;
706 } else {
707 response.status = developer::PACK_STATUS_ERROR;
709 Respond(OneArgument(response.ToValue().release()));
710 Release(); // Balanced in Run().
713 ExtensionFunction::ResponseAction DeveloperPrivatePackDirectoryFunction::Run() {
714 scoped_ptr<PackDirectory::Params> params(
715 PackDirectory::Params::Create(*args_));
716 EXTENSION_FUNCTION_VALIDATE(params);
718 int flags = params->flags ? *params->flags : 0;
719 item_path_str_ = params->path;
720 if (params->private_key_path)
721 key_path_str_ = *params->private_key_path;
723 base::FilePath root_directory =
724 base::FilePath::FromUTF8Unsafe(item_path_str_);
725 base::FilePath key_file = base::FilePath::FromUTF8Unsafe(key_path_str_);
727 developer::PackDirectoryResponse response;
728 if (root_directory.empty()) {
729 if (item_path_str_.empty())
730 response.message = l10n_util::GetStringUTF8(
731 IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED);
732 else
733 response.message = l10n_util::GetStringUTF8(
734 IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID);
736 response.status = developer::PACK_STATUS_ERROR;
737 return RespondNow(OneArgument(response.ToValue().release()));
740 if (!key_path_str_.empty() && key_file.empty()) {
741 response.message = l10n_util::GetStringUTF8(
742 IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID);
743 response.status = developer::PACK_STATUS_ERROR;
744 return RespondNow(OneArgument(response.ToValue().release()));
747 AddRef(); // Balanced in OnPackSuccess / OnPackFailure.
749 // TODO(devlin): Why is PackExtensionJob ref-counted?
750 pack_job_ = new PackExtensionJob(this, root_directory, key_file, flags);
751 pack_job_->Start();
752 return RespondLater();
755 DeveloperPrivatePackDirectoryFunction::DeveloperPrivatePackDirectoryFunction() {
758 DeveloperPrivatePackDirectoryFunction::
759 ~DeveloperPrivatePackDirectoryFunction() {}
761 DeveloperPrivateLoadUnpackedFunction::~DeveloperPrivateLoadUnpackedFunction() {}
763 bool DeveloperPrivateLoadDirectoryFunction::RunAsync() {
764 // TODO(grv) : add unittests.
765 std::string directory_url_str;
766 std::string filesystem_name;
767 std::string filesystem_path;
769 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
770 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
771 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &directory_url_str));
773 context_ = content::BrowserContext::GetStoragePartition(
774 GetProfile(), render_view_host()->GetSiteInstance())
775 ->GetFileSystemContext();
777 // Directory url is non empty only for syncfilesystem.
778 if (!directory_url_str.empty()) {
779 storage::FileSystemURL directory_url =
780 context_->CrackURL(GURL(directory_url_str));
781 if (!directory_url.is_valid() ||
782 directory_url.type() != storage::kFileSystemTypeSyncable) {
783 SetError("DirectoryEntry of unsupported filesystem.");
784 return false;
786 return LoadByFileSystemAPI(directory_url);
787 } else {
788 // Check if the DirecotryEntry is the instance of chrome filesystem.
789 if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name,
790 filesystem_path,
791 render_view_host_,
792 &project_base_path_,
793 &error_)) {
794 SetError("DirectoryEntry of unsupported filesystem.");
795 return false;
798 // Try to load using the FileSystem API backend, in case the filesystem
799 // points to a non-native local directory.
800 std::string filesystem_id;
801 bool cracked =
802 storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id);
803 CHECK(cracked);
804 base::FilePath virtual_path =
805 storage::IsolatedContext::GetInstance()
806 ->CreateVirtualRootPath(filesystem_id)
807 .Append(base::FilePath::FromUTF8Unsafe(filesystem_path));
808 storage::FileSystemURL directory_url = context_->CreateCrackedFileSystemURL(
809 extensions::Extension::GetBaseURLFromExtensionId(extension_id()),
810 storage::kFileSystemTypeIsolated,
811 virtual_path);
813 if (directory_url.is_valid() &&
814 directory_url.type() != storage::kFileSystemTypeNativeLocal &&
815 directory_url.type() != storage::kFileSystemTypeRestrictedNativeLocal &&
816 directory_url.type() != storage::kFileSystemTypeDragged) {
817 return LoadByFileSystemAPI(directory_url);
820 Load();
823 return true;
826 bool DeveloperPrivateLoadDirectoryFunction::LoadByFileSystemAPI(
827 const storage::FileSystemURL& directory_url) {
828 std::string directory_url_str = directory_url.ToGURL().spec();
830 size_t pos = 0;
831 // Parse the project directory name from the project url. The project url is
832 // expected to have project name as the suffix.
833 if ((pos = directory_url_str.rfind("/")) == std::string::npos) {
834 SetError("Invalid Directory entry.");
835 return false;
838 std::string project_name;
839 project_name = directory_url_str.substr(pos + 1);
840 project_base_url_ = directory_url_str.substr(0, pos + 1);
842 base::FilePath project_path(GetProfile()->GetPath());
843 project_path = project_path.AppendASCII(kUnpackedAppsFolder);
844 project_path = project_path.Append(
845 base::FilePath::FromUTF8Unsafe(project_name));
847 project_base_path_ = project_path;
849 content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
850 base::Bind(&DeveloperPrivateLoadDirectoryFunction::
851 ClearExistingDirectoryContent,
852 this,
853 project_base_path_));
854 return true;
857 void DeveloperPrivateLoadDirectoryFunction::Load() {
858 ExtensionService* service = GetExtensionService(GetProfile());
859 UnpackedInstaller::Create(service)->Load(project_base_path_);
861 // TODO(grv) : The unpacked installer should fire an event when complete
862 // and return the extension_id.
863 SetResult(new base::StringValue("-1"));
864 SendResponse(true);
867 void DeveloperPrivateLoadDirectoryFunction::ClearExistingDirectoryContent(
868 const base::FilePath& project_path) {
870 // Clear the project directory before copying new files.
871 base::DeleteFile(project_path, true /*recursive*/);
873 pending_copy_operations_count_ = 1;
875 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
876 base::Bind(&DeveloperPrivateLoadDirectoryFunction::
877 ReadDirectoryByFileSystemAPI,
878 this, project_path, project_path.BaseName()));
881 void DeveloperPrivateLoadDirectoryFunction::ReadDirectoryByFileSystemAPI(
882 const base::FilePath& project_path,
883 const base::FilePath& destination_path) {
884 GURL project_url = GURL(project_base_url_ + destination_path.AsUTF8Unsafe());
885 storage::FileSystemURL url = context_->CrackURL(project_url);
887 context_->operation_runner()->ReadDirectory(
888 url, base::Bind(&DeveloperPrivateLoadDirectoryFunction::
889 ReadDirectoryByFileSystemAPICb,
890 this, project_path, destination_path));
893 void DeveloperPrivateLoadDirectoryFunction::ReadDirectoryByFileSystemAPICb(
894 const base::FilePath& project_path,
895 const base::FilePath& destination_path,
896 base::File::Error status,
897 const storage::FileSystemOperation::FileEntryList& file_list,
898 bool has_more) {
899 if (status != base::File::FILE_OK) {
900 DLOG(ERROR) << "Error in copying files from sync filesystem.";
901 return;
904 // We add 1 to the pending copy operations for both files and directories. We
905 // release the directory copy operation once all the files under the directory
906 // are added for copying. We do that to ensure that pendingCopyOperationsCount
907 // does not become zero before all copy operations are finished.
908 // In case the directory happens to be executing the last copy operation it
909 // will call SendResponse to send the response to the API. The pending copy
910 // operations of files are released by the CopyFile function.
911 pending_copy_operations_count_ += file_list.size();
913 for (size_t i = 0; i < file_list.size(); ++i) {
914 if (file_list[i].is_directory) {
915 ReadDirectoryByFileSystemAPI(project_path.Append(file_list[i].name),
916 destination_path.Append(file_list[i].name));
917 continue;
920 GURL project_url = GURL(project_base_url_ +
921 destination_path.Append(file_list[i].name).AsUTF8Unsafe());
922 storage::FileSystemURL url = context_->CrackURL(project_url);
924 base::FilePath target_path = project_path;
925 target_path = target_path.Append(file_list[i].name);
927 context_->operation_runner()->CreateSnapshotFile(
928 url,
929 base::Bind(&DeveloperPrivateLoadDirectoryFunction::SnapshotFileCallback,
930 this,
931 target_path));
934 if (!has_more) {
935 // Directory copy operation released here.
936 pending_copy_operations_count_--;
938 if (!pending_copy_operations_count_) {
939 content::BrowserThread::PostTask(
940 content::BrowserThread::UI, FROM_HERE,
941 base::Bind(&DeveloperPrivateLoadDirectoryFunction::SendResponse,
942 this,
943 success_));
948 void DeveloperPrivateLoadDirectoryFunction::SnapshotFileCallback(
949 const base::FilePath& target_path,
950 base::File::Error result,
951 const base::File::Info& file_info,
952 const base::FilePath& src_path,
953 const scoped_refptr<storage::ShareableFileReference>& file_ref) {
954 if (result != base::File::FILE_OK) {
955 SetError("Error in copying files from sync filesystem.");
956 success_ = false;
957 return;
960 content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
961 base::Bind(&DeveloperPrivateLoadDirectoryFunction::CopyFile,
962 this,
963 src_path,
964 target_path));
967 void DeveloperPrivateLoadDirectoryFunction::CopyFile(
968 const base::FilePath& src_path,
969 const base::FilePath& target_path) {
970 if (!base::CreateDirectory(target_path.DirName())) {
971 SetError("Error in copying files from sync filesystem.");
972 success_ = false;
975 if (success_)
976 base::CopyFile(src_path, target_path);
978 CHECK(pending_copy_operations_count_ > 0);
979 pending_copy_operations_count_--;
981 if (!pending_copy_operations_count_) {
982 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
983 base::Bind(&DeveloperPrivateLoadDirectoryFunction::Load,
984 this));
988 DeveloperPrivateLoadDirectoryFunction::DeveloperPrivateLoadDirectoryFunction()
989 : pending_copy_operations_count_(0), success_(true) {}
991 DeveloperPrivateLoadDirectoryFunction::~DeveloperPrivateLoadDirectoryFunction()
994 ExtensionFunction::ResponseAction DeveloperPrivateChoosePathFunction::Run() {
995 scoped_ptr<developer::ChoosePath::Params> params(
996 developer::ChoosePath::Params::Create(*args_));
997 EXTENSION_FUNCTION_VALIDATE(params);
999 ui::SelectFileDialog::Type type = ui::SelectFileDialog::SELECT_FOLDER;
1000 ui::SelectFileDialog::FileTypeInfo info;
1002 if (params->select_type == developer::SELECT_TYPE_FILE)
1003 type = ui::SelectFileDialog::SELECT_OPEN_FILE;
1004 base::string16 select_title;
1006 int file_type_index = 0;
1007 if (params->file_type == developer::FILE_TYPE_LOAD) {
1008 select_title = l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY);
1009 } else if (params->file_type == developer::FILE_TYPE_PEM) {
1010 select_title = l10n_util::GetStringUTF16(
1011 IDS_EXTENSION_PACK_DIALOG_SELECT_KEY);
1012 info.extensions.push_back(std::vector<base::FilePath::StringType>(
1013 1, FILE_PATH_LITERAL("pem")));
1014 info.extension_description_overrides.push_back(
1015 l10n_util::GetStringUTF16(
1016 IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION));
1017 info.include_all_files = true;
1018 file_type_index = 1;
1019 } else {
1020 NOTREACHED();
1023 if (!ShowPicker(
1024 type,
1025 select_title,
1026 info,
1027 file_type_index)) {
1028 return RespondNow(Error(kCouldNotShowSelectFileDialogError));
1031 AddRef(); // Balanced by FileSelected / FileSelectionCanceled.
1032 return RespondLater();
1035 void DeveloperPrivateChoosePathFunction::FileSelected(
1036 const base::FilePath& path) {
1037 Respond(OneArgument(new base::StringValue(path.LossyDisplayName())));
1038 Release();
1041 void DeveloperPrivateChoosePathFunction::FileSelectionCanceled() {
1042 // This isn't really an error, but we should keep it like this for
1043 // backward compatability.
1044 Respond(Error(kFileSelectionCanceled));
1045 Release();
1048 DeveloperPrivateChoosePathFunction::~DeveloperPrivateChoosePathFunction() {}
1050 bool DeveloperPrivateIsProfileManagedFunction::RunSync() {
1051 SetResult(new base::FundamentalValue(GetProfile()->IsSupervised()));
1052 return true;
1055 DeveloperPrivateIsProfileManagedFunction::
1056 ~DeveloperPrivateIsProfileManagedFunction() {
1059 DeveloperPrivateRequestFileSourceFunction::
1060 DeveloperPrivateRequestFileSourceFunction() {}
1062 DeveloperPrivateRequestFileSourceFunction::
1063 ~DeveloperPrivateRequestFileSourceFunction() {}
1065 ExtensionFunction::ResponseAction
1066 DeveloperPrivateRequestFileSourceFunction::Run() {
1067 params_ = developer::RequestFileSource::Params::Create(*args_);
1068 EXTENSION_FUNCTION_VALIDATE(params_);
1070 const developer::RequestFileSourceProperties& properties =
1071 params_->properties;
1072 const Extension* extension = GetExtensionById(properties.extension_id);
1073 if (!extension)
1074 return RespondNow(Error(kNoSuchExtensionError));
1076 // Under no circumstances should we ever need to reference a file outside of
1077 // the extension's directory. If it tries to, abort.
1078 base::FilePath path_suffix =
1079 base::FilePath::FromUTF8Unsafe(properties.path_suffix);
1080 if (path_suffix.empty() || path_suffix.ReferencesParent())
1081 return RespondNow(Error(kInvalidPathError));
1083 if (properties.path_suffix == kManifestFile && !properties.manifest_key)
1084 return RespondNow(Error(kManifestKeyIsRequiredError));
1086 base::PostTaskAndReplyWithResult(
1087 content::BrowserThread::GetBlockingPool(),
1088 FROM_HERE,
1089 base::Bind(&ReadFileToString, extension->path().Append(path_suffix)),
1090 base::Bind(&DeveloperPrivateRequestFileSourceFunction::Finish, this));
1092 return RespondLater();
1095 void DeveloperPrivateRequestFileSourceFunction::Finish(
1096 const std::string& file_contents) {
1097 const developer::RequestFileSourceProperties& properties =
1098 params_->properties;
1099 const Extension* extension = GetExtensionById(properties.extension_id);
1100 if (!extension) {
1101 Respond(Error(kNoSuchExtensionError));
1102 return;
1105 developer::RequestFileSourceResponse response;
1106 base::FilePath path_suffix =
1107 base::FilePath::FromUTF8Unsafe(properties.path_suffix);
1108 base::FilePath path = extension->path().Append(path_suffix);
1109 response.title = base::StringPrintf("%s: %s",
1110 extension->name().c_str(),
1111 path.BaseName().AsUTF8Unsafe().c_str());
1112 response.message = properties.message;
1114 scoped_ptr<FileHighlighter> highlighter;
1115 if (properties.path_suffix == kManifestFile) {
1116 highlighter.reset(new ManifestHighlighter(
1117 file_contents,
1118 *properties.manifest_key,
1119 properties.manifest_specific ?
1120 *properties.manifest_specific : std::string()));
1121 } else {
1122 highlighter.reset(new SourceHighlighter(
1123 file_contents,
1124 properties.line_number ? *properties.line_number : 0));
1127 response.before_highlight = highlighter->GetBeforeFeature();
1128 response.highlight = highlighter->GetFeature();
1129 response.after_highlight = highlighter->GetAfterFeature();
1131 Respond(OneArgument(response.ToValue().release()));
1134 DeveloperPrivateOpenDevToolsFunction::DeveloperPrivateOpenDevToolsFunction() {}
1135 DeveloperPrivateOpenDevToolsFunction::~DeveloperPrivateOpenDevToolsFunction() {}
1137 ExtensionFunction::ResponseAction
1138 DeveloperPrivateOpenDevToolsFunction::Run() {
1139 scoped_ptr<developer::OpenDevTools::Params> params(
1140 developer::OpenDevTools::Params::Create(*args_));
1141 EXTENSION_FUNCTION_VALIDATE(params);
1142 const developer::OpenDevToolsProperties& properties = params->properties;
1144 if (properties.render_process_id == -1) {
1145 // This is a lazy background page.
1146 const Extension* extension = properties.extension_id ?
1147 ExtensionRegistry::Get(browser_context())->enabled_extensions().GetByID(
1148 *properties.extension_id) : nullptr;
1149 if (!extension)
1150 return RespondNow(Error(kNoSuchExtensionError));
1152 Profile* profile = Profile::FromBrowserContext(browser_context());
1153 if (properties.incognito && *properties.incognito)
1154 profile = profile->GetOffTheRecordProfile();
1156 // Wakes up the background page and opens the inspect window.
1157 devtools_util::InspectBackgroundPage(extension, profile);
1158 return RespondNow(NoArguments());
1161 content::RenderViewHost* rvh =
1162 content::RenderViewHost::FromID(properties.render_process_id,
1163 properties.render_view_id);
1165 content::WebContents* web_contents =
1166 rvh ? content::WebContents::FromRenderViewHost(rvh) : nullptr;
1167 // It's possible that the render view was closed since we last updated the
1168 // links. Handle this gracefully.
1169 if (!web_contents)
1170 return RespondNow(Error(kNoSuchRendererError));
1172 // If we include a url, we should inspect it specifically (and not just the
1173 // render view).
1174 if (properties.url) {
1175 // Line/column numbers are reported in display-friendly 1-based numbers,
1176 // but are inspected in zero-based numbers.
1177 // Default to the first line/column.
1178 DevToolsWindow::OpenDevToolsWindow(
1179 web_contents,
1180 DevToolsToggleAction::Reveal(
1181 base::UTF8ToUTF16(*properties.url),
1182 properties.line_number ? *properties.line_number - 1 : 0,
1183 properties.column_number ? *properties.column_number - 1 : 0));
1184 } else {
1185 DevToolsWindow::OpenDevToolsWindow(web_contents);
1188 // Once we open the inspector, we focus on the appropriate tab...
1189 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
1191 // ... but some pages (popups and apps) don't have tabs, and some (background
1192 // pages) don't have an associated browser. For these, the inspector opens in
1193 // a new window, and our work is done.
1194 if (!browser || !browser->is_type_tabbed())
1195 return RespondNow(NoArguments());
1197 TabStripModel* tab_strip = browser->tab_strip_model();
1198 tab_strip->ActivateTabAt(tab_strip->GetIndexOfWebContents(web_contents),
1199 false); // Not through direct user gesture.
1200 return RespondNow(NoArguments());
1203 DeveloperPrivateDeleteExtensionErrorsFunction::
1204 ~DeveloperPrivateDeleteExtensionErrorsFunction() {}
1206 ExtensionFunction::ResponseAction
1207 DeveloperPrivateDeleteExtensionErrorsFunction::Run() {
1208 scoped_ptr<developer::DeleteExtensionErrors::Params> params(
1209 developer::DeleteExtensionErrors::Params::Create(*args_));
1210 EXTENSION_FUNCTION_VALIDATE(params);
1211 const developer::DeleteExtensionErrorsProperties& properties =
1212 params->properties;
1214 ErrorConsole* error_console =
1215 ErrorConsole::Get(Profile::FromBrowserContext(browser_context()));
1216 int type = -1;
1217 if (properties.type != developer::ERROR_TYPE_NONE) {
1218 type = properties.type == developer::ERROR_TYPE_MANIFEST ?
1219 ExtensionError::MANIFEST_ERROR : ExtensionError::RUNTIME_ERROR;
1221 std::set<int> error_ids;
1222 if (properties.error_ids) {
1223 error_ids.insert(properties.error_ids->begin(),
1224 properties.error_ids->end());
1226 error_console->RemoveErrors(ErrorMap::Filter(
1227 properties.extension_id, type, error_ids, false));
1229 return RespondNow(NoArguments());
1232 } // namespace api
1234 } // namespace extensions