summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjackhou@chromium.org <jackhou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-18 06:51:00 +0000
committerjackhou@chromium.org <jackhou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-18 06:51:00 +0000
commit434df0a3b3c6a72072c66209c1721f03cbed756f (patch)
tree63c88a148de3227b0443b38284e2187ed26e0691
parentb42a6050117330a2c042d7509d56a0b653a3d9a2 (diff)
downloadchromium_src-434df0a3b3c6a72072c66209c1721f03cbed756f.zip
chromium_src-434df0a3b3c6a72072c66209c1721f03cbed756f.tar.gz
chromium_src-434df0a3b3c6a72072c66209c1721f03cbed756f.tar.bz2
[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, 386024 NOTRY=true Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=276368 Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=277743 Review URL: https://codereview.chromium.org/316493002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@277955 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--apps/app_shim/app_shim_interactive_uitest_mac.mm307
-rw-r--r--apps/app_shim/chrome_main_app_mode_mac.mm6
-rw-r--r--chrome/app/app_mode-Info.plist2
-rw-r--r--chrome/browser/web_applications/web_app_mac.h5
-rw-r--r--chrome/browser/web_applications/web_app_mac.mm14
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/mac/app_mode_common.h4
-rw-r--r--chrome/common/mac/app_mode_common.mm1
8 files changed, 335 insertions, 5 deletions
diff --git a/apps/app_shim/app_shim_interactive_uitest_mac.mm b/apps/app_shim/app_shim_interactive_uitest_mac.mm
new file mode 100644
index 0000000..1cfdf79
--- /dev/null
+++ b/apps/app_shim/app_shim_interactive_uitest_mac.mm
@@ -0,0 +1,307 @@
+// 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 {
+
+// Shims require static libraries http://crbug.com/386024.
+#if defined(COMPONENT_BUILD)
+#define MAYBE_Launch DISABLED_Launch
+#else
+#define MAYBE_Launch Launch
+#endif
+
+// 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, MAYBE_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
diff --git a/apps/app_shim/chrome_main_app_mode_mac.mm b/apps/app_shim/chrome_main_app_mode_mac.mm
index ee81b77..18b8f9e 100644
--- a/apps/app_shim/chrome_main_app_mode_mac.mm
+++ b/apps/app_shim/chrome_main_app_mode_mac.mm
@@ -611,7 +611,11 @@ int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
main_message_loop.set_thread_name("MainThread");
base::PlatformThread::SetName("CrAppShimMain");
- if (pid == -1) {
+ // In tests, launching Chrome does nothing, and we won't get a ping response,
+ // so just assume the socket exists.
+ if (pid == -1 &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ app_mode::kLaunchedForTest)) {
// Launch Chrome if it isn't already running.
ProcessSerialNumber psn;
CommandLine command_line(CommandLine::NO_PROGRAM);
diff --git a/chrome/app/app_mode-Info.plist b/chrome/app/app_mode-Info.plist
index 9ac4fb4..1ca1b95 100644
--- a/chrome/app/app_mode-Info.plist
+++ b/chrome/app/app_mode-Info.plist
@@ -26,8 +26,6 @@
<string>@APP_MODE_SHORTCUT_URL@</string>
<key>CrBundleIdentifier</key>
<string>@APP_MODE_BROWSER_BUNDLE_ID@</string>
- <key>LSFileQuarantineEnabled</key>
- <true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}.0</string>
<key>NSAppleScriptEnabled</key>
diff --git a/chrome/browser/web_applications/web_app_mac.h b/chrome/browser/web_applications/web_app_mac.h
index 46702e4..a0a8b20 100644
--- a/chrome/browser/web_applications/web_app_mac.h
+++ b/chrome/browser/web_applications/web_app_mac.h
@@ -14,6 +14,11 @@
#include "chrome/browser/web_applications/web_app.h"
#include "extensions/common/manifest_handlers/file_handler_info.h"
+// Whether to enable update and launch of app shims in tests. (Normally shims
+// are never created or launched in tests). Note that update only creates
+// internal shim bundles, i.e. it does not create new shims in ~/Applications.
+extern bool g_app_shims_allow_update_and_launch_in_tests;
+
namespace web_app {
// Returns the full path of the .app shim that would be created by
diff --git a/chrome/browser/web_applications/web_app_mac.mm b/chrome/browser/web_applications/web_app_mac.mm
index 7bfa3d3..b46170c 100644
--- a/chrome/browser/web_applications/web_app_mac.mm
+++ b/chrome/browser/web_applications/web_app_mac.mm
@@ -47,6 +47,8 @@
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_family.h"
+bool g_app_shims_allow_update_and_launch_in_tests = false;
+
namespace {
// Launch Services Key to run as an agent app, which doesn't launch in the dock.
@@ -564,7 +566,11 @@ size_t WebAppShortcutCreator::CreateShortcutsIn(
return succeeded;
}
+ // Remove the quarantine attribute from both the bundle and the executable.
base::mac::RemoveQuarantineAttribute(dst_path.Append(app_name));
+ base::mac::RemoveQuarantineAttribute(
+ dst_path.Append(app_name)
+ .Append("Contents").Append("MacOS").Append("app_mode_loader"));
++succeeded;
}
@@ -863,8 +869,10 @@ base::FilePath GetAppInstallPath(const ShortcutInfo& shortcut_info) {
}
void MaybeLaunchShortcut(const ShortcutInfo& shortcut_info) {
- if (AppShimsDisabledForTest())
+ if (AppShimsDisabledForTest() &&
+ !g_app_shims_allow_update_and_launch_in_tests) {
return;
+ }
content::BrowserThread::PostTask(
content::BrowserThread::FILE, FROM_HERE,
@@ -976,8 +984,10 @@ void UpdatePlatformShortcuts(
const ShortcutInfo& shortcut_info,
const extensions::FileHandlersInfo& file_handlers_info) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
- if (AppShimsDisabledForTest())
+ if (AppShimsDisabledForTest() &&
+ !g_app_shims_allow_update_and_launch_in_tests) {
return;
+ }
WebAppShortcutCreator shortcut_creator(
app_data_path, shortcut_info, file_handlers_info);
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index b5b9b24..3ed8ecd 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -44,6 +44,7 @@
'HAS_OUT_OF_PROC_TEST_RUNNER',
],
'sources': [
+ '../apps/app_shim/app_shim_interactive_uitest_mac.mm',
'../apps/app_shim/app_shim_quit_interactive_uitest_mac.mm',
'../apps/app_window_interactive_uitest.cc',
'../ui/base/clipboard/clipboard_unittest.cc',
diff --git a/chrome/common/mac/app_mode_common.h b/chrome/common/mac/app_mode_common.h
index 2022217..4d686da 100644
--- a/chrome/common/mac/app_mode_common.h
+++ b/chrome/common/mac/app_mode_common.h
@@ -37,6 +37,10 @@ extern const char kAppListModeId[];
// launch_now = false. This associates the shim without launching the app.
extern const char kLaunchedByChromeProcessId[];
+// Indicates to the shim that it was launched for a test, so don't attempt to
+// launch Chrome.
+extern const char kLaunchedForTest[];
+
// Path to an app shim bundle. Indicates to Chrome that this shim attempted to
// launch but failed.
extern const char kAppShimError[];
diff --git a/chrome/common/mac/app_mode_common.mm b/chrome/common/mac/app_mode_common.mm
index 977bc14..9996090 100644
--- a/chrome/common/mac/app_mode_common.mm
+++ b/chrome/common/mac/app_mode_common.mm
@@ -14,6 +14,7 @@ const char kAppShimSocketSymlinkName[] = "App Shim Socket";
const char kAppListModeId[] = "app_list";
const char kLaunchedByChromeProcessId[] = "launched-by-chrome-process-id";
+const char kLaunchedForTest[] = "launched-for-test";
const char kAppShimError[] = "app-shim-error";