| // 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 |