From de6691adc22ab40387f554ee306db32b48a063a2 Mon Sep 17 00:00:00 2001
From: g1879 <g1879@qq.com>
Date: Wed, 18 Oct 2023 17:52:27 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Browser=E7=B1=BB=EF=BC=8C?=
 =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=9E=B6=E6=9E=84=EF=BC=8C=E6=9C=AA=E5=AE=8C?=
 =?UTF-8?q?=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 DrissionPage/browser.py                   | 137 ++++++++++++++++++++++
 DrissionPage/browser.pyi                  |  48 ++++++++
 DrissionPage/browser_download_manager.py  |  71 +++++------
 DrissionPage/browser_download_manager.pyi |  15 ++-
 DrissionPage/chromium_base.py             |  16 +--
 DrissionPage/chromium_base.pyi            |  13 +-
 DrissionPage/chromium_driver.py           |   4 +
 DrissionPage/chromium_frame.py            |   8 +-
 DrissionPage/chromium_page.py             |  74 +++---------
 DrissionPage/chromium_page.pyi            |  13 +-
 DrissionPage/chromium_tab.py              |   2 +
 DrissionPage/chromium_tab.pyi             |   5 +-
 DrissionPage/commons/browser.py           |   4 +-
 DrissionPage/setter.py                    |   2 +-
 DrissionPage/waiter.py                    |   8 +-
 DrissionPage/web_page.py                  |   2 -
 16 files changed, 278 insertions(+), 144 deletions(-)
 create mode 100644 DrissionPage/browser.py
 create mode 100644 DrissionPage/browser.pyi

