diff --git a/api/Browser.md b/api/Browser.md index 46135cc0..58e56698 100644 --- a/api/Browser.md +++ b/api/Browser.md @@ -5,8 +5,18 @@ Remember to free all browser references for the browser to shut down cleanly. Otherwise data such as cookies or other storage might not be flushed to disk -when closing app, and other issues might occur as well. To free a reference -just assign a None value to a "browser" variable. +when closing app, and other issues might occur as well. If you store +a reference to Frame somewhere in your code then to free it just assign +a None value to the variable. + +To compare browser objects always use [GetIdentifier()](#getidentifier) +method. Do not compare two Browser objects variables directly. There +are some edge cases when after the OnBeforeClose event browser objects +are no more globally referenced thus a new instance is created that +wraps upstream CefBrowser object. Browser objects that were globally +unreferenced do not have properties of the original Browser object, +for example they do not have client callbacks, javascript bindings +or user data set. Table of contents: diff --git a/api/Frame.md b/api/Frame.md index f214bdcd..aef391b4 100644 --- a/api/Frame.md +++ b/api/Frame.md @@ -3,6 +3,19 @@ # Frame (object) +Remember to free all frame references for the browser to shut down cleanly. +Otherwise data such as cookies or other storage might not be flushed to disk +when closing app, and other issues might occur as well. If you store +a reference to Frame somewhere in your code then to free it just assign +a None value to the variable. + +To compare frame objects always use [GetIdentifier()](#getidentifier) +method. Do not compare two Frame objects variables directly. There +are some edge cases when after the OnBeforeClose event frame objects +are no more globally referenced thus a new instance is created that +wraps upstream CefFrame object. Frame objects that were globally +unreferenced do not have properties of the original Frame object. + Table of contents: * [Methods](#methods) diff --git a/api/JavascriptCallback.md b/api/JavascriptCallback.md index 6f4ba790..97b3bc06 100644 --- a/api/JavascriptCallback.md +++ b/api/JavascriptCallback.md @@ -15,7 +15,9 @@ See also [Issue #11](../issues/11) (Throw JS / Python exceptions according to ex Table of contents: * [Methods](#methods) * [Call](#call) - * [GetName](#getname) + * [GetFrame](#getframe) + * [GetId](#getid) + * [GetFunctionName](#getfunctionname) ## Methods @@ -30,10 +32,29 @@ Table of contents: Call the javascript callback function. -For a list of allowed types for `mixed` see [JavascriptBindings](JavascriptBindings.md).IsValueAllowed(). +For a list of allowed types for `mixed` see JavascriptBindings.[IsValueAllowed()](JavascriptBindings.md#isvalueallowed). -### GetName +### GetFrame + +| | | +| --- | --- | +| __Return__ | [Frame](Frame.md) | + +Get Frame object associated with this callback. If Browser was destroyed +then Frame may be None. + + +### GetId + +| | | +| --- | --- | +| __Return__ | int | + +Get this callback's identifier. + + +### GetFunctionName | | | | --- | --- | diff --git a/src/browser.pyx b/src/browser.pyx index c5aadd53..d27a75e7 100644 --- a/src/browser.pyx +++ b/src/browser.pyx @@ -17,75 +17,109 @@ MOUSEBUTTON_RIGHT = cef_types.MBT_RIGHT # get segmentation faults, as they will be garbage collected. cdef dict g_pyBrowsers = {} + +# Unreferenced browsers are added to this list in OnBeforeClose(). +# Must keep a list of unreferenced browsers so that a new reference +# is not created in GetPyBrowser() when browser was closed. +cdef list g_unreferenced_browsers = [] # [int identifier, ..] + +# Browsers that are about to be closed are added to this list in +# CloseBrowser(). cdef list g_closed_browsers = [] # [int identifier, ..] cdef PyBrowser GetPyBrowserById(int browserId): + """May return None value so always check returned value.""" if browserId in g_pyBrowsers: return g_pyBrowsers[browserId] return None -cdef PyBrowser GetPyBrowser(CefRefPtr[CefBrowser] cefBrowser): +cdef PyBrowser GetPyBrowser(CefRefPtr[CefBrowser] cefBrowser, + callerIdStr="GetPyBrowser"): + """The second argument 'callerIdStr' is so that a debug + message can be displayed informing which CEF handler callback + is being called to which an incomplete PyBrowser instance is + provided.""" + global g_pyBrowsers + + # This probably ain't needed, but just to be sure. if cefBrowser == NULL or not cefBrowser.get(): - # noinspection PyUnresolvedReferences - Debug("GetPyBrowser(): returning None") - return None + raise Exception("{caller}: CefBrowser reference is NULL" + .format(caller=callerIdStr)) cdef PyBrowser pyBrowser cdef int browserId - cdef int identifier - browserId = cefBrowser.get().GetIdentifier() + if browserId in g_pyBrowsers: return g_pyBrowsers[browserId] + # This code probably ain't needed. + # ---- + cdef list toRemove = [] + cdef int identifier for identifier, pyBrowser in g_pyBrowsers.items(): if not pyBrowser.cefBrowser.get(): - # noinspection PyUnresolvedReferences - Debug("GetPyBrowser(): removing an empty CefBrowser reference, " - "browserId=%s" % identifier) - del g_pyBrowsers[identifier] + toRemove.append(identifier) + for identifier in toRemove: + Debug("GetPyBrowser(): removing an empty CefBrowser reference," + " browserId=%s" % identifier) + RemovePyBrowser(identifier) + # ---- - # noinspection PyUnresolvedReferences - Debug("GetPyBrowser(): creating new PyBrowser, browserId=%s" % browserId) pyBrowser = PyBrowser() pyBrowser.cefBrowser = cefBrowser - g_pyBrowsers[browserId] = pyBrowser - - # Inherit client callbacks and javascript bindings - # from parent browser. - - # Checking __outerWindowHandle as we should not inherit - # client callbacks and javascript bindings if the browser - # was created explicitily by calling CreateBrowserSync(). - - # Popups inherit client callbacks by default. - - # Popups inherit javascript bindings only when "bindToPopups" - # constructor param was set to True. cdef WindowHandle openerHandle cdef dict clientCallbacks cdef JavascriptBindings javascriptBindings cdef PyBrowser tempPyBrowser - if pyBrowser.IsPopup() and \ - not pyBrowser.GetUserData("__outerWindowHandle"): - openerHandle = pyBrowser.GetOpenerWindowHandle() - for identifier, tempPyBrowser in g_pyBrowsers.items(): - if tempPyBrowser.GetWindowHandle() == openerHandle: - clientCallbacks = tempPyBrowser.GetClientCallbacksDict() - if clientCallbacks: - pyBrowser.SetClientCallbacksDict(clientCallbacks) - javascriptBindings = tempPyBrowser.GetJavascriptBindings() - if javascriptBindings: - if javascriptBindings.GetBindToPopups(): - pyBrowser.SetJavascriptBindings(javascriptBindings) + if browserId in g_unreferenced_browsers: + # This browser was already unreferenced due to OnBeforeClose + # was already called. An incomplete new instance of Browser + # object is created. This instance doesn't have the client + # callbacks, javascript bindings or user data that was already + # available in the original Browser object. + Debug("{caller}: Browser was already globally unreferenced" + ", a new incomplete instance is created, browser id={id}" + .format(caller=callerIdStr, id=str(browserId))) + else: + # This is first creation of browser. Store a reference globally + # and inherit client callbacks and javascript bindings from + # parent browsers. + Debug("GetPyBrowser(): create new PyBrowser, browserId=%s" + % browserId) + + g_pyBrowsers[browserId] = pyBrowser + + # Inherit client callbacks and javascript bindings + # from parent browser. + # - Checking __outerWindowHandle as we should not inherit + # client callbacks and javascript bindings if the browser + # was created explicitily by calling CreateBrowserSync(). + # - Popups inherit client callbacks by default. + # - Popups inherit javascript bindings only when "bindToPopups" + # constructor param was set to True. + + if pyBrowser.IsPopup() and \ + not pyBrowser.GetUserData("__outerWindowHandle"): + openerHandle = pyBrowser.GetOpenerWindowHandle() + for identifier, tempPyBrowser in g_pyBrowsers.items(): + if tempPyBrowser.GetWindowHandle() == openerHandle: + clientCallbacks = tempPyBrowser.GetClientCallbacksDict() + if clientCallbacks: + pyBrowser.SetClientCallbacksDict(clientCallbacks) + javascriptBindings = tempPyBrowser.GetJavascriptBindings() + if javascriptBindings: + if javascriptBindings.GetBindToPopups(): + pyBrowser.SetJavascriptBindings(javascriptBindings) + return pyBrowser cdef void RemovePyBrowser(int browserId) except *: # Called from LifespanHandler_OnBeforeClose(). - global g_pyBrowsers + global g_pyBrowsers, g_unreferenced_browsers if browserId in g_pyBrowsers: if len(g_pyBrowsers) == 1: # This is the last browser remaining. @@ -97,6 +131,7 @@ cdef void RemovePyBrowser(int browserId) except *: # noinspection PyUnresolvedReferences Debug("del g_pyBrowsers[%s]" % browserId) del g_pyBrowsers[browserId] + g_unreferenced_browsers.append(browserId) else: # noinspection PyUnresolvedReferences Debug("RemovePyBrowser() FAILED: browser not found, id = %s" \ @@ -116,7 +151,7 @@ cdef public void PyBrowser_ShowDevTools(CefRefPtr[CefBrowser] cefBrowser # Called from ClientHandler::OnContextMenuCommand cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "ShowDevTools") pyBrowser.ShowDevTools() except: (exc_type, exc_value, exc_trace) = sys.exc_info() diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 941fff00..6fb922e1 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -838,7 +838,8 @@ def CreateBrowserSync(windowInfo=None, requestContextHandler.get().SetBrowser(cefBrowser) cdef PyBrowser pyBrowser = GetPyBrowser(cefBrowser) - pyBrowser.SetUserData("__outerWindowHandle", int(windowInfo.parentWindowHandle)) + pyBrowser.SetUserData("__outerWindowHandle", + int(windowInfo.parentWindowHandle)) return pyBrowser @@ -889,6 +890,10 @@ def Shutdown(): # Run some message loop work, force closing browsers and then run # some message loop work again for the browsers to close cleanly. # + # UPDATE: This code needs to be rechecked. There were enhancements + # to unrferencing globally stored Browser objects in + # g_pyBrowsers. See Issue #330 and its commits. + # # CASE 1: # There might be a case when python error occured after creating # browser, but before any message loop was run. In such case diff --git a/src/frame.pyx b/src/frame.pyx index 9a3faca9..9342719c 100644 --- a/src/frame.pyx +++ b/src/frame.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "cefpython.pyx" +include "browser.pyx" cdef dict g_pyFrames = {} @@ -17,25 +18,51 @@ cdef PyFrame GetPyFrameById(int browserId, object frameId): cdef PyFrame GetPyFrame(CefRefPtr[CefFrame] cefFrame): global g_pyFrames + + # This code probably ain't needed, but just to be sure. if cefFrame == NULL or not cefFrame.get(): - Debug("GetPyFrame(): returning None") - return + raise Exception("GetPyFrame(): CefFrame reference is NULL") + cdef PyFrame pyFrame cdef object frameId = cefFrame.get().GetIdentifier() # int64 cdef int browserId = cefFrame.get().GetBrowser().get().GetIdentifier() assert (frameId and browserId), "frameId or browserId empty" cdef object uniqueFrameId = GetUniqueFrameId(browserId, frameId) + if uniqueFrameId in g_pyFrames: return g_pyFrames[uniqueFrameId] + + # This code probably ain't needed. + # ---- + cdef list toRemove = [] for uFid, pyFrame in g_pyFrames.items(): if not pyFrame.cefFrame.get(): - Debug("GetPyFrame(): removing an empty CefFrame reference, " \ - "uniqueFrameId = %s" % uniqueFrameId) - del g_pyFrames[uFid] - # Debug("GetPyFrame(): creating new PyFrame, frameId=%s" % frameId) + toRemove.append(uFid) + for uFid in toRemove: + Debug("GetPyFrame(): removing an empty CefFrame reference, " + "uniqueFrameId = %s" % uniqueFrameId) + del g_pyFrames[uFid] + # ---- + pyFrame = PyFrame(browserId, frameId) pyFrame.cefFrame = cefFrame - g_pyFrames[uniqueFrameId] = pyFrame + + if browserId in g_unreferenced_browsers: + # Browser was already globally unreferenced in OnBeforeClose, + # thus all frames are globally unreferenced too. Create a new + # incomplete instance of PyFrame object. Read comments in + # browser.pyx > GetPyBrowser and in Browser.md for what + # "incomplete" means. + pass + else: + # Keep a global reference to this frame only if the browser + # wasn't destroyed in OnBeforeClose. Otherwise we would leave + # dead frames references living forever. + # SIDE EFFECT: two calls to GetPyFrame for the same frame object + # may return two different PyFrame objects. Compare + # frame objects always using GetIdentifier(). + # Debug("GetPyFrame(): create new PyFrame, frameId=%s" % frameId) + g_pyFrames[uniqueFrameId] = pyFrame return pyFrame cdef void RemovePyFrame(int browserId, object frameId) except *: diff --git a/src/handlers/display_handler.pyx b/src/handlers/display_handler.pyx index 3db61a7c..d67cb4d1 100644 --- a/src/handlers/display_handler.pyx +++ b/src/handlers/display_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cdef public void DisplayHandler_OnAddressChange( CefRefPtr[CefBrowser] cefBrowser, @@ -14,7 +15,7 @@ cdef public void DisplayHandler_OnAddressChange( cdef py_string pyUrl cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnAddressChange") pyFrame = GetPyFrame(cefFrame) pyUrl = CefToPyString(cefUrl) callback = pyBrowser.GetClientCallback("OnAddressChange") @@ -32,7 +33,7 @@ cdef public void DisplayHandler_OnTitleChange( cdef py_string pyTitle cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnTitleChange") pyTitle = CefToPyString(cefTitle) callback = pyBrowser.GetClientCallback("OnTitleChange") if callback: @@ -51,7 +52,7 @@ cdef public cpp_bool DisplayHandler_OnTooltip( cdef object callback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnTooltip") pyText = CefToPyString(cefText) pyTextOut = [pyText] callback = pyBrowser.GetClientCallback("OnTooltip") @@ -73,7 +74,7 @@ cdef public void DisplayHandler_OnStatusMessage( cdef py_string pyValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnStatusMessage") pyValue = CefToPyString(cefValue) callback = pyBrowser.GetClientCallback("OnStatusMessage") if callback: @@ -94,7 +95,7 @@ cdef public cpp_bool DisplayHandler_OnConsoleMessage( cdef py_bool returnValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnConsoleMessage") pyMessage = CefToPyString(cefMessage) pySource = CefToPyString(cefSource) callback = pyBrowser.GetClientCallback("OnConsoleMessage") diff --git a/src/handlers/focus_handler.pyx b/src/handlers/focus_handler.pyx index f966c613..71c04523 100644 --- a/src/handlers/focus_handler.pyx +++ b/src/handlers/focus_handler.pyx @@ -3,6 +3,8 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" + cimport cef_types from cef_types cimport TID_UI @@ -17,7 +19,7 @@ cdef public void FocusHandler_OnTakeFocus( cdef PyBrowser browser try: assert IsThread(TID_UI), "Must be called on the UI thread" - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "OnTakeFocus") callback = browser.GetClientCallback("OnTakeFocus") if callback: callback(browser=browser, next=next_) @@ -34,7 +36,7 @@ cdef public cpp_bool FocusHandler_OnSetFocus( cdef py_bool ret try: assert IsThread(TID_UI), "Must be called on the UI thread" - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "OnSetFocus") callback = browser.GetClientCallback("OnSetFocus") if callback: ret = callback(browser=browser, source=source) @@ -52,7 +54,7 @@ cdef public void FocusHandler_OnGotFocus( cdef PyBrowser browser try: assert IsThread(TID_UI), "Must be called on the UI thread" - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "OnGotFocus") callback = browser.GetClientCallback("OnGotFocus") if callback: callback(browser=browser) diff --git a/src/handlers/javascript_dialog_handler.pyx b/src/handlers/javascript_dialog_handler.pyx index 0c7fa071..283aad54 100644 --- a/src/handlers/javascript_dialog_handler.pyx +++ b/src/handlers/javascript_dialog_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" # enum cef_jsdialog_type_t cimport cef_types @@ -48,7 +49,7 @@ cdef public cpp_bool JavascriptDialogHandler_OnJavascriptDialog( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnJavascriptDialog") pyOriginUrl = CefToPyString(origin_url) pyMessageText = CefToPyString(message_text) pyDefaultPromptText = CefToPyString(default_prompt_text) @@ -86,7 +87,8 @@ cdef public cpp_bool JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, + "OnBeforeUnloadJavascriptDialog") pyMessageText = CefToPyString(message_text) pyIsReload = bool(is_reload) pyCallback = CreatePyJavascriptDialogCallback(callback) @@ -110,7 +112,8 @@ cdef public void JavascriptDialogHandler_OnResetJavascriptDialogState( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, + "OnResetJavascriptDialogState") callback = pyBrowser.GetClientCallback( "OnResetJavascriptDialogState") if callback: @@ -124,7 +127,8 @@ cdef public void JavascriptDialogHandler_OnJavascriptDialogClosed( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, + "OnJavascriptDialogClosed") callback = pyBrowser.GetClientCallback("OnJavascriptDialogClosed") if callback: callback(browser=pyBrowser) diff --git a/src/handlers/keyboard_handler.pyx b/src/handlers/keyboard_handler.pyx index 408487c5..c895b9b5 100644 --- a/src/handlers/keyboard_handler.pyx +++ b/src/handlers/keyboard_handler.pyx @@ -54,7 +54,7 @@ cdef public cpp_bool KeyboardHandler_OnPreKeyEvent( cdef py_bool returnValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPreKeyEvent") pyEvent = CefToPyKeyEvent(cefEvent) pyIsKeyboardShortcutOut = [cefIsKeyboardShortcut[0]] callback = pyBrowser.GetClientCallback("OnPreKeyEvent") @@ -82,7 +82,7 @@ cdef public cpp_bool KeyboardHandler_OnKeyEvent( cdef py_bool returnValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnKeyEvent") pyEvent = CefToPyKeyEvent(cefEvent) callback = pyBrowser.GetClientCallback("OnKeyEvent") if callback: diff --git a/src/handlers/lifespan_handler.pyx b/src/handlers/lifespan_handler.pyx index dbc37943..ad6730b1 100644 --- a/src/handlers/lifespan_handler.pyx +++ b/src/handlers/lifespan_handler.pyx @@ -47,7 +47,7 @@ cdef public cpp_bool LifespanHandler_OnBeforePopup( cdef object callback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforePopup") pyFrame = GetPyFrame(cefFrame) pyTargetUrl = CefToPyString(targetUrl) pyTargetFrameName = CefToPyString(targetFrameName) @@ -84,7 +84,7 @@ cdef public void LifespanHandler_OnAfterCreated( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnAfterCreated") callback = GetGlobalClientCallback("OnAfterCreated") if callback: callback(browser=pyBrowser) @@ -97,7 +97,7 @@ cdef public cpp_bool LifespanHandler_DoClose( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "DoClose") callback = pyBrowser.GetClientCallback("DoClose") if callback: return bool(callback(browser=pyBrowser)) @@ -112,7 +112,8 @@ cdef public void LifespanHandler_OnBeforeClose( cdef PyBrowser pyBrowser cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + Debug("LifespanHandler_OnBeforeClose") + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeClose") callback = pyBrowser.GetClientCallback("OnBeforeClose") if callback: callback(browser=pyBrowser) @@ -120,6 +121,8 @@ cdef public void LifespanHandler_OnBeforeClose( RemovePyFramesForBrowser(pyBrowser.GetIdentifier()) RemovePyBrowser(pyBrowser.GetIdentifier()) if g_MessageLoop_called and not len(g_pyBrowsers): + # Automatically quit message loop when last browser was closed. + # This is required for hello_world.py example to work. QuitMessageLoop() except: diff --git a/src/handlers/load_handler.pyx b/src/handlers/load_handler.pyx index 47e1d28d..c7627f5c 100644 --- a/src/handlers/load_handler.pyx +++ b/src/handlers/load_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cdef public void LoadHandler_OnLoadingStateChange( CefRefPtr[CefBrowser] cefBrowser, @@ -13,7 +14,7 @@ cdef public void LoadHandler_OnLoadingStateChange( cdef PyBrowser pyBrowser cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadingStateChange") callback = pyBrowser.GetClientCallback("OnLoadingStateChange") if callback: callback(browser=pyBrowser, @@ -32,7 +33,7 @@ cdef public void LoadHandler_OnLoadStart( cdef PyFrame pyFrame cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadStart") pyFrame = GetPyFrame(cefFrame) clientCallback = pyBrowser.GetClientCallback("OnLoadStart") if clientCallback: @@ -50,7 +51,7 @@ cdef public void LoadHandler_OnLoadEnd( cdef PyFrame pyFrame cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadEnd") pyFrame = GetPyFrame(cefFrame) clientCallback = pyBrowser.GetClientCallback("OnLoadEnd") if clientCallback: @@ -77,7 +78,7 @@ cdef public void LoadHandler_OnLoadError( # the error code will be ERR_ABORTED. In such cases calls # to OnLoadError should be ignored and not handled by user # scripts. The wxpython example implements such behavior. - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadError") pyFrame = GetPyFrame(cefFrame) errorTextOut = [CefToPyString(cefErrorText)] clientCallback = pyBrowser.GetClientCallback("OnLoadError") diff --git a/src/handlers/render_handler.pyx b/src/handlers/render_handler.pyx index acbb5c28..1594d6bd 100644 --- a/src/handlers/render_handler.pyx +++ b/src/handlers/render_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cimport cef_types @@ -29,7 +30,7 @@ cdef public cpp_bool RenderHandler_GetRootScreenRect( cdef list pyRect = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetRootScreenRect") callback = pyBrowser.GetClientCallback("GetRootScreenRect") if callback: ret = callback(browser=pyBrowser, rect_out=pyRect) @@ -56,7 +57,7 @@ cdef public cpp_bool RenderHandler_GetViewRect( cdef list pyRect = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetViewRect") callback = pyBrowser.GetClientCallback("GetViewRect") if callback: ret = callback(browser=pyBrowser, rect_out=pyRect) @@ -83,7 +84,7 @@ cdef public cpp_bool RenderHandler_GetScreenRect( cdef list pyRect = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetScreenRect") callback = pyBrowser.GetClientCallback("GetScreenRect") if callback: ret = callback(browser=pyBrowser, rect_out=pyRect) @@ -112,7 +113,7 @@ cdef public cpp_bool RenderHandler_GetScreenPoint( cdef list screenCoordinates = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetScreenPoint") callback = pyBrowser.GetClientCallback("GetScreenPoint") if callback: ret = callback(browser=pyBrowser, @@ -146,7 +147,7 @@ cdef public void RenderHandler_OnPopupShow( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPopupShow") callback = pyBrowser.GetClientCallback("OnPopupShow") if callback: callback(browser=pyBrowser, show=show) @@ -161,7 +162,7 @@ cdef public void RenderHandler_OnPopupSize( cdef PyBrowser pyBrowser cdef list pyRect try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPopupSize") callback = pyBrowser.GetClientCallback("OnPopupSize") if callback: pyRect = [cefRect.x, cefRect.y, cefRect.width, cefRect.height] @@ -187,8 +188,7 @@ cdef public void RenderHandler_OnPaint( cdef CefRect cefRect cdef PaintBuffer paintBuffer try: - pyBrowser = GetPyBrowser(cefBrowser) - + pyBrowser = GetPyBrowser(cefBrowser, "OnPaint") iterator = cefDirtyRects.begin() while iterator != cefDirtyRects.end(): cefRect = deref(iterator) @@ -223,7 +223,7 @@ cdef public void RenderHandler_OnCursorChange( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnCursorChange") callback = pyBrowser.GetClientCallback("OnCursorChange") if callback: callback(browser=pyBrowser, cursor=cursor) @@ -236,7 +236,7 @@ cdef public void RenderHandler_OnScrollOffsetChanged( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnScrollOffsetChanged") callback = pyBrowser.GetClientCallback("OnScrollOffsetChanged") if callback: callback(browser=pyBrowser) @@ -254,7 +254,7 @@ cdef public cpp_bool RenderHandler_StartDragging( cdef DragData drag_data cdef py_bool ret try: - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "StartDragging") drag_data = DragData_Init(cef_drag_data) callback = browser.GetClientCallback("StartDragging") if callback: @@ -280,7 +280,7 @@ cdef public void RenderHandler_UpdateDragCursor( ) except * with gil: cdef PyBrowser browser try: - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "UpdateDragCursor") callback = browser.GetClientCallback("UpdateDragCursor") if callback: callback(browser=browser, operation=operation) diff --git a/src/handlers/request_handler.pyx b/src/handlers/request_handler.pyx index 259e23a1..b2cadbe7 100644 --- a/src/handlers/request_handler.pyx +++ b/src/handlers/request_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" # cef_termination_status_t cimport cef_types @@ -67,7 +68,7 @@ cdef public cpp_bool RequestHandler_OnBeforeBrowse( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeBrowse") pyFrame = GetPyFrame(cefFrame) pyRequest = CreatePyRequest(cefRequest) pyIsRedirect = bool(cefIsRedirect) @@ -97,7 +98,7 @@ cdef public cpp_bool RequestHandler_OnBeforeResourceLoad( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeResourceLoad") pyFrame = GetPyFrame(cefFrame) pyRequest = CreatePyRequest(cefRequest) clientCallback = pyBrowser.GetClientCallback("OnBeforeResourceLoad") @@ -125,7 +126,7 @@ cdef public CefRefPtr[CefResourceHandler] RequestHandler_GetResourceHandler( cdef object clientCallback cdef object returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetResourceHandler") pyFrame = GetPyFrame(cefFrame) pyRequest = CreatePyRequest(cefRequest) clientCallback = pyBrowser.GetClientCallback("GetResourceHandler") @@ -161,7 +162,7 @@ cdef public void RequestHandler_OnResourceRedirect( cdef PyResponse pyResponse cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnResourceRedirect") pyFrame = GetPyFrame(cefFrame) pyOldUrl = CefToPyString(cefOldUrl) pyNewUrlOut = [CefToPyString(cefNewUrl)] @@ -206,7 +207,7 @@ cdef public cpp_bool RequestHandler_GetAuthCredentials( cdef list pyPasswordOut cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetAuthCredentials") pyFrame = GetPyFrame(cefFrame) pyIsProxy = bool(cefIsProxy) pyHost = CefToPyString(cefHost) @@ -260,7 +261,7 @@ cdef public cpp_bool RequestHandler_OnQuotaRequest( cdef py_bool returnValue cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnQuotaRequest") pyOriginUrl = CefToPyString(cefOriginUrl) clientCallback = pyBrowser.GetClientCallback("OnQuotaRequest") if clientCallback: @@ -290,7 +291,7 @@ cdef public CefRefPtr[CefCookieManager] RequestHandler_GetCookieManager( cdef object clientCallback cdef PyCookieManager returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetCookieManager") pyMainUrl = CefToPyString(cefMainUrl) if pyBrowser: # Browser may be empty. @@ -322,7 +323,7 @@ cdef public void RequestHandler_OnProtocolExecution( cdef list pyAllowOSExecutionOut cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnProtocolExecution") pyUrl = CefToPyString(cefUrl) pyAllowOSExecutionOut = [bool(cefAllowOSExecution)] clientCallback = pyBrowser.GetClientCallback("OnProtocolExecution") @@ -357,7 +358,7 @@ cdef public cpp_bool RequestHandler_OnBeforePluginLoad( cdef py_bool returnValue cdef object clientCallback try: - py_browser = GetPyBrowser(browser) + py_browser = GetPyBrowser(browser, "OnBeforePluginLoad") py_plugin_info = CreatePyWebPluginInfo(plugin_info) clientCallback = GetGlobalClientCallback("OnBeforePluginLoad") if clientCallback: @@ -412,7 +413,7 @@ cdef public void RequestHandler_OnRendererProcessTerminated( cdef PyBrowser pyBrowser cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnRendererProcessTerminated") clientCallback = pyBrowser.GetClientCallback( "OnRendererProcessTerminated") if clientCallback: @@ -434,7 +435,7 @@ cdef public void RequestHandler_OnPluginCrashed( cdef PyBrowser pyBrowser cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPluginCrashed") clientCallback = pyBrowser.GetClientCallback("OnPluginCrashed") if clientCallback: clientCallback( diff --git a/src/handlers/v8context_handler.pyx b/src/handlers/v8context_handler.pyx index 741daab8..704f6ad9 100644 --- a/src/handlers/v8context_handler.pyx +++ b/src/handlers/v8context_handler.pyx @@ -9,6 +9,7 @@ # a bit delayed due to asynchronous way this is being done. include "../cefpython.pyx" +include "../browser.pyx" cdef public void V8ContextHandler_OnContextCreated( CefRefPtr[CefBrowser] cefBrowser, @@ -19,7 +20,7 @@ cdef public void V8ContextHandler_OnContextCreated( cdef object clientCallback try: Debug("V8ContextHandler_OnContextCreated()") - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnContextCreated") pyBrowser.SetUserData("__v8ContextCreated", True) pyFrame = GetPyFrame(cefFrame) # User defined callback. @@ -48,6 +49,10 @@ cdef public void V8ContextHandler_OnContextReleased( # were released. Debug("V8ContextHandler_OnContextReleased()") pyBrowser = GetPyBrowserById(browserId) + if not pyBrowser: + Debug("OnContextReleased: Browser doesn't exist anymore, id={id}" + .format(id=str(browserId))) + return pyFrame = GetPyFrameById(browserId, frameId) if pyBrowser and pyFrame: clientCallback = pyBrowser.GetClientCallback("OnContextReleased") @@ -56,10 +61,10 @@ cdef public void V8ContextHandler_OnContextReleased( else: if not pyBrowser: Debug("V8ContextHandler_OnContextReleased() WARNING: " - "pyBrowser not found") + "PyBrowser not found") if not pyFrame: Debug("V8ContextHandler_OnContextReleased() WARNING: " - "pyFrame not found") + "PyFrame not found") RemovePyFrame(browserId, frameId) except: (exc_type, exc_value, exc_trace) = sys.exc_info() diff --git a/src/handlers/v8function_handler.pyx b/src/handlers/v8function_handler.pyx index c4cfd89d..2c9b3f1c 100644 --- a/src/handlers/v8function_handler.pyx +++ b/src/handlers/v8function_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cdef public void V8FunctionHandler_Execute( CefRefPtr[CefBrowser] cefBrowser, @@ -18,7 +19,7 @@ cdef public void V8FunctionHandler_Execute( cdef object returnValue cdef py_string jsErrorMessage try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "V8FunctionHandler_Execute") pyFrame = GetPyFrame(cefFrame) functionName = CefToPyString(cefFunctionName) Debug("V8FunctionHandler_Execute(): functionName=%s" % functionName) diff --git a/src/javascript_callback.pyx b/src/javascript_callback.pyx index 81a06eb8..be474be7 100644 --- a/src/javascript_callback.pyx +++ b/src/javascript_callback.pyx @@ -3,9 +3,11 @@ # Project website: https://github.com/cztomczak/cefpython include "cefpython.pyx" +include "browser.pyx" cdef JavascriptCallback CreateJavascriptCallback(int callbackId, - CefRefPtr[CefBrowser] cefBrowser, object frameId, py_string functionName): + CefRefPtr[CefBrowser] cefBrowser, object frameId, + py_string functionName): # frameId is int64 cdef JavascriptCallback jsCallback = JavascriptCallback() jsCallback.callbackId = callbackId @@ -17,6 +19,8 @@ cdef JavascriptCallback CreateJavascriptCallback(int callbackId, return jsCallback cdef class JavascriptCallback: + """A javascript callback object may still live while browser/frame + are destroyed. Always check frame/browser for None value.""" cdef int callbackId cdef PyFrame frame cdef py_string functionName @@ -32,18 +36,24 @@ cdef class JavascriptCallback: "ExecuteJavascriptCallback", [self.callbackId] + list(args)) else: - Debug("JavascriptCallback.Call() FAILED: browser not found, " \ - "callbackId = %s" % self.callbackId) + # This code probably ain't needed + raise Exception("JavascriptCallback.Call() FAILED: browser" + " not found, callbackId = %s" + % self.callbackId) else: - Debug("JavascriptCallback.Call() FAILED: frame not found, " \ - "callbackId = %s" % self.callbackId) + # This code probably ain't needed + raise Exception("JavascriptCallback.Call() FAILED: frame not found" + ", callbackId = %s" % self.callbackId) def GetFunctionName(self): return self.functionName def GetName(self): - # DEPRECATED name. + """@deprecated.""" return self.GetFunctionName() + def GetId(self): + return self.callbackId + def GetFrame(self): return self.frame diff --git a/tools/build.py b/tools/build.py index fce02f34..9b6db351 100644 --- a/tools/build.py +++ b/tools/build.py @@ -184,6 +184,17 @@ def check_directories(): if not os.path.exists(BUILD_CEFPYTHON): os.makedirs(BUILD_CEFPYTHON) + # Info if directory missing + if not os.path.exists(CEF_BINARIES_LIBRARIES): + prebuilt_name = get_cef_binaries_libraries_basename(OS_POSTFIX2) + print("[build.py] ERROR: Couldn't find CEF prebuilt binaries and" + " libraries: 'build/{prebuilt_dir}/'. Download it" + " from GitHub released tagged eg. 'v50-upstream` or download" + " CEF binaries from Spotify Automated Builds and then run" + "`automate.py --prebuilt-cef`." + .format(prebuilt_dir=prebuilt_name)) + sys.exit(1) + # Check directories exist assert os.path.exists(BUILD_DIR) assert os.path.exists(BUILD_CEFPYTHON)