diff --git a/cef3/cef.gyp b/cef3/cef.gyp index c6313152a..301b8150c 100644 --- a/cef3/cef.gyp +++ b/cef3/cef.gyp @@ -916,6 +916,8 @@ 'libcef/renderer/dom_node_impl.h', 'libcef/renderer/frame_impl.cc', 'libcef/renderer/frame_impl.h', + 'libcef/renderer/render_message_filter.cc', + 'libcef/renderer/render_message_filter.h', 'libcef/renderer/render_process_observer.cc', 'libcef/renderer/render_process_observer.h', 'libcef/renderer/render_urlrequest_impl.cc', diff --git a/cef3/include/capi/cef_render_process_handler_capi.h b/cef3/include/capi/cef_render_process_handler_capi.h index 6ff24d0b7..d38a5a971 100644 --- a/cef3/include/capi/cef_render_process_handler_capi.h +++ b/cef3/include/capi/cef_render_process_handler_capi.h @@ -100,6 +100,17 @@ typedef struct _cef_render_process_handler_t { struct _cef_browser_t* browser, struct _cef_frame_t* frame, struct _cef_v8context_t* context); + /// + // Called for global uncaught exceptions. Execution of this callback is + // disabled by default. To enable set + // CefSettings.uncaught_exception_stack_size > 0. + /// + void (CEF_CALLBACK *on_uncaught_exception)( + struct _cef_render_process_handler_t* self, + struct _cef_browser_t* browser, struct _cef_frame_t* frame, + struct _cef_v8context_t* context, struct _cef_v8exception_t* exception, + struct _cef_v8stack_trace_t* stackTrace); + /// // Called when a new node in the the browser gets focus. The |node| value may // be NULL if no specific node has gained focus. The node object passed to diff --git a/cef3/include/cef_render_process_handler.h b/cef3/include/cef_render_process_handler.h index f81fc2dee..5d65bcf17 100644 --- a/cef3/include/cef_render_process_handler.h +++ b/cef3/include/cef_render_process_handler.h @@ -95,6 +95,18 @@ class CefRenderProcessHandler : public virtual CefBase { CefRefPtr frame, CefRefPtr context) {} + /// + // Called for global uncaught exceptions. Execution of this callback is + // disabled by default. To enable set + // CefSettings.uncaught_exception_stack_size > 0. + /// + /*--cef()--*/ + virtual void OnUncaughtException(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) {} + /// // Called when a new node in the the browser gets focus. The |node| value may // be empty if no specific node has gained focus. The node object passed to diff --git a/cef3/include/internal/cef_types.h b/cef3/include/internal/cef_types.h index 14051a338..157bc9f98 100644 --- a/cef3/include/internal/cef_types.h +++ b/cef3/include/internal/cef_types.h @@ -253,6 +253,14 @@ typedef struct _cef_settings_t { /// int remote_debugging_port; + /// + // The number of stack trace frames to capture for uncaught exceptions. + // Specify a positive value to enable the CefV8ContextHandler:: + // OnUncaughtException() callback. Specify 0 (default value) and + // OnUncaughtException() will not be called. + /// + int uncaught_exception_stack_size; + /// // By default CEF V8 references will be invalidated (the IsValid() method will // return false) after the owning context has been released. This reduces the @@ -757,28 +765,28 @@ enum cef_urlrequest_flags_t { // Default behavior. /// UR_FLAG_NONE = 0, - + /// // If set the cache will be skipped when handling the request. /// UR_FLAG_SKIP_CACHE = 1 << 0, - + /// // If set user name, password, and cookies may be sent with the request. /// UR_FLAG_ALLOW_CACHED_CREDENTIALS = 1 << 1, - + /// // If set cookies may be sent with the request and saved from the response. // UR_FLAG_ALLOW_CACHED_CREDENTIALS must also be set. /// UR_FLAG_ALLOW_COOKIES = 1 << 2, - + /// // If set upload progress events will be generated when a request has a body. /// UR_FLAG_REPORT_UPLOAD_PROGRESS = 1 << 3, - + /// // If set load timing info will be collected for the request. /// @@ -810,23 +818,23 @@ enum cef_urlrequest_status_t { // Unknown status. /// UR_UNKNOWN = 0, - + /// // Request succeeded. /// UR_SUCCESS, - + /// // An IO request is pending, and the caller will be informed when it is // completed. /// UR_IO_PENDING, - + /// // Request was canceled programatically. /// UR_CANCELED, - + /// // Request failed for some reason. /// @@ -1121,7 +1129,7 @@ typedef struct _cef_key_event_t { // The type of keyboard event. /// cef_key_event_type_t type; - + /// // Bit flags describing any pressed modifier keys. See // cef_key_event_modifiers_t for values. diff --git a/cef3/include/internal/cef_types_wrappers.h b/cef3/include/internal/cef_types_wrappers.h index 4b0673e8b..b2c0001b1 100644 --- a/cef3/include/internal/cef_types_wrappers.h +++ b/cef3/include/internal/cef_types_wrappers.h @@ -300,6 +300,7 @@ struct CefSettingsTraits { &target->locales_dir_path, copy); target->pack_loading_disabled = src->pack_loading_disabled; target->remote_debugging_port = src->remote_debugging_port; + target->uncaught_exception_stack_size = src->uncaught_exception_stack_size; target->context_safety_implementation = src->context_safety_implementation; } }; diff --git a/cef3/libcef/browser/content_browser_client.cc b/cef3/libcef/browser/content_browser_client.cc index 8aa6098c2..08abd60bc 100644 --- a/cef3/libcef/browser/content_browser_client.cc +++ b/cef3/libcef/browser/content_browser_client.cc @@ -279,6 +279,7 @@ void CefContentBrowserClient::AppendExtraCommandLineSwitches( static const char* const kSwitchNames[] = { switches::kContextSafetyImplementation, switches::kProductVersion, + switches::kUncaughtExceptionStackSize, }; command_line->CopySwitchesFrom(browser_cmd, kSwitchNames, arraysize(kSwitchNames)); diff --git a/cef3/libcef/common/cef_switches.cc b/cef3/libcef/common/cef_switches.cc index d1ef19b73..817d3b533 100644 --- a/cef3/libcef/common/cef_switches.cc +++ b/cef3/libcef/common/cef_switches.cc @@ -30,6 +30,9 @@ const char kLocalesDirPath[] = "locales-dir-path"; // Path to locales directory. const char kPackLoadingDisabled[] = "pack-loading-disabled"; +// Stack size for uncaught exceptions. +const char kUncaughtExceptionStackSize[] = "uncaught-exception-stack-size"; + // Context safety implementation type. const char kContextSafetyImplementation[] = "context-safety-implementation"; diff --git a/cef3/libcef/common/cef_switches.h b/cef3/libcef/common/cef_switches.h index 1feac7961..3966de214 100644 --- a/cef3/libcef/common/cef_switches.h +++ b/cef3/libcef/common/cef_switches.h @@ -22,6 +22,7 @@ extern const char kLogSeverity_Disable[]; extern const char kResourcesDirPath[]; extern const char kLocalesDirPath[]; extern const char kPackLoadingDisabled[]; +extern const char kUncaughtExceptionStackSize[]; extern const char kContextSafetyImplementation[]; } // namespace switches diff --git a/cef3/libcef/common/main_delegate.cc b/cef3/libcef/common/main_delegate.cc index 42e7c1f71..6cd67e97b 100644 --- a/cef3/libcef/common/main_delegate.cc +++ b/cef3/libcef/common/main_delegate.cc @@ -252,6 +252,11 @@ bool CefMainDelegate::BasicStartupComplete(int* exit_code) { base::IntToString(settings.remote_debugging_port)); } + if (settings.uncaught_exception_stack_size > 0) { + command_line->AppendSwitchASCII(switches::kUncaughtExceptionStackSize, + base::IntToString(settings.uncaught_exception_stack_size)); + } + if (settings.context_safety_implementation != 0) { command_line->AppendSwitchASCII(switches::kContextSafetyImplementation, base::IntToString(settings.context_safety_implementation)); diff --git a/cef3/libcef/renderer/content_renderer_client.cc b/cef3/libcef/renderer/content_renderer_client.cc index ccdc8435c..c843d5bce 100644 --- a/cef3/libcef/renderer/content_renderer_client.cc +++ b/cef3/libcef/renderer/content_renderer_client.cc @@ -17,6 +17,7 @@ MSVC_POP_WARNING(); #include "libcef/common/content_client.h" #include "libcef/renderer/browser_impl.h" #include "libcef/renderer/chrome_bindings.h" +#include "libcef/renderer/render_message_filter.h" #include "libcef/renderer/render_process_observer.h" #include "libcef/renderer/thread_util.h" #include "libcef/renderer/v8_impl.h" @@ -71,7 +72,9 @@ struct CefContentRendererClient::SchemeInfo { bool is_display_isolated; }; -CefContentRendererClient::CefContentRendererClient() { +CefContentRendererClient::CefContentRendererClient() + : devtools_agent_count_(0), + uncaught_exception_stack_size_(0) { } CefContentRendererClient::~CefContentRendererClient() { @@ -148,12 +151,33 @@ void CefContentRendererClient::RegisterCustomSchemes() { } } +void CefContentRendererClient::DevToolsAgentAttached() { + CEF_REQUIRE_RT(); + ++devtools_agent_count_; +} + +void CefContentRendererClient::DevToolsAgentDetached() { + CEF_REQUIRE_RT(); + --devtools_agent_count_; + if (devtools_agent_count_ == 0 && uncaught_exception_stack_size_ > 0) { + // When the last DevToolsAgent is detached the stack size is set to 0. + // Restore the user-specified stack size here. + v8::V8::SetCaptureStackTraceForUncaughtExceptions(true, + uncaught_exception_stack_size_, v8::StackTrace::kDetailed); + } +} + +void CefContentRendererClient::SetUncaughtExceptionStackSize(int stackSize) { + uncaught_exception_stack_size_ = stackSize; +} + void CefContentRendererClient::RenderThreadStarted() { render_loop_ = base::MessageLoopProxy::current(); observer_.reset(new CefRenderProcessObserver()); content::RenderThread* thread = content::RenderThread::Get(); thread->AddObserver(observer_.get()); + thread->GetChannel()->AddFilter(new CefRenderMessageFilter); WebKit::WebPrerenderingSupport::initialize(new CefPrerenderingSupport()); diff --git a/cef3/libcef/renderer/content_renderer_client.h b/cef3/libcef/renderer/content_renderer_client.h index a38adb46d..00f3dc90a 100644 --- a/cef3/libcef/renderer/content_renderer_client.h +++ b/cef3/libcef/renderer/content_renderer_client.h @@ -48,6 +48,10 @@ class CefContentRendererClient : public content::ContentRendererClient { // Render thread message loop proxy. base::MessageLoopProxy* render_loop() const { return render_loop_.get(); } + void DevToolsAgentAttached(); + void DevToolsAgentDetached(); + void SetUncaughtExceptionStackSize(int stackSize); + private: // ContentRendererClient implementation. virtual void RenderThreadStarted() OVERRIDE; @@ -71,6 +75,9 @@ class CefContentRendererClient : public content::ContentRendererClient { struct SchemeInfo; typedef std::list SchemeInfoList; SchemeInfoList scheme_info_list_; + + int devtools_agent_count_; + int uncaught_exception_stack_size_; }; #endif // CEF_LIBCEF_RENDERER_CONTENT_RENDERER_CLIENT_H_ diff --git a/cef3/libcef/renderer/render_message_filter.cc b/cef3/libcef/renderer/render_message_filter.cc new file mode 100644 index 000000000..1997483b3 --- /dev/null +++ b/cef3/libcef/renderer/render_message_filter.cc @@ -0,0 +1,82 @@ +/// Copyright (c) 2012 The Chromium Embedded Framework Authors. +// Portions (c) 2011 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 "libcef/renderer/render_message_filter.h" +#include "libcef/renderer/thread_util.h" +#include "libcef/common/cef_messages.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "content/common/devtools_messages.h" +#include "googleurl/src/gurl.h" +#include "googleurl/src/url_util.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" + +CefRenderMessageFilter::CefRenderMessageFilter() + : channel_(NULL) { +} + +CefRenderMessageFilter::~CefRenderMessageFilter() { +} + +void CefRenderMessageFilter::OnFilterAdded(IPC::Channel* channel) { + channel_ = channel; +} + +void CefRenderMessageFilter::OnFilterRemoved() { +} + +bool CefRenderMessageFilter::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + if (message.type() == DevToolsAgentMsg_Attach::ID || + message.type() == DevToolsAgentMsg_Reattach::ID || + message.type() == DevToolsAgentMsg_Detach::ID) { + // Observe the DevTools messages but don't handle them. + handled = false; + } + + IPC_BEGIN_MESSAGE_MAP(CefRenderMessageFilter, message) + IPC_MESSAGE_HANDLER(DevToolsAgentMsg_Attach, OnDevToolsAgentAttach) + IPC_MESSAGE_HANDLER(DevToolsAgentMsg_Reattach, OnDevToolsAgentReattach) + IPC_MESSAGE_HANDLER(DevToolsAgentMsg_Detach, OnDevToolsAgentDetach) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void CefRenderMessageFilter::OnDevToolsAgentAttach() { + CEF_POST_TASK_RT( + base::Bind(&CefRenderMessageFilter::OnDevToolsAgentAttach_RT, this)); +} + +void CefRenderMessageFilter::OnDevToolsAgentReattach( + const std::string& agent_state) { + // Treat reattach the same as attach. + OnDevToolsAgentAttach(); +} + +void CefRenderMessageFilter::OnDevToolsAgentDetach() { + // CefContentRendererClient::DevToolsAgentDetached() needs to be called after + // the IPC message has been handled by DevToolsAgent. A workaround for this is + // to first post to the IO thread and then post to the renderer thread. + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&CefRenderMessageFilter::OnDevToolsAgentDetach_IOT, this)); +} + +void CefRenderMessageFilter::OnDevToolsAgentAttach_RT() { + CEF_REQUIRE_RT(); + CefContentRendererClient::Get()->DevToolsAgentAttached(); +} + +void CefRenderMessageFilter::OnDevToolsAgentDetach_IOT() { + CEF_POST_TASK_RT( + base::Bind(&CefRenderMessageFilter::OnDevToolsAgentDetach_RT, this)); +} + +void CefRenderMessageFilter::OnDevToolsAgentDetach_RT() { + CEF_REQUIRE_RT(); + CefContentRendererClient::Get()->DevToolsAgentDetached(); +} diff --git a/cef3/libcef/renderer/render_message_filter.h b/cef3/libcef/renderer/render_message_filter.h new file mode 100644 index 000000000..e48e26637 --- /dev/null +++ b/cef3/libcef/renderer/render_message_filter.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 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. + +#ifndef CEF_LIBCEF_RENDERER_RENDER_MESSAGE_FILTER_H_ +#define CEF_LIBCEF_RENDERER_RENDER_MESSAGE_FILTER_H_ + +#include +#include "ipc/ipc_channel_proxy.h" + +// This class sends and receives control messages on the renderer process. +class CefRenderMessageFilter : public IPC::ChannelProxy::MessageFilter { + public: + CefRenderMessageFilter(); + virtual ~CefRenderMessageFilter(); + + // IPC::ChannelProxy::MessageFilter implementation. + virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE; + virtual void OnFilterRemoved() OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + private: + // Message handlers called on the IO thread. + void OnDevToolsAgentAttach(); + void OnDevToolsAgentReattach(const std::string& agent_state); + void OnDevToolsAgentDetach(); + + void OnDevToolsAgentAttach_RT(); + void OnDevToolsAgentDetach_IOT(); + void OnDevToolsAgentDetach_RT(); + + IPC::Channel* channel_; + + DISALLOW_COPY_AND_ASSIGN(CefRenderMessageFilter); +}; + + +#endif // CEF_LIBCEF_RENDERER_RENDER_MESSAGE_FILTER_H_ diff --git a/cef3/libcef/renderer/render_process_observer.cc b/cef3/libcef/renderer/render_process_observer.cc index ac1d8fa6d..5373b88de 100644 --- a/cef3/libcef/renderer/render_process_observer.cc +++ b/cef3/libcef/renderer/render_process_observer.cc @@ -5,11 +5,15 @@ #include "libcef/renderer/render_process_observer.h" #include "libcef/common/cef_messages.h" +#include "libcef/common/cef_switches.h" +#include "libcef/common/command_line_impl.h" #include "libcef/common/content_client.h" #include "libcef/renderer/content_renderer_client.h" +#include "libcef/renderer/v8_impl.h" #include "base/bind.h" #include "base/path_service.h" +#include "base/string_number_conversions.h" #include "googleurl/src/gurl.h" #include "googleurl/src/url_util.h" #include "media/base/media.h" @@ -53,6 +57,23 @@ void CefRenderProcessObserver::WebKitInitialized() { // Register any custom schemes with WebKit. CefContentRendererClient::Get()->RegisterCustomSchemes(); + // The number of stack trace frames to capture for uncaught exceptions. + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kUncaughtExceptionStackSize)) { + int uncaught_exception_stack_size = 0; + base::StringToInt( + command_line.GetSwitchValueASCII(switches::kUncaughtExceptionStackSize), + &uncaught_exception_stack_size); + + if (uncaught_exception_stack_size > 0) { + CefContentRendererClient::Get()->SetUncaughtExceptionStackSize( + uncaught_exception_stack_size); + v8::V8::AddMessageListener(&CefV8MessageHandler); + v8::V8::SetCaptureStackTraceForUncaughtExceptions(true, + uncaught_exception_stack_size, v8::StackTrace::kDetailed); + } + } + // Notify the render process handler. CefRefPtr application = CefContentClient::Get()->application(); if (application.get()) { diff --git a/cef3/libcef/renderer/v8_impl.cc b/cef3/libcef/renderer/v8_impl.cc index 6b22b0b5f..898edb713 100644 --- a/cef3/libcef/renderer/v8_impl.cc +++ b/cef3/libcef/renderer/v8_impl.cc @@ -21,6 +21,7 @@ MSVC_POP_WARNING(); #include "libcef/renderer/v8_impl.h" #include "libcef/common/cef_switches.h" +#include "libcef/common/content_client.h" #include "libcef/common/tracker.h" #include "libcef/renderer/browser_impl.h" #include "libcef/renderer/thread_util.h" @@ -1665,3 +1666,28 @@ bool CefV8StackFrameImpl::IsConstructor() { v8::HandleScope handle_scope; return GetHandle()->IsConstructor(); } + +void CefV8MessageHandler(v8::Handle message, + v8::Handle data) { + CEF_REQUIRE_RT_RETURN(void()); + + CefRefPtr context = CefV8Context::GetCurrentContext(); + CefRefPtr browser = context->GetBrowser(); + CefRefPtr frame = context->GetFrame(); + + v8::Handle v8Stack = message->GetStackTrace(); + DCHECK(!v8Stack.IsEmpty()); + CefRefPtr stackTrace = new CefV8StackTraceImpl(v8Stack); + + CefRefPtr application = CefContentClient::Get()->application(); + if (!application.get()) + return; + + CefRefPtr handler = + application->GetRenderProcessHandler(); + if (!handler.get()) + return; + + CefRefPtr exception = new CefV8ExceptionImpl(message); + handler->OnUncaughtException(browser, frame, context, exception, stackTrace); +} diff --git a/cef3/libcef/renderer/v8_impl.h b/cef3/libcef/renderer/v8_impl.h index 0264162ad..58c577f11 100644 --- a/cef3/libcef/renderer/v8_impl.h +++ b/cef3/libcef/renderer/v8_impl.h @@ -293,4 +293,7 @@ class CefV8StackFrameImpl : public CefV8StackFrame { DISALLOW_COPY_AND_ASSIGN(CefV8StackFrameImpl); }; +void CefV8MessageHandler(v8::Handle message, + v8::Handle data); + #endif // CEF_LIBCEF_RENDERER_V8_IMPL_H_ diff --git a/cef3/libcef_dll/cpptoc/render_process_handler_cpptoc.cc b/cef3/libcef_dll/cpptoc/render_process_handler_cpptoc.cc index 904b83fd5..5b61b638a 100644 --- a/cef3/libcef_dll/cpptoc/render_process_handler_cpptoc.cc +++ b/cef3/libcef_dll/cpptoc/render_process_handler_cpptoc.cc @@ -16,6 +16,8 @@ #include "libcef_dll/ctocpp/frame_ctocpp.h" #include "libcef_dll/ctocpp/process_message_ctocpp.h" #include "libcef_dll/ctocpp/v8context_ctocpp.h" +#include "libcef_dll/ctocpp/v8exception_ctocpp.h" +#include "libcef_dll/ctocpp/v8stack_trace_ctocpp.h" // MEMBER FUNCTIONS - Body may be edited by hand. @@ -134,6 +136,46 @@ void CEF_CALLBACK render_process_handler_on_context_released( CefV8ContextCToCpp::Wrap(context)); } +void CEF_CALLBACK render_process_handler_on_uncaught_exception( + struct _cef_render_process_handler_t* self, cef_browser_t* browser, + cef_frame_t* frame, struct _cef_v8context_t* context, + struct _cef_v8exception_t* exception, + struct _cef_v8stack_trace_t* stackTrace) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + // Verify param: frame; type: refptr_diff + DCHECK(frame); + if (!frame) + return; + // Verify param: context; type: refptr_diff + DCHECK(context); + if (!context) + return; + // Verify param: exception; type: refptr_diff + DCHECK(exception); + if (!exception) + return; + // Verify param: stackTrace; type: refptr_diff + DCHECK(stackTrace); + if (!stackTrace) + return; + + // Execute + CefRenderProcessHandlerCppToC::Get(self)->OnUncaughtException( + CefBrowserCToCpp::Wrap(browser), + CefFrameCToCpp::Wrap(frame), + CefV8ContextCToCpp::Wrap(context), + CefV8ExceptionCToCpp::Wrap(exception), + CefV8StackTraceCToCpp::Wrap(stackTrace)); +} + void CEF_CALLBACK render_process_handler_on_focused_node_changed( struct _cef_render_process_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, cef_domnode_t* node) { @@ -202,6 +244,8 @@ CefRenderProcessHandlerCppToC::CefRenderProcessHandlerCppToC( render_process_handler_on_context_created; struct_.struct_.on_context_released = render_process_handler_on_context_released; + struct_.struct_.on_uncaught_exception = + render_process_handler_on_uncaught_exception; struct_.struct_.on_focused_node_changed = render_process_handler_on_focused_node_changed; struct_.struct_.on_process_message_received = diff --git a/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.cc b/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.cc index 9f0be5cee..dd6c799cd 100644 --- a/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.cc +++ b/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.cc @@ -15,6 +15,8 @@ #include "libcef_dll/cpptoc/frame_cpptoc.h" #include "libcef_dll/cpptoc/process_message_cpptoc.h" #include "libcef_dll/cpptoc/v8context_cpptoc.h" +#include "libcef_dll/cpptoc/v8exception_cpptoc.h" +#include "libcef_dll/cpptoc/v8stack_trace_cpptoc.h" #include "libcef_dll/ctocpp/render_process_handler_ctocpp.h" @@ -130,6 +132,45 @@ void CefRenderProcessHandlerCToCpp::OnContextReleased( CefV8ContextCppToC::Wrap(context)); } +void CefRenderProcessHandlerCToCpp::OnUncaughtException( + CefRefPtr browser, CefRefPtr frame, + CefRefPtr context, CefRefPtr exception, + CefRefPtr stackTrace) { + if (CEF_MEMBER_MISSING(struct_, on_uncaught_exception)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + // Verify param: frame; type: refptr_diff + DCHECK(frame.get()); + if (!frame.get()) + return; + // Verify param: context; type: refptr_diff + DCHECK(context.get()); + if (!context.get()) + return; + // Verify param: exception; type: refptr_diff + DCHECK(exception.get()); + if (!exception.get()) + return; + // Verify param: stackTrace; type: refptr_diff + DCHECK(stackTrace.get()); + if (!stackTrace.get()) + return; + + // Execute + struct_->on_uncaught_exception(struct_, + CefBrowserCppToC::Wrap(browser), + CefFrameCppToC::Wrap(frame), + CefV8ContextCppToC::Wrap(context), + CefV8ExceptionCppToC::Wrap(exception), + CefV8StackTraceCppToC::Wrap(stackTrace)); +} + void CefRenderProcessHandlerCToCpp::OnFocusedNodeChanged( CefRefPtr browser, CefRefPtr frame, CefRefPtr node) { diff --git a/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.h b/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.h index d488a81b6..dd6040232 100644 --- a/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.h +++ b/cef3/libcef_dll/ctocpp/render_process_handler_ctocpp.h @@ -42,6 +42,10 @@ class CefRenderProcessHandlerCToCpp CefRefPtr frame, CefRefPtr context) OVERRIDE; virtual void OnContextReleased(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) OVERRIDE; + virtual void OnUncaughtException(CefRefPtr browser, + CefRefPtr frame, CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) OVERRIDE; virtual void OnFocusedNodeChanged(CefRefPtr browser, CefRefPtr frame, CefRefPtr node) OVERRIDE; virtual bool OnProcessMessageReceived(CefRefPtr browser, diff --git a/cef3/tests/cefclient/client_app.cpp b/cef3/tests/cefclient/client_app.cpp index 2f108fe19..48972aa65 100644 --- a/cef3/tests/cefclient/client_app.cpp +++ b/cef3/tests/cefclient/client_app.cpp @@ -272,6 +272,13 @@ void ClientApp::OnWebKitInitialized() { (*it)->OnWebKitInitialized(this); } +void ClientApp::OnBrowserDestroyed(CefRefPtr browser) { + // Execute delegate callbacks. + RenderDelegateSet::iterator it = render_delegates_.begin(); + for (; it != render_delegates_.end(); ++it) + (*it)->OnBrowserDestroyed(this, browser); +} + void ClientApp::OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) { @@ -302,6 +309,19 @@ void ClientApp::OnContextReleased(CefRefPtr browser, } } +void ClientApp::OnUncaughtException(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) { + // Execute delegate callbacks. + RenderDelegateSet::iterator it = render_delegates_.begin(); + for (; it != render_delegates_.end(); ++it) { + (*it)->OnUncaughtException(this, browser, frame, context, exception, + stackTrace); + } +} + void ClientApp::OnFocusedNodeChanged(CefRefPtr browser, CefRefPtr frame, CefRefPtr node) { diff --git a/cef3/tests/cefclient/client_app.h b/cef3/tests/cefclient/client_app.h index 9839ba681..9bfe0e9ae 100644 --- a/cef3/tests/cefclient/client_app.h +++ b/cef3/tests/cefclient/client_app.h @@ -28,8 +28,9 @@ class ClientApp : public CefApp, virtual void OnContextInitialized(CefRefPtr app) { } - // Called on the browser process IO thread before a child process is launched. - // Provides an opportunity to modify the child process command line. + // Called on the browser process IO thread before a child process is + // launched Provides an opportunity to modify the child process command + // line. virtual void OnBeforeChildProcessLaunch( CefRefPtr app, CefRefPtr command_line) { @@ -51,6 +52,11 @@ class ClientApp : public CefApp, virtual void OnWebKitInitialized(CefRefPtr app) { } + // Called before a browser is destroyed. + virtual void OnBrowserDestroyed(CefRefPtr app, + CefRefPtr browser) { + } + // Called when a V8 context is created. Used to create V8 window bindings // and set message callbacks. RenderDelegates should check for unique URLs // to avoid interfering with each other. @@ -69,6 +75,16 @@ class ClientApp : public CefApp, CefRefPtr context) { } + // Global V8 exception handler, disabled by default, to enable set + // CefSettings.uncaught_exception_stack_size > 0. + virtual void OnUncaughtException(CefRefPtr app, + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) { + } + // Called when the focused node in a frame has changed. virtual void OnFocusedNodeChanged(CefRefPtr app, CefRefPtr browser, @@ -152,12 +168,19 @@ class ClientApp : public CefApp, // CefRenderProcessHandler methods. virtual void OnRenderThreadCreated() OVERRIDE; virtual void OnWebKitInitialized() OVERRIDE; + virtual void OnBrowserDestroyed(CefRefPtr browser) OVERRIDE; virtual void OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) OVERRIDE; virtual void OnContextReleased(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) OVERRIDE; + virtual void OnUncaughtException(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) + OVERRIDE; virtual void OnFocusedNodeChanged(CefRefPtr browser, CefRefPtr frame, CefRefPtr node) OVERRIDE; diff --git a/cef3/tests/unittests/test_suite.cc b/cef3/tests/unittests/test_suite.cc index 4a65035fc..db254ae13 100644 --- a/cef3/tests/unittests/test_suite.cc +++ b/cef3/tests/unittests/test_suite.cc @@ -58,6 +58,10 @@ void CefTestSuite::GetSettings(CefSettings& settings) { if (!other_javascript_flags.empty()) javascript_flags += " " + other_javascript_flags; CefString(&settings.javascript_flags) = javascript_flags; + + // Necessary for V8Test.OnUncaughtException tests. + settings.uncaught_exception_stack_size = 10; + settings.remote_debugging_port = 12345; } // static diff --git a/cef3/tests/unittests/v8_unittest.cc b/cef3/tests/unittests/v8_unittest.cc index e8d571452..67977db58 100644 --- a/cef3/tests/unittests/v8_unittest.cc +++ b/cef3/tests/unittests/v8_unittest.cc @@ -25,8 +25,12 @@ const char kV8BindingTestUrl[] = "http://tests/V8Test.BindingTest"; const char kV8ContextParentTestUrl[] = "http://tests/V8Test.ContextParentTest"; const char kV8ContextChildTestUrl[] = "http://tests/V8Test.ContextChildTest"; const char kV8NavTestUrl[] = "http://tests/V8Test.NavTest"; +const char kV8OnUncaughtExceptionTestUrl[] = + "http://tests/V8Test.OnUncaughtException"; const char kV8TestMsg[] = "V8Test.Test"; const char kV8TestCmdArg[] = "v8-test"; +const char kV8DevToolsURLMsg[] = "V8Test.DevToolsURL"; +const char kV8DevToolsLoadHookMsg[] = "V8Test.DevToolsLoadHook"; enum V8TestMode { V8TEST_NONE = 0, @@ -65,6 +69,8 @@ enum V8TestMode { V8TEST_BINDING, V8TEST_STACK_TRACE, V8TEST_EXTENSION, + V8TEST_ON_UNCAUGHT_EXCEPTION, + V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS, }; // Set to the current test being run in the browser process. Will always be @@ -97,7 +103,8 @@ class V8BrowserTest : public ClientApp::BrowserDelegate { class V8RendererTest : public ClientApp::RenderDelegate { public: V8RendererTest() - : test_mode_(V8TEST_NONE) { + : test_mode_(V8TEST_NONE), + devtools_url_("") { } // Run a test when the process message is received from the browser. @@ -206,6 +213,12 @@ class V8RendererTest : public ClientApp::RenderDelegate { case V8TEST_STACK_TRACE: RunStackTraceTest(); break; + case V8TEST_ON_UNCAUGHT_EXCEPTION: + RunOnUncaughtExceptionTest(); + break; + case V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS: + RunOnUncaughtExceptionDevToolsTest(); + break; default: // Was a startup test. EXPECT_TRUE(startup_test_success_); @@ -1558,6 +1571,49 @@ class V8RendererTest : public ClientApp::RenderDelegate { DestroyTest(); } + void RunOnUncaughtExceptionTest() { + test_context_ = + browser_->GetMainFrame()->GetV8Context(); + browser_->GetMainFrame()->ExecuteJavaScript( + "window.setTimeout(test, 0)", "about:blank", 0); + } + + void RunOnUncaughtExceptionDevToolsTest() { + EXPECT_FALSE(browser_->IsPopup()); + test_context_ = + browser_->GetMainFrame()->GetV8Context(); + // Show DevTools. + EXPECT_FALSE(devtools_url_.empty()); + browser_->GetMainFrame()->ExecuteJavaScript( + "window.open('" + devtools_url_ + "');", "about:blank", 0); + } + + void OnUncaughtException(CefRefPtr app, + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) OVERRIDE { + if (test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION || + test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS) { + EXPECT_TRUE(test_context_->IsSame(context)); + EXPECT_STREQ("Uncaught ReferenceError: asd is not defined", + exception->GetMessage().ToString().c_str()); + std::ostringstream stackFormatted; + for (int i = 0; i < stackTrace->GetFrameCount(); ++i) { + stackFormatted << "at " + << stackTrace->GetFrame(i)->GetFunctionName().ToString() + << "() in " << stackTrace->GetFrame(i)->GetScriptName().ToString() + << " on line " << stackTrace->GetFrame(i)->GetLineNumber() << "\n"; + } + const char* stackFormattedShouldBe = + "at test2() in http://tests/V8Test.OnUncaughtException on line 3\n" + "at test() in http://tests/V8Test.OnUncaughtException on line 2\n"; + EXPECT_STREQ(stackFormattedShouldBe, stackFormatted.str().c_str()); + DestroyTest(); + } + } + // Test execution of a native function when the extension is loaded. void RunExtensionTest() { std::string code = "native function v8_extension_test();" @@ -1606,6 +1662,16 @@ class V8RendererTest : public ClientApp::RenderDelegate { CefRefPtr browser, CefRefPtr frame, CefRefPtr context) OVERRIDE { + if (test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS) { + if (browser_.get() == NULL) { + app_ = app; + browser_ = browser; + } + // The test is run from OnProcessMessageReceived(), after the message + // and the devtools url has been received from OnLoadEnd(). + return; + } + app_ = app; browser_ = browser; @@ -1686,6 +1752,119 @@ class V8RendererTest : public ClientApp::RenderDelegate { } } + virtual void OnBrowserDestroyed(CefRefPtr app, + CefRefPtr browser) OVERRIDE { + if (test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS) { + if (browser->IsPopup()) { + // After window destruction there is still a call to + // ScriptController::setCaptureCallStackForUncaughtExceptions(0), + // for which we have to wait. + CefPostDelayedTask(TID_RENDERER, NewCefRunnableMethod( + this, &V8RendererTest::DevToolsClosed), 1000); + } + } + } + + virtual bool OnProcessMessageReceived(CefRefPtr app, + CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) + OVERRIDE { + if (test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS) { + EXPECT_TRUE(browser.get()); + EXPECT_EQ(PID_BROWSER, source_process); + EXPECT_TRUE(message.get()); + EXPECT_TRUE(message->IsReadOnly()); + + if (message->GetName() == kV8DevToolsURLMsg) { + EXPECT_FALSE(browser->IsPopup()); + devtools_url_ = message->GetArgumentList()->GetString(0); + EXPECT_FALSE(devtools_url_.empty()); + if (!TestFailed()) { + CefPostTask(TID_RENDERER, + NewCefRunnableMethod(this, &V8RendererTest::RunTest)); + } + } else if (message->GetName() == kV8DevToolsLoadHookMsg) { + EXPECT_TRUE(browser->IsPopup()); + DevToolsLoadHook(browser); + } else { + EXPECT_TRUE(false) << "not reached"; + return false; + } + return true; + } + return false; + } + + void DevToolsLoadHook(CefRefPtr browser) { + EXPECT_TRUE(browser->IsPopup()); + CefRefPtr context = browser->GetMainFrame()->GetV8Context(); + static const char* kFuncName = "DevToolsLoaded"; + + class Handler : public CefV8Handler { + public: + Handler() {} + virtual bool Execute(const CefString& name, + CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception) OVERRIDE { + EXPECT_STREQ(kFuncName, name.ToString().c_str()); + if (name == kFuncName) { + EXPECT_TRUE(exception.empty()); + retval = CefV8Value::CreateNull(); + EXPECT_TRUE(retval.get()); + renderer_test_->DevToolsLoaded(browser_); + return true; + } + return false; + } + CefRefPtr renderer_test_; + CefRefPtr browser_; + IMPLEMENT_REFCOUNTING(Handler); + }; + + EXPECT_TRUE(context->Enter()); + Handler* handler = new Handler; + handler->renderer_test_ = this; + handler->browser_ = browser; + CefRefPtr handlerPtr(handler); + CefRefPtr func = + CefV8Value::CreateFunction(kFuncName, handler); + EXPECT_TRUE(func.get()); + EXPECT_TRUE(context->GetGlobal()->SetValue( + kFuncName, func, V8_PROPERTY_ATTRIBUTE_NONE)); + EXPECT_TRUE(context->Exit()); + + // Call DevToolsLoaded() when DevTools window completed loading. + std::string jsCode = "(function(){" + " var oldLoadCompleted = InspectorFrontendAPI.loadCompleted;" + " InspectorFrontendAPI.loadCompleted = function(){" + " oldLoadCompleted.call(InspectorFrontendAPI);" + " console.log('InspectorFrontendAPI.loadCompleted event fired');" + " window.DevToolsLoaded();" + " };" + "})();"; + + CefRefPtr retval; + CefRefPtr exception; + EXPECT_TRUE(context->Eval(CefString(jsCode), retval, exception)); + } + + void DevToolsLoaded(CefRefPtr browser) { + EXPECT_TRUE(browser->IsPopup()); + EXPECT_NE(browser->GetIdentifier(), browser_->GetIdentifier()); + CefRefPtr retval; + CefRefPtr exception; + EXPECT_TRUE(browser->GetMainFrame()->GetV8Context()->Eval( + "window.close()", retval, exception)); + } + + void DevToolsClosed() { + browser_->GetMainFrame()->ExecuteJavaScript( + "window.setTimeout(test, 0);", "about:blank", 0); + } + protected: // Return from the test. void DestroyTest() { @@ -1707,6 +1886,9 @@ class V8RendererTest : public ClientApp::RenderDelegate { app_ = NULL; browser_ = NULL; + test_context_ = NULL; + test_object_ = NULL; + devtools_url_ = ""; } // Return the V8 context. @@ -1719,6 +1901,7 @@ class V8RendererTest : public ClientApp::RenderDelegate { CefRefPtr app_; CefRefPtr browser_; V8TestMode test_mode_; + std::string devtools_url_; CefRefPtr test_context_; CefRefPtr test_object_; @@ -1748,6 +1931,17 @@ class V8TestHandler : public TestHandler { "CHILD", "text/html"); CreateBrowser(kV8ContextParentTestUrl); + } else if (test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION || + test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS) { + AddResource(kV8OnUncaughtExceptionTestUrl, "" + "