diff --git a/DrissionPage/browser.py b/DrissionPage/browser.py
new file mode 100644
index 0000000..36e522e
--- /dev/null
+++ b/DrissionPage/browser.py
@@ -0,0 +1,137 @@
+# -*- coding:utf-8 -*-
+from time import sleep
+
+from .browser_download_manager import BrowserDownloadManager
+from .chromium_driver import BrowserDriver
+
+
+class Browser(object):
+    BROWSERS = {}
+
+    def __new__(cls, browser_id, page):
+        """
+        :param browser_id: BrowserDriver对象
+        :param page: ChromiumPage对象
+        """
+        if browser_id in cls.BROWSERS:
+            return cls.BROWSERS[browser_id]
+        return object.__new__(cls)
+
+    def __init__(self, browser_id, page):
+        """
+        :param page: BrowserDriver对象
+        :param page: ChromiumPage对象
+        """
+        if hasattr(self, '_created'):
+            return
+        self._created = True
+        Browser.BROWSERS[browser_id] = self
+
+        self.page = page
+        self.address = page.address
+        self._driver = BrowserDriver(browser_id, 'browser', page.address)
+        self.id = browser_id
+        self._frames = {}
+
+        self._process_id = None
+        r = self.run_cdp('SystemInfo.getProcessInfo')
+        for i in r.get('processInfo', []):
+            if i['type'] == 'browser':
+                self._process_id = i['id']
+                break
+
+        self._dl_mgr = BrowserDownloadManager(self)
+
+        self.run_cdp('Target.setDiscoverTargets')
+        self._driver.set_listener('Target.targetDestroyed', self._onTargetDestroyed)
+
+    def _onTargetDestroyed(self, **kwargs):
+        """标签页关闭时执行"""
+        tab_id = kwargs['targetId']
+        self._dl_mgr.clear_tab_info(tab_id)
+        for k, i in self._frames.items():
+            if i == tab_id:
+                self._frames.pop(k)
+
+    def run_cdp(self, cmd, **cmd_args):
+        """执行Chrome DevTools Protocol语句
+        :param cmd: 协议项目
+        :param cmd_args: 参数
+        :return: 执行的结果
+        """
+        return self._driver.call_method(cmd, **cmd_args)
+
+    @property
+    def driver(self):
+        return self._driver
+
+    @property
+    def tabs_count(self):
+        """返回标签页数量"""
+        return len(self.tabs)
+
+    @property
+    def tabs(self):
+        """返回所有标签页id组成的列表"""
+        j = self._driver.get(f'http://{self.address}/json').json()  # 不要改用cdp
+        return [i['id'] for i in j if i['type'] == 'page']
+
+    @property
+    def process_id(self):
+        """返回浏览器进程id"""
+        return self._process_id
+
+    def find_tabs(self, title=None, url=None, tab_type=None, single=True):
+        """查找符合条件的tab,返回它们的id组成的列表
+        :param title: 要匹配title的文本
+        :param url: 要匹配url的文本
+        :param tab_type: tab类型,可用列表输入多个
+        :param single: 是否返回首个结果的id,为False返回所有信息
+        :return: tab id或tab dict
+        """
+        tabs = self._driver.get(f'http://{self.address}/json').json()  # 不要改用cdp
+
+        if isinstance(tab_type, str):
+            tab_type = {tab_type}
+        elif isinstance(tab_type, (list, tuple, set)):
+            tab_type = set(tab_type)
+        elif tab_type is not None:
+            raise TypeError('tab_type只能是set、list、tuple、str、None。')
+
+        r = [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url'])
+                                 and (tab_type is None or i['type'] in tab_type))]
+        return r[0]['id'] if r and single else r
+
+    def close_tab(self, tab_id):
+        """关闭标签页
+        :param tab_id: 标签页id
+        :return: None
+        """
+        self._driver.get(f'http://{self.address}/json/close/{tab_id}')
+
+    def activate_tab(self, tab_id):
+        """使标签页变为活动状态
+        :param tab_id: 标签页id
+        :return: None
+        """
+        self._driver.get(f'http://{self.address}/json/activate/{tab_id}')
+
+    def get_window_bounds(self):
+        """返回浏览器窗口位置和大小信息"""
+        return self.run_cdp('Browser.getWindowForTarget', targetId=self.id)['bounds']
+
+    def quit(self):
+        """关闭浏览器"""
+        self.run_cdp('Browser.close')
+        self.driver.stop()
+
+        if self.process_id:
+            from os import popen
+            from platform import system
+            txt = f'tasklist | findstr {self.process_id}' if system().lower() == 'windows' \
+                else f'ps -ef | grep  {self.process_id}'
+            while True:
+                p = popen(txt)
+                if f'  {self.process_id} ' not in p.read():
+                    break
+                sleep(.2)
diff --git a/DrissionPage/browser.pyi b/DrissionPage/browser.pyi
new file mode 100644
index 0000000..91e9fdb
--- /dev/null
+++ b/DrissionPage/browser.pyi
@@ -0,0 +1,48 @@
+# -*- coding:utf-8 -*-
+from typing import List, Optional, Union
+
+from .browser_download_manager import BrowserDownloadManager
+from .chromium_page import ChromiumPage
+from .chromium_driver import BrowserDriver
+
+
+class Browser(object):
+    BROWSERS: dict = ...
+    page: ChromiumPage = ...
+    _driver: BrowserDriver = ...
+    id: str = ...
+    address: str = ...
+    _frames: dict = ...
+    _process_id: Optional[int] = ...
+    _dl_mgr: BrowserDownloadManager = ...
+
+    def __new__(cls, browser_id: str, page: ChromiumPage): ...
+
+    def __init__(self, browser_id: str, page: ChromiumPage): ...
+
+    def run_cdp(self, cmd, **cmd_args) -> dict: ...
+
+    @property
+    def driver(self) -> BrowserDriver: ...
+
+    @property
+    def tabs_count(self) -> int: ...
+
+    @property
+    def tabs(self) -> List[str]: ...
+
+    @property
+    def process_id(self) -> Optional[int]: ...
+
+    def find_tabs(self, title: str = None, url: str = None,
+                  tab_type: Union[str, list, tuple] = None, single: bool = True) -> Union[str, List[str]]: ...
+
+    def close_tab(self, tab_id: str) -> None: ...
+
+    def activate_tab(self, tab_id: str) -> None: ...
+
+    def get_window_bounds(self) -> dict: ...
+
+    def _onTargetDestroyed(self, **kwargs): ...
+
+    def quit(self) -> None: ...
diff --git a/DrissionPage/browser_download_manager.py b/DrissionPage/browser_download_manager.py
index 9bcfd3e..2d6cba5 100644
--- a/DrissionPage/browser_download_manager.py
+++ b/DrissionPage/browser_download_manager.py
@@ -8,42 +8,26 @@ from .commons.tools import get_usable_path
 
 
 class BrowserDownloadManager(object):
-    BROWSERS = {}
 
-    def __new__(cls, page):
+    def __init__(self, browser):
         """
-        :param page: ChromiumPage对象
+        :param browser: Browser对象
         """
-        if page.browser_driver.id in cls.BROWSERS:
-            return cls.BROWSERS[page.browser_driver.id]
-        return object.__new__(cls)
-
-    def __init__(self, page):
-        """
-        :param page: ChromiumPage对象
-        """
-        if hasattr(self, '_created'):
-            return
-        self._created = True
-
-        self._page = page
+        self._browser = browser
+        self._page = browser.page
         self._when_download_file_exists = 'rename'
 
