| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/extension_function.h" |
| |
| #include <memory> |
| #include <numeric> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/dcheck_is_on.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/no_destructor.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/trace_event/memory_allocator_dump.h" |
| #include "base/trace_event/memory_dump_manager.h" |
| #include "base/trace_event/memory_dump_provider.h" |
| #include "base/trace_event/trace_event.h" |
| #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h" |
| #include "components/keyed_service/core/keyed_service_shutdown_notifier.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "extensions/browser/bad_message.h" |
| #include "extensions/browser/browser_frame_context_data.h" |
| #include "extensions/browser/browser_process_context_data.h" |
| #include "extensions/browser/extension_function_crash_keys.h" |
| #include "extensions/browser/extension_function_dispatcher.h" |
| #include "extensions/browser/extension_function_registry.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_util.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "extensions/browser/renderer_startup_helper.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension_api.h" |
| #include "extensions/common/mojom/renderer.mojom.h" |
| #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom-forward.h" |
| |
| using content::BrowserThread; |
| using content::WebContents; |
| using extensions::ErrorUtils; |
| using extensions::ExtensionAPI; |
| using extensions::Feature; |
| |
| namespace { |
| |
| class ExtensionFunctionMemoryDumpProvider |
| : public base::trace_event::MemoryDumpProvider { |
| public: |
| ExtensionFunctionMemoryDumpProvider() { |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| this, "ExtensionFunctions", |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| } |
| |
| ExtensionFunctionMemoryDumpProvider( |
| const ExtensionFunctionMemoryDumpProvider&) = delete; |
| ExtensionFunctionMemoryDumpProvider& operator=( |
| const ExtensionFunctionMemoryDumpProvider&) = delete; |
| ~ExtensionFunctionMemoryDumpProvider() override { |
| base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
| this); |
| } |
| |
| void AddFunctionName(const char* function_name) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(function_name); |
| auto it = function_map_.emplace(function_name, 0); |
| it.first->second++; |
| } |
| |
| void RemoveFunctionName(const char* function_name) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(function_name); |
| auto it = function_map_.find(function_name); |
| DCHECK(it != function_map_.end()); |
| DCHECK_GE(it->second, static_cast<uint64_t>(1)); |
| if (it->second == 1) { |
| function_map_.erase(it); |
| } else { |
| it->second--; |
| } |
| } |
| |
| static ExtensionFunctionMemoryDumpProvider& GetInstance() { |
| static base::NoDestructor<ExtensionFunctionMemoryDumpProvider> tracker; |
| return *tracker; |
| } |
| |
| private: |
| // base::trace_event::MemoryDumpProvider: |
| bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, |
| base::trace_event::ProcessMemoryDump* pmd) override { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| auto* dump = pmd->CreateAllocatorDump("extensions/functions"); |
| uint64_t function_count = |
| std::accumulate(function_map_.begin(), function_map_.end(), 0, |
| [](uint64_t total, auto& function_pair) { |
| return total + function_pair.second; |
| }); |
| dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, |
| base::trace_event::MemoryAllocatorDump::kUnitsObjects, |
| function_count); |
| // Collects the top 5 ExtensionFunctions with the most instances on memory |
| // dump. |
| std::vector<std::pair<const char*, uint64_t>> results(5); |
| std::partial_sort_copy(function_map_.begin(), function_map_.end(), |
| results.begin(), results.end(), |
| [](const auto& lhs, const auto& rhs) { |
| return lhs.second > rhs.second; |
| }); |
| for (const auto& function_pair : results) { |
| if (function_pair.first) { |
| TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("memory-infra"), |
| "ExtensionFunction::OnMemoryDump", "function", |
| function_pair.first, "count", function_pair.second); |
| } |
| } |
| return true; |
| } |
| |
| // This map is keyed based on const char* pointer since all the strings used |
| // here are defined in the registry held by the caller. The value needs to be |
| // stored as pointer to be able to add privacy safe trace events. |
| std::map<const char*, uint64_t> function_map_; |
| |
| // Makes sure all methods are called from the same thread. |
| base::ThreadChecker thread_checker_; |
| }; |
| |
| void EnsureMemoryDumpProviderExists() { |
| std::ignore = ExtensionFunctionMemoryDumpProvider::GetInstance(); |
| } |
| |
| // Logs UMA about the performance for a given extension function run. |
| void LogUma(bool success, |
| base::TimeDelta elapsed_time, |
| extensions::functions::HistogramValue histogram_value) { |
| // Note: Certain functions perform actions that are inherently slow - such as |
| // anything waiting on user action. As such, we can't always assume that a |
| // long execution time equates to a poorly-performing function. |
| if (success) { |
| if (elapsed_time < base::Milliseconds(1)) { |
| base::UmaHistogramSparse("Extensions.Functions.SucceededTime.LessThan1ms", |
| histogram_value); |
| } else if (elapsed_time < base::Milliseconds(5)) { |
| base::UmaHistogramSparse("Extensions.Functions.SucceededTime.1msTo5ms", |
| histogram_value); |
| } else if (elapsed_time < base::Milliseconds(10)) { |
| base::UmaHistogramSparse("Extensions.Functions.SucceededTime.5msTo10ms", |
| histogram_value); |
| } else { |
| base::UmaHistogramSparse("Extensions.Functions.SucceededTime.Over10ms", |
| histogram_value); |
| if (elapsed_time >= base::Seconds(270)) { |
| base::UmaHistogramSparse("Extensions.Functions.SucceededTime.Over270s", |
| histogram_value); |
| } |
| } |
| UMA_HISTOGRAM_TIMES("Extensions.Functions.SucceededTotalExecutionTime", |
| elapsed_time); |
| } else { |
| if (elapsed_time < base::Milliseconds(1)) { |
| base::UmaHistogramSparse("Extensions.Functions.FailedTime.LessThan1ms", |
| histogram_value); |
| } else if (elapsed_time < base::Milliseconds(5)) { |
| base::UmaHistogramSparse("Extensions.Functions.FailedTime.1msTo5ms", |
| histogram_value); |
| } else if (elapsed_time < base::Milliseconds(10)) { |
| base::UmaHistogramSparse("Extensions.Functions.FailedTime.5msTo10ms", |
| histogram_value); |
| } else { |
| base::UmaHistogramSparse("Extensions.Functions.FailedTime.Over10ms", |
| histogram_value); |
| if (elapsed_time >= base::Seconds(270)) { |
| base::UmaHistogramSparse("Extensions.Functions.FailedTime.Over270s", |
| histogram_value); |
| } |
| } |
| } |
| } |
| |
| void LogBadMessage(extensions::functions::HistogramValue histogram_value) { |
| base::RecordAction(base::UserMetricsAction("BadMessageTerminate_EFD")); |
| // Track the specific function's |histogram_value|, as this may indicate a |
| // bug in that API's implementation. |
| const char* histogram_name = "Extensions.BadMessageFunctionName"; |
| base::UmaHistogramSparse(histogram_name, histogram_value); |
| } |
| |
| template <class T> |
| void ReceivedBadMessage(T* bad_message_sender, |
| extensions::bad_message::BadMessageReason reason, |
| extensions::functions::HistogramValue histogram_value) { |
| LogBadMessage(histogram_value); |
| // The renderer has done validation before sending extension api requests. |
| // Therefore, we should never receive a request that is invalid in a way |
| // that JSON validation in the renderer should have caught. It could be an |
| // attacker trying to exploit the browser, so we crash the renderer instead. |
| extensions::bad_message::ReceivedBadMessage(bad_message_sender, reason); |
| } |
| |
| // Used in implementation of ScopedUserGestureForTests. |
| class UserGestureForTests { |
| public: |
| static UserGestureForTests* GetInstance(); |
| |
| // Returns true if there is at least one ScopedUserGestureForTests object |
| // alive. |
| bool HaveGesture(); |
| |
| // These should be called when a ScopedUserGestureForTests object is |
| // created/destroyed respectively. |
| void IncrementCount(); |
| void DecrementCount(); |
| |
| private: |
| UserGestureForTests(); |
| friend struct base::DefaultSingletonTraits<UserGestureForTests>; |
| |
| base::Lock lock_; // for protecting access to |count_| |
| int count_; |
| }; |
| |
| // static |
| UserGestureForTests* UserGestureForTests::GetInstance() { |
| return base::Singleton<UserGestureForTests>::get(); |
| } |
| |
| UserGestureForTests::UserGestureForTests() : count_(0) {} |
| |
| bool UserGestureForTests::HaveGesture() { |
| base::AutoLock autolock(lock_); |
| return count_ > 0; |
| } |
| |
| void UserGestureForTests::IncrementCount() { |
| base::AutoLock autolock(lock_); |
| ++count_; |
| } |
| |
| void UserGestureForTests::DecrementCount() { |
| base::AutoLock autolock(lock_); |
| --count_; |
| } |
| |
| class BrowserContextShutdownNotifierFactory |
| : public BrowserContextKeyedServiceShutdownNotifierFactory { |
| public: |
| static BrowserContextShutdownNotifierFactory* GetInstance() { |
| static base::NoDestructor<BrowserContextShutdownNotifierFactory> s_factory; |
| return s_factory.get(); |
| } |
| |
| // No copying. |
| BrowserContextShutdownNotifierFactory( |
| const BrowserContextShutdownNotifierFactory&) = delete; |
| BrowserContextShutdownNotifierFactory& operator=( |
| const BrowserContextShutdownNotifierFactory&) = delete; |
| |
| private: |
| friend class base::NoDestructor<BrowserContextShutdownNotifierFactory>; |
| BrowserContextShutdownNotifierFactory() |
| : BrowserContextKeyedServiceShutdownNotifierFactory("ExtensionFunction") { |
| } |
| |
| content::BrowserContext* GetBrowserContextToUse( |
| content::BrowserContext* context) const override { |
| return extensions::ExtensionsBrowserClient::Get()->GetContextOwnInstance( |
| context, /*force_guest_profile=*/true); |
| } |
| }; |
| |
| } // namespace |
| |
| // static |
| void ExtensionFunction::EnsureShutdownNotifierFactoryBuilt() { |
| BrowserContextShutdownNotifierFactory::GetInstance(); |
| } |
| |
| // static |
| bool ExtensionFunction::ignore_all_did_respond_for_testing_do_not_use = false; |
| |
| // static |
| const char ExtensionFunction::kUnknownErrorDoNotUse[] = "Unknown error."; |
| |
| // Helper class to track the lifetime of ExtensionFunction's RenderFrameHost and |
| // notify the function when it is deleted, as well as forwarding any messages |
| // to the ExtensionFunction. |
| class ExtensionFunction::RenderFrameHostTracker |
| : public content::WebContentsObserver { |
| public: |
| explicit RenderFrameHostTracker(ExtensionFunction* function) |
| : content::WebContentsObserver( |
| WebContents::FromRenderFrameHost(function->render_frame_host())), |
| function_(function) {} |
| |
| RenderFrameHostTracker(const RenderFrameHostTracker&) = delete; |
| RenderFrameHostTracker& operator=(const RenderFrameHostTracker&) = delete; |
| |
| private: |
| // content::WebContentsObserver: |
| void RenderFrameDeleted( |
| content::RenderFrameHost* render_frame_host) override { |
| if (render_frame_host == function_->render_frame_host()) { |
| function_->SetRenderFrameHost(nullptr); |
| } |
| } |
| |
| raw_ptr<ExtensionFunction> function_; // Owns us. |
| }; |
| |
| ExtensionFunction::ResponseValue::ResponseValue(bool success, PassKey) |
| : success_(success) {} |
| ExtensionFunction::ResponseValue::ResponseValue(ResponseValue&& other) = |
| default; |
| ExtensionFunction::ResponseValue::~ResponseValue() = default; |
| |
| ExtensionFunction::ExtensionFunction() { |
| EnsureMemoryDumpProviderExists(); |
| } |
| |
| ExtensionFunction::RespondNowAction::RespondNowAction( |
| ResponseValue result, |
| SendResponseCallback send_response) |
| : result_(std::move(result)), send_response_(std::move(send_response)) {} |
| ExtensionFunction::RespondNowAction::RespondNowAction( |
| RespondNowAction&& other) = default; |
| ExtensionFunction::RespondNowAction::~RespondNowAction() = default; |
| |
| void ExtensionFunction::RespondNowAction::Execute() { |
| std::move(send_response_).Run(result_.success()); |
| } |
| |
| ExtensionFunction::ResponseAction::ResponseAction(PassKey) {} |
| ExtensionFunction::ResponseAction::ResponseAction(RespondNowAction action, |
| PassKey) |
| : action_(std::move(action)) {} |
| ExtensionFunction::ResponseAction::ResponseAction(ResponseAction&& other) = |
| default; |
| ExtensionFunction::ResponseAction::~ResponseAction() = default; |
| |
| void ExtensionFunction::ResponseAction::Execute() { |
| if (action_) { |
| action_->Execute(); |
| } |
| } |
| |
| ExtensionFunction::~ExtensionFunction() { |
| // `name_` may not be set in unit tests. |
| std::string safe_name = name() ? name() : "<unknown>"; |
| // Crash keys added for https://crbug.com/1435545. |
| SCOPED_CRASH_KEY_STRING256("extensions", "destructing_ext_func_name", |
| safe_name); |
| |
| if (name()) { // name_ may not be set in unit tests. |
| ExtensionFunctionMemoryDumpProvider::GetInstance().RemoveFunctionName( |
| name()); |
| } |
| if (dispatcher() && (render_frame_host() || is_from_service_worker())) { |
| dispatcher()->OnExtensionFunctionCompleted(*this); |
| } |
| // Delete the WebContentsObserver before updating the extension function |
| // crash keys so we capture the extension ID if this call hangs or crashes. |
| // http://crbug.com/1435545 |
| tracker_.reset(); |
| // The function may not have run due to quota limits. |
| if (extension() && did_run_) { |
| extensions::extension_function_crash_keys::EndExtensionFunctionCall( |
| extension_id()); |
| } |
| |
| // The extension function should always respond to avoid leaks in the |
| // renderer, dangling callbacks, etc. The exception is if the system is |
| // shutting down or if the extension has been unloaded. |
| #if DCHECK_IS_ON() |
| auto can_be_destroyed_before_responding = [this]() { |
| extensions::ExtensionsBrowserClient* browser_client = |
| extensions::ExtensionsBrowserClient::Get(); |
| if (!browser_client || browser_client->IsShuttingDown()) { |
| return true; |
| } |
| |
| if (ignore_all_did_respond_for_testing_do_not_use) { |
| return true; |
| } |
| |
| if (!browser_context()) { |
| return true; |
| } |
| |
| auto* registry = extensions::ExtensionRegistry::Get(browser_context()); |
| if (registry && extension() && |
| !registry->enabled_extensions().Contains(extension_id())) { |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| DCHECK(did_respond() || can_be_destroyed_before_responding()) << name(); |
| |
| // If ignore_did_respond_for_testing() has been called it could cause another |
| // DCHECK about not calling Mojo callback. |
| // Since the ExtensionFunction request on the frame is a Mojo message |
| // which has a reply callback, it should be called before it's destroyed. |
| if (!response_callback_.is_null()) { |
| constexpr char kShouldCallMojoCallback[] = "Ignored did_respond()"; |
| std::move(response_callback_) |
| .Run(ResponseType::FAILED, base::Value::List(), kShouldCallMojoCallback, |
| nullptr); |
| } |
| #endif // DCHECK_IS_ON() |
| } |
| |
| void ExtensionFunction::AddResponseTarget() { |
| if (dispatcher()) { |
| dispatcher()->AddResponseTarget(this); |
| } |
| } |
| |
| std::unique_ptr<extensions::ContextData> ExtensionFunction::GetContextData() |
| const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (is_from_service_worker()) { |
| return std::make_unique<extensions::BrowserProcessContextData>( |
| content::RenderProcessHost::FromID(source_process_id_)); |
| } else { |
| return std::make_unique<extensions::BrowserFrameContextData>( |
| render_frame_host()); |
| } |
| } |
| |
| bool ExtensionFunction::HasPermission() const { |
| Feature::Availability availability = |
| ExtensionAPI::GetSharedInstance()->IsAvailable( |
| name_, extension_.get(), source_context_type_, source_url(), |
| extensions::CheckAliasStatus::ALLOWED, context_id_, |
| *GetContextData()); |
| return availability.is_available(); |
| } |
| |
| void ExtensionFunction::RespondWithError(std::string error) { |
| Respond(Error(std::move(error))); |
| } |
| |
| bool ExtensionFunction::PreRunValidation(std::string* error) { |
| // TODO(crbug.com/625646) This is a partial fix to avoid crashes when certain |
| // extension functions run during shutdown. Browser or Notification creation |
| // for example create a ScopedKeepAlive, which hit a CHECK if the browser is |
| // shutting down. This fixes the current problem as the known issues happen |
| // through synchronous calls from Run(), but posted tasks will not be covered. |
| // A possible fix would involve refactoring ExtensionFunction: unrefcount |
| // here and use weakptrs for the tasks, then have it owned by something that |
| // will be destroyed naturally in the course of shut down. |
| if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown()) { |
| *error = "The browser is shutting down."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| ExtensionFunction::ResponseAction ExtensionFunction::RunWithValidation() { |
| DCHECK(!did_run_); |
| did_run_ = true; |
| |
| if (extension()) { |
| extensions::extension_function_crash_keys::StartExtensionFunctionCall( |
| extension_id()); |
| } |
| |
| std::string error; |
| if (!PreRunValidation(&error)) { |
| DCHECK(!error.empty() || bad_message_); |
| return bad_message_ ? ValidationFailure(this) : RespondNow(Error(error)); |
| } |
| return Run(); |
| } |
| |
| bool ExtensionFunction::ShouldSkipQuotaLimiting() const { |
| return false; |
| } |
| |
| void ExtensionFunction::OnQuotaExceeded(std::string violation_error) { |
| RespondWithError(std::move(violation_error)); |
| } |
| |
| void ExtensionFunction::SetArgs(base::Value::List args) { |
| DCHECK(!args_.has_value()); |
| args_ = std::move(args); |
| } |
| |
| const base::Value::List* ExtensionFunction::GetResultListForTest() const { |
| return results_ ? &(*results_) : nullptr; |
| } |
| |
| const std::string& ExtensionFunction::GetError() const { |
| return error_; |
| } |
| |
| void ExtensionFunction::SetName(const char* name) { |
| DCHECK_EQ(nullptr, name_) << "SetName() called twice!"; |
| DCHECK_NE(nullptr, name) << "Passed in nullptr to SetName()!"; |
| name_ = name; |
| ExtensionFunctionMemoryDumpProvider::GetInstance().AddFunctionName(name); |
| } |
| |
| void ExtensionFunction::SetBadMessage() { |
| bad_message_ = true; |
| |
| if (render_frame_host()) { |
| ReceivedBadMessage(render_frame_host()->GetProcess(), |
| is_from_service_worker() |
| ? extensions::bad_message::EFD_BAD_MESSAGE_WORKER |
| : extensions::bad_message::EFD_BAD_MESSAGE, |
| histogram_value()); |
| } |
| } |
| |
| bool ExtensionFunction::user_gesture() const { |
| return user_gesture_ || UserGestureForTests::GetInstance()->HaveGesture(); |
| } |
| |
| void ExtensionFunction::SetBrowserContextForTesting( |
| content::BrowserContext* context) { |
| browser_context_for_testing_ = context; |
| } |
| |
| content::BrowserContext* ExtensionFunction::browser_context() const { |
| if (browser_context_for_testing_) { |
| return browser_context_for_testing_; |
| } |
| return browser_context_; |
| } |
| |
| void ExtensionFunction::SetDispatcher( |
| const base::WeakPtr<extensions::ExtensionFunctionDispatcher>& dispatcher) { |
| dispatcher_ = dispatcher; |
| |
| // Update |browser_context_| to the one from the dispatcher. Make it reset to |
| // nullptr on shutdown. |
| if (!dispatcher_ || !dispatcher_->browser_context()) { |
| browser_context_ = nullptr; |
| shutdown_subscription_ = base::CallbackListSubscription(); |
| return; |
| } |
| browser_context_ = dispatcher_->browser_context(); |
| context_id_ = extensions::util::GetBrowserContextId(browser_context_); |
| shutdown_subscription_ = |
| BrowserContextShutdownNotifierFactory::GetInstance() |
| ->Get(browser_context_) |
| ->Subscribe(base::BindRepeating(&ExtensionFunction::Shutdown, |
| base::Unretained(this))); |
| } |
| |
| void ExtensionFunction::Shutdown() { |
| // Wait until the end of this function to delete |this|, in case |
| // OnBrowserContextShutdown() decrements the refcount. |
| scoped_refptr<ExtensionFunction> keep_alive{this}; |
| |
| // Allow the extension function to perform any cleanup before nulling out |
| // `browser_context_`. |
| OnBrowserContextShutdown(); |
| browser_context_ = nullptr; |
| } |
| |
| void ExtensionFunction::SetRenderFrameHost( |
| content::RenderFrameHost* render_frame_host) { |
| // An extension function from Service Worker does not have a RenderFrameHost. |
| if (is_from_service_worker()) { |
| DCHECK(!render_frame_host); |
| return; |
| } |
| |
| DCHECK_NE(render_frame_host_ == nullptr, render_frame_host == nullptr); |
| render_frame_host_ = render_frame_host; |
| tracker_.reset(render_frame_host ? new RenderFrameHostTracker(this) |
| : nullptr); |
| } |
| |
| content::WebContents* ExtensionFunction::GetSenderWebContents() { |
| return render_frame_host_ |
| ? content::WebContents::FromRenderFrameHost(render_frame_host_) |
| : nullptr; |
| } |
| |
| bool ExtensionFunction::ShouldKeepWorkerAliveIndefinitely() { |
| return false; |
| } |
| |
| void ExtensionFunction::OnResponseAck() { |
| // Derived classes must override this if they require and implement an |
| // ACK from the renderer. |
| NOTREACHED(); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::NoArguments() { |
| return CreateArgumentListResponse(base::Value::List()); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::ArgumentList( |
| base::Value::List results) { |
| return CreateArgumentListResponse(std::move(results)); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::Error(std::string error) { |
| return CreateErrorResponseValue(std::move(error)); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::ErrorWithArguments( |
| base::Value::List args, |
| const std::string& error) { |
| return CreateErrorWithArgumentsResponse(std::move(args), error); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::BadMessage() { |
| return CreateBadMessageResponse(); |
| } |
| |
| ExtensionFunction::ResponseAction ExtensionFunction::RespondNow( |
| ResponseValue result) { |
| return ResponseAction( |
| RespondNowAction( |
| std::move(result), |
| base::BindOnce(&ExtensionFunction::SendResponseImpl, this)), |
| PassKey()); |
| } |
| |
| ExtensionFunction::ResponseAction ExtensionFunction::RespondLater() { |
| return ResponseAction(PassKey()); |
| } |
| |
| ExtensionFunction::ResponseAction ExtensionFunction::AlreadyResponded() { |
| DCHECK(did_respond()) << "ExtensionFunction did not call Respond()," |
| " but Run() returned AlreadyResponded()"; |
| return ResponseAction(PassKey()); |
| } |
| |
| // static |
| ExtensionFunction::ResponseAction ExtensionFunction::ValidationFailure( |
| ExtensionFunction* function) { |
| return function->RespondNow(function->BadMessage()); |
| } |
| |
| void ExtensionFunction::Respond(ResponseValue result) { |
| SendResponseImpl(result.success()); |
| } |
| |
| void ExtensionFunction::OnResponded() {} |
| |
| bool ExtensionFunction::HasOptionalArgument(size_t index) { |
| DCHECK(args_); |
| return index < args_->size() && !(*args_)[index].is_none(); |
| } |
| |
| void ExtensionFunction::WriteToConsole(blink::mojom::ConsoleMessageLevel level, |
| const std::string& message) { |
| // TODO(crbug.com/1096166): Service Worker-based extensions don't have a |
| // RenderFrameHost. |
| if (!render_frame_host_) { |
| return; |
| } |
| render_frame_host_->AddMessageToConsole(level, message); |
| } |
| |
| void ExtensionFunction::ReportInspectorIssue( |
| blink::mojom::InspectorIssueInfoPtr info) { |
| // TODO(crbug.com/1096166): Service Worker-based extensions don't have a |
| // RenderFrameHost. |
| if (!render_frame_host_) { |
| return; |
| } |
| render_frame_host_->ReportInspectorIssue(std::move(info)); |
| } |
| |
| void ExtensionFunction::SetTransferredBlobs( |
| std::vector<blink::mojom::SerializedBlobPtr> blobs) { |
| DCHECK(transferred_blobs_.empty()); // Should only be called once. |
| transferred_blobs_ = std::move(blobs); |
| } |
| |
| void ExtensionFunction::SendResponseImpl(bool success) { |
| DCHECK(!response_callback_.is_null()); |
| DCHECK(!did_respond_) << name_; |
| did_respond_ = true; |
| |
| ResponseType response = success ? SUCCEEDED : FAILED; |
| if (bad_message_) { |
| response = BAD_MESSAGE; |
| LOG(ERROR) << "Bad extension message " << name_; |
| } |
| response_type_ = std::make_unique<ResponseType>(response); |
| |
| // If results were never set, we send an empty argument list. |
| if (!results_) { |
| results_.emplace(); |
| } |
| |
| base::Value::List results; |
| if (preserve_results_for_testing_) { |
| // Keep |results_| untouched. |
| results = results_->Clone(); |
| } else { |
| results = std::move(*results_); |
| } |
| |
| extensions::mojom::ExtraResponseDataPtr extra_data; |
| if (!transferred_blobs_.empty()) { |
| extra_data = extensions::mojom::ExtraResponseData::New( |
| std::move(transferred_blobs_)); |
| } |
| std::move(response_callback_) |
| .Run(response, std::move(results), GetError(), std::move(extra_data)); |
| LogUma(success, timer_.Elapsed(), histogram_value_); |
| |
| OnResponded(); |
| } |
| |
| ExtensionFunction::ScopedUserGestureForTests::ScopedUserGestureForTests() { |
| UserGestureForTests::GetInstance()->IncrementCount(); |
| } |
| |
| ExtensionFunction::ScopedUserGestureForTests::~ScopedUserGestureForTests() { |
| UserGestureForTests::GetInstance()->DecrementCount(); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::CreateArgumentListResponse( |
| base::Value::List result) { |
| SetFunctionResults(std::move(result)); |
| // It would be nice to DCHECK(error.empty()) but some legacy extension |
| // function implementations... I'm looking at chrome.input.ime... do this |
| // for some reason. |
| return ResponseValue(true, PassKey()); |
| } |
| |
| ExtensionFunction::ResponseValue |
| ExtensionFunction::CreateErrorWithArgumentsResponse(base::Value::List result, |
| const std::string& error) { |
| SetFunctionResults(std::move(result)); |
| SetFunctionError(error); |
| return ResponseValue(false, PassKey()); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::CreateErrorResponseValue( |
| std::string error) { |
| // It would be nice to DCHECK(!error.empty()) but too many legacy extension |
| // function implementations don't set error but signal failure. |
| SetFunctionError(std::move(error)); |
| return ResponseValue(false, PassKey()); |
| } |
| |
| ExtensionFunction::ResponseValue ExtensionFunction::CreateBadMessageResponse() { |
| SetBadMessage(); |
| return ResponseValue(false, PassKey()); |
| } |
| |
| void ExtensionFunction::SetFunctionResults(base::Value::List results) { |
| DCHECK(!results_) << "Function " << name() << " already has results set."; |
| results_ = std::move(results); |
| } |
| |
| void ExtensionFunction::SetFunctionError(std::string error) { |
| DCHECK(error_.empty()) << "Function " << name() << "already has an error."; |
| error_ = std::move(error); |
| } |