blob: 3a95988135f85ae4042460ede48b57cecc0d5c7a [file] [log] [blame]
// Copyright 2017 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/renderer/bindings/api_binding_util.h"
#include "base/logging.h"
#include "base/observer_list.h"
#include "base/supports_user_data.h"
#include "build/build_config.h"
#include "extensions/renderer/bindings/get_per_context_data.h"
#include "extensions/renderer/bindings/js_runner.h"
#include "gin/converter.h"
#include "gin/per_context_data.h"
namespace extensions {
namespace binding {
namespace {
bool g_response_validation_enabled =
#if DCHECK_IS_ON()
true;
#else
false;
#endif
} // namespace
class ContextInvalidationData : public base::SupportsUserData::Data {
public:
ContextInvalidationData();
~ContextInvalidationData() override;
static constexpr char kPerContextDataKey[] = "extension_context_invalidation";
void Invalidate();
void AddListener(ContextInvalidationListener* listener);
void RemoveListener(ContextInvalidationListener* listener);
bool is_context_valid() const { return is_context_valid_; }
private:
bool is_context_valid_ = true;
base::ObserverList<ContextInvalidationListener>::Unchecked
invalidation_listeners_;
DISALLOW_COPY_AND_ASSIGN(ContextInvalidationData);
};
constexpr char ContextInvalidationData::kPerContextDataKey[];
ContextInvalidationData::ContextInvalidationData() = default;
ContextInvalidationData::~ContextInvalidationData() {
if (is_context_valid_)
Invalidate();
}
void ContextInvalidationData::AddListener(
ContextInvalidationListener* listener) {
DCHECK(is_context_valid_);
invalidation_listeners_.AddObserver(listener);
}
void ContextInvalidationData::RemoveListener(
ContextInvalidationListener* listener) {
DCHECK(is_context_valid_);
DCHECK(invalidation_listeners_.HasObserver(listener));
invalidation_listeners_.RemoveObserver(listener);
}
void ContextInvalidationData::Invalidate() {
DCHECK(is_context_valid_);
is_context_valid_ = false;
for (ContextInvalidationListener& listener : invalidation_listeners_)
listener.OnInvalidated();
}
bool IsContextValid(v8::Local<v8::Context> context) {
gin::PerContextData* per_context_data = gin::PerContextData::From(context);
if (!per_context_data)
return false;
auto* invalidation_data =
static_cast<ContextInvalidationData*>(per_context_data->GetUserData(
ContextInvalidationData::kPerContextDataKey));
// The context is valid if we've never created invalidation data for it, or if
// we have and it hasn't been marked as invalid.
bool is_context_valid =
!invalidation_data || invalidation_data->is_context_valid();
if (is_context_valid) {
// As long as the context is valid, there should be an associated
// JSRunner.
// TODO(devlin): (Likely) Remove this once https://crbug.com/819968, since
// this shouldn't necessarily be a hard dependency. At least downgrade it
// to a DCHECK.
CHECK(JSRunner::Get(context));
}
return is_context_valid;
}
bool IsContextValidOrThrowError(v8::Local<v8::Context> context) {
if (IsContextValid(context))
return true;
v8::Isolate* isolate = context->GetIsolate();
isolate->ThrowException(v8::Exception::Error(
gin::StringToV8(isolate, "Extension context invalidated.")));
return false;
}
void InvalidateContext(v8::Local<v8::Context> context) {
ContextInvalidationData* data =
GetPerContextData<ContextInvalidationData>(context, kCreateIfMissing);
if (!data)
return;
data->Invalidate();
}
std::string GetPlatformString() {
#if defined(OS_CHROMEOS)
return "chromeos";
#elif defined(OS_LINUX)
return "linux";
#elif defined(OS_MACOSX)
return "mac";
#elif defined(OS_WIN)
return "win";
#else
NOTREACHED();
return std::string();
#endif
}
ContextInvalidationListener::ContextInvalidationListener(
v8::Local<v8::Context> context,
base::OnceClosure on_invalidated)
: on_invalidated_(std::move(on_invalidated)),
context_invalidation_data_(
GetPerContextData<ContextInvalidationData>(context,
kCreateIfMissing)) {
// We should never add an invalidation observer to an invalid context.
DCHECK(context_invalidation_data_);
DCHECK(context_invalidation_data_->is_context_valid());
context_invalidation_data_->AddListener(this);
}
ContextInvalidationListener::~ContextInvalidationListener() {
if (!on_invalidated_)
return; // Context was invalidated.
DCHECK(context_invalidation_data_);
context_invalidation_data_->RemoveListener(this);
}
void ContextInvalidationListener::OnInvalidated() {
DCHECK(on_invalidated_);
context_invalidation_data_ = nullptr;
std::move(on_invalidated_).Run();
}
bool IsResponseValidationEnabled() {
return g_response_validation_enabled;
}
std::unique_ptr<base::AutoReset<bool>> SetResponseValidationEnabledForTesting(
bool is_enabled) {
return std::make_unique<base::AutoReset<bool>>(&g_response_validation_enabled,
is_enabled);
}
} // namespace binding
} // namespace extensions