-        t = TabDownloadSettings(page.tab_id)
-        t.path = page.download_path
-        self._tabs_settings = {page.tab_id: t}  # {tab_id: TabDownloadSettings}
+        t = TabDownloadSettings(self._page.tab_id)
+        t.path = self._page.download_path
         self._missions = {}  # {guid: DownloadMission}
+        self._tabs_settings = {self._page.tab_id: t}  # {tab_id: TabDownloadSettings}
         self._tab_missions = {}  # {tab_id: DownloadMission}
-        self._guid_and_tab = {}  # 记录guid在哪个tab
         self._flags = {}  # {tab_id: [bool, DownloadMission]}
 
-        self._page.browser_driver.set_listener('Browser.downloadProgress', self._onDownloadProgress)
-        self._page.browser_driver.set_listener('Browser.downloadWillBegin', self._onDownloadWillBegin)
-        self._page.browser_driver.call_method('Browser.setDownloadBehavior',
-                                              downloadPath=self._page.download_path,
-                                              behavior='allowAndName', eventsEnabled=True)
-
-        BrowserDownloadManager.BROWSERS[page.browser_driver.id] = self
+        self._browser.driver.set_listener('Browser.downloadProgress', self._onDownloadProgress)
+        self._browser.driver.set_listener('Browser.downloadWillBegin', self._onDownloadWillBegin)
+        self._browser.run_cdp('Browser.setDownloadBehavior', downloadPath=self._page.download_path,
+                              behavior='allowAndName', eventsEnabled=True)
 
     @property
     def missions(self):
@@ -58,9 +42,8 @@ class BrowserDownloadManager(object):
         """
         self._tabs_settings.setdefault(tab_id, TabDownloadSettings(tab_id)).path = str(Path(path).absolute())
         if tab_id == self._page.tab_id:
-            self._page.browser_driver.call_method('Browser.setDownloadBehavior',
-                                                  downloadPath=str(Path(path).absolute()),
-                                                  behavior='allowAndName', eventsEnabled=True)
+            self._browser.run_cdp('Browser.setDownloadBehavior', downloadPath=str(Path(path).absolute()),
+                                  behavior='allowAndName', eventsEnabled=True)
 
     def set_rename(self, tab_id, rename):
         """设置某个tab的重命名文件名
@@ -100,14 +83,6 @@ class BrowserDownloadManager(object):
         """
         return self._tab_missions.get(tab_id, [])
 
-    def set_mission(self, tab_id, guid):
-        """绑定tab和下载任务信息
-        :param tab_id: tab id
-        :param guid: 下载任务id
-        :return: None
-        """
-        self._guid_and_tab[guid] = tab_id
-
     def set_done(self, mission, state, final_path=None):
         """设置任务结束
         :param mission: 任务对象
@@ -121,6 +96,7 @@ class BrowserDownloadManager(object):
         if mission.tab_id in self._tab_missions and mission.id in self._tab_missions[mission.tab_id]:
             self._tab_missions[mission.tab_id].remove(mission.id)
         self._missions.pop(mission.id)
+        mission._is_done = True
 
     def cancel(self, mission):
         """取消任务
@@ -128,7 +104,7 @@ class BrowserDownloadManager(object):
         :return: None
         """
         mission.state = 'canceled'
-        self._page.browser_driver.call_method('Browser.cancelDownload', guid=mission.id)
+        self._browser.run_cdp('Browser.cancelDownload', guid=mission.id)
         if mission.final_path:
             Path(mission.final_path).unlink(True)
 
