forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Mac] Add interactive App Shim test.
This test creates shims in the user data dir and actually starts them up and expects them to connect to the IPC socket. BUG=168080 Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=276368 Review URL: https://codereview.chromium.org/316493002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@277743 0039d316-1c4b-4281-b951-d872f2087c98
- Loading branch information
jackhou@chromium.org
committed
Jun 17, 2014
1 parent
3d24645
commit bf57960
Showing
8 changed files
with
328 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,300 @@ | ||
// Copyright 2014 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#import <Cocoa/Cocoa.h> | ||
#include <vector> | ||
|
||
#include "apps/app_shim/app_shim_handler_mac.h" | ||
#include "apps/app_shim/app_shim_host_manager_mac.h" | ||
#include "apps/app_shim/extension_app_shim_handler_mac.h" | ||
#include "apps/switches.h" | ||
#include "apps/ui/native_app_window.h" | ||
#include "base/auto_reset.h" | ||
#include "base/callback.h" | ||
#include "base/files/file_path_watcher.h" | ||
#include "base/mac/foundation_util.h" | ||
#include "base/mac/launch_services_util.h" | ||
#include "base/mac/scoped_nsobject.h" | ||
#include "base/path_service.h" | ||
#include "base/process/launch.h" | ||
#include "base/strings/sys_string_conversions.h" | ||
#include "base/test/test_timeouts.h" | ||
#include "chrome/browser/apps/app_browsertest_util.h" | ||
#include "chrome/browser/browser_process.h" | ||
#include "chrome/browser/extensions/extension_test_message_listener.h" | ||
#include "chrome/browser/profiles/profile.h" | ||
#include "chrome/browser/web_applications/web_app_mac.h" | ||
#include "chrome/common/chrome_paths.h" | ||
#include "chrome/common/chrome_switches.h" | ||
#include "chrome/common/mac/app_mode_common.h" | ||
#include "content/public/test/test_utils.h" | ||
#include "extensions/browser/extension_registry.h" | ||
#import "ui/events/test/cocoa_test_event_utils.h" | ||
|
||
namespace { | ||
|
||
// General end-to-end test for app shims. | ||
class AppShimInteractiveTest : public extensions::PlatformAppBrowserTest { | ||
protected: | ||
AppShimInteractiveTest() | ||
: auto_reset_(&g_app_shims_allow_update_and_launch_in_tests, true) {} | ||
|
||
private: | ||
// Temporarily enable app shims. | ||
base::AutoReset<bool> auto_reset_; | ||
|
||
DISALLOW_COPY_AND_ASSIGN(AppShimInteractiveTest); | ||
}; | ||
|
||
// Watches for changes to a file. This is designed to be used from the the UI | ||
// thread. | ||
class WindowedFilePathWatcher | ||
: public base::RefCountedThreadSafe<WindowedFilePathWatcher> { | ||
public: | ||
WindowedFilePathWatcher(const base::FilePath& path) : observed_(false) { | ||
content::BrowserThread::PostTask( | ||
content::BrowserThread::FILE, | ||
FROM_HERE, | ||
base::Bind(&WindowedFilePathWatcher::Watch, this, path)); | ||
} | ||
|
||
void Wait() { | ||
if (observed_) | ||
return; | ||
|
||
run_loop_.reset(new base::RunLoop); | ||
run_loop_->Run(); | ||
} | ||
|
||
protected: | ||
friend class base::RefCountedThreadSafe<WindowedFilePathWatcher>; | ||
virtual ~WindowedFilePathWatcher() {} | ||
|
||
void Watch(const base::FilePath& path) { | ||
watcher_.Watch( | ||
path, false, base::Bind(&WindowedFilePathWatcher::Observe, this)); | ||
} | ||
|
||
void Observe(const base::FilePath& path, bool error) { | ||
content::BrowserThread::PostTask( | ||
content::BrowserThread::UI, | ||
FROM_HERE, | ||
base::Bind(&WindowedFilePathWatcher::StopRunLoop, this)); | ||
} | ||
|
||
void StopRunLoop() { | ||
observed_ = true; | ||
if (run_loop_.get()) | ||
run_loop_->Quit(); | ||
} | ||
|
||
private: | ||
base::FilePathWatcher watcher_; | ||
bool observed_; | ||
scoped_ptr<base::RunLoop> run_loop_; | ||
|
||
DISALLOW_COPY_AND_ASSIGN(WindowedFilePathWatcher); | ||
}; | ||
|
||
// Watches for an app shim to connect. | ||
class WindowedAppShimLaunchObserver : public apps::AppShimHandler { | ||
public: | ||
WindowedAppShimLaunchObserver(const std::string& app_id) | ||
: app_mode_id_(app_id), | ||
observed_(false) { | ||
apps::AppShimHandler::RegisterHandler(app_id, this); | ||
} | ||
|
||
void Wait() { | ||
if (observed_) | ||
return; | ||
|
||
run_loop_.reset(new base::RunLoop); | ||
run_loop_->Run(); | ||
} | ||
|
||
// AppShimHandler overrides: | ||
virtual void OnShimLaunch(Host* host, | ||
apps::AppShimLaunchType launch_type, | ||
const std::vector<base::FilePath>& files) OVERRIDE { | ||
// Remove self and pass through to the default handler. | ||
apps::AppShimHandler::RemoveHandler(app_mode_id_); | ||
apps::AppShimHandler::GetForAppMode(app_mode_id_) | ||
->OnShimLaunch(host, launch_type, files); | ||
observed_ = true; | ||
if (run_loop_.get()) | ||
run_loop_->Quit(); | ||
} | ||
virtual void OnShimClose(Host* host) OVERRIDE {} | ||
virtual void OnShimFocus(Host* host, | ||
apps::AppShimFocusType focus_type, | ||
const std::vector<base::FilePath>& files) OVERRIDE {} | ||
virtual void OnShimSetHidden(Host* host, bool hidden) OVERRIDE {} | ||
virtual void OnShimQuit(Host* host) OVERRIDE {} | ||
|
||
private: | ||
std::string app_mode_id_; | ||
bool observed_; | ||
scoped_ptr<base::RunLoop> run_loop_; | ||
|
||
DISALLOW_COPY_AND_ASSIGN(WindowedAppShimLaunchObserver); | ||
}; | ||
|
||
NSString* GetBundleID(const base::FilePath& shim_path) { | ||
base::FilePath plist_path = shim_path.Append("Contents").Append("Info.plist"); | ||
NSMutableDictionary* plist = [NSMutableDictionary | ||
dictionaryWithContentsOfFile:base::mac::FilePathToNSString(plist_path)]; | ||
return [plist objectForKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)]; | ||
} | ||
|
||
bool HasAppShimHost(Profile* profile, const std::string& app_id) { | ||
return g_browser_process->platform_part() | ||
->app_shim_host_manager() | ||
->extension_app_shim_handler() | ||
->FindHost(profile, app_id); | ||
} | ||
|
||
} // namespace | ||
|
||
// Watches for NSNotifications from the shared workspace. | ||
@interface WindowedNSNotificationObserver : NSObject { | ||
@private | ||
base::scoped_nsobject<NSString> bundleId_; | ||
BOOL notificationReceived_; | ||
scoped_ptr<base::RunLoop> runLoop_; | ||
} | ||
|
||
- (id)initForNotification:(NSString*)name | ||
andBundleId:(NSString*)bundleId; | ||
- (void)observe:(NSNotification*)notification; | ||
- (void)wait; | ||
@end | ||
|
||
@implementation WindowedNSNotificationObserver | ||
|
||
- (id)initForNotification:(NSString*)name | ||
andBundleId:(NSString*)bundleId { | ||
if (self = [super init]) { | ||
bundleId_.reset([[bundleId copy] retain]); | ||
[[[NSWorkspace sharedWorkspace] notificationCenter] | ||
addObserver:self | ||
selector:@selector(observe:) | ||
name:name | ||
object:nil]; | ||
} | ||
return self; | ||
} | ||
|
||
- (void)observe:(NSNotification*)notification { | ||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | ||
|
||
NSRunningApplication* application = | ||
[[notification userInfo] objectForKey:NSWorkspaceApplicationKey]; | ||
if (![[application bundleIdentifier] isEqualToString:bundleId_]) | ||
return; | ||
|
||
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; | ||
notificationReceived_ = YES; | ||
if (runLoop_.get()) | ||
runLoop_->Quit(); | ||
} | ||
|
||
- (void)wait { | ||
if (notificationReceived_) | ||
return; | ||
|
||
runLoop_.reset(new base::RunLoop); | ||
runLoop_->Run(); | ||
} | ||
|
||
@end | ||
|
||
namespace apps { | ||
|
||
// Test that launching the shim for an app starts the app, and vice versa. | ||
// These two cases are combined because the time to run the test is dominated | ||
// by loading the extension and creating the shim. | ||
IN_PROC_BROWSER_TEST_F(AppShimInteractiveTest, Launch) { | ||
// Install the app. | ||
const extensions::Extension* app = InstallPlatformApp("minimal"); | ||
|
||
// Use a WebAppShortcutCreator to get the path. | ||
web_app::WebAppShortcutCreator shortcut_creator( | ||
web_app::GetWebAppDataDirectory(profile()->GetPath(), app->id(), GURL()), | ||
web_app::ShortcutInfoForExtensionAndProfile(app, profile()), | ||
extensions::FileHandlersInfo()); | ||
base::FilePath shim_path = shortcut_creator.GetInternalShortcutPath(); | ||
EXPECT_FALSE(base::PathExists(shim_path)); | ||
|
||
// Create the internal app shim by simulating an app update. FilePathWatcher | ||
// is used to wait for file operations on the shim to be finished before | ||
// attempting to launch it. Since all of the file operations are done in the | ||
// same event on the FILE thread, everything will be done by the time the | ||
// watcher's callback is executed. | ||
scoped_refptr<WindowedFilePathWatcher> file_watcher = | ||
new WindowedFilePathWatcher(shim_path); | ||
web_app::UpdateAllShortcuts(base::string16(), profile(), app); | ||
file_watcher->Wait(); | ||
NSString* bundle_id = GetBundleID(shim_path); | ||
|
||
// Case 1: Launch the shim, it should start the app. | ||
{ | ||
ExtensionTestMessageListener launched_listener("Launched", false); | ||
CommandLine shim_cmdline(CommandLine::NO_PROGRAM); | ||
shim_cmdline.AppendSwitch(app_mode::kLaunchedForTest); | ||
ProcessSerialNumber shim_psn; | ||
ASSERT_TRUE(base::mac::OpenApplicationWithPath( | ||
shim_path, shim_cmdline, kLSLaunchDefaults, &shim_psn)); | ||
ASSERT_TRUE(launched_listener.WaitUntilSatisfied()); | ||
|
||
ASSERT_TRUE(GetFirstAppWindow()); | ||
EXPECT_TRUE(HasAppShimHost(profile(), app->id())); | ||
|
||
// If the window is closed, the shim should quit. | ||
pid_t shim_pid; | ||
EXPECT_EQ(noErr, GetProcessPID(&shim_psn, &shim_pid)); | ||
GetFirstAppWindow()->GetBaseWindow()->Close(); | ||
ASSERT_TRUE( | ||
base::WaitForSingleProcess(shim_pid, TestTimeouts::action_timeout())); | ||
|
||
EXPECT_FALSE(GetFirstAppWindow()); | ||
EXPECT_FALSE(HasAppShimHost(profile(), app->id())); | ||
} | ||
|
||
// Case 2: Launch the app, it should start the shim. | ||
{ | ||
base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer; | ||
ns_observer.reset([[WindowedNSNotificationObserver alloc] | ||
initForNotification:NSWorkspaceDidLaunchApplicationNotification | ||
andBundleId:bundle_id]); | ||
WindowedAppShimLaunchObserver observer(app->id()); | ||
LaunchPlatformApp(app); | ||
[ns_observer wait]; | ||
observer.Wait(); | ||
|
||
EXPECT_TRUE(GetFirstAppWindow()); | ||
EXPECT_TRUE(HasAppShimHost(profile(), app->id())); | ||
|
||
// Quitting the shim will eventually cause it to quit. It actually | ||
// intercepts the -terminate, sends an AppShimHostMsg_QuitApp to Chrome, | ||
// and returns NSTerminateLater. Chrome responds by closing all windows of | ||
// the app. Once all windows are closed, Chrome closes the IPC channel, | ||
// which causes the shim to actually terminate. | ||
NSArray* running_shim = [NSRunningApplication | ||
runningApplicationsWithBundleIdentifier:bundle_id]; | ||
ASSERT_EQ(1u, [running_shim count]); | ||
|
||
ns_observer.reset([[WindowedNSNotificationObserver alloc] | ||
initForNotification:NSWorkspaceDidTerminateApplicationNotification | ||
andBundleId:bundle_id]); | ||
[base::mac::ObjCCastStrict<NSRunningApplication>( | ||
[running_shim objectAtIndex:0]) terminate]; | ||
[ns_observer wait]; | ||
|
||
EXPECT_FALSE(GetFirstAppWindow()); | ||
EXPECT_FALSE(HasAppShimHost(profile(), app->id())); | ||
} | ||
} | ||
|
||
} // namespace apps |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.