| // Copyright 2017 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/renderer/bindings/exception_handler.h" |
| |
| #include "base/check.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/supports_user_data.h" |
| #include "extensions/renderer/bindings/api_binding_util.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" |
| #include "gin/public/wrappable_pointer_tags.h" |
| #include "gin/wrappable.h" |
| #include "v8/include/cppgc/allocation.h" |
| #include "v8/include/v8-cppgc.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| struct ExceptionHandlerPerContextData : public base::SupportsUserData::Data { |
| static constexpr char kPerContextDataKey[] = "extension_exception_handler"; |
| |
| v8::Global<v8::Function> custom_handler; |
| }; |
| |
| constexpr char ExceptionHandlerPerContextData::kPerContextDataKey[]; |
| |
| // A helper class to wrap an ExceptionHandler WeakPtr in a v8::Value. |
| class WrappedExceptionHandler |
| : public gin::Wrappable<WrappedExceptionHandler> { |
| public: |
| static constexpr gin::WrapperInfo kWrapperInfo = { |
| {gin::kEmbedderNativeGin}, |
| gin::kWrappedExceptionHandler}; |
| |
| const gin::WrapperInfo* wrapper_info() const override { return &kWrapperInfo; } |
| |
| base::WeakPtr<ExceptionHandler> exception_handler; |
| }; |
| |
| } // namespace |
| |
| ExceptionHandler::ExceptionHandler( |
| const binding::AddConsoleError& add_console_error) |
| : add_console_error_(add_console_error) {} |
| ExceptionHandler::~ExceptionHandler() = default; |
| |
| v8::Local<v8::Value> ExceptionHandler::GetV8Wrapper(v8::Isolate* isolate) { |
| auto* wrapper = cppgc::MakeGarbageCollected<WrappedExceptionHandler>( |
| isolate->GetCppHeap()->GetAllocationHandle()); |
| wrapper->exception_handler = weak_factory_.GetWeakPtr(); |
| return wrapper->GetWrapper(isolate).ToLocalChecked(); |
| } |
| |
| ExceptionHandler* ExceptionHandler::FromV8Wrapper(v8::Isolate* isolate, |
| v8::Local<v8::Value> value) { |
| WrappedExceptionHandler* handler; |
| if (!gin::ConvertFromV8(isolate, value, &handler)) |
| return nullptr; |
| return handler->exception_handler.get(); |
| } |
| |
| void ExceptionHandler::HandleException(v8::Local<v8::Context> context, |
| const std::string& message, |
| v8::TryCatch* try_catch) { |
| DCHECK(try_catch->HasCaught()); |
| |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| v8::HandleScope handle_scope(isolate); |
| |
| v8::Local<v8::Value> message_value; |
| { |
| v8::TryCatch inner_try_catch(isolate); |
| inner_try_catch.SetVerbose(true); |
| v8::Local<v8::Value> stack_trace_value; |
| if (try_catch->StackTrace(context).ToLocal(&stack_trace_value)) { |
| message_value = stack_trace_value; |
| } else if (!try_catch->Message().IsEmpty()) { |
| message_value = try_catch->Message()->Get(); |
| } |
| } |
| |
| std::string full_message = |
| !message_value.IsEmpty() |
| ? base::StringPrintf("%s: %s", message.c_str(), |
| gin::V8ToString(isolate, message_value).c_str()) |
| : message; |
| HandleException(context, full_message, try_catch->Exception()); |
| try_catch->Reset(); // Reset() to avoid handling the error more than once. |
| } |
| |
| void ExceptionHandler::HandleException(v8::Local<v8::Context> context, |
| const std::string& full_message, |
| v8::Local<v8::Value> exception_value) { |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| v8::HandleScope handle_scope(isolate); |
| |
| v8::Local<v8::Function> handler = GetCustomHandler(context); |
| if (!handler.IsEmpty()) { |
| v8::Local<v8::Value> arguments[] = { |
| gin::StringToV8(isolate, full_message), exception_value, |
| }; |
| // Hopefully, handling an exception doesn't throw an exception - but it's |
| // possible. Handle this gracefully, and log errors normally. |
| v8::TryCatch handler_try_catch(isolate); |
| handler_try_catch.SetVerbose(true); |
| JSRunner::Get(context)->RunJSFunction(handler, context, arguments); |
| } else { |
| add_console_error_.Run(context, full_message); |
| } |
| } |
| |
| void ExceptionHandler::SetHandlerForContext(v8::Local<v8::Context> context, |
| v8::Local<v8::Function> handler) { |
| ExceptionHandlerPerContextData* data = |
| GetPerContextData<ExceptionHandlerPerContextData>(context, |
| kCreateIfMissing); |
| DCHECK(data); |
| data->custom_handler.Reset(v8::Isolate::GetCurrent(), handler); |
| } |
| |
| void ExceptionHandler::RunExtensionCallback( |
| v8::Local<v8::Context> context, |
| v8::Local<v8::Function> extension_callback, |
| v8::LocalVector<v8::Value> callback_arguments, |
| const std::string& message) { |
| v8::TryCatch try_catch(v8::Isolate::GetCurrent()); |
| |
| // TODO(devlin): JSRunner::RunJSFunction() isn't guaranteed to run |
| // synchronously, so if JS is suspended at this moment, the `try_catch` here |
| // is insufficient. |
| JSRunner::Get(context)->RunJSFunction(extension_callback, context, |
| callback_arguments); |
| |
| // Since arbitrary JS has ran, the context may have been invalidated. If it |
| // was, bail. |
| if (!binding::IsContextValid(context)) |
| return; |
| |
| if (try_catch.HasCaught()) |
| HandleException(context, message, &try_catch); |
| } |
| |
| v8::Local<v8::Function> ExceptionHandler::GetCustomHandler( |
| v8::Local<v8::Context> context) { |
| ExceptionHandlerPerContextData* data = |
| GetPerContextData<ExceptionHandlerPerContextData>(context, |
| kDontCreateIfMissing); |
| return data ? data->custom_handler.Get(v8::Isolate::GetCurrent()) |
| : v8::Local<v8::Function>(); |
| } |
| |
| } // namespace extensions |