@@ -138,12 +114,22 @@ class BrowserDownloadManager(object):
         :return: None
         """
         mission.state = 'skipped'
-        self._page.browser_driver.call_method('Browser.cancelDownload', guid=mission.id)
+        self._browser.run_cdp('Browser.cancelDownload', guid=mission.id)
+
+    def clear_tab_info(self, tab_id):
+        """当tab关闭时清除有关信息
+        :param tab_id: 标签页id
+        :return: None
+        """
+        self._tabs_settings.pop(tab_id)
+        self._tab_missions.pop(tab_id)
+        self._flags.pop(tab_id)
+        TabDownloadSettings.TABS.pop(tab_id)
 
     def _onDownloadWillBegin(self, **kwargs):
         """用于获取弹出新标签页触发的下载任务"""
         guid = kwargs['guid']
-        tab_id = self._page._frames.get(kwargs['frameId'], self._page.tab_id)
+        tab_id = self._browser._frames.get(kwargs['frameId'], self._page.tab_id)
 
         settings = TabDownloadSettings(tab_id)
         if settings.rename:
@@ -249,6 +235,7 @@ class DownloadMission(object):
         self.received_bytes = 0
         self.final_path = None
         self.save_path = save_path
+        self._is_done = False
 
     def __repr__(self):
         return f'<DownloadMission {id(self)} {self.rate}>'
@@ -261,7 +248,7 @@ class DownloadMission(object):
     @property
     def is_done(self):
         """返回任务是否在运行中"""
-        return self.state in ('completed', 'skipped', 'canceled')
+        return self._is_done
 
     def cancel(self):
         """取消该任务,如任务已完成,删除已下载的文件"""
diff --git a/DrissionPage/browser_download_manager.pyi b/DrissionPage/browser_download_manager.pyi
index fde5479..6a9a434 100644
--- a/DrissionPage/browser_download_manager.pyi
+++ b/DrissionPage/browser_download_manager.pyi
@@ -1,21 +1,19 @@
 from pathlib import Path
 from typing import Dict, Optional, Union
 
-from chromium_page import ChromiumPage
+from .browser import Browser
+from .chromium_page import ChromiumPage
 
 
 class BrowserDownloadManager(object):
-    BROWSERS: Dict[str, BrowserDownloadManager] = ...
+    _browser: Browser = ...
     _page: ChromiumPage = ...
     _missions: Dict[str, DownloadMission] = ...
     _tab_missions: dict = ...
     _tabs_settings: Dict[str, TabDownloadSettings] = ...
-    _guid_and_tab: Dict[str, str] = ...
     _flags: dict = ...
 
-    def __new__(cls, page: ChromiumPage): ...
-
-    def __init__(self, page: ChromiumPage): ...
+    def __init__(self, browser: Browser): ...
 
     @property
     def missions(self) -> Dict[str, DownloadMission]: ...
@@ -32,14 +30,14 @@ class BrowserDownloadManager(object):
 
     def get_tab_missions(self, tab_id: str) -> list: ...
 
-    def set_mission(self, tab_id: str, guid: str) -> None: ...
-
     def set_done(self, mission: DownloadMission, state: str, final_path: str = None) -> None: ...
 
     def cancel(self, mission: DownloadMission) -> None: ...
 
     def skip(self, mission: DownloadMission) -> None: ...
 
+    def clear_tab_info(self, tab_id: str) -> None: ...
+
     def _onDownloadWillBegin(self, **kwargs) -> None: ...
 
     def _onDownloadProgress(self, **kwargs) -> None: ...
@@ -68,6 +66,7 @@ class DownloadMission(object):
     received_bytes: int = ...
     final_path: Optional[str] = ...
     save_path: str = ...
+    _is_done: bool = ...
 
     def __init__(self, mgr: BrowserDownloadManager, tab_id: str, _id: str, path: str, name: str, url: str,
                  save_path: str): ...
diff --git a/DrissionPage/chromium_base.py b/DrissionPage/chromium_base.py
index 0d652d0..8c10d16 100644
--- a/DrissionPage/chromium_base.py
+++ b/DrissionPage/chromium_base.py
@@ -11,7 +11,7 @@ from re import findall
 from threading import Thread
 from time import perf_counter, sleep, time
 
-from requests import Session
+from requests import get
 
 from .action_chains import ActionChains
 from .base import BasePage
@@ -79,9 +79,8 @@ class ChromiumBase(BasePage):
         """
         self._chromium_init()
         if not tab_id:
-            u = f'http://{self.address}/json'
-            json = self._control_session.get(u).json()
-            self._control_session.get(u, headers={'Connection': 'close'})
+            json = get(f'http://{self.address}/json', headers={'Connection': 'close'}).json()
+
             tab_id = [i['id'] for i in json if i['type'] == 'page']
             if not tab_id:
                 raise BrowserConnectError('浏览器连接失败,可能是浏览器版本原因。')
@@ -92,9 +91,6 @@ class ChromiumBase(BasePage):
 
     def _chromium_init(self):
         """浏览器初始设置"""
-        self._control_session = Session()
-        self._control_session.keep_alive = False
-        self._control_session.proxies = {'http': None, 'https': None}
         self._first_run = True
         self._is_reading = False
         self._upload_list = None
@@ -277,9 +273,13 @@ class ChromiumBase(BasePage):
         return self.ele(loc_or_str, timeout)
 
     @property
-    def browser(self):
+    def main(self):
         return self._page
 
+    @property
+    def browser(self):
+        return self._browser
+
     @property
     def driver(self):
         """返回用于控制浏览器的ChromiumDriver对象"""
