blob: 6b216dff3dbd71ec152d2f6dd54e497dc134c1f0 [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 "chrome/browser/chromeos/login/ui/login_feedback.h"
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/extensions/api/feedback_private/feedback_private_api.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/grit/browser_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_observer.h"
#include "extensions/common/extension.h"
#include "ui/aura/window.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"
namespace chromeos {
namespace {
extensions::ComponentLoader* GetComponentLoader(
content::BrowserContext* context) {
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(context);
ExtensionService* extension_service = extension_system->extension_service();
return extension_service->component_loader();
}
extensions::ProcessManager* GetProcessManager(
content::BrowserContext* context) {
return extensions::ProcessManager::Get(context);
}
// Ensures that the feedback extension is loaded on the signin profile and
// invokes the callback when the extension is ready to use. Unload the
// extension and delete itself when the extension's background page shuts down.
class FeedbackExtensionLoader : public extensions::ProcessManagerObserver,
public content::WebContentsObserver {
public:
// Loads the feedback extension on the given profile and invokes
// |on_ready_callback| when it is ready.
static void Load(Profile* profile, const base::Closure& on_ready_callback);
private:
explicit FeedbackExtensionLoader(Profile* profile);
~FeedbackExtensionLoader() override;
void Initialize();
void AddOnReadyCallback(const base::Closure& on_ready_callback);
void RunOnReadyCallbacks();
// extensions::ProcessManagerObserver
void OnBackgroundHostCreated(extensions::ExtensionHost* host) override;
void OnBackgroundHostClose(const std::string& extension_id) override;
// content::WebContentsObserver
void DocumentOnLoadCompletedInMainFrame() override;
Profile* const profile_;
std::vector<base::Closure> on_ready_callbacks_;
bool ready_ = false;
DISALLOW_COPY_AND_ASSIGN(FeedbackExtensionLoader);
};
// Current live instance of FeedbackExtensionLoader.
FeedbackExtensionLoader* instance = nullptr;
// static
void FeedbackExtensionLoader::Load(Profile* profile,
const base::Closure& on_ready_callback) {
if (instance == nullptr) {
instance = new FeedbackExtensionLoader(profile);
instance->Initialize();
}
DCHECK_EQ(instance->profile_, profile);
DCHECK(!on_ready_callback.is_null());
instance->AddOnReadyCallback(on_ready_callback);
}
FeedbackExtensionLoader::FeedbackExtensionLoader(Profile* profile)
: profile_(profile) {}
FeedbackExtensionLoader::~FeedbackExtensionLoader() {
DCHECK_EQ(instance, this);
instance = nullptr;
GetProcessManager(profile_)->RemoveObserver(this);
GetComponentLoader(profile_)->Remove(extension_misc::kFeedbackExtensionId);
}
void FeedbackExtensionLoader::Initialize() {
extensions::ProcessManager* pm = GetProcessManager(profile_);
pm->AddObserver(this);
extensions::ExtensionHost* const host =
pm->GetBackgroundHostForExtension(extension_misc::kFeedbackExtensionId);
if (host) {
OnBackgroundHostCreated(host);
if (!host->host_contents()->IsLoading())
DocumentOnLoadCompletedInMainFrame();
return;
}
extensions::ComponentLoader* component_loader = GetComponentLoader(profile_);
if (!component_loader->Exists(extension_misc::kFeedbackExtensionId)) {
component_loader->Add(IDR_FEEDBACK_MANIFEST,
base::FilePath(FILE_PATH_LITERAL("feedback")));
}
}
void FeedbackExtensionLoader::AddOnReadyCallback(
const base::Closure& on_ready_callback) {
on_ready_callbacks_.push_back(on_ready_callback);
if (ready_)
RunOnReadyCallbacks();
}
void FeedbackExtensionLoader::RunOnReadyCallbacks() {
std::vector<base::Closure> callbacks;
callbacks.swap(on_ready_callbacks_);
for (const auto& callback : callbacks)
callback.Run();
}
void FeedbackExtensionLoader::OnBackgroundHostCreated(
extensions::ExtensionHost* host) {
if (host->extension_id() == extension_misc::kFeedbackExtensionId)
Observe(host->host_contents());
}
void FeedbackExtensionLoader::OnBackgroundHostClose(
const std::string& extension_id) {
if (extension_id == extension_misc::kFeedbackExtensionId)
delete this;
}
void FeedbackExtensionLoader::DocumentOnLoadCompletedInMainFrame() {
ready_ = true;
RunOnReadyCallbacks();
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// LoginFeedback::FeedbackWindowHandler
class LoginFeedback::FeedbackWindowHandler
: public extensions::AppWindowRegistry::Observer {
public:
explicit FeedbackWindowHandler(LoginFeedback* owner);
~FeedbackWindowHandler() override;
bool HasFeedbackAppWindow() const;
// extensions::AppWindowRegistry::Observer
void OnAppWindowAdded(extensions::AppWindow* app_window) override;
void OnAppWindowRemoved(extensions::AppWindow* app_window) override;
private:
LoginFeedback* const owner_;
extensions::AppWindowRegistry* const window_registry_;
DISALLOW_COPY_AND_ASSIGN(FeedbackWindowHandler);
};
LoginFeedback::FeedbackWindowHandler::FeedbackWindowHandler(
LoginFeedback* owner)
: owner_(owner),
window_registry_(extensions::AppWindowRegistry::Get(owner_->profile_)) {
window_registry_->AddObserver(this);
}
LoginFeedback::FeedbackWindowHandler::~FeedbackWindowHandler() {
window_registry_->RemoveObserver(this);
}
bool LoginFeedback::FeedbackWindowHandler::HasFeedbackAppWindow() const {
return !window_registry_
->GetAppWindowsForApp(extension_misc::kFeedbackExtensionId)
.empty();
}
void LoginFeedback::FeedbackWindowHandler::OnAppWindowAdded(
extensions::AppWindow* app_window) {
if (app_window->extension_id() != extension_misc::kFeedbackExtensionId)
return;
// Move the feedback window to the same container as the login screen and make
// it a transient child of the login screen.
views::Widget::ReparentNativeView(
app_window->GetNativeWindow(),
LoginDisplayHost::default_host()->GetNativeWindow()->parent());
wm::AddTransientChild(LoginDisplayHost::default_host()->GetNativeWindow(),
app_window->GetNativeWindow());
}
void LoginFeedback::FeedbackWindowHandler::OnAppWindowRemoved(
extensions::AppWindow* app_window) {
if (app_window->extension_id() != extension_misc::kFeedbackExtensionId)
return;
if (!HasFeedbackAppWindow())
owner_->OnFeedbackFinished();
}
////////////////////////////////////////////////////////////////////////////////
// LoginFeedback
LoginFeedback::LoginFeedback(Profile* signin_profile)
: profile_(signin_profile), weak_factory_(this) {}
LoginFeedback::~LoginFeedback() {}
void LoginFeedback::Request(const std::string& description,
const base::Closure& finished_callback) {
description_ = description;
finished_callback_ = finished_callback;
feedback_window_handler_.reset(new FeedbackWindowHandler(this));
FeedbackExtensionLoader::Load(
profile_,
base::Bind(&LoginFeedback::EnsureFeedbackUI, weak_factory_.GetWeakPtr()));
// Triggers the extension background to be loaded.
EnsureFeedbackUI();
}
void LoginFeedback::EnsureFeedbackUI() {
// Bail if any feedback app window is opened.
if (feedback_window_handler_->HasFeedbackAppWindow())
return;
extensions::FeedbackPrivateAPI* api =
extensions::FeedbackPrivateAPI::GetFactoryInstance()->Get(profile_);
api->RequestFeedbackForFlow(
description_, "Login", GURL(),
extensions::api::feedback_private::FeedbackFlow::FEEDBACK_FLOW_LOGIN);
// Make sure there is a feedback app window opened.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&LoginFeedback::EnsureFeedbackUI, weak_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(1));
}
void LoginFeedback::OnFeedbackFinished() {
if (!finished_callback_.is_null())
finished_callback_.Run();
}
} // namespace chromeos