blob: 791631fe98aefb88b9b99c7effdfd12fec8f2dfe [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 "extensions/test/extension_test_notification_observer.h"
#include <memory>
#include "base/bind.h"
#include "base/containers/contains.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/notification_types.h"
#include "extensions/common/extension.h"
namespace extensions {
namespace {
// A callback that returns true if the condition has been met and takes no
// arguments.
using ConditionCallback = base::RepeatingCallback<bool(void)>;
const Extension* GetNonTerminatedExtensions(const std::string& id,
content::BrowserContext* context) {
return ExtensionRegistry::Get(context)->GetExtensionById(
id, ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::TERMINATED);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// NotificationSet::ForwardingWebContentsObserver
class ExtensionTestNotificationObserver::NotificationSet::
ForwardingWebContentsObserver : public content::WebContentsObserver {
public:
ForwardingWebContentsObserver(
content::WebContents* contents,
ExtensionTestNotificationObserver::NotificationSet* owner)
: WebContentsObserver(contents), owner_(owner) {}
private:
// content::WebContentsObserver
void WebContentsDestroyed() override {
// Do not add code after this line, deletes `this`.
owner_->WebContentsDestroyed(web_contents());
}
ExtensionTestNotificationObserver::NotificationSet* owner_;
};
////////////////////////////////////////////////////////////////////////////////
// ExtensionTestNotificationObserver::NotificationSet
ExtensionTestNotificationObserver::NotificationSet::NotificationSet() = default;
ExtensionTestNotificationObserver::NotificationSet::~NotificationSet() =
default;
void ExtensionTestNotificationObserver::NotificationSet::Add(
int type,
const content::NotificationSource& source) {
notification_registrar_.Add(this, type, source);
}
void ExtensionTestNotificationObserver::NotificationSet::Add(int type) {
Add(type, content::NotificationService::AllSources());
}
void ExtensionTestNotificationObserver::NotificationSet::
AddExtensionFrameUnregistration(ProcessManager* manager) {
process_manager_observation_.Observe(manager);
}
void ExtensionTestNotificationObserver::NotificationSet::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
closure_list_.Notify();
}
void ExtensionTestNotificationObserver::NotificationSet::
AddWebContentsDestroyed(extensions::ProcessManager* manager) {
for (content::RenderFrameHost* render_frame_host : manager->GetAllFrames()) {
content::WebContents* contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
if (!base::Contains(web_contents_observers_, contents)) {
web_contents_observers_[contents] =
std::make_unique<ForwardingWebContentsObserver>(contents, this);
}
}
}
void ExtensionTestNotificationObserver::NotificationSet::
OnExtensionFrameUnregistered(const std::string& extension_id,
content::RenderFrameHost* render_frame_host) {
closure_list_.Notify();
}
void ExtensionTestNotificationObserver::NotificationSet::WebContentsDestroyed(
content::WebContents* web_contents) {
web_contents_observers_.erase(web_contents);
closure_list_.Notify();
}
////////////////////////////////////////////////////////////////////////////////
// ExtensionTestNotificationObserver
ExtensionTestNotificationObserver::ExtensionTestNotificationObserver(
content::BrowserContext* context)
: context_(context),
crx_installers_done_observed_(0) {
if (context_)
registry_observation_.Observe(ExtensionRegistry::Get(context_));
}
ExtensionTestNotificationObserver::~ExtensionTestNotificationObserver() {}
void ExtensionTestNotificationObserver::WaitForNotification(
int notification_type) {
// TODO(bauerb): Using a WindowedNotificationObserver like this can break
// easily, if the notification we're waiting for is sent before this method.
// Change it so that the WindowedNotificationObserver is constructed earlier.
content::NotificationRegistrar registrar;
registrar.Add(this, notification_type,
content::NotificationService::AllSources());
content::WindowedNotificationObserver(
notification_type, content::NotificationService::AllSources())
.Wait();
}
bool ExtensionTestNotificationObserver::WaitForExtensionCrash(
const std::string& extension_id) {
if (!GetNonTerminatedExtensions(extension_id, context_)) {
// The extension is already unloaded, presumably due to a crash.
return true;
}
content::WindowedNotificationObserver(
NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
content::NotificationService::AllSources())
.Wait();
// GetNonTerminatedExtensions consults ExtensionRegistry which gets updated
// asynchronously in a task posted when
// NOTIFICATION_EXTENSION_PROCESS_TERMINATED is handled, so let this task run.
base::RunLoop().RunUntilIdle();
return (GetNonTerminatedExtensions(extension_id, context_) == NULL);
}
bool ExtensionTestNotificationObserver::WaitForCrxInstallerDone() {
int before = crx_installers_done_observed_;
WaitForNotification(NOTIFICATION_CRX_INSTALLER_DONE);
return crx_installers_done_observed_ == before + 1 &&
!last_loaded_extension_id_.empty();
}
void ExtensionTestNotificationObserver::Watch(
int type,
const content::NotificationSource& source) {
CHECK(!observer_);
observer_ =
std::make_unique<content::WindowedNotificationObserver>(type, source);
registrar_.Add(this, type, source);
}
void ExtensionTestNotificationObserver::Wait() {
observer_->Wait();
registrar_.RemoveAll();
observer_.reset();
}
void ExtensionTestNotificationObserver::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case NOTIFICATION_CRX_INSTALLER_DONE:
VLOG(1) << "Got CRX_INSTALLER_DONE notification.";
{
const Extension* extension =
content::Details<const Extension>(details).ptr();
if (extension)
last_loaded_extension_id_ = extension->id();
else
last_loaded_extension_id_.clear();
}
++crx_installers_done_observed_;
break;
default:
NOTREACHED();
break;
}
}
void ExtensionTestNotificationObserver::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
last_loaded_extension_id_ = extension->id();
VLOG(1) << "Got EXTENSION_LOADED notification.";
}
void ExtensionTestNotificationObserver::OnShutdown(
ExtensionRegistry* registry) {
registry_observation_.Reset();
}
void ExtensionTestNotificationObserver::WaitForCondition(
const ConditionCallback& condition,
NotificationSet* notification_set) {
if (condition.Run())
return;
condition_ = condition;
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
base::CallbackListSubscription subscription;
if (notification_set) {
subscription = notification_set->closure_list().Add(base::BindRepeating(
&ExtensionTestNotificationObserver::MaybeQuit, base::Unretained(this)));
}
run_loop.Run();
condition_.Reset();
quit_closure_.Reset();
}
void ExtensionTestNotificationObserver::MaybeQuit() {
if (condition_.Run())
std::move(quit_closure_).Run();
}
} // namespace extensions