diff --git a/DrissionPage/chromium_base.pyi b/DrissionPage/chromium_base.pyi
index 358c52c..22ac5df 100644
--- a/DrissionPage/chromium_base.pyi
+++ b/DrissionPage/chromium_base.pyi
@@ -7,8 +7,8 @@ from pathlib import Path
 from typing import Union, Tuple, List, Any
 
 from DataRecorder import Recorder
-from requests import Session
 
+from .browser import Browser
 from .action_chains import ActionChains
 from .base import BasePage
 from .chromium_driver import ChromiumDriver
@@ -27,8 +27,8 @@ class ChromiumBase(BasePage):
                  address: Union[str, int],
                  tab_id: str = None,
                  timeout: float = None):
+        self._browser: Browser = ...
         self._page: ChromiumPage = ...
-        self._control_session: Session = ...
         self.address: str = ...
         self._tab_obj: ChromiumDriver = ...
         self._is_reading: bool = ...
@@ -47,10 +47,6 @@ class ChromiumBase(BasePage):
         self._screencast: Screencast = ...
         self._actions: ActionChains = ...
         self._listener: NetworkListener = ...
-        # self._wait_download_flag: bool = ...
-        # self._download_rename: str = ...
-        # self._when_download_file_exists: str = ...
-        # self._download_missions: set = ...
 
     def _connect_browser(self, tab_id: str = None) -> None: ...
 
@@ -88,7 +84,10 @@ class ChromiumBase(BasePage):
                  timeout: float = None) -> ChromiumElement: ...
 
     @property
-    def browser(self) -> ChromiumPage: ...
+    def main(self) -> ChromiumPage: ...
+
+    @property
+    def browser(self) -> Browser: ...
 
     @property
     def title(self) -> str: ...
diff --git a/DrissionPage/chromium_driver.py b/DrissionPage/chromium_driver.py
index 05aab26..cd89be0 100644
--- a/DrissionPage/chromium_driver.py
+++ b/DrissionPage/chromium_driver.py
@@ -7,6 +7,7 @@ from json import dumps, loads
 from queue import Queue, Empty
 from threading import Thread, Event
 
+from requests import get
 from websocket import WebSocketTimeoutException, WebSocketException, WebSocketConnectionClosedException, \
     create_connection
 
@@ -223,3 +224,6 @@ class BrowserDriver(ChromiumDriver):
 
     def __repr__(self):
         return f"<BrowserDriver {self.id}>"
+
+    def get(self, url):
+        return get(url, headers={'Connection': 'close'})
diff --git a/DrissionPage/chromium_frame.py b/DrissionPage/chromium_frame.py
index 21cd5c7..6ad88f8 100644
--- a/DrissionPage/chromium_frame.py
+++ b/DrissionPage/chromium_frame.py
@@ -8,6 +8,8 @@ from re import search
 from threading import Thread
 from time import sleep, perf_counter
 
+from requests import get
+
 from .chromium_base import ChromiumBase, ChromiumPageScroll
 from .chromium_element import ChromiumElement
 from .errors import ContextLossError
@@ -24,8 +26,10 @@ class ChromiumFrame(ChromiumBase):
         page_type = str(type(page))
         if 'ChromiumPage' in page_type or 'WebPage' in page_type:
             self._page = self._target_page = self.tab = page
+            self._browser = page.browser
         else:  # Tab、Frame
             self._page = page.page
+            self._browser = self._page.browser
             self._target_page = page
             self.tab = page.tab if 'ChromiumFrame' in page_type else page
 
@@ -87,9 +91,7 @@ class ChromiumFrame(ChromiumBase):
         try:
             super()._driver_init(tab_id)
         except:
-            u = f'http://{self.address}/json'
-            self._control_session.get(u)
-            self._control_session.get(u, headers={'Connection': 'close'})
+            get(f'http://{self.address}/json', headers={'Connection': 'close'})
             super()._driver_init(tab_id)
 
     def _reload(self):
diff --git a/DrissionPage/chromium_page.py b/DrissionPage/chromium_page.py
index 24fb94f..ab1ece7 100644
--- a/DrissionPage/chromium_page.py
+++ b/DrissionPage/chromium_page.py
@@ -6,9 +6,11 @@
 from pathlib import Path
 from time import perf_counter, sleep
 
-from .browser_download_manager import BrowserDownloadManager
+from requests import get
+
+from .browser import Browser
 from .chromium_base import ChromiumBase, Timeout
