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/runtime/runtime_api.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/metrics/histogram.h"
13 #include "base/values.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_warning_service.h"
18 #include "chrome/browser/extensions/updater/extension_updater.h"
19 #include "chrome/browser/omaha_query_params/omaha_query_params.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/browser/ui/browser_finder.h"
23 #include "chrome/browser/ui/browser_navigator.h"
24 #include "chrome/browser/ui/browser_window.h"
25 #include "chrome/common/extensions/api/runtime.h"
26 #include "content/public/browser/child_process_security_policy.h"
27 #include "content/public/browser/notification_service.h"
28 #include "content/public/browser/render_process_host.h"
29 #include "content/public/browser/render_view_host.h"
30 #include "extensions/browser/event_router.h"
31 #include "extensions/browser/extension_host.h"
32 #include "extensions/browser/extension_registry.h"
33 #include "extensions/browser/extension_system.h"
34 #include "extensions/browser/extensions_browser_client.h"
35 #include "extensions/browser/lazy_background_task_queue.h"
36 #include "extensions/browser/process_manager.h"
37 #include "extensions/common/error_utils.h"
38 #include "extensions/common/extension.h"
39 #include "extensions/common/manifest_handlers/background_info.h"
41 #include "webkit/browser/fileapi/isolated_context.h"
43 #if defined(OS_CHROMEOS)
44 #include "chrome/browser/chromeos/login/user_manager.h"
45 #include "chromeos/dbus/dbus_thread_manager.h"
46 #include "chromeos/dbus/power_manager_client.h"
49 using content::BrowserContext
;
51 namespace GetPlatformInfo
= extensions::api::runtime::GetPlatformInfo
;
53 namespace extensions
{
55 namespace runtime
= api::runtime
;
59 const char kNoBackgroundPageError
[] = "You do not have a background page.";
60 const char kPageLoadError
[] = "Background page failed to load.";
61 const char kInstallReason
[] = "reason";
62 const char kInstallReasonChromeUpdate
[] = "chrome_update";
63 const char kInstallReasonUpdate
[] = "update";
64 const char kInstallReasonInstall
[] = "install";
65 const char kInstallPreviousVersion
[] = "previousVersion";
66 const char kInvalidUrlError
[] = "Invalid URL.";
67 const char kUpdatesDisabledError
[] = "Autoupdate is not enabled.";
68 const char kUpdateFound
[] = "update_available";
69 const char kUpdateNotFound
[] = "no_update";
70 const char kUpdateThrottled
[] = "throttled";
72 // A preference key storing the url loaded when an extension is uninstalled.
73 const char kUninstallUrl
[] = "uninstall_url";
75 // The name of the directory to be returned by getPackageDirectoryEntry. This
76 // particular value does not matter to user code, but is chosen for consistency
77 // with the equivalent Pepper API.
78 const char kPackageDirectoryPath
[] = "crxfs";
80 // If an extension reloads itself within this many miliseconds of reloading
81 // itself, the reload is considered suspiciously fast.
82 const int kFastReloadTime
= 10000;
84 // After this many suspiciously fast consecutive reloads, an extension will get
86 const int kFastReloadCount
= 5;
88 void DispatchOnStartupEventImpl(BrowserContext
* browser_context
,
89 const std::string
& extension_id
,
91 ExtensionHost
* host
) {
92 // A NULL host from the LazyBackgroundTaskQueue means the page failed to
94 if (!host
&& !first_call
)
97 // Don't send onStartup events to incognito browser contexts.
98 if (browser_context
->IsOffTheRecord())
101 if (ExtensionsBrowserClient::Get()->IsShuttingDown() ||
102 !ExtensionsBrowserClient::Get()->IsValidContext(browser_context
))
104 ExtensionSystem
* system
= ExtensionSystem::Get(browser_context
);
108 // If this is a persistent background page, we want to wait for it to load
109 // (it might not be ready, since this is startup). But only enqueue once.
110 // If it fails to load the first time, don't bother trying again.
111 const Extension
* extension
=
112 ExtensionRegistry::Get(browser_context
)->enabled_extensions().GetByID(
114 if (extension
&& BackgroundInfo::HasPersistentBackgroundPage(extension
) &&
116 system
->lazy_background_task_queue()->
117 ShouldEnqueueTask(browser_context
, extension
)) {
118 system
->lazy_background_task_queue()->AddPendingTask(
119 browser_context
, extension_id
,
120 base::Bind(&DispatchOnStartupEventImpl
,
121 browser_context
, extension_id
, false));
125 scoped_ptr
<base::ListValue
> event_args(new base::ListValue());
126 scoped_ptr
<Event
> event(new Event(runtime::OnStartup::kEventName
,
128 system
->event_router()->DispatchEventToExtension(extension_id
, event
.Pass());
131 void SetUninstallURL(ExtensionPrefs
* prefs
,
132 const std::string
& extension_id
,
133 const std::string
& url_string
) {
134 prefs
->UpdateExtensionPref(extension_id
,
136 new base::StringValue(url_string
));
139 #if defined(ENABLE_EXTENSIONS)
140 std::string
GetUninstallURL(ExtensionPrefs
* prefs
,
141 const std::string
& extension_id
) {
142 std::string url_string
;
143 prefs
->ReadPrefAsString(extension_id
, kUninstallUrl
, &url_string
);
146 #endif // defined(ENABLE_EXTENSIONS)
150 ///////////////////////////////////////////////////////////////////////////////
152 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<RuntimeAPI
> >
153 g_factory
= LAZY_INSTANCE_INITIALIZER
;
156 BrowserContextKeyedAPIFactory
<RuntimeAPI
>* RuntimeAPI::GetFactoryInstance() {
157 return g_factory
.Pointer();
160 RuntimeAPI::RuntimeAPI(content::BrowserContext
* context
)
161 : browser_context_(context
),
162 dispatch_chrome_updated_event_(false),
163 registered_for_updates_(false) {
164 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY
,
165 content::Source
<BrowserContext
>(context
));
167 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
168 content::Source
<BrowserContext
>(context
));
169 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED
,
170 content::Source
<BrowserContext
>(context
));
171 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
172 content::Source
<BrowserContext
>(context
));
174 // Check if registered events are up-to-date. We can only do this once
175 // per browser context, since it updates internal state when called.
176 dispatch_chrome_updated_event_
=
177 ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_
);
180 RuntimeAPI::~RuntimeAPI() {
181 if (registered_for_updates_
) {
182 ExtensionSystem::Get(browser_context_
)->
183 extension_service()->RemoveUpdateObserver(this);
187 void RuntimeAPI::Observe(int type
,
188 const content::NotificationSource
& source
,
189 const content::NotificationDetails
& details
) {
191 case chrome::NOTIFICATION_EXTENSIONS_READY
: {
195 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
: {
196 const Extension
* extension
=
197 content::Details
<const Extension
>(details
).ptr();
198 OnExtensionLoaded(extension
);
201 case chrome::NOTIFICATION_EXTENSION_INSTALLED
: {
202 const Extension
* extension
=
203 content::Details
<const InstalledExtensionInfo
>(details
)->extension
;
204 OnExtensionInstalled(extension
);
207 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED
: {
208 const Extension
* extension
=
209 content::Details
<const Extension
>(details
).ptr();
210 OnExtensionUninstalled(extension
);
219 void RuntimeAPI::OnExtensionsReady() {
220 // We're done restarting Chrome after an update.
221 dispatch_chrome_updated_event_
= false;
223 registered_for_updates_
= true;
225 ExtensionSystem
* extension_system
= ExtensionSystem::Get(browser_context_
);
226 extension_system
->extension_service()->AddUpdateObserver(this);
228 // RuntimeAPI is redirected in incognito, so |browser_context_| is never
229 // incognito. We don't observe incognito ProcessManagers but that is OK
230 // because we don't send onStartup events to incognito browser contexts.
231 DCHECK(!browser_context_
->IsOffTheRecord());
232 // Some tests use partially constructed Profiles without a process manager.
233 if (extension_system
->process_manager())
234 extension_system
->process_manager()->AddObserver(this);
237 void RuntimeAPI::OnExtensionLoaded(const Extension
* extension
) {
238 if (!dispatch_chrome_updated_event_
)
241 // Dispatch the onInstalled event with reason "chrome_update".
242 base::MessageLoop::current()->PostTask(
244 base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent
,
251 void RuntimeAPI::OnExtensionInstalled(const Extension
* extension
) {
252 // Ephemeral apps are not considered to be installed and do not receive
253 // the onInstalled() event.
254 if (extension
->is_ephemeral())
257 // Get the previous version to check if this is an upgrade.
258 ExtensionService
* service
= ExtensionSystem::Get(
259 browser_context_
)->extension_service();
260 const Extension
* old
= service
->GetExtensionById(extension
->id(), true);
263 old_version
= *old
->version();
265 // Dispatch the onInstalled event.
266 base::MessageLoop::current()->PostTask(
268 base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent
,
276 void RuntimeAPI::OnExtensionUninstalled(const Extension
* extension
) {
277 // Ephemeral apps are not considered to be installed, so the uninstall URL
278 // is not invoked when they are removed.
279 if (extension
->is_ephemeral())
282 Profile
* profile
= Profile::FromBrowserContext(browser_context_
);
283 RuntimeEventRouter::OnExtensionUninstalled(profile
, extension
->id());
286 void RuntimeAPI::Shutdown() {
287 // ExtensionSystem deletes its ProcessManager during the Shutdown() phase, so
288 // the observer must be removed here and not in the RuntimeAPI destructor.
289 ProcessManager
* process_manager
=
290 ExtensionSystem::Get(browser_context_
)->process_manager();
291 // Some tests use partially constructed Profiles without a process manager.
293 process_manager
->RemoveObserver(this);
296 void RuntimeAPI::OnAppUpdateAvailable(const Extension
* extension
) {
297 Profile
* profile
= Profile::FromBrowserContext(browser_context_
);
298 RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
299 profile
, extension
->id(), extension
->manifest()->value());
302 void RuntimeAPI::OnChromeUpdateAvailable() {
303 Profile
* profile
= Profile::FromBrowserContext(browser_context_
);
304 RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(profile
);
307 void RuntimeAPI::OnBackgroundHostStartup(const Extension
* extension
) {
308 RuntimeEventRouter::DispatchOnStartupEvent(browser_context_
, extension
->id());
311 void RuntimeAPI::MaybeReloadExtension(const std::string
& extension_id
) {
312 std::pair
<base::TimeTicks
, int>& reload_info
=
313 last_reload_time_
[extension_id
];
314 base::TimeTicks now
= base::TimeTicks::Now();
315 if (reload_info
.first
.is_null() ||
316 (now
- reload_info
.first
).InMilliseconds() > kFastReloadTime
) {
317 reload_info
.second
= 0;
319 reload_info
.second
++;
321 if (!reload_info
.first
.is_null()) {
322 UMA_HISTOGRAM_LONG_TIMES("Extensions.RuntimeReloadTime",
323 now
- reload_info
.first
);
325 UMA_HISTOGRAM_COUNTS_100("Extensions.RuntimeReloadFastCount",
327 reload_info
.first
= now
;
329 ExtensionService
* service
=
330 ExtensionSystem::Get(browser_context_
)->extension_service();
331 if (reload_info
.second
>= kFastReloadCount
) {
332 // Unloading an extension clears all warnings, so first terminate the
333 // extension, and then add the warning. Since this is called from an
334 // extension function unloading the extension has to be done
335 // asynchronously. Fortunately PostTask guarentees FIFO order so just
337 base::MessageLoop::current()->PostTask(
339 base::Bind(&ExtensionService::TerminateExtension
,
340 service
->AsWeakPtr(),
342 ExtensionWarningSet warnings
;
344 ExtensionWarning::CreateReloadTooFrequentWarning(extension_id
));
345 base::MessageLoop::current()->PostTask(
347 base::Bind(&ExtensionWarningService::NotifyWarningsOnUI
,
351 // We can't call ReloadExtension directly, since when this method finishes
352 // it tries to decrease the reference count for the extension, which fails
353 // if the extension has already been reloaded; so instead we post a task.
354 base::MessageLoop::current()->PostTask(
356 base::Bind(&ExtensionService::ReloadExtension
,
357 service
->AsWeakPtr(),
362 ///////////////////////////////////////////////////////////////////////////////
365 void RuntimeEventRouter::DispatchOnStartupEvent(
366 content::BrowserContext
* context
, const std::string
& extension_id
) {
367 DispatchOnStartupEventImpl(context
, extension_id
, true, NULL
);
371 void RuntimeEventRouter::DispatchOnInstalledEvent(
372 content::BrowserContext
* context
,
373 const std::string
& extension_id
,
374 const Version
& old_version
,
375 bool chrome_updated
) {
376 if (!ExtensionsBrowserClient::Get()->IsValidContext(context
))
378 ExtensionSystem
* system
= ExtensionSystem::Get(context
);
382 scoped_ptr
<base::ListValue
> event_args(new base::ListValue());
383 base::DictionaryValue
* info
= new base::DictionaryValue();
384 event_args
->Append(info
);
385 if (old_version
.IsValid()) {
386 info
->SetString(kInstallReason
, kInstallReasonUpdate
);
387 info
->SetString(kInstallPreviousVersion
, old_version
.GetString());
388 } else if (chrome_updated
) {
389 info
->SetString(kInstallReason
, kInstallReasonChromeUpdate
);
391 info
->SetString(kInstallReason
, kInstallReasonInstall
);
393 DCHECK(system
->event_router());
394 scoped_ptr
<Event
> event(new Event(runtime::OnInstalled::kEventName
,
396 system
->event_router()->DispatchEventWithLazyListener(extension_id
,
401 void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
403 const std::string
& extension_id
,
404 const base::DictionaryValue
* manifest
) {
405 ExtensionSystem
* system
= ExtensionSystem::Get(profile
);
409 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
410 args
->Append(manifest
->DeepCopy());
411 DCHECK(system
->event_router());
412 scoped_ptr
<Event
> event(new Event(runtime::OnUpdateAvailable::kEventName
,
414 system
->event_router()->DispatchEventToExtension(extension_id
, event
.Pass());
418 void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(
420 ExtensionSystem
* system
= ExtensionSystem::Get(profile
);
424 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
425 DCHECK(system
->event_router());
426 scoped_ptr
<Event
> event(new Event(
427 runtime::OnBrowserUpdateAvailable::kEventName
, args
.Pass()));
428 system
->event_router()->BroadcastEvent(event
.Pass());
432 void RuntimeEventRouter::DispatchOnRestartRequiredEvent(
434 const std::string
& app_id
,
435 api::runtime::OnRestartRequired::Reason reason
) {
436 ExtensionSystem
* system
= ExtensionSystem::Get(profile
);
440 scoped_ptr
<Event
> event(
441 new Event(runtime::OnRestartRequired::kEventName
,
442 api::runtime::OnRestartRequired::Create(reason
)));
444 DCHECK(system
->event_router());
445 system
->event_router()->DispatchEventToExtension(app_id
, event
.Pass());
449 void RuntimeEventRouter::OnExtensionUninstalled(
451 const std::string
& extension_id
) {
452 #if defined(ENABLE_EXTENSIONS)
453 GURL
uninstall_url(GetUninstallURL(ExtensionPrefs::Get(profile
),
456 if (uninstall_url
.is_empty())
459 Browser
* browser
= chrome::FindLastActiveWithProfile(profile
,
460 chrome::GetActiveDesktop());
462 browser
= new Browser(Browser::CreateParams(profile
,
463 chrome::GetActiveDesktop()));
465 chrome::NavigateParams
params(browser
, uninstall_url
,
466 content::PAGE_TRANSITION_CLIENT_REDIRECT
);
467 params
.disposition
= NEW_FOREGROUND_TAB
;
468 params
.user_gesture
= false;
469 chrome::Navigate(¶ms
);
470 #endif // defined(ENABLE_EXTENSIONS)
473 bool RuntimeGetBackgroundPageFunction::RunImpl() {
474 ExtensionSystem
* system
= ExtensionSystem::Get(GetProfile());
475 ExtensionHost
* host
= system
->process_manager()->
476 GetBackgroundHostForExtension(extension_id());
477 if (system
->lazy_background_task_queue()->ShouldEnqueueTask(GetProfile(),
479 system
->lazy_background_task_queue()->AddPendingTask(
482 base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded
, this));
486 error_
= kNoBackgroundPageError
;
493 void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost
* host
) {
497 error_
= kPageLoadError
;
502 bool RuntimeSetUninstallURLFunction::RunSync() {
503 std::string url_string
;
504 EXTENSION_FUNCTION_VALIDATE(args_
->GetString(0, &url_string
));
506 GURL
url(url_string
);
507 if (!url
.is_valid()) {
508 error_
= ErrorUtils::FormatErrorMessage(kInvalidUrlError
, url_string
);
513 ExtensionPrefs::Get(GetProfile()), extension_id(), url_string
);
517 bool RuntimeReloadFunction::RunSync() {
518 RuntimeAPI::GetFactoryInstance()->Get(GetProfile())->MaybeReloadExtension(
523 RuntimeRequestUpdateCheckFunction::RuntimeRequestUpdateCheckFunction() {
524 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND
,
525 content::NotificationService::AllSources());
528 bool RuntimeRequestUpdateCheckFunction::RunImpl() {
529 ExtensionSystem
* system
= ExtensionSystem::Get(GetProfile());
530 ExtensionService
* service
= system
->extension_service();
531 ExtensionUpdater
* updater
= service
->updater();
533 error_
= kUpdatesDisabledError
;
538 if (!updater
->CheckExtensionSoon(extension_id(), base::Bind(
539 &RuntimeRequestUpdateCheckFunction::CheckComplete
, this))) {
541 SetResult(new base::StringValue(kUpdateThrottled
));
547 void RuntimeRequestUpdateCheckFunction::CheckComplete() {
553 // Since no UPDATE_FOUND notification was seen, this generally would mean
554 // that no update is found, but a previous update check might have already
555 // queued up an update, so check for that here to make sure we return the
557 ExtensionSystem
* system
= ExtensionSystem::Get(GetProfile());
558 ExtensionService
* service
= system
->extension_service();
559 const Extension
* update
= service
->GetPendingExtensionUpdate(extension_id());
561 ReplyUpdateFound(update
->VersionString());
563 SetResult(new base::StringValue(kUpdateNotFound
));
568 void RuntimeRequestUpdateCheckFunction::Observe(
570 const content::NotificationSource
& source
,
571 const content::NotificationDetails
& details
) {
575 DCHECK(type
== chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND
);
576 typedef const std::pair
<std::string
, Version
> UpdateDetails
;
577 const std::string
& id
= content::Details
<UpdateDetails
>(details
)->first
;
578 const Version
& version
= content::Details
<UpdateDetails
>(details
)->second
;
579 if (id
== extension_id()) {
580 ReplyUpdateFound(version
.GetString());
584 void RuntimeRequestUpdateCheckFunction::ReplyUpdateFound(
585 const std::string
& version
) {
587 results_
.reset(new base::ListValue
);
588 results_
->AppendString(kUpdateFound
);
589 base::DictionaryValue
* details
= new base::DictionaryValue
;
590 results_
->Append(details
);
591 details
->SetString("version", version
);
595 bool RuntimeRestartFunction::RunSync() {
596 #if defined(OS_CHROMEOS)
597 if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) {
598 chromeos::DBusThreadManager::Get()
599 ->GetPowerManagerClient()
604 SetError("Function available only for ChromeOS kiosk mode.");
608 bool RuntimeGetPlatformInfoFunction::RunSync() {
609 GetPlatformInfo::Results::PlatformInfo info
;
611 const char* os
= chrome::OmahaQueryParams::GetOS();
612 if (strcmp(os
, "mac") == 0) {
613 info
.os
= GetPlatformInfo::Results::PlatformInfo::OS_MAC_
;
614 } else if (strcmp(os
, "win") == 0) {
615 info
.os
= GetPlatformInfo::Results::PlatformInfo::OS_WIN_
;
616 } else if (strcmp(os
, "android") == 0) {
617 info
.os
= GetPlatformInfo::Results::PlatformInfo::OS_ANDROID_
;
618 } else if (strcmp(os
, "cros") == 0) {
619 info
.os
= GetPlatformInfo::Results::PlatformInfo::OS_CROS_
;
620 } else if (strcmp(os
, "linux") == 0) {
621 info
.os
= GetPlatformInfo::Results::PlatformInfo::OS_LINUX_
;
622 } else if (strcmp(os
, "openbsd") == 0) {
623 info
.os
= GetPlatformInfo::Results::PlatformInfo::OS_OPENBSD_
;
629 const char* arch
= chrome::OmahaQueryParams::GetArch();
630 if (strcmp(arch
, "arm") == 0) {
631 info
.arch
= GetPlatformInfo::Results::PlatformInfo::ARCH_ARM
;
632 } else if (strcmp(arch
, "x86") == 0) {
633 info
.arch
= GetPlatformInfo::Results::PlatformInfo::ARCH_X86_32
;
634 } else if (strcmp(arch
, "x64") == 0) {
635 info
.arch
= GetPlatformInfo::Results::PlatformInfo::ARCH_X86_64
;
641 const char* nacl_arch
= chrome::OmahaQueryParams::GetNaclArch();
642 if (strcmp(nacl_arch
, "arm") == 0) {
643 info
.nacl_arch
= GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_ARM
;
644 } else if (strcmp(nacl_arch
, "x86-32") == 0) {
645 info
.nacl_arch
= GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_X86_32
;
646 } else if (strcmp(nacl_arch
, "x86-64") == 0) {
647 info
.nacl_arch
= GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_X86_64
;
653 results_
= GetPlatformInfo::Results::Create(info
);
657 bool RuntimeGetPackageDirectoryEntryFunction::RunSync() {
658 fileapi::IsolatedContext
* isolated_context
=
659 fileapi::IsolatedContext::GetInstance();
660 DCHECK(isolated_context
);
662 std::string relative_path
= kPackageDirectoryPath
;
663 base::FilePath path
= extension_
->path();
664 std::string filesystem_id
= isolated_context
->RegisterFileSystemForPath(
665 fileapi::kFileSystemTypeNativeLocal
, path
, &relative_path
);
667 int renderer_id
= render_view_host_
->GetProcess()->GetID();
668 content::ChildProcessSecurityPolicy
* policy
=
669 content::ChildProcessSecurityPolicy::GetInstance();
670 policy
->GrantReadFileSystem(renderer_id
, filesystem_id
);
671 base::DictionaryValue
* dict
= new base::DictionaryValue();
673 dict
->SetString("fileSystemId", filesystem_id
);
674 dict
->SetString("baseName", relative_path
);
678 } // namespace extensions