diff --git a/src/client_handler/cookie_access_filter.h b/src/client_handler/cookie_access_filter.h index 4d0c57c9..e914314c 100644 --- a/src/client_handler/cookie_access_filter.h +++ b/src/client_handler/cookie_access_filter.h @@ -2,6 +2,7 @@ // All rights reserved. Licensed under BSD 3-clause license. // Project website: https://github.com/cztomczak/cefpython +#pragma once #include "common/cefpython_public_api.h" #include "include/cef_resource_request_handler.h" diff --git a/src/client_handler/request_handler.cpp b/src/client_handler/request_handler.cpp index a070a9b7..33ff73f2 100644 --- a/src/client_handler/request_handler.cpp +++ b/src/client_handler/request_handler.cpp @@ -7,6 +7,20 @@ #include "include/base/cef_callback.h" +CefRefPtr RequestHandler::GetResourceRequestHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) { + // Returning a handler enables the CookieAccessFilter, which delivers the + // CanSendCookie/CanSaveCookie callbacks (issue #676). + return new ResourceRequestHandler(); +} + + bool RequestHandler::OnBeforeBrowse(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, diff --git a/src/client_handler/request_handler.h b/src/client_handler/request_handler.h index 6ce8cde6..eef31f4c 100644 --- a/src/client_handler/request_handler.h +++ b/src/client_handler/request_handler.h @@ -6,6 +6,7 @@ #include "include/cef_request_handler.h" #include "include/base/cef_callback.h" #include "cookie_access_filter.h" +#include "resource_request_handler.h" typedef cef_return_value_t ReturnValue; @@ -17,6 +18,15 @@ class RequestHandler : public CefRequestHandler, RequestHandler(){} virtual ~RequestHandler(){} + CefRefPtr GetResourceRequestHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) override; + bool OnBeforeBrowse(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, diff --git a/src/client_handler/resource_request_handler.h b/src/client_handler/resource_request_handler.h new file mode 100644 index 00000000..b5e3bf2e --- /dev/null +++ b/src/client_handler/resource_request_handler.h @@ -0,0 +1,30 @@ +// Copyright (c) 2026 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#pragma once +#include "include/cef_resource_request_handler.h" +#include "cookie_access_filter.h" + +// Minimal CefResourceRequestHandler that returns a CookieAccessFilter. +// CanSendCookie/CanSaveCookie were moved out of CefRequestHandler into +// CefCookieAccessFilter with the NetworkService migration (CEF based on +// Chromium 75, see CEF issue #2622), reachable only via this intermediate +// interface (CefRequestHandler::GetResourceRequestHandler -> +// CefResourceRequestHandler::GetCookieAccessFilter). Without it the +// CanSendCookie/CanSaveCookie callbacks are never invoked (issue #676). +class ResourceRequestHandler : public CefResourceRequestHandler { +public: + ResourceRequestHandler() {} + virtual ~ResourceRequestHandler() {} + + CefRefPtr GetCookieAccessFilter( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override { + return new CookieAccessFilter(); + } + +private: + IMPLEMENT_REFCOUNTING(ResourceRequestHandler); +}; diff --git a/src/frame.pyx b/src/frame.pyx index 493d3094..02a7c85c 100644 --- a/src/frame.pyx +++ b/src/frame.pyx @@ -28,8 +28,18 @@ cdef PyFrame GetPyFrame(CefRefPtr[CefFrame] cefFrame): cdef PyFrame pyFrame cdef CefString frameId = cefFrame.get().GetIdentifier() - cdef int browserId = cefFrame.get().GetBrowser().get().GetIdentifier() - assert (not frameId.empty() and browserId), "frameId or browserId empty" + cdef CefRefPtr[CefBrowser] cefBrowser = cefFrame.get().GetBrowser() + if not cefBrowser.get(): + # Issue #676: CefFrame::GetBrowser() may return NULL when called off + # the UI thread -- e.g. from the IO-thread CanSendCookie/CanSaveCookie + # callbacks for cross-origin subresource requests, or for requests not + # associated with a live frame (service workers / CefURLRequest). + # Raising here prevents a SIGSEGV from the naked .get().GetIdentifier() + # chain that was previously here. Callers that can hit this (the cookie + # filter) guard for it and fall back before calling GetPyFrame. + raise Exception("GetPyFrame(): CefBrowser is NULL" + " (CefFrame.GetBrowser() unavailable off the UI thread?)") + cdef int browserId = cefBrowser.get().GetIdentifier() cdef str uniqueFrameId = GetUniqueFrameId(browserId, CefToPyString(frameId)) if frameId.empty(): diff --git a/src/handlers/cookie_access_filter.pyx b/src/handlers/cookie_access_filter.pyx index 55c4918f..8943aa11 100644 --- a/src/handlers/cookie_access_filter.pyx +++ b/src/handlers/cookie_access_filter.pyx @@ -25,6 +25,17 @@ cdef public cpp_bool CookieAccessFilter_CanSendCookie( # browser was closed. if IsBrowserClosed(cef_browser): return False + # Issue #676: This callback runs on the IO thread, where + # CefFrame::GetBrowser() may return NULL (observed for cross-origin + # subresource requests). CEF also documents browser/frame as optional + # ("may be NULL for requests originating from service workers or + # CefURLRequest", cef_resource_request_handler.h > CefCookieAccessFilter). + # Without a resolvable browser we cannot build a PyFrame to dispatch on, + # so log and allow the cookie by default (CEF's own default is true). + if not cef_frame.get() or not cef_frame.get().GetBrowser().get(): + Debug("CanSendCookie: no resolvable browser for the request" + " (IO-thread / service-worker request); allowing by default") + return True browser = GetPyBrowser(cef_browser, "CanSendCookie") frame = GetPyFrame(cef_frame) @@ -64,6 +75,14 @@ cdef public cpp_bool CookieAccessFilter_CanSaveCookie( # browser was closed. if IsBrowserClosed(cef_browser): return False + # Issue #676: see CanSendCookie above. On the IO thread + # CefFrame::GetBrowser() may be NULL (and CEF documents browser/frame + # as optional). Without a resolvable browser we cannot build a PyFrame, + # so log and allow the cookie by default. + if not cef_frame.get() or not cef_frame.get().GetBrowser().get(): + Debug("CanSaveCookie: no resolvable browser for the request" + " (IO-thread / service-worker request); allowing by default") + return True browser = GetPyBrowser(cef_browser, "CanSaveCookie") frame = GetPyFrame(cef_frame)