From 1671a40361b283c6b6036614b0615b2898d9872a Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 18 Aug 2018 19:11:10 +0200 Subject: [PATCH] Add RenderHandler.OnTextSelectionChanged and Browser.Invalidate (#403). Add tests for OnTextSelectionChanged. Add on_load_end() helper func in unit tests. --- api/Browser.md | 17 +++++++++++++++ api/RenderHandler.md | 19 +++++++++++++++++ src/browser.pyx | 7 ++++++- src/cef_v59..v66_changes.txt | 5 +++-- src/client_handler/render_handler.cpp | 8 +++++++ src/client_handler/render_handler.h | 4 ++++ src/extern/cef/cef_browser.pxd | 3 ++- src/extern/cef/cef_types.pxd | 4 ++++ src/handlers/render_handler.pyx | 19 +++++++++++++++++ unittests/_common.py | 12 +++++++++++ unittests/osr_test.py | 30 ++++++++++++++++++++++++++- 11 files changed, 123 insertions(+), 5 deletions(-) diff --git a/api/Browser.md b/api/Browser.md index cf2ab61d..beda31e5 100644 --- a/api/Browser.md +++ b/api/Browser.md @@ -591,6 +591,23 @@ machinery. Returns true if a document has been loaded in the browser. +### Invalidate + +| | | +| --- | --- | +| element_type | PaintElementType | +| __Return__ | void | + +Description from upstream CEF: +> Invalidate the view. The browser will call CefRenderHandler::OnPaint +> asynchronously. This method is only used when window rendering is +> disabled. + +`PaintElementType` enum values defined in cefpython module: +* PET_VIEW +* PET_POPUP + + ### IsFullscreen | | | diff --git a/api/RenderHandler.md b/api/RenderHandler.md index bb25ac01..3b0d0f0d 100644 --- a/api/RenderHandler.md +++ b/api/RenderHandler.md @@ -177,6 +177,25 @@ Called when the browser's cursor has changed. If |type| is CT_CUSTOM then Called when the scroll offset has changed. +### OnTextSelectionChanged + +| Parameter | Type | +| --- | --- | +| browser | [Browser](Browser.md) | +| selected_text | str | +| selected_range | list[x, y] | +| __Return__ | void | + +Description from upstream CEF: +> Called when text selection has changed for the specified |browser|. +> |selected_text| is the currently selected text and |selected_range| is +> the character range. + +NOTE: this callback seems to be called only when selecting text +with a mouse. When selecting text programmatically using javascript +code it doesn't get called. + + ### StartDragging | Parameter | Type | diff --git a/src/browser.pyx b/src/browser.pyx index 5f26e7d7..335654d6 100644 --- a/src/browser.pyx +++ b/src/browser.pyx @@ -247,7 +247,8 @@ cdef class PyBrowser: "GetScreenRect", "OnPopupShow", "OnPopupSize", "OnPaint", "OnCursorChange", "OnScrollOffsetChanged", - "StartDragging", "UpdateDragCursor"] + "StartDragging", "UpdateDragCursor", + "OnTextSelectionChanged"] # JavascriptDialogHandler self.allowedClientCallbacks += ["OnJavascriptDialog", "OnBeforeUnloadJavascriptDialog", @@ -476,6 +477,10 @@ cdef class PyBrowser: cpdef py_bool HasDocument(self): return self.GetCefBrowser().get().HasDocument() + cpdef py_void Invalidate(self, + cef_types.cef_paint_element_type_t element_type): + return self.GetCefBrowserHost().get().Invalidate(element_type) + cpdef py_bool IsFullscreen(self): return bool(self.isFullscreen) diff --git a/src/cef_v59..v66_changes.txt b/src/cef_v59..v66_changes.txt index 7a6fc5fb..2bb23ccf 100644 --- a/src/cef_v59..v66_changes.txt +++ b/src/cef_v59..v66_changes.txt @@ -71,6 +71,8 @@ NEW FEATURES ------------ + unittests/osr_test.py - new test for off-screen rendering mode ++ cefpython.SetGlobalClientHandler ++ Browser.Invalidate internal/cef_types.h + cef_log_severity_t: new key LOGSEVERITY_DEBUG (no need to expose, @@ -85,10 +87,9 @@ internal/cef_types.h cef_accessibility_handler.h + CefAccessibilityHandler + CefRenderHandler::GetAccessibilityHandler -+ cefpython.SetGlobalClientHandler cef_render_handler.h -- OnTextSelectionChanged ++ OnTextSelectionChanged cef_browser.h + SetAccessibilityState diff --git a/src/client_handler/render_handler.cpp b/src/client_handler/render_handler.cpp index f6b4d5be..cf45d15e 100644 --- a/src/client_handler/render_handler.cpp +++ b/src/client_handler/render_handler.cpp @@ -106,3 +106,11 @@ void RenderHandler::UpdateDragCursor(CefRefPtr browser, REQUIRE_UI_THREAD(); RenderHandler_UpdateDragCursor(browser, operation); } + +void RenderHandler::OnTextSelectionChanged(CefRefPtr browser, + const CefString& selected_text, + const CefRange& selected_range) { + REQUIRE_UI_THREAD(); + RenderHandler_OnTextSelectionChanged(browser, selected_text, + selected_range); +} diff --git a/src/client_handler/render_handler.h b/src/client_handler/render_handler.h index b9eb59fa..75eee86c 100644 --- a/src/client_handler/render_handler.h +++ b/src/client_handler/render_handler.h @@ -64,6 +64,10 @@ class RenderHandler : public CefRenderHandler, void UpdateDragCursor(CefRefPtr browser, cef_drag_operations_mask_t operation) override; + void OnTextSelectionChanged(CefRefPtr browser, + const CefString& selected_text, + const CefRange& selected_range) override; + private: IMPLEMENT_REFCOUNTING(RenderHandler); }; diff --git a/src/extern/cef/cef_browser.pxd b/src/extern/cef/cef_browser.pxd index 6c5e599a..31a4a29d 100644 --- a/src/extern/cef/cef_browser.pxd +++ b/src/extern/cef/cef_browser.pxd @@ -11,7 +11,7 @@ from libcpp cimport bool as cpp_bool from libcpp.vector cimport vector as cpp_vector from cef_frame cimport CefFrame cimport cef_types -from cef_types cimport int64, cef_state_t +from cef_types cimport int64, cef_state_t, PaintElementType from cef_types cimport CefBrowserSettings, CefPoint from cef_drag_data cimport CefDragData from cef_types cimport CefMouseEvent @@ -87,6 +87,7 @@ cdef extern from "include/cef_browser.h": void AddWordToDictionary(const CefString& word) void SetAccessibilityState(cef_state_t accessibility_state) + void Invalidate(cef_types.cef_paint_element_type_t type) cdef cppclass CefBrowser: diff --git a/src/extern/cef/cef_types.pxd b/src/extern/cef/cef_types.pxd index 70263780..6bd21ba4 100644 --- a/src/extern/cef/cef_types.pxd +++ b/src/extern/cef/cef_types.pxd @@ -377,3 +377,7 @@ cdef extern from "include/internal/cef_types.h": ctypedef enum cef_focus_source_t: FOCUS_SOURCE_NAVIGATION, FOCUS_SOURCE_SYSTEM, + + cdef cppclass CefRange: + int from_val "from" + int to_val "to" diff --git a/src/handlers/render_handler.pyx b/src/handlers/render_handler.pyx index 1594d6bd..a8ad4912 100644 --- a/src/handlers/render_handler.pyx +++ b/src/handlers/render_handler.pyx @@ -4,8 +4,10 @@ include "../cefpython.pyx" include "../browser.pyx" +include "../string_utils.pyx" cimport cef_types +from cef_types cimport CefRange # cef_paint_element_type_t, PaintElementType PET_VIEW = cef_types.PET_VIEW @@ -288,3 +290,20 @@ cdef public void RenderHandler_UpdateDragCursor( (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) +cdef public void RenderHandler_OnTextSelectionChanged( + CefRefPtr[CefBrowser] cef_browser, + const CefString& selected_text, + const CefRange& selected_range + ) except * with gil: + cdef PyBrowser browser + try: + browser = GetPyBrowser(cef_browser, "OnTextSelectionChanged") + callback = browser.GetClientCallback("OnTextSelectionChanged") + if callback: + callback(browser=browser, + selected_text=CefToPyString(selected_text), + selected_range=[selected_range.from_val, + selected_range.to_val]) + except: + (exc_type, exc_value, exc_trace) = sys.exc_info() + sys.excepthook(exc_type, exc_value, exc_trace) diff --git a/unittests/_common.py b/unittests/_common.py index 5011bda4..59052583 100644 --- a/unittests/_common.py +++ b/unittests/_common.py @@ -14,6 +14,7 @@ g_subtests_ran = 0 g_js_code_completed = False +g_on_load_end_callbacks = [] def subtest_message(message): @@ -51,6 +52,11 @@ def do_message_loop_work(work_loops): time.sleep(0.01) +def on_load_end(callback, *args): + global g_on_load_end_callbacks + g_on_load_end_callbacks.append([callback, args]) + + def js_code_completed(): """Sometimes window.onload can execute before javascript bindings are ready if the document loads very fast. When setting javascript @@ -154,6 +160,12 @@ def OnLoadEnd(self, browser, frame, http_code, **_): frame.GetSource(self.frame_source_visitor) browser.ExecuteJavascript("print('LoadHandler.OnLoadEnd() ok')") + subtest_message("Executing callbacks registered with on_load_end()") + global g_on_load_end_callbacks + for callback_data in g_on_load_end_callbacks: + callback_data[0](*callback_data[1]) + del g_on_load_end_callbacks + def OnLoadingStateChange(self, browser, is_loading, can_go_back, can_go_forward, **_): if is_loading: diff --git a/unittests/osr_test.py b/unittests/osr_test.py index 4c26cb8f..323f2de6 100644 --- a/unittests/osr_test.py +++ b/unittests/osr_test.py @@ -58,6 +58,7 @@

Off-screen rendering test

+
Test selection.
""" @@ -128,6 +129,9 @@ def test_osr(self): browser.SendFocusEvent(True) browser.WasResized() + # Test selection + on_load_end(select_h1_text, browser) + # Message loop run_message_loop() @@ -188,6 +192,17 @@ def _OnAccessibilityLocationChange(self, **_): self._OnAccessibilityLocationChange_True = True +def select_h1_text(browser): + browser.SendMouseClickEvent(0, 0, cef.MOUSEBUTTON_LEFT, + mouseUp=False, clickCount=1) + browser.SendMouseMoveEvent(400, 20, mouseLeave=False, + modifiers=cef.EVENTFLAG_LEFT_MOUSE_BUTTON) + browser.SendMouseClickEvent(400, 20, cef.MOUSEBUTTON_LEFT, + mouseUp=True, clickCount=1) + browser.Invalidate(cef.PET_VIEW) + subtest_message("select_h1_text() ok") + + class RenderHandler(object): def __init__(self, test_case): self.test_case = test_case @@ -198,6 +213,7 @@ def __init__(self, test_case): self.GetViewRect_True = False self.OnPaint_True = False + self.OnTextSelectionChanged_True = False def GetViewRect(self, rect_out, **_): """Called to retrieve the view rectangle which is relative @@ -208,7 +224,7 @@ def GetViewRect(self, rect_out, **_): rect_out.extend([0, 0, 800, 600]) return True - def OnPaint(self, element_type, paint_buffer, **_): + def OnPaint(self, browser, element_type, paint_buffer, **_): """Called when an element should be painted.""" if element_type == cef.PET_VIEW: self.test_case.assertEqual(paint_buffer.width, 800) @@ -219,6 +235,18 @@ def OnPaint(self, element_type, paint_buffer, **_): else: raise Exception("Unsupported element_type in OnPaint") + def OnTextSelectionChanged(self, selected_text, selected_range, **_): + if not self.OnTextSelectionChanged_True: + self.OnTextSelectionChanged_True = True + # First call + self.test_case.assertEqual(selected_text, "") + self.test_case.assertEqual(selected_range, [0, 0]) + else: + # Second call. + #

tag should be selected. + self.test_case.assertEqual(selected_text, + "Off-screen rendering test") + if __name__ == "__main__": _test_runner.main(os.path.basename(__file__))