blob: dca40a91b5270d1f592439e071bbd88147ad2381 [file] [log] [blame]
// Copyright 2016 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 "base/callback_helpers.h"
#include "base/run_loop.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/pref_registry/testing_pref_service_syncable.h"
#include "extensions/browser/api/runtime/runtime_api.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/api_unittest.h"
#include "extensions/browser/test_extensions_browser_client.h"
#include "extensions/browser/test_runtime_api_delegate.h"
#include "extensions/common/manifest.h"
namespace extensions {
namespace {
// A RuntimeAPIDelegate that simulates a successful restart request every time.
class DelayedRestartTestApiDelegate : public TestRuntimeAPIDelegate {
public:
DelayedRestartTestApiDelegate() {}
~DelayedRestartTestApiDelegate() override {}
// TestRuntimeAPIDelegate:
bool RestartDevice(std::string* error_message) override {
if (!quit_closure_.is_null())
base::ResetAndReturn(&quit_closure_).Run();
*error_message = "Success.";
restart_done_ = true;
return true;
}
base::TimeTicks WaitForSuccessfulRestart() {
if (!restart_done_) {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
restart_done_ = false;
return base::TimeTicks::Now();
}
private:
base::Closure quit_closure_;
bool restart_done_ = false;
DISALLOW_COPY_AND_ASSIGN(DelayedRestartTestApiDelegate);
};
class DelayedRestartExtensionsBrowserClient
: public TestExtensionsBrowserClient {
public:
DelayedRestartExtensionsBrowserClient(content::BrowserContext* context)
: TestExtensionsBrowserClient(context) {}
~DelayedRestartExtensionsBrowserClient() override {}
// TestExtensionsBrowserClient:
PrefService* GetPrefServiceForContext(
content::BrowserContext* context) override {
return &testing_pref_service_;
}
std::unique_ptr<RuntimeAPIDelegate> CreateRuntimeAPIDelegate(
content::BrowserContext* context) const override {
const_cast<DelayedRestartExtensionsBrowserClient*>(this)->api_delegate_ =
new DelayedRestartTestApiDelegate();
return base::WrapUnique(api_delegate_);
}
user_prefs::TestingPrefServiceSyncable* testing_pref_service() {
return &testing_pref_service_;
}
DelayedRestartTestApiDelegate* api_delegate() const {
CHECK(api_delegate_);
return api_delegate_;
}
private:
DelayedRestartTestApiDelegate* api_delegate_ = nullptr; // Not owned.
user_prefs::TestingPrefServiceSyncable testing_pref_service_;
DISALLOW_COPY_AND_ASSIGN(DelayedRestartExtensionsBrowserClient);
};
} // namespace
class RestartAfterDelayApiTest : public ApiUnitTest {
public:
RestartAfterDelayApiTest() {}
~RestartAfterDelayApiTest() override {}
void SetUp() override {
ApiUnitTest::SetUp();
// Use our ExtensionsBrowserClient that returns our RuntimeAPIDelegate.
test_browser_client_.reset(
new DelayedRestartExtensionsBrowserClient(browser_context()));
test_browser_client_->set_extension_system_factory(
extensions_browser_client()->extension_system_factory());
ExtensionsBrowserClient::Set(test_browser_client_.get());
// The RuntimeAPI should only be accessed (i.e. constructed) after the above
// ExtensionsBrowserClient has been setup.
RuntimeAPI* runtime_api =
RuntimeAPI::GetFactoryInstance()->Get(browser_context());
runtime_api->set_min_duration_between_restarts_for_testing(
base::TimeDelta::FromSeconds(2));
runtime_api->AllowNonKioskAppsInRestartAfterDelayForTesting();
RuntimeAPI::RegisterPrefs(
test_browser_client_->testing_pref_service()->registry());
}
base::TimeTicks WaitForSuccessfulRestart() {
return test_browser_client_->api_delegate()->WaitForSuccessfulRestart();
}
bool IsDelayedRestartTimerRunning() {
return RuntimeAPI::GetFactoryInstance()
->Get(browser_context())
->restart_after_delay_timer_.IsRunning();
}
base::TimeTicks desired_restart_time() {
return RuntimeAPI::GetFactoryInstance()
->Get(browser_context())
->restart_after_delay_timer_.desired_run_time();
}
void RunRestartAfterDelayFunction(const std::string& args,
const std::string& expected_error) {
RunRestartAfterDelayFunctionForExtention(args, extension(), expected_error);
}
void RunRestartAfterDelayFunctionForExtention(
const std::string& args,
const Extension* extension,
const std::string& expected_error) {
std::string error = RunFunctionGetError(
new RuntimeRestartAfterDelayFunction(), extension, args);
ASSERT_EQ(error, expected_error);
}
void RunRestartFunctionAssertNoError() {
std::string error =
RunFunctionGetError(new RuntimeRestartFunction(), extension(), "[]");
ASSERT_TRUE(error.empty()) << error;
}
private:
std::string RunFunctionGetError(UIThreadExtensionFunction* function,
const Extension* extension,
const std::string& args) {
scoped_refptr<ExtensionFunction> function_owner(function);
function->set_extension(extension);
function->set_has_callback(true);
api_test_utils::RunFunction(function, args, browser_context());
return function->GetError();
}
std::unique_ptr<DelayedRestartExtensionsBrowserClient> test_browser_client_;
DISALLOW_COPY_AND_ASSIGN(RestartAfterDelayApiTest);
};
TEST_F(RestartAfterDelayApiTest, RestartAfterDelayTest) {
RunRestartAfterDelayFunction("[-1]", "");
ASSERT_FALSE(IsDelayedRestartTimerRunning());
RunRestartAfterDelayFunction("[-2]", "Invalid argument: -2.");
// Request a restart after 3 seconds.
base::TimeTicks now = base::TimeTicks::Now();
RunRestartAfterDelayFunction("[3]", "");
ASSERT_TRUE(IsDelayedRestartTimerRunning());
ASSERT_GE(desired_restart_time() - now, base::TimeDelta::FromSeconds(3));
// Request another restart after 4 seconds. It should reschedule the previous
// request.
now = base::TimeTicks::Now();
RunRestartAfterDelayFunction("[4]", "");
ASSERT_TRUE(IsDelayedRestartTimerRunning());
ASSERT_GE(desired_restart_time() - now, base::TimeDelta::FromSeconds(4));
// Create another extension and make it attempt to use the api, and expect a
// failure.
std::unique_ptr<base::DictionaryValue> test_extension_value(
api_test_utils::ParseDictionary("{\n"
" \"name\": \"Test\",\n"
" \"version\": \"2.0\",\n"
" \"app\": {\n"
" \"background\": {\n"
" \"scripts\": [\"background.js\"]\n"
" }\n"
" }\n"
"}"));
scoped_refptr<Extension> test_extension(api_test_utils::CreateExtension(
Manifest::INTERNAL, test_extension_value.get(), "id2"));
RunRestartAfterDelayFunctionForExtention(
"[5]", test_extension.get(), "Not the first extension to call this API.");
// Cancel restart requests.
RunRestartAfterDelayFunction("[-1]", "");
ASSERT_FALSE(IsDelayedRestartTimerRunning());
// Schedule a restart and wait for it to happen.
now = base::TimeTicks::Now();
RunRestartAfterDelayFunction("[1]", "");
ASSERT_TRUE(IsDelayedRestartTimerRunning());
ASSERT_GE(desired_restart_time() - now, base::TimeDelta::FromSeconds(1));
base::TimeTicks last_restart_time = WaitForSuccessfulRestart();
ASSERT_FALSE(IsDelayedRestartTimerRunning());
ASSERT_GE(base::TimeTicks::Now() - now, base::TimeDelta::FromSeconds(1));
// This is a restart request that will be throttled, because it happens too
// soon after a successful restart.
RunRestartAfterDelayFunction(
"[1]", "Restart was requested too soon. It was throttled instead.");
ASSERT_TRUE(IsDelayedRestartTimerRunning());
// Restart will happen 2 seconds later, even though the request was just one
// second.
ASSERT_NEAR((desired_restart_time() - last_restart_time).InSecondsF(),
base::TimeDelta::FromSeconds(2).InSecondsF(), 0.5);
// Calling chrome.runtime.restart() will not clear the throttle, and any
// subsequent calls to chrome.runtime.restartAfterDelay will still be
// throttled.
WaitForSuccessfulRestart();
RunRestartFunctionAssertNoError();
last_restart_time = WaitForSuccessfulRestart();
RunRestartAfterDelayFunction(
"[1]", "Restart was requested too soon. It was throttled instead.");
ASSERT_TRUE(IsDelayedRestartTimerRunning());
// Restart will happen 2 seconds later, even though the request was just one
// second.
ASSERT_NEAR((desired_restart_time() - last_restart_time).InSecondsF(),
base::TimeDelta::FromSeconds(2).InSecondsF(), 0.5);
}
} // namespace extensions