// Copyright 2013 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/browser/extension_function.h" #include #include #include #include "base/bind.h" #include "base/dcheck_is_on.h" #include "base/logging.h" #include "base/macros.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/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/notification_source.h" #include "content/public/browser/notification_types.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/blob_holder.h" #include "extensions/browser/extension_function_dispatcher.h" #include "extensions/browser/extension_function_registry.h" #include "extensions/browser/extension_message_filter.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/renderer_startup_helper.h" #include "extensions/common/constants.h" #include "extensions/common/error_utils.h" #include "extensions/common/extension_api.h" #include "extensions/common/extension_messages.h" #include "extensions/common/mojom/renderer.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::ThreadTaskRunnerHandle::Get()); } 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(1)); if (it->second == 1) function_map_.erase(it); else it->second--; } static ExtensionFunctionMemoryDumpProvider& GetInstance() { static base::NoDestructor 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> 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 function_map_; // Makes sure all methods are called from the same thread. base::ThreadChecker thread_checker_; }; void EnsureMemoryDumpProviderExists() { ALLOW_UNUSED_LOCAL(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::TimeDelta::FromMilliseconds(1)) { base::UmaHistogramSparse("Extensions.Functions.SucceededTime.LessThan1ms", histogram_value); } else if (elapsed_time < base::TimeDelta::FromMilliseconds(5)) { base::UmaHistogramSparse("Extensions.Functions.SucceededTime.1msTo5ms", histogram_value); } else if (elapsed_time < base::TimeDelta::FromMilliseconds(10)) { base::UmaHistogramSparse("Extensions.Functions.SucceededTime.5msTo10ms", histogram_value); } else { base::UmaHistogramSparse("Extensions.Functions.SucceededTime.Over10ms", histogram_value); } UMA_HISTOGRAM_TIMES("Extensions.Functions.SucceededTotalExecutionTime", elapsed_time); } else { if (elapsed_time < base::TimeDelta::FromMilliseconds(1)) { base::UmaHistogramSparse("Extensions.Functions.FailedTime.LessThan1ms", histogram_value); } else if (elapsed_time < base::TimeDelta::FromMilliseconds(5)) { base::UmaHistogramSparse("Extensions.Functions.FailedTime.1msTo5ms", histogram_value); } else if (elapsed_time < base::TimeDelta::FromMilliseconds(10)) { base::UmaHistogramSparse("Extensions.Functions.FailedTime.5msTo10ms", histogram_value); } else { base::UmaHistogramSparse("Extensions.Functions.FailedTime.Over10ms", histogram_value); } UMA_HISTOGRAM_TIMES("Extensions.Functions.FailedTotalExecutionTime", elapsed_time); } } 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. base::UmaHistogramSparse("Extensions.BadMessageFunctionName", histogram_value); } template 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); } class ArgumentListResponseValue : public ExtensionFunction::ResponseValueObject { public: ArgumentListResponseValue(ExtensionFunction* function, base::Value result) { SetFunctionResults(function, 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. } ~ArgumentListResponseValue() override = default; bool Apply() override { return true; } }; class ErrorWithArgumentsResponseValue : public ArgumentListResponseValue { public: ErrorWithArgumentsResponseValue(ExtensionFunction* function, base::Value result, const std::string& error) : ArgumentListResponseValue(function, std::move(result)) { SetFunctionError(function, error); } ~ErrorWithArgumentsResponseValue() override = default; bool Apply() override { return false; } }; class ErrorResponseValue : public ExtensionFunction::ResponseValueObject { public: ErrorResponseValue(ExtensionFunction* function, 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(function, std::move(error)); } ~ErrorResponseValue() override {} bool Apply() override { return false; } }; class BadMessageResponseValue : public ExtensionFunction::ResponseValueObject { public: explicit BadMessageResponseValue(ExtensionFunction* function) { function->SetBadMessage(); NOTREACHED() << function->name() << ": bad message"; } ~BadMessageResponseValue() override {} bool Apply() override { return false; } }; class RespondNowAction : public ExtensionFunction::ResponseActionObject { public: typedef base::OnceCallback SendResponseCallback; RespondNowAction(ExtensionFunction::ResponseValue result, SendResponseCallback send_response) : result_(std::move(result)), send_response_(std::move(send_response)) {} ~RespondNowAction() override = default; void Execute() override { std::move(send_response_).Run(result_->Apply()); } private: ExtensionFunction::ResponseValue result_; SendResponseCallback send_response_; }; class RespondLaterAction : public ExtensionFunction::ResponseActionObject { public: ~RespondLaterAction() override {} void Execute() override {} }; class AlreadyRespondedAction : public ExtensionFunction::ResponseActionObject { public: ~AlreadyRespondedAction() override {} void Execute() override {} }; // 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; base::Lock lock_; // for protecting access to |count_| int count_; }; // static UserGestureForTests* UserGestureForTests::GetInstance() { return base::Singleton::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 s_factory; return s_factory.get(); } // No copying. BrowserContextShutdownNotifierFactory( const BrowserContextShutdownNotifierFactory&) = delete; BrowserContextShutdownNotifierFactory& operator=( const BrowserContextShutdownNotifierFactory&) = delete; private: friend class base::NoDestructor; BrowserContextShutdownNotifierFactory() : BrowserContextKeyedServiceShutdownNotifierFactory("ExtensionFunction") { } }; } // namespace // static void ExtensionFunction::EnsureShutdownNotifierFactoryBuilt() { BrowserContextShutdownNotifierFactory::GetInstance(); } void ExtensionFunction::ResponseValueObject::SetFunctionResults( ExtensionFunction* function, base::Value results) { DCHECK(!function->results_) << "Function " << function->name_ << "already has results set."; function->results_ = base::ListValue::From(base::Value::ToUniquePtrValue(std::move(results))); } void ExtensionFunction::ResponseValueObject::SetFunctionError( ExtensionFunction* function, std::string error) { DCHECK(function->error_.empty()) << "Function " << function->name_ << "already has an error."; function->error_ = std::move(error); } // 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) {} private: // content::WebContentsObserver: void RenderFrameDeleted( content::RenderFrameHost* render_frame_host) override { if (render_frame_host == function_->render_frame_host()) function_->SetRenderFrameHost(nullptr); } bool OnMessageReceived(const IPC::Message& message, content::RenderFrameHost* render_frame_host) override { return render_frame_host == function_->render_frame_host() && function_->OnMessageReceived(message); } ExtensionFunction* function_; // Owns us. DISALLOW_COPY_AND_ASSIGN(RenderFrameHostTracker); }; ExtensionFunction::ExtensionFunction() { EnsureMemoryDumpProviderExists(); } ExtensionFunction::~ExtensionFunction() { 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( extension(), is_from_service_worker(), name()); } // 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(base::Value::Type::LIST), kShouldCallMojoCallback); } #endif // DCHECK_IS_ON() } void ExtensionFunction::AddWorkerResponseTarget() { DCHECK(is_from_service_worker()); if (dispatcher()) dispatcher()->AddWorkerResponseTarget(this); } bool ExtensionFunction::HasPermission() const { Feature::Availability availability = ExtensionAPI::GetSharedInstance()->IsAvailable( name_, extension_.get(), source_context_type_, source_url(), extensions::CheckAliasStatus::ALLOWED); 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() { #if DCHECK_IS_ON() DCHECK(!did_run_); did_run_ = true; #endif 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 args) { DCHECK(args.is_list()); DCHECK(!args_.get()); // Should only be called once. args_ = base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args))); } const base::ListValue* ExtensionFunction::GetResultList() const { return results_.get(); } 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(); } bool ExtensionFunction::OnMessageReceived(const IPC::Message& message) { return false; } 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& 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(); shutdown_subscription_ = BrowserContextShutdownNotifierFactory::GetInstance() ->Get(browser_context_) ->Subscribe(base::BindRepeating(&ExtensionFunction::Shutdown, base::Unretained(this))); } void ExtensionFunction::Shutdown() { 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; } void ExtensionFunction::OnServiceWorkerAck() { // Derived classes must override this if they require and implement an // ACK from the Service Worker. NOTREACHED(); } ExtensionFunction::ResponseValue ExtensionFunction::NoArguments() { return ResponseValue(new ArgumentListResponseValue( this, base::Value(base::Value::Type::LIST))); } ExtensionFunction::ResponseValue ExtensionFunction::OneArgument( base::Value arg) { base::Value args(base::Value::Type::LIST); args.Append(std::move(arg)); return ResponseValue(new ArgumentListResponseValue(this, std::move(args))); } ExtensionFunction::ResponseValue ExtensionFunction::TwoArguments( base::Value arg1, base::Value arg2) { base::Value args(base::Value::Type::LIST); args.Append(std::move(arg1)); args.Append(std::move(arg2)); return ResponseValue(new ArgumentListResponseValue(this, std::move(args))); } ExtensionFunction::ResponseValue ExtensionFunction::ArgumentList( std::vector results) { return ResponseValue( new ArgumentListResponseValue(this, base::Value(std::move(results)))); } ExtensionFunction::ResponseValue ExtensionFunction::ArgumentList( std::unique_ptr args) { base::Value new_args; if (args) new_args = base::Value::FromUniquePtrValue(std::move(args)); return ResponseValue( new ArgumentListResponseValue(this, std::move(new_args))); } ExtensionFunction::ResponseValue ExtensionFunction::Error(std::string error) { return ResponseValue(new ErrorResponseValue(this, std::move(error))); } ExtensionFunction::ResponseValue ExtensionFunction::Error( const std::string& format, const std::string& s1) { return ResponseValue( new ErrorResponseValue(this, ErrorUtils::FormatErrorMessage(format, s1))); } ExtensionFunction::ResponseValue ExtensionFunction::Error( const std::string& format, const std::string& s1, const std::string& s2) { return ResponseValue(new ErrorResponseValue( this, ErrorUtils::FormatErrorMessage(format, s1, s2))); } ExtensionFunction::ResponseValue ExtensionFunction::Error( const std::string& format, const std::string& s1, const std::string& s2, const std::string& s3) { return ResponseValue(new ErrorResponseValue( this, ErrorUtils::FormatErrorMessage(format, s1, s2, s3))); } ExtensionFunction::ResponseValue ExtensionFunction::ErrorWithArguments( std::vector args, const std::string& error) { return ResponseValue(new ErrorWithArgumentsResponseValue( this, base::Value(std::move(args)), error)); } ExtensionFunction::ResponseValue ExtensionFunction::ErrorWithArguments( std::unique_ptr args, const std::string& error) { base::Value new_args; if (args) new_args = base::Value::FromUniquePtrValue(std::move(args)); return ResponseValue( new ErrorWithArgumentsResponseValue(this, std::move(new_args), error)); } ExtensionFunction::ResponseValue ExtensionFunction::BadMessage() { return ResponseValue(new BadMessageResponseValue(this)); } ExtensionFunction::ResponseAction ExtensionFunction::RespondNow( ResponseValue result) { return ResponseAction(new RespondNowAction( std::move(result), base::BindOnce(&ExtensionFunction::SendResponseImpl, this))); } ExtensionFunction::ResponseAction ExtensionFunction::RespondLater() { return ResponseAction(new RespondLaterAction()); } ExtensionFunction::ResponseAction ExtensionFunction::AlreadyResponded() { DCHECK(did_respond()) << "ExtensionFunction did not call Respond()," " but Run() returned AlreadyResponded()"; return ResponseAction(new AlreadyRespondedAction()); } // static ExtensionFunction::ResponseAction ExtensionFunction::ValidationFailure( ExtensionFunction* function) { return function->RespondNow(function->BadMessage()); } void ExtensionFunction::Respond(ResponseValue result) { SendResponseImpl(result->Apply()); } void ExtensionFunction::OnResponded() { if (!transferred_blob_uuids_.empty()) { extensions::mojom::Renderer* renderer = extensions::RendererStartupHelperFactory::GetForBrowserContext( browser_context()) ->GetRenderer( content::RenderProcessHost::FromID(source_process_id())); if (renderer) { renderer->TransferBlobs( base::BindOnce(&ExtensionFunction::OnTransferBlobsAck, this, source_process_id(), transferred_blob_uuids_)); } } } bool ExtensionFunction::HasOptionalArgument(size_t index) { base::Value* value; return args_->Get(index, &value) && !value->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; // Only the main frame handles dev tools messages. WebContents::FromRenderFrameHost(render_frame_host_) ->GetMainFrame() ->AddMessageToConsole(level, message); } void ExtensionFunction::SetTransferredBlobUUIDs( const std::vector& blob_uuids) { DCHECK(transferred_blob_uuids_.empty()); // Should only be called once. transferred_blob_uuids_ = blob_uuids; } 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(response); // If results were never set, we send an empty argument list. if (!results_) results_ = std::make_unique(); std::move(response_callback_).Run(response, *results_, GetError()); LogUma(success, timer_.Elapsed(), histogram_value_); OnResponded(); } void ExtensionFunction::OnTransferBlobsAck( int process_id, const std::vector& blob_uuids) { content::RenderProcessHost* process = content::RenderProcessHost::FromID(process_id); if (!process) return; extensions::BlobHolder::FromRenderProcessHost(process)->DropBlobs(blob_uuids); } ExtensionFunction::ScopedUserGestureForTests::ScopedUserGestureForTests() { UserGestureForTests::GetInstance()->IncrementCount(); } ExtensionFunction::ScopedUserGestureForTests::~ScopedUserGestureForTests() { UserGestureForTests::GetInstance()->DecrementCount(); }