diff options
author | jeremya@chromium.org <jeremya@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-18 05:54:50 +0000 |
---|---|---|
committer | jeremya@chromium.org <jeremya@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-18 05:54:50 +0000 |
commit | acd2121ce008fa99fa0e89255b1b95b616f02beb (patch) | |
tree | 21af3be507c53f610c51ccabe8a1b7044f87f5ba /apps/app_shim | |
parent | ffec1ff8da92badfa2a46037547d697a315ea4c5 (diff) | |
download | chromium_src-acd2121ce008fa99fa0e89255b1b95b616f02beb.zip chromium_src-acd2121ce008fa99fa0e89255b1b95b616f02beb.tar.gz chromium_src-acd2121ce008fa99fa0e89255b1b95b616f02beb.tar.bz2 |
[mac] App shims
The AppShimHostController uses an IPC::ChannelFactory to listen on a socket for
app shims connecting. App shims send a launch message when they start, so we
translate that into actually launching the app.
I changed chrome_main_app_mode_mac.mm to use the IPC channel instead of sending
an Apple Event as before, and added an event loop and an IO thread.
BUG=138733
Review URL: https://chromiumcodereview.appspot.com/12623005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188675 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'apps/app_shim')
-rw-r--r-- | apps/app_shim/OWNERS | 1 | ||||
-rw-r--r-- | apps/app_shim/app_shim_host_mac.cc | 153 | ||||
-rw-r--r-- | apps/app_shim/app_shim_host_mac.h | 81 | ||||
-rw-r--r-- | apps/app_shim/app_shim_host_mac_unittest.cc | 124 | ||||
-rw-r--r-- | apps/app_shim/app_shim_host_manager_mac.h | 40 | ||||
-rw-r--r-- | apps/app_shim/app_shim_host_manager_mac.mm | 71 | ||||
-rw-r--r-- | apps/app_shim/app_shim_messages.h | 22 |
7 files changed, 492 insertions, 0 deletions
diff --git a/apps/app_shim/OWNERS b/apps/app_shim/OWNERS new file mode 100644 index 0000000..c046a32 --- /dev/null +++ b/apps/app_shim/OWNERS @@ -0,0 +1 @@ +jeremya@chromium.org diff --git a/apps/app_shim/app_shim_host_mac.cc b/apps/app_shim/app_shim_host_mac.cc new file mode 100644 index 0000000..8cff755 --- /dev/null +++ b/apps/app_shim/app_shim_host_mac.cc @@ -0,0 +1,153 @@ +// Copyright 2013 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. + +#include "apps/app_shim/app_shim_host_mac.h" + +#include "apps/app_shim/app_shim_messages.h" +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/shell_window_registry.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/extensions/application_launch.h" +#include "chrome/common/extensions/extension_constants.h" +#include "ipc/ipc_channel_proxy.h" + +AppShimHost::AppShimHost() + : channel_(NULL), profile_(NULL) { +} + +AppShimHost::~AppShimHost() { + DCHECK(CalledOnValidThread()); +} + +void AppShimHost::ServeChannel(const IPC::ChannelHandle& handle) { + DCHECK(CalledOnValidThread()); + DCHECK(!channel_.get()); + channel_.reset(new IPC::ChannelProxy(handle, IPC::Channel::MODE_SERVER, this, + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))); +} + +bool AppShimHost::OnMessageReceived(const IPC::Message& message) { + DCHECK(CalledOnValidThread()); + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AppShimHost, message) + IPC_MESSAGE_HANDLER(AppShimHostMsg_LaunchApp, OnLaunchApp) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +bool AppShimHost::Send(IPC::Message* message) { + DCHECK(channel_.get()); + return channel_->Send(message); +} + +void AppShimHost::OnLaunchApp(std::string profile_dir, std::string app_id) { + DCHECK(CalledOnValidThread()); + bool success = LaunchAppImpl(profile_dir, app_id); + Send(new AppShimMsg_LaunchApp_Done(success)); +} + +bool AppShimHost::LaunchAppImpl(const std::string& profile_dir, + const std::string& app_id) { + DCHECK(CalledOnValidThread()); + if (profile_) { + // Only one app launch message per channel. + return false; + } + if (!extensions::Extension::IdIsValid(app_id)) { + LOG(ERROR) << "Bad app ID from app shim launch message."; + return false; + } + Profile* profile = FetchProfileForDirectory(profile_dir); + if (!profile) + return false; + + if (!LaunchApp(profile, app_id)) + return false; + + profile_ = profile; + app_id_ = app_id; + + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, + content::Source<Profile>(profile_)); + return true; +} + +Profile* AppShimHost::FetchProfileForDirectory(const std::string& profile_dir) { + ProfileManager* profileManager = g_browser_process->profile_manager(); + // Even though the name of this function is "unsafe", there's no security + // issue here -- the check for the profile name in the profile info cache + // ensures that we never access any directory that isn't a known profile. + base::FilePath path = base::FilePath::FromUTF8Unsafe(profile_dir); + path = profileManager->user_data_dir().Append(path); + ProfileInfoCache& cache = profileManager->GetProfileInfoCache(); + // This ensures that the given profile path is acutally a profile that we + // already know about. + if (cache.GetIndexOfProfileWithPath(path) == std::string::npos) { + LOG(ERROR) << "Requested directory is not a known profile."; + return NULL; + } + Profile* profile = profileManager->GetProfile(path); + if (!profile) { + LOG(ERROR) << "Couldn't get profile for directory '" << profile_dir << "'."; + return NULL; + } + return profile; +} + +bool AppShimHost::LaunchApp(Profile* profile, const std::string& app_id) { + extensions::ExtensionSystem* extension_system = + extensions::ExtensionSystem::Get(profile); + ExtensionServiceInterface* extension_service = + extension_system->extension_service(); + const extensions::Extension* extension = + extension_service->GetExtensionById( + app_id, false); + if (!extension) { + LOG(ERROR) << "Attempted to launch nonexistent app with id '" + << app_id << "'."; + return false; + } + // TODO(jeremya): Handle the case that launching the app fails. Probably we + // need to watch for 'app successfully launched' or at least 'background page + // exists/was created' and time out with failure if we don't see that sign of + // life within a certain window. + chrome::AppLaunchParams params(profile, + extension, + extension_misc::LAUNCH_NONE, + NEW_WINDOW); + chrome::OpenApplication(params); + return true; +} + +void AppShimHost::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK(CalledOnValidThread()); + DCHECK(content::Source<Profile>(source).ptr() == profile_); + switch (type) { + case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { + extensions::ExtensionHost* extension_host = + content::Details<extensions::ExtensionHost>(details).ptr(); + if (app_id_ == extension_host->extension_id()) + Close(); + break; + } + default: + NOTREACHED() << "Unexpected notification sent."; + break; + } +} + +void AppShimHost::Close() { + DCHECK(CalledOnValidThread()); + delete this; +} diff --git a/apps/app_shim/app_shim_host_mac.h b/apps/app_shim/app_shim_host_mac.h new file mode 100644 index 0000000..8ebc0cd --- /dev/null +++ b/apps/app_shim/app_shim_host_mac.h @@ -0,0 +1,81 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MAC_H_ +#define CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MAC_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_sender.h" + +class Profile; + +namespace IPC { +struct ChannelHandle; +class ChannelProxy; +class Message; +} // namespace IPC + +// This is the counterpart to AppShimController in +// chrome/app/chrome_main_app_mode_mac.mm. The AppShimHost owns itself, and is +// destroyed when the app it corresponds to is closed or when the channel +// connected to the app shim is closed. +class AppShimHost : public IPC::Listener, + public IPC::Sender, + public content::NotificationObserver, + public base::NonThreadSafe { + public: + AppShimHost(); + virtual ~AppShimHost(); + + // Creates a new server-side IPC channel at |handle|, which should contain a + // file descriptor of a channel created by an IPC::ChannelFactory, and begins + // listening for messages on it. + void ServeChannel(const IPC::ChannelHandle& handle); + + const std::string& app_id() const { return app_id_; } + const Profile* profile() const { return profile_; } + + protected: + // Used internally; virtual so they can be mocked for testing. + virtual Profile* FetchProfileForDirectory(const std::string& profile_dir); + virtual bool LaunchApp(Profile* profile, const std::string& app_id); + + // IPC::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + // IPC::Sender implementation. + virtual bool Send(IPC::Message* message) OVERRIDE; + + private: + // The app shim process is requesting that an app be launched. Once it has + // done so the |profile| and |app_id| are stored, and all future messages + // from the app shim relate to the app it launched. It is an error for the + // app shim to send multiple launch messages. + void OnLaunchApp(std::string profile, std::string app_id); + + bool LaunchAppImpl(const std::string& profile_dir, const std::string& app_id); + + // The AppShimHost listens to the NOTIFICATION_EXTENSION_HOST_DESTROYED + // message to detect when the app closes. When that happens, the AppShimHost + // closes the channel, which causes the app shim process to quit. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Closes the channel and destroys the AppShimHost. + void Close(); + + scoped_ptr<IPC::ChannelProxy> channel_; + std::string app_id_; + Profile* profile_; + content::NotificationRegistrar registrar_; +}; + +#endif // CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MAC_H_ diff --git a/apps/app_shim/app_shim_host_mac_unittest.cc b/apps/app_shim/app_shim_host_mac_unittest.cc new file mode 100644 index 0000000..802c8e9 --- /dev/null +++ b/apps/app_shim/app_shim_host_mac_unittest.cc @@ -0,0 +1,124 @@ +// Copyright 2013 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. + +#include "apps/app_shim/app_shim_host_mac.h" + +#include "apps/app_shim/app_shim_messages.h" +#include "base/memory/scoped_vector.h" +#include "chrome/test/base/testing_profile.h" +#include "ipc/ipc_message.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TestingAppShimHost : public AppShimHost { + public: + explicit TestingAppShimHost(Profile* profile); + virtual ~TestingAppShimHost() {} + + bool ReceiveMessage(IPC::Message* message); + + const std::vector<IPC::Message*>& sent_messages() { + return sent_messages_.get(); + } + + void set_fails_profile(bool fails_profile) { + fails_profile_ = fails_profile; + } + + void set_fails_launch(bool fails_launch) { + fails_launch_ = fails_launch; + } + + protected: + virtual Profile* FetchProfileForDirectory(const std::string& profile_dir) + OVERRIDE; + virtual bool LaunchApp(Profile* profile, const std::string& app_id) OVERRIDE; + + virtual bool Send(IPC::Message* message) OVERRIDE; + + Profile* test_profile_; + bool fails_profile_; + bool fails_launch_; + + ScopedVector<IPC::Message> sent_messages_; + + DISALLOW_COPY_AND_ASSIGN(TestingAppShimHost); +}; + +TestingAppShimHost::TestingAppShimHost(Profile* profile) + : test_profile_(profile), + fails_profile_(false), + fails_launch_(false) { +} + +bool TestingAppShimHost::ReceiveMessage(IPC::Message* message) { + bool handled = OnMessageReceived(*message); + delete message; + return handled; +} + +bool TestingAppShimHost::Send(IPC::Message* message) { + sent_messages_.push_back(message); + return true; +} + +Profile* TestingAppShimHost::FetchProfileForDirectory( + const std::string& profile_dir) { + return fails_profile_ ? NULL : test_profile_; +} + +bool TestingAppShimHost::LaunchApp( + Profile* profile, const std::string& app_id) { + return !fails_launch_; +} + +class AppShimHostTest : public testing::Test { + public: + TestingAppShimHost* host() { return host_.get(); } + TestingProfile* profile() { return profile_.get(); } + + bool LaunchWasSuccessful() { + EXPECT_EQ(1u, host()->sent_messages().size()); + IPC::Message* message = host()->sent_messages()[0]; + EXPECT_EQ(AppShimMsg_LaunchApp_Done::ID, message->type()); + AppShimMsg_LaunchApp_Done::Param param; + AppShimMsg_LaunchApp_Done::Read(message, ¶m); + return param.a; + } + + private: + virtual void SetUp() OVERRIDE { + profile_.reset(new TestingProfile); + host_.reset(new TestingAppShimHost(profile())); + } + + scoped_ptr<TestingAppShimHost> host_; + scoped_ptr<TestingProfile> profile_; +}; + +static const std::string kTestAppId = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +static const std::string kTestProfileDir = "Default"; + +TEST_F(AppShimHostTest, TestLaunchApp) { + host()->ReceiveMessage( + new AppShimHostMsg_LaunchApp(kTestProfileDir, kTestAppId)); + ASSERT_EQ(kTestAppId, host()->app_id()); + ASSERT_EQ(profile(), host()->profile()); + ASSERT_TRUE(LaunchWasSuccessful()); +} + +TEST_F(AppShimHostTest, TestFailProfile) { + host()->set_fails_profile(true); + host()->ReceiveMessage( + new AppShimHostMsg_LaunchApp(kTestProfileDir, kTestAppId)); + ASSERT_TRUE(host()->app_id().empty()); + ASSERT_FALSE(LaunchWasSuccessful()); +} + +TEST_F(AppShimHostTest, TestFailLaunch) { + host()->set_fails_launch(true); + host()->ReceiveMessage( + new AppShimHostMsg_LaunchApp(kTestProfileDir, kTestAppId)); + ASSERT_TRUE(host()->app_id().empty()); + ASSERT_FALSE(LaunchWasSuccessful()); +} diff --git a/apps/app_shim/app_shim_host_manager_mac.h b/apps/app_shim/app_shim_host_manager_mac.h new file mode 100644 index 0000000..13283ee --- /dev/null +++ b/apps/app_shim/app_shim_host_manager_mac.h @@ -0,0 +1,40 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MANAGER_MAC_H_ +#define CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MANAGER_MAC_H_ + +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread.h" +#include "ipc/ipc_channel_factory.h" + +// The AppShimHostManager receives connections from app shims on a UNIX +// socket (|factory_|) and creates a helper object to manage the connection. +class AppShimHostManager + : public IPC::ChannelFactory::Delegate, + public base::SupportsWeakPtr<AppShimHostManager> { + public: + AppShimHostManager(); + virtual ~AppShimHostManager(); + + private: + // IPC::ChannelFactory::Delegate implementation. + virtual void OnClientConnected(const IPC::ChannelHandle& handle) OVERRIDE; + virtual void OnListenError() OVERRIDE; + + // The |factory_| must be created on a thread which allows blocking I/O, so + // part of the initialization of this class must be carried out on the file + // thread. + void InitOnFileThread(); + + // Called on the IO thread to begin listening for connections from app shims. + void ListenOnIOThread(); + + scoped_ptr<IPC::ChannelFactory> factory_; + + DISALLOW_COPY_AND_ASSIGN(AppShimHostManager); +}; + +#endif // CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MANAGER_MAC_H_ diff --git a/apps/app_shim/app_shim_host_manager_mac.mm b/apps/app_shim/app_shim_host_manager_mac.mm new file mode 100644 index 0000000..6133b9b --- /dev/null +++ b/apps/app_shim/app_shim_host_manager_mac.mm @@ -0,0 +1,71 @@ +// Copyright 2013 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. + +#include "apps/app_shim/app_shim_host_manager_mac.h" + +#include "apps/app_shim/app_shim_host_mac.h" +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "chrome/browser/browser_process.h" +#include "chrome/common/chrome_paths.h" +#include "content/public/browser/browser_thread.h" +#include "chrome/common/mac/app_mode_common.h" + +using content::BrowserThread; + +namespace { + +void CreateAppShimHost(const IPC::ChannelHandle& handle) { + // AppShimHost takes ownership of itself. + (new AppShimHost)->ServeChannel(handle); +} + +} // namespace + +AppShimHostManager::AppShimHostManager() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&AppShimHostManager::InitOnFileThread, + base::Unretained(this))); +} + +AppShimHostManager::~AppShimHostManager() { +} + +void AppShimHostManager::InitOnFileThread() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + base::FilePath user_data_dir; + if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { + LOG(ERROR) << "Couldn't get user data directory while creating App Shim " + << "Host manager."; + return; + } + base::FilePath socket_path = + user_data_dir.Append(app_mode::kAppShimSocketName); + factory_.reset(new IPC::ChannelFactory(socket_path, this)); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&AppShimHostManager::ListenOnIOThread, + base::Unretained(this))); +} + +void AppShimHostManager::ListenOnIOThread() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + factory_->Listen(); +} + +void AppShimHostManager::OnClientConnected( + const IPC::ChannelHandle& handle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&CreateAppShimHost, handle)); +} + +void AppShimHostManager::OnListenError() { + // TODO(jeremya): set a timeout and attempt to reconstruct the channel. +} diff --git a/apps/app_shim/app_shim_messages.h b/apps/app_shim/app_shim_messages.h new file mode 100644 index 0000000..1e49ce4 --- /dev/null +++ b/apps/app_shim/app_shim_messages.h @@ -0,0 +1,22 @@ +// Copyright 2013 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. + +// Multiply-included message file, hence no include guard. + +#include <string> + +#include "ipc/ipc_message_macros.h" + +#define IPC_MESSAGE_START AppShimMsgStart + +// Signals that a previous LaunchApp message has been processed, and lets the +// app shim process know whether the app launch was successful. +IPC_MESSAGE_CONTROL1(AppShimMsg_LaunchApp_Done, + bool /* succeeded */) + +// Tells the main Chrome process to launch a particular app with the given +// profile name and app id. +IPC_MESSAGE_CONTROL2(AppShimHostMsg_LaunchApp, + std::string /* profile name */, + std::string /* app id */) |