""" Android WebKit 组件 — WebView, WebSettings, WebViewClient, WebChromeClient 等 方法签名完全对齐 Android SDK """ import os import sys from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from .._core import ( Stub, AndroidOnlyError, android_context, resolve_android_path, to_android_path, _sdcard_root, _app_data_dir, _package_name, ) # ═══════════════════════════════════════════════════════════════ # WebResourceRequest / WebResourceResponse / WebResourceError # ═══════════════════════════════════════════════════════════════ class WebResourceRequest(Stub): """WebResourceRequest — 资源请求""" def __init__(self, url: str = "", is_for_main_frame: bool = False, is_redirect: bool = False, method: str = "GET", request_headers: Optional[Dict[str, str]] = None): super().__init__() self._url = url self._is_for_main_frame = is_for_main_frame self._is_redirect = is_redirect self._method = method self._request_headers = request_headers or {} def getUrl(self) -> str: return self._url def isForMainFrame(self) -> bool: return self._is_for_main_frame def isRedirect(self) -> bool: return self._is_redirect def getMethod(self) -> str: return self._method def getRequestHeaders(self) -> Dict[str, str]: return dict(self._request_headers) def toString(self) -> str: return f"WebResourceRequest(url={self._url})" class WebResourceResponse(Stub): """WebResourceResponse — 资源响应""" def __init__(self, mime_type: str = "", encoding: str = "", data: Any = None, status_code: int = 200, reason_phrase: str = "OK", response_headers: Optional[Dict[str, str]] = None): super().__init__() self._mime_type = mime_type self._encoding = encoding self._data = data self._status_code = status_code self._reason_phrase = reason_phrase self._response_headers = response_headers or {} def getMimeType(self) -> str: return self._mime_type def getEncoding(self) -> str: return self._encoding def getData(self) -> Any: return self._data def getStatusCode(self) -> int: return self._status_code def getReasonPhrase(self) -> str: return self._reason_phrase def getResponseHeaders(self) -> Dict[str, str]: return dict(self._response_headers) class WebResourceError(Stub): """WebResourceError — 资源错误""" ERROR_UNKNOWN = -1 ERROR_HOST_LOOKUP = -2 ERROR_UNSUPPORTED_AUTH_SCHEME = -3 ERROR_AUTHENTICATION = -4 ERROR_PROXY_AUTHENTICATION = -5 ERROR_CONNECT = -6 ERROR_IO = -7 ERROR_TIMEOUT = -8 ERROR_REDIRECT_LOOP = -9 ERROR_UNSUPPORTED_SCHEME = -10 ERROR_FAILED_SSL_HANDSHAKE = -11 ERROR_BAD_URL = -12 ERROR_FILE = -13 ERROR_FILE_NOT_FOUND = -14 ERROR_TOO_MANY_REQUESTS = -15 def __init__(self, error_code: int = ERROR_UNKNOWN, description: str = ""): super().__init__() self._error_code = error_code self._description = description def getErrorCode(self) -> int: return self._error_code def getDescription(self) -> str: return self._description # ═══════════════════════════════════════════════════════════════ # WebMessage / WebMessagePort # ═══════════════════════════════════════════════════════════════ class WebMessage(Stub): """WebMessage — Web 消息""" def __init__(self, data: str = ""): super().__init__() self._data = data def getData(self) -> str: return self._data class WebMessagePort(Stub): """WebMessagePort — Web 消息端口""" def postMessage(self, message: WebMessage): pass def close(self): pass def setWebMessageCallback(self, callback: Any): pass # ═══════════════════════════════════════════════════════════════ # JavascriptInterface # ═══════════════════════════════════════════════════════════════ class JavascriptInterface(Stub): """JavascriptInterface — JavaScript 接口注解""" def __init__(self, value: str = ""): super().__init__() self._value = value def getValue(self) -> str: return self._value # ═══════════════════════════════════════════════════════════════ # JsResult / JsPromptResult # ═══════════════════════════════════════════════════════════════ class JsResult(Stub): """JsResult — JS 对话框结果""" def __init__(self): super().__init__() self._confirmed = False def confirm(self): self._confirmed = True def cancel(self): self._confirmed = False class JsPromptResult(JsResult): """JsPromptResult — JS 提示结果""" def __init__(self): super().__init__() self._string_result: str = "" def confirm(self, result: str = ""): self._confirmed = True self._string_result = result def getStringResult(self) -> str: return self._string_result # ═══════════════════════════════════════════════════════════════ # HttpAuthHandler / SslErrorHandler # ═══════════════════════════════════════════════════════════════ class HttpAuthHandler(Stub): """HttpAuthHandler — HTTP 认证处理器""" def __init__(self): super().__init__() self._proceeded = False self._cancelled = False def proceed(self, username: str, password: str): self._proceeded = True def cancel(self): self._cancelled = True def useHttpAuthUsernamePassword(self) -> bool: return False class SslErrorHandler(Stub): """SslErrorHandler — SSL 错误处理器""" def __init__(self): super().__init__() self._proceeded = False self._cancelled = False def proceed(self): self._proceeded = True def cancel(self): self._cancelled = True # ═══════════════════════════════════════════════════════════════ # PermissionRequest # ═══════════════════════════════════════════════════════════════ class PermissionRequest(Stub): """PermissionRequest — 权限请求""" RESOURCE_VIDEO_CAPTURE = "android.webkit.resource.VIDEO_CAPTURE" RESOURCE_AUDIO_CAPTURE = "android.webkit.resource.AUDIO_CAPTURE" RESOURCE_PROTECTED_MEDIA_ID = "android.webkit.resource.PROTECTED_MEDIA_ID" RESOURCE_MIDI_SYSEX = "android.webkit.resource.MIDI_SYSEX" def __init__(self): super().__init__() self._origin: Any = None self._resources: List[str] = [] self._granted_resources: List[str] = [] def getOrigin(self) -> Any: return self._origin def getResources(self) -> List[str]: return list(self._resources) def getGrantedResources(self) -> List[str]: return list(self._granted_resources) def grant(self, resources: List[str]): self._granted_resources = list(resources) def deny(self): self._granted_resources.clear() # ═══════════════════════════════════════════════════════════════ # GeolocationPermissions / WebStorage # ═══════════════════════════════════════════════════════════════ class GeolocationPermissions(Stub): """GeolocationPermissions — 地理位置权限""" @staticmethod def getInstance() -> "GeolocationPermissions": return GeolocationPermissions() def allow(self, origin: str): pass def clear(self, origin: str): pass def clearAll(self): pass def getAllowed(self, origin: str, callback: Any): pass def getOrigins(self, callback: Any): pass class WebStorage(Stub): """WebStorage — Web 存储""" @staticmethod def getInstance() -> "WebStorage": return WebStorage() def deleteAllData(self): pass def deleteOrigin(self, origin: str): pass def getOrigins(self, callback: Any): pass def getQuotaForOrigin(self, origin: str, callback: Any): pass def getUsageForOrigin(self, origin: str, callback: Any): pass # ═══════════════════════════════════════════════════════════════ # WebBackForwardList / WebHistoryItem # ═══════════════════════════════════════════════════════════════ class WebHistoryItem(Stub): """WebHistoryItem — 历史项""" def __init__(self): super().__init__() self._url: str = "" self._original_url: str = "" self._title: str = "" self._favicon: Any = None def getUrl(self) -> str: return self._url def getOriginalUrl(self) -> str: return self._original_url def getTitle(self) -> str: return self._title def getFavicon(self) -> Any: return self._favicon def toString(self) -> str: return f"WebHistoryItem(url={self._url})" class WebBackForwardList(Stub): """WebBackForwardList — 前进后退列表""" def __init__(self): super().__init__() self._items: List[WebHistoryItem] = [] self._current_index: int = -1 def getCurrentItem(self) -> Optional[WebHistoryItem]: if 0 <= self._current_index < len(self._items): return self._items[self._current_index] return None def getCurrentIndex(self) -> int: return self._current_index def getItemAtIndex(self, index: int) -> Optional[WebHistoryItem]: if 0 <= index < len(self._items): return self._items[index] return None def getSize(self) -> int: return len(self._items) # ═══════════════════════════════════════════════════════════════ # CookieManager / CookieSyncManager # ═══════════════════════════════════════════════════════════════ class CookieManager(Stub): """CookieManager — Cookie 管理器""" _instance: Optional["CookieManager"] = None def __init__(self): super().__init__() self._accept_cookies: bool = True self._accept_third_party: bool = True self._cookies: Dict[str, str] = {} @staticmethod def getInstance() -> "CookieManager": if CookieManager._instance is None: CookieManager._instance = CookieManager() return CookieManager._instance def setAcceptCookies(self, accept: bool): self._accept_cookies = accept def acceptCookies(self) -> bool: return self._accept_cookies def setCookie(self, url: str, value: str): if self._accept_cookies: self._cookies[url] = value def getCookie(self, url: str) -> Optional[str]: return self._cookies.get(url) def setAcceptThirdPartyCookies(self, webview: Any, accept: bool): self._accept_third_party = accept def acceptThirdPartyCookies(self) -> bool: return self._accept_third_party def removeSessionCookie(self): pass def removeAllCookies(self): self._cookies.clear() def removeAllCookiesSync(self): self._cookies.clear() def removeSessionCookies(self): pass def flush(self): pass def hasCookies(self) -> bool: return bool(self._cookies) def removeExpiredCookie(self): pass class CookieSyncManager(Stub): """CookieSyncManager — Cookie 同步管理器""" _instance: Optional["CookieSyncManager"] = None @staticmethod def createInstance(context: Any) -> "CookieSyncManager": if CookieSyncManager._instance is None: CookieSyncManager._instance = CookieSyncManager() return CookieSyncManager._instance @staticmethod def getInstance() -> "CookieSyncManager": if CookieSyncManager._instance is None: CookieSyncManager._instance = CookieSyncManager() return CookieSyncManager._instance def sync(self): pass def startSync(self): pass def stopSync(self): pass def resetSync(self): pass # ═══════════════════════════════════════════════════════════════ # WebSettings # ═══════════════════════════════════════════════════════════════ class WebSettings(Stub): """WebSettings — WebView 设置""" LOAD_DEFAULT = -1 LOAD_CACHE_ELSE_NETWORK = 1 LOAD_NO_CACHE = 2 LOAD_CACHE_ONLY = 3 MIXED_CONTENT_ALWAYS_ALLOW = 0 MIXED_CONTENT_NEVER_ALLOW = 1 MIXED_CONTENT_COMPATIBILITY_MODE = 2 class LayoutAlgorithm(Stub): NORMAL = 0 SINGLE_COLUMN = 1 NARROW_COLUMNS = 2 TEXT_AUTOSIZING = 3 def __init__(self): super().__init__() self._js_enabled: bool = True self._dom_storage_enabled: bool = True self._allow_file_access: bool = True self._allow_content_access: bool = True self._allow_file_access_from_urls: bool = False self._allow_universal_access_from_urls: bool = False self._built_in_zoom_controls: bool = True self._display_zoom_controls: bool = True self._support_zoom: bool = True self._load_with_overview_mode: bool = False self._use_wide_viewport: bool = False self._user_agent_string: str = "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36" self._default_text_encoding: str = "UTF-8" self._cache_mode: int = self.LOAD_DEFAULT self._app_cache_enabled: bool = False self._app_cache_path: str = "" self._database_enabled: bool = False self._geolocation_enabled: bool = False self._js_can_open_windows: bool = False self._loads_images_automatically: bool = True self._block_network_image: bool = False self._block_network_loads: bool = False self._minimum_font_size: int = 8 self._default_font_size: int = 16 self._text_zoom: int = 100 self._layout_algorithm: int = WebSettings.LayoutAlgorithm.NORMAL self._mixed_content_mode: int = self.MIXED_CONTENT_NEVER_ALLOW self._offscreen_pre_raster: bool = False self._safe_browsing_enabled: bool = True self._javascript_can_open_windows_automatically: bool = False def setJavaScriptEnabled(self, enabled: bool): self._js_enabled = enabled def setDomStorageEnabled(self, enabled: bool): self._dom_storage_enabled = enabled def setAllowFileAccess(self, allow: bool): self._allow_file_access = allow def setAllowContentAccess(self, allow: bool): self._allow_content_access = allow def setAllowFileAccessFromFileURLs(self, allow: bool): self._allow_file_access_from_urls = allow def setAllowUniversalAccessFromFileURLs(self, allow: bool): self._allow_universal_access_from_urls = allow def setBuiltInZoomControls(self, enabled: bool): self._built_in_zoom_controls = enabled def setDisplayZoomControls(self, enabled: bool): self._display_zoom_controls = enabled def setSupportZoom(self, support: bool): self._support_zoom = support def setLoadWithOverviewMode(self, overview: bool): self._load_with_overview_mode = overview def setUseWideViewPort(self, use: bool): self._use_wide_viewport = use def setUserAgentString(self, ua: str): self._user_agent_string = ua def getUserAgentString(self) -> str: return self._user_agent_string def setDefaultTextEncodingName(self, encoding: str): self._default_text_encoding = encoding def getDefaultTextEncodingName(self) -> str: return self._default_text_encoding def setCacheMode(self, mode: int): self._cache_mode = mode def getCacheMode(self) -> int: return self._cache_mode def setAppCacheEnabled(self, enabled: bool): self._app_cache_enabled = enabled def setAppCachePath(self, path: str): self._app_cache_path = path def setDatabaseEnabled(self, enabled: bool): self._database_enabled = enabled def setGeolocationEnabled(self, enabled: bool): self._geolocation_enabled = enabled def setJavaScriptCanOpenWindowsAutomatically(self, auto: bool): self._javascript_can_open_windows_automatically = auto def setLoadsImagesAutomatically(self, auto: bool): self._loads_images_automatically = auto def setBlockNetworkImage(self, block: bool): self._block_network_image = block def setBlockNetworkLoads(self, block: bool): self._block_network_loads = block def setMinimumFontSize(self, size: int): self._minimum_font_size = size def getMinimumFontSize(self) -> int: return self._minimum_font_size def setDefaultFontSize(self, size: int): self._default_font_size = size def getDefaultFontSize(self) -> int: return self._default_font_size def setTextZoom(self, zoom: int): self._text_zoom = zoom def getTextZoom(self) -> int: return self._text_zoom def setLayoutAlgorithm(self, algo: int): self._layout_algorithm = algo def getLayoutAlgorithm(self) -> int: return self._layout_algorithm def setMixedContentMode(self, mode: int): self._mixed_content_mode = mode def getMixedContentMode(self) -> int: return self._mixed_content_mode def setOffscreenPreRaster(self, enabled: bool): self._offscreen_pre_raster = enabled def getOffscreenPreRaster(self) -> bool: return self._offscreen_pre_raster def setSafeBrowsingEnabled(self, enabled: bool): self._safe_browsing_enabled = enabled def getSafeBrowsingEnabled(self) -> bool: return self._safe_browsing_enabled def toString(self) -> str: return f"WebSettings(js={self._js_enabled})" # ═══════════════════════════════════════════════════════════════ # WebViewClient # ═══════════════════════════════════════════════════════════════ class WebViewClient(Stub): """WebViewClient — WebView 客户端""" def onPageStarted(self, view: "WebView", url: str, favicon: Any): pass def onPageFinished(self, view: "WebView", url: str): pass def onLoadResource(self, view: "WebView", url: str): pass def shouldOverrideUrlLoading(self, view: "WebView", request: Any) -> bool: return False def shouldOverrideUrlLoading(self, view: "WebView", url: str) -> bool: return False def shouldInterceptRequest(self, view: "WebView", request: WebResourceRequest) -> Optional[WebResourceResponse]: return None def shouldInterceptRequest(self, view: "WebView", url: str) -> Optional[WebResourceResponse]: return None def onReceivedHttpError(self, view: "WebView", request: WebResourceRequest, error_response: WebResourceResponse): pass def onReceivedSslError(self, view: "WebView", handler: SslErrorHandler, error: Any): pass def onReceivedClientCertRequest(self, view: "WebView", request: Any): pass def onReceivedHttpAuthRequest(self, view: "WebView", handler: HttpAuthHandler, host: str, realm: str): pass def onFormResubmission(self, view: "WebView", dont_resend: Any, resend: Any): pass def doUpdateVisitedHistory(self, view: "WebView", url: str, is_reload: bool): pass def onScaleChanged(self, view: "WebView", old_scale: float, new_scale: float): pass def onUnhandledKeyEvent(self, view: "WebView", event: Any): pass def shouldOverrideKeyEvent(self, view: "WebView", event: Any) -> bool: return False # ═══════════════════════════════════════════════════════════════ # WebChromeClient # ═══════════════════════════════════════════════════════════════ class WebChromeClient(Stub): """WebChromeClient — WebView Chrome 客户端""" def onProgressChanged(self, view: "WebView", new_progress: int): pass def onReceivedTitle(self, view: "WebView", title: str): pass def onReceivedIcon(self, view: "WebView", icon: Any): pass def onReceivedTouchIconUrl(self, view: "WebView", url: str, precomposed: bool): pass def onShowCustomView(self, view: Any, callback: Any): pass def onHideCustomView(self): pass def onCreateWindow(self, view: "WebView", is_dialog: bool, is_user_gesture: bool, result_msg: Any) -> bool: return False def onRequestFocus(self, view: "WebView"): pass def onCloseWindow(self, window: "WebView"): pass def onJsAlert(self, view: "WebView", url: str, message: str, result: JsResult) -> bool: return False def onJsConfirm(self, view: "WebView", url: str, message: str, result: JsResult) -> bool: return False def onJsPrompt(self, view: "WebView", url: str, message: str, default_value: str, result: JsPromptResult) -> bool: return False def onJsBeforeUnload(self, view: "WebView", url: str, message: str, result: JsResult) -> bool: return False def onExceededDatabaseQuota(self, url: str, database_identifier: str, quota: int, estimated_database_size: int, total_quota: int, quota_updater: Any): pass def onReachedMaxAppCacheSize(self, space_needed: int, total_quota: int, quota_updater: Any): pass def onGeolocationPermissionsShowPrompt(self, origin: str, callback: Any): pass def onGeolocationPermissionsHidePrompt(self): pass def onPermissionRequest(self, request: PermissionRequest): pass def onPermissionRequestCanceled(self, request: PermissionRequest): pass def onConsoleMessage(self, message: Any) -> bool: return True # ═══════════════════════════════════════════════════════════════ # WebView # ═══════════════════════════════════════════════════════════════ class WebView(Stub): """WebView — Web 视图""" _instance_count = 0 def __init__(self, context: Any = None, attrs: Any = None): super().__init__() WebView._instance_count += 1 self._context = context self._settings = WebSettings() self._webview_client: Optional[WebViewClient] = None self._chrome_client: Optional[WebChromeClient] = None self._current_url: str = "" self._original_url: str = "" self._title: str = "" self._progress: int = 100 self._content_height: int = 0 self._scale: float = 1.0 self._background_color: int = 0xFFFFFFFF self._back_forward_list = WebBackForwardList() self._javascript_interfaces: Dict[str, Any] = {} self._paused: bool = False self._timers_paused: bool = False self._network_available: bool = True def loadUrl(self, url: str, additional_headers: Optional[Dict[str, str]] = None): self._current_url = url self._original_url = url self._title = url self._progress = 0 if self._webview_client: try: self._webview_client.onPageStarted(self, url, None) self._webview_client.onPageFinished(self, url) except Exception: pass self._progress = 100 def loadData(self, data: str, mime_type: str = "text/html", encoding: str = "utf-8"): self._current_url = "data:text/html;charset=utf-8," self._progress = 100 def loadDataWithBaseURL(self, base_url: str, data: str, mime_type: str, encoding: str, history_url: str): self._current_url = history_url or base_url self._progress = 100 def setWebViewClient(self, client: WebViewClient): self._webview_client = client def setWebChromeClient(self, client: WebChromeClient): self._chrome_client = client def getSettings(self) -> WebSettings: return self._settings def goBack(self): pass def goForward(self): pass def canGoBack(self) -> bool: return False def canGoForward(self) -> bool: return False def goBackOrForward(self, steps: int): pass def reload(self): pass def stopLoading(self): pass def clearHistory(self): pass def clearCache(self, include_disk_files: bool): pass def clearView(self): pass def destroy(self): WebView._instance_count -= 1 def getUrl(self) -> Optional[str]: return self._current_url or None def getTitle(self) -> Optional[str]: return self._title or None def getProgress(self) -> int: return self._progress def getContentHeight(self) -> int: return self._content_height def getScale(self) -> float: return self._scale def addJavascriptInterface(self, obj: Any, interface_name: str): self._javascript_interfaces[interface_name] = obj def removeJavascriptInterface(self, interface_name: str): if interface_name in self._javascript_interfaces: del self._javascript_interfaces[interface_name] def evaluateJavascript(self, script: str, callback: Any = None): pass def setInitialScale(self, scale_in_percent: int): self._scale = scale_in_percent / 100.0 def setBackgroundColor(self, color: int): self._background_color = color def scrollTo(self, x: int, y: int): pass def scrollBy(self, x: int, y: int): pass def pageUp(self, top: bool) -> bool: return True def pageDown(self, bottom: bool) -> bool: return True def findNext(self, forward: bool): pass def findAll(self, find: str) -> int: return 0 def findAllAsync(self, find: str): pass def clearMatches(self): pass def zoomIn(self): self._scale *= 1.25 def zoomOut(self): self._scale /= 1.25 def setNetworkAvailable(self, available: bool): self._network_available = available def pauseTimers(self): self._timers_paused = True def resumeTimers(self): self._timers_paused = False def onPause(self): self._paused = True def onResume(self): self._paused = False def onWindowFocusChanged(self, has_focus: bool): pass def setLayoutParams(self, params: Any): pass def requestFocus(self, direction: int = 0): pass def getOriginalUrl(self) -> Optional[str]: return self._original_url or None def canZoomIn(self) -> bool: return True def canZoomOut(self) -> bool: return self._scale > 0.25 def toString(self) -> str: return f"WebView(url={self._current_url})" # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "WebView", "WebSettings", "WebViewClient", "WebChromeClient", "WebResourceRequest", "WebResourceResponse", "WebResourceError", "WebMessage", "WebMessagePort", "JavascriptInterface", "CookieManager", "CookieSyncManager", "JsResult", "JsPromptResult", "HttpAuthHandler", "SslErrorHandler", "PermissionRequest", "GeolocationPermissions", "WebStorage", "WebBackForwardList", "WebHistoryItem", ]