-from .chromium_driver import ChromiumDriver, BrowserDriver
+from .chromium_driver import ChromiumDriver
 from .chromium_tab import ChromiumTab
 from .commons.browser import connect_browser
 from .configs.chromium_options import ChromiumOptions
@@ -27,9 +29,7 @@ class ChromiumPage(ChromiumBase):
         :param timeout: 超时时间
         """
         self._page = self
-        self._frames = {}
         super().__init__(addr_driver_opts, tab_id)
-        self._dl_mgr = BrowserDownloadManager(self)
         self.set.timeouts(implicit=timeout)
 
     def _set_start_options(self, addr_driver_opts, none):
@@ -79,9 +79,7 @@ class ChromiumPage(ChromiumBase):
         if not self._tab_obj:  # 不是传入driver的情况
             connect_browser(self._driver_options)
             if not tab_id:
-                u = f'http://{self.address}/json'
-                json = self._control_session.get(u).json()
-                self._control_session.get(u, headers={'Connection': 'close'})
+                json = get(f'http://{self.address}/json', headers={'Connection': 'close'}).json()
                 tab_id = [i['id'] for i in json if i['type'] == 'page']
                 if not tab_id:
                     raise BrowserConnectError('浏览器连接失败,可能是浏览器版本原因。')
@@ -95,10 +93,8 @@ class ChromiumPage(ChromiumBase):
 
     def _page_init(self):
         """浏览器相关设置"""
-        u = f'http://{self.address}/json/version'
-        ws = self._control_session.get(u).json()['webSocketDebuggerUrl']
-        self._control_session.get(u, headers={'Connection': 'close'})
-        self._browser_driver = BrowserDriver(ws.split('/')[-1], 'browser', self.address)
+        ws = get(f'http://{self.address}/json/version', headers={'Connection': 'close'}).json()['webSocketDebuggerUrl']
+        self._browser = Browser(ws.split('/')[-1], self)
 
         self._alert = Alert()
         self._tab_obj.set_listener('Page.javascriptDialogOpening', self._on_alert_open)
@@ -107,32 +103,20 @@ class ChromiumPage(ChromiumBase):
         self._rect = None
         self._main_tab = self.tab_id
 
-        self._process_id = None
-        r = self.browser_driver.call_method('SystemInfo.getProcessInfo')
-        if 'processInfo' not in r:
-            return None
-        for i in r['processInfo']:
-            if i['type'] == 'browser':
-                self._process_id = i['id']
-                break
-
     @property
-    def browser_driver(self):
+    def browser(self):
         """返回用于控制浏览器cdp的driver"""
-        return self._browser_driver
+        return self._browser
 
     @property
     def tabs_count(self):
         """返回标签页数量"""
-        return len(self.tabs)
+        return self.browser.tabs_count
 
     @property
     def tabs(self):
         """返回所有标签页id组成的列表"""
-        u = f'http://{self.address}/json'
-        j = self._control_session.get(u).json()  # 不要改用cdp
-        self._control_session.get(u, headers={'Connection': 'close'})
-        return [i['id'] for i in j if i['type'] == 'page']
+        return self.browser.tabs
 
     @property
     def main_tab(self):
@@ -146,7 +130,7 @@ class ChromiumPage(ChromiumBase):
     @property
     def process_id(self):
         """返回浏览器进程id"""
-        return self._process_id
+        return self.browser.process_id
 
     @property
     def set(self):
@@ -183,19 +167,7 @@ class ChromiumPage(ChromiumBase):
         :param single: 是否返回首个结果的id,为False返回所有信息
         :return: tab id或tab dict
         """
