Commit c176d79e authored by Michael Giuffrida's avatar Michael Giuffrida Committed by Commit Bot

AppShell: Enable chrome.runtime.reload()

Allow apps to reload themselves, and ensure the ShellDesktopControllerAura
keeps app_shell alive long enough for the app to come back up.

Add browser tests for ShellDesktopControllerAura and
chrome.runtime.reload() in AppShell.

Bug: 762642,759867
Change-Id: If09ad83e6d3073461ebf8b53afa69a6c2aab7e15
Reviewed-on: https://chromium-review.googlesource.com/912752Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Commit-Queue: Michael Giuffrida <michaelpg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542261}
parent 4720a9cf
......@@ -95,6 +95,8 @@ source_set("app_shell_lib") {
"browser/api/feedback_private/shell_feedback_private_delegate.h",
"browser/api/identity/identity_api.cc",
"browser/api/identity/identity_api.h",
"browser/api/runtime/shell_runtime_api_delegate.cc",
"browser/api/runtime/shell_runtime_api_delegate.h",
"browser/default_shell_browser_main_delegate.cc",
"browser/default_shell_browser_main_delegate.h",
"browser/delegates/shell_kiosk_delegate.cc",
......@@ -161,8 +163,6 @@ source_set("app_shell_lib") {
"browser/shell_oauth2_token_service_delegate.h",
"browser/shell_prefs.cc",
"browser/shell_prefs.h",
"browser/shell_runtime_api_delegate.cc",
"browser/shell_runtime_api_delegate.h",
"browser/shell_special_storage_policy.cc",
"browser/shell_special_storage_policy.h",
"browser/shell_speech_recognition_manager_delegate.cc",
......@@ -456,6 +456,7 @@ if (is_mac) {
source_set("browser_tests") {
testonly = true
sources = [
"browser/api/runtime/runtime_apitest.cc",
"browser/geolocation/geolocation_apitest.cc",
"browser/shell_browsertest.cc",
"test/shell_apitest.cc",
......@@ -473,15 +474,18 @@ source_set("browser_tests") {
":app_shell_lib",
"//base",
"//base/test:test_support",
"//components/keep_alive_registry",
"//components/version_info",
"//content/shell:content_shell_lib",
"//content/test:test_support",
"//extensions:test_support",
"//extensions/browser",
"//extensions/browser:test_support",
"//extensions/common",
]
if (use_aura) {
sources += [ "browser/shell_desktop_controller_aura_browsertest.cc" ]
deps += [ "//ui/aura" ]
}
}
......
// Copyright 2018 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 "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/extension_id.h"
#include "extensions/shell/browser/shell_extension_system.h"
#include "extensions/shell/test/shell_apitest.h"
#include "extensions/test/result_catcher.h"
namespace extensions {
using ShellRuntimeApiTest = ShellApiTest;
IN_PROC_BROWSER_TEST_F(ShellRuntimeApiTest, RuntimeReload) {
const Extension* extension = nullptr;
// Load the extension and wait for it to be ready.
{
ResultCatcher catcher;
ASSERT_TRUE(extension = LoadExtension("extension"));
ASSERT_TRUE(catcher.GetNextResult());
}
const ExtensionId extension_id = extension->id();
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
// Reload the extension and wait for a pair of
// ExtensionRegistry::OnExtensionUnloaded()/Loaded() calls.
TestExtensionRegistryObserver registry_observer(registry, extension_id);
ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
browser_context(), extension_id, "chrome.runtime.reload();"));
ASSERT_EQ(extension, registry_observer.WaitForExtensionUnloaded());
EXPECT_TRUE(registry->disabled_extensions().Contains(extension_id));
ASSERT_TRUE(extension = registry_observer.WaitForExtensionLoaded());
ASSERT_EQ(extension->id(), extension_id);
EXPECT_TRUE(registry->enabled_extensions().Contains(extension_id));
// Wait for the background page to load.
{
ResultCatcher catcher;
ASSERT_TRUE(catcher.GetNextResult());
}
}
IN_PROC_BROWSER_TEST_F(ShellRuntimeApiTest, RuntimeReloadApp) {
const Extension* extension = nullptr;
// Load and launch the app and wait for it to create a window.
{
ResultCatcher catcher;
extension = LoadApp("platform_app");
ASSERT_TRUE(catcher.GetNextResult());
}
const ExtensionId extension_id = extension->id();
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
// Reload the extension and wait for a pair of
// ExtensionRegistry::OnExtensionUnloaded()/Loaded() calls.
TestExtensionRegistryObserver registry_observer(registry, extension_id);
ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
browser_context(), extension_id, "chrome.runtime.reload();"));
ASSERT_EQ(extension, registry_observer.WaitForExtensionUnloaded());
EXPECT_TRUE(registry->disabled_extensions().Contains(extension_id));
ASSERT_TRUE(extension = registry_observer.WaitForExtensionLoaded());
ASSERT_EQ(extension->id(), extension_id);
EXPECT_TRUE(registry->enabled_extensions().Contains(extension_id));
// Reloading the app should launch it again automatically.
// Wait for the app to create a new window.
{
ResultCatcher catcher;
ASSERT_TRUE(catcher.GetNextResult());
}
}
} // namespace extensions
......@@ -2,10 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/shell/browser/shell_runtime_api_delegate.h"
#include "extensions/shell/browser/api/runtime/shell_runtime_api_delegate.h"
#include "build/build_config.h"
#include "extensions/common/api/runtime.h"
#include "extensions/shell/browser/shell_extension_system.h"
#if defined(OS_CHROMEOS)
#include "chromeos/dbus/dbus_thread_manager.h"
......@@ -17,19 +18,21 @@ using extensions::api::runtime::PlatformInfo;
namespace extensions {
ShellRuntimeAPIDelegate::ShellRuntimeAPIDelegate() {
ShellRuntimeAPIDelegate::ShellRuntimeAPIDelegate(
content::BrowserContext* browser_context)
: browser_context_(browser_context) {
DCHECK(browser_context_);
}
ShellRuntimeAPIDelegate::~ShellRuntimeAPIDelegate() {
}
ShellRuntimeAPIDelegate::~ShellRuntimeAPIDelegate() = default;
void ShellRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) {
}
void ShellRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) {}
void ShellRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) {
}
void ShellRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) {}
void ShellRuntimeAPIDelegate::ReloadExtension(const std::string& extension_id) {
static_cast<ShellExtensionSystem*>(ExtensionSystem::Get(browser_context_))
->ReloadExtension(extension_id);
}
bool ShellRuntimeAPIDelegate::CheckForUpdates(
......@@ -38,8 +41,7 @@ bool ShellRuntimeAPIDelegate::CheckForUpdates(
return false;
}
void ShellRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {
}
void ShellRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {}
bool ShellRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) {
#if defined(OS_CHROMEOS)
......
......@@ -2,17 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
#define EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
#ifndef EXTENSIONS_SHELL_BROWSER_API_RUNTIME_SHELL_RUNTIME_API_DELEGATE_H_
#define EXTENSIONS_SHELL_BROWSER_API_RUNTIME_SHELL_RUNTIME_API_DELEGATE_H_
#include "base/macros.h"
#include "extensions/browser/api/runtime/runtime_api_delegate.h"
namespace content {
class BrowserContext;
} // namespace content
namespace extensions {
class ShellRuntimeAPIDelegate : public RuntimeAPIDelegate {
public:
ShellRuntimeAPIDelegate();
explicit ShellRuntimeAPIDelegate(content::BrowserContext* browser_context);
~ShellRuntimeAPIDelegate() override;
// RuntimeAPIDelegate implementation.
......@@ -26,9 +30,11 @@ class ShellRuntimeAPIDelegate : public RuntimeAPIDelegate {
bool RestartDevice(std::string* error_message) override;
private:
content::BrowserContext* browser_context_;
DISALLOW_COPY_AND_ASSIGN(ShellRuntimeAPIDelegate);
};
} // namespace extensions
#endif // EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
#endif // EXTENSIONS_SHELL_BROWSER_API_RUNTIME_SHELL_RUNTIME_API_DELEGATE_H_
......@@ -2,10 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/logging.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/notification_types.h"
......
......@@ -10,6 +10,7 @@
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "extensions/shell/browser/shell_app_window_client.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/window.h"
......@@ -161,10 +162,15 @@ ShellDesktopControllerAura::~ShellDesktopControllerAura() {
}
void ShellDesktopControllerAura::Run() {
KeepAliveRegistry::GetInstance()->AddObserver(this);
base::RunLoop run_loop;
run_loop_ = &run_loop;
run_loop.Run();
run_loop_ = nullptr;
KeepAliveRegistry::GetInstance()->SetIsShuttingDown(true);
KeepAliveRegistry::GetInstance()->RemoveObserver(this);
}
void ShellDesktopControllerAura::AddAppWindow(AppWindow* app_window,
......@@ -198,9 +204,7 @@ void ShellDesktopControllerAura::CloseRootWindowController(
TearDownRootWindowController(it->second.get());
root_window_controllers_.erase(it);
// run_loop_ may be null in tests.
if (run_loop_ && root_window_controllers_.empty())
run_loop_->QuitWhenIdle();
MaybeQuit();
}
#if defined(OS_CHROMEOS)
......@@ -234,6 +238,15 @@ ui::EventDispatchDetails ShellDesktopControllerAura::DispatchKeyEventPostIME(
return GetPrimaryHost()->DispatchKeyEventPostIME(key_event);
}
void ShellDesktopControllerAura::OnKeepAliveStateChanged(
bool is_keeping_alive) {
if (!is_keeping_alive)
MaybeQuit();
}
void ShellDesktopControllerAura::OnKeepAliveRestartStateChanged(
bool can_restart) {}
aura::WindowTreeHost* ShellDesktopControllerAura::GetPrimaryHost() {
if (root_window_controllers_.empty())
return nullptr;
......@@ -337,6 +350,19 @@ void ShellDesktopControllerAura::TearDownRootWindowController(
root->host()->window()->RemovePreTargetHandler(focus_controller_.get());
}
void ShellDesktopControllerAura::MaybeQuit() {
// run_loop_ may be null in tests.
if (!run_loop_)
return;
// Quit if there are no app windows open and no keep-alives waiting for apps
// to relaunch.
if (root_window_controllers_.empty() &&
!KeepAliveRegistry::GetInstance()->IsKeepingAlive()) {
run_loop_->QuitWhenIdle();
}
}
#if defined(OS_CHROMEOS)
gfx::Size ShellDesktopControllerAura::GetStartingWindowSize() {
gfx::Size size;
......
......@@ -11,6 +11,7 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "build/build_config.h"
#include "components/keep_alive_registry/keep_alive_state_observer.h"
#include "extensions/shell/browser/desktop_controller.h"
#include "extensions/shell/browser/root_window_controller.h"
#include "ui/aura/window.h"
......@@ -69,7 +70,8 @@ class ShellDesktopControllerAura
public chromeos::PowerManagerClient::Observer,
public display::DisplayConfigurator::Observer,
#endif
public ui::internal::InputMethodDelegate {
public ui::internal::InputMethodDelegate,
public KeepAliveStateObserver {
public:
explicit ShellDesktopControllerAura(content::BrowserContext* browser_context);
~ShellDesktopControllerAura() override;
......@@ -84,19 +86,23 @@ class ShellDesktopControllerAura
RootWindowController* root_window_controller) override;
#if defined(OS_CHROMEOS)
// chromeos::PowerManagerClient::Observer overrides:
// chromeos::PowerManagerClient::Observer:
void PowerButtonEventReceived(bool down,
const base::TimeTicks& timestamp) override;
// display::DisplayConfigurator::Observer overrides.
// display::DisplayConfigurator::Observer:
void OnDisplayModeChanged(
const display::DisplayConfigurator::DisplayStateList& displays) override;
#endif
// ui::internal::InputMethodDelegate overrides:
// ui::internal::InputMethodDelegate:
ui::EventDispatchDetails DispatchKeyEventPostIME(
ui::KeyEvent* key_event) override;
// KeepAliveStateObserver:
void OnKeepAliveStateChanged(bool is_keeping_alive) override;
void OnKeepAliveRestartStateChanged(bool can_restart) override;
// Returns the WindowTreeHost for the primary display.
aura::WindowTreeHost* GetPrimaryHost();
......@@ -119,6 +125,10 @@ class ShellDesktopControllerAura
// Removes handlers from the RootWindowController so it can be destroyed.
void TearDownRootWindowController(RootWindowController* root);
// Quits if there are no app windows, and no keep-alives waiting for apps to
// relaunch.
void MaybeQuit();
#if defined(OS_CHROMEOS)
// Returns the desired dimensions of the RootWindowController from the command
// line, or falls back to a default size.
......
// Copyright 2018 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 "extensions/shell/browser/shell_desktop_controller_aura.h"
#include "base/macros.h"
#include "base/task_scheduler/post_task.h"
#include "base/test/bind_test_util.h"
#include "base/time/time.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/shell/browser/desktop_controller.h"
#include "extensions/shell/test/shell_apitest.h"
#include "extensions/test/result_catcher.h"
namespace extensions {
// Tests that spin up the ShellDesktopControllerAura and run async tasks like
// launching and reloading apps.
class ShellDesktopControllerAuraBrowserTest : public ShellApiTest {
public:
ShellDesktopControllerAuraBrowserTest() = default;
~ShellDesktopControllerAuraBrowserTest() override = default;
// Loads and launches a platform app that opens an app window.
void LoadAndLaunchApp() {
ASSERT_FALSE(app_);
app_ = LoadApp("platform_app");
ASSERT_TRUE(app_);
// Wait for app window to load.
ResultCatcher catcher;
EXPECT_TRUE(catcher.GetNextResult());
// A window was created.
EXPECT_EQ(1u,
AppWindowRegistry::Get(browser_context())->app_windows().size());
}
protected:
// Returns an open app window.
AppWindow* GetAppWindow() {
EXPECT_GT(AppWindowRegistry::Get(browser_context())->app_windows().size(),
0u);
return AppWindowRegistry::Get(browser_context())->app_windows().front();
}
// ShellApiTest:
void SetUpOnMainThread() override {
ShellApiTest::SetUpOnMainThread();
desktop_controller_ =
static_cast<ShellDesktopControllerAura*>(DesktopController::instance());
ASSERT_TRUE(desktop_controller_);
}
void TearDownOnMainThread() override {
EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
ShellApiTest::TearDownOnMainThread();
}
ShellDesktopControllerAura* desktop_controller_ = nullptr;
scoped_refptr<const Extension> app_;
private:
DISALLOW_COPY_AND_ASSIGN(ShellDesktopControllerAuraBrowserTest);
};
// Test that closing the app window stops the DesktopController.
IN_PROC_BROWSER_TEST_F(ShellDesktopControllerAuraBrowserTest, CloseAppWindow) {
bool test_succeeded = false;
// Post a task so everything runs after the DesktopController starts.
base::ThreadTaskRunnerHandle::Get()->PostTaskAndReply(
FROM_HERE,
// Asynchronously launch the app.
base::BindOnce(&ShellDesktopControllerAuraBrowserTest::LoadAndLaunchApp,
base::Unretained(this)),
// Once the app launches, run the test.
base::BindLambdaForTesting([this, &test_succeeded]() {
// Close the app window so DesktopController quits.
GetAppWindow()->OnNativeClose();
test_succeeded = true;
}));
// Start DesktopController. It should run until the last app window closes.
desktop_controller_->Run();
EXPECT_TRUE(test_succeeded)
<< "DesktopController quit before test completed.";
}
// Test that the DesktopController runs until all app windows close.
IN_PROC_BROWSER_TEST_F(ShellDesktopControllerAuraBrowserTest, TwoAppWindows) {
bool test_succeeded = false;
// Post a task so everything runs after the DesktopController starts.
base::ThreadTaskRunnerHandle::Get()->PostTaskAndReply(
FROM_HERE,
// Asynchronously launch the app.
base::BindOnce(&ShellDesktopControllerAuraBrowserTest::LoadAndLaunchApp,
base::Unretained(this)),
// Once the app launches, run the test.
base::BindLambdaForTesting([this, &test_succeeded]() {
// Create a second app window.
ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
browser_context(), app_->id(),
"chrome.app.window.create('/hello.html');"));
ResultCatcher catcher;
catcher.GetNextResult();
// Close the first app window.
GetAppWindow()->OnNativeClose();
// One window is still open, so the DesktopController should still be
// running. Post a task to close the last window.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::BindLambdaForTesting([this, &test_succeeded]() {
GetAppWindow()->OnNativeClose();
test_succeeded = true;
}),
// A regression might cause DesktopController to quit before the
// last window closes. To ensure we catch this, wait a while before
// closing the last window. If DesktopController::Run() finishes
// before we close the last window and update |test_succeeded|, the
// test fails.
base::TimeDelta::FromMilliseconds(500));
}));
desktop_controller_->Run();
EXPECT_TRUE(test_succeeded)
<< "DesktopController quit before test completed.";
}
// Test that the DesktopController stays open while an app reloads, even though
// the app window closes.
IN_PROC_BROWSER_TEST_F(ShellDesktopControllerAuraBrowserTest, ReloadApp) {
bool test_succeeded = false;
// Post a task so everything runs after the DesktopController starts.
base::ThreadTaskRunnerHandle::Get()->PostTaskAndReply(
FROM_HERE,
// Asynchronously launch the app.
base::BindOnce(&ShellDesktopControllerAuraBrowserTest::LoadAndLaunchApp,
base::Unretained(this)),
// Once the app launches, run the test.
base::BindLambdaForTesting([this, &test_succeeded]() {
// Reload the app.
ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
browser_context(), app_->id(), "chrome.runtime.reload();"));
// Wait for the app window to re-open.
ResultCatcher catcher;
ASSERT_TRUE(catcher.GetNextResult());
// Close the new window after a delay. DesktopController should remain
// open until the window closes.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::BindLambdaForTesting([this, &test_succeeded]() {
AppWindow* app_window = AppWindowRegistry::Get(browser_context())
->app_windows()
.front();
app_window->OnNativeClose();
test_succeeded = true;
}),
base::TimeDelta::FromMilliseconds(500));
}));
desktop_controller_->Run();
EXPECT_TRUE(test_succeeded)
<< "DesktopController quit before test completed.";
}
} // namespace extensions
......@@ -70,6 +70,10 @@ void ShellExtensionSystem::LaunchApp(const ExtensionId& extension_id) {
apps::LaunchPlatformApp(browser_context_, extension, SOURCE_UNTRACKED);
}
void ShellExtensionSystem::ReloadExtension(const ExtensionId& extension_id) {
extension_loader_->ReloadExtension(extension_id);
}
void ShellExtensionSystem::Shutdown() {
extension_loader_.reset();
}
......
......@@ -52,6 +52,9 @@ class ShellExtensionSystem : public ExtensionSystem {
// Launch the app with id |extension_id|.
void LaunchApp(const ExtensionId& extension_id);
// Reloads the extension with id |extension_id|.
void ReloadExtension(const ExtensionId& extension_id);
// KeyedService implementation:
void Shutdown() override;
......
......@@ -23,13 +23,13 @@
#include "extensions/browser/url_request_util.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/shell/browser/api/generated_api_registration.h"
#include "extensions/shell/browser/api/runtime/shell_runtime_api_delegate.h"
#include "extensions/shell/browser/delegates/shell_kiosk_delegate.h"
#include "extensions/shell/browser/shell_extension_host_delegate.h"
#include "extensions/shell/browser/shell_extension_system_factory.h"
#include "extensions/shell/browser/shell_extension_web_contents_observer.h"
#include "extensions/shell/browser/shell_extensions_api_client.h"
#include "extensions/shell/browser/shell_navigation_ui_data.h"
#include "extensions/shell/browser/shell_runtime_api_delegate.h"
#if defined(OS_CHROMEOS)
#include "chromeos/login/login_state.h"
......@@ -229,7 +229,7 @@ void ShellExtensionsBrowserClient::RegisterExtensionInterfaces(
std::unique_ptr<RuntimeAPIDelegate>
ShellExtensionsBrowserClient::CreateRuntimeAPIDelegate(
content::BrowserContext* context) const {
return std::make_unique<ShellRuntimeAPIDelegate>();
return std::make_unique<ShellRuntimeAPIDelegate>(context);
}
const ComponentExtensionResourceManager*
......
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