1 // Copyright 2014 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 "apps/saved_files_service.h"
6 #include "base/files/scoped_temp_dir.h"
7 #include "base/stl_util.h"
8 #include "chrome/browser/apps/app_browsertest_util.h"
9 #include "chrome/browser/apps/ephemeral_app_service.h"
10 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/extensions/extension_test_message_listener.h"
13 #include "chrome/browser/extensions/extension_util.h"
14 #include "chrome/browser/notifications/desktop_notification_service.h"
15 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
16 #include "chrome/common/extensions/api/alarms.h"
17 #include "content/public/test/browser_test.h"
18 #include "content/public/test/test_utils.h"
19 #include "extensions/browser/event_router.h"
20 #include "extensions/browser/extension_prefs.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/browser/process_manager.h"
23 #include "extensions/common/switches.h"
24 #include "ui/message_center/message_center.h"
25 #include "ui/message_center/notifier_settings.h"
27 using extensions::Event
;
28 using extensions::EventRouter
;
29 using extensions::Extension
;
30 using extensions::ExtensionInfo
;
31 using extensions::ExtensionPrefs
;
32 using extensions::ExtensionSystem
;
33 using extensions::Manifest
;
34 using extensions::PlatformAppBrowserTest
;
38 namespace alarms
= extensions::api::alarms
;
40 const char kDispatchEventTestApp
[] = "ephemeral_apps/dispatch_event";
41 const char kMessagingReceiverApp
[] = "ephemeral_apps/messaging_receiver";
42 const char kMessagingReceiverAppV2
[] = "ephemeral_apps/messaging_receiver2";
43 const char kNotificationsTestApp
[] = "ephemeral_apps/notification_settings";
44 const char kFileSystemTestApp
[] = "ephemeral_apps/filesystem_retain_entries";
45 const char kRetainDataApp
[] = "ephemeral_apps/retain_data";
47 typedef std::vector
<message_center::Notifier
*> NotifierList
;
49 bool IsNotifierInList(const message_center::NotifierId
& notifier_id
,
50 const NotifierList
& notifiers
) {
51 for (NotifierList::const_iterator it
= notifiers
.begin();
52 it
!= notifiers
.end(); ++it
) {
53 const message_center::Notifier
* notifier
= *it
;
54 if (notifier
->notifier_id
== notifier_id
)
61 bool IsAppInExtensionsInfo(const ExtensionPrefs::ExtensionsInfo
& ext_info
,
62 const std::string
& extension_id
) {
63 for (size_t i
= 0; i
< ext_info
.size(); ++i
) {
64 ExtensionInfo
* info
= ext_info
.at(i
).get();
65 if (info
->extension_id
== extension_id
)
74 class EphemeralAppBrowserTest
: public PlatformAppBrowserTest
{
76 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
77 // Skip PlatformAppBrowserTest, which sets different values for the switches
79 ExtensionBrowserTest::SetUpCommandLine(command_line
);
81 // Make event pages get suspended immediately.
82 command_line
->AppendSwitchASCII(
83 extensions::switches::kEventPageIdleTime
, "10");
84 command_line
->AppendSwitchASCII(
85 extensions::switches::kEventPageSuspendingTime
, "10");
88 base::FilePath
GetTestPath(const char* test_path
) {
89 return test_data_dir_
.AppendASCII("platform_apps").AppendASCII(test_path
);
92 const Extension
* InstallEphemeralApp(const char* test_path
,
93 Manifest::Location manifest_location
) {
94 const Extension
* extension
=
95 InstallExtensionWithSourceAndFlags(
96 GetTestPath(test_path
),
99 Extension::IS_EPHEMERAL
);
103 const Extension
* InstallEphemeralApp(const char* test_path
) {
104 return InstallEphemeralApp(test_path
, Manifest::INTERNAL
);
107 const Extension
* InstallAndLaunchEphemeralApp(const char* test_path
) {
108 ExtensionTestMessageListener
launched_listener("launched", false);
109 const Extension
* extension
= InstallEphemeralApp(test_path
);
110 EXPECT_TRUE(extension
);
114 LaunchPlatformApp(extension
);
115 bool wait_result
= launched_listener
.WaitUntilSatisfied();
116 EXPECT_TRUE(wait_result
);
123 bool LaunchAppAndRunTest(const Extension
* app
, const char* test_name
) {
124 ExtensionTestMessageListener
launched_listener("launched", true);
125 LaunchPlatformApp(app
);
126 if (!launched_listener
.WaitUntilSatisfied()) {
127 message_
= "Failed to receive launched message from test";
131 launched_listener
.Reply(test_name
);
133 ResultCatcher catcher
;
134 if (!catcher
.GetNextResult()) {
135 message_
= catcher
.message();
143 void CloseApp(const std::string
& app_id
) {
144 content::WindowedNotificationObserver
event_page_destroyed_signal(
145 chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED
,
146 content::Source
<Profile
>(browser()->profile()));
148 EXPECT_EQ(1U, GetAppWindowCountForApp(app_id
));
149 apps::AppWindow
* app_window
= GetFirstAppWindowForApp(app_id
);
150 ASSERT_TRUE(app_window
);
151 CloseAppWindow(app_window
);
153 event_page_destroyed_signal
.Wait();
156 void EvictApp(const std::string
& app_id
) {
157 // Uninstall the app, which is what happens when ephemeral apps get evicted
159 content::WindowedNotificationObserver
uninstalled_signal(
160 chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
161 content::Source
<Profile
>(browser()->profile()));
163 ExtensionService
* service
=
164 ExtensionSystem::Get(browser()->profile())->extension_service();
165 ASSERT_TRUE(service
);
166 service
->UninstallExtension(app_id
, false, NULL
);
168 uninstalled_signal
.Wait();
171 void VerifyAppNotLoaded(const std::string
& app_id
) {
172 EXPECT_FALSE(ExtensionSystem::Get(browser()->profile())->
173 process_manager()->GetBackgroundHostForExtension(app_id
));
176 void DispatchAlarmEvent(EventRouter
* event_router
,
177 const std::string
& app_id
) {
178 alarms::Alarm dummy_alarm
;
179 dummy_alarm
.name
= "test_alarm";
181 scoped_ptr
<base::ListValue
> args(new base::ListValue());
182 args
->Append(dummy_alarm
.ToValue().release());
183 scoped_ptr
<Event
> event(new Event(alarms::OnAlarm::kEventName
,
186 event_router
->DispatchEventToExtension(app_id
, event
.Pass());
189 void GarbageCollectData() {
190 EphemeralAppService
* service
=
191 EphemeralAppService::Get(browser()->profile());
192 ASSERT_TRUE(service
);
193 service
->GarbageCollectData();
197 // Verify that ephemeral apps can be launched and receive system events when
198 // they are running. Once they are inactive they should not receive system
200 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
, EventDispatchWhenLaunched
) {
201 const Extension
* extension
=
202 InstallAndLaunchEphemeralApp(kDispatchEventTestApp
);
203 ASSERT_TRUE(extension
);
205 // Send a fake alarm event to the app and verify that a response is
207 EventRouter
* event_router
= EventRouter::Get(browser()->profile());
208 ASSERT_TRUE(event_router
);
210 ExtensionTestMessageListener
alarm_received_listener("alarm_received", false);
211 DispatchAlarmEvent(event_router
, extension
->id());
212 ASSERT_TRUE(alarm_received_listener
.WaitUntilSatisfied());
214 CloseApp(extension
->id());
216 // The app needs to be launched once in order to have the onAlarm() event
218 ASSERT_TRUE(event_router
->ExtensionHasEventListener(
219 extension
->id(), alarms::OnAlarm::kEventName
));
221 // Dispatch the alarm event again and verify that the event page did not get
222 // loaded for the app.
223 DispatchAlarmEvent(event_router
, extension
->id());
224 VerifyAppNotLoaded(extension
->id());
227 // Verify that ephemeral apps will receive messages while they are running.
228 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
, ReceiveMessagesWhenLaunched
) {
229 const Extension
* receiver
=
230 InstallAndLaunchEphemeralApp(kMessagingReceiverApp
);
231 ASSERT_TRUE(receiver
);
233 // Verify that messages are received while the app is running.
234 ExtensionApiTest::ResultCatcher result_catcher
;
235 LoadAndLaunchPlatformApp("ephemeral_apps/messaging_sender_success");
236 EXPECT_TRUE(result_catcher
.GetNextResult());
238 CloseApp(receiver
->id());
240 // Verify that messages are not received while the app is inactive.
241 LoadAndLaunchPlatformApp("ephemeral_apps/messaging_sender_fail");
242 EXPECT_TRUE(result_catcher
.GetNextResult());
245 // Verify that an updated ephemeral app will still have its ephemeral flag
247 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
, UpdateEphemeralApp
) {
248 const Extension
* app_v1
= InstallEphemeralApp(kMessagingReceiverApp
);
250 ASSERT_TRUE(app_v1
->is_ephemeral());
251 std::string app_id
= app_v1
->id();
252 base::Version app_original_version
= *app_v1
->version();
253 app_v1
= NULL
; // The extension object will be destroyed during update.
255 // Pack version 2 of the app.
256 base::ScopedTempDir temp_dir
;
257 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
259 base::FilePath crx_path
= temp_dir
.path().AppendASCII("temp.crx");
260 if (!base::DeleteFile(crx_path
, false)) {
261 ADD_FAILURE() << "Failed to delete crx: " << crx_path
.value();
265 base::FilePath app_v2_path
= PackExtensionWithOptions(
266 GetTestPath(kMessagingReceiverAppV2
),
268 GetTestPath(kMessagingReceiverApp
).ReplaceExtension(
269 FILE_PATH_LITERAL(".pem")),
271 ASSERT_FALSE(app_v2_path
.empty());
273 // Update the ephemeral app and wait for the update to finish.
274 extensions::CrxInstaller
* crx_installer
= NULL
;
275 content::WindowedNotificationObserver
windowed_observer(
276 chrome::NOTIFICATION_CRX_INSTALLER_DONE
,
277 content::Source
<extensions::CrxInstaller
>(crx_installer
));
278 ExtensionService
* service
=
279 ExtensionSystem::Get(browser()->profile())->extension_service();
280 EXPECT_TRUE(service
->UpdateExtension(app_id
, app_v2_path
, true,
282 windowed_observer
.Wait();
284 const Extension
* app_v2
= service
->GetExtensionById(app_id
, false);
286 EXPECT_TRUE(app_v2
->version()->CompareTo(app_original_version
) > 0);
287 EXPECT_TRUE(app_v2
->is_ephemeral());
290 // Verify that if notifications have been disabled for an ephemeral app, it will
291 // remain disabled even after being evicted from the cache.
292 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
, StickyNotificationSettings
) {
293 const Extension
* app
= InstallEphemeralApp(kNotificationsTestApp
);
296 // Disable notifications for this app.
297 DesktopNotificationService
* notification_service
=
298 DesktopNotificationServiceFactory::GetForProfile(browser()->profile());
299 ASSERT_TRUE(notification_service
);
301 message_center::NotifierId
notifier_id(
302 message_center::NotifierId::APPLICATION
, app
->id());
303 EXPECT_TRUE(notification_service
->IsNotifierEnabled(notifier_id
));
304 notification_service
->SetNotifierEnabled(notifier_id
, false);
305 EXPECT_FALSE(notification_service
->IsNotifierEnabled(notifier_id
));
310 // Reinstall the ephemeral app and verify that notifications remain disabled.
311 app
= InstallEphemeralApp(kNotificationsTestApp
);
313 message_center::NotifierId
reinstalled_notifier_id(
314 message_center::NotifierId::APPLICATION
, app
->id());
315 EXPECT_FALSE(notification_service
->IsNotifierEnabled(
316 reinstalled_notifier_id
));
319 // Verify that only running ephemeral apps will appear in the Notification
320 // Settings UI. Inactive, cached ephemeral apps should not appear.
321 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
,
322 IncludeRunningEphemeralAppsInNotifiers
) {
323 message_center::NotifierSettingsProvider
* settings_provider
=
324 message_center::MessageCenter::Get()->GetNotifierSettingsProvider();
325 // TODO(tmdiep): Remove once notifications settings are supported across
326 // all platforms. This test will fail for Linux GTK.
327 if (!settings_provider
)
330 const Extension
* app
= InstallAndLaunchEphemeralApp(kNotificationsTestApp
);
332 message_center::NotifierId
notifier_id(
333 message_center::NotifierId::APPLICATION
, app
->id());
335 // Since the ephemeral app is running, it should be included in the list
336 // of notifiers to show in the UI.
337 NotifierList notifiers
;
338 STLElementDeleter
<NotifierList
> notifier_deleter(¬ifiers
);
340 settings_provider
->GetNotifierList(¬ifiers
);
341 EXPECT_TRUE(IsNotifierInList(notifier_id
, notifiers
));
342 STLDeleteElements(¬ifiers
);
344 // Close the ephemeral app.
347 // Inactive ephemeral apps should not be included in the list of notifiers to
349 settings_provider
->GetNotifierList(¬ifiers
);
350 EXPECT_FALSE(IsNotifierInList(notifier_id
, notifiers
));
353 // Verify that ephemeral apps will have no ability to retain file entries after
354 // close. Normal retainEntry behavior for installed apps is tested in
355 // FileSystemApiTest.
356 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
,
357 DisableRetainFileSystemEntries
) {
358 // Create a dummy file that we can just return to the test.
359 base::ScopedTempDir temp_dir
;
360 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
361 base::FilePath temp_file
;
362 ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir
.path(), &temp_file
));
364 using extensions::FileSystemChooseEntryFunction
;
365 FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
367 // The temporary file needs to be registered for the tests to pass on
369 FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
370 "temp", temp_dir
.path());
372 // The first test opens the file and writes the file handle to local storage.
373 const Extension
* app
= InstallEphemeralApp(kFileSystemTestApp
,
375 ASSERT_TRUE(LaunchAppAndRunTest(app
, "OpenAndRetainFile")) << message_
;
377 // Verify that after the app has been closed, all retained entries are
379 std::vector
<apps::SavedFileEntry
> file_entries
=
380 apps::SavedFilesService::Get(browser()->profile())
381 ->GetAllFileEntries(app
->id());
382 EXPECT_TRUE(file_entries
.empty());
384 // The second test verifies that the file cannot be reopened.
385 ASSERT_TRUE(LaunchAppAndRunTest(app
, "RestoreRetainedFile")) << message_
;
388 // Verify that once evicted from the cache, the data of ephemeral apps will not
390 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
, RetainData
) {
391 // Phase 1 - Install the ephemeral app and write data to various storage.
392 const Extension
* app
= InstallEphemeralApp(kRetainDataApp
);
394 ASSERT_TRUE(LaunchAppAndRunTest(app
, "WriteData")) << message_
;
396 // Sanity check to ensure that the ReadData tests should pass before the app
398 ASSERT_TRUE(LaunchAppAndRunTest(app
, "ReadData")) << message_
;
401 const std::string app_id
= app
->id();
405 // The app should be in the list of evicted apps.
406 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(browser()->profile());
408 scoped_ptr
<ExtensionPrefs::ExtensionsInfo
> extensions_info(
409 prefs
->GetEvictedEphemeralAppsInfo());
410 EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info
, app_id
));
412 // The app should not be in the list of installed extensions.
413 extensions_info
= prefs
->GetInstalledExtensionsInfo();
414 EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info
, app_id
));
416 // Ensure the evicted app is considered to have isolated storage. This will
417 // prevent its data from getting garbage collected by
418 // ExtensionService::GarbageCollectIsolatedStorage().
419 GURL site_url
= extensions::util::GetSiteForExtensionId(
420 app_id
, browser()->profile());
421 EXPECT_TRUE(extensions::util::SiteHasIsolatedStorage(
422 site_url
, browser()->profile()));
424 // Phase 2 - Reinstall the ephemeral app and verify that data still exists
426 app
= InstallEphemeralApp(kRetainDataApp
);
428 EXPECT_TRUE(LaunchAppAndRunTest(app
, "ReadData")) << message_
;
430 // The app should now be in the list of installed extensions, but not in the
431 // list of evicted apps.
432 extensions_info
= prefs
->GetInstalledExtensionsInfo();
433 EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info
, app_id
));
434 extensions_info
= prefs
->GetEvictedEphemeralAppsInfo();
435 EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info
, app_id
));
438 // Verify that the data of regular installed apps are deleted on uninstall.
439 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
, RemoveInstalledData
) {
440 // Install the ephemeral app and write data to various storage.
441 const Extension
* app
= InstallPlatformApp(kRetainDataApp
);
443 ASSERT_TRUE(LaunchAppAndRunTest(app
, "WriteData")) << message_
;
446 const std::string app_id
= app
->id();
450 // The app should not be in the preferences.
451 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(browser()->profile());
453 scoped_ptr
<ExtensionPrefs::ExtensionsInfo
> extensions_info(
454 prefs
->GetEvictedEphemeralAppsInfo());
455 EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info
, app_id
));
456 extensions_info
= prefs
->GetInstalledExtensionsInfo();
457 EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info
, app_id
));
459 // Reinstall the app and verify that all data has been reset.
460 app
= InstallPlatformApp(kRetainDataApp
);
461 ASSERT_TRUE(LaunchAppAndRunTest(app
, "DataReset")) << message_
;
464 // Verify that once evicted from the cache, ephemeral apps will remain in
465 // extension prefs, but marked as evicted. After garbage collection of data,
466 // both their data and preferences should be removed.
467 IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest
, GarbageCollectData
) {
468 // Create two apps. Both will be evicted from the cache, but the data of
469 // one will be garbage collected.
470 const Extension
* evict_app
= InstallEphemeralApp(kRetainDataApp
);
471 ASSERT_TRUE(evict_app
);
472 ASSERT_TRUE(LaunchAppAndRunTest(evict_app
, "WriteData")) << message_
;
473 std::string evict_app_id
= evict_app
->id();
474 EvictApp(evict_app_id
);
477 const Extension
* retain_app
= InstallEphemeralApp(kDispatchEventTestApp
);
478 ASSERT_TRUE(retain_app
);
479 std::string retain_app_id
= retain_app
->id();
480 EvictApp(retain_app_id
);
483 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(browser()->profile());
486 // Both apps should be in the list of evicted apps.
487 scoped_ptr
<ExtensionPrefs::ExtensionsInfo
> extensions_info(
488 prefs
->GetEvictedEphemeralAppsInfo());
489 EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info
, retain_app_id
));
490 EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info
, evict_app_id
));
492 // Set a fake last launch time so that the ephemeral app's data will be
493 // garbage collected.
494 base::Time launch_time
=
495 base::Time::Now() - base::TimeDelta::FromDays(
496 EphemeralAppService::kDataInactiveThreshold
+ 1);
497 prefs
->SetLastLaunchTime(evict_app_id
, launch_time
);
498 prefs
->SetLastLaunchTime(retain_app_id
, base::Time::Now());
500 // Garbage collect data.
501 GarbageCollectData();
503 // The garbage collected app should no longer be in the preferences.
504 extensions_info
= prefs
->GetEvictedEphemeralAppsInfo();
505 EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info
, retain_app_id
));
506 ASSERT_FALSE(IsAppInExtensionsInfo(*extensions_info
, evict_app_id
));
508 // Reinstall the app and verify that all data has been reset.
509 evict_app
= InstallEphemeralApp(kRetainDataApp
);
510 ASSERT_TRUE(LaunchAppAndRunTest(evict_app
, "DataReset")) << message_
;