-        u = f'http://{self.address}/json'
-        tabs = self._control_session.get(u).json()  # 不要改用cdp
-        self._control_session.get(u, headers={'Connection': 'close'})
-        if isinstance(tab_type, str):
-            tab_type = {tab_type}
-        elif isinstance(tab_type, (list, tuple, set)):
-            tab_type = set(tab_type)
-        elif tab_type is not None:
-            raise TypeError('tab_type只能是set、list、tuple、str、None。')
-
-        r = [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url'])
-                                 and (tab_type is None or i['type'] in tab_type))]
-        return r[0]['id'] if r and single else r
+        return self._browser.find_tabs(title, url, tab_type, single)
 
     def _new_tab(self, url=None, switch_to=False):
         """新建一个标签页,该标签页在最后面
@@ -265,7 +237,7 @@ class ChromiumPage(ChromiumBase):
             tab_id = self.latest_tab
 
         if activate:
-            self._control_session.get(f'http://{self.address}/json/activate/{tab_id}')
+            self.browser.activate_tab(tab_id)
 
         if tab_id == self.tab_id:
             return
@@ -305,7 +277,7 @@ class ChromiumPage(ChromiumBase):
             self.driver.stop()
 
         for tab in tabs:
-            self._control_session.get(f'http://{self.address}/json/close/{tab}')
+            self.browser.close_tab(tab)
         while len(self.tabs) != end_len:
             sleep(.1)
 
@@ -345,19 +317,7 @@ class ChromiumPage(ChromiumBase):
 
     def quit(self):
         """关闭浏览器"""
-        self._tab_obj.call_method('Browser.close')
-        self._tab_obj.stop()
-
-        if self.process_id:
-            from os import popen
-            from platform import system
-            txt = f'tasklist | findstr {self.process_id}' if system().lower() == 'windows' \
-                else f'ps -ef | grep  {self.process_id}'
-            while True:
-                p = popen(txt)
-                if f'  {self.process_id} ' not in p.read():
-                    break
-                sleep(.2)
+        self.browser.quit()
 
     def _on_alert_close(self, **kwargs):
         """alert关闭时触发的方法"""
@@ -448,7 +408,7 @@ class ChromiumTabRect(object):
 
     def _get_browser_rect(self):
         """获取浏览器范围信息"""
-        return self._page.browser_driver.call_method('Browser.getWindowForTarget', targetId=self._page.tab_id)['bounds']
+        return self._page.browser.get_window_bounds()
 
 
 class Alert(object):
diff --git a/DrissionPage/chromium_page.pyi b/DrissionPage/chromium_page.pyi
index a6d81ec..90f9f09 100644
--- a/DrissionPage/chromium_page.pyi
+++ b/DrissionPage/chromium_page.pyi
@@ -3,9 +3,9 @@
 @Author  :   g1879
 @Contact :   g1879@qq.com
 """
-from typing import Union, Tuple, List
+from typing import Union, Tuple, List, Optional
 
-from .browser_download_manager import BrowserDownloadManager
+from .browser import Browser
 from .chromium_base import ChromiumBase
 from .chromium_driver import ChromiumDriver
 from .chromium_tab import ChromiumTab
@@ -21,13 +21,10 @@ class ChromiumPage(ChromiumBase):
                  tab_id: str = None,
                  timeout: float = None):
         self._driver_options: ChromiumOptions = ...
-        self._process_id: str = ...
-        self._dl_mgr: BrowserDownloadManager = ...
         self._main_tab: str = ...
         self._alert: Alert = ...
-        self._browser_driver: ChromiumDriver = ...
+        self._browser: Browser = ...
         self._rect: ChromiumTabRect = ...
