From 3ad75597ca42a7995e816c2672d87caf54500553 Mon Sep 17 00:00:00 2001 From: fanyong920 <1023079644@qq.com> Date: Tue, 17 Dec 2024 16:29:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7=203.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- ...43\345\206\263\346\226\271\346\241\210.md" | 222 - README.md | 120 +- api.md | 3969 ----------------- example/pom.xml | 11 +- .../java/com/ruiyun/example/A_LaunchTest.java | 260 +- .../com/ruiyun/example/B_PageGotoTest.java | 31 +- .../com/ruiyun/example/C_PageGoBackTest.java | 14 +- .../ruiyun/example/D_PageGoForwardTest.java | 19 +- .../com/ruiyun/example/E_PageEventsTest.java | 168 +- .../ruiyun/example/F_PageViewPortTest.java | 25 +- .../com/ruiyun/example/G_PageContentTest.java | 35 +- .../com/ruiyun/example/H_PageEvaluteTest.java | 45 +- .../com/ruiyun/example/I_PageMouseTest.java | 40 +- .../com/ruiyun/example/J_RequestTest.java | 152 +- .../java/com/ruiyun/example/K_PDFTest.java | 40 +- .../com/ruiyun/example/L_CDPSessionTest.java | 21 +- .../com/ruiyun/example/M_ResponseTest.java | 32 +- .../ruiyun/example/N_ExposeFunctionTest.java | 28 +- .../example/O_WaitForNavigationTest.java | 20 +- .../com/ruiyun/example/P_AddStyleTagTest.java | 12 +- .../com/ruiyun/example/PageExtendExample.java | 30 - .../com/ruiyun/example/Q_ScreenshotTest.java | 31 +- .../example/R_ElementHandleApiTest.java | 193 +- .../com/ruiyun/example/S_PageApiTest.java | 265 +- .../example/T_BroswerContextApiTest.java | 137 +- .../main/java/com/ruiyun/example/Test.java | 94 +- .../main/java/com/ruiyun/example/Test2.java | 49 +- .../com/ruiyun/example/U_BroswerApiTest.java | 160 +- .../com/ruiyun/example/V_DownloadApiTest.java | 52 +- .../example/W_AccessibilityApiTest.java | 10 +- .../example/X_WorkingWithExtension.java | 23 +- .../com/ruiyun/example/Z_FrameApiTest.java | 15 +- .../src/main/resources/META-INF/MANIFEST.MF | 3 + .../main/resources}/simplelogger.properties | 2 +- pom.xml | 8 +- .../ruiyun/jvppeteer/api/core/Browser.java | 207 + .../jvppeteer/api/core/BrowserContext.java | 136 + .../ruiyun/jvppeteer/api/core/CDPSession.java | 29 + .../ruiyun/jvppeteer/api/core/Connection.java | 92 + .../com/ruiyun/jvppeteer/api/core/Dialog.java | 79 + .../{ => api}/core/ElementHandle.java | 1350 +++--- .../{events => api/core}/EventEmitter.java | 41 +- .../com/ruiyun/jvppeteer/api/core/Frame.java | 682 +++ .../ruiyun/jvppeteer/api/core/JSHandle.java | 170 + .../jvppeteer/{ => api}/core/Keyboard.java | 295 +- .../com/ruiyun/jvppeteer/api/core/Mouse.java | 132 + .../com/ruiyun/jvppeteer/api/core/Page.java | 1618 +++++++ .../com/ruiyun/jvppeteer/api/core/Realm.java | 90 + .../jvppeteer/{ => api}/core/Request.java | 647 ++- .../ruiyun/jvppeteer/api/core/Response.java | 126 + .../com/ruiyun/jvppeteer/api/core/Target.java | 69 + .../jvppeteer/api/core/TouchHandle.java | 12 + .../jvppeteer/api/core/Touchscreen.java | 64 + .../ruiyun/jvppeteer/api/core/WebWorker.java | 106 + .../api/events/BrowserContextEvents.java | 8 + .../jvppeteer/api/events/BrowserEvents.java | 41 + .../api/events/ConnectionEvents.java | 92 + .../jvppeteer/api/events/FrameEvents.java | 19 + .../jvppeteer/api/events/PageEvents.java | 119 + .../jvppeteer/api/events/TrustedEmitter.java | 25 + .../jvppeteer/bidi/core/BidiBrowser.java | 272 ++ .../bidi/core/BidiBrowserContext.java | 281 ++ .../bidi/core/BidiBrowserTarget.java | 51 + .../jvppeteer/bidi/core/BidiCdpSession.java | 103 + .../jvppeteer/bidi/core/BidiConnection.java | 237 + .../jvppeteer/bidi/core/BidiDeserializer.java | 136 + .../jvppeteer/bidi/core/BidiDialog.java | 24 + .../bidi/core/BidiElementHandle.java | 108 + .../bidi/core/BidiExposeableFunction.java | 219 + .../ruiyun/jvppeteer/bidi/core/BidiFrame.java | 530 +++ .../jvppeteer/bidi/core/BidiFrameRealm.java | 116 + .../jvppeteer/bidi/core/BidiFrameTarget.java | 64 + .../jvppeteer/bidi/core/BidiJSHandle.java | 96 + .../jvppeteer/bidi/core/BidiKeyboard.java | 356 ++ .../bidi/core/BidiLifeCycleWatch.java | 200 + .../ruiyun/jvppeteer/bidi/core/BidiMouse.java | 190 + .../ruiyun/jvppeteer/bidi/core/BidiPage.java | 905 ++++ .../jvppeteer/bidi/core/BidiPageTarget.java | 57 + .../ruiyun/jvppeteer/bidi/core/BidiRealm.java | 183 + .../jvppeteer/bidi/core/BidiRealmCore.java | 129 + .../jvppeteer/bidi/core/BidiRequest.java | 304 ++ .../jvppeteer/bidi/core/BidiResponse.java | 148 + .../jvppeteer/bidi/core/BidiSerializer.java | 115 + .../jvppeteer/bidi/core/BidiTouchHandle.java | 109 + .../jvppeteer/bidi/core/BidiTouchscreen.java | 30 + .../jvppeteer/bidi/core/BidiWebWorker.java | 50 + .../jvppeteer/bidi/core/BidiWorkerRealm.java | 45 + .../jvppeteer/bidi/core/BidiWorkerTarget.java | 56 + .../jvppeteer/bidi/core/BrowserCore.java | 229 + .../jvppeteer/bidi/core/BrowsingContext.java | 516 +++ .../bidi/core/DedicatedWorkerRealm.java | 60 + .../jvppeteer/bidi/core/Navigation.java | 186 + .../jvppeteer/bidi/core/NoneSourceAction.java | 4 + .../jvppeteer/bidi/core/ReadinessState.java | 12 + .../jvppeteer/bidi/core/RequestCore.java | 278 ++ .../ruiyun/jvppeteer/bidi/core/Session.java | 143 + .../bidi/core/SharedWorkerRealm.java | 57 + .../jvppeteer/bidi/core/UserContext.java | 197 + .../jvppeteer/bidi/core/UserPrompt.java | 127 + .../jvppeteer/bidi/core/WindowRealm.java | 78 + .../bidi/entities/AddInterceptOptions.java | 28 + .../entities/AddPreloadScriptOptions.java | 33 + .../bidi/entities/ArrayLocalValue.java | 24 + .../bidi/entities/AuthCredentials.java | 46 + .../bidi/entities/AuthRequiredParameters.java | 78 + .../bidi/entities/BaseParameters.java | 69 + .../entities/BeforeRequestSentParameters.java | 78 + .../jvppeteer/bidi/entities/BidiViewport.java | 38 + .../jvppeteer/bidi/entities/BytesValue.java | 38 + .../bidi/entities/CallFunctionOptions.java | 53 + .../jvppeteer/bidi/entities/Capabilities.java | 85 + .../bidi/entities/CapabilitiesRequest.java | 24 + .../bidi/entities/CapabilityRequest.java | 66 + .../entities/CaptureScreenshotOptions.java | 60 + .../bidi/entities/ChannelProperties.java | 31 + .../jvppeteer/bidi/entities/ChannelValue.java | 33 + .../bidi/entities/ClipRectangle.java | 82 + .../jvppeteer/bidi/entities/ClosedEvent.java | 27 + .../entities/Cookie.java} | 72 +- .../jvppeteer/bidi/entities/CookieFilter.java | 115 + .../CreateBrowsingContextOptions.java | 37 + .../jvppeteer/bidi/entities/CreateType.java | 6 + .../bidi/entities/EvaluateOptions.java | 31 + .../bidi/entities/EvaluateResult.java | 51 + .../bidi/entities/ExceptionDetails.java | 51 + .../bidi/entities/FetchErrorParameters.java | 78 + .../bidi/entities/FetchTimingInfo.java | 123 + .../bidi/entities/GetCookiesOptions.java | 13 + .../entities/HandleUserPromptOptions.java | 38 + .../jvppeteer/bidi/entities/Header.java | 38 + .../jvppeteer/bidi/entities/ImageFormat.java | 38 + .../jvppeteer/bidi/entities/Initiator.java | 69 + .../jvppeteer/bidi/entities/InputId.java | 23 + .../bidi/entities/InterceptPhase.java | 17 + .../jvppeteer/bidi/entities/LocalValue.java | 31 + .../jvppeteer/bidi/entities/LogEntry.java | 126 + .../bidi/entities/MessageParameters.java | 31 + .../bidi/entities/NavigationInfo.java | 24 + .../jvppeteer/bidi/entities/NewResult.java | 23 + .../jvppeteer/bidi/entities/Orientation.java | 10 + .../jvppeteer/bidi/entities/Origin.java | 10 + .../bidi/entities/PartialCookie.java | 151 + .../bidi/entities/PartitionDescriptor.java | 61 + .../bidi/entities/PermissionOverride.java | 29 + .../bidi/entities/PermissionState.java | 12 + .../entities/PointerCommonProperties.java | 80 + .../bidi/entities/PointerParameters.java | 27 + .../jvppeteer/bidi/entities/PointerType.java | 12 + .../bidi/entities/PrintMarginParameters.java | 60 + .../jvppeteer/bidi/entities/PrintOptions.java | 82 + .../bidi/entities/PrintPageParameters.java | 38 + .../bidi/entities/ProxyConfiguration.java | 95 + .../entities/RealmDestroyedParameters.java | 14 + .../jvppeteer/bidi/entities/RealmInfo.java | 60 + .../jvppeteer/bidi/entities/RealmType.java | 32 + .../bidi/entities/ReloadParameters.java | 33 + .../bidi/entities/RemoteReference.java | 22 + .../jvppeteer/bidi/entities/RemoteValue.java | 62 + .../jvppeteer/bidi/entities/RequestData.java | 126 + .../entities/ResponseCompletedParameters.java | 78 + .../bidi/entities/ResponseContent.java | 20 + .../jvppeteer/bidi/entities/ResponseData.java | 145 + .../entities/ResponseStartedParameters.java | 78 + .../bidi/entities/ResultOwnership.java | 10 + .../jvppeteer/bidi/entities/SameSite.java | 12 + .../bidi/entities/SerializationOptions.java | 40 + .../bidi/entities/SetViewportParameters.java | 32 + .../bidi/entities/SharedReference.java | 27 + .../jvppeteer/bidi/entities/Source.java | 30 + .../bidi/entities/SourceActions.java | 207 + .../bidi/entities/SourceActionsType.java | 28 + .../SupportedWebDriverCapabilities.java | 33 + .../SupportedWebDriverCapability.java | 53 + .../jvppeteer/bidi/entities/Target.java | 31 + .../jvppeteer/bidi/entities/UrlPattern.java | 67 + .../entities/UserPromptClosedParameters.java | 51 + .../bidi/entities/UserPromptHandler.java | 52 + .../bidi/entities/UserPromptHandlerType.java | 19 + .../entities/UserPromptOpenedParameters.java | 60 + .../bidi/entities/UserPromptResult.java | 31 + .../bidi/entities/UserPromptType.java | 16 + .../ruiyun/jvppeteer/bidi/entities/Value.java | 38 + .../bidi/entities/WaitForOptions.java | 54 + .../bidi/events/ContextCreatedEvent.java | 82 + .../bidi/events/NavigationInfoEvent.java | 51 + .../bidi/events/SharedworkerEvent.java | 15 + .../jvppeteer/{ => cdp}/core/AXNode.java | 19 +- .../{ => cdp}/core/Accessibility.java | 11 +- .../{ => cdp}/core/BrowserFetcher.java | 285 +- .../{ => cdp}/core/BrowserRunner.java | 169 +- .../jvppeteer/{ => cdp}/core/CSSCoverage.java | 32 +- .../Browser.java => cdp/core/CdpBrowser.java} | 254 +- .../jvppeteer/cdp/core/CdpBrowserContext.java | 157 + .../ruiyun/jvppeteer/cdp/core/CdpDialog.java | 36 + .../jvppeteer/cdp/core/CdpElementHandle.java | 164 + .../ruiyun/jvppeteer/cdp/core/CdpFrame.java | 407 ++ .../jvppeteer/cdp/core/CdpJSHandle.java | 95 + .../jvppeteer/cdp/core/CdpKeyboard.java | 219 + .../Mouse.java => cdp/core/CdpMouse.java} | 67 +- .../ruiyun/jvppeteer/cdp/core/CdpPage.java | 1093 +++++ .../ruiyun/jvppeteer/cdp/core/CdpRequest.java | 321 ++ .../core/CdpResponse.java} | 49 +- .../Target.java => cdp/core/CdpTarget.java} | 86 +- .../jvppeteer/cdp/core/CdpTouchHandle.java | 68 + .../jvppeteer/cdp/core/CdpTouchscreen.java | 47 + .../jvppeteer/cdp/core/CdpWebWorker.java | 83 + .../{ => cdp}/core/ChromeTargetManager.java | 110 +- .../jvppeteer/{ => cdp}/core/Coverage.java | 21 +- .../jvppeteer/cdp/core/DevToolsTarget.java | 13 + .../{ => cdp}/core/EmulationManager.java | 54 +- .../jvppeteer/cdp/core/ExecutionContext.java | 451 ++ .../jvppeteer/{ => cdp}/core/FileChooser.java | 6 +- .../{ => cdp}/core/FirefoxTargetManager.java | 61 +- .../{ => cdp}/core/FrameManager.java | 173 +- .../jvppeteer/{ => cdp}/core/FrameTree.java | 41 +- .../{ => cdp}/core/FrameTreeEvent.java | 4 +- .../{ => cdp}/core/IsolatedWorld.java | 74 +- .../jvppeteer/{ => cdp}/core/JSCoverage.java | 40 +- .../{ => cdp}/core/LifecycleWatcher.java | 80 +- .../{ => cdp}/core/NetworkEventManager.java | 21 +- .../{ => cdp}/core/NetworkManager.java | 194 +- .../jvppeteer/cdp/core/OtherTarget.java | 11 + .../jvppeteer/{ => cdp}/core/PageTarget.java | 32 +- .../jvppeteer/{ => cdp}/core/Puppeteer.java | 92 +- .../{ => cdp}/core/TargetManager.java | 17 +- .../jvppeteer/{ => cdp}/core/Tracing.java | 19 +- .../ruiyun/jvppeteer/cdp/core/WaitTask.java | 171 + .../{ => cdp}/core/WorkerTarget.java | 16 +- .../jvppeteer/{ => cdp}/entities/AXNode.java | 2 +- .../{ => cdp}/entities/AXProperty.java | 2 +- .../{ => cdp}/entities/AXRelatedNode.java | 2 +- .../jvppeteer/{ => cdp}/entities/AXValue.java | 2 +- .../{ => cdp}/entities/AXValueSource.java | 2 +- .../{ => cdp}/entities/ActiveProperty.java | 2 +- .../{ => cdp}/entities/AdFrameStatus.java | 2 +- .../{ => cdp}/entities/AuthChallenge.java | 2 +- .../entities/AuthChallengeResponse.java | 2 +- .../{ => cdp}/entities/AutofillData.java | 2 +- .../jvppeteer/{ => cdp}/entities/AuxData.java | 2 +- .../jvppeteer/{ => cdp}/entities/Binding.java | 18 +- .../{ => cdp}/entities/BindingPayload.java | 2 +- .../entities/BlockedSetCookieWithReason.java | 2 +- .../{ => cdp}/entities/BoundingBox.java | 3 +- .../{ => cdp}/entities/BoxModel.java | 2 +- .../entities/BrowserConnectOptions.java | 32 +- .../entities/BrowserContextOptions.java | 2 +- .../BrowserLaunchArgumentOptions.java | 12 +- .../entities/CSSCoverageOptions.java | 2 +- .../entities/CSSStyleSheetHeader.java | 2 +- .../{ => cdp}/entities/CallFrame.java | 2 +- .../{ => cdp}/entities/ClickOptions.java | 2 +- .../{ => cdp}/entities/ClientProvider.java | 5 +- .../{ => cdp}/entities/ConnectOptions.java | 24 +- .../{ => cdp}/entities/ConsoleMessage.java | 21 +- .../entities/ConsoleMessageLocation.java | 2 +- .../cdp/entities/ConsoleMessageType.java | 23 + .../entities/ContinueRequestOverrides.java | 10 +- .../jvppeteer/{ => cdp}/entities/Cookie.java | 30 +- .../jvppeteer/cdp/entities/CookieParam.java | 185 + .../entities/CookiePartitionKey.java | 2 +- .../cdp/entities/CookiePriority.java | 12 + .../cdp/entities/CookieSameSite.java | 7 + .../cdp/entities/CookieSourceScheme.java | 12 + .../{ => cdp}/entities/CorsErrorStatus.java | 2 +- .../{ => cdp}/entities/CoverageEntry.java | 2 +- .../{ => cdp}/entities/CoveragePoint.java | 2 +- .../{ => cdp}/entities/CoverageRange.java | 2 +- .../entities/CpuThrottlingState.java | 2 +- .../{ => cdp}/entities/Credentials.java | 2 +- .../{ => cdp}/entities/CreditCard.java | 2 +- .../{ => cdp}/entities/CustomPreview.java | 2 +- .../{ => cdp}/entities/DebugInfo.java | 2 +- .../entities/DefaultBackgroundColorState.java | 2 +- .../entities/DeleteCookiesRequest.java | 11 +- .../jvppeteer/{ => cdp}/entities/Device.java | 2 +- .../entities/DeviceRequestPromptDevice.java | 2 +- .../{ => cdp}/entities/DialogType.java | 4 +- .../entities/DisposableStackConsumer.java | 2 +- .../{ => cdp}/entities/DownloadOptions.java | 12 +- .../entities/DownloadPolicy.java} | 6 +- .../{ => cdp}/entities/DownloadState.java | 2 +- .../{ => cdp}/entities/DragData.java | 2 +- .../{ => cdp}/entities/DragDataItem.java | 2 +- .../entities/DragInterceptedEvent.java | 2 +- .../entities/ElementScreenshotOptions.java | 2 +- .../{ => cdp}/entities/EmulatedState.java | 2 +- .../{ => cdp}/entities/EntryPreview.java | 2 +- .../{ => cdp}/entities/ErrorReasons.java | 2 +- .../{ => cdp}/entities/EvaluateResponse.java | 2 +- .../{ => cdp}/entities/EvaluateType.java | 2 +- .../{ => cdp}/entities/ExceptionDetails.java | 2 +- .../entities/ExecutionContextDescription.java | 2 +- .../entities/ExemptedSetCookieWithReason.java | 2 +- .../{ => cdp}/entities/FetcherOptions.java | 4 +- .../{ => cdp}/entities/FilterEntry.java | 2 +- .../entities/FrameAddScriptTagOptions.java | 2 +- .../entities/FrameAddStyleTagOptions.java | 6 +- .../{ => cdp}/entities/FramePayload.java | 2 +- .../{ => cdp}/entities/FunctionCoverage.java | 2 +- .../{ => cdp}/entities/GeoLocationState.java | 2 +- .../entities/GeolocationOptions.java | 2 +- .../entities/GetMetricsResponse.java | 2 +- .../GetNavigationHistoryResponse.java | 2 +- .../entities/GetVersionResponse.java | 2 +- .../{ => cdp}/entities/GoToOptions.java | 7 +- .../{ => cdp}/entities/HeaderEntry.java | 10 +- .../entities/IdleOverridesState.java | 2 +- .../{ => cdp}/entities/ImageType.java | 2 +- .../{ => cdp}/entities/Initiator.java | 2 +- .../entities/InterceptResolutionAction.java | 2 +- .../entities/InterceptResolutionState.java | 2 +- .../{ => cdp}/entities/Interception.java | 2 +- .../entities/InternalNetworkConditions.java | 2 +- .../{ => cdp}/entities/JSCoverageEntry.java | 2 +- .../{ => cdp}/entities/JSCoverageOptions.java | 2 +- .../entities/JavascriptEnabledState.java | 2 +- .../{ => cdp}/entities/KeyDefinition.java | 2 +- .../{ => cdp}/entities/KeyDescription.java | 2 +- .../{ => cdp}/entities/KeyDownOptions.java | 2 +- .../{ => cdp}/entities/KeyPressOptions.java | 2 +- .../entities/KeyboardTypeOptions.java | 2 +- .../{ => cdp}/entities/LaunchOptions.java | 32 +- .../{ => cdp}/entities/LengthUnit.java | 2 +- .../{ => cdp}/entities/LogEntry.java | 2 +- .../{ => cdp}/entities/MediaFeature.java | 2 +- .../entities/MediaFeaturesState.java | 2 +- .../{ => cdp}/entities/MediaTypeState.java | 2 +- .../jvppeteer/{ => cdp}/entities/Metric.java | 2 +- .../jvppeteer/{ => cdp}/entities/Metrics.java | 2 +- .../{ => cdp}/entities/MouseClickOptions.java | 15 +- .../cdp/entities/MouseMoveOptions.java | 26 + .../{ => cdp}/entities/MouseOptions.java | 2 +- .../{ => cdp}/entities/MouseState.java | 6 +- .../{ => cdp}/entities/MouseWheelOptions.java | 2 +- .../{ => cdp}/entities/NavigationEntry.java | 2 +- .../{ => cdp}/entities/NetworkConditions.java | 2 +- .../entities/NewDocumentScriptEvaluation.java | 2 +- .../{ => cdp}/entities/ObjectPreview.java | 2 +- .../jvppeteer/{ => cdp}/entities/Offset.java | 2 +- .../{ => cdp}/entities/PDFMargin.java | 2 +- .../{ => cdp}/entities/PDFOptions.java | 2 +- .../{ => cdp}/entities/PageMetrics.java | 2 +- .../{ => cdp}/entities/PaperFormats.java | 2 +- .../jvppeteer/{ => cdp}/entities/Point.java | 3 +- .../{ => cdp}/entities/PostDataEntry.java | 2 +- .../{ => cdp}/entities/PreloadScript.java | 12 +- .../{ => cdp}/entities/PropertyPreview.java | 2 +- .../jvppeteer/cdp/entities/Protocol.java | 6 + .../{ => cdp}/entities/QueuedEventGroup.java | 8 +- .../jvppeteer/{ => cdp}/entities/RGBA.java | 2 +- .../jvppeteer/{ => cdp}/entities/Range.java | 2 +- .../{ => cdp}/entities/RedirectInfo.java | 4 +- .../{ => cdp}/entities/RemoteAddress.java | 2 +- .../{ => cdp}/entities/RemoteObject.java | 2 +- .../{ => cdp}/entities/RequestPayload.java | 2 +- .../{ => cdp}/entities/ResourceTiming.java | 29 +- .../{ => cdp}/entities/ResourceType.java | 8 +- .../entities/ResponseForRequest.java | 2 +- .../{ => cdp}/entities/ResponsePayload.java | 2 +- .../entities/ResponseSecurityDetails.java | 4 +- .../{ => cdp}/entities/RevisionInfo.java | 2 +- .../{ => cdp}/entities/ScreenCastFormat.java | 2 +- .../{ => cdp}/entities/ScreenOrientation.java | 2 +- .../entities/ScreenRecorderOptions.java | 2 +- .../entities/ScreencastFrameMetadata.java | 2 +- .../{ => cdp}/entities/ScreencastOptions.java | 2 +- .../{ => cdp}/entities/ScreenshotClip.java | 3 +- .../{ => cdp}/entities/ScreenshotOptions.java | 2 +- .../{ => cdp}/entities/ScriptCoverage.java | 2 +- .../{ => cdp}/entities/SecurityDetails.java | 2 +- .../cdp/entities/SelectorParseResult.java | 53 + .../{ => cdp}/entities/SerializedAXNode.java | 5 +- .../entities/ServiceWorkerRouterInfo.java | 2 +- .../entities/SignedCertificateTimestamp.java | 2 +- .../{ => cdp}/entities/SnapshotOptions.java | 10 +- .../{ => cdp}/entities/StackTrace.java | 2 +- .../{ => cdp}/entities/StackTraceId.java | 2 +- .../entities/TakePreciseCoverageResponse.java | 2 +- .../{ => cdp}/entities/TargetInfo.java | 2 +- .../{ => cdp}/entities/TargetType.java | 2 +- .../{ => cdp}/entities/Timeoutable.java | 2 +- .../{ => cdp}/entities/Timestamp.java | 2 +- .../{ => cdp}/entities/TimezoneState.java | 2 +- .../ruiyun/jvppeteer/cdp/entities/Token.java | 50 + .../{ => cdp}/entities/TouchPoint.java | 2 +- .../{ => cdp}/entities/TrustTokenParams.java | 2 +- .../jvppeteer/{ => cdp}/entities/Updater.java | 4 +- .../entities/UserAgentBrandVersion.java | 2 +- .../{ => cdp}/entities/UserAgentMetadata.java | 18 +- .../{ => cdp}/entities/Viewport.java | 2 +- .../{ => cdp}/entities/ViewportState.java | 2 +- .../{ => cdp}/entities/VisionDeficiency.java | 2 +- .../entities/VisionDeficiencyState.java | 2 +- .../entities/WaitForNetworkIdleOptions.java | 18 +- .../{ => cdp}/entities/WaitForOptions.java | 3 +- .../entities/WaitForSelectorOptions.java | 23 +- .../cdp/entities/WaitTaskOptions.java | 40 + .../events/AttachedToTargetEvent.java | 4 +- .../{ => cdp}/events/AuthRequiredEvent.java | 6 +- .../{ => cdp}/events/BindingCalledEvent.java | 2 +- .../events/ConsoleAPICalledEvent.java | 6 +- .../events/DetachedFromTargetEvent.java | 2 +- .../events/DeviceRequestPromptedEvent.java | 4 +- .../events/DownloadProgressEvent.java | 4 +- .../events/DownloadWillBeginEvent.java | 2 +- .../{ => cdp}/events/EntryAddedEvent.java | 4 +- .../events/ExceptionThrownEvent.java | 5 +- .../events/ExecutionContextCreatedEvent.java | 4 +- .../ExecutionContextDestroyedEvent.java | 2 +- .../events/FileChooserOpenedEvent.java | 2 +- .../{ => cdp}/events/FrameAttachedEvent.java | 4 +- .../{ => cdp}/events/FrameDetachedEvent.java | 2 +- .../{ => cdp}/events/FrameNavigatedEvent.java | 4 +- .../events/FrameStartedLoadingEvent.java | 2 +- .../events/FrameStoppedLoadingEvent.java | 2 +- .../events/IsolatedWorldEmitter.java | 4 +- .../events/JavascriptDialogOpeningEvent.java | 4 +- .../{ => cdp}/events/LifecycleEvent.java | 2 +- .../{ => cdp}/events/LoadingFailedEvent.java | 4 +- .../events/LoadingFinishedEvent.java | 2 +- .../{ => cdp}/events/MetricsEvent.java | 4 +- .../events/NavigatedWithinDocumentEvent.java | 2 +- .../{ => cdp}/events/RequestPausedEvent.java | 6 +- .../events/RequestServedFromCacheEvent.java | 2 +- .../events/RequestWillBeSentEvent.java | 8 +- .../events/ResponseReceivedEvent.java | 4 +- .../ResponseReceivedExtraInfoEvent.java | 23 +- .../events/ScreencastFrameEvent.java | 4 +- .../{ => cdp}/events/ScriptParsedEvent.java | 6 +- .../events/StyleSheetAddedEvent.java | 4 +- .../{ => cdp}/events/TargetCreatedEvent.java | 4 +- .../events/TargetDestroyedEvent.java | 2 +- .../events/TargetInfoChangedEvent.java | 4 +- .../events/TracingCompleteEvent.java | 2 +- .../jvppeteer/common/ARIAQueryHandler.java | 39 +- .../jvppeteer/common/BrowserRevision.java | 24 + .../jvppeteer/common/CSSQueryHandler.java | 21 + .../jvppeteer/common/ChromeEnvironment.java | 12 +- .../ruiyun/jvppeteer/common/ConsoleAPI.java | 7 +- .../com/ruiyun/jvppeteer/common/Constant.java | 180 +- .../jvppeteer/common/DeviceRequestPrompt.java | 16 +- .../common/DeviceRequestPromptManager.java | 14 +- .../jvppeteer/common/DisposableStack.java | 40 + .../jvppeteer/common/FirefoxChannel.java | 2 +- .../jvppeteer/common/FrameProvider.java | 2 +- .../jvppeteer/common/PQueryHandler.java | 25 + .../jvppeteer/common/PierceQueryHandler.java | 25 + .../jvppeteer/common/PollingOptions.java | 11 + .../common/PredefinedNetworkConditions.java | 2 +- .../jvppeteer/common/PrimitiveValue.java | 15 + .../com/ruiyun/jvppeteer/common/Product.java | 10 +- .../PuppeteerLifeCycle.java | 10 +- .../ruiyun/jvppeteer/common/QueryHandler.java | 133 +- .../jvppeteer/common/QuerySelector.java | 16 +- .../jvppeteer/common/ScreenRecorder.java | 39 +- .../ruiyun/jvppeteer/common/TaskManager.java | 39 +- .../jvppeteer/common/TextQueryHandler.java | 19 + .../jvppeteer/common/WebPermission.java | 2 - .../jvppeteer/common/XPathQueryHandler.java | 28 + .../ruiyun/jvppeteer/core/BrowserContext.java | 196 - .../ruiyun/jvppeteer/core/DevToolsTarget.java | 13 - .../com/ruiyun/jvppeteer/core/Dialog.java | 93 - .../jvppeteer/core/ExecutionContext.java | 459 -- .../java/com/ruiyun/jvppeteer/core/Frame.java | 845 ---- .../com/ruiyun/jvppeteer/core/JSHandle.java | 143 - .../ruiyun/jvppeteer/core/OtherTarget.java | 11 - .../java/com/ruiyun/jvppeteer/core/Page.java | 2543 ----------- .../com/ruiyun/jvppeteer/core/PageExtend.java | 65 - .../java/com/ruiyun/jvppeteer/core/Realm.java | 73 - .../ruiyun/jvppeteer/core/Touchscreen.java | 93 - .../com/ruiyun/jvppeteer/core/WaitTask.java | 228 - .../com/ruiyun/jvppeteer/core/WebWorker.java | 123 - .../entities/ConsoleMessageType.java | 23 - .../jvppeteer/entities/LogEntryLevel.java | 14 - .../jvppeteer/entities/MouseMoveOptions.java | 13 - .../jvppeteer/entities/WaitTaskOptions.java | 52 - .../jvppeteer/launch/BrowserLauncher.java | 242 +- .../jvppeteer/launch/ChromeLauncher.java | 33 +- .../jvppeteer/launch/FirefoxLauncher.java | 80 +- .../ruiyun/jvppeteer/transport/Callback.java | 2 +- .../jvppeteer/transport/CallbackRegistry.java | 17 +- .../{CDPSession.java => CdpCDPSession.java} | 113 +- .../{Connection.java => CdpConnection.java} | 599 ++- .../transport/ConnectionTransport.java | 2 + .../jvppeteer/transport/PipeTransport.java | 1 + .../jvppeteer/transport/SessionFactory.java | 2 + .../transport/WebSocketTransport.java | 42 +- .../transport/WebSocketTransportFactory.java | 72 +- .../com/ruiyun/jvppeteer/util/FileUtil.java | 2 - .../com/ruiyun/jvppeteer/util/Helper.java | 214 +- .../jvppeteer/util/QueryHandlerUtil.java | 228 +- .../com/ruiyun/jvppeteer/util/StringUtil.java | 2 - .../install-chrome-for-testing-linux.sh | 48 +- .../install-chrome-for-testing-win.ps1 | 67 +- 495 files changed, 24308 insertions(+), 13665 deletions(-) delete mode 100644 "1.1.5\347\211\210\346\234\254\344\271\213\345\211\215\347\232\204\345\206\205\345\255\230\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210.md" delete mode 100644 api.md delete mode 100644 example/src/main/java/com/ruiyun/example/PageExtendExample.java create mode 100644 example/src/main/resources/META-INF/MANIFEST.MF rename example/{ => src/main/resources}/simplelogger.properties (85%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Browser.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/BrowserContext.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/CDPSession.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Connection.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Dialog.java rename src/main/java/com/ruiyun/jvppeteer/{ => api}/core/ElementHandle.java (76%) rename src/main/java/com/ruiyun/jvppeteer/{events => api/core}/EventEmitter.java (74%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Frame.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/JSHandle.java rename src/main/java/com/ruiyun/jvppeteer/{ => api}/core/Keyboard.java (75%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Mouse.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Page.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Realm.java rename src/main/java/com/ruiyun/jvppeteer/{ => api}/core/Request.java (53%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Response.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Target.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/TouchHandle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/Touchscreen.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/core/WebWorker.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/events/BrowserContextEvents.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/events/BrowserEvents.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/events/ConnectionEvents.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/events/FrameEvents.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/events/PageEvents.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/api/events/TrustedEmitter.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiBrowser.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiBrowserContext.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiBrowserTarget.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiCdpSession.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiConnection.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiDeserializer.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiDialog.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiElementHandle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiExposeableFunction.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiFrame.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiFrameRealm.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiFrameTarget.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiJSHandle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiKeyboard.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiLifeCycleWatch.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiMouse.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiPage.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiPageTarget.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiRealm.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiRealmCore.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiRequest.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiResponse.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiSerializer.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiTouchHandle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiTouchscreen.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiWebWorker.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiWorkerRealm.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BidiWorkerTarget.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BrowserCore.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/BrowsingContext.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/DedicatedWorkerRealm.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/Navigation.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/NoneSourceAction.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/ReadinessState.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/RequestCore.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/Session.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/SharedWorkerRealm.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/UserContext.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/UserPrompt.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/core/WindowRealm.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/AddInterceptOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/AddPreloadScriptOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ArrayLocalValue.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/AuthCredentials.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/AuthRequiredParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/BaseParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/BeforeRequestSentParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/BidiViewport.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/BytesValue.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/CallFunctionOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Capabilities.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/CapabilitiesRequest.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/CapabilityRequest.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/CaptureScreenshotOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ChannelProperties.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ChannelValue.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ClipRectangle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ClosedEvent.java rename src/main/java/com/ruiyun/jvppeteer/{entities/CookieParam.java => bidi/entities/Cookie.java} (51%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/CookieFilter.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/CreateBrowsingContextOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/CreateType.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/EvaluateOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/EvaluateResult.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ExceptionDetails.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/FetchErrorParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/FetchTimingInfo.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/GetCookiesOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/HandleUserPromptOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Header.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ImageFormat.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Initiator.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/InputId.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/InterceptPhase.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/LocalValue.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/LogEntry.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/MessageParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/NavigationInfo.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/NewResult.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Orientation.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Origin.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PartialCookie.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PartitionDescriptor.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PermissionOverride.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PermissionState.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PointerCommonProperties.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PointerParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PointerType.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PrintMarginParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PrintOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/PrintPageParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ProxyConfiguration.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/RealmDestroyedParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/RealmInfo.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/RealmType.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ReloadParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/RemoteReference.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/RemoteValue.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/RequestData.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ResponseCompletedParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ResponseContent.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ResponseData.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ResponseStartedParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/ResultOwnership.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SameSite.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SerializationOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SetViewportParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SharedReference.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Source.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SourceActions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SourceActionsType.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SupportedWebDriverCapabilities.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/SupportedWebDriverCapability.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Target.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/UrlPattern.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/UserPromptClosedParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/UserPromptHandler.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/UserPromptHandlerType.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/UserPromptOpenedParameters.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/UserPromptResult.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/UserPromptType.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/Value.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/entities/WaitForOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/events/ContextCreatedEvent.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/events/NavigationInfoEvent.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/bidi/events/SharedworkerEvent.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/AXNode.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/Accessibility.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/BrowserFetcher.java (78%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/BrowserRunner.java (55%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/CSSCoverage.java (79%) rename src/main/java/com/ruiyun/jvppeteer/{core/Browser.java => cdp/core/CdpBrowser.java} (57%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpBrowserContext.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpDialog.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpElementHandle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpFrame.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpJSHandle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpKeyboard.java rename src/main/java/com/ruiyun/jvppeteer/{core/Mouse.java => cdp/core/CdpMouse.java} (89%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpPage.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpRequest.java rename src/main/java/com/ruiyun/jvppeteer/{core/Response.java => cdp/core/CdpResponse.java} (82%) rename src/main/java/com/ruiyun/jvppeteer/{core/Target.java => cdp/core/CdpTarget.java} (75%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchHandle.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchscreen.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpWebWorker.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/ChromeTargetManager.java (78%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/Coverage.java (88%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/DevToolsTarget.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/EmulationManager.java (88%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/ExecutionContext.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/FileChooser.java (90%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/FirefoxTargetManager.java (72%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/FrameManager.java (76%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/FrameTree.java (70%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/FrameTreeEvent.java (83%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/IsolatedWorld.java (75%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/JSCoverage.java (81%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/LifecycleWatcher.java (69%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/NetworkEventManager.java (90%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/NetworkManager.java (73%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/OtherTarget.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/PageTarget.java (65%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/Puppeteer.java (72%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/TargetManager.java (66%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/Tracing.java (89%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/core/WaitTask.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/core/WorkerTarget.java (50%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AXNode.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AXProperty.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AXRelatedNode.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AXValue.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AXValueSource.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ActiveProperty.java (86%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AdFrameStatus.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AuthChallenge.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AuthChallengeResponse.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AutofillData.java (89%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/AuxData.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Binding.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/BindingPayload.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/BlockedSetCookieWithReason.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/BoundingBox.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/BoxModel.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/BrowserConnectOptions.java (72%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/BrowserContextOptions.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/BrowserLaunchArgumentOptions.java (80%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CSSCoverageOptions.java (89%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CSSStyleSheetHeader.java (99%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CallFrame.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ClickOptions.java (88%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ClientProvider.java (60%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ConnectOptions.java (57%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ConsoleMessage.java (80%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ConsoleMessageLocation.java (97%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessageType.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ContinueRequestOverrides.java (74%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Cookie.java (85%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieParam.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CookiePartitionKey.java (94%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookiePriority.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSameSite.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSourceScheme.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CorsErrorStatus.java (91%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CoverageEntry.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CoveragePoint.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CoverageRange.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CpuThrottlingState.java (90%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Credentials.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CreditCard.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/CustomPreview.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DebugInfo.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DefaultBackgroundColorState.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DeleteCookiesRequest.java (85%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Device.java (99%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DeviceRequestPromptDevice.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DialogType.java (86%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DisposableStackConsumer.java (67%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DownloadOptions.java (80%) rename src/main/java/com/ruiyun/jvppeteer/{entities/DownloadBehavior.java => cdp/entities/DownloadPolicy.java} (69%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DownloadState.java (83%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DragData.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DragDataItem.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/DragInterceptedEvent.java (82%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ElementScreenshotOptions.java (88%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/EmulatedState.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/EntryPreview.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ErrorReasons.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/EvaluateResponse.java (91%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/EvaluateType.java (76%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ExceptionDetails.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ExecutionContextDescription.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ExemptedSetCookieWithReason.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/FetcherOptions.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/FilterEntry.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/FrameAddScriptTagOptions.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/FrameAddStyleTagOptions.java (82%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/FramePayload.java (99%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/FunctionCoverage.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/GeoLocationState.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/GeolocationOptions.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/GetMetricsResponse.java (85%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/GetNavigationHistoryResponse.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/GetVersionResponse.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/GoToOptions.java (89%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/HeaderEntry.java (69%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/IdleOverridesState.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ImageType.java (79%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Initiator.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/InterceptResolutionAction.java (89%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/InterceptResolutionState.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Interception.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/InternalNetworkConditions.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/JSCoverageEntry.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/JSCoverageOptions.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/JavascriptEnabledState.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/KeyDefinition.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/KeyDescription.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/KeyDownOptions.java (91%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/KeyPressOptions.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/KeyboardTypeOptions.java (81%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/LaunchOptions.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/LengthUnit.java (83%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/LogEntry.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/MediaFeature.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/MediaFeaturesState.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/MediaTypeState.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Metric.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Metrics.java (99%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/MouseClickOptions.java (55%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseMoveOptions.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/MouseOptions.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/MouseState.java (69%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/MouseWheelOptions.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/NavigationEntry.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/NetworkConditions.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/NewDocumentScriptEvaluation.java (90%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ObjectPreview.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Offset.java (87%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/PDFMargin.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/PDFOptions.java (99%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/PageMetrics.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/PaperFormats.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Point.java (90%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/PostDataEntry.java (81%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/PreloadScript.java (54%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/PropertyPreview.java (97%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/Protocol.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/QueuedEventGroup.java (80%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/RGBA.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Range.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/RedirectInfo.java (81%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/RemoteAddress.java (91%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/RemoteObject.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/RequestPayload.java (99%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ResourceTiming.java (82%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ResourceType.java (81%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ResponseForRequest.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ResponsePayload.java (99%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ResponseSecurityDetails.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/RevisionInfo.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScreenCastFormat.java (84%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScreenOrientation.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScreenRecorderOptions.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScreencastFrameMetadata.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScreencastOptions.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScreenshotClip.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScreenshotOptions.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ScriptCoverage.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/SecurityDetails.java (99%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/SelectorParseResult.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/SerializedAXNode.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ServiceWorkerRouterInfo.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/SignedCertificateTimestamp.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/SnapshotOptions.java (69%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/StackTrace.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/StackTraceId.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/TakePreciseCoverageResponse.java (88%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/TargetInfo.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/TargetType.java (91%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Timeoutable.java (91%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Timestamp.java (88%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/TimezoneState.java (91%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/Token.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/TouchPoint.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/TrustTokenParams.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Updater.java (52%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/UserAgentBrandVersion.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/UserAgentMetadata.java (78%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/Viewport.java (98%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/ViewportState.java (89%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/VisionDeficiency.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/VisionDeficiencyState.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/WaitForNetworkIdleOptions.java (71%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/WaitForOptions.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/entities/WaitForSelectorOptions.java (66%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitTaskOptions.java rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/AttachedToTargetEvent.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/AuthRequiredEvent.java (91%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/BindingCalledEvent.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ConsoleAPICalledEvent.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/DetachedFromTargetEvent.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/DeviceRequestPromptedEvent.java (88%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/DownloadProgressEvent.java (92%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/DownloadWillBeginEvent.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/EntryAddedEvent.java (82%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ExceptionThrownEvent.java (86%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ExecutionContextCreatedEvent.java (78%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ExecutionContextDestroyedEvent.java (90%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/FileChooserOpenedEvent.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/FrameAttachedEvent.java (90%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/FrameDetachedEvent.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/FrameNavigatedEvent.java (85%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/FrameStartedLoadingEvent.java (89%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/FrameStoppedLoadingEvent.java (87%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/IsolatedWorldEmitter.java (84%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/JavascriptDialogOpeningEvent.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/LifecycleEvent.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/LoadingFailedEvent.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/LoadingFinishedEvent.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/MetricsEvent.java (85%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/NavigatedWithinDocumentEvent.java (93%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/RequestPausedEvent.java (96%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/RequestServedFromCacheEvent.java (89%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/RequestWillBeSentEvent.java (94%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ResponseReceivedEvent.java (95%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ResponseReceivedExtraInfoEvent.java (75%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ScreencastFrameEvent.java (88%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/ScriptParsedEvent.java (97%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/StyleSheetAddedEvent.java (74%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/TargetCreatedEvent.java (76%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/TargetDestroyedEvent.java (84%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/TargetInfoChangedEvent.java (80%) rename src/main/java/com/ruiyun/jvppeteer/{ => cdp}/events/TracingCompleteEvent.java (92%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/BrowserRevision.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/CSSQueryHandler.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/DisposableStack.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/PQueryHandler.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/PierceQueryHandler.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/PollingOptions.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/PrimitiveValue.java rename src/main/java/com/ruiyun/jvppeteer/{entities => common}/PuppeteerLifeCycle.java (81%) create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/TextQueryHandler.java create mode 100644 src/main/java/com/ruiyun/jvppeteer/common/XPathQueryHandler.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/BrowserContext.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/DevToolsTarget.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/Dialog.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/ExecutionContext.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/Frame.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/JSHandle.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/OtherTarget.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/Page.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/PageExtend.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/Realm.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/Touchscreen.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/WaitTask.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/core/WebWorker.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessageType.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/entities/LogEntryLevel.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/entities/MouseMoveOptions.java delete mode 100644 src/main/java/com/ruiyun/jvppeteer/entities/WaitTaskOptions.java rename src/main/java/com/ruiyun/jvppeteer/transport/{CDPSession.java => CdpCDPSession.java} (51%) rename src/main/java/com/ruiyun/jvppeteer/transport/{Connection.java => CdpConnection.java} (60%) diff --git a/.gitignore b/.gitignore index 7ea0acb9..c13cd45e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.class .settings/** .idea/** -.local-browser/** +.local-cdpBrowser/** *.png *.jpg target/** diff --git "a/1.1.5\347\211\210\346\234\254\344\271\213\345\211\215\347\232\204\345\206\205\345\255\230\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/1.1.5\347\211\210\346\234\254\344\271\213\345\211\215\347\232\204\345\206\205\345\255\230\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210.md" deleted file mode 100644 index 70048096..00000000 --- "a/1.1.5\347\211\210\346\234\254\344\271\213\345\211\215\347\232\204\345\206\205\345\255\230\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210.md" +++ /dev/null @@ -1,222 +0,0 @@ - - -# 解决思路: - -通过获取 PID ,执行shell命令杀死进程。 - -如果单单在需要 LINUX 上获取 PID,可以不导入 JNA 包,LINUX上获取 PID 可以 通过反射得到。 - -更高版本的 JDK ,通过JDK 自带的方法就能获取。 - -## step 1 导入jna包 - -```xml - - net.java.dev.jna - jna - 5.13.0 - -``` -## step 2 创建内部接口 -```java -public interface Kernel32 extends StdCallLibrary { - Kernel32 INSTANCE = Native.load("kernel32", Kernel32.class); - long GetProcessId(Long hProcess); -} -``` - -## step 3 写获取进程id的方法 - -````java - import com.ruiyun.jvppeteer.core.Browser; - -/** - * 获取进程id - * @param process chrome进程 可以通过{@link com.ruiyun.jvppeteer.core.Browser#process()} 获取} - * @return 进程id - */ -public static String getProcessId(Process process) { - long pid = -1; - Field field; - if (Platform.isWindows()) { - try { - field = process.getClass().getDeclaredField("handle"); - field.setAccessible(true); - pid = BrowserRunner.Kernel32.INSTANCE.GetProcessId((Long) field.get(process)); - } catch (Exception e) { - LOGGER.error("Failed to get processId on Windows platform.", e); - } - } else if (Platform.isLinux() || Platform.isAIX()) { - try { - String version = System.getProperty("java.version"); - double jdkversion = Double.parseDouble(version.substring(0, 3)); - Class clazz; - //如果生产环境是jdk8,就不用if判断了 - if (jdkversion <= 1.8) { - clazz = Class.forName("java.lang.UNIXProcess"); - } else { - clazz = Class.forName("java.lang.ProcessImpl"); - } - field = clazz.getDeclaredField("pid"); - field.setAccessible(true); - pid = (Integer) field.get(process); - } catch (Throwable e) { - LOGGER.error("Failed to get processId on Linux or Aix platform.", e); - } - } - return String.valueOf(pid); -} -```` -## step 4 通过kill 命令杀死进程 - -```java -/** - * kill 掉浏览器进程 - */ -public boolean kill() { - try { - String pid = pidMap.get(this.process); - if("-1".equals(pid)){ - LOGGER.warn("Chrome process pid is -1,will not use kill cmd"); - return false; - } - if(StringUtil.isEmpty(pid) ){ - LOGGER.warn("Chrome process pid is empty,will not use kill cmd"); - return false; - } - Process exec; - String command = ""; - if (Platform.isWindows()) { - command = "cmd.exe /c taskkill /PID " + pid + " /F /T "; - } else if (Platform.isLinux() || Platform.isAIX()) { - command = "kill -9 " + pid; - } - LOGGER.info("kill chrome process by pid,command: kill -9 {}", pid); - exec = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",command}); - return exec.waitFor(Constant.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (Exception e) { - LOGGER.error("kill chrome process error ", e); - return false; - } -} -``` -## 完整代码示例 - -```java - -package com.ruiyun.example; - -import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.BrowserRunner; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.entities.LaunchOptions; -import com.ruiyun.jvppeteer.util.StringUtil; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.win32.StdCallLibrary; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -public class KillExample { - - private static final Logger LOGGER = LoggerFactory.getLogger(KillExample.class); - /** - * 多个browser的时候用pids储存pid - */ - private static Map pids = new HashMap<>(); - - public static void main(String[] args) throws IOException, InterruptedException { - LaunchOptions launchOptions = new LaunchOptionsBuilder().withExecutablePath("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe").withIgnoreDefaultArgs(Collections.singletonList("--enable-automation")).withHeadless(false).build(); - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); - page.goTo("https://www.baidu.com/?tn=98012088_10_dg&ch=3"); - Process process = browser.process(); - String processId = getProcessId(process); - KillExample.LOGGER.info("process pid {}", processId); - // 做一些其他操作 - - - browser.close(); //可以关闭websocket连接 - kill(processId); - } - - public interface Kernel32 extends StdCallLibrary { - Kernel32 INSTANCE = Native.load("kernel32", Kernel32.class); - - long GetProcessId(Long hProcess); - } - - public static String getProcessId(Process process) { - long pid = -1; - Field field; - if (Platform.isWindows()) { - try { - field = process.getClass().getDeclaredField("handle"); - field.setAccessible(true); - pid = KillExample.Kernel32.INSTANCE.GetProcessId((Long) field.get(process)); - } catch (Exception e) { - KillExample.LOGGER.error("Failed to get processId on Windows platform.", e); - } - } else if (Platform.isLinux() || Platform.isAIX()) { - try { - String version = System.getProperty("java.version"); - double jdkversion = Double.parseDouble(version.substring(0, 3)); - Class clazz; - //如果生产环境是jdk8,就不用if判断了 - if (jdkversion <= 1.8) { - clazz = Class.forName("java.lang.UNIXProcess"); - } else { - clazz = Class.forName("java.lang.ProcessImpl"); - } - field = clazz.getDeclaredField("pid"); - field.setAccessible(true); - pid = (Integer) field.get(process); - } catch (Throwable e) { - KillExample.LOGGER.error("Failed to get processId on Linux or Aix platform.", e); - } - } - return String.valueOf(pid); - } - - public static boolean kill(String pid) { - try { - if ("-1".equals(pid)) { - LOGGER.warn("Chrome process pid is -1,will not use kill cmd"); - return false; - } - if (StringUtil.isEmpty(pid)) { - LOGGER.warn("Chrome process pid is empty,will not use kill cmd"); - return false; - } - Process exec = null; - String command; - if (Platform.isWindows()) { - command = "cmd.exe /c taskkill /PID " + pid + " /F /T "; - exec = Runtime.getRuntime().exec(command); - } else if (Platform.isLinux() || Platform.isAIX()) { - command = "kill -9 " + pid; - exec = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", command}); - } - if (exec != null) { - return exec.waitFor(Constant.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } - } catch (Exception e) { - LOGGER.error("kill chrome process error ", e); - return false; - } - return false; - } -} - -``` - - diff --git a/README.md b/README.md index d96271a3..c9905edd 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,13 @@ # Jvppeteer

-下载最新版本的chromuim Maven Central Issue resolution status + Maven Central Quality Gate Status

+# **Java API For Chrome and Firefox** -## 注意 ->通过maven导入的jar包,1.1.5及之前的版本,都存在linux上杀不死chrome的bug,可以通过 1.1.5版本之前的内存问题解决方案 自行解决,仓库的代码已经将解决方案代码加上了,拉取下来打jar也可以用。 - -**本库的灵感来自 [Puppeteer(Node.js)](https://github.com/puppeteer/puppeteer), API 也与其基本上保持一致,做这个库是为了方便使用 Java 操控 [用于测试的Chrome](https://googlechromelabs.github.io/chrome-for-testing/#stable) (即Chrome for Testing,下面简称 Chrome)或 Chromium** - - - - - >Jvppeteer 通过 [DevTools](https://chromedevtools.github.io/devtools-protocol/) 控制 Chromium 或 Chrome。 - >默认情况下,以 headless (无界面)模式运行,也可以通过配置运行'有界面'模式。 +Jvppeteer 通过 [DevTools](https://chromedevtools.github.io/devtools-protocol/) and [WebDriver](https://pptr.nodejs.cn/webdriver-bidi) 控制 Chrome for Testing(下面简称 Chrome)或 Firefox。目前,Jvppeteer 仅支持通过 [DevTools](https://chromedevtools.github.io/devtools-protocol/) 控制 Chrome、默认通过 [WebDriver](https://pptr.nodejs.cn/webdriver-bidi) 控制 Firefox。 默认情况下,以 headless (无 UI)模式运行,也可以通过配置运行'有UI'模式。 你可以在浏览器中手动执行的绝大多数操作都可以使用 Jvppeteer 来完成! 下面是一些示例: @@ -63,7 +55,7 @@ compile "io.github.fanyong920:jvppeteer:2.2.5" 该库使用 [SLF4J](https://www.slf4j.org/) 进行日志记录,并且不附带任何默认日志记录实现。 -调试程序将日志级别设置为 TRACE。 +调试程序将日志级别设置为 Debug。 #### 独立 jar @@ -112,7 +104,7 @@ compile "io.github.fanyong920:jvppeteer:2.2.5" Jvpeteer 提供 Chrome、Chromium、ChromeDriver、Chrome Headless Shell 四种浏览器的下载功能。 -下载 Chromium、ChromeDriver、Chrome Headless Shell 必须明确下载版本,Chrome 有默认版本,存放在Constant#VERSION 中。 +下载 Chromium、ChromeDriver、Chrome Headless Shell 必须明确下载版本,Chrome 有默认版本,存放在BrowserRevision.class 中。 关于下载浏览器的版本选择,可以浏览一下这两个网页:[Chrome for Testing availability](https://googlechromelabs.github.io/chrome-for-testing/#stable) 与 [JSON API endpoints](https://github.com/GoogleChromeLabs/chrome-for-testing) @@ -124,8 +116,9 @@ Jvpeteer 提供 Chrome、Chromium、ChromeDriver、Chrome Headless Shell 四种 下表提供了 Jvppeteer 版本与绑定的浏览器版本之间的映射。如果没有列出完全匹配的 Jvppeteer 版本,则支持的浏览器版本是紧接在前的版本: -| 2.2.0--2.2.5 | [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/#stable) 130.0.6723.58 | +| 3.0.0 | [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/#stable) 131.0.6778.87 & Firefox stable_133.0 | | :----------: | :----------------------------------------------------------: | +| 2.2.0--2.2.5 | [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/#stable) 130.0.6723.58 | | 2.1.2 | [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/#stable) 128.0.6613.137 | | 2.1.1 | [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/#stable) 128.0.6613.137 | | 2.1.0 | [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/#stable) 128.0.6613.137 | @@ -153,9 +146,9 @@ Jvpeteer 提供 Chrome、Chromium、ChromeDriver、Chrome Headless Shell 四种 使用浏览器后,必须关闭它,使用 Browser.close() 关闭。 ```java - Browser browser = Puppeteer.launch(); - Page page = browser.newPage(); - browser.close(); + Browser cdpBrowser = Puppeteer.launch(); + Page page = cdpBrowser.newPage(); + cdpBrowser.close(); ``` #### 4、浏览器上下文 @@ -163,9 +156,9 @@ Jvpeteer 提供 Chrome、Chromium、ChromeDriver、Chrome Headless Shell 四种 如果你需要隔离自动化任务,请使用 BrowserContexts。Cookie 和本地存储不在浏览器上下文之间共享。 ```java - Browser browser = Puppeteer.launch(launchOptions); - BrowserContext defaultBrowserContext = browser.defaultBrowserContext(); - Page page = defaultBrowserContext.newPage(); + Browser cdpBrowser = Puppeteer.launch(launchOptions); + BrowserContext defaultCdpBrowserContext = cdpBrowser.defaultCdpBrowserContext(); + Page page = defaultCdpBrowserContext.newPage(); new Thread(() -> { try { page.evaluate("() => window.open('https://www.example.com/')"); @@ -173,48 +166,48 @@ Jvpeteer 提供 Chrome、Chromium、ChromeDriver、Chrome Headless Shell 四种 throw new RuntimeException(e); } }).start(); - Target target1 = defaultBrowserContext.waitForTarget(target -> target.url().equals("https://www.example.com/")); + Target target1 = defaultCdpBrowserContext.waitForTarget(target -> target.url().equals("https://www.example.com/")); System.out.println("target1:" + target1.url()); - List pages = defaultBrowserContext.pages(); + List pages = defaultCdpBrowserContext.pages(); System.out.println("size1:" + pages.size()); - defaultBrowserContext.newPage(); - System.out.println("size2:" + defaultBrowserContext.pages().size()); - List targets = defaultBrowserContext.targets(); + defaultCdpBrowserContext.newPage(); + System.out.println("size2:" + defaultCdpBrowserContext.pages().size()); + List targets = defaultCdpBrowserContext.targets(); for (Target target : targets) { System.out.println("all target forEach:(" + target.type() + ":" + target.url() + ")"); } - BrowserContext browserContext = browser.createBrowserContext(); - Page page1 = browserContext.newPage(); - Browser browser1 = browserContext.browser(); - System.out.println("broswer equals:" + (browser1 == browser)); - browserContext.overridePermissions("https://www.baidu.com", WebPermission.GEOLOCATION); + BrowserContext cdpBrowserContext = cdpBrowser.createBrowserContext(); + Page page1 = cdpBrowserContext.newPage(); + Browser cdpBrowser1 = cdpBrowserContext.cdpBrowser(); + System.out.println("broswer equals:" + (cdpBrowser1 == cdpBrowser)); + cdpBrowserContext.overridePermissions("https://www.baidu.com", WebPermission.GEOLOCATION); page1.goTo("https://www.baidu.com"); - browserContext.close(); - System.out.println("close: " + browserContext.closed()); + cdpBrowserContext.close(); + System.out.println("close: " + cdpBrowserContext.closed()); //默认浏览器不能关闭 - defaultBrowserContext.close(); + defaultCdpBrowserContext.close(); Thread.sleep(15000); - browser.close(); + cdpBrowser.close(); ``` 在浏览器上下文中,你可以打开一个新的页面,可以获得浏览器上下文的所有页面,可以通过关闭 浏览器上下文 来关闭 对应的所有页面,也可以给浏览器上下文授予独特的权限。**提醒 :创建多个浏览器上下文比创建多个浏览器好 ** #### 5、连接到远程的浏览器 -如果你在 Jvppeteer 之外打开了一个新的浏览器,你可以用 Puppeteer.connect() 方法连接,连接远程的浏览器需要 URL,该 URL 可以是 Websocket URL(格式是 ws://HOST:PORT/devtools/browser/),也可以是 Browser URL (格式是 http://HOST:PORT )。 +如果你在 Jvppeteer 之外打开了一个新的浏览器,你可以用 Puppeteer.connect() 方法连接,连接远程的浏览器需要 URL,该 URL 可以是 Websocket URL(格式是 ws://HOST:PORT/devtools/cdpBrowser/),也可以是 Browser URL (格式是 http://HOST:PORT )。 在 Browser URL 中的 PORT 是 debuggingPort,在浏览器启动时候加上参数:-remote-debugging-port=xxxx,debuggingPort 即 xxxx。 Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version 可以获取到 WebSocket URL ```java - String wsEndpoint = browser.wsEndpoint(); - browser.disconnect(); + String wsEndpoint = cdpBrowser.wsEndpoint(); + cdpBrowser.disconnect(); //ws连接 - Browser wsBrowser = Puppeteer.connect(wsEndpoint); - wsBrowser.disconnect(); + Browser wsCdpBrowser = Puppeteer.connect(wsEndpoint); + wsCdpBrowser.disconnect(); //url连接 http://host:port 因为启动时候配置DebuggingPort=9222 所以url = localhost:9222 - Browser urlBrowser = Puppeteer.connect("http://localhost:9222"); + Browser urlCdpBrowser = Puppeteer.connect("http://localhost:9222"); ``` #### 6、页面打印PDF @@ -227,8 +220,8 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version ArrayList args = new ArrayList<>();//添加一些额外的启动参数 args.add("--no-sandbox");//pdf必须添加这个参数,不然无法打印,具体看这里https://github.com/puppeteer/puppeteer/issues/12470 launchOptions.setArgs(args); - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); + Browser cdpBrowser = Puppeteer.launch(launchOptions); + Page page = cdpBrowser.newPage(); GoToOptions goToOptions = new GoToOptions(); goToOptions.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.NETWORKIDLE)); page.goTo("https://www.baidu.com/?tn=68018901_16_pg",goToOptions); @@ -241,7 +234,7 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version pdfOptions.setScale(1.1);//缩放比例1.1 page.pdf(pdfOptions); //关闭浏览器 - browser.close(); + cdpBrowser.close(); ``` 默认情况下,Page.pdf() 等待字体加载。 @@ -253,8 +246,8 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version ```java @Test public void test3() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); + Browser cdpBrowser = Puppeteer.launch(launchOptions); + Page page = cdpBrowser.newPage(); page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); ScreenshotOptions screenshotOptions = new ScreenshotOptions(); screenshotOptions.setPath("baidu.png"); @@ -264,13 +257,13 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version //截图的更多 screenshotOptions.setCaptureBeyondViewport(true); page.screenshot(screenshotOptions); - browser.close(); + cdpBrowser.close(); } @Test public void test4() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); + Browser cdpBrowser = Puppeteer.launch(launchOptions); + Page page = cdpBrowser.newPage(); page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); ScreenshotOptions screenshotOptions = new ScreenshotOptions(); screenshotOptions.setPath("baidu.png"); @@ -281,13 +274,13 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version //全屏截图 screenshotOptions.setFullPage(true); page.screenshot(screenshotOptions); - browser.close(); + cdpBrowser.close(); } @Test public void test5() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); + Browser cdpBrowser = Puppeteer.launch(launchOptions); + Page page = cdpBrowser.newPage(); page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); ScreenshotOptions screenshotOptions = new ScreenshotOptions(); screenshotOptions.setPath("baidu.jpeg"); @@ -298,7 +291,7 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version //全屏截图 screenshotOptions.setFullPage(true); page.screenshot(screenshotOptions); - browser.close(); + cdpBrowser.close(); } ``` @@ -312,8 +305,8 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version */ @Test public void test25() throws IOException { - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); + Browser cdpBrowser = Puppeteer.launch(launchOptions); + Page page = cdpBrowser.newPage(); page.goTo("https://www.geetest.com/demo/slide-en.html"); ScreencastOptions screencastOptions = new ScreencastOptions(); screencastOptions.setPath("D:\\test\\test2.webm"); @@ -326,7 +319,7 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version page.type("#username", "123456789", 200); page.type("#password", "123456789", 200); screencast.stop(); - browser.close(); + cdpBrowser.close(); } /** @@ -334,8 +327,8 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version */ @Test public void test26() throws IOException { - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); + Browser cdpBrowser = Puppeteer.launch(launchOptions); + Page page = cdpBrowser.newPage(); page.goTo("https://www.geetest.com/demo/slide-en.html"); ScreencastOptions screencastOptions = new ScreencastOptions(); screencastOptions.setPath("D:\\test\\test.gif"); @@ -345,7 +338,7 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version page.type("#username", "123456789", 200); page.type("#password", "123456789", 200); screencast.stop(); - browser.close(); + cdpBrowser.close(); } ``` @@ -355,16 +348,13 @@ Browser URL 后加上 /json/version,格式是:http://HOST:PORT/json/version 如果你在 Linux 上安装 Chrome 并运行 遇到麻烦,或者在某个场景中遇到麻烦,可以 在 [Puppeteer(Node.js)](https://github.com/puppeteer/puppeteer) 库中的 [Troubleshooting](https://pptr.dev/troubleshooting) 寻找答案,也可以在其 issues 中寻找一些解决问题的思路,或者google baidu puppeteer的解决方案,再应用到你的问题上。 -### 四、JDK21 的尝试 - -在 dev21分支 上使用了 jdk21 进行了部分代码修改,主要在 Connection 类上使用了虚拟线程处理消息,有兴趣可以自己打 JAR 包试试。 - -### 五、资源 +### 四、资源 1. [Puppeteer中文文档](https://pptr.nodejs.cn/) : 更加详细的 API 文档 ,多看看了解一下 2. [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) :CDP 协议 -3. [Chrome命令行启动参数](https://peter.sh/experiments/chromium-command-line-switches/) +3. [本项目的WIKI]([Home · fanyong920/jvppeteer Wiki](https://github.com/fanyong920/jvppeteer/wiki)) 目前写有一些内容,后续有想到新的,会持续更新 +4. [命令行启动参数大全](https://github.com/GoogleChrome/chrome-launcher/blob/d6be1f3250ef7ff7648ae58c4e92e48509bdbe7c/src/flags.ts#L67) -### 六、执照 +### 五、执照 此仓库中找到的所有内容均已获得 Apache 许可。有关详细信息,请参见`LICENSE`文件 diff --git a/api.md b/api.md deleted file mode 100644 index 3fd1036b..00000000 --- a/api.md +++ /dev/null @@ -1,3969 +0,0 @@ - -# Jvppeteer API Tip-Of-Tree - - - -##### 目录 - - - -- [前言](#前言) -- [环境变量](#环境变量) -- [与 Chrome 插件一起工作](#与 Chrome 插件一起工作) -- [class: Puppeteer](#class-puppeteer) - * [puppeteer.connect(options)](#puppeteerconnectoptions) - * [puppeteer.createBrowserFetcher([options])](#puppeteercreatebrowserfetcheroptions) - * [puppeteer.defaultArgs([options])](#puppeteerdefaultargsoptions) - * [puppeteer.devices](#puppeteerdevices) - * [puppeteer.errors](#puppeteererrors) - * [puppeteer.executablePath()](#puppeteerexecutablepath) - * [puppeteer.launch([options])](#puppeteerlaunchoptions) - * [puppeteer.product](#puppeteerproduct) -- [class: BrowserFetcher](#class-browserfetcher) - * [browserFetcher.canDownload(revision)](#browserfetchercandownloadrevision) - * [browserFetcher.download(revision[, progressCallback])](#browserfetcherdownloadrevision-progresscallback) - * [browserFetcher.host()](#browserfetcherhost) - * [browserFetcher.localRevisions()](#browserfetcherlocalrevisions) - * [browserFetcher.platform()](#browserfetcherplatform) - * [browserFetcher.product()](#browserfetcherproduct) - * [browserFetcher.remove(revision)](#browserfetcherremoverevision) - * [browserFetcher.revisionInfo(revision)](#browserfetcherrevisioninforevision) -- [class: Browser](#class-browser) - * [event: 'disconnected'](#event-disconnected) - * [event: 'targetchanged'](#event-targetchanged) - * [event: 'targetcreated'](#event-targetcreated) - * [event: 'targetdestroyed'](#event-targetdestroyed) - * [browser.browserContexts()](#browserbrowsercontexts) - * [browser.close()](#browserclose) - * [browser.createIncognitoBrowserContext()](#browsercreateincognitobrowsercontext) - * [browser.defaultBrowserContext()](#browserdefaultbrowsercontext) - * [browser.disconnect()](#browserdisconnect) - * [browser.isConnected()](#browserisconnected) - * [browser.newPage()](#browsernewpage) - * [browser.pages()](#browserpages) - * [browser.process()](#browserprocess) - * [browser.target()](#browsertarget) - * [browser.targets()](#browsertargets) - * [browser.userAgent()](#browseruseragent) - * [browser.version()](#browserversion) - * [browser.waitForTarget(predicate[, options])](#browserwaitfortargetpredicate-options) - * [browser.wsEndpoint()](#browserwsendpoint) -- [class: BrowserContext](#class-browsercontext) - * [event: 'targetchanged'](#event-targetchanged-1) - * [event: 'targetcreated'](#event-targetcreated-1) - * [event: 'targetdestroyed'](#event-targetdestroyed-1) - * [browserContext.browser()](#browsercontextbrowser) - * [browserContext.clearPermissionOverrides()](#browsercontextclearpermissionoverrides) - * [browserContext.close()](#browsercontextclose) - * [browserContext.isIncognito()](#browsercontextisincognito) - * [browserContext.newPage()](#browsercontextnewpage) - * [browserContext.overridePermissions(origin, permissions)](#browsercontextoverridepermissionsorigin-permissions) - * [browserContext.pages()](#browsercontextpages) - * [browserContext.targets()](#browsercontexttargets) - * [browserContext.waitForTarget(predicate[, options])](#browsercontextwaitfortargetpredicate-options) -- [class: Page](#class-page) - * [event: 'close'](#event-close) - * [event: 'console'](#event-console) - * [event: 'dialog'](#event-dialog) - * [event: 'domcontentloaded'](#event-domcontentloaded) - * [event: 'error'](#event-error) - * [event: 'frameattached'](#event-frameattached) - * [event: 'framedetached'](#event-framedetached) - * [event: 'framenavigated'](#event-framenavigated) - * [event: 'load'](#event-load) - * [event: 'metrics'](#event-metrics) - * [event: 'pageerror'](#event-pageerror) - * [event: 'popup'](#event-popup) - * [event: 'request'](#event-request) - * [event: 'requestfailed'](#event-requestfailed) - * [event: 'requestfinished'](#event-requestfinished) - * [event: 'response'](#event-response) - * [event: 'workercreated'](#event-workercreated) - * [event: 'workerdestroyed'](#event-workerdestroyed) - * [page.$(selector)](#pageselector) - * [page.$$(selector)](#pageselector-1) - * [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) - * [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1) - * [page.$x(expression)](#pagexexpression) - * [page.accessibility](#pageaccessibility) - * [page.addScriptTag(options)](#pageaddscripttagoptions) - * [page.addStyleTag(options)](#pageaddstyletagoptions) - * [page.authenticate(credentials)](#pageauthenticatecredentials) - * [page.bringToFront()](#pagebringtofront) - * [page.browser()](#pagebrowser) - * [page.browserContext()](#pagebrowsercontext) - * [page.click(selector[, options])](#pageclickselector-options) - * [page.close([options])](#pagecloseoptions) - * [page.content()](#pagecontent) - * [page.cookies([...urls])](#pagecookiesurls) - * [page.coverage](#pagecoverage) - * [page.deleteCookie(...cookies)](#pagedeletecookiecookies) - * [page.emulate(options)](#pageemulateoptions) - * [page.emulateMediaFeatures(features)](#pageemulatemediafeaturesfeatures) - * [page.emulateMediaType(type)](#pageemulatemediatypetype) - * [page.emulateTimezone(timezoneId)](#pageemulatetimezonetimezoneid) - * [page.emulateVisionDeficiency(type)](#pageemulatevisiondeficiencytype) - * [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args) - * [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args) - * [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args) - * [page.exposeFunction(name, puppeteerFunction)](#pageexposefunctionname-puppeteerfunction) - * [page.focus(selector)](#pagefocusselector) - * [page.frames()](#pageframes) - * [page.goBack([options])](#pagegobackoptions) - * [page.goForward([options])](#pagegoforwardoptions) - * [page.goto(url[, options])](#pagegotourl-options) - * [page.hover(selector)](#pagehoverselector) - * [page.isClosed()](#pageisclosed) - * [page.isJavaScriptEnabled()](#pageisjavascriptenabled) - * [page.keyboard](#pagekeyboard) - * [page.mainFrame()](#pagemainframe) - * [page.metrics()](#pagemetrics) - * [page.mouse](#pagemouse) - * [page.pdf([options])](#pagepdfoptions) - * [page.queryObjects(prototypeHandle)](#pagequeryobjectsprototypehandle) - * [page.reload([options])](#pagereloadoptions) - * [page.screenshot([options])](#pagescreenshotoptions) - * [page.select(selector, ...values)](#pageselectselector-values) - * [page.setBypassCSP(enabled)](#pagesetbypasscspenabled) - * [page.setCacheEnabled([enabled])](#pagesetcacheenabledenabled) - * [page.setContent(html[, options])](#pagesetcontenthtml-options) - * [page.setCookie(...cookies)](#pagesetcookiecookies) - * [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) - * [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) - * [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders) - * [page.setGeolocation(options)](#pagesetgeolocationoptions) - * [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled) - * [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled) - * [page.setRequestInterception(value)](#pagesetrequestinterceptionvalue) - * [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) - * [page.setViewport(viewport)](#pagesetviewportviewport) - * [page.tap(selector)](#pagetapselector) - * [page.target()](#pagetarget) - * [page.title()](#pagetitle) - * [page.touchscreen](#pagetouchscreen) - * [page.tracing](#pagetracing) - * [page.type(selector, text[, options])](#pagetypeselector-text-options) - * [page.url()](#pageurl) - * [page.viewport()](#pageviewport) - * [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args) - * [page.waitForFileChooser([options])](#pagewaitforfilechooseroptions) - * [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args) - * [page.waitForNavigation([options])](#pagewaitfornavigationoptions) - * [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options) - * [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options) - * [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) - * [page.waitForXPath(xpath[, options])](#pagewaitforxpathxpath-options) - * [page.workers()](#pageworkers) - * [GeolocationOptions](#geolocationoptions) - * [WaitTimeoutOptions](#waittimeoutoptions) -- [class: WebWorker](#class-webworker) - * [webWorker.evaluate(pageFunction[, ...args])](#webworkerevaluatepagefunction-args) - * [webWorker.evaluateHandle(pageFunction[, ...args])](#webworkerevaluatehandlepagefunction-args) - * [webWorker.executionContext()](#webworkerexecutioncontext) - * [webWorker.url()](#webworkerurl) -- [class: Accessibility](#class-accessibility) - * [accessibility.snapshot([options])](#accessibilitysnapshotoptions) -- [class: Keyboard](#class-keyboard) - * [keyboard.down(key[, options])](#keyboarddownkey-options) - * [keyboard.press(key[, options])](#keyboardpresskey-options) - * [keyboard.sendCharacter(char)](#keyboardsendcharacterchar) - * [keyboard.type(text[, options])](#keyboardtypetext-options) - * [keyboard.up(key)](#keyboardupkey) -- [class: Mouse](#class-mouse) - * [mouse.click(x, y[, options])](#mouseclickx-y-options) - * [mouse.down([options])](#mousedownoptions) - * [mouse.move(x, y[, options])](#mousemovex-y-options) - * [mouse.up([options])](#mouseupoptions) - * [mouse.wheel([options])](#mousewheeloptions) -- [class: Touchscreen](#class-touchscreen) - * [touchscreen.tap(x, y)](#touchscreentapx-y) -- [class: Tracing](#class-tracing) - * [tracing.start([options])](#tracingstartoptions) - * [tracing.stop()](#tracingstop) -- [class: FileChooser](#class-filechooser) - * [fileChooser.accept(filePaths)](#filechooseracceptfilepaths) - * [fileChooser.cancel()](#filechoosercancel) - * [fileChooser.isMultiple()](#filechooserismultiple) -- [class: Dialog](#class-dialog) - * [dialog.accept([promptText])](#dialogacceptprompttext) - * [dialog.defaultValue()](#dialogdefaultvalue) - * [dialog.dismiss()](#dialogdismiss) - * [dialog.message()](#dialogmessage) - * [dialog.type()](#dialogtype) -- [class: ConsoleMessage](#class-consolemessage) - * [consoleMessage.args()](#consolemessageargs) - * [consoleMessage.location()](#consolemessagelocation) - * [consoleMessage.text()](#consolemessagetext) - * [consoleMessage.type()](#consolemessagetype) -- [class: Frame](#class-frame) - * [frame.$(selector)](#frameselector) - * [frame.$$(selector)](#frameselector-1) - * [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args) - * [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1) - * [frame.$x(expression)](#framexexpression) - * [frame.addScriptTag(options)](#frameaddscripttagoptions) - * [frame.addStyleTag(options)](#frameaddstyletagoptions) - * [frame.childFrames()](#framechildframes) - * [frame.click(selector[, options])](#frameclickselector-options) - * [frame.content()](#framecontent) - * [frame.evaluate(pageFunction[, ...args])](#frameevaluatepagefunction-args) - * [frame.evaluateHandle(pageFunction[, ...args])](#frameevaluatehandlepagefunction-args) - * [frame.executionContext()](#frameexecutioncontext) - * [frame.focus(selector)](#framefocusselector) - * [frame.goto(url[, options])](#framegotourl-options) - * [frame.hover(selector)](#framehoverselector) - * [frame.isDetached()](#frameisdetached) - * [frame.name()](#framename) - * [frame.parentFrame()](#frameparentframe) - * [frame.select(selector, ...values)](#frameselectselector-values) - * [frame.setContent(html[, options])](#framesetcontenthtml-options) - * [frame.tap(selector)](#frametapselector) - * [frame.title()](#frametitle) - * [frame.type(selector, text[, options])](#frametypeselector-text-options) - * [frame.url()](#frameurl) - * [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args) - * [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args) - * [frame.waitForNavigation([options])](#framewaitfornavigationoptions) - * [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options) - * [frame.waitForXPath(xpath[, options])](#framewaitforxpathxpath-options) -- [class: ExecutionContext](#class-executioncontext) - * [executionContext.evaluate(pageFunction[, ...args])](#executioncontextevaluatepagefunction-args) - * [executionContext.evaluateHandle(pageFunction[, ...args])](#executioncontextevaluatehandlepagefunction-args) - * [executionContext.frame()](#executioncontextframe) - * [executionContext.queryObjects(prototypeHandle)](#executioncontextqueryobjectsprototypehandle) -- [class: JSHandle](#class-jshandle) - * [jsHandle.asElement()](#jshandleaselement) - * [jsHandle.dispose()](#jshandledispose) - * [jsHandle.evaluate(pageFunction[, ...args])](#jshandleevaluatepagefunction-args) - * [jsHandle.evaluateHandle(pageFunction[, ...args])](#jshandleevaluatehandlepagefunction-args) - * [jsHandle.executionContext()](#jshandleexecutioncontext) - * [jsHandle.getProperties()](#jshandlegetproperties) - * [jsHandle.getProperty(propertyName)](#jshandlegetpropertypropertyname) - * [jsHandle.jsonValue()](#jshandlejsonvalue) -- [class: ElementHandle](#class-elementhandle) - * [elementHandle.$(selector)](#elementhandleselector) - * [elementHandle.$$(selector)](#elementhandleselector-1) - * [elementHandle.$$eval(selector, pageFunction[, ...args])](#elementhandleevalselector-pagefunction-args) - * [elementHandle.$eval(selector, pageFunction[, ...args])](#elementhandleevalselector-pagefunction-args-1) - * [elementHandle.$x(expression)](#elementhandlexexpression) - * [elementHandle.asElement()](#elementhandleaselement) - * [elementHandle.boundingBox()](#elementhandleboundingbox) - * [elementHandle.boxModel()](#elementhandleboxmodel) - * [elementHandle.click([options])](#elementhandleclickoptions) - * [elementHandle.contentFrame()](#elementhandlecontentframe) - * [elementHandle.dispose()](#elementhandledispose) - * [elementHandle.evaluate(pageFunction[, ...args])](#elementhandleevaluatepagefunction-args) - * [elementHandle.evaluateHandle(pageFunction[, ...args])](#elementhandleevaluatehandlepagefunction-args) - * [elementHandle.executionContext()](#elementhandleexecutioncontext) - * [elementHandle.focus()](#elementhandlefocus) - * [elementHandle.getProperties()](#elementhandlegetproperties) - * [elementHandle.getProperty(propertyName)](#elementhandlegetpropertypropertyname) - * [elementHandle.hover()](#elementhandlehover) - * [elementHandle.isIntersectingViewport()](#elementhandleisintersectingviewport) - * [elementHandle.jsonValue()](#elementhandlejsonvalue) - * [elementHandle.press(key[, options])](#elementhandlepresskey-options) - * [elementHandle.screenshot([options])](#elementhandlescreenshotoptions) - * [elementHandle.select(...values)](#elementhandleselectvalues) - * [elementHandle.tap()](#elementhandletap) - * [elementHandle.toString()](#elementhandletostring) - * [elementHandle.type(text[, options])](#elementhandletypetext-options) - * [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths) -- [class: HTTPRequest](#class-httprequest) - * [httpRequest.abort([errorCode])](#httprequestaborterrorcode) - * [httpRequest.continue([overrides])](#httprequestcontinueoverrides) - * [httpRequest.failure()](#httprequestfailure) - * [httpRequest.frame()](#httprequestframe) - * [httpRequest.headers()](#httprequestheaders) - * [httpRequest.isNavigationRequest()](#httprequestisnavigationrequest) - * [httpRequest.method()](#httprequestmethod) - * [httpRequest.postData()](#httprequestpostdata) - * [httpRequest.redirectChain()](#httprequestredirectchain) - * [httpRequest.resourceType()](#httprequestresourcetype) - * [httpRequest.respond(response)](#httprequestrespondresponse) - * [httpRequest.response()](#httprequestresponse) - * [httpRequest.url()](#httprequesturl) -- [class: HTTPResponse](#class-httpresponse) - * [httpResponse.buffer()](#httpresponsebuffer) - * [httpResponse.frame()](#httpresponseframe) - * [httpResponse.fromCache()](#httpresponsefromcache) - * [httpResponse.fromServiceWorker()](#httpresponsefromserviceworker) - * [httpResponse.headers()](#httpresponseheaders) - * [httpResponse.json()](#httpresponsejson) - * [httpResponse.ok()](#httpresponseok) - * [httpResponse.remoteAddress()](#httpresponseremoteaddress) - * [httpResponse.request()](#httpresponserequest) - * [httpResponse.securityDetails()](#httpresponsesecuritydetails) - * [httpResponse.status()](#httpresponsestatus) - * [httpResponse.statusText()](#httpresponsestatustext) - * [httpResponse.text()](#httpresponsetext) - * [httpResponse.url()](#httpresponseurl) -- [class: SecurityDetails](#class-securitydetails) - * [securityDetails.issuer()](#securitydetailsissuer) - * [securityDetails.protocol()](#securitydetailsprotocol) - * [securityDetails.subjectAlternativeNames()](#securitydetailssubjectalternativenames) - * [securityDetails.subjectName()](#securitydetailssubjectname) - * [securityDetails.validFrom()](#securitydetailsvalidfrom) - * [securityDetails.validTo()](#securitydetailsvalidto) -- [class: Target](#class-target) - * [target.browser()](#targetbrowser) - * [target.browserContext()](#targetbrowsercontext) - * [target.createCDPSession()](#targetcreatecdpsession) - * [target.opener()](#targetopener) - * [target.page()](#targetpage) - * [target.type()](#targettype) - * [target.url()](#targeturl) - * [target.webWorker()](#targetworker) -- [class: CDPSession](#class-cdpsession) - * [cdpSession.detach()](#cdpsessiondetach) - * [cdpSession.send(method[, ...paramArgs])](#cdpsessionsendmethod-paramargs) -- [class: Coverage](#class-coverage) - * [coverage.startCSSCoverage([options])](#coveragestartcsscoverageoptions) - * [coverage.startJSCoverage([options])](#coveragestartjscoverageoptions) - * [coverage.stopCSSCoverage()](#coveragestopcsscoverage) - * [coverage.stopJSCoverage()](#coveragestopjscoverage) -- [class: TimeoutError](#class-timeouterror) -- [class: EventEmitter](#class-eventemitter) - * [eventEmitter.addListener(event, handler)](#eventemitteraddlistenerevent-handler) - * [eventEmitter.emit(event, [eventData])](#eventemitteremitevent-eventdata) - * [eventEmitter.listenerCount(event)](#eventemitterlistenercountevent) - * [eventEmitter.off(event, handler)](#eventemitteroffevent-handler) - * [eventEmitter.on(event, handler)](#eventemitteronevent-handler) - * [eventEmitter.once(event, handler)](#eventemitteronceevent-handler) - * [eventEmitter.removeAllListeners([event])](#eventemitterremovealllistenersevent) - * [eventEmitter.removeListener(event, handler)](#eventemitterremovelistenerevent-handler) - - -### 前言 - -Jvppeteer 提供 Java 的 API 通过 DevTools Protocol 去控制 Chromium or Chrome。 - -- [`Jvppeteer`](#class-jvppeteer) 通过 [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) 与浏览器讯。 -- [`Browser`](#class-browser) 拥有多个浏览器上下文的浏览器实例。 -- [`BrowserContext`](#class-browsercontext) 一个浏览器会话对应一个实例,能够拥有多个实例。 -- [`Page`](#class-page) 至少又一个框架:主框架. 其他的框架 可以通过 [iframe](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe) 或者 [frame](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/frame) 标签创建. -- [`Frame`](#class-frame) 至少拥有一个执行上下文 - 默认执行上下文 - 框架的 JavaScript 被执行的地方. 一个框架或许还有其他的执行上下文,这些其他的上下文是与浏览器插件相关联。 -- [`Worker`](#class-webWorker) 具有单个执行上下文,并便于与[WebWorkers](https://developer.mozilla.org/zh_CN/docs/Web/API/Web_Workers_API)交互. - -### 环境变量 - -Jvppeteer寻找某些环境变量来辅助其操作,通过System.setProperty()设置即可。 - -- `PUPPETEER_CHROMIUM_REVISION` - 当你下载多个版本时,指定一个版本进行启动 -- `PUPPETEER_EXECUTABLE_PATH` - 直接指定 Chrome 或者 Chromium 的启动路径。与 [puppeteer.launch([options])](#puppeteerlaunchoptions)的executablePath 参数功能一致。 -- jvppeteer_common_thread_number - Jvppeteer内置了一个线程池,主要用于用户监听事件分发,该参数可以自定义内置线程池线程数量,默认是服务器核数。 - - -### 与 Chrome 插件一起工作 - -Jvppeteer 可以用来测试 Chrome 插件。 - -> **注意** 目前 Chrome / Chromium 的插件只能在有头模式下工作. - -下面的代码展示了如何获取插件的背景页([background page](https://developer.chrome.com/extensions/background_pages)) -```java -BrowserFetcher.downloadIfNotExist(null); -ArrayList additionalArgs = new ArrayList<>(); -additionalArgs.add("--no-sandbox"); -additionalArgs.add("--disable-setuid-sandbox"); -//指定插件所在的文件夹,如果手上暂时没有插件,可以我个人的插件https://github.com/fanyong920/crawlItem.git 克隆下来即可 -String pathToExtension = "E:\\workspace\\crawlItem"; -additionalArgs.add("--disable-extensions-except="+pathToExtension); -additionalArgs.add("--load-extension="+pathToExtension); - -//插件的加载在有头模式下才能生效 -Browser browser = Puppeteer.launch(false); - -List targets = browser.targets(); -for (Target target : targets) { - if("background_page".equals(target.type())){ - System.out.println("目标名称=" + target.getTargetInfo().getTitle()); - Page backgroundPage = target.page(); - } -} -browser.close(); -``` - -> **注意** 尚无法测试插件的弹出窗口或内容脚本。 - -### class: Puppeteer - -Puppeteer 提供了启动 Chrome 的方法,下面是典型的启动 Chrome 的例子 - -```java - //自动下载,第一次下载后不会再下载 - BrowserFetcher.downloadIfNotExist(null); - Browser browser = Puppeteer.launch(false); - Page page = browser.newPage(); - page.goTo("https://www.baidu.com/?tn=98012088_10_dg&ch=3"); - // 做一些其他操作 - browser.close(); -``` - -#### puppeteer.connect(options) -- `options` <[BrowserOptions]> - - `ignoreHTTPSErrors` <[boolean]> 是否在导航期间忽略 HTTPS 错误. 默认是 `false`。 - - `defaultViewport` 为每个页面设置一个默认视口大小。默认是 800x600。如果为 `null` 的话就禁用视图口。 - - `width` <[number]> 页面宽度像素。 - - `height` <[number]> 页面高度像素。 - - `deviceScaleFactor` <[number]> 设置设备的缩放(可以认为是 dpr)。默认是 `1`。 - - `isMobile` <[boolean]> 是否在页面中设置了 `meta viewport` 标签。默认是 `false`。 - - `hasTouch`<[boolean]> 指定viewport是否支持触摸事件。默认是 `false`。 - - `isLandscape` <[boolean]> 指定视口是否处于横向模式。默认是 `false`。 - - `slowMo` <[number]> 将 Puppeteer 操作减少指定的毫秒数。这样你就可以看清发生了什么,这很有用。 -- `browserWSEndpoint` 一个 浏览器 websocket 端点链接 -- `browserURL` 连接浏览器的地址,格式 `http://${host}:${port}`. 通过这个地址从 [元数据端点](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target)获取到对应的 `browserWSEndpoint`,然后通过这个端点连接浏览器 -- `transport` <[ConnectionTransport]> 指定供Puppeteer使用的自定义传输对象,还处于实验性阶段 -- `product` <[string]> 目前只支持 `chrome`. -- returns: <[Browser]> - -此方法将 Puppeteer 添加到已有的 Chromium 实例 - -#### puppeteer.createBrowserFetcher([options]) -- `options` <[Object]> - - `host` <[string]> 要使用的下载主机. 默认是 `https://npm.taobao.org/mirrors`。 - - `path` <[string]> 下载文件夹的路径. 默认是 `/.local-chromium`, `` 是 项目根目录。 - - `platform` <"linux"|"mac"|"win32"|"win64">可能的值有: `mac`, `win32`, `win64`, `linux`。默认是当前平台。 - - `product` <"chrome"|"firefox"> [string] 运行的浏览器类型,目前只支持 chrome -- returns: <[BrowserFetcher]> - -#### puppeteer.defaultArgs([options]) -- `options` <[Object]> 设置浏览器可选项。有一下字段: - - `headless` <[boolean]> 是否在 [无头模式](https://developers.google.com/web/updates/2017/04/headless-chrome) 下运行浏览器。默认是 `true` 除非 `devtools` 选项是 `true`。 - - `args` <[Array]<[string]>> 传递给浏览器实例的其他参数。可以 [在这](http://peter.sh/experiments/chromium-command-line-switches/) 找到 Chromium 标志列表。 - - `userDataDir` <[string]> [用户数据目录](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md) 的路径 - - `devtools` <[boolean]> 是否为每个选项卡自动打开 DevTools 面板。如果这个选项是 `true` 的话, `headless` 选项将被设置为 `false`。 -- returns: <[Array]<[string]>> - -Chromium 启动时使用的默认参数。 - -#### puppeteer.executablePath() -- returns: <[string]> 运行 Chromium 的所在路径 - - -#### puppeteer.launch([options]) -- `options` <[Object]> 在浏览器上设置的一组可配置选项。 有以下字段: - - `product` <[string]> 可以选择 chrome 或者 firrfox,目前只支持 chrome - - `ignoreHTTPSErrors` <[boolean]>是否在导航期间忽略 HTTPS 错误. 默认是 `false`。 - - `headless` <[boolean]> 是否以 [无头模式](https://developers.google.com/web/updates/2017/04/headless-chrome) 运行浏览器。默认是 `true`,除非 `devtools` 选项是 `true`。 - - `executablePath` <[string]> 可运行 Chromium 或 Chrome 可执行文件的路径,而不是绑定的 Chromium。如果 `executablePath` 是一个相对路径,那么他相对于 当前项目根目录 解析。 - - `slowMo` <[int]> 将 Puppeteer 操作减少指定的毫秒数。这样你就可以看清发生了什么,这很有用。 - - `defaultViewport` 为每个页面设置一个默认视口大小。默认是 800x600。如果为 `null` 的话就禁用视图口。 - - `width` <[int]> 页面宽度像素。 - - `height` <[int]> 页面高度像素。 - - `deviceScaleFactor` <[Number]> 设置设备的缩放(可以认为是 dpr)。默认是 `1` - - `isMobile` <[boolean]> 是否在页面中设置了 `meta viewport` 标签。默认是 `false`。 - - `hasTouch`<[boolean]>指定viewport是否支持触摸事件。默认是 `false`。 - - `isLandscape` <[boolean]> 指定视口是否处于横向模式。默认是 `false`。 - - `args` <[Array]<[string]>> 传递给浏览器实例的其他参数。 这些参数可以参考 [这里](http://peter.sh/experiments/chromium-command-line-switches/)。 - - `ignoreAllDefaultArgs` <[boolean]> 如果是 `true`,那将不使用默认参数 - - `ignoreDefaultArgs` <[Array<[string]>]> 忽略指定的默认参数 - - `handleSIGINT` <[boolean]> Ctrl-C 关闭浏览器进程。默认是 `true`。 - - `handleSIGTERM` <[boolean]> 关闭 SIGTERM 上的浏览器进程。默认是 `true`。 - - `handleSIGHUP` <[boolean]> 关闭 SIGHUP 上的浏览器进程。默认是 `true`. - - `timeout` <[number]> 等待浏览器实例启动的最长时间(以毫秒为单位)。默认是 `30000` (30 秒). 通过 `0` 来禁用超时。 - - `dumpio` <[boolean]> 是否将浏览器进程标准输出和标准错误输入到 `System.out` 和 `System.err` 中。默认是 `false`。 - - `userDataDir` <[string]> [用户数据目录](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md) 路径。 - - `devtools` <[boolean]> 是否为每个选项卡自动打开DevTools面板。如果这个选项是 `true`,`headless` 选项将会设置成 `false`。 - - `pipe` <[boolean]> 通过管道而不是WebSocket连接到浏览器。默认是 `false`。目前 Java 暂时不支持管道连接浏览器 -- returns: <[Browser]> 浏览器实例 - - -你可以使用 `ignoreDefaultArgs` 过滤默认参数中的 `--enable-automation` : -```java -LaunchOptions launchOptions = new LaunchOptionsBuilder().withIgnoreDefaultArgs(Arrays.asList("--enable-automation")).withHeadless(false).build(); - Browser browser = Puppeteer.launch(launchOptions); -``` - -> **注意** Puppeteer也可以用于控制Chrome浏览器,但与捆绑的Chromium版本配合使用效果最好。 使用任何其他版本效果无法保证。 使用`executablePath`选项时要格外小心。 -> -> 看 [`文章1`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) 了解 Chromium 与 Chrome 的区别. [`文章2`](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) 了解在Linux使用 Jvppeteer 的一些不同. - -#### puppeteer.product -- returns: <[string]> 返回浏览器的名称(目前只支持“ chrome”) - - -### class: BrowserFetcher - -BrowserFetcher 可以用来下载和管理不同版本的 Chromium。 - -BrowserFetcher 操作一个修订版本字符串,修订版本字符串指定了一个 Chromium 的确定版本,例如 `"533271"`。修订版本字符串可以从 [https://npm.taobao.org/mirrors/chromium-browser-snapshots](https://npm.taobao.org/mirrors/chromium-browser-snapshots) 获取。 - -看下面这个例子,他将告诉你如何使用 BrowserFetcher 下载一个指定版本的 Chromium - -```java -//自动下载,第一次下载后不会再下载,下载到项目根目录下 - RevisionInfo revisionInfo = BrowserFetcher.downloadIfNotExist("533271"); - - //当项目根目录下只有一个版本的 Chromium 时,Puppeteer.launch()会自动寻找到该版本,当有多个版本时需要制定路径 - Puppeteer.launch(new LaunchOptionsBuilder().withExecutablePath(revisionInfo.getExecutablePath()).build()); -``` - -> **注意** BrowserFetcher 不适用于与共享下载目录的其他实例同时运行。 - -#### browserFetcher.canDownload(revision) -- `revision` <[string]> 修订版本号, 检查其可用性 -- `proxy` <[Proxy]> 代理 -- returns: <[Promise]<[boolean]>> 返回 `true` 如果该修订版本可以从主机下载 - -该方法将会发起一个 HEAD 请求来检查该修订版本是否有效。 - -#### browserFetcher.download(revision[, progressCallback]) -- `revision` <[string]> 下载的浏览器版本. -- `progressCallback` <[function]([number], [number])> 下载回调,用于显示下载进度 - - `downloadedBytes` <[number]> 已下载的大小,单位是M - - `totalBytes` <[number]> 下载总大小 ,单位是M -- returns: <[Promise]<[Object]>> 返回下载的版本信息 - - `revision` <[string]> 下载的 - - `folderPath` <[string]> 下载的 Chromium 被解压的文件夹 - - `executablePath` <[string]> 执行的路径 - - `url` <[string]> 下载的 url - - `local` <[boolean]> 是否存在本地磁盘 - -该方法将会发起一个 GET 请求来从主机下载该修订版本。 - -#### browserFetcher.host() -- returns: <[string]> 下载的网址 - -#### browserFetcher.localRevisions() -- returns: <[Promise]<[Array]<[string]>>> 返回项目根目录下存在的所有版本 - -#### browserFetcher.platform() -- returns: <[string]> 返回目前使用的平台 , `mac`, `linux`, `win32` or `win64` 之一. - -#### browserFetcher.product() -- returns: <[string]> 目前只支持 Chrome - -#### browserFetcher.remove(revision) -- `revision` <[string]> 删除项目根目录下指定版本的浏览器文件 - -#### browserFetcher.revisionInfo(revision) -- `revision` <[string]> 要获取的版本 -- returns: <[Object]> - - `revision` <[string]> 指定的版本浏览器 - - `folderPath` <[string]> 该版本浏览器所在的文件夹 - - `executablePath` <[string]> 该版本浏览器可执行的路径 - - `url` <[string]> 该版本浏览器的下载 url - - `local` <[boolean]> 是否存在本地磁盘 - - `product` <[string]> `chrome` or `firefox` 之一,目前只支持 `chrome` - -### class: Browser - -* 继承: [EventEmitter](#class-eventemitter) - -当用 Jvppeteer打开一个 Chromium 实例是会自动创建一个 Browser 对象, 可以通过 [`puppeteer.launch`](#puppeteerlaunchoptions) 或者 [`puppeteer.connect`](#puppeteerconnectoptions) 获取 Browser 对象 - -下面是用 [Browser](##class Browser) 创建 [Page](###class Page) 对象的例子: -```java - Browser browser = puppeteer.launch(); - Page page = browser.newPage(); - page.goTo('https://example.com'); - browser.close(); -``` - -下面是断开 [Browser](##class-browser) 和重新连接 [Browser](##class-browser) 的例子 : -```java - Browser browser = puppeteer.launch(); - //拿到对应Browser对象的 endpoint 以方便接下来能重连到 Chromium - String browserWSEndpoint = browser.wsEndpoint(); - // 断开连接 - browser.disconnect(); - - // 使用 endpoint 重新建立连接 - Browser browser2 = puppeteer.connect(browserWSEndpoint); - // 关闭 Chromium - browser2.close(); - -``` -#### event: 'disconnected' -断开浏览器连接的事件. 以下两种情况可能引发这个事件: -- 关闭浏览器或者浏览器崩溃 -- 触发 [`browser.disconnect`](#browserdisconnect) - -#### event: 'targetchanged' -当目标的 url 改变时被触发 - -> **注意** 这包括默认浏览器上下文中的目标更改。 - - -#### event: 'targetcreated' -当目标被创建时被触发,例如当通过 [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) 或 [`browser.newPage`](https://zhaoqize.github.io/puppeteer-api-zh_CN/#?product=Puppeteer&version=v10.2.0&show=api-browsernewpage) 打开一个新的页面。 - -> **注意** 这包括默认浏览器上下文中的目标销毁。 - -#### event: 'targetdestroyed' -当目标被销毁时被触发,例如当一个页面被关闭时。 - -> **注意** 这包括默认浏览器上下文中的目标销毁。 - -#### browser.browserContexts() -返回一个包含所有打开的浏览器上下文的数组。在新创建的浏览器中,这将返回 [BrowserContext](###class-browsercontext) 的单一实例。 - -#### browser.close() -关闭 Chromium 以及所有页面 (如果有). [Browser](##class-browser) 对象本身被认为是处理过的并不能再被使用。 - -#### browser.createIncognitoBrowserContext() -创建一个匿名的浏览器上下文。这将不会与其他浏览器上下文分享 cookies/cache - -```java - Browser browser = Puppeteer.launch(); - // 创建一个匿名的浏览器上下文 - BrowserContext context = browser.createIncognitoBrowserContext(); - // 在一个原生的上下文中创建一个新页面 - Page page = await context.newPage(); - // 做一些事情 - page.goTo('https://example.com'); -``` - -#### browser.defaultBrowserContext() -返回默认的浏览器上下文,默认的浏览器上下文不能被关闭 - -#### browser.disconnect() - -断开 Jvppeteer 和浏览器的连接,但 Chromium 进程仍然在运行。在调用 `disconnect` 之后,[Browser](##class-browser) 对象本身被认为是处理过的并不能再被使用。 - -#### browser.isConnected() - -- returns: <[boolean]> - -是否已经连接浏览器 - -#### browser.newPage() -在默认的浏览器上下文中打开一个新的页面 - -#### browser.pages() -- returns: 返回一个包含所有打开的页面的数组。页面不可见的,比如 `"background_page"` 将不会列在这。不过你可以通过 [target.page()](####target.page()) 找到它们。 - -返回一个浏览器中所有页面的数组。 在多个浏览器上下文的情况下, 该方法将返回一个包含所有浏览器上下文中所有页面的数组。 - -#### browser.process() - 产生浏览器的进程。如果浏览器实例是由 [`puppeteer.connect`](####puppeteer.connect(options)) 方法创建的则返回null。 - -#### browser.target() -返回浏览器相关的目标对象。 - -#### browser.targets() -浏览器内所有活动目标组成的数组。在多个浏览器上下文的情况下,该方法将返回一个包含所有浏览器上下文中的所有目标的数组。 - -#### browser.userAgent() -返回浏览器的 userAgent - -> **注意** 页面可以通过 [page.setUserAgent](#pagesetuseragentuseragent) 重新设置 userAgent - -#### browser.version() - 对于无头的 Chromium,这类似于 `HeadlessChrome/61.0.3153.0`. 对于非无头的Chromium, 这类似于 `Chrome/61.0.3153.0。` - -> **注意** browser.version() 的格式可能在未来版本的 Chromium 中发生变化。 - -#### browser.waitForTarget(predicate,options) -- `predicate` 每个目标要运行的函数 -- `timeout` <[number]> 最大等待毫秒时间. Pass `0` to disable the timeout 设置 0 可以无限等待. 默认等待30秒. - - 在所有的浏览器上下文中寻找目标 - -下面是一个通过`window.open`找到一个目标,这个目标对应一个打开的页面: -```java -page.evaluate(“() => window.open('https://www.example.com/')”); -Target newWindowTarget = browser.waitForTarget(“target => target.url() === 'https://www.example.com/'”); -``` - -#### browser.wsEndpoint() -返回浏览器 websocket 的地址 - -[puppeteer.connect](####puppeteer.connect(options)) 可以将浏览器 websocket 端作为一个参数。其格式为 `ws://${host}:${port}/devtools/browser/`。 - -你可以从 `http://${host}:${port}/json/version` 找到 `webSocketDebuggerUrl` 。了解更多有关 [devtools protocol](https://chromedevtools.github.io/devtools-protocol) 和 [browser endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target) 的信息 - -### class: BrowserContext - -* extends: [EventEmitter](#class-eventemitter) - -BrowserContexts provide a way to operate multiple independent browser sessions. When a browser is launched, it has -a single BrowserContext used by default. The method `browser.newPage()` creates a page in the default browser context. - -If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser -context. - -Puppeteer allows creation of "incognito" browser contexts with `browser.createIncognitoBrowserContext()` method. -"Incognito" browser contexts don't write any browsing data to disk. - -```js -// Create a new incognito browser context -const context = await browser.createIncognitoBrowserContext(); -// Create a new page inside context. -const page = await context.newPage(); -// ... do stuff with page ... -await page.goto('https://example.com'); -// Dispose context once it's no longer needed. -await context.close(); -``` - -#### event: 'targetchanged' -- <[Target]> - -Emitted when the url of a target inside the browser context changes. - -#### event: 'targetcreated' -- <[Target]> - -Emitted when a new target is created inside the browser context, for example when a new page is opened by [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or [`browserContext.newPage`](#browsercontextnewpage). - -#### event: 'targetdestroyed' -- <[Target]> - -Emitted when a target inside the browser context is destroyed, for example when a page is closed. - -#### browserContext.browser() -- returns: <[Browser]> - -The browser this browser context belongs to. - -#### browserContext.clearPermissionOverrides() -- returns: <[Promise]> - -Clears all permission overrides for the browser context. - -```js -const context = browser.defaultBrowserContext(); -context.overridePermissions('https://example.com', ['clipboard-read']); -// do stuff .. -context.clearPermissionOverrides(); -``` - -#### browserContext.close() -- returns: <[Promise]> - -Closes the browser context. All the targets that belong to the browser context -will be closed. - -> **NOTE** only incognito browser contexts can be closed. - -#### browserContext.isIncognito() -- returns: <[boolean]> - -Returns whether BrowserContext is incognito. -The default browser context is the only non-incognito browser context. - -> **NOTE** the default browser context cannot be closed. - -#### browserContext.newPage() -- returns: <[Promise]<[Page]>> - -Creates a new page in the browser context. - - -#### browserContext.overridePermissions(origin, permissions) -- `origin` <[string]> The [origin] to grant permissions to, e.g. "https://example.com". -- `permissions` <[Array]<[string]>> An array of permissions to grant. All permissions that are not listed here will be automatically denied. Permissions can be one of the following values: - - `'geolocation'` - - `'midi'` - - `'midi-sysex'` (system-exclusive midi) - - `'notifications'` - - `'push'` - - `'camera'` - - `'microphone'` - - `'background-sync'` - - `'ambient-light-sensor'` - - `'accelerometer'` - - `'gyroscope'` - - `'magnetometer'` - - `'accessibility-events'` - - `'clipboard-read'` - - `'clipboard-write'` - - `'payment-handler'` -- returns: <[Promise]> - - -```js -const context = browser.defaultBrowserContext(); -await context.overridePermissions('https://html5demos.com', ['geolocation']); -``` - - -#### browserContext.pages() -- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage). - -An array of all pages inside the browser context. - -#### browserContext.targets() -- returns: <[Array]<[Target]>> - -An array of all active targets inside the browser context. - -#### browserContext.waitForTarget(predicate[, options]) -- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target -- `options` <[Object]> - - `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds. -- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function. - -This searches for a target in this specific browser context. - -An example of finding a target for a page opened via `window.open`: -```js -await page.evaluate(() => window.open('https://www.example.com/')); -const newWindowTarget = await browserContext.waitForTarget(target => target.url() === 'https://www.example.com/'); -``` - -### class: Page - -* extends: [EventEmitter](#class-eventemitter) - -Page provides methods to interact with a single tab or [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium. One [Browser] instance might have multiple [Page] instances. - -This example creates a page, navigates it to a URL, and then saves a screenshot: -```js -const puppeteer = require('puppeteer'); - -(async () => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - await page.goto('https://example.com'); - await page.screenshot({path: 'screenshot.png'}); - await browser.close(); -})(); -``` - -The Page class emits various events (described below) which can be handled using -any of the [`EventEmitter`](#class-eventemitter) methods, such as `on`, `once` -or `off`. - -This example logs a message for a single page `load` event: -```js -page.once('load', () => console.log('Page loaded!')); -``` - -To unsubscribe from events use the `off` method: - -```js -function logRequest(interceptedRequest) { - console.log('A request was made:', interceptedRequest.url()); -} -page.on('request', logRequest); -// Sometime later... -page.off('request', logRequest); -``` - -#### event: 'close' - -Emitted when the page closes. - -#### event: 'console' -- <[ConsoleMessage]> - -Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also emitted if the page throws an error or a warning. - -The arguments passed into `console.log` appear as arguments on the event handler. - -An example of handling `console` event: -```js -page.on('console', msg => { - for (let i = 0; i < msg.args().length; ++i) - console.log(`${i}: ${msg.args()[i]}`); -}); -page.evaluate(() => console.log('hello', 5, {foo: 'bar'})); -``` - -#### event: 'dialog' -- <[Dialog]> - -Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Puppeteer can respond to the dialog via [Dialog]'s [accept](#dialogacceptprompttext) or [dismiss](#dialogdismiss) methods. - -#### event: 'domcontentloaded' - -Emitted when the JavaScript [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) event is dispatched. - -#### event: 'error' -- <[Error]> - -Emitted when the page crashes. - -> **NOTE** `error` event has a special meaning in Node, see [error events](https://nodejs.org/api/events.html#events_error_events) for details. - -#### event: 'frameattached' -- <[Frame]> - -Emitted when a frame is attached. - -#### event: 'framedetached' -- <[Frame]> - -Emitted when a frame is detached. - -#### event: 'framenavigated' -- <[Frame]> - -Emitted when a frame is navigated to a new url. - -#### event: 'load' - -Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/Web/Events/load) event is dispatched. - -#### event: 'metrics' -- <[Object]> - - `title` <[string]> The title passed to `console.timeStamp`. - - `metrics` <[Object]> Object containing metrics as key/value pairs. The values - of metrics are of <[number]> type. - -Emitted when the JavaScript code makes a call to `console.timeStamp`. For the list -of metrics see `page.metrics`. - -#### event: 'pageerror' -- <[Error]> The exception message - -Emitted when an uncaught exception happens within the page. - -#### event: 'popup' -- <[Page]> Page corresponding to "popup" window - -Emitted when the page opens a new tab or window. - -```js -const [popup] = await Promise.all([ - new Promise(resolve => page.once('popup', resolve)), - page.click('a[target=_blank]'), -]); -``` - -```js -const [popup] = await Promise.all([ - new Promise(resolve => page.once('popup', resolve)), - page.evaluate(() => window.open('https://example.com')), -]); -``` - -#### event: 'request' -- <[HTTPRequest]> - -Emitted when a page issues a request. The [request] object is read-only. -In order to intercept and mutate requests, see `page.setRequestInterception`. - -#### event: 'requestfailed' -- <[HTTPRequest]> - -Emitted when a request fails, for example by timing out. - -> **NOTE** HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete with [`'requestfinished'`](#event-requestfinished) event and not with [`'requestfailed'`](#event-requestfailed). - -#### event: 'requestfinished' -- <[HTTPRequest]> - -Emitted when a request finishes successfully. - -#### event: 'response' -- <[HTTPResponse]> - -Emitted when a [response] is received. - -#### event: 'workercreated' -- <[Worker]> - -Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is spawned by the page. - -#### event: 'workerdestroyed' -- <[Worker]> - -Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is terminated. - -#### page.$(selector) -- `selector` <[string]> A [selector] to query page for -- returns: <[Promise]> - -The method runs `document.querySelector` within the page. If no element matches the selector, the return value resolves to `null`. - -Shortcut for [page.mainFrame().$(selector)](#frameselector). - -#### page.$$(selector) -- `selector` <[string]> A [selector] to query page for -- returns: <[Promise]<[Array]<[ElementHandle]>>> - -The method runs `document.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`. - -Shortcut for [page.mainFrame().$$(selector)](#frameselector-1). - -#### page.$$eval(selector, pageFunction[, ...args]) -- `selector` <[string]> A [selector] to query page for -- `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context -- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` -- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - -This method runs `Array.from(document.querySelectorAll(selector))` within the page and passes it as the first argument to `pageFunction`. - -If `pageFunction` returns a [Promise], then `page.$$eval` would wait for the promise to resolve and return its value. - -Examples: -```js -const divCount = await page.$$eval('div', divs => divs.length); -``` - -```js -const options = await page.$$eval('div > span.options', options => options.map(option => option.textContent)); -``` - -#### page.$eval(selector, pageFunction[, ...args]) -- `selector` <[string]> A [selector] to query page for -- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context -- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` -- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - -This method runs `document.querySelector` within the page and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error. - -If `pageFunction` returns a [Promise], then `page.$eval` would wait for the promise to resolve and return its value. - -Examples: -```js -const searchValue = await page.$eval('#search', el => el.value); -const preloadHref = await page.$eval('link[rel=preload]', el => el.href); -const html = await page.$eval('.main-container', e => e.outerHTML); -``` - -Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args). - -#### page.$x(expression) -- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate). -- returns: <[Promise]<[Array]<[ElementHandle]>>> - -The method evaluates the XPath expression. - -Shortcut for [page.mainFrame().$x(expression)](#framexexpression) - -#### page.accessibility -- returns: <[Accessibility]> - -#### page.addScriptTag(options) -- `options` <[Object]> - - `url` <[string]> URL of a script to be added. - - `path` <[string]> Path to the JavaScript file to be injected into frame. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). - - `content` <[string]> Raw JavaScript content to be injected into frame. - - `type` <[string]> Script type. Use 'module' in order to load a Javascript ES6 module. See [script](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) for more details. -- returns: <[Promise]<[ElementHandle]>> which resolves to the added tag when the script's onload fires or when the script content was injected into frame. - -Adds a `\n" + + "\n" + + "\n" + + "

欢迎来到示例页面

\n" + + "\n" + + "\n"; + page.setContent(content); + //触发 PageEvents.Error 和 PageError +// try { +// if(version.contains("firefox")){ +// page.goTo("about:crashcontent"); +// }else { +// page.goTo("chrome://crash"); +// } +// } catch (ExecutionException | InterruptedException e) { + +// } + //触发 PageEvents.Popup + page.evaluate("window.open('about:blank')"); + //触发 PageEvents.Console + page.evaluate("console.log('hello')"); //触发 page close事件 page.close(); //等待5s看看效果 Thread.sleep(5000); + } } @Test public void test4() throws Exception { //启动浏览器 - try (Browser browser = Puppeteer.launch(launchOptions)) { - //打开一个页面 - Page page = browser.newPage(); - page.on(Page.PageEvent.Popup, (Consumer) page1 -> System.out.println("popup:" + page1.url())); - page.on(Page.PageEvent.WorkerCreated, (Consumer) worker -> System.out.println("workercreate:" + worker.url())); - page.on(Page.PageEvent.WorkerDestroyed, (Consumer) worker -> System.out.println("workerdestroy:" + worker.url())); - page.goTo("https://www.baidu.com/?tn=68018901_16_pg", false); - //等待hao123按钮出现 - page.waitForSelector("#s-top-left > a:nth-child(2)"); - //点击hao123按钮,打开一个新页面 - page.click("#s-top-left > a:nth-child(2)"); + Browser browser = Puppeteer.launch(launchOptions); + //打开一个页面 + Page page = browser.newPage(); + System.out.println(browser.version()); + page.on(PageEvents.Popup, (Consumer) page1 -> System.out.println("popup:" + page1.url())); + page.once(PageEvents.WorkerCreated, (Consumer) worker -> System.out.println("workerCreate:" + worker.url())); + page.once(PageEvents.WorkerDestroyed, (Consumer) worker -> System.out.println("workerDestroy:" + worker.url())); - //等待5s看看效果 - Thread.sleep(5000); - //关闭浏览器 + page.goTo("https://www.baidu.com/"); + WaitForSelectorOptions waitForSelectorOptions = new WaitForSelectorOptions(); + waitForSelectorOptions.setTimeout(5000); + ElementHandle elementHandle = page.waitForSelector("#su", waitForSelectorOptions); + if (Objects.nonNull(elementHandle)) { + System.out.println("wait for selector: " + elementHandle); } + //等待hao123按钮出现 + //点击hao123按钮,打开一个新页面 + page.click("#s-top-left > a:nth-child(3)"); + page.setContent("\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n" + + ""); + //等待5s看看效果 + Thread.sleep(5000); + //关闭浏览器 + browser.close(); } } diff --git a/example/src/main/java/com/ruiyun/example/F_PageViewPortTest.java b/example/src/main/java/com/ruiyun/example/F_PageViewPortTest.java index 3bc754d9..99319185 100644 --- a/example/src/main/java/com/ruiyun/example/F_PageViewPortTest.java +++ b/example/src/main/java/com/ruiyun/example/F_PageViewPortTest.java @@ -1,14 +1,14 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.entities.Device; -import com.ruiyun.jvppeteer.entities.Viewport; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.BoundingBox; +import com.ruiyun.jvppeteer.cdp.entities.Device; +import com.ruiyun.jvppeteer.cdp.entities.Viewport; import org.junit.Test; -import java.util.LinkedHashMap; - public class F_PageViewPortTest extends A_LaunchTest { @Test @@ -18,8 +18,9 @@ public void test2() throws Exception { //打开一个页面 Page page = browser.newPage(); page.goTo("https://www.baidu.com"); + Thread.sleep(2000); //修改页面大小 - page.setViewport(new Viewport(2000, 1800)); + page.setViewport(new Viewport(1200, 900)); //等待5s看看效果 Thread.sleep(5000); //关闭浏览器 @@ -33,11 +34,11 @@ public void test3() throws Exception { //打开一个页面 Page page = browser.newPage(); - LinkedHashMap scrollDimensions = (LinkedHashMap) page.mainFrame().isolatedRealm().evaluate("() => {\n" + " const element = document.documentElement;\n" + " return {\n" + " width: element.scrollWidth,\n" + " height: element.scrollHeight,\n" + " };\n" + " }"); - + Object response = page.mainFrame().isolatedRealm().evaluate("() => {\n" + " const element = document.documentElement;\n" + " return {\n" + " width: element.scrollWidth,\n" + " height: element.scrollHeight,\n" + " };\n" + " }"); + BoundingBox scrollDimensions = Constant.OBJECTMAPPER.convertValue(response, BoundingBox.class); Viewport viewport = new Viewport(); - viewport.setWidth(scrollDimensions.get("width")); - viewport.setHeight(scrollDimensions.get("height")); + viewport.setWidth((int) scrollDimensions.getWidth()); + viewport.setHeight((int) scrollDimensions.getHeight()); page.setViewport(viewport); page.goTo("https://www.baidu.com"); browser.close(); diff --git a/example/src/main/java/com/ruiyun/example/G_PageContentTest.java b/example/src/main/java/com/ruiyun/example/G_PageContentTest.java index 397f82c7..78804327 100644 --- a/example/src/main/java/com/ruiyun/example/G_PageContentTest.java +++ b/example/src/main/java/com/ruiyun/example/G_PageContentTest.java @@ -1,14 +1,17 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Dialog; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.entities.ConsoleMessage; -import com.ruiyun.jvppeteer.entities.PageMetrics; -import org.junit.Test; - +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Dialog; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.PageEvents; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessage; +import com.ruiyun.jvppeteer.cdp.entities.PageMetrics; +import com.ruiyun.jvppeteer.cdp.entities.WaitForOptions; +import java.util.Arrays; import java.util.function.Consumer; +import org.junit.Test; public class G_PageContentTest extends A_LaunchTest { @@ -21,8 +24,13 @@ public void test2() throws Exception { //点击确认框的确定按钮 // dialog.accept("确定"); //解除对话框,就是关闭对话框 - page.on(Page.PageEvent.Dialog, (Consumer) Dialog::dismiss); - page.on(Page.PageEvent.Console, (Consumer) consoleMessage -> System.out.println("console=" + consoleMessage.text())); + page.on(PageEvents.Dialog, (Consumer) dialog -> { + System.out.println("接收到一个弹窗,现在将它关闭:"); + dialog.dismiss(); + }); + page.on(PageEvents.Console, (Consumer) consoleMessage -> System.out.println("console=" + consoleMessage.text())); + WaitForOptions options = new WaitForOptions(); + options.setWaitUntil(Arrays.asList(PuppeteerLifeCycle.domcontentloaded, PuppeteerLifeCycle.networkIdle)); //手动设置页面内容 page.setContent("\n" + "\n" + @@ -46,7 +54,9 @@ public void test2() throws Exception { "\n" + "

欢迎来到示例页面

\n" + "\n" + - "\n"); + "\n", options); + //这里睡眠几秒,可以确保接受到弹窗和console.log事件,因为是事件是通过其他线程响应的,立刻执行browser.close,不确定关闭之前能接收到, + Thread.sleep(5000); } } @@ -56,7 +66,8 @@ public void test3() throws Exception { try (Browser browser = Puppeteer.launch(launchOptions)) { //打开一个页面 Page page = browser.newPage(); - page.on(Page.PageEvent.Metrics, (Consumer) System.out::println); + //webdriver不支持 PageEvents.Metrics事件和方法 + page.on(PageEvents.Metrics, (Consumer) System.out::println); //手动设置页面内容 String content = "\n" + "\n" + diff --git a/example/src/main/java/com/ruiyun/example/H_PageEvaluteTest.java b/example/src/main/java/com/ruiyun/example/H_PageEvaluteTest.java index 76569d4b..4f7a0642 100644 --- a/example/src/main/java/com/ruiyun/example/H_PageEvaluteTest.java +++ b/example/src/main/java/com/ruiyun/example/H_PageEvaluteTest.java @@ -1,12 +1,14 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.entities.ConsoleMessage; -import org.junit.Test; - +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.PageEvents; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessage; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.common.PrimitiveValue; import java.util.function.Consumer; +import org.junit.Test; public class H_PageEvaluteTest extends A_LaunchTest { @Test @@ -16,9 +18,36 @@ public void test2() throws Exception { //打开一个页面 Page page = browser.newPage(); //监听devtool控制台输出 - page.on(Page.PageEvent.Console, (Consumer) consoleMessage -> System.out.println("console=" + consoleMessage.text())); + page.on(PageEvents.Console, (Consumer) consoleMessage -> System.out.println("console=" + consoleMessage.text())); + //在devtool 控制台打印输出 + page.evaluate("console.log('hello', 5, {foo: 'bar'})"); + //等待5s看看效果 + Thread.sleep(5000); + } + } + + /** + * 传递特属值 + * @throws Exception + */ + @Test + public void test3() throws Exception { + //启动浏览器 + try (Browser browser = Puppeteer.launch(launchOptions)) { + //打开一个页面 + Page page = browser.newPage(); + //监听devtool控制台输出 + page.on(PageEvents.Console, (Consumer) consoleMessage -> System.out.println("console=" + consoleMessage.text())); //在devtool 控制台打印输出 - page.evaluate("() => console.log('hello', 5, {foo: 'bar'})"); + page.evaluate("async (arg1,arg2,arg3,arg4,arg5) =>{\n" + + " console.log(null == undefined)\n" + + " console.log(-0 == -0.0)\n" + + " console.log(arg1 == null)\n" + + " console.log(arg2 == undefined)\n" + + " console.log(arg3 == -0.0)\n" + + " console.log(arg4 == Infinity)\n" + + " console.log(arg5 == -Infinity)\n" + + "}", PrimitiveValue.Null, PrimitiveValue.Undefined,Constant.Navigate_Zero,Constant.Infinity,Constant.Navigate_Infinity); //等待5s看看效果 Thread.sleep(5000); } diff --git a/example/src/main/java/com/ruiyun/example/I_PageMouseTest.java b/example/src/main/java/com/ruiyun/example/I_PageMouseTest.java index 54921039..d27d954a 100644 --- a/example/src/main/java/com/ruiyun/example/I_PageMouseTest.java +++ b/example/src/main/java/com/ruiyun/example/I_PageMouseTest.java @@ -1,18 +1,31 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.entities.MouseWheelOptions; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.PageEvents; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.MouseWheelOptions; +import com.ruiyun.jvppeteer.util.Helper; +import java.util.Collections; import org.junit.Test; public class I_PageMouseTest extends A_LaunchTest { @Test public void test2() throws Exception { - //启动浏览器 + //隐身模式启动浏览器 + launchOptions.setArgs(Collections.singletonList("-private")); try (Browser browser = Puppeteer.launch(launchOptions)) { //打开一个页面 + Page page = browser.newPage(); + page.on(PageEvents.PageError, e -> { + System.out.println("page error:" + e); + //火狐浏览器在https://pptr.nodejs.cn/api/puppeteer.pageevent页面加载发生错误,刷新页面 + page.reload(); + }); + page.on(PageEvents.Error, e -> { + System.out.println("error:" + e); + }); page.goTo("https://pptr.nodejs.cn/api/puppeteer.pageevent"); //点击搜索框 page.click("#__docusaurus > nav > div.navbar__inner > div.navbar__items.navbar__items--right > div.navbarSearchContainer_IP3a > button"); @@ -31,14 +44,19 @@ public void test3() throws Exception { try (Browser browser = Puppeteer.launch(launchOptions)) { //打开一个页面 Page page = browser.newPage(); + page.on(PageEvents.PageError, e -> { + System.out.println("page error:" + e); + //页面加载发生错误,刷新页面 + page.reload(); + }); page.goTo("https://pptr.nodejs.cn/api/puppeteer.pageevent"); MouseWheelOptions options = new MouseWheelOptions(); -// options.setDeltaX(100); - options.setDeltaY(2000); - //鼠标滚轮事件 - page.mouse().wheel(options); - //等待5s看看效果 - Thread.sleep(5000); + for (int i = 0; i < 5; i++) { + Helper.justWait(1000); + options.setDeltaY(200); + //鼠标滚轮事件 + page.mouse().wheel(options); + } } } } diff --git a/example/src/main/java/com/ruiyun/example/J_RequestTest.java b/example/src/main/java/com/ruiyun/example/J_RequestTest.java index 88f2c4f0..6436f578 100644 --- a/example/src/main/java/com/ruiyun/example/J_RequestTest.java +++ b/example/src/main/java/com/ruiyun/example/J_RequestTest.java @@ -1,23 +1,23 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Request; -import com.ruiyun.jvppeteer.core.Response; -import com.ruiyun.jvppeteer.entities.ContinueRequestOverrides; -import com.ruiyun.jvppeteer.entities.GoToOptions; -import com.ruiyun.jvppeteer.entities.MouseWheelOptions; -import com.ruiyun.jvppeteer.entities.PuppeteerLifeCycle; -import com.ruiyun.jvppeteer.entities.ResourceType; -import com.ruiyun.jvppeteer.entities.ResponseForRequest; -import com.ruiyun.jvppeteer.util.StringUtil; -import org.junit.Test; - +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.core.Request; +import com.ruiyun.jvppeteer.api.core.Response; +import com.ruiyun.jvppeteer.api.events.PageEvents; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; +import com.ruiyun.jvppeteer.cdp.core.CdpRequest; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.ContinueRequestOverrides; +import com.ruiyun.jvppeteer.cdp.entities.GoToOptions; +import com.ruiyun.jvppeteer.cdp.entities.HeaderEntry; +import com.ruiyun.jvppeteer.cdp.entities.ResourceTiming; +import com.ruiyun.jvppeteer.cdp.entities.ResourceType; +import com.ruiyun.jvppeteer.cdp.entities.ResponseForRequest; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.function.Consumer; +import org.junit.Test; public class J_RequestTest extends A_LaunchTest { @Test @@ -26,32 +26,36 @@ public void test2() throws Exception { try (Browser browser = Puppeteer.launch(launchOptions)) { //打开一个页面 Page page = browser.newPage(); - page.on(Page.PageEvent.Request, (Consumer) request -> { - boolean hasPostData = request.hasPostData(); - if (hasPostData) { - String postData = request.postData(); - if (StringUtil.isEmpty(postData)) { - postData = request.fetchPostData(); - } - System.out.println("请求体:" + postData); - } + String version = browser.version(); + page.on(PageEvents.Request, (Consumer) request -> { + System.out.println("header:"+request.headers()); } ); - page.on(Page.PageEvent.Response, (Consumer) response -> { - List redirectChain = response.request().redirectChain(); - if (!redirectChain.isEmpty()) {//不为空,说明response对应的request已经重定向,该集合记录的是重定向链 - for (Request request : redirectChain) { - System.out.println("url: " + request.url() + ", id: " + request.id()); - try { - Response response1 = request.response(); - System.out.println("response1: " + new String(response1.content())); - } catch (Exception e) { - System.out.println("request(id:" + request.id() + ")重定向,不能获取response.content()"); + page.on(PageEvents.Response, (Consumer) response -> { + + //cdp + if(!version.contains("firefox")){ + List redirectChain = response.request().redirectChain(); + if (!redirectChain.isEmpty()) {//不为空,说明response对应的request已经重定向,该集合记录的是重定向链 + for (Request request : redirectChain) { + System.out.println("url: " + request.url() + ", id: " + request.id()); + try { + Response response1 = request.response(); + System.out.println("response1: " + new String(response1.content())); + } catch (Exception e) { + System.out.println("request(id:" + request.id() + ")重定向,不能获取response.content()"); + } } + } else { + System.out.println("response2: " + new String(response.content())); } }else { - System.out.println("response2: " + new String(response.content())); + ResourceTiming timing = response.timing(); + System.out.println("timing: " + timing); } + + + //bidi }); //这个页面有重定向请求,你可以按F12打开devtool开发者工具,自己刷新页面看网络请求 page.goTo("https://cn.bing.com/search?q=%E5%9C%A8%E7%BA%BF%E8%A7%A3%E7%A0%81&qs=n&form=QBRE&sp=-1&lq=0&pq=%E5%9C%A8%E7%BA%BF%E8%A7%A3%E7%A0%81&sc=10-4&sk=&cvid=BD22167E4CA44C1482F65B007AE9B7CA&ghsh=0&ghacc=0&ghpl="); @@ -70,33 +74,55 @@ public void test4() throws Exception { try (Browser browser = Puppeteer.launch(launchOptions)) { //打开一个页面 Page page = browser.newPage(); + String version = browser.version(); //拦截请求开关 page.setRequestInterception(true); - page.on(Page.PageEvent.Request, (Consumer) request -> { - if (request.resourceType().equals(ResourceType.Image)) {//拦截图片 - request.abort(); - } else if (request.url().startsWith("https://www.baidu.com")) { - //自定义请求的response - ResponseForRequest responseForRequest = new ResponseForRequest(); - //responseForRequest.setBody("Not Found!"); - responseForRequest.setBody("百度一下,你就知道"); - responseForRequest.setStatus(404); - responseForRequest.setContentType("text/plain; charset=utf-8"); - request.continueRequest(); - } else { - //修改请求头 - Map headers = request.headers(); - headers.put("foo", "bar"); - ContinueRequestOverrides overrides = new ContinueRequestOverrides(); - overrides.setHeaders(headers); - request.continueRequest(overrides); + page.on(PageEvents.Request, (Consumer) request -> { + if(version.contains("firefox")){ + if (request.url().startsWith("https://www.baidu.com")) { + //自定义请求的response + ResponseForRequest responseForRequest = new ResponseForRequest(); + //responseForRequest.setBody("Not Found!"); + responseForRequest.setBody("百度一下,你就知道"); + responseForRequest.setStatus(404); + responseForRequest.setContentType("text/plain; charset=utf-8"); + request.respond(responseForRequest); + } else { + //修改请求头 + List headers = request.headers(); + headers.add(new HeaderEntry("foo", "bar")); + ContinueRequestOverrides overrides = new ContinueRequestOverrides(); + overrides.setHeaders(headers); + request.continueRequest(overrides); + } + }else { + if (request.resourceType().equals(ResourceType.Image)) {//拦截图片 + request.abort(); + } else if (request.url().startsWith("https://www.baidu.com")) { + //自定义请求的response + ResponseForRequest responseForRequest = new ResponseForRequest(); + //responseForRequest.setBody("Not Found!"); + responseForRequest.setBody("百度一下,你就知道"); + responseForRequest.setStatus(404); + responseForRequest.setContentType("text/plain; charset=utf-8"); + responseForRequest.setHeaders(request.headers()); + request.respond(responseForRequest); + } else { + //修改请求头 + List headers = request.headers(); + headers.add(new HeaderEntry("foo", "bar")); + ContinueRequestOverrides overrides = new ContinueRequestOverrides(); + overrides.setHeaders(headers); + request.continueRequest(overrides); + } } + }); GoToOptions options = new GoToOptions(); //如果不设置 domcontentloaded 算页面导航完成的话,那么goTo方法会超时,因为图片请求被拦截了,页面不会达到loaded阶段 - options.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.DOMCONTENT_LOADED)); + options.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.domcontentloaded)); page.goTo("https://www.baidu.com/", options); - Thread.sleep(2000); + Thread.sleep(5000); } } @@ -107,12 +133,12 @@ public void test4() throws Exception { public void test5() throws Exception { //打开开发者工具 launchOptions.setDevtools(true); - try (Browser browser = Puppeteer.launch(launchOptions)) { + try (Browser cdpBrowser = Puppeteer.launch(launchOptions)) { //打开一个页面 - Page page = browser.newPage(); + Page page = cdpBrowser.newPage(); //拦截请求开关 page.setRequestInterception(true); - page.on(Page.PageEvent.Request, (Consumer) request -> { + page.on(PageEvents.Request, (Consumer) request -> { if (request.resourceType().equals(ResourceType.Image)) {//拦截图片 request.abort(); } else if (request.url().startsWith("https://www.baidu.com")) { @@ -122,19 +148,19 @@ public void test5() throws Exception { responseForRequest.setBody("百度一下,你就知道"); responseForRequest.setStatus(404); responseForRequest.setContentType("text/plain; charset=utf-8"); - request.respond(responseForRequest,1); + request.respond(responseForRequest, 1); } else { //修改请求头 - Map headers = request.headers(); - headers.put("foo", "bar"); + List headers = request.headers(); + headers.add(new HeaderEntry("foo", "bar")); ContinueRequestOverrides overrides = new ContinueRequestOverrides(); overrides.setHeaders(headers); - request.continueRequest(overrides,2); + request.continueRequest(overrides, 2); } }); GoToOptions options = new GoToOptions(); //如果不设置 domcontentloaded 算页面导航完成的话,那么goTo方法会超时,因为图片请求被拦截了,页面不会达到loaded阶段 - options.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.DOMCONTENT_LOADED)); + options.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.domcontentloaded)); page.goTo("https://www.baidu.com/", options); Thread.sleep(5000); } diff --git a/example/src/main/java/com/ruiyun/example/K_PDFTest.java b/example/src/main/java/com/ruiyun/example/K_PDFTest.java index a168c452..6163ece5 100644 --- a/example/src/main/java/com/ruiyun/example/K_PDFTest.java +++ b/example/src/main/java/com/ruiyun/example/K_PDFTest.java @@ -1,17 +1,16 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.entities.GoToOptions; -import com.ruiyun.jvppeteer.entities.PDFMargin; -import com.ruiyun.jvppeteer.entities.PDFOptions; -import com.ruiyun.jvppeteer.entities.PaperFormats; -import com.ruiyun.jvppeteer.entities.PuppeteerLifeCycle; -import org.junit.Test; - +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.GoToOptions; +import com.ruiyun.jvppeteer.cdp.entities.PDFMargin; +import com.ruiyun.jvppeteer.cdp.entities.PDFOptions; +import com.ruiyun.jvppeteer.cdp.entities.PaperFormats; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; import java.util.ArrayList; import java.util.Collections; +import org.junit.Test; public class K_PDFTest extends A_LaunchTest { /** @@ -21,14 +20,13 @@ public class K_PDFTest extends A_LaunchTest { @Test public void test2() throws Exception { setPdfOptions(); - //launchOptions.setHeadlessShell(true);//chrome-headless-shell浏览器才能用这个参数,这个是旧的headless模式 - try (Browser browser = Puppeteer.launch(launchOptions)) { - Page page = browser.newPage(); + try (Browser cdpBrowser = Puppeteer.launch(launchOptions)) { + Page page = cdpBrowser.newPage(); GoToOptions goToOptions = new GoToOptions(); - goToOptions.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.NETWORKIDLE)); + goToOptions.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.networkIdle)); page.goTo("https://www.baidu.com/?tn=68018901_16_pg", goToOptions); PDFOptions pdfOptions = new PDFOptions(); - pdfOptions.setPath("baidu.pdf"); + pdfOptions.setPath("baidu1.pdf"); pdfOptions.setOutline(true);//生成大纲 pdfOptions.setFormat(PaperFormats.a4);//A4大小 pdfOptions.setPrintBackground(true);//打印背景图形,百度一下这个蓝色按钮就显示出来了 @@ -49,18 +47,17 @@ private void setPdfOptions() { @Test public void test3() throws Exception { setPdfOptions(); - //launchOptions.setHeadlessShell(true);//chrome-headless-shell浏览器才能用这个参数,这个是旧的headless模式 try (Browser browser = Puppeteer.launch(launchOptions)) { Page page = browser.newPage(); GoToOptions goToOptions = new GoToOptions(); //这个网页要设置页面加载到NETWORKIDLE状态才能打印,不然打印空白 - goToOptions.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.NETWORKIDLE)); + goToOptions.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.networkIdle)); page.goTo("https://ysotmain.ysclass.net/zhxy/#/redirect/login", goToOptions); PDFOptions pdfOptions = new PDFOptions(); pdfOptions.setPath("login.pdf"); pdfOptions.setOutline(true);//生成大纲 pdfOptions.setPrintBackground(true);//打印背景图形 - pdfOptions.setPreferCSSPageSize(false); + pdfOptions.setPreferCSSPageSize(true); PDFMargin pdfMargin = new PDFMargin(); pdfMargin.setTop("1cm");//px,in,cm,mm等单位都可以,会自动转换 pdfMargin.setBottom("1cm"); @@ -75,12 +72,11 @@ public void test3() throws Exception { @Test public void test4() throws Exception { setPdfOptions(); - //launchOptions.setHeadlessShell(true);//chrome-headless-shell浏览器才能用这个参数,这个是旧的headless模式 - try (Browser browser = Puppeteer.launch(launchOptions)) { - Page page = browser.newPage(); + try (Browser cdpBrowser = Puppeteer.launch(launchOptions)) { + Page page = cdpBrowser.newPage(); GoToOptions goToOptions = new GoToOptions(); //这个网页要设置页面加载到NETWORKIDLE状态才能打印,不然打印空白 - goToOptions.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.NETWORKIDLE)); + goToOptions.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.networkIdle)); page.goTo("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=68018901_16_pg&wd=github%20issue%E6%8F%92%E5%85%A5%E4%BB%A3%E7%A0%81&fenlei=256&rsv_pq=0xbd5e4b7d00111880&rsv_t=6f17bbxW8QaU8EIJyxwmrz7SMP%2BoYZZ%2FmyCT8UNsYdh1vYqTbeQ6agOLXO7x%2Fs0MSCPjSI0&rqlang=en&rsv_dl=tb&rsv_enter=1&rsv_sug3=55&rsv_sug1=43&rsv_sug7=101&rsv_sug2=0&rsv_btype=i&inputT=21300&rsv_sug4=21887", goToOptions); PDFOptions pdfOptions = new PDFOptions(); pdfOptions.setFooterTemplate("
\n" + diff --git a/example/src/main/java/com/ruiyun/example/L_CDPSessionTest.java b/example/src/main/java/com/ruiyun/example/L_CDPSessionTest.java index 0cd19136..37115c3a 100644 --- a/example/src/main/java/com/ruiyun/example/L_CDPSessionTest.java +++ b/example/src/main/java/com/ruiyun/example/L_CDPSessionTest.java @@ -1,10 +1,10 @@ package com.ruiyun.example; import com.fasterxml.jackson.databind.JsonNode; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Target; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Target; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; import org.junit.Test; public class L_CDPSessionTest extends A_LaunchTest { @@ -17,10 +17,7 @@ public void test2() throws Exception { try (Browser browser = Puppeteer.launch(launchOptions)) { Target target = browser.target();//browser对应的target - CDPSession session = target.session();//已经附属到target的session - if (session == null) { - session = target.createCDPSession(); - } + CDPSession session = target.createCDPSession(); System.out.println("session id:" + session.id()); //这个命令是浏览器级别的session才能请求,所以要从browser的target创建session,如果是page级别的请求,就从page的target创建session //具体的protocol命令,请看https://chromedevtools.github.io/devtools-protocol/tot/SystemInfo/ @@ -43,17 +40,11 @@ public void test3() throws Exception { try (Browser browser = Puppeteer.launch(launchOptions)) { Target target = browser.target();//browser对应的target - CDPSession session = target.session();//已经附属到target的session - if (session == null) { - session = target.createCDPSession(); - } + CDPSession session = target.createCDPSession(); JsonNode res = session.send("Browser.getVersion"); System.out.println("version: " + res); } } - - - } diff --git a/example/src/main/java/com/ruiyun/example/M_ResponseTest.java b/example/src/main/java/com/ruiyun/example/M_ResponseTest.java index 8eff3848..65443c50 100644 --- a/example/src/main/java/com/ruiyun/example/M_ResponseTest.java +++ b/example/src/main/java/com/ruiyun/example/M_ResponseTest.java @@ -1,17 +1,19 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Request; -import com.ruiyun.jvppeteer.entities.ContinueRequestOverrides; -import com.ruiyun.jvppeteer.entities.GoToOptions; -import com.ruiyun.jvppeteer.entities.PuppeteerLifeCycle; -import com.ruiyun.jvppeteer.entities.ResourceType; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.PageEvents; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.core.CdpRequest; +import com.ruiyun.jvppeteer.cdp.entities.ContinueRequestOverrides; +import com.ruiyun.jvppeteer.cdp.entities.GoToOptions; +import com.ruiyun.jvppeteer.cdp.entities.HeaderEntry; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; +import com.ruiyun.jvppeteer.cdp.entities.ResourceType; +import java.util.List; import org.junit.Test; import java.util.Collections; -import java.util.Map; import java.util.function.Consumer; public class M_ResponseTest extends A_LaunchTest { @@ -23,17 +25,17 @@ public class M_ResponseTest extends A_LaunchTest { public void test4() throws Exception { //打开开发者工具 launchOptions.setDevtools(true); - try (Browser browser = Puppeteer.launch(launchOptions)) { + try (Browser cdpBrowser = Puppeteer.launch(launchOptions)) { //打开一个页面 - Page page = browser.newPage(); + Page page = cdpBrowser.newPage(); page.setRequestInterception(true); - page.on(Page.PageEvent.Request, (Consumer) request -> { + page.on(PageEvents.Request, (Consumer) request -> { if (request.resourceType().equals(ResourceType.Image)) {//拦截图片 request.abort(); } else { - Map headers = request.headers(); - headers.put("foo", "bar"); + List headers = request.headers(); + headers.add(new HeaderEntry("foo", "bar")); ContinueRequestOverrides overrides = new ContinueRequestOverrides(); overrides.setHeaders(headers); request.continueRequest(overrides); @@ -41,7 +43,7 @@ public void test4() throws Exception { }); GoToOptions options = new GoToOptions(); //如果不设置 domcontentloaded 算页面导航完成的话,那么goTo方法会超时,因为图片请求被拦截了,页面不会达到loaded阶段 - options.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.DOMCONTENT_LOADED)); + options.setWaitUntil(Collections.singletonList(PuppeteerLifeCycle.domcontentloaded)); page.goTo("https://pptr.nodejs.cn/api/puppeteer.pageevent", options); Thread.sleep(5000); } diff --git a/example/src/main/java/com/ruiyun/example/N_ExposeFunctionTest.java b/example/src/main/java/com/ruiyun/example/N_ExposeFunctionTest.java index c276d935..87a9ad24 100644 --- a/example/src/main/java/com/ruiyun/example/N_ExposeFunctionTest.java +++ b/example/src/main/java/com/ruiyun/example/N_ExposeFunctionTest.java @@ -1,11 +1,12 @@ package com.ruiyun.example; import com.fasterxml.jackson.core.JsonProcessingException; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.PageEvents; import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.entities.ConsoleMessage; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessage; import org.junit.Test; import java.io.IOException; @@ -22,19 +23,19 @@ public class N_ExposeFunctionTest extends A_LaunchTest { public void test4() throws Exception { //打开开发者工具 launchOptions.setDevtools(true); - try (Browser browser = Puppeteer.launch(launchOptions)) { + try (Browser cdpBrowser = Puppeteer.launch(launchOptions)) { //打开一个页面 - Page page = browser.newPage(); - page.on(Page.PageEvent.Console, (Consumer) consoleMessage -> System.out.println("consoleMessage: " + consoleMessage.text())); + Page page = cdpBrowser.newPage(); + page.on(PageEvents.Console, (Consumer) consoleMessage -> System.out.println(consoleMessage.text())); //exposeFunction有两个参数,第一个参数是在页面创建了一个函数名为md5的函数,函数实现逻辑为第二个参数。可以使用page.evaluate()调用md5函数进行测试 page.exposeFunction("md5", (args) -> { try { - System.out.println("args: " + Constant.OBJECTMAPPER.writeValueAsString(args)); + System.out.println("接收到浏览器的参数args: " + Constant.OBJECTMAPPER.writeValueAsString(args)+",并在 Jvppeteer 程序内计算 MD5"); } catch (JsonProcessingException e) { throw new RuntimeException(e); } String md5 = getMD5((String) args.get(0)); - System.out.println("自行打印md5:" + md5); + System.out.println(" Jvppeteer 程序内计算的md5:" + md5); return md5; }); //调用md5函数 @@ -42,7 +43,7 @@ public void test4() throws Exception { " // use window.md5 to compute hashes\n" + " const myString = 'PUPPETEER';\n" + " const myHash = await window.md5(myString);\n" + - " console.log(`md5 of ${myString} is ${myHash}`);\n" + + " console.log(`浏览器接收到的md5 of ${myString} is ${myHash}`);\n" + " }"); //删除md5函数 page.removeExposedFunction("md5"); @@ -63,14 +64,15 @@ public void test5() throws Exception { try (Browser browser = Puppeteer.launch(launchOptions)) { //打开一个页面 Page page = browser.newPage(); - page.on(Page.PageEvent.Console, (Consumer) consoleMessage -> System.out.println("consoleMessage: " + consoleMessage.text())); + page.on(PageEvents.Console, (Consumer) consoleMessage -> System.out.println("浏览器接收到计算结果,并打印: " + consoleMessage.text())); page.exposeFunction("readfile", (filePath) -> { try { + System.out.println("接收到浏览器的参数:"+Constant.OBJECTMAPPER.writeValueAsString(filePath)+",并在 Jvppeteer 程序内进行计算。"); List strings = Files.readAllLines(Paths.get((String) filePath.get(0)), StandardCharsets.UTF_8); - System.out.println("自行打印readfile: " + String.join("\n", strings)); + System.out.println("程序内计算结果: " + String.join("\n", strings)); return String.join("\n", strings); } catch (IOException e) { - return "出错啦"; + return "Jvppeteer 出错啦"; } }); //调用md5函数 diff --git a/example/src/main/java/com/ruiyun/example/O_WaitForNavigationTest.java b/example/src/main/java/com/ruiyun/example/O_WaitForNavigationTest.java index c3c4a22c..193fa23a 100644 --- a/example/src/main/java/com/ruiyun/example/O_WaitForNavigationTest.java +++ b/example/src/main/java/com/ruiyun/example/O_WaitForNavigationTest.java @@ -1,11 +1,11 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Response; -import com.ruiyun.jvppeteer.entities.WaitForOptions; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.core.Response; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.WaitForOptions; import org.junit.Test; public class O_WaitForNavigationTest extends A_LaunchTest { @@ -18,16 +18,16 @@ public void test4() throws Exception { //打开一个页面 Page page = browser.newPage(); page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); - CDPSession cdpSession = page.target().createCDPSession(); + CDPSession session = page.target().createCDPSession(); //方式1:只是发送reload命令,不等待完成才可以用到waitForNavigation - cdpSession.send("Page.reload", null, null, false); + session.send("Page.reload", null, null, false); WaitForOptions options = new WaitForOptions(); options.setIgnoreSameDocumentNavigation(true); Response response = page.waitForNavigation(options); - cdpSession.detach(); + session.detach(); //方式2: - // page.reload(); + // page.reload(); //方式1是方式2的具体实现 diff --git a/example/src/main/java/com/ruiyun/example/P_AddStyleTagTest.java b/example/src/main/java/com/ruiyun/example/P_AddStyleTagTest.java index df619f5d..f3ae1228 100644 --- a/example/src/main/java/com/ruiyun/example/P_AddStyleTagTest.java +++ b/example/src/main/java/com/ruiyun/example/P_AddStyleTagTest.java @@ -1,17 +1,16 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.entities.FrameAddStyleTagOptions; -import com.ruiyun.jvppeteer.entities.ScreenshotOptions; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.FrameAddStyleTagOptions; +import com.ruiyun.jvppeteer.cdp.entities.ScreenshotOptions; import org.junit.Test; public class P_AddStyleTagTest extends A_LaunchTest { @Test public void test4() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); //打开一个页面 Page page = browser.newPage(); @@ -25,6 +24,7 @@ public void test4() throws Exception { screenshotOptions.setFullPage(true); page.screenshot(screenshotOptions); page.$("#su").screenshot("baidu2.png"); + Thread.sleep(9000); browser.close(); } diff --git a/example/src/main/java/com/ruiyun/example/PageExtendExample.java b/example/src/main/java/com/ruiyun/example/PageExtendExample.java deleted file mode 100644 index e99b3573..00000000 --- a/example/src/main/java/com/ruiyun/example/PageExtendExample.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ruiyun.example; - -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.ElementHandle; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.PageExtend; -import com.ruiyun.jvppeteer.entities.LaunchOptions; -import org.junit.Test; - -import java.util.Collections; -import java.util.List; - -public class PageExtendExample { - - @Test - public void test1() throws Exception { - - LaunchOptions launchOptions = LaunchOptions.builder().ignoreDefaultArgs(Collections.singletonList("--enable-automation")).headless(false).executablePath("C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe").build(); - Browser browser = Puppeteer.launch(launchOptions); - Page page = browser.newPage(); - page.goTo("https://www.baidu.com"); - ElementHandle ele1 = PageExtend.byId(page, "s_tab"); - ElementHandle ele2 = PageExtend.byClass(page, "s_tab"); - ElementHandle ele3 = PageExtend.byTag(page, "a"); - List eleList1 = PageExtend.byTagList(page, "a"); - String html = PageExtend.html(page); - browser.close(); - } -} diff --git a/example/src/main/java/com/ruiyun/example/Q_ScreenshotTest.java b/example/src/main/java/com/ruiyun/example/Q_ScreenshotTest.java index 49a0a21a..71675858 100644 --- a/example/src/main/java/com/ruiyun/example/Q_ScreenshotTest.java +++ b/example/src/main/java/com/ruiyun/example/Q_ScreenshotTest.java @@ -1,9 +1,11 @@ package com.ruiyun.example; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.entities.*; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.FrameAddStyleTagOptions; +import com.ruiyun.jvppeteer.cdp.entities.ImageType; +import com.ruiyun.jvppeteer.cdp.entities.ScreenshotOptions; import org.junit.Test; public class Q_ScreenshotTest extends A_LaunchTest { @@ -17,7 +19,8 @@ public void test3() throws Exception { page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); ScreenshotOptions screenshotOptions = new ScreenshotOptions(); screenshotOptions.setPath("baidu.png"); - screenshotOptions.setOmitBackground(true); + //webdriver bidi 不支持该参数 +// screenshotOptions.setOmitBackground(true); //全屏截图 screenshotOptions.setFullPage(true); //截图的更多 @@ -33,7 +36,7 @@ public void test4() throws Exception { Page page = browser.newPage(); page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); ScreenshotOptions screenshotOptions = new ScreenshotOptions(); - screenshotOptions.setPath("baidu.png"); + screenshotOptions.setPath("baidu2.png"); //指定图片类型,path指定的名称中的后缀便不起作用了 screenshotOptions.setType(ImageType.JPEG); //jpg可以设置这个选项 @@ -47,12 +50,12 @@ public void test4() throws Exception { @Test public void test5() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); + Browser cdpBrowser = Puppeteer.launch(launchOptions); //打开一个页面 - Page page = browser.newPage(); + Page page = cdpBrowser.newPage(); page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); ScreenshotOptions screenshotOptions = new ScreenshotOptions(); - screenshotOptions.setPath("baidu.jpeg"); + screenshotOptions.setPath("baidu3.jpeg"); //指定图片类型,path指定的名称中的后缀便不起作用了 screenshotOptions.setType(ImageType.WEBP); //jpg可以设置这个选项 @@ -61,15 +64,15 @@ public void test5() throws Exception { screenshotOptions.setFullPage(true); page.screenshot(screenshotOptions); - browser.close(); + cdpBrowser.close(); } //某个元素截图 @Test public void test6() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); + Browser cdpBrowser = Puppeteer.launch(launchOptions); //打开一个页面 - Page page = browser.newPage(); + Page page = cdpBrowser.newPage(); page.goTo("https://www.baidu.com/?tn=68018901_16_pg"); FrameAddStyleTagOptions options = new FrameAddStyleTagOptions(); //修改一下百度一下按钮的颜色 @@ -79,7 +82,7 @@ public void test6() throws Exception { screenshotOptions.setPath("baidu.png"); screenshotOptions.setFullPage(true); page.screenshot(screenshotOptions); - page.$("#su").screenshot("baidu2.png"); - browser.close(); + page.$("#su").screenshot("baidu4.png"); + cdpBrowser.close(); } } diff --git a/example/src/main/java/com/ruiyun/example/R_ElementHandleApiTest.java b/example/src/main/java/com/ruiyun/example/R_ElementHandleApiTest.java index a6eb5bf8..6eb49dfc 100644 --- a/example/src/main/java/com/ruiyun/example/R_ElementHandleApiTest.java +++ b/example/src/main/java/com/ruiyun/example/R_ElementHandleApiTest.java @@ -1,26 +1,28 @@ package com.ruiyun.example; import com.fasterxml.jackson.core.JsonProcessingException; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.ElementHandle; -import com.ruiyun.jvppeteer.core.Frame; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.core.Puppeteer; -import com.ruiyun.jvppeteer.entities.AutofillData; -import com.ruiyun.jvppeteer.entities.BoundingBox; -import com.ruiyun.jvppeteer.entities.BoxModel; -import com.ruiyun.jvppeteer.entities.ConsoleMessage; -import com.ruiyun.jvppeteer.entities.CreditCard; -import com.ruiyun.jvppeteer.entities.DragData; -import com.ruiyun.jvppeteer.entities.Point; -import com.ruiyun.jvppeteer.entities.Viewport; -import org.junit.Test; - -import java.io.IOException; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.PageEvents; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; +import com.ruiyun.jvppeteer.cdp.core.Puppeteer; +import com.ruiyun.jvppeteer.cdp.entities.AutofillData; +import com.ruiyun.jvppeteer.cdp.entities.BoundingBox; +import com.ruiyun.jvppeteer.cdp.entities.BoxModel; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessage; +import com.ruiyun.jvppeteer.cdp.entities.CreditCard; +import com.ruiyun.jvppeteer.cdp.entities.DragData; +import com.ruiyun.jvppeteer.cdp.entities.GoToOptions; +import com.ruiyun.jvppeteer.cdp.entities.Point; +import com.ruiyun.jvppeteer.cdp.entities.Viewport; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import org.junit.Test; public class R_ElementHandleApiTest extends A_LaunchTest { /** @@ -29,29 +31,37 @@ public class R_ElementHandleApiTest extends A_LaunchTest { @Test public void test3() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); + Browser Browser = Puppeteer.launch(launchOptions); //打开一个页面 - Page page = browser.newPage(); + Page page = Browser.newPage(); //方式1 waitForSelector - //page.goTo("https://www.bookstack.cn/read/HTTP-Status-codes/websocket.md",false); - // ElementHandle elementHandle = page.waitForSelector("#page-content"); - + page.goTo("https://www.bookstack.cn/read/HTTP-Status-codes/websocket.md"); + try { + ElementHandle elementHandle = page.waitForSelector("#page-content"); + BoxModel boxModel = elementHandle.boxModel(); + elementHandle.dispose();//手动释放,防止内存泄露 + double height = boxModel.getHeight(); + double width = boxModel.getWidth(); + System.out.println(height + ":" + width); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } //方式2 waitForSelector - new Thread(() -> { - try { - ElementHandle elementHandle = page.waitForSelector("#page-content"); - BoxModel boxModel = elementHandle.boxModel(); - elementHandle.dispose();//手动释放,防止内存泄露 - double height = boxModel.getHeight(); - double width = boxModel.getWidth(); - System.out.println(height + ":" + width); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - }).start(); +// new Thread(() -> { +// try { +// ElementHandle elementHandle = page.waitForSelector("#page-content"); +// BoxModel boxModel = elementHandle.boxModel(); +// elementHandle.dispose();//手动释放,防止内存泄露 +// double height = boxModel.getHeight(); +// double width = boxModel.getWidth(); +// System.out.println(height + ":" + width); +// } catch (JsonProcessingException e) { +// throw new RuntimeException(e); +// } +// }).start(); page.goTo("https://www.bookstack.cn/read/HTTP-Status-codes/websocket.md"); - browser.close(); + Browser.close(); } /** @@ -60,14 +70,14 @@ public void test3() throws Exception { @Test public void test4() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); + Browser Browser = Puppeteer.launch(launchOptions); //打开一个页面 - Page page = browser.newPage(); - page.goTo("https://pptr.nodejs.cn/api/puppeteer.elementhandle.isvisible"); + Page page = Browser.newPage(); + page.goTo("https://www.baidu.com"); - page.click("#__docusaurus > nav > div.navbar__inner > div.navbar__items.navbar__items--right > div.navbarSearchContainer_Bca1 > button"); //等待输入框出现 - ElementHandle elementHandle = page.waitForSelector("#docsearch-input"); + //selector可能会变,报错及时更改 + ElementHandle elementHandle = page.$("#kw"); elementHandle.type("jvppeteer测试"); boolean intersectingViewport = elementHandle.isIntersectingViewport(); @@ -78,15 +88,10 @@ public void test4() throws Exception { elementHandle.press("Escape"); boolean isHidden = elementHandle.isHidden(); System.out.println("isHidden: " + isHidden); - page.waitForSelector("#__docusaurus > nav > div.navbar__inner > div.navbar__items.navbar__items--right > div.navbarSearchContainer_Bca1 > button"); + ElementHandle $ = page.$("#__docusaurus > nav > div.navbar__inner > div.navbar__items.navbar__items--right > div.navbarSearchContainer_Bca1 > button"); + System.out.println($); - //esc按键释放后,该元素消失,那么isIntersectingViewport便会抛出错误 - try { - elementHandle.isIntersectingViewport(); - } catch (Exception e) { - System.out.println("intersectingViewport2: 出错了"); - } - browser.close(); + Browser.close(); } /** @@ -94,13 +99,24 @@ public void test4() throws Exception { */ @Test public void test5() throws Exception { - Browser browser = Puppeteer.launch(launchOptions); + Browser Browser = Puppeteer.launch(launchOptions); //打开一个页面 - Page page = browser.newPage(); + Page page = Browser.newPage(); + page.on(PageEvents.PageError, e -> { + System.out.println("page error:" + e); + //火狐浏览器在https://pptr.nodejs.cn/api/puppeteer.pageevent页面加载发生错误,刷新页面 + page.reload(); + }); + page.on(PageEvents.Error, e -> { + System.out.println("error:" + e); + //火狐浏览器在https://pptr.nodejs.cn/api/puppeteer.pageevent页面加载发生错误,刷新页面 + page.reload(); + }); + //火狐浏览器在这个页面发生错误 page.goTo("https://pptr.nodejs.cn/api/puppeteer.elementhandle.isvisible"); //selector可能会变,报错及时更改 - ElementHandle elementHandle = page.$("#__docusaurus_skipToContent_fallback > div > div > main > div > div > div.col.docItemCol_VOVn > div > nav > a.pagination-nav__link.pagination-nav__link--prev"); + ElementHandle elementHandle = page.$("#__docusaurus > nav > div.navbar__inner > div:nth-child(1) > a.navbar__brand > b"); try { elementHandle.hover(); //利用 point 和 boundingBox可以用来拖动验证条 @@ -112,7 +128,7 @@ public void test5() throws Exception { } finally { elementHandle.dispose(); } - browser.close(); + Browser.close(); } /** @@ -123,12 +139,12 @@ public void test6() throws Exception { ArrayList args = new ArrayList<>();//添加一些额外的启动参数 args.add("--no-sandbox"); launchOptions.setArgs(args); - Browser browser = Puppeteer.launch(launchOptions); + Browser Browser = Puppeteer.launch(launchOptions); //打开一个页面 - Page page = browser.newPage(); + Page page = Browser.newPage(); //打开这个。ElementHandle.drag才有返回值,不打开返回null, setDragInterception(true)已过时 // page.setDragInterception(true); - page.on(Page.PageEvent.Console, (Consumer) consoleMessage -> System.out.println("consoleMessage: " + consoleMessage.text())); + page.on(PageEvents.Console, (Consumer) consoleMessage -> System.out.println("consoleMessage: " + consoleMessage.text())); page.setContent(""); + System.out.println("frame size:" + page.frames().size()); Frame frame = page.frames().get(1); - ElementHandle frameElement = frame.frameElement(); + JSHandle frameElement = frame.frameElement(); Object evaluate = frameElement.evaluate("el => {\n" + " return el.tagName.toLocaleLowerCase();\n" + " }"); diff --git a/example/src/main/resources/META-INF/MANIFEST.MF b/example/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..2cb15c32 --- /dev/null +++ b/example/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.ruiyun.example.Test + diff --git a/example/simplelogger.properties b/example/src/main/resources/simplelogger.properties similarity index 85% rename from example/simplelogger.properties rename to example/src/main/resources/simplelogger.properties index aa4322e9..cde79825 100644 --- a/example/simplelogger.properties +++ b/example/src/main/resources/simplelogger.properties @@ -1,5 +1,5 @@ org.slf4j.simpleLogger.logFile=System.out -org.slf4j.simpleLogger.defaultLogLevel=trace +org.slf4j.simpleLogger.defaultLogLevel=info org.slf4j.simpleLogger.showDateTime=true org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS org.slf4j.simpleLogger.showThreadName=false diff --git a/pom.xml b/pom.xml index e42c2ed7..0ea1ce84 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.github.fanyong920 jvppeteer jar - 2.2.5 + 3.0.0 jvppeteer java version of puppeteer https://github.com/fanyong920/jvppeteer @@ -92,12 +92,6 @@ org.slf4j slf4j-api - - org.slf4j - slf4j-simple - test - - org.java-websocket Java-WebSocket diff --git a/src/main/java/com/ruiyun/jvppeteer/api/core/Browser.java b/src/main/java/com/ruiyun/jvppeteer/api/core/Browser.java new file mode 100644 index 00000000..dec619d5 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/Browser.java @@ -0,0 +1,207 @@ +package com.ruiyun.jvppeteer.api.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.ruiyun.jvppeteer.api.events.BrowserEvents; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.cdp.entities.BrowserContextOptions; +import com.ruiyun.jvppeteer.cdp.entities.DebugInfo; +import com.ruiyun.jvppeteer.cdp.entities.DownloadOptions; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import static com.ruiyun.jvppeteer.util.Helper.filter; +import static com.ruiyun.jvppeteer.util.Helper.waitForCondition; + +public abstract class Browser extends EventEmitter implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(Browser.class); + /** + * 主动调用 browser.close时候为true + * 当 connection 断开,浏览器进程未关闭时候,杀死浏览器进程 + */ + public volatile boolean autoClose; + + /** + * 获取关联的 Process。 + * + * @return 浏览器进程对象 + */ + public abstract Process process(); + + /** + * 创建一个新的 浏览器上下文。 + *

+ * 这不会与其他 浏览器上下文 共享 cookie/缓存 + * + * @return 返回创建的浏览器上下文对象 + */ + public BrowserContext createBrowserContext() { + return this.createBrowserContext(new BrowserContextOptions()); + } + + /** + * 创建一个新的 浏览器上下文。 + *

+ * 这不会与其他 浏览器上下文 共享 cookie/缓存 + * + * @param options 浏览器上下文选项,包含代理服务器设置等 + * @return 返回创建的浏览器上下文对象 + */ + public abstract BrowserContext createBrowserContext(BrowserContextOptions options); + + /** + * 获取打开的 浏览器上下文 列表。 + *

+ * 在新创建的 browser 中,这将返回 BrowserContext 的单个实例。 + * + * @return 打开的 浏览器上下文 列表 + */ + public abstract List browserContexts(); + + /** + * 获取默认 浏览器上下文。 + *

+ * 默认 浏览器上下文 无法关闭。 + * + * @return 默认 浏览器上下文 + */ + public abstract BrowserContext defaultBrowserContext(); + + /** + * 获取用于连接到此 browser 的 WebSocket URL。 + *

+ * 这通常与 Puppeteer.connect() 一起使用。 + *

+ * 你可以从 http://HOST:PORT/json/version 找到调试器 URL (webSocketDebuggerUrl)。 + *

+ * 请参阅 浏览器端点 了解更多信息。 + * + * @return WebSocket URL + */ + public abstract String wsEndpoint(); + + /** + * 在 默认浏览器上下文 中创建新的 page。 + * + * @return 新创建的页面对象 + */ + public abstract Page newPage(); + + /** + * 获取所有活动的 targets。 + *

+ * 如果有多个 浏览器上下文,则返回所有 浏览器上下文 中的所有 targets。 + * + * @return 所有活动的 targets + */ + public abstract List targets(); + + /** + * 获取与 默认浏览器上下文 关联的 target。 + * + * @return 默认浏览器上下文 关联的 target + */ + public abstract Target target(); + + /** + * 等待直到出现与给定 predicate 匹配的 target 并返回它. + * 此方法用于在一定超时时间内,持续检查是否出现符合特定条件的目标对象. + * + * @param predicate 用于筛选目标对象的条件,符合条件的目标将被返回. + * @param timeout 等待的最大时间(以毫秒为单位),超过此时间将抛出异常. + * @return 返回符合 predicate 条件的目标对象. + */ + public Target waitForTarget(Predicate predicate, int timeout) { + Supplier conditionChecker = () -> filter(this.targets(), predicate); + return waitForCondition(conditionChecker, timeout, "Waiting for target failed: timeout " + timeout + "ms exceeded"); + } + + /** + * 等待直到出现与给定 predicate 匹配的 target 并返回它. + *

+ * 默认等待时间是30s + * + * @param predicate 用于筛选目标对象的条件,符合条件的目标将被返回. + * @return 返回符合 predicate 条件的目标对象. + */ + public Target waitForTarget(Predicate predicate) { + return this.waitForTarget(predicate, Constant.DEFAULT_TIMEOUT); + } + + /** + * 获取此 Browser 内所有打开的 pages 的列表。 + *

+ * 如果有多个 浏览器上下文,则返回所有 浏览器上下文 中的所有 pages。 + * + * @return 所有打开的 pages + */ + public List pages() { + return this.browserContexts().stream().flatMap(context -> context.pages().stream()).collect(Collectors.toList()); + } + + /** + * 获取表示此 浏览器的 名称和版本的字符串。 + *

+ * 对于无头浏览器,这与 "HeadlessChrome/61.0.3153.0" 类似。对于非无头或新无头,这与 "Chrome/61.0.3153.0" 类似。 + *

+ * 对于火狐浏览器,这与 "Firefox/116.0a1"类似 + *

+ * Browser.version() 的格式可能会随着浏览器的未来版本而改变。 + * + * @return 浏览器版本 + * @throws JsonProcessingException 序列化错误 + */ + public abstract String version() throws JsonProcessingException; + + /** + * 获取此 浏览器的 原始用户代理。 + *

+ * Pages 可以使用 Page.setUserAgent() 覆盖用户代理。e + * + * @return 原始用户代理 + */ + public abstract String userAgent(); + + /** + * 断开 Jvppeteer 与该 browser 的连接,但保持进程运行。 + */ + public abstract void disconnect(); + + /** + * Jvppeteer 是否连接到此 browser。 + * + * @return 连接返回true, 不连接返回false + */ + public abstract boolean connected(); + + public void disposeSymbol() { + try { + if (Objects.nonNull(this.process())) { + this.close(); + } else { + this.disconnect(); + } + } catch (Exception e) { + LOGGER.error("jvppeteer error", e); + } + } + + /** + * 从 Jvppeteer 获取 debug 信息 + *

+ * 目前,信息包括待处理的协议调用。将来,我们可能会添加更多信息。 + * + * @return debug 信息 + */ + public abstract DebugInfo debugInfo(); + + public abstract void setDownloadBehavior(DownloadOptions downloadOptions); + + public abstract void cancelDownload(String key, String id); + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/api/core/BrowserContext.java b/src/main/java/com/ruiyun/jvppeteer/api/core/BrowserContext.java new file mode 100644 index 00000000..01c8969c --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/BrowserContext.java @@ -0,0 +1,136 @@ +package com.ruiyun.jvppeteer.api.core; + +import com.ruiyun.jvppeteer.api.events.BrowserContextEvents; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.common.WebPermission; +import com.ruiyun.jvppeteer.cdp.entities.Cookie; +import com.ruiyun.jvppeteer.cdp.entities.CookieParam; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.function.Supplier; + + +import static com.ruiyun.jvppeteer.util.Helper.filter; +import static com.ruiyun.jvppeteer.util.Helper.waitForCondition; + +public abstract class BrowserContext extends EventEmitter { + /** + * 获取此 浏览器上下文 内所有活动的 targets。 + *

+ * 此方法通过过滤当前浏览器的所有 targets,只返回属于当前浏览器上下文(browserContext)的 targets。 + * 这对于当您想要对特定浏览器上下文中的所有页面或框架进行操作时非常有用。 + * + * @return 返回一个 List,包含当前浏览器上下文中所有活动 targets 的列表。 + */ + public abstract List targets(); + + /** + * 等待直到出现与给定 predicate 匹配的 target 并返回它。 + * + * @param predicate 一个断言,用于测试每个target是否为匹配项 + * @return 返回与predicate匹配的target + */ + public Target waitForTarget(Predicate predicate) { + return this.waitForTarget(predicate, Constant.DEFAULT_TIMEOUT); + } + + /** + * 等待直到出现与给定 predicate 匹配的 target 并返回它。 + * + * @param predicate 一个断言,用于测试每个target是否为匹配项 + * @param timeout 等待超时时间 + * @return 返回与predicate匹配的target + */ + + public Target waitForTarget(Predicate predicate, int timeout) { + Supplier conditionChecker = () -> filter(this.targets(), predicate); + return waitForCondition(conditionChecker, timeout, "waiting for target failed: timeout " + timeout + "ms exceeded"); + } + + /** + * 获取此 浏览器上下文 内所有打开的 pages 的列表。 + *

+ * 不可见的 pages,例如 "background_page",这里不会列出。你可以使用 Target.page() 找到它们。 + * + * @return 所有打开的 pages + */ + public abstract List pages(); + + /** + * 授予指定页面的权限设置 + * + * @param origin 权限来源,通常是一个URL + * @param webPermissions 权限列表,表示要授予的权限,没有授予的权限默认是拒绝 + */ + public abstract void overridePermissions(String origin, WebPermission... webPermissions); + + /** + * 在给定的 origin 内授予此 浏览器上下文 给定的 permissions。 + */ + public abstract void clearPermissionOverrides(); + + /** + * 在此 浏览器上下文 中创建一个新的 page。 + * + * @return 新创建的 Page 实例 + */ + public abstract Page newPage(); + + /** + * 获取与此 浏览器上下文 关联的 browser。 + * + * @return 返回与此浏览器上下文关联的 Browser 对象。 + */ + public abstract Browser browser(); + + /** + * 关闭此 浏览器上下文 和所有关联的 pages。 + */ + public abstract void close(); + + /** + * 获取当前浏览器上下文的所有cookie + * + * @return 该浏览器上下文的所有cookie + */ + public abstract List cookies(); + + + /** + * 在当前浏览器上下文的设置cookie + */ + public abstract void setCookie(CookieParam... cookies); + + /** + * 在当前浏览器上下文删除指定cookie + * + * @param cookies 指定删除的cookie + */ + public void deleteCookie(Cookie... cookies) { + if (Objects.isNull(cookies)) { + return; + } + for (Cookie cookie : cookies) { + cookie.setExpires(1); + this.setCookie(Constant.OBJECTMAPPER.convertValue(cookie, CookieParam.class)); + } + + } + + /** + * 该浏览器上下文是否关闭 + * + * @return 是否关闭 + */ + public boolean closed() { + return !this.browser().browserContexts().contains(this); + } + + /** + * 获取当前对象的ID + * + * @return 当前对象的ID + */ + public abstract String id(); +} diff --git a/src/main/java/com/ruiyun/jvppeteer/api/core/CDPSession.java b/src/main/java/com/ruiyun/jvppeteer/api/core/CDPSession.java new file mode 100644 index 00000000..da79b80a --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/CDPSession.java @@ -0,0 +1,29 @@ +package com.ruiyun.jvppeteer.api.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; + +public abstract class CDPSession extends EventEmitter { + + public abstract Connection getConnection(); + + public CDPSession parentSession() { + return null; + } + + public abstract String id(); + + public abstract void detach(); + + public abstract void onClosed(); + + public JsonNode send(String method) { + return this.send(method, null); + } + + public JsonNode send(String method, Object params) { + return this.send(method, params, null, true); + } + + public abstract JsonNode send(String method, Object params, Integer timeout, boolean isBlocking); +} diff --git a/src/main/java/com/ruiyun/jvppeteer/api/core/Connection.java b/src/main/java/com/ruiyun/jvppeteer/api/core/Connection.java new file mode 100644 index 00000000..506aa2aa --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/Connection.java @@ -0,0 +1,92 @@ +package com.ruiyun.jvppeteer.api.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.exception.ProtocolException; +import com.ruiyun.jvppeteer.transport.CallbackRegistry; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; +import com.ruiyun.jvppeteer.transport.ConnectionTransport; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import static com.ruiyun.jvppeteer.common.Constant.JV_HANDLE_MESSAGE_THREAD; + +public abstract class Connection extends EventEmitter { + protected static final Logger LOGGER = LoggerFactory.getLogger(Connection.class); + protected final String url; + protected final ConnectionTransport transport; + protected final int delay; + protected final int timeout; + protected final Map sessions = new ConcurrentHashMap<>(); + protected volatile boolean closed; + protected final Set manuallyAttached = new HashSet<>(); + protected final CallbackRegistry callbacks = new CallbackRegistry();//并发 + protected final AtomicLong id = new AtomicLong(1); + protected AtomicLong messageThreadId = new AtomicLong(1); + protected ExecutorService handleMessageExecutorService = Executors.newSingleThreadExecutor(r -> new Thread(r, JV_HANDLE_MESSAGE_THREAD + messageThreadId.getAndIncrement())); + Runnable closeRunner; + + public Connection(String url, ConnectionTransport transport, int delay, int timeout) { + super(); + this.url = url; + this.transport = transport; + this.delay = delay; + this.timeout = timeout; + this.transport.setConnection(this); + } + + public JsonNode send(String method) { + return send(method, null); + } + + public JsonNode send(String method, Object params) { + return this.send(method, params, null, true); + } + + public JsonNode send(String method, Object params, Integer timeout, boolean isBlocking) { + return this.rawSend(method, params, null, timeout, isBlocking); + } + + public abstract JsonNode rawSend(String method, Object params, String sessionId, Integer timeout, boolean isBlocking); + + public abstract void onMessage(String message); + + public abstract String url(); + + public abstract void dispose(); + + public abstract boolean closed(); + + public abstract List getPendingProtocolErrors(); + + public abstract CDPSession session(String sessionId); + + public abstract boolean isAutoAttached(String targetId); + + public abstract CDPSession _createSession(TargetInfo targetInfo, boolean isAutoAttachEmulated); + + public abstract void onClose(); + + protected Runnable handleMessageRunnable(JsonNode response) { + return () -> { + }; + } + + public void setCloseRunner(Runnable closeRunner) { + this.closeRunner = closeRunner; + } + + public Runnable closeRunner() { + return this.closeRunner; + } +} \ No newline at end of file diff --git a/src/main/java/com/ruiyun/jvppeteer/api/core/Dialog.java b/src/main/java/com/ruiyun/jvppeteer/api/core/Dialog.java new file mode 100644 index 00000000..8f460b06 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/Dialog.java @@ -0,0 +1,79 @@ +package com.ruiyun.jvppeteer.api.core; + +import com.ruiyun.jvppeteer.cdp.entities.DialogType; +import com.ruiyun.jvppeteer.util.StringUtil; + +public abstract class Dialog { + protected String type; + protected String message; + protected String defaultValue = ""; + protected boolean handled; + + public Dialog() { + super(); + } + + public Dialog(DialogType type, String message, String defaultValue) { + super(); + this.type = type.getType(); + this.message = message; + if (StringUtil.isNotEmpty(defaultValue)) + this.defaultValue = defaultValue; + } + + /** + * 返回 dialog 的类型 + * + * @return {string} + */ + public String type() { + return this.type; + } + + /** + * 返回 dialog 的信息 + * + * @return {string} + */ + public String message() { + return this.message; + } + + /** + * 返回 dialog 的默认值 + * + * @return {string} + */ + public String defaultValue() { + return this.defaultValue; + } + + /** + * 处理 dialog + * + * @param accept 是否接受 dialog + * @param text 根据 text 来处理 dialog 比如 text = 确定,就点击确定 + * @return 处理的结果 + */ + protected abstract boolean handle(boolean accept, String text); + + /** + * 接受对话框 + * + * @param promptText 在提示中输入的文本。如果对话框type不提示,则不会引起任何影响 + * @return 对话框关闭后返回 + */ + public boolean accept(String promptText) { + return this.handle(true, promptText); + } + + /** + * 不接受对话框的内容 + * + * @return 对话框关闭后返回 + */ + public Boolean dismiss() { + return this.handle(false, ""); + } + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/ElementHandle.java b/src/main/java/com/ruiyun/jvppeteer/api/core/ElementHandle.java similarity index 76% rename from src/main/java/com/ruiyun/jvppeteer/core/ElementHandle.java rename to src/main/java/com/ruiyun/jvppeteer/api/core/ElementHandle.java index 78289d87..ce27a022 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/ElementHandle.java +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/ElementHandle.java @@ -1,77 +1,57 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.api.core; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.bidi.entities.PrintMarginParameters; +import com.ruiyun.jvppeteer.cdp.entities.AutofillData; +import com.ruiyun.jvppeteer.cdp.entities.BoundingBox; +import com.ruiyun.jvppeteer.cdp.entities.BoxModel; +import com.ruiyun.jvppeteer.cdp.entities.ClickOptions; +import com.ruiyun.jvppeteer.cdp.entities.DragData; +import com.ruiyun.jvppeteer.cdp.entities.ElementScreenshotOptions; +import com.ruiyun.jvppeteer.cdp.entities.KeyPressOptions; +import com.ruiyun.jvppeteer.cdp.entities.KeyboardTypeOptions; +import com.ruiyun.jvppeteer.cdp.entities.Offset; +import com.ruiyun.jvppeteer.cdp.entities.Point; +import com.ruiyun.jvppeteer.cdp.entities.RemoteObject; +import com.ruiyun.jvppeteer.cdp.entities.ScreenshotClip; +import com.ruiyun.jvppeteer.cdp.entities.WaitForSelectorOptions; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.LazyArg; -import com.ruiyun.jvppeteer.common.ParamsFactory; import com.ruiyun.jvppeteer.common.QuerySelector; -import com.ruiyun.jvppeteer.entities.AutofillData; -import com.ruiyun.jvppeteer.entities.BoundingBox; -import com.ruiyun.jvppeteer.entities.BoxModel; -import com.ruiyun.jvppeteer.entities.ClickOptions; -import com.ruiyun.jvppeteer.entities.DragData; -import com.ruiyun.jvppeteer.entities.ElementScreenshotOptions; -import com.ruiyun.jvppeteer.entities.KeyPressOptions; -import com.ruiyun.jvppeteer.entities.KeyboardTypeOptions; -import com.ruiyun.jvppeteer.entities.Offset; -import com.ruiyun.jvppeteer.entities.Point; -import com.ruiyun.jvppeteer.entities.RemoteObject; -import com.ruiyun.jvppeteer.entities.ScreenshotClip; import com.ruiyun.jvppeteer.exception.EvaluateException; import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.transport.CDPSession; import com.ruiyun.jvppeteer.util.QueryHandlerUtil; -import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.lang.reflect.Array; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.AccessControlException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.concurrent.ExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import static com.ruiyun.jvppeteer.util.Helper.withSourcePuppeteerURLIfNone; -/** - * ElementHandle 表示页内 DOM 元素。 - *

- * ElementHandles 可以使用 Page.$() 方法创建。 - *

- * ElementHandle 会阻止 DOM 元素被垃圾回收,除非句柄是 disposed。当其原始框架被导航时,ElementHandles 会被自动处理。 - *

b - * ElementHandle 实例可以用作 Page.$eval() 和 Page.evaluate() 方法中的参数。 - *

- * 此类的构造函数被标记为内部构造函数。第三方代码不应直接调用构造函数或创建扩展 ElementHandle 类的子类。 - */ -public class ElementHandle extends JSHandle { - private static final Logger LOGGER = LoggerFactory.getLogger(ElementHandle.class); - private static final Set NON_ELEMENT_NODE_ROLES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("StaticText", "InlineTextBox"))); - private final JSHandle handle; +public abstract class ElementHandle extends JSHandle { + protected static final Logger LOGGER = LoggerFactory.getLogger(ElementHandle.class); //隔离的JSHandle - private volatile ElementHandle isolatedHandle; + protected volatile ElementHandle isolatedHandle; + protected final JSHandle handle; - ElementHandle(IsolatedWorld world, RemoteObject remoteObject) { - super(world, remoteObject); - this.handle = new JSHandle(world, remoteObject); + public ElementHandle(JSHandle handle) { + super(); + this.handle = handle; } /** @@ -84,7 +64,7 @@ public class ElementHandle extends JSHandle { * @return 当前句柄或其隔离句柄 * @throws JsonProcessingException 如果转换过程出现错误 */ - public ElementHandle adoptIsolatedHandle() throws JsonProcessingException { + protected ElementHandle adoptIsolatedHandle() throws JsonProcessingException { if (this.realm() == this.frame().isolatedRealm()) { return this; } @@ -169,246 +149,192 @@ public T adoptResult(T isolatedResult) throws JsonProcessingException { return isolatedResult; } - public CDPSession client() { - return this.handle.client(); - } - - public RemoteObject getRemoteObject() { - return this.handle.getRemoteObject(); - } - - public IsolatedWorld realm() { - return this.handle.realm().toIsolatedWorld(); - } - - public Frame frame() { - return this.realm().toIsolatedWorld().getFrame(); - } - - public FrameManager getFrameManager() { - return this.frame().frameManager(); - } - /** - * 解析与元素关联的框架(如果有)。HTMLIFrameElements 始终存在。 + * 返回 当前对象的 remoteObject 对象的id * - * @return Frame + * @return 当前对象的 remoteObject 对象的id */ - public Frame contentFrame() { - Map params = ParamsFactory.create(); - params.put("objectId", this.id()); - JsonNode nodeInfo = this.client().send("DOM.describeNode", params); - JsonNode frameId = nodeInfo.get("node").get("frameId"); - if (frameId == null || StringUtil.isEmpty(frameId.asText())) - return null; - return this.getFrameManager().frame(frameId.asText()); - } - - protected void scrollIntoViewIfNeeded() throws JsonProcessingException, EvaluateException { - if (this.isIntersectingViewport(1)) { - return; - } - this.scrollIntoView(); + @Override + public String id() { + return this.handle.id(); } /** - * 将当前元素滚动到视图中。此方法通过自动化协议客户端或调用 element.scrollIntoView 方法来实现。 - * 它确保元素的中心位置在浏览器视图的中心。 + * 当前对象是否已经释放 * - * @throws JsonProcessingException 当处理JSON数据时发生错误。 - * @throws EvaluateException 当执行脚本评估时发生错误。 + * @return true 代表释放 */ - public void scrollIntoView() throws JsonProcessingException, EvaluateException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - wrapThis.assertConnectedElement(); - wrapThis.evaluate("async (element) => {\n" + - " element.scrollIntoView({\n" + - " block: 'center',\n" + - " inline: 'center',\n" + - " behavior: 'instant',\n" + - " });\n" + - " }"); - } - - public String id() { - return this.handle.id(); - } - - private void assertConnectedElement() throws JsonProcessingException, EvaluateException { - Object error = this.evaluate("async element => {\n" + - " if (!element.isConnected) {\n" + - " return 'Node is detached from document';\n" + - " }\n" + - " if (element.nodeType !== Node.ELEMENT_NODE) {\n" + - " return 'Node is not of type HTMLElement';\n" + - " }\n" + - " return;\n" + - " }"); - if (error != null) { - throw new JvppeteerException((String) error); - } + @Override + public boolean disposed() { + return this.handle.disposed(); } /** - * 上传文件方法 - *

- * 该方法负责将提供的文件路径列表上传到特定的输入元素它首先确定输入元素是否支持多文件上传, - * 然后验证文件路径列表的大小是否符合上传条件接着检查每个文件路径是否可读,如果不可读,则抛出异常 - *

- * 当文件路径列表为空时,该方法通过评估一段脚本来更新输入元素的files属性以模拟用户操作,当列表不为空时, - * 它会通过DOM API设置文件输入元素的文件列表 + * 获取表示当前对象属性的句柄映射。 * - * @param filePaths 文件路径列表 - * @throws JsonProcessingException 当JSON处理失败时抛出 - * @throws EvaluateException 当JavaScript评估失败时抛出 + * @return 返回 JSHandle 的实例 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException 给定函数执行的异常 */ - public void uploadFile(List filePaths) throws JsonProcessingException, EvaluateException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - boolean isMultiple = (Boolean) wrapThis.evaluate("(element) => element.multiple"); - ValidateUtil.assertArg(filePaths.size() <= 1 || isMultiple, "Multiple file uploads only work with "); - List files = filePaths.stream().map(filePath -> { - Path absolutePath = Paths.get(filePath).toAbsolutePath(); - boolean readable = Files.isReadable(absolutePath); - if (!readable) { - throw new AccessControlException(filePath + "is not readable"); - } - return absolutePath.toString(); - }).collect(Collectors.toList()); - // The zero-length array is a special case, it seems that DOM.setFileInputFiles does - // not actually update the files in that case, so the solution is to eval the element - // value to a new FileList directly. - if (files.isEmpty()) { - String pageFunction = "element => {\n" + - " element.files = new DataTransfer().files;\n" + - "\n" + - " // Dispatch events for this case because it should behave akin to a user action.\n" + - " element.dispatchEvent(\n" + - " new Event('input', {bubbles: true, composed: true})\n" + - " );\n" + - " element.dispatchEvent(new Event('change', {bubbles: true}));\n" + - " }"; - wrapThis.evaluate(pageFunction); - } else { - String objectId = wrapThis.id(); - Map params = ParamsFactory.create(); - params.put("objectId", objectId); - JsonNode node = wrapThis.client().send("DOM.describeNode", params); - int backendNodeId = node.get("node").get("backendNodeId").asInt(); - params.clear(); - params.put("objectId", objectId); - params.put("files", files); - params.put("backendNodeId", backendNodeId); - wrapThis.client().send("DOM.setFileInputFiles", params); - } + @Override + public JSHandle getProperty(String propertyName) throws JsonProcessingException, EvaluateException { + return this.adoptResult(this.adoptIsolatedHandle().handle().getProperty(propertyName)); } /** - * 如果该元素是表单输入,则可以使用 ElementHandle.autofill() 来测试表单是否与浏览器的自动填充实现兼容。如果无法自动填写表单,则会引发错误。 - *

- * 目前,仅支持自动填充信用卡信息,并且在 Chrome 中仅支持新的 headless 和 headful 模式。 + * 获取表示当前对象属性的句柄映射。 * - * @param data 自动填写表单数据 + * @return 返回 JSHandle 的实例 的集合 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException 给定函数执行的异常 */ - public void autofill(AutofillData data) { - Map params = ParamsFactory.create(); - params.put("objectId", this.id()); - JsonNode response = this.client().send("DOM.describeNode", params); - int fieldId = response.get("node").get("backendNodeId").asInt(); - String frameId = this.frame().id(); - params.clear(); - params.put("fieldId", fieldId); - params.put("frameId", frameId); - params.put("card", data.getCreditCard()); - this.client().send("Autofill.trigger", params); - } - - public List queryAXTree(String name, String role) throws JsonProcessingException { - Map params = ParamsFactory.create(); - params.put("objectId", this.id()); - params.put("accessibleName", name); - params.put("role", role); - JsonNode response = this.client().send("Accessibility.queryAXTree", params); - JsonNode nodes = response.get("nodes"); - Iterator elements = nodes.elements(); - List result = new ArrayList<>(); - while (elements.hasNext()) { - JsonNode node = elements.next(); - if (node.hasNonNull("ignored") && node.get("ignored").asBoolean() || !node.hasNonNull("role") || NON_ELEMENT_NODE_ROLES.contains(node.get("role").get("value").asText())) { - continue; - } - result.add(this.realm().adoptBackendNode(node.get("backendDOMNodeId").asInt()).asElement()); - } - return result; - } - @Override public Map getProperties() throws JsonProcessingException { return this.adoptResult(this.adoptIsolatedHandle().handle().getProperties()); } - @Override - public JSHandle getProperty(String propertyName) throws JsonProcessingException, EvaluateException { - return this.adoptResult(this.adoptIsolatedHandle().handle().getProperty(propertyName)); - } - - @Override - public boolean disposed() { - return this.handle.disposed(); + /** + * 使用当前对象作为第一个参数来执行给定的 JS 函数。 + * + * @param pptrFunction 给定函数 + * @return 给定函数执行的结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException 给定函数执行的异常 + */ + public Object evaluate(String pptrFunction) throws JsonProcessingException, EvaluateException { + return this.evaluate(pptrFunction, null); } - public Object evaluate(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluate(pageFunction, null); + /** + * 使用当前对象作为第一个参数来执行给定的 JS 函数。 + * + * @param pptrFunction 给定函数 + * @param args 给定函数的参数,第一个参数是当前对象 + * @return 给定函数执行的结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException 给定函数执行的异常 + */ + public Object evaluate(String pptrFunction, List args) throws JsonProcessingException, EvaluateException { + pptrFunction = withSourcePuppeteerURLIfNone("evaluate", pptrFunction); + return this.handle.evaluate(pptrFunction, args); } - public Object evaluate(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluate", pageFunction); - return this.handle.evaluate(pageFunction, args); - } + /** + * 使用当前对象作为第一个参数来执行给定的 JS 函数。 + * + * @param pptrFunction 给定函数 + * @return 返回 JSHandle 的实例 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException 给定函数执行的异常 + */ - public JSHandle evaluateHandle(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluateHandle(pageFunction, null); + public JSHandle evaluateHandle(String pptrFunction) throws JsonProcessingException, EvaluateException { + return this.evaluateHandle(pptrFunction, null); } + /** + * 使用当前对象作为第一个参数来执行给定的 JS 函数。 + * + * @param pptrFunction 给定函数 + * @param args 给定函数的参数,第一个参数是当前对象 + * @return 返回 JSHandle 的实例 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException 给定函数执行的异常 + */ @Override - public JSHandle evaluateHandle(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pageFunction); - return this.handle.evaluateHandle(pageFunction, args); + public JSHandle evaluateHandle(String pptrFunction, List args) throws JsonProcessingException, EvaluateException { + pptrFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pptrFunction); + return this.handle.evaluateHandle(pptrFunction, args); } + /** + * 表示引用对象的可序列化部分的普通对象 + * + * @return 给定函数执行的结果 + * @throws JsonProcessingException 如果对象由于循环而无法序列化,则抛出该异常。 + * @throws EvaluateException 给定函数执行的异常 + */ @Override public Object jsonValue() throws JsonProcessingException, EvaluateException { return this.adoptResult(this.adoptIsolatedHandle().handle().jsonValue()); } + /** + * 返回 JSHandle 的字符串表示形式。 + * + * @return JSHandle 的字符串表示形式。 + */ @Override public String toString() { return this.handle.toString(); } + /** + * 提供对支持当前对象的 Protocol.Runtime.RemoteObject 的访问。 + * + * @return 当前对象的 Protocol.Runtime.RemoteObject + */ + public RemoteObject remoteObject() { + return this.handle.remoteObject(); + } + + /** + * 释放当前对象 + *

+ * 当页面被重新导航或者关闭,ElementHandles 会被自动处理。 + */ @Override public void dispose() { this.handle.dispose(); } + /** + * 将 JSHandle 转成 ElementHandle 的方法 + *

+ * 如果对象是 JSHandle 返回 null,如果对象是 ElementHandle,返回当前对象 + * + * @return 当前实例 + */ public ElementHandle asElement() { return this; } + /** + * 与当前对象对应的 Frame + * + * @return Frame实例 + */ + public abstract Frame frame(); + + /** + * 查询当前元素内与给定选择器匹配的第一个元素。 + * + * @param selector 选择器 + * @return 与给定选择器匹配的第一个元素 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS函数执行异常 + */ public ElementHandle $(String selector) throws JsonProcessingException, EvaluateException { - String defaultHandler = "(element, selector) => element.querySelector(selector)"; - QuerySelector queryHandlerAndSelector = QueryHandlerUtil.getQueryHandlerAndSelector(selector, defaultHandler); - JSHandle handle = this.adoptIsolatedHandle().evaluateHandle(queryHandlerAndSelector.getQueryHandler().queryOne(), Collections.singletonList(queryHandlerAndSelector.getUpdatedSelector())); + QuerySelector queryHandlerAndSelector = QueryHandlerUtil.getQueryHandlerAndSelector(selector); + JSHandle handle = this.adoptIsolatedHandle().evaluateHandle(queryHandlerAndSelector.getQueryHandler().querySelector(), Arrays.asList(queryHandlerAndSelector.getUpdatedSelector(), new LazyArg())); ElementHandle element = handle.asElement(); - if (element != null) { + if (Objects.nonNull(element)) { return this.adoptResult(element); } return null; } + /** + * 查询当前元素内与给定选择器匹配的所有元素。 + * + * @param selector 选择器 + * @return 与给定选择器匹配的所有元素 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS函数执行异常 + */ public List $$(String selector) throws JsonProcessingException, EvaluateException { - String defaultHandler = "(element, selector) => element.querySelectorAll(selector)"; - QuerySelector queryHandlerAndSelector = QueryHandlerUtil.getQueryHandlerAndSelector(selector, defaultHandler); - JSHandle arrayHandle = this.adoptIsolatedHandle().evaluateHandle(queryHandlerAndSelector.getQueryHandler().queryAll(), Collections.singletonList(queryHandlerAndSelector.getUpdatedSelector())); + QuerySelector queryHandlerAndSelector = QueryHandlerUtil.getQueryHandlerAndSelector(selector); + JSHandle arrayHandle = this.adoptIsolatedHandle().evaluateHandle(queryHandlerAndSelector.getQueryHandler().querySelectorAll(), Arrays.asList(queryHandlerAndSelector.getUpdatedSelector(), new LazyArg())); Map properties = this.adoptResult(arrayHandle.getProperties()); arrayHandle.dispose(); List result = new ArrayList<>(); @@ -420,109 +346,131 @@ public ElementHandle asElement() { return result; } + /** - * 在当前元素中找到指定选择器的第一个元素,并运行给定的函数。 + * 在当前元素中找到指定选择器的第一个元素,并运行给定的 JS 函数。当前对象是给定 JS 函数的第一个参数 + *

+ * 可以用来获取元素内的属性,修改元素属性等 * * @param selector 在当前元素内查找元素的选择器 - * @param pageFunction 给定的函数 + * @param pptrFunction 给定的函数 * @return 找到的第一个元素的ElementHandle * @throws JsonProcessingException 当处理JSON时发生错误 * @throws EvaluateException 当页面函数执行失败时抛出异常 */ - public Object $eval(String selector, String pageFunction) throws JsonProcessingException, EvaluateException { - return this.$eval(selector, pageFunction, null); + public Object $eval(String selector, String pptrFunction) throws JsonProcessingException, EvaluateException { + return this.$eval(selector, pptrFunction, null); } /** - * 在当前元素中找到指定选择器的第一个元素,并运行给定的函数。 + * 在当前元素中找到指定选择器的第一个元素,并运行给定的 JS 函数。当前对象是给定 JS 函数的第一个参数 + *

+ * 可以用来获取元素内的属性,修改元素属性等 * * @param selector 在当前元素内查找元素的选择器 - * @param pageFunction 给定的函数 - * @param args pageFunction的参数,如果有的话 + * @param pptrFunction 给定的 JS 函数 + * @param args pptrFunction 的参数,如果有的话 * @return 找到的第一个元素的ElementHandle * @throws JsonProcessingException 当处理JSON时发生错误 * @throws EvaluateException 当页面函数执行失败时抛出异常 */ - public Object $eval(String selector, String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("$eval", pageFunction); + public Object $eval(String selector, String pptrFunction, List args) throws JsonProcessingException, EvaluateException { + pptrFunction = withSourcePuppeteerURLIfNone("$eval", pptrFunction); ElementHandle elementHandle = this.$(selector); if (elementHandle == null) throw new JvppeteerException("Error: failed to find element matching selector " + selector); - Object result = elementHandle.evaluate(pageFunction, args); + Object result = elementHandle.evaluate(pptrFunction, args); elementHandle.dispose(); return result; } /** - * 对当前元素中找到所有与给定选择器匹配的元素,并运行给定函数。 + * 对当前元素中找到所有与给定选择器匹配的元素,并运行给定的 JS 函数。当前对象是给定 JS 函数的第一个参数 * * @param selector 在当前元素内查找元素的选择器 - * @param pageFunction 给定的函数 - * @return 找到的所有元素的ElementHandle列表 + * @param pptrFunction 给定的 JS 函数 + * @return 找到的所有元素的 ElementHandle 列表 * @throws JsonProcessingException 当处理JSON时发生错误 * @throws EvaluateException 当页面函数执行失败时抛出异常 */ - public Object $$eval(String selector, String pageFunction) throws JsonProcessingException, EvaluateException { - return this.$$eval(selector, pageFunction, null); + public Object $$eval(String selector, String pptrFunction) throws JsonProcessingException, EvaluateException { + return this.$$eval(selector, pptrFunction, null); } /** - * 对当前元素中找到所有与给定选择器匹配的元素,并运行给定函数。 + * 对当前元素中找到所有与给定选择器匹配的元素,并运行给定的 JS 函数。当前对象是给定 JS 函数的第一个参数 + *

+ * 与 $eval 函数的区别:可以同时对匹配的所有元素进行操作 * * @param selector 在当前元素内查找元素的选择器 - * @param pageFunction 给定的函数 - * @param args pageFunction的参数,如果有的话 + * @param pptrFunction 给定的 JS 函数 + * @param args pptrFunction 的参数,如果有的话 * @return 找到的所有元素的ElementHandle列表 * @throws JsonProcessingException 当处理JSON时发生错误 * @throws EvaluateException 当页面函数执行失败时抛出异常 */ - public Object $$eval(String selector, String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("$$eval", pageFunction); + public Object $$eval(String selector, String pptrFunction, List args) throws JsonProcessingException, EvaluateException { + pptrFunction = withSourcePuppeteerURLIfNone("$$eval", pptrFunction); List results = this.$$(selector); JSHandle arrayHandle = this.evaluateHandle("(_, ...elements) => {\n" + " return elements;\n" + " }", new ArrayList<>(results)); - Object result = arrayHandle.evaluate(pageFunction, args); + Object result = arrayHandle.evaluate(pptrFunction, args); results.forEach(ElementHandle::dispose); arrayHandle.dispose(); return result; } - + /** + * 检查可见性 + * + * @param visibility 要检查可见性 true代表可见,false 代表不可见 + * @return 是否可见 + * @throws JsonProcessingException 当处理JSON时发生错误 + * @throws EvaluateException 当页面函数执行失败时抛出异常 + */ private boolean checkVisibility(boolean visibility) throws JsonProcessingException, EvaluateException { List args = new ArrayList<>(); args.add(new LazyArg()); args.add(visibility); - return (boolean) this.evaluate("function checkVisibility(node, visible) {\n" + - " if (!node) {\n" + - " return visible === false;\n" + - " }\n" + - " if (visible === undefined) {\n" + - " return node;\n" + - " }\n" + - " const element = (\n" + - " node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n" + - " );\n" + - "\n" + - " const style = window.getComputedStyle(element);\n" + - " const rect = element.getBoundingClientRect();\n" + - " const isVisible =\n" + - " style &&\n" + - " !['hidden', 'collapse'].includes(style.visibility) &&\n" + - " !(rect.width === 0 || rect.height === 0);\n" + - " return visible === isVisible ? true : false;\n" + - "}", args); + return (boolean) this.evaluate("async (element, PuppeteerUtil, visibility) => {\n" + + " return Boolean(PuppeteerUtil.checkVisibility(element, visibility));\n" + + " }", args); } + /** + * 当前元素是否可见 + * + * @return true 代表可见 + * @throws JsonProcessingException 当处理JSON时发生错误 + * @throws EvaluateException 当页面函数执行失败时抛出异常 + */ public boolean isVisible() throws JsonProcessingException, EvaluateException { return this.adoptIsolatedHandle().checkVisibility(true); } + /** + * 当前元素是否隐藏 + * + * @return true 代表隐藏 + * @throws JsonProcessingException 当处理JSON时发生错误 + * @throws EvaluateException 当页面函数执行失败时抛出异常 + */ public boolean isHidden() throws JsonProcessingException, EvaluateException { return this.adoptIsolatedHandle().checkVisibility(false); } + /** + * 将当前句柄转换为给定的元素类型。 + *

+ * 如果句柄不匹配,则会出现错误。句柄不会被自动处置。 + * + * @param tagName 给定的元素类型 + * @return 转换后的元素 + * @throws JsonProcessingException 当处理JSON时发生错误 + * @throws EvaluateException 当页面函数执行失败时抛出异常 + */ public ElementHandle toElement(String tagName) throws JsonProcessingException, EvaluateException { boolean isMatchingTagName = (boolean) this.adoptIsolatedHandle().evaluate("(node, tagName) => {\n" + " return node.nodeName === tagName.toUpperCase();\n" + @@ -533,6 +481,13 @@ public ElementHandle toElement(String tagName) throws JsonProcessingException, E return this; } + /** + * 解析与元素关联的框架(如果有)。HTMLIFrameElements 始终存在。 + * + * @return Frame + */ + public abstract Frame contentFrame() throws JsonProcessingException; + /** * 返回元素内的中点,除非提供了特定的偏移量。 * @@ -563,7 +518,6 @@ public Point clickablePoint(Offset offset) throws JsonProcessingException, Evalu return new Point(box.getX() + (box.getWidth() / 2), box.getY() + box.getHeight() / 2); } - @SuppressWarnings("unchecked") private BoundingBox clickableBox() throws JsonProcessingException, EvaluateException { Object boxes = this.evaluate("element => {\n" + " if (!(element instanceof Element)) {\n" + @@ -591,7 +545,7 @@ private BoundingBox clickableBox() throws JsonProcessingException, EvaluateExcep throw new JvppeteerException("Unsupported frame type"); } try { - LinkedHashMap parentBox = (LinkedHashMap) elementHandle.evaluate("element => {\n" + + Object response = elementHandle.evaluate("element => {\n" + " // Element is not visible.\n" + " if (element.getClientRects().length === 0) {\n" + " return null;\n" + @@ -609,12 +563,13 @@ private BoundingBox clickableBox() throws JsonProcessingException, EvaluateExcep " parseInt(style.borderTopWidth, 10),\n" + " };\n" + " }"); - if (parentBox == null) { + if (response == null) { return null; } + PrintMarginParameters parentBox = Constant.OBJECTMAPPER.convertValue(response, PrintMarginParameters.class); for (BoundingBox box : boundingBoxes) { - box.setX(box.getX() + parentBox.get("left")); - box.setY(box.getY() + parentBox.get("top")); + box.setX(box.getX() + parentBox.getLeft()); + box.setY(box.getY() + parentBox.getTop()); } elementHandle.intersectBoundingBoxesWithFrame(boundingBoxes); frame = parentFrame; @@ -673,6 +628,13 @@ public void click() throws JsonProcessingException, EvaluateException { this.click(new ClickOptions()); } + /** + * 如果需要,此方法将元素滚动到视图中,然后使用 Page.mouse 单击元素的中心。如果元素与 DOM 分离,该方法会抛出错误 + * + * @param options 可选的点击参数 + * @throws JsonProcessingException 当处理JSON时发生错误 + * @throws EvaluateException 当页面函数执行失败时抛出异常 + */ public void click(ClickOptions options) throws JsonProcessingException, EvaluateException { ElementHandle wrapThis = this.adoptIsolatedHandle(); wrapThis.scrollIntoViewIfNeeded(); @@ -681,233 +643,291 @@ public void click(ClickOptions options) throws JsonProcessingException, Evaluate } /** - * 此方法返回元素的边界框(相对于主框架),如果元素是 不是布局的一部分,则返回 null(例如:display: none). + * 将当前元素拖动到目标元素上 * - * @return 元素的边界框 - * @throws JsonProcessingException 当处理JSON时发生错误 - * @throws EvaluateException 当页面函数执行失败时抛出异常 + * @param target 目标元素 + * @return 当启用拖动拦截时,将返回拖动负载。 + * @throws JsonProcessingException 抛出异常 */ - public BoundingBox boundingBox() throws JsonProcessingException, EvaluateException { + public DragData drag(ElementHandle target) throws JsonProcessingException { ElementHandle wrapThis = this.adoptIsolatedHandle(); - Object box = wrapThis.evaluate("element => {\n" + - " if (!(element instanceof Element)) {\n" + - " return null;\n" + - " }\n" + - " // Element is not visible.\n" + - " if (element.getClientRects().length === 0) {\n" + - " return null;\n" + - " }\n" + - " const rect = element.getBoundingClientRect();\n" + - " return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};\n" + - " }"); - if (box == null) - return null; - Offset offset = wrapThis.getTopLeftCornerOfFrame(); - if (offset == null) { - return null; + wrapThis.scrollIntoViewIfNeeded(); + Page page = wrapThis.frame().page(); + if (page.isDragInterceptionEnabled()) { + Point source = wrapThis.clickablePoint(); + Point point = target.clickablePoint(); + return page.mouse().drag(source, point); } - JsonNode boxNode = Constant.OBJECTMAPPER.readTree(Constant.OBJECTMAPPER.writeValueAsString(box)); - return new BoundingBox(boxNode.get("x").asDouble() + offset.getX(), boxNode.get("y").asDouble() + offset.getY(), boxNode.get("width").asDouble(), boxNode.get("height").asDouble()); + try { + if (!page.isDragging()) { + page.setDragging(true); + wrapThis.hover(); + page.mouse().down(); + } + target.hover(); + } catch (JsonProcessingException | EvaluateException e) { + page.setDragging(false); + throw e; + } + return null; } - @SuppressWarnings("unchecked") - private Offset getTopLeftCornerOfFrame() throws JsonProcessingException, EvaluateException { - Offset point = new Offset(); - Frame frame = this.frame(); - Frame parentFrame; - while (frame != null && (parentFrame = frame.parentFrame()) != null) { - ElementHandle elementHandle = frame.frameElement(); - if (elementHandle == null) { - throw new JvppeteerException("Unsupported frame type"); + /** + * 将当前元素拖动到指定位置 + * + * @param point 指定位置 + * @return 当启用拖动拦截时,将返回拖动负载。 + * @throws JsonProcessingException 抛出异常 + */ + public DragData drag(Point point) throws JsonProcessingException { + ElementHandle wrapThis = this.adoptIsolatedHandle(); + Page page = wrapThis.frame().page(); + if (page.isDragInterceptionEnabled()) { + Point source = wrapThis.clickablePoint(); + return page.mouse().drag(source, point); + } + try { + if (!page.isDragging()) { + page.setDragging(true); + wrapThis.hover(); + page.mouse().down(); } - LinkedHashMap parentBox = (LinkedHashMap) elementHandle.evaluate("element => {\n" + - " // Element is not visible.\n" + - " if (element.getClientRects().length === 0) {\n" + - " return null;\n" + - " }\n" + - " const rect = element.getBoundingClientRect();\n" + - " const style = window.getComputedStyle(element);\n" + - " return {\n" + - " left:\n" + - " rect.left +\n" + - " parseInt(style.paddingLeft, 10) +\n" + - " parseInt(style.borderLeftWidth, 10),\n" + - " top:\n" + - " rect.top +\n" + - " parseInt(style.paddingTop, 10) +\n" + - " parseInt(style.borderTopWidth, 10),\n" + - " };\n" + - " }"); - if (parentBox == null) { - return null; - } - point.setX(point.getX() + parentBox.get("left")); - point.setY(point.getY() + parentBox.get("top")); - frame = parentFrame; + page.mouse().move(point.getX(), point.getY()); + } catch (JsonProcessingException | EvaluateException e) { + page.setDragging(false); + throw e; } - return point; + return null; } /** - * 此方法返回元素的框,如果元素是 不是布局的一部分,则返回 null(例如:display: none)。 - *

- * 盒子被表示为点数组;每个点都是一个对象 {x, y}。箱点按顺时针顺序排序。 + * 根据给定的值列表来选择元素,并返回选中的值列表 + * 该方法通过在浏览器环境中执行一段JavaScript代码来实现对HTML选择框的操作 + * 包括单选和多选情况的处理,并确保触发相应的输入和变化事件 * - * @return BoxModel - * @throws JsonProcessingException 当处理JSON时发生错误 - * @throws EvaluateException 当页面函数执行失败时抛出异常 + * @param values 要选中的值的列表 + * @return 选中的值的列表 + * @throws JsonProcessingException 如果JavaScript代码序列化失败 + * @throws EvaluateException 如果在浏览器环境中执行JavaScript代码失败 */ - public BoxModel boxModel() throws JsonProcessingException, EvaluateException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - Object response = wrapThis.evaluate("element => {\n" + - " if (!(element instanceof Element)) {\n" + - " return null;\n" + - " }\n" + - " // Element is not visible.\n" + - " if (element.getClientRects().length === 0) {\n" + - " return null;\n" + - " }\n" + - " const rect = element.getBoundingClientRect();\n" + - " const style = window.getComputedStyle(element);\n" + - " const offsets = {\n" + - " padding: {\n" + - " left: parseInt(style.paddingLeft, 10),\n" + - " top: parseInt(style.paddingTop, 10),\n" + - " right: parseInt(style.paddingRight, 10),\n" + - " bottom: parseInt(style.paddingBottom, 10),\n" + - " },\n" + - " margin: {\n" + - " left: -parseInt(style.marginLeft, 10),\n" + - " top: -parseInt(style.marginTop, 10),\n" + - " right: -parseInt(style.marginRight, 10),\n" + - " bottom: -parseInt(style.marginBottom, 10),\n" + - " },\n" + - " border: {\n" + - " left: parseInt(style.borderLeft, 10),\n" + - " top: parseInt(style.borderTop, 10),\n" + - " right: parseInt(style.borderRight, 10),\n" + - " bottom: parseInt(style.borderBottom, 10),\n" + - " },\n" + - " };\n" + - " const border = [\n" + - " {x: rect.left, y: rect.top},\n" + - " {x: rect.left + rect.width, y: rect.top},\n" + - " {x: rect.left + rect.width, y: rect.top + rect.bottom},\n" + - " {x: rect.left, y: rect.top + rect.bottom},\n" + - " ];\n" + - " const padding = transformQuadWithOffsets(border, offsets.border);\n" + - " const content = transformQuadWithOffsets(padding, offsets.padding);\n" + - " const margin = transformQuadWithOffsets(border, offsets.margin);\n" + - " return {\n" + - " content,\n" + - " padding,\n" + - " border,\n" + - " margin,\n" + - " width: rect.width,\n" + - " height: rect.height,\n" + - " };\n" + + @SuppressWarnings("unchecked") + public List select(List values) throws JsonProcessingException, EvaluateException { + /* + * its evaluate function is properly typed with generics we can + * return here and remove the typecasting + */ + String pptrFunction = "(element, vals) => {\n" + + " const values = new Set(vals);\n" + + " if (!(element instanceof HTMLSelectElement)) {\n" + + " throw new Error('Element is not a element.');\n" + - " }\n" + - "\n" + - " const selectedValues = new Set();\n" + - " if (!element.multiple) {\n" + - " for (const option of element.options) {\n" + - " option.selected = false;\n" + - " }\n" + - " for (const option of element.options) {\n" + - " if (values.has(option.value)) {\n" + - " option.selected = true;\n" + - " selectedValues.add(option.value);\n" + - " break;\n" + - " }\n" + - " }\n" + - " } else {\n" + - " for (const option of element.options) {\n" + - " option.selected = values.has(option.value);\n" + - " if (option.selected) {\n" + - " selectedValues.add(option.value);\n" + - " }\n" + - " }\n" + - " }\n" + - " element.dispatchEvent(new Event('input', {bubbles: true}));\n" + - " element.dispatchEvent(new Event('change', {bubbles: true}));\n" + - " return [...selectedValues.values()];\n" + - " }"; - - return (List) this.adoptIsolatedHandle().evaluate(pageFunction, Collections.singletonList(values)); - } - - /** - * 点击元素,如果元素不可见,则滚动到可见。 + * 此方法返回元素的边界框(相对于主框架),如果元素是 不是布局的一部分,则返回 null(例如:display: none). * - * @throws JsonProcessingException 如果序列化失败 - * @throws EvaluateException 如果执行失败 + * @return 元素的边界框 + * @throws JsonProcessingException 当处理JSON时发生错误 + * @throws EvaluateException 当页面函数执行失败时抛出异常 */ - public void tap() throws JsonProcessingException, EvaluateException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - wrapThis.scrollIntoViewIfNeeded(); - Point point = wrapThis.clickablePoint(); - wrapThis.frame().page().touchscreen().tap(point.getX(), point.getY()); - } - - public void touchStart() throws JsonProcessingException, EvaluateException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - wrapThis.scrollIntoViewIfNeeded(); - Point point = wrapThis.clickablePoint(); - wrapThis.frame().page().touchscreen().touchStart(point.getX(), point.getY()); - } - - public void touchMove() throws JsonProcessingException, EvaluateException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - wrapThis.scrollIntoViewIfNeeded(); - Point point = wrapThis.clickablePoint(); - wrapThis.frame().page().touchscreen().touchMove(point.getX(), point.getY()); - } - - public void touchEnd() throws JsonProcessingException, EvaluateException { + public BoundingBox boundingBox() throws JsonProcessingException, EvaluateException { ElementHandle wrapThis = this.adoptIsolatedHandle(); - wrapThis.scrollIntoViewIfNeeded(); - wrapThis.frame().page().touchscreen().touchEnd(); - } - - public void type(String text) throws JsonProcessingException, EvaluateException { - this.type(text, 0); + Object box = wrapThis.evaluate("element => {\n" + + " if (!(element instanceof Element)) {\n" + + " return null;\n" + + " }\n" + + " // Element is not visible.\n" + + " if (element.getClientRects().length === 0) {\n" + + " return null;\n" + + " }\n" + + " const rect = element.getBoundingClientRect();\n" + + " return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};\n" + + " }"); + if (box == null) + return null; + Offset offset = wrapThis.getTopLeftCornerOfFrame(); + if (offset == null) { + return null; + } + JsonNode boxNode = Constant.OBJECTMAPPER.readTree(Constant.OBJECTMAPPER.writeValueAsString(box)); + return new BoundingBox(boxNode.get("x").asDouble() + offset.getX(), boxNode.get("y").asDouble() + offset.getY(), boxNode.get("width").asDouble(), boxNode.get("height").asDouble()); } - public void type(String text, long delay) throws JsonProcessingException, EvaluateException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - wrapThis.focus(); - KeyboardTypeOptions options = new KeyboardTypeOptions(); - options.setDelay(delay); - wrapThis.frame().page().keyboard().type(text, options); + @SuppressWarnings("unchecked") + private Offset getTopLeftCornerOfFrame() throws JsonProcessingException, EvaluateException { + Offset point = new Offset(); + Frame frame = this.frame(); + Frame parentFrame; + while (frame != null && (parentFrame = frame.parentFrame()) != null) { + ElementHandle elementHandle = frame.frameElement(); + if (elementHandle == null) { + throw new JvppeteerException("Unsupported frame type"); + } + LinkedHashMap parentBox = (LinkedHashMap) elementHandle.evaluate("element => {\n" + + " // Element is not visible.\n" + + " if (element.getClientRects().length === 0) {\n" + + " return null;\n" + + " }\n" + + " const rect = element.getBoundingClientRect();\n" + + " const style = window.getComputedStyle(element);\n" + + " return {\n" + + " left:\n" + + " rect.left +\n" + + " parseInt(style.paddingLeft, 10) +\n" + + " parseInt(style.borderLeftWidth, 10),\n" + + " top:\n" + + " rect.top +\n" + + " parseInt(style.paddingTop, 10) +\n" + + " parseInt(style.borderTopWidth, 10),\n" + + " };\n" + + " }"); + if (parentBox == null) { + return null; + } + point.setX(point.getX() + parentBox.get("left")); + point.setY(point.getY() + parentBox.get("top")); + frame = parentFrame; + } + return point; } /** - * 聚焦元素,然后使用 Keyboard.down() 和 Keyboard.up()。 - *

- * 如果 key 是单个字符,并且除了 Shift 之外没有按下修饰键,则还将生成 keypress/input 事件。可以指定 text 选项来强制生成输入事件。 + * 此方法返回元素的框,如果元素是 不是布局的一部分,则返回 null(例如:display: none)。 *

- * 注意修饰键确实会影响 elementHandle.press。按住 Shift 将以大写形式键入文本。 - *

+ * 盒子被表示为点数组;每个点都是一个对象 {x, y}。箱点按顺时针顺序排序。 * - * @param key 要按下的键 + * @return BoxModel + * @throws JsonProcessingException 当处理JSON时发生错误 + * @throws EvaluateException 当页面函数执行失败时抛出异常 */ - public void press(String key) throws JsonProcessingException, EvaluateException { - this.press(key, new KeyPressOptions()); + public BoxModel boxModel() throws JsonProcessingException, EvaluateException { + ElementHandle wrapThis = this.adoptIsolatedHandle(); + Object response = wrapThis.evaluate("element => {\n" + + " if (!(element instanceof Element)) {\n" + + " return null;\n" + + " }\n" + + " // Element is not visible.\n" + + " if (element.getClientRects().length === 0) {\n" + + " return null;\n" + + " }\n" + + " const rect = element.getBoundingClientRect();\n" + + " const style = window.getComputedStyle(element);\n" + + " const offsets = {\n" + + " padding: {\n" + + " left: parseInt(style.paddingLeft, 10),\n" + + " top: parseInt(style.paddingTop, 10),\n" + + " right: parseInt(style.paddingRight, 10),\n" + + " bottom: parseInt(style.paddingBottom, 10),\n" + + " },\n" + + " margin: {\n" + + " left: -parseInt(style.marginLeft, 10),\n" + + " top: -parseInt(style.marginTop, 10),\n" + + " right: -parseInt(style.marginRight, 10),\n" + + " bottom: -parseInt(style.marginBottom, 10),\n" + + " },\n" + + " border: {\n" + + " left: parseInt(style.borderLeft, 10),\n" + + " top: parseInt(style.borderTop, 10),\n" + + " right: parseInt(style.borderRight, 10),\n" + + " bottom: parseInt(style.borderBottom, 10),\n" + + " },\n" + + " };\n" + + " const border = [\n" + + " {x: rect.left, y: rect.top},\n" + + " {x: rect.left + rect.width, y: rect.top},\n" + + " {x: rect.left + rect.width, y: rect.top + rect.bottom},\n" + + " {x: rect.left, y: rect.top + rect.bottom},\n" + + " ];\n" + + " const padding = transformQuadWithOffsets(border, offsets.border);\n" + + " const content = transformQuadWithOffsets(padding, offsets.padding);\n" + + " const margin = transformQuadWithOffsets(border, offsets.margin);\n" + + " return {\n" + + " content,\n" + + " padding,\n" + + " border,\n" + + " margin,\n" + + " width: rect.width,\n" + + " height: rect.height,\n" + + " };\n" + + "\n" + + " function transformQuadWithOffsets(\n" + + " quad,\n" + + " offsets\n" + + " ) {\n" + + " return [\n" + + " {\n" + + " x: quad[0].x + offsets.left,\n" + + " y: quad[0].y + offsets.top,\n" + + " },\n" + + " {\n" + + " x: quad[1].x - offsets.right,\n" + + " y: quad[1].y + offsets.top,\n" + + " },\n" + + " {\n" + + " x: quad[2].x - offsets.right,\n" + + " y: quad[2].y - offsets.bottom,\n" + + " },\n" + + " {\n" + + " x: quad[3].x + offsets.left,\n" + + " y: quad[3].y - offsets.bottom,\n" + + " },\n" + + " ];\n" + + " }\n" + + "}"); + if (response == null) { + return null; + } + Offset offset = wrapThis.getTopLeftCornerOfFrame(); + if (offset == null) { + return null; + } + BoxModel model = Constant.OBJECTMAPPER.readValue(Constant.OBJECTMAPPER.writeValueAsString(response), BoxModel.class); + model.getContent().forEach(point -> { + point.setX(point.getX() + offset.getX()); + point.setY(point.getY() + offset.getY()); + }); + model.getPadding().forEach(point -> { + point.setX(point.getX() + offset.getX()); + point.setY(point.getY() + offset.getY()); + }); + model.getBorder().forEach(point -> { + point.setX(point.getX() + offset.getX()); + point.setY(point.getY() + offset.getY()); + }); + model.getMargin().forEach(point -> { + point.setX(point.getX() + offset.getX()); + point.setY(point.getY() + offset.getY()); + }); + return model; } - /** - * 聚焦元素,然后使用 Keyboard.down() 和 Keyboard.up()。 - * - * @param key 要按下的键 - * @param options 选项 - */ - public void press(String key, KeyPressOptions options) throws JsonProcessingException, EvaluateException { - this.adoptIsolatedHandle().focus(); - this.adoptIsolatedHandle().frame().page().keyboard().press(key, options); + public String screenshot(String path) throws IOException, EvaluateException, ExecutionException, InterruptedException { + ElementScreenshotOptions options = new ElementScreenshotOptions(); + options.setPath(path); + return this.screenshot(options); } /** - * 将当前元素拖动到目标元素上 + * 如果需要,此方法将元素滚动到视图中,然后使用 Page.screenshot() 截取元素的屏幕截图。如果元素与 DOM 分离,该方法会抛出错误。 * - * @param target 目标元素 - * @return 当启用拖动拦截时,将返回拖动负载。 - * @throws JsonProcessingException 抛出异常 + * @param options 截图配置 + * @return 返回图片路径 + * @throws IOException IO异常 + * @throws EvaluateException 截图异常 */ - public DragData drag(ElementHandle target) throws JsonProcessingException { + public String screenshot(ElementScreenshotOptions options) throws IOException, EvaluateException, ExecutionException, InterruptedException { ElementHandle wrapThis = this.adoptIsolatedHandle(); - wrapThis.scrollIntoViewIfNeeded(); Page page = wrapThis.frame().page(); - if (page.isDragInterceptionEnabled()) { - Point source = wrapThis.clickablePoint(); - Point point = target.clickablePoint(); - return page.mouse().drag(source, point); + if (options.getScrollIntoView()) { + this.scrollIntoViewIfNeeded(); } - try { - if (!page.isDragging()) { - page.setIsDragging(true); - wrapThis.hover(); - page.mouse().down(); - } - target.hover(); - } catch (JsonProcessingException | EvaluateException e) { - page.setIsDragging(false); - throw e; + BoundingBox elementClip = wrapThis.nonEmptyVisibleBoundingBox(); + Object arr = wrapThis.evaluate("() => {\n" + + " if (!window.visualViewport) {\n" + + " throw new Error('window.visualViewport is not supported.');\n" + + " }\n" + + " return [\n" + + " window.visualViewport.pageLeft,\n" + + " window.visualViewport.pageTop,\n" + + " ];\n" + + " }"); + JsonNode arrNode = Constant.OBJECTMAPPER.readTree(Constant.OBJECTMAPPER.writeValueAsString(arr)); + + elementClip.setX(elementClip.getX() + arrNode.get(0).asDouble()); + elementClip.setY(elementClip.getY() + arrNode.get(1).asDouble()); + if (options.getClip() != null) { + elementClip.setX(elementClip.getX() + options.getClip().getX()); + elementClip.setY(elementClip.getY() + options.getClip().getY()); + elementClip.setWidth(options.getClip().getWidth()); + elementClip.setHeight(options.getClip().getHeight()); } - return null; + options.setClip(new ScreenshotClip(elementClip.getX(), elementClip.getY(), elementClip.getWidth(), elementClip.getHeight(), 1)); + return page.screenshot(options); } /** - * 将当前元素拖动到指定位置 + * 等待直到指定的选择器匹配的元素满足某些条件(可见、隐藏或存在). * - * @param point 指定位置 - * @return 当启用拖动拦截时,将返回拖动负载。 - * @throws JsonProcessingException 抛出异常 + * @param selector 选择器字符串,用于选择目标元素. + * @param options 包含等待条件的选项,如元素可见或隐藏. + * @return 返回匹配选择器的目标元素的句柄. */ - public DragData drag(Point point) throws JsonProcessingException { - ElementHandle wrapThis = this.adoptIsolatedHandle(); - Page page = wrapThis.frame().page(); - if (page.isDragInterceptionEnabled()) { - Point source = wrapThis.clickablePoint(); - return page.mouse().drag(source, point); - } - try { - if (!page.isDragging()) { - page.setIsDragging(true); - wrapThis.hover(); - page.mouse().down(); - } - page.mouse().move(point.getX(), point.getY()); - } catch (JsonProcessingException | EvaluateException e) { - page.setIsDragging(false); - throw e; - } - return null; + public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) throws JsonProcessingException { + QuerySelector querySelector = QueryHandlerUtil.getQueryHandlerAndSelector(selector); + options.setPolling(querySelector.getPolling()); + return querySelector.getQueryHandler().waitFor(this.adoptIsolatedHandle(), selector, options); + } + + private BoundingBox nonEmptyVisibleBoundingBox() throws JsonProcessingException, EvaluateException { + BoundingBox box = this.boundingBox(); + Objects.requireNonNull(box, "Node is either not visible or not an HTMLElement"); + ValidateUtil.assertArg(box.getWidth() != 0, "Node has 0 width."); + ValidateUtil.assertArg(box.getHeight() != 0, "Node has 0 height."); + return box; } - public JSHandle handle() { + private JSHandle handle() { return this.handle; } } - diff --git a/src/main/java/com/ruiyun/jvppeteer/events/EventEmitter.java b/src/main/java/com/ruiyun/jvppeteer/api/core/EventEmitter.java similarity index 74% rename from src/main/java/com/ruiyun/jvppeteer/events/EventEmitter.java rename to src/main/java/com/ruiyun/jvppeteer/api/core/EventEmitter.java index b9ce4e3e..a0900a63 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/EventEmitter.java +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/EventEmitter.java @@ -1,9 +1,11 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.api.core; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -14,9 +16,9 @@ * 事件发布,事件监听,模仿nodejs的EventEmitter */ public class EventEmitter { - private static final Logger LOGGER = LoggerFactory.getLogger(EventEmitter.class); + protected static final Logger LOGGER = LoggerFactory.getLogger(EventEmitter.class); /** - * 事件发布,事件监听,模仿nodejs的EventEmitter + * 储存所有的监听器 */ private final Map>> listeners = new ConcurrentHashMap<>(); @@ -40,14 +42,27 @@ public EventEmitter on(EventType eventType, Consumer listener) { * @param listener 事件的处理器 */ public void off(EventType eventType, Consumer listener) { - List> list = listeners.get(eventType); - if (list == null) { - return; - } - list.removeAll(Collections.singleton(listener)); - if (list.isEmpty()) { - listeners.remove(eventType); + if (Objects.isNull(eventType)) { + Iterator>>> iterator = listeners.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry>> entry = iterator.next(); + entry.getValue().remove(listener); + } + } else { + List> list = listeners.get(eventType); + if (Objects.isNull(list)) { + return; + } + if (Objects.isNull(listener)) { + listeners.remove(eventType); + } else { + list.removeAll(Collections.singleton(listener)); + if (list.isEmpty()) { + listeners.remove(eventType); + } + } } + } /** @@ -99,8 +114,8 @@ public int listenerCount(EventType eventType) { * * @param eventType 事件类型 */ - public void removeAllListener(EventType eventType) { - if (eventType == null) { + public void removeAllListeners(EventType eventType) { + if (Objects.isNull(eventType)) { this.listeners.clear(); return; } @@ -124,7 +139,7 @@ public void removeListener(EventType eventType, Consumer listener) { /** * 释放所有监听器 */ - public void dispose() { + public void disposeSymbol() { this.listeners.clear(); } diff --git a/src/main/java/com/ruiyun/jvppeteer/api/core/Frame.java b/src/main/java/com/ruiyun/jvppeteer/api/core/Frame.java new file mode 100644 index 00000000..596e87be --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/api/core/Frame.java @@ -0,0 +1,682 @@ +package com.ruiyun.jvppeteer.api.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.ruiyun.jvppeteer.api.events.FrameEvents; +import com.ruiyun.jvppeteer.cdp.core.Accessibility; +import com.ruiyun.jvppeteer.cdp.entities.ClickOptions; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateType; +import com.ruiyun.jvppeteer.cdp.entities.FrameAddScriptTagOptions; +import com.ruiyun.jvppeteer.cdp.entities.FrameAddStyleTagOptions; +import com.ruiyun.jvppeteer.cdp.entities.GoToOptions; +import com.ruiyun.jvppeteer.cdp.entities.WaitForOptions; +import com.ruiyun.jvppeteer.cdp.entities.WaitForSelectorOptions; +import com.ruiyun.jvppeteer.common.DeviceRequestPrompt; +import com.ruiyun.jvppeteer.common.QuerySelector; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.util.QueryHandlerUtil; +import com.ruiyun.jvppeteer.util.StringUtil; +import com.ruiyun.jvppeteer.util.ValidateUtil; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + + +import static com.ruiyun.jvppeteer.common.Constant.DEFAULT_BATCH_SIZE; +import static com.ruiyun.jvppeteer.util.Helper.withSourcePuppeteerURLIfNone; + +public abstract class Frame extends EventEmitter { + protected String id; + protected String parentId; + protected Accessibility accessibility; + protected volatile boolean hasStartedLoading; + protected String name; + protected ElementHandle document; + + public Frame() { + super(); + } + + /** + * 与框架关联的页面。 + * + * @return 与框架关联的页面。 + */ + public abstract Page page(); + + /** + * 将框架或页面导航到给定的 url。 + * + * @param url 将框架导航到的 URL。URL 应包含方案,例如 https:// + * @param options 可选的配置等待行为的选项。 + * @return waitForResult = true 返回页面导航的响应,如果存在多个重定向,导航将使用最后一个重定向的响应进行解析。否则返回 null + */ + public abstract Response goTo(String url, GoToOptions options); + + /** + * 等到导航完成 + * + * @param options 可选的等待导航选项 + * @param navigateRunner 一个需要的执行的步骤 + * @return 导航的响应 + */ + public abstract Response waitForNavigation(WaitForOptions options, Runnable navigateRunner); + + /** + * 当前框架的客户端 + * + * @return 客户端 + */ + public abstract CDPSession client(); + + public abstract Accessibility accessibility(); + + public abstract Realm mainRealm(); + + public abstract Realm isolatedRealm(); + + /** + * 返回当前框架的 Document 对象 + * + * @return 当前框架的 Document 对象 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException 执行 JS 函数异常 + */ + public ElementHandle document() throws JsonProcessingException { + if (this.document == null) { + this.document = this.mainRealm().evaluateHandle("() => {\n" + + " return document;\n" + + " }", null).asElement(); + } + return this.document; + } + + /** + * 用来清理已经被释放的 Document 句柄 + */ + public void clearDocumentHandle() { + this.document = null; + } + + /** + * 返回与此框架相关联的元素 + * + * @return 与此框架相关联的元素 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public ElementHandle frameElement() throws JsonProcessingException { + Frame parentFrame = this.parentFrame(); + if (Objects.isNull(parentFrame)) { + return null; + } + JSHandle list = parentFrame.isolatedRealm().evaluateHandle("() => {\n" + + " return document.querySelectorAll('iframe,frame');\n" + + " }", null); + ElementHandle result = null; + List lists = this.transposeIterableHandle(list); + try { + Iterator iterator = lists.iterator(); + while (iterator.hasNext()) { + JSHandle iframe = iterator.next(); + Frame frame = iframe.asElement().contentFrame(); + if (frame != null && frame.id().equals(this.id)) { + result = iframe.asElement(); + iterator.remove(); + break; + } + } + } finally { + lists.forEach(JSHandle::dispose); + Optional.of(list).ifPresent(JSHandle::dispose); + } + if (Objects.isNull(result)) { + return null; + } + return parentFrame.mainRealm().adoptHandle(result); + } + + /** + * 行为与 Page.evaluateHandle() 相同,只是它在此框架的上下文中运行。 + *

+ * 详情请参阅 Page.evaluateHandle()。 + * + * @param pptrFunction 给定的 JS 函数 + * @param args JS 函数的参数 + * @return pptrFunction 执行的结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public JSHandle evaluateHandle(String pptrFunction, List args) throws JsonProcessingException { + pptrFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pptrFunction); + return this.mainRealm().evaluateHandle(pptrFunction, args); + } + + /** + * 行为与 Page.evaluate() 相同,只是它在此框架的上下文中运行。 + *

+ * 详情请参阅 Page.evaluate()。 + * + * @param pptrFunction 给定的 JS 函数 + * @param type 指定了 pptrFunction 的类型 + * @param args pptrFunction 的参数 + * @return pptrFunction 的执行结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public Object evaluate(String pptrFunction, EvaluateType type, List args) throws JsonProcessingException { + pptrFunction = withSourcePuppeteerURLIfNone("evaluate", pptrFunction); + return this.mainRealm().evaluate(pptrFunction, type, args); + } + + /** + * 行为与 Page.evaluate() 相同,只是它在此框架的上下文中运行。 + *

+ * 详情请参阅 Page.evaluate()。 + * + * @param pptrFunction 给定的 JS 函数 + * @return pptrFunction 的执行结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public Object evaluate(String pptrFunction) throws JsonProcessingException { + return this.evaluate(pptrFunction, null, null); + } + + /** + * 行为与 Page.evaluate() 相同,只是它在此框架的上下文中运行。 + *

+ * 详情请参阅 Page.evaluate()。 + * + * @param pptrFunction 给定的 JS 函数 + * @param args pptrFunction 的参数 + * @return pptrFunction 的执行结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public Object evaluate(String pptrFunction, List args) throws JsonProcessingException { + return this.evaluate(pptrFunction, null, args); + } + + /** + * 查询框架中与给定选择器匹配的第一个元素。 + * + * @param selector 选择器 + * @return 与给定选择器匹配的第一个元素 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public ElementHandle $(String selector) throws JsonProcessingException { + ElementHandle document = this.document(); + return document.$(selector); + } + + /** + * 查询框架中与给定选择器匹配的所有元素。 + * + * @param selector 选择器 + * @return 与给定选择器匹配的所有元素 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public List $$(String selector) throws JsonProcessingException { + ElementHandle document = this.document(); + return document.$$(selector); + } + + /** + * 对与框架中给定选择器匹配的第一个元素运行给定 JS 函数。 + *

+ * 如果给定的函数返回一个 Promise,那么此方法将等待直到 Promise 解析。 + * + * @param selector 选择器 + * @param pptrFunction 给定 JS 函数 + * @param args pptrFunction 的参数 + * @return pptrFunction 运行的结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public Object $eval(String selector, String pptrFunction, List args) throws JsonProcessingException { + pptrFunction = withSourcePuppeteerURLIfNone("$eval", pptrFunction); + ElementHandle document = this.document(); + return document.$eval(selector, pptrFunction, args); + } + + /** + * 对与框架中给定选择器匹配的元素数组运行给定 JS 函数。 + *

+ * 如果给定的函数返回一个 Promise,那么此方法将等待直到 Promise 解析。 + * + * @param selector 选择器 + * @param pptrFunction 给定 JS 函数 + * @param args pptrFunction 的参数 + * @return pptrFunction 运行的结果 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public Object $$eval(String selector, String pptrFunction, List args) throws JsonProcessingException { + pptrFunction = withSourcePuppeteerURLIfNone("$$eval", pptrFunction); + ElementHandle document = this.document(); + return document.$$eval(selector, pptrFunction, args); + } + + /** + * 等待直到指定的选择器匹配的元素满足某些条件(可见、隐藏或存在). + * + * @param selector 选择器字符串,用于选择目标元素. + * @param options 包含等待条件的选项,如元素可见或隐藏. + * @return 返回匹配选择器的目标元素的句柄. + */ + public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) { + QuerySelector querySelector = QueryHandlerUtil.getQueryHandlerAndSelector(selector); + options.setPolling(querySelector.getPolling()); + return querySelector.getQueryHandler().waitFor(this, selector, options); + } + + /** + * 等待 pptrFunction 执行完成 + * + * @param pptrFunction 等待的函数 + * @param options 可选配置 + * @param args pptrFunction 的参数 + * @return pptrFunction 执行的结果 + */ + public JSHandle waitForFunction(String pptrFunction, WaitForSelectorOptions options,EvaluateType type, Object... args) throws ExecutionException, InterruptedException, TimeoutException { + return this.mainRealm().waitForFunction(pptrFunction, options, type, args); + } + + /** + * 框架的完整 HTML 内容,包括 DOCTYPE。 + * + * @return 框架的完整 HTML 内容,包括 DOCTYPE。 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public String content() throws JsonProcessingException, EvaluateException { + return (String) this.evaluate("() => {\n" + + " let content = '';\n" + + " for (const node of document.childNodes) {\n" + + " switch (node) {\n" + + " case document.documentElement:\n" + + " content += document.documentElement.outerHTML;\n" + + " break;\n" + + " default:\n" + + " content += new XMLSerializer().serializeToString(node);\n" + + " break;\n" + + " }\n" + + " }\n" + + "\n" + + " return content;\n" + + " }"); + } + + /** + * 设置框架的内容 + * + * @param html 要分配给页面的 HTML 标记。 + * @param options (可选的)用于配置超时前多长时间以及何时认为内容设置成功的选项。 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public abstract void setContent(String html, WaitForOptions options) throws JsonProcessingException, InterruptedException, ExecutionException; + + /** + * 设置框架的内容 + * + * @param content 要分配给页面的 HTML 标记。 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public void setFrameContent(String content) throws JsonProcessingException { + this.evaluate("(content) => {\n" + + " document.open();\n" + + " document.write(content);\n" + + " document.close();\n" + + " }", Collections.singletonList(content)); + } + + /** + * 框架的名字 + * + * @return 框架的名字 + */ + public String name() { + return this.name == null ? "" : this.name; + } + + /** + * 框架的 url + * + * @return 框架的 url + */ + public abstract String url(); + + /** + * 父框架(如果有)。分离框架和主框架返回 null。 + * + * @return 父框架 + */ + public abstract Frame parentFrame(); + + /** + * 子框架数组。 + * + * @return 子框架数组。 + */ + public abstract List childFrames(); + + /** + * Is`true` if the frame has been detached. Otherwise, `false`. + * + * @return true or false + */ + public abstract boolean detached(); + + public boolean disposed() { + return this.detached(); + } + + + /** + * 在当前文档中添加一个脚本标签。 + *

+ * 此方法使用提供的选项创建一个脚本标签,可以是通过URL、文件路径或直接通过内容来加载脚本。 + * 它支持异步执行,并处理脚本加载的生命周期事件,如'load'和'error'。 + * + * @param options 脚本标签的选项,包括URL、路径或内容等。 + * @return 返回新创建的脚本元素的句柄。 + * @throws IOException 当读取脚本文件时发生IO错误。 + * @throws EvaluateException 当脚本执行失败时抛出。 + */ + public ElementHandle addScriptTag(FrameAddScriptTagOptions options) throws IOException, EvaluateException { + if (options == null) { + throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); + } + if (StringUtil.isEmpty(options.getUrl()) && StringUtil.isEmpty(options.getPath()) && StringUtil.isEmpty(options.getContent())) { + throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); + } + if (StringUtil.isEmpty(options.getType())) { + options.setType("text/javascript"); + } + if (StringUtil.isNotEmpty(options.getPath())) { + List contents = Files.readAllLines(Paths.get(options.getPath()), StandardCharsets.UTF_8); + options.setContent(String.join("\n", contents) + "//# sourceURL=" + options.getPath().replaceAll("\n", "")); + } + return this.mainRealm().evaluateHandle("async ({url, id, type, content}) => {\n" + + " return await new Promise((resolve, reject) => {\n" + + " const script = document.createElement('script');\n" + + " script.type = type;\n" + + " script.text = content;\n" + + " script.addEventListener(\n" + + " 'error',\n" + + " event => {\n" + + " reject(new Error(event.message ?? 'Could not load script'));\n" + + " },\n" + + " {once: true}\n" + + " );\n" + + " if (id) {\n" + + " script.id = id;\n" + + " }\n" + + " if (url) {\n" + + " script.src = url;\n" + + " script.addEventListener(\n" + + " 'load',\n" + + " () => {\n" + + " resolve(script);\n" + + " },\n" + + " {once: true}\n" + + " );\n" + + " document.head.appendChild(script);\n" + + " } else {\n" + + " document.head.appendChild(script);\n" + + " resolve(script);\n" + + " }\n" + + " });\n" + + " }", Collections.singletonList(options)).asElement(); + + } + + /** + * 向文档头部添加样式标签 + *

+ * 该方法用于在HTML文档的头部内插入一个新的样式标签。它支持通过URL链接到外部样式表, + * 或者直接包含样式内容。当提供样式文件的路径时,将读取该文件的内容并作为内联样式添加。 + * + * @param options 样式标签的配置选项,包括url、path或content属性 + * @return 插入的样式元素的句柄 + * @throws IOException 当读取样式文件时可能抛出的IO异常 + * @throws EvaluateException 当在页面上执行JavaScript时发生错误时抛出的异常 + */ + public ElementHandle addStyleTag(FrameAddStyleTagOptions options) throws IOException, EvaluateException { + if (options == null) { + throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); + } + if (StringUtil.isEmpty(options.getUrl()) && StringUtil.isEmpty(options.getPath()) && StringUtil.isEmpty(options.getContent())) { + throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); + } + String content; + if (StringUtil.isNotEmpty(options.getPath())) { + List contents = Files.readAllLines(Paths.get(options.getPath()), StandardCharsets.UTF_8); + content = String.join("\n", contents) + "/*# sourceURL=" + options.getPath().replaceAll("\n", "") + "*/"; + options.setContent(content); + } + return this.mainRealm().transferHandle(this.isolatedRealm().evaluateHandle("async ({url, content}) => {\n" + + " return await new Promise(\n" + + " (resolve, reject) => {\n" + + " let element;\n" + + " if (!url) {\n" + + " element = document.createElement('style');\n" + + " element.appendChild(document.createTextNode(content));\n" + + " } else {\n" + + " const link = document.createElement('link');\n" + + " link.rel = 'stylesheet';\n" + + " link.href = url;\n" + + " element = link;\n" + + " }\n" + + " element.addEventListener(\n" + + " 'load',\n" + + " () => {\n" + + " resolve(element);\n" + + " },\n" + + " {once: true}\n" + + " );\n" + + " element.addEventListener(\n" + + " 'error',\n" + + " event => {\n" + + " reject(\n" + + " new Error(\n" + + " (event ).message ?? 'Could not load style'\n" + + " )\n" + + " );\n" + + " },\n" + + " {once: true}\n" + + " );\n" + + " document.head.appendChild(element);\n" + + " return element;\n" + + " }\n" + + " );\n" + + " }", Collections.singletonList(options))).asElement(); + } + + /** + * 单击找到的第一个与 selector 匹配的元素。 + * + * @param selector 选择器 + * @param options (可选的)配置 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public void click(String selector, ClickOptions options) throws JsonProcessingException, EvaluateException { + ElementHandle handle = this.$(selector); + Objects.requireNonNull(handle, "No node found for selector: " + selector); + handle.click(options); + handle.dispose(); + } + + /** + * 聚焦与 selector 匹配的第一个元素。 + * + * @param selector 选择器 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public void focus(String selector) throws JsonProcessingException, EvaluateException { + ElementHandle handle = this.$(selector); + ValidateUtil.assertArg(handle != null, "No node found for selector: " + selector); + handle.focus(); + handle.dispose(); + } + + /** + * 将指针悬停在与 selector 匹配的第一个元素的中心上 + * + * @param selector 选择器 + * @throws JsonProcessingException 序列化异常 + * @throws EvaluateException JS 函数执行异常 + */ + public void hover(String selector) throws JsonProcessingException, EvaluateException { + ElementHandle handle = this.$(selector); + ValidateUtil.assertArg(handle != null, "No node found for selector: " + selector); + handle.hover(); + handle.dispose(); + } + + /** + * 在第一个 select 元素上选择与 selector 匹配的一组值。 + * + * @param selector 选择器 + * @param values 要选择的值的数组。如果 "); + List files = filePaths.stream().map(filePath -> { + Path absolutePath = Paths.get(filePath).toAbsolutePath(); + boolean readable = Files.isReadable(absolutePath); + if (!readable) { + throw new AccessControlException(filePath + "is not readable"); + } + return absolutePath.toString(); + }).collect(Collectors.toList()); + // The zero-length array is a special case, it seems that DOM.setFileInputFiles does + // not actually update the files in that case, so the solution is to eval the element + // value to a new FileList directly. + if (files.isEmpty()) { + String pptrFunction = "element => {\n" + + " element.files = new DataTransfer().files;\n" + + "\n" + + " // Dispatch events for this case because it should behave akin to a user action.\n" + + " element.dispatchEvent(\n" + + " new Event('input', {bubbles: true, composed: true})\n" + + " );\n" + + " element.dispatchEvent(new Event('change', {bubbles: true}));\n" + + " }"; + wrapThis.evaluate(pptrFunction); + } else { + String objectId = wrapThis.id(); + Map params = ParamsFactory.create(); + params.put("objectId", objectId); + JsonNode node = ((CdpElementHandle) wrapThis).client().send("DOM.describeNode", params); + int backendNodeId = node.get("node").get("backendNodeId").asInt(); + params.clear(); + params.put("objectId", objectId); + params.put("files", files); + params.put("backendNodeId", backendNodeId); + ((CdpElementHandle) wrapThis).client().send("DOM.setFileInputFiles", params); + } + } + + @Override + public void autofill(AutofillData data) { + Map params = ParamsFactory.create(); + params.put("objectId", this.id()); + JsonNode response = this.client().send("DOM.describeNode", params); + int fieldId = response.get("node").get("backendNodeId").asInt(); + String frameId = this.frame().id(); + params.clear(); + params.put("fieldId", fieldId); + params.put("frameId", frameId); + params.put("card", data.getCreditCard()); + this.client().send("Autofill.trigger", params); + } + + @Override + public List queryAXTree(String name, String role) throws JsonProcessingException { + Map params = ParamsFactory.create(); + params.put("objectId", this.id()); + params.put("accessibleName", name); + params.put("role", role); + JsonNode response = this.client().send("Accessibility.queryAXTree", params); + JsonNode nodes = response.get("nodes"); + Iterator elements = nodes.elements(); + List result = new ArrayList<>(); + while (elements.hasNext()) { + JsonNode node = elements.next(); + if (node.hasNonNull("ignored") && node.get("ignored").asBoolean() || !node.hasNonNull("role") || NON_ELEMENT_NODE_ROLES.contains(node.get("role").get("value").asText())) { + continue; + } + result.add(this.realm().adoptBackendNode(node.get("backendDOMNodeId").asInt()).asElement()); + } + return result; + } + +} + diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpFrame.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpFrame.java new file mode 100644 index 00000000..765be8af --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpFrame.java @@ -0,0 +1,407 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.FrameEvents; +import com.ruiyun.jvppeteer.common.DeviceRequestPrompt; +import com.ruiyun.jvppeteer.common.DeviceRequestPromptManager; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; +import com.ruiyun.jvppeteer.cdp.entities.Binding; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateType; +import com.ruiyun.jvppeteer.cdp.entities.FramePayload; +import com.ruiyun.jvppeteer.cdp.entities.GoToOptions; +import com.ruiyun.jvppeteer.cdp.entities.PreloadScript; +import com.ruiyun.jvppeteer.cdp.entities.WaitForOptions; +import com.ruiyun.jvppeteer.cdp.events.BindingCalledEvent; +import com.ruiyun.jvppeteer.cdp.events.ConsoleAPICalledEvent; +import com.ruiyun.jvppeteer.cdp.events.IsolatedWorldEmitter; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.exception.TimeoutException; +import com.ruiyun.jvppeteer.util.Helper; +import com.ruiyun.jvppeteer.util.StringUtil; +import com.ruiyun.jvppeteer.util.ValidateUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + + +import static com.ruiyun.jvppeteer.common.Constant.CDP_BINDING_PREFIX; +import static com.ruiyun.jvppeteer.common.Constant.MAIN_WORLD; +import static com.ruiyun.jvppeteer.common.Constant.PUPPETEER_WORLD; + +public class CdpFrame extends Frame { + private String url; + private boolean detached; + private CDPSession client; + private final FrameManager frameManager; + private String loaderId; + + private final Set lifecycleEvents = new HashSet<>(); + + private final Map worlds = new HashMap<>(); + + public Map worlds() { + return worlds; + } + + public CdpFrame(FrameManager frameManager, String frameId, String parentFrameId, CDPSession client) { + super(); + this.frameManager = frameManager; + this.url = ""; + this.id = frameId; + this.parentId = parentFrameId; + this.client = client; + this.detached = false; + this.loaderId = ""; + this.worlds.put(MAIN_WORLD, new IsolatedWorld(this, null, this.frameManager.timeoutSettings())); + this.worlds.put(PUPPETEER_WORLD, new IsolatedWorld(this, null, this.frameManager.timeoutSettings())); + this.accessibility = new Accessibility(this.worlds.get(MAIN_WORLD)); + this.on(FrameEvents.FrameSwappedByActivation, (ignore) -> { + this.onLoadingStarted(); + this.onLoadingStopped(); + }); + this.worlds.get(MAIN_WORLD).emitter().on(IsolatedWorldEmitter.IsolatedWorldEventType.Consoleapicalled, (event) -> this.onMainWorldConsoleApiCalled((ConsoleAPICalledEvent) event)); + this.worlds.get(MAIN_WORLD).emitter().on(IsolatedWorldEmitter.IsolatedWorldEventType.Bindingcalled, (event) -> this.onMainWorldBindingCalled((BindingCalledEvent) event)); + } + + private void onMainWorldBindingCalled(BindingCalledEvent event) { + List args = new ArrayList<>(); + args.add(this.worlds.get(MAIN_WORLD)); + args.add(event); + this.frameManager.emit(FrameManager.FrameManagerEvent.BindingCalled, args); + } + + private void onMainWorldConsoleApiCalled(ConsoleAPICalledEvent event) { + this.frameManager.emit(FrameManager.FrameManagerEvent.ConsoleApiCalled, new Object[]{this.worlds.get(MAIN_WORLD), event}); + } + + public CDPSession client() { + return this.client; + } + + public void updateId(String id) { + this.id = id; + } + + public void updateClient(CDPSession client) { + this.client = client; + } + + public Page page() { + return this.frameManager.page(); + } + + public CdpResponse goTo(String url, GoToOptions options) { + String referrer; + String refererPolicy; + List waitUntil; + Integer timeout; + if (options == null) { + referrer = this.frameManager.networkManager().extraHTTPHeaders().get("referer"); + refererPolicy = this.frameManager.networkManager().extraHTTPHeaders().get("referer_policy"); + waitUntil = new ArrayList<>(); + waitUntil.add(PuppeteerLifeCycle.load); + timeout = this.frameManager().timeoutSettings().navigationTimeout(); + } else { + if (StringUtil.isEmpty(referrer = options.getReferer())) { + referrer = this.frameManager.networkManager().extraHTTPHeaders().get("referer"); + } + if (ValidateUtil.isEmpty(waitUntil = options.getWaitUntil())) { + waitUntil = new ArrayList<>(); + waitUntil.add(PuppeteerLifeCycle.load); + } + if ((timeout = options.getTimeout()) == null) { + timeout = this.frameManager.timeoutSettings().navigationTimeout(); + } + if (StringUtil.isEmpty(refererPolicy = options.getReferrerPolicy())) { + refererPolicy = this.frameManager.networkManager().extraHTTPHeaders().get("referer"); + } + } + AtomicBoolean ensureNewDocumentNavigation = new AtomicBoolean(false); + LifecycleWatcher watcher = new LifecycleWatcher(this.frameManager.networkManager(), this, waitUntil); + try { + this.navigate(this.client, url, referrer, refererPolicy, this.id(), ensureNewDocumentNavigation); + String timeoutMessage = "Navigation timeout of " + timeout + " ms exceeded"; + Supplier conditionChecker = () -> { + if (watcher.terminationIsDone()) { + throw new TimeoutException(timeoutMessage); + } + if (ensureNewDocumentNavigation.get()) { + if (watcher.newDocumentNavigationIsDone()) { + return true; + } + } else { + if (watcher.sameDocumentNavigationIsDone()) { + return true; + } + } + return null; + }; + Helper.waitForCondition(conditionChecker, timeout, timeoutMessage); + return watcher.navigationResponse(); + } finally { + watcher.dispose(); + } + } + + @Override + public CdpResponse waitForNavigation(WaitForOptions options, Runnable navigateRunner) { + Integer timeout; + List waitUntil; + boolean ignoreSameDocumentNavigation; + if (options == null) { + ignoreSameDocumentNavigation = false; + waitUntil = new ArrayList<>(); + waitUntil.add(PuppeteerLifeCycle.load); + timeout = this.frameManager.timeoutSettings().navigationTimeout(); + } else { + if (ValidateUtil.isEmpty(waitUntil = options.getWaitUntil())) { + waitUntil = new ArrayList<>(); + waitUntil.add(PuppeteerLifeCycle.load); + } + if ((timeout = options.getTimeout()) == null) { + timeout = this.frameManager.timeoutSettings().navigationTimeout(); + } + ignoreSameDocumentNavigation = options.getIgnoreSameDocumentNavigation(); + } + LifecycleWatcher watcher = new LifecycleWatcher(this.frameManager.networkManager(), this, waitUntil); + // 如果是reload页面,需要在等待之前发送刷新命令 + Optional.ofNullable(navigateRunner).ifPresent(Runnable::run); + try { + long base = System.currentTimeMillis(); + long now = 0; + while (true) { + long delay = timeout - now; + if (delay <= 0) { + throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); + } + if (watcher.terminationIsDone()) { + throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); + } + if (!ignoreSameDocumentNavigation) {//不忽略sameDocumentNavigation + if ((watcher.newDocumentNavigationIsDone() || watcher.sameDocumentNavigationIsDone()) && watcher.navigationResponseIsDone()) { + break; + } + } else { + if (watcher.newDocumentNavigationIsDone() && watcher.navigationResponseIsDone()) { + break; + } + } + now = System.currentTimeMillis() - base; + } + return watcher.navigationResponse(); + } finally { + watcher.dispose(); + } + } + + public IsolatedWorld mainRealm() { + return this.worlds.get(MAIN_WORLD); + } + + public IsolatedWorld isolatedRealm() { + return this.worlds.get(PUPPETEER_WORLD); + } + + public void setContent(String html, WaitForOptions options) throws JsonProcessingException, EvaluateException { + List waitUntil; + Integer timeout; + if (options == null) { + waitUntil = new ArrayList<>(); + waitUntil.add(PuppeteerLifeCycle.load); + timeout = this.frameManager.timeoutSettings().navigationTimeout(); + } else { + if (ValidateUtil.isEmpty(waitUntil = options.getWaitUntil())) { + waitUntil = new ArrayList<>(); + waitUntil.add(PuppeteerLifeCycle.load); + } + if ((timeout = options.getTimeout()) == null) { + timeout = this.frameManager.timeoutSettings().navigationTimeout(); + } + } + LifecycleWatcher watcher = new LifecycleWatcher(this.frameManager.networkManager(), this, waitUntil); + this.setFrameContent(html); + try { + long base = System.currentTimeMillis(); + long now = 0; + while (true) { + long delay = timeout - now; + if (delay <= 0) { + throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); + } + if (watcher.terminationIsDone()) { + throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); + } + if (watcher.lifecycleIsDone()) { + break; + } + now = System.currentTimeMillis() - base; + } + } finally { + watcher.dispose(); + } + } + + public String url() { + return this.url; + } + + public Frame parentFrame() { + return this.frameManager.frameTree().parentFrame(this.id); + } + + public List childFrames() { + return this.frameManager.frameTree().childFrames(this.id); + } + + public DeviceRequestPromptManager deviceRequestPromptManager() { + return this.frameManager.deviceRequestPromptManager(this.client); + } + + private void navigate(CDPSession client, String url, String referrer, String referrerPolicy, String frameId, AtomicBoolean ensureNewDocumentNavigation) { + Map params = ParamsFactory.create(); + params.put("url", url); + params.put("referrer", referrer); + params.put("frameId", frameId); + params.put("referrerPolicy", referrerPolicy); + JsonNode response = client.send("Page.navigate", params); + if (response == null) { + return; + } + if (StringUtil.isNotEmpty(response.get("loaderId").asText())) { + ensureNewDocumentNavigation.set(true); + } + String errorText = null; + if (response.get("errorText") != null && StringUtil.isNotEmpty(errorText = response.get("errorText").asText()) && "net::ERR_HTTP_RESPONSE_CODE_FAILURE".equals(response.get("errorText").asText())) { + return; + } + if (StringUtil.isNotEmpty(errorText)) throw new JvppeteerException(errorText + " at " + url); + } + + public void addPreloadScript(PreloadScript preloadScript) { + if (this.client != this.frameManager.client() && this != this.frameManager.mainFrame()) { + return; + } + if (StringUtil.isNotEmpty(preloadScript.getIdForFrame(this))) { + return; + } + Map params = ParamsFactory.create(); + params.put("source", preloadScript.getSource()); + JsonNode response = this.client.send("Page.addScriptToEvaluateOnNewDocument", params); + preloadScript.setIdForFrame(this, response.get("identifier").asText()); + } + + public void addExposedFunctionBinding(Binding binding) throws JsonProcessingException, EvaluateException { + if (this != this.frameManager.mainFrame() && !this.hasStartedLoading) { + return; + } + Map params = ParamsFactory.create(); + params.put("name", CDP_BINDING_PREFIX + binding.name()); + this.client.send("Runtime.addBinding", params); + this.evaluate(binding.initSource(), EvaluateType.STRING, null); + } + + public void removeExposedFunctionBinding(Binding binding) throws JsonProcessingException { + Map params = ParamsFactory.create(); + params.put("name", CDP_BINDING_PREFIX + binding.name()); + this.client.send("Runtime.removeBinding", params); + this.evaluate("name => {\n" + + " // Removes the dangling Puppeteer binding wrapper.\n" + + " // @ts-expect-error: In a different context.\n" + + " globalThis[name] = undefined;\n" + + " }", Collections.singletonList(binding.name())); + } + + public DeviceRequestPrompt waitForDevicePrompt(int timeout) { + return this.deviceRequestPromptManager().waitForDevicePrompt(timeout); + } + + public void navigated(FramePayload framePayload) { + this.name = framePayload.getName(); + this.url = framePayload.getUrl() + (framePayload.getUrlFragment() == null ? "" : framePayload.getUrlFragment()); + } + + public void navigatedWithinDocument(String url) { + this.url = url; + } + + public void onLifecycleEvent(String loaderId, String name) { + if ("init".equals(name)) { + this.loaderId = loaderId; + this.lifecycleEvents.clear(); + } + this.lifecycleEvents.add(name); + } + + public void onLoadingStopped() { + this.lifecycleEvents.add("DOMContentLoaded"); + this.lifecycleEvents.add("load"); + } + + public void onLoadingStarted() { + this.hasStartedLoading = true; + } + + public boolean detached() { + return this.detached; + } + + public void dispose() { + if (this.detached) { + return; + } + this.detached = true; + this.worlds.get(MAIN_WORLD).dispose(); + this.worlds.get(PUPPETEER_WORLD).dispose(); + } + + public ElementHandle frameElement() throws JsonProcessingException, EvaluateException { + CdpTarget target = (CdpTarget) this.page().target(); + boolean isFirefox = target.targetManager() instanceof FirefoxTargetManager; + if (isFirefox) { + return super.frameElement(); + } + Frame parentFrame = this.parentFrame(); + if (parentFrame == null) { + return null; + } + Map params = ParamsFactory.create(); + params.put("frameId", this.id); + JsonNode response = parentFrame.client().send("DOM.getFrameOwner", params); + return parentFrame.mainRealm().adoptBackendNode(response.get("backendNodeId").asInt()).asElement(); + } + + public String loaderId() { + return loaderId; + } + + public FrameManager frameManager() { + return this.frameManager; + } + + public Accessibility accessibility() { + return accessibility; + } + + public Set lifecycleEvents() { + return lifecycleEvents; + } + + + public void setId(String frameId) { + this.id = frameId; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpJSHandle.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpJSHandle.java new file mode 100644 index 00000000..17146307 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpJSHandle.java @@ -0,0 +1,95 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.JSHandle; +import com.ruiyun.jvppeteer.api.core.Realm; +import com.ruiyun.jvppeteer.cdp.entities.RemoteObject; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.util.Helper; +import com.ruiyun.jvppeteer.util.StringUtil; +import java.util.ArrayList; + +/** + * 表示对 JavaScript 对象的引用。可以使用 Page.evaluateHandle() 创建实例。 + *

+ * 句柄可防止引用的 JavaScript 对象被垃圾回收,除非句柄特意为 disposed。当 JSHandles 关联的框架被导航离开或父上下文被破坏时,JSHandles 会被自动处置。 + *

+ * 句柄可用作任何评估函数(例如 Page.$eval()、Page.evaluate() 和 Page.evaluateHandle())的参数。它们被解析为其引用的对象。 + */ +public class CdpJSHandle extends JSHandle { + + private final RemoteObject remoteObject; + private boolean disposed = false; + private final IsolatedWorld world; + + CdpJSHandle(IsolatedWorld world, RemoteObject remoteObject) { + super(); + this.world = world; + this.remoteObject = remoteObject; + } + + @Override + public boolean disposed() { + return disposed; + } + + @Override + public Realm realm() { + return this.world; + } + + public CDPSession client() { + return this.realm().environment().client(); + } + + @Override + public Object jsonValue() throws JsonProcessingException, EvaluateException { + if (StringUtil.isNotEmpty(this.remoteObject.getObjectId())) { + Object value = this.evaluate("object => {\n" + + " return object;\n" + + " }", new ArrayList<>()); + if (value == null) { + throw new JvppeteerException("Could not serialize referenced object"); + } + return value; + } + return Helper.valueFromRemoteObject(this.remoteObject); + } + + @Override + /* This always returns null but children can define this and return an ElementHandle */ + public ElementHandle asElement() { + return null; + } + + + @Override + public void dispose() { + if (this.disposed) + return; + this.disposed = true; + Helper.releaseObject(this.client(), this.remoteObject); + } + + public String toString() { + if (StringUtil.isNotEmpty(this.remoteObject.getObjectId())) { + String type = StringUtil.isNotEmpty(this.remoteObject.getSubtype()) ? this.remoteObject.getSubtype() : this.remoteObject.getType(); + return "JSHandle@" + type; + } + return "JSHandle:" + Helper.valueFromRemoteObject(this.remoteObject); + } + + @Override + public RemoteObject remoteObject() { + return this.remoteObject; + } + + @Override + public String id() { + return this.remoteObject.getObjectId(); + } + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpKeyboard.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpKeyboard.java new file mode 100644 index 00000000..129f91e3 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpKeyboard.java @@ -0,0 +1,219 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Keyboard; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.cdp.entities.KeyDefinition; +import com.ruiyun.jvppeteer.cdp.entities.KeyDescription; +import com.ruiyun.jvppeteer.cdp.entities.KeyDownOptions; +import com.ruiyun.jvppeteer.cdp.entities.KeyPressOptions; +import com.ruiyun.jvppeteer.cdp.entities.KeyboardTypeOptions; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; +import com.ruiyun.jvppeteer.util.StringUtil; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + + +import static com.ruiyun.jvppeteer.util.Helper.justWait; + +public class CdpKeyboard extends Keyboard { + + private CDPSession client; + + private int modifiers; + + private final Set pressedKeys = new HashSet<>(); + + + public CdpKeyboard(CDPSession client) { + super(); + this.client = client; + } + + /** + * 调度 keydown 事件。

+ * 如果 key 是单个字符,并且除了 Shift 之外没有按下任何修改键,也会生成 keypress/input 事件。可以指定 text 选项来强制生成输入事件。如果 key 是修饰键、Shift、Meta、Control 或 Alt,则后续按键将在该修饰键处于活动状态时发送。要释放修饰键,请使用 Keyboard.up()。 + *

+ * 按下该键一次后,后续调用 Keyboard.down() 会将 repeat 设置为 true。要释放密钥,请使用 Keyboard.up()。 + *

+ * 修饰键确实会影响 Keyboard.down()。按住 Shift 将以大写形式键入文本。 + *

+ * + * @param key 要按下的键的名称,例如 ArrowLeft。有关所有键名称的列表,请参阅 {@link Keyboard#keyDefinitions}。 + * @param options 选项。接受文本,如果指定,则使用该文本生成输入事件。接受命令,如果指定,则为键盘快捷键的命令 + */ + public void down(String key, KeyDownOptions options) { + KeyDescription description = this.keyDescriptionForString(key); + boolean autoRepeat = this.pressedKeys.contains(description.getCode()); + this.pressedKeys.add(description.getCode()); + this.modifiers |= this.modifierBit(description.getKey()); + if (StringUtil.isEmpty(options.getText())) { + options.setText(description.getText()); + } + Map params = ParamsFactory.create(); + if (StringUtil.isNotEmpty(options.getText())) { + params.put("type", "keyDown"); + } else { + params.put("type", "rawKeyDown"); + } + params.put("modifiers", this.modifiers); + params.put("windowsVirtualKeyCode", description.getKeyCode()); + params.put("code", description.getCode()); + params.put("key", description.getKey()); + params.put("text", options.getText()); + params.put("unmodifiedText", options.getText()); + params.put("autoRepeat", autoRepeat); + params.put("location", description.getLocation()); + params.put("isKeypad", description.getLocation() == 3); + params.put("commands", options.getCommands()); + this.client.send("Input.dispatchKeyEvent", params); + } + + /** + * 调度 keydown 事件。

+ * 如果 key 是单个字符,并且除了 Shift 之外没有按下任何修改键,也会生成 keypress/input 事件。可以指定 text 选项来强制生成输入事件。如果 key 是修饰键、Shift、Meta、Control 或 Alt,则后续按键将在该修饰键处于活动状态时发送。要释放修饰键,请使用 Keyboard.up()。 + *

+ * 按下该键一次后,后续调用 Keyboard.down() 会将 repeat 设置为 true。要释放密钥,请使用 Keyboard.up()。 + *

+ * 修饰键确实会影响 Keyboard.down()。按住 Shift 将以大写形式键入文本。 + *

+ * + * @param key 要按下的键的名称,例如 ArrowLeft。有关所有键名称的列表,请参阅 {@link CdpKeyboard#keyDefinitions}。 + */ + public void down(String key) { + this.down(key, new KeyDownOptions()); + } + + /** + * 调度 keyup 事件。 + * + * @param key 要释放的密钥的名称, + */ + public void up(String key) { + KeyDescription description = this.keyDescriptionForString(key); + this.modifiers &= ~this.modifierBit(description.getKey()); + this.pressedKeys.remove(description.getCode()); + Map params = ParamsFactory.create(); + params.put("type", "keyUp"); + params.put("modifiers", this.modifiers); + params.put("key", description.getKey()); + params.put("windowsVirtualKeyCode", description.getKeyCode()); + params.put("code", description.getCode()); + params.put("location", description.getLocation()); + this.client.send("Input.dispatchKeyEvent", params); + } + + /** + * 调度 keypress 和 input 事件。这不会发送 keydown 或 keyup 事件。 + *

+ * 修改键不会影响 Keyboard.sendCharacter。按住 Shift 将不会键入大写文本。 + * + * @param cha 要发送到页面的字符。 + */ + public void sendCharacter(String cha) { + Map params = ParamsFactory.create(); + params.put("text", cha); + this.client.send("Input.insertText", params); + } + + private boolean charIsKey(String c) { + return keyDefinitions.containsKey(c); + } + + /** + * 为文本中的每个字符发送 keydown、keypress/input 和 keyup 事件。 + * + * @param text 要输入的文本。 + * @param options 选项 + */ + public void type(String text, KeyboardTypeOptions options) { + for (int i = 0; i < text.length(); i++) { + String c = String.valueOf(text.charAt(i)); + if (this.charIsKey(c)) { + KeyPressOptions pressOptions = new KeyPressOptions(); + pressOptions.setDelay(options.getDelay()); + this.press(c, pressOptions); + } else { + if (options.getDelay() > 0) { + justWait(options.getDelay()); + } + this.sendCharacter(c); + } + } + } + + + /** + * Keyboard.down() 和 Keyboard.up() 的快捷方式。 + * + * @param key 要按下的键的名称,例如 ArrowLeft。有关所有键名称的列表,请参阅 {@link CdpKeyboard#keyDefinitions}。 + * @param options 选项 + */ + public void press(String key, KeyPressOptions options) { + KeyDownOptions downOptions = new KeyDownOptions(); + downOptions.setText(options.getText()); + downOptions.setCommands(options.getCommands()); + this.down(key, downOptions); + if (options.getDelay() > 0) + justWait(options.getDelay()); + this.up(key); + } + + private int modifierBit(String key) { + if ("Alt".equals(key)) + return 1; + if ("Control".equals(key)) + return 2; + if ("Meta".equals(key)) + return 4; + if ("Shift".equals(key)) + return 8; + return 0; + } + + private KeyDescription keyDescriptionForString(String keyString) { + + int shift = this.modifiers & 8; + KeyDescription description = new KeyDescription("", 0, "", "", 0); + KeyDefinition definition = keyDefinitions.get(keyString); + Objects.requireNonNull(definition, "Unknown key: " + keyString); + + if (StringUtil.isNotEmpty(definition.getKey())) + description.setKey(definition.getKey()); + if (shift != 0 && StringUtil.isNotEmpty(definition.getShiftKey())) + description.setKey(definition.getShiftKey()); + + if (definition.getKeyCode() != 0) + description.setKeyCode(definition.getKeyCode()); + if (shift != 0 && definition.getShiftKeyCode() != 0) + description.setKeyCode(definition.getShiftKeyCode()); + + if (StringUtil.isNotEmpty(definition.getCode())) + description.setCode(definition.getCode()); + if (definition.getLocation() != 0) + description.setLocation(definition.getLocation()); + + if (description.getKey().length() == 1) + description.setText(description.getKey()); + + if (StringUtil.isNotEmpty(definition.getText())) + description.setText(definition.getText()); + if (shift != 0 && StringUtil.isNotEmpty(definition.getShiftText())) + description.setText(definition.getShiftText()); + + // if any modifiers besides shift are pressed, no text should be sent + if ((this.modifiers & ~8) != 0) + description.setText(""); + return description; + } + + int getModifiers() { + return modifiers; + } + + void updateClient(CdpCDPSession client) { + this.client = client; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Mouse.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpMouse.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/core/Mouse.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpMouse.java index 8bd38535..a1dd5e3d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/Mouse.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpMouse.java @@ -1,19 +1,21 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Mouse; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; import com.ruiyun.jvppeteer.common.AwaitableResult; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.ClickOptions; -import com.ruiyun.jvppeteer.entities.DragData; -import com.ruiyun.jvppeteer.entities.DragInterceptedEvent; -import com.ruiyun.jvppeteer.entities.MouseClickOptions; -import com.ruiyun.jvppeteer.entities.MouseMoveOptions; -import com.ruiyun.jvppeteer.entities.MouseOptions; -import com.ruiyun.jvppeteer.entities.MouseState; -import com.ruiyun.jvppeteer.entities.MouseWheelOptions; -import com.ruiyun.jvppeteer.entities.Point; +import com.ruiyun.jvppeteer.cdp.entities.ClickOptions; +import com.ruiyun.jvppeteer.cdp.entities.DragData; +import com.ruiyun.jvppeteer.cdp.entities.DragInterceptedEvent; +import com.ruiyun.jvppeteer.cdp.entities.MouseClickOptions; +import com.ruiyun.jvppeteer.cdp.entities.MouseMoveOptions; +import com.ruiyun.jvppeteer.cdp.entities.MouseOptions; +import com.ruiyun.jvppeteer.cdp.entities.MouseState; +import com.ruiyun.jvppeteer.cdp.entities.MouseWheelOptions; +import com.ruiyun.jvppeteer.cdp.entities.Point; import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.transport.CDPSession; - +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -21,21 +23,22 @@ import java.util.function.Consumer; import java.util.function.Function; + import static com.ruiyun.jvppeteer.util.Helper.justWait; -public class Mouse { +public class CdpMouse extends Mouse { private CDPSession client; - private final Keyboard keyboard; + private final CdpKeyboard keyboard; private MouseState state = new MouseState(); private final List transactions = new ArrayList<>(); - public Mouse(CDPSession client, Keyboard keyboard) { + public CdpMouse(CDPSession client, CdpKeyboard keyboard) { this.client = client; this.keyboard = keyboard; } - public Transaction createTransaction() { + private Transaction createTransaction() { MouseState transaction = new MouseState(); this.transactions.add(transaction); Transaction result = new Transaction(); @@ -50,7 +53,7 @@ public Transaction createTransaction() { return result; } - public void withTransaction(Function, Object> action) { + private void withTransaction(Function, Object> action) { Transaction transaction = this.createTransaction(); try { action.apply(transaction.update); @@ -62,6 +65,7 @@ public void withTransaction(Function, Object> action) { } + @Override public void reset() { MouseButtonFlag[] values = MouseButtonFlag.values(); for (MouseButtonFlag value : values) { @@ -100,11 +104,11 @@ public void reset() { } } - public void move(double x, double y) { - this.move(x, y, new MouseMoveOptions()); - } - + @Override public void move(double x, double y, MouseMoveOptions options) { + if(Objects.isNull(options.getSteps())){ + options.setSteps(1); + } Point from = this.state.getPosition(); Point to = new Point(x, y); for (int i = 1; i <= options.getSteps(); i++) { @@ -144,10 +148,7 @@ private String getButtonFromPressedButtons(int buttons) { return "none"; } - public void down() { - this.down(new MouseOptions()); - } - + @Override public void down(MouseOptions options) { MouseButtonFlag flag = this.getFlag(options.getButton()); Objects.requireNonNull(flag, "Unsupported mouse button: " + options.getButton()); @@ -172,10 +173,7 @@ public void down(MouseOptions options) { this.client.send("Input.dispatchMouseEvent", params); } - public void up() { - this.up(new ClickOptions()); - } - + @Override public void up(MouseOptions options) { MouseButtonFlag flag = this.getFlag(options.getButton()); Objects.requireNonNull(flag, "Unsupported mouse button: " + options.getButton()); @@ -200,6 +198,7 @@ public void up(MouseOptions options) { this.client.send("Input.dispatchMouseEvent", params); } + @Override public void click(double x, double y, MouseClickOptions options) { if (options.getCount() < 1) { throw new JvppeteerException("Click must occur a positive number of times."); @@ -257,6 +256,7 @@ public int buttonNameToButton(String buttonName) { * * @param options 选项 */ + @Override public void wheel(MouseWheelOptions options) { Map params = ParamsFactory.create(); params.put("type", "mouseWheel"); @@ -272,7 +272,7 @@ public void wheel(MouseWheelOptions options) { } - public void updateClient(CDPSession newSession) { + public void updateClient(CdpCDPSession newSession) { this.client = newSession; } @@ -280,15 +280,17 @@ public MouseState getState() { return state; } + @Override public DragData drag(Point start, Point target) { AwaitableResult waitableResult = AwaitableResult.create(); - this.client.once(CDPSession.CDPSessionEvent.Input_dragIntercepted, (event) -> waitableResult.onSuccess(((DragInterceptedEvent) event).getData())); + this.client.once(ConnectionEvents.Input_dragIntercepted, (event) -> waitableResult.onSuccess(((DragInterceptedEvent) event).getData())); this.move(start.getX(), start.getY()); this.down(); this.move(target.getX(), target.getY()); return waitableResult.waitingGetResult(); } + @Override public void dragEnter(Point target, DragData data) { Map params = ParamsFactory.create(); params.put("type", "dragEnter"); @@ -299,6 +301,7 @@ public void dragEnter(Point target, DragData data) { this.client.send("Input.dispatchDragEvent", params); } + @Override public void dragOver(Point target, DragData data) { Map params = ParamsFactory.create(); params.put("type", "dragOver"); @@ -309,6 +312,7 @@ public void dragOver(Point target, DragData data) { this.client.send("Input.dispatchDragEvent", params); } + @Override public void drop(Point target, DragData data) { Map params = ParamsFactory.create(); params.put("type", "drop"); @@ -319,6 +323,7 @@ public void drop(Point target, DragData data) { this.client.send("Input.dispatchDragEvent", params); } + @Override public void dragAndDrop(Point start, Point target, int delay) throws InterruptedException { DragData data = this.drag(start, target); this.dragEnter(target, data); diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpPage.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpPage.java new file mode 100644 index 00000000..cf1aaee3 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpPage.java @@ -0,0 +1,1093 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.BrowserContext; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.JSHandle; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.core.Response; +import com.ruiyun.jvppeteer.api.core.Target; +import com.ruiyun.jvppeteer.api.core.WebWorker; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.api.events.PageEvents; +import com.ruiyun.jvppeteer.common.AwaitableResult; +import com.ruiyun.jvppeteer.common.BindingFunction; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.common.MediaType; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.cdp.entities.Binding; +import com.ruiyun.jvppeteer.cdp.entities.BindingPayload; +import com.ruiyun.jvppeteer.cdp.entities.CallFrame; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessage; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessageLocation; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessageType; +import com.ruiyun.jvppeteer.cdp.entities.Cookie; +import com.ruiyun.jvppeteer.cdp.entities.CookieParam; +import com.ruiyun.jvppeteer.cdp.entities.Credentials; +import com.ruiyun.jvppeteer.cdp.entities.DeleteCookiesRequest; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateType; +import com.ruiyun.jvppeteer.cdp.entities.GeolocationOptions; +import com.ruiyun.jvppeteer.cdp.entities.GetMetricsResponse; +import com.ruiyun.jvppeteer.cdp.entities.GetNavigationHistoryResponse; +import com.ruiyun.jvppeteer.cdp.entities.GoToOptions; +import com.ruiyun.jvppeteer.cdp.entities.IdleOverridesState; +import com.ruiyun.jvppeteer.cdp.entities.ImageType; +import com.ruiyun.jvppeteer.cdp.entities.LengthUnit; +import com.ruiyun.jvppeteer.cdp.entities.MediaFeature; +import com.ruiyun.jvppeteer.cdp.entities.Metric; +import com.ruiyun.jvppeteer.cdp.entities.Metrics; +import com.ruiyun.jvppeteer.cdp.entities.NavigationEntry; +import com.ruiyun.jvppeteer.cdp.entities.NetworkConditions; +import com.ruiyun.jvppeteer.cdp.entities.NewDocumentScriptEvaluation; +import com.ruiyun.jvppeteer.cdp.entities.PDFMargin; +import com.ruiyun.jvppeteer.cdp.entities.PDFOptions; +import com.ruiyun.jvppeteer.cdp.entities.PageMetrics; +import com.ruiyun.jvppeteer.cdp.entities.PaperFormats; +import com.ruiyun.jvppeteer.cdp.entities.RemoteObject; +import com.ruiyun.jvppeteer.cdp.entities.ScreenshotClip; +import com.ruiyun.jvppeteer.cdp.entities.ScreenshotOptions; +import com.ruiyun.jvppeteer.cdp.entities.StackTrace; +import com.ruiyun.jvppeteer.cdp.entities.UserAgentMetadata; +import com.ruiyun.jvppeteer.cdp.entities.Viewport; +import com.ruiyun.jvppeteer.cdp.entities.VisionDeficiency; +import com.ruiyun.jvppeteer.cdp.entities.WaitForOptions; +import com.ruiyun.jvppeteer.cdp.events.BindingCalledEvent; +import com.ruiyun.jvppeteer.cdp.events.ConsoleAPICalledEvent; +import com.ruiyun.jvppeteer.cdp.events.EntryAddedEvent; +import com.ruiyun.jvppeteer.cdp.events.ExceptionThrownEvent; +import com.ruiyun.jvppeteer.cdp.events.FileChooserOpenedEvent; +import com.ruiyun.jvppeteer.cdp.events.JavascriptDialogOpeningEvent; +import com.ruiyun.jvppeteer.cdp.events.MetricsEvent; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.exception.ProtocolException; +import com.ruiyun.jvppeteer.exception.TargetCloseException; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; +import com.ruiyun.jvppeteer.util.Helper; +import com.ruiyun.jvppeteer.util.StringUtil; +import com.ruiyun.jvppeteer.util.ValidateUtil; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + + +import static com.ruiyun.jvppeteer.common.Constant.ABOUT_BLANK; +import static com.ruiyun.jvppeteer.common.Constant.CDP_BINDING_PREFIX; +import static com.ruiyun.jvppeteer.common.Constant.MAIN_WORLD; +import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; +import static com.ruiyun.jvppeteer.common.Constant.STREAM; +import static com.ruiyun.jvppeteer.common.Constant.supportedMetrics; +import static com.ruiyun.jvppeteer.util.Helper.convertCookiesPartitionKeyFromPuppeteerToCdp; +import static com.ruiyun.jvppeteer.util.Helper.throwError; + +public class CdpPage extends Page { + + private volatile boolean closed = false; + private final TargetManager targetManager; + private CDPSession primaryTargetClient; + private CdpTarget primaryTarget; + private final CDPSession tabTargetClient; + private final CdpTarget tabTarget; + private final CdpKeyboard keyboard; + private final CdpMouse mouse; + private final CdpTouchscreen touchscreen; + private FrameManager frameManager; + private final EmulationManager emulationManager; + private final Tracing tracing; + private final Map bindings = new HashMap<>(); + private final Map exposedFunctions = new HashMap<>(); + private final Coverage coverage; + private Viewport viewport; + private final Map workers = new HashMap<>(); + private final Set> fileChooserResults = new HashSet<>(); + private final AwaitableResult sessionCloseResult = AwaitableResult.create(); + private boolean serviceWorkerBypassed = false; + private boolean userDragInterceptionEnabled = false; + + public CdpPage(CDPSession client, CdpTarget target) { + super(); + this.primaryTargetClient = client; + this.tabTargetClient = client.parentSession(); + Objects.requireNonNull(this.tabTargetClient, "Tab target session is not defined."); + this.tabTarget = ((CdpCDPSession) this.tabTargetClient).getTarget(); + Objects.requireNonNull(this.tabTarget, "Tab target is not defined."); + this.primaryTarget = target; + this.targetManager = target.targetManager(); + this.keyboard = new CdpKeyboard(client); + this.mouse = new CdpMouse(client, this.keyboard); + this.touchscreen = new CdpTouchscreen(client, this.keyboard); + this.frameManager = new FrameManager(client, this, this._timeoutSettings); + this.emulationManager = new EmulationManager(client); + this.tracing = new Tracing(client); + this.coverage = new Coverage(client); + this.viewport = null; + Map> frameManagerHandlers = Collections.unmodifiableMap(new HashMap>() {{ + put(FrameManager.FrameManagerEvent.FrameAttached, ((Consumer) (frame) -> CdpPage.this.emit(PageEvents.FrameAttached, frame))); + put(FrameManager.FrameManagerEvent.FrameDetached, ((Consumer) (frame) -> CdpPage.this.emit(PageEvents.FrameDetached, frame))); + put(FrameManager.FrameManagerEvent.FrameNavigated, ((Consumer) (frame) -> CdpPage.this.emit(PageEvents.FrameNavigated, frame))); + put(FrameManager.FrameManagerEvent.ConsoleApiCalled, (Consumer) arg -> CdpPage.this.onConsoleAPI((IsolatedWorld) arg[0], (ConsoleAPICalledEvent) arg[1])); + put(FrameManager.FrameManagerEvent.BindingCalled, (Consumer>) arg -> CdpPage.this.onBindingCalled((IsolatedWorld) arg.get(0), (BindingCalledEvent) arg.get(1))); + }}); + frameManagerHandlers.forEach(this.frameManager::on); + + Map> networkManagerHandlers = Collections.unmodifiableMap(new HashMap>() {{ + put(NetworkManager.NetworkManagerEvent.Request, ((Consumer) (request) -> CdpPage.this.emit(PageEvents.Request, request))); + put(NetworkManager.NetworkManagerEvent.RequestServedFromCache, ((Consumer) (request) -> CdpPage.this.emit(PageEvents.RequestServedFromCache, request))); + put(NetworkManager.NetworkManagerEvent.Response, ((Consumer) (response) -> CdpPage.this.emit(PageEvents.Response, response))); + put(NetworkManager.NetworkManagerEvent.RequestFailed, ((Consumer) (request) -> CdpPage.this.emit(PageEvents.RequestFailed, request))); + put(NetworkManager.NetworkManagerEvent.RequestFinished, ((Consumer) (request) -> CdpPage.this.emit(PageEvents.RequestFinished, request))); + }}); + networkManagerHandlers.forEach((key, value) -> this.frameManager.networkManager().on(key, value)); + + this.tabTargetClient.on(ConnectionEvents.CDPSession_Swapped, (Consumer) CdpPage.this::onActivation); + this.tabTargetClient.on(ConnectionEvents.CDPSession_Ready, (Consumer) CdpPage.this::onSecondaryTarget); + + Consumer onDetachedFromTarget = (cdpTarget) -> { + if (cdpTarget.session() == null) { + return; + } + String sessionId = cdpTarget.session().id(); + CdpWebWorker webWorker = CdpPage.this.workers.get(sessionId); + if (webWorker == null) { + return; + } + CdpPage.this.workers.remove(sessionId); + CdpPage.this.emit(PageEvents.WorkerDestroyed, webWorker); + }; + this.targetManager.on(TargetManager.TargetManagerEvent.TargetGone, onDetachedFromTarget); + this.tabTarget.setOnCloseRunner(() -> { + CdpPage.this.targetManager.off(TargetManager.TargetManagerEvent.TargetGone, onDetachedFromTarget); + CdpPage.this.emit(PageEvents.Close, true); + CdpPage.this.closed = true; + }); + + this.setupPrimaryTargetListeners(); + this.attachExistingTargets(); + } + + private void attachExistingTargets() { + List childTargets = this.targetManager.getChildTargets(this.primaryTarget); + List queue = new ArrayList<>(childTargets); + int idx = 0; + while (idx < queue.size()) { + CdpTarget next = queue.get(idx); + idx++; + CdpCDPSession session = (CdpCDPSession) next.session(); + if (Objects.nonNull(session)) { + this.onAttachedToTarget.accept(session); + } + queue.addAll(this.targetManager.getChildTargets(next)); + } + } + + private void onActivation(CdpCDPSession newSession) { + this.primaryTargetClient = newSession; + this.primaryTarget = ((CdpCDPSession) this.primaryTargetClient).getTarget(); + Objects.requireNonNull(this.primaryTarget, "Missing target on swap"); + this.keyboard.updateClient(newSession); + this.mouse.updateClient(newSession); + this.touchscreen.updateClient(newSession); + this.emulationManager.updateClient(newSession); + this.tracing.updateClient(newSession); + this.coverage.updateClient(newSession); + this.frameManager.swapFrameTree(newSession); + this.setupPrimaryTargetListeners(); + } + + /** + * 为主要目标设置侦听器。在导航到预置页面期间,主要目标可能会更改。 + */ + private void setupPrimaryTargetListeners() { + Map> sessionHandlers = Collections.unmodifiableMap(new HashMap>() {{ + put(ConnectionEvents.CDPSession_Ready, (session) -> CdpPage.this.onAttachedToTarget.accept((CdpCDPSession) session)); + put(ConnectionEvents.CDPSession_Disconnected, ((ignore) -> sessionCloseResult.onSuccess(new TargetCloseException("Target closed")))); + put(ConnectionEvents.Page_domContentEventFired, ((ignore) -> CdpPage.this.emit(PageEvents.Domcontentloaded, true))); + put(ConnectionEvents.Page_loadEventFired, ((ignore) -> CdpPage.this.emit(PageEvents.Load, true))); + put(ConnectionEvents.Page_javascriptDialogOpening, ((Consumer) CdpPage.this::onDialog)); + put(ConnectionEvents.Runtime_exceptionThrown, (Consumer) CdpPage.this::handleException); + put(ConnectionEvents.Inspector_targetCrashed, (ignore) -> CdpPage.this.onTargetCrashed()); + put(ConnectionEvents.Performance_metrics, (Consumer) CdpPage.this::emitMetrics); + put(ConnectionEvents.Log_entryAdded, (Consumer) CdpPage.this::onLogEntryAdded); + put(ConnectionEvents.Page_fileChooserOpened, (Consumer) CdpPage.this::onFileChooser); + }}); + sessionHandlers.forEach((eventName, handler) -> this.primaryTargetClient.on(eventName, handler)); + } + + private void onSecondaryTarget(CdpCDPSession session) { + if (!"prerender".equals(session.getTarget().subtype())) { + return; + } + try { + this.frameManager.registerSpeculativeSession(session); + } catch (Exception e) { + LOGGER.error("frameManager registerSpeculativeSession error: ", e); + } + try { + this.emulationManager.registerSpeculativeSession(session); + } catch (Exception e) { + LOGGER.error("emulationManager registerSpeculativeSession error: ", e); + } + } + + /** + * 这里是WebSocketConnectReadThread 线程执行的方法,不能暂停!! + */ + private final Consumer onAttachedToTarget = (session) -> { + this.frameManager.onAttachedToTarget(session.getTarget()); + if ("worker".equals(session.getTarget().getTargetInfo().getType())) { + CdpWebWorker webWorker = new CdpWebWorker(session, session.getTarget().url(), session.getTarget().getTargetId(), session.getTarget().type(), CdpPage.this::addConsoleMessage, CdpPage.this::handleException); + this.workers.put(session.id(), webWorker); + this.emit(PageEvents.WorkerCreated, webWorker); + } + session.on(ConnectionEvents.CDPSession_Ready, this.onAttachedToTarget); + }; + + private void initialize() { + try { + frameManager.initialize(this.primaryTargetClient, null); + Map params = new HashMap<>(); + this.primaryTargetClient.send("Performance.enable", params); + this.primaryTargetClient.send("Log.enable", params); + } catch (Exception e) { + if (e instanceof ProtocolException) { + LOGGER.error("initialize error: ", e); + } else { + throw e; + } + } + } + + private void onFileChooser(FileChooserOpenedEvent event) { + if (this.fileChooserResults.isEmpty()) { + return; + } + CdpFrame frame = this.frameManager.frame(event.getFrameId()); + Objects.requireNonNull(frame, "This should never happen."); + IsolatedWorld mainWorld = frame.worlds().get(MAIN_WORLD); + ElementHandle handle = null; + try { + handle = mainWorld.adoptBackendNode(event.getBackendNodeId()).asElement(); + } catch (JsonProcessingException e) { + throwError(e); + } + FileChooser fileChooser = new FileChooser(handle, event); + for (AwaitableResult subject : this.fileChooserResults) { + subject.onSuccess(fileChooser); + } + this.fileChooserResults.clear(); + } + + public CDPSession client() { + return this.primaryTargetClient; + } + + public boolean isServiceWorkerBypassed() { + return this.serviceWorkerBypassed; + } + + /** + * 我们不再支持拦截拖动有效负载。使用 ElementHandle 上的新拖动 API 进行拖动(或仅使用 Page.mouse)。 + * + * @return 如果拖动事件被拦截,则为 true,否则为 false。 + */ + @Deprecated + public boolean isDragInterceptionEnabled() { + return this.userDragInterceptionEnabled; + } + + public boolean isJavaScriptEnabled() { + return this.emulationManager.javascriptEnabled(); + } + + public AwaitableResult fileChooserWaitFor() { + AwaitableResult result = AwaitableResult.create(); + boolean needsEnable = this.fileChooserResults.isEmpty(); + this.fileChooserResults.add(result); + if (needsEnable) { + Map params = new HashMap<>(); + params.put("enabled", true); + this.primaryTargetClient.send("Page.setInterceptFileChooserDialog", params); + } + return result; + } + + public void setGeolocation(GeolocationOptions options) { + this.emulationManager.setGeolocation(options); + } + + public Target target() { + return this.primaryTarget; + } + + public Browser browser() { + return this.primaryTarget.browser(); + } + + public BrowserContext browserContext() { + return this.primaryTarget.browserContext(); + } + + private void onTargetCrashed() { + this.emit(PageEvents.Error, new JvppeteerException("Page crashed!")); + } + + private void onLogEntryAdded(EntryAddedEvent event) { + if (ValidateUtil.isNotEmpty(event.getEntry().getArgs())) + event.getEntry().getArgs().forEach(arg -> Helper.releaseObject(this.primaryTargetClient, arg)); + if (!"worker".equals(event.getEntry().getSource())) { + List locations = new ArrayList<>(); + locations.add(new ConsoleMessageLocation(event.getEntry().getUrl(), event.getEntry().getLineNumber())); + this.emit(PageEvents.Console, new ConsoleMessage(convertConsoleMessageLevel(event.getEntry().getLevel()), event.getEntry().getText(), Collections.emptyList(), locations, null)); + } + } + + public CdpFrame mainFrame() { + return this.frameManager.mainFrame(); + } + + public CdpKeyboard keyboard() { + return this.keyboard; + } + + public CdpTouchscreen touchscreen() { + return this.touchscreen; + } + + public Coverage coverage() { + return this.coverage; + } + + public Tracing tracing() { + return this.tracing; + } + + public List frames() { + return this.frameManager.frames(); + } + + public List workers() { + return new ArrayList<>(this.workers.values()); + } + + public void setRequestInterception(boolean value) { + this.frameManager.networkManager().setRequestInterception(value); + } + + public void setBypassServiceWorker(boolean bypass) { + this.serviceWorkerBypassed = bypass; + Map params = new HashMap<>(); + params.put("bypass", bypass); + this.primaryTargetClient.send("Network.setBypassServiceWorker", params); + } + + @Deprecated + public void setDragInterception(boolean enabled) { + this.userDragInterceptionEnabled = enabled; + Map params = new HashMap<>(); + params.put("enabled", enabled); + this.primaryTargetClient.send("Input.setInterceptDrags", params); + } + + public void setOfflineMode(boolean enabled) { + this.frameManager.networkManager().setOfflineMode(enabled); + } + + public void emulateNetworkConditions(NetworkConditions networkConditions) { + this.frameManager.networkManager().emulateNetworkConditions(networkConditions); + } + + public void setDefaultNavigationTimeout(int timeout) { + this._timeoutSettings.setDefaultNavigationTimeout(timeout); + } + + public int getDefaultTimeout() { + return this._timeoutSettings.timeout(); + } + + @Override + public int getDefaultNavigationTimeout() { + return this._timeoutSettings.navigationTimeout(); + } + + /** + * 此方法会改变下面几个方法的默认30秒等待时间: + * ${@link CdpPage#goTo(String)} + * ${@link CdpPage#goTo(String, GoToOptions)} + * ${@link CdpPage#goBack(WaitForOptions)} + * ${@link CdpPage#goForward(WaitForOptions)} + * ${@link CdpPage#reload(WaitForOptions)} + * ${@link CdpPage#waitForNavigation()} + * + * @param timeout 超时时间 + */ + + public void setDefaultTimeout(int timeout) { + this._timeoutSettings.setDefaultTimeout(timeout); + } + + public JSHandle queryObjects(JSHandle prototypeHandle) throws JsonProcessingException { + ValidateUtil.assertArg(!prototypeHandle.disposed(), "Prototype JSHandle is disposed!"); + ValidateUtil.assertArg(StringUtil.isNotEmpty(prototypeHandle.remoteObject().getObjectId()), "Prototype JSHandle must not be referencing primitive value"); + Map params = new HashMap<>(); + params.put("prototypeObjectId", prototypeHandle.remoteObject().getObjectId()); + JsonNode response = this.mainFrame().client().send("Runtime.queryObjects", params); + return this.mainFrame().mainRealm().createJSHandle(OBJECTMAPPER.treeToValue(response.get("objects"), RemoteObject.class)); + } + + /** + * 返回当前页面的cookies + * + * @return cookies + */ + public List cookies() { + return this.cookies(this.url()); + } + + /** + * 根据提供的URL列表获取Cookies + *

+ * 如果未指定 URL,则此方法返回当前页面 URL 的 cookie。如果指定了 URL,则仅返回这些 URL 的 cookie。 + *

+ * + * @param urls URL列表,如果没有提供或为null,将使用当前页面的URL + * @return Cookie对象列表,表示请求的cookies + */ + public List cookies(String... urls) { + if (urls == null || urls.length == 0) { + return new ArrayList<>(); + } + Map params = new HashMap<>(); + params.put("urls", urls); + JsonNode result = this.primaryTargetClient.send("Network.getCookies", params); + JsonNode cookiesNode = result.get("cookies"); + Iterator elements = cookiesNode.elements(); + List cookies = new ArrayList<>(); + while (elements.hasNext()) { + JsonNode cookieNode = elements.next(); + Cookie cookie = OBJECTMAPPER.convertValue(cookieNode, Cookie.class); + JsonNode partitionKey = cookieNode.path("partitionKey"); + if (!partitionKey.isMissingNode()) { + cookie.setPartitionKey(partitionKey.get("topLevelSite")); + } else { + cookie.setPartitionKey(null); + } + cookies.add(cookie); + } + return cookies; + } + + public void deleteCookie(DeleteCookiesRequest... cookies) { + if (cookies == null || cookies.length == 0) { + return; + } + String pageURL = this.url(); + for (DeleteCookiesRequest cookie : cookies) { + cookie.setPartitionKey(convertCookiesPartitionKeyFromPuppeteerToCdp(cookie.getPartitionKey())); + if (StringUtil.isEmpty(cookie.getUrl()) && pageURL.startsWith("http")) { + cookie.setUrl(pageURL); + } + this.primaryTargetClient.send("Network.deleteCookies", cookie); + if (pageURL.startsWith("http") && Objects.isNull(cookie.getPartitionKey())) { + URI uri = URI.create(pageURL); + ObjectNode partitionKey = OBJECTMAPPER.createObjectNode(); + partitionKey.put("topLevelSite", getOrigin(uri)); + partitionKey.put("hasCrossSiteAncestor", false); + cookie.setPartitionKey(partitionKey); + this.primaryTargetClient.send("Network.deleteCookies", cookie); + } + } + } + + private String getOrigin(URI uri) { + String scheme = uri.getScheme(); + if ("http".equals(scheme) || "https".equals(scheme)) { + return scheme + "://" + uri.getHost(); + } else { + return getOrigin(URI.create(uri.getSchemeSpecificPart())); + } + } + + public void setCookie(CookieParam... cookies) { + if (cookies == null || cookies.length == 0) { + return; + } + String pageURL = this.url(); + boolean startsWithHTTP = pageURL.startsWith("http"); + ArrayList cookieParams = new ArrayList<>(Arrays.asList(cookies)); + cookieParams.replaceAll(cookie -> { + if (StringUtil.isEmpty(cookie.getUrl()) && startsWithHTTP) cookie.setUrl(pageURL); + ValidateUtil.assertArg(!ABOUT_BLANK.equals(cookie.getUrl()), "Blank page can not have cookie " + cookie.getName()); + if (StringUtil.isNotEmpty(cookie.getUrl())) { + ValidateUtil.assertArg(!cookie.getUrl().startsWith("data:"), "Data URL page can not have cookie " + cookie.getName()); + } + return cookie; + }); + List deleteCookiesParameters = new ArrayList<>(); + for (CookieParam cookie : cookieParams) { + deleteCookiesParameters.add(new DeleteCookiesRequest(cookie.getName(), cookie.getUrl(), cookie.getDomain(), cookie.getPath())); + } + this.deleteCookie(deleteCookiesParameters.toArray(new DeleteCookiesRequest[0])); + Map params = new HashMap<>(); + params.put("cookies", cookieParams); + this.primaryTargetClient.send("Network.setCookies", params); + } + + public void exposeFunction(String name, BindingFunction pptrFunction) throws EvaluateException, JsonProcessingException { + ValidateUtil.assertArg(!this.bindings.containsKey(name), MessageFormat.format("Failed to add page binding with name {0}: window[{1}] already exists!", name, name)); + String source = Helper.evaluationString(addPageBinding(), "exposedFun", name, CDP_BINDING_PREFIX); + Binding binding = new Binding(name, pptrFunction, source); + this.bindings.put(name, binding); + NewDocumentScriptEvaluation response = this.frameManager.evaluateOnNewDocument(source); + this.frameManager.addExposedFunctionBinding(binding); + this.exposedFunctions.put(name, response.getIdentifier()); + } + + public void removeExposedFunction(String name) throws JsonProcessingException { + String exposedFunctionId = this.exposedFunctions.get(name); + if (exposedFunctionId == null) { + throw new JvppeteerException("Failed to remove page binding with name '" + name + "' window['" + name + "'] does not exists!"); + } + this.exposedFunctions.remove(name); + Binding binging = this.bindings.remove(name); + this.frameManager.removeScriptToEvaluateOnNewDocument(exposedFunctionId); + this.frameManager.removeExposedFunctionBinding(binging); + } + + public void authenticate(Credentials credentials) { + this.frameManager.networkManager().authenticate(credentials); + } + + public void setExtraHTTPHeaders(Map headers) { + this.frameManager.networkManager().setExtraHTTPHeaders(headers); + } + + public void setUserAgent(String userAgent, UserAgentMetadata userAgentMetadata) { + this.frameManager.networkManager().setUserAgent(userAgent, userAgentMetadata); + } + + public Metrics metrics() throws JsonProcessingException { + GetMetricsResponse response = OBJECTMAPPER.treeToValue(this.primaryTargetClient.send("Performance.getMetrics"), GetMetricsResponse.class); + return this.buildMetricsObject(response.getMetrics()); + } + + private void emitMetrics(MetricsEvent event) { + PageMetrics pageMetrics = new PageMetrics(); + Metrics metrics = this.buildMetricsObject(event.getMetrics()); + pageMetrics.setMetrics(metrics); + pageMetrics.setTitle(event.getTitle()); + this.emit(PageEvents.Metrics, pageMetrics); + } + + private Metrics buildMetricsObject(List metrics) { + Metrics result = new Metrics(); + if (ValidateUtil.isNotEmpty(metrics)) { + for (Metric metric : metrics) { + if (supportedMetrics.contains(metric.getName())) { + switch (metric.getName()) { + case "Timestamp": + result.setTimestamp(metric.getValue()); + break; + case "Documents": + result.setDocuments(metric.getValue()); + break; + case "Frames": + result.setFrames(metric.getValue()); + break; + case "JSEventListeners": + result.setJSEventListeners(metric.getValue()); + break; + case "Nodes": + result.setNodes(metric.getValue()); + break; + case "LayoutCount": + result.setLayoutCount(metric.getValue()); + break; + case "RecalcStyleCount": + result.setRecalcStyleCount(metric.getValue()); + break; + case "LayoutDuration": + result.setLayoutDuration(metric.getValue()); + break; + case "RecalcStyleDuration": + result.setRecalcStyleDuration(metric.getValue()); + break; + case "ScriptDuration": + result.setScriptDuration(metric.getValue()); + break; + case "TaskDuration": + result.setTaskDuration(metric.getValue()); + break; + case "JSHeapUsedSize": + result.setJSHeapUsedSize(metric.getValue()); + break; + case "JSHeapTotalSize": + result.setJSHeapTotalSize(metric.getValue()); + break; + } + } + } + } + return result; + } + + private void handleException(ExceptionThrownEvent event) { + this.emit(PageEvents.PageError, Helper.createClientError(event.getExceptionDetails())); + } + + private void onConsoleAPI(IsolatedWorld world, ConsoleAPICalledEvent event) { + List values = new ArrayList<>(); + if (ValidateUtil.isNotEmpty(event.getArgs())) { + for (int i = 0; i < event.getArgs().size(); i++) { + RemoteObject arg = event.getArgs().get(i); + values.add(world.createJSHandle(arg)); + } + } + this.addConsoleMessage(convertConsoleMessageLevel(event.getType()), values, event.getStackTrace()); + } + + //不能阻塞 WebSocketConnectReadThread + private void addConsoleMessage(ConsoleMessageType type, List args, StackTrace stackTrace) { + if (this.listenerCount(PageEvents.Console) == 0) { + args.forEach(JSHandle::dispose); + return; + } + List textTokens = new ArrayList<>(); + for (JSHandle arg : args) { + RemoteObject remoteObject = arg.remoteObject(); + if (StringUtil.isNotEmpty(remoteObject.getObjectId())) { + textTokens.add(arg.toString()); + } else { + textTokens.add(Helper.valueFromRemoteObject(remoteObject) + ""); + } + } + List stackTraceLocations = new ArrayList<>(); + if (stackTrace != null) { + if (ValidateUtil.isNotEmpty(stackTrace.getCallFrames())) { + for (CallFrame callFrame : stackTrace.getCallFrames()) { + stackTraceLocations.add(new ConsoleMessageLocation(callFrame.getUrl(), callFrame.getLineNumber(), callFrame.getColumnNumber())); + } + } + } + ConsoleMessage message = new ConsoleMessage(type, String.join(" ", textTokens), args, stackTraceLocations, null); + this.emit(PageEvents.Console, message); + } + + public Response reload(WaitForOptions options) { + options.setIgnoreSameDocumentNavigation(true); + return this.waitForNavigation(options, () -> { + this.primaryTargetClient.send("Page.reload", null, null, false); + }); + } + + public CDPSession createCDPSession() { + return this.target().createCDPSession(); + } + + /** + * 导航到页面历史的前一个页面 + *

+ * options 导航配置,可选值: + *

otimeout 跳转等待时间,单位是毫秒, 默认是30秒, 传 0 表示无限等待。可以通过page.setDefaultNavigationTimeout(timeout)方法修改默认值 + *

owaitUntil 满足什么条件认为页面跳转完成,默认是load事件触发时。指定事件数组,那么所有事件触发后才认为是跳转完成。事件包括: + *

+     * oload - 页面的load事件触发时
+     * odomcontentloaded - 页面的DOMContentLoaded事件触发时
+     * onetworkidle0 - 不再有网络连接时触发(至少500毫秒后)
+     * onetworkidle2 - 只有2个网络连接时触发(至少500毫秒后)
+     * 
+ * + * @param options 见上面注释 + * @return 响应 + * @throws JsonProcessingException 如果JSON解析失败 + */ + public Response goBack(WaitForOptions options) throws JsonProcessingException { + return this.go(-1, options); + } + + /** + * 导航到页面历史的后一个页面。 + * options 导航配置,可选值: + *

otimeout 跳转等待时间,单位是毫秒, 默认是30秒, 传 0 表示无限等待。可以通过page.setDefaultNavigationTimeout(timeout)方法修改默认值 + *

owaitUntil 满足什么条件认为页面跳转完成,默认是load事件触发时。指定事件数组,那么所有事件触发后才认为是跳转完成。事件包括: + *

+     * oload - 页面的load事件触发时
+     * odomcontentloaded - 页面的DOMContentLoaded事件触发时
+     * onetworkidle0 - 不再有网络连接时触发(至少500毫秒后)
+     * onetworkidle2 - 只有2个网络连接时触发(至少500毫秒后)
+     * 
+ * + * @param options 等待选项 + */ + public Response goForward(WaitForOptions options) throws JsonProcessingException { + return this.go(+1, options); + } + + private Response go(int delta, WaitForOptions options) throws JsonProcessingException { + JsonNode historyNode = this.primaryTargetClient.send("Page.getNavigationHistory"); + GetNavigationHistoryResponse history = OBJECTMAPPER.treeToValue(historyNode, GetNavigationHistoryResponse.class); + NavigationEntry entry = history.getEntries().get(history.getCurrentIndex() + delta); + if (entry == null) return null; + Map params = new HashMap<>(); + params.put("entryId", entry.getId()); + this.primaryTargetClient.send("Page.navigateToHistoryEntry", params, null, false); + return this.waitForNavigation(options); + } + + public void bringToFront() { + this.primaryTargetClient.send("Page.bringToFront"); + } + + public void setJavaScriptEnabled(boolean enabled) { + this.emulationManager.setJavaScriptEnabled(enabled); + } + + public void setBypassCSP(boolean enabled) { + Map params = new HashMap<>(); + params.put("enabled", enabled); + this.primaryTargetClient.send("Page.setBypassCSP", params); + } + + public void emulateMediaType(MediaType type) { + this.emulationManager.emulateMediaType(type); + } + + public void emulateCPUThrottling(double factor) { + this.emulationManager.emulateCPUThrottling(factor); + } + + public void emulateMediaFeatures(List features) { + this.emulationManager.emulateMediaFeatures(features); + } + + public void emulateTimezone(String timezoneId) { + this.emulationManager.emulateTimezone(timezoneId); + } + + /** + * 模拟空闲状态。如果未设置参数,则清除空闲状态模拟 + * + * @param overrides 模拟空闲状态。如果未设置,则清除空闲覆盖 + */ + public void emulateIdleState(IdleOverridesState.Overrides overrides) { + this.emulationManager.emulateIdleState(overrides); + } + + public void emulateVisionDeficiency(VisionDeficiency type) { + this.emulationManager.emulateVisionDeficiency(type); + } + + public void setViewport(Viewport viewport) { + boolean needsReload = this.emulationManager.emulateViewport(viewport); + this.viewport = viewport; + if (needsReload) this.reload(new WaitForOptions()); + } + + public Viewport viewport() { + return this.viewport; + } + + public NewDocumentScriptEvaluation evaluateOnNewDocument(String pptrFunction, EvaluateType type, Object... args) throws JsonProcessingException { + String source; + if (Objects.equals(EvaluateType.STRING, type)) { + ValidateUtil.assertArg(args.length == 0, "Cannot evaluate a string with arguments"); + source = pptrFunction; + } else { + source = Helper.evaluationString(pptrFunction, args); + } + return this.frameManager.evaluateOnNewDocument(source); + } + + public void removeScriptToEvaluateOnNewDocument(String identifier) { + Map identifierKeys = new HashMap<>(); + identifierKeys.put("identifier", identifier); + this.primaryTargetClient.send("Page.removeScriptToEvaluateOnNewDocument", identifierKeys); + } + + public void setCacheEnabled(boolean enabled) { + this.frameManager.networkManager().setCacheEnabled(enabled); + } + + protected String _screenshot(ScreenshotOptions options) { + CdpTarget target = (CdpTarget) this.target(); + boolean isFirefox = target.targetManager() instanceof FirefoxTargetManager; + Map params = ParamsFactory.create(); + try { + if (!isFirefox && options.getOmitBackground() && (ImageType.PNG.equals(options.getType()) || ImageType.WEBP.equals(options.getType()))) { + this.emulationManager.setTransparentBackgroundColor(); + } + ScreenshotClip clip = options.getClip(); + if (clip != null && !options.getCaptureBeyondViewport()) { + Object response = this.mainFrame().isolatedRealm().evaluate("() => {\n" + " const {\n" + " height,\n" + " pageLeft: x,\n" + " pageTop: y,\n" + " width,\n" + " } = window.visualViewport;\n" + " return {x, y, height, width};\n" + " }", null); + JsonNode responseNode = OBJECTMAPPER.readTree(OBJECTMAPPER.writeValueAsString(response)); + clip = getIntersectionRect(clip, responseNode); + } + params.put("format", options.getType().toString()); + if (options.getOptimizeForSpeed()) { + params.put("optimizeForSpeed", options.getOptimizeForSpeed()); + } + if (options.getQuality() != null) { + params.put("quality", Math.round(options.getQuality())); + } + if (clip != null) { + params.put("clip", clip); + } + if (!options.getFromSurface()) { + params.put("fromSurface", options.getFromSurface()); + } + params.put("captureBeyondViewport", options.getCaptureBeyondViewport()); + JsonNode result = this.primaryTargetClient.send("Page.captureScreenshot", params); + String data = result.get(Constant.DATA).asText(); + byte[] buffer = Base64.getDecoder().decode(data); + if (StringUtil.isNotEmpty(options.getPath())) { + Files.write(Paths.get(options.getPath()), buffer, StandardOpenOption.CREATE, StandardOpenOption.WRITE); + } + return data; + } catch (Exception var) { + LOGGER.error("_screenshot error: ", var); + } finally { + if (options.getOmitBackground() && (ImageType.PNG.equals(options.getType()) || ImageType.WEBP.equals(options.getType()))) { + this.emulationManager.resetDefaultBackgroundColor(); + } + } + return null; + } + + /** + * @see href="https://w3c.github.io/webdriver-bidi/#rectangle-intersection + */ + private ScreenshotClip getIntersectionRect(ScreenshotClip clip, JsonNode viewport) { + double x = Math.max(clip.getX(), viewport.get("x").asDouble()); + double y = Math.max(clip.getY(), viewport.get("y").asDouble()); + return new ScreenshotClip(x, y, Math.max(Math.min(clip.getX() + clip.getWidth(), viewport.get("x").asDouble() + viewport.get("width").asDouble()) - x, 0), Math.max(Math.min(clip.getY() + clip.getHeight(), viewport.get("y").asDouble() + viewport.get("height").asDouble()) - y, 0), 1); + } + + public byte[] pdf(PDFOptions options, LengthUnit lengthUnit) throws IOException { + double paperWidth = 8.5; + double paperHeight = 11; + if (options.getFormat() != null) { + PaperFormats format = options.getFormat(); + paperWidth = format.getWidth(); + paperHeight = format.getHeight(); + } else { + Double width = convertPrintParameterToInches(options.getWidth(), lengthUnit); + if (width != null) { + paperWidth = width; + } + Double height = convertPrintParameterToInches(options.getHeight(), lengthUnit); + if (height != null) { + paperHeight = height; + } + } + PDFMargin margin = options.getMargin(); + Number marginTop, marginLeft, marginBottom, marginRight; + if ((marginTop = convertPrintParameterToInches(margin.getTop(), lengthUnit)) == null) { + marginTop = 0; + } + if ((marginLeft = convertPrintParameterToInches(margin.getLeft(), lengthUnit)) == null) { + marginLeft = 0; + } + if ((marginBottom = convertPrintParameterToInches(margin.getBottom(), lengthUnit)) == null) { + marginBottom = 0; + } + if ((marginRight = convertPrintParameterToInches(margin.getRight(), lengthUnit)) == null) { + marginRight = 0; + } + if (options.getOutline()) { + options.setTagged(true); + } + if (options.getOmitBackground()) { + this.emulationManager.setTransparentBackgroundColor(); + } + if (options.getWaitForFonts()) { + this.mainFrame().evaluate("() => { return document.fonts.ready;}"); + } + Map params = ParamsFactory.create(); + params.put("transferMode", "ReturnAsStream"); + params.put("landscape", options.getLandscape()); + params.put("displayHeaderFooter", options.getDisplayHeaderFooter()); + params.put("headerTemplate", options.getHeaderTemplate()); + params.put("footerTemplate", options.getFooterTemplate()); + params.put("printBackground", options.getPrintBackground()); + params.put("scale", options.getScale()); + params.put("paperWidth", paperWidth); + params.put("paperHeight", paperHeight); + params.put("marginTop", marginTop); + params.put("marginBottom", marginBottom); + params.put("marginLeft", marginLeft); + params.put("marginRight", marginRight); + params.put("pageRanges", options.getPageRanges()); + params.put("preferCSSPageSize", options.getPreferCSSPageSize()); + params.put("generateTaggedPDF", options.getTagged()); + params.put("generateDocumentOutline", options.getOutline()); + + JsonNode result = this.primaryTargetClient.send("Page.printToPDF", params); + + if (options.getOmitBackground()) { + this.emulationManager.resetDefaultBackgroundColor(); + } + JsonNode handle = result.get(STREAM); + ValidateUtil.assertArg(handle != null, "Page.printToPDF result has no stream handle. Please check your chrome version. result=" + result); + return Helper.readProtocolStream(this.primaryTargetClient, handle.asText(), options.getPath()); + + } + + public void close(boolean runBeforeUnload) { + synchronized (this.browserContext()) { + ValidateUtil.assertArg(this.primaryTargetClient.getConnection() != null, "Protocol error: Connection closed. Most likely the page has been closed."); + if (runBeforeUnload) { + this.primaryTargetClient.send("Page.close"); + } else { + Map params = new HashMap<>(); + params.put("targetId", this.primaryTarget.getTargetId()); + this.primaryTargetClient.getConnection().send("Target.closeTarget", params); + this.tabTarget.waitForTargetClose(); + } + } + } + + public boolean isClosed() { + return this.closed; + } + + public CdpMouse mouse() { + return mouse; + } + + /** + * 创建一个page对象 + * + * @param client 与页面通讯的客户端 + * @param target 目标 + * @param viewport 视图 + * @return 页面实例 + */ + public static CdpPage create(CDPSession client, CdpTarget target, Viewport viewport) { + CdpPage page = new CdpPage(client, target); + page.initialize(); + if (viewport != null) { + page.setViewport(viewport); + } + return page; + } + + + private void onBindingCalled(IsolatedWorld world, BindingCalledEvent event) { + String payloadStr = event.getPayload(); + BindingPayload payload; + try { + payload = OBJECTMAPPER.readValue(payloadStr, BindingPayload.class); + } catch (JsonProcessingException e) { + return; + } + if (!"exposedFun".equals(payload.getType())) { + return; + } + if (world.context() == null) { + return; + } + Binding binding = this.bindings.get(payload.getName()); + Optional.ofNullable(binding).ifPresent(b -> b.run(world.context(), payload.getSeq(), payload.getArgs(), payload.getIsTrivial())); + } + + /** + * 当js对话框出现的时候触发,比如alert, prompt, confirm 或者 beforeunload。Puppeteer可以通过Dialog's accept 或者 dismiss来响应弹窗。 + * + * @param event 触发事件 + */ + private void onDialog(JavascriptDialogOpeningEvent event) { + CdpDialog dialog = new CdpDialog(this.primaryTargetClient, event.getType(), event.getMessage(), event.getDefaultPrompt()); + this.emit(PageEvents.Dialog, dialog); + } + + /** + * 根据启用状态切换忽略每个请求的缓存。默认情况下,缓存已启用。 + */ + private String addPageBinding() { + return "function addPageBinding(type, name, prefix) {\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " if (globalThis[name]) {\n" + + " return;\n" + + " }\n" + + "\n" + + " Object.assign(globalThis, {\n" + + " [name](...args) {\n" + + "\n" + + "\n" + + " const callPuppeteer = globalThis[name];\n" + + " callPuppeteer.args ??= new Map();\n" + + " callPuppeteer.callbacks ??= new Map();\n" + + " const seq = (callPuppeteer.lastSeq ?? 0) + 1;\n" + + " callPuppeteer.lastSeq = seq;\n" + + " callPuppeteer.args.set(seq, args);\n" + + "\n" + + "\n" + + " globalThis[prefix + name](JSON.stringify({\n" + + " type,\n" + + " name,\n" + + " seq,\n" + + " args,\n" + + " isTrivial: !args.some(value => {\n" + + " return value instanceof Node;\n" + + " }),\n" + + " }));\n" + + " return new Promise((resolve, reject) => {\n" + + " callPuppeteer.callbacks.set(seq, {\n" + + " resolve(value) {\n" + + " callPuppeteer.args.delete(seq);\n" + + " resolve(value);\n" + + " },\n" + + " reject(value) {\n" + + " callPuppeteer.args.delete(seq);\n" + + " reject(value);\n" + + " },\n" + + " });\n" + + " });\n" + + " },\n" + + " });\n" + + "}"; + } + + private ConsoleMessageType convertConsoleMessageLevel(String method) { + if ("warning".equals(method)) { + return ConsoleMessageType.warn; + } + return ConsoleMessageType.valueOf(method); + } + + + public void setIsDragging(boolean isDragging) { + this.isDragging = isDragging; + } + + public boolean isDragging() { + return this.isDragging; + } + + public Accessibility accessibility() { + return this.mainFrame().accessibility(); + } + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpRequest.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpRequest.java new file mode 100644 index 00000000..5a5e0133 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpRequest.java @@ -0,0 +1,321 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.core.Request; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.cdp.entities.ContinueRequestOverrides; +import com.ruiyun.jvppeteer.cdp.entities.ErrorReasons; +import com.ruiyun.jvppeteer.cdp.entities.HeaderEntry; +import com.ruiyun.jvppeteer.cdp.entities.Initiator; +import com.ruiyun.jvppeteer.cdp.entities.ResourceType; +import com.ruiyun.jvppeteer.cdp.entities.ResponseForRequest; +import com.ruiyun.jvppeteer.cdp.events.RequestWillBeSentEvent; +import com.ruiyun.jvppeteer.util.Base64Util; +import com.ruiyun.jvppeteer.util.StringUtil; +import com.ruiyun.jvppeteer.util.ValidateUtil; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class CdpRequest extends Request { + + private volatile String id; + private CDPSession client; + private volatile boolean isNavigationRequest; + private volatile String url; + private volatile ResourceType resourceType; + private volatile String method; + private volatile boolean hasPostData; + private volatile String postData; + private volatile List headers = new ArrayList<>(); + private Frame frame; + private volatile Initiator initiator; + + public CdpRequest() { + super(); + } + + public CdpRequest(CDPSession client, Frame frame, String interceptionId, boolean allowInterception, RequestWillBeSentEvent event, List redirectChain) { + super(); + this.client = client; + this.id = event.getRequestId(); + if (event.getRequestId() != null) { + if (event.getRequestId().equals(event.getLoaderId()) && "Document".equals(event.getType())) { + this.isNavigationRequest = true; + } + } else { + this.isNavigationRequest = false; + } + this.interceptionId = interceptionId; + this.url = event.getRequest().getUrl(); + this.resourceType = StringUtil.isEmpty(event.getType()) ? ResourceType.Other : ResourceType.valueOf(event.getType()); + this.method = event.getRequest().getMethod(); + this.postData = event.getRequest().getPostData(); + this.hasPostData = event.getRequest().getHasPostData(); + this.frame = frame; + this.redirectChain = redirectChain; + this.initiator = event.getInitiator(); + this.interception.setEnabled(allowInterception); + for (Map.Entry entry : event.getRequest().getHeaders().entrySet()) { + this.headers.add(new HeaderEntry(entry.getKey().toLowerCase(), entry.getValue())); + } + } + + public CDPSession client() { + return this.client; + } + + public String url() { + return this.url; + } + + public ResourceType resourceType() { + return this.resourceType; + } + + /** + * 使用的方法(GET、POST 等) + * + * @return 方法 + */ + public String method() { + return this.method; + } + + /** + * 请求体数据 + * + * @return 请求体 + */ + public String postData() { + return this.postData; + } + + /** + * 是否有请求体 + *

+ * 当请求有 POST 数据时为 true。 + *

+ * 请注意,当数据太长或不易以解码形式提供时,当此标志为真时,HTTPRequest.postData() 可能仍然未定义。 + *

+ * 在这种情况下,请使用 HTTPRequest.fetchPostData()。 + * + * @return 是否有请求体 + */ + public boolean hasPostData() { + return this.hasPostData; + } + + /** + * 从浏览器获取请求的 POST 数据。 + * + * @return POST 数据 + */ + public String fetchPostData() { + Map params = ParamsFactory.create(); + params.put("requestId", this.id); + JsonNode response = this.client.send("Network.getRequestPostData", params); + return response.get("postData").asText(); + } + + /** + * 具有与请求关联的 HTTP 标头的对象。所有标头名称均为小写。 + * + * @return Map + */ + public List headers() { + return this.headers; + } + + /** + * 请求对应的响应 + * + * @return Response + */ + public CdpResponse response() { + return this.response; + } + + public Frame frame() { + return frame; + } + + public void setHeaders(List headers) { + this.headers = headers; + } + + /** + * 如果请求是当前帧导航的驱动程序,则为 True。 + * + * @return boolean + */ + public boolean isNavigationRequest() { + return isNavigationRequest; + } + + /** + * 请求的发起者。 + * + * @return Initiator + */ + public Initiator initiator() { + return this.initiator; + } + + /** + * 重定向链 + *

+ * redirectChain 是为获取资源而发起的请求链。 + *

+ * 请求链 - 如果服务器至少响应一个重定向,则该链将包含所有重定向的请求。 + * + * @return List + */ + public List redirectChain() { + return this.redirectChain; + } + + /** + * 访问有关请求失败的信息。 + * + * @return 如果请求失败,则可以返回一个带有 errorText 的对象,其中包含人类可读的错误消息,例如 net::ERR_FAILED,不保证会有失败文本。

请求成功,返回null + */ + public String failure() { + return this.failureText; + } + + + public void _continue(ContinueRequestOverrides overrides) { + this.interception.setHandled(true); + ValidateUtil.assertArg(StringUtil.isNotEmpty(this.interceptionId), "HTTPRequest is missing _interceptionId needed for Fetch.continueRequest"); + Map params = ParamsFactory.create(); + params.put("requestId", this.interceptionId); + params.put("url", overrides.getUrl()); + params.put("method", overrides.getMethod()); + if (StringUtil.isNotEmpty(overrides.getPostData())) { + params.put("postData", new String(Base64.getEncoder().encode(overrides.getPostData().getBytes()), StandardCharsets.UTF_8)); + } else { + params.put("postData", ""); + } + params.put("headers", filterHeaders(overrides.getHeaders())); + try { + this.client.send("Fetch.continueRequest", params); + } catch (Exception e) { + this.interception.setHandled(false); + handleError(e); + } + } + + + + + public void _respond(ResponseForRequest response) { + this.interception.setHandled(true); + String base64Body = null; + int contentLength = 0; + if (StringUtil.isNotEmpty(response.getBody())) { + byte[] byteBody = response.getBody().getBytes(StandardCharsets.UTF_8); + base64Body = Base64Util.encode(byteBody); + contentLength = byteBody.length; + } + List responseHeaders = headers(response, base64Body, contentLength); + ValidateUtil.assertArg(StringUtil.isNotEmpty(this.interceptionId), "HTTPRequest is missing _interceptionId needed for Fetch.fulfillRequest"); + Map params = ParamsFactory.create(); + params.put("requestId", this.interceptionId); + params.put("responseCode", response.getStatus()); + params.put("responsePhrase", STATUS_TEXTS.get(response.getStatus())); + params.put("responseHeaders", filterHeaders(responseHeaders)); + if (Objects.nonNull(base64Body)) { + params.put("body", base64Body); + } + try { + this.client.send("Fetch.fulfillRequest", params); + } catch (Exception e) { + this.interception.setHandled(false); + handleError(e); + } + } + + private static List headers(ResponseForRequest response, String base64Body, int contentLength) { + List responseHeaders = new ArrayList<>(); + boolean hasContentLength = false; + if (ValidateUtil.isNotEmpty(response.getHeaders())) { + for (HeaderEntry header : response.getHeaders()) { + String name = header.getName().toLowerCase(); + responseHeaders.add(new HeaderEntry(name, header.getValue())); + if (name.equals("content-length")) { + hasContentLength = true; + } + } + } + if (StringUtil.isNotEmpty(response.getContentType())) { + responseHeaders.add(new HeaderEntry("content-type", response.getContentType())); + } + if (base64Body != null && !hasContentLength) { + responseHeaders.add(new HeaderEntry("content-length", String.valueOf(contentLength))); + } + return responseHeaders; + } + + + public void _abort(ErrorReasons errorCode) { + this.interception.setHandled(true); + ValidateUtil.assertArg(StringUtil.isNotEmpty(this.interceptionId), "HTTPRequest is missing _interceptionId needed for Fetch.fulfillRequest"); + String errorReason = errorCode.getName(); + Map params = ParamsFactory.create(); + params.put("requestId", this.interceptionId); + params.put("errorReason", errorReason); + try { + this.client.send("Fetch.failRequest", params); + } catch (Exception e) { + handleError(e); + } + + } + + public String interceptionId() { + return this.interceptionId; + } + + private List filterHeaders(List headers) { + if (headers == null) { + return null; + } + Iterator iterator = headers.iterator(); + while (iterator.hasNext()) { + HeaderEntry next = iterator.next(); + if (StringUtil.isEmpty(next.getValue())) { + iterator.remove(); + } + } + return headers; + } + + protected void setResponse(CdpResponse response) { + this.response = response; + } + + public String id() { + return id; + } + + protected void setFailureText(String failureText) { + this.failureText = failureText; + } + + public boolean fromMemoryCache() { + return fromMemoryCache; + } + + + + public void setClient(CDPSession client) { + this.client = client; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Response.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpResponse.java similarity index 82% rename from src/main/java/com/ruiyun/jvppeteer/core/Response.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpResponse.java index cb051d35..0c2340dd 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/Response.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpResponse.java @@ -1,21 +1,24 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; import com.fasterxml.jackson.databind.JsonNode; -import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.core.Request; +import com.ruiyun.jvppeteer.api.core.Response; import com.ruiyun.jvppeteer.common.AwaitableResult; -import com.ruiyun.jvppeteer.entities.RemoteAddress; -import com.ruiyun.jvppeteer.entities.ResourceTiming; -import com.ruiyun.jvppeteer.entities.ResponsePayload; -import com.ruiyun.jvppeteer.entities.ResponseSecurityDetails; -import com.ruiyun.jvppeteer.events.ResponseReceivedExtraInfoEvent; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.cdp.entities.HeaderEntry; +import com.ruiyun.jvppeteer.cdp.entities.RemoteAddress; +import com.ruiyun.jvppeteer.cdp.entities.ResourceTiming; +import com.ruiyun.jvppeteer.cdp.entities.ResponsePayload; +import com.ruiyun.jvppeteer.cdp.entities.ResponseSecurityDetails; +import com.ruiyun.jvppeteer.cdp.events.ResponseReceivedExtraInfoEvent; import com.ruiyun.jvppeteer.exception.JvppeteerException; import com.ruiyun.jvppeteer.exception.ProtocolException; -import com.ruiyun.jvppeteer.transport.CDPSession; import com.ruiyun.jvppeteer.util.StringUtil; - import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Base64; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -23,9 +26,9 @@ import static com.ruiyun.jvppeteer.util.Helper.throwError; -public class Response { +public class CdpResponse extends Response { - private Request request; + private volatile CdpRequest request; private final AwaitableResult contentResult = AwaitableResult.create(); private final AwaitableResult bodyLoadedResult = AwaitableResult.create(); private volatile RemoteAddress remoteAddress; @@ -34,14 +37,16 @@ public class Response { private volatile String url; private volatile boolean fromDiskCache; private volatile boolean fromServiceWorker; - private volatile Map headers; + private volatile List headers; private volatile ResponseSecurityDetails securityDetails; private ResourceTiming timing; - public Response() { + public CdpResponse() { + super(); } - public Response(Request request, ResponsePayload responsePayload, ResponseReceivedExtraInfoEvent extraInfo) { + public CdpResponse(CdpRequest request, ResponsePayload responsePayload, ResponseReceivedExtraInfoEvent extraInfo) { + super(); this.request = request; this.remoteAddress = new RemoteAddress(responsePayload.getRemoteIPAddress(), responsePayload.getRemotePort()); this.statusText = StringUtil.isNotEmpty(this.parseStatusTextFromExtraInfo(extraInfo)) ? this.parseStatusTextFromExtraInfo(extraInfo) : responsePayload.getStatusText(); @@ -49,7 +54,7 @@ public Response(Request request, ResponsePayload responsePayload, ResponseReceiv this.fromDiskCache = responsePayload.getFromDiskCache(); this.fromServiceWorker = responsePayload.getFromServiceWorker(); this.status = extraInfo != null ? extraInfo.getStatusCode() : responsePayload.getStatus(); - this.headers = new HashMap<>(); + this.headers = new ArrayList<>(); Map headers; if (extraInfo != null) { headers = extraInfo.getHeaders(); @@ -58,7 +63,7 @@ public Response(Request request, ResponsePayload responsePayload, ResponseReceiv } if (headers != null && !headers.isEmpty()) { for (Map.Entry entry : headers.entrySet()) { - this.headers.put(entry.getKey().toLowerCase(), entry.getValue()); + this.headers.add(new HeaderEntry(entry.getKey().toLowerCase(), entry.getValue())); } } this.securityDetails = responsePayload.getSecurityDetails() != null ? new ResponseSecurityDetails(responsePayload.getSecurityDetails()) : null; @@ -110,6 +115,8 @@ private void getResponseBody() { Map params = ParamsFactory.create(); params.put("requestId", this.request.id()); try { + // Use CDPSession from corresponding request to retrieve body, as it's client + // might have been updated (e.g. for an adopted OOPIF). JsonNode response = this.request.client().send("Network.getResponseBody", params); if (response != null) { if (response.get("base64Encoded").asBoolean()) { @@ -138,15 +145,11 @@ public int status() { return this.status; } - public boolean ok() { - return this.status == 0 || (this.status >= 200 && this.status <= 299); - } - public String statusText() { return this.statusText; } - public Map headers() { + public List headers() { return this.headers; } @@ -167,7 +170,7 @@ public byte[] content() { if (!this.contentResult.isDone()) { if (StringUtil.isEmpty(this.bodyLoadedResult.get())) { this.getResponseBody(); - }else { + } else { throw new JvppeteerException(this.bodyLoadedResult.get()); } } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Target.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTarget.java similarity index 75% rename from src/main/java/com/ruiyun/jvppeteer/core/Target.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTarget.java index 637a5e8a..92a26771 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/Target.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTarget.java @@ -1,14 +1,17 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; -import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.api.core.BrowserContext; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.core.Target; import com.ruiyun.jvppeteer.common.AwaitableResult; -import com.ruiyun.jvppeteer.entities.TargetInfo; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.entities.TargetType; import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.entities.TargetType; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import com.ruiyun.jvppeteer.transport.SessionFactory; import com.ruiyun.jvppeteer.util.StringUtil; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -18,33 +21,33 @@ * CDP 目标

* 在 CDP 中,目标是可以调试的东西,例如Frame、Page或worker。 */ -public class Target { - private BrowserContext browserContext; +public class CdpTarget implements Target { + private CdpBrowserContext cdpBrowserContext; private CDPSession session; protected TargetInfo targetInfo; private TargetManager targetManager; protected SessionFactory sessionFactory; - private final List childTargets = new ArrayList<>(); + private final List childTargets = new ArrayList<>(); public final AwaitableResult isClosedResult = AwaitableResult.create(); final AwaitableResult initializedResult = AwaitableResult.create(); private String targetId; - protected WebWorker webWorker; + protected CdpWebWorker webWorker; private Runnable onCloseRunner = () -> { }; - public Target() { + public CdpTarget() { super(); } - public Target(TargetInfo targetInfo, CDPSession session, BrowserContext browserContext, TargetManager targetManager, SessionFactory sessionFactory) { + public CdpTarget(TargetInfo targetInfo, CDPSession session, CdpBrowserContext cdpBrowserContext, TargetManager targetManager, SessionFactory sessionFactory) { this.session = session; this.targetManager = targetManager; this.targetInfo = targetInfo; - this.browserContext = browserContext; + this.cdpBrowserContext = cdpBrowserContext; this.targetId = targetInfo.getTargetId(); this.sessionFactory = sessionFactory; if (this.session != null) { - this.session.setTarget(this); + ((CdpCDPSession) this.session).setTarget(this); } } @@ -62,9 +65,9 @@ public Page asPage() { CDPSession session = this.session(); if (session == null) { session = this.createCDPSession(); - return Page.create(session, this, null); + return CdpPage.create(session, this, null); } - return Page.create(session, this, null); + return CdpPage.create(session, this, null); } public String subtype() { @@ -75,15 +78,15 @@ public CDPSession session() { return this.session; } - public void addChildTarget(Target target) { + public void addChildTarget(CdpTarget target) { this.childTargets.add(target); } - public void removeChildTarget(Target target) { + public void removeChildTarget(CdpTarget target) { this.childTargets.remove(target); } - public List childTargets() { + public List childTargets() { return this.childTargets; } @@ -94,16 +97,11 @@ public SessionFactory sessionFactory() { return this.sessionFactory; } - /** - * 创建附加到目标的 Chrome Devtools 协议会话。 - * - * @return 会话 - */ public CDPSession createCDPSession() { if (this.sessionFactory == null) { throw new JvppeteerException("sessionFactory is not initialized"); } - CDPSession cdpSession = this.sessionFactory.create(false); + CdpCDPSession cdpSession = (CdpCDPSession) this.sessionFactory.create(false); cdpSession.setTarget(this); return cdpSession; } @@ -112,12 +110,6 @@ public String url() { return this.targetInfo.getUrl(); } - /** - * 确定这是什么类型的目标。

- * 注意:背景页是谷歌插件里的页面 - * - * @return 目标类型 - */ public TargetType type() { String type = this.targetInfo.getType(); switch (type) { @@ -151,28 +143,19 @@ public TargetInfo getTargetInfo() { return this.targetInfo; } - /** - * 获取目标所属的浏览器。 - * - * @return Browser - */ - public Browser browser() { - if (this.browserContext == null) { + public CdpBrowser browser() { + if (this.cdpBrowserContext == null) { throw new JvppeteerException("browserContext is not initialized"); } - return this.browserContext.browser(); + return this.cdpBrowserContext.browser(); } - /** - * 获取目标所属的浏览器上下文。 - * - * @return BrowserContext - */ + public BrowserContext browserContext() { - if (this.browserContext == null) { + if (this.cdpBrowserContext == null) { throw new JvppeteerException("browserContext is not initialized"); } - return this.browserContext; + return this.cdpBrowserContext; } /** @@ -185,7 +168,7 @@ public Target opener() { if (StringUtil.isEmpty(openerId)) { return null; } - for (Target target : this.browser().targets()) { + for (CdpTarget target : this.browser().targets()) { if (target.getTargetId().equals(openerId)) { return target; } @@ -212,15 +195,6 @@ private void checkIfInitialized() { } } - /** - * 如果目标类型不是“page”,"webview","background_page",那么返回null - * - * @return Page - */ - public Page page() { - return null; - } - public String getTargetId() { return this.targetId; } diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchHandle.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchHandle.java new file mode 100644 index 00000000..9f822de2 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchHandle.java @@ -0,0 +1,68 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.TouchHandle; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.cdp.entities.TouchPoint; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class CdpTouchHandle extends TouchHandle { + private boolean started; + private final CdpTouchscreen touchScreen; + private final TouchPoint touchPoint; + private CDPSession client; + private final CdpKeyboard keyboard; + + public CdpTouchHandle(CDPSession client, CdpTouchscreen touchScreen, CdpKeyboard keyboard, TouchPoint touchPoint) { + this.client = client; + this.touchScreen = touchScreen; + this.keyboard = keyboard; + this.touchPoint = touchPoint; + } + + public void start() { + if (this.started) { + throw new JvppeteerException("Touch has already started"); + } + Map params = ParamsFactory.create(); + params.put("type", "touchStart"); + List touchPoints = new ArrayList<>(); + touchPoints.add(this.touchPoint); + params.put("touchPoints", touchPoints); + params.put("modifiers", this.keyboard.getModifiers()); + this.client.send("Input.dispatchTouchEvent", params); + this.started = true; + } + + public void updateClient(CDPSession client) { + this.client = client; + } + + @Override + public void move(double x, double y) { + Map params = ParamsFactory.create(); + params.put("type", "touchMove"); + List touchPoints = new ArrayList<>(); + this.touchPoint.setX(Math.round(x)); + this.touchPoint.setY(Math.round(y)); + touchPoints.add(this.touchPoint); + params.put("touchPoints", touchPoints); + params.put("modifiers", this.keyboard.getModifiers()); + this.client.send("Input.dispatchTouchEvent", params); + } + + @Override + public void end() { + Map params = ParamsFactory.create(); + params.put("type", "touchEnd"); + List touchPoints = new ArrayList<>(); + touchPoints.add(this.touchPoint); + params.put("touchPoints", touchPoints); + params.put("modifiers", this.keyboard.getModifiers()); + this.client.send("Input.dispatchTouchEvent", params); + this.touchScreen.removeHandle(this); + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchscreen.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchscreen.java new file mode 100644 index 00000000..cf20b0ca --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpTouchscreen.java @@ -0,0 +1,47 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.TouchHandle; +import com.ruiyun.jvppeteer.api.core.Touchscreen; +import com.ruiyun.jvppeteer.cdp.entities.TouchPoint; + +public class CdpTouchscreen extends Touchscreen { + private CDPSession client; + private final CdpKeyboard keyboard; + + public CdpTouchscreen(CDPSession client, CdpKeyboard keyboard) { + super(); + this.client = client; + this.keyboard = keyboard; + } + + public void updateClient(CDPSession client) { + this.client = client; + this.touches.forEach(t -> t.updateClient(client)); + } + + /** + * 调度 touchstart 事件。 + * + * @param x tap的水平位置。 + * @param y tap的垂直位置。 + */ + @Override + public TouchHandle touchStart(double x, double y, ObjectNode origin) { + long id = this.idGenerator.getAndIncrement(); + TouchPoint touchPoint = new TouchPoint(); + touchPoint.setX(Math.round(x)); + touchPoint.setY(Math.round(y)); + touchPoint.setRadiusX(0.5); + touchPoint.setRadiusY(0.5); + touchPoint.setForce(0.5); + touchPoint.setId(id); + CdpTouchHandle touch = new CdpTouchHandle(this.client, this, this.keyboard, touchPoint); + touch.start(); + this.touches.add(touch); + return touch; + } + + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpWebWorker.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpWebWorker.java new file mode 100644 index 00000000..31a005ee --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/CdpWebWorker.java @@ -0,0 +1,83 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Connection; +import com.ruiyun.jvppeteer.api.core.Realm; +import com.ruiyun.jvppeteer.api.core.WebWorker; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.common.ConsoleAPI; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.common.TimeoutSettings; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessageType; +import com.ruiyun.jvppeteer.cdp.entities.TargetType; +import com.ruiyun.jvppeteer.cdp.events.ConsoleAPICalledEvent; +import com.ruiyun.jvppeteer.cdp.events.ExceptionThrownEvent; +import com.ruiyun.jvppeteer.cdp.events.ExecutionContextCreatedEvent; +import com.ruiyun.jvppeteer.cdp.events.IsolatedWorldEmitter; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * The events `workercreated` and `workerdestroyed` are emitted on the page object to signal the worker lifecycle. + */ +public class CdpWebWorker extends WebWorker { + private final IsolatedWorld world; + private final CDPSession client; + private final String id; + private final TargetType targetType; + + public CdpWebWorker(CDPSession client, String url, String targetId, TargetType targetType, ConsoleAPI consoleAPICalled, Consumer exceptionThrown) { + super(url); + this.id = targetId; + this.client = client; + this.targetType = targetType; + this.world = new IsolatedWorld(null, this, new TimeoutSettings()); + this.client.once(ConnectionEvents.Runtime_executionContextCreated, (Consumer) event -> this.world.setContext(new ExecutionContext(client, event.getContext(), world))); + this.world.emitter().on(IsolatedWorldEmitter.IsolatedWorldEventType.Consoleapicalled, (Consumer) event -> consoleAPICalled.call(ConsoleMessageType.valueOf(event.getType().toUpperCase()), event.getArgs().stream().map((object) -> new CdpJSHandle(world, object)).collect(Collectors.toList()), event.getStackTrace())); + this.client.on(ConnectionEvents.Runtime_exceptionThrown, exceptionThrown); + this.client.once(ConnectionEvents.CDPSession_Disconnected, (ignored) -> this.world.dispose()); + } + + public Realm mainRealm() { + return this.world; + } + + public CDPSession client() { + return this.client; + } + + public void close() throws EvaluateException, JsonProcessingException { + switch (this.targetType) { + case SERVICE_WORKER: + case SHARED_WORKER: { + Connection connection = this.client.getConnection(); + if (Objects.nonNull(connection)) { + Map params = ParamsFactory.create(); + params.put(Constant.TARGET_ID, this.id); + connection.send("Target.closeTarget", params); + params.clear(); + params.put(Constant.SESSION_ID, this.client.id()); + connection.send("Target.detachFromTarget", params, null, false); + } + break; + } + default: { + this.evaluate("() => {\n" + + " self.close();\n" + + " }"); + } + + } + } + + + + +} + + diff --git a/src/main/java/com/ruiyun/jvppeteer/core/ChromeTargetManager.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/ChromeTargetManager.java similarity index 78% rename from src/main/java/com/ruiyun/jvppeteer/core/ChromeTargetManager.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/ChromeTargetManager.java index 1868d7af..1fded055 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/ChromeTargetManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/ChromeTargetManager.java @@ -1,22 +1,26 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Connection; +import com.ruiyun.jvppeteer.api.core.Target; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; import com.ruiyun.jvppeteer.common.AwaitableResult; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.FilterEntry; -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.events.AttachedToTargetEvent; -import com.ruiyun.jvppeteer.events.DetachedFromTargetEvent; -import com.ruiyun.jvppeteer.events.TargetCreatedEvent; -import com.ruiyun.jvppeteer.events.TargetDestroyedEvent; -import com.ruiyun.jvppeteer.events.TargetInfoChangedEvent; +import com.ruiyun.jvppeteer.cdp.entities.FilterEntry; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.events.AttachedToTargetEvent; +import com.ruiyun.jvppeteer.cdp.events.DetachedFromTargetEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetCreatedEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetDestroyedEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetInfoChangedEvent; import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.transport.Connection; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -31,8 +35,8 @@ public class ChromeTargetManager extends TargetManager { private final Connection connection; private final Map discoveredTargetsByTargetId = new HashMap<>(); - private final Map attachedTargetsByTargetId = new ConcurrentHashMap<>(); - private final Map attachedTargetsBySessionId = new HashMap<>(); + private final Map attachedTargetsByTargetId = new ConcurrentHashMap<>(); + private final Map attachedTargetsBySessionId = new HashMap<>(); private final Set ignoredTargets = new HashSet<>(); private final Function targetFilterCallback; private final TargetFactory targetFactory; @@ -41,12 +45,12 @@ public class ChromeTargetManager extends TargetManager { private final Map> detachedFromTargetListenersBySession = new WeakHashMap<>(); private final Map> detachedFromTargetListenersByConnection = new WeakHashMap<>(); private final AwaitableResult initializeResult = AwaitableResult.create(); - private final Set targetsIdsForInit = new HashSet<>(); + private final Set targetsIdsForInit = Collections.synchronizedSet(new HashSet<>()); private final boolean waitForInitiallyDiscoveredTargets; private final List discoveryFilter = new ArrayList() {{ add(new FilterEntry()); }}; - private final Map> listeners = new HashMap<>(); + private final Map> listeners = new HashMap<>(); public ChromeTargetManager(Connection connection, TargetFactory targetFactory, Function targetFilterCallback, boolean waitForInitiallyDiscoveredTargets) { super(); @@ -55,20 +59,20 @@ public ChromeTargetManager(Connection connection, TargetFactory targetFactory, F this.targetFactory = targetFactory; this.waitForInitiallyDiscoveredTargets = waitForInitiallyDiscoveredTargets; Consumer onTargetCreatedListener = this::onTargetCreated; - this.connection.on(CDPSession.CDPSessionEvent.Target_targetCreated, onTargetCreatedListener); - this.listeners.put(CDPSession.CDPSessionEvent.Target_targetCreated, onTargetCreatedListener); + this.connection.on(ConnectionEvents.Target_targetCreated, onTargetCreatedListener); + this.listeners.put(ConnectionEvents.Target_targetCreated, onTargetCreatedListener); Consumer onTargetDestroyedListener = this::onTargetDestroyed; - this.connection.on(CDPSession.CDPSessionEvent.Target_targetDestroyed, onTargetDestroyedListener); - this.listeners.put(CDPSession.CDPSessionEvent.Target_targetDestroyed, onTargetDestroyedListener); + this.connection.on(ConnectionEvents.Target_targetDestroyed, onTargetDestroyedListener); + this.listeners.put(ConnectionEvents.Target_targetDestroyed, onTargetDestroyedListener); Consumer onTargetInfoChangedListener = this::onTargetInfoChanged; - this.connection.on(CDPSession.CDPSessionEvent.Target_targetInfoChanged, onTargetInfoChangedListener); - this.listeners.put(CDPSession.CDPSessionEvent.Target_targetInfoChanged, onTargetInfoChangedListener); + this.connection.on(ConnectionEvents.Target_targetInfoChanged, onTargetInfoChangedListener); + this.listeners.put(ConnectionEvents.Target_targetInfoChanged, onTargetInfoChangedListener); - Consumer onSessionDetachedListener = this::onSessionDetached; - this.connection.on(CDPSession.CDPSessionEvent.sessionDetached, onSessionDetachedListener); - this.listeners.put(CDPSession.CDPSessionEvent.sessionDetached, onSessionDetachedListener); + Consumer onSessionDetachedListener = this::onSessionDetached; + this.connection.on(ConnectionEvents.sessionDetached, onSessionDetachedListener); + this.listeners.put(ConnectionEvents.sessionDetached, onSessionDetachedListener); this.setupAttachmentListeners(this.connection); } @@ -80,7 +84,7 @@ public void storeExistingTargetsForInit() { this.discoveredTargetsByTargetId.forEach((targetId, targetInfo) -> { boolean isPageOrFrame = "page".equals(targetInfo.getType()) || "iframe".equals(targetInfo.getType()); boolean isExtension = targetInfo.getUrl().startsWith("chrome-extension://"); - Target targetForFilter = new Target(targetInfo, null, null, this, null); + CdpTarget targetForFilter = new CdpTarget(targetInfo, null, null, this, null); if ((this.targetFilterCallback == null || this.targetFilterCallback.apply(targetForFilter)) && isPageOrFrame && !isExtension) { this.targetsIdsForInit.add(targetInfo.getTargetId()); } @@ -113,12 +117,12 @@ public void dispose() { } @Override - public Map getAvailableTargets() { + public Map getAvailableTargets() { return this.attachedTargetsByTargetId; } @Override - public List getChildTargets(Target target) { + public List getChildTargets(CdpTarget target) { return target.childTargets(); } @@ -126,15 +130,15 @@ private void setupAttachmentListeners(Connection connection) { Consumer listener = (event) -> this.onAttachedToTarget(connection, event); ValidateUtil.assertArg(!this.attachedToTargetListenersByConnection.containsKey(connection), "Already attached to connection"); this.attachedToTargetListenersByConnection.put(connection, listener); - connection.on(CDPSession.CDPSessionEvent.Target_attachedToTarget, listener); + connection.on(ConnectionEvents.Target_attachedToTarget, listener); Consumer detachedListener = this::onDetachedFromTarget; ValidateUtil.assertArg(!this.detachedFromTargetListenersByConnection.containsKey(connection), "Already attached to connection"); this.detachedFromTargetListenersByConnection.put(connection, detachedListener); - connection.on(CDPSession.CDPSessionEvent.Target_detachedFromTarget, detachedListener); + connection.on(ConnectionEvents.Target_detachedFromTarget, detachedListener); } private void onDetachedFromTarget(DetachedFromTargetEvent event) { - Target target = this.attachedTargetsBySessionId.remove(event.getSessionId()); + CdpTarget target = this.attachedTargetsBySessionId.remove(event.getSessionId()); if (target == null) { return; } @@ -143,12 +147,12 @@ private void onDetachedFromTarget(DetachedFromTargetEvent event) { } private void onDetachedFromTarget(CDPSession parentSession, DetachedFromTargetEvent event) { - Target target = this.attachedTargetsBySessionId.get(event.getSessionId()); + CdpTarget target = this.attachedTargetsBySessionId.get(event.getSessionId()); this.attachedTargetsBySessionId.remove(event.getSessionId()); if (target == null) { return; } - parentSession.getTarget().removeChildTarget(target); + ((CdpCDPSession)parentSession).getTarget().removeChildTarget(target); this.attachedTargetsByTargetId.remove(target.getTargetId()); this.emit(TargetManagerEvent.TargetGone, target); } @@ -168,14 +172,14 @@ private void onAttachedToTarget(Connection parentConnection, AttachedToTargetEve if (this.attachedTargetsByTargetId.containsKey(targetInfo.getTargetId())) { return; } - Target target = this.targetFactory.create(targetInfo, null, null); + CdpTarget target = this.targetFactory.create(targetInfo, null, null); target.initialize(); this.attachedTargetsByTargetId.put(targetInfo.getTargetId(), target); this.emit(TargetManagerEvent.TargetAvailable, target); return; } boolean isExistingTarget = this.attachedTargetsByTargetId.containsKey(targetInfo.getTargetId()); - Target target = isExistingTarget ? this.attachedTargetsByTargetId.get(targetInfo.getTargetId()) : this.targetFactory.create(targetInfo, session, null); + CdpTarget target = isExistingTarget ? this.attachedTargetsByTargetId.get(targetInfo.getTargetId()) : this.targetFactory.create(targetInfo, session, null); if (this.targetFilterCallback != null && !this.targetFilterCallback.apply(target)) { this.ignoredTargets.add(targetInfo.getTargetId()); this.finishInitializationIfReady(targetInfo.getTargetId()); @@ -184,14 +188,14 @@ private void onAttachedToTarget(Connection parentConnection, AttachedToTargetEve } this.setupAttachmentListeners(session); if (isExistingTarget) { - session.setTarget(target); + ((CdpCDPSession)session).setTarget(target); this.attachedTargetsBySessionId.put(session.id(), this.attachedTargetsByTargetId.get(targetInfo.getTargetId())); } else { target.initialize(); this.attachedTargetsByTargetId.put(targetInfo.getTargetId(), target); this.attachedTargetsBySessionId.put(session.id(), target); } - parentConnection.emit(CDPSession.CDPSessionEvent.CDPSession_Ready, session); + parentConnection.emit(ConnectionEvents.CDPSession_Ready, session); this.targetsIdsForInit.remove(target.getTargetId()); if (!isExistingTarget) { this.emit(TargetManagerEvent.TargetAvailable, target); @@ -226,14 +230,14 @@ private void onAttachedToTarget(CDPSession parentSession, AttachedToTargetEvent if (this.attachedTargetsByTargetId.containsKey(targetInfo.getTargetId())) { return; } - Target target = this.targetFactory.create(targetInfo, null, null); + CdpTarget target = this.targetFactory.create(targetInfo, null, null); target.initialize(); this.attachedTargetsByTargetId.put(targetInfo.getTargetId(), target); this.emit(TargetManagerEvent.TargetAvailable, target); return; } boolean isExistingTarget = this.attachedTargetsByTargetId.containsKey(targetInfo.getTargetId()); - Target target = isExistingTarget ? this.attachedTargetsByTargetId.get(targetInfo.getTargetId()) : this.targetFactory.create(targetInfo, session, parentSession); + CdpTarget target = isExistingTarget ? this.attachedTargetsByTargetId.get(targetInfo.getTargetId()) : this.targetFactory.create(targetInfo, session, parentSession); if (this.targetFilterCallback != null && !this.targetFilterCallback.apply(target)) { this.ignoredTargets.add(targetInfo.getTargetId()); this.finishInitializationIfReady(targetInfo.getTargetId()); @@ -242,16 +246,16 @@ private void onAttachedToTarget(CDPSession parentSession, AttachedToTargetEvent } this.setupAttachmentListeners(session); if (isExistingTarget) { - session.setTarget(target); + ((CdpCDPSession)session).setTarget(target); this.attachedTargetsBySessionId.put(session.id(), this.attachedTargetsByTargetId.get(targetInfo.getTargetId())); } else { target.initialize(); this.attachedTargetsByTargetId.put(targetInfo.getTargetId(), target); this.attachedTargetsBySessionId.put(session.id(), target); } - Target parentTarget = parentSession.getTarget(); + CdpTarget parentTarget = ((CdpCDPSession)parentSession).getTarget(); parentTarget.addChildTarget(target); - parentSession.emit(CDPSession.CDPSessionEvent.CDPSession_Ready, session); + parentSession.emit(ConnectionEvents.CDPSession_Ready, session); this.targetsIdsForInit.remove(target.getTargetId()); if (!isExistingTarget) { this.emit(TargetManagerEvent.TargetAvailable, target); @@ -313,11 +317,11 @@ private void setupAttachmentListeners(CDPSession session) { Consumer listener = (event) -> this.onAttachedToTarget(session, event); ValidateUtil.assertArg(!this.attachedToTargetListenersBySession.containsKey(session), "Already attached to connection"); this.attachedToTargetListenersBySession.put(session, listener); - session.on(CDPSession.CDPSessionEvent.Target_attachedToTarget, listener); + session.on(ConnectionEvents.Target_attachedToTarget, listener); Consumer detachedListener = (event) -> this.onDetachedFromTarget(session, event); ValidateUtil.assertArg(!this.detachedFromTargetListenersBySession.containsKey(session), "Already attached to connection"); this.detachedFromTargetListenersBySession.put(session, detachedListener); - session.on(CDPSession.CDPSessionEvent.Target_detachedFromTarget, detachedListener); + session.on(ConnectionEvents.Target_detachedFromTarget, detachedListener); } private void onTargetCreated(TargetCreatedEvent event) { @@ -327,7 +331,7 @@ private void onTargetCreated(TargetCreatedEvent event) { if (this.attachedTargetsByTargetId.containsKey(event.getTargetInfo().getTargetId())) { return; } - Target target = this.targetFactory.create(event.getTargetInfo(), null, null); + CdpTarget target = this.targetFactory.create(event.getTargetInfo(), null, null); target.initialize(); this.attachedTargetsByTargetId.put(event.getTargetInfo().getTargetId(), target); } @@ -339,7 +343,7 @@ private void onTargetDestroyed(TargetDestroyedEvent event) { this.finishInitializationIfReady(event.getTargetId()); if (targetInfo != null) { if ("service_worker".equals(targetInfo.getType()) && this.attachedTargetsByTargetId.containsKey(event.getTargetId())) { - Target target = this.attachedTargetsByTargetId.get(event.getTargetId()); + CdpTarget target = this.attachedTargetsByTargetId.get(event.getTargetId()); if (target != null) { this.emit(TargetManagerEvent.TargetGone, target); this.attachedTargetsByTargetId.remove(event.getTargetId()); @@ -353,17 +357,17 @@ private void onTargetInfoChanged(TargetInfoChangedEvent event) { if (this.ignoredTargets.contains(event.getTargetInfo().getTargetId()) || !this.attachedTargetsByTargetId.containsKey(event.getTargetInfo().getTargetId()) || !event.getTargetInfo().getAttached()) { return; } - Target target = this.attachedTargetsByTargetId.get(event.getTargetInfo().getTargetId()); + CdpTarget target = this.attachedTargetsByTargetId.get(event.getTargetInfo().getTargetId()); if (target == null) { return; } String previousURL = target.url(); - boolean wasInitialized = target.initializedResult.isDone() && Target.InitializationStatus.SUCCESS.equals(target.initializedResult.get()); + boolean wasInitialized = target.initializedResult.isDone() && CdpTarget.InitializationStatus.SUCCESS.equals(target.initializedResult.get()); if (isPageTargetBecomingPrimary(target, event.getTargetInfo())) { CDPSession session = target.session(); Objects.requireNonNull(session, "Target that is being activated is missing a CDPSession."); if (session.parentSession() != null) { - session.parentSession().emit(CDPSession.CDPSessionEvent.CDPSession_Swapped, session); + session.parentSession().emit(ConnectionEvents.CDPSession_Swapped, session); } } target.targetInfoChanged(event.getTargetInfo()); @@ -372,22 +376,22 @@ private void onTargetInfoChanged(TargetInfoChangedEvent event) { } } - private boolean isPageTargetBecomingPrimary(Target target, TargetInfo targetInfo) { + private boolean isPageTargetBecomingPrimary(CdpTarget target, TargetInfo targetInfo) { return StringUtil.isNotEmpty(target.subtype()) && StringUtil.isEmpty(targetInfo.getSubtype()); } - public void onSessionDetached(CDPSession session) { + public void onSessionDetached(CdpCDPSession session) { this.removeAttachmentListeners(session); } private void removeAttachmentListeners(CDPSession session) { Consumer listener = this.attachedToTargetListenersBySession.get(session); if (listener != null) { - session.off(CDPSession.CDPSessionEvent.Target_attachedToTarget, listener); + session.off(ConnectionEvents.Target_attachedToTarget, listener); this.attachedToTargetListenersBySession.remove(session); } if (this.detachedFromTargetListenersBySession.containsKey(session)) { - session.off(CDPSession.CDPSessionEvent.Target_detachedFromTarget, this.detachedFromTargetListenersBySession.get(session)); + session.off(ConnectionEvents.Target_detachedFromTarget, this.detachedFromTargetListenersBySession.get(session)); this.detachedFromTargetListenersBySession.remove(session); } } @@ -395,11 +399,11 @@ private void removeAttachmentListeners(CDPSession session) { private void removeAttachmentListeners(Connection connection) { Consumer listener = this.attachedToTargetListenersByConnection.get(connection); if (listener != null) { - connection.off(CDPSession.CDPSessionEvent.Target_attachedToTarget, listener); + connection.off(ConnectionEvents.Target_attachedToTarget, listener); this.attachedToTargetListenersByConnection.remove(connection); } if (this.detachedFromTargetListenersByConnection.containsKey(connection)) { - connection.off(CDPSession.CDPSessionEvent.Target_detachedFromTarget, this.detachedFromTargetListenersByConnection.get(connection)); + connection.off(ConnectionEvents.Target_detachedFromTarget, this.detachedFromTargetListenersByConnection.get(connection)); this.detachedFromTargetListenersByConnection.remove(connection); } } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Coverage.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/Coverage.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/core/Coverage.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/Coverage.java index 9caebac6..26baa1ed 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/Coverage.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/Coverage.java @@ -1,14 +1,15 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; import com.fasterxml.jackson.core.JsonProcessingException; -import com.ruiyun.jvppeteer.entities.CSSCoverageOptions; -import com.ruiyun.jvppeteer.entities.CoveragePoint; -import com.ruiyun.jvppeteer.entities.JSCoverageEntry; -import com.ruiyun.jvppeteer.entities.JSCoverageOptions; -import com.ruiyun.jvppeteer.entities.Range; -import com.ruiyun.jvppeteer.entities.CoverageEntry; -import com.ruiyun.jvppeteer.entities.CoverageRange; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.cdp.entities.CSSCoverageOptions; +import com.ruiyun.jvppeteer.cdp.entities.CoveragePoint; +import com.ruiyun.jvppeteer.cdp.entities.JSCoverageEntry; +import com.ruiyun.jvppeteer.cdp.entities.JSCoverageOptions; +import com.ruiyun.jvppeteer.cdp.entities.Range; +import com.ruiyun.jvppeteer.cdp.entities.CoverageEntry; +import com.ruiyun.jvppeteer.cdp.entities.CoverageRange; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import com.ruiyun.jvppeteer.util.ValidateUtil; import java.util.ArrayList; @@ -30,7 +31,7 @@ public Coverage(CDPSession client) { this.jsCoverage = new JSCoverage(client); } - public void updateClient(CDPSession client) { + public void updateClient(CdpCDPSession client) { this.cssCoverage.updateClient(client); this.jsCoverage.updateClient(client); } diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/DevToolsTarget.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/DevToolsTarget.java new file mode 100644 index 00000000..7d496cb1 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/DevToolsTarget.java @@ -0,0 +1,13 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.entities.Viewport; +import com.ruiyun.jvppeteer.transport.SessionFactory; + +public class DevToolsTarget extends PageTarget { + + public DevToolsTarget(TargetInfo targetInfo, CDPSession session, CdpBrowserContext cdpBrowserContext, TargetManager targetManager, SessionFactory sessionFactory, Viewport defaultViewport) { + super(targetInfo, session, cdpBrowserContext, targetManager, sessionFactory, defaultViewport); + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/EmulationManager.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/EmulationManager.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/core/EmulationManager.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/EmulationManager.java index 7689c98f..d4d3bcc4 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/EmulationManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/EmulationManager.java @@ -1,27 +1,29 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; import com.ruiyun.jvppeteer.common.MediaType; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.ClientProvider; -import com.ruiyun.jvppeteer.entities.CpuThrottlingState; -import com.ruiyun.jvppeteer.entities.DefaultBackgroundColorState; -import com.ruiyun.jvppeteer.entities.EmulatedState; -import com.ruiyun.jvppeteer.entities.GeoLocationState; -import com.ruiyun.jvppeteer.entities.GeolocationOptions; -import com.ruiyun.jvppeteer.entities.IdleOverridesState; -import com.ruiyun.jvppeteer.entities.JavascriptEnabledState; -import com.ruiyun.jvppeteer.entities.MediaFeature; -import com.ruiyun.jvppeteer.entities.MediaFeaturesState; -import com.ruiyun.jvppeteer.entities.MediaTypeState; -import com.ruiyun.jvppeteer.entities.RGBA; -import com.ruiyun.jvppeteer.entities.ScreenOrientation; -import com.ruiyun.jvppeteer.entities.TimezoneState; -import com.ruiyun.jvppeteer.entities.Updater; -import com.ruiyun.jvppeteer.entities.Viewport; -import com.ruiyun.jvppeteer.entities.ViewportState; -import com.ruiyun.jvppeteer.entities.VisionDeficiency; -import com.ruiyun.jvppeteer.entities.VisionDeficiencyState; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.cdp.entities.ClientProvider; +import com.ruiyun.jvppeteer.cdp.entities.CpuThrottlingState; +import com.ruiyun.jvppeteer.cdp.entities.DefaultBackgroundColorState; +import com.ruiyun.jvppeteer.cdp.entities.EmulatedState; +import com.ruiyun.jvppeteer.cdp.entities.GeoLocationState; +import com.ruiyun.jvppeteer.cdp.entities.GeolocationOptions; +import com.ruiyun.jvppeteer.cdp.entities.IdleOverridesState; +import com.ruiyun.jvppeteer.cdp.entities.JavascriptEnabledState; +import com.ruiyun.jvppeteer.cdp.entities.MediaFeature; +import com.ruiyun.jvppeteer.cdp.entities.MediaFeaturesState; +import com.ruiyun.jvppeteer.cdp.entities.MediaTypeState; +import com.ruiyun.jvppeteer.cdp.entities.RGBA; +import com.ruiyun.jvppeteer.cdp.entities.ScreenOrientation; +import com.ruiyun.jvppeteer.cdp.entities.TimezoneState; +import com.ruiyun.jvppeteer.cdp.entities.Updater; +import com.ruiyun.jvppeteer.cdp.entities.Viewport; +import com.ruiyun.jvppeteer.cdp.entities.ViewportState; +import com.ruiyun.jvppeteer.cdp.entities.VisionDeficiency; +import com.ruiyun.jvppeteer.cdp.entities.VisionDeficiencyState; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; import org.slf4j.Logger; @@ -38,7 +40,7 @@ public class EmulationManager implements ClientProvider { private static final Logger LOGGER = LoggerFactory.getLogger(EmulationManager.class); final List> states = new ArrayList<>(); - final Set secondaryClients = new HashSet<>(); + final Set secondaryClients = new HashSet<>(); private CDPSession client; private boolean emulatingMobile = false; private boolean hasTouch = false; @@ -57,7 +59,7 @@ public EmulationManager(CDPSession client) { this.client = client; } - public void updateClient(CDPSession client) { + public void updateClient(CdpCDPSession client) { this.client = client; this.secondaryClients.remove(client); } @@ -75,16 +77,16 @@ public List clients() { return cdpSessionList; } - public void registerSpeculativeSession(CDPSession _client){ + public void registerSpeculativeSession(CdpCDPSession _client){ this.secondaryClients.add(_client); - client.once(CDPSession.CDPSessionEvent.CDPSession_Disconnected, event -> this.secondaryClients.remove(_client) + client.once(ConnectionEvents.CDPSession_Disconnected, event -> this.secondaryClients.remove(_client) ); // We don't await here because we want to register all state changes before // the target is unpaused. this.states.forEach(EmulatedState::send); } - public boolean getJavascriptEnabled() { + public boolean javascriptEnabled() { return this.javascriptEnabledState.state.javaScriptEnabled; } diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/ExecutionContext.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/ExecutionContext.java new file mode 100644 index 00000000..72b775a5 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/ExecutionContext.java @@ -0,0 +1,451 @@ +package com.ruiyun.jvppeteer.cdp.core; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.EventEmitter; +import com.ruiyun.jvppeteer.api.core.JSHandle; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.cdp.entities.Binding; +import com.ruiyun.jvppeteer.cdp.entities.BindingPayload; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateResponse; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateType; +import com.ruiyun.jvppeteer.cdp.entities.ExceptionDetails; +import com.ruiyun.jvppeteer.cdp.entities.ExecutionContextDescription; +import com.ruiyun.jvppeteer.cdp.entities.RemoteObject; +import com.ruiyun.jvppeteer.cdp.events.BindingCalledEvent; +import com.ruiyun.jvppeteer.cdp.events.ConsoleAPICalledEvent; +import com.ruiyun.jvppeteer.cdp.events.ExecutionContextDestroyedEvent; +import com.ruiyun.jvppeteer.common.ARIAQueryHandler; +import com.ruiyun.jvppeteer.common.BindingFunction; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.common.LazyArg; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.util.Helper; +import com.ruiyun.jvppeteer.util.StringUtil; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import static com.ruiyun.jvppeteer.common.Constant.CDP_BINDING_PREFIX; +import static com.ruiyun.jvppeteer.common.Constant.Infinity; +import static com.ruiyun.jvppeteer.common.Constant.NaN; +import static com.ruiyun.jvppeteer.common.Constant.Navigate_Infinity; +import static com.ruiyun.jvppeteer.common.Constant.Navigate_Zero; +import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; +import static com.ruiyun.jvppeteer.common.Constant.Source; +import static com.ruiyun.jvppeteer.util.Helper.setSourceUrlComment; +import static com.ruiyun.jvppeteer.util.Helper.throwError; + +public class ExecutionContext extends EventEmitter { + private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionContext.class); + private final CDPSession client; + private String name; + private final int id; + private final IsolatedWorld world; + private final Map> listener = new HashMap<>(); + private final Map bindings = new HashMap<>(); + private volatile JSHandle puppeteerUtil; + + public ExecutionContext(CDPSession client, ExecutionContextDescription contextPayload, IsolatedWorld world) { + this.client = client; + this.world = world; + this.id = contextPayload.getId(); + if (StringUtil.isNotEmpty(contextPayload.getName())) { + this.name = contextPayload.getName(); + } + setListener(client); + } + + private void setListener(CDPSession client) { + Consumer bindingCalled = this::onBindingCalled; + client.on(ConnectionEvents.Runtime_bindingCalled, bindingCalled); + this.listener.put(ConnectionEvents.Runtime_bindingCalled, bindingCalled); + + Consumer executionContextDestroyed = event -> { + if (event.getExecutionContextId() == this.id) { + this.dispose(); + } + }; + client.on(ConnectionEvents.Runtime_executionContextDestroyed, executionContextDestroyed); + this.listener.put(ConnectionEvents.Runtime_executionContextDestroyed, executionContextDestroyed); + + Consumer executionContextsCleared = event -> this.dispose(); + client.on(ConnectionEvents.Runtime_executionContextsCleared, executionContextsCleared); + this.listener.put(ConnectionEvents.Runtime_executionContextsCleared, executionContextsCleared); + + Consumer consoleAPICalled = ExecutionContext.this::onConsoleAPI; + client.on(ConnectionEvents.Runtime_consoleAPICalled, consoleAPICalled); + this.listener.put(ConnectionEvents.Runtime_consoleAPICalled, consoleAPICalled); + + Consumer disconnected = event -> this.dispose(); + client.on(ConnectionEvents.CDPSession_Disconnected, disconnected); + this.listener.put(ConnectionEvents.CDPSession_Disconnected, disconnected); + } + + public void addBinding(Binding binding) { + if (this.bindings.containsKey(binding.name())) { + return; + } + Map params = ParamsFactory.create(); + params.put("name", CDP_BINDING_PREFIX + binding.name()); + if (StringUtil.isNotEmpty(this.name)) { + params.put("executionContextName", this.name); + } else { + params.put("executionContextId", this.id); + } + synchronized (this) { + try { + this.client.send("Runtime.addBinding", params); + List args = new ArrayList<>(); + args.add("internal"); + args.add(binding.name()); + args.add(CDP_BINDING_PREFIX); + this.evaluate(addPageBinding(), args); + this.bindings.put(binding.name(), binding); + } catch (Exception e) { + if (e.getMessage().contains("Execution context was destroyed")) { + return; + } + if (e.getMessage().contains("Cannot find context with specified id'")) { + return; + } + LOGGER.error("addBinding error:", e); + } + } + } + + private String addPageBinding() { + return "function addPageBinding(type, name, prefix) {\n" + + " // Depending on the frame loading state either Runtime.evaluate or\n" + + " // Page.addScriptToEvaluateOnNewDocument might succeed. Let's check that we \n" + + " // don't re-wrap Puppeteer's binding. \n" + + " // @ts-expect-error: In a different context. \n" + + " if (globalThis[name]) {\n" + + " return;\n" + + " }\n" + + " // We replace the CDP binding with a Puppeteer binding.\n" + + " Object.assign(globalThis, {\n" + + " [name](...args) {\n" + + " // This is the Puppeteer binding.\n" + + " // @ts-expect-error: In a different context.\n" + + " const callPuppeteer = globalThis[name];\n" + + " callPuppeteer.args ??= new Map();\n" + + " callPuppeteer.callbacks ??= new Map();\n" + + " const seq = (callPuppeteer.lastSeq ?? 0) + 1;\n" + + " callPuppeteer.lastSeq = seq;\n" + + " callPuppeteer.args.set(seq, args);\n" + + " // @ts-expect-error: In a different context.\n" + + " // Needs to be the same as CDP_BINDING_PREFIX.\n" + + " globalThis[prefix + name](JSON.stringify({\n" + + " type,\n" + + " name,\n" + + " seq,\n" + + " args,\n" + + " isTrivial: !args.some(value => {\n" + + " return value instanceof Node;\n" + + " }),\n" + + " }));\n" + + " return new Promise((resolve, reject) => {\n" + + " callPuppeteer.callbacks.set(seq, {\n" + + " resolve(value) {\n" + + " callPuppeteer.args.delete(seq);\n" + + " resolve(value);\n" + + " },\n" + + " reject(value) {\n" + + " callPuppeteer.args.delete(seq);\n" + + " reject(value);\n" + + " },\n" + + " });\n" + + " });\n" + + " },\n" + + " })\n" + + "}"; + } + + private void onBindingCalled(BindingCalledEvent event) { + if (event.getExecutionContextId() != this.id) { + return; + } + String payloadStr = event.getPayload(); + BindingPayload payload; + try { + payload = OBJECTMAPPER.readValue(payloadStr, BindingPayload.class); + } catch (JsonProcessingException e) { + return; + } + if (!"internal".equals(payload.getType())) { + this.emit(ExecutionContextEvent.Bindingcalled, event); + return; + } + if (!this.bindings.containsKey(payload.getName())) { + this.emit(ExecutionContextEvent.Bindingcalled, event); + return; + } + Binding binding = this.bindings.get(payload.getName()); + try { + if (binding != null) { + binding.run(this, payload.getSeq(), payload.getArgs(), payload.getIsTrivial()); + } + } catch (Exception e) { + LOGGER.error("onBindingCalled error", e); + } + } + + public int getId() { + return id; + } + + private void onConsoleAPI(ConsoleAPICalledEvent event) { + if (event.getExecutionContextId() != this.id) { + return; + } + this.emit(ExecutionContextEvent.Consoleapicalled, event); + } + + public CdpJSHandle evaluateHandle(String pptrFunction, EvaluateType type, List args) throws JsonProcessingException { + Object handle = this.evaluateInternal(false, pptrFunction, type, args); + if (handle == null) { + return null; + } + return (CdpJSHandle) handle; + } + + public JSHandle evaluateHandle(String pptrFunction, List args) throws JsonProcessingException { + Object handle = this.evaluateInternal(false, pptrFunction, Helper.isFunction(pptrFunction) ? EvaluateType.FUNCTION : EvaluateType.STRING, args); + if (handle == null) { + return null; + } + return (JSHandle) handle; + } + + public Object evaluate(String pptrFunction, EvaluateType type, List args) throws JsonProcessingException { + if (type == null) { + type = Helper.isFunction(pptrFunction) ? EvaluateType.FUNCTION : EvaluateType.STRING; + } + return this.evaluateInternal(true, pptrFunction, type, args); + } + + public Object evaluate(String pptrFunction, List args) throws JsonProcessingException { + return this.evaluate(pptrFunction, null, args); + } + + private EvaluateResponse rewriteError(Exception e) { + if (e.getMessage() != null && e.getMessage().contains("Object reference chain is too long")) { + RemoteObject remoteObject = new RemoteObject(); + remoteObject.setType("undefined"); + EvaluateResponse response = new EvaluateResponse(); + response.setResult(remoteObject); + return response; + } + if (e.getMessage() != null && e.getMessage().contains("Object couldn't be returned by value")) { + RemoteObject remoteObject = new RemoteObject(); + remoteObject.setType("undefined"); + EvaluateResponse response = new EvaluateResponse(); + response.setResult(remoteObject); + return response; + } + + if (e.getMessage() != null && (e.getMessage().endsWith("Cannot find context with specified id") || e.getMessage().endsWith("Inspected target navigated or closed"))) { + throw new JvppeteerException("Execution context was destroyed, most likely because of a navigation."); + } + throwError(e); + return null; + } + + /** + * 这里的EvaluateType有时候是明确指定为String的,不指定的情况下,会自动判断是字符串还是函数 + *

+ * {@link CdpFrame#addExposedFunctionBinding(Binding)}就指定了是String + */ + private Object evaluateInternal(boolean returnByValue, String pptrFunction, EvaluateType type, List args) throws JsonProcessingException { + pptrFunction = setSourceUrlComment(pptrFunction); + if (EvaluateType.STRING.equals(type)) { + Map params = new HashMap<>(); + params.put("expression", pptrFunction); + params.put("contextId", this.id); + params.put("returnByValue", returnByValue); + params.put("awaitPromise", true); + params.put("userGesture", true); + EvaluateResponse result; + try { + result = OBJECTMAPPER.treeToValue(this.client.send("Runtime.evaluate", params), EvaluateResponse.class); + } catch (Exception e) { + result = rewriteError(e); + } + ExceptionDetails exceptionDetails = result.getExceptionDetails(); + if (exceptionDetails != null) { + Object evaluationError = Helper.createCdpEvaluationError(exceptionDetails); + if (evaluationError instanceof EvaluateException) { + throw (EvaluateException) evaluationError; + } else { + throw new EvaluateException(OBJECTMAPPER.writeValueAsString(evaluationError)); + } + } + RemoteObject remoteObject = result.getResult(); + return returnByValue ? Helper.valueFromRemoteObject(remoteObject) : this.world.createJSHandle(remoteObject); + } + Map params = new HashMap<>(); + List argList = new ArrayList<>(); + if (args != null) { + for (Object arg : args) { + if (Objects.nonNull(arg) && arg instanceof LazyArg) { + initPuppeteerUtil(); + argList.add(convertArgument(this, this.puppeteerUtil)); + } else { + argList.add(convertArgument(this, arg)); + } + } + } + params.put("functionDeclaration", pptrFunction); + params.put("executionContextId", this.id); + params.put("arguments", argList); + params.put("returnByValue", returnByValue); + params.put("awaitPromise", true); + params.put("userGesture", true); + EvaluateResponse callFunctionOnPromise; + try { + try {//第一个try用来添加message,第二个try是重写错误,返回结果 + callFunctionOnPromise = OBJECTMAPPER.treeToValue(this.client.send("Runtime.callFunctionOn", params), EvaluateResponse.class); + } catch (Exception e) { + if (e.getMessage().startsWith("Converting circular structure to JSON")) + throw new JvppeteerException(e.getMessage() + " Recursive objects are not allowed."); + else + throw e; + } + } catch (Exception e) { + callFunctionOnPromise = rewriteError(e); + } + if (callFunctionOnPromise == null) { + return null; + } + ExceptionDetails exceptionDetails = callFunctionOnPromise.getExceptionDetails(); + if (exceptionDetails != null) { + Object evaluationError = Helper.createCdpEvaluationError(exceptionDetails); + if (evaluationError instanceof EvaluateException) { + throw (EvaluateException) evaluationError; + } else { + throw new EvaluateException(OBJECTMAPPER.writeValueAsString(evaluationError)); + } + } + RemoteObject remoteObject = callFunctionOnPromise.getResult(); + return returnByValue ? Helper.valueFromRemoteObject(remoteObject) : this.world.createJSHandle(remoteObject); + } + + private void initPuppeteerUtil() throws JsonProcessingException { + if (this.puppeteerUtil == null) { + synchronized (this) { + if (this.puppeteerUtil == null) { + BindingFunction queryOneFunction = (args) -> { + ElementHandle element = (ElementHandle) args.get(0); + String selector = (String) args.get(1); + try { + ARIAQueryHandler ariaQueryHandler = new ARIAQueryHandler(); + return ariaQueryHandler.queryOne(element, selector); + } catch (JsonProcessingException e) { + return null; + } + }; + Binding ariaQuerySelectorBinding = new Binding("__ariaQuerySelector", queryOneFunction, ""); + BindingFunction queryAllFunction = (args) -> { + ElementHandle element = (ElementHandle) args.get(0); + String selector = (String) args.get(1); + ARIAQueryHandler ariaQueryHandler = new ARIAQueryHandler(); + try { + return ariaQueryHandler.queryAll(element, selector); + } catch (JsonProcessingException e) { + return null; + } + }; + Binding ariaQuerySelectorAllBinding = new Binding("__ariaQuerySelectorAll", queryAllFunction, ""); + this.addBinding(ariaQuerySelectorBinding); + this.addBinding(ariaQuerySelectorAllBinding); + this.puppeteerUtil = this.evaluateHandle(Source, EvaluateType.STRING, null); + } + } + } + } + + private JsonNode convertArgument(ExecutionContext context, Object arg) { + ObjectNode objectNode = Constant.OBJECTMAPPER.createObjectNode(); + if (Objects.isNull(arg)) { + return objectNode; + } + if (arg instanceof BigInteger) { // eslint-disable-line valid-typeof + objectNode.put("unserializableValue", arg + "n"); + return objectNode; + } + if (Navigate_Zero.equals(arg)) { + objectNode.put("unserializableValue", Navigate_Zero); + return objectNode; + } + if (Infinity.equals(arg)) { + objectNode.put("unserializableValue", Infinity); + return objectNode; + } + if (Navigate_Infinity.equals(arg)) { + objectNode.put("unserializableValue", Navigate_Infinity); + return objectNode; + } + if (NaN.equals(arg)) { + objectNode.put("unserializableValue", NaN); + return objectNode; + } + CdpJSHandle objectHandle = arg instanceof CdpJSHandle ? (CdpJSHandle) arg : null; + if (objectHandle != null) { + if (objectHandle.realm() != context.world()) { + throw new JvppeteerException("JSHandles can be evaluated only in the context they were created!"); + } + if (objectHandle.disposed()) { + throw new JvppeteerException("JSHandle is disposed!" + objectHandle.remoteObject().getObjectId()); + + } + if (objectHandle.remoteObject().getUnserializableValue() != null) { + objectNode.put("unserializableValue", objectHandle.remoteObject().getUnserializableValue()); + return objectNode; + } + if (StringUtil.isEmpty(objectHandle.remoteObject().getObjectId())) { + return objectNode.putPOJO("value", objectHandle.remoteObject().getValue()); + } + return objectNode.put("objectId", objectHandle.remoteObject().getObjectId()); + } + return objectNode.putPOJO("value", arg); + } + + private IsolatedWorld world() { + return this.world; + } + + + public void dispose() { + this.listener.forEach(this.client::off); + this.emit(ExecutionContextEvent.Disposed, true); + } + + public enum ExecutionContextEvent { + Disposed("disposed"), + Consoleapicalled("consoleapicalled"), + Bindingcalled("bindingcalled"); + private String eventType; + + ExecutionContextEvent(String eventType) { + + } + + public String getEventType() { + return eventType; + } + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/FileChooser.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FileChooser.java similarity index 90% rename from src/main/java/com/ruiyun/jvppeteer/core/FileChooser.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/FileChooser.java index 2e1eb259..af4151e8 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/FileChooser.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FileChooser.java @@ -1,10 +1,10 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; import com.fasterxml.jackson.core.JsonProcessingException; -import com.ruiyun.jvppeteer.events.FileChooserOpenedEvent; +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.cdp.events.FileChooserOpenedEvent; import com.ruiyun.jvppeteer.exception.EvaluateException; import com.ruiyun.jvppeteer.util.ValidateUtil; - import java.util.List; /** diff --git a/src/main/java/com/ruiyun/jvppeteer/core/FirefoxTargetManager.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FirefoxTargetManager.java similarity index 72% rename from src/main/java/com/ruiyun/jvppeteer/core/FirefoxTargetManager.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/FirefoxTargetManager.java index eb408867..f312b0cd 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/FirefoxTargetManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FirefoxTargetManager.java @@ -1,14 +1,17 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Connection; +import com.ruiyun.jvppeteer.api.core.Target; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; import com.ruiyun.jvppeteer.common.AwaitableResult; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.FilterEntry; -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.events.AttachedToTargetEvent; -import com.ruiyun.jvppeteer.events.TargetCreatedEvent; -import com.ruiyun.jvppeteer.events.TargetDestroyedEvent; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.transport.Connection; +import com.ruiyun.jvppeteer.cdp.entities.FilterEntry; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.events.AttachedToTargetEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetCreatedEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetDestroyedEvent; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -24,11 +27,11 @@ public class FirefoxTargetManager extends TargetManager { private final Connection connection; private final Map discoveredTargetsByTargetId = new HashMap<>(); - private final Map availableTargetsByTargetId = new ConcurrentHashMap<>(); + private final Map availableTargetsByTargetId = new ConcurrentHashMap<>(); private final Function targetFilterCallback; private final TargetFactory targetFactory; - private final Map> listeners = new HashMap<>(); - private final Map availableTargetsBySessionId = new HashMap<>(); + private final Map> listeners = new HashMap<>(); + private final Map availableTargetsBySessionId = new HashMap<>(); private Set targetsIdsForInit = new HashSet<>(); private final AwaitableResult initializeResult = AwaitableResult.create(); private final Map> attachedToTargetListenersBySession = new WeakHashMap<>(); @@ -41,23 +44,23 @@ public FirefoxTargetManager(Connection connection, TargetFactory targetFactory, this.targetFactory = targetFactory; Consumer onTargetCreatedListener = this::onTargetCreated; - this.connection.on(CDPSession.CDPSessionEvent.Target_targetCreated, onTargetCreatedListener); - this.listeners.put(CDPSession.CDPSessionEvent.Target_targetCreated, onTargetCreatedListener); + this.connection.on(ConnectionEvents.Target_targetCreated, onTargetCreatedListener); + this.listeners.put(ConnectionEvents.Target_targetCreated, onTargetCreatedListener); Consumer onTargetDestroyedListener = this::onTargetDestroyed; - this.connection.on(CDPSession.CDPSessionEvent.Target_targetDestroyed, onTargetDestroyedListener); - this.listeners.put(CDPSession.CDPSessionEvent.Target_targetDestroyed, onTargetDestroyedListener); - Consumer onSessionDetachedListener = this::onSessionDetached; - this.connection.on(CDPSession.CDPSessionEvent.sessionDetached, onSessionDetachedListener); + this.connection.on(ConnectionEvents.Target_targetDestroyed, onTargetDestroyedListener); + this.listeners.put(ConnectionEvents.Target_targetDestroyed, onTargetDestroyedListener); + Consumer onSessionDetachedListener = this::onSessionDetached; + this.connection.on(ConnectionEvents.sessionDetached, onSessionDetachedListener); this.setupAttachmentListeners(this.connection); } @Override - public Map getAvailableTargets() { + public Map getAvailableTargets() { return this.availableTargetsByTargetId; } @Override - public List getChildTargets(Target target) { + public List getChildTargets(CdpTarget target) { return new ArrayList<>(); } @@ -83,14 +86,14 @@ private void setupAttachmentListeners(Connection session) { Consumer listener = (event) -> this.onAttachedToTarget(session, event); assert (!this.attachedToTargetListenersByConnection.containsKey(session)); this.attachedToTargetListenersByConnection.put(session, listener); - session.on(CDPSession.CDPSessionEvent.Target_attachedToTarget, listener); + session.on(ConnectionEvents.Target_attachedToTarget, listener); } private void setupAttachmentListeners(CDPSession session) { Consumer listener = (event) -> this.onAttachedToTarget(session, event); assert (!this.attachedToTargetListenersBySession.containsKey(session)); this.attachedToTargetListenersBySession.put(session, listener); - session.on(CDPSession.CDPSessionEvent.Target_attachedToTarget, listener); + session.on(ConnectionEvents.Target_attachedToTarget, listener); } private void onTargetCreated(TargetCreatedEvent event) { @@ -99,13 +102,13 @@ private void onTargetCreated(TargetCreatedEvent event) { } this.discoveredTargetsByTargetId.put(event.getTargetInfo().getTargetId(), event.getTargetInfo()); if ("browser".equals(event.getTargetInfo().getType()) && event.getTargetInfo().getAttached()) { - Target target = this.targetFactory.create(event.getTargetInfo(), null, null); + CdpTarget target = this.targetFactory.create(event.getTargetInfo(), null, null); target.initialize(); this.availableTargetsByTargetId.put(event.getTargetInfo().getTargetId(), target); this.finishInitializationIfReady(target.getTargetId()); return; } - Target target = this.targetFactory.create(event.getTargetInfo(), null, null); + CdpTarget target = this.targetFactory.create(event.getTargetInfo(), null, null); if (Objects.nonNull(this.targetFilterCallback) && !this.targetFilterCallback.apply(target)) { this.finishInitializationIfReady(event.getTargetInfo().getTargetId()); return; @@ -126,7 +129,7 @@ private void finishInitializationIfReady(String targetId) { private void onTargetDestroyed(TargetDestroyedEvent event) { this.discoveredTargetsByTargetId.remove(event.getTargetId()); this.finishInitializationIfReady(event.getTargetId()); - Target target = this.availableTargetsByTargetId.get(event.getTargetId()); + CdpTarget target = this.availableTargetsByTargetId.get(event.getTargetId()); if (Objects.nonNull(target)) { this.emit(TargetManagerEvent.TargetGone, target); this.availableTargetsByTargetId.remove(event.getTargetId()); @@ -141,27 +144,27 @@ public void onSessionDetached(CDPSession session) { private void removeSessionListeners(CDPSession session) { Consumer consumer = this.attachedToTargetListenersBySession.remove(session); if (Objects.nonNull(consumer)) { - session.off(CDPSession.CDPSessionEvent.Target_attachedToTarget, consumer); + session.off(ConnectionEvents.Target_attachedToTarget, consumer); } } private void onAttachedToTarget(CDPSession parentSession, AttachedToTargetEvent event) { CDPSession session = handleAttached(event); - parentSession.emit(CDPSession.CDPSessionEvent.CDPSession_Ready, session); + parentSession.emit(ConnectionEvents.CDPSession_Ready, session); } private void onAttachedToTarget(Connection parentSession, AttachedToTargetEvent event) { CDPSession session = handleAttached(event); - parentSession.emit(CDPSession.CDPSessionEvent.CDPSession_Ready, session); + parentSession.emit(ConnectionEvents.CDPSession_Ready, session); } private CDPSession handleAttached(AttachedToTargetEvent event) { TargetInfo targetInfo = event.getTargetInfo(); CDPSession session = this.connection.session(event.getSessionId()); Objects.requireNonNull(session, "Session " + event.getSessionId() + " was not created."); - Target target = this.availableTargetsByTargetId.get(targetInfo.getTargetId()); + CdpTarget target = this.availableTargetsByTargetId.get(targetInfo.getTargetId()); Objects.requireNonNull(target, "Target " + targetInfo.getTargetId() + " is missing"); - session.setTarget(target); + ((CdpCDPSession) session).setTarget(target); this.setupAttachmentListeners(session); this.availableTargetsBySessionId.put(session.id(), this.availableTargetsByTargetId.get(targetInfo.getTargetId())); return session; diff --git a/src/main/java/com/ruiyun/jvppeteer/core/FrameManager.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameManager.java similarity index 76% rename from src/main/java/com/ruiyun/jvppeteer/core/FrameManager.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameManager.java index cb44a80a..df1c97d4 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/FrameManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameManager.java @@ -1,36 +1,36 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.EventEmitter; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.api.events.FrameEvents; import com.ruiyun.jvppeteer.common.AwaitableResult; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.DeviceRequestPromptManager; import com.ruiyun.jvppeteer.common.FrameProvider; import com.ruiyun.jvppeteer.common.ParamsFactory; import com.ruiyun.jvppeteer.common.TimeoutSettings; -import com.ruiyun.jvppeteer.entities.Binding; -import com.ruiyun.jvppeteer.entities.ExecutionContextDescription; -import com.ruiyun.jvppeteer.entities.FramePayload; -import com.ruiyun.jvppeteer.entities.NewDocumentScriptEvaluation; -import com.ruiyun.jvppeteer.entities.PreloadScript; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.events.ExecutionContextCreatedEvent; -import com.ruiyun.jvppeteer.events.FrameAttachedEvent; -import com.ruiyun.jvppeteer.events.FrameDetachedEvent; -import com.ruiyun.jvppeteer.events.FrameNavigatedEvent; -import com.ruiyun.jvppeteer.events.FrameStartedLoadingEvent; -import com.ruiyun.jvppeteer.events.FrameStoppedLoadingEvent; -import com.ruiyun.jvppeteer.events.LifecycleEvent; -import com.ruiyun.jvppeteer.events.NavigatedWithinDocumentEvent; +import com.ruiyun.jvppeteer.cdp.entities.Binding; +import com.ruiyun.jvppeteer.cdp.entities.ExecutionContextDescription; +import com.ruiyun.jvppeteer.cdp.entities.FramePayload; +import com.ruiyun.jvppeteer.cdp.entities.NewDocumentScriptEvaluation; +import com.ruiyun.jvppeteer.cdp.entities.PreloadScript; +import com.ruiyun.jvppeteer.cdp.events.ExecutionContextCreatedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameAttachedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameDetachedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameNavigatedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameStartedLoadingEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameStoppedLoadingEvent; +import com.ruiyun.jvppeteer.cdp.events.LifecycleEvent; +import com.ruiyun.jvppeteer.cdp.events.NavigatedWithinDocumentEvent; import com.ruiyun.jvppeteer.exception.EvaluateException; import com.ruiyun.jvppeteer.exception.JvppeteerException; import com.ruiyun.jvppeteer.exception.TargetCloseException; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -42,37 +42,41 @@ import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import static com.ruiyun.jvppeteer.common.Constant.INTERNAL_URL; import static com.ruiyun.jvppeteer.common.Constant.MAIN_WORLD; import static com.ruiyun.jvppeteer.common.Constant.PUPPETEER_WORLD; import static com.ruiyun.jvppeteer.common.Constant.TIME_FOR_WAITING_FOR_SWAP; +import static com.ruiyun.jvppeteer.common.Constant.UTILITY_WORLD_NAME; import static com.ruiyun.jvppeteer.util.Helper.throwError; public class FrameManager extends EventEmitter implements FrameProvider { private static final Logger LOGGER = LoggerFactory.getLogger(FrameManager.class); - private static final String UTILITY_WORLD_NAME = "__puppeteer_utility_world__"; - private final Page page; + + private final CdpPage page; private final NetworkManager networkManager; private final TimeoutSettings timeoutSettings; private final Set isolatedWorlds = new HashSet<>(); private CDPSession client; private final Map scriptsToEvaluateOnNewDocument = new HashMap<>(); private final Set bindings = new HashSet<>(); - private final FrameTree frameTree = new FrameTree(); + private final FrameTree frameTree = new FrameTree<>(); private final Set frameNavigatedReceived = new HashSet<>(); private final Map deviceRequestPromptManagerMap = new WeakHashMap<>(); private AwaitableResult frameTreeHandled; - public FrameManager(CDPSession client, Page page, TimeoutSettings timeoutSettings) { + public FrameManager(CDPSession client, CdpPage page, TimeoutSettings timeoutSettings) { super(); this.client = client; this.page = page; this.networkManager = new NetworkManager(this); this.timeoutSettings = timeoutSettings; setupEventListeners(this.client); - client.once(CDPSession.CDPSessionEvent.CDPSession_Disconnected, (ignored) -> { + client.once(ConnectionEvents.CDPSession_Disconnected, (ignored) -> { try { this.onClientDisconnect(); } catch (Exception e) { @@ -81,12 +85,12 @@ public FrameManager(CDPSession client, Page page, TimeoutSettings timeoutSetting }); } - public Page page() { + public CdpPage page() { return this.page; } private void onClientDisconnect() { - Frame mainFrame = this.frameTree.getMainFrame(); + CdpFrame mainFrame = this.frameTree.getMainFrame(); if (mainFrame == null) { return; } @@ -94,7 +98,7 @@ private void onClientDisconnect() { AwaitableResult swappedSubject = AwaitableResult.create(); Consumer onSwapped = (ignored) -> swappedSubject.onSuccess(true); try { - mainFrame.once(Frame.FrameEvent.FrameSwappedByActivation, onSwapped); + mainFrame.once(FrameEvents.FrameSwappedByActivation, onSwapped); swappedSubject.waiting(TIME_FOR_WAITING_FOR_SWAP, TimeUnit.MILLISECONDS); } catch (Exception err) { this.removeFramesRecursively(mainFrame); @@ -103,16 +107,16 @@ private void onClientDisconnect() { public void swapFrameTree(CDPSession client) { this.client = client; - Frame frame = this.frameTree.getMainFrame(); + CdpFrame frame = this.frameTree.getMainFrame(); if (frame != null) { - this.frameNavigatedReceived.add(this.client.getTarget().getTargetId()); + this.frameNavigatedReceived.add(((CdpCDPSession)this.client).getTarget().getTargetId()); this.frameTree.removeFrame(frame); - frame.updateId(this.client.getTarget().getTargetId()); + frame.updateId(((CdpCDPSession)this.client).getTarget().getTargetId()); this.frameTree.addFrame(frame); frame.updateClient(client); } this.setupEventListeners(client); - client.once(CDPSession.CDPSessionEvent.CDPSession_Disconnected, (ignored) -> { + client.once(ConnectionEvents.CDPSession_Disconnected, (ignored) -> { try { this.onClientDisconnect(); } catch (Exception e) { @@ -122,46 +126,46 @@ public void swapFrameTree(CDPSession client) { this.initialize(client, frame); this.networkManager.addClient(client); if (frame != null) { - frame.emit(Frame.FrameEvent.FrameSwappedByActivation, true); + frame.emit(FrameEvents.FrameSwappedByActivation, true); } } - public void registerSpeculativeSession(CDPSession client) { + public void registerSpeculativeSession(CdpCDPSession client) { this.networkManager.addClient(client); } private void setupEventListeners(CDPSession session) { - session.on(CDPSession.CDPSessionEvent.Page_frameAttached, (Consumer) event -> { + session.on(ConnectionEvents.Page_frameAttached, (Consumer) event -> { Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onFrameAttached(session, event.getFrameId(), event.getParentFrameId()); }); - session.on(CDPSession.CDPSessionEvent.Page_frameNavigated, (Consumer) event -> { + session.on(ConnectionEvents.Page_frameNavigated, (Consumer) event -> { this.frameNavigatedReceived.add(event.getFrame().getId()); Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onFrameNavigated(event.getFrame(), event.getType()); }); - session.on(CDPSession.CDPSessionEvent.Page_navigatedWithinDocument, (Consumer) event -> { + session.on(ConnectionEvents.Page_navigatedWithinDocument, (Consumer) event -> { Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onFrameNavigatedWithinDocument(event.getFrameId(), event.getUrl()); }); - session.on(CDPSession.CDPSessionEvent.Page_frameDetached, (Consumer) event -> { + session.on(ConnectionEvents.Page_frameDetached, (Consumer) event -> { Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onFrameDetached(event.getFrameId(), event.getReason()); }); - session.on(CDPSession.CDPSessionEvent.Page_frameStartedLoading, (Consumer) event -> { + session.on(ConnectionEvents.Page_frameStartedLoading, (Consumer) event -> { Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onFrameStartedLoading(event.getFrameId()); }); - session.on(CDPSession.CDPSessionEvent.Page_frameStoppedLoading, (Consumer) event -> { + session.on(ConnectionEvents.Page_frameStoppedLoading, (Consumer) event -> { Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onFrameStoppedLoading(event.getFrameId()); }); - session.on(CDPSession.CDPSessionEvent.Runtime_executionContextCreated, (Consumer) event -> { + session.on(ConnectionEvents.Runtime_executionContextCreated, (Consumer) event -> { Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onExecutionContextCreated(event.getContext(), session); }); - session.on(CDPSession.CDPSessionEvent.Page_lifecycleEvent, (Consumer) event -> { + session.on(ConnectionEvents.Page_lifecycleEvent, (Consumer) event -> { Optional.ofNullable(this.frameTreeHandled).ifPresent(AwaitableResult::waitingGetResult); this.onLifecycleEvent(event); }); @@ -180,20 +184,20 @@ public NetworkManager networkManager() { return this.networkManager; } - public void initialize(CDPSession client, Frame frame) { + public void initialize(CDPSession client, CdpFrame frame) { try { Optional.ofNullable(this.frameTreeHandled).ifPresent(handle -> handle.onSuccess(true)); this.frameTreeHandled = AwaitableResult.create(); this.networkManager.addClient(client); - client.send("Page.enable",null,null,false); + client.send("Page.enable", null, null, false); /* @type Protocol.Page.getFrameTreeReturnValue*/ - JsonNode result = client.send("Page.getFrameTree",null,null,true); + JsonNode result = client.send("Page.getFrameTree", null, null, true); FrameTreeEvent frameTree = Constant.OBJECTMAPPER.treeToValue(result.get("frameTree"), FrameTreeEvent.class); this.handleFrameTree(client, frameTree); Optional.ofNullable(this.frameTreeHandled).ifPresent(handle -> handle.onSuccess(true)); Map params = ParamsFactory.create(); params.put("enabled", true); - client.send("Page.setLifecycleEventsEnabled", params,null,false); + client.send("Page.setLifecycleEventsEnabled", params, null, false); client.send("Runtime.enable"); this.createIsolatedWorld(client, UTILITY_WORLD_NAME); if (frame != null) { @@ -210,31 +214,31 @@ public void initialize(CDPSession client, Frame frame) { } - public Frame mainFrame() { - Frame mainFrame = this.frameTree.getMainFrame(); - Objects.requireNonNull(mainFrame, "Requesting main frame too early!"); + public CdpFrame mainFrame() { + CdpFrame mainFrame = this.frameTree.getMainFrame(); + Objects.requireNonNull(mainFrame, "Requesting main frame too early!"); return mainFrame; } - public List frames() { + public List frames() { return new ArrayList<>(this.frameTree.frames()); } @Override - public Frame frame(String frameId) { + public CdpFrame frame(String frameId) { return this.frameTree.getById(frameId); } public void addExposedFunctionBinding(Binding binding) throws JsonProcessingException, EvaluateException { this.bindings.add(binding); - for (Frame frame : this.frames()) { + for (CdpFrame frame : this.frames()) { frame.addExposedFunctionBinding(binding); } } - public void removeExposedFunctionBinding(Binding binding) throws JsonProcessingException, EvaluateException { + public void removeExposedFunctionBinding(Binding binding) throws JsonProcessingException { this.bindings.remove(binding); - for (Frame frame : this.frames()) { + for (CdpFrame frame : this.frames()) { frame.removeExposedFunctionBinding(binding); } } @@ -248,7 +252,7 @@ public NewDocumentScriptEvaluation evaluateOnNewDocument(String source) { String identifier = response.get("identifier").asText(); PreloadScript preloadScript = new PreloadScript(this.mainFrame(), identifier, source); this.scriptsToEvaluateOnNewDocument.put(identifier, preloadScript); - for (Frame frame : this.frames()) { + for (CdpFrame frame : this.frames()) { frame.addPreloadScript(preloadScript); } return new NewDocumentScriptEvaluation(identifier); @@ -260,7 +264,7 @@ public void removeScriptToEvaluateOnNewDocument(String identifier) { throw new JvppeteerException("Script to evaluate on new document with id " + identifier + " not found"); } this.scriptsToEvaluateOnNewDocument.remove(identifier); - for (Frame frame : this.frames()) { + for (CdpFrame frame : this.frames()) { String identifier2 = preloadScript.getIdForFrame(frame); if (StringUtil.isEmpty(identifier2)) { return; @@ -277,11 +281,11 @@ public void removeScriptToEvaluateOnNewDocument(String identifier) { } } - public void onAttachedToTarget(Target target) { + public void onAttachedToTarget(CdpTarget target) { if (!"iframe".equals(target.getTargetInfo().getType())) { return; } - Frame frame = this.frame(target.getTargetInfo().getTargetId()); + CdpFrame frame = this.frame(target.getTargetInfo().getTargetId()); if (frame != null) { frame.updateClient(target.session()); } @@ -299,25 +303,25 @@ public DeviceRequestPromptManager deviceRequestPromptManager(CDPSession client) } private void onLifecycleEvent(LifecycleEvent event) { - Frame frame = this.frame(event.getFrameId()); + CdpFrame frame = this.frame(event.getFrameId()); if (frame == null) return; frame.onLifecycleEvent(event.getLoaderId(), event.getName()); this.emit(FrameManagerEvent.LifecycleEvent, frame); - frame.emit(Frame.FrameEvent.LifecycleEvent, true); + frame.emit(FrameEvents.LifecycleEvent, true); } private void onFrameStoppedLoading(String frameId) { - Frame frame = this.frame(frameId); + CdpFrame frame = this.frame(frameId); if (frame == null) return; frame.onLoadingStopped(); this.emit(FrameManagerEvent.LifecycleEvent, frame); - frame.emit(Frame.FrameEvent.LifecycleEvent, true); + frame.emit(FrameEvents.LifecycleEvent, true); } private void onFrameStartedLoading(String frameId) { - Frame frame = this.frame(frameId); + CdpFrame frame = this.frame(frameId); if (frame == null) { return; } @@ -342,14 +346,14 @@ private void handleFrameTree(CDPSession session, FrameTreeEvent frameTree) { } private void onFrameAttached(CDPSession session, String frameId, String parentFrameId) { - Frame frame = this.frame(frameId); + CdpFrame frame = this.frame(frameId); if (frame != null) { if (session != null && frame.client() != this.client) { frame.updateClient(session); } return; } - frame = new Frame(this, frameId, parentFrameId, session); + frame = new CdpFrame(this, frameId, parentFrameId, session); this.frameTree.addFrame(frame); this.emit(FrameManagerEvent.FrameAttached, frame); } @@ -357,11 +361,11 @@ private void onFrameAttached(CDPSession session, String frameId, String parentFr private void onFrameNavigated(FramePayload framePayload, String navigationType) { String frameId = framePayload.getId(); boolean isMainFrame = StringUtil.isEmpty(framePayload.getParentId()); - Frame frame = this.frameTree.getById(frameId); + CdpFrame frame = this.frameTree.getById(frameId); // Detach all child frames first. if (frame != null) { if (ValidateUtil.isNotEmpty(frame.childFrames())) { - for (Frame childFrame : frame.childFrames()) { + for (CdpFrame childFrame : frame.childFrames()) { this.removeFramesRecursively(childFrame); } } @@ -374,7 +378,7 @@ private void onFrameNavigated(FramePayload framePayload, String navigationType) frame.setId(frameId); } else { // Initial main frame navigation. - frame = new Frame(this, frameId, null, this.client); + frame = new CdpFrame(this, frameId, null, this.client); } this.frameTree.addFrame(frame); } @@ -383,7 +387,7 @@ private void onFrameNavigated(FramePayload framePayload, String navigationType) frame = this.frameTree.waitForFrame(frameId); frame.navigated(framePayload); this.emit(FrameManagerEvent.FrameNavigated, frame); - frame.emit(Frame.FrameEvent.FrameNavigated, navigationType); + frame.emit(FrameEvents.FrameNavigated, navigationType); } private void createIsolatedWorld(CDPSession session, String name) { @@ -411,20 +415,23 @@ private void createIsolatedWorld(CDPSession session, String name) { } private void onFrameNavigatedWithinDocument(String frameId, String url) { - Frame frame = this.frame(frameId); - if (frame == null) { + CdpFrame frame = this.frame(frameId); + if (Objects.isNull(frame)) { return; } frame.navigatedWithinDocument(url); this.emit(FrameManagerEvent.FrameNavigatedWithinDocument, frame); - frame.emit(Frame.FrameEvent.FrameNavigatedWithinDocument, true); + frame.emit(FrameEvents.FrameNavigatedWithinDocument, true); this.emit(FrameManagerEvent.FrameNavigated, frame); - frame.emit(Frame.FrameEvent.FrameNavigated, "Navigation"); + frame.emit(FrameEvents.FrameNavigated, "Navigation"); } private void onFrameDetached(String frameId, String reason) { - Frame frame = this.frame(frameId); - if (frame == null) return; + CdpFrame frame = this.frame(frameId); + if (Objects.isNull(frame)) return; + if(StringUtil.isEmpty(reason)){ + return; + } switch (reason) { case "remove": // Only remove the frame if the reason for the detached event is @@ -434,16 +441,16 @@ private void onFrameDetached(String frameId, String reason) { break; case "swap": this.emit(FrameManagerEvent.FrameSwapped, frame); - frame.emit(Frame.FrameEvent.FrameSwapped, true); + frame.emit(FrameEvents.FrameSwapped, true); break; } } private void onExecutionContextCreated(ExecutionContextDescription contextPayload, CDPSession session) { String frameId = contextPayload.getAuxData() != null ? contextPayload.getAuxData().getFrameId() : null; - Frame frame = this.frame(frameId); + CdpFrame frame = this.frame(frameId); IsolatedWorld world = null; - if (frame != null) { + if (Objects.nonNull(frame)) { if (frame.client() != session) { return; } @@ -456,11 +463,11 @@ private void onExecutionContextCreated(ExecutionContextDescription contextPayloa world = frame.worlds().get(PUPPETEER_WORLD); } } - if (world == null) { + if (Objects.isNull(world)) { return; } CDPSession client; - if (frame.client() != null) { + if (Objects.nonNull(frame.client())) { client = frame.client(); } else { client = this.client; @@ -469,19 +476,19 @@ private void onExecutionContextCreated(ExecutionContextDescription contextPayloa world.setContext(context); } - private void removeFramesRecursively(Frame childFrame) { + private void removeFramesRecursively(CdpFrame childFrame) { if (ValidateUtil.isNotEmpty(childFrame.childFrames())) { - for (Frame frame : childFrame.childFrames()) { + for (CdpFrame frame : childFrame.childFrames()) { this.removeFramesRecursively(frame); } } childFrame.dispose(); this.frameTree.removeFrame(childFrame); this.emit(FrameManagerEvent.FrameDetached, childFrame); - childFrame.emit(Frame.FrameEvent.FrameDetached, childFrame); + childFrame.emit(FrameEvents.FrameDetached, childFrame); } - public FrameTree frameTree() { + public FrameTree frameTree() { return this.frameTree; } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/FrameTree.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameTree.java similarity index 70% rename from src/main/java/com/ruiyun/jvppeteer/core/FrameTree.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameTree.java index 3a202fb5..5407ea5b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/FrameTree.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameTree.java @@ -1,34 +1,35 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.Frame; import com.ruiyun.jvppeteer.common.AwaitableResult; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; -public class FrameTree { - private final Map frames = Collections.synchronizedMap(new LinkedHashMap<>()); +public class FrameTree { + private final Map frames = Collections.synchronizedMap(new LinkedHashMap<>()); private final Map parentIds = new ConcurrentHashMap<>(); private final Map> childIds = new ConcurrentHashMap<>(); - private Frame mainFrame; + private FrameType mainFrame; private volatile boolean isMainFrameStale = false; - final Map>> waitRequests = new ConcurrentHashMap<>(); + final Map>> waitRequests = new ConcurrentHashMap<>(); - public Frame getMainFrame() { + public FrameType getMainFrame() { return this.mainFrame; } - public Frame getById(String frameId) { + public FrameType getById(String frameId) { return this.frames.get(frameId); } @@ -38,21 +39,21 @@ public Frame getById(String frameId) { * @param frameId 等待的frame的id * @return 等待的frame */ - public Frame waitForFrame(String frameId) { - AwaitableResult awaitableResult = AwaitableResult.create(); + public FrameType waitForFrame(String frameId) { + AwaitableResult awaitableResult = AwaitableResult.create(); this.waitRequests.computeIfAbsent(frameId, k -> new CopyOnWriteArraySet<>()).add(awaitableResult); - Frame frame = this.getById(frameId); - if (frame != null) { + FrameType frame = this.getById(frameId); + if (Objects.nonNull(frame)) { return frame; } return awaitableResult.waitingGetResult(Constant.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); } - public List frames() { + public List frames() { return new ArrayList<>(this.frames.values()); } - public void addFrame(Frame frame) { + public void addFrame(FrameType frame) { this.frames.put(frame.id(), frame); if (StringUtil.isNotEmpty(frame.parentId())) { this.parentIds.put(frame.id(), frame.parentId()); @@ -61,13 +62,13 @@ public void addFrame(Frame frame) { this.mainFrame = frame; this.isMainFrameStale = false; } - Set> callbacks = this.waitRequests.remove(frame.id()); + Set> callbacks = this.waitRequests.remove(frame.id()); if (ValidateUtil.isNotEmpty(callbacks)) { callbacks.forEach(request -> request.onSuccess(frame)); } } - public void removeFrame(Frame frame) { + public void removeFrame(FrameType frame) { String frameId = frame.id(); this.frames.remove(frameId); this.parentIds.remove(frameId); // Retrieve and remove in one operation @@ -81,14 +82,14 @@ public void removeFrame(Frame frame) { } } - public List childFrames(String frameId) { + public List childFrames(String frameId) { Set childIds = this.childIds.get(frameId); if (childIds == null) { return new ArrayList<>(); } - List frames = new ArrayList<>(); + List frames = new ArrayList<>(); for (String id : childIds) { - Frame frame = this.getById(id); + FrameType frame = this.getById(id); if (frame != null) { frames.add(frame); } @@ -96,7 +97,7 @@ public List childFrames(String frameId) { return frames; } - public Frame parentFrame(String frameId) { + public FrameType parentFrame(String frameId) { String parentId = this.parentIds.get(frameId); return StringUtil.isNotEmpty(parentId) ? this.getById(parentId) : null; } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/FrameTreeEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameTreeEvent.java similarity index 83% rename from src/main/java/com/ruiyun/jvppeteer/core/FrameTreeEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameTreeEvent.java index cb23e5bd..85da511b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/FrameTreeEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/FrameTreeEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; -import com.ruiyun.jvppeteer.entities.FramePayload; +import com.ruiyun.jvppeteer.cdp.entities.FramePayload; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/core/IsolatedWorld.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/IsolatedWorld.java similarity index 75% rename from src/main/java/com/ruiyun/jvppeteer/core/IsolatedWorld.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/IsolatedWorld.java index d5e988e6..d28c8317 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/IsolatedWorld.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/IsolatedWorld.java @@ -1,27 +1,29 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.JSHandle; +import com.ruiyun.jvppeteer.api.core.Realm; import com.ruiyun.jvppeteer.common.ChromeEnvironment; import com.ruiyun.jvppeteer.common.ParamsFactory; import com.ruiyun.jvppeteer.common.TimeoutSettings; -import com.ruiyun.jvppeteer.entities.EvaluateType; -import com.ruiyun.jvppeteer.entities.RemoteObject; -import com.ruiyun.jvppeteer.events.BindingCalledEvent; -import com.ruiyun.jvppeteer.events.ConsoleAPICalledEvent; -import com.ruiyun.jvppeteer.events.IsolatedWorldEmitter; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateType; +import com.ruiyun.jvppeteer.cdp.entities.RemoteObject; +import com.ruiyun.jvppeteer.cdp.events.BindingCalledEvent; +import com.ruiyun.jvppeteer.cdp.events.ConsoleAPICalledEvent; +import com.ruiyun.jvppeteer.cdp.events.IsolatedWorldEmitter; import com.ruiyun.jvppeteer.exception.EvaluateException; import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.transport.CDPSession; import com.ruiyun.jvppeteer.util.Helper; import com.ruiyun.jvppeteer.util.StringUtil; - import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; + import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; import static com.ruiyun.jvppeteer.util.Helper.withSourcePuppeteerURLIfNone; @@ -30,11 +32,11 @@ public class IsolatedWorld extends Realm { private volatile ExecutionContext context; private final IsolatedWorldEmitter emitter = new IsolatedWorldEmitter(); - private final Frame frame; - private final WebWorker webWorker; + private final CdpFrame frame; + private final CdpWebWorker webWorker; private ChromeEnvironment chromeEnvironment; - public IsolatedWorld(Frame frame, WebWorker webWorker, TimeoutSettings timeoutSettings) { + public IsolatedWorld(CdpFrame frame, CdpWebWorker webWorker, TimeoutSettings timeoutSettings) { super(timeoutSettings); this.frame = frame; this.webWorker = webWorker; @@ -65,11 +67,10 @@ public IsolatedWorldEmitter emitter() { return this.emitter; } - protected IsolatedWorld toIsolatedWorld() { + public IsolatedWorld toIsolatedWorld() { return this; } - // WebSocketConnectReadThread public void setContext(ExecutionContext context) { Optional.ofNullable(this.context).ifPresent(ExecutionContext::dispose); context.once(ExecutionContext.ExecutionContextEvent.Disposed, (ignore) -> this.onContextDisposed()); @@ -80,8 +81,6 @@ public void setContext(ExecutionContext context) { this.taskManager.rerunAll(); } - - //WebSocketConnectReadThread private void onContextDisposed() { this.context = null; if (this.frame != null) { @@ -89,12 +88,10 @@ private void onContextDisposed() { } } - //WebSocketConnectReadThread private void onContextConsoleApiCalled(ConsoleAPICalledEvent event) { this.emitter.emit(IsolatedWorldEmitter.IsolatedWorldEventType.Consoleapicalled, event); } - //WebSocketConnectReadThread private void onContextBindingCalled(BindingCalledEvent event) { this.emitter.emit(IsolatedWorldEmitter.IsolatedWorldEventType.Bindingcalled, event); } @@ -132,35 +129,29 @@ private ExecutionContext waitForExecutionContext() { return result[0]; } - public JSHandle evaluateHandle(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluateHandle(pageFunction, null); + public JSHandle evaluateHandle(String pptrFunction) throws JsonProcessingException, EvaluateException { + return this.evaluateHandle(pptrFunction, null); } - public JSHandle evaluateHandle(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pageFunction); + public JSHandle evaluateHandle(String pptrFunction, List args) throws JsonProcessingException { + pptrFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pptrFunction); ExecutionContext context = this.executionContext(); if (context == null) { context = this.waitForExecutionContext(); } - return context.evaluateHandle(pageFunction, args); - } - - public Object evaluate(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluate(pageFunction, null); + return context.evaluateHandle(pptrFunction, args); } - public Object evaluate(String pageFunction, EvaluateType type, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluate", pageFunction); + public Object evaluate(String pptrFunction, EvaluateType type, List args) throws JsonProcessingException { + pptrFunction = withSourcePuppeteerURLIfNone("evaluate", pptrFunction); ExecutionContext context = this.executionContext(); if (context == null) { context = this.waitForExecutionContext(); } - return context.evaluate(pageFunction, type, args); + return context.evaluate(pptrFunction, type, args); } - public Object evaluate(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - return this.evaluate(pageFunction, null, args); - } + public JSHandle adoptBackendNode(int backendNodeId) throws JsonProcessingException { ExecutionContext executionContext = this.executionContext(); @@ -174,27 +165,28 @@ public JSHandle adoptBackendNode(int backendNodeId) throws JsonProcessingExcepti return this.createJSHandle(OBJECTMAPPER.treeToValue(result.get("object"), RemoteObject.class)); } - public JSHandle adoptHandle(JSHandle handle) throws JsonProcessingException, EvaluateException { + @SuppressWarnings("unchecked") + public T adoptHandle(T handle) throws JsonProcessingException { if (handle.realm() == this) { // If the context has already adopted this handle, clone it so downstream // disposal doesn't become an issue. - return handle.evaluateHandle("value => {\n" + + return (T) handle.evaluateHandle("value => {\n" + " return value;\n" + " }"); } Map params = ParamsFactory.create(); params.put("objectId", handle.id()); JsonNode nodeInfo = this.client().send("DOM.describeNode", params); - return this.adoptBackendNode(nodeInfo.get("node").get("backendNodeId").asInt()); + return (T) this.adoptBackendNode(nodeInfo.get("node").get("backendNodeId").asInt()); } - + @SuppressWarnings("unchecked") public T transferHandle(T handle) throws JsonProcessingException { if (handle.realm() == this) { return handle; } // Implies it's a primitive value, probably. - RemoteObject remoteObject = handle.getRemoteObject(); + RemoteObject remoteObject = handle.remoteObject(); if (StringUtil.isEmpty(remoteObject.getObjectId())) { return handle; } @@ -207,19 +199,19 @@ public T transferHandle(T handle) throws JsonProcessingExce public JSHandle createJSHandle(RemoteObject remoteObject) { if ("node".equals(remoteObject.getSubtype())) { - return new ElementHandle(this, remoteObject); + return new CdpElementHandle(this, remoteObject); } - return new JSHandle(this, remoteObject); + return new CdpJSHandle(this, remoteObject); } public void dispose() { Optional.ofNullable(this.context).ifPresent(ExecutionContext::dispose); this.emitter.emit(IsolatedWorldEmitter.IsolatedWorldEventType.Disposed, true); super.dispose(); - this.emitter.removeAllListener(null); + this.emitter.removeAllListeners(null); } - public Frame getFrame() { + public CdpFrame getFrame() { return this.frame; } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/JSCoverage.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/JSCoverage.java similarity index 81% rename from src/main/java/com/ruiyun/jvppeteer/core/JSCoverage.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/JSCoverage.java index b1385368..426ff6f9 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/JSCoverage.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/JSCoverage.java @@ -1,28 +1,30 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.CoverageRange; -import com.ruiyun.jvppeteer.entities.FunctionCoverage; -import com.ruiyun.jvppeteer.entities.JSCoverageEntry; -import com.ruiyun.jvppeteer.entities.JSCoverageOptions; -import com.ruiyun.jvppeteer.entities.Range; -import com.ruiyun.jvppeteer.entities.ScriptCoverage; -import com.ruiyun.jvppeteer.entities.TakePreciseCoverageResponse; -import com.ruiyun.jvppeteer.events.ScriptParsedEvent; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.cdp.entities.CoverageRange; +import com.ruiyun.jvppeteer.cdp.entities.FunctionCoverage; +import com.ruiyun.jvppeteer.cdp.entities.JSCoverageEntry; +import com.ruiyun.jvppeteer.cdp.entities.JSCoverageOptions; +import com.ruiyun.jvppeteer.cdp.entities.Range; +import com.ruiyun.jvppeteer.cdp.entities.ScriptCoverage; +import com.ruiyun.jvppeteer.cdp.entities.TakePreciseCoverageResponse; +import com.ruiyun.jvppeteer.cdp.events.ScriptParsedEvent; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import static com.ruiyun.jvppeteer.util.Helper.isPuppeteerURL; @@ -32,7 +34,7 @@ public class JSCoverage { private volatile boolean enabled; private final Map scriptSources = new HashMap<>(); private final Map scriptURLs = new HashMap<>(); - private final Map> listeners = new HashMap<>(); + private final Map> listeners = new HashMap<>(); private boolean resetOnNavigation; private boolean reportAnonymousScripts; private boolean includeRawScriptCoverage; @@ -50,12 +52,12 @@ public void start(JSCoverageOptions options) { this.scriptURLs.clear(); this.scriptSources.clear(); Consumer onScriptParsed = this::onScriptParsed; - this.client.on(CDPSession.CDPSessionEvent.Debugger_scriptParsed, onScriptParsed); - this.listeners.put(CDPSession.CDPSessionEvent.Debugger_scriptParsed, onScriptParsed); + this.client.on(ConnectionEvents.Debugger_scriptParsed, onScriptParsed); + this.listeners.put(ConnectionEvents.Debugger_scriptParsed, onScriptParsed); Consumer executionContextsCleared = (event) -> this.onExecutionContextsCleared(); - this.client.on(CDPSession.CDPSessionEvent.Runtime_executionContextsCleared, executionContextsCleared); - this.listeners.put(CDPSession.CDPSessionEvent.Runtime_executionContextsCleared, executionContextsCleared); + this.client.on(ConnectionEvents.Runtime_executionContextsCleared, executionContextsCleared); + this.listeners.put(ConnectionEvents.Runtime_executionContextsCleared, executionContextsCleared); this.client.send("Profiler.enable", null, null, false); Map params = ParamsFactory.create(); @@ -129,7 +131,7 @@ public List stop() throws JsonProcessingException { return coverage; } - public void updateClient(CDPSession client) { + public void updateClient(CdpCDPSession client) { this.client = client; } } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/LifecycleWatcher.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/LifecycleWatcher.java similarity index 69% rename from src/main/java/com/ruiyun/jvppeteer/core/LifecycleWatcher.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/LifecycleWatcher.java index 7284ff7b..6f2adfbc 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/LifecycleWatcher.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/LifecycleWatcher.java @@ -1,7 +1,9 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.events.FrameEvents; import com.ruiyun.jvppeteer.common.AwaitableResult; -import com.ruiyun.jvppeteer.entities.PuppeteerLifeCycle; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; import com.ruiyun.jvppeteer.exception.JvppeteerException; import com.ruiyun.jvppeteer.util.ValidateUtil; @@ -9,17 +11,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; public class LifecycleWatcher { private final List expectedLifecycle = new ArrayList<>(); - private final Map> frameListeners = new HashMap<>(); + private final Map> frameListeners = new HashMap<>(); private final Map> networkListeners = new HashMap<>(); private final Map> frameManagerListeners = new HashMap<>(); - private Frame frame; - private Request navigationRequest; - private String initialLoaderId; + private final CdpFrame frame; + private CdpRequest navigationRequest; + private final String initialLoaderId; private boolean hasSameDocumentNavigation; private final AwaitableResult lifecycleResult = AwaitableResult.create(); private final AwaitableResult sameDocumentNavigationResult = AwaitableResult.create(); @@ -27,72 +30,57 @@ public class LifecycleWatcher { public final AwaitableResult terminationResult = AwaitableResult.create(); public AwaitableResult navigationResponseReceived; private boolean swapped = false; - private NetworkManager networkManager; + private final NetworkManager networkManager; - - public LifecycleWatcher() { - super(); - } - - public LifecycleWatcher(NetworkManager networkManager, Frame frame, List waitUntil) { + public LifecycleWatcher(NetworkManager networkManager, CdpFrame frame, List waitUntil) { super(); this.frame = frame; this.initialLoaderId = frame.loaderId(); this.networkManager = networkManager; waitUntil.forEach(value -> { - if (PuppeteerLifeCycle.DOMCONTENT_LOADED.equals(value)) { - this.expectedLifecycle.add("DOMContentLoaded"); - } else if (PuppeteerLifeCycle.NETWORKIDLE.equals(value)) { - this.expectedLifecycle.add("networkIdle"); - } else if (PuppeteerLifeCycle.NETWORKIDLE_2.equals(value)) { - this.expectedLifecycle.add("networkAlmostIdle"); - } else if (PuppeteerLifeCycle.LOAD.equals(value)) { - this.expectedLifecycle.add("load"); - } else { - throw new IllegalArgumentException("Unknown value for options.waitUntil: " + value); - } + this.expectedLifecycle.add(value.getValue()); }); Consumer lifecycleListener = (ignore) -> this.checkLifecycleComplete(); this.frame.frameManager().on(FrameManager.FrameManagerEvent.LifecycleEvent, lifecycleListener); this.frameManagerListeners.put(FrameManager.FrameManagerEvent.LifecycleEvent, lifecycleListener); Consumer frameNavigatedWithinDocumentListener = (ignore) -> this.navigatedWithinDocument(); - this.frame.on(Frame.FrameEvent.FrameNavigatedWithinDocument, frameNavigatedWithinDocumentListener); - this.frameListeners.put(Frame.FrameEvent.FrameNavigatedWithinDocument, frameNavigatedWithinDocumentListener); + this.frame.on(FrameEvents.FrameNavigatedWithinDocument, frameNavigatedWithinDocumentListener); + this.frameListeners.put(FrameEvents.FrameNavigatedWithinDocument, frameNavigatedWithinDocumentListener); Consumer frameNavigatedListener = this::navigated; - this.frame.on(Frame.FrameEvent.FrameNavigated, frameNavigatedListener); - this.frameListeners.put(Frame.FrameEvent.FrameNavigated, frameNavigatedListener); + this.frame.on(FrameEvents.FrameNavigated, frameNavigatedListener); + this.frameListeners.put(FrameEvents.FrameNavigated, frameNavigatedListener); Consumer frameSwappedListener = (ignore) -> this.frameSwapped(); - frame.on(Frame.FrameEvent.FrameSwapped, frameSwappedListener); - this.frameListeners.put(Frame.FrameEvent.FrameSwapped, frameSwappedListener); + frame.on(FrameEvents.FrameSwapped, frameSwappedListener); + this.frameListeners.put(FrameEvents.FrameSwapped, frameSwappedListener); Consumer frameSwappedByActivationListener = (ignore) -> this.frameSwapped(); - this.frame.on(Frame.FrameEvent.FrameSwappedByActivation, frameSwappedByActivationListener); - this.frameListeners.put(Frame.FrameEvent.FrameSwappedByActivation, frameSwappedByActivationListener); + this.frame.on(FrameEvents.FrameSwappedByActivation, frameSwappedByActivationListener); + this.frameListeners.put(FrameEvents.FrameSwappedByActivation, frameSwappedByActivationListener); - Consumer frameDetachedListener = this::frameDetached; - this.frame.on(Frame.FrameEvent.FrameDetached, frameDetachedListener); - this.frameListeners.put(Frame.FrameEvent.FrameDetached, frameDetachedListener); + Consumer frameDetachedListener = this::frameDetached; + this.frame.on(FrameEvents.FrameDetached, frameDetachedListener); + this.frameListeners.put(FrameEvents.FrameDetached, frameDetachedListener); - Consumer requestListener = this::onRequest; + Consumer requestListener = this::onRequest; this.networkManager.on(NetworkManager.NetworkManagerEvent.Request, requestListener); this.networkListeners.put(NetworkManager.NetworkManagerEvent.Request, requestListener); - Consumer requestFailedListener = this::onRequestFailed; + Consumer requestFailedListener = this::onRequestFailed; this.networkManager.on(NetworkManager.NetworkManagerEvent.RequestFailed, requestFailedListener); this.networkListeners.put(NetworkManager.NetworkManagerEvent.RequestFailed, requestFailedListener); - Consumer responseListener = this::onResponse; + Consumer responseListener = this::onResponse; this.networkManager.on(NetworkManager.NetworkManagerEvent.Response, responseListener); this.networkListeners.put(NetworkManager.NetworkManagerEvent.Response, responseListener); this.checkLifecycleComplete(); } - private void onRequestFailed(Request request) { + private void onRequestFailed(CdpRequest request) { if (this.navigationRequest != null) { if (!this.navigationRequest.id().equals(request.id())) { return; @@ -103,7 +91,7 @@ private void onRequestFailed(Request request) { } } - private void onResponse(Response response) { + private void onResponse(CdpResponse response) { if (this.navigationRequest != null) { if (!this.navigationRequest.id().equals(response.request().id())) { return; @@ -114,7 +102,7 @@ private void onResponse(Response response) { } } - private void frameDetached(Frame frame) { + private void frameDetached(CdpFrame frame) { if (this.frame.equals(frame)) { terminationResult.onSuccess(new JvppeteerException("Navigating frame was detached'")); return; @@ -146,10 +134,8 @@ public boolean newDocumentNavigationIsDone() { return this.newDocumentNavigationResult.isDone(); } - private void onRequest(Request request) { - if (!(request.frame() != null && request.frame().equals(this.frame)) || !request.isNavigationRequest()) { - return; - } else if (request.frame() == null && this.frame == null) { //两个frame都为null的情况 + private void onRequest(CdpRequest request) { + if (!Objects.equals(request.frame(),this.frame) || !request.isNavigationRequest()) { return; } this.navigationRequest = request; @@ -189,7 +175,7 @@ private void checkLifecycleComplete() { private boolean checkLifecycle(Frame frame, List expectedLifecycle) { if (ValidateUtil.isNotEmpty(expectedLifecycle)) { for (String event : expectedLifecycle) { - if (!frame.lifecycleEvents().contains(event)) return false; + if (!((CdpFrame)frame).lifecycleEvents().contains(event)) return false; } } if (ValidateUtil.isNotEmpty(frame.childFrames())) { @@ -222,7 +208,7 @@ public boolean navigationResponseIsDone() { return true; } - public Response navigationResponse() { + public CdpResponse navigationResponse() { return this.navigationRequest != null ? this.navigationRequest.response() : null; } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/NetworkEventManager.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/NetworkEventManager.java similarity index 90% rename from src/main/java/com/ruiyun/jvppeteer/core/NetworkEventManager.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/NetworkEventManager.java index b17ac501..457a46f8 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/NetworkEventManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/NetworkEventManager.java @@ -1,15 +1,14 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; -import com.ruiyun.jvppeteer.entities.QueuedEventGroup; -import com.ruiyun.jvppeteer.entities.RedirectInfo; -import com.ruiyun.jvppeteer.events.RequestPausedEvent; -import com.ruiyun.jvppeteer.events.RequestWillBeSentEvent; -import com.ruiyun.jvppeteer.events.ResponseReceivedExtraInfoEvent; +import com.ruiyun.jvppeteer.cdp.entities.QueuedEventGroup; +import com.ruiyun.jvppeteer.cdp.entities.RedirectInfo; +import com.ruiyun.jvppeteer.cdp.events.RequestPausedEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestWillBeSentEvent; +import com.ruiyun.jvppeteer.cdp.events.ResponseReceivedExtraInfoEvent; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; public class NetworkEventManager { @@ -47,7 +46,7 @@ public class NetworkEventManager { */ private final Map requestWillBeSentMap = new HashMap<>(); private final Map requestPausedMap = new HashMap<>(); - private final Map httpRequestsMap = new HashMap<>(); + private final Map httpRequestsMap = new HashMap<>(); /** * The below maps are used to reconcile Network.responseReceivedExtraInfo @@ -94,7 +93,7 @@ public RedirectInfo takeQueuedRedirectInfo(String fetchRequestId) { public int inFlightRequestsCount() { int inFlightRequestCounter = 0; - for (Request request : this.httpRequestsMap.values()) { + for (CdpRequest request : this.httpRequestsMap.values()) { if (request.response() == null) { inFlightRequestCounter++; } @@ -126,11 +125,11 @@ public void storeRequestPaused(String networkRequestId, RequestPausedEvent event this.requestPausedMap.put(networkRequestId, event); } - public Request getRequest(String networkRequestId) { + public CdpRequest getRequest(String networkRequestId) { return this.httpRequestsMap.get(networkRequestId); } - public void storeRequest(String networkRequestId, Request request) { + public void storeRequest(String networkRequestId, CdpRequest request) { this.httpRequestsMap.put(networkRequestId, request); } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/NetworkManager.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/NetworkManager.java similarity index 73% rename from src/main/java/com/ruiyun/jvppeteer/core/NetworkManager.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/NetworkManager.java index 7b549cdd..04554c9e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/NetworkManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/NetworkManager.java @@ -1,27 +1,30 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.EventEmitter; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.core.Request; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.FrameProvider; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.AuthChallengeResponse; -import com.ruiyun.jvppeteer.entities.Credentials; -import com.ruiyun.jvppeteer.entities.InternalNetworkConditions; -import com.ruiyun.jvppeteer.entities.NetworkConditions; -import com.ruiyun.jvppeteer.entities.QueuedEventGroup; -import com.ruiyun.jvppeteer.entities.RedirectInfo; -import com.ruiyun.jvppeteer.entities.ResponsePayload; -import com.ruiyun.jvppeteer.entities.UserAgentMetadata; -import com.ruiyun.jvppeteer.events.AuthRequiredEvent; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.events.LoadingFailedEvent; -import com.ruiyun.jvppeteer.events.LoadingFinishedEvent; -import com.ruiyun.jvppeteer.events.RequestPausedEvent; -import com.ruiyun.jvppeteer.events.RequestServedFromCacheEvent; -import com.ruiyun.jvppeteer.events.RequestWillBeSentEvent; -import com.ruiyun.jvppeteer.events.ResponseReceivedEvent; -import com.ruiyun.jvppeteer.events.ResponseReceivedExtraInfoEvent; +import com.ruiyun.jvppeteer.cdp.entities.AuthChallengeResponse; +import com.ruiyun.jvppeteer.cdp.entities.Credentials; +import com.ruiyun.jvppeteer.cdp.entities.InternalNetworkConditions; +import com.ruiyun.jvppeteer.cdp.entities.NetworkConditions; +import com.ruiyun.jvppeteer.cdp.entities.QueuedEventGroup; +import com.ruiyun.jvppeteer.cdp.entities.RedirectInfo; +import com.ruiyun.jvppeteer.cdp.entities.ResponsePayload; +import com.ruiyun.jvppeteer.cdp.entities.UserAgentMetadata; +import com.ruiyun.jvppeteer.cdp.events.AuthRequiredEvent; +import com.ruiyun.jvppeteer.cdp.events.LoadingFailedEvent; +import com.ruiyun.jvppeteer.cdp.events.LoadingFinishedEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestPausedEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestServedFromCacheEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestWillBeSentEvent; +import com.ruiyun.jvppeteer.cdp.events.ResponseReceivedEvent; +import com.ruiyun.jvppeteer.cdp.events.ResponseReceivedExtraInfoEvent; import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.transport.CDPSession; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; import java.util.ArrayList; @@ -47,7 +50,7 @@ public class NetworkManager extends EventEmitter>> clients = new HashMap<>(); + private final Map>> clients = new HashMap<>(); private volatile boolean userRequestInterceptionEnabled = false; public NetworkManager(FrameProvider frameManager) { @@ -59,42 +62,42 @@ public void addClient(CDPSession client) { if (this.clients.containsKey(client)) { return; } - Map> listeners = new HashMap<>(); + Map> listeners = new HashMap<>(); Consumer requestPaused = event -> this.onRequestPaused(client, event); - client.on(CDPSession.CDPSessionEvent.Fetch_requestPaused, requestPaused); - listeners.put(CDPSession.CDPSessionEvent.Fetch_requestPaused, requestPaused); + client.on(ConnectionEvents.Fetch_requestPaused, requestPaused); + listeners.put(ConnectionEvents.Fetch_requestPaused, requestPaused); Consumer authRequired = event -> this.onAuthRequired(client, event); - client.on(CDPSession.CDPSessionEvent.Fetch_authRequired, authRequired); - listeners.put(CDPSession.CDPSessionEvent.Fetch_authRequired, authRequired); + client.on(ConnectionEvents.Fetch_authRequired, authRequired); + listeners.put(ConnectionEvents.Fetch_authRequired, authRequired); Consumer requestWillBeSent = event -> this.onRequestWillBeSent(client, event); - client.on(CDPSession.CDPSessionEvent.Network_requestWillBeSent, requestWillBeSent); - listeners.put(CDPSession.CDPSessionEvent.Network_requestWillBeSent, requestWillBeSent); + client.on(ConnectionEvents.Network_requestWillBeSent, requestWillBeSent); + listeners.put(ConnectionEvents.Network_requestWillBeSent, requestWillBeSent); Consumer requestServedFromCache = event -> this.onRequestServedFromCache(client, event); - client.on(CDPSession.CDPSessionEvent.Network_requestServedFromCache, requestServedFromCache); - listeners.put(CDPSession.CDPSessionEvent.Network_requestServedFromCache, requestServedFromCache); + client.on(ConnectionEvents.Network_requestServedFromCache, requestServedFromCache); + listeners.put(ConnectionEvents.Network_requestServedFromCache, requestServedFromCache); Consumer responseReceived = event -> this.onResponseReceived(client, event); - client.on(CDPSession.CDPSessionEvent.Network_responseReceived, responseReceived); - listeners.put(CDPSession.CDPSessionEvent.Network_responseReceived, responseReceived); + client.on(ConnectionEvents.Network_responseReceived, responseReceived); + listeners.put(ConnectionEvents.Network_responseReceived, responseReceived); Consumer loadingFinished = event -> this.onLoadingFinished(client, event); - client.on(CDPSession.CDPSessionEvent.Network_loadingFinished, loadingFinished); - listeners.put(CDPSession.CDPSessionEvent.Network_loadingFinished, loadingFinished); + client.on(ConnectionEvents.Network_loadingFinished, loadingFinished); + listeners.put(ConnectionEvents.Network_loadingFinished, loadingFinished); - Consumer loadingFailed = event -> this.onLoadingFailed(client,event); - client.on(CDPSession.CDPSessionEvent.Network_loadingFailed, loadingFailed); - listeners.put(CDPSession.CDPSessionEvent.Network_loadingFailed, loadingFailed); + Consumer loadingFailed = event -> this.onLoadingFailed(client, event); + client.on(ConnectionEvents.Network_loadingFailed, loadingFailed); + listeners.put(ConnectionEvents.Network_loadingFailed, loadingFailed); Consumer responseReceivedExtraInfo = event -> this.onResponseReceivedExtraInfo(client, event); - client.on(CDPSession.CDPSessionEvent.Network_responseReceivedExtraInfo, responseReceivedExtraInfo); - listeners.put(CDPSession.CDPSessionEvent.Network_responseReceivedExtraInfo, responseReceivedExtraInfo); + client.on(ConnectionEvents.Network_responseReceivedExtraInfo, responseReceivedExtraInfo); + listeners.put(ConnectionEvents.Network_responseReceivedExtraInfo, responseReceivedExtraInfo); Consumer disconnected = (ignore) -> this.removeClient(client); - client.on(CDPSession.CDPSessionEvent.CDPSession_Disconnected, disconnected); - listeners.put(CDPSession.CDPSessionEvent.CDPSession_Disconnected, disconnected); + client.on(ConnectionEvents.CDPSession_Disconnected, disconnected); + listeners.put(ConnectionEvents.CDPSession_Disconnected, disconnected); this.clients.put(client, listeners); @@ -107,8 +110,8 @@ public void addClient(CDPSession client) { } public void removeClient(CDPSession client) { - Map> listeners = this.clients.remove(client); - if (listeners != null) {//取消监听 + Map> listeners = this.clients.remove(client); + if (Objects.nonNull(listeners)) {//取消监听 listeners.forEach(client::off); } } @@ -241,7 +244,7 @@ public void onRequestWillBeSent(CDPSession client, RequestWillBeSentEvent event) String networkRequestId = event.getRequestId(); this.networkEventManager.storeRequestWillBeSent(networkRequestId, event); RequestPausedEvent requestPausedEvent = this.networkEventManager.getRequestPaused(networkRequestId); - if (requestPausedEvent != null) { + if (Objects.nonNull(requestPausedEvent)) { String fetchRequestId = requestPausedEvent.getRequestId(); this.patchRequestEventHeaders(event, requestPausedEvent); this.onRequest(client, event, fetchRequestId, false); @@ -282,26 +285,30 @@ public void onAuthRequired(CDPSession client, AuthRequiredEvent event) { public void onRequestPaused(CDPSession client, RequestPausedEvent event) { if (!this.userRequestInterceptionEnabled && this.protocolRequestInterceptionEnabled) { - Map params = ParamsFactory.create(); - params.put("requestId", event.getRequestId()); - client.send("Fetch.continueRequest", params); + try { + Map params = ParamsFactory.create(); + params.put("requestId", event.getRequestId()); + client.send("Fetch.continueRequest", params); + } catch (Exception e) { + LOGGER.error("jvppeteer error"); + } } - String networkId = event.getNetworkId(); - String requestId = event.getRequestId(); - if (StringUtil.isEmpty(networkId)) { + String networkRequestId = event.getNetworkId(); + String fetchRequestId = event.getRequestId(); + if (StringUtil.isEmpty(networkRequestId)) { this.onRequestWithoutNetworkInstrumentation(client, event); return; } - RequestWillBeSentEvent requestWillBeSentEvent = this.networkEventManager.getRequestWillBeSent(networkId); - if (requestWillBeSentEvent != null && (!requestWillBeSentEvent.getRequest().getUrl().equals(event.getRequest().getUrl()) || !requestWillBeSentEvent.getRequest().getMethod().equals(event.getRequest().getMethod()))) { - this.networkEventManager.forgetRequestWillBeSent(networkId); - return; + RequestWillBeSentEvent requestWillBeSentEvent = this.networkEventManager.getRequestWillBeSent(networkRequestId); + if (Objects.nonNull(requestWillBeSentEvent) && (!Objects.equals(requestWillBeSentEvent.getRequest().getUrl(), event.getRequest().getUrl()) || !Objects.equals(requestWillBeSentEvent.getRequest().getMethod(),event.getRequest().getMethod()))) { + this.networkEventManager.forgetRequestWillBeSent(networkRequestId); + requestWillBeSentEvent = null ; } if (requestWillBeSentEvent != null) { this.patchRequestEventHeaders(requestWillBeSentEvent, event); - this.onRequest(client, requestWillBeSentEvent, requestId, false); + this.onRequest(client, requestWillBeSentEvent, fetchRequestId, false); } else { - this.networkEventManager.storeRequestPaused(networkId, event); + this.networkEventManager.storeRequestPaused(networkRequestId, event); } } @@ -314,7 +321,7 @@ private void onRequestWithoutNetworkInstrumentation(CDPSession client, RequestPa RequestWillBeSentEvent requestWillBeSent = new RequestWillBeSentEvent(); requestWillBeSent.setRequestId(event.getRequestId()); requestWillBeSent.setRequest(event.getRequest()); - Request request = new Request(client, frame, event.getRequestId(), this.userRequestInterceptionEnabled, requestWillBeSent, new ArrayList<>()); + CdpRequest request = new CdpRequest(client, frame, event.getRequestId(), this.userRequestInterceptionEnabled, requestWillBeSent, new ArrayList<>()); this.emit(NetworkManagerEvent.Request, request); request.finalizeInterceptions(); } @@ -332,20 +339,20 @@ private void patchRequestEventHeaders(RequestWillBeSentEvent requestWillBeSentEv private void onResponseReceivedExtraInfo(CDPSession client, ResponseReceivedExtraInfoEvent event) { RedirectInfo redirectInfo = this.networkEventManager.takeQueuedRedirectInfo(event.getRequestId()); - if (redirectInfo != null) { + if (Objects.nonNull(redirectInfo)) { this.networkEventManager.responseExtraInfo(event.getRequestId()).offer(event); this.onRequest(client, redirectInfo.getEvent(), redirectInfo.getFetchRequestId(), false); return; } QueuedEventGroup queuedEvents = this.networkEventManager.getQueuedEventGroup(event.getRequestId()); - if (queuedEvents != null) { + if (Objects.nonNull(queuedEvents)) { this.networkEventManager.forgetQueuedEventGroup(event.getRequestId()); this.emitResponseEvent(client, queuedEvents.getResponseReceivedEvent(), event); - if (queuedEvents.getLoadingFinishedEvent() != null) { - this.emitLoadingFinished(client,queuedEvents.getLoadingFinishedEvent()); + if (Objects.nonNull(queuedEvents.getLoadingFinishedEvent())) { + this.emitLoadingFinished(client, queuedEvents.getLoadingFinishedEvent()); } - if (queuedEvents.getLoadingFailedEvent() != null) { - this.emitLoadingFailed(client,queuedEvents.getLoadingFailedEvent()); + if (Objects.nonNull(queuedEvents.getLoadingFailedEvent())) { + this.emitLoadingFailed(client, queuedEvents.getLoadingFailedEvent()); } return; } @@ -353,34 +360,34 @@ private void onResponseReceivedExtraInfo(CDPSession client, ResponseReceivedExtr } private void emitLoadingFailed(CDPSession client, LoadingFailedEvent event) { - Request request = this.networkEventManager.getRequest(event.getRequestId()); + CdpRequest request = this.networkEventManager.getRequest(event.getRequestId()); if (request == null) { return; } this.maybeReassignOOPIFRequestClient(client, request); request.setFailureText(event.getErrorText()); - Response response = request.response(); - if (response != null) { + CdpResponse response = request.response(); + if (Objects.nonNull(response)) { response.resolveBody(null); } this.forgetRequest(request, true); this.emit(NetworkManagerEvent.RequestFailed, request); } - private void emitLoadingFinished(CDPSession client,LoadingFinishedEvent event) { - Request request = this.networkEventManager.getRequest(event.getRequestId()); + private void emitLoadingFinished(CDPSession client, LoadingFinishedEvent event) { + CdpRequest request = this.networkEventManager.getRequest(event.getRequestId()); if (request == null) { return; } this.maybeReassignOOPIFRequestClient(client, request); - if (request.response() != null) { + if (Objects.nonNull(request.response())) { request.response().resolveBody(null); } this.forgetRequest(request, true); this.emit(NetworkManagerEvent.RequestFinished, request); } - private void maybeReassignOOPIFRequestClient(CDPSession client, Request request) { + private void maybeReassignOOPIFRequestClient(CDPSession client, CdpRequest request) { // Document requests for OOPIFs start in the parent frame but are adopted by their // child frame, meaning their loadingFinished and loadingFailed events are fired on // the child session. In this case we reassign the request CDPSession to ensure all @@ -394,11 +401,18 @@ private void maybeReassignOOPIFRequestClient(CDPSession client, Request request) private void onRequest(CDPSession client, RequestWillBeSentEvent event, String fetchRequestId, boolean fromMemoryCache) { List redirectChain = new ArrayList<>(); - if (event.getRedirectResponse() != null) { + if (Objects.nonNull(event.getRedirectResponse())) { + // We want to emit a response and requestfinished for the + // redirectResponse, but we can't do so unless we have a + // responseExtraInfo ready to pair it up with. If we don't have any + // responseExtraInfos saved in our queue, they we have to wait until + // the next one to emit response and requestfinished, *and* we should + // also wait to emit this Request too because it should come after the + // response/requestfinished. ResponseReceivedExtraInfoEvent redirectResponseExtraInfo = null; if (event.getRedirectHasExtraInfo()) { redirectResponseExtraInfo = this.networkEventManager.responseExtraInfo(event.getRequestId()).poll(); - if (redirectResponseExtraInfo == null) { + if (Objects.isNull(redirectResponseExtraInfo)) { RedirectInfo redirectInfo = new RedirectInfo(); redirectInfo.setEvent(event); redirectInfo.setFetchRequestId(fetchRequestId); @@ -406,8 +420,8 @@ private void onRequest(CDPSession client, RequestWillBeSentEvent event, String f return; } } - Request request = this.networkEventManager.getRequest(event.getRequestId()); - if (request != null) { + CdpRequest request = this.networkEventManager.getRequest(event.getRequestId()); + if (Objects.nonNull(request)) { this.handleRequestRedirect(client, request, event.getRedirectResponse(), redirectResponseExtraInfo); redirectChain = request.redirectChain(); } @@ -417,16 +431,15 @@ private void onRequest(CDPSession client, RequestWillBeSentEvent event, String f if (StringUtil.isNotEmpty(frameId)) { frame = this.frameManager.frame(frameId); } - Request request = new Request(client, frame, fetchRequestId, this.userRequestInterceptionEnabled, event, redirectChain); + CdpRequest request = new CdpRequest(client, frame, fetchRequestId, this.userRequestInterceptionEnabled, event, redirectChain); request.setFromMemoryCache(fromMemoryCache); this.networkEventManager.storeRequest(event.getRequestId(), request); this.emit(NetworkManagerEvent.Request, request); request.finalizeInterceptions(); } - //不能阻塞 WebSocketConnectReadThread - private void handleRequestRedirect(CDPSession _client, Request request, ResponsePayload responsePayload, ResponseReceivedExtraInfoEvent extraInfo) { - Response response = new Response(request, responsePayload, extraInfo); + private void handleRequestRedirect(CDPSession _client, CdpRequest request, ResponsePayload responsePayload, ResponseReceivedExtraInfoEvent extraInfo) { + CdpResponse response = new CdpResponse(request, responsePayload, extraInfo); request.setResponse(response); request.redirectChain().add(request); response.resolveBody("Response body is unavailable for redirect responses,request url: " + request.url()); @@ -435,12 +448,11 @@ private void handleRequestRedirect(CDPSession _client, Request request, Response this.emit(NetworkManagerEvent.RequestFinished, request); } - // WebSocketConnectReadThread - private void forgetRequest(Request request, boolean events) { + private void forgetRequest(CdpRequest request, boolean events) { String requestId = request.id(); String interceptionId = request.interceptionId(); this.networkEventManager.forgetRequest(requestId); - if (interceptionId != null) { + if (StringUtil.isNotEmpty(interceptionId)) { this.attemptedAuthentications.remove(interceptionId); } if (events) { @@ -450,19 +462,19 @@ private void forgetRequest(Request request, boolean events) { public void onLoadingFinished(CDPSession client, LoadingFinishedEvent event) { QueuedEventGroup queuedEvents = this.networkEventManager.getQueuedEventGroup(event.getRequestId()); - if (queuedEvents != null) { + if (Objects.nonNull(queuedEvents)) { queuedEvents.setLoadingFinishedEvent(event); } else { - this.emitLoadingFinished(client,event); + this.emitLoadingFinished(client, event); } } private void onResponseReceived(CDPSession client, ResponseReceivedEvent event) { - Request request = this.networkEventManager.getRequest(event.getRequestId()); + CdpRequest request = this.networkEventManager.getRequest(event.getRequestId()); ResponseReceivedExtraInfoEvent extraInfo = null; if (request != null && !request.fromMemoryCache() && event.getHasExtraInfo()) { extraInfo = this.networkEventManager.responseExtraInfo(event.getRequestId()).poll(); - if (extraInfo == null) { + if (Objects.isNull(extraInfo)) { QueuedEventGroup group = new QueuedEventGroup(); group.setResponseReceivedEvent(event); this.networkEventManager.queueEventGroup(event.getRequestId(), group); @@ -472,18 +484,18 @@ private void onResponseReceived(CDPSession client, ResponseReceivedEvent event) this.emitResponseEvent(client, event, extraInfo); } - public void onLoadingFailed(CDPSession client,LoadingFailedEvent event) { + public void onLoadingFailed(CDPSession client, LoadingFailedEvent event) { QueuedEventGroup queuedEvents = this.networkEventManager.getQueuedEventGroup(event.getRequestId()); if (queuedEvents != null) { queuedEvents.setLoadingFailedEvent(event); } else { - this.emitLoadingFailed(client,event); + this.emitLoadingFailed(client, event); } } public void onRequestServedFromCache(CDPSession client, RequestServedFromCacheEvent event) { RequestWillBeSentEvent requestWillBeSentEvent = this.networkEventManager.getRequestWillBeSent(event.getRequestId()); - Request request = this.networkEventManager.getRequest(event.getRequestId()); + CdpRequest request = this.networkEventManager.getRequest(event.getRequestId()); if (Objects.nonNull(request)) { request.setFromMemoryCache(true); } @@ -499,18 +511,18 @@ public void onRequestServedFromCache(CDPSession client, RequestServedFromCacheEv } public void emitResponseEvent(CDPSession _client, ResponseReceivedEvent responseReceived, ResponseReceivedExtraInfoEvent extraInfo) { - Request request = this.networkEventManager.getRequest(responseReceived.getRequestId()); - if (request == null) { + CdpRequest request = this.networkEventManager.getRequest(responseReceived.getRequestId()); + if (Objects.isNull(request)) { return; } List extraInfos = this.networkEventManager.responseExtraInfo(responseReceived.getRequestId()); if (ValidateUtil.isNotEmpty(extraInfos)) { - LOGGER.error("Unexpected extraInfo events for request {}", responseReceived.getRequestId()); + LOGGER.warn("Unexpected extraInfo events for request {}", responseReceived.getRequestId()); } if (responseReceived.getResponse().getFromDiskCache()) { extraInfo = null; } - Response response = new Response(request, responseReceived.getResponse(), extraInfo); + CdpResponse response = new CdpResponse(request, responseReceived.getResponse(), extraInfo); request.setResponse(response); this.emit(NetworkManagerEvent.Response, response); } diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/OtherTarget.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/OtherTarget.java new file mode 100644 index 00000000..a243992e --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/OtherTarget.java @@ -0,0 +1,11 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.transport.SessionFactory; + +public class OtherTarget extends CdpTarget { + public OtherTarget(TargetInfo targetInfo, CDPSession session, CdpBrowserContext cdpBrowserContext, TargetManager targetManager, SessionFactory sessionFactory) { + super(targetInfo, session, cdpBrowserContext, targetManager, sessionFactory); + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/PageTarget.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/PageTarget.java similarity index 65% rename from src/main/java/com/ruiyun/jvppeteer/core/PageTarget.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/PageTarget.java index ac14d25a..10e0f6d3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/PageTarget.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/PageTarget.java @@ -1,18 +1,20 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Target; +import com.ruiyun.jvppeteer.api.events.PageEvents; import com.ruiyun.jvppeteer.common.AwaitableResult; -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.entities.TargetType; -import com.ruiyun.jvppeteer.entities.Viewport; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.entities.TargetType; +import com.ruiyun.jvppeteer.cdp.entities.Viewport; import com.ruiyun.jvppeteer.transport.SessionFactory; -public class PageTarget extends Target { +public class PageTarget extends CdpTarget { private final Viewport defaultViewport; - protected AwaitableResult pageResult; + protected AwaitableResult pageResult; - public PageTarget(TargetInfo targetInfo, CDPSession session, BrowserContext browserContext, TargetManager targetManager, SessionFactory sessionFactory, Viewport defaultViewport) { - super(targetInfo, session, browserContext, targetManager, sessionFactory); + public PageTarget(TargetInfo targetInfo, CDPSession session, CdpBrowserContext cdpBrowserContext, TargetManager targetManager, SessionFactory sessionFactory, Viewport defaultViewport) { + super(targetInfo, session, cdpBrowserContext, targetManager, sessionFactory); this.defaultViewport = defaultViewport; } @@ -21,14 +23,14 @@ public void initialize() { this.checkIfInitialized(); } - public Page page() { + public CdpPage page() { if (this.pageResult == null) { pageResult = AwaitableResult.create(); CDPSession session = this.session(); if (session == null) { session = this.sessionFactory().create(false); } - pageResult.onSuccess(Page.create(session, this, this.defaultViewport)); + pageResult.onSuccess(CdpPage.create(session, this, this.defaultViewport)); } return this.pageResult.get(); } @@ -66,13 +68,13 @@ private void initializedCallback(InitializationStatus result) { super.initialize(); return; } - Page openerPage = ((PageTarget) opener).pageResult.waitingGetResult(); - if (openerPage.listenerCount(Page.PageEvent.Popup) == 0) { + CdpPage openerPage = ((PageTarget) opener).pageResult.waitingGetResult(); + if (openerPage.listenerCount(PageEvents.Popup) == 0) { super.initialize(); return; } - Page pupopPage = this.page(); - openerPage.emit(Page.PageEvent.Popup, pupopPage); + CdpPage pupopPage = this.page(); + openerPage.emit(PageEvents.Popup, pupopPage); super.initialize(); } } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Puppeteer.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/Puppeteer.java similarity index 72% rename from src/main/java/com/ruiyun/jvppeteer/core/Puppeteer.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/Puppeteer.java index ff96e0fc..6e6a00c7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/Puppeteer.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/Puppeteer.java @@ -1,16 +1,16 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; -import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.common.BrowserRevision; import com.ruiyun.jvppeteer.common.Environment; import com.ruiyun.jvppeteer.common.Product; -import com.ruiyun.jvppeteer.entities.ConnectOptions; -import com.ruiyun.jvppeteer.entities.FetcherOptions; -import com.ruiyun.jvppeteer.entities.LaunchOptions; -import com.ruiyun.jvppeteer.entities.RevisionInfo; +import com.ruiyun.jvppeteer.cdp.entities.ConnectOptions; +import com.ruiyun.jvppeteer.cdp.entities.FetcherOptions; +import com.ruiyun.jvppeteer.cdp.entities.LaunchOptions; +import com.ruiyun.jvppeteer.cdp.entities.RevisionInfo; import com.ruiyun.jvppeteer.launch.BrowserLauncher; import com.ruiyun.jvppeteer.launch.ChromeLauncher; import com.ruiyun.jvppeteer.launch.FirefoxLauncher; -import com.ruiyun.jvppeteer.transport.ConnectionTransport; import com.ruiyun.jvppeteer.util.Helper; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; @@ -28,7 +28,7 @@ */ public class Puppeteer { - private Product product = Product.CHROME; + private Product product = Product.Chrome; private BrowserLauncher launcher; private Environment env = null; private String cacheDir; @@ -84,60 +84,26 @@ public static Browser rawLaunch(LaunchOptions options, Puppeteer puppeteer) thro } /** - * 连接一个已经存在的浏览器实例 - * browserWSEndpoint、browserURL、transport有其中一个就行了 - *

browserWSEndpoint:类似 UUID 的字符串,可通过{@link Browser#wsEndpoint()}获取

- *

browserURL: 类似 localhost:8080 这个地址

- *

transport: 之前已经创建好的 ConnectionTransport

+ * 建立与浏览器的连接 + * 该方法使用给定的连接选项,通过Puppeteer实例来启动和管理浏览器的连接 + *

+ * browserWSEndpoint、browserURL或者ConnectionTransport至少有一个配置 + * 此外还要指定连接协议 protocol,如果不指定,默认通过cdp连接 + *

+ * browserWSEndpoint的形式是 ws:http://host:port,可通过{@link Browser#wsEndpoint()}获取

+ *

browserURL: 类似 localhost:8080

+ * firefox浏览器连接协议 webdriver bidi,chrome浏览器连接协议 cdp * - * @param options 连接的浏览器选项 - * @return 浏览器实例 - * @throws IOException IO异常 - * @throws InterruptedException 中断异常 + * @param options 连接到浏览器所需的配置选项,例如浏览器的browserWSEndpoint、browserURL或者ConnectionTransport等 + * @return 返回一个Browser对象,表示与浏览器的连接 + * @throws Exception 如果连接过程中出现错误,将抛出异常 */ - private static Browser connect(ConnectOptions options) throws IOException, InterruptedException { + public static Browser connect(ConnectOptions options) throws Exception { Puppeteer puppeteer = new Puppeteer(); adoptLauncher(puppeteer); return puppeteer.getLauncher().connect(options); } - - /** - * 连接一个已经存在的 Browser 实例 - *

browserWSEndpoint:类似 UUID 的字符串,可通过{@link Browser#wsEndpoint()}获取

- *

browserURL: 类似 localhost:8080 这个地址

- * - * @param browserWSEndpointOrURL 一个Browser实例对应一个browserWSEndpoint - * @return 浏览器实例 - * @throws IOException IO异常 - * @throws InterruptedException 中断异常 - */ - public static Browser connect(String browserWSEndpointOrURL) throws IOException, InterruptedException { - ConnectOptions options = new ConnectOptions(); - if (browserWSEndpointOrURL.startsWith("ws:")) { - options.setBrowserWSEndpoint(browserWSEndpointOrURL); - } else { - options.setBrowserURL(browserWSEndpointOrURL); - } - return Puppeteer.connect(options); - } - - /** - * 连接一个已经存在的 Browser 实例 - *

transport: 之前已经创建好的 ConnectionTransport

- * - * @param transport websocket http transport 三选一 - * @return 浏览器实例 - * @throws IOException IO异常 - * @throws InterruptedException 中断异常 - */ - public static Browser connect(ConnectionTransport transport) throws IOException, InterruptedException { - ConnectOptions options = new ConnectOptions(); - options.setTransport(transport); - return Puppeteer.connect(options); - } - - /** * 适配chrome or firefox 浏览器 */ @@ -159,17 +125,17 @@ private static void adoptLauncher(Puppeteer puppeteer) { } } if (product == null) { - product = Product.CHROME; + product = Product.Chrome; puppeteer.setProduct(product); } switch (product) { - case FIREFOX: + case Firefox: launcher = new FirefoxLauncher(puppeteer.getCacheDir(), product); break; - case CHROME: - case CHROMIUM: - case CHROMEDRIVER: - case CHROMEHEADLESSSHELL: + case Chrome: + case Chromium: + case Chromedriver: + case Chrome_headless_shell: default: launcher = new ChromeLauncher(puppeteer.getCacheDir(), product); } @@ -177,7 +143,7 @@ private static void adoptLauncher(Puppeteer puppeteer) { } /** - * 采用默认配置(Product#CHROME,Constant#VERSION)下载浏览器 + * 下载默认版本的浏览器,默认下载 chrome 浏览器,默认版本配置在 {@link BrowserRevision} *

* 本方法负责下载用于自动化测试的浏览器该方法使用Puppeteer类来初始化浏览器Fetcher对象, * 并通过该对象下载浏览器到指定的项目根目录下 @@ -187,7 +153,7 @@ private static void adoptLauncher(Puppeteer puppeteer) { * @throws InterruptedException 如果在下载过程中线程被中断 */ public static RevisionInfo downloadBrowser() throws IOException, InterruptedException { - return Puppeteer.downloadBrowser(Constant.VERSION); + return Puppeteer.downloadBrowser(BrowserRevision.getVersion(Product.Chrome)); } /** diff --git a/src/main/java/com/ruiyun/jvppeteer/core/TargetManager.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/TargetManager.java similarity index 66% rename from src/main/java/com/ruiyun/jvppeteer/core/TargetManager.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/TargetManager.java index ac5e0e97..928b2b29 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/TargetManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/TargetManager.java @@ -1,15 +1,14 @@ -package com.ruiyun.jvppeteer.core; - -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.transport.CDPSession; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.EventEmitter; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; import java.util.List; import java.util.Map; -public abstract class TargetManager extends EventEmitter { - public abstract Map getAvailableTargets(); - public abstract List getChildTargets(Target target); +public abstract class TargetManager extends EventEmitter { + public abstract Map getAvailableTargets(); + public abstract List getChildTargets(CdpTarget target); public abstract void initialize(); public abstract void dispose(); public enum TargetManagerEvent{ @@ -37,7 +36,7 @@ public void setEventName(String eventName) { } @FunctionalInterface public interface TargetFactory{ - Target create(TargetInfo targetInfo, CDPSession session,CDPSession parentSession); + CdpTarget create(TargetInfo targetInfo, CDPSession session, CDPSession parentSession); } } diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Tracing.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/Tracing.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/core/Tracing.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/Tracing.java index 454b2b05..adfb6ebb 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/Tracing.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/Tracing.java @@ -1,16 +1,15 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.common.AwaitableResult; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.common.AwaitableResult; -import com.ruiyun.jvppeteer.events.TracingCompleteEvent; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.cdp.events.TracingCompleteEvent; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import com.ruiyun.jvppeteer.util.Helper; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.util.HashSet; import java.util.List; @@ -18,6 +17,8 @@ import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * You can use [`tracing.start`](#tracingstartoptions) and [`tracing.stop`](#tracingstop) to create a trace file which can be opened in Chrome DevTools or [timeline viewer](https://chromedevtools.github.io/timeline-viewer/) @@ -81,7 +82,7 @@ public void start(String path, boolean screenshots, Set categories) { */ public void stop() { AwaitableResult waitableResult = AwaitableResult.create(); - this.client.once(CDPSession.CDPSessionEvent.Tracing_tracingComplete, (Consumer) event -> { + this.client.once(ConnectionEvents.Tracing_tracingComplete, (Consumer) event -> { try { ValidateUtil.assertArg(StringUtil.isNotEmpty(event.getStream()), "Missing \"stream\""); Helper.readProtocolStream(Tracing.this.client, event.getStream(), Tracing.this.path); @@ -95,7 +96,7 @@ public void stop() { waitableResult.waiting(); } - void updateClient(CDPSession newSession) { + void updateClient(CdpCDPSession newSession) { this.client = newSession; } } diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/core/WaitTask.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/WaitTask.java new file mode 100644 index 00000000..0276556c --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/WaitTask.java @@ -0,0 +1,171 @@ +package com.ruiyun.jvppeteer.cdp.core; + +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.JSHandle; +import com.ruiyun.jvppeteer.api.core.Realm; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateType; +import com.ruiyun.jvppeteer.cdp.entities.WaitTaskOptions; +import com.ruiyun.jvppeteer.common.AwaitableResult; +import com.ruiyun.jvppeteer.common.LazyArg; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.util.StringUtil; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.java_websocket.util.NamedThreadFactory; + + +import static com.ruiyun.jvppeteer.util.Helper.throwError; + +public class WaitTask { + private final Realm world; + private final String polling; + private final List args = new ArrayList<>(); + private final String fn; + private final ElementHandle root; + public static final ExecutorService waitTaskService = Executors.newCachedThreadPool(new NamedThreadFactory("jvppeteer-waitTask-service")); + private final int timeout; + private volatile JSHandle poller; + private final AwaitableResult result = AwaitableResult.create(); + private final CompletableFuture rerunFuture; + + public WaitTask(Realm world, WaitTaskOptions options, String pptrFunction, EvaluateType type, Object... args) { + this.world = world; + this.polling = options.getPolling(); + this.root = options.getRoot(); + this.timeout = options.getTimeout(); + if (EvaluateType.STRING.equals(type)) { + this.fn = "() => {return (" + pptrFunction + ");}"; + } else { + this.fn = pptrFunction; + } + + Optional.ofNullable(args).ifPresent(args1 -> this.args.addAll(Arrays.asList(args1))); + this.world.taskManager.add(this); + + this.rerunFuture = CompletableFuture.supplyAsync(this::rerun, waitTaskService); + } + + public JSHandle rerun() { + try { + switch (this.polling) { + case "raf": + List args = new ArrayList<>(); + args.add(new LazyArg()); + args.add(this.fn); + args.addAll(this.args); + this.poller = this.world.evaluateHandle("({RAFPoller, createFunction}, fn, ...args) => {\n" + + " const fun = createFunction(fn);\n" + + " return new RAFPoller(() => {\n" + + " return fun(...args);\n" + + " });\n" + + "}", args); + break; + case "mutation": + List args1 = new ArrayList<>(); + args1.add(new LazyArg()); + args1.add(this.root); + args1.add(this.fn); + args1.addAll(this.args); + this.poller = this.world.evaluateHandle("({MutationPoller, createFunction}, root, fn, ...args) => {\n" + + " const fun = createFunction(fn);\n" + + " return new MutationPoller(() => {\n" + + " return fun(...args);\n" + + " }, root || document);\n" + + "}", args1); + break; + default: + List args2 = new ArrayList<>(); + args2.add(new LazyArg()); + args2.add(this.polling); + args2.add(this.fn); + args2.addAll(this.args); + this.poller = this.world.evaluateHandle("({IntervalPoller, createFunction}, ms, fn, ...args) => {\n" + + " const fun = createFunction(fn);\n" + + " return new IntervalPoller(() => {\n" + + " return fun(...args);\n" + + " }, ms);\n" + + "}", args2); + + } + this.poller.evaluate("poller => {\n" + + " void poller.start();\n" + + "}"); + + return this.poller.evaluateHandle("poller => {\n" + + " return poller.result();\n" + + " }"); + } catch (Exception error) { + Throwable badError = this.getBadError(error); + if (Objects.nonNull(badError)) { + this.terminate(badError); + } + return null; + } + + } + + public void terminate(Throwable error) { + this.world.taskManager.delete(this); + if (Objects.nonNull(this.poller)) { + try { + this.poller.evaluateHandle("async poller => {\n" + + " await poller.stop();\n" + + " }"); + if (Objects.nonNull(this.poller)) { + this.poller.dispose(); + this.poller = null; + } + } catch (Exception ignored) { + // Ignore errors since they most likely come from low-level cleanup. + } + } + if (error != null && !this.result.isDone()) { + this.result.complete(); + throwError(error); + } + } + + public JSHandle result() throws ExecutionException, InterruptedException, java.util.concurrent.TimeoutException { + return this.rerunFuture.get(this.timeout, TimeUnit.MILLISECONDS); + } + + private Throwable getBadError(Throwable error) { + if (error instanceof EvaluateException) { + // When frame is detached the task should have been terminated by the IsolatedWorld. + // This can fail if we were adding this task while the frame was detached, + // so we terminate here instead. + if (StringUtil.isNotEmpty(error.getMessage()) && error.getMessage().contains("Execution context is not available in detached frame")) { + return new JvppeteerException("Waiting failed: Frame detached"); + } + // When the page is navigated, the promise is rejected. + // We will try again in the new execution context. + if (StringUtil.isNotEmpty(error.getMessage()) && error.getMessage().contains("Execution context was destroyed")) { + return null; + } + + // We could have tried to evaluate in a context which was already + // destroyed. + if (StringUtil.isNotEmpty(error.getMessage()) && error.getMessage().contains("Cannot find context with specified id")) { + return null; + } + + // Errors coming from WebDriver BiDi. TODO: Adjust messages after + // https://github.com/w3c/webdriver-bidi/issues/540 is resolved. + if (StringUtil.isNotEmpty(error.getMessage()) && error.getMessage().contains("AbortError: Actor 'MessageHandlerFrame' destroyed")) { + return null; + } + return error; + } + return new JvppeteerException("WaitTask failed with an error: " + error.getMessage(),error); + } + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/WorkerTarget.java b/src/main/java/com/ruiyun/jvppeteer/cdp/core/WorkerTarget.java similarity index 50% rename from src/main/java/com/ruiyun/jvppeteer/core/WorkerTarget.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/core/WorkerTarget.java index 0f897b71..51c911db 100644 --- a/src/main/java/com/ruiyun/jvppeteer/core/WorkerTarget.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/core/WorkerTarget.java @@ -1,18 +1,18 @@ -package com.ruiyun.jvppeteer.core; +package com.ruiyun.jvppeteer.cdp.core; -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; import com.ruiyun.jvppeteer.transport.SessionFactory; -public class WorkerTarget extends Target { - public WorkerTarget(TargetInfo targetInfo, CDPSession session, BrowserContext browserContext, TargetManager targetManager, SessionFactory sessionFactory) { - super(targetInfo, session, browserContext, targetManager, sessionFactory); +public class WorkerTarget extends CdpTarget { + public WorkerTarget(TargetInfo targetInfo, CDPSession session, CdpBrowserContext cdpBrowserContext, TargetManager targetManager, SessionFactory sessionFactory) { + super(targetInfo, session, cdpBrowserContext, targetManager, sessionFactory); } /** * 如果目标不是 "service_worker" 或 "shared_worker" 类型,则返回 null。 */ - public WebWorker worker() { + public CdpWebWorker worker() { if (!"service_worker".equals(this.targetInfo.getType()) && !"shared_worker".equals(this.targetInfo.getType())) return null; if (this.webWorker == null) { @@ -20,7 +20,7 @@ public WebWorker worker() { if (this.session() == null){ session = this.sessionFactory.create(false); } - this.webWorker = new WebWorker(session, this.targetInfo.getUrl(), this.getTargetId(),this.type(),(arg1, arg2, arg3) -> {} /* consoleAPICalled */, (arg) -> {} /* exceptionThrown */); + this.webWorker = new CdpWebWorker(session, this.targetInfo.getUrl(), this.getTargetId(),this.type(),(arg1, arg2, arg3) -> {} /* consoleAPICalled */, (arg) -> {} /* exceptionThrown */); } return this.webWorker; } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AXNode.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXNode.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/AXNode.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXNode.java index f0592311..dc900923 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AXNode.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXNode.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AXProperty.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXProperty.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/AXProperty.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXProperty.java index 054b6b94..cf3d4262 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AXProperty.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXProperty.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class AXProperty { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AXRelatedNode.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXRelatedNode.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/AXRelatedNode.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXRelatedNode.java index 7bbed633..506ec519 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AXRelatedNode.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXRelatedNode.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class AXRelatedNode { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AXValue.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXValue.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/AXValue.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXValue.java index 0a900f2f..d337c1bf 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AXValue.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXValue.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AXValueSource.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXValueSource.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/AXValueSource.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXValueSource.java index b7acf9d0..4c3f8712 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AXValueSource.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AXValueSource.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * A single source for a computed AX property. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ActiveProperty.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ActiveProperty.java similarity index 86% rename from src/main/java/com/ruiyun/jvppeteer/entities/ActiveProperty.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ActiveProperty.java index b24f4006..c8c54db3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ActiveProperty.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ActiveProperty.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ActiveProperty { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AdFrameStatus.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AdFrameStatus.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/AdFrameStatus.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AdFrameStatus.java index 08890854..5f506f12 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AdFrameStatus.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AdFrameStatus.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AuthChallenge.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuthChallenge.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/AuthChallenge.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuthChallenge.java index 437d30e9..8474f029 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AuthChallenge.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuthChallenge.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Authorization challenge for HTTP status code 401 or 407. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AuthChallengeResponse.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuthChallengeResponse.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/AuthChallengeResponse.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuthChallengeResponse.java index 301024be..b1637e05 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AuthChallengeResponse.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuthChallengeResponse.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class AuthChallengeResponse { private String response = "Default"; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AutofillData.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AutofillData.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/entities/AutofillData.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AutofillData.java index 430fb121..48b3fe95 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AutofillData.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AutofillData.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class AutofillData { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/AuxData.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuxData.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/AuxData.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuxData.java index d7ab5ec4..5517e5e5 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/AuxData.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/AuxData.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class AuxData { private String frameId; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Binding.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Binding.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/Binding.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Binding.java index 2a9eeaba..5065b634 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Binding.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Binding.java @@ -1,15 +1,15 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.api.core.JSHandle; import com.ruiyun.jvppeteer.common.BindingFunction; -import com.ruiyun.jvppeteer.core.ExecutionContext; -import com.ruiyun.jvppeteer.core.JSHandle; +import com.ruiyun.jvppeteer.cdp.core.CdpJSHandle; +import com.ruiyun.jvppeteer.cdp.core.ExecutionContext; import com.ruiyun.jvppeteer.exception.EvaluateException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Binding { private static final Logger LOGGER = LoggerFactory.getLogger(Binding.class); @@ -45,7 +45,7 @@ public void run(ExecutionContext context, int id, List args, boolean isT Map properties = handles.getProperties(); properties.forEach((key, handle) -> { if (args.contains(key)) { - if (handle.getRemoteObject().getSubtype().equals("node")) { + if (handle.remoteObject().getSubtype().equals("node")) { args.set(Integer.parseInt(key), handle); } else { handle.dispose(); @@ -72,8 +72,8 @@ public void run(ExecutionContext context, int id, List args, boolean isT " }", params); for (Object arg : args) { - if (arg instanceof JSHandle) { - ((JSHandle) arg).dispose(); + if (arg instanceof CdpJSHandle) { + ((CdpJSHandle) arg).dispose(); } } } catch (Exception e) { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/BindingPayload.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BindingPayload.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/BindingPayload.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/BindingPayload.java index 314fe780..1a03e0d7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/BindingPayload.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BindingPayload.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/BlockedSetCookieWithReason.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BlockedSetCookieWithReason.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/BlockedSetCookieWithReason.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/BlockedSetCookieWithReason.java index ac82f764..4bd51ea1 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/BlockedSetCookieWithReason.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BlockedSetCookieWithReason.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/BoundingBox.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BoundingBox.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/BoundingBox.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/BoundingBox.java index cb3473df..a48191db 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/BoundingBox.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BoundingBox.java @@ -1,10 +1,11 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class BoundingBox extends Point { private double width; private double height; public BoundingBox() { + super(); } public BoundingBox copy(double x, double y, double width, double height) { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/BoxModel.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BoxModel.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/BoxModel.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/BoxModel.java index 0fd0af34..5fa14070 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/BoxModel.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BoxModel.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/BrowserConnectOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserConnectOptions.java similarity index 72% rename from src/main/java/com/ruiyun/jvppeteer/entities/BrowserConnectOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserConnectOptions.java index 92b6f085..c62e0302 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/BrowserConnectOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserConnectOptions.java @@ -1,8 +1,7 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.api.core.Target; import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.core.Target; - import java.util.function.Function; public class BrowserConnectOptions extends BrowserLaunchArgumentOptions { @@ -36,6 +35,12 @@ public class BrowserConnectOptions extends BrowserLaunchArgumentOptions { * Timeout setting for individual protocol (CDP) calls. */ private int protocolTimeout = Constant.DEFAULT_TIMEOUT; + /** + * 连接协议 + * firefox -- webdriver bidi + * chrome -- cdp + */ + private Protocol protocolType; public BrowserConnectOptions() { super(); @@ -88,4 +93,25 @@ public int getProtocolTimeout() { public void setProtocolTimeout(int protocolTimeout) { this.protocolTimeout = protocolTimeout; } + + public Protocol getProtocolType() { + return protocolType; + } + + public void setProtocolType(Protocol protocolType) { + this.protocolType = protocolType; + } + + @Override + public String toString() { + return "BrowserConnectOptions{" + + "acceptInsecureCerts=" + acceptInsecureCerts + + ", defaultViewport=" + defaultViewport + + ", slowMo=" + slowMo + + ", targetFilter=" + targetFilter + + ", isPageTarget=" + isPageTarget + + ", protocolTimeout=" + protocolTimeout + + ", protocolType=" + protocolType + + '}'; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/BrowserContextOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserContextOptions.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/BrowserContextOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserContextOptions.java index bc0db8ae..70a4384d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/BrowserContextOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserContextOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/BrowserLaunchArgumentOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserLaunchArgumentOptions.java similarity index 80% rename from src/main/java/com/ruiyun/jvppeteer/entities/BrowserLaunchArgumentOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserLaunchArgumentOptions.java index d158643e..02dbf645 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/BrowserLaunchArgumentOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/BrowserLaunchArgumentOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.ArrayList; import java.util.List; @@ -72,4 +72,14 @@ public void setDebuggingPort(int debuggingPort) { this.debuggingPort = debuggingPort; } + @Override + public String toString() { + return "BrowserLaunchArgumentOptions{" + + "headless=" + headless + + ", args=" + args + + ", userDataDir='" + userDataDir + '\'' + + ", devtools=" + devtools + + ", debuggingPort=" + debuggingPort + + '}'; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CSSCoverageOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CSSCoverageOptions.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/entities/CSSCoverageOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CSSCoverageOptions.java index 2ffdfb03..0de58a5f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CSSCoverageOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CSSCoverageOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CSSCoverageOptions { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CSSStyleSheetHeader.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CSSStyleSheetHeader.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/CSSStyleSheetHeader.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CSSStyleSheetHeader.java index 42fc55c6..8e87989e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CSSStyleSheetHeader.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CSSStyleSheetHeader.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * CSS stylesheet metainformation. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CallFrame.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CallFrame.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/CallFrame.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CallFrame.java index ef79ffda..cb49e7a5 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CallFrame.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CallFrame.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CallFrame { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ClickOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ClickOptions.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/entities/ClickOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ClickOptions.java index 5dbe5cde..87302f97 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ClickOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ClickOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ClickOptions extends MouseClickOptions { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ClientProvider.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ClientProvider.java similarity index 60% rename from src/main/java/com/ruiyun/jvppeteer/entities/ClientProvider.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ClientProvider.java index 119c00d3..21a53997 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ClientProvider.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ClientProvider.java @@ -1,7 +1,6 @@ -package com.ruiyun.jvppeteer.entities; - -import com.ruiyun.jvppeteer.transport.CDPSession; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.api.core.CDPSession; import java.util.List; public interface ClientProvider { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ConnectOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConnectOptions.java similarity index 57% rename from src/main/java/com/ruiyun/jvppeteer/entities/ConnectOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConnectOptions.java index 1cc0a2ec..42e222aa 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ConnectOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConnectOptions.java @@ -1,5 +1,6 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.bidi.entities.SupportedWebDriverCapabilities; import com.ruiyun.jvppeteer.transport.ConnectionTransport; import java.util.Map; @@ -9,7 +10,7 @@ public class ConnectOptions extends BrowserConnectOptions { private String browserURL; private ConnectionTransport transport; private Map headers; - + private SupportedWebDriverCapabilities capabilities; public String getBrowserWSEndpoint() { return browserWSEndpoint; } @@ -41,4 +42,23 @@ public Map getHeaders() { public void setHeaders(Map headers) { this.headers = headers; } + + public SupportedWebDriverCapabilities getCapabilities() { + return capabilities; + } + + public void setCapabilities(SupportedWebDriverCapabilities capabilities) { + this.capabilities = capabilities; + } + + @Override + public String toString() { + return "ConnectOptions{" + + "browserWSEndpoint='" + browserWSEndpoint + '\'' + + ", browserURL='" + browserURL + '\'' + + ", transport=" + transport + + ", headers=" + headers + + ", capabilities=" + capabilities + + '}'; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessage.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessage.java similarity index 80% rename from src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessage.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessage.java index 84d24a91..fab053e3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessage.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessage.java @@ -1,31 +1,30 @@ -package com.ruiyun.jvppeteer.entities; - -import com.ruiyun.jvppeteer.core.JSHandle; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.core.JSHandle; import java.util.List; +import java.util.Objects; /** * ConsoleMessage objects are dispatched by page via the 'console' event. */ public class ConsoleMessage { - private ConsoleMessageType type; - private List args; - private List stackTraceLocations; - private String text; + private Frame frame; public ConsoleMessage() { } - public ConsoleMessage(ConsoleMessageType type, String text, List args, List stackTraceLocations) { + public ConsoleMessage(ConsoleMessageType type, String text, List args, List stackTraceLocations, Frame frame) { super(); this.type = type; this.text = text; this.args = args; this.stackTraceLocations = stackTraceLocations; + this.frame = frame; } /** @@ -50,11 +49,7 @@ public void setArgs(List args) { } public ConsoleMessageLocation location() { - return this.stackTraceLocations.isEmpty() ? null : this.stackTraceLocations.get(0); - } - - public void setLocation(List stackTraceLocations) { - this.stackTraceLocations = stackTraceLocations; + return this.stackTraceLocations.isEmpty() ? (Objects.nonNull(this.frame) ? new ConsoleMessageLocation(this.frame.url(), 0) : new ConsoleMessageLocation()) : this.stackTraceLocations.get(0); } public String text() { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessageLocation.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessageLocation.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessageLocation.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessageLocation.java index 087c9595..809d68c3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessageLocation.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessageLocation.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ConsoleMessageLocation { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessageType.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessageType.java new file mode 100644 index 00000000..02ac584a --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ConsoleMessageType.java @@ -0,0 +1,23 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +public enum ConsoleMessageType { + log, + debug, + info, + error, + warn, + dir, + dirxml, + table, + trace, + clear, + startGroup, + startGroupCollapsed, + endGroup, + Assert, + profile, + profileEnd, + count, + timeEnd, + verbose +} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ContinueRequestOverrides.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ContinueRequestOverrides.java similarity index 74% rename from src/main/java/com/ruiyun/jvppeteer/entities/ContinueRequestOverrides.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ContinueRequestOverrides.java index 98d06700..a9b0c8f7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ContinueRequestOverrides.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ContinueRequestOverrides.java @@ -1,12 +1,12 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import java.util.Map; +import java.util.List; public class ContinueRequestOverrides { private String url; private String method; private String postData; - private Map headers; + private List headers; public String getUrl() { return url; @@ -32,11 +32,11 @@ public void setPostData(String postData) { this.postData = postData; } - public Map getHeaders() { + public List getHeaders() { return headers; } - public void setHeaders(Map headers) { + public void setHeaders(List headers) { this.headers = headers; } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Cookie.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Cookie.java similarity index 85% rename from src/main/java/com/ruiyun/jvppeteer/entities/Cookie.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Cookie.java index 7036149c..d81e1330 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Cookie.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Cookie.java @@ -1,6 +1,8 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; +import com.fasterxml.jackson.databind.JsonNode; + /** * Cookie object */ @@ -45,12 +47,12 @@ public class Cookie { * Cookie SameSite type. * "Strict"|"Lax"|"None"; */ - private String sameSite; + private CookieSameSite sameSite; /** * Cookie Priority * "Low"|"Medium"|"High"; */ - private String priority; + private CookiePriority priority; /** * True if cookie is SameParty. */ @@ -58,7 +60,7 @@ public class Cookie { /** * Cookie source scheme type. */ - private String sourceScheme; + private CookieSourceScheme sourceScheme; /** * Cookie source port. Valid values are {-1, [1, 65535]}, -1 indicates an unspecified port. * An unspecified port value allows protocol clients to emulate legacy cookie scope for the port. @@ -66,9 +68,9 @@ public class Cookie { */ private int sourcePort; /** - * Cookie partition key. + * Cookie partition key.用JsonNode 兼容String 和 拥有{@link CookiePartitionKey}对象属性 这两种情况 */ - private CookiePartitionKey partitionKey; + private JsonNode partitionKey; /** * True if cookie partition key is opaque. */ @@ -146,19 +148,19 @@ public void setSession(boolean session) { this.session = session; } - public String getSameSite() { + public CookieSameSite getSameSite() { return sameSite; } - public void setSameSite(String sameSite) { + public void setSameSite(CookieSameSite sameSite) { this.sameSite = sameSite; } - public String getPriority() { + public CookiePriority getPriority() { return priority; } - public void setPriority(String priority) { + public void setPriority(CookiePriority priority) { this.priority = priority; } @@ -171,11 +173,11 @@ public void setSameParty(boolean sameParty) { this.sameParty = sameParty; } - public String getSourceScheme() { + public CookieSourceScheme getSourceScheme() { return sourceScheme; } - public void setSourceScheme(String sourceScheme) { + public void setSourceScheme(CookieSourceScheme sourceScheme) { this.sourceScheme = sourceScheme; } @@ -187,11 +189,11 @@ public void setSourcePort(int sourcePort) { this.sourcePort = sourcePort; } - public CookiePartitionKey getPartitionKey() { + public JsonNode getPartitionKey() { return partitionKey; } - public void setPartitionKey(CookiePartitionKey partitionKey) { + public void setPartitionKey(JsonNode partitionKey) { this.partitionKey = partitionKey; } diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieParam.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieParam.java new file mode 100644 index 00000000..a9cbfcde --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieParam.java @@ -0,0 +1,185 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +import com.fasterxml.jackson.databind.JsonNode; + +public class CookieParam { + /** + * Cookie name. + */ + private String name; + /** + * Cookie value. + */ + private String value; + /** + * The request-URI to associate with the setting of the cookie. This value can affect + * the default domain, path, and source scheme values of the created cookie. + */ + private String url; + /** + * Cookie domain. + */ + private String domain; + /** + * Cookie path. + */ + private String path; + /** + * True if cookie is secure. + */ + private Boolean secure; + /** + * True if cookie is http-only. + */ + private Boolean httpOnly; + /** + * Cookie SameSite type. + */ + private CookieSameSite sameSite; + /** + * Cookie expiration date, session cookie if not set + */ + private Long expires; + /** + * Cookie Priority + * "Low"|"Medium"|"High"; + */ + private CookiePriority priority; + + /** + * True if cookie is SameParty. + */ + private boolean sameParty; + /** + * Cookie source scheme type. + */ + private CookieSourceScheme sourceScheme; + /** + * Cookie partition key. 用JsonNode 兼容String 和 拥有{@link CookiePartitionKey}对象属性 这两种情况 + */ + private JsonNode partitionKey; + + public CookieParam() { + } + + public CookieParam(String name, String value, String url, String domain, String path, Boolean secure, Boolean httpOnly, CookieSameSite sameSite, Long expires, CookiePriority priority, boolean sameParty, CookieSourceScheme sourceScheme, JsonNode partitionKey) { + this.name = name; + this.value = value; + this.url = url; + this.domain = domain; + this.path = path; + this.secure = secure; + this.httpOnly = httpOnly; + this.sameSite = sameSite; + this.expires = expires; + this.priority = priority; + this.sameParty = sameParty; + this.sourceScheme = sourceScheme; + this.partitionKey = partitionKey; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Long getExpires() { + return expires; + } + + public void setExpires(Long expires) { + this.expires = expires; + } + + public Boolean getHttpOnly() { + return httpOnly; + } + + public void setHttpOnly(Boolean httpOnly) { + this.httpOnly = httpOnly; + } + + public Boolean getSecure() { + return secure; + } + + public void setSecure(Boolean secure) { + this.secure = secure; + } + + public CookieSameSite getSameSite() { + return sameSite; + } + + public void setSameSite(CookieSameSite sameSite) { + this.sameSite = sameSite; + } + + + public CookiePriority getPriority() { + return priority; + } + + public void setPriority(CookiePriority priority) { + this.priority = priority; + } + + public boolean getSameParty() { + return sameParty; + } + + public void setSameParty(boolean sameParty) { + this.sameParty = sameParty; + } + + public CookieSourceScheme getSourceScheme() { + return sourceScheme; + } + + public void setSourceScheme(CookieSourceScheme sourceScheme) { + this.sourceScheme = sourceScheme; + } + + public JsonNode getPartitionKey() { + return partitionKey; + } + + public void setPartitionKey(JsonNode partitionKey) { + this.partitionKey = partitionKey; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CookiePartitionKey.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookiePartitionKey.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/CookiePartitionKey.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookiePartitionKey.java index 5009155a..f6771f4e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CookiePartitionKey.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookiePartitionKey.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CookiePartitionKey { private String topLevelSite; diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookiePriority.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookiePriority.java new file mode 100644 index 00000000..c3923897 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookiePriority.java @@ -0,0 +1,12 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum CookiePriority { + @JsonProperty("Low") + Low, + @JsonProperty("Medium") + Medium, + @JsonProperty("High") + High, +} diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSameSite.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSameSite.java new file mode 100644 index 00000000..8b6f005f --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSameSite.java @@ -0,0 +1,7 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +public enum CookieSameSite { + Strict, + Lax, + None +} diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSourceScheme.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSourceScheme.java new file mode 100644 index 00000000..55e3213f --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CookieSourceScheme.java @@ -0,0 +1,12 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum CookieSourceScheme { + @JsonProperty("Unset") + Unset, + @JsonProperty("NonSecure") + NonSecure, + @JsonProperty("Secure") + Secure, +} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CorsErrorStatus.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CorsErrorStatus.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/entities/CorsErrorStatus.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CorsErrorStatus.java index 331e4d7a..e1af4391 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CorsErrorStatus.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CorsErrorStatus.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CorsErrorStatus { private String corsError; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CoverageEntry.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoverageEntry.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/CoverageEntry.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoverageEntry.java index 1104100b..ef9c31c8 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CoverageEntry.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoverageEntry.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CoveragePoint.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoveragePoint.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/CoveragePoint.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoveragePoint.java index c2bee1a3..58f157f1 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CoveragePoint.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoveragePoint.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CoveragePoint { private double offset; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CoverageRange.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoverageRange.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/CoverageRange.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoverageRange.java index f42333a3..5231dd78 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CoverageRange.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CoverageRange.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Coverage data for a source range. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CpuThrottlingState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CpuThrottlingState.java similarity index 90% rename from src/main/java/com/ruiyun/jvppeteer/entities/CpuThrottlingState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CpuThrottlingState.java index 59cd516e..7d53d339 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CpuThrottlingState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CpuThrottlingState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CpuThrottlingState extends ActiveProperty { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Credentials.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Credentials.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/Credentials.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Credentials.java index 8d2957b5..22e0b4b3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Credentials.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Credentials.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * 验证信息 diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CreditCard.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CreditCard.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/CreditCard.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CreditCard.java index f53bb920..31978c1c 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CreditCard.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CreditCard.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CreditCard { private String number; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/CustomPreview.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CustomPreview.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/CustomPreview.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/CustomPreview.java index 328c0f48..a79ae88d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/CustomPreview.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/CustomPreview.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class CustomPreview { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DebugInfo.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DebugInfo.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/DebugInfo.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DebugInfo.java index 07b26448..a018b70e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DebugInfo.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DebugInfo.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import com.ruiyun.jvppeteer.exception.ProtocolException; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DefaultBackgroundColorState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DefaultBackgroundColorState.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/DefaultBackgroundColorState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DefaultBackgroundColorState.java index 7028539b..321f1c78 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DefaultBackgroundColorState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DefaultBackgroundColorState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class DefaultBackgroundColorState extends ActiveProperty { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DeleteCookiesRequest.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DeleteCookiesRequest.java similarity index 85% rename from src/main/java/com/ruiyun/jvppeteer/entities/DeleteCookiesRequest.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DeleteCookiesRequest.java index 60388413..7b5b3f5f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DeleteCookiesRequest.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DeleteCookiesRequest.java @@ -1,4 +1,6 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; + +import com.fasterxml.jackson.databind.node.ObjectNode; /** * Deletes browser cookies with matching name and url or domain/path pair. @@ -26,9 +28,10 @@ public class DeleteCookiesRequest { /** * 如果指定了该值,则在给定的分区键中删除cookies。在Chrome中,分区键匹配分区cookie可用的顶级站点。 + * 可以是string,也可以是 {@link CookiePartitionKey}对象,为了兼容这两种情况,使用了 ObjectNode * 在Firefox中,它匹配源起源(说明)。 */ - private CookiePartitionKey partitionKey; + private ObjectNode partitionKey; public DeleteCookiesRequest() { super(); @@ -79,11 +82,11 @@ public void setPath(String path) { this.path = path; } - public CookiePartitionKey getPartitionKey() { + public ObjectNode getPartitionKey() { return partitionKey; } - public void setPartitionKey(CookiePartitionKey partitionKey) { + public void setPartitionKey(ObjectNode partitionKey) { this.partitionKey = partitionKey; } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Device.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Device.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/Device.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Device.java index ce463918..58ca7d72 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Device.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Device.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * 可以模拟的手机设备枚举类 diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DeviceRequestPromptDevice.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DeviceRequestPromptDevice.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/DeviceRequestPromptDevice.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DeviceRequestPromptDevice.java index 2e4d10b3..1c81232c 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DeviceRequestPromptDevice.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DeviceRequestPromptDevice.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class DeviceRequestPromptDevice { private String id; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DialogType.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DialogType.java similarity index 86% rename from src/main/java/com/ruiyun/jvppeteer/entities/DialogType.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DialogType.java index 64b61edc..33d420d0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DialogType.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DialogType.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import com.fasterxml.jackson.annotation.JsonProperty; @@ -6,7 +6,7 @@ public enum DialogType { @JsonProperty("alert") Alert("alert"), @JsonProperty("beforeunload") - BeforeUnload("beforeunload"), + Beforeunload("beforeunload"), @JsonProperty("confirm") Confirm("confirm"), @JsonProperty("prompt") diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DisposableStackConsumer.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DisposableStackConsumer.java similarity index 67% rename from src/main/java/com/ruiyun/jvppeteer/entities/DisposableStackConsumer.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DisposableStackConsumer.java index 2a536a8d..a1d8f9f0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DisposableStackConsumer.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DisposableStackConsumer.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; @FunctionalInterface public interface DisposableStackConsumer { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DownloadOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadOptions.java similarity index 80% rename from src/main/java/com/ruiyun/jvppeteer/entities/DownloadOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadOptions.java index 46f5225e..890d4683 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DownloadOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadOptions.java @@ -1,6 +1,4 @@ -package com.ruiyun.jvppeteer.entities; - -import java.util.Objects; +package com.ruiyun.jvppeteer.cdp.entities; public class DownloadOptions { /** @@ -8,7 +6,7 @@ public class DownloadOptions { *

* 默认值是DownloadBehavior.Default */ - private DownloadBehavior behavior; + private DownloadPolicy behavior; /** * 设置 哪个BrowserContext 下载行为。省略时,将使用默认浏览器上下文 @@ -23,18 +21,18 @@ public class DownloadOptions { */ private boolean eventsEnabled; - public DownloadOptions(DownloadBehavior behavior, String browserContextId, String downloadPath, boolean eventsEnabled) { + public DownloadOptions(DownloadPolicy behavior, String browserContextId, String downloadPath, boolean eventsEnabled) { this.behavior = behavior; this.browserContextId = browserContextId; this.downloadPath = downloadPath; this.eventsEnabled = eventsEnabled; } - public DownloadBehavior getBehavior() { + public DownloadPolicy getBehavior() { return behavior; } - public void setBehavior(DownloadBehavior behavior) { + public void setBehavior(DownloadPolicy behavior) { this.behavior = behavior; } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DownloadBehavior.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadPolicy.java similarity index 69% rename from src/main/java/com/ruiyun/jvppeteer/entities/DownloadBehavior.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadPolicy.java index f8493b2d..37ec89df 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DownloadBehavior.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadPolicy.java @@ -1,12 +1,12 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -public enum DownloadBehavior { +public enum DownloadPolicy { Deny("deny"), Allow("allow"), AllowAndName("allowAndName"), Default("default"); - DownloadBehavior(String behavior) { + DownloadPolicy(String behavior) { this.behavior = behavior.toLowerCase(); } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DownloadState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadState.java similarity index 83% rename from src/main/java/com/ruiyun/jvppeteer/entities/DownloadState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadState.java index d06bc6b8..d4cf5e53 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DownloadState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DownloadState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DragData.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragData.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/DragData.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragData.java index d63a5660..dc78cd68 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DragData.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragData.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DragDataItem.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragDataItem.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/DragDataItem.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragDataItem.java index 84468180..cee90c1d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DragDataItem.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragDataItem.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class DragDataItem { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/DragInterceptedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragInterceptedEvent.java similarity index 82% rename from src/main/java/com/ruiyun/jvppeteer/entities/DragInterceptedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragInterceptedEvent.java index 57a8f5c5..1934e1a2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/DragInterceptedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/DragInterceptedEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class DragInterceptedEvent { private DragData data; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ElementScreenshotOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ElementScreenshotOptions.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/entities/ElementScreenshotOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ElementScreenshotOptions.java index cdb4d41c..af6d79c5 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ElementScreenshotOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ElementScreenshotOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ElementScreenshotOptions extends ScreenshotOptions{ private boolean scrollIntoView = true; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/EmulatedState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EmulatedState.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/EmulatedState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/EmulatedState.java index 97a94b6a..5c5fc5a2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/EmulatedState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EmulatedState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class EmulatedState { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/EntryPreview.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EntryPreview.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/EntryPreview.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/EntryPreview.java index d230df20..4bb3b6fa 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/EntryPreview.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EntryPreview.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class EntryPreview { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ErrorReasons.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ErrorReasons.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/ErrorReasons.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ErrorReasons.java index f2e99a1d..39a41aca 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ErrorReasons.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ErrorReasons.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum ErrorReasons { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/EvaluateResponse.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EvaluateResponse.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/entities/EvaluateResponse.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/EvaluateResponse.java index 8ecc2e7b..7e4555b7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/EvaluateResponse.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EvaluateResponse.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class EvaluateResponse { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/EvaluateType.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EvaluateType.java similarity index 76% rename from src/main/java/com/ruiyun/jvppeteer/entities/EvaluateType.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/EvaluateType.java index 60e3d5c0..5c33282d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/EvaluateType.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/EvaluateType.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum EvaluateType { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ExceptionDetails.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExceptionDetails.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/ExceptionDetails.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExceptionDetails.java index 9fdfabb2..66b58e52 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ExceptionDetails.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExceptionDetails.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.Map; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ExecutionContextDescription.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExecutionContextDescription.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/ExecutionContextDescription.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExecutionContextDescription.java index 1960db0d..dad7acd1 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ExecutionContextDescription.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExecutionContextDescription.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Description of an isolated world. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ExemptedSetCookieWithReason.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExemptedSetCookieWithReason.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/ExemptedSetCookieWithReason.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExemptedSetCookieWithReason.java index 7540e77b..19e703e4 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ExemptedSetCookieWithReason.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ExemptedSetCookieWithReason.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ExemptedSetCookieWithReason { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/FetcherOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FetcherOptions.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/FetcherOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/FetcherOptions.java index ac2ee37e..61fa2432 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/FetcherOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FetcherOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import com.ruiyun.jvppeteer.common.ChromeReleaseChannel; import com.ruiyun.jvppeteer.common.Product; @@ -21,7 +21,7 @@ public class FetcherOptions { /** * 下载哪种类型的chrome浏览器,默认是chrome */ - private Product product = Product.CHROME; + private Product product = Product.Chrome; /** * 浏览器的版本,格式是xxx.xxx.xxx.xxx,一共四位数,例如 80.0.3987.87 *

diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/FilterEntry.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FilterEntry.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/FilterEntry.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/FilterEntry.java index dab805bb..abe0fd16 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/FilterEntry.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FilterEntry.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class FilterEntry { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/FrameAddScriptTagOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FrameAddScriptTagOptions.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/FrameAddScriptTagOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/FrameAddScriptTagOptions.java index 98add192..79dd758d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/FrameAddScriptTagOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FrameAddScriptTagOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * 用于添加脚本标签的选项。 diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/FrameAddStyleTagOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FrameAddStyleTagOptions.java similarity index 82% rename from src/main/java/com/ruiyun/jvppeteer/entities/FrameAddStyleTagOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/FrameAddStyleTagOptions.java index a86ab4e2..d5193ac8 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/FrameAddStyleTagOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FrameAddStyleTagOptions.java @@ -1,9 +1,9 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.core.Frame; +import com.ruiyun.jvppeteer.cdp.core.CdpFrame; /** - * 在${@link Frame#addScriptTag(FrameAddScriptTagOptions)}用到 + * 在${@link CdpFrame#addScriptTag(FrameAddScriptTagOptions)}用到 */ public class FrameAddStyleTagOptions { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/FramePayload.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FramePayload.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/FramePayload.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/FramePayload.java index e5d76ad4..6de897fd 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/FramePayload.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FramePayload.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/FunctionCoverage.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FunctionCoverage.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/FunctionCoverage.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/FunctionCoverage.java index a71a1fc5..81acadf2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/FunctionCoverage.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/FunctionCoverage.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/GeoLocationState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GeoLocationState.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/GeoLocationState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/GeoLocationState.java index bb32dc82..a2b40e3d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/GeoLocationState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GeoLocationState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class GeoLocationState extends ActiveProperty { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/GeolocationOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GeolocationOptions.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/GeolocationOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/GeolocationOptions.java index 349bd482..26638b7b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/GeolocationOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GeolocationOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class GeolocationOptions { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/GetMetricsResponse.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetMetricsResponse.java similarity index 85% rename from src/main/java/com/ruiyun/jvppeteer/entities/GetMetricsResponse.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetMetricsResponse.java index 00f2d4d2..16061d5b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/GetMetricsResponse.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetMetricsResponse.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/GetNavigationHistoryResponse.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetNavigationHistoryResponse.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/GetNavigationHistoryResponse.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetNavigationHistoryResponse.java index 21040545..2a89d677 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/GetNavigationHistoryResponse.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetNavigationHistoryResponse.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/GetVersionResponse.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetVersionResponse.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/GetVersionResponse.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetVersionResponse.java index 5a3a42e5..c3a1a9dc 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/GetVersionResponse.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GetVersionResponse.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class GetVersionResponse { private String protocolVersion; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/GoToOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GoToOptions.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/entities/GoToOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/GoToOptions.java index 478bc750..9b8a9bb3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/GoToOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/GoToOptions.java @@ -1,11 +1,12 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.core.Page; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; +import com.ruiyun.jvppeteer.cdp.core.CdpPage; import java.util.List; /** - * ${@link Page#goTo} + * ${@link CdpPage#goTo} * 导航到页面的用的 */ public class GoToOptions { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/HeaderEntry.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/HeaderEntry.java similarity index 69% rename from src/main/java/com/ruiyun/jvppeteer/entities/HeaderEntry.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/HeaderEntry.java index 0ac764b1..8561f9e6 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/HeaderEntry.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/HeaderEntry.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Response HTTP header entry @@ -32,4 +32,12 @@ public String getValue() { public void setValue(String value) { this.value = value; } + + @Override + public String toString() { + return "HeaderEntry{" + + "name='" + name + '\'' + + ", value='" + value + '\'' + + '}'; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/IdleOverridesState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/IdleOverridesState.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/IdleOverridesState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/IdleOverridesState.java index 1e919ab3..f97cbd3a 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/IdleOverridesState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/IdleOverridesState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class IdleOverridesState extends ActiveProperty { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ImageType.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ImageType.java similarity index 79% rename from src/main/java/com/ruiyun/jvppeteer/entities/ImageType.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ImageType.java index df4be41f..9fb83255 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ImageType.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ImageType.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum ImageType { PNG, diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Initiator.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Initiator.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/Initiator.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Initiator.java index 2359217d..644b4885 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Initiator.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Initiator.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Information about the request initiator. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/InterceptResolutionAction.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/InterceptResolutionAction.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/entities/InterceptResolutionAction.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/InterceptResolutionAction.java index 679bf152..bb195b22 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/InterceptResolutionAction.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/InterceptResolutionAction.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum InterceptResolutionAction { ABORT("abort"), diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/InterceptResolutionState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/InterceptResolutionState.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/InterceptResolutionState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/InterceptResolutionState.java index e46cd69d..e6bcac49 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/InterceptResolutionState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/InterceptResolutionState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class InterceptResolutionState { private InterceptResolutionAction action = InterceptResolutionAction.NONE; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Interception.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Interception.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/Interception.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Interception.java index da8d887c..fbf385bf 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Interception.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Interception.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/InternalNetworkConditions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/InternalNetworkConditions.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/InternalNetworkConditions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/InternalNetworkConditions.java index 861bd13f..f96878c2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/InternalNetworkConditions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/InternalNetworkConditions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class InternalNetworkConditions extends NetworkConditions { private boolean offline; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/JSCoverageEntry.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/JSCoverageEntry.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/JSCoverageEntry.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/JSCoverageEntry.java index 03673152..341d335b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/JSCoverageEntry.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/JSCoverageEntry.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/JSCoverageOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/JSCoverageOptions.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/JSCoverageOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/JSCoverageOptions.java index 90fc4ad9..ce7ed183 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/JSCoverageOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/JSCoverageOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class JSCoverageOptions { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/JavascriptEnabledState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/JavascriptEnabledState.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/JavascriptEnabledState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/JavascriptEnabledState.java index e8ac1c85..7a7588fd 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/JavascriptEnabledState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/JavascriptEnabledState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class JavascriptEnabledState extends ActiveProperty { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/KeyDefinition.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDefinition.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/KeyDefinition.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDefinition.java index b8aa14ab..de53b378 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/KeyDefinition.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDefinition.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class KeyDefinition { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/KeyDescription.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDescription.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/KeyDescription.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDescription.java index 16e655d3..47bfd94c 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/KeyDescription.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDescription.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class KeyDescription { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/KeyDownOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDownOptions.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/entities/KeyDownOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDownOptions.java index e7116c6c..b2349008 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/KeyDownOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyDownOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/KeyPressOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyPressOptions.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/KeyPressOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyPressOptions.java index eb908e57..8931c5e4 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/KeyPressOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyPressOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/KeyboardTypeOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyboardTypeOptions.java similarity index 81% rename from src/main/java/com/ruiyun/jvppeteer/entities/KeyboardTypeOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyboardTypeOptions.java index 71d14ecc..7c3e9d9f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/KeyboardTypeOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/KeyboardTypeOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class KeyboardTypeOptions { private long delay; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/LaunchOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/LaunchOptions.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/LaunchOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/LaunchOptions.java index 37f5bc1f..ea3ac594 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/LaunchOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/LaunchOptions.java @@ -1,11 +1,10 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.api.core.Target; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.Environment; import com.ruiyun.jvppeteer.common.Product; -import com.ruiyun.jvppeteer.core.Target; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -27,6 +26,7 @@ private LaunchOptions(Builder builder) { this.preferredRevision = builder.preferredRevision; this.cacheDir = builder.cacheDir; this.extraPrefsFirefox = builder.extraPrefsFirefox; + this.protocol = builder.protocol; this.setAcceptInsecureCerts(builder.acceptInsecureCerts); this.setDefaultViewport(builder.defaultViewport); this.setSlowMo(builder.slowMo); @@ -108,6 +108,16 @@ private LaunchOptions(Builder builder) { * {@link | Additional preferences} that can be passed when launching with Firefox. */ private Map extraPrefsFirefox; + /** + * 使用的通讯协议,CHROME 默认是 CDP,也仅支持 CDP + *

+ * firefox 默认使用 WebDriverBiDi,也仅支持 WebDriverBiDi + *

+ * 目前该字段作为保留字段,方便以后支持更多协议 + */ + private Protocol protocol; + + public static Builder builder() { return new Builder(); } @@ -119,7 +129,7 @@ public static class Builder { private int slowMo; private Function targetFilter; private Function isPageTarget; - private int protocolTimeout = 180_000; + private int protocolTimeout = Constant.DEFAULT_TIMEOUT; private boolean headless = true; private List args = new ArrayList<>(); private String userDataDir; @@ -137,6 +147,7 @@ public static class Builder { private String preferredRevision; private String cacheDir; private Map extraPrefsFirefox; + private Protocol protocol; private Builder() { } @@ -256,6 +267,11 @@ public Builder extraPrefsFirefox(Map extraPrefsFirefox) { return this; } + public Builder protocol(Protocol protocol) { + this.protocol = protocol; + return this; + } + public LaunchOptions build() { return new LaunchOptions(this); } @@ -348,4 +364,12 @@ public Map getExtraPrefsFirefox() { public void setExtraPrefsFirefox(Map extraPrefsFirefox) { this.extraPrefsFirefox = extraPrefsFirefox; } + + public Protocol getProtocol() { + return protocol; + } + + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/LengthUnit.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/LengthUnit.java similarity index 83% rename from src/main/java/com/ruiyun/jvppeteer/entities/LengthUnit.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/LengthUnit.java index 67dfb5b2..db9a4e70 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/LengthUnit.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/LengthUnit.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum LengthUnit { CM("cm"), diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/LogEntry.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/LogEntry.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/LogEntry.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/LogEntry.java index e6ec43e5..ff6d566b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/LogEntry.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/LogEntry.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.math.BigDecimal; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MediaFeature.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaFeature.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/MediaFeature.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaFeature.java index a1c03e50..95530c33 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MediaFeature.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaFeature.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class MediaFeature { public String name; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MediaFeaturesState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaFeaturesState.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/MediaFeaturesState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaFeaturesState.java index c719f459..b928c4f5 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MediaFeaturesState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaFeaturesState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MediaTypeState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaTypeState.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/MediaTypeState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaTypeState.java index 0d8e7be5..7c9c72fd 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MediaTypeState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MediaTypeState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import com.ruiyun.jvppeteer.common.MediaType; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Metric.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Metric.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/Metric.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Metric.java index 61e52b7e..7615171e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Metric.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Metric.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Metrics.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Metrics.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/Metrics.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Metrics.java index 1dc1c79d..c5857156 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Metrics.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Metrics.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MouseClickOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseClickOptions.java similarity index 55% rename from src/main/java/com/ruiyun/jvppeteer/entities/MouseClickOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseClickOptions.java index 15ab65dd..7afe3b61 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MouseClickOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseClickOptions.java @@ -1,9 +1,12 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; + +import com.fasterxml.jackson.databind.node.ObjectNode; public class MouseClickOptions extends MouseOptions { private int delay; private int count = 1; - + //bidi 使用 + private ObjectNode origin; public int getDelay() { return delay; } @@ -19,4 +22,12 @@ public int getCount() { public void setCount(int count) { this.count = count; } + + public ObjectNode getOrigin() { + return origin; + } + + public void setOrigin(ObjectNode origin) { + this.origin = origin; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseMoveOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseMoveOptions.java new file mode 100644 index 00000000..10d32cbd --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseMoveOptions.java @@ -0,0 +1,26 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class MouseMoveOptions { + private Integer steps; + //bidi 使用 + private ObjectNode origin; + + public Integer getSteps() { + return steps; + } + + public void setSteps(Integer steps) { + this.steps = steps; + } + + + public ObjectNode getOrigin() { + return origin; + } + + public void setOrigin(ObjectNode origin) { + this.origin = origin; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MouseOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseOptions.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/MouseOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseOptions.java index 3aa7ca1e..f4a4d6f0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MouseOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MouseState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseState.java similarity index 69% rename from src/main/java/com/ruiyun/jvppeteer/entities/MouseState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseState.java index 6702d3bf..41c0ae91 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MouseState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseState.java @@ -1,10 +1,10 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.core.Mouse; +import com.ruiyun.jvppeteer.cdp.core.CdpMouse; public class MouseState { private Point position = new Point(); - private int buttons = Mouse.MouseButtonFlag.None.getValue(); + private int buttons = CdpMouse.MouseButtonFlag.None.getValue(); public Point getPosition() { return position; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MouseWheelOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseWheelOptions.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/MouseWheelOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseWheelOptions.java index 96aef1f4..1d2d51d6 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MouseWheelOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/MouseWheelOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class MouseWheelOptions { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/NavigationEntry.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/NavigationEntry.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/NavigationEntry.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/NavigationEntry.java index 3a6b1fa2..8973bff0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/NavigationEntry.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/NavigationEntry.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Navigation history entry. */ diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/NetworkConditions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/NetworkConditions.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/NetworkConditions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/NetworkConditions.java index 2983a5bf..b53967d2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/NetworkConditions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/NetworkConditions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class NetworkConditions { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/NewDocumentScriptEvaluation.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/NewDocumentScriptEvaluation.java similarity index 90% rename from src/main/java/com/ruiyun/jvppeteer/entities/NewDocumentScriptEvaluation.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/NewDocumentScriptEvaluation.java index 8ed337d1..fcedb5e0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/NewDocumentScriptEvaluation.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/NewDocumentScriptEvaluation.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class NewDocumentScriptEvaluation { private String identifier; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ObjectPreview.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ObjectPreview.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/ObjectPreview.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ObjectPreview.java index 1b9ba30a..b20d55a7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ObjectPreview.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ObjectPreview.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Offset.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Offset.java similarity index 87% rename from src/main/java/com/ruiyun/jvppeteer/entities/Offset.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Offset.java index 272a56e7..2f34699d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Offset.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Offset.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class Offset { private double x; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PDFMargin.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PDFMargin.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/PDFMargin.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/PDFMargin.java index 99d06a69..ffc0ab42 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PDFMargin.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PDFMargin.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class PDFMargin { //上边距 以cm为单位 diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PDFOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PDFOptions.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/PDFOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/PDFOptions.java index 8637bf17..a8a8c6e4 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PDFOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PDFOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * 生成pdf时候需要的参数 diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PageMetrics.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PageMetrics.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/PageMetrics.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/PageMetrics.java index 6a3bc1f9..9b9203d0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PageMetrics.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PageMetrics.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class PageMetrics { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PaperFormats.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PaperFormats.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/PaperFormats.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/PaperFormats.java index ea402e7e..6bee25b2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PaperFormats.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PaperFormats.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum PaperFormats { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Point.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Point.java similarity index 90% rename from src/main/java/com/ruiyun/jvppeteer/entities/Point.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Point.java index 52e80b19..41695eb1 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Point.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Point.java @@ -1,10 +1,11 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class Point { private double x; private double y; public Point() { + super(); } public Point(double x, double y) { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PostDataEntry.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PostDataEntry.java similarity index 81% rename from src/main/java/com/ruiyun/jvppeteer/entities/PostDataEntry.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/PostDataEntry.java index 3e84de69..4783f72a 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PostDataEntry.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PostDataEntry.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class PostDataEntry { private String bytes; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PreloadScript.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PreloadScript.java similarity index 54% rename from src/main/java/com/ruiyun/jvppeteer/entities/PreloadScript.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/PreloadScript.java index 12f73fc8..1e55fe22 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PreloadScript.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PreloadScript.java @@ -1,15 +1,15 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.core.Frame; +import com.ruiyun.jvppeteer.cdp.core.CdpFrame; import java.util.WeakHashMap; public class PreloadScript { private final String id; private final String source; - private final WeakHashMap frameToId = new WeakHashMap<>(); + private final WeakHashMap frameToId = new WeakHashMap<>(); - public PreloadScript(Frame mainFrame, String id, String source) { + public PreloadScript(CdpFrame mainFrame, String id, String source) { this.id = id; this.source = source; this.frameToId.put(mainFrame, id); @@ -23,11 +23,11 @@ public String getSource() { return source; } - public String getIdForFrame(Frame frame) { + public String getIdForFrame(CdpFrame frame) { return frameToId.get(frame); } - public void setIdForFrame(Frame frame, String identifier) { + public void setIdForFrame(CdpFrame frame, String identifier) { frameToId.put(frame, identifier); } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PropertyPreview.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PropertyPreview.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/PropertyPreview.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/PropertyPreview.java index 443c3978..1e50fb2c 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PropertyPreview.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/PropertyPreview.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class PropertyPreview { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Protocol.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Protocol.java new file mode 100644 index 00000000..e50f2da4 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Protocol.java @@ -0,0 +1,6 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +public enum Protocol { + CDP, + WebDriverBiDi +} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/QueuedEventGroup.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/QueuedEventGroup.java similarity index 80% rename from src/main/java/com/ruiyun/jvppeteer/entities/QueuedEventGroup.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/QueuedEventGroup.java index f0fa4d09..548fd205 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/QueuedEventGroup.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/QueuedEventGroup.java @@ -1,8 +1,8 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.events.LoadingFailedEvent; -import com.ruiyun.jvppeteer.events.LoadingFinishedEvent; -import com.ruiyun.jvppeteer.events.ResponseReceivedEvent; +import com.ruiyun.jvppeteer.cdp.events.LoadingFailedEvent; +import com.ruiyun.jvppeteer.cdp.events.LoadingFinishedEvent; +import com.ruiyun.jvppeteer.cdp.events.ResponseReceivedEvent; public class QueuedEventGroup { private ResponseReceivedEvent responseReceivedEvent; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/RGBA.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RGBA.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/RGBA.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/RGBA.java index 9e9483ae..e34f57a6 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/RGBA.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RGBA.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class RGBA { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Range.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Range.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/Range.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Range.java index ca3cda5c..9830c842 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Range.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Range.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class Range { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/RedirectInfo.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RedirectInfo.java similarity index 81% rename from src/main/java/com/ruiyun/jvppeteer/entities/RedirectInfo.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/RedirectInfo.java index f9e8527f..7935864b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/RedirectInfo.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RedirectInfo.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.events.RequestWillBeSentEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestWillBeSentEvent; public class RedirectInfo { private RequestWillBeSentEvent event; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/RemoteAddress.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RemoteAddress.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/entities/RemoteAddress.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/RemoteAddress.java index b689cbad..65bb169e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/RemoteAddress.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RemoteAddress.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class RemoteAddress { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/RemoteObject.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RemoteObject.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/RemoteObject.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/RemoteObject.java index 8b2f7afd..3b013e32 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/RemoteObject.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RemoteObject.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Mirror object referencing original JavaScript object. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/RequestPayload.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RequestPayload.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/RequestPayload.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/RequestPayload.java index 90c5afb3..69ced21e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/RequestPayload.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RequestPayload.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ResourceTiming.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResourceTiming.java similarity index 82% rename from src/main/java/com/ruiyun/jvppeteer/entities/ResourceTiming.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResourceTiming.java index 1793b097..96aa0e52 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ResourceTiming.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResourceTiming.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ResourceTiming { /** @@ -274,4 +274,31 @@ public long getReceiveHeadersEnd() { public void setReceiveHeadersEnd(long receiveHeadersEnd) { this.receiveHeadersEnd = receiveHeadersEnd; } + + @Override + public String toString() { + return "ResourceTiming{" + + "requestTime=" + requestTime + + ", proxyStart=" + proxyStart + + ", proxyEnd=" + proxyEnd + + ", dnsStart=" + dnsStart + + ", dnsEnd=" + dnsEnd + + ", connectStart=" + connectStart + + ", connectEnd=" + connectEnd + + ", sslStart=" + sslStart + + ", sslEnd=" + sslEnd + + ", workerStart=" + workerStart + + ", workerReady=" + workerReady + + ", workerFetchStart=" + workerFetchStart + + ", workerRespondWithSettled=" + workerRespondWithSettled + + ", workerRouterEvaluationStart=" + workerRouterEvaluationStart + + ", workerCacheLookupStart=" + workerCacheLookupStart + + ", sendStart=" + sendStart + + ", sendEnd=" + sendEnd + + ", pushStart=" + pushStart + + ", pushEnd=" + pushEnd + + ", receiveHeadersStart=" + receiveHeadersStart + + ", receiveHeadersEnd=" + receiveHeadersEnd + + '}'; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ResourceType.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResourceType.java similarity index 81% rename from src/main/java/com/ruiyun/jvppeteer/entities/ResourceType.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResourceType.java index cd2f6a09..c394f86a 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ResourceType.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResourceType.java @@ -1,6 +1,12 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum ResourceType { + //firefox + Img("Img"), + Beacon("Beacon"), + Xhr("Xhr"), + Subdocument("Subdocument"), + //chrome Document("Document"), Stylesheet("Stylesheet"), Image("Image"), diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ResponseForRequest.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponseForRequest.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/ResponseForRequest.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponseForRequest.java index 89ace9a1..91b2285f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ResponseForRequest.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponseForRequest.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ResponsePayload.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponsePayload.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/ResponsePayload.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponsePayload.java index 95f3267d..4256c8b7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ResponsePayload.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponsePayload.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.Map; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ResponseSecurityDetails.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponseSecurityDetails.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/ResponseSecurityDetails.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponseSecurityDetails.java index 19c63693..0a9408a9 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ResponseSecurityDetails.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ResponseSecurityDetails.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; @@ -12,7 +12,7 @@ public class ResponseSecurityDetails { public ResponseSecurityDetails() {} - public ResponseSecurityDetails(com.ruiyun.jvppeteer.entities.SecurityDetails securityPayload) { + public ResponseSecurityDetails(SecurityDetails securityPayload) { this.subjectName = securityPayload.getSubjectName(); this.issuer = securityPayload.getIssuer(); this.validFrom = securityPayload.getValidFrom(); diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/RevisionInfo.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RevisionInfo.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/RevisionInfo.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/RevisionInfo.java index 09771bc2..1f3cb960 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/RevisionInfo.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/RevisionInfo.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import com.ruiyun.jvppeteer.common.Product; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenCastFormat.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenCastFormat.java similarity index 84% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScreenCastFormat.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenCastFormat.java index f121f316..ff3d768c 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenCastFormat.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenCastFormat.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum ScreenCastFormat { WEBM("webm"), diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenOrientation.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenOrientation.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScreenOrientation.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenOrientation.java index 1b2c78a8..9855467b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenOrientation.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenOrientation.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * Screen orientation. diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenRecorderOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenRecorderOptions.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScreenRecorderOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenRecorderOptions.java index 2da52aa8..6050cb41 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenRecorderOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenRecorderOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ScreenRecorderOptions { private Double speed; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScreencastFrameMetadata.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreencastFrameMetadata.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScreencastFrameMetadata.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreencastFrameMetadata.java index a67b822c..754d39cd 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScreencastFrameMetadata.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreencastFrameMetadata.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScreencastOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreencastOptions.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScreencastOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreencastOptions.java index e9dd38ae..d7211550 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScreencastOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreencastOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ScreencastOptions { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenshotClip.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenshotClip.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScreenshotClip.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenshotClip.java index ce514dca..d9e424c7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenshotClip.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenshotClip.java @@ -1,10 +1,11 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ScreenshotClip extends BoundingBox { private double scale = 1; public ScreenshotClip() { + super(); } @Override diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenshotOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenshotOptions.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScreenshotOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenshotOptions.java index aa90e88f..c233b7d9 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScreenshotOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScreenshotOptions.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ScreenshotOptions { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ScriptCoverage.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScriptCoverage.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/ScriptCoverage.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScriptCoverage.java index 3ff6f5f4..3dd25f7a 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ScriptCoverage.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ScriptCoverage.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/SecurityDetails.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SecurityDetails.java similarity index 99% rename from src/main/java/com/ruiyun/jvppeteer/entities/SecurityDetails.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/SecurityDetails.java index 5eefafd0..34ab8dbb 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/SecurityDetails.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SecurityDetails.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SelectorParseResult.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SelectorParseResult.java new file mode 100644 index 00000000..d2626def --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SelectorParseResult.java @@ -0,0 +1,53 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; + +public class SelectorParseResult { + private List selectors; + private boolean isPureCSS; + private boolean hasPseudoClasses; + private boolean hasAria; + + public List getSelectors() { + return selectors; + } + + public void setSelectors(List selectors) { + this.selectors = selectors; + } + + public boolean getPureCSS() { + return isPureCSS; + } + + public void setPureCSS(boolean pureCSS) { + isPureCSS = pureCSS; + } + + public boolean getHasPseudoClasses() { + return hasPseudoClasses; + } + + public void setHasPseudoClasses(boolean hasPseudoClasses) { + this.hasPseudoClasses = hasPseudoClasses; + } + + public boolean getHasAria() { + return hasAria; + } + + public void setHasAria(boolean hasAria) { + this.hasAria = hasAria; + } + + @Override + public String toString() { + return "SelectorParseResult{" + + "selectors=" + selectors + + ", isPureCSS=" + isPureCSS + + ", hasPseudoClasses=" + hasPseudoClasses + + ", hasAria=" + hasAria + + '}'; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/SerializedAXNode.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SerializedAXNode.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/SerializedAXNode.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/SerializedAXNode.java index 95568fcb..78bbdf8f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/SerializedAXNode.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SerializedAXNode.java @@ -1,7 +1,6 @@ -package com.ruiyun.jvppeteer.entities; - -import com.ruiyun.jvppeteer.core.ElementHandle; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.api.core.ElementHandle; import java.util.List; public class SerializedAXNode { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ServiceWorkerRouterInfo.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ServiceWorkerRouterInfo.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/ServiceWorkerRouterInfo.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ServiceWorkerRouterInfo.java index 52248f1f..4bcd0176 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ServiceWorkerRouterInfo.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ServiceWorkerRouterInfo.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ServiceWorkerRouterInfo { private int ruleIdMatched; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/SignedCertificateTimestamp.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SignedCertificateTimestamp.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/SignedCertificateTimestamp.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/SignedCertificateTimestamp.java index f91075ed..3ae18196 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/SignedCertificateTimestamp.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SignedCertificateTimestamp.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/SnapshotOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SnapshotOptions.java similarity index 69% rename from src/main/java/com/ruiyun/jvppeteer/entities/SnapshotOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/SnapshotOptions.java index 85d9c701..4269cfc9 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/SnapshotOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/SnapshotOptions.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.core.ElementHandle; +import com.ruiyun.jvppeteer.cdp.core.CdpElementHandle; public class SnapshotOptions { /** @@ -12,7 +12,7 @@ public class SnapshotOptions { *

* 整个页面的根节点 */ - private ElementHandle root; + private CdpElementHandle root; public boolean getInterestingOnly() { return interestingOnly; @@ -22,11 +22,11 @@ public void setInterestingOnly(boolean interestingOnly) { this.interestingOnly = interestingOnly; } - public ElementHandle getRoot() { + public CdpElementHandle getRoot() { return root; } - public void setRoot(ElementHandle root) { + public void setRoot(CdpElementHandle root) { this.root = root; } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/StackTrace.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/StackTrace.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/entities/StackTrace.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/StackTrace.java index 6d4f9cb6..8da67bbb 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/StackTrace.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/StackTrace.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/StackTraceId.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/StackTraceId.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/StackTraceId.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/StackTraceId.java index 86615f7f..595630a2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/StackTraceId.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/StackTraceId.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * If `debuggerId` is set stack trace comes from another debugger and can be resolved there. This diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/TakePreciseCoverageResponse.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TakePreciseCoverageResponse.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/entities/TakePreciseCoverageResponse.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/TakePreciseCoverageResponse.java index 4d91f443..08d8b4ac 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/TakePreciseCoverageResponse.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TakePreciseCoverageResponse.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/TargetInfo.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TargetInfo.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/TargetInfo.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/TargetInfo.java index c4c2bd6c..6f0c0cbe 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/TargetInfo.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TargetInfo.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class TargetInfo { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/TargetType.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TargetType.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/entities/TargetType.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/TargetType.java index cfb3b7a8..7afa15b9 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/TargetType.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TargetType.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public enum TargetType { PAGE("page"), diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Timeoutable.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Timeoutable.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/entities/Timeoutable.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Timeoutable.java index 43c48c23..6e262f69 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Timeoutable.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Timeoutable.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import com.ruiyun.jvppeteer.common.Constant; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Timestamp.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Timestamp.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/entities/Timestamp.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Timestamp.java index 9bca626f..950206cb 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Timestamp.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Timestamp.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/TimezoneState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TimezoneState.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/entities/TimezoneState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/TimezoneState.java index e070cf2a..33103316 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/TimezoneState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TimezoneState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class TimezoneState extends ActiveProperty{ diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Token.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Token.java new file mode 100644 index 00000000..87b191b0 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Token.java @@ -0,0 +1,50 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +public class Token { + private String type; + private String argument; + private String name; + private String content; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getArgument() { + return argument; + } + + public void setArgument(String argument) { + this.argument = argument; + } + + @Override + public String toString() { + return "Token{" + + "type='" + type + '\'' + + ", argument='" + argument + '\'' + + ", name='" + name + '\'' + + ", content='" + content + '\'' + + '}'; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/TouchPoint.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TouchPoint.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/entities/TouchPoint.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/TouchPoint.java index 22005a36..1725dcad 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/TouchPoint.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TouchPoint.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class TouchPoint extends Point { private double radiusX; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/TrustTokenParams.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TrustTokenParams.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/TrustTokenParams.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/TrustTokenParams.java index b2a39b0b..4404c0de 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/TrustTokenParams.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/TrustTokenParams.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Updater.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Updater.java similarity index 52% rename from src/main/java/com/ruiyun/jvppeteer/entities/Updater.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Updater.java index c5874241..5388ec6a 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Updater.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Updater.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.CDPSession; @FunctionalInterface public interface Updater{ diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/UserAgentBrandVersion.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/UserAgentBrandVersion.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/UserAgentBrandVersion.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/UserAgentBrandVersion.java index ab8f28d0..cbd4e1e5 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/UserAgentBrandVersion.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/UserAgentBrandVersion.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class UserAgentBrandVersion { private String brand; diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/UserAgentMetadata.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/UserAgentMetadata.java similarity index 78% rename from src/main/java/com/ruiyun/jvppeteer/entities/UserAgentMetadata.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/UserAgentMetadata.java index cddeb126..cff6d79c 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/UserAgentMetadata.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/UserAgentMetadata.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; import java.util.List; /** @@ -102,4 +102,20 @@ public String getFullVersion() { public void setFullVersion(String fullVersion) { this.fullVersion = fullVersion; } + + @Override + public String toString() { + return "UserAgentMetadata{" + + "brands=" + brands + + ", fullVersionList=" + fullVersionList + + ", fullVersion='" + fullVersion + '\'' + + ", platform='" + platform + '\'' + + ", platformVersion='" + platformVersion + '\'' + + ", architecture='" + architecture + '\'' + + ", model='" + model + '\'' + + ", mobile=" + mobile + + ", bitness='" + bitness + '\'' + + ", wow64=" + wow64 + + '}'; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/Viewport.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Viewport.java similarity index 98% rename from src/main/java/com/ruiyun/jvppeteer/entities/Viewport.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/Viewport.java index 416621ca..da36c16f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/Viewport.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/Viewport.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class Viewport { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ViewportState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ViewportState.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/entities/ViewportState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/ViewportState.java index d390214e..8a1f854b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ViewportState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/ViewportState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class ViewportState extends ActiveProperty{ diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/VisionDeficiency.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/VisionDeficiency.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/entities/VisionDeficiency.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/VisionDeficiency.java index ad4ba293..6d48b1d3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/VisionDeficiency.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/VisionDeficiency.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; /** * 视力缺陷类,设置不同级别的实力缺陷,截图有不同的效果 diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/VisionDeficiencyState.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/VisionDeficiencyState.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/entities/VisionDeficiencyState.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/VisionDeficiencyState.java index 5a06eb04..c24f62a6 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/VisionDeficiencyState.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/VisionDeficiencyState.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; public class VisionDeficiencyState extends ActiveProperty { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/WaitForNetworkIdleOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForNetworkIdleOptions.java similarity index 71% rename from src/main/java/com/ruiyun/jvppeteer/entities/WaitForNetworkIdleOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForNetworkIdleOptions.java index e4556dfb..468d5a76 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/WaitForNetworkIdleOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForNetworkIdleOptions.java @@ -1,24 +1,26 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; + +import static com.ruiyun.jvppeteer.common.Constant.NETWORK_IDLE_TIME; public class WaitForNetworkIdleOptions { /** * 网络应空闲的时间(以毫秒为单位)。 */ - public int idleTime; + private Integer idleTime = NETWORK_IDLE_TIME; /** * 被视为不活动的最大并发网络连接数。 */ - public int concurrency; + private int concurrency; /** * 最长等待时间(以毫秒为单位)。传递 0 以禁用超时。 * 可以使用 Page.setDefaultTimeout() 方法更改默认值。 */ - public int timeout; + private Integer timeout; public WaitForNetworkIdleOptions() { } - public WaitForNetworkIdleOptions(int idleTime, int concurrency, int timeout) { + public WaitForNetworkIdleOptions(Integer idleTime, int concurrency, Integer timeout) { this.idleTime = idleTime; this.concurrency = concurrency; this.timeout = timeout; @@ -28,7 +30,7 @@ public int getIdleTime() { return idleTime; } - public void setIdleTime(int idleTime) { + public void setIdleTime(Integer idleTime) { this.idleTime = idleTime; } @@ -40,11 +42,11 @@ public void setConcurrency(int concurrency) { this.concurrency = concurrency; } - public int getTimeout() { + public Integer getTimeout() { return timeout; } - public void setTimeout(int timeout) { + public void setTimeout(Integer timeout) { this.timeout = timeout; } diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/WaitForOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForOptions.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/entities/WaitForOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForOptions.java index 6bb90d98..a311ef38 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/WaitForOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForOptions.java @@ -1,5 +1,6 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; +import com.ruiyun.jvppeteer.common.PuppeteerLifeCycle; import java.util.List; public class WaitForOptions { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/WaitForSelectorOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForSelectorOptions.java similarity index 66% rename from src/main/java/com/ruiyun/jvppeteer/entities/WaitForSelectorOptions.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForSelectorOptions.java index bc48b5a9..d228a699 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/WaitForSelectorOptions.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitForSelectorOptions.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.cdp.entities; -import com.ruiyun.jvppeteer.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.ElementHandle; public class WaitForSelectorOptions { /** @@ -11,9 +11,24 @@ public class WaitForSelectorOptions { * 等待所选元素在 DOM 中不存在或被隐藏。有关元素不可见性的定义,请参阅 ElementHandle.isHidden()。 */ private boolean hidden; - + /** + * Maximum time to wait in milliseconds. Defaults to `30000` (30 seconds). + * Pass `0` to disable the timeout. Puppeteer's default timeout can be changed + * using {@link com.ruiyun.jvppeteer.api.core.Page#setDefaultTimeout(int)}. + */ private Integer timeout; - + /** + * An interval at which the `pageFunction` is executed, defaults to `raf`. If + * `polling` is a number, then it is treated as an interval in milliseconds at + * which the function would be executed. If `polling` is a string, then it can + * be one of the following values: + *

+ * - `raf` - to constantly execute `pageFunction` in `requestAnimationFrame` + * callback. This is the tightest polling mode which is suitable to observe + * styling changes. + *

+ * - `mutation` - to execute `pageFunction` on every DOM mutation. + */ private String polling; private ElementHandle root; diff --git a/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitTaskOptions.java b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitTaskOptions.java new file mode 100644 index 00000000..4ed4f6e6 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/entities/WaitTaskOptions.java @@ -0,0 +1,40 @@ +package com.ruiyun.jvppeteer.cdp.entities; + +import com.ruiyun.jvppeteer.api.core.ElementHandle; + +public class WaitTaskOptions { + private String polling; + private ElementHandle root; + private int timeout; + + + public WaitTaskOptions(String polling, ElementHandle root, int timeout) { + this.polling = polling; + this.timeout = timeout; + this.root = root; + } + + public String getPolling() { + return polling; + } + + public void setPolling(String polling) { + this.polling = polling; + } + + public ElementHandle getRoot() { + return root; + } + + public void setRoot(ElementHandle root) { + this.root = root; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/events/AttachedToTargetEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/AttachedToTargetEvent.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/events/AttachedToTargetEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/AttachedToTargetEvent.java index 7602ccfb..704f7485 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/AttachedToTargetEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/AttachedToTargetEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; public class AttachedToTargetEvent { private String sessionId; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/AuthRequiredEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/AuthRequiredEvent.java similarity index 91% rename from src/main/java/com/ruiyun/jvppeteer/events/AuthRequiredEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/AuthRequiredEvent.java index a8eb0d25..0ce9a8b1 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/AuthRequiredEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/AuthRequiredEvent.java @@ -1,7 +1,7 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.AuthChallenge; -import com.ruiyun.jvppeteer.entities.RequestPayload; +import com.ruiyun.jvppeteer.cdp.entities.AuthChallenge; +import com.ruiyun.jvppeteer.cdp.entities.RequestPayload; /** * Issued when the domain is enabled with handleAuthRequests set to true. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/BindingCalledEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/BindingCalledEvent.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/events/BindingCalledEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/BindingCalledEvent.java index df93f6d1..a7375e17 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/BindingCalledEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/BindingCalledEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; public class BindingCalledEvent { diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ConsoleAPICalledEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ConsoleAPICalledEvent.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/events/ConsoleAPICalledEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ConsoleAPICalledEvent.java index 2b2daed5..7990715f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ConsoleAPICalledEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ConsoleAPICalledEvent.java @@ -1,7 +1,7 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.RemoteObject; -import com.ruiyun.jvppeteer.entities.StackTrace; +import com.ruiyun.jvppeteer.cdp.entities.RemoteObject; +import com.ruiyun.jvppeteer.cdp.entities.StackTrace; import java.math.BigDecimal; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/DetachedFromTargetEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DetachedFromTargetEvent.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/events/DetachedFromTargetEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/DetachedFromTargetEvent.java index af3c8dd1..b01ec20e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/DetachedFromTargetEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DetachedFromTargetEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; public class DetachedFromTargetEvent { private String sessionId; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/DeviceRequestPromptedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DeviceRequestPromptedEvent.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/events/DeviceRequestPromptedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/DeviceRequestPromptedEvent.java index 2c1f10ec..77642cca 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/DeviceRequestPromptedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DeviceRequestPromptedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.DeviceRequestPromptDevice; +import com.ruiyun.jvppeteer.cdp.entities.DeviceRequestPromptDevice; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/DownloadProgressEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DownloadProgressEvent.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/events/DownloadProgressEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/DownloadProgressEvent.java index ff205a61..7c21b215 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/DownloadProgressEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DownloadProgressEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.DownloadState; +import com.ruiyun.jvppeteer.cdp.entities.DownloadState; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/DownloadWillBeginEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DownloadWillBeginEvent.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/events/DownloadWillBeginEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/DownloadWillBeginEvent.java index 90fa196a..8c2b8cef 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/DownloadWillBeginEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/DownloadWillBeginEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; public class DownloadWillBeginEvent { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/events/EntryAddedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/EntryAddedEvent.java similarity index 82% rename from src/main/java/com/ruiyun/jvppeteer/events/EntryAddedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/EntryAddedEvent.java index b44e049c..253a145d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/EntryAddedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/EntryAddedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.LogEntry; +import com.ruiyun.jvppeteer.cdp.entities.LogEntry; /** * Issued when new message was logged. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ExceptionThrownEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ExceptionThrownEvent.java similarity index 86% rename from src/main/java/com/ruiyun/jvppeteer/events/ExceptionThrownEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ExceptionThrownEvent.java index 0808b126..42587db6 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ExceptionThrownEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ExceptionThrownEvent.java @@ -1,7 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.Timestamp; -import com.ruiyun.jvppeteer.entities.ExceptionDetails; +import com.ruiyun.jvppeteer.cdp.entities.ExceptionDetails; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ExecutionContextCreatedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ExecutionContextCreatedEvent.java similarity index 78% rename from src/main/java/com/ruiyun/jvppeteer/events/ExecutionContextCreatedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ExecutionContextCreatedEvent.java index 3fe755e2..43b800e2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ExecutionContextCreatedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ExecutionContextCreatedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.ExecutionContextDescription; +import com.ruiyun.jvppeteer.cdp.entities.ExecutionContextDescription; /** * Issued when new execution context is created. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ExecutionContextDestroyedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ExecutionContextDestroyedEvent.java similarity index 90% rename from src/main/java/com/ruiyun/jvppeteer/events/ExecutionContextDestroyedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ExecutionContextDestroyedEvent.java index 87f5c338..9ea1db9d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ExecutionContextDestroyedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ExecutionContextDestroyedEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; /** * Issued when execution context is destroyed. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/FileChooserOpenedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FileChooserOpenedEvent.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/events/FileChooserOpenedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/FileChooserOpenedEvent.java index e1e77b16..4870aed4 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/FileChooserOpenedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FileChooserOpenedEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; /** * Emitted only when `page.interceptFileChooser` is enabled. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/FrameAttachedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameAttachedEvent.java similarity index 90% rename from src/main/java/com/ruiyun/jvppeteer/events/FrameAttachedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameAttachedEvent.java index aba71c91..f51ab1fe 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/FrameAttachedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameAttachedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.StackTrace; +import com.ruiyun.jvppeteer.cdp.entities.StackTrace; /** * Fired when frame has been attached to its parent. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/FrameDetachedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameDetachedEvent.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/events/FrameDetachedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameDetachedEvent.java index eed4c98f..c429642d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/FrameDetachedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameDetachedEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; /** * Fired when frame has been detached from its parent. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/FrameNavigatedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameNavigatedEvent.java similarity index 85% rename from src/main/java/com/ruiyun/jvppeteer/events/FrameNavigatedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameNavigatedEvent.java index e3971515..1c9a82ad 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/FrameNavigatedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameNavigatedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.FramePayload; +import com.ruiyun.jvppeteer.cdp.entities.FramePayload; /** * Fired once navigation of the frame has completed. Frame is now associated with the new loader. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/FrameStartedLoadingEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameStartedLoadingEvent.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/events/FrameStartedLoadingEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameStartedLoadingEvent.java index 4c32db42..7b7acb11 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/FrameStartedLoadingEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameStartedLoadingEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; /** * Fired when frame has stopped loading. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/FrameStoppedLoadingEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameStoppedLoadingEvent.java similarity index 87% rename from src/main/java/com/ruiyun/jvppeteer/events/FrameStoppedLoadingEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameStoppedLoadingEvent.java index 2254c439..9e01c86b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/FrameStoppedLoadingEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/FrameStoppedLoadingEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; public class FrameStoppedLoadingEvent { diff --git a/src/main/java/com/ruiyun/jvppeteer/events/IsolatedWorldEmitter.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/IsolatedWorldEmitter.java similarity index 84% rename from src/main/java/com/ruiyun/jvppeteer/events/IsolatedWorldEmitter.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/IsolatedWorldEmitter.java index dc93745d..40e17da6 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/IsolatedWorldEmitter.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/IsolatedWorldEmitter.java @@ -1,4 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; + +import com.ruiyun.jvppeteer.api.core.EventEmitter; public class IsolatedWorldEmitter extends EventEmitter { diff --git a/src/main/java/com/ruiyun/jvppeteer/events/JavascriptDialogOpeningEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/JavascriptDialogOpeningEvent.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/events/JavascriptDialogOpeningEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/JavascriptDialogOpeningEvent.java index 2025e139..167cc341 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/JavascriptDialogOpeningEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/JavascriptDialogOpeningEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.DialogType; +import com.ruiyun.jvppeteer.cdp.entities.DialogType; /** * Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) is about to diff --git a/src/main/java/com/ruiyun/jvppeteer/events/LifecycleEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/LifecycleEvent.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/events/LifecycleEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/LifecycleEvent.java index 88c37eed..4aced617 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/LifecycleEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/LifecycleEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/LoadingFailedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/LoadingFailedEvent.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/events/LoadingFailedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/LoadingFailedEvent.java index e72ae5d3..6a4c0f64 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/LoadingFailedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/LoadingFailedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.CorsErrorStatus; +import com.ruiyun.jvppeteer.cdp.entities.CorsErrorStatus; /** * Fired when HTTP request has failed to load. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/LoadingFinishedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/LoadingFinishedEvent.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/events/LoadingFinishedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/LoadingFinishedEvent.java index 6947a9d6..e1ac4650 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/LoadingFinishedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/LoadingFinishedEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; /** * Fired when HTTP request has finished loading. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/MetricsEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/MetricsEvent.java similarity index 85% rename from src/main/java/com/ruiyun/jvppeteer/events/MetricsEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/MetricsEvent.java index 8a74389f..784aa8e0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/MetricsEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/MetricsEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.Metric; +import com.ruiyun.jvppeteer.cdp.entities.Metric; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/NavigatedWithinDocumentEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/NavigatedWithinDocumentEvent.java similarity index 93% rename from src/main/java/com/ruiyun/jvppeteer/events/NavigatedWithinDocumentEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/NavigatedWithinDocumentEvent.java index 377e9407..389ae2d5 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/NavigatedWithinDocumentEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/NavigatedWithinDocumentEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; /** * Fired when same-document navigation happens, e.g. due to history API usage or anchor navigation. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/RequestPausedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestPausedEvent.java similarity index 96% rename from src/main/java/com/ruiyun/jvppeteer/events/RequestPausedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestPausedEvent.java index 38424bb6..f9929955 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/RequestPausedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestPausedEvent.java @@ -1,7 +1,7 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.RequestPayload; -import com.ruiyun.jvppeteer.entities.HeaderEntry; +import com.ruiyun.jvppeteer.cdp.entities.RequestPayload; +import com.ruiyun.jvppeteer.cdp.entities.HeaderEntry; import java.util.List; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/RequestServedFromCacheEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestServedFromCacheEvent.java similarity index 89% rename from src/main/java/com/ruiyun/jvppeteer/events/RequestServedFromCacheEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestServedFromCacheEvent.java index 204ea1c2..ed408f9c 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/RequestServedFromCacheEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestServedFromCacheEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; /** * Fired if request ended up loading from cache. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/RequestWillBeSentEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestWillBeSentEvent.java similarity index 94% rename from src/main/java/com/ruiyun/jvppeteer/events/RequestWillBeSentEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestWillBeSentEvent.java index b838f084..45b6d8bd 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/RequestWillBeSentEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/RequestWillBeSentEvent.java @@ -1,8 +1,8 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.Initiator; -import com.ruiyun.jvppeteer.entities.RequestPayload; -import com.ruiyun.jvppeteer.entities.ResponsePayload; +import com.ruiyun.jvppeteer.cdp.entities.Initiator; +import com.ruiyun.jvppeteer.cdp.entities.RequestPayload; +import com.ruiyun.jvppeteer.cdp.entities.ResponsePayload; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ResponseReceivedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ResponseReceivedEvent.java similarity index 95% rename from src/main/java/com/ruiyun/jvppeteer/events/ResponseReceivedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ResponseReceivedEvent.java index 700cecac..a26aa96d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ResponseReceivedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ResponseReceivedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.ResponsePayload; +import com.ruiyun.jvppeteer.cdp.entities.ResponsePayload; import java.math.BigDecimal; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ResponseReceivedExtraInfoEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ResponseReceivedExtraInfoEvent.java similarity index 75% rename from src/main/java/com/ruiyun/jvppeteer/events/ResponseReceivedExtraInfoEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ResponseReceivedExtraInfoEvent.java index 3e32242e..0199e7b1 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ResponseReceivedExtraInfoEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ResponseReceivedExtraInfoEvent.java @@ -1,20 +1,17 @@ -package com.ruiyun.jvppeteer.events; - -import com.ruiyun.jvppeteer.entities.BlockedSetCookieWithReason; -import com.ruiyun.jvppeteer.entities.CookiePartitionKey; -import com.ruiyun.jvppeteer.entities.ExemptedSetCookieWithReason; +package com.ruiyun.jvppeteer.cdp.events; +import com.ruiyun.jvppeteer.cdp.entities.BlockedSetCookieWithReason; +import com.ruiyun.jvppeteer.cdp.entities.ExemptedSetCookieWithReason; import java.util.List; import java.util.Map; public class ResponseReceivedExtraInfoEvent { private String requestId; private List blockedCookies; - private Map headers; + private Map headers; private String resourceIPAddressSpace; private int statusCode; private String headersText; - private CookiePartitionKey cookiePartitionKey; private boolean cookiePartitionKeyOpaque; private List exemptedCookies; @@ -42,14 +39,6 @@ public void setCookiePartitionKeyOpaque(boolean cookiePartitionKeyOpaque) { this.cookiePartitionKeyOpaque = cookiePartitionKeyOpaque; } - public CookiePartitionKey getCookiePartitionKey() { - return cookiePartitionKey; - } - - public void setCookiePartitionKey(CookiePartitionKey cookiePartitionKey) { - this.cookiePartitionKey = cookiePartitionKey; - } - public String getHeadersText() { return headersText; } @@ -82,11 +71,11 @@ public void setHeaders(Map headers) { this.headers = headers; } - public List getBlockedCookies() { + public List getBlockedCookies() { return blockedCookies; } - public void setBlockedCookies(List blockedCookies) { + public void setBlockedCookies(List blockedCookies) { this.blockedCookies = blockedCookies; } } diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ScreencastFrameEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ScreencastFrameEvent.java similarity index 88% rename from src/main/java/com/ruiyun/jvppeteer/events/ScreencastFrameEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ScreencastFrameEvent.java index 83fd9c7c..78e570b0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ScreencastFrameEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ScreencastFrameEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.ScreencastFrameMetadata; +import com.ruiyun.jvppeteer.cdp.entities.ScreencastFrameMetadata; public class ScreencastFrameEvent { private String data; diff --git a/src/main/java/com/ruiyun/jvppeteer/events/ScriptParsedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ScriptParsedEvent.java similarity index 97% rename from src/main/java/com/ruiyun/jvppeteer/events/ScriptParsedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/ScriptParsedEvent.java index 81459740..6fb5dd53 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/ScriptParsedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/ScriptParsedEvent.java @@ -1,7 +1,7 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.AuxData; -import com.ruiyun.jvppeteer.entities.StackTrace; +import com.ruiyun.jvppeteer.cdp.entities.AuxData; +import com.ruiyun.jvppeteer.cdp.entities.StackTrace; /** * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/StyleSheetAddedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/StyleSheetAddedEvent.java similarity index 74% rename from src/main/java/com/ruiyun/jvppeteer/events/StyleSheetAddedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/StyleSheetAddedEvent.java index bb43f9d5..19348b7e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/StyleSheetAddedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/StyleSheetAddedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.CSSStyleSheetHeader; +import com.ruiyun.jvppeteer.cdp.entities.CSSStyleSheetHeader; public class StyleSheetAddedEvent { /** diff --git a/src/main/java/com/ruiyun/jvppeteer/events/TargetCreatedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetCreatedEvent.java similarity index 76% rename from src/main/java/com/ruiyun/jvppeteer/events/TargetCreatedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetCreatedEvent.java index e88ec66b..720b2c92 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/TargetCreatedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetCreatedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; /** * Issued when a possible inspection target is created. diff --git a/src/main/java/com/ruiyun/jvppeteer/events/TargetDestroyedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetDestroyedEvent.java similarity index 84% rename from src/main/java/com/ruiyun/jvppeteer/events/TargetDestroyedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetDestroyedEvent.java index b85e3357..497e6ccb 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/TargetDestroyedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetDestroyedEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; public class TargetDestroyedEvent { diff --git a/src/main/java/com/ruiyun/jvppeteer/events/TargetInfoChangedEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetInfoChangedEvent.java similarity index 80% rename from src/main/java/com/ruiyun/jvppeteer/events/TargetInfoChangedEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetInfoChangedEvent.java index 11a8844b..01a24dad 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/TargetInfoChangedEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TargetInfoChangedEvent.java @@ -1,6 +1,6 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; -import com.ruiyun.jvppeteer.entities.TargetInfo; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; /** * Issued when some information about a target has changed. This only happens between diff --git a/src/main/java/com/ruiyun/jvppeteer/events/TracingCompleteEvent.java b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TracingCompleteEvent.java similarity index 92% rename from src/main/java/com/ruiyun/jvppeteer/events/TracingCompleteEvent.java rename to src/main/java/com/ruiyun/jvppeteer/cdp/events/TracingCompleteEvent.java index 8707d288..ac114553 100644 --- a/src/main/java/com/ruiyun/jvppeteer/events/TracingCompleteEvent.java +++ b/src/main/java/com/ruiyun/jvppeteer/cdp/events/TracingCompleteEvent.java @@ -1,4 +1,4 @@ -package com.ruiyun.jvppeteer.events; +package com.ruiyun.jvppeteer.cdp.events; public class TracingCompleteEvent { diff --git a/src/main/java/com/ruiyun/jvppeteer/common/ARIAQueryHandler.java b/src/main/java/com/ruiyun/jvppeteer/common/ARIAQueryHandler.java index 960ab2e0..b9930543 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/ARIAQueryHandler.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/ARIAQueryHandler.java @@ -1,35 +1,27 @@ package com.ruiyun.jvppeteer.common; import com.fasterxml.jackson.core.JsonProcessingException; -import com.ruiyun.jvppeteer.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.ElementHandle; import com.ruiyun.jvppeteer.util.StringUtil; - import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class ARIAQueryHandler { +public class ARIAQueryHandler extends QueryHandler { private final static String ATTRIBUTE_REGEXP = "\\[\\s*(?\\w+)\\s*=\\s*(?\"|')(?\\\\.|.*?(?=\\k))\\k\\s*\\]"; - private final ElementHandle element; - private final String selector; - - public ARIAQueryHandler(ElementHandle element, String selector) { - this.element = element; - this.selector = selector; - } - public ElementHandle queryOne() throws JsonProcessingException { - return queryAll().stream().findFirst().get(); + public ElementHandle queryOne(ElementHandle element, String selector) throws JsonProcessingException { + return this.queryAll(element, selector).stream().findFirst().orElse(null); } - public List queryAll() throws JsonProcessingException { - ARIASelector ariaSelector = parseARIASelector(this.selector); + public List queryAll(ElementHandle element, String selector) throws JsonProcessingException { + ARIASelector ariaSelector = parseARIASelector(selector); return element.queryAXTree(ariaSelector.getName(), ariaSelector.getRole()); } - private ARIASelector parseARIASelector(String selector) { + private static ARIASelector parseARIASelector(String selector) { ARIASelector queryOptions = new ARIASelector(); Pattern selectorPattern = Pattern.compile(ATTRIBUTE_REGEXP, Pattern.MULTILINE); Matcher matcher = selectorPattern.matcher(selector); @@ -57,4 +49,21 @@ private ARIASelector parseARIASelector(String selector) { private static boolean isKnownAttribute(String attribute) { return Arrays.asList("name", "role").contains(attribute); } + + @Override + public String querySelector() { + return "async (\n" + + " node,\n" + + " selector,\n" + + " {ariaQuerySelector},\n" + + ") => {\n" + + " return await ariaQuerySelector(node, selector);\n" + + "}"; + } + + @Override + public String querySelectorAll() { + return ""; + } + } diff --git a/src/main/java/com/ruiyun/jvppeteer/common/BrowserRevision.java b/src/main/java/com/ruiyun/jvppeteer/common/BrowserRevision.java new file mode 100644 index 00000000..acf3aa3d --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/BrowserRevision.java @@ -0,0 +1,24 @@ +package com.ruiyun.jvppeteer.common; + +public class BrowserRevision { + + private final static String CHROME_VERSION = "131.0.6778.87"; + private final static String FIREFOX_VERSION = "stable_133.0"; + + /** + * 获取默认浏览器版本,最好使用默认指定的版本,否则有些cdp api参数会失效 + * + * @return 默认的浏览器版本 + */ + public static String getVersion(Product product) { + if (product == null) { + throw new NullPointerException("product is null"); + } + if (Product.Chrome.equals(product) || Product.Chromium.equals(product) || Product.Chrome_headless_shell.equals(product) || Product.Chromedriver.equals(product)) { + return CHROME_VERSION; + } else { + return FIREFOX_VERSION; + } + } + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/CSSQueryHandler.java b/src/main/java/com/ruiyun/jvppeteer/common/CSSQueryHandler.java new file mode 100644 index 00000000..fdbe93a3 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/CSSQueryHandler.java @@ -0,0 +1,21 @@ +package com.ruiyun.jvppeteer.common; + +public class CSSQueryHandler extends QueryHandler{ + @Override + public String querySelector() { + return "(element, selector, { cssQuerySelector }\n" + + ") => {\n" + + " return cssQuerySelector(element, selector);\n" + + "}"; + } + + @Override + public String querySelectorAll() { + return "(element,\n" + + " selector,\n" + + " { cssQuerySelectorAll }\n" + + ") => {\n" + + " return cssQuerySelectorAll(element, selector);\n" + + "}"; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/ChromeEnvironment.java b/src/main/java/com/ruiyun/jvppeteer/common/ChromeEnvironment.java index 2aa449d0..de797d66 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/ChromeEnvironment.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/ChromeEnvironment.java @@ -1,20 +1,22 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.core.Realm; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Realm; public class ChromeEnvironment { final CDPSession client; final Realm mainRealm; - public ChromeEnvironment( CDPSession client, Realm mainRealm) { + + public ChromeEnvironment(CDPSession client, Realm mainRealm) { this.client = client; this.mainRealm = mainRealm; } - public CDPSession client(){ + public CDPSession client() { return client; } - Realm mainRealm(){ + + Realm mainRealm() { return mainRealm; } } diff --git a/src/main/java/com/ruiyun/jvppeteer/common/ConsoleAPI.java b/src/main/java/com/ruiyun/jvppeteer/common/ConsoleAPI.java index 262bccd3..12ad8b28 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/ConsoleAPI.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/ConsoleAPI.java @@ -1,9 +1,8 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.core.JSHandle; -import com.ruiyun.jvppeteer.entities.ConsoleMessageType; -import com.ruiyun.jvppeteer.entities.StackTrace; - +import com.ruiyun.jvppeteer.api.core.JSHandle; +import com.ruiyun.jvppeteer.cdp.entities.ConsoleMessageType; +import com.ruiyun.jvppeteer.cdp.entities.StackTrace; import java.util.List; /** diff --git a/src/main/java/com/ruiyun/jvppeteer/common/Constant.java b/src/main/java/com/ruiyun/jvppeteer/common/Constant.java index 17e38f6c..5a382bcc 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/Constant.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/Constant.java @@ -5,44 +5,58 @@ import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.ruiyun.jvppeteer.entities.DragInterceptedEvent; -import com.ruiyun.jvppeteer.events.AttachedToTargetEvent; -import com.ruiyun.jvppeteer.events.AuthRequiredEvent; -import com.ruiyun.jvppeteer.events.BindingCalledEvent; -import com.ruiyun.jvppeteer.events.ConsoleAPICalledEvent; -import com.ruiyun.jvppeteer.events.DetachedFromTargetEvent; -import com.ruiyun.jvppeteer.events.DownloadProgressEvent; -import com.ruiyun.jvppeteer.events.DownloadWillBeginEvent; -import com.ruiyun.jvppeteer.events.EntryAddedEvent; -import com.ruiyun.jvppeteer.events.ExceptionThrownEvent; -import com.ruiyun.jvppeteer.events.ExecutionContextCreatedEvent; -import com.ruiyun.jvppeteer.events.ExecutionContextDestroyedEvent; -import com.ruiyun.jvppeteer.events.FileChooserOpenedEvent; -import com.ruiyun.jvppeteer.events.FrameAttachedEvent; -import com.ruiyun.jvppeteer.events.FrameDetachedEvent; -import com.ruiyun.jvppeteer.events.FrameNavigatedEvent; -import com.ruiyun.jvppeteer.events.FrameStartedLoadingEvent; -import com.ruiyun.jvppeteer.events.FrameStoppedLoadingEvent; -import com.ruiyun.jvppeteer.events.JavascriptDialogOpeningEvent; -import com.ruiyun.jvppeteer.events.LifecycleEvent; -import com.ruiyun.jvppeteer.events.LoadingFailedEvent; -import com.ruiyun.jvppeteer.events.LoadingFinishedEvent; -import com.ruiyun.jvppeteer.events.MetricsEvent; -import com.ruiyun.jvppeteer.events.NavigatedWithinDocumentEvent; -import com.ruiyun.jvppeteer.events.RequestPausedEvent; -import com.ruiyun.jvppeteer.events.RequestServedFromCacheEvent; -import com.ruiyun.jvppeteer.events.RequestWillBeSentEvent; -import com.ruiyun.jvppeteer.events.ResponseReceivedEvent; -import com.ruiyun.jvppeteer.events.ResponseReceivedExtraInfoEvent; -import com.ruiyun.jvppeteer.events.ScreencastFrameEvent; -import com.ruiyun.jvppeteer.events.ScriptParsedEvent; -import com.ruiyun.jvppeteer.events.StyleSheetAddedEvent; -import com.ruiyun.jvppeteer.events.TargetCreatedEvent; -import com.ruiyun.jvppeteer.events.TargetDestroyedEvent; -import com.ruiyun.jvppeteer.events.TargetInfoChangedEvent; -import com.ruiyun.jvppeteer.events.TracingCompleteEvent; -import com.ruiyun.jvppeteer.transport.CDPSession; - +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.bidi.entities.AuthRequiredParameters; +import com.ruiyun.jvppeteer.bidi.entities.BaseParameters; +import com.ruiyun.jvppeteer.bidi.entities.ClosedEvent; +import com.ruiyun.jvppeteer.bidi.entities.FetchErrorParameters; +import com.ruiyun.jvppeteer.bidi.entities.LogEntry; +import com.ruiyun.jvppeteer.bidi.entities.MessageParameters; +import com.ruiyun.jvppeteer.bidi.entities.RealmDestroyedParameters; +import com.ruiyun.jvppeteer.bidi.entities.RealmInfo; +import com.ruiyun.jvppeteer.bidi.entities.ResponseCompletedParameters; +import com.ruiyun.jvppeteer.bidi.entities.ResponseStartedParameters; +import com.ruiyun.jvppeteer.bidi.entities.UserPromptClosedParameters; +import com.ruiyun.jvppeteer.bidi.entities.UserPromptOpenedParameters; +import com.ruiyun.jvppeteer.bidi.events.ContextCreatedEvent; +import com.ruiyun.jvppeteer.bidi.events.NavigationInfoEvent; +import com.ruiyun.jvppeteer.cdp.entities.DragInterceptedEvent; +import com.ruiyun.jvppeteer.cdp.events.AttachedToTargetEvent; +import com.ruiyun.jvppeteer.cdp.events.AuthRequiredEvent; +import com.ruiyun.jvppeteer.cdp.events.BindingCalledEvent; +import com.ruiyun.jvppeteer.cdp.events.ConsoleAPICalledEvent; +import com.ruiyun.jvppeteer.cdp.events.DetachedFromTargetEvent; +import com.ruiyun.jvppeteer.cdp.events.DownloadProgressEvent; +import com.ruiyun.jvppeteer.cdp.events.DownloadWillBeginEvent; +import com.ruiyun.jvppeteer.cdp.events.EntryAddedEvent; +import com.ruiyun.jvppeteer.cdp.events.ExceptionThrownEvent; +import com.ruiyun.jvppeteer.cdp.events.ExecutionContextCreatedEvent; +import com.ruiyun.jvppeteer.cdp.events.ExecutionContextDestroyedEvent; +import com.ruiyun.jvppeteer.cdp.events.FileChooserOpenedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameAttachedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameDetachedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameNavigatedEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameStartedLoadingEvent; +import com.ruiyun.jvppeteer.cdp.events.FrameStoppedLoadingEvent; +import com.ruiyun.jvppeteer.cdp.events.JavascriptDialogOpeningEvent; +import com.ruiyun.jvppeteer.cdp.events.LifecycleEvent; +import com.ruiyun.jvppeteer.cdp.events.LoadingFailedEvent; +import com.ruiyun.jvppeteer.cdp.events.LoadingFinishedEvent; +import com.ruiyun.jvppeteer.cdp.events.MetricsEvent; +import com.ruiyun.jvppeteer.cdp.events.NavigatedWithinDocumentEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestPausedEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestServedFromCacheEvent; +import com.ruiyun.jvppeteer.cdp.events.RequestWillBeSentEvent; +import com.ruiyun.jvppeteer.cdp.events.ResponseReceivedEvent; +import com.ruiyun.jvppeteer.cdp.events.ResponseReceivedExtraInfoEvent; +import com.ruiyun.jvppeteer.cdp.events.ScreencastFrameEvent; +import com.ruiyun.jvppeteer.cdp.events.ScriptParsedEvent; +import com.ruiyun.jvppeteer.cdp.events.StyleSheetAddedEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetCreatedEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetDestroyedEvent; +import com.ruiyun.jvppeteer.cdp.events.TargetInfoChangedEvent; +import com.ruiyun.jvppeteer.cdp.events.TracingCompleteEvent; +import com.ruiyun.jvppeteer.transport.CdpCDPSession; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -60,10 +74,6 @@ */ public interface Constant { - /** - * 默认浏览器版本,最好使用默认指定的版本,否则有些cdp api参数会失效 - */ - String VERSION = "130.0.6723.58"; /** * chrome 临时文件夹前缀 */ @@ -82,6 +92,7 @@ public interface Constant { */ String[] EXECUTABLE_ENV = {"JVPPETEER_EXECUTABLE_PATH", "java_config_jvppeteer_executable_path", "java_package_config_jvppeteer_executable_path"}; + String JVPPETEER_TEST_EXPERIMENTAL_CHROME_FEATURES = "JVPPETEER_TEST_EXPERIMENTAL_CHROME_FEATURES"; /** * 把浏览器版本存放到环境变量的字段 */ @@ -196,6 +207,7 @@ public interface Constant { String ID = "id"; String RESULT = "result"; String SESSION_ID = "sessionId"; + String SESSION = "session"; String TARGET_INFO = "targetInfo"; String TYPE = "type"; String ERROR = "error"; @@ -211,7 +223,7 @@ public interface Constant { /** * 默认的超时时间:启动浏览器实例超时,websocket接受消息超时等 */ - int DEFAULT_TIMEOUT = 30000; + int DEFAULT_TIMEOUT = 30_000; /** * 追踪信息的默认分类 @@ -234,6 +246,9 @@ public interface Constant { // add("disabled-by-default-v8.cpu_profiler.hires"); } }; + String JVPPETEER_VERSION = "3.0.0"; + + String UTILITY_WORLD_NAME = "__puppeteer_utility_world__" + JVPPETEER_VERSION; String CDP_BINDING_PREFIX = "puppeteer_"; @@ -250,25 +265,23 @@ public interface Constant { * 空白页面 */ String ABOUT_BLANK = "about:blank"; - /** - * 从websocket接受到消息后,如果这个消息包含有事件并且该事件有监听器,则放到这个线程中执行 - */ - String JV_EMIT_EVENT_THREAD = "JvEmitEventThread-"; + String JV_HANDLE_MESSAGE_THREAD = "JvHandleMessageThread-"; /** * connection cdpsession的监听器执行时所对应的类 */ - Map> LISTENER_CLASSES = new HashMap>() { + Map> LISTENER_CLASSES = new HashMap>() { { - for (CDPSession.CDPSessionEvent event : CDPSession.CDPSessionEvent.values()) { + for (ConnectionEvents event : ConnectionEvents.values()) { switch (event.getEventName()) { + //cdp case "CDPSession.Disconnected": put(event.getEventName(), null); break; case "sessionattached": case "sessionDetached": - put(event.getEventName(), CDPSession.class); + put(event.getEventName(), CdpCDPSession.class); break; case "Target.targetCreated": put(event.getEventName(), TargetCreatedEvent.class); @@ -378,11 +391,63 @@ public interface Constant { case "Browser.downloadWillBegin": put(event.getEventName(), DownloadWillBeginEvent.class); break; + //bidi + case "browsingContext.contextCreated": + case "browsingContext.contextDestroyed": + put(event.getEventName(), ContextCreatedEvent.class); + break; + case "ended": + put(event.getEventName(), ClosedEvent.class); + break; + case "browsingContext.navigationAborted": + case "browsingContext.navigationFailed": + case "browsingContext.fragmentNavigated": + case "browsingContext.navigationStarted": + put(event.getEventName(), NavigationInfoEvent.class); + break; + case "script.realmCreated": + put(event.getEventName(), RealmInfo.class); + break; + case "script.realmDestroyed": + put(event.getEventName(), RealmDestroyedParameters.class); + break; + case "browsingContext.load": + case "browsingContext.domContentLoaded": + put(event.getEventName(), NavigationInfoEvent.class); + break; + case "network.authRequired": + put(event.getEventName(), AuthRequiredParameters.class); + break; + case "network.fetchError": + put(event.getEventName(), FetchErrorParameters.class); + break; + case "network.responseCompleted": + put(event.getEventName(), ResponseCompletedParameters.class); + break; + case "network.beforeRequestSent": + put(event.getEventName(), BaseParameters.class); + break; + case "network.responseStarted": + put(event.getEventName(), ResponseStartedParameters.class); + break; + case "log.entryAdded": + put(event.getEventName(), LogEntry.class); + break; + case "browsingContext.userPromptOpened": + put(event.getEventName(), UserPromptOpenedParameters.class); + break; + case "browsingContext.userPromptClosed": + put(event.getEventName(), UserPromptClosedParameters.class); + break; + case "script.message": + put(event.getEventName(), MessageParameters.class); + break; } } } }; + Map CLOSE_REASON = new HashMap() {{ put(1000, "Normal closure"); put(1001, "Endpoint is leaving, possibly due to a server error or because the browser is navigating away from the page with the open connection"); @@ -426,5 +491,18 @@ public interface Constant { } }; - List EVENTS = Arrays.stream(CDPSession.CDPSessionEvent.values()).map(CDPSession.CDPSessionEvent::getEventName).collect(Collectors.toList()); + List EVENTS = Arrays.stream(ConnectionEvents.values()).map(ConnectionEvents::getEventName).collect(Collectors.toList()); + + String CDP_SPECIFIC_PREFIX = "goog:"; + String PREFS_JS = "prefs.js"; + String USER_JS = "user.js"; + String BACKUP_SUFFIX = ".bak"; + /** + * 应用与 PuppeteerUtil + */ + String Source = "(() => {\n const module = {};\n \"use strict\";var g=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var l=(t,e)=>{for(var r in e)g(t,r,{get:e[r],enumerable:!0})},J=(t,e,r,o)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let n of B(e))!Y.call(t,n)&&n!==r&&g(t,n,{get:()=>e[n],enumerable:!(o=X(e,n))||o.enumerable});return t};var z=t=>J(g({},\"__esModule\",{value:!0}),t);var pe={};l(pe,{default:()=>he});module.exports=z(pe);var N=class extends Error{constructor(e,r){super(e,r),this.name=this.constructor.name}get[Symbol.toStringTag](){return this.constructor.name}},p=class extends N{};var c=class t{static create(e){return new t(e)}static async race(e){let r=new Set;try{let o=e.map(n=>n instanceof t?(n.#n&&r.add(n),n.valueOrThrow()):n);return await Promise.race(o)}finally{for(let o of r)o.reject(new Error(\"Timeout cleared\"))}}#e=!1;#r=!1;#o;#t;#a=new Promise(e=>{this.#t=e});#n;#i;constructor(e){e&&e.timeout>0&&(this.#i=new p(e.message),this.#n=setTimeout(()=>{this.reject(this.#i)},e.timeout))}#l(e){clearTimeout(this.#n),this.#o=e,this.#t()}resolve(e){this.#r||this.#e||(this.#e=!0,this.#l(e))}reject(e){this.#r||this.#e||(this.#r=!0,this.#l(e))}resolved(){return this.#e}finished(){return this.#e||this.#r}value(){return this.#o}#s;valueOrThrow(){return this.#s||(this.#s=(async()=>{if(await this.#a,this.#r)throw this.#o;return this.#o})()),this.#s}};var L=new Map,F=t=>{let e=L.get(t);return e||(e=new Function(`return ${t}`)(),L.set(t,e),e)};var x={};l(x,{ariaQuerySelector:()=>G,ariaQuerySelectorAll:()=>b});var G=(t,e)=>globalThis.__ariaQuerySelector(t,e),b=async function*(t,e){yield*await globalThis.__ariaQuerySelectorAll(t,e)};var E={};l(E,{cssQuerySelector:()=>K,cssQuerySelectorAll:()=>Z});var K=(t,e)=>t.querySelector(e),Z=function(t,e){return t.querySelectorAll(e)};var A={};l(A,{customQuerySelectors:()=>P});var v=class{#e=new Map;register(e,r){if(!r.queryOne&&r.queryAll){let o=r.queryAll;r.queryOne=(n,i)=>{for(let s of o(n,i))return s;return null}}else if(r.queryOne&&!r.queryAll){let o=r.queryOne;r.queryAll=(n,i)=>{let s=o(n,i);return s?[s]:[]}}else if(!r.queryOne||!r.queryAll)throw new Error(\"At least one query method must be defined.\");this.#e.set(e,{querySelector:r.queryOne,querySelectorAll:r.queryAll})}unregister(e){this.#e.delete(e)}get(e){return this.#e.get(e)}clear(){this.#e.clear()}},P=new v;var R={};l(R,{pierceQuerySelector:()=>ee,pierceQuerySelectorAll:()=>te});var ee=(t,e)=>{let r=null,o=n=>{let i=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT);do{let s=i.currentNode;s.shadowRoot&&o(s.shadowRoot),!(s instanceof ShadowRoot)&&s!==n&&!r&&s.matches(e)&&(r=s)}while(!r&&i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r},te=(t,e)=>{let r=[],o=n=>{let i=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT);do{let s=i.currentNode;s.shadowRoot&&o(s.shadowRoot),!(s instanceof ShadowRoot)&&s!==n&&s.matches(e)&&r.push(s)}while(i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r};var u=(t,e)=>{if(!t)throw new Error(e)};var y=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=new MutationObserver(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())}),this.#o.observe(this.#r,{childList:!0,subtree:!0,attributes:!0})}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(this.#o.disconnect(),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}},w=class{#e;#r;constructor(e){this.#e=e}async start(){let e=this.#r=c.create(),r=await this.#e();if(r){e.resolve(r);return}let o=async()=>{if(e.finished())return;let n=await this.#e();if(!n){window.requestAnimationFrame(o);return}e.resolve(n),await this.stop()};window.requestAnimationFrame(o)}async stop(){u(this.#r,\"Polling never started.\"),this.#r.finished()||this.#r.reject(new Error(\"Polling stopped\"))}result(){return u(this.#r,\"Polling never started.\"),this.#r.valueOrThrow()}},S=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=setInterval(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())},this.#r)}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(clearInterval(this.#o),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}};var _={};l(_,{PCombinator:()=>H,pQuerySelector:()=>fe,pQuerySelectorAll:()=>$});var a=class{static async*map(e,r){for await(let o of e)yield await r(o)}static async*flatMap(e,r){for await(let o of e)yield*r(o)}static async collect(e){let r=[];for await(let o of e)r.push(o);return r}static async first(e){for await(let r of e)return r}};var C={};l(C,{textQuerySelectorAll:()=>m});var re=new Set([\"checkbox\",\"image\",\"radio\"]),oe=t=>t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLInputElement&&!re.has(t.type),ne=new Set([\"SCRIPT\",\"STYLE\"]),f=t=>!ne.has(t.nodeName)&&!document.head?.contains(t),I=new WeakMap,j=t=>{for(;t;)I.delete(t),t instanceof ShadowRoot?t=t.host:t=t.parentNode},W=new WeakSet,se=new MutationObserver(t=>{for(let e of t)j(e.target)}),d=t=>{let e=I.get(t);if(e||(e={full:\"\",immediate:[]},!f(t)))return e;let r=\"\";if(oe(t))e.full=t.value,e.immediate.push(t.value),t.addEventListener(\"input\",o=>{j(o.target)},{once:!0,capture:!0});else{for(let o=t.firstChild;o;o=o.nextSibling){if(o.nodeType===Node.TEXT_NODE){e.full+=o.nodeValue??\"\",r+=o.nodeValue??\"\";continue}r&&e.immediate.push(r),r=\"\",o.nodeType===Node.ELEMENT_NODE&&(e.full+=d(o).full)}r&&e.immediate.push(r),t instanceof Element&&t.shadowRoot&&(e.full+=d(t.shadowRoot).full),W.has(t)||(se.observe(t,{childList:!0,characterData:!0,subtree:!0}),W.add(t))}return I.set(t,e),e};var m=function*(t,e){let r=!1;for(let o of t.childNodes)if(o instanceof Element&&f(o)){let n;o.shadowRoot?n=m(o.shadowRoot,e):n=m(o,e);for(let i of n)yield i,r=!0}r||t instanceof Element&&f(t)&&d(t).full.includes(e)&&(yield t)};var k={};l(k,{checkVisibility:()=>le,pierce:()=>T,pierceAll:()=>O});var ie=[\"hidden\",\"collapse\"],le=(t,e)=>{if(!t)return e===!1;if(e===void 0)return t;let r=t.nodeType===Node.TEXT_NODE?t.parentElement:t,o=window.getComputedStyle(r),n=o&&!ie.includes(o.visibility)&&!ae(r);return e===n?t:!1};function ae(t){let e=t.getBoundingClientRect();return e.width===0||e.height===0}var ce=t=>\"shadowRoot\"in t&&t.shadowRoot instanceof ShadowRoot;function*T(t){ce(t)?yield t.shadowRoot:yield t}function*O(t){t=T(t).next().value,yield t;let e=[document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT)];for(let r of e){let o;for(;o=r.nextNode();)o.shadowRoot&&(yield o.shadowRoot,e.push(document.createTreeWalker(o.shadowRoot,NodeFilter.SHOW_ELEMENT)))}}var Q={};l(Q,{xpathQuerySelectorAll:()=>q});var q=function*(t,e,r=-1){let n=(t.ownerDocument||document).evaluate(e,t,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE),i=[],s;for(;(s=n.iterateNext())&&(i.push(s),!(r&&i.length===r)););for(let h=0;h(r.Descendent=\">>>\",r.Child=\">>>>\",r))(H||{}),V=t=>\"querySelectorAll\"in t,M=class{#e;#r=[];#o=void 0;elements;constructor(e,r){this.elements=[e],this.#e=r,this.#t()}async run(){if(typeof this.#o==\"string\")switch(this.#o.trimStart()){case\":scope\":this.#t();break}for(;this.#o!==void 0;this.#t()){let e=this.#o;typeof e==\"string\"?e[0]&&ue.test(e[0])?this.elements=a.flatMap(this.elements,async function*(r){V(r)&&(yield*r.querySelectorAll(e))}):this.elements=a.flatMap(this.elements,async function*(r){if(!r.parentElement){if(!V(r))return;yield*r.querySelectorAll(e);return}let o=0;for(let n of r.parentElement.children)if(++o,n===r)break;yield*r.parentElement.querySelectorAll(`:scope>:nth-child(${o})${e}`)}):this.elements=a.flatMap(this.elements,async function*(r){switch(e.name){case\"text\":yield*m(r,e.value);break;case\"xpath\":yield*q(r,e.value);break;case\"aria\":yield*b(r,e.value);break;default:let o=P.get(e.name);if(!o)throw new Error(`Unknown selector type: ${e.name}`);yield*o.querySelectorAll(r,e.value)}})}}#t(){if(this.#r.length!==0){this.#o=this.#r.shift();return}if(this.#e.length===0){this.#o=void 0;return}let e=this.#e.shift();switch(e){case\">>>>\":{this.elements=a.flatMap(this.elements,T),this.#t();break}case\">>>\":{this.elements=a.flatMap(this.elements,O),this.#t();break}default:this.#r=e,this.#t();break}}},D=class{#e=new WeakMap;calculate(e,r=[]){if(e===null)return r;e instanceof ShadowRoot&&(e=e.host);let o=this.#e.get(e);if(o)return[...o,...r];let n=0;for(let s=e.previousSibling;s;s=s.previousSibling)++n;let i=this.calculate(e.parentNode,[n]);return this.#e.set(e,i),[...i,...r]}},U=(t,e)=>{if(t.length+e.length===0)return 0;let[r=-1,...o]=t,[n=-1,...i]=e;return r===n?U(o,i):r[o,r.calculate(o)]).sort(([,o],[,n])=>U(o,n)).map(([o])=>o)},$=function(t,e){let r=JSON.parse(e);if(r.some(o=>{let n=0;return o.some(i=>(typeof i==\"string\"?++n:n=0,n>1))}))throw new Error(\"Multiple deep combinators found in sequence.\");return de(a.flatMap(r,o=>{let n=new M(t,o);return n.run(),n.elements}))},fe=async function(t,e){for await(let r of $(t,e))return r;return null};var me=Object.freeze({...x,...A,...R,..._,...C,...k,...Q,...E,Deferred:c,createFunction:F,createTextContent:d,IntervalPoller:S,isSuitableNodeForTextMatching:f,MutationPoller:y,RAFPoller:w}),he=me;\n\n \n return module.exports.default;\n })()"; + String NaN = "NaN"; + String Infinity = "Infinity"; + String Navigate_Infinity = "-Infinity"; + String Navigate_Zero = "-0"; } diff --git a/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPrompt.java b/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPrompt.java index 25a0ca96..06905107 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPrompt.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPrompt.java @@ -1,10 +1,10 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.entities.DeviceRequestPromptDevice; -import com.ruiyun.jvppeteer.events.DeviceRequestPromptedEvent; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.cdp.entities.DeviceRequestPromptDevice; +import com.ruiyun.jvppeteer.cdp.events.DeviceRequestPromptedEvent; import com.ruiyun.jvppeteer.util.ValidateUtil; - import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -31,8 +31,8 @@ public DeviceRequestPrompt(CDPSession client, TimeoutSettings timeoutSettings, D this.client = client; this.timeoutSettings = timeoutSettings; this.id = firstEvent.getId(); - this.client.on(CDPSession.CDPSessionEvent.DeviceAccess_deviceRequestPrompted, this.updateDevicesHandle); - this.client.on(CDPSession.CDPSessionEvent.Target_detachedFromTarget, (ignore) -> this.client = null); + this.client.on(ConnectionEvents.DeviceAccess_deviceRequestPrompted, this.updateDevicesHandle); + this.client.on(ConnectionEvents.Target_detachedFromTarget, (ignore) -> this.client = null); } private void updateDevicesHandle(DeviceRequestPromptedEvent event) { @@ -89,7 +89,7 @@ public void select(DeviceRequestPromptDevice device) { Objects.requireNonNull(this.client, "Cannot select device through detached session!"); ValidateUtil.assertArg(this.devices.contains(device), "Cannot select unknown device!"); ValidateUtil.assertArg(!this.handled, "Cannot select DeviceRequestPrompt which is already handled!"); - this.client.off(CDPSession.CDPSessionEvent.DeviceAccess_deviceRequestPrompted, this.updateDevicesHandle); + this.client.off(ConnectionEvents.DeviceAccess_deviceRequestPrompted, this.updateDevicesHandle); this.handled = true; this.client.send("DeviceAccess.selectPrompt", new HashMap() {{ put("id", DeviceRequestPrompt.this.id); @@ -106,7 +106,7 @@ public void select(DeviceRequestPromptDevice device) { public void cancel() { Objects.requireNonNull(this.client, "Cannot cancel prompt through detached session!"); ValidateUtil.assertArg(!this.handled, "Cannot cancel DeviceRequestPrompt which is already handled!"); - this.client.off(CDPSession.CDPSessionEvent.DeviceAccess_deviceRequestPrompted, this.updateDevicesHandle); + this.client.off(ConnectionEvents.DeviceAccess_deviceRequestPrompted, this.updateDevicesHandle); this.handled = true; this.client.send("DeviceAccess.cancelPrompt", new HashMap() {{ put("id", DeviceRequestPrompt.this.id); diff --git a/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPromptManager.java b/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPromptManager.java index a7fe71f8..c24a54eb 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPromptManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/DeviceRequestPromptManager.java @@ -1,14 +1,14 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.events.DeviceRequestPromptedEvent; -import com.ruiyun.jvppeteer.transport.CDPSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.cdp.events.DeviceRequestPromptedEvent; import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DeviceRequestPromptManager { private static final Logger LOGGER = LoggerFactory.getLogger(DeviceRequestPromptManager.class); @@ -19,8 +19,8 @@ public class DeviceRequestPromptManager { public DeviceRequestPromptManager(CDPSession client, TimeoutSettings timeoutSettings) { this.client = client; this.timeoutSettings = timeoutSettings; - this.client.on(CDPSession.CDPSessionEvent.DeviceAccess_deviceRequestPrompted, (event) -> this.onDeviceRequestPrompted((DeviceRequestPromptedEvent) event)); - this.client.on(CDPSession.CDPSessionEvent.Target_detachedFromTarget, (ignore) -> this.client = null); + this.client.on(ConnectionEvents.DeviceAccess_deviceRequestPrompted, (event) -> this.onDeviceRequestPrompted((DeviceRequestPromptedEvent) event)); + this.client.on(ConnectionEvents.Target_detachedFromTarget, (ignore) -> this.client = null); } private void onDeviceRequestPrompted(DeviceRequestPromptedEvent event) { diff --git a/src/main/java/com/ruiyun/jvppeteer/common/DisposableStack.java b/src/main/java/com/ruiyun/jvppeteer/common/DisposableStack.java new file mode 100644 index 00000000..44b34ded --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/DisposableStack.java @@ -0,0 +1,40 @@ +package com.ruiyun.jvppeteer.common; + +import com.ruiyun.jvppeteer.api.core.EventEmitter; +import java.util.function.Consumer; + +public class DisposableStack { + private EventEmitter emitter; + private EventType type; + private Consumer consumer; + + public DisposableStack(EventEmitter emitter, EventType type, Consumer consumer) { + this.emitter = emitter; + this.type = type; + this.consumer = consumer; + } + + public EventEmitter getEmitter() { + return emitter; + } + + public void setEmitter(EventEmitter emitter) { + this.emitter = emitter; + } + + public EventType getType() { + return type; + } + + public void setType(EventType type) { + this.type = type; + } + + public Consumer getConsumer() { + return consumer; + } + + public void setConsumer(Consumer consumer) { + this.consumer = consumer; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/FirefoxChannel.java b/src/main/java/com/ruiyun/jvppeteer/common/FirefoxChannel.java index 21990ff6..54cb7385 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/FirefoxChannel.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/FirefoxChannel.java @@ -5,5 +5,5 @@ public enum FirefoxChannel { ESR, DEVEDITION, BETA, - NIGHTLY; + NIGHTLY } diff --git a/src/main/java/com/ruiyun/jvppeteer/common/FrameProvider.java b/src/main/java/com/ruiyun/jvppeteer/common/FrameProvider.java index bdead856..88ccc331 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/FrameProvider.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/FrameProvider.java @@ -1,6 +1,6 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.core.Frame; +import com.ruiyun.jvppeteer.api.core.Frame; @FunctionalInterface public interface FrameProvider { diff --git a/src/main/java/com/ruiyun/jvppeteer/common/PQueryHandler.java b/src/main/java/com/ruiyun/jvppeteer/common/PQueryHandler.java new file mode 100644 index 00000000..a6cd00c9 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/PQueryHandler.java @@ -0,0 +1,25 @@ +package com.ruiyun.jvppeteer.common; + +public class PQueryHandler extends QueryHandler{ + @Override + public String querySelector() { + return "(\n" + + " element,\n" + + " selector,\n" + + " {pQuerySelector},\n" + + " ) => {\n" + + " return pQuerySelector(element, selector);\n" + + " }"; + } + + @Override + public String querySelectorAll() { + return "(\n" + + " element,\n" + + " selector,\n" + + " {pQuerySelectorAll},\n" + + " ) => {\n" + + " return pQuerySelectorAll(element, selector);\n" + + " }"; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/PierceQueryHandler.java b/src/main/java/com/ruiyun/jvppeteer/common/PierceQueryHandler.java new file mode 100644 index 00000000..6b85ae0e --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/PierceQueryHandler.java @@ -0,0 +1,25 @@ +package com.ruiyun.jvppeteer.common; + +public class PierceQueryHandler extends QueryHandler{ + @Override + public String querySelector() { + return "(\n" + + " element,\n" + + " selector,\n" + + " { pierceQuerySelector }\n" + + ") => {\n" + + " return pierceQuerySelector(element, selector);\n" + + "}"; + } + + @Override + public String querySelectorAll() { + return "(\n" + + " element,\n" + + " selector,\n" + + " { pierceQuerySelectorAll }\n" + + ") => {\n" + + " return pierceQuerySelectorAll(element, selector);\n" + + "}"; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/PollingOptions.java b/src/main/java/com/ruiyun/jvppeteer/common/PollingOptions.java new file mode 100644 index 00000000..076e5fbb --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/PollingOptions.java @@ -0,0 +1,11 @@ +package com.ruiyun.jvppeteer.common; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum PollingOptions { + @JsonProperty("raf") + Raf, + @JsonProperty("mutation") + Mutation + +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/PredefinedNetworkConditions.java b/src/main/java/com/ruiyun/jvppeteer/common/PredefinedNetworkConditions.java index 4b52c9ac..1244bc8b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/PredefinedNetworkConditions.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/PredefinedNetworkConditions.java @@ -1,6 +1,6 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.entities.NetworkConditions; +import com.ruiyun.jvppeteer.cdp.entities.NetworkConditions; public enum PredefinedNetworkConditions { // ~500Kbps down ~500Kbps up 400ms RTT diff --git a/src/main/java/com/ruiyun/jvppeteer/common/PrimitiveValue.java b/src/main/java/com/ruiyun/jvppeteer/common/PrimitiveValue.java new file mode 100644 index 00000000..d0913e92 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/PrimitiveValue.java @@ -0,0 +1,15 @@ +package com.ruiyun.jvppeteer.common; + +public enum PrimitiveValue { + Undefined("undefined"), + Null("null"); + private final String value; + + PrimitiveValue(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/Product.java b/src/main/java/com/ruiyun/jvppeteer/common/Product.java index 9d7f37a0..167211f2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/Product.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/Product.java @@ -1,11 +1,11 @@ package com.ruiyun.jvppeteer.common; public enum Product { - CHROMIUM("chromium"), - CHROME("chrome"), - CHROMEDRIVER("chromedriver"), - FIREFOX("firefox"), - CHROMEHEADLESSSHELL("chrome-headless-shell"); + Chromium("chromium"), + Chrome("chrome"), + Chromedriver("chromedriver"), + Firefox("firefox"), + Chrome_headless_shell("chrome-headless-shell"); private final String product; Product(String product) { diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/PuppeteerLifeCycle.java b/src/main/java/com/ruiyun/jvppeteer/common/PuppeteerLifeCycle.java similarity index 81% rename from src/main/java/com/ruiyun/jvppeteer/entities/PuppeteerLifeCycle.java rename to src/main/java/com/ruiyun/jvppeteer/common/PuppeteerLifeCycle.java index 69e768c3..fa87ca0d 100644 --- a/src/main/java/com/ruiyun/jvppeteer/entities/PuppeteerLifeCycle.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/PuppeteerLifeCycle.java @@ -1,23 +1,23 @@ -package com.ruiyun.jvppeteer.entities; +package com.ruiyun.jvppeteer.common; public enum PuppeteerLifeCycle { /** * 当前页面不再有网络连接时触发(至少500毫秒后) */ - NETWORKIDLE("networkidle"), + networkIdle("networkIdle"), /** * 网页所有资源载入后触发,浏览器上加载转环停止旋转 */ - LOAD("load"), + load("load"), /** * 当 HTML 文档已完全解析,并且所有延迟脚本(script defer src=“...” 和 script type=“module”)都已下载并执行时,将触发 DOMContentLoaded 事件。

* 它不会等待其他内容(如图像、子帧和异步脚本)完成加载。 */ - DOMCONTENT_LOADED("domcontentloaded"), + domcontentloaded("DOMContentLoaded"), /** * 当前网络连接数少于2后触发 */ - NETWORKIDLE_2("networkidle2"); + networkIdle2("networkAlmostIdle"); private final String value; PuppeteerLifeCycle(String value) { diff --git a/src/main/java/com/ruiyun/jvppeteer/common/QueryHandler.java b/src/main/java/com/ruiyun/jvppeteer/common/QueryHandler.java index 4e8dcf78..a3898c22 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/QueryHandler.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/QueryHandler.java @@ -1,9 +1,136 @@ package com.ruiyun.jvppeteer.common; -public interface QueryHandler { +import com.fasterxml.jackson.core.JsonProcessingException; +import com.ruiyun.jvppeteer.api.core.ElementHandle; +import com.ruiyun.jvppeteer.api.core.Frame; +import com.ruiyun.jvppeteer.api.core.JSHandle; +import com.ruiyun.jvppeteer.cdp.entities.EvaluateType; +import com.ruiyun.jvppeteer.cdp.entities.WaitForSelectorOptions; +import com.ruiyun.jvppeteer.exception.EvaluateException; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.util.StringUtil; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; - String queryOne(); - String queryAll(); +import static com.ruiyun.jvppeteer.api.core.Frame.transposeIterableHandle; +import static com.ruiyun.jvppeteer.util.Helper.throwError; + +public abstract class QueryHandler { + + + public ElementHandle queryOne(ElementHandle element, String selector) throws JsonProcessingException { + JSHandle handle = element.evaluateHandle(this.querySelector(), Arrays.asList(selector, new LazyArg())); + if (Objects.isNull(handle)) { + return null; + } + return handle.asElement(); + } + + public List queryAll(ElementHandle element, String selector) throws JsonProcessingException { + JSHandle handle = element.evaluateHandle(this.querySelectorAll(), Arrays.asList(selector, new LazyArg())); + if (Objects.isNull(handle)) { + return null; + } + List handles = transposeIterableHandle(handle); + return handles.stream().map(JSHandle::asElement).filter(Objects::nonNull).collect(java.util.stream.Collectors.toList()); + } + + public abstract String querySelector(); + + public abstract String querySelectorAll(); + + + public ElementHandle waitFor(ElementHandle handle, String selector, WaitForSelectorOptions options) throws JsonProcessingException { + Frame frame = handle.frame(); + ElementHandle element = frame.isolatedRealm().adoptHandle(handle); + String polling = options.getVisible() || options.getHidden() ? "raf" : options.getPolling(); + WaitForSelectorOptions waitForSelectorOptions = new WaitForSelectorOptions(); + waitForSelectorOptions.setPolling(polling); + waitForSelectorOptions.setRoot(element); + waitForSelectorOptions.setTimeout(options.getTimeout()); + try { + JSHandle result = frame.isolatedRealm().waitForFunction("async (PuppeteerUtil, query, selector, root, visible) => {\n" + + " const querySelector = PuppeteerUtil.createFunction(\n" + + " query,\n" + + " );\n" + + " const node = await querySelector(\n" + + " root ?? document,\n" + + " selector,\n" + + " PuppeteerUtil,\n" + + " );\n" + + " return PuppeteerUtil.checkVisibility(node, visible);\n" + + "}", waitForSelectorOptions, EvaluateType.FUNCTION, new LazyArg(), getQuerySelector(this), selector, element, options.getVisible() ? Boolean.TRUE : options.getHidden() ? Boolean.FALSE : null); + if (Objects.isNull(result) || Objects.isNull(result.asElement())) { + return null; + } + return frame.mainRealm().transferHandle(result.asElement()); + } catch (Exception e) { + if (!(e instanceof EvaluateException)) { + throwError(e); + } + EvaluateException error = (EvaluateException) e; + if (Objects.equals("AbortError", error.getName())) { + throw error; + } + throw new EvaluateException("Waiting for selector " + selector + " failed: ${error.message}",e); + } + } + + public ElementHandle waitFor(Frame frame, String selector, WaitForSelectorOptions options) { + ElementHandle element = null; + String polling = options.getVisible() || options.getHidden() ? "raf" : options.getPolling(); + WaitForSelectorOptions waitForSelectorOptions = new WaitForSelectorOptions(); + waitForSelectorOptions.setPolling(polling); + waitForSelectorOptions.setRoot(element); + waitForSelectorOptions.setTimeout(options.getTimeout()); + try { + JSHandle handle = frame.isolatedRealm().waitForFunction("async (PuppeteerUtil, query, selector, root, visible) => {\n" + + " const querySelector = PuppeteerUtil.createFunction(\n" + + " query,\n" + + " );\n" + + " const node = await querySelector(\n" + + " root ?? document,\n" + + " selector,\n" + + " PuppeteerUtil,\n" + + " );\n" + + " return PuppeteerUtil.checkVisibility(node, visible);\n" + + "}", waitForSelectorOptions, EvaluateType.FUNCTION, new LazyArg(), getQuerySelector(this), selector, null, options.getVisible() ? Boolean.TRUE : options.getHidden() ? Boolean.FALSE : null); + if (Objects.isNull(handle) || Objects.isNull(handle.asElement())) { + return null; + } + return frame.mainRealm().transferHandle(handle.asElement()); + } catch (Exception e) { + if (!(e instanceof EvaluateException)) { + throwError(e); + } + EvaluateException error = (EvaluateException) e; + if (Objects.equals("AbortError", error.getName())) { + throw error; + } + throw new EvaluateException("Waiting for selector " + selector + " failed: ${error.message}",e); + } + } + + public static String getQuerySelector(QueryHandler handler) { + if (StringUtil.isNotEmpty(handler.querySelector())) { + return handler.querySelector(); + } else if (StringUtil.isNotEmpty(handler.querySelectorAll())) { + return handler.querySelectorAll(); + } else { + throw new JvppeteerException("Cannot create default `querySelector`."); + } + } + + public static String getQuerySelectorAll(QueryHandler handler) { + if (StringUtil.isNotEmpty(handler.querySelectorAll())) { + return handler.querySelectorAll(); + } else if (StringUtil.isNotEmpty(handler.querySelector())) { + return handler.querySelector(); + } else { + throw new JvppeteerException("Cannot create default `querySelector`."); + } + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/common/QuerySelector.java b/src/main/java/com/ruiyun/jvppeteer/common/QuerySelector.java index 3502d590..5ffb1bff 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/QuerySelector.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/QuerySelector.java @@ -6,14 +6,14 @@ public class QuerySelector { private QueryHandler queryHandler; - public QuerySelector() { - super(); - } + private String polling; - public QuerySelector(String updatedSelector, QueryHandler queryHandler) { + + public QuerySelector(String updatedSelector, QueryHandler queryHandler, String polling) { super(); this.updatedSelector = updatedSelector; this.queryHandler = queryHandler; + this.polling = polling; } public String getUpdatedSelector() { @@ -31,4 +31,12 @@ public QueryHandler getQueryHandler() { public void setQueryHandler(QueryHandler queryHandler) { this.queryHandler = queryHandler; } + + public String getPolling() { + return polling; + } + + public void setPolling(String polling) { + this.polling = polling; + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/common/ScreenRecorder.java b/src/main/java/com/ruiyun/jvppeteer/common/ScreenRecorder.java index 5776a5c3..404f3f83 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/ScreenRecorder.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/ScreenRecorder.java @@ -1,19 +1,17 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.core.Page; -import com.ruiyun.jvppeteer.entities.ScreenCastFormat; -import com.ruiyun.jvppeteer.entities.ScreenRecorderOptions; -import com.ruiyun.jvppeteer.entities.Viewport; -import com.ruiyun.jvppeteer.events.ScreencastFrameEvent; -import com.ruiyun.jvppeteer.transport.CDPSession; +import com.ruiyun.jvppeteer.api.core.Page; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.cdp.entities.ScreenCastFormat; +import com.ruiyun.jvppeteer.cdp.entities.ScreenRecorderOptions; +import com.ruiyun.jvppeteer.cdp.entities.Viewport; +import com.ruiyun.jvppeteer.cdp.events.ScreencastFrameEvent; +import com.ruiyun.jvppeteer.exception.JvppeteerException; import com.ruiyun.jvppeteer.util.Base64Util; import com.ruiyun.jvppeteer.util.FileUtil; import com.ruiyun.jvppeteer.util.Helper; import com.ruiyun.jvppeteer.util.StreamUtil; import com.ruiyun.jvppeteer.util.StringUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; @@ -28,11 +26,15 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import static com.ruiyun.jvppeteer.entities.ScreenCastFormat.GIF; -import static com.ruiyun.jvppeteer.entities.ScreenCastFormat.WEBM; + +import static com.ruiyun.jvppeteer.cdp.entities.ScreenCastFormat.GIF; +import static com.ruiyun.jvppeteer.cdp.entities.ScreenCastFormat.WEBM; public class ScreenRecorder { private static final Logger LOGGER = LoggerFactory.getLogger(ScreenRecorder.class); @@ -51,6 +53,7 @@ public class ScreenRecorder { AtomicLong imgIndex = new AtomicLong(0); private volatile boolean stoped = false; + public ScreenRecorder(Page page, double width, double height, ScreenRecorderOptions options, Viewport defaultViewport, Viewport tempViewport) { this.page = page; this.options = options; @@ -60,10 +63,16 @@ public ScreenRecorder(Page page, double width, double height, ScreenRecorderOpti this.tempViewport = tempViewport; createTempCacheDir(); - Consumer closeListener = (o) -> ScreenRecorder.this.stop(); + Consumer closeListener = (o) -> { + try { + ScreenRecorder.this.stop(); + } catch (ExecutionException | InterruptedException e) { + throw new JvppeteerException(e); + } + }; this.stoped = false; - page.mainFrame().client().once(CDPSession.CDPSessionEvent.disconnected, closeListener); - page.mainFrame().client().on(CDPSession.CDPSessionEvent.Page_screencastFrame, (Consumer) this::writeFrame); + page.mainFrame().client().once(ConnectionEvents.disconnected, closeListener); + page.mainFrame().client().on(ConnectionEvents.Page_screencastFrame, (Consumer) this::writeFrame); } private void createTempCacheDir() { @@ -132,7 +141,7 @@ private List getFormatArgs(ScreenCastFormat format) { /** * 停止屏幕录制 */ - public void stop() { + public void stop() throws ExecutionException, InterruptedException { //先暂停屏幕录制 try { this.previousTimestamp = currentTimestamp(); diff --git a/src/main/java/com/ruiyun/jvppeteer/common/TaskManager.java b/src/main/java/com/ruiyun/jvppeteer/common/TaskManager.java index 3abd7d89..ac39bd5e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/TaskManager.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/TaskManager.java @@ -1,14 +1,12 @@ package com.ruiyun.jvppeteer.common; -import com.ruiyun.jvppeteer.core.WaitTask; +import com.ruiyun.jvppeteer.cdp.core.WaitTask; import com.ruiyun.jvppeteer.exception.JvppeteerException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.HashSet; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -//done public class TaskManager { private static final Logger LOGGER = LoggerFactory.getLogger(TaskManager.class); private final Set tasks = new HashSet<>(); @@ -25,10 +23,8 @@ public void terminateAll(JvppeteerException error) { tasks.forEach(task -> { try { task.terminate(error); - } catch ( - Exception e - ) { - LOGGER.error("", e); + } catch (Exception e) { + LOGGER.error("jvppeteer error", e); } } ); @@ -36,13 +32,22 @@ public void terminateAll(JvppeteerException error) { } public void rerunAll() { - this.tasks.forEach(task -> { - try { - task.rerun(); - }catch (Exception e){ - LOGGER.error("",e); - } - }); - // 等待所有任务完成 +// CompletionService completionService = new ExecutorCompletionService<>(WaitTask.waitTaskService); +// List> futures = new java.util.ArrayList<>(); +// this.tasks.forEach(task -> { +// Future future = completionService.submit(() -> { +// task.rerun(); +// return null; +// }); +// futures.add(future); +// +// }); +// for (Future future : futures) { +// try { +// future.get(); +// } catch (Exception e) { +// LOGGER.error("jvppeteer error", e); +// } +// } } } diff --git a/src/main/java/com/ruiyun/jvppeteer/common/TextQueryHandler.java b/src/main/java/com/ruiyun/jvppeteer/common/TextQueryHandler.java new file mode 100644 index 00000000..6d924157 --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/TextQueryHandler.java @@ -0,0 +1,19 @@ +package com.ruiyun.jvppeteer.common; + +public class TextQueryHandler extends QueryHandler{ + @Override + public String querySelector() { + return ""; + } + + @Override + public String querySelectorAll() { + return "(\n" + + " element,\n" + + " selector,\n" + + " {textQuerySelectorAll},\n" + + ") => {\n" + + " return textQuerySelectorAll(element, selector);\n" + + "}"; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/common/WebPermission.java b/src/main/java/com/ruiyun/jvppeteer/common/WebPermission.java index f49c9f5a..e371915f 100644 --- a/src/main/java/com/ruiyun/jvppeteer/common/WebPermission.java +++ b/src/main/java/com/ruiyun/jvppeteer/common/WebPermission.java @@ -4,8 +4,6 @@ public enum WebPermission { GEOLOCATION("geolocation"), MIDI("midi"), NOTIFICATIONS("notifications"), - // 不再使用的权限注释掉 - // PUSH("push"), CAMERA("camera"), MICROPHONE("microphone"), BACKGROUND_SYNC("background-sync"), diff --git a/src/main/java/com/ruiyun/jvppeteer/common/XPathQueryHandler.java b/src/main/java/com/ruiyun/jvppeteer/common/XPathQueryHandler.java new file mode 100644 index 00000000..baa0d06d --- /dev/null +++ b/src/main/java/com/ruiyun/jvppeteer/common/XPathQueryHandler.java @@ -0,0 +1,28 @@ +package com.ruiyun.jvppeteer.common; + +public class XPathQueryHandler extends QueryHandler{ + @Override + public String querySelector() { + return "(\n" + + " element,\n" + + " selector,\n" + + " {xpathQuerySelectorAll}\n" + + ") => {\n" + + " for (const result of xpathQuerySelectorAll(element, selector, 1)) {\n" + + " return result;\n" + + " }\n" + + " return null;\n" + + "}"; + } + + @Override + public String querySelectorAll() { + return "(\n" + + " element,\n" + + " selector,\n" + + " {xpathQuerySelectorAll},\n" + + ") => {\n" + + " return xpathQuerySelectorAll(element, selector);\n" + + "}"; + } +} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/BrowserContext.java b/src/main/java/com/ruiyun/jvppeteer/core/BrowserContext.java deleted file mode 100644 index f39fa8a8..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/BrowserContext.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.common.WebPermission; -import com.ruiyun.jvppeteer.entities.TargetType; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.transport.Connection; -import com.ruiyun.jvppeteer.util.StringUtil; -import com.ruiyun.jvppeteer.util.ValidateUtil; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import static com.ruiyun.jvppeteer.common.Constant.WEB_PERMISSION_TO_PROTOCOL_PERMISSION; -import static com.ruiyun.jvppeteer.util.Helper.filter; -import static com.ruiyun.jvppeteer.util.Helper.waitForCondition; - -/** - * BrowserContext 代表 browser 中的各个用户上下文。 - *

- * 启动 browser 时,它至少有一个默认 浏览器上下文。其他可以使用 Browser.createBrowserContext() 创建。每个上下文都有独立的存储(cookies/localStorage/等) - *

- * BrowserContext emits 各种事件记录在 BrowserContextEvent 枚举中。 - *

- * 如果 page 打开另一个 page,例如 使用 window.open,弹出窗口将属于父 页面的浏览器上下文。 - *

- * 在 Chrome 中,所有非默认上下文都是隐身的,如果在启动浏览器时提供 --incognito 参数,默认浏览器上下文 可能会隐身。 - *

- * 此类的构造函数被标记为内部构造函数。第三方代码不应直接调用构造函数或创建扩展 BrowserContext 类的子类。 - */ -public class BrowserContext extends EventEmitter { - /** - * 浏览器对应的websocket client包装类,用于发送和接受消息 - */ - private Connection connection; - /** - * 浏览器上下文对应的浏览器,一个上下文只有一个浏览器,但是一个浏览器可能有多个上下文 - */ - private Browser browser; - /** - * 浏览器上下文id - */ - private String id; - - public BrowserContext() { - super(); - } - - public BrowserContext(Connection connection, Browser browser, String contextId) { - super(); - this.connection = connection; - this.browser = browser; - this.id = contextId; - } - - /** - * 获取此 浏览器上下文 内所有活动的 targets。 - *

- * 此方法通过过滤当前浏览器的所有 targets,只返回属于当前浏览器上下文(browserContext)的 targets。 - * 这对于当您想要对特定浏览器上下文中的所有页面或框架进行操作时非常有用。 - * - * @return 返回一个 List,包含当前浏览器上下文中所有活动 targets 的列表。 - */ - public List targets() { - return this.browser.targets().stream().filter(target -> target.browserContext() == this).collect(Collectors.toList()); - } - - /** - * 获取此 浏览器上下文 内所有打开的 pages 的列表。 - *

- * 不可见的 pages,例如 "background_page",这里不会列出。你可以使用 Target.page() 找到它们。 - * - * @return 所有打开的 pages - */ - public List pages() { - return this.targets().stream().filter(target -> TargetType.PAGE.equals(target.type()) || (TargetType.OTHER.equals(target.type()) && this.browser.getIsPageTargetCallback() != null ? this.browser.getIsPageTargetCallback().apply(target) : true)).map(Target::page).filter(Objects::nonNull).collect(Collectors.toList()); - } - - /** - * 重写指定页面的权限设置 - * - * @param origin 权限来源,通常是一个URL - * @param webPermissions 权限列表,表示要重写的权限 - */ - public void overridePermissions(String origin, WebPermission... webPermissions) { - List protocolPermissions = new ArrayList<>(); - if (webPermissions != null) { - for (WebPermission permission : webPermissions) { - String protocolPermission = WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission); - ValidateUtil.assertArg(protocolPermission != null, "Unknown permission: " + permission); - protocolPermissions.add(protocolPermission); - } - } - Map params = ParamsFactory.create(); - params.put("origin", origin); - if (StringUtil.isNotEmpty(this.id)) { - params.put("browserContextId", this.id); - } - params.put("permissions", protocolPermissions); - this.connection.send("Browser.grantPermissions", params); - } - - /** - * 在给定的 origin 内授予此 浏览器上下文 给定的 permissions。 - */ - public void clearPermissionOverrides() { - Map params = ParamsFactory.create(); - params.put("browserContextId", this.id); - this.connection.send("Browser.resetPermissions", params); - } - - /** - * 在此 浏览器上下文 中创建一个新的 page。 - * - * @return 新创建的 Page 实例 - */ - public Page newPage() { - synchronized (this) { - return this.browser.createPageInContext(this.id); - } - } - - /** - * 关闭此 浏览器上下文 和所有关联的 pages。 - */ - public void close() { - ValidateUtil.assertArg(StringUtil.isNotEmpty(this.id), "Default BrowserContext cannot be closed!"); - this.browser.disposeContext(this.id); - } - - public boolean closed() { - return !this.browser.browserContexts().contains(this); - } - - /** - * 等待直到出现与给定 predicate 匹配的 target 并返回它。 - * - * @param predicate 一个断言,用于测试每个target是否为匹配项 - * @return 返回与predicate匹配的target - */ - public Target waitForTarget(Predicate predicate) { - return this.waitForTarget(predicate, Constant.DEFAULT_TIMEOUT); - } - - /** - * 等待直到出现与给定 predicate 匹配的 target 并返回它。 - * - * @param predicate 一个断言,用于测试每个target是否为匹配项 - * @param timeout 等待超时时间 - * @return 返回与predicate匹配的target - */ - public Target waitForTarget(Predicate predicate, int timeout) { - Supplier conditionChecker = () -> filter(this.targets(), predicate); - return waitForCondition(conditionChecker, timeout, "waiting for target failed: timeout " + timeout + "ms exceeded"); - } - - /** - * 获取与此 浏览器上下文 关联的 browser。 - * - * @return 返回与此浏览器上下文关联的 Browser 对象。 - */ - public Browser browser() { - return browser; - } - - /** - * 获取当前对象的ID - * - * @return 当前对象的ID - */ - public String getId() { - return this.id; - } - - public enum BrowserContextEvent { - TargetChanged("targetchanged"), - TargetCreated("targetcreated"), - TargetDestroyed("targetdestroyed"); - private final String eventName; - - BrowserContextEvent(String eventName) { - this.eventName = eventName; - } - - public String getEventName() { - return eventName; - } - } - -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/DevToolsTarget.java b/src/main/java/com/ruiyun/jvppeteer/core/DevToolsTarget.java deleted file mode 100644 index 4c6d3365..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/DevToolsTarget.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.entities.Viewport; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.transport.SessionFactory; - -public class DevToolsTarget extends PageTarget { - - public DevToolsTarget(TargetInfo targetInfo, CDPSession session, BrowserContext browserContext, TargetManager targetManager, SessionFactory sessionFactory, Viewport defaultViewport) { - super(targetInfo, session, browserContext, targetManager, sessionFactory, defaultViewport); - } -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Dialog.java b/src/main/java/com/ruiyun/jvppeteer/core/Dialog.java deleted file mode 100644 index 83dba267..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/Dialog.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.DialogType; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.util.StringUtil; -import com.ruiyun.jvppeteer.util.ValidateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.Future; - -public class Dialog { - - private static final Logger LOGGER = LoggerFactory.getLogger(Dialog.class); - private CDPSession client; - private String type; - private String message; - private String defaultValue = ""; - private boolean handled; - public Dialog() { - super(); - } - - public Dialog(CDPSession client, DialogType type, String message, String defaultValue) { - super(); - this.client = client; - this.type = type.getType(); - this.message = message; - if (StringUtil.isNotEmpty(defaultValue)) - this.defaultValue = defaultValue; - } - - /** - * @return {string} - */ - public String type() { - return this.type; - } - - /** - * @return {string} - */ - public String message() { - return this.message; - } - - /** - * @return {string} - */ - public String defaultValue() { - return this.defaultValue; - } - - private Future handle(boolean accept, String text) { - ValidateUtil.assertArg(!this.handled, "Cannot accept dialog which is already handled!"); - return ForkJoinPool.commonPool().submit(() -> { - try { - this.handled = true; - Map params = ParamsFactory.create(); - params.put("accept", accept); - params.put("promptText", text); - this.client.send("Page.handleJavaScriptDialog", params); - } catch (Exception e) { - LOGGER.error("Dialog accept error ", e); - return false; - } - return true; - }); - } - - /** - * 接受对话框 - * - * @param promptText 在提示中输入的文本。如果对话框type不提示,则不会引起任何影响 - * @return 对话框关闭后返回 - */ - public Future accept(String promptText) { - return this.handle(true, promptText); - } - - /** - * 不接受对话框的内容 - * - * @return 对话框关闭后返回 - */ - public Future dismiss() { - return this.handle(false, ""); - } - -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/ExecutionContext.java b/src/main/java/com/ruiyun/jvppeteer/core/ExecutionContext.java deleted file mode 100644 index f66f7d30..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/ExecutionContext.java +++ /dev/null @@ -1,459 +0,0 @@ -package com.ruiyun.jvppeteer.core; - - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.common.LazyArg; -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.Binding; -import com.ruiyun.jvppeteer.entities.BindingPayload; -import com.ruiyun.jvppeteer.entities.EvaluateResponse; -import com.ruiyun.jvppeteer.entities.EvaluateType; -import com.ruiyun.jvppeteer.entities.ExceptionDetails; -import com.ruiyun.jvppeteer.entities.ExecutionContextDescription; -import com.ruiyun.jvppeteer.entities.RemoteObject; -import com.ruiyun.jvppeteer.events.BindingCalledEvent; -import com.ruiyun.jvppeteer.events.ConsoleAPICalledEvent; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.events.ExecutionContextDestroyedEvent; -import com.ruiyun.jvppeteer.exception.EvaluateException; -import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.util.Helper; -import com.ruiyun.jvppeteer.util.StringUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; - -import static com.ruiyun.jvppeteer.common.Constant.CDP_BINDING_PREFIX; -import static com.ruiyun.jvppeteer.common.Constant.INTERNAL_URL; -import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; -import static com.ruiyun.jvppeteer.common.Constant.SOURCE_URL_REGEX; -import static com.ruiyun.jvppeteer.util.Helper.throwError; - -public class ExecutionContext extends EventEmitter { - private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionContext.class); - private final CDPSession client; - private String name; - private final int id; - private final IsolatedWorld world; - private final Map> listener = new HashMap<>(); - private final Map bindings = new HashMap<>(); - private static final AtomicLong pptrFunctionId = new AtomicLong(0); - private volatile JSHandle puppeteerUtil; - String source = "\"use strict\";var g=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var l=(t,e)=>{for(var r in e)g(t,r,{get:e[r],enumerable:!0})},J=(t,e,r,o)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let n of B(e))!Y.call(t,n)&&n!==r&&g(t,n,{get:()=>e[n],enumerable:!(o=X(e,n))||o.enumerable});return t};var z=t=>J(g({},\"__esModule\",{value:!0}),t);var pe={};l(pe,{default:()=>he});module.exports=z(pe);var N=class extends Error{constructor(e,r){super(e,r),this.name=this.constructor.name}get[Symbol.toStringTag](){return this.constructor.name}},p=class extends N{};var c=class t{static create(e){return new t(e)}static async race(e){let r=new Set;try{let o=e.map(n=>n instanceof t?(n.#n&&r.add(n),n.valueOrThrow()):n);return await Promise.race(o)}finally{for(let o of r)o.reject(new Error(\"Timeout cleared\"))}}#e=!1;#r=!1;#o;#t;#a=new Promise(e=>{this.#t=e});#n;#i;constructor(e){e&&e.timeout>0&&(this.#i=new p(e.message),this.#n=setTimeout(()=>{this.reject(this.#i)},e.timeout))}#l(e){clearTimeout(this.#n),this.#o=e,this.#t()}resolve(e){this.#r||this.#e||(this.#e=!0,this.#l(e))}reject(e){this.#r||this.#e||(this.#r=!0,this.#l(e))}resolved(){return this.#e}finished(){return this.#e||this.#r}value(){return this.#o}#s;valueOrThrow(){return this.#s||(this.#s=(async()=>{if(await this.#a,this.#r)throw this.#o;return this.#o})()),this.#s}};var L=new Map,F=t=>{let e=L.get(t);return e||(e=new Function(`return ${t}`)(),L.set(t,e),e)};var x={};l(x,{ariaQuerySelector:()=>G,ariaQuerySelectorAll:()=>b});var G=(t,e)=>globalThis.__ariaQuerySelector(t,e),b=async function*(t,e){yield*await globalThis.__ariaQuerySelectorAll(t,e)};var E={};l(E,{cssQuerySelector:()=>K,cssQuerySelectorAll:()=>Z});var K=(t,e)=>t.querySelector(e),Z=function(t,e){return t.querySelectorAll(e)};var A={};l(A,{customQuerySelectors:()=>P});var v=class{#e=new Map;register(e,r){if(!r.queryOne&&r.queryAll){let o=r.queryAll;r.queryOne=(n,i)=>{for(let s of o(n,i))return s;return null}}else if(r.queryOne&&!r.queryAll){let o=r.queryOne;r.queryAll=(n,i)=>{let s=o(n,i);return s?[s]:[]}}else if(!r.queryOne||!r.queryAll)throw new Error(\"At least one query method must be defined.\");this.#e.set(e,{querySelector:r.queryOne,querySelectorAll:r.queryAll})}unregister(e){this.#e.delete(e)}get(e){return this.#e.get(e)}clear(){this.#e.clear()}},P=new v;var R={};l(R,{pierceQuerySelector:()=>ee,pierceQuerySelectorAll:()=>te});var ee=(t,e)=>{let r=null,o=n=>{let i=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT);do{let s=i.currentNode;s.shadowRoot&&o(s.shadowRoot),!(s instanceof ShadowRoot)&&s!==n&&!r&&s.matches(e)&&(r=s)}while(!r&&i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r},te=(t,e)=>{let r=[],o=n=>{let i=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT);do{let s=i.currentNode;s.shadowRoot&&o(s.shadowRoot),!(s instanceof ShadowRoot)&&s!==n&&s.matches(e)&&r.push(s)}while(i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r};var u=(t,e)=>{if(!t)throw new Error(e)};var y=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=new MutationObserver(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())}),this.#o.observe(this.#r,{childList:!0,subtree:!0,attributes:!0})}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(this.#o.disconnect(),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}},w=class{#e;#r;constructor(e){this.#e=e}async start(){let e=this.#r=c.create(),r=await this.#e();if(r){e.resolve(r);return}let o=async()=>{if(e.finished())return;let n=await this.#e();if(!n){window.requestAnimationFrame(o);return}e.resolve(n),await this.stop()};window.requestAnimationFrame(o)}async stop(){u(this.#r,\"Polling never started.\"),this.#r.finished()||this.#r.reject(new Error(\"Polling stopped\"))}result(){return u(this.#r,\"Polling never started.\"),this.#r.valueOrThrow()}},T=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=setInterval(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())},this.#r)}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(clearInterval(this.#o),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}};var _={};l(_,{PCombinator:()=>H,pQuerySelector:()=>fe,pQuerySelectorAll:()=>$});var a=class{static async*map(e,r){for await(let o of e)yield await r(o)}static async*flatMap(e,r){for await(let o of e)yield*r(o)}static async collect(e){let r=[];for await(let o of e)r.push(o);return r}static async first(e){for await(let r of e)return r}};var C={};l(C,{textQuerySelectorAll:()=>m});var re=new Set([\"checkbox\",\"image\",\"radio\"]),oe=t=>t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLInputElement&&!re.has(t.type),ne=new Set([\"SCRIPT\",\"STYLE\"]),f=t=>!ne.has(t.nodeName)&&!document.head?.contains(t),I=new WeakMap,j=t=>{for(;t;)I.delete(t),t instanceof ShadowRoot?t=t.host:t=t.parentNode},W=new WeakSet,se=new MutationObserver(t=>{for(let e of t)j(e.target)}),d=t=>{let e=I.get(t);if(e||(e={full:\"\",immediate:[]},!f(t)))return e;let r=\"\";if(oe(t))e.full=t.value,e.immediate.push(t.value),t.addEventListener(\"input\",o=>{j(o.target)},{once:!0,capture:!0});else{for(let o=t.firstChild;o;o=o.nextSibling){if(o.nodeType===Node.TEXT_NODE){e.full+=o.nodeValue??\"\",r+=o.nodeValue??\"\";continue}r&&e.immediate.push(r),r=\"\",o.nodeType===Node.ELEMENT_NODE&&(e.full+=d(o).full)}r&&e.immediate.push(r),t instanceof Element&&t.shadowRoot&&(e.full+=d(t.shadowRoot).full),W.has(t)||(se.observe(t,{childList:!0,characterData:!0,subtree:!0}),W.add(t))}return I.set(t,e),e};var m=function*(t,e){let r=!1;for(let o of t.childNodes)if(o instanceof Element&&f(o)){let n;o.shadowRoot?n=m(o.shadowRoot,e):n=m(o,e);for(let i of n)yield i,r=!0}r||t instanceof Element&&f(t)&&d(t).full.includes(e)&&(yield t)};var k={};l(k,{checkVisibility:()=>le,pierce:()=>S,pierceAll:()=>O});var ie=[\"hidden\",\"collapse\"],le=(t,e)=>{if(!t)return e===!1;if(e===void 0)return t;let r=t.nodeType===Node.TEXT_NODE?t.parentElement:t,o=window.getComputedStyle(r),n=o&&!ie.includes(o.visibility)&&!ae(r);return e===n?t:!1};function ae(t){let e=t.getBoundingClientRect();return e.width===0||e.height===0}var ce=t=>\"shadowRoot\"in t&&t.shadowRoot instanceof ShadowRoot;function*S(t){ce(t)?yield t.shadowRoot:yield t}function*O(t){t=S(t).next().value,yield t;let e=[document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT)];for(let r of e){let o;for(;o=r.nextNode();)o.shadowRoot&&(yield o.shadowRoot,e.push(document.createTreeWalker(o.shadowRoot,NodeFilter.SHOW_ELEMENT)))}}var Q={};l(Q,{xpathQuerySelectorAll:()=>q});var q=function*(t,e,r=-1){let n=(t.ownerDocument||document).evaluate(e,t,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE),i=[],s;for(;(s=n.iterateNext())&&(i.push(s),!(r&&i.length===r)););for(let h=0;h(r.Descendent=\">>>\",r.Child=\">>>>\",r))(H||{}),V=t=>\"querySelectorAll\"in t,M=class{#e;#r=[];#o=void 0;elements;constructor(e,r){this.elements=[e],this.#e=r,this.#t()}async run(){if(typeof this.#o==\"string\")switch(this.#o.trimStart()){case\":scope\":this.#t();break}for(;this.#o!==void 0;this.#t()){let e=this.#o;typeof e==\"string\"?e[0]&&ue.test(e[0])?this.elements=a.flatMap(this.elements,async function*(r){V(r)&&(yield*r.querySelectorAll(e))}):this.elements=a.flatMap(this.elements,async function*(r){if(!r.parentElement){if(!V(r))return;yield*r.querySelectorAll(e);return}let o=0;for(let n of r.parentElement.children)if(++o,n===r)break;yield*r.parentElement.querySelectorAll(`:scope>:nth-child(${o})${e}`)}):this.elements=a.flatMap(this.elements,async function*(r){switch(e.name){case\"text\":yield*m(r,e.value);break;case\"xpath\":yield*q(r,e.value);break;case\"aria\":yield*b(r,e.value);break;default:let o=P.get(e.name);if(!o)throw new Error(`Unknown selector type: ${e.name}`);yield*o.querySelectorAll(r,e.value)}})}}#t(){if(this.#r.length!==0){this.#o=this.#r.shift();return}if(this.#e.length===0){this.#o=void 0;return}let e=this.#e.shift();switch(e){case\">>>>\":{this.elements=a.flatMap(this.elements,S),this.#t();break}case\">>>\":{this.elements=a.flatMap(this.elements,O),this.#t();break}default:this.#r=e,this.#t();break}}},D=class{#e=new WeakMap;calculate(e,r=[]){if(e===null)return r;e instanceof ShadowRoot&&(e=e.host);let o=this.#e.get(e);if(o)return[...o,...r];let n=0;for(let s=e.previousSibling;s;s=s.previousSibling)++n;let i=this.calculate(e.parentNode,[n]);return this.#e.set(e,i),[...i,...r]}},U=(t,e)=>{if(t.length+e.length===0)return 0;let[r=-1,...o]=t,[n=-1,...i]=e;return r===n?U(o,i):r[o,r.calculate(o)]).sort(([,o],[,n])=>U(o,n)).map(([o])=>o)},$=function(t,e){let r=JSON.parse(e);if(r.some(o=>{let n=0;return o.some(i=>(typeof i==\"string\"?++n:n=0,n>1))}))throw new Error(\"Multiple deep combinators found in sequence.\");return de(a.flatMap(r,o=>{let n=new M(t,o);return n.run(),n.elements}))},fe=async function(t,e){for await(let r of $(t,e))return r;return null};var me=Object.freeze({...x,...A,...R,..._,...C,...k,...Q,...E,Deferred:c,createFunction:F,createTextContent:d,IntervalPoller:T,isSuitableNodeForTextMatching:f,MutationPoller:y,RAFPoller:w}),he=me;\n"; - - public ExecutionContext(CDPSession client, ExecutionContextDescription contextPayload, IsolatedWorld world) { - this.client = client; - this.world = world; - this.id = contextPayload.getId(); - if (StringUtil.isNotEmpty(contextPayload.getName())) { - this.name = contextPayload.getName(); - } - setListener(client); - } - - private void setListener(CDPSession client) { - Consumer bindingCalled = this::onBindingCalled; - client.on(CDPSession.CDPSessionEvent.Runtime_bindingCalled, bindingCalled); - this.listener.put(CDPSession.CDPSessionEvent.Runtime_bindingCalled, bindingCalled); - - Consumer executionContextDestroyed = event -> { - if (event.getExecutionContextId() == this.id) { - this.dispose(); - } - }; - client.on(CDPSession.CDPSessionEvent.Runtime_executionContextDestroyed, executionContextDestroyed); - this.listener.put(CDPSession.CDPSessionEvent.Runtime_executionContextDestroyed, executionContextDestroyed); - - Consumer executionContextsCleared = event -> this.dispose(); - client.on(CDPSession.CDPSessionEvent.Runtime_executionContextsCleared, executionContextsCleared); - this.listener.put(CDPSession.CDPSessionEvent.Runtime_executionContextsCleared, executionContextsCleared); - - Consumer consoleAPICalled = ExecutionContext.this::onConsoleAPI; - client.on(CDPSession.CDPSessionEvent.Runtime_consoleAPICalled, consoleAPICalled); - this.listener.put(CDPSession.CDPSessionEvent.Runtime_consoleAPICalled, consoleAPICalled); - - Consumer disconnected = event -> this.dispose(); - client.on(CDPSession.CDPSessionEvent.CDPSession_Disconnected, disconnected); - this.listener.put(CDPSession.CDPSessionEvent.CDPSession_Disconnected, disconnected); - } - - public void addBinding(Binding binding) { - if (this.bindings.containsKey(binding.name())) { - return; - } - Map params = ParamsFactory.create(); - params.put("name", CDP_BINDING_PREFIX + binding.name()); - if (StringUtil.isNotEmpty(this.name)) { - params.put("executionContextName", this.name); - } else { - params.put("executionContextId", this.id); - } - synchronized (this) { - try { - this.client.send("Runtime.addBinding", params); - List args = new ArrayList<>(); - args.add("internal"); - args.add(binding.name()); - args.add(CDP_BINDING_PREFIX); - this.evaluate(addPageBinding(), args); - this.bindings.put(binding.name(), binding); - } catch (Exception e) { - if (e.getMessage().contains("Execution context was destroyed")) { - return; - } - if (e.getMessage().contains("Cannot find context with specified id'")) { - return; - } - LOGGER.error("addBinding error:", e); - } - } - } - - private String addPageBinding() { - return "function addPageBinding(type,name,prefix) {\n" + - " // Depending on the frame loading state either Runtime.evaluate or\n" + - " // Page.addScriptToEvaluateOnNewDocument might succeed. Let's check that we\n" + - " // don't re-wrap Puppeteer's binding.\n" + - " // @ts-expect-error: In a different context.\n" + - " if (globalThis[name]) {\n" + - " return;\n" + - " }\n" + - "\n" + - " // We replace the CDP binding with a Puppeteer binding.\n" + - " Object.assign(globalThis, {\n" + - " [name](...args) {\n" + - " // This is the Puppeteer binding.\n" + - " // @ts-expect-error: In a different context.\n" + - " const callPuppeteer = globalThis[name];\n" + - " callPuppeteer.args ??= new Map();\n" + - " callPuppeteer.callbacks ??= new Map();\n" + - "\n" + - " const seq = (callPuppeteer.lastSeq ?? 0) + 1;\n" + - " callPuppeteer.lastSeq = seq;\n" + - " callPuppeteer.args.set(seq, args);\n" + - "\n" + - " // @ts-expect-error: In a different context.\n" + - " // Needs to be the same as CDP_BINDING_PREFIX.\n" + - " globalThis[prefix + name](\n" + - " JSON.stringify({\n" + - " type,\n" + - " name,\n" + - " seq,\n" + - " args,\n" + - " isTrivial: !args.some(value => {\n" + - " return value instanceof Node;\n" + - " }),\n" + - " })\n" + - " );\n" + - "\n" + - " return new Promise((resolve, reject) => {\n" + - " callPuppeteer.callbacks.set(seq, {\n" + - " resolve(value) {\n" + - " callPuppeteer.args.delete(seq);\n" + - " resolve(value);\n" + - " },\n" + - " reject(value) {\n" + - " callPuppeteer.args.delete(seq);\n" + - " reject(value);\n" + - " },\n" + - " });\n" + - " });\n" + - " },\n" + - " });\n" + - "}"; - } - - private void onBindingCalled(BindingCalledEvent event) { - if (event.getExecutionContextId() != this.id) { - return; - } - String payloadStr = event.getPayload(); - BindingPayload payload; - try { - payload = OBJECTMAPPER.readValue(payloadStr, BindingPayload.class); - } catch (JsonProcessingException e) { - return; - } - if (!"internal".equals(payload.getType())) { - this.emit(ExecutionContextEvent.Bindingcalled, event); - return; - } - if (!this.bindings.containsKey(payload.getName())) { - this.emit(ExecutionContextEvent.Bindingcalled, event); - return; - } - Binding binding = this.bindings.get(payload.getName()); - try { - if (binding != null) { - binding.run(this, payload.getSeq(), payload.getArgs(), payload.getIsTrivial()); - } - } catch (Exception e) { - LOGGER.error("onBindingCalled error", e); - } - } - - public int getId() { - return id; - } - - private void onConsoleAPI(ConsoleAPICalledEvent event) { - if (event.getExecutionContextId() != this.id) { - return; - } - this.emit(ExecutionContextEvent.Consoleapicalled, event); - } - - public JSHandle evaluateHandle(String pageFunction, EvaluateType type, List args) throws JsonProcessingException, EvaluateException { - Object handle = this.evaluateInternal(false, pageFunction, type, args); - if (handle == null) { - return null; - } - return (JSHandle) handle; - } - - public JSHandle evaluateHandle(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - Object handle = this.evaluateInternal(false, pageFunction, Helper.isFunction(pageFunction) ? EvaluateType.FUNCTION : EvaluateType.STRING, args); - if (handle == null) { - return null; - } - return (JSHandle) handle; - } - - public Object evaluate(String pageFunction, EvaluateType type, List args) throws JsonProcessingException, EvaluateException { - if (type == null) { - type = Helper.isFunction(pageFunction) ? EvaluateType.FUNCTION : EvaluateType.STRING; - } - return this.evaluateInternal(true, pageFunction, type, args); - } - - public Object evaluate(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - return this.evaluate(pageFunction, null, args); - } - - private String setSourceUrlComment(String pageFunction) { - if (SOURCE_URL_REGEX.matcher(pageFunction).find()) { - return pageFunction; - } else { - return pageFunction + "\n" + "//# sourceURL=" + INTERNAL_URL + pptrFunctionId.incrementAndGet() + "\n"; - } - } - - private EvaluateResponse rewriteError(Exception e) { - if (e.getMessage() != null && e.getMessage().contains("Object reference chain is too long")) { - RemoteObject remoteObject = new RemoteObject(); - remoteObject.setType("undefined"); - EvaluateResponse response = new EvaluateResponse(); - response.setResult(remoteObject); - return response; - } - if (e.getMessage() != null && e.getMessage().contains("Object couldn't be returned by value")) { - RemoteObject remoteObject = new RemoteObject(); - remoteObject.setType("undefined"); - EvaluateResponse response = new EvaluateResponse(); - response.setResult(remoteObject); - return response; - } - - if (e.getMessage() != null && (e.getMessage().endsWith("Cannot find context with specified id") || e.getMessage().endsWith("Inspected target navigated or closed"))) { - throw new JvppeteerException("Execution context was destroyed, most likely because of a navigation."); - } - throwError(e); - return null; - } - - /** - * 这里的EvaluateType有时候是明确指定为String的,不指定的情况下,会自动判断是字符串还是函数 - *

- * {@link Frame#addExposedFunctionBinding(Binding)}就指定了是String - */ - private Object evaluateInternal(boolean returnByValue, String pageFunction, EvaluateType type, List args) throws JsonProcessingException, EvaluateException { - pageFunction = setSourceUrlComment(pageFunction); - if (EvaluateType.STRING.equals(type)) { - Map params = new HashMap<>(); - params.put("expression", pageFunction); - params.put("contextId", this.id); - params.put("returnByValue", returnByValue); - params.put("awaitPromise", true); - params.put("userGesture", true); - EvaluateResponse result; - try { - result = OBJECTMAPPER.treeToValue(this.client.send("Runtime.evaluate", params), EvaluateResponse.class); - } catch (Exception e) { - result = rewriteError(e); - } - ExceptionDetails exceptionDetails = result.getExceptionDetails(); - if (exceptionDetails != null) { - Object evaluationError = Helper.createEvaluationError(exceptionDetails); - if (evaluationError instanceof EvaluateException) { - throw (EvaluateException) evaluationError; - } else { - throw new EvaluateException(OBJECTMAPPER.writeValueAsString(evaluationError)); - } - } - RemoteObject remoteObject = result.getResult(); - return returnByValue ? Helper.valueFromRemoteObject(remoteObject) : this.world.createJSHandle(remoteObject); - } - Map params = new HashMap<>(); - List argList = new ArrayList<>(); - if (args != null) { - for (Object arg : args) { - if (arg instanceof LazyArg) { - initPuppeteerUtil(); - } else { - argList.add(convertArgument(this, arg)); - } - } - } - params.put("functionDeclaration", pageFunction); - params.put("executionContextId", this.id); - params.put("arguments", argList); - params.put("returnByValue", returnByValue); - params.put("awaitPromise", true); - params.put("userGesture", true); - EvaluateResponse callFunctionOnPromise; - try { - try {//第一个try用来添加message,第二个try是重写错误,返回结果 - callFunctionOnPromise = OBJECTMAPPER.treeToValue(this.client.send("Runtime.callFunctionOn", params), EvaluateResponse.class); - } catch (Exception e) { - if (e.getMessage().startsWith("Converting circular structure to JSON")) - throw new JvppeteerException(e.getMessage() + " Recursive objects are not allowed."); - else - throw e; - } - } catch (Exception e) { - callFunctionOnPromise = rewriteError(e); - } - if (callFunctionOnPromise == null) { - return null; - } - ExceptionDetails exceptionDetails = callFunctionOnPromise.getExceptionDetails(); - if (exceptionDetails != null) { - Object evaluationError = Helper.createEvaluationError(exceptionDetails); - if (evaluationError instanceof EvaluateException) { - throw (EvaluateException) evaluationError; - } else { - throw new EvaluateException(OBJECTMAPPER.writeValueAsString(evaluationError)); - } - } - RemoteObject remoteObject = callFunctionOnPromise.getResult(); - return returnByValue ? Helper.valueFromRemoteObject(remoteObject) : this.world.createJSHandle(remoteObject); - } - - private void initPuppeteerUtil() throws JsonProcessingException { - if (puppeteerUtil == null) { - synchronized (this) { - if (puppeteerUtil == null) { -// BindingFunction queryOneFunction = (args) -> { -// ElementHandle element = (ElementHandle) args.get(0); -// String selector = (String) args.get(2); -// ARIAQueryHandler ariaQueryHandler = new ARIAQueryHandler(element, selector); -// try { -// return ariaQueryHandler.queryOne(); -// } catch (JsonProcessingException e) { -// return null; -// } -// }; -// Binding ariaQuerySelectorBinding = new Binding("__ariaQuerySelector", queryOneFunction, ""); -// BindingFunction queryAllFunction = (args) -> { -// ElementHandle element = (ElementHandle) args.get(0); -// String selector = (String) args.get(2); -// ARIAQueryHandler ariaQueryHandler = new ARIAQueryHandler(element, selector); -// try { -// return ariaQueryHandler.queryAll(); -// } catch (JsonProcessingException e) { -// return null; -// } -// }; -// Binding ariaQuerySelectorAllBinding = new Binding("__ariaQuerySelector", queryAllFunction, ""); -// this.addBinding(ariaQuerySelectorBinding); -// this.addBinding(ariaQuerySelectorAllBinding); - String pageFunction = "(() => {\n const module = {};\n \"use strict\";var g=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var l=(t,e)=>{for(var r in e)g(t,r,{get:e[r],enumerable:!0})},J=(t,e,r,o)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let n of B(e))!Y.call(t,n)&&n!==r&&g(t,n,{get:()=>e[n],enumerable:!(o=X(e,n))||o.enumerable});return t};var z=t=>J(g({},\"__esModule\",{value:!0}),t);var pe={};l(pe,{default:()=>he});module.exports=z(pe);var N=class extends Error{constructor(e,r){super(e,r),this.name=this.constructor.name}get[Symbol.toStringTag](){return this.constructor.name}},p=class extends N{};var c=class t{static create(e){return new t(e)}static async race(e){let r=new Set;try{let o=e.map(n=>n instanceof t?(n.#n&&r.add(n),n.valueOrThrow()):n);return await Promise.race(o)}finally{for(let o of r)o.reject(new Error(\"Timeout cleared\"))}}#e=!1;#r=!1;#o;#t;#a=new Promise(e=>{this.#t=e});#n;#i;constructor(e){e&&e.timeout>0&&(this.#i=new p(e.message),this.#n=setTimeout(()=>{this.reject(this.#i)},e.timeout))}#l(e){clearTimeout(this.#n),this.#o=e,this.#t()}resolve(e){this.#r||this.#e||(this.#e=!0,this.#l(e))}reject(e){this.#r||this.#e||(this.#r=!0,this.#l(e))}resolved(){return this.#e}finished(){return this.#e||this.#r}value(){return this.#o}#s;valueOrThrow(){return this.#s||(this.#s=(async()=>{if(await this.#a,this.#r)throw this.#o;return this.#o})()),this.#s}};var L=new Map,F=t=>{let e=L.get(t);return e||(e=new Function(`return ${t}`)(),L.set(t,e),e)};var x={};l(x,{ariaQuerySelector:()=>G,ariaQuerySelectorAll:()=>b});var G=(t,e)=>globalThis.__ariaQuerySelector(t,e),b=async function*(t,e){yield*await globalThis.__ariaQuerySelectorAll(t,e)};var E={};l(E,{cssQuerySelector:()=>K,cssQuerySelectorAll:()=>Z});var K=(t,e)=>t.querySelector(e),Z=function(t,e){return t.querySelectorAll(e)};var A={};l(A,{customQuerySelectors:()=>P});var v=class{#e=new Map;register(e,r){if(!r.queryOne&&r.queryAll){let o=r.queryAll;r.queryOne=(n,i)=>{for(let s of o(n,i))return s;return null}}else if(r.queryOne&&!r.queryAll){let o=r.queryOne;r.queryAll=(n,i)=>{let s=o(n,i);return s?[s]:[]}}else if(!r.queryOne||!r.queryAll)throw new Error(\"At least one query method must be defined.\");this.#e.set(e,{querySelector:r.queryOne,querySelectorAll:r.queryAll})}unregister(e){this.#e.delete(e)}get(e){return this.#e.get(e)}clear(){this.#e.clear()}},P=new v;var R={};l(R,{pierceQuerySelector:()=>ee,pierceQuerySelectorAll:()=>te});var ee=(t,e)=>{let r=null,o=n=>{let i=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT);do{let s=i.currentNode;s.shadowRoot&&o(s.shadowRoot),!(s instanceof ShadowRoot)&&s!==n&&!r&&s.matches(e)&&(r=s)}while(!r&&i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r},te=(t,e)=>{let r=[],o=n=>{let i=document.createTreeWalker(n,NodeFilter.SHOW_ELEMENT);do{let s=i.currentNode;s.shadowRoot&&o(s.shadowRoot),!(s instanceof ShadowRoot)&&s!==n&&s.matches(e)&&r.push(s)}while(i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r};var u=(t,e)=>{if(!t)throw new Error(e)};var y=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=new MutationObserver(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())}),this.#o.observe(this.#r,{childList:!0,subtree:!0,attributes:!0})}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(this.#o.disconnect(),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}},w=class{#e;#r;constructor(e){this.#e=e}async start(){let e=this.#r=c.create(),r=await this.#e();if(r){e.resolve(r);return}let o=async()=>{if(e.finished())return;let n=await this.#e();if(!n){window.requestAnimationFrame(o);return}e.resolve(n),await this.stop()};window.requestAnimationFrame(o)}async stop(){u(this.#r,\"Polling never started.\"),this.#r.finished()||this.#r.reject(new Error(\"Polling stopped\"))}result(){return u(this.#r,\"Polling never started.\"),this.#r.valueOrThrow()}},T=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=setInterval(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())},this.#r)}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(clearInterval(this.#o),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}};var _={};l(_,{PCombinator:()=>H,pQuerySelector:()=>fe,pQuerySelectorAll:()=>$});var a=class{static async*map(e,r){for await(let o of e)yield await r(o)}static async*flatMap(e,r){for await(let o of e)yield*r(o)}static async collect(e){let r=[];for await(let o of e)r.push(o);return r}static async first(e){for await(let r of e)return r}};var C={};l(C,{textQuerySelectorAll:()=>m});var re=new Set([\"checkbox\",\"image\",\"radio\"]),oe=t=>t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLInputElement&&!re.has(t.type),ne=new Set([\"SCRIPT\",\"STYLE\"]),f=t=>!ne.has(t.nodeName)&&!document.head?.contains(t),I=new WeakMap,j=t=>{for(;t;)I.delete(t),t instanceof ShadowRoot?t=t.host:t=t.parentNode},W=new WeakSet,se=new MutationObserver(t=>{for(let e of t)j(e.target)}),d=t=>{let e=I.get(t);if(e||(e={full:\"\",immediate:[]},!f(t)))return e;let r=\"\";if(oe(t))e.full=t.value,e.immediate.push(t.value),t.addEventListener(\"input\",o=>{j(o.target)},{once:!0,capture:!0});else{for(let o=t.firstChild;o;o=o.nextSibling){if(o.nodeType===Node.TEXT_NODE){e.full+=o.nodeValue??\"\",r+=o.nodeValue??\"\";continue}r&&e.immediate.push(r),r=\"\",o.nodeType===Node.ELEMENT_NODE&&(e.full+=d(o).full)}r&&e.immediate.push(r),t instanceof Element&&t.shadowRoot&&(e.full+=d(t.shadowRoot).full),W.has(t)||(se.observe(t,{childList:!0,characterData:!0,subtree:!0}),W.add(t))}return I.set(t,e),e};var m=function*(t,e){let r=!1;for(let o of t.childNodes)if(o instanceof Element&&f(o)){let n;o.shadowRoot?n=m(o.shadowRoot,e):n=m(o,e);for(let i of n)yield i,r=!0}r||t instanceof Element&&f(t)&&d(t).full.includes(e)&&(yield t)};var k={};l(k,{checkVisibility:()=>le,pierce:()=>S,pierceAll:()=>O});var ie=[\"hidden\",\"collapse\"],le=(t,e)=>{if(!t)return e===!1;if(e===void 0)return t;let r=t.nodeType===Node.TEXT_NODE?t.parentElement:t,o=window.getComputedStyle(r),n=o&&!ie.includes(o.visibility)&&!ae(r);return e===n?t:!1};function ae(t){let e=t.getBoundingClientRect();return e.width===0||e.height===0}var ce=t=>\"shadowRoot\"in t&&t.shadowRoot instanceof ShadowRoot;function*S(t){ce(t)?yield t.shadowRoot:yield t}function*O(t){t=S(t).next().value,yield t;let e=[document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT)];for(let r of e){let o;for(;o=r.nextNode();)o.shadowRoot&&(yield o.shadowRoot,e.push(document.createTreeWalker(o.shadowRoot,NodeFilter.SHOW_ELEMENT)))}}var Q={};l(Q,{xpathQuerySelectorAll:()=>q});var q=function*(t,e,r=-1){let n=(t.ownerDocument||document).evaluate(e,t,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE),i=[],s;for(;(s=n.iterateNext())&&(i.push(s),!(r&&i.length===r)););for(let h=0;h(r.Descendent=\">>>\",r.Child=\">>>>\",r))(H||{}),V=t=>\"querySelectorAll\"in t,M=class{#e;#r=[];#o=void 0;elements;constructor(e,r){this.elements=[e],this.#e=r,this.#t()}async run(){if(typeof this.#o==\"string\")switch(this.#o.trimStart()){case\":scope\":this.#t();break}for(;this.#o!==void 0;this.#t()){let e=this.#o;typeof e==\"string\"?e[0]&&ue.test(e[0])?this.elements=a.flatMap(this.elements,async function*(r){V(r)&&(yield*r.querySelectorAll(e))}):this.elements=a.flatMap(this.elements,async function*(r){if(!r.parentElement){if(!V(r))return;yield*r.querySelectorAll(e);return}let o=0;for(let n of r.parentElement.children)if(++o,n===r)break;yield*r.parentElement.querySelectorAll(`:scope>:nth-child(${o})${e}`)}):this.elements=a.flatMap(this.elements,async function*(r){switch(e.name){case\"text\":yield*m(r,e.value);break;case\"xpath\":yield*q(r,e.value);break;case\"aria\":yield*b(r,e.value);break;default:let o=P.get(e.name);if(!o)throw new Error(`Unknown selector type: ${e.name}`);yield*o.querySelectorAll(r,e.value)}})}}#t(){if(this.#r.length!==0){this.#o=this.#r.shift();return}if(this.#e.length===0){this.#o=void 0;return}let e=this.#e.shift();switch(e){case\">>>>\":{this.elements=a.flatMap(this.elements,S),this.#t();break}case\">>>\":{this.elements=a.flatMap(this.elements,O),this.#t();break}default:this.#r=e,this.#t();break}}},D=class{#e=new WeakMap;calculate(e,r=[]){if(e===null)return r;e instanceof ShadowRoot&&(e=e.host);let o=this.#e.get(e);if(o)return[...o,...r];let n=0;for(let s=e.previousSibling;s;s=s.previousSibling)++n;let i=this.calculate(e.parentNode,[n]);return this.#e.set(e,i),[...i,...r]}},U=(t,e)=>{if(t.length+e.length===0)return 0;let[r=-1,...o]=t,[n=-1,...i]=e;return r===n?U(o,i):r[o,r.calculate(o)]).sort(([,o],[,n])=>U(o,n)).map(([o])=>o)},$=function(t,e){let r=JSON.parse(e);if(r.some(o=>{let n=0;return o.some(i=>(typeof i==\"string\"?++n:n=0,n>1))}))throw new Error(\"Multiple deep combinators found in sequence.\");return de(a.flatMap(r,o=>{let n=new M(t,o);return n.run(),n.elements}))},fe=async function(t,e){for await(let r of $(t,e))return r;return null};var me=Object.freeze({...x,...A,...R,..._,...C,...k,...Q,...E,Deferred:c,createFunction:F,createTextContent:d,IntervalPoller:T,isSuitableNodeForTextMatching:f,MutationPoller:y,RAFPoller:w}),he=me;\n\n \n return module.exports.default;\n })()"; - this.puppeteerUtil = this.evaluateHandle(pageFunction, EvaluateType.STRING, null); - } - } - } - } - - private JsonNode convertArgument(ExecutionContext context, Object arg) { - ObjectNode objectNode = Constant.OBJECTMAPPER.createObjectNode(); - if (arg == null) { - objectNode.put("value", ""); - return objectNode; - } - if (arg instanceof BigInteger) { // eslint-disable-line valid-typeof - objectNode.put("unserializableValue", arg + "n"); - return objectNode; - } - if ("-0".equals(arg)) { - objectNode.put("unserializableValue", "-0"); - return objectNode; - } - if ("Infinity".equals(arg)) { - objectNode.put("unserializableValue", "Infinity"); - return objectNode; - } - if ("-Infinity".equals(arg)) { - objectNode.put("unserializableValue", "-Infinity"); - return objectNode; - } - if ("NaN".equals(arg)) { - objectNode.put("unserializableValue", "NaN"); - return objectNode; - } - JSHandle objectHandle = arg instanceof JSHandle ? (JSHandle) arg : null; - if (objectHandle != null) { - if (objectHandle.realm() != context.world()) { - throw new JvppeteerException("JSHandles can be evaluated only in the context they were created!"); - } - if (objectHandle.disposed()) { - throw new JvppeteerException("JSHandle is disposed!" + objectHandle.getRemoteObject().getObjectId()); - - } - if (objectHandle.getRemoteObject().getUnserializableValue() != null) { - objectNode.put("unserializableValue", objectHandle.getRemoteObject().getUnserializableValue()); - return objectNode; - } - if (StringUtil.isEmpty(objectHandle.getRemoteObject().getObjectId())) { - return objectNode.putPOJO("value", objectHandle.getRemoteObject().getValue()); - } - return objectNode.put("objectId", objectHandle.getRemoteObject().getObjectId()); - } - return objectNode.putPOJO("value", arg); - } - - private IsolatedWorld world() { - return this.world; - } - - - public void dispose() { - this.listener.forEach(this.client::off); - this.emit(ExecutionContextEvent.Disposed, true); - } - - public enum ExecutionContextEvent { - Disposed("disposed"), - Consoleapicalled("consoleapicalled"), - Bindingcalled("bindingcalled"); - private String eventType; - - ExecutionContextEvent(String eventType) { - - } - - public String getEventType() { - return eventType; - } - } -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Frame.java b/src/main/java/com/ruiyun/jvppeteer/core/Frame.java deleted file mode 100644 index 5d72a463..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/Frame.java +++ /dev/null @@ -1,845 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.ruiyun.jvppeteer.common.DeviceRequestPrompt; -import com.ruiyun.jvppeteer.common.DeviceRequestPromptManager; -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.common.QueryHandler; -import com.ruiyun.jvppeteer.common.QuerySelector; -import com.ruiyun.jvppeteer.entities.Binding; -import com.ruiyun.jvppeteer.entities.ClickOptions; -import com.ruiyun.jvppeteer.entities.EvaluateType; -import com.ruiyun.jvppeteer.entities.FrameAddScriptTagOptions; -import com.ruiyun.jvppeteer.entities.FrameAddStyleTagOptions; -import com.ruiyun.jvppeteer.entities.FramePayload; -import com.ruiyun.jvppeteer.entities.GoToOptions; -import com.ruiyun.jvppeteer.entities.PreloadScript; -import com.ruiyun.jvppeteer.entities.PuppeteerLifeCycle; -import com.ruiyun.jvppeteer.entities.WaitForOptions; -import com.ruiyun.jvppeteer.entities.WaitForSelectorOptions; -import com.ruiyun.jvppeteer.events.BindingCalledEvent; -import com.ruiyun.jvppeteer.events.ConsoleAPICalledEvent; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.events.IsolatedWorldEmitter; -import com.ruiyun.jvppeteer.exception.EvaluateException; -import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.exception.TimeoutException; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.util.Helper; -import com.ruiyun.jvppeteer.util.QueryHandlerUtil; -import com.ruiyun.jvppeteer.util.StringUtil; -import com.ruiyun.jvppeteer.util.ValidateUtil; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; - - -import static com.ruiyun.jvppeteer.common.Constant.CDP_BINDING_PREFIX; -import static com.ruiyun.jvppeteer.common.Constant.DEFAULT_BATCH_SIZE; -import static com.ruiyun.jvppeteer.common.Constant.MAIN_WORLD; -import static com.ruiyun.jvppeteer.common.Constant.PUPPETEER_WORLD; -import static com.ruiyun.jvppeteer.util.Helper.withSourcePuppeteerURLIfNone; - -public class Frame extends EventEmitter { - private String url; - private boolean detached; - private CDPSession client; - private final FrameManager frameManager; - private String loaderId; - - public Set lifecycleEvents() { - return lifecycleEvents; - } - - private final Set lifecycleEvents = new HashSet<>(); - private String id; - private final String parentId; - private final Accessibility accessibility; - private final Map worlds = new HashMap<>(); - private boolean hasStartedLoading; - private String name; - private ElementHandle document; - - public Map worlds() { - return worlds; - } - - public Frame(FrameManager frameManager, String frameId, String parentFrameId, CDPSession client) { - super(); - this.frameManager = frameManager; - this.url = ""; - this.id = frameId; - this.parentId = parentFrameId; - this.client = client; - this.detached = false; - this.loaderId = ""; - this.worlds.put(MAIN_WORLD, new IsolatedWorld(this, null, this.frameManager.timeoutSettings())); - this.worlds.put(PUPPETEER_WORLD, new IsolatedWorld(this, null, this.frameManager.timeoutSettings())); - this.accessibility = new Accessibility(this.worlds.get(MAIN_WORLD)); - this.on(FrameEvent.FrameSwappedByActivation, (ignore) -> { - this.onLoadingStarted(); - this.onLoadingStopped(); - }); - this.worlds.get(MAIN_WORLD).emitter().on(IsolatedWorldEmitter.IsolatedWorldEventType.Consoleapicalled, (event) -> this.onMainWorldConsoleApiCalled((ConsoleAPICalledEvent) event)); - this.worlds.get(MAIN_WORLD).emitter().on(IsolatedWorldEmitter.IsolatedWorldEventType.Bindingcalled, (event) -> this.onMainWorldBindingCalled((BindingCalledEvent) event)); - } - - private void onMainWorldBindingCalled(BindingCalledEvent event) { - List args = new ArrayList<>(); - args.add(this.worlds.get(MAIN_WORLD)); - args.add(event); - this.frameManager.emit(FrameManager.FrameManagerEvent.BindingCalled, args); - } - - private void onMainWorldConsoleApiCalled(ConsoleAPICalledEvent event) { - this.frameManager.emit(FrameManager.FrameManagerEvent.ConsoleApiCalled, new Object[]{this.worlds.get(MAIN_WORLD), event}); - } - - public void onLoadingStarted() { - this.hasStartedLoading = true; - } - - public CDPSession client() { - return this.client; - } - - public void updateId(String id) { - this.id = id; - } - - public void updateClient(CDPSession client) { - this.client = client; - } - - public Page page() { - return this.frameManager.page(); - } - - public Response goTo(String url, GoToOptions options, boolean isBlocking) { - String referrer; - String refererPolicy; - List waitUntil; - Integer timeout; - if (options == null) { - referrer = this.frameManager.networkManager().extraHTTPHeaders().get("referer"); - refererPolicy = this.frameManager.networkManager().extraHTTPHeaders().get("referer_policy"); - waitUntil = new ArrayList<>(); - waitUntil.add(PuppeteerLifeCycle.LOAD); - timeout = this.frameManager().timeoutSettings().navigationTimeout(); - } else { - if (StringUtil.isEmpty(referrer = options.getReferer())) { - referrer = this.frameManager.networkManager().extraHTTPHeaders().get("referer"); - } - if (ValidateUtil.isEmpty(waitUntil = options.getWaitUntil())) { - waitUntil = new ArrayList<>(); - waitUntil.add(PuppeteerLifeCycle.LOAD); - } - if ((timeout = options.getTimeout()) == null) { - timeout = this.frameManager.timeoutSettings().navigationTimeout(); - } - if (StringUtil.isEmpty(refererPolicy = options.getReferrerPolicy())) { - refererPolicy = this.frameManager.networkManager().extraHTTPHeaders().get("referer"); - } - } - if (!isBlocking) {//不等待结果返回,只是发送指令 - Map params = ParamsFactory.create(); - params.put("url", url); - params.put("frameId", this.id()); - params.put("referrer", referrer); - params.put("referrerPolicy", refererPolicy); - this.client.send("Page.navigate", params, null, false); - return null; - } - AtomicBoolean ensureNewDocumentNavigation = new AtomicBoolean(false); - LifecycleWatcher watcher = new LifecycleWatcher(this.frameManager.networkManager(), this, waitUntil); - try { - this.navigate(this.client, url, referrer, refererPolicy, this.id(), ensureNewDocumentNavigation); - String timeoutMessage = "Navigation timeout of " + timeout + " ms exceeded"; - Supplier conditionChecker = () -> { - if (watcher.terminationIsDone()) { - throw new TimeoutException(timeoutMessage); - } - if (ensureNewDocumentNavigation.get()) { - if (watcher.newDocumentNavigationIsDone()) { - return true; - } - } else { - if (watcher.sameDocumentNavigationIsDone()) { - return true; - } - } - return null; - }; - Helper.waitForCondition(conditionChecker, timeout, timeoutMessage); - return watcher.navigationResponse(); - } finally { - watcher.dispose(); - } - } - - private void navigate(CDPSession client, String url, String referrer, String referrerPolicy, String frameId, AtomicBoolean ensureNewDocumentNavigation) { - Map params = ParamsFactory.create(); - params.put("url", url); - params.put("referrer", referrer); - params.put("frameId", frameId); - params.put("referrerPolicy", referrerPolicy); - JsonNode response = client.send("Page.navigate", params); - if (response == null) { - return; - } - if (StringUtil.isNotEmpty(response.get("loaderId").asText())) { - ensureNewDocumentNavigation.set(true); - } - String errorText = null; - if (response.get("errorText") != null && StringUtil.isNotEmpty(errorText = response.get("errorText").asText()) && "net::ERR_HTTP_RESPONSE_CODE_FAILURE".equals(response.get("errorText").asText())) { - return; - } - if (StringUtil.isNotEmpty(errorText)) throw new JvppeteerException(errorText + " at " + url); - } - - public List childFrames() { - return this.frameManager.frameTree().childFrames(this.id); - } - - public DeviceRequestPromptManager deviceRequestPromptManager() { - return this.frameManager.deviceRequestPromptManager(this.client); - } - - public void dispose() { - if (this.detached) { - return; - } - this.detached = true; - this.worlds.get(MAIN_WORLD).dispose(); - this.worlds.get(PUPPETEER_WORLD).dispose(); - } - - public void navigatedWithinDocument(String url) { - this.url = url; - } - - public Response waitForNavigation(WaitForOptions options, boolean reload) { - Integer timeout; - List waitUntil; - boolean ignoreSameDocumentNavigation; - if (options == null) { - ignoreSameDocumentNavigation = false; - waitUntil = new ArrayList<>(); - waitUntil.add(PuppeteerLifeCycle.LOAD); - timeout = this.frameManager.timeoutSettings().navigationTimeout(); - } else { - if (ValidateUtil.isEmpty(waitUntil = options.getWaitUntil())) { - waitUntil = new ArrayList<>(); - waitUntil.add(PuppeteerLifeCycle.LOAD); - } - if ((timeout = options.getTimeout()) == null) { - timeout = this.frameManager.timeoutSettings().navigationTimeout(); - } - ignoreSameDocumentNavigation = options.getIgnoreSameDocumentNavigation(); - } - LifecycleWatcher watcher = new LifecycleWatcher(this.frameManager.networkManager(), this, waitUntil); - // 如果是reload页面,需要在等待之前发送刷新命令 - if (reload) { - this.client.send("Page.reload", null, null, false); - } - try { - long base = System.currentTimeMillis(); - long now = 0; - while (true) { - long delay = timeout - now; - if (delay <= 0) { - throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); - } - if (watcher.terminationIsDone()) { - throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); - } - if (!ignoreSameDocumentNavigation) {//不忽略sameDocumentNavigation - if ((watcher.newDocumentNavigationIsDone() || watcher.sameDocumentNavigationIsDone()) && watcher.navigationResponseIsDone()) { - break; - } - } else { - if (watcher.newDocumentNavigationIsDone() && watcher.navigationResponseIsDone()) { - break; - } - } - now = System.currentTimeMillis() - base; - } - return watcher.navigationResponse(); - } finally { - watcher.dispose(); - } - } - - public IsolatedWorld mainRealm() { - return this.worlds.get(MAIN_WORLD); - } - - public IsolatedWorld isolatedRealm() { - return this.worlds.get(PUPPETEER_WORLD); - } - - public void setContent(String html, WaitForOptions options) throws JsonProcessingException, EvaluateException { - List waitUntil; - Integer timeout; - if (options == null) { - waitUntil = new ArrayList<>(); - waitUntil.add(PuppeteerLifeCycle.LOAD); - timeout = this.frameManager.timeoutSettings().navigationTimeout(); - } else { - if (ValidateUtil.isEmpty(waitUntil = options.getWaitUntil())) { - waitUntil = new ArrayList<>(); - waitUntil.add(PuppeteerLifeCycle.LOAD); - } - if ((timeout = options.getTimeout()) == null) { - timeout = this.frameManager.timeoutSettings().navigationTimeout(); - } - } - LifecycleWatcher watcher = new LifecycleWatcher(this.frameManager.networkManager(), this, waitUntil); - this.setFrameContent(html); - try { - long base = System.currentTimeMillis(); - long now = 0; - while (true) { - long delay = timeout - now; - if (delay <= 0) { - throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); - } - if (watcher.terminationIsDone()) { - throw new TimeoutException("Navigation timeout of " + timeout + " ms exceeded"); - } - if (watcher.lifecycleIsDone()) { - break; - } - now = System.currentTimeMillis() - base; - } - } finally { - watcher.dispose(); - } - } - - public void setFrameContent(String content) throws JsonProcessingException, EvaluateException { - this.evaluate("(content) => {\n" + - " document.open();\n" + - " document.write(content);\n" + - " document.close();\n" + - " }", Collections.singletonList(content)); - } - - public String url() { - return this.url; - } - - public Frame parentFrame() { - return this.frameManager.frameTree().parentFrame(this.id); - } - - public void navigated(FramePayload framePayload) { - this.name = framePayload.getName(); - this.url = framePayload.getUrl() + (framePayload.getUrlFragment() == null ? "" : framePayload.getUrlFragment()); - } - - public void onLifecycleEvent(String loaderId, String name) { - if ("init".equals(name)) { - this.loaderId = loaderId; - this.lifecycleEvents.clear(); - } - this.lifecycleEvents.add(name); - } - - public void onLoadingStopped() { - this.lifecycleEvents.add("DOMContentLoaded"); - this.lifecycleEvents.add("load"); - } - - public boolean detached() { - return this.detached; - } - - public String id() { - return id; - } - - public String parentId() { - return this.parentId; - } - - public DeviceRequestPrompt waitForDevicePrompt(int timeout) { - return this.deviceRequestPromptManager().waitForDevicePrompt(timeout); - } - - public void removeExposedFunctionBinding(Binding binding) throws JsonProcessingException, EvaluateException { - Map params = ParamsFactory.create(); - params.put("name", CDP_BINDING_PREFIX + binding.name()); - this.client.send("Runtime.removeBinding", params); - this.evaluate("name => {\n" + - " // Removes the dangling Puppeteer binding wrapper.\n" + - " // @ts-expect-error: In a different context.\n" + - " globalThis[name] = undefined;\n" + - " }", Collections.singletonList(binding.name())); - } - - public void addExposedFunctionBinding(Binding binding) throws JsonProcessingException, EvaluateException { - if (this != this.frameManager.mainFrame() && !this.hasStartedLoading) { - return; - } - Map params = ParamsFactory.create(); - params.put("name", CDP_BINDING_PREFIX + binding.name()); - this.client.send("Runtime.addBinding", params); - this.evaluate(binding.initSource(), EvaluateType.STRING, null); - } - - public void addPreloadScript(PreloadScript preloadScript) { - if (this.client != this.frameManager.client() && this != this.frameManager.mainFrame()) { - return; - } - if (StringUtil.isNotEmpty(preloadScript.getIdForFrame(this))) { - return; - } - Map params = ParamsFactory.create(); - params.put("source", preloadScript.getSource()); - JsonNode response = this.client.send("Page.addScriptToEvaluateOnNewDocument", params); - preloadScript.setIdForFrame(this, response.get("identifier").asText()); - } - - public ElementHandle document() throws JsonProcessingException, EvaluateException { - if (this.document == null) { - this.document = this.mainRealm().evaluateHandle("() => {\n" + - " return document;\n" + - " }", null).asElement(); - } - return this.document; - } - - public void clearDocumentHandle() { - this.document = null; - } - - public ElementHandle frameElement() throws JsonProcessingException, EvaluateException { - boolean isFirefox = this.page().target().targetManager() instanceof FirefoxTargetManager; - if (isFirefox) { - return firefoxFrameElement(); - } - Frame parentFrame = this.parentFrame(); - if (parentFrame == null) { - return null; - } - Map params = ParamsFactory.create(); - params.put("frameId", this.id); - JsonNode response = parentFrame.client().send("DOM.getFrameOwner", params); - return parentFrame.mainRealm().adoptBackendNode(response.get("backendNodeId").asInt()).asElement(); - } - - public ElementHandle firefoxFrameElement() throws JsonProcessingException, EvaluateException { - Frame parentFrame = this.parentFrame(); - if (parentFrame == null) { - return null; - } - JSHandle list = parentFrame.isolatedRealm().evaluateHandle("() => {\n" + - " return document.querySelectorAll('iframe,frame');\n" + - " }"); - ElementHandle result = null; - List lists = this.transposeIterableHandle(list); - try { - for (JSHandle iframe : lists) { - Frame frame = iframe.asElement().contentFrame(); - if (frame != null && frame.id().equals(this.id)) { - result = iframe.asElement(); - lists.remove(iframe); - break; - } - } - - } finally { - lists.forEach(JSHandle::dispose); - Optional.of(list).ifPresent(JSHandle::dispose); - } - return result; - } - - private List transposeIterableHandle(JSHandle list) throws JsonProcessingException { - JSHandle generatorHandle = null; - try { - generatorHandle = list.evaluateHandle("iterable => {\n" + - " return (async function* () {\n" + - " yield* iterable;\n" + - " })();\n" + - " }"); - return transposeIteratorHandle(generatorHandle); - } finally { - Optional.ofNullable(generatorHandle).ifPresent(JSHandle::dispose); - } - } - - private List transposeIteratorHandle(JSHandle iterator) throws JsonProcessingException, EvaluateException { - int size = DEFAULT_BATCH_SIZE; - List results = new ArrayList<>(); - List result; - while ((result = fastTransposeIteratorHandle(iterator, size)) != null) { - results.addAll(result); - size <<= 1; - } - return results; - } - - private List fastTransposeIteratorHandle(JSHandle iterator, int size) throws JsonProcessingException, EvaluateException { - JSHandle array = null; - Collection handles; - try { - array = iterator.evaluateHandle("async (iterator, size) => {\n" + - " const results = [];\n" + - " while (results.length < size) {\n" + - " const result = await iterator.next();\n" + - " if (result.done) {\n" + - " break;\n" + - " }\n" + - " results.push(result.value);\n" + - " }\n" + - " return results;\n" + - " }", Collections.singletonList(size)); - Map properties = array.getProperties(); - handles = properties.values(); - if (properties.isEmpty()) { - return null; - } - return new ArrayList<>(handles); - } finally { - Optional.ofNullable(array).ifPresent(JSHandle::dispose); - } - } - - public ElementHandle $(String selector) throws JsonProcessingException, EvaluateException { - ElementHandle document = this.document(); - return document.$(selector); - } - - public List $$(String selector) throws JsonProcessingException, EvaluateException { - ElementHandle document = this.document(); - return document.$$(selector); - } - - public Object $eval(String selector, String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("$eval", pageFunction); - ElementHandle document = this.document(); - return document.$eval(selector, pageFunction, args); - } - - public Object $$eval(String selector, String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("$$eval", pageFunction); - ElementHandle document = this.document(); - return document.$$eval(selector, pageFunction, args); - } - - /** - * 等待直到指定的选择器匹配的元素满足某些条件(可见、隐藏或存在). - * - * @param selector 选择器字符串,用于选择目标元素. - * @param options 包含等待条件的选项,如元素可见或隐藏. - * @return 返回匹配选择器的目标元素的句柄. - * @throws JsonProcessingException 如果在处理JSON时发生错误. - */ - public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) throws JsonProcessingException { - boolean waitForVisible = options.getVisible(); - boolean waitForHidden = options.getHidden(); - String polling = waitForVisible || waitForHidden ? "raf" : "mutation"; - options.setPolling(polling); - QuerySelector queryHandlerAndSelector = QueryHandlerUtil.getQueryHandlerAndSelector(selector, "(element, selector) =>\n" + - " element.querySelector(selector)"); - QueryHandler queryHandler = queryHandlerAndSelector.getQueryHandler(); - String updatedSelector = queryHandlerAndSelector.getUpdatedSelector(); - String predicate = "function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {\n" + - " const node = isXPath\n" + - " ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue\n" + - " : predicateQueryHandler\n" + - " ? predicateQueryHandler(document, selectorOrXPath)\n" + - " : document.querySelector(selectorOrXPath);\n" + - " if (!node)\n" + - " return waitForHidden;\n" + - " if (!waitForVisible && !waitForHidden)\n" + - " return node;\n" + - " const element = node.nodeType === Node.TEXT_NODE\n" + - " ? node.parentElement\n" + - " : node;\n" + - " const style = window.getComputedStyle(element);\n" + - " const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();\n" + - " const success = waitForVisible === isVisible || waitForHidden === !isVisible;\n" + - " return success ? node : null;\n" + - " function hasVisibleBoundingBox() {\n" + - " const rect = element.getBoundingClientRect();\n" + - " return !!(rect.top || rect.bottom || rect.width || rect.height);\n" + - " }\n" + - " }"; - List args = new ArrayList<>(Arrays.asList(updatedSelector, false, waitForVisible, waitForHidden)); - JSHandle handle = this.isolatedRealm().waitForFunction(predicate, queryHandler.queryOne(), options, args); - try { - if (handle == null) - return null; - JSHandle result = this.mainRealm().transferHandle(handle); - return result.asElement(); - } finally { - Optional.ofNullable(handle).ifPresent(JSHandle::dispose); - } - } - - public JSHandle waitForFunction(String pageFunction, WaitForSelectorOptions options, List args) { - return this.mainRealm().waitForFunction(pageFunction, null, options, args); - } - - public String content() throws JsonProcessingException, EvaluateException { - return (String) this.evaluate("() => {\n" + - " let content = '';\n" + - " for (const node of document.childNodes) {\n" + - " switch (node) {\n" + - " case document.documentElement:\n" + - " content += document.documentElement.outerHTML;\n" + - " break;\n" + - " default:\n" + - " content += new XMLSerializer().serializeToString(node);\n" + - " break;\n" + - " }\n" + - " }\n" + - "\n" + - " return content;\n" + - " }"); - } - - public Object evaluate(String pageFunction, EvaluateType type, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluate", pageFunction); - return this.mainRealm().evaluate(pageFunction, type, args); - } - - public Object evaluate(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluate(pageFunction, null, null); - } - - public Object evaluate(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - return this.evaluate(pageFunction, null, args); - } - - public JSHandle evaluateHandle(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pageFunction); - return this.mainRealm().evaluateHandle(pageFunction, args); - } - - public String name() { - return this.name == null ? "" : this.name; - } - - /** - * 在当前文档中添加一个脚本标签。 - *

- * 此方法使用提供的选项创建一个脚本标签,可以是通过URL、文件路径或直接通过内容来加载脚本。 - * 它支持异步执行,并处理脚本加载的生命周期事件,如'load'和'error'。 - * - * @param options 脚本标签的选项,包括URL、路径或内容等。 - * @return 返回新创建的脚本元素的句柄。 - * @throws IOException 当读取脚本文件时发生IO错误。 - * @throws EvaluateException 当脚本执行失败时抛出。 - */ - public ElementHandle addScriptTag(FrameAddScriptTagOptions options) throws IOException, EvaluateException { - if (options == null) { - throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); - } - if (StringUtil.isEmpty(options.getUrl()) && StringUtil.isEmpty(options.getPath()) && StringUtil.isEmpty(options.getContent())) { - throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); - } - if (StringUtil.isEmpty(options.getType())) { - options.setType("text/javascript"); - } - if (StringUtil.isNotEmpty(options.getPath())) { - List contents = Files.readAllLines(Paths.get(options.getPath()), StandardCharsets.UTF_8); - options.setContent(String.join("\n", contents) + "//# sourceURL=" + options.getPath().replaceAll("\n", "")); - } - return this.mainRealm().evaluateHandle("async ({url, id, type, content}) => {\n" + - " return await new Promise((resolve, reject) => {\n" + - " const script = document.createElement('script');\n" + - " script.type = type;\n" + - " script.text = content;\n" + - " script.addEventListener(\n" + - " 'error',\n" + - " event => {\n" + - " reject(new Error(event.message ?? 'Could not load script'));\n" + - " },\n" + - " {once: true}\n" + - " );\n" + - " if (id) {\n" + - " script.id = id;\n" + - " }\n" + - " if (url) {\n" + - " script.src = url;\n" + - " script.addEventListener(\n" + - " 'load',\n" + - " () => {\n" + - " resolve(script);\n" + - " },\n" + - " {once: true}\n" + - " );\n" + - " document.head.appendChild(script);\n" + - " } else {\n" + - " document.head.appendChild(script);\n" + - " resolve(script);\n" + - " }\n" + - " });\n" + - " }", Collections.singletonList(options)).asElement(); - - } - - /** - * 向文档头部添加样式标签 - *

- * 该方法用于在HTML文档的头部内插入一个新的样式标签。它支持通过URL链接到外部样式表, - * 或者直接包含样式内容。当提供样式文件的路径时,将读取该文件的内容并作为内联样式添加。 - * - * @param options 样式标签的配置选项,包括url、path或content属性 - * @return 插入的样式元素的句柄 - * @throws IOException 当读取样式文件时可能抛出的IO异常 - * @throws EvaluateException 当在页面上执行JavaScript时发生错误时抛出的异常 - */ - public ElementHandle addStyleTag(FrameAddStyleTagOptions options) throws IOException, EvaluateException { - if (options == null) { - throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); - } - if (StringUtil.isEmpty(options.getUrl()) && StringUtil.isEmpty(options.getPath()) && StringUtil.isEmpty(options.getContent())) { - throw new JvppeteerException("Provide an object with a `url`, `path` or `content` property"); - } - String content; - if (StringUtil.isNotEmpty(options.getPath())) { - List contents = Files.readAllLines(Paths.get(options.getPath()), StandardCharsets.UTF_8); - content = String.join("\n", contents) + "/*# sourceURL=" + options.getPath().replaceAll("\n", "") + "*/"; - options.setContent(content); - } - return this.mainRealm().transferHandle(this.isolatedRealm().evaluateHandle("async ({url, content}) => {\n" + - " return await new Promise(\n" + - " (resolve, reject) => {\n" + - " let element;\n" + - " if (!url) {\n" + - " element = document.createElement('style');\n" + - " element.appendChild(document.createTextNode(content));\n" + - " } else {\n" + - " const link = document.createElement('link');\n" + - " link.rel = 'stylesheet';\n" + - " link.href = url;\n" + - " element = link;\n" + - " }\n" + - " element.addEventListener(\n" + - " 'load',\n" + - " () => {\n" + - " resolve(element);\n" + - " },\n" + - " {once: true}\n" + - " );\n" + - " element.addEventListener(\n" + - " 'error',\n" + - " event => {\n" + - " reject(\n" + - " new Error(\n" + - " (event ).message ?? 'Could not load style'\n" + - " )\n" + - " );\n" + - " },\n" + - " {once: true}\n" + - " );\n" + - " document.head.appendChild(element);\n" + - " return element;\n" + - " }\n" + - " );\n" + - " }", Collections.singletonList(options))).asElement(); - } - - public void click(String selector, ClickOptions options) throws JsonProcessingException, EvaluateException { - ElementHandle handle = this.$(selector); - Objects.requireNonNull(handle, "No node found for selector: " + selector); - handle.click(options); - handle.dispose(); - } - - public void focus(String selector) throws JsonProcessingException, EvaluateException { - ElementHandle handle = this.$(selector); - ValidateUtil.assertArg(handle != null, "No node found for selector: " + selector); - handle.focus(); - handle.dispose(); - } - - public void hover(String selector) throws JsonProcessingException, EvaluateException { - ElementHandle handle = this.$(selector); - ValidateUtil.assertArg(handle != null, "No node found for selector: " + selector); - handle.hover(); - handle.dispose(); - } - - public List select(String selector, List values) throws JsonProcessingException, EvaluateException { - ElementHandle handle = this.$(selector); - ValidateUtil.assertArg(handle != null, "No node found for selector: " + selector); - List result = handle.select(values); - handle.dispose(); - return result; - } - - public void tap(String selector) throws JsonProcessingException, EvaluateException { - ElementHandle handle = this.$(selector); - ValidateUtil.assertArg(handle != null, "No node found for selector: " + selector); - handle.tap(); - handle.dispose(); - } - - public void type(String selector, String text, long delay) throws JsonProcessingException, EvaluateException { - ElementHandle handle = this.$(selector); - ValidateUtil.assertArg(handle != null, "No node found for selector: " + selector); - handle.type(text, delay); - handle.dispose(); - } - - public String title() throws JsonProcessingException, EvaluateException { - return (String) this.isolatedRealm().evaluate("() => {\n" + - " return document.title;\n" + - " }"); - } - - public String loaderId() { - return loaderId; - } - - public FrameManager frameManager() { - return this.frameManager; - } - - public Accessibility accessibility() { - return accessibility; - } - - public void setId(String frameId) { - this.id = frameId; - } - - public enum FrameEvent { - FrameNavigated("Frame.FrameNavigated"), - FrameSwapped("Frame.FrameSwapped"), - LifecycleEvent("Frame.LifecycleEvent"), - FrameNavigatedWithinDocument("Frame.FrameNavigatedWithinDocument"), - FrameDetached("Frame.FrameDetached"), - FrameSwappedByActivation("Frame.FrameSwappedByActivation"); - private final String eventType; - - FrameEvent(String eventType) { - this.eventType = eventType; - } - - public String getEventType() { - return eventType; - } - } -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/JSHandle.java b/src/main/java/com/ruiyun/jvppeteer/core/JSHandle.java deleted file mode 100644 index 16d887ee..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/JSHandle.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.RemoteObject; -import com.ruiyun.jvppeteer.exception.EvaluateException; -import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.util.Helper; -import com.ruiyun.jvppeteer.util.StringUtil; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static com.ruiyun.jvppeteer.util.Helper.withSourcePuppeteerURLIfNone; - -/** - * 表示对 JavaScript 对象的引用。可以使用 Page.evaluateHandle() 创建实例。 - *

- * 句柄可防止引用的 JavaScript 对象被垃圾回收,除非句柄特意为 disposed。当 JSHandles 关联的框架被导航离开或父上下文被破坏时,JSHandles 会被自动处置。 - *

- * 句柄可用作任何评估函数(例如 Page.$eval()、Page.evaluate() 和 Page.evaluateHandle())的参数。它们被解析为其引用的对象。 - */ -public class JSHandle { - - private final RemoteObject remoteObject; - private boolean disposed = false; - private final IsolatedWorld world; - - JSHandle(IsolatedWorld world, RemoteObject remoteObject) { - this.world = world; - this.remoteObject = remoteObject; - } - - public boolean disposed() { - return disposed; - } - - public Realm realm() { - return this.world; - } - - public CDPSession client() { - return this.realm().environment().client(); - } - - public Object jsonValue() throws JsonProcessingException, EvaluateException { - if (StringUtil.isNotEmpty(this.remoteObject.getObjectId())) { - Object value = this.evaluate("object => {\n" + - " return object;\n" + - " }", new ArrayList<>()); - if (value == null) { - throw new JvppeteerException("Could not serialize referenced object"); - } - return value; - } - return Helper.valueFromRemoteObject(this.remoteObject); - } - - /* This always returns null but children can define this and return an ElementHandle */ - public ElementHandle asElement() { - return null; - } - - - public Object evaluate(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluate", pageFunction); - List argsArray = new ArrayList<>(); - argsArray.add(this); - if (args != null) { - argsArray.addAll(args); - } - return this.realm().evaluate(pageFunction, argsArray); - } - - public JSHandle evaluateHandle(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluateHandle(pageFunction, null); - } - - public JSHandle evaluateHandle(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pageFunction); - List argsArray = new ArrayList<>(); - argsArray.add(this); - if (args != null) { - argsArray.addAll(args); - } - return this.realm().evaluateHandle(pageFunction, argsArray); - } - - public JSHandle getProperty(String propertyName) throws JsonProcessingException, EvaluateException { - String pageFunction = "(object, propertyName) => {\n" + - " return object[propertyName];\n" + - " }"; - return this.evaluateHandle(pageFunction, Collections.singletonList(propertyName)); - } - - public Map getProperties() throws JsonProcessingException { - Map params = ParamsFactory.create(); - params.put("objectId", this.remoteObject.getObjectId()); - params.put("ownProperties", true); - JsonNode response = this.client().send("Runtime.getProperties", params); - Map result = new LinkedHashMap<>(); - Iterator iterator = response.get(Constant.RESULT).iterator(); - while (iterator.hasNext()) { - JsonNode property = iterator.next(); - if (!property.get("enumerable").asBoolean() || !property.hasNonNull("value")) { - continue; - } - result.put(property.get("name").asText(), this.world.createJSHandle(Constant.OBJECTMAPPER.treeToValue(property.get("value"), RemoteObject.class))); - } - return result; - } - - public void dispose() { - if (this.disposed) - return; - this.disposed = true; - Helper.releaseObject(this.client(), this.remoteObject); - } - - public String toString() { - if (StringUtil.isNotEmpty(this.remoteObject.getObjectId())) { - String type = StringUtil.isNotEmpty(this.remoteObject.getSubtype()) ? this.remoteObject.getSubtype() : this.remoteObject.getType(); - return "JSHandle@" + type; - } - return "JSHandle:" + Helper.valueFromRemoteObject(this.remoteObject); - } - - public RemoteObject getRemoteObject() { - return this.remoteObject; - } - - public String id() { - return this.remoteObject.getObjectId(); - } - -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/OtherTarget.java b/src/main/java/com/ruiyun/jvppeteer/core/OtherTarget.java deleted file mode 100644 index f501da27..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/OtherTarget.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.transport.SessionFactory; - -public class OtherTarget extends Target { - public OtherTarget(TargetInfo targetInfo, CDPSession session, BrowserContext browserContext, TargetManager targetManager, SessionFactory sessionFactory) { - super(targetInfo, session, browserContext, targetManager, sessionFactory); - } -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Page.java b/src/main/java/com/ruiyun/jvppeteer/core/Page.java deleted file mode 100644 index a12bb434..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/Page.java +++ /dev/null @@ -1,2543 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.ruiyun.jvppeteer.common.AwaitableResult; -import com.ruiyun.jvppeteer.common.BindingFunction; -import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.common.DeviceRequestPrompt; -import com.ruiyun.jvppeteer.common.MediaType; -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.common.ScreenRecorder; -import com.ruiyun.jvppeteer.common.TimeoutSettings; -import com.ruiyun.jvppeteer.common.WebPermission; -import com.ruiyun.jvppeteer.entities.Binding; -import com.ruiyun.jvppeteer.entities.BindingPayload; -import com.ruiyun.jvppeteer.entities.BoundingBox; -import com.ruiyun.jvppeteer.entities.CallFrame; -import com.ruiyun.jvppeteer.entities.ClickOptions; -import com.ruiyun.jvppeteer.entities.ConsoleMessage; -import com.ruiyun.jvppeteer.entities.ConsoleMessageLocation; -import com.ruiyun.jvppeteer.entities.ConsoleMessageType; -import com.ruiyun.jvppeteer.entities.Cookie; -import com.ruiyun.jvppeteer.entities.CookieParam; -import com.ruiyun.jvppeteer.entities.Credentials; -import com.ruiyun.jvppeteer.entities.DeleteCookiesRequest; -import com.ruiyun.jvppeteer.entities.Device; -import com.ruiyun.jvppeteer.entities.EvaluateType; -import com.ruiyun.jvppeteer.entities.FrameAddScriptTagOptions; -import com.ruiyun.jvppeteer.entities.FrameAddStyleTagOptions; -import com.ruiyun.jvppeteer.entities.GeolocationOptions; -import com.ruiyun.jvppeteer.entities.GetMetricsResponse; -import com.ruiyun.jvppeteer.entities.GetNavigationHistoryResponse; -import com.ruiyun.jvppeteer.entities.GoToOptions; -import com.ruiyun.jvppeteer.entities.IdleOverridesState; -import com.ruiyun.jvppeteer.entities.ImageType; -import com.ruiyun.jvppeteer.entities.LengthUnit; -import com.ruiyun.jvppeteer.entities.MediaFeature; -import com.ruiyun.jvppeteer.entities.Metric; -import com.ruiyun.jvppeteer.entities.Metrics; -import com.ruiyun.jvppeteer.entities.NavigationEntry; -import com.ruiyun.jvppeteer.entities.NetworkConditions; -import com.ruiyun.jvppeteer.entities.NewDocumentScriptEvaluation; -import com.ruiyun.jvppeteer.entities.PDFMargin; -import com.ruiyun.jvppeteer.entities.PDFOptions; -import com.ruiyun.jvppeteer.entities.PageMetrics; -import com.ruiyun.jvppeteer.entities.PaperFormats; -import com.ruiyun.jvppeteer.entities.RemoteObject; -import com.ruiyun.jvppeteer.entities.ScreenRecorderOptions; -import com.ruiyun.jvppeteer.entities.ScreencastOptions; -import com.ruiyun.jvppeteer.entities.ScreenshotClip; -import com.ruiyun.jvppeteer.entities.ScreenshotOptions; -import com.ruiyun.jvppeteer.entities.StackTrace; -import com.ruiyun.jvppeteer.entities.UserAgentMetadata; -import com.ruiyun.jvppeteer.entities.Viewport; -import com.ruiyun.jvppeteer.entities.VisionDeficiency; -import com.ruiyun.jvppeteer.entities.WaitForOptions; -import com.ruiyun.jvppeteer.entities.WaitForSelectorOptions; -import com.ruiyun.jvppeteer.events.BindingCalledEvent; -import com.ruiyun.jvppeteer.events.ConsoleAPICalledEvent; -import com.ruiyun.jvppeteer.events.EntryAddedEvent; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.events.ExceptionThrownEvent; -import com.ruiyun.jvppeteer.events.FileChooserOpenedEvent; -import com.ruiyun.jvppeteer.events.JavascriptDialogOpeningEvent; -import com.ruiyun.jvppeteer.events.MetricsEvent; -import com.ruiyun.jvppeteer.events.ScreencastFrameEvent; -import com.ruiyun.jvppeteer.exception.EvaluateException; -import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.exception.ProtocolException; -import com.ruiyun.jvppeteer.exception.TargetCloseException; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.util.Helper; -import com.ruiyun.jvppeteer.util.StringUtil; -import com.ruiyun.jvppeteer.util.ValidateUtil; -import java.beans.IntrospectionException; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.Supplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -import static com.ruiyun.jvppeteer.common.Constant.ABOUT_BLANK; -import static com.ruiyun.jvppeteer.common.Constant.CDP_BINDING_PREFIX; -import static com.ruiyun.jvppeteer.common.Constant.MAIN_WORLD; -import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; -import static com.ruiyun.jvppeteer.common.Constant.STREAM; -import static com.ruiyun.jvppeteer.common.Constant.supportedMetrics; -import static com.ruiyun.jvppeteer.util.Helper.throwError; -import static com.ruiyun.jvppeteer.util.Helper.withSourcePuppeteerURLIfNone; - -public class Page extends EventEmitter { - private static final Logger LOGGER = LoggerFactory.getLogger(Page.class); - private volatile boolean closed = false; - private final TargetManager targetManager; - private CDPSession primaryTargetClient; - private Target primaryTarget; - private final CDPSession tabTargetClient; - private final Target tabTarget; - private final Keyboard keyboard; - private final Mouse mouse; - private final Touchscreen touchscreen; - private FrameManager frameManager; - private final EmulationManager emulationManager; - private final Tracing tracing; - private final Map bindings = new HashMap<>(); - private final Map exposedFunctions = new HashMap<>(); - private final Coverage coverage; - private Viewport viewport; - private final Map workers = new HashMap<>(); - private final Set> fileChooserResults = new HashSet<>(); - private final AwaitableResult sessionCloseResult = AwaitableResult.create(); - private boolean serviceWorkerBypassed = false; - private boolean userDragInterceptionEnabled = false; - - protected final TimeoutSettings _timeoutSettings = new TimeoutSettings(); - final Map, Consumer> requestHandlers = new WeakHashMap<>(); - private boolean isDragging; - - public Page(CDPSession client, Target target) { - super(); - this.primaryTargetClient = client; - this.tabTargetClient = client.parentSession(); - Objects.requireNonNull(this.tabTargetClient, "Tab target session is not defined."); - this.tabTarget = this.tabTargetClient.getTarget(); - Objects.requireNonNull(this.tabTarget, "Tab target is not defined."); - this.primaryTarget = target; - this.targetManager = target.targetManager(); - this.keyboard = new Keyboard(client); - this.mouse = new Mouse(client, this.keyboard); - this.touchscreen = new Touchscreen(client, this.keyboard); - this.frameManager = new FrameManager(client, this, this._timeoutSettings); - this.emulationManager = new EmulationManager(client); - this.tracing = new Tracing(client); - this.coverage = new Coverage(client); - this.viewport = null; - Map> frameManagerHandlers = Collections.unmodifiableMap(new HashMap>() {{ - put(FrameManager.FrameManagerEvent.FrameAttached, ((Consumer) (frame) -> Page.this.emit(PageEvent.FrameAttached, frame))); - put(FrameManager.FrameManagerEvent.FrameDetached, ((Consumer) (frame) -> Page.this.emit(PageEvent.FrameDetached, frame))); - put(FrameManager.FrameManagerEvent.FrameNavigated, ((Consumer) (frame) -> Page.this.emit(PageEvent.FrameNavigated, frame))); - put(FrameManager.FrameManagerEvent.ConsoleApiCalled, (Consumer) arg -> Page.this.onConsoleAPI((IsolatedWorld) arg[0], (ConsoleAPICalledEvent) arg[1])); - put(FrameManager.FrameManagerEvent.BindingCalled, (Consumer>) arg -> Page.this.onBindingCalled((IsolatedWorld) arg.get(0), (BindingCalledEvent) arg.get(1))); - }}); - frameManagerHandlers.forEach(this.frameManager::on); - - Map> networkManagerHandlers = Collections.unmodifiableMap(new HashMap>() {{ - put(NetworkManager.NetworkManagerEvent.Request, ((Consumer) (request) -> Page.this.emit(PageEvent.Request, request))); - put(NetworkManager.NetworkManagerEvent.RequestServedFromCache, ((Consumer) (request) -> Page.this.emit(PageEvent.RequestServedFromCache, request))); - put(NetworkManager.NetworkManagerEvent.Response, ((Consumer) (response) -> Page.this.emit(PageEvent.Response, response))); - put(NetworkManager.NetworkManagerEvent.RequestFailed, ((Consumer) (request) -> Page.this.emit(PageEvent.RequestFailed, request))); - put(NetworkManager.NetworkManagerEvent.RequestFinished, ((Consumer) (request) -> Page.this.emit(PageEvent.RequestFinished, request))); - }}); - networkManagerHandlers.forEach((key, value) -> this.frameManager.networkManager().on(key, value)); - - this.tabTargetClient.on(CDPSession.CDPSessionEvent.CDPSession_Swapped, (Consumer) Page.this::onActivation); - this.tabTargetClient.on(CDPSession.CDPSessionEvent.CDPSession_Ready, (Consumer) Page.this::onSecondaryTarget); - - Consumer onDetachedFromTarget = (cdpTarget) -> { - if (cdpTarget.session() == null) { - return; - } - String sessionId = cdpTarget.session().id(); - WebWorker webWorker = Page.this.workers.get(sessionId); - if (webWorker == null) { - return; - } - Page.this.workers.remove(sessionId); - Page.this.emit(PageEvent.WorkerDestroyed, webWorker); - }; - this.targetManager.on(TargetManager.TargetManagerEvent.TargetGone, onDetachedFromTarget); - this.tabTarget.setOnCloseRunner(() -> { - Page.this.targetManager.off(TargetManager.TargetManagerEvent.TargetGone, onDetachedFromTarget); - Page.this.emit(PageEvent.Close, true); - Page.this.closed = true; - }); - - this.setupPrimaryTargetListeners(); - this.attachExistingTargets(); - } - - @Override - public EventEmitter on(PageEvent type, Consumer handler) { - if (type != PageEvent.Request) { - return super.on(type, handler); - } - Consumer wrapper = this.requestHandlers.get(handler); - Consumer handlerWrapper = (Consumer) handler; - if (wrapper == null) { - wrapper = event -> event.enqueueInterceptAction(() -> handlerWrapper.accept(event)); - } - this.requestHandlers.put(handlerWrapper, wrapper); - return super.on(type, wrapper); - } - - @Override - public void off(PageEvent type, Consumer handler) { - if (type == PageEvent.Request) { - handler = this.requestHandlers.get(handler); - } - super.off(type, handler); - } - - private void attachExistingTargets() { - List childTargets = this.targetManager.getChildTargets(this.primaryTarget); - List queue = new ArrayList<>(childTargets); - int idx = 0; - while (idx < queue.size()) { - Target next = queue.get(idx); - idx++; - CDPSession session = next.session(); - if (session != null) { - this.onAttachedToTarget.accept(session); - } - queue.addAll(this.targetManager.getChildTargets(next)); - } - } - - private void onActivation(CDPSession newSession) { - this.primaryTargetClient = newSession; - this.primaryTarget = this.primaryTargetClient.getTarget(); - Objects.requireNonNull(this.primaryTarget, "Missing target on swap"); - this.keyboard.updateClient(newSession); - this.mouse.updateClient(newSession); - this.touchscreen.updateClient(newSession); - this.emulationManager.updateClient(newSession); - this.tracing.updateClient(newSession); - this.coverage.updateClient(newSession); - this.frameManager.swapFrameTree(newSession); - this.setupPrimaryTargetListeners(); - } - - /** - * 为主要目标设置侦听器。在导航到预置页面期间,主要目标可能会更改。 - */ - private void setupPrimaryTargetListeners() { - Map> sessionHandlers = Collections.unmodifiableMap(new HashMap>() {{ - put(CDPSession.CDPSessionEvent.CDPSession_Ready, (session) -> Page.this.onAttachedToTarget.accept((CDPSession) session)); - put(CDPSession.CDPSessionEvent.CDPSession_Disconnected, ((ignore) -> sessionCloseResult.onSuccess(new TargetCloseException("Target closed")))); - put(CDPSession.CDPSessionEvent.Page_domContentEventFired, ((ignore) -> Page.this.emit(PageEvent.Domcontentloaded, true))); - put(CDPSession.CDPSessionEvent.Page_loadEventFired, ((ignore) -> Page.this.emit(PageEvent.Load, true))); - put(CDPSession.CDPSessionEvent.Page_javascriptDialogOpening, ((Consumer) Page.this::onDialog)); - put(CDPSession.CDPSessionEvent.Runtime_exceptionThrown, (Consumer) Page.this::handleException); - put(CDPSession.CDPSessionEvent.Inspector_targetCrashed, (ignore) -> Page.this.onTargetCrashed()); - put(CDPSession.CDPSessionEvent.Performance_metrics, (Consumer) Page.this::emitMetrics); - put(CDPSession.CDPSessionEvent.Log_entryAdded, (Consumer) Page.this::onLogEntryAdded); - put(CDPSession.CDPSessionEvent.Page_fileChooserOpened, (Consumer) Page.this::onFileChooser); - }}); - sessionHandlers.forEach((eventName, handler) -> this.primaryTargetClient.on(eventName, handler)); - } - - private void onSecondaryTarget(CDPSession session) { - if (!"prerender".equals(session.getTarget().subtype())) { - return; - } - try { - this.frameManager.registerSpeculativeSession(session); - } catch (Exception e) { - LOGGER.error("frameManager registerSpeculativeSession error: ", e); - } - try { - this.emulationManager.registerSpeculativeSession(session); - } catch (Exception e) { - LOGGER.error("emulationManager registerSpeculativeSession error: ", e); - } - } - - /** - * 这里是WebSocketConnectReadThread 线程执行的方法,不能暂停!! - */ - private final Consumer onAttachedToTarget = (session) -> { - this.frameManager.onAttachedToTarget(session.getTarget()); - if ("worker".equals(session.getTarget().getTargetInfo().getType())) { - WebWorker webWorker = new WebWorker(session, session.getTarget().url(), session.getTarget().getTargetId(), session.getTarget().type(), Page.this::addConsoleMessage, Page.this::handleException); - this.workers.put(session.id(), webWorker); - this.emit(PageEvent.WorkerCreated, webWorker); - } - session.on(CDPSession.CDPSessionEvent.CDPSession_Ready, this.onAttachedToTarget); - }; - - private void initialize() { - try { - frameManager.initialize(this.primaryTargetClient, null); - Map params = new HashMap<>(); - this.primaryTargetClient.send("Performance.enable", params); - this.primaryTargetClient.send("Log.enable", params); - } catch (Exception e) { - if (e instanceof ProtocolException) { - LOGGER.error("initialize error: ", e); - } else { - throw e; - } - } - } - - private void onFileChooser(FileChooserOpenedEvent event) { - if (this.fileChooserResults.isEmpty()) { - return; - } - Frame frame = this.frameManager.frame(event.getFrameId()); - Objects.requireNonNull(frame, "This should never happen."); - IsolatedWorld mainWorld = frame.worlds().get(MAIN_WORLD); - ElementHandle handle = null; - try { - handle = mainWorld.adoptBackendNode(event.getBackendNodeId()).asElement(); - } catch (JsonProcessingException e) { - throwError(e); - } - FileChooser fileChooser = new FileChooser(handle, event); - for (AwaitableResult subject : this.fileChooserResults) { - subject.onSuccess(fileChooser); - } - this.fileChooserResults.clear(); - } - - public CDPSession client() { - return this.primaryTargetClient; - } - - public boolean isServiceWorkerBypassed() { - return this.serviceWorkerBypassed; - } - - /** - * 我们不再支持拦截拖动有效负载。使用 ElementHandle 上的新拖动 API 进行拖动(或仅使用 Page.mouse)。 - * - * @return 如果拖动事件被拦截,则为 true,否则为 false。 - */ - @Deprecated - public boolean isDragInterceptionEnabled() { - return this.userDragInterceptionEnabled; - } - - public boolean isJavaScriptEnabled() { - return this.emulationManager.getJavascriptEnabled(); - } - - /** - * 创建并返回一个可等待的文件选择器结果 - * 当需要拦截文件选择器对话框时,此方法将非常有用 - * - * @return 返回一个可等待的文件选择器结果,调用者可以通过这个对象来获取用户选择的文件 - */ - public AwaitableResult fileChooserWaitFor() { - AwaitableResult result = AwaitableResult.create(); - boolean needsEnable = this.fileChooserResults.isEmpty(); - this.fileChooserResults.add(result); - if (needsEnable) { - Map params = new HashMap<>(); - params.put("enabled", true); - this.primaryTargetClient.send("Page.setInterceptFileChooserDialog", params); - } - return result; - } - - /** - * 设置页面的地理位置

- * 考虑使用 {@link BrowserContext#overridePermissions(String, WebPermission...)} 授予页面读取其地理位置的权限。 - * - * @param options 地理位置具体信息 - */ - public void setGeolocation(GeolocationOptions options) { - this.emulationManager.setGeolocation(options); - } - - /** - * 创建此页面的目标。 - * - * @return 目标 - */ - public Target target() { - return this.primaryTarget; - } - - /** - * 返回页面隶属的浏览器 - * - * @return 浏览器实例 - */ - public Browser browser() { - return this.primaryTarget.browser(); - } - - /** - * 获取页面所属的浏览器上下文。 - * - * @return 浏览器上下文 - */ - public BrowserContext browserContext() { - return this.primaryTarget.browserContext(); - } - - private void onTargetCrashed() { - this.emit(PageEvent.Error, new JvppeteerException("Page crashed!")); - } - - private void onLogEntryAdded(EntryAddedEvent event) { - if (ValidateUtil.isNotEmpty(event.getEntry().getArgs())) - event.getEntry().getArgs().forEach(arg -> Helper.releaseObject(this.primaryTargetClient, arg)); - if (!"worker".equals(event.getEntry().getSource())) { - List locations = new ArrayList<>(); - locations.add(new ConsoleMessageLocation(event.getEntry().getUrl(), event.getEntry().getLineNumber())); - this.emit(PageEvent.Console, new ConsoleMessage(convertConsoleMessageLevel(event.getEntry().getLevel()), event.getEntry().getText(), Collections.emptyList(), locations)); - } - } - - /** - * 页面的主框架。 - * - * @return 主框架 - */ - public Frame mainFrame() { - return this.frameManager.mainFrame(); - } - - /** - * 虚拟键盘 - * - * @return 虚拟键盘 - */ - public Keyboard keyboard() { - return this.keyboard; - } - - /** - * 触控屏幕 - * - * @return 触控屏幕 - */ - public Touchscreen touchscreen() { - return this.touchscreen; - } - - public Coverage coverage() { - return this.coverage; - } - - public Tracing tracing() { - return this.tracing; - } - - /** - * 附加到页面的所有框架的数组。 - * - * @return iframe标签 - */ - public List frames() { - return this.frameManager.frames(); - } - - /** - * 该方法返回所有与页面关联的 WebWorkers - * - * @return WebWorkers - */ - public List workers() { - return new ArrayList<>(this.workers.values()); - } - - /** - * 启用请求拦截器,会激活 request.abort, request.continue 和 request.respond 方法。这提供了修改页面发出的网络请求的功能。

- * 一旦启用请求拦截,每个请求都将停止,除非它继续,响应或中止

- * - * @param value 是否启用请求拦截器 - */ - public void setRequestInterception(boolean value) { - this.frameManager.networkManager().setRequestInterception(value); - } - - /** - * 切换忽略每个请求的 Service Worker。 - * - * @param bypass 是否忽略 - */ - public void setBypassServiceWorker(boolean bypass) { - this.serviceWorkerBypassed = bypass; - Map params = new HashMap<>(); - params.put("bypass", bypass); - this.primaryTargetClient.send("Network.setBypassServiceWorker", params); - } - - /** - * 我们不再支持拦截拖动有效负载。使用 ElementHandle 上的新拖动 API 进行拖动(或仅使用 Page.mouse) - * - * @param enabled 是否启用 - */ - @Deprecated - public void setDragInterception(boolean enabled) { - this.userDragInterceptionEnabled = enabled; - Map params = new HashMap<>(); - params.put("enabled", enabled); - this.primaryTargetClient.send("Input.setInterceptDrags", params); - } - - /** - * 将网络连接设置为离线。 - *

- * 它不会改变 Page.emulateNetworkConditions() 中使用的参数 - * - * @param enabled 设置 true, 启用离线模式。 - */ - public void setOfflineMode(boolean enabled) { - this.frameManager.networkManager().setOfflineMode(enabled); - } - - /** - * 模拟网络条件 - *

- * 这不会影响 WebSocket 和 WebRTC PeerConnections(请参阅这里 )。 - *

- * 要将页面设置为离线,你可以使用 Page.setOfflineMode()。

- * 通过导入 PredefinedNetworkConditions 可以使用预定义网络条件列表。

- * - * @param networkConditions 传递 null 将禁用网络条件模拟。 - */ - public void emulateNetworkConditions(NetworkConditions networkConditions) { - this.frameManager.networkManager().emulateNetworkConditions(networkConditions); - } - - /** - * 此方法会改变下面几个方法的默认30秒等待时间: - * ${@link Page#goTo(String)} - * ${@link Page#goTo(String, GoToOptions, boolean)} - * ${@link Page#goBack(WaitForOptions)} - * ${@link Page#goForward(WaitForOptions)} - * ${@link Page#reload(WaitForOptions)} - * ${@link Page#setContent(String) } - * ${@link Page#waitForNavigation()} - * - * @param timeout 超时时间 - */ - - public void setDefaultNavigationTimeout(int timeout) { - this._timeoutSettings.setDefaultNavigationTimeout(timeout); - } - - public int getDefaultTimeout() { - return this._timeoutSettings.timeout(); - } - - /** - * 此方法会改变下面几个方法的默认30秒等待时间: - * ${@link Page#goTo(String)} - * ${@link Page#goTo(String, GoToOptions, boolean)} - * ${@link Page#goBack(WaitForOptions)} - * ${@link Page#goForward(WaitForOptions)} - * ${@link Page#reload(WaitForOptions)} - * ${@link Page#waitForNavigation()} - * - * @param timeout 超时时间 - */ - - public void setDefaultTimeout(int timeout) { - this._timeoutSettings.setDefaultTimeout(timeout); - } - - /** - * 此方法遍历js堆栈,找到所有带有指定原型的对象 - *

- * - * @param prototypeHandle 原型处理器 - * @return 代表页面元素的一个实例 - * @throws JsonProcessingException Json解析异常 - */ - public JSHandle queryObjects(JSHandle prototypeHandle) throws JsonProcessingException { - ValidateUtil.assertArg(!prototypeHandle.disposed(), "Prototype JSHandle is disposed!"); - ValidateUtil.assertArg(StringUtil.isNotEmpty(prototypeHandle.getRemoteObject().getObjectId()), "Prototype JSHandle must not be referencing primitive value"); - Map params = new HashMap<>(); - params.put("prototypeObjectId", prototypeHandle.getRemoteObject().getObjectId()); - JsonNode response = this.mainFrame().client().send("Runtime.queryObjects", params); - return this.mainFrame().mainRealm().createJSHandle(OBJECTMAPPER.treeToValue(response.get("objects"), RemoteObject.class)); - } - - /** - * 返回当前页面的cookies - * - * @return cookies - * @throws JsonProcessingException 如果处理JSON时发生错误 - */ - public List cookies() throws JsonProcessingException { - return this.cookies(this.url()); - } - - /** - * 根据提供的URL列表获取Cookies - *

- * 如果未指定 URL,则此方法返回当前页面 URL 的 cookie。如果指定了 URL,则仅返回这些 URL 的 cookie。 - *

- * - * @param urls URL列表,如果没有提供或为null,将使用当前页面的URL - * @return Cookie对象列表,表示请求的cookies - * @throws JsonProcessingException 如果处理JSON时发生错误 - */ - public List cookies(String... urls) throws JsonProcessingException { - if (urls == null || urls.length == 0) { - return new ArrayList<>(); - } - Map params = new HashMap<>(); - params.put("urls", urls); - JsonNode result = this.primaryTargetClient.send("Network.getCookies", params); - JsonNode cookiesNode = result.get("cookies"); - Iterator elements = cookiesNode.elements(); - List cookies = new ArrayList<>(); - while (elements.hasNext()) { - JsonNode cookieNode = elements.next(); - Cookie cookie = OBJECTMAPPER.treeToValue(cookieNode, Cookie.class); - cookies.add(cookie); - } - return cookies; - } - - /** - * 删除指定名称的cookies - *

- * 本方法接收一个或多个cookie名称,并创建对应的DeleteCookiesRequest对象列表, - * 然后调用deleteCookie方法删除这些cookies - * - * @param names 需要删除的cookie名称列表 - */ - public void deleteCookie(String... names) { - List cookies = new ArrayList<>(); - for (String name : names) { - cookies.add(new DeleteCookiesRequest(name)); - } - this.deleteCookie(cookies); - } - - /** - * 删除指定的cookies - * - * @param cookies 待删除的cookies请求列表,每个元素包含待删除的cookie信息 - */ - public void deleteCookie(List cookies) { - String pageURL = this.url(); - for (DeleteCookiesRequest cookie : cookies) { - if (StringUtil.isEmpty(cookie.getUrl()) && pageURL.startsWith("http")) { - cookie.setUrl(pageURL); - } - Map params = ParamsFactory.create(); - params.put("name", cookie.getName()); - params.put("url", cookie.getUrl()); - params.put("domain", cookie.getDomain()); - params.put("path", cookie.getPath()); - this.primaryTargetClient.send("Network.deleteCookies", params); - } - } - - public void setCookie(List cookies) { - if (cookies == null || cookies.isEmpty()) { - return; - } - String pageURL = this.url(); - boolean startsWithHTTP = pageURL.startsWith("http"); - cookies.replaceAll(cookie -> { - if (StringUtil.isEmpty(cookie.getUrl()) && startsWithHTTP) cookie.setUrl(pageURL); - ValidateUtil.assertArg(!ABOUT_BLANK.equals(cookie.getUrl()), "Blank page can not have cookie " + cookie.getName()); - if (StringUtil.isNotEmpty(cookie.getUrl())) { - ValidateUtil.assertArg(!cookie.getUrl().startsWith("data:"), "Data URL page can not have cookie " + cookie.getName()); - } - return cookie; - }); - List deleteCookiesParameters = new ArrayList<>(); - for (CookieParam cookie : cookies) { - deleteCookiesParameters.add(new DeleteCookiesRequest(cookie.getName(), cookie.getUrl(), cookie.getDomain(), cookie.getPath())); - } - this.deleteCookie(deleteCookiesParameters); - Map params = new HashMap<>(); - params.put("cookies", cookies); - this.primaryTargetClient.send("Network.setCookies", params); - } - - /** - * 该方法在页面的 window 对象上添加一个名为 name 的函数。 - *

- * 调用时,该函数会执行 pptrFunction,结果是 pptrFunction 的返回值。 - *

- * - * @param name 窗口对象上的函数名称 - * @param pptrFunction 将在 Puppeteer 上下文中调用的回调函数。 - * @throws JsonProcessingException 如果处理JSON时发生错误 - * @throws EvaluateException 如果在浏览器端执行函数时发生错误 - */ - public void exposeFunction(String name, BindingFunction pptrFunction) throws EvaluateException, JsonProcessingException { - ValidateUtil.assertArg(!this.bindings.containsKey(name), MessageFormat.format("Failed to add page binding with name {0}: window[{1}] already exists!", name, name)); - String source = Helper.evaluationString(addPageBinding(), "exposedFun", name, CDP_BINDING_PREFIX); - Binding binding = new Binding(name, pptrFunction, source); - this.bindings.put(name, binding); - NewDocumentScriptEvaluation response = this.frameManager.evaluateOnNewDocument(source); - this.frameManager.addExposedFunctionBinding(binding); - this.exposedFunctions.put(name, response.getIdentifier()); - } - - /** - * 该方法从页面的 window 对象中删除先前通过 Page.exposeFunction() 添加的名为 name 的函数。 - * - * @param name 要删除的函数名称 - * @throws JsonProcessingException 如果处理JSON时发生错误 - * @throws EvaluateException 如果在浏览器端执行函数时发生错误 - */ - public void removeExposedFunction(String name) throws JsonProcessingException, EvaluateException { - String exposedFunctionId = this.exposedFunctions.get(name); - if (exposedFunctionId == null) { - throw new JvppeteerException("Failed to remove page binding with name '" + name + "' window['" + name + "'] does not exists!"); - } - this.exposedFunctions.remove(name); - Binding binging = this.bindings.remove(name); - this.frameManager.removeScriptToEvaluateOnNewDocument(exposedFunctionId); - this.frameManager.removeExposedFunctionBinding(binging); - } - - /** - * 为HTTP authentication 提供认证凭据 。 - *

- * 传 null 禁用认证。 - *

- * 将在后台打开请求拦截以实现身份验证。这可能会影响性能。 - * - * @param credentials 验证信息 - */ - public void authenticate(Credentials credentials) { - this.frameManager.networkManager().authenticate(credentials); - } - - - /** - * 当前页面发起的每个请求都会带上这些请求头 - * 注意 此方法不保证请求头的顺序 - * - * @param headers 每个 HTTP 请求都会带上这些请求头。值必须是字符串 - */ - public void setExtraHTTPHeaders(Map headers) { - this.frameManager.networkManager().setExtraHTTPHeaders(headers); - } - - /** - * 给页面设置userAgent - * - * @param userAgent 此页面中使用的特定用户代理 - */ - public void setUserAgent(String userAgent) { - this.frameManager.networkManager().setUserAgent(userAgent, null); - } - - /** - * 给页面设置userAgent - * - * @param userAgent 此页面中使用的特定用户代理 - */ - public void setUserAgent(String userAgent, UserAgentMetadata userAgentMetadata) { - this.frameManager.networkManager().setUserAgent(userAgent, userAgentMetadata); - } - - /** - * 获取性能指标 - *

- * 本方法通过向目标客户端发送“Performance.getMetrics”请求来获取当前的性能指标 - *

- * 接收到的响应将被转换成GetMetricsResponse对象,然后用于构建并返回一个Metrics对象 - *

- * Metrics对象: - *

-     *
-     * Timestamp :获取指标样本时的时间戳。
-     *
-     * Documents:页面中的文档数。
-     *
-     * ¥Documents : Number of documents in the page.
-     *
-     * Frames:页面中的帧数。
-     *
-     * JSEventListeners:页面中的事件数。
-     *
-     * Nodes :页面中 DOM 节点的数量。
-     *
-     * LayoutCount:完整或部分页面布局的总数。
-     *
-     * RecalcStyleCount:页面样式重新计算的总数。
-     *
-     * LayoutDuration:所有页面布局的总持续时间。
-     *
-     * RecalcStyleDuration:所有页面样式重新计算的总持续时间。
-     *
-     * ScriptDuration :JavaScript 执行的总持续时间。
-     *
-     * TaskDuration :浏览器执行的所有任务的总持续时间。
-     *
-     * JSHeapUsedSize:使用的 JavaScript 堆大小。
-     *
-     * JSHeapTotalSize:JavaScript 堆总大小。
-     *  
- *

- * 所有时间戳都是单调时间:自过去任意点以来单调增加的时间(以秒为单位)。 - * - * @return Metrics对象,包含当前获取到的性能指标 - * @throws IllegalAccessException 当反射访问权限出现问题时抛出 - * @throws IntrospectionException 当自省操作出现问题时抛出 - * @throws InvocationTargetException 调用目标方法时,目标方法抛出异常 - * @throws JsonProcessingException 处理JSON时抛出异常 - */ - public Metrics metrics() throws IllegalAccessException, IntrospectionException, InvocationTargetException, JsonProcessingException { - GetMetricsResponse response = OBJECTMAPPER.treeToValue(this.primaryTargetClient.send("Performance.getMetrics"), GetMetricsResponse.class); - return this.buildMetricsObject(response.getMetrics()); - } - - private void emitMetrics(MetricsEvent event) { - PageMetrics pageMetrics = new PageMetrics(); - Metrics metrics = this.buildMetricsObject(event.getMetrics()); - pageMetrics.setMetrics(metrics); - pageMetrics.setTitle(event.getTitle()); - this.emit(PageEvent.Metrics, pageMetrics); - } - - private Metrics buildMetricsObject(List metrics) { - Metrics result = new Metrics(); - if (ValidateUtil.isNotEmpty(metrics)) { - for (Metric metric : metrics) { - if (supportedMetrics.contains(metric.getName())) { - switch (metric.getName()) { - case "Timestamp": - result.setTimestamp(metric.getValue()); - break; - case "Documents": - result.setDocuments(metric.getValue()); - break; - case "Frames": - result.setFrames(metric.getValue()); - break; - case "JSEventListeners": - result.setJSEventListeners(metric.getValue()); - break; - case "Nodes": - result.setNodes(metric.getValue()); - break; - case "LayoutCount": - result.setLayoutCount(metric.getValue()); - break; - case "RecalcStyleCount": - result.setRecalcStyleCount(metric.getValue()); - break; - case "LayoutDuration": - result.setLayoutDuration(metric.getValue()); - break; - case "RecalcStyleDuration": - result.setRecalcStyleDuration(metric.getValue()); - break; - case "ScriptDuration": - result.setScriptDuration(metric.getValue()); - break; - case "TaskDuration": - result.setTaskDuration(metric.getValue()); - break; - case "JSHeapUsedSize": - result.setJSHeapUsedSize(metric.getValue()); - break; - case "JSHeapTotalSize": - result.setJSHeapTotalSize(metric.getValue()); - break; - } - } - } - } - return result; - } - - private void handleException(ExceptionThrownEvent event) { - this.emit(PageEvent.PageError, Helper.createClientError(event.getExceptionDetails())); - } - - private void onConsoleAPI(IsolatedWorld world, ConsoleAPICalledEvent event) { - List values = new ArrayList<>(); - if (ValidateUtil.isNotEmpty(event.getArgs())) { - for (int i = 0; i < event.getArgs().size(); i++) { - RemoteObject arg = event.getArgs().get(i); - values.add(world.createJSHandle(arg)); - } - } - this.addConsoleMessage(convertConsoleMessageLevel(event.getType()), values, event.getStackTrace()); - } - - //不能阻塞 WebSocketConnectReadThread - private void addConsoleMessage(ConsoleMessageType type, List args, StackTrace stackTrace) { - if (this.listenerCount(PageEvent.Console) == 0) { - args.forEach(JSHandle::dispose); - return; - } - List textTokens = new ArrayList<>(); - for (JSHandle arg : args) { - RemoteObject remoteObject = arg.getRemoteObject(); - if (StringUtil.isNotEmpty(remoteObject.getObjectId())) { - textTokens.add(arg.toString()); - } else { - textTokens.add(Helper.valueFromRemoteObject(remoteObject) + ""); - } - } - List stackTraceLocations = new ArrayList<>(); - if (stackTrace != null) { - if (ValidateUtil.isNotEmpty(stackTrace.getCallFrames())) { - for (CallFrame callFrame : stackTrace.getCallFrames()) { - stackTraceLocations.add(new ConsoleMessageLocation(callFrame.getUrl(), callFrame.getLineNumber(), callFrame.getColumnNumber())); - } - } - } - ConsoleMessage message = new ConsoleMessage(type, String.join(" ", textTokens), args, stackTraceLocations); - this.emit(PageEvent.Console, message); - } - - /** - * 重新加载页面 - */ - public Response reload() { - WaitForOptions options = new WaitForOptions(); - options.setIgnoreSameDocumentNavigation(true); - return this.waitForNavigation(options, true); - } - - /** - * 重新加载页面 - * - * @param options 与${@link Page#goTo(String, GoToOptions)}中的options是一样的配置 - * @return 响应 - */ - public Response reload(WaitForOptions options) { - options.setIgnoreSameDocumentNavigation(true); - return this.waitForNavigation(options, true); - } - - /** - * 创建附加到页面的 Chrome Devtools 协议会话。 - * - * @return CDPSession - */ - public CDPSession createCDPSession() { - return this.target().createCDPSession(); - } - - /** - * 此方法导航到历史记录中的上一页 - * - * @return 如果存在多个重定向,导航将使用最后一个重定向的响应进行解析。如果无法返回,则解析为 null。 - */ - public Response goBack() throws JsonProcessingException { - return this.go(-1, new WaitForOptions()); - } - - /** - * 导航到页面历史的前一个页面 - *

- * options 导航配置,可选值: - *

otimeout 跳转等待时间,单位是毫秒, 默认是30秒, 传 0 表示无限等待。可以通过page.setDefaultNavigationTimeout(timeout)方法修改默认值 - *

owaitUntil 满足什么条件认为页面跳转完成,默认是load事件触发时。指定事件数组,那么所有事件触发后才认为是跳转完成。事件包括: - *

-     * oload - 页面的load事件触发时
-     * odomcontentloaded - 页面的DOMContentLoaded事件触发时
-     * onetworkidle0 - 不再有网络连接时触发(至少500毫秒后)
-     * onetworkidle2 - 只有2个网络连接时触发(至少500毫秒后)
-     * 
- * - * @param options 见上面注释 - * @return 响应 - * @throws JsonProcessingException 如果JSON解析失败 - */ - public Response goBack(WaitForOptions options) throws JsonProcessingException { - return this.go(-1, options); - } - - public Response goForward() throws JsonProcessingException { - return this.go(+1, new WaitForOptions()); - } - - /** - * 导航到页面历史的后一个页面。 - * options 导航配置,可选值: - *

otimeout 跳转等待时间,单位是毫秒, 默认是30秒, 传 0 表示无限等待。可以通过page.setDefaultNavigationTimeout(timeout)方法修改默认值 - *

owaitUntil 满足什么条件认为页面跳转完成,默认是load事件触发时。指定事件数组,那么所有事件触发后才认为是跳转完成。事件包括: - *

-     * oload - 页面的load事件触发时
-     * odomcontentloaded - 页面的DOMContentLoaded事件触发时
-     * onetworkidle0 - 不再有网络连接时触发(至少500毫秒后)
-     * onetworkidle2 - 只有2个网络连接时触发(至少500毫秒后)
-     * 
- * - * @param options 等待选项 - */ - public Response goForward(WaitForOptions options) throws JsonProcessingException { - return this.go(+1, options); - } - - private Response go(int delta, WaitForOptions options) throws JsonProcessingException { - JsonNode historyNode = this.primaryTargetClient.send("Page.getNavigationHistory"); - GetNavigationHistoryResponse history = OBJECTMAPPER.treeToValue(historyNode, GetNavigationHistoryResponse.class); - NavigationEntry entry = history.getEntries().get(history.getCurrentIndex() + delta); - if (entry == null) return null; - Map params = new HashMap<>(); - params.put("entryId", entry.getId()); - this.primaryTargetClient.send("Page.navigateToHistoryEntry", params, null, false); - return this.waitForNavigation(options); - } - - /** - * 相当于多个tab时,切换到某个tab。 - */ - public void bringToFront() { - this.primaryTargetClient.send("Page.bringToFront"); - } - - /** - * 是否在页面上启用 JavaScript。 - * - * @param enabled 是否启用 - */ - public void setJavaScriptEnabled(boolean enabled) { - this.emulationManager.setJavaScriptEnabled(enabled); - } - - /** - * 切换绕过页面的内容安全策略。

- * 注意:CSP 绕过发生在 CSP 初始化时而不是评估时。通常,这意味着应在导航到域之前调用 page.setBypassCSP。 - *

- * - * @param enabled 是否绕过 - */ - public void setBypassCSP(boolean enabled) { - Map params = new HashMap<>(); - params.put("enabled", enabled); - this.primaryTargetClient.send("Page.setBypassCSP", params); - } - - /** - * 改变页面的css媒体类型。支持的值仅包括 'screen', 'print' 和 null。传 null 禁用媒体模拟 - * - * @param type css媒体类型 - */ - public void emulateMediaType(MediaType type) { - this.emulationManager.emulateMediaType(type); - } - - /** - * 启用 CPU 限制以模拟慢速 CPU。 - * - * @param factor 减速系数(1 表示无油门,2 表示 2 倍减速,等等)。 - */ - public void emulateCPUThrottling(double factor) { - this.emulationManager.emulateCPUThrottling(factor); - } - - /** - * 模拟媒体特征 - * - * @param features 给定一组媒体特性对象,在页面上模拟 CSS 媒体特性,每个媒体特性对象的name必须符合正则表达式 : - * ^(?:prefers-(?:color-scheme|reduced-motion)|color-gamut)$" - */ - public void emulateMediaFeatures(List features) { - this.emulationManager.emulateMediaFeatures(features); - } - - /** - * 更改页面的时区,传null将禁用将时区仿真 - * 时区id列表 - * - * @param timezoneId 时区id - */ - public void emulateTimezone(String timezoneId) { - this.emulationManager.emulateTimezone(timezoneId); - } - - /** - * 模拟空闲状态。如果未设置参数,则清除空闲状态模拟 - * - * @param overrides 模拟空闲状态。如果未设置,则清除空闲覆盖 - */ - public void emulateIdleState(IdleOverridesState.Overrides overrides) { - this.emulationManager.emulateIdleState(overrides); - } - - /** - * 模拟页面上给定的视力障碍,不同视力障碍,截图有不同效果 - * - * @param type 视力障碍类型 - */ - public void emulateVisionDeficiency(VisionDeficiency type) { - this.emulationManager.emulateVisionDeficiency(type); - } - - /** - * 如果是一个浏览器多个页面的情况,每个页面都可以有单独的viewport - *

注意 在大部分情况下,改变 viewport 会重新加载页面以设置 isMobile 或者 hasTouch

- * - * @param viewport 设置的视图 - */ - public void setViewport(Viewport viewport) { - boolean needsReload = this.emulationManager.emulateViewport(viewport); - this.viewport = viewport; - if (needsReload) this.reload(new WaitForOptions()); - } - - /** - * 获取Viewport,Viewport各个参数的含义: - * width 宽度,单位是像素 - * height 高度,单位是像素 - * deviceScaleFactor 定义设备缩放, (类似于 dpr)。 默认 1。 - * isMobile 要不要包含meta viewport 标签。 默认 false。 - * hasTouch 指定终端是否支持触摸。 默认 false - * isLandscape 指定终端是不是 landscape 模式。 默认 false。 - * - * @return Viewport - */ - public Viewport viewport() { - return this.viewport; - } - - /** - * 在新dom产生之际执行给定的javascript - *

当你的js代码为函数时,type={@link EvaluateType#FUNCTION}

- *

当你的js代码为字符串时,type={@link EvaluateType#STRING}

- * - * @param pageFunction js代码 - * @param args 当你js代码是函数时,js函数的参数 - */ - public NewDocumentScriptEvaluation evaluateOnNewDocument(String pageFunction, Object... args) throws JsonProcessingException { - String source = Helper.evaluationString(pageFunction, args); - return this.frameManager.evaluateOnNewDocument(source); - } - - /** - * 在新dom产生之际执行给定的javascript - *

当你的js代码为函数时,type={@link EvaluateType#FUNCTION}

- *

当你的js代码为字符串时,type={@link EvaluateType#STRING}

- * - * @param pageFunction js代码 - * @param type 一般为PageEvaluateType#FUNCTION - * @param args 当你js代码是函数时,js函数的参数 - * @return NewDocumentScriptEvaluation 执行脚本的标志符 - * @throws JsonProcessingException json异常 - */ - public NewDocumentScriptEvaluation evaluateOnNewDocument(String pageFunction, EvaluateType type, Object... args) throws JsonProcessingException { - String source; - if (Objects.equals(EvaluateType.STRING, type)) { - ValidateUtil.assertArg(args.length == 0, "Cannot evaluate a string with arguments"); - source = pageFunction; - } else { - source = Helper.evaluationString(pageFunction, args); - } - return this.frameManager.evaluateOnNewDocument(source); - } - - /** - * 删除通过 Page.evaluateOnNewDocument 注入页面的脚本。 - * - * @param identifier 脚本标识符 - */ - public void removeScriptToEvaluateOnNewDocument(String identifier) { - Map identifierKeys = new HashMap<>(); - identifierKeys.put("identifier", identifier); - this.primaryTargetClient.send("Page.removeScriptToEvaluateOnNewDocument", identifierKeys); - } - - /** - * 根据启用状态切换忽略每个请求的缓存。默认情况下,缓存已启用。 - * - * @param enabled 设置缓存的 enabled 状态 - */ - public void setCacheEnabled(boolean enabled) { - this.frameManager.networkManager().setCacheEnabled(enabled); - } - - /** - *

截图

- * 备注 在OS X上 截图需要至少1/6秒。:查看讨论。 - * - * @param options 截图选项 - * @return 图片base64的字节 - */ - @SuppressWarnings({"unchecked"}) - public String screenshot(ScreenshotOptions options) { - synchronized (this.browserContext()) {//一个上下文只能有一个截图操作 - this.bringToFront(); - if (StringUtil.isNotEmpty(options.getPath())) { - String filePath = options.getPath(); - String path = filePath.substring(0, filePath.lastIndexOf('.') + 1).toLowerCase(); - options.setPath(path + options.getType().toString()); - } - if (options.getType().equals(ImageType.JPG)) { - options.setType(ImageType.JPEG); - } - if (options.getQuality() != null) { - ValidateUtil.assertArg(options.getQuality() > 0 && options.getQuality() <= 100, "Expected quality (" + options.getQuality() + ") to be between 0 and 100 ,inclusive)."); - ValidateUtil.assertArg(Arrays.asList("jpeg", "webp").contains(options.getType().name().toLowerCase()), options.getType().toString() + "screenshots do not support quality."); - } - - if (options.getClip() != null) { - ValidateUtil.assertArg(options.getClip().getWidth() > 0, "'width' in 'clip' must be positive."); - ValidateUtil.assertArg(options.getClip().getHeight() > 0, "'height' in 'clip' must be positive."); - } - Viewport fullViewport = null; - try { - if (options.getClip() != null) { - // If `captureBeyondViewport` is `false`, then we set the viewport to - // capture the full page. Note this may be affected by on-page CSS and - // JavaScript. - ValidateUtil.assertArg(!options.getFullPage(), "'clip' and 'fullPage' are mutually exclusive"); - options.setClip(roundRectangle(normalizeRectangle(options.getClip()))); - } else { - if (options.getFullPage()) { - if (!options.getCaptureBeyondViewport()) { - LinkedHashMap scrollDimensions = (LinkedHashMap) this.mainFrame().isolatedRealm().evaluate("() => {\n" + " const element = document.documentElement;\n" + " return {\n" + " width: element.scrollWidth,\n" + " height: element.scrollHeight,\n" + " };\n" + " }", null); - fullViewport = new Viewport(scrollDimensions.get("width"), scrollDimensions.get("height"), this.viewport.getDeviceScaleFactor(), this.viewport.getIsMobile(), this.viewport.getHasTouch(), this.viewport.getIsLandscape()); - this.setViewport(fullViewport); - } - } else { - options.setCaptureBeyondViewport(false); - } - } - return this._screenshot(options); - } catch (Exception e) { - LOGGER.error("_screenshot error: ", e); - } finally { - if (fullViewport != null) { - this.setViewport(this.viewport); - } - } - return ""; - } - - } - - private String _screenshot(ScreenshotOptions options) { - boolean isFirefox = this.target().targetManager() instanceof FirefoxTargetManager; - Map params = ParamsFactory.create(); - try { - if (!isFirefox && options.getOmitBackground() && (ImageType.PNG.equals(options.getType()) || ImageType.WEBP.equals(options.getType()))) { - this.emulationManager.setTransparentBackgroundColor(); - } - ScreenshotClip clip = options.getClip(); - if (clip != null && !options.getCaptureBeyondViewport()) { - Object response = this.mainFrame().isolatedRealm().evaluate("() => {\n" + " const {\n" + " height,\n" + " pageLeft: x,\n" + " pageTop: y,\n" + " width,\n" + " } = window.visualViewport;\n" + " return {x, y, height, width};\n" + " }", null); - JsonNode responseNode = OBJECTMAPPER.readTree(OBJECTMAPPER.writeValueAsString(response)); - clip = getIntersectionRect(clip, responseNode); - } - params.put("format", options.getType().toString()); - if (options.getOptimizeForSpeed()) { - params.put("optimizeForSpeed", options.getOptimizeForSpeed()); - } - if (options.getQuality() != null) { - params.put("quality", Math.round(options.getQuality())); - } - if (clip != null) { - params.put("clip", clip); - } - if (!options.getFromSurface()) { - params.put("fromSurface", options.getFromSurface()); - } - params.put("captureBeyondViewport", options.getCaptureBeyondViewport()); - JsonNode result = this.primaryTargetClient.send("Page.captureScreenshot", params); - String data = result.get(Constant.DATA).asText(); - byte[] buffer = Base64.getDecoder().decode(data); - if (StringUtil.isNotEmpty(options.getPath())) { - Files.write(Paths.get(options.getPath()), buffer, StandardOpenOption.CREATE, StandardOpenOption.WRITE); - } - return data; - } catch (Exception var) { - LOGGER.error("_screenshot error: ", var); - } finally { - if (options.getOmitBackground() && (ImageType.PNG.equals(options.getType()) || ImageType.WEBP.equals(options.getType()))) { - this.emulationManager.resetDefaultBackgroundColor(); - } - } - return null; - } - - /** - * @see href="https://w3c.github.io/webdriver-bidi/#rectangle-intersection - */ - private ScreenshotClip getIntersectionRect(ScreenshotClip clip, JsonNode viewport) { - double x = Math.max(clip.getX(), viewport.get("x").asDouble()); - double y = Math.max(clip.getY(), viewport.get("y").asDouble()); - return new ScreenshotClip(x, y, Math.max(Math.min(clip.getX() + clip.getWidth(), viewport.get("x").asDouble() + viewport.get("width").asDouble()) - x, 0), Math.max(Math.min(clip.getY() + clip.getHeight(), viewport.get("y").asDouble() + viewport.get("height").asDouble()) - y, 0), 1); - } - - private ScreenshotClip roundRectangle(ScreenshotClip clip) { - double x = Math.round(clip.getX()); - double y = Math.round(clip.getY()); - double width = Math.round(clip.getWidth() + clip.getX() - x); - double height = Math.round(clip.getHeight() + clip.getY() - y); - ScreenshotClip screenshotClip = new ScreenshotClip(x, y, width, height); - screenshotClip.setScale(clip.getScale()); - return screenshotClip; - } - - /** - * @see href="https://w3c.github.io/webdriver-bidi/#normalize-rect - */ - private ScreenshotClip normalizeRectangle(ScreenshotClip clip) { - double x; - double y; - double width; - double height; - if (clip.getWidth() < 0) { - x = clip.getX() + clip.getWidth(); - width = -clip.getWidth(); - } else { - x = clip.getX(); - width = clip.getWidth(); - } - if (clip.getHeight() < 0) { - y = clip.getY() + clip.getHeight(); - height = -clip.getHeight(); - } else { - y = clip.getY(); - height = clip.getHeight(); - } - ScreenshotClip copy = clip.copy(x, y, width, height); - copy.setScale(clip.getScale()); - return copy; - } - - /** - * 屏幕截图 - * - * @param path 截图文件全路径 - * @return base64编码后的图片数据 - */ - public String screenshot(String path) { - return this.screenshot(new ScreenshotOptions(path)); - } - - /** - * 生成当前页面的pdf格式,带着 pring css media。如果要生成带着 screen media的pdf,在page.pdf() 前面先调用 page.emulateMedia('screen') - *

注意 目前仅支持无头模式的 Chrome

- * - * @param options 选项 - * @return pdf文件的字节数组数据 - * @throws IOException IO异常 - */ - public byte[] pdf(PDFOptions options) throws IOException { - return this.pdf(options, LengthUnit.IN); - } - - /** - * 生成当前页面的pdf格式,带着 pring css media。如果要生成带着 screen media的pdf,在page.pdf() 前面先调用 page.emulateMedia('screen') - *

注意 目前仅支持无头模式的 Chrome

- * - * @param path pdf存放的路径 - * @throws IOException IO异常 - */ - public void pdf(String path) throws IOException { - this.pdf(new PDFOptions(path), LengthUnit.IN); - } - - /** - * 生成当前页面的pdf格式,带着 pring css media。如果要生成带着 screen media的pdf,在page.pdf() 前面先调用 page.emulateMedia('screen') - *

注意 目前仅支持无头模式的 Chrome

- * - * @param options 选项 - * @param lengthUnit 单位 - * @return pdf文件的字节数组数据 - * @throws IOException IO异常 - */ - public byte[] pdf(PDFOptions options, LengthUnit lengthUnit) throws IOException { - double paperWidth = 8.5; - double paperHeight = 11; - if (options.getFormat() != null) { - PaperFormats format = options.getFormat(); - paperWidth = format.getWidth(); - paperHeight = format.getHeight(); - } else { - Double width = convertPrintParameterToInches(options.getWidth(), lengthUnit); - if (width != null) { - paperWidth = width; - } - Double height = convertPrintParameterToInches(options.getHeight(), lengthUnit); - if (height != null) { - paperHeight = height; - } - } - PDFMargin margin = options.getMargin(); - Number marginTop, marginLeft, marginBottom, marginRight; - if ((marginTop = convertPrintParameterToInches(margin.getTop(), lengthUnit)) == null) { - marginTop = 0; - } - if ((marginLeft = convertPrintParameterToInches(margin.getLeft(), lengthUnit)) == null) { - marginLeft = 0; - } - if ((marginBottom = convertPrintParameterToInches(margin.getBottom(), lengthUnit)) == null) { - marginBottom = 0; - } - if ((marginRight = convertPrintParameterToInches(margin.getRight(), lengthUnit)) == null) { - marginRight = 0; - } - if (options.getOutline()) { - options.setTagged(true); - } - if (options.getOmitBackground()) { - this.emulationManager.setTransparentBackgroundColor(); - } - if (options.getWaitForFonts()) { - this.mainFrame().evaluate("() => { return document.fonts.ready;}"); - } - Map params = ParamsFactory.create(); - params.put("transferMode", "ReturnAsStream"); - params.put("landscape", options.getLandscape()); - params.put("displayHeaderFooter", options.getDisplayHeaderFooter()); - params.put("headerTemplate", options.getHeaderTemplate()); - params.put("footerTemplate", options.getFooterTemplate()); - params.put("printBackground", options.getPrintBackground()); - params.put("scale", options.getScale()); - params.put("paperWidth", paperWidth); - params.put("paperHeight", paperHeight); - params.put("marginTop", marginTop); - params.put("marginBottom", marginBottom); - params.put("marginLeft", marginLeft); - params.put("marginRight", marginRight); - params.put("pageRanges", options.getPageRanges()); - params.put("preferCSSPageSize", options.getPreferCSSPageSize()); - params.put("generateTaggedPDF", options.getTagged()); - params.put("generateDocumentOutline", options.getOutline()); - - JsonNode result = this.primaryTargetClient.send("Page.printToPDF", params); - - if (options.getOmitBackground()) { - this.emulationManager.resetDefaultBackgroundColor(); - } - JsonNode handle = result.get(STREAM); - ValidateUtil.assertArg(handle != null, "Page.printToPDF result has no stream handle. Please check your chrome version. result=" + result); - return Helper.readProtocolStream(this.primaryTargetClient, handle.asText(), options.getPath()); - - } - - /** - * page.close() 在 beforeunload 处理之前默认不执行 - *

注意 如果 runBeforeUnload 设置为true,可能会弹出一个 beforeunload 对话框。 这个对话框需要通过页面的 'dialog' 事件手动处理

- */ - public void close() { - this.close(false); - } - - public void close(boolean runBeforeUnload) { - synchronized (this.browserContext()) { - ValidateUtil.assertArg(this.primaryTargetClient.getConnection() != null, "Protocol error: Connection closed. Most likely the page has been closed."); - if (runBeforeUnload) { - this.primaryTargetClient.send("Page.close"); - } else { - Map params = new HashMap<>(); - params.put("targetId", this.primaryTarget.getTargetId()); - this.primaryTargetClient.getConnection().send("Target.closeTarget", params); - this.tabTarget.waitForTargetClose(); - } - } - } - - /** - * 表示页面是否被关闭。 - * - * @return 页面是否被关闭。 - */ - public boolean isClosed() { - return this.closed; - } - - public Mouse mouse() { - return mouse; - } - - /** - * 此方法通常与从 API(例如 WebBluetooth)触发设备请求的操作结合使用。

- * 提醒

- * 必须在发送设备请求之前调用此函数。它不会返回当前活动的设备提示。

- * 默认等待事件是30s - * - * @return DeviceRequestPrompt 设备请求 - */ - public DeviceRequestPrompt waitForDevicePrompt() { - return this.waitForDevicePrompt(this._timeoutSettings.timeout()); - } - - /** - * 此方法通常与从 API(例如 WebBluetooth)触发设备请求的操作结合使用。

- * 提醒

- * 必须在发送设备请求之前调用此函数。它不会返回当前活动的设备提示。

- * - * @param timeout 超时时间,默认是30s - * @return DeviceRequestPrompt - */ - public DeviceRequestPrompt waitForDevicePrompt(int timeout) { - return this.mainFrame().waitForDevicePrompt(timeout); - } - - /** - * 此方法找到一个匹配 selector 选择器的元素,如果需要会把此元素滚动到可视,然后通过 page.mouse 点击它。 如果选择器没有匹配任何元素,此方法将会报错。 - * 默认是阻塞的,会等待点击完成指令返回 - * - * @param selector 选择器 - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public void click(String selector) throws JsonProcessingException, EvaluateException { - this.click(selector, new ClickOptions()); - } - - - public void click(String selector, ClickOptions options) throws JsonProcessingException, EvaluateException { - this.mainFrame().click(selector, options); - } - - /** - * 此方法找到一个匹配selector的元素,并且把焦点给它。 如果没有匹配的元素,此方法将报错。 - * - * @param selector 要给焦点的元素的选择器selector。如果有多个匹配的元素,焦点给第一个元素。 - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public void focus(String selector) throws JsonProcessingException, EvaluateException { - this.mainFrame().focus(selector); - } - - /** - * 此方法找到一个匹配的元素,如果需要会把此元素滚动到可视,然后通过 page.mouse 来hover到元素的中间。 如果没有匹配的元素,此方法将会报错。 - * - * @param selector 要hover的元素的选择器。如果有多个匹配的元素,hover第一个。 - * @throws EvaluateException 函数执行错误 - * @throws JsonProcessingException JSON异常 - */ - public void hover(String selector) throws JsonProcessingException, EvaluateException { - this.mainFrame().hover(selector); - } - - /** - * 当提供的选择器完成选中后,触发change和input事件 如果没有元素匹配指定选择器,将报错。 - * - * @param selector 要查找的选择器 - * @param values 要选择的选项的值。如果 select 具有 multiple 属性,则考虑所有值,否则仅考虑第一个值。 - * @return 选择器集合 - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public List select(String selector, List values) throws JsonProcessingException, EvaluateException { - return this.mainFrame().select(selector, values); - } - - /** - * 此方法找到一个匹配的元素,如果需要会把此元素滚动到视图中,然后通过 page.touchscreen 来点击元素的中间位置 如果没有匹配的元素,此方法会报错 - * - * @param selector 要点击的元素的选择器。如果有多个匹配的元素,点击第一个 - * @throws EvaluateException 函数执行错误 - * @throws JsonProcessingException JSON异常 - */ - public void tap(String selector) throws JsonProcessingException, EvaluateException { - this.mainFrame().tap(selector); - } - - /** - * 每个字符输入后都会触发 keydown, keypress/input 和 keyup 事件 - *

要点击特殊按键,比如 Control 或 ArrowDown,用 keyboard.press

- * - * @param selector 要输入内容的元素选择器。如果有多个匹配的元素,输入到第一个匹配的元素。 - * @param text 要输入的内容 - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public void type(String selector, String text) throws JsonProcessingException, EvaluateException { - this.mainFrame().type(selector, text, 0); - } - - /** - * 每个字符输入后都会触发 keydown, keypress/input 和 keyup 事件 - *

要点击特殊按键,比如 Control 或 ArrowDown,用 keyboard.press

- * - * @param selector 要输入内容的元素选择器。如果有多个匹配的元素,输入到第一个匹配的元素。 - * @param text 要输入的内容 - * @param delay 每个字符输入的延迟,单位是毫秒。默认是 0。 - * @throws EvaluateException 函数执行错误 - * @throws JsonProcessingException JSON异常 - */ - public void type(String selector, String text, long delay) throws JsonProcessingException, EvaluateException { - this.mainFrame().type(selector, text, delay); - } - - /** - * 此方法在页面内执行 document.querySelector - *

- * 查找与选择器匹配的第一个元素,如果没有元素匹配指定选择器,返回值是 null。 - * - * @param selector 要查询页面的 selector。CSS 选择器 可以按原样传递,Puppeteer 特定的选择器语法 允许通过 text、a11y 角色和名称、xpath 和 跨影子根组合这些查询 进行查询。或者,你可以使用前缀 prefix 指定选择器类型。 - * @return 匹配的第一个元素 - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public ElementHandle $(String selector) throws JsonProcessingException, EvaluateException { - return this.mainFrame().$(selector); - } - - /** - * 此方法在页面内执行 document.querySelectorAll。如果没有元素匹配指定选择器,返回值是 []。 - * - * @param selector 选择器 - * @return ElementHandle集合 - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public List $$(String selector) throws JsonProcessingException, EvaluateException { - return this.mainFrame().$$(selector); - } - - /** - * 此方法和 page.evaluate 的唯一区别是此方法返回的是页内类型(JSHandle) - * - * @param pageFunction 要在页面实例上下文中执行的方法 - * @param args 要在页面实例上下文中执行的方法的参数 - * @return 代表页面元素的实例 - * @throws EvaluateException 函数执行错误 - * @throws JsonProcessingException JSON异常 - */ - public JSHandle evaluateHandle(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pageFunction); - return this.mainFrame().evaluateHandle(pageFunction, args); - } - - /** - * page.evaluate 和 page.evaluateHandle 之间的唯一区别是 evaluateHandle 将返回封装在页内对象中的值{@code JSHandle}。 - * - * @param pageFunction 要执行的字符串 - * @return JSHandle - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public JSHandle evaluateHandle(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluateHandle(pageFunction, null); - } - - public Object $eval(String selector, String pageFunction) throws JsonProcessingException, EvaluateException { - return this.$eval(selector, pageFunction, null); - } - - /** - * 此方法在页面内执行 document.querySelector,然后把匹配到的元素作为第一个参数传给 pageFunction。 - * - * @param selector 选择器 - * @param pageFunction 在浏览器实例上下文中要执行的方法 - * @param args 要传给 pageFunction 的参数。(比如你的代码里生成了一个变量,在页面中执行方法时需要用到,可以通过这个 args 传进去) - * @return pageFunction 的返回值 - * @throws EvaluateException 函数执行错误 - * @throws JsonProcessingException JSON异常 - */ - public Object $eval(String selector, String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("$eval", pageFunction); - return this.mainFrame().$eval(selector, pageFunction, args); - } - - /** - * 此方法在页面内执行 Array.from(document.querySelectorAll(selector)),然后把匹配到的元素数组作为第一个参数传给 pageFunction。 - * - * @param selector 一个框架选择器 - * @param pageFunction 在浏览器实例上下文中要执行的方法 - * @return pageFunction 的返回值 - * @throws JsonProcessingException JSON异常 - * @throws EvaluateException 函数执行错误 - */ - public Object $$eval(String selector, String pageFunction) throws JsonProcessingException, EvaluateException { - return this.$$eval(selector, pageFunction, new ArrayList<>()); - } - - /** - * 此方法在页面内执行 Array.from(document.querySelectorAll(selector)),然后把匹配到的元素数组作为第一个参数传给 pageFunction。 - * - * @param selector 一个框架选择器 - * @param pageFunction 在浏览器实例上下文中要执行的方法 - * @param args 要传给 pageFunction 的参数。(比如你的代码里生成了一个变量,在页面中执行方法时需要用到,可以通过这个 args 传进去) - * @return pageFunction 的返回值 - * @throws EvaluateException 函数执行错误 - * @throws JsonProcessingException JSON异常 - */ - public Object $$eval(String selector, String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("$$eval", pageFunction); - return this.mainFrame().$$eval(selector, pageFunction, args); - } - - /** - * 在当前文档的主要框架中添加脚本标签 - *

- * 此方法封装了在当前文档的主要框架中添加一个脚本标签的操作它委托 - * {@link Frame#addScriptTag(FrameAddScriptTagOptions)} 方法来执行实际的操作 - * - * @param options 脚本标签的配置选项,包含了脚本标签的各类属性如URL、位置等 - * @return 返回新添加的脚本标签的元素句柄 - * @throws IOException 当网络通信失败时抛出此异常 - * @throws EvaluateException 在页面中执行JavaScript时发生错误时抛出此异常 - */ - public ElementHandle addScriptTag(FrameAddScriptTagOptions options) throws IOException, EvaluateException { - return this.mainFrame().addScriptTag(options); - } - - - /** - * 将 link rel="stylesheet" 标记添加到具有所需 URL 的页面中,或将 style type="text/css" 标记添加到内容中。 - * - * @param options link标签 - * @return 注入完成的tag标签。当style的onload触发或者代码被注入到frame。 - * @throws IOException 异常 - * @throws EvaluateException 在页面中执行JavaScript时发生错误时抛出此异常 - */ - public ElementHandle addStyleTag(FrameAddStyleTagOptions options) throws IOException, EvaluateException { - return this.mainFrame().addStyleTag(options); - } - - /** - * 返回页面的地址 - * - * @return 页面地址 - */ - public String url() { - return this.mainFrame().url(); - } - - /** - * 获取当前页面的内容 - *

- * 此方法通过调用 mainFrame 的 content 方法来实现 - * 它封装了 mainFrame 的内容获取逻辑,以便在需要时统一处理可能的变化 - * - * @return 当前页面的内容 - * @throws JsonProcessingException 如果在处理 JSON 时发生错误 - * @throws EvaluateException 在页面中执行JavaScript时发生错误时抛出此异常 - */ - public String content() throws JsonProcessingException, EvaluateException { - return this.mainFrame().content(); - } - - /** - * 给页面设置html - * - * @param html 分派给页面的HTML。 - * @throws JsonProcessingException 如果在处理 JSON 时发生错误 - * @throws EvaluateException 在页面中执行JavaScript时发生错误时抛出此异常 - */ - public void setContent(String html) throws JsonProcessingException, EvaluateException { - this.setContent(html, new WaitForOptions()); - } - - /** - * 给页面设置html - * - * @param html 分派给页面的HTML。 - * @param options timeout 加载资源的超时时间,默认值为30秒,传入0禁用超时. 可以使用 page.setDefaultNavigationTimeout(timeout) 或者 page.setDefaultTimeout(timeout) 方法修改默认值 - * waitUntil HTML设置成功的标志事件, 默认为 load。 如果给定的是一个事件数组,那么当所有事件之后,给定的内容才被认为设置成功。 事件可以是: - * load - load事件触发后,设置HTML内容完成。 - *

domcontentloaded - DOMContentLoaded 事件触发后,设置HTML内容完成。

- *

networkidle0 - 不再有网络连接时(至少500毫秒之后),设置HTML内容完成。

- *

networkidle2 - 只剩2个网络连接时(至少500毫秒之后),设置HTML内容完成。

- * @throws EvaluateException 在页面中执行JavaScript时发生错误时抛出此异常 - * @throws JsonProcessingException JSON异常 - */ - public void setContent(String html, WaitForOptions options) throws JsonProcessingException, EvaluateException { - this.mainFrame().setContent(html, options); - } - - /** - *

导航到指定的url,可以配置是否阻塞,可以配合下面这个方法使用,但是不限于这个方法

- * {@link Page#waitForResponse(String)} - * 因为如果不阻塞的话,页面在加载完成时,waitForResponse等waitFor方法会接受不到结果而抛出超时异常 - * - * @param url 导航的地址 - * @param isBlock true代表阻塞 - * @return 不阻塞的话返回null - */ - public Response goTo(String url, boolean isBlock) { - return this.goTo(url, new GoToOptions(), isBlock); - } - - /** - *

导航到指定的url - *

以下情况此方法将报错: - *

发生了 SSL 错误 (比如有些自签名的https证书). - *

目标地址无效 - *

超时 - *

主页面不能加载 - * - * @param url url - * @param options

timeout 跳转等待时间,单位是毫秒, 默认是30秒, 传 0 表示无限等待。可以通过page.setDefaultNavigationTimeout(timeout)方法修改默认值 - *

waitUntil 满足什么条件认为页面跳转完成,默认是 load 事件触发时。指定事件数组,那么所有事件触发后才认为是跳转完成。事件包括: - *

load - 页面的load事件触发时 - *

domcontentloaded - 页面的 DOMContentLoaded 事件触发时 - *

networkidle0 - 不再有网络连接时触发(至少500毫秒后) - *

networkidle2 - 只有2个网络连接时触发(至少500毫秒后) - *

referer Referer header value. If provided it will take preference over the referer header value set by page.setExtraHTTPHeaders(). - * @return Response - */ - public Response goTo(String url, GoToOptions options) { - return this.goTo(url, options, true); - } - - /** - *

导航到指定的url - *

以下情况此方法将报错: - *

发生了 SSL 错误 (比如有些自签名的https证书). - *

目标地址无效 - *

超时 - *

主页面不能加载 - * - * @param url url - * @param options

timeout 跳转等待时间,单位是毫秒, 默认是30秒, 传 0 表示无限等待。可以通过page.setDefaultNavigationTimeout(timeout)方法修改默认值 - *

waitUntil 满足什么条件认为页面跳转完成,默认是 load 事件触发时。指定事件数组,那么所有事件触发后才认为是跳转完成。事件包括: - *

load - 页面的load事件触发时 - *

domcontentloaded - 页面的 DOMContentLoaded 事件触发时 - *

networkidle0 - 不再有网络连接时触发(至少500毫秒后) - *

networkidle2 - 只有2个网络连接时触发(至少500毫秒后) - *

referer Referer header value. If provided it will take preference over the referer header value set by page.setExtraHTTPHeaders(). - * @param isBlock 是否阻塞,不阻塞代表只是发导航命令出去,并不等待导航结果,同时也不会抛异常 - * @return Response - */ - public Response goTo(String url, GoToOptions options, boolean isBlock) { - return this.mainFrame().goTo(url, options, isBlock); - } - - /** - * 创建一个page对象 - * - * @param client 与页面通讯的客户端 - * @param target 目标 - * @param viewport 视图 - * @return 页面实例 - */ - public static Page create(CDPSession client, Target target, Viewport viewport) { - Page page = new Page(client, target); - page.initialize(); - if (viewport != null) { - page.setViewport(viewport); - } - return page; - } - - /** - * 导航到某个网站 - *

以下情况此方法将报错:

- *

发生了 SSL 错误 (比如有些自签名的https证书).

- *

目标地址无效

- *

超时

- *

主页面不能加载

- * - * @param url 导航到的地址. 地址应该带有http协议, 比如 https://. - * @return 响应 - */ - public Response goTo(String url) { - return this.goTo(url, true); - } - - /** - * 此方法在页面跳转到一个新地址或重新加载时解析,如果你的代码会间接引起页面跳转,这个方法比较有用 - *

比如你在在代码中使用了Page.click()方法,引起了页面跳转 - * 注意 通过 History API 改变地址会认为是一次跳转。 - * - * @return 响应 - */ - public Response waitForNavigation() { - return this.mainFrame().waitForNavigation(new WaitForOptions(), false); - } - - /** - * 此方法在页面跳转到一个新地址或重新加载时解析,如果你的代码会间接引起页面跳转,这个方法比较有用 - *

比如你在在代码中使用了Page.click()方法,引起了页面跳转 - * 注意 通过 History API 改变地址会认为是一次跳转。 - * - * @param options 可选的等待选项 - * @return 响应 - */ - public Response waitForNavigation(WaitForOptions options, boolean reload) { - return this.mainFrame().waitForNavigation(options, reload); - } - - /** - * 此方法在页面跳转到一个新地址或重新加载时解析,如果你的代码会间接引起页面跳转,这个方法比较有用 - *

比如你在在代码中使用了Page.click()方法,引起了页面跳转 - * 注意 通过 History API 改变地址会认为是一次跳转。 - * - * @param options 可选的等待选项 - * @return 响应 - */ - public Response waitForNavigation(WaitForOptions options) { - return this.mainFrame().waitForNavigation(options, false); - } - - /** - * 等到某个请求,url或者predicate只有有一个不为空,默认等待时间是30s - * - * @param url 等待的请求 - * @return 要等到的请求 - */ - public Request waitForRequest(String url) { - ValidateUtil.assertArg(StringUtil.isNotEmpty(url), "waitForRequest url must not be empty"); - return this.waitForRequest(url, null, this._timeoutSettings.timeout()); - } - - /** - * 等到某个请求,url或者predicate只有有一个不为空 - *

当url不为空时, type = PageEvaluateType.STRING

- *

当predicate不为空时, type = PageEvaluateType.FUNCTION

- * - * @param url 等待的请求 - * @param predicate 方法 - * @param timeout 超时时间 - * @return 要等到的请求 - */ - public Request waitForRequest(String url, Predicate predicate, int timeout) { - if (timeout < 0) { - timeout = this._timeoutSettings.timeout(); - } - AtomicReference result = new AtomicReference<>(); - Predicate requestPredicate = request -> { - if (StringUtil.isNotEmpty(url)) { - return url.equals(request.url()); - } else if (predicate != null) { - return predicate.test(request); - } - return false; - }; - AtomicReference targetCloseException = new AtomicReference<>(); - Consumer targetCloseListener = (ignore) -> targetCloseException.set(new TargetCloseException("Page closed!")); - this.once(PageEvent.Close, targetCloseListener); - Consumer requestListener = request -> { - if (requestPredicate.test(request)) { - result.set(request); - } - }; - this.on(PageEvent.Request, requestListener); - Supplier conditionChecker = () -> { - if (targetCloseException.get() != null) { - throw targetCloseException.get(); - } - return result.get() == null ? null : true; - }; - try { - Helper.waitForCondition(conditionChecker, timeout, "WaitForRequest timeout of " + timeout + " ms exceeded"); - } finally { - this.off(PageEvent.Request, requestListener); - this.off(PageEvent.Close, targetCloseListener); - } - return result.get(); - } - - /** - * 等到某个请求,默认等待的时间是30s - * - * @param predicate 判断具体某个请求 - * @return 要等到的请求 - */ - public Response waitForResponse(Predicate predicate) { - return this.waitForResponse(null, predicate); - } - - /** - * 等到某个请求,默认等待的时间是30s - * - * @param url 等待的请求 - * @return 要等到的请求 - */ - public Response waitForResponse(String url) { - return this.waitForResponse(url, null, this._timeoutSettings.timeout()); - } - - /** - * 等到某个请求,url或者predicate只有有一个不为空,默认等待的时间是30s - *

当url不为空时, type = PageEvaluateType.STRING

- *

当predicate不为空时, type = PageEvaluateType.FUNCTION

- * - * @param url 等待的请求 - * @param predicate 方法 - * @return 要等到的请求 - */ - public Response waitForResponse(String url, Predicate predicate) { - return this.waitForResponse(url, predicate, this._timeoutSettings.timeout()); - } - - /** - * 等到某个请求,url或者predicate只有有一个不为空 - *

当url不为空时, type = PageEvaluateType.STRING

- *

当predicate不为空时, type = PageEvaluateType.FUNCTION

- * - * @param url 等待的请求 - * @param predicate 方法 - * @param timeout 超时时间 - * @return 要等到的请求 - */ - public Response waitForResponse(String url, Predicate predicate, int timeout) { - if (timeout <= 0) timeout = this._timeoutSettings.timeout(); - Predicate predi = response -> { - if (StringUtil.isNotEmpty(url)) { - return url.equals(response.url()); - } else if (predicate != null) { - return predicate.test(response); - } - return false; - }; - AtomicReference result = new AtomicReference<>(); - Consumer responseListener = response -> { - if (predi.test(response)) { - result.set(response); - } - }; - this.on(PageEvent.Response, responseListener); - AtomicReference targetCloseException = new AtomicReference<>(); - this.once(PageEvent.Close, (s) -> targetCloseException.set(new TargetCloseException("Page closed!"))); - Supplier conditonChecker = () -> { - if (targetCloseException.get() != null) { - throw targetCloseException.get(); - } - return result.get() == null ? null : true; - }; - try { - Helper.waitForCondition(conditonChecker, timeout, "WaitForResponse timeout of " + timeout + " ms exceeded"); - } finally { - this.off(PageEvent.Response, responseListener); - } - return result.get(); - } - - /** - * 等待匹配给定条件的帧出现。 - *

- * 默认超时时间是30s - * - * @param url 匹配帧的url - * @return 匹配的帧 - */ - public Frame waitForFrame(String url) { - return this.waitForFrame(url, null, Constant.DEFAULT_TIMEOUT); - } - - /** - * 等待匹配给定条件的帧出现。 - *

- * 默认超时时间是30s - * - * @param framePredicate 匹配的表达式 - * @return 匹配的帧 - */ - public Frame waitForFrame(Predicate framePredicate) { - return this.waitForFrame(null, framePredicate, Constant.DEFAULT_TIMEOUT); - } - - /** - * 等待匹配给定条件的帧出现。 - * - * @param url 等待的url - * @param framePredicate 匹配的规则 - * @param timeout 超时时间 - * @return 匹配的帧 - */ - public Frame waitForFrame(String url, Predicate framePredicate, int timeout) { - if (timeout <= 0) timeout = this._timeoutSettings.timeout(); - Predicate predicate = frame -> { - if (StringUtil.isNotEmpty(url)) { - return url.equals(frame.url()); - } else if (framePredicate != null) { - return framePredicate.test(frame); - } - return false; - }; - AtomicReference targetCloseException = new AtomicReference<>(); - this.once(PageEvent.Close, (s) -> targetCloseException.set(new TargetCloseException("Page closed!"))); - Supplier conditionChecker = () -> { - if (targetCloseException.get() != null) { - throw targetCloseException.get(); - } - return Helper.filter(this.frames(), predicate); - }; - return Helper.waitForCondition(conditionChecker, timeout, "WaitForFrame timeout of " + timeout + " ms exceeded"); - } - - /** - * 执行一段 JavaScript代码 - * - * @param pageFunction 要执行的字符串 - * @return pageFunction执行结果 - * @throws EvaluateException 运行JS代码时,可能抛出的异常 - * @throws JsonProcessingException json序列化异常 - */ - public Object evaluate(String pageFunction) throws JsonProcessingException, EvaluateException { - return this.evaluate(pageFunction, null); - } - - /** - * 执行一段 JavaScript代码 - * - * @param pageFunction 要执行的字符串 - * @param args 如果pageFunction 是 Javascript 函数的话,args就是函数上的参数 - * @return pageFunction执行结果 - * @throws EvaluateException 运行JS代码时,可能抛出的异常 - * @throws JsonProcessingException json序列化异常 - */ - public Object evaluate(String pageFunction, List args) throws JsonProcessingException, EvaluateException { - pageFunction = withSourcePuppeteerURLIfNone("evaluate", pageFunction); - return this.mainFrame().evaluate(pageFunction, args); - } - - private void onBindingCalled(IsolatedWorld world, BindingCalledEvent event) { - String payloadStr = event.getPayload(); - BindingPayload payload; - try { - payload = OBJECTMAPPER.readValue(payloadStr, BindingPayload.class); - } catch (JsonProcessingException e) { - return; - } - if (!"exposedFun".equals(payload.getType())) { - return; - } - if (world.context() == null) { - return; - } - Binding binding = this.bindings.get(payload.getName()); - Optional.ofNullable(binding).ifPresent(b -> b.run(world.context(), payload.getSeq(), payload.getArgs(), payload.getIsTrivial())); - } - - /** - * 当js对话框出现的时候触发,比如alert, prompt, confirm 或者 beforeunload。Puppeteer可以通过Dialog's accept 或者 dismiss来响应弹窗。 - * - * @param event 触发事件 - */ - private void onDialog(JavascriptDialogOpeningEvent event) { - Dialog dialog = new Dialog(this.primaryTargetClient, event.getType(), event.getMessage(), event.getDefaultPrompt()); - this.emit(PageEvent.Dialog, dialog); - } - - /** - * 根据启用状态切换忽略每个请求的缓存。默认情况下,缓存已启用。 - */ - private String addPageBinding() { - return "function addPageBinding(type, name, prefix) {\n" + - "\n" + - "\n" + - "\n" + - "\n" + - " if (globalThis[name]) {\n" + - " return;\n" + - " }\n" + - "\n" + - " Object.assign(globalThis, {\n" + - " [name](...args) {\n" + - "\n" + - "\n" + - " const callPuppeteer = globalThis[name];\n" + - " callPuppeteer.args ??= new Map();\n" + - " callPuppeteer.callbacks ??= new Map();\n" + - " const seq = (callPuppeteer.lastSeq ?? 0) + 1;\n" + - " callPuppeteer.lastSeq = seq;\n" + - " callPuppeteer.args.set(seq, args);\n" + - "\n" + - "\n" + - " globalThis[prefix + name](JSON.stringify({\n" + - " type,\n" + - " name,\n" + - " seq,\n" + - " args,\n" + - " isTrivial: !args.some(value => {\n" + - " return value instanceof Node;\n" + - " }),\n" + - " }));\n" + - " return new Promise((resolve, reject) => {\n" + - " callPuppeteer.callbacks.set(seq, {\n" + - " resolve(value) {\n" + - " callPuppeteer.args.delete(seq);\n" + - " resolve(value);\n" + - " },\n" + - " reject(value) {\n" + - " callPuppeteer.args.delete(seq);\n" + - " reject(value);\n" + - " },\n" + - " });\n" + - " });\n" + - " },\n" + - " });\n" + - "}"; - } - - /** - * 等待指定的选择器匹配的元素出现在页面中,如果调用此方法时已经有匹配的元素,那么此方法立即返回。 如果指定的选择器在超时时间后扔不出现,此方法会报错。 - * - * @param selector 要等待的元素选择器 - * @return ElementHandle - */ - public ElementHandle waitForSelector(String selector) throws JsonProcessingException { - return this.waitForSelector(selector, new WaitForSelectorOptions()); - } - - /** - * 等待指定的选择器匹配的元素出现在页面中,如果调用此方法时已经有匹配的元素,那么此方法立即返回。 如果指定的选择器在超时时间后扔不出现,此方法会报错。 - * - * @param selector 要等待的元素选择器 - * @param options 可选参数 - * @return ElementHandle 返回的handle - */ - public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) throws JsonProcessingException { - return this.mainFrame().waitForSelector(selector, options); - } - - /** - * 等待提供的函数 pageFunction 在页面上下文中计算时返回真值。 - * - * @param pageFunction 将在浏览器上下文中评估函数,直到返回真值。 - * @return JSHandle 指定的页面元素 对象 - */ - public JSHandle waitForFunction(String pageFunction) { - return this.waitForFunction(pageFunction, new WaitForSelectorOptions()); - } - - - /** - * 等待提供的函数 pageFunction 在页面上下文中计算时返回真值。 - * - * @param pageFunction 将在浏览器上下文中评估函数,直到返回真值。 - * @param options 等待函数的选项,包括超时设置等 - * @param args 传递给函数的参数,可以是任意类型 - * @return JSHandle 返回函数执行结果的JSHandle对象 - */ - public JSHandle waitForFunction(String pageFunction, WaitForSelectorOptions options, Object... args) { - return this.mainFrame().waitForFunction(pageFunction, options, args == null ? null : Arrays.asList(args)); - } - - private static final Map unitToPixels = new HashMap() { - private static final long serialVersionUID = -4861220887908575532L; - - { - put("px", 1.00); - put("in", 96.00); - put("cm", 37.8); - put("mm", 3.78); - } - }; - - private Double convertPrintParameterToInches(String parameter, LengthUnit lengthUnit) { - if (StringUtil.isEmpty(parameter)) { - return null; - } - double pixels; - if (Helper.isNumber(parameter)) { - pixels = Double.parseDouble(parameter); - } else if (parameter.endsWith("px") || parameter.endsWith("in") || parameter.endsWith("cm") || parameter.endsWith("mm")) { - String unit = parameter.substring(parameter.length() - 2).toLowerCase(); - String valueText; - if (unitToPixels.containsKey(unit)) { - valueText = parameter.substring(0, parameter.length() - 2); - } else { - // In case of unknown unit try to parse the whole parameter as number of pixels. - // This is consistent with phantom's paperSize behavior. - unit = "px"; - valueText = parameter; - } - double value = Double.parseDouble(valueText); - ValidateUtil.assertArg(!Double.isNaN(value), "Failed to parse parameter value: " + parameter); - pixels = value * unitToPixels.get(unit); - } else { - throw new IllegalArgumentException("page.pdf() Cannot handle parameter type: " + parameter); - } - return pixels / unitToPixels.get(lengthUnit.getValue()); - } - - private ConsoleMessageType convertConsoleMessageLevel(String method) { - if ("warning".equals(method)) { - return ConsoleMessageType.WARN; - } - return ConsoleMessageType.valueOf(method.toUpperCase()); - } - - /** - * 获取当前主框架的标题 - *

- * 该方法通过调用主框架的title方法来获取页面标题如果在处理JSON数据时发生错误,该方法将抛出JsonProcessingException异常 - * - * @return 当前主框架的标题 - * @throws JsonProcessingException 如果在处理JSON数据时发生错误 - */ - public String title() throws JsonProcessingException { - return this.mainFrame().title(); - } - - /** - * 模拟各种设备打开浏览器 - *

- * 所有录制内容均为 WebM 格式,使用 VP9 视频编解码器。FPS 为 30。 - *

- * 你的系统上必须安装有 ffmpeg。 - * - * @param device 设备类型 - */ - public void emulate(Device device) { - this.setUserAgent(device.getUserAgent()); - this.setViewport(device.getViewport()); - } - - public void setIsDragging(boolean isDragging) { - this.isDragging = isDragging; - } - - public boolean isDragging() { - return this.isDragging; - } - - public Accessibility accessibility() { - return this.mainFrame().accessibility(); - } - - AtomicLong screencastSessionCount = new AtomicLong(0); - private volatile boolean startScreencasted = false; - - /** - * 捕获此 page 的截屏视频。可录制为webm和gif - * - * @param options 配置截屏行为 - * @return ScreenRecorder 屏幕录制实例,用于停止录制 - * @throws IOException IO异常 - */ - public ScreenRecorder screencast(ScreencastOptions options) throws IOException { - ValidateUtil.assertArg(StringUtil.isNotBlank(options.getPath()), "Path must be specified"); - if (options.getFormat() != null) { - ValidateUtil.assertArg(options.getPath().endsWith(options.getFormat().getFormat()), "Extension of Path (" + options.getPath() + ")+ has to match the used output format (" + options.getFormat().getFormat() + ")."); - } - Viewport defaultViewport = this.viewport(); - Viewport tempViewport = null; - if (defaultViewport != null && defaultViewport.getDeviceScaleFactor() != 0) { - tempViewport = new Viewport(defaultViewport.getWidth(), defaultViewport.getHeight(), 0.00, defaultViewport.getIsMobile(), defaultViewport.getHasTouch(), defaultViewport.getIsLandscape()); - this.setViewport(tempViewport); - } - ArrayList response = (ArrayList) this.mainFrame().isolatedRealm().evaluate("() => {\n" + - " return [\n" + - " window.visualViewport.width * window.devicePixelRatio,\n" + - " window.visualViewport.height * window.devicePixelRatio,\n" + - " window.devicePixelRatio,\n" + - " ]\n" + - " }"); - double width = Double.parseDouble(response.get(0) + ""); - double height = Double.parseDouble(response.get(1) + ""); - double devicePixelRatio = Double.parseDouble(response.get(2) + ""); - BoundingBox crop = null; - if (Objects.nonNull(options.getCrop())) { - BoundingBox boundingBox = roundRectangle(normalizeRectangle(new ScreenshotClip(options.getCrop().getX(), options.getCrop().getY(), options.getCrop().getWidth(), options.getCrop().getHeight()))); - if (boundingBox.getX() < 0 || boundingBox.getY() < 0) { - throw new JvppeteerException("crop.x and crop.y must be greater than or equal to 0."); - } - if (boundingBox.getWidth() <= 0 || boundingBox.getHeight() <= 0) { - throw new JvppeteerException("crop.width and crop.height must be greater than 0."); - } - - double viewportWidth = width / devicePixelRatio; - double viewportHeight = height / devicePixelRatio; - if (boundingBox.getX() + boundingBox.getWidth() > viewportWidth) { - throw new JvppeteerException( - "crop.width cannot be larger than the viewport width(" + viewportWidth + ")"); - } - if (boundingBox.getY() + boundingBox.getHeight() > viewportHeight) { - throw new JvppeteerException( - "crop.height cannot be larger than the viewport width(" + viewportHeight + ")"); - } - crop = new BoundingBox(boundingBox.getX() * devicePixelRatio, boundingBox.getY() * devicePixelRatio, boundingBox.getWidth() * devicePixelRatio, boundingBox.getHeight() * devicePixelRatio); - } - if (options.getSpeed() <= 0) { - throw new JvppeteerException("speed must be greater than 0."); - } - if (options.getScale() <= 0) { - throw new JvppeteerException("scale must be greater than 0."); - } - ScreenRecorder recorder = new ScreenRecorder(this, width, height, new ScreenRecorderOptions(options.getSpeed(), crop, options.getPath(), options.getFormat(), options.getScale(), options.getFfmpegPath()), defaultViewport, tempViewport); - try { - this.startScreencast(); - } catch (Exception e) { - recorder.stop(); - LOGGER.error("startScreencast error: ", e); - return null; - } - return recorder; - - } - - private void startScreencast() { - screencastSessionCount.incrementAndGet(); - if (!startScreencasted) { - synchronized (this) { - if (!this.startScreencasted) { - AwaitableResult awaitableResult = AwaitableResult.create(); - this.mainFrame().client().on(CDPSession.CDPSessionEvent.Page_screencastFrame, (Consumer) event -> awaitableResult.complete()); - Map params = ParamsFactory.create(); - params.put("format", "png"); - this.mainFrame().client().send("Page.startScreencast", params); - awaitableResult.waiting(); - this.startScreencasted = true; - } - } - } - } - - public void stopScreencast() { - long count = screencastSessionCount.decrementAndGet(); - if (!this.startScreencasted) { - return; - } - this.startScreencasted = false; - if (count == 0) { - this.mainFrame().client().send("Page.stopScreencast"); - } - } - - public enum PageEvent { - /** - * 页面关闭时候发射该事件 - */ - Close("close"), - /** - * 当页面中的 JavaScript 调用控制台 API 方法之一时发出, - * 例如 'console.log' 或 'console.dir'。如果页面抛出 - * 错误或警告,也会触发该事件 - *

- * {@link ConsoleMessage} 代表一个console事件的相关信息 - */ - Console("console"), - /** - * 当 JavaScript 对话框出现时触发,例如 alert、prompt、confirm 或 beforeunload。Jvppeteer 可以通过 {@link Dialog#accept} or {@link Dialog#dismiss}. 响应对话。 - *

- * {@link Dialog} 代表一个dialog事件的相关信息 - */ - Dialog("dialog"), - /** - * 当页面加载到加载到DOMContentLoaded时触发

- * 点击查看DOMContentLoaded介绍 - */ - Domcontentloaded("domcontentloaded"), - /** - * 当页面崩溃时触发,返回一个 {@link Error} - */ - Error("error"), - /** - * 当一个Frame被添加时触发,返回一个 {@link Frame} - */ - FrameAttached("frameattached"), - /** - * 当一个Frame被移除时触发,返回一个 {@link Frame} - */ - FrameDetached("framedetached"), - /** - * 当一个Frame 被导航到新的URL时触发,返回一个 {@link Frame}. - */ - FrameNavigated("framenavigated"), - /** - * load 事件在加载整个页面时触发,包括所有依赖资源,例如样式表、脚本、iframe 和图像,但延迟加载的资源除外。 这与 DOMContentLoaded 相反,后者在页面 DOM 加载后立即触发,而无需等待资源完成加载。 - *

- * 此事件不可取消,也不会冒泡。 - * 点击查看load - */ - Load("load"), - /** - * 当 JavaScript 代码调用 `console.timeStamp` 时触发此事件。 - * metrics列表 见 {@link Page#metrics}. - *

- * {@link PageMetrics} 代表一个dialog事件的相关信息 - *

- */ - Metrics("metrics"), - /** - * 当页面内发生未捕获的异常时触发。包含 {@link PageEvent#Error}。 - */ - PageError("pageerror"), - /** - * 当页面打开新选项卡或窗口时触发。

- * 包含与弹出窗口对应的页。

- * {@link Page} 代表一个popup事件的相关信息 - */ - Popup("popup"), - /** - * 当页面发送请求并包含 HTTPRequest 时触发。 - *

- * 该对象是只读的。请参阅 Page.setRequestInterception() 了解拦截和修改请求。 - */ - Request("request"), - /** - * 当请求最终从缓存加载时触发。包含 HTTPRequest。 - *

- * 对于某些请求,可能包含未定义。具体见这里 - */ - RequestServedFromCache("requestservedfromcache"), - /** - * 当请求失败时触发,例如超时。 - * {@link Request}.代表一个requestfailed事件的相关信息 - *

- * 包含 Request。 - *

- * 从 HTTP 角度来看,HTTP 错误响应(例如 404 或 503)仍然是成功响应,因此请求将通过 requestfinished 事件完成,而不是通过 requestfailed 事件完成。 - */ - RequestFailed("requestfailed"), - /** - * 当请求成功完成时触发。包含 Request。 - *

- * {@link Request}.代表一个requestfinished事件的相关信息 - */ - RequestFinished("requestfinished"), - /** - * 收到响应时触发。包含 HTTPResponse。 - * {@link Response}.代表一个response事件的相关信息 - */ - Response("response"), - /** - * 当页面生成专用 WebWorker 时触发。 - *

- * 了解WebWorker - */ - WorkerCreated("workercreated"), - /** - * 当页面生成专用 WebWorker 时触发。 - *

- * 了解WebWorker - */ - WorkerDestroyed("workerdestroyed"); - - private String eventName; - - PageEvent(String eventName) { - this.eventName = eventName; - } - - public String getEventName() { - return eventName; - } - - public void setEventName(String eventName) { - this.eventName = eventName; - } - } -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/PageExtend.java b/src/main/java/com/ruiyun/jvppeteer/core/PageExtend.java deleted file mode 100644 index 2f0d2f7a..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/PageExtend.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.ruiyun.jvppeteer.exception.EvaluateException; -import com.ruiyun.jvppeteer.util.StringUtil; - -import java.util.List; - -public class PageExtend { - - - public static String html(Page page) throws JsonProcessingException, EvaluateException { - if (null == page) return null; - ElementHandle handle = byTag(page, "html"); - if (null == handle) return null; - JSHandle jsHandle = handle.getProperty("outerHTML"); - if (null == jsHandle) return null; - return jsHandle.jsonValue().toString(); - } - - public static String text(Page page) throws JsonProcessingException, EvaluateException { - if (null == page) return null; - ElementHandle handle = byTag(page, "html"); - if (null == handle) return null; - JSHandle jsHandle = handle.getProperty("innerText"); - if (null == jsHandle) return null; - return jsHandle.jsonValue().toString(); - } - - public static ElementHandle byId(Page page, String param) throws EvaluateException, JsonProcessingException { - if (null == page || StringUtil.isEmpty(param)) { - return null; - } - return page.$("#".concat(param)); - } - - public static ElementHandle byTag(Page page, String param) throws EvaluateException, JsonProcessingException { - if (null == page || StringUtil.isEmpty(param)) { - return null; - } - return page.$(param); - } - - public static ElementHandle byClass(Page page, String param) throws EvaluateException, JsonProcessingException { - if (null == page || StringUtil.isEmpty(param)) { - return null; - } - return page.$(".".concat(param)); - } - - public static List byTagList(Page page, String param) throws EvaluateException, JsonProcessingException { - if (null == page || StringUtil.isEmpty(param)) { - return null; - } - return page.$$(param); - } - - public static List byClassList(Page page, String param) throws EvaluateException, JsonProcessingException { - if (null == page || StringUtil.isEmpty(param)) { - return null; - } - return page.$$(".".concat(param)); - } - -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Realm.java b/src/main/java/com/ruiyun/jvppeteer/core/Realm.java deleted file mode 100644 index ee7fc349..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/Realm.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.ruiyun.jvppeteer.core; - - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.ruiyun.jvppeteer.common.ChromeEnvironment; -import com.ruiyun.jvppeteer.common.TaskManager; -import com.ruiyun.jvppeteer.common.TimeoutSettings; -import com.ruiyun.jvppeteer.exception.EvaluateException; -import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.entities.WaitForSelectorOptions; -import com.ruiyun.jvppeteer.entities.WaitTaskOptions; -import com.ruiyun.jvppeteer.entities.EvaluateType; -import com.ruiyun.jvppeteer.util.Helper; -import com.ruiyun.jvppeteer.util.StringUtil; - -import java.util.List; - -public abstract class Realm { - - protected final TimeoutSettings timeoutSettings; - public final TaskManager taskManager = new TaskManager(); - private boolean disposed = false; - - public Realm(TimeoutSettings timeoutSettings) { - this.timeoutSettings = timeoutSettings; - } - - public abstract ChromeEnvironment environment(); - - public abstract JSHandle adoptHandle(JSHandle handle) throws JsonProcessingException, EvaluateException; - - public abstract T transferHandle(T handle) throws JsonProcessingException; - - public abstract JSHandle evaluateHandle(String pageFunction, List args) throws JsonProcessingException, EvaluateException; - - public abstract Object evaluate(String pageFunction, List args) throws JsonProcessingException, EvaluateException; - - - public JSHandle waitForFunction(String pageFunction, String predicateQueryHandlerBody, WaitForSelectorOptions options, List args) { - String polling = "raf"; - int timeout = this.timeoutSettings.timeout(); - - if (StringUtil.isNotEmpty(options.getPolling())) { - if (Helper.isNumber(options.getPolling())) { - if (Integer.parseInt(options.getPolling()) < 0) { - throw new IllegalArgumentException("Cannot poll with non-positive interval"); - } - } else { - polling = options.getPolling(); - } - } - if (options.getTimeout() != null) { - timeout = options.getTimeout(); - } - return new WaitTask(this, new WaitTaskOptions(polling, timeout, options.getRoot(), true), pageFunction, predicateQueryHandlerBody, Helper.isFunction(pageFunction) ? EvaluateType.FUNCTION : EvaluateType.STRING, args).result(); - } - - protected IsolatedWorld toIsolatedWorld() { - return null; - } - - public boolean disposed() { - return this.disposed; - } - - public void dispose() { - this.disposed = true; - this.taskManager.terminateAll(new JvppeteerException("waitForFunction failed: frame got detached.")); - } - - public abstract JSHandle adoptBackendNode(int backendNodeId) throws JsonProcessingException; - -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/Touchscreen.java b/src/main/java/com/ruiyun/jvppeteer/core/Touchscreen.java deleted file mode 100644 index dc99f89a..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/Touchscreen.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.TouchPoint; -import com.ruiyun.jvppeteer.transport.CDPSession; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class Touchscreen { - - private CDPSession client; - - private final Keyboard keyboard; - - public Touchscreen(CDPSession client, Keyboard keyboard) { - this.client = client; - this.keyboard = keyboard; - } - - /** - * 调度 touchstart 和 touchend 事件。 - * - * @param x tap的水平位置。 - * @param y tap的垂直位置。 - */ - public void tap(double x, double y) { - this.touchStart(x, y); - this.touchEnd(); - } - - /** - * 调度 touchstart 事件。 - * - * @param x tap的水平位置。 - * @param y tap的垂直位置。 - */ - public void touchStart(double x, double y) { - Map params = ParamsFactory.create(); - params.put("type", "touchStart"); - List touchPoints = new ArrayList<>(); - TouchPoint touchPoint = new TouchPoint(); - touchPoint.setX(Math.round(x)); - touchPoint.setY(Math.round(y)); - touchPoint.setRadiusX(0.5); - touchPoint.setRadiusY(0.5); - touchPoint.setForce(0.5); - touchPoints.add(touchPoint); - params.put("touchPoints", touchPoints); - params.put("modifiers", this.keyboard.getModifiers()); - this.client.send("Input.dispatchTouchEvent", params); - } - - /** - * 调度 touchMove 事件。 - * - * @param x 移动的水平位置。 - * @param y 移动的垂直位置。 - */ - public void touchMove(double x, double y) { - Map params = ParamsFactory.create(); - params.put("type", "touchMove"); - List touchPoints = new ArrayList<>(); - TouchPoint touchPoint = new TouchPoint(); - touchPoint.setX(Math.round(x)); - touchPoint.setY(Math.round(y)); - touchPoint.setRadiusX(0.5); - touchPoint.setRadiusY(0.5); - touchPoint.setForce(0.5); - touchPoints.add(touchPoint); - params.put("touchPoints", touchPoints); - params.put("modifiers", this.keyboard.getModifiers()); - this.client.send("Input.dispatchTouchEvent", params); - } - - /** - * 调度 touchend 事件。 - */ - public void touchEnd() { - Map params = ParamsFactory.create(); - params.put("type", "touchEnd"); - List touchPoints = new ArrayList<>(); - params.put("touchPoints", touchPoints); - params.put("modifiers", this.keyboard.getModifiers()); - this.client.send("Input.dispatchTouchEvent", params); - } - - public void updateClient(CDPSession client) { - this.client = client; - } - -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/WaitTask.java b/src/main/java/com/ruiyun/jvppeteer/core/WaitTask.java deleted file mode 100644 index 9e026fe7..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/WaitTask.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.ruiyun.jvppeteer.common.AwaitableResult; -import com.ruiyun.jvppeteer.entities.EvaluateType; -import com.ruiyun.jvppeteer.entities.WaitTaskOptions; -import com.ruiyun.jvppeteer.util.Helper; -import com.ruiyun.jvppeteer.util.StringUtil; -import com.ruiyun.jvppeteer.util.ValidateUtil; - -import java.math.BigDecimal; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static com.ruiyun.jvppeteer.util.Helper.throwError; - -public class WaitTask { - private final AtomicInteger runCount; - private boolean terminated; - private final AwaitableResult result = AwaitableResult.create(); - private final Realm world; - private final String polling; - private List args = new ArrayList<>(); - private final int timeout; - private final String predicateBody; - - public WaitTask(Realm world, WaitTaskOptions options, String predicateBody, String predicateQueryHandlerBody, EvaluateType type, List args) { - - this.polling = options.getPolling(); - if (Helper.isNumber(this.polling)) { - ValidateUtil.assertArg(new BigDecimal(this.polling).compareTo(new BigDecimal(0)) > 0, "Cannot poll with non-positive interval: " + this.polling); - } else { - ValidateUtil.assertArg("raf".equals(this.polling) || "mutation".equals(this.polling), "Unknown polling option: " + this.polling); - } - this.world = world; -// this.root = options.getRoot(); - this.timeout = options.getTimeout(); - - - if (EvaluateType.STRING.equals(type)) { - this.predicateBody = "return (" + predicateBody + ");"; - } else { - if (StringUtil.isNotEmpty(predicateQueryHandlerBody)) { - this.predicateBody = "\n" + - " return (function wrapper(args) {\n" + - " const predicateQueryHandler = " + predicateQueryHandlerBody + ";\n" + - " return (" + predicateBody + ")(...args);\n" + - " })(args);"; - } else { - this.predicateBody = "return (" + predicateBody + ")(...args);"; - } - } - Optional.ofNullable(args).ifPresent(args1 -> this.args = args1); - this.runCount = new AtomicInteger(0); - this.world.taskManager.add(this); - try { - long start = System.currentTimeMillis(); - this.rerun(); - long end = System.currentTimeMillis(); - if (timeout > 0 && (end - start) > timeout) { - this.terminate(new RuntimeException(MessageFormat.format("waitForFunction failed: timeout {0}ms exceeded", timeout))); - } - } catch (Exception e) { - this.terminate(e); - } - - } - - public void rerun() { - int count = runCount.incrementAndGet(); - Exception error = null; - JSHandle success = null; - try { - List args = new ArrayList<>(); - args.add(this.predicateBody); - args.add(this.polling); - args.add(this.timeout); - args.addAll(this.args); - success = this.world.evaluateHandle(waitForPredicatePageFunction(), args); - - if (this.terminated || count != this.runCount.get()) { - if (success != null) - success.dispose(); - return; - } - } catch (Exception e) { - error = e; - } - // Ignore timeouts in pageScript - we track timeouts ourselves. - // If the frame's execution context has already changed, `frame.evaluate` will - // throw an error - ignore this predicate run altogether. - boolean isChanged = false; - try { - if (success != null) { - this.world.evaluate("s => !s", Collections.singletonList(success)); - } - } catch (Exception e) { - isChanged = true; - } - - if (error == null && isChanged) { - success.dispose(); - return; - } - // When the page is navigated, the promise is rejected. - // Try again right away. - if (error != null && error.getMessage().contains("Execution context was destroyed")) { - return; - } - if (error != null && error.getMessage().contains("Cannot find context with specified id")) - return; - if (error != null) { - throwError(error); - } else { - this.result.onSuccess(success); - } - - this.terminate(null); - } - - private String waitForPredicatePageFunction() { - - return "async function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {\n" + - " const predicate = new Function('...args', predicateBody);\n" + - " let timedOut = false;\n" + - " if (timeout)\n" + - " setTimeout(() => timedOut = true, timeout);\n" + - " if (polling === 'raf')\n" + - " return await pollRaf();\n" + - " if (polling === 'mutation')\n" + - " return await pollMutation();\n" + - " if (typeof polling === 'number')\n" + - " return await pollInterval(polling);\n" + - "\n" + - " /**\n" + - " * @return {!Promise<*>}\n" + - " */\n" + - " function pollMutation() {\n" + - " const success = predicate.apply(null, args);\n" + - " if (success)\n" + - " return Promise.resolve(success);\n" + - "\n" + - " let fulfill;\n" + - " const result = new Promise(x => fulfill = x);\n" + - " const observer = new MutationObserver(mutations => {\n" + - " if (timedOut) {\n" + - " observer.disconnect();\n" + - " fulfill();\n" + - " }\n" + - " const success = predicate.apply(null, args);\n" + - " if (success) {\n" + - " observer.disconnect();\n" + - " fulfill(success);\n" + - " }\n" + - " });\n" + - " observer.observe(document, {\n" + - " childList: true,\n" + - " subtree: true,\n" + - " attributes: true\n" + - " });\n" + - " return result;\n" + - " }\n" + - "\n" + - " /**\n" + - " * @return {!Promise<*>}\n" + - " */\n" + - " function pollRaf() {\n" + - " let fulfill;\n" + - " const result = new Promise(x => fulfill = x);\n" + - " onRaf();\n" + - " return result;\n" + - "\n" + - " function onRaf() {\n" + - " if (timedOut) {\n" + - " fulfill();\n" + - " return;\n" + - " }\n" + - " const success = predicate.apply(null, args);\n" + - " if (success)\n" + - " fulfill(success);\n" + - " else\n" + - " requestAnimationFrame(onRaf);\n" + - " }\n" + - " }\n" + - "\n" + - " /**\n" + - " * @param {number} pollInterval\n" + - " * @return {!Promise<*>}\n" + - " */\n" + - " function pollInterval(pollInterval) {\n" + - " let fulfill;\n" + - " const result = new Promise(x => fulfill = x);\n" + - " onTimeout();\n" + - " return result;\n" + - "\n" + - " function onTimeout() {\n" + - " if (timedOut) {\n" + - " fulfill();\n" + - " return;\n" + - " }\n" + - " const success = predicate.apply(null, args);\n" + - " if (success)\n" + - " fulfill(success);\n" + - " else\n" + - " setTimeout(onTimeout, pollInterval);\n" + - " }\n" + - " }\n" + - "}"; - } - - public void terminate(Exception error) { - this.terminated = true; - this.world.taskManager.delete(this); - if (error != null && !this.result.isDone()) { - this.result.complete(); - throwError(error); - } - } - - public JSHandle result() { - return this.result.waitingGetResult(this.timeout, TimeUnit.MILLISECONDS); - } - -} diff --git a/src/main/java/com/ruiyun/jvppeteer/core/WebWorker.java b/src/main/java/com/ruiyun/jvppeteer/core/WebWorker.java deleted file mode 100644 index bf0c551c..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/core/WebWorker.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.ruiyun.jvppeteer.core; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.ruiyun.jvppeteer.common.ConsoleAPI; -import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.common.TimeoutSettings; -import com.ruiyun.jvppeteer.entities.ConsoleMessageType; -import com.ruiyun.jvppeteer.entities.TargetType; -import com.ruiyun.jvppeteer.events.ConsoleAPICalledEvent; -import com.ruiyun.jvppeteer.events.ExceptionThrownEvent; -import com.ruiyun.jvppeteer.events.ExecutionContextCreatedEvent; -import com.ruiyun.jvppeteer.events.IsolatedWorldEmitter; -import com.ruiyun.jvppeteer.exception.EvaluateException; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.transport.Connection; - -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import static com.ruiyun.jvppeteer.util.Helper.withSourcePuppeteerURLIfNone; - -/** - * The events `workercreated` and `workerdestroyed` are emitted on the page object to signal the worker lifecycle. - */ -public class WebWorker { - private final IsolatedWorld world; - private final CDPSession client; - private final String id; - private final TargetType targetType; - private final String url; - private ExecutionContext context; - - - public WebWorker(CDPSession client, String url, String targetId, TargetType targetType, ConsoleAPI consoleAPICalled, Consumer exceptionThrown) { - super(); - this.url = url; - this.id = targetId; - this.client = client; - this.targetType = targetType; - TimeoutSettings timeoutSettings = new TimeoutSettings(); - this.world = new IsolatedWorld(null, this, timeoutSettings); - this.client.once(CDPSession.CDPSessionEvent.Runtime_executionContextCreated, (Consumer) event -> this.world.setContext(new ExecutionContext(client, event.getContext(), world))); - this.world.emitter().on(IsolatedWorldEmitter.IsolatedWorldEventType.Consoleapicalled, (Consumer) event -> consoleAPICalled.call(ConsoleMessageType.valueOf(event.getType().toUpperCase()), event.getArgs().stream().map((object) -> new JSHandle(world, object)).collect(Collectors.toList()), event.getStackTrace())); - this.client.on(CDPSession.CDPSessionEvent.Runtime_exceptionThrown, exceptionThrown); - this.client.once(CDPSession.CDPSessionEvent.CDPSession_Disconnected, (ignored) -> this.world.dispose()); - } - - public Realm mainRealm() { - return this.world; - } - - public CDPSession client() { - return this.client; - } - - public void close() throws EvaluateException, JsonProcessingException { - switch (this.targetType) { - case SERVICE_WORKER: - case SHARED_WORKER: { - Connection connection = this.client.getConnection(); - if (connection != null) { - Map params = ParamsFactory.create(); - params.put(Constant.TARGET_ID, this.id); - connection.send("Target.closeTarget", params); - params.clear(); - params.put(Constant.SESSION_ID, this.client.id()); - connection.send("Target.detachFromTarget", params, null, false); - } - break; - } - default: { - this.evaluate("() => {\n" + - " self.close();\n" + - " }"); - } - - } - } - - /** - * 此 Web Worker 的 URL。 - * - * @return URL - */ - public String url() { - return this.url; - } - - /** - * 根据经验,如果给定函数的返回值比 JSON 对象(例如大多数类)更复杂,那么 evaluate _ 可能 _ 返回一些截断值(或 {})。这是因为我们返回的不是实际的返回值,而是通过协议将返回值传输到 Puppeteer 的结果的反序列化版本。 - *

- * 一般来说,如果 evaluate 无法正确序列化返回值或者你需要一个可变的 handle 作为返回对象,则应该使用 evaluateHandle。 - * - * @param pageFunction 要执行的 JavaScript 函数 - * @return pageFunction 执行结果 - * @throws EvaluateException 如果在浏览器端执行函数时发生错误 - * @throws JsonProcessingException 如果在序列化返回值时发生错误 - */ - public Object evaluate(String pageFunction) throws EvaluateException, JsonProcessingException { - return this.evaluate(pageFunction, null); - } - - public JSHandle evaluateHandle(String pageFunction) throws EvaluateException, JsonProcessingException { - return this.evaluateHandle(pageFunction, null); - } - - public JSHandle evaluateHandle(String pageFunction, List args) throws EvaluateException, JsonProcessingException { - pageFunction = withSourcePuppeteerURLIfNone("evaluateHandle", pageFunction); - return this.mainRealm().evaluateHandle(pageFunction, args); - } - - public Object evaluate(String pageFunction, List args) throws EvaluateException, JsonProcessingException { - pageFunction = withSourcePuppeteerURLIfNone("evaluate", pageFunction); - return this.mainRealm().evaluate(pageFunction, args); - } - - -} - - diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessageType.java b/src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessageType.java deleted file mode 100644 index 5e58ff12..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/entities/ConsoleMessageType.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ruiyun.jvppeteer.entities; - -public enum ConsoleMessageType { - LOG, - DEBUG, - INFO, - ERROR, - WARN, - DIR, - DIRXML, - TABLE, - TRACE, - CLEAR, - STARTGROUP, - STARTGROUPCOLLAPSED, - ENDGROUP, - ASSERT, - PROFILE, - PROFILEEND, - COUNT, - TIMEEND, - VERBOSE -} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/LogEntryLevel.java b/src/main/java/com/ruiyun/jvppeteer/entities/LogEntryLevel.java deleted file mode 100644 index 331ed8e5..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/entities/LogEntryLevel.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ruiyun.jvppeteer.entities; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public enum LogEntryLevel { - @JsonProperty("verbose") - VERBOSE, - @JsonProperty("info") - INFO, - @JsonProperty("warning") - WARNING, - @JsonProperty("error") - ERROR -} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/MouseMoveOptions.java b/src/main/java/com/ruiyun/jvppeteer/entities/MouseMoveOptions.java deleted file mode 100644 index fe43bcec..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/entities/MouseMoveOptions.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ruiyun.jvppeteer.entities; - -public class MouseMoveOptions { - private int steps = 1; - - public int getSteps() { - return steps; - } - - public void setSteps(int steps) { - this.steps = steps; - } -} diff --git a/src/main/java/com/ruiyun/jvppeteer/entities/WaitTaskOptions.java b/src/main/java/com/ruiyun/jvppeteer/entities/WaitTaskOptions.java deleted file mode 100644 index 9db0d986..00000000 --- a/src/main/java/com/ruiyun/jvppeteer/entities/WaitTaskOptions.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.ruiyun.jvppeteer.entities; - -import com.ruiyun.jvppeteer.core.ElementHandle; - -public class WaitTaskOptions { - private String polling; - private ElementHandle root; - private int timeout; - //waitforfunction == true waitforselector = false - private boolean predicateAcceptsContextElement; - public WaitTaskOptions() { - } - - public WaitTaskOptions(String polling, int timeout, ElementHandle root, boolean predicateAcceptsContextElement) { - this.polling = polling; - this.timeout = timeout; - this.root = root; - this.predicateAcceptsContextElement = predicateAcceptsContextElement; - } - - public String getPolling() { - return polling; - } - - public void setPolling(String polling) { - this.polling = polling; - } - - public ElementHandle getRoot() { - return root; - } - - public void setRoot(ElementHandle root) { - this.root = root; - } - - public int getTimeout() { - return timeout; - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public boolean getPredicateAcceptsContextElement() { - return predicateAcceptsContextElement; - } - - public void setPredicateAcceptsContextElement(boolean predicateAcceptsContextElement) { - this.predicateAcceptsContextElement = predicateAcceptsContextElement; - } -} diff --git a/src/main/java/com/ruiyun/jvppeteer/launch/BrowserLauncher.java b/src/main/java/com/ruiyun/jvppeteer/launch/BrowserLauncher.java index 2ec2f34c..06f3f3a3 100644 --- a/src/main/java/com/ruiyun/jvppeteer/launch/BrowserLauncher.java +++ b/src/main/java/com/ruiyun/jvppeteer/launch/BrowserLauncher.java @@ -1,37 +1,53 @@ package com.ruiyun.jvppeteer.launch; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.api.core.Connection; +import com.ruiyun.jvppeteer.bidi.core.BidiBrowser; +import com.ruiyun.jvppeteer.bidi.core.BidiConnection; +import com.ruiyun.jvppeteer.cdp.core.BrowserFetcher; +import com.ruiyun.jvppeteer.cdp.core.BrowserRunner; +import com.ruiyun.jvppeteer.cdp.core.CdpBrowser; +import com.ruiyun.jvppeteer.cdp.entities.ConnectOptions; +import com.ruiyun.jvppeteer.cdp.entities.FetcherOptions; +import com.ruiyun.jvppeteer.cdp.entities.GetVersionResponse; +import com.ruiyun.jvppeteer.cdp.entities.LaunchOptions; +import com.ruiyun.jvppeteer.cdp.entities.Protocol; +import com.ruiyun.jvppeteer.cdp.entities.RevisionInfo; +import com.ruiyun.jvppeteer.cdp.entities.TargetType; import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.common.Environment; import com.ruiyun.jvppeteer.common.Product; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.core.BrowserFetcher; -import com.ruiyun.jvppeteer.core.BrowserRunner; -import com.ruiyun.jvppeteer.entities.ConnectOptions; -import com.ruiyun.jvppeteer.entities.FetcherOptions; -import com.ruiyun.jvppeteer.entities.GetVersionResponse; -import com.ruiyun.jvppeteer.entities.LaunchOptions; -import com.ruiyun.jvppeteer.entities.RevisionInfo; -import com.ruiyun.jvppeteer.entities.TargetType; import com.ruiyun.jvppeteer.exception.JvppeteerException; import com.ruiyun.jvppeteer.exception.LaunchException; -import com.ruiyun.jvppeteer.transport.Connection; +import com.ruiyun.jvppeteer.exception.ProtocolException; +import com.ruiyun.jvppeteer.exception.TimeoutException; +import com.ruiyun.jvppeteer.transport.CdpConnection; +import com.ruiyun.jvppeteer.transport.ConnectionTransport; import com.ruiyun.jvppeteer.transport.WebSocketTransport; +import com.ruiyun.jvppeteer.transport.WebSocketTransportFactory; import com.ruiyun.jvppeteer.util.FileUtil; import com.ruiyun.jvppeteer.util.Helper; import com.ruiyun.jvppeteer.util.StreamUtil; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +59,8 @@ public abstract class BrowserLauncher { protected Product product; protected String cacheDir; protected String executablePath; + private static final Pattern WS_ENDPOINT_PATTERN = Pattern.compile("^DevTools listening on (ws://.*)$"); + private static final Pattern BiDi_ENDPOINT_PATTERN = Pattern.compile("^WebDriver BiDi listening on (ws://.*)$"); public BrowserLauncher(String cacheDir, Product product) { super(); @@ -50,7 +68,6 @@ public BrowserLauncher(String cacheDir, Product product) { this.product = product; } - Environment env = System::getenv; public abstract Browser launch(LaunchOptions options) throws IOException; @@ -77,7 +94,7 @@ public String computeExecutablePath(String preferredExecutablePath, String prefe } /*环境变量中配置了chromeExecutable,就使用环境变量中的路径*/ for (int i = 0; i < Constant.EXECUTABLE_ENV.length; i++) { - preferredExecutablePath = env.getEnv(Constant.EXECUTABLE_ENV[i]); + preferredExecutablePath = System.getProperty(Constant.EXECUTABLE_ENV[i]); if (StringUtil.isNotEmpty(preferredExecutablePath)) { boolean assertDir = FileUtil.assertExecutable(preferredExecutablePath); if (!assertDir) { @@ -88,14 +105,17 @@ public String computeExecutablePath(String preferredExecutablePath, String prefe } /*指定了首选版本*/ if (StringUtil.isNotEmpty(preferredRevision)) { - RevisionInfo revisionInfo = browserFetcher.revisionInfo(preferredRevision); + RevisionInfo revisionInfo = browserFetcher.revisionInfo(preferredRevision.replace("stable_", "")); if (!revisionInfo.getLocal()) throw new LaunchException(MessageFormat.format("Could not find browser preferredRevision {0}. Please download a browser binary.", preferredRevision)); return revisionInfo.getExecutablePath(); } /*环境变量中配置了版本,就用环境变量中的版本*/ - String revision = env.getEnv(Constant.JVPPETEER_PRODUCT_REVISION_ENV); + String revision = System.getProperty(Constant.JVPPETEER_PRODUCT_REVISION_ENV); + if (StringUtil.isNotEmpty(revision)) { + revision = revision.replace("stable_", ""); + } if (StringUtil.isNotEmpty(revision)) { RevisionInfo revisionInfo = browserFetcher.revisionInfo(revision); if (!revisionInfo.getLocal()) { @@ -129,26 +149,136 @@ public String computeExecutablePath(String preferredExecutablePath, String prefe throw new LaunchException("Could not find anyone browser executablePath"); } - protected Browser run(LaunchOptions options, List chromeArguments, String temporaryUserDataDir, boolean usePipe, List defaultArgs) { - BrowserRunner runner = new BrowserRunner(this.executablePath, chromeArguments, temporaryUserDataDir); + protected Browser createBrowser(LaunchOptions options, List chromeArguments, String temporaryUserDataDir, boolean usePipe, List defaultArgs, String customizedUserDataDir) { + BrowserRunner runner = new BrowserRunner(this.executablePath, chromeArguments, temporaryUserDataDir, options.getProduct(), options.getProtocol(), customizedUserDataDir); try { - runner.start(); - Connection connection = runner.setUpConnection(usePipe, options.getProtocolTimeout(), options.getSlowMo(), options.getDumpio()); - Runnable closeCallback = runner::closeBrowser; - Browser browser = Browser.create(options.getProduct(), connection, new ArrayList<>(), options.getAcceptInsecureCerts(), options.getDefaultViewport(), runner.getProcess(), closeCallback, options.getTargetFilter(), null, true); - browser.setExecutablePath(this.executablePath); - browser.setDefaultArgs(defaultArgs); - if (options.getWaitForInitialPage()) { - browser.waitForTarget(t -> TargetType.PAGE.equals(t.type()), options.getTimeout()); + Connection connection; + if (usePipe) { + throw new LaunchException("Not supported pipe connect to browser"); + } else { + if (Objects.equals(options.getProduct(), Product.Firefox)) { + runner.start(); + String endpoint = this.waitForWSEndpoint(options.getTimeout(), options.getDumpio(), options.getProtocol(), runner.getProcess()); + if (Protocol.WebDriverBiDi.equals(options.getProtocol())) { + ConnectionTransport transport = WebSocketTransportFactory.create(endpoint + "/session"); + connection = new BidiConnection(endpoint + "/session", transport, options.getSlowMo(), options.getTimeout()); + runner.setConnection(connection); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Connect to browser by webDriverBidi url: {}", endpoint + "/session"); + } + Runnable closeCallback = runner::closeBrowser; + return createBiDiBrowser((BidiConnection) connection, closeCallback, runner.getProcess(), options); + } else { + ConnectionTransport transport = WebSocketTransportFactory.create(endpoint); + connection = new CdpConnection(endpoint, transport, options.getSlowMo(), options.getTimeout()); + runner.setConnection(connection); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Connect to browser by websocket url: {}", endpoint); + } + return createCdpBrowser(options, defaultArgs, runner, connection); + } + } else { + if (Objects.equals(options.getProtocol(), Protocol.CDP)) { + runner.start(); + String endpoint = this.waitForWSEndpoint(options.getTimeout(), options.getDumpio(), options.getProtocol(), runner.getProcess()); + ConnectionTransport transport = WebSocketTransportFactory.create(endpoint); + connection = new CdpConnection(endpoint, transport, options.getSlowMo(), options.getTimeout()); + runner.setConnection(connection); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Connect to browser by websocket url: {}", endpoint); + } + return createCdpBrowser(options, defaultArgs, runner, connection); + } else { + throw new LaunchException("Chrome dose not support connect to browser by webdriver-bidi"); + } + } } - connection.setBrowser(browser); - runner.setPid(getBrowserPid(runner.getProcess())); - return browser; - } catch (IOException | InterruptedException e) { + } catch (Exception e) { runner.closeBrowser(); - LOGGER.error("Failed to launch the browser process:{}", e.getMessage(), e); - return null; + throw new LaunchException("Failed to launch the browser process: " + e.getMessage(), e); + } + } + /** + * waiting for browser ws url + * + * @param timeout 等待超时时间 + * @param dumpio 是否用标准输出打印 chrome 进程的输出流 + * @return 连接websocket的url + */ + private String waitForWSEndpoint(int timeout, boolean dumpio, Protocol protocol, Process process) { + return new StreamReader(timeout, dumpio, process.getInputStream(), protocol).waitFor(); + } + + static class StreamReader { + private final StringBuilder chromeOutputBuilder = new StringBuilder(); + private volatile String wsEndpoint = null; + private final int timeout; + private final boolean dumpio; + private final InputStream inputStream; + private final Protocol protocol; + + public StreamReader(int timeout, boolean dumpio, InputStream inputStream, Protocol protocol) { + this.timeout = timeout; + this.dumpio = dumpio; + this.inputStream = inputStream; + this.protocol = protocol; + } + + public String waitFor() { + try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader reader = new BufferedReader(inputStreamReader)) { + long now = System.currentTimeMillis(); + long base = 0; + String line; + while ((line = reader.readLine()) != null) { + long remaining = timeout - base; + if (remaining <= 0) { + throw new TimeoutException("Failed to launch the browser process!" + "Chrome output: " + chromeOutputBuilder); + } + if (dumpio) { + System.out.println(line); + } + //只要是 Product.Firefox 就是 用 webdriver-bidi + Matcher matcher = Objects.equals(Protocol.WebDriverBiDi, this.protocol) ? BiDi_ENDPOINT_PATTERN.matcher(line) : WS_ENDPOINT_PATTERN.matcher(line); + if (matcher.find()) { + wsEndpoint = matcher.group(1); + return wsEndpoint; + } + chromeOutputBuilder.append(line).append(System.lineSeparator()); + base = System.currentTimeMillis() - now; + } + throw new LaunchException("Failed to launch the browser process! Chrome process Output: " + chromeOutputBuilder); + } catch (Exception e) { + throw new LaunchException("Failed to launch the browser process! " + e.getMessage() + "Chrome process Output: " + chromeOutputBuilder, e); + } + } + } + + private CdpBrowser createCdpBrowser(LaunchOptions options, List defaultArgs, BrowserRunner runner, Connection connection) { + Runnable closeCallback = runner::closeBrowser; + CdpBrowser cdpBrowser = CdpBrowser.create(options.getProduct(), connection, new ArrayList<>(), options.getAcceptInsecureCerts(), options.getDefaultViewport(), runner.getProcess(), closeCallback, options.getTargetFilter(), null, true); + cdpBrowser.setExecutablePath(this.executablePath); + cdpBrowser.setDefaultArgs(defaultArgs); + if (options.getWaitForInitialPage()) { + cdpBrowser.waitForTarget(t -> TargetType.PAGE.equals(t.type()), options.getTimeout()); } + connection.setCloseRunner(() -> { + if (!cdpBrowser.autoClose) { + LOGGER.info("Websocket connection has been closed,now shutting down browser process"); + cdpBrowser.disconnect(); + try { + cdpBrowser.close(); + } catch (Exception e) { + LOGGER.trace("jvppeteer error", e); + } + } + }); + runner.setPid(getBrowserPid(runner.getProcess())); + return cdpBrowser; + } + + private Browser createBiDiBrowser(BidiConnection connection, Runnable closeCallback, Process process, LaunchOptions options) throws IOException { + return BidiBrowser.create(process, closeCallback, connection, null, options.getDefaultViewport(), options.getAcceptInsecureCerts(), null); } /** @@ -168,30 +298,54 @@ public String executablePath() { return this.executablePath; } - public Browser connect(ConnectOptions options) throws IOException, InterruptedException { - final Connection connection; + public Browser connect(ConnectOptions options) throws Exception { + ConnectionTransport connectionTransport; + String endpointUrl; if (options.getTransport() != null) { - connection = new Connection("", options.getTransport(), options.getSlowMo(), options.getProtocolTimeout()); + connectionTransport = options.getTransport(); + endpointUrl = ""; } else if (StringUtil.isNotEmpty(options.getBrowserWSEndpoint())) { - WebSocketTransport connectionTransport = WebSocketTransport.create(options.getBrowserWSEndpoint()); - connection = new Connection(options.getBrowserWSEndpoint(), connectionTransport, options.getSlowMo(), options.getTimeout()); + connectionTransport = WebSocketTransportFactory.create(options.getBrowserWSEndpoint(), options.getHeaders(), options.getProtocolTimeout()); + endpointUrl = options.getBrowserWSEndpoint(); } else if (StringUtil.isNotEmpty(options.getBrowserURL())) { - String connectionURL = getWSEndpoint(options.getBrowserURL()); - WebSocketTransport connectionTransport = WebSocketTransport.create(connectionURL); - connection = new Connection(connectionURL, connectionTransport, options.getSlowMo(), options.getTimeout()); + endpointUrl = getWSEndpoint(options.getBrowserURL()); + connectionTransport = WebSocketTransport.create(endpointUrl); } else { throw new IllegalArgumentException("Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect"); } + if (Objects.equals(options.getProtocolType(), Protocol.WebDriverBiDi)) { + return connectToBiDiBrowse(connectionTransport, endpointUrl, options); + } else { + return connectToCdpBrowser(connectionTransport, endpointUrl, options); + } + } + + private CdpBrowser connectToCdpBrowser(ConnectionTransport connectionTransport, String url, ConnectOptions options) throws IOException { + Connection connection = new CdpConnection(url, connectionTransport, options.getSlowMo(), options.getProtocolTimeout()); JsonNode result = connection.send("Target.getBrowserContexts"); JavaType javaType = Constant.OBJECTMAPPER.getTypeFactory().constructParametricType(ArrayList.class, String.class); List browserContextIds; - Runnable closeFunction = () -> connection.send("Browser.close"); + Runnable closeCallback = () -> connection.send("Browser.close"); browserContextIds = Constant.OBJECTMAPPER.readerFor(javaType).readValue(result.get("browserContextIds")); GetVersionResponse version = getVersion(connection); - Product product = version.getProduct().toLowerCase().contains("firefox") ? Product.FIREFOX : Product.CHROME; - Browser browser = Browser.create(product, connection, browserContextIds, options.getAcceptInsecureCerts(), options.getDefaultViewport(), null, closeFunction, options.getTargetFilter(), options.getIsPageTarget(), true); - connection.setBrowser(browser); - return browser; + Product product = version.getProduct().toLowerCase().contains("firefox") ? Product.Firefox : Product.Chrome; + return CdpBrowser.create(product, connection, browserContextIds, options.getAcceptInsecureCerts(), options.getDefaultViewport(), null, closeCallback, options.getTargetFilter(), options.getIsPageTarget(), true); + } + + private BidiBrowser connectToBiDiBrowse(ConnectionTransport connectionTransport, String url, ConnectOptions options) throws JsonProcessingException { + // Try pure BiDi first. + BidiConnection pureBidiConnection = new BidiConnection(url, connectionTransport, options.getSlowMo(), options.getProtocolTimeout()); + try { + JsonNode result = pureBidiConnection.send("session.status", Collections.emptyMap()); + if (result.has("type") && Objects.equals(result.get("type").asText(), "success")) { + return BidiBrowser.create(null, null, pureBidiConnection, null, options.getDefaultViewport(), options.getAcceptInsecureCerts(), options.getCapabilities()); + } + } catch (Exception e) { + if (!(e instanceof ProtocolException)) { + throw e; + } + } + throw new JvppeteerException("Fail to connect Browser by options " + options); } /** diff --git a/src/main/java/com/ruiyun/jvppeteer/launch/ChromeLauncher.java b/src/main/java/com/ruiyun/jvppeteer/launch/ChromeLauncher.java index 65e95323..c8c018b2 100644 --- a/src/main/java/com/ruiyun/jvppeteer/launch/ChromeLauncher.java +++ b/src/main/java/com/ruiyun/jvppeteer/launch/ChromeLauncher.java @@ -1,20 +1,25 @@ package com.ruiyun.jvppeteer.launch; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.cdp.entities.BrowserLaunchArgumentOptions; +import com.ruiyun.jvppeteer.cdp.entities.LaunchOptions; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.Product; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.entities.BrowserLaunchArgumentOptions; -import com.ruiyun.jvppeteer.entities.LaunchOptions; +import com.ruiyun.jvppeteer.exception.LaunchException; import com.ruiyun.jvppeteer.util.FileUtil; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; import java.io.IOException; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; + +import static com.ruiyun.jvppeteer.common.Constant.JVPPETEER_TEST_EXPERIMENTAL_CHROME_FEATURES; + public class ChromeLauncher extends BrowserLauncher { @@ -28,7 +33,14 @@ public Browser launch(LaunchOptions options) throws IOException { options.setArgs(new ArrayList<>()); } this.executablePath = this.computeExecutablePath(options.getExecutablePath(), options.getPreferredRevision()); + if (!Paths.get(this.executablePath).getFileName().toString().toLowerCase().contains(options.getProduct().getProduct())) { + throw new LaunchException("The ExecutablePath does not match the product, The executablePath is " + this.executablePath + ",but the product is " + options.getProduct()); + } + + //临时的 UserDataDir String temporaryUserDataDir = null; + //自定义的 UserDataDir + String customizedUserDataDir = null; List defaultArgs = this.defaultArgs(options); List chromeArguments = new ArrayList<>(defaultArgs); boolean isCustomUserDir = false; @@ -39,6 +51,7 @@ public Browser launch(LaunchOptions options) throws IOException { } if (arg.startsWith("--user-data-dir")) { isCustomUserDir = true; + customizedUserDataDir = arg.replace("--user-data-dir=", ""); } } if (!isCustomUserDir) { @@ -56,13 +69,11 @@ public Browser launch(LaunchOptions options) throws IOException { } boolean usePipe = chromeArguments.contains("--remote-debugging-pipe"); LOGGER.trace("Calling {} {}", this.executablePath, String.join(" ", chromeArguments)); - return run(options, chromeArguments, temporaryUserDataDir, usePipe, defaultArgs); + Browser browser = createBrowser(options, chromeArguments, temporaryUserDataDir, usePipe, defaultArgs, customizedUserDataDir); + LOGGER.info("Successfully launch the browser, the executablePath is {}, the protocol is {}", this.executablePath,options.getProtocol()); + return browser; } - - - - /** * 返回默认的启动参数 * @@ -75,7 +86,7 @@ public List defaultArgs(LaunchOptions options) { if (ValidateUtil.isNotEmpty(options.getArgs()) && !userDisabledFeatures.isEmpty()) { removeMatchingFlags(options, "--disable-features"); } - boolean turnOnExperimentalFeaturesForTesting = "true".equals(env.getEnv("PUPPETEER_TEST_EXPERIMENTAL_CHROME_FEATURES")); + boolean turnOnExperimentalFeaturesForTesting = "true".equals(System.getProperty(JVPPETEER_TEST_EXPERIMENTAL_CHROME_FEATURES)); List disabledFeatures = new ArrayList<>(); disabledFeatures.add("Translate"); disabledFeatures.add("AcceptCHFrame"); @@ -121,7 +132,7 @@ public List defaultArgs(LaunchOptions options) { headless = false; } if (headless) { - if (Product.CHROMEHEADLESSSHELL.equals(options.getProduct()) || this.executablePath.contains(Product.CHROMEHEADLESSSHELL.getProduct())) { + if (Product.Chrome_headless_shell.equals(options.getProduct()) || this.executablePath.contains(Product.Chrome_headless_shell.getProduct())) { chromeArguments.add("--headless"); } else { chromeArguments.add("--headless=new"); @@ -169,6 +180,4 @@ private List getFeatures(String flag, List options) { } - - } diff --git a/src/main/java/com/ruiyun/jvppeteer/launch/FirefoxLauncher.java b/src/main/java/com/ruiyun/jvppeteer/launch/FirefoxLauncher.java index bfe2a92f..80d51207 100644 --- a/src/main/java/com/ruiyun/jvppeteer/launch/FirefoxLauncher.java +++ b/src/main/java/com/ruiyun/jvppeteer/launch/FirefoxLauncher.java @@ -1,9 +1,11 @@ package com.ruiyun.jvppeteer.launch; +import com.ruiyun.jvppeteer.api.core.Browser; +import com.ruiyun.jvppeteer.cdp.entities.LaunchOptions; +import com.ruiyun.jvppeteer.cdp.entities.Protocol; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.Product; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.entities.LaunchOptions; +import com.ruiyun.jvppeteer.exception.LaunchException; import com.ruiyun.jvppeteer.util.FileUtil; import com.ruiyun.jvppeteer.util.Helper; import com.ruiyun.jvppeteer.util.StringUtil; @@ -12,6 +14,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.HashMap; @@ -20,7 +23,12 @@ import java.util.Map; import java.util.Objects; -public class FirefoxLauncher extends BrowserLauncher { + +import static com.ruiyun.jvppeteer.common.Constant.BACKUP_SUFFIX; +import static com.ruiyun.jvppeteer.common.Constant.PREFS_JS; +import static com.ruiyun.jvppeteer.common.Constant.USER_JS; + +public class FirefoxLauncher extends com.ruiyun.jvppeteer.launch.BrowserLauncher { public FirefoxLauncher(String cacheDir, Product product) { super(cacheDir, product); @@ -32,7 +40,13 @@ public Browser launch(LaunchOptions options) throws IOException { options.setArgs(new ArrayList<>()); } this.executablePath = this.computeExecutablePath(options.getExecutablePath(), options.getPreferredRevision()); + if (!Paths.get(this.executablePath).getFileName().toString().toLowerCase().contains(options.getProduct().getProduct())) { + throw new LaunchException("The ExecutablePath does not match the product, The executablePath is " + this.executablePath + ",but the product is " + options.getProduct()); + } + //临时的 UserDataDir String temporaryUserDataDir = null; + //自定义的 UserDataDir + String customizedUserDataDir = null; List defaultArgs = this.defaultArgs(options); List firefoxArguments = new ArrayList<>(defaultArgs); boolean isCustomUserDir = false; @@ -40,10 +54,18 @@ public Browser launch(LaunchOptions options) throws IOException { for (String arg : firefoxArguments) { if (arg.startsWith("--remote-debugging-")) { isCustomRemoteDebugger = true; + break; } - if (arg.equals("-profile") || arg.equals("--profile")) { - isCustomUserDir = true; - } + } + int profileIndex = firefoxArguments.indexOf("-profile"); + int profileIndex2 = firefoxArguments.indexOf("--profile"); + if (profileIndex != -1) { + isCustomUserDir = true; + customizedUserDataDir = firefoxArguments.get(profileIndex + 1); + } + if (profileIndex2 != -1) { + isCustomUserDir = true; + customizedUserDataDir = firefoxArguments.get(profileIndex2 + 1); } if (!isCustomUserDir) { temporaryUserDataDir = FileUtil.createProfileDir(Constant.FIREFOX_PROFILE_PREFIX); @@ -59,10 +81,14 @@ public Browser launch(LaunchOptions options) throws IOException { } } - createProfile(temporaryUserDataDir, getPreferences(options)); + createProfile(StringUtil.isNotEmpty(temporaryUserDataDir) ? temporaryUserDataDir : customizedUserDataDir, getPreferences(options)); boolean usePipe = firefoxArguments.contains("--remote-debugging-pipe"); - LOGGER.trace("Calling {} {}", this.executablePath, String.join(" ", firefoxArguments)); - return run(options, firefoxArguments, temporaryUserDataDir, usePipe, defaultArgs); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Calling {} {}", this.executablePath, String.join(" ", firefoxArguments)); + } + Browser browser = createBrowser(options, firefoxArguments, temporaryUserDataDir, usePipe, defaultArgs, customizedUserDataDir); + LOGGER.info("Successfully launch the browser, the executablePath is {}, the protocol is {}", this.executablePath,options.getProtocol()); + return browser; } private Map getPreferences(LaunchOptions options) { @@ -71,10 +97,25 @@ private Map getPreferences(LaunchOptions options) { prefs.putAll(options.getExtraPrefsFirefox()); } // Only enable the WebDriver BiDi protocol - prefs.put("browser.tabs.closeWindowWithLastTab", false); - prefs.put("network.cookie.cookieBehavior", 0); - prefs.put("fission.bfcacheInParent", false); - prefs.put("remote.active-protocols", 2); +// prefs.put("remote.active-protocols", 1); + if (Protocol.WebDriverBiDi.equals(options.getProtocol())) { + prefs.put("remote.active-protocols", 1); + } else { + // Do not close the window when the last tab gets closed + prefs.put("browser.tabs.closeWindowWithLastTab", false); + // Prevent various error message on the console + // jest-puppeteer asserts that no error message is emitted by the console + prefs.put("network.cookie.cookieBehavior", 0); + // Temporarily force disable BFCache in parent (https://bit.ly/bug-1732263) + prefs.put("fission.bfcacheInParent", false); + // Only enable the CDP protocol + prefs.put("remote.active-protocols", 2); + } + // Force all web content to use a single content process. TODO: remove + // this once Firefox supports mouse event dispatch from the main frame + // context. Once this happens, webContentIsolationStrategy should only + // be set for CDP. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=1773393 prefs.put("fission.webContentIsolationStrategy", 0); return prefs; } @@ -86,24 +127,23 @@ private void createProfile(String userDir, Map preferences) thro } Map defaultProfilePreferences = defaultProfilePreferences(); defaultProfilePreferences.putAll(preferences); - String prefsPath = Helper.join(userDir, "prefs.js"); - String userPath = Helper.join(userDir, "user.js"); + String prefsPath = Helper.join(userDir, PREFS_JS); + String userPath = Helper.join(userDir, USER_JS); backupFile(userPath); StringBuilder sb = new StringBuilder(); for (Map.Entry entry : defaultProfilePreferences.entrySet()) { - sb.append("user_pref(\"").append(entry.getKey()).append("\", ").append( Constant.OBJECTMAPPER.writeValueAsString(entry.getValue())).append(");\n"); - + sb.append("user_pref(\"").append(entry.getKey()).append("\", ").append(Constant.OBJECTMAPPER.writeValueAsString(entry.getValue())).append(");\n"); } - Files.write(Paths.get(userPath),sb.toString().getBytes(),StandardOpenOption.CREATE, StandardOpenOption.WRITE); + Files.write(Paths.get(userPath), sb.toString().getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); backupFile(prefsPath); } private void backupFile(String input) throws IOException { Path source = Paths.get(input); - if(!Files.exists(source)){ + if (!Files.exists(source)) { return; } - Files.copy(source,Paths.get(input+ ".bak")); + Files.copy(source, Paths.get(input + BACKUP_SUFFIX), StandardCopyOption.REPLACE_EXISTING); } private Map defaultProfilePreferences() { diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/Callback.java b/src/main/java/com/ruiyun/jvppeteer/transport/Callback.java index d53ae439..ebf1bc89 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/Callback.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/Callback.java @@ -63,7 +63,7 @@ public JsonNode waitForResponse() throws InterruptedException { } return waitingResponse.get(); } else { - throw new JvppeteerException("Timeout < 0"); + throw new JvppeteerException("Timeout must be greater than 0"); } } diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/CallbackRegistry.java b/src/main/java/com/ruiyun/jvppeteer/transport/CallbackRegistry.java index cb492976..4acfc9d7 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/CallbackRegistry.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/CallbackRegistry.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.ruiyun.jvppeteer.exception.ProtocolException; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -25,7 +24,7 @@ public class CallbackRegistry { private final Map eventCallbacks = new ConcurrentHashMap<>(); public JsonNode create(Callback callback, Consumer request, boolean isBlocking) { - put(callback, isBlocking); + put(callback); try { //send request request.accept(callback.id()); @@ -42,14 +41,12 @@ public JsonNode create(Callback callback, Consumer request, boolean isBloc } } - private void put(Callback callback, boolean isBlocking) { - if (isBlocking) {//只有等待结果的,才放进去 - String name = Thread.currentThread().getName(); - if (name.startsWith(JV_HANDLE_MESSAGE_THREAD)) {//说明是 JV_HANDLE_MESSAGE_THREAD 线程中发送的请求接受到的消息 - eventCallbacks.put(callback.id(), callback); - } else { - callbacks.put(callback.id(), callback); - } + private void put(Callback callback) { + String name = Thread.currentThread().getName(); + if (name.startsWith(JV_HANDLE_MESSAGE_THREAD)) {//说明是JV_EMIT_EVENT_THREAD线程中发送的请求接受到的消息 + eventCallbacks.put(callback.id(), callback); + } else { + callbacks.put(callback.id(), callback); } } diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/CDPSession.java b/src/main/java/com/ruiyun/jvppeteer/transport/CdpCDPSession.java similarity index 51% rename from src/main/java/com/ruiyun/jvppeteer/transport/CDPSession.java rename to src/main/java/com/ruiyun/jvppeteer/transport/CdpCDPSession.java index 1df447aa..6b457b2e 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/CDPSession.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/CdpCDPSession.java @@ -1,9 +1,11 @@ package com.ruiyun.jvppeteer.transport; import com.fasterxml.jackson.databind.JsonNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Connection; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.core.Target; -import com.ruiyun.jvppeteer.events.EventEmitter; +import com.ruiyun.jvppeteer.cdp.core.CdpTarget; import com.ruiyun.jvppeteer.exception.JvppeteerException; import com.ruiyun.jvppeteer.util.StringUtil; import com.ruiyun.jvppeteer.util.ValidateUtil; @@ -20,7 +22,7 @@ import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; import static com.ruiyun.jvppeteer.common.Constant.PARAMS; import static com.ruiyun.jvppeteer.common.Constant.SESSION_ID; -import static com.ruiyun.jvppeteer.transport.Connection.resolveCallback; +import static com.ruiyun.jvppeteer.transport.CdpConnection.handleCdpCallback; /** * The CDPSession instances are used to talk raw Chrome Devtools Protocol: @@ -32,15 +34,15 @@ * Documentation on DevTools Protocol can be found here: DevTools Protocol Viewer. * Getting Started with : DevTools Protocol */ -public class CDPSession extends EventEmitter { - private static final Logger LOGGER = LoggerFactory.getLogger(CDPSession.class); +public class CdpCDPSession extends CDPSession { + private static final Logger LOGGER = LoggerFactory.getLogger(CdpCDPSession.class); private final String targetType; private final String sessionId; - private Connection connection; + private CdpConnection connection; private final String parentSessionId; - private Target target; + private CdpTarget target; - public CDPSession(Connection connection, String targetType, String sessionId, String parentSessionId) { + public CdpCDPSession(CdpConnection connection, String targetType, String sessionId, String parentSessionId) { super(); this.targetType = targetType; this.sessionId = sessionId; @@ -60,14 +62,14 @@ public CDPSession parentSession() { } } - public void setTarget(Target target) { + public void setTarget(CdpTarget target) { this.target = target; } public void onClosed() { this.connection = null; - this.emit(CDPSessionEvent.CDPSession_Disconnected, true); + this.emit(ConnectionEvents.CDPSession_Disconnected, true); } public JsonNode send(String method) { @@ -78,8 +80,8 @@ public JsonNode send(String method, Map params) { return this.send(method, params, null, true); } - public JsonNode send(String method, Map params, Integer timeout, boolean isBlocking) { - if (this.connection == null || this.connection.closed) { + public JsonNode send(String method, Object params, Integer timeout, boolean isBlocking) { + if (this.connection == null || this.connection.closed()) { throw new JvppeteerException("Protocol error (" + method + "): Session closed. Most likely the" + this.targetType + "has been closed."); } return this.connection.rawSend(method, params, this.sessionId, timeout, isBlocking); @@ -118,7 +120,7 @@ public void onMessage(JsonNode response, CallbackRegistry callbacks) { try { if (response.hasNonNull(ID)) {//long类型的id,说明属于这次发送消息后接受的回应 long id = response.get(ID).asLong(); - resolveCallback(callbacks, response, id, false); + handleCdpCallback(callbacks, response, id, false); } else {//发射数据,执行事件的监听方法 ValidateUtil.assertArg(!response.hasNonNull(ID), "Should not contain id, " + response); if (Objects.isNull(connection)) { @@ -127,7 +129,7 @@ public void onMessage(JsonNode response, CallbackRegistry callbacks) { String method = methodNode.asText(); boolean match = EVENTS.contains(method); if (match) {//不匹配就是没有监听该事件 - this.emit(CDPSessionEvent.valueOf(method.replace(".", "_")), LISTENER_CLASSES.get(method) == null ? true : OBJECTMAPPER.treeToValue(paramsNode, LISTENER_CLASSES.get(method))); + this.emit(ConnectionEvents.valueOf(method.replace(".", "_")), LISTENER_CLASSES.get(method) == null ? true : OBJECTMAPPER.treeToValue(paramsNode, LISTENER_CLASSES.get(method))); } } } catch (Exception e) { @@ -143,90 +145,9 @@ public String id() { return this.sessionId; } - public Target getTarget() { + public CdpTarget getTarget() { Objects.requireNonNull(this.target, "Target must exist"); return this.target; } - public enum CDPSessionEvent { - CDPSession_Disconnected("CDPSession.Disconnected"), - CDPSession_Swapped("CDPSession.Swapped"), - CDPSession_Ready("CDPSession.Ready"), - sessionAttached("sessionattached"), - sessionDetached("sessiondetached"), - - Page_domContentEventFired("Page.domContentEventFired"), - Page_loadEventFired("Page.loadEventFired"), - Page_javascriptDialogOpening("Page.javascriptDialogOpening"), - Page_fileChooserOpened("Page.fileChooserOpened"), - Page_frameStartedLoading("Page.frameStartedLoading"), - Page_frameAttached("Page.frameAttached"), - Page_frameNavigated("Page.frameNavigated"), - Page_navigatedWithinDocument("Page.navigatedWithinDocument"), - Page_frameDetached("Page.frameDetached"), - Page_frameStoppedLoading("Page.frameStoppedLoading"), - Page_lifecycleEvent("Page.lifecycleEvent"), - Page_screencastFrame("Page.screencastFrame"), - - Runtime_executionContextCreated("Runtime.executionContextCreated"), - Runtime_executionContextDestroyed("Runtime.executionContextDestroyed"), - Runtime_executionContextsCleared("Runtime.executionContextsCleared"), - Runtime_exceptionThrown("Runtime.exceptionThrown"), - Runtime_consoleAPICalled("Runtime.consoleAPICalled"), - Runtime_bindingCalled("Runtime.bindingCalled"), - - Inspector_targetCrashed("Inspector.targetCrashed"), - Performance_metrics("Performance.metrics"), - Log_entryAdded("Log.entryAdded"), - - Target_targetCreated("Target.targetCreated"), - Target_targetDestroyed("Target.targetDestroyed"), - Target_targetInfoChanged("Target.targetInfoChanged"), - Target_attachedToTarget("Target.attachedToTarget"), - Target_detachedFromTarget("Target.detachedFromTarget"), - Debugger_scriptParsed("Debugger.scriptParsed"), - - CSS_styleSheetAdded("CSS.styleSheetAdded"), - DeviceAccess_deviceRequestPrompted("DeviceAccess.deviceRequestPrompted"), - - targetcreated("targetcreated"), - targetdestroyed("targetdestroyed"), - targetchanged("targetchanged"), - disconnected("disconnected"), - - Fetch_requestPaused("Fetch.requestPaused"), - Fetch_authRequired("Fetch.authRequired"), - - Network_requestWillBeSent("Network.requestWillBeSent"), - Network_requestServedFromCache("Network.requestServedFromCache"), - Network_responseReceived("Network.responseReceived"), - Network_loadingFinished("Network.loadingFinished"), - Network_loadingFailed("Network.loadingFailed"), - Network_responseReceivedExtraInfo("Network.responseReceivedExtraInfo"), - - Tracing_tracingComplete("Tracing.tracingComplete"), - Input_dragIntercepted("Input.dragIntercepted"), - /** - * 下载进度时触发 - */ - Browser_downloadProgress("Browser.downloadProgress"), - - /** - * 当页面准备开始下载时促发 - */ - Browser_downloadWillBegin("Browser.downloadWillBegin"); - private String eventName; - - CDPSessionEvent(String eventName) { - this.eventName = eventName; - } - - public String getEventName() { - return eventName; - } - - public void setEventName(String eventName) { - this.eventName = eventName; - } - } } diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/Connection.java b/src/main/java/com/ruiyun/jvppeteer/transport/CdpConnection.java similarity index 60% rename from src/main/java/com/ruiyun/jvppeteer/transport/Connection.java rename to src/main/java/com/ruiyun/jvppeteer/transport/CdpConnection.java index 4d65a27b..85d6dce0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/Connection.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/CdpConnection.java @@ -1,332 +1,267 @@ -package com.ruiyun.jvppeteer.transport; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ruiyun.jvppeteer.common.Constant; -import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.core.Browser; -import com.ruiyun.jvppeteer.entities.TargetInfo; -import com.ruiyun.jvppeteer.events.EventEmitter; -import com.ruiyun.jvppeteer.exception.JvppeteerException; -import com.ruiyun.jvppeteer.exception.ProtocolException; -import com.ruiyun.jvppeteer.util.StringUtil; -import com.ruiyun.jvppeteer.util.ValidateUtil; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -import static com.ruiyun.jvppeteer.common.Constant.CODE; -import static com.ruiyun.jvppeteer.common.Constant.ERROR; -import static com.ruiyun.jvppeteer.common.Constant.EVENTS; -import static com.ruiyun.jvppeteer.common.Constant.ID; -import static com.ruiyun.jvppeteer.common.Constant.JV_HANDLE_MESSAGE_THREAD; -import static com.ruiyun.jvppeteer.common.Constant.LISTENER_CLASSES; -import static com.ruiyun.jvppeteer.common.Constant.METHOD; -import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; -import static com.ruiyun.jvppeteer.common.Constant.PARAMS; -import static com.ruiyun.jvppeteer.common.Constant.RESULT; -import static com.ruiyun.jvppeteer.common.Constant.SESSION_ID; -import static com.ruiyun.jvppeteer.util.Helper.createProtocolErrorMessage; - -/** - * web socket client 浏览器级别的连接 - * - * @author fff - */ -public class Connection extends EventEmitter implements Consumer { - private static final Logger LOGGER = LoggerFactory.getLogger(Connection.class); - private final String url; - private final ConnectionTransport transport; - private final int delay; - private final int timeout; - private final Map sessions = new ConcurrentHashMap<>(); - protected volatile boolean closed; - final Set manuallyAttached = new HashSet<>(); - private final CallbackRegistry callbacks = new CallbackRegistry();//并发 - final AtomicLong id = new AtomicLong(1); - private Browser browser; - - ExecutorService handleMessageExecutorService = Executors.newSingleThreadExecutor(r -> new Thread(r, JV_HANDLE_MESSAGE_THREAD + messageThreadId.getAndIncrement())); - - public Connection(String url, ConnectionTransport transport, int delay, int timeout) { - super(); - this.url = url; - this.transport = transport; - this.delay = delay; - this.timeout = timeout; - this.transport.setConnection(this); - } - - private Runnable handleMessageRunnable(JsonNode response) { - return () -> { - try { - String method; - if (response.hasNonNull(METHOD)) { - method = response.get(METHOD).asText(); - } else { - method = null; - } - String sessionId = null; - JsonNode paramsNode; - if (response.hasNonNull(PARAMS)) { - paramsNode = response.get(PARAMS); - if (paramsNode.hasNonNull(SESSION_ID)) { - sessionId = paramsNode.get(SESSION_ID).asText(); - } - } else { - paramsNode = null; - } - String parentSessionId = ""; - if (response.hasNonNull(SESSION_ID)) { - parentSessionId = response.get(SESSION_ID).asText(); - } - if ("Target.attachedToTarget".equals(method)) {//attached to target -> page attached to browser - assert paramsNode != null; - JsonNode typeNode = paramsNode.get(Constant.TARGET_INFO).get(Constant.TYPE); - CDPSession cdpSession = new CDPSession(this, typeNode.asText(), sessionId, parentSessionId); - this.sessions.put(sessionId, cdpSession); - this.emit(CDPSession.CDPSessionEvent.sessionAttached, cdpSession); - CDPSession parentSession = this.sessions.get(parentSessionId); - if (Objects.nonNull(parentSession)) { - parentSession.emit(CDPSession.CDPSessionEvent.sessionAttached, cdpSession); - } - } else if ("Target.detachedFromTarget".equals(method)) {//页面与浏览器脱离关系 - CDPSession cdpSession = this.sessions.get(sessionId); - if (Objects.nonNull(cdpSession)) { - cdpSession.onClosed(); - this.sessions.remove(sessionId); - this.emit(CDPSession.CDPSessionEvent.sessionDetached, cdpSession); - CDPSession parentSession = this.sessions.get(parentSessionId); - if (Objects.nonNull(parentSession)) { - parentSession.emit(CDPSession.CDPSessionEvent.sessionDetached, cdpSession); - } - } - } - if (StringUtil.isNotEmpty(parentSessionId)) { - CDPSession parentSession = this.sessions.get(parentSessionId); - if (Objects.nonNull(parentSession)) { - parentSession.onMessage(response, callbacks); - } - } else if (response.hasNonNull(ID)) {//long类型的id,说明属于这次发送消息后接受的回应 - long id = response.get(ID).asLong(); - resolveCallback(this.callbacks, response, id, false); - } else {//是一个事件,那么响应监听器 - boolean match = EVENTS.contains(method); - if (match) {//匹配就是有监听该事件 - assert method != null; - this.emit(CDPSession.CDPSessionEvent.valueOf(method.replace(".", "_")), LISTENER_CLASSES.get(method) == null ? true : OBJECTMAPPER.treeToValue(paramsNode, LISTENER_CLASSES.get(method))); - } - - } - } catch (Exception e) { - LOGGER.error("Handle message error: ", e); - } - - }; - - } - - /** - * 解析回调并根据响应结果进行处理 - *

- * 此方法主要用于根据服务器返回的响应数据来解决回调操作它检查响应中是否包含错误信息, - * 如果包含,则调用reject方法回传错误信息和错误代码;否则,调用resolve方法回传响应结果 - * - * @param callbacks 回调注册表,用于管理所有的回调操作 - * @param response 浏览器返回的JSON响应数据 - * @param id 请求的唯一标识符,用于匹配对应的回调操作 - * @param handleListenerThread 是否在监听线程中处理回调,默认为false - */ - protected static void resolveCallback(CallbackRegistry callbacks, JsonNode response, long id, boolean handleListenerThread) { - if (response.hasNonNull(ERROR)) { - callbacks.reject(id, createProtocolErrorMessage(response), response.get(ERROR).hasNonNull(CODE) ? response.get(ERROR).get(CODE).asInt() : 0, handleListenerThread); - } else { - callbacks.resolve(id, response.get(RESULT), handleListenerThread); - } - } - - private static final AtomicLong messageThreadId = new AtomicLong(1); - - public boolean isAutoAttached(String targetId) { - return !this.manuallyAttached.remove(targetId); - } - - public JsonNode send(String method) { - return this.rawSend(method, null, null, this.timeout, true); - } - - public JsonNode send(String method, Map params) { - return this.rawSend(method, params, null, this.timeout, true); - } - - public JsonNode send(String method, Map params, Integer timeout, boolean isBlocking) { - return this.rawSend(method, params, null, timeout, isBlocking); - } - - public JsonNode rawSend(String method, Map params, String sessionId, Integer timeout, - boolean isBlocking) { - ValidateUtil.assertArg(!this.closed, "Protocol error: Connection closed."); - if (timeout == null) { - timeout = this.timeout; - } - Callback callback = new Callback(this.id.incrementAndGet(), method, timeout); - return this.callbacks.create(callback, (id) -> { - ObjectNode objectNode = OBJECTMAPPER.createObjectNode(); - objectNode.put(METHOD, method); - if (params != null) { - objectNode.set(PARAMS, OBJECTMAPPER.valueToTree(params)); - } - objectNode.put(ID, id); - if (StringUtil.isNotEmpty(sessionId)) { - objectNode.put(SESSION_ID, sessionId); - } - String stringifiedMessage = objectNode.toString(); - LOGGER.trace("jvppeteer:protocol:SEND ► {}", stringifiedMessage); - this.transport.send(stringifiedMessage); - }, isBlocking); - } - - /** - * recevie message from browser by websocket - * - * @param message 从浏览器接受到的消息 - */ - public void onMessage(String message) { - try { - if (StringUtil.isEmpty(message)) { - return; - } - if (delay > 0) { - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - // 恢复中断状态 - Thread.currentThread().interrupt(); - LOGGER.error("slowMo browser Fail:", e); - } - } - LOGGER.trace("jvppeteer:protocol:RECV ◀ {}", message); - JsonNode readTree = OBJECTMAPPER.readTree(message); - if (readTree.hasNonNull(ID)) {//long类型的id,说明属于发送请求后接收到的消息 - long id = readTree.get(ID).asLong(); - resolveCallback(this.callbacks, readTree, id, true); - } - this.handleMessageExecutorService.submit(handleMessageRunnable(readTree)); - } catch (Exception e) { - LOGGER.error("onMessage error:", e); - } - } - - /** - * 从{@link CDPSession}中拿到对应的{@link Connection} - * - * @param client cdpsession - * @return Connection - */ - public static Connection fromSession(CDPSession client) { - return client.getConnection(); - } - - /** - * 创建一个{@link CDPSession} - * - * @param targetInfo target info - * @return CDPSession client - */ - public CDPSession createSession(TargetInfo targetInfo) { - return this._createSession(targetInfo, false); - } - - public CDPSession _createSession(TargetInfo targetInfo, boolean isAutoAttachEmulated) { - if (!isAutoAttachEmulated) { - this.manuallyAttached.add(targetInfo.getTargetId()); - } - Map params = ParamsFactory.create(); - params.put("targetId", targetInfo.getTargetId()); - params.put("flatten", true); - JsonNode response = this.send("Target.attachToTarget", params, null, true); - if (response.hasNonNull(SESSION_ID)) { - String sessionId = response.get(SESSION_ID).asText(); - CDPSession cdpSession = this.sessions.get(sessionId); - if (cdpSession == null) { - throw new JvppeteerException("CDPSession creation failed."); - } - return cdpSession; - } else { - throw new JvppeteerException("CDPSession creation failed."); - } - } - - public String url() { - return this.url; - } - - public CDPSession session(String sessionId) { - return this.sessions.get(sessionId); - } - - @Override - public void accept(String t) { - onMessage(t); - } - - public void dispose() { - this.onClose();//清理Connection资源 - this.transport.close();//关闭websocket - } - - private void onClose() { - if (this.closed) - return; - this.closed = true; - this.transport.setConnection(null); - this.handleMessageExecutorService.shutdown(); -// waitForHandleMessageThreadFinish(); - this.callbacks.clear(); - for (CDPSession session : this.sessions.values()) - session.onClosed(); - this.sessions.clear(); - this.emit(CDPSession.CDPSessionEvent.CDPSession_Disconnected, true); - } - - private void waitForHandleMessageThreadFinish() { - //暂停接受任务 - this.handleMessageExecutorService.shutdown(); - //等待三分钟执行剩下的任务 - try { - this.handleMessageExecutorService.awaitTermination(3, TimeUnit.MINUTES); - } catch (InterruptedException e) { - LOGGER.error("jvppeteer error", e); - } - - } - - public List getPendingProtocolErrors() { - return new ArrayList<>(this.callbacks.getPendingProtocolErrors()); - } - - public boolean closed() { - return this.closed; - } - - public Browser browser() { - return browser; - } - - public void setBrowser(Browser browser) { - this.browser = browser; - } -} - +package com.ruiyun.jvppeteer.transport; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Connection; +import com.ruiyun.jvppeteer.api.events.ConnectionEvents; +import com.ruiyun.jvppeteer.cdp.entities.TargetInfo; +import com.ruiyun.jvppeteer.common.Constant; +import com.ruiyun.jvppeteer.common.ParamsFactory; +import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.exception.ProtocolException; +import com.ruiyun.jvppeteer.util.Helper; +import com.ruiyun.jvppeteer.util.StringUtil; +import com.ruiyun.jvppeteer.util.ValidateUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + + +import static com.ruiyun.jvppeteer.common.Constant.CODE; +import static com.ruiyun.jvppeteer.common.Constant.ERROR; +import static com.ruiyun.jvppeteer.common.Constant.EVENTS; +import static com.ruiyun.jvppeteer.common.Constant.ID; +import static com.ruiyun.jvppeteer.common.Constant.LISTENER_CLASSES; +import static com.ruiyun.jvppeteer.common.Constant.METHOD; +import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; +import static com.ruiyun.jvppeteer.common.Constant.PARAMS; +import static com.ruiyun.jvppeteer.common.Constant.RESULT; +import static com.ruiyun.jvppeteer.common.Constant.SESSION_ID; +import static com.ruiyun.jvppeteer.util.Helper.createProtocolErrorMessage; + +/** + * web socket client 浏览器级别的连接 + * + * @author fff + */ +public class CdpConnection extends Connection { + + + public CdpConnection(String url, ConnectionTransport transport, int delay, int timeout) { + super(url, transport, delay, timeout); + } + + protected Runnable handleMessageRunnable(JsonNode response) { + return () -> { + try { + String method; + if (response.hasNonNull(METHOD)) { + method = response.get(METHOD).asText(); + } else { + method = null; + } + String sessionId = null; + JsonNode paramsNode; + if (response.hasNonNull(PARAMS)) { + paramsNode = response.get(PARAMS); + if (paramsNode.hasNonNull(SESSION_ID)) { + sessionId = paramsNode.get(SESSION_ID).asText(); + } + } else { + paramsNode = null; + } + String parentSessionId = ""; + if (response.hasNonNull(SESSION_ID)) { + parentSessionId = response.get(SESSION_ID).asText(); + } + if ("Target.attachedToTarget".equals(method)) {//attached to target -> page attached to browser + assert paramsNode != null; + JsonNode typeNode = paramsNode.get(Constant.TARGET_INFO).get(Constant.TYPE); + CdpCDPSession cdpSession = new CdpCDPSession(this, typeNode.asText(), sessionId, parentSessionId); + this.sessions.put(sessionId, cdpSession); + this.emit(ConnectionEvents.sessionAttached, cdpSession); + CDPSession parentSession = this.sessions.get(parentSessionId); + if (Objects.nonNull(parentSession)) { + parentSession.emit(ConnectionEvents.sessionAttached, cdpSession); + } + } else if ("Target.detachedFromTarget".equals(method)) {//页面与浏览器脱离关系 + CDPSession cdpSession = this.sessions.get(sessionId); + if (Objects.nonNull(cdpSession)) { + cdpSession.onClosed(); + this.sessions.remove(sessionId); + this.emit(ConnectionEvents.sessionDetached, cdpSession); + CDPSession parentSession = this.sessions.get(parentSessionId); + if (Objects.nonNull(parentSession)) { + parentSession.emit(ConnectionEvents.sessionDetached, cdpSession); + } + } + } + if (StringUtil.isNotEmpty(parentSessionId)) { + CdpCDPSession parentSession = this.sessions.get(parentSessionId); + if (Objects.nonNull(parentSession)) { + parentSession.onMessage(response, callbacks); + } + } else if (response.hasNonNull(ID)) {//long类型的id,说明属于这次发送消息后接受的回应 + long id = response.get(ID).asLong(); + handleCdpCallback(this.callbacks, response, id, false); + } else {//是一个事件,那么响应监听器 + boolean match = EVENTS.contains(method); + if (match) {//匹配就是有监听该事件 + assert method != null; + this.emit(ConnectionEvents.valueOf(method.replace(".", "_")), LISTENER_CLASSES.get(method) == null ? true : OBJECTMAPPER.treeToValue(paramsNode, LISTENER_CLASSES.get(method))); + } + } + } catch (Exception e) { + LOGGER.error("Handle message error: ", e); + } + }; + } + + public boolean isAutoAttached(String targetId) { + return !this.manuallyAttached.remove(targetId); + } + + public JsonNode rawSend(String method, Object params, String sessionId, Integer timeout, + boolean isBlocking) { + ValidateUtil.assertArg(!this.closed, "Protocol error: Connection closed."); + if (timeout == null) { + timeout = this.timeout; + } + Callback callback = new Callback(this.id.incrementAndGet(), method, timeout); + return this.callbacks.create(callback, (id) -> { + ObjectNode objectNode = OBJECTMAPPER.createObjectNode(); + objectNode.put(METHOD, method); + if (params != null) { + objectNode.putPOJO(PARAMS, params); + } + objectNode.put(ID, id); + if (StringUtil.isNotEmpty(sessionId)) { + objectNode.put(SESSION_ID, sessionId); + } + String stringifiedMessage; + try { + stringifiedMessage = OBJECTMAPPER.writeValueAsString(objectNode); + } catch (JsonProcessingException e) { + throw new JvppeteerException(e); + } +// LOGGER.info("jvppeteer:protocol:SEND ► {}", stringifiedMessage); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("jvppeteer:protocol:SEND ► {}", stringifiedMessage); + } + this.transport.send(stringifiedMessage); + }, isBlocking); + } + + @Override + public void onMessage(String message) { + try { + if (StringUtil.isEmpty(message)) { + return; + } + if (this.delay > 0) { + Helper.justWait(this.delay); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("jvppeteer:protocol:RECV ◀ {}", message); + } + JsonNode readTree = OBJECTMAPPER.readTree(message); + if (readTree.hasNonNull(ID)) {//long类型的id,说明属于发送请求后接收到的消息 + long id = readTree.get(ID).asLong(); + handleCdpCallback(this.callbacks, readTree, id, true); + } + this.handleMessageExecutorService.submit(handleMessageRunnable(readTree)); + } catch (Exception e) { + LOGGER.error("jvppeteer error:", e); + } + } + + /** + * 解析回调并根据响应结果进行处理 + *

+ * 此方法主要用于根据服务器返回的响应数据来解决回调操作它检查响应中是否包含错误信息, + * 如果包含,则调用reject方法回传错误信息和错误代码;否则,调用resolve方法回传响应结果 + * + * @param callbacks 回调注册表,用于管理所有的回调操作 + * @param response 浏览器返回的JSON响应数据 + * @param id 请求的唯一标识符,用于匹配对应的回调操作 + * @param handleListenerThread 是否在监听线程中处理回调,默认为false + */ + static void handleCdpCallback(CallbackRegistry callbacks, JsonNode response, long id, boolean handleListenerThread) { + if (response.hasNonNull(ERROR)) { + callbacks.reject(id, createProtocolErrorMessage(response), response.get(ERROR).hasNonNull(CODE) ? response.get(ERROR).get(CODE).asInt() : 0, handleListenerThread); + } else { + callbacks.resolve(id, response.get(RESULT), handleListenerThread); + } + } + + /** + * 从{@link CdpCDPSession}中拿到对应的{@link CdpConnection} + * + * @param client cdpsession + * @return Connection + */ + public static Connection fromSession(CdpCDPSession client) { + return client.getConnection(); + } + + /** + * 根据给定的 Target info 创建一个{@link CdpCDPSession} + * + * @param targetInfo 给定的 Target info + * @return CDPSession 创建的 + */ + public CDPSession createSession(TargetInfo targetInfo) { + return this._createSession(targetInfo, false); + } + + public CDPSession _createSession(TargetInfo targetInfo, boolean isAutoAttachEmulated) { + if (!isAutoAttachEmulated) { + this.manuallyAttached.add(targetInfo.getTargetId()); + } + Map params = ParamsFactory.create(); + params.put("targetId", targetInfo.getTargetId()); + params.put("flatten", true); + JsonNode response = this.send("Target.attachToTarget", params, null, true); + if (response.hasNonNull(SESSION_ID)) { + String sessionId = response.get(SESSION_ID).asText(); + CDPSession cdpSession = this.sessions.get(sessionId); + if (cdpSession == null) { + throw new JvppeteerException("CDPSession creation failed."); + } + return cdpSession; + } else { + throw new JvppeteerException("CDPSession creation failed."); + } + } + + public String url() { + return this.url; + } + + public CDPSession session(String sessionId) { + return this.sessions.get(sessionId); + } + + + public void dispose() { + this.onClose();//清理Connection资源 + this.transport.close();//关闭websocket + } + + public void onClose() { + if (this.closed) + return; + this.closed = true; + this.transport.setConnection(null); + this.handleMessageExecutorService.shutdown(); +// waitForHandleMessageThreadFinish(); + this.callbacks.clear(); + for (CDPSession session : this.sessions.values()) + session.onClosed(); + this.sessions.clear(); + this.emit(ConnectionEvents.CDPSession_Disconnected, true); + } + + + public List getPendingProtocolErrors() { + return new ArrayList<>(this.callbacks.getPendingProtocolErrors()); + } + + public boolean closed() { + return this.closed; + } +} + diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/ConnectionTransport.java b/src/main/java/com/ruiyun/jvppeteer/transport/ConnectionTransport.java index 04b88ae6..8ee34e06 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/ConnectionTransport.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/ConnectionTransport.java @@ -1,5 +1,7 @@ package com.ruiyun.jvppeteer.transport; +import com.ruiyun.jvppeteer.api.core.Connection; + public interface ConnectionTransport { diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/PipeTransport.java b/src/main/java/com/ruiyun/jvppeteer/transport/PipeTransport.java index e3e04de4..5dd63066 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/PipeTransport.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/PipeTransport.java @@ -1,5 +1,6 @@ package com.ruiyun.jvppeteer.transport; +import com.ruiyun.jvppeteer.api.core.Connection; import com.ruiyun.jvppeteer.util.StreamUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/SessionFactory.java b/src/main/java/com/ruiyun/jvppeteer/transport/SessionFactory.java index d3d08dd4..f5f1b618 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/SessionFactory.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/SessionFactory.java @@ -1,5 +1,7 @@ package com.ruiyun.jvppeteer.transport; +import com.ruiyun.jvppeteer.api.core.CDPSession; + @FunctionalInterface public interface SessionFactory { CDPSession create(boolean isAutoAttachEmulated); diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransport.java b/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransport.java index c480ab91..3ddf0a40 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransport.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransport.java @@ -1,7 +1,11 @@ package com.ruiyun.jvppeteer.transport; -import com.ruiyun.jvppeteer.core.Browser; +import com.ruiyun.jvppeteer.api.core.Connection; import com.ruiyun.jvppeteer.util.StringUtil; +import java.net.URI; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import org.java_websocket.WebSocket; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; @@ -10,10 +14,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.URI; -import java.util.Map; -import java.util.Objects; - import static com.ruiyun.jvppeteer.common.Constant.CLOSE_REASON; @@ -32,16 +32,17 @@ public WebSocketTransport(URI serverUri, Draft protocolDraft, Map @@ -72,15 +73,8 @@ public void onMessage(String message) { @Override public void onClose(int code, String reason, boolean remote) {//这里是WebSocketClient的实现方法,当websocket closed的时候会调用onClose LOGGER.info("Websocket connection closed by {} Code: {} Reason: {}", remote ? "remote peer" : "us", code, StringUtil.isEmpty(reason) ? CLOSE_REASON.get(code) : reason); - if (Objects.nonNull(this.connection)) {//浏览器以外关闭时候,connection不为空 - Browser browser = connection.browser(); - - if (!browser.isClosing()) { - LOGGER.info("Websocket connection has been closed,now shutting down browser process"); - browser.disconnect(); - browser.close(); - } - } + //浏览器以外关闭时候,connection不为空 + Optional.ofNullable(this.connection).map(Connection::closeRunner).ifPresent(Runnable::run); } @Override @@ -90,16 +84,20 @@ public void onError(Exception e) { @Override public void onOpen(ServerHandshake serverHandshake) { - LOGGER.info("Websocket serverHandshake status: {}", serverHandshake.getHttpStatusMessage()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Websocket serverHandshake status: {}", serverHandshake.getHttpStatusMessage()); + } } @Override - public void onWebsocketPong(WebSocket conn, Framedata f) { - LOGGER.trace("Websocket connection receive pong: {}", f); + public void setConnection(Connection connection) { + this.connection = connection; } @Override - public void setConnection(Connection connection) { - this.connection = connection; + public void onWebsocketPong(WebSocket conn, Framedata f) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Websocket connection receive pong response: {}", f); + } } } diff --git a/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransportFactory.java b/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransportFactory.java index 30500b1a..07d99288 100644 --- a/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransportFactory.java +++ b/src/main/java/com/ruiyun/jvppeteer/transport/WebSocketTransportFactory.java @@ -2,48 +2,48 @@ import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.exception.LaunchException; +import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; import org.java_websocket.drafts.Draft_6455; -import java.net.URI; - public class WebSocketTransportFactory implements Constant { - /** - * create websocket client - * - * @param url 连接websocket的地址 - * @return WebSocketTransport websocket客户端 - * @throws InterruptedException 被打断异常 - */ - public static WebSocketTransport create(String url) throws InterruptedException { - return create(url, null, Constant.DEFAULT_TIMEOUT); - } - /** - * create websocket client - * - * @param url 连接websocket的地址 - * @param httpHeaders 请求头 - * @return WebSocketTransport websocket客户端 - * @throws InterruptedException 被打断异常 - */ - public static WebSocketTransport create(String url, Map httpHeaders, int timeout) throws InterruptedException { - Map headers = new HashMap<>(); - headers.put("User-Agent", "Puppeteer 2.2.3"); - if (Objects.nonNull(httpHeaders)) { - headers.putAll(httpHeaders); - } - // 默认是60s的心跳机制 - WebSocketTransport client = new WebSocketTransport(URI.create(url), new Draft_6455(), headers, timeout); - //30s 连接的超时时间 - boolean connected = client.connectBlocking(timeout, TimeUnit.MILLISECONDS); - if (!connected) { - throw new LaunchException("Websocket connection was not successful, please check if the URL(" + url + ") is effective."); - } - return client; - } - + /** + * create websocket client + * + * @param url 连接websocket的地址 + * @return WebSocketTransport websocket客户端 + * @throws InterruptedException 被打断异常 + */ + public static ConnectionTransport create(String url) throws Exception { + return create(url, null, Constant.DEFAULT_TIMEOUT); + } + + /** + * create websocket client + * + * @param url 连接websocket的地址 + * @param httpHeaders 请求头 + * @return WebSocketTransport websocket客户端 + * @throws InterruptedException 被打断异常 + */ + public static ConnectionTransport create(String url, Map httpHeaders, int timeout) throws Exception { + Map headers = new HashMap<>(); + headers.put("User-Agent", "Puppeteer 2.2.3"); + if (Objects.nonNull(httpHeaders)) { + headers.putAll(httpHeaders); + } + // 默认是60s的心跳机制 + WebSocketTransport client = new WebSocketTransport(URI.create(url), new Draft_6455(), headers, timeout); + //30s 连接的超时时间 + boolean connected = client.connectBlocking(timeout, TimeUnit.MILLISECONDS); + if (!connected) { + throw new LaunchException("Websocket connection was not successful, please check if the URL(" + url + ") is effective."); + } + return client; + } + } diff --git a/src/main/java/com/ruiyun/jvppeteer/util/FileUtil.java b/src/main/java/com/ruiyun/jvppeteer/util/FileUtil.java index ce6bbc2d..f6b8793b 100644 --- a/src/main/java/com/ruiyun/jvppeteer/util/FileUtil.java +++ b/src/main/java/com/ruiyun/jvppeteer/util/FileUtil.java @@ -1,8 +1,6 @@ package com.ruiyun.jvppeteer.util; import com.ruiyun.jvppeteer.exception.JvppeteerException; - -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/src/main/java/com/ruiyun/jvppeteer/util/Helper.java b/src/main/java/com/ruiyun/jvppeteer/util/Helper.java index 287af441..a995f5fa 100644 --- a/src/main/java/com/ruiyun/jvppeteer/util/Helper.java +++ b/src/main/java/com/ruiyun/jvppeteer/util/Helper.java @@ -2,23 +2,25 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ruiyun.jvppeteer.api.core.CDPSession; +import com.ruiyun.jvppeteer.api.core.Connection; +import com.ruiyun.jvppeteer.bidi.entities.SameSite; +import com.ruiyun.jvppeteer.cdp.entities.CallFrame; +import com.ruiyun.jvppeteer.cdp.entities.Cookie; +import com.ruiyun.jvppeteer.cdp.entities.CookiePriority; +import com.ruiyun.jvppeteer.cdp.entities.CookieSameSite; +import com.ruiyun.jvppeteer.cdp.entities.CookieSourceScheme; +import com.ruiyun.jvppeteer.cdp.entities.ExceptionDetails; +import com.ruiyun.jvppeteer.cdp.entities.GetVersionResponse; +import com.ruiyun.jvppeteer.cdp.entities.RemoteObject; +import com.ruiyun.jvppeteer.cdp.entities.StackTrace; import com.ruiyun.jvppeteer.common.Constant; import com.ruiyun.jvppeteer.common.ParamsFactory; -import com.ruiyun.jvppeteer.entities.CallFrame; -import com.ruiyun.jvppeteer.entities.ExceptionDetails; -import com.ruiyun.jvppeteer.entities.GetVersionResponse; -import com.ruiyun.jvppeteer.entities.RemoteObject; -import com.ruiyun.jvppeteer.entities.StackTrace; import com.ruiyun.jvppeteer.exception.EvaluateException; import com.ruiyun.jvppeteer.exception.JvppeteerException; +import com.ruiyun.jvppeteer.exception.ProtocolException; import com.ruiyun.jvppeteer.exception.TimeoutException; -import com.ruiyun.jvppeteer.transport.CDPSession; -import com.ruiyun.jvppeteer.transport.Connection; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -38,14 +40,21 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import static com.ruiyun.jvppeteer.common.Constant.CDP_SPECIFIC_PREFIX; import static com.ruiyun.jvppeteer.common.Constant.INTERNAL_URL; +import static com.ruiyun.jvppeteer.common.Constant.OBJECTMAPPER; import static com.ruiyun.jvppeteer.common.Constant.SOURCE_URL_REGEX; /** @@ -100,7 +109,7 @@ public static Object createClientError(ExceptionDetails exceptionDetails) { return error; } - public static Object createEvaluationError(ExceptionDetails exceptionDetails) { + public static Object createCdpEvaluationError(ExceptionDetails exceptionDetails) { String name = ""; String message; if (exceptionDetails.getException() == null) { @@ -156,6 +165,39 @@ public static Object createEvaluationError(ExceptionDetails exceptionDetails) { return error; } + public static void createBidiEvaluationError(com.ruiyun.jvppeteer.bidi.entities.ExceptionDetails details) { + if (!Objects.equals("error", details.getException().getType())) { + throw new EvaluateException(String.valueOf(details.getException().getValue())); + } + String[] parts = details.getText().split(": ", 2); + String name = parts.length > 0 ? parts[0] : ""; + String message = parts.length > 1 ? String.join(": ", Arrays.copyOfRange(parts, 1, parts.length)) : ""; + LinkedList stackLines = new LinkedList<>(); + StringBuilder stringBuilder = new StringBuilder(); + if (details.getStackTrace() != null) { + for (int i = details.getStackTrace().getCallFrames().size(); i-- > 0; ) { + CallFrame callFrame = details.getStackTrace().getCallFrames().get(i); + if (isPuppeteerURL(callFrame.getUrl()) && !INTERNAL_URL.equals(callFrame.getUrl())) { + PuppeteerURL url = parse(callFrame.getUrl()); + try { + stringBuilder.append("\n jvppeteer print: at ").append(URLDecoder.decode(url.getSiteString(), StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException e) { + stringBuilder.append(url.getSiteString()).append(", :").append(callFrame.getLineNumber()).append(":").append(callFrame.getColumnNumber()); + } + stackLines.addFirst(stringBuilder.toString()); + stringBuilder.setLength(0); + } else { + String functionName = StringUtil.isNotEmpty(callFrame.getFunctionName()) ? callFrame.getFunctionName() : ""; + stringBuilder.append("\n at ").append(functionName).append("(").append(callFrame.getUrl()).append(":").append(callFrame.getLineNumber()).append(":").append(callFrame.getColumnNumber()); + stackLines.add(stringBuilder.toString()); + stringBuilder.setLength(0); + } + } + } + throw new EvaluateException("name: " + name + ", text: " + details.getText() + ", message: " + message + String.join("\n", stackLines)); + } + + public static PuppeteerURL parse(String url) { url = url.substring("pptr:".length()); String[] split = url.split(";"); @@ -172,9 +214,9 @@ public static PuppeteerURL parse(String url) { return new PuppeteerURL(); } - public static String withSourcePuppeteerURLIfNone(String functionName, String pageFunction) { - if (SOURCE_URL_REGEX.matcher(pageFunction).find()) { - return pageFunction; + public static String withSourcePuppeteerURLIfNone(String functionName, String pptrFunction) { + if (SOURCE_URL_REGEX.matcher(pptrFunction).find()) { + return pptrFunction; } else { List args = new ArrayList<>(); // 获取当前调用堆栈 @@ -185,7 +227,7 @@ public static String withSourcePuppeteerURLIfNone(String functionName, String pa } catch (UnsupportedEncodingException e) { args.add("ModuleJob.run%20(node%3Ainternal%2Fmodules%2Fesm%2Fmodule_job%3A222%3A25)"); } - return pageFunction + "\n" + "//# sourceURL=pptr:" + String.join(";", args) + "\n"; + return pptrFunction + "\n" + "//# sourceURL=pptr:" + String.join(";", args) + "\n"; } } @@ -366,12 +408,12 @@ public static String evaluationString(String fun, Object... args) throws JsonPro /** * 判断js字符串是否是一个函数 * - * @param pageFunction js字符串 + * @param pptrFunction js字符串 * @return true代表是js函数 */ - public static boolean isFunction(String pageFunction) { - pageFunction = pageFunction.trim(); - return pageFunction.startsWith("function") || pageFunction.startsWith("async") || pageFunction.contains("=>"); + public static boolean isFunction(String pptrFunction) { + pptrFunction = pptrFunction.trim(); + return pptrFunction.startsWith("function") || pptrFunction.startsWith("async") || pptrFunction.contains("=>"); } /** @@ -379,22 +421,24 @@ public static boolean isFunction(String pageFunction) { * * @param process 进程 * @return 进程id + * @throws ClassNotFoundException class not found * @throws NoSuchFieldException field not found * @throws IllegalAccessException illegal access */ - public static long getPidForLinuxOrMac(Process process) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + public static long getPidForLinuxOrMac(Process process) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { long pid = -1; if (Helper.isMac() || Helper.isLinux()) { String version = System.getProperty("java.version"); double jdkVersion = Double.parseDouble(version.substring(0, 3)); + Class clazz; if (jdkVersion <= 1.8) { - Field field = process.getClass().getDeclaredField("pid"); - field.setAccessible(true); - pid = field.getLong(process); + clazz = Class.forName("java.lang.UNIXProcess"); } else { - Method pidMethod = Process.class.getMethod("pid"); - pid = (long) pidMethod.invoke(process); + clazz = Class.forName("java.lang.ProcessImpl"); } + Field field = clazz.getDeclaredField("pid"); + field.setAccessible(true); + pid = (Integer) field.get(process); } return pid; } @@ -445,14 +489,11 @@ public static T waitForCondition(Supplier conditionChecker, long timeout, } public static void justWait(long timeout) { - long now = System.currentTimeMillis(); - long base = 0; - while (true) { - long remaining = timeout - base; - if (remaining <= 0) { - break; - } - base = System.currentTimeMillis() - now; + CountDownLatch latch = new CountDownLatch(1); + try { + latch.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new JvppeteerException(e); } } @@ -471,7 +512,7 @@ public static boolean isPuppeteerURL(String url) { return url.startsWith("pptr:"); } - public static void throwError(Exception error) { + public static void throwError(Throwable error) { if (error instanceof RuntimeException) { throw (RuntimeException) error; } else { @@ -479,8 +520,105 @@ public static void throwError(Exception error) { } } - public static GetVersionResponse getVersion(Connection connection) throws JsonProcessingException { - return Constant.OBJECTMAPPER.treeToValue(connection.send("Browser.getVersion"), GetVersionResponse.class); + public static GetVersionResponse getVersion(Connection connection) { + return Constant.OBJECTMAPPER.convertValue(connection.send("Browser.getVersion"), GetVersionResponse.class); + } + + public static void rewriteNavigationError(String message, int timeout, Exception error) { + if (error instanceof ProtocolException) { + String newMessage = error.getMessage() + " at " + message; + throw new ProtocolException(newMessage, error); + } else if (error instanceof TimeoutException) { + String newMessage = "Navigation timeout of " + timeout + " ms exceeded"; + throw new TimeoutException(newMessage, error); + } + throwError(error); + } + + public static String setSourceUrlComment(String pptrFunction) { + if (SOURCE_URL_REGEX.matcher(pptrFunction).find()) { + return pptrFunction; + } else { + return pptrFunction + "\n" + "//# sourceURL=" + INTERNAL_URL + "\n"; + } + } + + public static ObjectNode convertCookiesPartitionKeyFromPuppeteerToCdp(JsonNode partitionKey) { + if (Objects.isNull(partitionKey)) { + return null; + } + ObjectNode objectNode = OBJECTMAPPER.createObjectNode(); + if (partitionKey.isTextual()) { + + objectNode.put("topLevelSite", partitionKey.asText()); + objectNode.put("hasCrossSiteAncestor", false); + } else { + objectNode.set("topLevelSite", partitionKey.get("sourceOrigin")); + objectNode.put("hasCrossSiteAncestor", !partitionKey.path("hasCrossSiteAncestor").isMissingNode() && partitionKey.path("hasCrossSiteAncestor").asBoolean()); + } + return objectNode; + } + + public static Cookie bidiToPuppeteerCookie(JsonNode bidiCookie) { + Cookie cookie = new Cookie(); + cookie.setName(bidiCookie.path("name").asText()); + cookie.setValue(bidiCookie.at("/value/value").asText()); + cookie.setDomain(bidiCookie.get("domain").asText()); + cookie.setPath(bidiCookie.get("path").asText()); + cookie.setSize(bidiCookie.get("size").asInt()); + cookie.setHttpOnly(bidiCookie.get("httpOnly").asBoolean()); + cookie.setSecure(bidiCookie.get("secure").asBoolean()); + cookie.setSameSite(convertCookiesSameSiteBiDiToCdp(bidiCookie.get("sameSite"))); + JsonNode expires = bidiCookie.path("expires"); + cookie.setExpires(expires.isMissingNode() ? -1 : expires.asInt()); + cookie.setSession(expires.isMissingNode() || expires.asInt() <= 0); + JsonNode sameParty = bidiCookie.path(CDP_SPECIFIC_PREFIX + "sameParty"); + if (!sameParty.isMissingNode()) { + cookie.setSameParty(sameParty.asBoolean()); + } + JsonNode sourceScheme = bidiCookie.path(CDP_SPECIFIC_PREFIX + "sourceScheme"); + if (!sourceScheme.isMissingNode()) { + cookie.setSourceScheme(CookieSourceScheme.valueOf(sourceScheme.asText())); + } + JsonNode partitionKeyOpaque = bidiCookie.path(CDP_SPECIFIC_PREFIX + "partitionKeyOpaque"); + if (!partitionKeyOpaque.isMissingNode()) { + cookie.setPartitionKeyOpaque(partitionKeyOpaque.asBoolean()); + } + JsonNode priority = bidiCookie.path(CDP_SPECIFIC_PREFIX + "priority"); + if (!priority.isMissingNode()) { + cookie.setPriority(CookiePriority.valueOf(partitionKeyOpaque.asText())); + } + JsonNode partitionKey = bidiCookie.path(CDP_SPECIFIC_PREFIX + "partitionKey"); + if (!partitionKey.isMissingNode()) { + if (partitionKey.isTextual()) { + cookie.setPartitionKey(partitionKey); + } else if (partitionKey.isObject()) { + ObjectNode partitionKeyNode = Constant.OBJECTMAPPER.createObjectNode(); + partitionKeyNode.set("partitionKey", partitionKey.get("topLevelSite")); + cookie.setPartitionKey(partitionKeyNode); + } + } + return cookie; + } + + public static CookieSameSite convertCookiesSameSiteBiDiToCdp(JsonNode sameSite) { + return Objects.equals("strict", sameSite.asText()) ? CookieSameSite.Strict : Objects.equals("lax", sameSite.asText()) ? CookieSameSite.Lax : CookieSameSite.None; + } + + public static SameSite convertCookiesSameSiteCdpToBiDi(CookieSameSite sameSite) { + return Objects.equals(sameSite, CookieSameSite.Strict) ? SameSite.Strict : Objects.equals(sameSite, CookieSameSite.Lax) ? SameSite.Lax : SameSite.None; } + public static String convertCookiesPartitionKeyFromPuppeteerToBiDi(JsonNode partitionKey) { + if (Objects.isNull(partitionKey)) { + return null; + } + if (partitionKey.isTextual()) { + return partitionKey.asText(); + } + if (!partitionKey.path("hasCrossSiteAncestor").isMissingNode()) { + throw new UnsupportedOperationException("WebDriver BiDi does not support `hasCrossSiteAncestor` yet."); + } + return partitionKey.get("sourceOrigin").asText(); + } } diff --git a/src/main/java/com/ruiyun/jvppeteer/util/QueryHandlerUtil.java b/src/main/java/com/ruiyun/jvppeteer/util/QueryHandlerUtil.java index 70fe7e96..ee5b4e55 100644 --- a/src/main/java/com/ruiyun/jvppeteer/util/QueryHandlerUtil.java +++ b/src/main/java/com/ruiyun/jvppeteer/util/QueryHandlerUtil.java @@ -1,11 +1,23 @@ package com.ruiyun.jvppeteer.util; +import com.ruiyun.jvppeteer.cdp.entities.SelectorParseResult; +import com.ruiyun.jvppeteer.cdp.entities.Token; +import com.ruiyun.jvppeteer.common.ARIAQueryHandler; +import com.ruiyun.jvppeteer.common.CSSQueryHandler; +import com.ruiyun.jvppeteer.common.PierceQueryHandler; import com.ruiyun.jvppeteer.common.QueryHandler; import com.ruiyun.jvppeteer.common.QuerySelector; - +import com.ruiyun.jvppeteer.common.TextQueryHandler; +import com.ruiyun.jvppeteer.common.XPathQueryHandler; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.StringTokenizer; +import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -13,6 +25,39 @@ public class QueryHandlerUtil { private static final Map customQueryHandlers = new HashMap<>(); + private static final List QUERY_SEPARATORS = Arrays.asList("=", "/"); + + private static final Pattern attribute = Pattern.compile("\\[\\s*(?:(?\\*|[-\\w\\P{ASCII}]*)\\|)?(?[-\\w\\P{ASCII}]+)\\s*(?:(?\\W?=)\\s*(?.+?)\\s*(\\s(?[iIsS]))?\\s*)?\\]", Pattern.UNICODE_CASE);// /gu + private static final Pattern clazz = Pattern.compile("\\.(?[-\\w\\P{ASCII}]+)", Pattern.UNICODE_CASE);// /gu + private static final Pattern combinator = Pattern.compile("\\s*(>>>>?|[\\s>+~])\\s*", Pattern.UNICODE_CASE);// /gu + private static final Pattern comma = Pattern.compile("\\s*,\\s*");// /g + private static final Pattern id = Pattern.compile("#(?[-\\w\\P{ASCII}]+)", Pattern.UNICODE_CASE);// /gu + private static final Pattern nesting = Pattern.compile("&");// /g + private static final Pattern pseudo_class = Pattern.compile(":(?[-\\w\\P{ASCII}]+)(?:\\((?¶*)\\))?", Pattern.UNICODE_CASE);// /gu + private static final Pattern pseudo_element = Pattern.compile("::(?[-\\w\\P{ASCII}]+)(?:\\((?¶*)\\))?", Pattern.UNICODE_CASE);// /gu + private static final Pattern type = Pattern.compile("(?:(?\\*|[-\\w\\P{ASCII}]*)\\|)?(?[-\\w\\P{ASCII}]+)", Pattern.UNICODE_CASE);// /gu + private static final Pattern universal = Pattern.compile("(?:(?\\*|[-\\w\\P{ASCII}]*)\\|)?\\*", Pattern.UNICODE_CASE);// /gu + private static final Map patterns = new HashMap<>(); + + static { + patterns.put("attribute", attribute); + patterns.put("class", clazz); + patterns.put("combinator", combinator); + patterns.put("comma", comma); + patterns.put("id", id); + patterns.put("nesting", nesting); + patterns.put("pseudo-class", pseudo_class); + patterns.put("pseudo-element", pseudo_element); + patterns.put("type", type); + patterns.put("universal", universal); + customQueryHandlers.put("aria", new ARIAQueryHandler()); + customQueryHandlers.put("pierce", new PierceQueryHandler()); + customQueryHandlers.put("xpath", new XPathQueryHandler()); + customQueryHandlers.put("text", new TextQueryHandler()); + customQueryHandlers.put("css", new CSSQueryHandler()); + + } + public static void registerCustomQueryHandler(String name, QueryHandler handler) { if (customQueryHandlers.containsKey(name)) throw new RuntimeException("A custom query handler named " + name + " already exists"); @@ -36,29 +81,168 @@ public void clearQueryHandlers() { customQueryHandlers.clear(); } - public static QuerySelector getQueryHandlerAndSelector(String selector, String defaultQueryHandler) { - Pattern pattern = Pattern.compile("^[a-zA-Z]+\\/"); - Matcher hasCustomQueryHandler = pattern.matcher(selector); - if (!hasCustomQueryHandler.find()) - return new QuerySelector(selector, new QueryHandler() { - @Override - public String queryOne() { - return "(element,selector) =>\n" + - " element.querySelector(selector)"; + public static QuerySelector getQueryHandlerAndSelector(String selector) { + for (Map.Entry entry : customQueryHandlers.entrySet()) { + String name = entry.getKey(); + QueryHandler queryHandler = entry.getValue(); + for (String separator : QUERY_SEPARATORS) { + String prefix = name + separator; + if (selector.startsWith(prefix)) { + int index = selector.indexOf(separator); + selector = selector.substring(index + 1); + return new QuerySelector(selector, queryHandler, Objects.equals("aria", name) ? "raf" : "mutation"); } + } + } + boolean hasPseudoClasses; + Pattern pattern = Pattern.compile(":(?[-\\w\\P{ASCII}]+)(?:\\((?¶*)\\))?", Pattern.UNICODE_CASE); + Matcher matcher = pattern.matcher(selector); + hasPseudoClasses = matcher.find(); + if (hasPseudoClasses) { + return new QuerySelector(selector, customQueryHandlers.get("css"), "raf"); + } else { + return new QuerySelector(selector, customQueryHandlers.get("css"), "mutation"); + } + } - @Override - public String queryAll() { - return "(element,selector) =>\n" + - " element.querySelectorAll(selector)"; + //todo + public static SelectorParseResult parsePSelectors(String selector) { + SelectorParseResult result = new SelectorParseResult(); + + List tokens = tokenize(selector); + return result; + } + + public static List tokenize(String selector) { + Map tokens = new TreeMap<>(); + StringTokenizer st = new StringTokenizer(selector); + int i = 0; + while (st.hasMoreElements()) { + String nextSelector = st.nextToken(); + i++; + for (Map.Entry entry : patterns.entrySet()) { + String type = entry.getKey(); + Pattern pattern = entry.getValue(); + if (Objects.equals("attribute", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + String content = matcher.group(); + token.setContent(content); + token.setType(type); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("class", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setName(content.substring(1)); + token.setContent(content); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("combinator", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("comma", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("id", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + token.setName(content.substring(1)); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("nesting", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("pseudo-class", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("pseudo-element", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + token.setName(content.replace("::", "")); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("type", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + token.setName(content); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } + } else if (Objects.equals("universal", type)) { + Matcher matcher = pattern.matcher(nextSelector); + boolean matches = matcher.matches(); + if (matches) { + Token token = new Token(); + token.setType(type); + String content = matcher.group(); + token.setContent(content); + tokens.put(i, token); + System.out.println("第" + i + "个: " + nextSelector + " " + token); + } } - }); - int index = selector.indexOf("/"); - String name = selector.substring(0, index); - String updatedSelector = selector.substring(index + 1); - QueryHandler queryHandler = customQueryHandlers().get(name); - if (queryHandler == null) - throw new RuntimeException("Query set to use " + name + ", but no query handler of that name was found"); - return new QuerySelector(updatedSelector, queryHandler); + + } + } + + return new ArrayList<>(tokens.values()); } + } diff --git a/src/main/java/com/ruiyun/jvppeteer/util/StringUtil.java b/src/main/java/com/ruiyun/jvppeteer/util/StringUtil.java index d42e784b..92d252e0 100644 --- a/src/main/java/com/ruiyun/jvppeteer/util/StringUtil.java +++ b/src/main/java/com/ruiyun/jvppeteer/util/StringUtil.java @@ -1,7 +1,5 @@ package com.ruiyun.jvppeteer.util; -import java.time.LocalDateTime; - public class StringUtil { public static boolean isEmpty(String s) { diff --git a/src/main/resources/scripts/install-chrome-for-testing-linux.sh b/src/main/resources/scripts/install-chrome-for-testing-linux.sh index e86af612..a957852f 100644 --- a/src/main/resources/scripts/install-chrome-for-testing-linux.sh +++ b/src/main/resources/scripts/install-chrome-for-testing-linux.sh @@ -27,23 +27,45 @@ fi # 3. Install curl to download chrome if ! command -v curl >/dev/null; then - apt-get install -y curl -fi - -# 4. download chrome beta from dl.google.com and install it. -cd "$1" #安装目录 -curl -O -L "$2" #下载地址 -unzip ./"$3.zip" #解压 $3例如 chrome-linux64.zip -rm -rf ./"$3.zip" -# 安装依赖 -if [[ -f /etc/debian_version ]]; then - apt install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils + if [[ -f /etc/debian_version ]]; then + apt-get install -y curl elif [[ -f /etc/centos-release ]]; then - yum install -y alsa-lib.x86_64 atk.x86_64 cups-libs.x86_64 gtk3.x86_64 ipa-gothic-fonts libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXrandr.x86_64 libXScrnSaver.x86_64 libXtst.x86_64 pango.x86_64 xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-fonts-cyrillic xorg-x11-fonts-misc xorg-x11-fonts-Type1 xorg-x11-utils + yum -y install curl else echo "Unknown Linux distribution." fi -./"$3"/"$4" --version +fi + +# 4. download chrome beta from dl.google.com and install it. +cd "$1" #$1是安装目录 +curl -O -L "$2" # $2是一个url,代表浏览器的下载地址 +if [[ "$4" == "firefox" ]];then #是火狐浏览器 + if ! command -v tar >/dev/null; then + if [[ -f /etc/debian_version ]]; then + apt-get install -y tar + elif [[ -f /etc/centos-release ]]; then + yum -y install tar + else + echo "Unknown Linux distribution." + fi + fi + tar -jxvf ./"$3.tar.bz2" + rm -rf ./"$3.tar.bz2" + ./"$4"/"$4" --version +else #是谷歌浏览器 + unzip ./"$3.zip" #解压 $3例如 chrome-linux64 + rm -rf ./"$3.zip" + # 安装依赖 + if [[ -f /etc/debian_version ]]; then + apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils + elif [[ -f /etc/centos-release ]]; then + yum install -y alsa-lib.x86_64 atk.x86_64 cups-libs.x86_64 gtk3.x86_64 ipa-gothic-fonts libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXrandr.x86_64 libXScrnSaver.x86_64 libXtst.x86_64 pango.x86_64 xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-fonts-cyrillic xorg-x11-fonts-misc xorg-x11-fonts-Type1 xorg-x11-utils + else + echo "Unknown Linux distribution." + fi + ./"$3"/"$4" --version +fi + diff --git a/src/main/resources/scripts/install-chrome-for-testing-win.ps1 b/src/main/resources/scripts/install-chrome-for-testing-win.ps1 index ea7cab17..84f72214 100644 --- a/src/main/resources/scripts/install-chrome-for-testing-win.ps1 +++ b/src/main/resources/scripts/install-chrome-for-testing-win.ps1 @@ -1,28 +1,55 @@ param( - #下载链接 + #�������� [string]$url, - #下载的压缩包的存放路径,不包括压缩包名称,例如C:\Users\fanyong\Desktop + + #���ص�ѹ�����Ĵ��·����������ѹ�������ƣ�����C:\Users\fanyong\Desktop [string]$savePath, - #压缩包名称,例如\chrome-win64 + + #ѹ�������ƣ�����chrome-win64 [string]$archive, - #执行名称,例如chrome.exe chrome chromium + + #ִ�����ƣ�����chrome.exe chrome chromium [string]$executableName ) - $ErrorActionPreference = 'Stop' -Write-Host "Downloading Chrome Browser" -$wc = New-Object net.webclient -#下载 -$wc.Downloadfile($url, "$savePath\$archive.zip") -Write-Host "Unzipping Chrome Browser" +$ErrorActionPreference = 'Stop' +$array = @("firefox.exe") +if ($array -contains $executableName) { + #�ǻ������� + Write-Host "Downloading firefox browser" + $wc = New-Object net.webclient + #���� + $wc.Downloadfile($url, "$savePath\$archive.exe") + Write-Host "install firefox browser" + #���û������� + $env:__compat_layer = 'RunAsInvoker' + #��װexe�ļ� + $process = Start-Process -FilePath "$savePath\$archive.exe" -ArgumentList "/ExtractDir=$savePath" -NoNewWindow -Wait -PassThru + #ɾ��ѹ���� + Remove-Item "$savePath\$archive.exe" + if (Test-Path "$savePath\core\$executableName") { + (Get-Item "$savePath\core\$executableName").VersionInfo + } else { + Write-Host "ERROR: failed to install firefox"br + exit 1 + } +}else{ + Write-Host "Downloading chrome browser" + $wc = New-Object net.webclient + #���� + $wc.Downloadfile($url, "$savePath\$archive.zip") + Write-Host "Unzipping Chrome Browser" + + #��ѹ�ļ� + Expand-Archive -LiteralPath "$savePath\$archive.zip" -DestinationPath "$savePath" + #ɾ��ѹ���� + Remove-Item "$savePath\$archive.zip" + #���� + if (Test-Path "$savePath\$archive\$executableName") { + (Get-Item "$savePath\$archive\$executableName").VersionInfo + } else { + Write-Host "ERROR: failed to install Google Chrome Beta" + exit 1 + } +} -#解压文件 -Expand-Archive -LiteralPath "$savePath\$archive.zip" -DestinationPath "$savePath" -#删除压缩包 -Remove-Item "$savePath\$archive.zip" -if (Test-Path "$savePath\$archive\$executableName") { - (Get-Item "$savePath\$archive\$executableName").VersionInfo -} else { - Write-Host "ERROR: failed to install Google Chrome Beta" - exit 1 -} \ No newline at end of file