diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 704c5d6..3b5c185 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -16,6 +16,7 @@ from .._commons.tools import get_usable_path from .._commons.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll from .._units.clicker import Clicker from .._units.element_states import ChromiumElementStates, ShadowRootStates +from .._units.locations import Locations from .._units.select_element import SelectElement from .._units.setter import ChromiumElementSetter from .._units.waiter import ChromiumElementWaiter @@ -1438,103 +1439,6 @@ def send_key(ele, modifier, key): ele.page.run_cdp('Input.dispatchKeyEvent', **data) -class Locations(object): - def __init__(self, ele): - """ - :param ele: ChromiumElement - """ - self._ele = ele - - @property - def location(self): - """返回元素左上角的绝对坐标""" - cl = self.viewport_location - return self._get_page_coord(cl[0], cl[1]) - - @property - def midpoint(self): - """返回元素中间点的绝对坐标""" - cl = self.viewport_midpoint - return self._get_page_coord(cl[0], cl[1]) - - @property - def click_point(self): - """返回元素接受点击的点的绝对坐标""" - cl = self.viewport_click_point - return self._get_page_coord(cl[0], cl[1]) - - @property - def viewport_location(self): - """返回元素左上角在视口中的坐标""" - m = self._get_viewport_rect('border') - return int(m[0]), int(m[1]) - - @property - def viewport_midpoint(self): - """返回元素中间点在视口中的坐标""" - m = self._get_viewport_rect('border') - return int(m[0] + (m[2] - m[0]) // 2), int(m[3] + (m[5] - m[3]) // 2) - - @property - def viewport_click_point(self): - """返回元素接受点击的点视口坐标""" - m = self._get_viewport_rect('padding') - return int(self.viewport_midpoint[0]), int(m[1]) + 3 - - @property - def screen_location(self): - """返回元素左上角在屏幕上坐标,左上角为(0, 0)""" - vx, vy = self._ele.page.rect.viewport_location - ex, ey = self.viewport_location - pr = self._ele.page.run_js('return window.devicePixelRatio;') - return int((vx + ex) * pr), int((ey + vy) * pr) - - @property - def screen_midpoint(self): - """返回元素中点在屏幕上坐标,左上角为(0, 0)""" - vx, vy = self._ele.page.rect.viewport_location - ex, ey = self.viewport_midpoint - pr = self._ele.page.run_js('return window.devicePixelRatio;') - return int((vx + ex) * pr), int((ey + vy) * pr) - - @property - def screen_click_point(self): - """返回元素中点在屏幕上坐标,左上角为(0, 0)""" - vx, vy = self._ele.page.rect.viewport_location - ex, ey = self.viewport_click_point - pr = self._ele.page.run_js('return window.devicePixelRatio;') - return int((vx + ex) * pr), int((ey + vy) * pr) - - @property - def rect(self): - """返回元素四个角坐标,顺序:坐上、右上、右下、左下,没有大小的元素抛出NoRectError""" - vr = self._get_viewport_rect('border') - r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] - sx = r['pageX'] - sy = r['pageY'] - return [(vr[0] + sx, vr[1] + sy), (vr[2] + sx, vr[3] + sy), (vr[4] + sx, vr[5] + sy), (vr[6] + sx, vr[7] + sy)] - - @property - def viewport_rect(self): - """返回元素四个角视口坐标,顺序:坐上、右上、右下、左下,没有大小的元素抛出NoRectError""" - r = self._get_viewport_rect('border') - return [(r[0], r[1]), (r[2], r[3]), (r[4], r[5]), (r[6], r[7])] - - def _get_viewport_rect(self, quad): - """按照类型返回在可视窗口中的范围 - :param quad: 方框类型,margin border padding - :return: 四个角坐标 - """ - return self._ele.page.run_cdp('DOM.getBoxModel', backendNodeId=self._ele.ids.backend_id)['model'][quad] - - def _get_page_coord(self, x, y): - """根据视口坐标获取绝对坐标""" - r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] - sx = r['pageX'] - sy = r['pageY'] - return x + sx, y + sy - - class ChromiumScroll(object): """用于滚动的对象""" diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index f65b149..c41464f 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -4,7 +4,7 @@ @Contact : g1879@qq.com """ from pathlib import Path -from typing import Union, Tuple, List, Any, Optional +from typing import Union, Tuple, List, Any from .._base.base import DrissionElement, BaseElement from .._commons.constants import NoneElement @@ -15,6 +15,7 @@ from .._pages.chromium_page import ChromiumPage from .._pages.web_page import WebPage from .._units.clicker import Clicker from .._units.element_states import ShadowRootStates, ChromiumElementStates +from .._units.locations import Locations from .._units.select_element import SelectElement from .._units.setter import ChromiumElementSetter from .._units.waiter import ChromiumElementWaiter @@ -363,48 +364,6 @@ def send_enter(ele: ChromiumElement) -> None: ... def send_key(ele: ChromiumElement, modifier: int, key: str) -> None: ... -class Locations(object): - def __init__(self, ele: ChromiumElement): - self._ele: ChromiumElement = ... - - @property - def location(self) -> Tuple[int, int]: ... - - @property - def midpoint(self) -> Tuple[int, int]: ... - - @property - def click_point(self) -> Tuple[int, int]: ... - - @property - def viewport_location(self) -> Tuple[int, int]: ... - - @property - def viewport_midpoint(self) -> Tuple[int, int]: ... - - @property - def viewport_click_point(self) -> Tuple[int, int]: ... - - @property - def screen_location(self) -> Tuple[int, int]: ... - - @property - def screen_midpoint(self) -> Tuple[int, int]: ... - - @property - def screen_click_point(self) -> Tuple[int, int]: ... - - @property - def rect(self) -> list: ... - - @property - def viewport_rect(self) -> list: ... - - def _get_viewport_rect(self, quad: str) -> Union[list, None]: ... - - def _get_page_coord(self, x: int, y: int) -> Tuple[int, int]: ... - - class ChromiumScroll(object): def __init__(self, page_or_ele: Union[ChromiumBase, ChromiumElement, ChromiumFrame]): self.t1: str = ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index a6726e8..e2c5b37 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -103,6 +103,11 @@ class ChromiumBase(BasePage): self._driver.call_method('Page.enable') self._driver.call_method('Emulation.setFocusEmulationEnabled', enabled=True) + r = self.run_cdp('Page.getFrameTree') + for i in findall(r"'id': '(.*?)'", str(r)): + self.browser._frames[i] = self.tab_id + self._frame_id = r['frameTree']['frame']['id'] + self._driver.set_listener('Page.frameStartedLoading', self._onFrameStartedLoading) self._driver.set_listener('Page.frameNavigated', self._onFrameNavigated) self._driver.set_listener('Page.domContentEventFired', self._onDomContentEventFired) @@ -111,10 +116,6 @@ class ChromiumBase(BasePage): self._driver.set_listener('Page.frameAttached', self._onFrameAttached) self._driver.set_listener('Page.frameDetached', self._onFrameDetached) - r = self.run_cdp('Page.getFrameTree') - for i in findall(r"'id': '(.*?)'", str(r)): - self.browser._frames[i] = self.tab_id - self._frame_id = r['frameTree']['frame']['id'] def _get_document(self): if self._is_reading: diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index eefc5ab..b71688d 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -7,7 +7,7 @@ from time import perf_counter, sleep from .._commons.constants import Settings from .._commons.web import offset_scroll -from ..errors import CanNotClickError, CDPError +from ..errors import CanNotClickError, CDPError, NoRectError class Clicker(object): @@ -26,7 +26,7 @@ class Clicker(object): """ return self.left(by_js, timeout) - def left(self, by_js=False, timeout=1): + def left(self, by_js=False, timeout=2): """点击元素,可选择是否用js点击 :param by_js: 是否用js点击,为None时先用模拟点击,遇到遮挡改用js,为True时直接用js点击,为False时只用模拟点击 :param timeout: 模拟点击的超时时间,等待元素可见、不被遮挡、进入视口 @@ -35,34 +35,48 @@ class Clicker(object): if not by_js: # 模拟点击 can_click = False timeout = self._ele.page.timeout if timeout is None else timeout - if timeout == 0 and self._ele.states.has_rect: - self._ele.scroll.to_see() - if self._ele.states.is_in_viewport and self._ele.states.is_enabled and self._ele.states.is_displayed: - can_click = True + rect = None + if timeout == 0: + try: + self._ele.scroll.to_see() + if self._ele.states.is_enabled and self._ele.states.is_displayed: + rect = self._ele.locations.viewport_rect + can_click = True + except NoRectError: + if by_js is False: + raise else: + rect = self._ele.states.has_rect end_time = perf_counter() + timeout - while not self._ele.states.has_rect and perf_counter() < end_time: + while not rect and perf_counter() < end_time: + rect = self._ele.states.has_rect sleep(.001) - if self._ele.states.has_rect: + + self._ele.wait.stop_moving(timeout=end_time - perf_counter()) + if rect: self._ele.scroll.to_see() + rect = self._ele.locations.rect while perf_counter() < end_time: - if (self._ele.states.is_in_viewport and self._ele.states.is_enabled - and self._ele.states.is_displayed): + if self._ele.states.is_enabled and self._ele.states.is_displayed: can_click = True break sleep(.001) - if not self._ele.states.has_rect or not self._ele.states.is_in_viewport: + elif by_js is False: + raise NoRectError + + if can_click and not self._ele.states.is_in_viewport: by_js = True elif can_click and (by_js is False or not self._ele.states.is_covered): - vx, vy = self._ele.locations.click_point + x = int(rect[1][0] - (rect[1][0] - rect[0][0]) / 2) + y = rect[0][0] + 3 try: - r = self._ele.page.run_cdp('DOM.getNodeForLocation', x=vx, y=vy, includeUserAgentShadowDOM=True, + r = self._ele.page.run_cdp('DOM.getNodeForLocation', x=x, y=y, includeUserAgentShadowDOM=True, ignorePointerEventsNone=True) if r['backendNodeId'] != self._ele.ids.backend_id: - vx, vy = self._ele.locations.viewport_click_point + vx, vy = self._ele.locations.viewport_midpoint else: vx, vy = self._ele.locations.viewport_click_point diff --git a/DrissionPage/_units/element_states.py b/DrissionPage/_units/element_states.py index efc2b97..73d724d 100644 --- a/DrissionPage/_units/element_states.py +++ b/DrissionPage/_units/element_states.py @@ -1,4 +1,8 @@ # -*- coding:utf-8 -*- +""" +@Author : g1879 +@Contact : g1879@qq.com +""" from .._commons.web import location_in_viewport from ..errors import CDPError, NoRectError @@ -71,9 +75,9 @@ class ChromiumElementStates(object): @property def has_rect(self): - """返回元素是否拥有位置和大小,没有返回False,有返回大小元组""" + """返回元素是否拥有位置和大小,没有返回False,有返回四个角在页面中坐标组成的列表""" try: - return self._ele.size + return self._ele.locations.rect except NoRectError: return False diff --git a/DrissionPage/_units/element_states.pyi b/DrissionPage/_units/element_states.pyi index d512f7f..81ebad5 100644 --- a/DrissionPage/_units/element_states.pyi +++ b/DrissionPage/_units/element_states.pyi @@ -1,5 +1,9 @@ # -*- coding:utf-8 -*- -from typing import Union, Tuple +""" +@Author : g1879 +@Contact : g1879@qq.com +""" +from typing import Union, Tuple, List from .._elements.chromium_element import ChromiumShadowRoot, ChromiumElement @@ -33,7 +37,7 @@ class ChromiumElementStates(object): def is_covered(self) -> bool: ... @property - def has_rect(self) -> Union[bool, Tuple[int, int]]: ... + def has_rect(self) -> Union[bool, List[Tuple[float, float]]]: ... class ShadowRootStates(object): diff --git a/DrissionPage/_units/locations.py b/DrissionPage/_units/locations.py new file mode 100644 index 0000000..a402681 --- /dev/null +++ b/DrissionPage/_units/locations.py @@ -0,0 +1,103 @@ +# -*- coding:utf-8 -*- +""" +@Author : g1879 +@Contact : g1879@qq.com +""" + + +class Locations(object): + def __init__(self, ele): + """ + :param ele: ChromiumElement + """ + self._ele = ele + + @property + def location(self): + """返回元素左上角的绝对坐标""" + cl = self.viewport_location + return self._get_page_coord(cl[0], cl[1]) + + @property + def midpoint(self): + """返回元素中间点的绝对坐标""" + cl = self.viewport_midpoint + return self._get_page_coord(cl[0], cl[1]) + + @property + def click_point(self): + """返回元素接受点击的点的绝对坐标""" + cl = self.viewport_click_point + return self._get_page_coord(cl[0], cl[1]) + + @property + def viewport_location(self): + """返回元素左上角在视口中的坐标""" + m = self._get_viewport_rect('border') + return int(m[0]), int(m[1]) + + @property + def viewport_midpoint(self): + """返回元素中间点在视口中的坐标""" + m = self._get_viewport_rect('border') + return int(m[0] + (m[2] - m[0]) // 2), int(m[3] + (m[5] - m[3]) // 2) + + @property + def viewport_click_point(self): + """返回元素接受点击的点视口坐标""" + m = self._get_viewport_rect('padding') + return int(self.viewport_midpoint[0]), int(m[1]) + 3 + + @property + def screen_location(self): + """返回元素左上角在屏幕上坐标,左上角为(0, 0)""" + vx, vy = self._ele.page.rect.viewport_location + ex, ey = self.viewport_location + pr = self._ele.page.run_js('return window.devicePixelRatio;') + return int((vx + ex) * pr), int((ey + vy) * pr) + + @property + def screen_midpoint(self): + """返回元素中点在屏幕上坐标,左上角为(0, 0)""" + vx, vy = self._ele.page.rect.viewport_location + ex, ey = self.viewport_midpoint + pr = self._ele.page.run_js('return window.devicePixelRatio;') + return int((vx + ex) * pr), int((ey + vy) * pr) + + @property + def screen_click_point(self): + """返回元素中点在屏幕上坐标,左上角为(0, 0)""" + vx, vy = self._ele.page.rect.viewport_location + ex, ey = self.viewport_click_point + pr = self._ele.page.run_js('return window.devicePixelRatio;') + return int((vx + ex) * pr), int((ey + vy) * pr) + + @property + def rect(self): + """返回元素四个角坐标,顺序:坐上、右上、右下、左下,没有大小的元素抛出NoRectError""" + vr = self._get_viewport_rect('border') + r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] + sx = int(r['pageX']) + sy = int(r['pageY']) + return [(vr[0] + sx, vr[1] + sy), (vr[2] + sx, vr[3] + sy), + (vr[4] + sx, vr[5] + sy), (vr[6] + sx, vr[7] + sy)] + + @property + def viewport_rect(self): + """返回元素四个角视口坐标,顺序:坐上、右上、右下、左下,没有大小的元素抛出NoRectError""" + r = self._get_viewport_rect('border') + return [(int(r[0]), int(r[1])), (int(r[2]), int(r[3])), (int(r[4]), int(r[5])), (int(r[6]), int(r[7]))] + + def _get_viewport_rect(self, quad): + """按照类型返回在可视窗口中的范围 + :param quad: 方框类型,margin border padding + :return: 四个角坐标 + """ + return self._ele.page.run_cdp('DOM.getBoxModel', backendNodeId=self._ele.ids.backend_id)['model'][quad] + + def _get_page_coord(self, x, y): + """根据视口坐标获取绝对坐标""" + r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] + sx = r['pageX'] + sy = r['pageY'] + return x + sx, y + sy diff --git a/DrissionPage/_units/locations.pyi b/DrissionPage/_units/locations.pyi new file mode 100644 index 0000000..5e61165 --- /dev/null +++ b/DrissionPage/_units/locations.pyi @@ -0,0 +1,50 @@ +# -*- coding:utf-8 -*- +""" +@Author : g1879 +@Contact : g1879@qq.com +""" +from typing import Tuple, Union, List + +from .._elements.chromium_element import ChromiumElement + + +class Locations(object): + def __init__(self, ele: ChromiumElement): + self._ele: ChromiumElement = ... + + @property + def location(self) -> Tuple[int, int]: ... + + @property + def midpoint(self) -> Tuple[int, int]: ... + + @property + def click_point(self) -> Tuple[int, int]: ... + + @property + def viewport_location(self) -> Tuple[int, int]: ... + + @property + def viewport_midpoint(self) -> Tuple[int, int]: ... + + @property + def viewport_click_point(self) -> Tuple[int, int]: ... + + @property + def screen_location(self) -> Tuple[int, int]: ... + + @property + def screen_midpoint(self) -> Tuple[int, int]: ... + + @property + def screen_click_point(self) -> Tuple[int, int]: ... + + @property + def rect(self) -> List[Tuple[int, int], ...]: ... + + @property + def viewport_rect(self) -> List[Tuple[int, int], ...]: ... + + def _get_viewport_rect(self, quad: str) -> Union[list, None]: ... + + def _get_page_coord(self, x: int, y: int) -> Tuple[int, int]: ...