-        self._frames: dict = ...
 
     def _connect_browser(self,
                          addr_driver_opts: Union[str, ChromiumDriver] = None,
@@ -38,7 +35,7 @@ class ChromiumPage(ChromiumBase):
     def _page_init(self) -> None: ...
 
     @property
-    def browser_driver(self) -> ChromiumDriver: ...
+    def browser(self) -> Browser: ...
 
     @property
     def tabs_count(self) -> int: ...
@@ -59,7 +56,7 @@ class ChromiumPage(ChromiumBase):
     def latest_tab(self) -> str: ...
 
     @property
-    def process_id(self) -> Union[None, int]: ...
+    def process_id(self) -> Optional[int]: ...
 
     @property
     def set(self) -> ChromiumPageSetter: ...
diff --git a/DrissionPage/chromium_tab.py b/DrissionPage/chromium_tab.py
index caad458..449a0a7 100644
--- a/DrissionPage/chromium_tab.py
+++ b/DrissionPage/chromium_tab.py
@@ -22,6 +22,7 @@ class ChromiumTab(ChromiumBase):
         :param tab_id: 要控制的标签页id,不指定默认为激活的
         """
         self._page = page
+        self._browser = page.browser
         super().__init__(page.address, tab_id, page.timeout)
 
     def _set_runtime_settings(self):
@@ -68,6 +69,7 @@ class WebPageTab(SessionPage, ChromiumTab):
         :param tab_id: 要控制的标签页id
         """
         self._page = page
+        self._browser = page.browser
         self.address = page.address
         self._debug = page._debug
         self._debug_recorder = page._debug_recorder
diff --git a/DrissionPage/chromium_tab.pyi b/DrissionPage/chromium_tab.pyi
index f38f8fa..15d04b4 100644
--- a/DrissionPage/chromium_tab.pyi
+++ b/DrissionPage/chromium_tab.pyi
@@ -7,7 +7,7 @@ from typing import Union, Tuple, Any, List
 
 from requests import Session, Response
 
-from waiter import ChromiumTabWaiter
+from .browser import Browser
 from .chromium_base import ChromiumBase
 from .chromium_element import ChromiumElement
 from .chromium_frame import ChromiumFrame
@@ -16,6 +16,7 @@ from .session_element import SessionElement
 from .session_page import SessionPage
 from .setter import TabSetter
 from .setter import WebPageTabSetter
+from .waiter import ChromiumTabWaiter
 from .web_page import WebPage
 
 
@@ -23,6 +24,7 @@ class ChromiumTab(ChromiumBase):
 
     def __init__(self, page: ChromiumPage, tab_id: str = None):
         self._page: ChromiumPage = ...
+        self._browser: Browser = ...
 
     def _set_runtime_settings(self) -> None: ...
 
@@ -44,6 +46,7 @@ class ChromiumTab(ChromiumBase):
 class WebPageTab(SessionPage, ChromiumTab):
     def __init__(self, page: WebPage, tab_id: str):
         self._page: WebPage = ...
+        self._browser: Browser = ...
         self._mode: str = ...
         self._has_driver = ...
         self._has_session = ...
diff --git a/DrissionPage/commons/browser.py b/DrissionPage/commons/browser.py
index c0d42d0..326bc7c 100644
--- a/DrissionPage/commons/browser.py
+++ b/DrissionPage/commons/browser.py
@@ -155,9 +155,7 @@ def test_connect(ip, port):
     end_time = perf_counter() + 30
     while perf_counter() < end_time:
         try:
-            u = f'http://{ip}:{port}/json'
-            tabs = requests_get(u, timeout=10, proxies={'http': None, 'https': None}).json()
-            requests_get(u, headers={'Connection': 'close'}, proxies={'http': None, 'https': None})
+            tabs = requests_get(f'http://{ip}:{port}/json', timeout=10, headers={'Connection': 'close'}, proxies={'http': None, 'https': None}).json()
             for tab in tabs:
                 if tab['type'] == 'page':
                     return
diff --git a/DrissionPage/setter.py b/DrissionPage/setter.py
index fefe638..3decfca 100644
--- a/DrissionPage/setter.py
+++ b/DrissionPage/setter.py
@@ -175,7 +175,7 @@ class ChromiumPageSetter(TabSetter):
             tab_or_id = self._page.tab_id
         elif not isinstance(tab_or_id, str):  # 传入Tab对象
             tab_or_id = tab_or_id.tab_id
-        self._page._control_session.get(f'http://{self._page.address}/json/activate/{tab_or_id}')
+        self._page.browser.activate_tab(tab_or_id)
 
 
 class SessionPageSetter(object):
diff --git a/DrissionPage/waiter.py b/DrissionPage/waiter.py
index 1d80385..71dfc46 100644
--- a/DrissionPage/waiter.py
+++ b/DrissionPage/waiter.py
@@ -237,20 +237,20 @@ class ChromiumPageWaiter(ChromiumTabWaiter):
         :return: 是否等待成功
         """
         if not timeout:
-            while self._driver._dl_mgr._missions:
+            while self._driver.browser._dl_mgr._missions:
                 sleep(.5)
             return True
 
         else:
             end_time = perf_counter() + timeout
             while end_time > perf_counter():
-                if not self._driver._dl_mgr._missions:
+                if not self._driver.browser._dl_mgr._missions:
                     return True
                 sleep(.5)
 
-            if self._driver._dl_mgr._missions:
+            if self._driver.browser._dl_mgr._missions:
                 if cancel_if_timeout:
-                    for m in list(self._driver._dl_mgr._missions.values()):
+                    for m in list(self._driver.browser._dl_mgr._missions.values()):
                         m.cancel()
                 return False
             else:
diff --git a/DrissionPage/web_page.py b/DrissionPage/web_page.py
index 3282a54..15e179c 100644
--- a/DrissionPage/web_page.py
+++ b/DrissionPage/web_page.py
@@ -6,7 +6,6 @@
 from requests import Session
 
 from .base import BasePage
-from .browser_download_manager import BrowserDownloadManager
 from .chromium_base import ChromiumBase, Timeout
 from .chromium_driver import ChromiumDriver
 from .chromium_page import ChromiumPage
@@ -53,7 +52,6 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
         self._set_runtime_settings()
         self._connect_browser()
         self._create_session()
-        self._dl_mgr = BrowserDownloadManager(self)
         self.set.timeouts(implicit=timeout)
 
     def _set_start_options(self, dr_opt, se_opt):