OnUncaughtException

" + "\n" + "\n", + "text/html"); + CreateBrowser(kV8OnUncaughtExceptionTestUrl); } else { EXPECT_TRUE(test_url_ != NULL); AddResource(test_url_, "" @@ -1756,6 +1950,27 @@ class V8TestHandler : public TestHandler { } } + virtual void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) OVERRIDE { + if (test_mode_ == V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS) { + if (browser->IsPopup()) { + EXPECT_STREQ(GetBrowser()->GetHost()->GetDevToolsURL(true).c_str(), + frame->GetURL().c_str()); + CefRefPtr return_msg = + CefProcessMessage::Create(kV8DevToolsLoadHookMsg); + EXPECT_TRUE(browser->SendProcessMessage(PID_RENDERER, return_msg)); + } else { + // Send the DevTools url message only for the main browser. + CefRefPtr return_msg = + CefProcessMessage::Create(kV8DevToolsURLMsg); + EXPECT_TRUE(return_msg->GetArgumentList()->SetString(0, + browser->GetHost()->GetDevToolsURL(true))); + EXPECT_TRUE(browser->SendProcessMessage(PID_RENDERER, return_msg)); + } + } + } + virtual bool OnProcessMessageReceived( CefRefPtr browser, CefProcessId source_process, @@ -1849,4 +2064,6 @@ V8_TEST_EX(ContextEntered, V8TEST_CONTEXT_ENTERED, NULL); V8_TEST(ContextInvalid, V8TEST_CONTEXT_INVALID); V8_TEST_EX(Binding, V8TEST_BINDING, kV8BindingTestUrl); V8_TEST(StackTrace, V8TEST_STACK_TRACE); +V8_TEST(OnUncaughtException, V8TEST_ON_UNCAUGHT_EXCEPTION); +V8_TEST(OnUncaughtExceptionDevTools, V8TEST_ON_UNCAUGHT_EXCEPTION_DEV_TOOLS); V8_TEST(Extension, V8TEST_EXTENSION);