Commit 8b440501 authored by tmdiep@chromium.org's avatar tmdiep@chromium.org

Do not dispatch events to inactive ephemeral apps

With the exception of the chrome.app.runtime.onLaunched() event,
events will only be dispatched to ephemeral apps that are running.
This was implemented by adding a "can_load_ephemeral_apps" flag to
extensions::Event. Only events which enable this flag will have the
ability to load the event page of apps.

BUG=312460
TEST=browser_tests (EphemeralAppBrowserTest.*)

Review URL: https://codereview.chromium.org/118033002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243477 0039d316-1c4b-4281-b951-d872f2087c98
parent a82d72a4
......@@ -65,8 +65,7 @@ const Extension* PlatformAppBrowserTest::LoadAndLaunchPlatformApp(
test_data_dir_.AppendASCII("platform_apps").AppendASCII(name));
EXPECT_TRUE(extension);
OpenApplication(AppLaunchParams(
browser()->profile(), extension, LAUNCH_CONTAINER_NONE, NEW_WINDOW));
LaunchPlatformApp(extension);
app_loaded_observer.Wait();
......@@ -90,14 +89,18 @@ const Extension* PlatformAppBrowserTest::InstallAndLaunchPlatformApp(
const Extension* extension = InstallPlatformApp(name);
OpenApplication(AppLaunchParams(
browser()->profile(), extension, LAUNCH_CONTAINER_NONE, NEW_WINDOW));
LaunchPlatformApp(extension);
app_loaded_observer.Wait();
return extension;
}
void PlatformAppBrowserTest::LaunchPlatformApp(const Extension* extension) {
OpenApplication(AppLaunchParams(
browser()->profile(), extension, LAUNCH_CONTAINER_NONE, NEW_WINDOW));
}
WebContents* PlatformAppBrowserTest::GetFirstShellWindowWebContents() {
ShellWindow* window = GetFirstShellWindow();
if (window)
......@@ -110,6 +113,20 @@ ShellWindow* PlatformAppBrowserTest::GetFirstShellWindow() {
return GetFirstShellWindowForBrowser(browser());
}
apps::ShellWindow* PlatformAppBrowserTest::GetFirstShellWindowForApp(
const std::string& app_id) {
ShellWindowRegistry* app_registry =
ShellWindowRegistry::Get(browser()->profile());
const ShellWindowRegistry::ShellWindowList& shell_windows =
app_registry->GetShellWindowsForApp(app_id);
ShellWindowRegistry::const_iterator iter = shell_windows.begin();
if (iter != shell_windows.end())
return *iter;
return NULL;
}
size_t PlatformAppBrowserTest::RunGetWindowsFunctionForExtension(
const Extension* extension) {
scoped_refptr<WindowsGetAllFunction> function = new WindowsGetAllFunction();
......@@ -139,6 +156,12 @@ size_t PlatformAppBrowserTest::GetShellWindowCount() {
shell_windows().size();
}
size_t PlatformAppBrowserTest::GetShellWindowCountForApp(
const std::string& app_id) {
return ShellWindowRegistry::Get(browser()->profile())->
GetShellWindowsForApp(app_id).size();
}
void PlatformAppBrowserTest::ClearCommandLineArgs() {
CommandLine* command_line = CommandLine::ForCurrentProcess();
CommandLine::StringVector args = command_line->GetArgs();
......
......@@ -41,6 +41,9 @@ class PlatformAppBrowserTest : public ExtensionApiTest {
// subdirectory. Waits until it is launched.
const Extension* InstallAndLaunchPlatformApp(const char* name);
// Launch the given platform app.
void LaunchPlatformApp(const Extension* extension);
// Gets the WebContents associated with the first shell window that is found
// (most tests only deal with one platform app window, so this is good
// enough).
......@@ -50,6 +53,9 @@ class PlatformAppBrowserTest : public ExtensionApiTest {
// platform app window, so this is good enough).
apps::ShellWindow* GetFirstShellWindow();
// Gets the first shell window for an app.
apps::ShellWindow* GetFirstShellWindowForApp(const std::string& app_id);
// Runs chrome.windows.getAll for the given extension and returns the number
// of windows that the function returns.
size_t RunGetWindowsFunctionForExtension(const Extension* extension);
......@@ -62,6 +68,9 @@ class PlatformAppBrowserTest : public ExtensionApiTest {
// Returns the number of shell windows.
size_t GetShellWindowCount();
// Returns the number of shell windows for a specific app.
size_t GetShellWindowCountForApp(const std::string& app_id);
// The command line already has an argument on it - about:blank, which
// is set by InProcessBrowserTest::PrepareTestCommandLine. For platform app
// launch tests we need to clear this.
......
// 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.
#include "chrome/browser/apps/app_browsertest_util.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
#include "chrome/common/extensions/api/alarms.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/switches.h"
using extensions::Event;
using extensions::EventRouter;
using extensions::Extension;
using extensions::ExtensionSystem;
using extensions::PlatformAppBrowserTest;
namespace {
namespace alarms = extensions::api::alarms;
const char kDispatchEventTestApp[] =
"platform_apps/ephemeral_apps/dispatch_event";
const char kMessagingReceiverApp[] =
"platform_apps/ephemeral_apps/messaging_receiver";
class EphemeralAppBrowserTest : public PlatformAppBrowserTest {
protected:
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
// Skip PlatformAppBrowserTest, which sets different values for the switches
// below.
ExtensionBrowserTest::SetUpCommandLine(command_line);
// Make event pages get suspended immediately.
command_line->AppendSwitchASCII(
extensions::switches::kEventPageIdleTime, "10");
command_line->AppendSwitchASCII(
extensions::switches::kEventPageSuspendingTime, "10");
}
const Extension* InstallEphemeralApp(const char* test_path) {
base::FilePath path = test_data_dir_.AppendASCII(test_path);
const Extension* extension =
InstallExtensionWithSourceAndFlags(
path,
1,
extensions::Manifest::UNPACKED,
Extension::IS_EPHEMERAL);
return extension;
}
const Extension* InstallAndLaunchEphemeralApp(const char* test_path) {
ExtensionTestMessageListener launched_listener("launched", false);
const Extension* extension = InstallEphemeralApp(test_path);
EXPECT_TRUE(extension);
if (!extension)
return NULL;
LaunchPlatformApp(extension);
bool wait_result = launched_listener.WaitUntilSatisfied();
EXPECT_TRUE(wait_result);
if (!wait_result)
return NULL;
return extension;
}
void CloseApp(const std::string& app_id) {
content::WindowedNotificationObserver event_page_destroyed_signal(
chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
content::Source<Profile>(browser()->profile()));
EXPECT_EQ(1U, GetShellWindowCountForApp(app_id));
apps::ShellWindow* shell_window = GetFirstShellWindowForApp(app_id);
ASSERT_TRUE(shell_window);
CloseShellWindow(shell_window);
event_page_destroyed_signal.Wait();
}
void VerifyAppNotLoaded(const std::string& app_id) {
EXPECT_FALSE(ExtensionSystem::Get(browser()->profile())->
process_manager()->GetBackgroundHostForExtension(app_id));
}
void DispatchAlarmEvent(EventRouter* event_router,
const std::string& app_id) {
alarms::Alarm dummy_alarm;
dummy_alarm.name = "test_alarm";
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(dummy_alarm.ToValue().release());
scoped_ptr<Event> event(new Event(alarms::OnAlarm::kEventName,
args.Pass()));
event_router->DispatchEventToExtension(app_id, event.Pass());
}
};
} // namespace
// Verify that ephemeral apps can be launched and receive system events when
// they are running. Once they are inactive they should not receive system
// events.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, EventDispatchWhenLaunched) {
const Extension* extension =
InstallAndLaunchEphemeralApp(kDispatchEventTestApp);
ASSERT_TRUE(extension);
// Send a fake alarm event to the app and verify that a response is
// received.
EventRouter* event_router =
ExtensionSystem::Get(browser()->profile())->event_router();
ASSERT_TRUE(event_router);
ExtensionTestMessageListener alarm_received_listener("alarm_received", false);
DispatchAlarmEvent(event_router, extension->id());
ASSERT_TRUE(alarm_received_listener.WaitUntilSatisfied());
CloseApp(extension->id());
// The app needs to be launched once in order to have the onAlarm() event
// registered.
ASSERT_TRUE(event_router->ExtensionHasEventListener(
extension->id(), alarms::OnAlarm::kEventName));
// Dispatch the alarm event again and verify that the event page did not get
// loaded for the app.
DispatchAlarmEvent(event_router, extension->id());
VerifyAppNotLoaded(extension->id());
}
// Verify that ephemeral apps will receive messages while they are running.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, ReceiveMessagesWhenLaunched) {
const Extension* receiver =
InstallAndLaunchEphemeralApp(kMessagingReceiverApp);
ASSERT_TRUE(receiver);
// Verify that messages are received while the app is running.
ExtensionApiTest::ResultCatcher result_catcher;
LoadAndLaunchPlatformApp("ephemeral_apps/messaging_sender_success");
EXPECT_TRUE(result_catcher.GetNextResult());
CloseApp(receiver->id());
// Verify that messages are not received while the app is inactive.
LoadAndLaunchPlatformApp("ephemeral_apps/messaging_sender_fail");
EXPECT_TRUE(result_catcher.GetNextResult());
}
......@@ -4,7 +4,6 @@
#include <vector>
#include "base/command_line.h"
#include "chrome/browser/apps/app_browsertest_util.h"
#include "chrome/browser/apps/ephemeral_app_service.h"
#include "chrome/browser/extensions/extension_prefs.h"
......@@ -13,7 +12,6 @@
#include "chrome/browser/profiles/profile.h"
#include "content/public/test/test_utils.h"
#include "extensions/common/manifest.h"
#include "extensions/common/switches.h"
using extensions::PlatformAppBrowserTest;
using extensions::Extension;
......@@ -32,24 +30,8 @@ const char* kTestApps[] = {
class EphemeralAppServiceBrowserTest : public PlatformAppBrowserTest {
protected:
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
// Skip PlatformAppBrowserTest, which sets different values for the switches
// below.
ExtensionBrowserTest::SetUpCommandLine(command_line);
// Make event pages get suspended immediately.
command_line->AppendSwitchASCII(
extensions::switches::kEventPageIdleTime, "10");
command_line->AppendSwitchASCII(
extensions::switches::kEventPageSuspendingTime, "10");
}
void LoadApps() {
for (int i = 0; i < kNumTestApps; ++i) {
content::WindowedNotificationObserver event_page_destroyed_signal(
chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
content::Source<Profile>(browser()->profile()));
base::FilePath path = test_data_dir_.AppendASCII(kTestApps[i]);
const Extension* extension =
InstallExtensionWithSourceAndFlags(
......@@ -58,9 +40,6 @@ class EphemeralAppServiceBrowserTest : public PlatformAppBrowserTest {
extensions::Manifest::UNPACKED,
Extension::IS_EPHEMERAL);
app_ids_.push_back(extension->id());
// Also wait for the app to become idle.
event_page_destroyed_signal.Wait();
}
ASSERT_EQ(kNumTestApps, (int) app_ids_.size());
......
......@@ -47,6 +47,7 @@ void DispatchOnLaunchedEventImpl(const std::string& extension_id,
scoped_ptr<Event> event(new Event(app_runtime::OnLaunched::kEventName,
args.Pass()));
event->restrict_to_browser_context = profile;
event->can_load_ephemeral_apps = true;
system->event_router()->DispatchEventWithLazyListener(extension_id,
event.Pass());
system->extension_service()->extension_prefs()->SetLastLaunchTime(
......
......@@ -201,14 +201,24 @@ void MessageService::OpenChannelToExtension(
return;
Profile* profile = Profile::FromBrowserContext(source->GetBrowserContext());
const Extension* target_extension = ExtensionSystem::Get(profile)->
extension_service()->extensions()->GetByID(target_extension_id);
ExtensionSystem* extension_system = ExtensionSystem::Get(profile);
DCHECK(extension_system);
const Extension* target_extension = extension_system->extension_service()->
extensions()->GetByID(target_extension_id);
if (!target_extension) {
DispatchOnDisconnect(
source, receiver_port_id, kReceivingEndDoesntExistError);
return;
}
// Only running ephemeral apps can receive messages. Idle cached ephemeral
// apps are invisible and should not be connectable.
if (extension_util::IsIdleEphemeralApp(target_extension, extension_system)) {
DispatchOnDisconnect(
source, receiver_port_id, kReceivingEndDoesntExistError);
return;
}
bool is_web_connection = false;
if (source_extension_id != target_extension_id) {
......
......@@ -170,4 +170,15 @@ bool IsExtensionInstalledPermanently(const std::string& extension_id,
return extension && !extension->is_ephemeral();
}
bool IsIdleEphemeralApp(const extensions::Extension* extension,
extensions::ExtensionSystem* extension_system) {
DCHECK(extension);
DCHECK(extension_system);
if (!extension->is_ephemeral())
return false;
return IsExtensionIdle(extension->id(), extension_system);
}
} // namespace extension_util
......@@ -63,6 +63,12 @@ bool IsExtensionIdle(const std::string& extension_id,
bool IsExtensionInstalledPermanently(const std::string& extension_id,
const ExtensionService* service);
// Returns true if the extension is an ephemeral app that is not currently
// running. Note that this function will always return false if |extension| is
// not an ephemeral app.
bool IsIdleEphemeralApp(const extensions::Extension* extension,
extensions::ExtensionSystem* extension_system);
} // namespace extension_util
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_UTIL_H_
......@@ -969,6 +969,7 @@
'browser/apps/app_crash_browsertest.cc',
'browser/apps/app_window_browsertest.cc',
'browser/apps/app_url_redirector_browsertest.cc',
'browser/apps/ephemeral_app_browsertest.cc',
'browser/apps/ephemeral_app_service_browsertest.cc',
'browser/apps/event_page_browsertest.cc',
'browser/apps/speech_recognition_browsertest.cc',
......
<!--
* 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.
-->
<!DOCTYPE html>
<html>
<body>
<script src="index.js"></script>
</body>
</html>
// 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.
onload = function() {
chrome.test.sendMessage('launched');
};
// 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.
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', {});
});
chrome.alarms.onAlarm.addListener(function(alarmInfo) {
chrome.test.sendMessage('alarm_received');
});
{
"name": "Ephemeral Apps Dispatch Events",
"version": "1.0",
"manifest_version": 2,
"app": {
"background": {
"scripts": ["main.js"]
}
},
"permissions": ["alarms"]
}
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPIEJLKSfSykP/FVN
HB6INoQcx+pw/VVpmL8zWDkOvc9F/vXx0JOFCSfVJOx4XwgwdNs18f3UvKe6aTmgG
CjtN6sE/s9dSYfvrLIvTaVH/P9cpMer3G+XaDZhnvm7Fr0fZenmX0+IKvqhCBDH4F
TgPJXvu79zHrrHdhV5GhqY8/NAgMBAAECgYBLfCFaFTi3qM9M3Y+KmX0PptvRf2jg
Z4OLEmCYnPcLUGHfqjC7LJE5zvxZSL0Usz7hS+yNsUWM6SXkygQ2oDj0M59dSho9n
NH+W/A33JwnKoE1qn+mO3T4QwoQORNiug4je8bHIbNmA5mjL7k52vvtSN/VR7Be+y
hqjuk808OX/QJBAPpo4Lx9SKEQcEntflEEvpWSCzH1317HG8peQuFcp6KTIAynAKh
DSTcfCanoCvkNl9UxD6zvXgWZHV4PTh4Mbc8CQQD3a0ut2Njio3MNGaNXQUvj7wGi
l598CiS5w/QZ6uBme1LmH+Y31D8oeODt5dMaMYZbxC+zLiipj9R802PmngujAkEAq
IcrMVA0MEoK+IZA7DAAQFOw+yfZ9ESWvITiCCA+phjUig9fTt8yCEn4TARiVinC1P
WW8jGLGsNo02QDlA+TWwJBAMQ94TfRkC0q8V1JoYnaq6PtdYGN+5x5uylwqBNKfZQ
ratRGJubPPQ0P23A14tuavnPYxvC6wtvCzgn37PwFDTsCQAXXNuBV2w4kqItKPuNy
lGS4yyrXcmCHXhfqg+tnf898lMrxuPF2OkLgijMFDBQvwZ6BFDZrQ+jPfwo0ooX7b
+8=
-----END PRIVATE KEY-----
<!--
* 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.
-->
<!DOCTYPE html>
<html>
<body>
<script src="index.js"></script>
</body>
</html>
// 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.
onload = function() {
chrome.test.sendMessage('launched');
};
// 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.
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', {});
});
chrome.runtime.onMessageExternal.addListener(function(msg, sender, callback) {
callback('ack_message');
});
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
port.postMessage("ack_connect_message");
});
});
{
"name": "Ephemeral Apps Messaging - Receiver",
"version": "1.0",
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDyBCSykn0spD/xVTRweiDaEHMfqcP1VaZi/M1g5Dr3PRf718dCThQkn1STseF8IMHTbNfH91Lynumk5oBgo7TerBP7PXUmH76yyL02lR/z/XKTHq9xvl2g2YZ75uxa9H2Xp5l9PiCr6oQgQx+BU4DyV77u/cx66x3YVeRoamPPzQIDAQAB",
"app": {
"background": {
"scripts": ["main.js"]
}
}
}
// 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.
var targetAppId = 'idjoelgddomapgkhmbcoejocojmpjnme';
// Send a message to the ephemeral app and expect no response.
function testSendMessage() {
chrome.runtime.sendMessage(targetAppId, 'hello', null, function(response) {
if (response)
chrome.test.fail();
else
chrome.test.succeed();
});
}
// Open a port to the ephemeral app and expect this to fail.
function testConnect() {
var port = chrome.runtime.connect(targetAppId);
var gotResponse = false;
port.onMessage.addListener(function(response) {
gotResponse = true;
chrome.test.fail();
port.disconnect();
});
port.onDisconnect.addListener(function() {
if (gotResponse)
chrome.test.fail();
else
chrome.test.succeed();
});
port.postMessage('hello');
}
chrome.app.runtime.onLaunched.addListener(function() {
chrome.test.runTests([
testSendMessage,
testConnect
]);
});
{
"name": "Ephemeral Apps Messaging - Sender Fail",
"version": "1.0",
"manifest_version": 2,
"app": {
"background": {
"scripts": ["main.js"]
}
}
}
// 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.
var targetAppId = 'idjoelgddomapgkhmbcoejocojmpjnme';
// Send a message to the ephemeral app and expect a response.
function testSendMessage() {
chrome.runtime.sendMessage(targetAppId, 'hello', null, function(response) {
if (response == 'ack_message')
chrome.test.succeed();
else
chrome.test.fail();
});
}
// Open a port to the ephemeral app, send a message and expect a response.
function testConnect() {
var port = chrome.runtime.connect(targetAppId);
var gotResponse = false;
port.onMessage.addListener(function(response) {
if (response == 'ack_connect_message') {
chrome.test.succeed();
gotResponse = true;
} else {
chrome.test.fail();
}
port.disconnect();
});
port.onDisconnect.addListener(function() {
if (gotResponse)
chrome.test.succeed();
else
chrome.test.fail();
});
port.postMessage('hello');
}
chrome.app.runtime.onLaunched.addListener(function() {
chrome.test.runTests([
testSendMessage,
testConnect
]);
});
{
"name": "Ephemeral Apps Messaging - Sender Success",
"version": "1.0",
"manifest_version": 2,
"app": {
"background": {
"scripts": ["main.js"]
}
}
}
......@@ -576,6 +576,15 @@ bool EventRouter::MaybeLoadLazyBackgroundPageToDispatchEvent(
BrowserContext* context,
const Extension* extension,
const linked_ptr<Event>& event) {
if (extension->is_ephemeral() && !event->can_load_ephemeral_apps) {
// Most events can only be dispatched to ephemeral apps that are already
// running.
ProcessManager* pm =
ExtensionSystem::GetForBrowserContext(context)->process_manager();
if (!pm->GetBackgroundHostForExtension(extension->id()))
return false;
}
if (!CanDispatchEventToBrowserContext(context, extension, event))
return false;
......@@ -723,7 +732,8 @@ Event::Event(const std::string& event_name,
: event_name(event_name),
event_args(event_args.Pass()),
restrict_to_browser_context(NULL),
user_gesture(EventRouter::USER_GESTURE_UNKNOWN) {
user_gesture(EventRouter::USER_GESTURE_UNKNOWN),
can_load_ephemeral_apps(false) {
DCHECK(this->event_args.get());
}
......@@ -733,7 +743,8 @@ Event::Event(const std::string& event_name,
: event_name(event_name),
event_args(event_args.Pass()),
restrict_to_browser_context(restrict_to_browser_context),
user_gesture(EventRouter::USER_GESTURE_UNKNOWN) {
user_gesture(EventRouter::USER_GESTURE_UNKNOWN),
can_load_ephemeral_apps(false) {
DCHECK(this->event_args.get());
}
......@@ -748,7 +759,8 @@ Event::Event(const std::string& event_name,
restrict_to_browser_context(restrict_to_browser_context),
event_url(event_url),
user_gesture(user_gesture),
filter_info(filter_info) {
filter_info(filter_info),
can_load_ephemeral_apps(false) {
DCHECK(this->event_args.get());
}
......
......@@ -338,6 +338,12 @@ struct Event {
// DispatchEvent, so callers don't need to worry about lifetime.
WillDispatchCallback will_dispatch_callback;
// If true, this event will always be dispatched to ephemeral apps, regardless
// of whether they are running or inactive. Defaults to false.
// Most events can only be dispatched to ephemeral apps that are already
// running. Cached ephemeral apps are inactive until launched by the user.
bool can_load_ephemeral_apps;
Event(const std::string& event_name,
scoped_ptr<base::ListValue> event_args);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment